Skip to content
Merged
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
168 changes: 168 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,16 @@ jobs:
node-version: '24'
cache: npm
cache-dependency-path: apps/web/package-lock.json
- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: '3.12'
enable-cache: true
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc
- run: npm ci
working-directory: apps/web
- run: uv sync --project apps/sidecar --extra dev --locked
- name: Parse PowerShell build scripts
shell: pwsh
run: |
Expand Down Expand Up @@ -86,5 +91,168 @@ jobs:
The Windows Release workflow replaces this file with evidence generated
from the exact PyAV/FFmpeg wheel bundled into the installer.
'@ | Set-Content -Encoding utf8 tooling\packaging\dist\FFMPEG_BUILD_INFO.txt
- name: Build NSIS packaging test fixture
shell: pwsh
run: |
$Tauri = (Resolve-Path 'apps\web\node_modules\.bin\tauri.cmd').Path
$Arguments = @(
'build',
'--config', 'apps\desktop\tauri.conf.json',
'--target', 'x86_64-pc-windows-msvc',
'--bundles', 'nsis',
'--ci',
'--no-sign'
)
& $Tauri @Arguments
if ($LASTEXITCODE -ne 0) { throw 'Unable to build the NSIS packaging test fixture.' }
- name: Test Windows NSIS packaging policies
shell: pwsh
env:
CAPTIONNEST_REQUIRE_NSIS_TESTS: '1'
run: uv run --project apps/sidecar --extra dev pytest tooling/tests/test_desktop_packaging.py
- run: cargo fmt --manifest-path apps/desktop/Cargo.toml --check
- run: cargo check --manifest-path apps/desktop/Cargo.toml --target x86_64-pc-windows-msvc --locked

installer-lifecycle:
runs-on: windows-latest
timeout-minutes: 180
env:
RELEASE_PYTHON_VERSION: '3.12'
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}\tooling\packaging\build\vcpkg-binary-cache
steps:
- uses: actions/checkout@v7

- name: Restore LGPL media wheel cache
id: restore_media_wheel_cache
uses: actions/cache/restore@v6
with:
path: tooling/packaging/dist/media-wheel
key: >-
media-wheel-${{ runner.os }}-${{ runner.arch }}-python-${{ env.RELEASE_PYTHON_VERSION }}-${{ hashFiles('scripts/build-media-wheel.ps1', 'tooling/packaging/media-runtime/vcpkg.json') }}

- name: Check out pinned vcpkg baseline
if: steps.restore_media_wheel_cache.outputs.cache-hit != 'true'
uses: actions/checkout@v7
with:
repository: microsoft/vcpkg
ref: db4723bd0a99eab031f1a3dee4336dca43049c87
path: tooling/packaging/build/vcpkg

- uses: actions/setup-node@v7
with:
node-version: '24'
cache: npm
cache-dependency-path: apps/web/package-lock.json

- uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
python-version: ${{ env.RELEASE_PYTHON_VERSION }}
enable-cache: true

- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc

- name: Restore vcpkg binary cache
if: steps.restore_media_wheel_cache.outputs.cache-hit != 'true'
id: restore_vcpkg_cache
uses: actions/cache/restore@v6
with:
path: tooling/packaging/build/vcpkg-binary-cache
key: vcpkg-windows-x64-${{ hashFiles('tooling/packaging/media-runtime/vcpkg.json') }}

- name: Bootstrap pinned vcpkg
if: steps.restore_media_wheel_cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
New-Item -ItemType Directory -Force $env:VCPKG_DEFAULT_BINARY_CACHE | Out-Null
& .\tooling\packaging\build\vcpkg\bootstrap-vcpkg.bat '-disableMetrics'
if ($LASTEXITCODE -ne 0) { throw 'Unable to bootstrap pinned vcpkg.' }

- run: npm ci
working-directory: apps/web

- run: uv sync --project apps/sidecar --extra asr --extra desktop --extra dev --locked

- name: Select lifecycle Python runtime
shell: pwsh
run: |
$Python = (Resolve-Path 'apps\sidecar\.venv\Scripts\python.exe').Path
"CAPTIONNEST_BUILD_PYTHON=$Python" | Add-Content -LiteralPath $env:GITHUB_ENV

- name: Build LGPL media wheel
if: steps.restore_media_wheel_cache.outputs.cache-hit != 'true'
shell: pwsh
run: |
.\scripts\build-media-wheel.ps1 `
-VcpkgRoot 'tooling\packaging\build\vcpkg' `
-PythonExecutable $env:CAPTIONNEST_BUILD_PYTHON

- name: Install and verify LGPL media wheel
shell: pwsh
run: |
.\scripts\install-media-wheel.ps1 `
-PythonExecutable $env:CAPTIONNEST_BUILD_PYTHON

- name: Build exact-HEAD release-chain NSIS installer
shell: pwsh
run: |
.\scripts\build-desktop.ps1 -PythonExecutable $env:CAPTIONNEST_BUILD_PYTHON
$Exact = @(
Get-ChildItem `
-LiteralPath 'apps\desktop\target\x86_64-pc-windows-msvc\release\bundle\nsis' `
-Filter 'CaptionNest_0.2.8_x64-setup.exe' `
-File
)
if ($Exact.Count -ne 1) { throw "Expected one exact-HEAD installer; found $($Exact.Count)." }
$ExactCopy = Join-Path $env:RUNNER_TEMP 'CaptionNest_0.2.8_exact_HEAD_x64-setup.exe'
Copy-Item -LiteralPath $Exact[0].FullName -Destination $ExactCopy
"CAPTIONNEST_CURRENT_INSTALLER=$ExactCopy" | Add-Content -LiteralPath $env:GITHUB_ENV

- name: Build higher-version upgrade fixture from exact HEAD
shell: pwsh
run: |
$Tauri = (Resolve-Path 'apps\web\node_modules\.bin\tauri.cmd').Path
$UpgradeConfig = Join-Path $env:RUNNER_TEMP 'captionnest-upgrade-config.json'
@{ version = '0.2.9' } |
ConvertTo-Json |
Set-Content -LiteralPath $UpgradeConfig -Encoding utf8NoBOM
$Arguments = @(
'build',
'--config', 'apps\desktop\tauri.conf.json',
'--config', $UpgradeConfig,
'--target', 'x86_64-pc-windows-msvc',
'--bundles', 'nsis',
'--ci',
'--no-sign',
'--ignore-version-mismatches'
)
& $Tauri @Arguments
if ($LASTEXITCODE -ne 0) { throw 'Unable to build the upgrade fixture.' }
$Upgrade = @(
Get-ChildItem `
-LiteralPath 'apps\desktop\target\x86_64-pc-windows-msvc\release\bundle\nsis' `
-Filter 'CaptionNest_0.2.9_x64-setup.exe' `
-File
)
if ($Upgrade.Count -ne 1) { throw "Expected one upgrade installer; found $($Upgrade.Count)." }
"CAPTIONNEST_UPGRADE_INSTALLER=$($Upgrade[0].FullName)" | Add-Content -LiteralPath $env:GITHUB_ENV

- name: Download and verify affected installer
shell: pwsh
run: |
$OldInstaller = Join-Path $env:RUNNER_TEMP 'CaptionNest_0.2.8_affected_x64-setup.exe'
Invoke-WebRequest `
-Uri 'https://github.com/coconilu/captionnest/releases/download/v0.2.8/CaptionNest_0.2.8_x64-setup.exe' `
-OutFile $OldInstaller
"CAPTIONNEST_OLD_INSTALLER=$OldInstaller" | Add-Content -LiteralPath $env:GITHUB_ENV

- name: Exercise isolated installer and model lifecycle
shell: pwsh
run: |
.\scripts\test-installer-model-retention.ps1 `
-OldInstallerPath $env:CAPTIONNEST_OLD_INSTALLER `
-CurrentInstallerPath $env:CAPTIONNEST_CURRENT_INSTALLER `
-UpgradeInstallerPath $env:CAPTIONNEST_UPGRADE_INSTALLER `
-ExpectedVersion '0.2.8' `
-UpgradeExpectedVersion '0.2.9'
1 change: 1 addition & 0 deletions apps/desktop/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"installMode": "currentUser",
"installerIcon": "icons/icon.ico",
"installerHooks": "windows/installer-hooks.nsh",
"template": "windows/installer.nsi",
"languages": ["SimpChinese", "English"]
}
}
Expand Down
19 changes: 13 additions & 6 deletions apps/desktop/windows/installer-hooks.nsh
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
!macro NSIS_HOOK_PREUNINSTALL
MessageBox MB_ICONEXCLAMATION|MB_OKCANCEL|MB_DEFBUTTON2 "卸载 CaptionNest 将同时删除本机下载的语音识别模型。模型文件可能占用数 GB 空间,删除后无法恢复。$\r$\n$\r$\n选择“确定”继续卸载,选择“取消”保留应用和模型。" /SD IDOK IDOK continue_uninstall
Abort
continue_uninstall:
!macroend
!include "${__FILEDIR__}\model-retention-policy.nsh"

!ifndef CAPTIONNEST_MODELS_DIR
!define CAPTIONNEST_MODELS_DIR "$LOCALAPPDATA\io.github.coconilu.captionnest\models"
!endif

!macro NSIS_HOOK_POSTUNINSTALL
RMDir /r "$LOCALAPPDATA\io.github.coconilu.captionnest\models"
; Tauri's uninstall confirmation page owns the user decision. Its built-in
; "delete app data" checkbox is unchecked by default and is skipped for
; passive, silent, and /UPDATE runs. Mirror the same guard here so models are
; never removed merely because an older program version is being replaced.
${If} $DeleteAppDataCheckboxState = 1
${AndIf} $UpdateMode <> 1
RMDir /r "${CAPTIONNEST_MODELS_DIR}"
${EndIf}
!macroend
Loading