mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-17 20:34:19 +08:00
✨ feat(cli): 新增独立命令行与发布链
- 新增无头运行时及连接、查询、导出、批处理、审计和 MCP 命令 - 复用活动数据根、密文存储与跨进程锁,落实写入安全和取消语义 - 增加六平台 CLI 归档、独立校验和、Docker、npm 与 WinGet 分发 - 隔离 GUI/CLI 更新资产并强化 macOS 签名与公证门禁 - 补充并发、审计、事务及发布契约回归测试 Refs #902
This commit is contained in:
275
.github/workflows/dev-build.yml
vendored
275
.github/workflows/dev-build.yml
vendored
@@ -27,13 +27,22 @@ jobs:
|
||||
|
||||
- name: Test release asset contracts
|
||||
run: |
|
||||
go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 -shellcheck= -pyflakes= \
|
||||
.github/workflows/release.yml \
|
||||
.github/workflows/dev-build.yml \
|
||||
.github/workflows/publish-release.yml
|
||||
bash tools/detect-changed-driver-agents.test.sh
|
||||
bash tools/generate-driver-agent-revisions.test.sh
|
||||
python3 tools/generate-driver-release-manifest.test.py
|
||||
python3 tools/generate-update-latest-manifest.test.py
|
||||
python3 tools/validate-gui-update-manifest.test.py
|
||||
python3 tools/prepare-vps-release-payload.test.py
|
||||
bash tools/vps-release-commit.test.sh
|
||||
python3 tools/package-driver-release-assets.test.py
|
||||
python3 tools/cli-release-assets.test.py
|
||||
python3 tools/npm-cli-wrapper.test.py
|
||||
python3 tools/validate-npm-cli-package-version.test.py
|
||||
python3 tools/generate-winget-cli-manifest.test.py
|
||||
python3 tools/legal-release-assets.test.py
|
||||
python3 tools/windows-release-artifacts.test.py
|
||||
python3 tools/generate-release-notes.test.py
|
||||
@@ -275,6 +284,85 @@ jobs:
|
||||
fi
|
||||
echo "release_source=dev-latest" >> "$GITHUB_OUTPUT"
|
||||
|
||||
cli:
|
||||
name: Build dev CLI ${{ matrix.goos }}/${{ matrix.goarch }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- goos: darwin
|
||||
goarch: amd64
|
||||
extension: tar.gz
|
||||
binary: gonavi
|
||||
- goos: darwin
|
||||
goarch: arm64
|
||||
extension: tar.gz
|
||||
binary: gonavi
|
||||
- goos: linux
|
||||
goarch: amd64
|
||||
extension: tar.gz
|
||||
binary: gonavi
|
||||
- goos: linux
|
||||
goarch: arm64
|
||||
extension: tar.gz
|
||||
binary: gonavi
|
||||
- goos: windows
|
||||
goarch: amd64
|
||||
extension: zip
|
||||
binary: gonavi.exe
|
||||
- goos: windows
|
||||
goarch: arm64
|
||||
extension: zip
|
||||
binary: gonavi.exe
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
- name: Build and package dev CLI
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="dev-${GITHUB_SHA:0:7}"
|
||||
asset="gonavi-cli_${version}_${{ matrix.goos }}_${{ matrix.goarch }}.${{ matrix.extension }}"
|
||||
stage="${RUNNER_TEMP}/gonavi-cli-${{ matrix.goos }}-${{ matrix.goarch }}"
|
||||
mkdir -p "$stage"
|
||||
./tools/generate-driver-agent-revisions.sh --platform "${{ matrix.goos }}/${{ matrix.goarch }}"
|
||||
CGO_ENABLED=0 GOOS="${{ matrix.goos }}" GOARCH="${{ matrix.goarch }}" \
|
||||
go build -trimpath -ldflags="-s -w -X GoNavi-Wails/internal/cli.Version=${version}" \
|
||||
-o "$stage/${{ matrix.binary }}" ./cmd/gonavi
|
||||
install -m 0644 LICENSE NOTICE "$stage/"
|
||||
if [[ "${{ matrix.extension }}" == "zip" ]]; then
|
||||
(cd "$stage" && zip -q -X "${GITHUB_WORKSPACE}/${asset}" "${{ matrix.binary }}" LICENSE NOTICE)
|
||||
else
|
||||
tar -C "$stage" -czf "$asset" "${{ matrix.binary }}" LICENSE NOTICE
|
||||
fi
|
||||
test -s "$asset"
|
||||
expected_entries="$(printf '%s\n' "${{ matrix.binary }}" LICENSE NOTICE | sort)"
|
||||
if [[ "${{ matrix.extension }}" == "zip" ]]; then
|
||||
actual_entries="$(unzip -Z1 "$asset" | sort)"
|
||||
else
|
||||
actual_entries="$(tar -tzf "$asset" | sed 's#^\./##' | sort)"
|
||||
fi
|
||||
if [[ "$actual_entries" != "$expected_entries" ]]; then
|
||||
echo "CLI archive contents are invalid for $asset" >&2
|
||||
printf 'expected:\n%s\nactual:\n%s\n' "$expected_entries" "$actual_entries" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Upload dev CLI artifact
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: dev-cli-artifact-${{ matrix.goos }}-${{ matrix.goarch }}
|
||||
path: gonavi-cli_*
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
build:
|
||||
name: Build ${{ matrix.platform }}
|
||||
needs:
|
||||
@@ -540,7 +628,7 @@ jobs:
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
SHORT_SHA=$(git rev-parse --short HEAD)
|
||||
SHORT_SHA="${GITHUB_SHA:0:7}"
|
||||
DEV_VERSION="dev-${SHORT_SHA}"
|
||||
echo "version=${DEV_VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "📌 Dev 版本号: ${DEV_VERSION}"
|
||||
@@ -831,12 +919,12 @@ jobs:
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
cd build/bin
|
||||
|
||||
APP_PATH=$(find . -maxdepth 1 -name "*.app" | head -n 1)
|
||||
if [ -z "$APP_PATH" ]; then
|
||||
echo "❌ 未找到 .app 应用包!"
|
||||
APP_PATH="./GoNavi.app"
|
||||
if [ ! -d "$APP_PATH" ]; then
|
||||
echo "❌ 未找到固定名称 GoNavi.app 应用包!"
|
||||
exit 1
|
||||
fi
|
||||
APP_NAME=$(basename "$APP_PATH")
|
||||
APP_NAME="GoNavi.app"
|
||||
mkdir -p "$APP_NAME/Contents/Resources"
|
||||
cp ../../LICENSE "$APP_NAME/Contents/Resources/LICENSE"
|
||||
cp ../../NOTICE "$APP_NAME/Contents/Resources/NOTICE"
|
||||
@@ -848,10 +936,7 @@ jobs:
|
||||
fi
|
||||
echo "ℹ️ macOS 产物不执行 UPX 压缩,保留原始主程序。"
|
||||
|
||||
echo "🔏 正在进行 Ad-hoc 签名..."
|
||||
if command -v xattr >/dev/null 2>&1; then
|
||||
xattr -cr "$APP_NAME" || true
|
||||
fi
|
||||
echo "🔏 Signing dev DMG with an ad-hoc identity (not for production distribution)..."
|
||||
codesign --force --deep --sign - "$APP_NAME"
|
||||
|
||||
DMG_NAME="${{ matrix.build_name }}.dmg"
|
||||
@@ -871,9 +956,9 @@ jobs:
|
||||
|
||||
VERIFY_MOUNT_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gonavi-dev-verify.XXXXXX")
|
||||
hdiutil attach -nobrowse -readonly -mountpoint "$VERIFY_MOUNT_DIR" "$DMG_NAME" >/dev/null
|
||||
PACKAGED_APP=$(find "$VERIFY_MOUNT_DIR" -maxdepth 1 -name "*.app" | head -n 1)
|
||||
if [ -z "$PACKAGED_APP" ]; then
|
||||
echo "❌ DMG 内未找到 .app 应用包!"
|
||||
PACKAGED_APP="$VERIFY_MOUNT_DIR/GoNavi.app"
|
||||
if [ ! -d "$PACKAGED_APP" ]; then
|
||||
echo "❌ DMG 内未找到固定名称 GoNavi.app 应用包!"
|
||||
hdiutil detach "$VERIFY_MOUNT_DIR" -quiet >/dev/null 2>&1 || true
|
||||
exit 1
|
||||
fi
|
||||
@@ -1093,6 +1178,7 @@ jobs:
|
||||
name: Publish Dev Pre-release
|
||||
needs:
|
||||
- build
|
||||
- cli
|
||||
- driver_agents
|
||||
# Serialize only the publication stage with stable releases so both
|
||||
# channels cannot publish shared mirror targets concurrently.
|
||||
@@ -1119,12 +1205,44 @@ jobs:
|
||||
pattern: dev-build-artifacts-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Download dev CLI artifacts
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: cli-assets
|
||||
pattern: dev-cli-artifact-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Validate dev CLI artifact staging
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="dev-${GITHUB_SHA:0:7}"
|
||||
expected=(
|
||||
"gonavi-cli_${version}_darwin_amd64.tar.gz"
|
||||
"gonavi-cli_${version}_darwin_arm64.tar.gz"
|
||||
"gonavi-cli_${version}_linux_amd64.tar.gz"
|
||||
"gonavi-cli_${version}_linux_arm64.tar.gz"
|
||||
"gonavi-cli_${version}_windows_amd64.zip"
|
||||
"gonavi-cli_${version}_windows_arm64.zip"
|
||||
)
|
||||
mapfile -t actual < <(find cli-assets -type f -printf '%P\n' | sort)
|
||||
expected_list="$(printf '%s\n' "${expected[@]}" | sort)"
|
||||
actual_list="$(printf '%s\n' "${actual[@]}")"
|
||||
if [[ "$actual_list" != "$expected_list" ]]; then
|
||||
echo "CLI artifact set is invalid" >&2
|
||||
printf 'expected:\n%s\nactual:\n%s\n' "$expected_list" "$actual_list" >&2
|
||||
exit 1
|
||||
fi
|
||||
for asset in "${expected[@]}"; do
|
||||
test -s "cli-assets/${asset}"
|
||||
done
|
||||
|
||||
- name: Add legal documents
|
||||
shell: bash
|
||||
run: install -m 0644 LICENSE NOTICE release-assets/
|
||||
|
||||
- name: List Assets
|
||||
run: ls -R release-assets
|
||||
run: ls -R release-assets cli-assets
|
||||
|
||||
- name: Download Previous Driver Manifest
|
||||
if: needs.driver_agents.outputs.has_changes == 'true' && needs.driver_agents.outputs.release_source != 'all'
|
||||
@@ -1190,26 +1308,107 @@ jobs:
|
||||
rm -rf drivers driver-provenance
|
||||
echo "has_driver_assets=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate dev CLI checksums
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="dev-${GITHUB_SHA:0:7}"
|
||||
expected=(
|
||||
"gonavi-cli_${version}_darwin_amd64.tar.gz"
|
||||
"gonavi-cli_${version}_darwin_arm64.tar.gz"
|
||||
"gonavi-cli_${version}_linux_amd64.tar.gz"
|
||||
"gonavi-cli_${version}_linux_arm64.tar.gz"
|
||||
"gonavi-cli_${version}_windows_amd64.zip"
|
||||
"gonavi-cli_${version}_windows_arm64.zip"
|
||||
)
|
||||
(cd cli-assets && sha256sum "${expected[@]}" > "gonavi-cli_${version}_checksums.txt")
|
||||
|
||||
- name: Generate SHA256SUMS
|
||||
shell: bash
|
||||
run: |
|
||||
cd release-assets
|
||||
FILES=()
|
||||
set -euo pipefail
|
||||
output="release-assets/SHA256SUMS"
|
||||
count=0
|
||||
declare -A seen=()
|
||||
: > "$output"
|
||||
while IFS= read -r file; do
|
||||
if [ -n "$file" ]; then
|
||||
case "$file" in
|
||||
SHA256SUMS|latest.json|latest-dev.json) continue ;;
|
||||
esac
|
||||
FILES+=("$file")
|
||||
name="$(basename "$file")"
|
||||
if [[ -n "${seen[$name]:-}" ]]; then
|
||||
echo "Duplicate release asset name across staging directories: ${name}" >&2
|
||||
exit 1
|
||||
fi
|
||||
done < <(find . -maxdepth 1 -type f ! -name SHA256SUMS -exec basename {} \; | sort)
|
||||
if [ ${#FILES[@]} -eq 0 ]; then
|
||||
seen["$name"]=1
|
||||
digest="$(sha256sum "$file" | awk '{ print $1 }')"
|
||||
printf '%s %s\n' "$digest" "$name" >> "$output"
|
||||
count=$((count + 1))
|
||||
done < <(find release-assets cli-assets -maxdepth 1 -type f \
|
||||
! -name SHA256SUMS ! -name latest.json ! -name latest-dev.json -print | sort)
|
||||
if [ "$count" -eq 0 ]; then
|
||||
echo "⚠️ 未找到可签名资产,生成空 SHA256SUMS"
|
||||
: > SHA256SUMS
|
||||
else
|
||||
sha256sum "${FILES[@]}" > SHA256SUMS
|
||||
fi
|
||||
|
||||
- name: Verify dev CLI release assets
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="dev-${GITHUB_SHA:0:7}"
|
||||
cli_checksum_name="gonavi-cli_${version}_checksums.txt"
|
||||
expected=(
|
||||
"gonavi-cli_${version}_darwin_amd64.tar.gz"
|
||||
"gonavi-cli_${version}_darwin_arm64.tar.gz"
|
||||
"gonavi-cli_${version}_linux_amd64.tar.gz"
|
||||
"gonavi-cli_${version}_linux_arm64.tar.gz"
|
||||
"gonavi-cli_${version}_windows_amd64.zip"
|
||||
"gonavi-cli_${version}_windows_arm64.zip"
|
||||
)
|
||||
expected_cli_files=("${expected[@]}" "$cli_checksum_name")
|
||||
mapfile -t actual_cli_files < <(find cli-assets -maxdepth 1 -type f -name 'gonavi-cli_*' -printf '%f\n' | sort)
|
||||
expected_cli_list="$(printf '%s\n' "${expected_cli_files[@]}" | sort)"
|
||||
actual_cli_list="$(printf '%s\n' "${actual_cli_files[@]}")"
|
||||
if [[ "$actual_cli_list" != "$expected_cli_list" ]]; then
|
||||
echo "CLI release asset set is invalid" >&2
|
||||
printf 'expected:\n%s\nactual:\n%s\n' "$expected_cli_list" "$actual_cli_list" >&2
|
||||
exit 1
|
||||
fi
|
||||
test -s "cli-assets/${cli_checksum_name}"
|
||||
if ! awk 'NF != 2 || length($1) != 64 || $1 !~ /^[0-9a-fA-F]+$/ { exit 1 }' "cli-assets/${cli_checksum_name}"; then
|
||||
echo "CLI checksum file contents are invalid" >&2
|
||||
exit 1
|
||||
fi
|
||||
mapfile -t checksum_assets < <(awk '{ print $2 }' "cli-assets/${cli_checksum_name}" | sort)
|
||||
checksum_asset_list="$(printf '%s\n' "${checksum_assets[@]}")"
|
||||
expected_checksum_list="$(printf '%s\n' "${expected[@]}" | sort)"
|
||||
if [[ "$checksum_asset_list" != "$expected_checksum_list" ]]; then
|
||||
echo "CLI checksum file contents are invalid" >&2
|
||||
printf 'expected:\n%s\nactual:\n%s\n' "$expected_checksum_list" "$checksum_asset_list" >&2
|
||||
exit 1
|
||||
fi
|
||||
for asset in "${expected[@]}"; do
|
||||
test -s "cli-assets/${asset}"
|
||||
grep -Fq " ${asset}" release-assets/SHA256SUMS
|
||||
global_digest="$(awk -v name="$asset" '$2 == name { print $1 }' release-assets/SHA256SUMS)"
|
||||
cli_digest="$(awk -v name="$asset" '$2 == name { print $1 }' "cli-assets/${cli_checksum_name}")"
|
||||
actual_digest="$(sha256sum "cli-assets/${asset}" | awk '{ print $1 }')"
|
||||
[[ -n "$global_digest" && "$global_digest" == "$cli_digest" && "$cli_digest" == "$actual_digest" ]]
|
||||
done
|
||||
grep -Fq " ${cli_checksum_name}" release-assets/SHA256SUMS
|
||||
(cd cli-assets && sha256sum --check "$cli_checksum_name")
|
||||
while read -r expected_digest asset; do
|
||||
if [[ -f "release-assets/${asset}" && -f "cli-assets/${asset}" ]]; then
|
||||
echo "Duplicate release asset name across staging directories: ${asset}" >&2
|
||||
exit 1
|
||||
elif [[ -f "release-assets/${asset}" ]]; then
|
||||
file="release-assets/${asset}"
|
||||
elif [[ -f "cli-assets/${asset}" ]]; then
|
||||
file="cli-assets/${asset}"
|
||||
else
|
||||
echo "Release asset listed in SHA256SUMS is missing: ${asset}" >&2
|
||||
exit 1
|
||||
fi
|
||||
actual_digest="$(sha256sum "$file" | awk '{ print $1 }')"
|
||||
[[ "$actual_digest" == "$expected_digest" ]]
|
||||
done < release-assets/SHA256SUMS
|
||||
|
||||
- name: Generate Driver SHA256SUMS
|
||||
if: steps.driver_assets.outputs.has_driver_assets == 'true'
|
||||
shell: bash
|
||||
@@ -1347,6 +1546,7 @@ jobs:
|
||||
--version "$DEV_VERSION" \
|
||||
--tag dev-latest \
|
||||
--channel dev \
|
||||
--component gui \
|
||||
--name "Dev Build (${DEV_VERSION})" \
|
||||
--download-base-url "https://download.syngnat.top/gonavi/dev/releases/download" \
|
||||
--download-tag "$DEV_VERSION" \
|
||||
@@ -1482,7 +1682,9 @@ jobs:
|
||||
tag_name: dev-latest
|
||||
name: "🧪 Dev Build (${{ steps.version.outputs.version }})"
|
||||
target_commitish: ${{ github.sha }}
|
||||
files: release-assets/*
|
||||
files: |
|
||||
release-assets/*
|
||||
cli-assets/*
|
||||
prerelease: true
|
||||
draft: false
|
||||
body_path: ${{ steps.changelog.outputs.body_file }}
|
||||
@@ -1499,21 +1701,12 @@ jobs:
|
||||
app_dir="release-assets"
|
||||
driver_dir="driver-release-assets"
|
||||
|
||||
jq -e \
|
||||
--arg version "${DEV_VERSION}" \
|
||||
--arg mirror_base "https://download.syngnat.top/gonavi/dev/releases/download" \
|
||||
--arg github_base "https://github.com/${GITHUB_REPOSITORY}/releases/download/dev-latest" \
|
||||
'
|
||||
.channel == "dev"
|
||||
and .tagName == "dev-latest"
|
||||
and .version == $version
|
||||
and (.assets | type == "array" and length > 0)
|
||||
and all(.assets[];
|
||||
(.url | type == "string" and startswith($mirror_base + "/" + $version + "/"))
|
||||
and (.apiUrl | type == "string" and startswith($github_base + "/"))
|
||||
and (.sha256 | type == "string" and test("^[0-9a-fA-F]{64}$"))
|
||||
)
|
||||
' "${app_dir}/latest-dev.json" >/dev/null
|
||||
python3 tools/validate-gui-update-manifest.py \
|
||||
--channel dev \
|
||||
--app-tag "${DEV_VERSION}" \
|
||||
--app-dir "${app_dir}" \
|
||||
--manifest "${app_dir}/latest-dev.json" \
|
||||
--github-repository "${GITHUB_REPOSITORY}"
|
||||
|
||||
driver_latest_file="${RUNNER_TEMP}/driver-dev-latest-index.json"
|
||||
if [[ "${HAS_DRIVER_ASSETS}" == "true" ]]; then
|
||||
|
||||
Reference in New Issue
Block a user