From ece0a35ce4e6d7f917380f89e3f2306654b9de53 Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 17:08:49 +0800 Subject: [PATCH 01/10] feat: package and verify Windows and macOS installers --- .github/workflows/package.yml | 19 ++-- README.md | 15 ++- apps/desktop/src-tauri/tauri.conf.json | 2 +- .../desktop/src-tauri/tauri.windows.conf.json | 11 +- docs/INSTALL.md | 40 +++++++ package.json | 1 + scripts/macos-install.command | 21 ++++ scripts/package-desktop.mjs | 41 +++++++ scripts/verify-macos-package.mjs | 29 +++++ scripts/verify-windows-package.ps1 | 29 +++++ scripts/verify-windows-ui.mjs | 107 ++++++++++++++++++ 11 files changed, 300 insertions(+), 15 deletions(-) create mode 100644 docs/INSTALL.md create mode 100644 scripts/macos-install.command create mode 100644 scripts/package-desktop.mjs create mode 100644 scripts/verify-macos-package.mjs create mode 100644 scripts/verify-windows-package.ps1 create mode 100644 scripts/verify-windows-ui.mjs diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 3c5b73f..c13fdc3 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -10,6 +10,7 @@ jobs: matrix: os: [macos-15, windows-2025] runs-on: ${{ matrix.os }} + timeout-minutes: 45 steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 @@ -20,6 +21,9 @@ jobs: node-version: 24.19.0 cache: pnpm - uses: dtolnay/rust-toolchain@1.99.0 + - uses: Swatinem/rust-cache@v2 + with: + workspaces: apps/desktop/src-tauri - run: pnpm install --frozen-lockfile - run: pnpm typecheck - name: macOS full tests including HTTP transport @@ -35,20 +39,15 @@ jobs: - run: node scripts/build-desktop.mjs --prepare-only - run: cargo test --locked --manifest-path apps/desktop/src-tauri/Cargo.toml - run: node scripts/build-desktop.mjs + - run: pnpm package:desktop - if: runner.os == 'macOS' - run: node scripts/verify-installed.mjs --app "apps/desktop/src-tauri/target/release/bundle/macos/Git View.app" --output installed-verification.json + run: node scripts/verify-macos-package.mjs - if: runner.os == 'Windows' shell: pwsh - run: | - New-Item -ItemType Directory -Force dist/desktop-portable - Copy-Item apps/desktop/src-tauri/target/release/git-view-desktop.exe dist/desktop-portable/ - Copy-Item apps/desktop/src-tauri/resources/* dist/desktop-portable/ -Recurse - node scripts/verify-installed.mjs --app dist/desktop-portable --output installed-verification.json + run: ./scripts/verify-windows-package.ps1 - uses: actions/upload-artifact@v4 + if: always() with: name: unsigned-desktop-${{ matrix.os }} - path: | - apps/desktop/src-tauri/target/release/bundle/ - dist/desktop-portable/ - installed-verification.json + path: dist/installers/ if-no-files-found: error diff --git a/README.md b/README.md index 7bfcec5..2f115ad 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Git View 是一个本地 Git 图形工具,用于查看文件差异、浏览提交历史,以及比较分支和提交。支持整文件暂存、提交和本地分支操作,写入前需要预览确认。 -支持中文和英文界面,通过本机 Git 读取仓库,无需连接远程服务。目前为桌面预览版,主要支持 macOS Apple silicon。 +支持中文和英文界面,通过本机 Git 读取仓库,无需连接远程服务。目前为桌面预览版,提供 macOS Apple silicon 和 Windows x64 安装包;Windows 暂时只开放查看。 ![提交历史与文件差异,浏览器中的演示仓库](docs/verification/screenshots/history-simplification-browser.png) @@ -22,7 +22,14 @@ Git View 是一个本地 Git 图形工具,用于查看文件差异、浏览提 ## 安装 -当前需要从源码构建。以下步骤适用于 macOS Apple silicon。 +从 [Releases](https://github.com/XSY-28/git-view/releases) 下载对应平台的预览安装包。两种包都自带 Node 运行时,使用时只需系统 Git,无需另装 Node、pnpm 或 Rust。 + +- **macOS Apple silicon**:下载 `macos-arm64.dmg`,打开后先退出 Git View,再双击 **Install Git View.command**。安装位置固定为 `~/Applications/Git View.app`,旧版本保留为 ZIP;完成后弹出磁盘映像。不要再复制一份到 `/Applications`。 +- **Windows x64**:下载 `windows-x64-setup.exe`,运行安装向导,再从开始菜单打开。需要 Git for Windows 与 WebView2;缺少 WebView2 时安装程序会下载运行时。Windows 仅支持查看,不开放暂存、提交和分支写入。 + +当前包尚未代码签名或 Apple 公证,系统可能提示无法验证发布者。确认下载来源后,使用系统提供的“仍要运行”或“隐私与安全性 → 仍要打开”入口,不要关闭系统安全机制。完整步骤与校验、卸载说明见[安装包说明](docs/INSTALL.md)。 + +### 从源码构建(macOS) 构建需要 Git、Node.js **24.19.0**、pnpm **11.7.0**、Rust 和 Xcode Command Line Tools。CI 使用 Rust 1.99.0。 @@ -66,7 +73,9 @@ node scripts/create-demo.mjs ## 更新 -更新本地源码后,先退出 Git View,再在项目根目录运行: +安装包用户退出 Git View 后,下载新版本并重新安装即可。macOS 会将旧版本备份为 ZIP。 + +从源码更新的 macOS 用户退出 Git View 后,在项目根目录运行: ```sh pnpm update:desktop diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 17cf17c..3e3fbac 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -15,6 +15,6 @@ "icon": ["icons/icon.png", "icons/icon.icns", "icons/icon.ico"], "targets": ["app"], "resources": { "resources/runtime/": "runtime/", "resources/app/": "app/" }, - "macOS": { "minimumSystemVersion": "12.0" } + "macOS": { "minimumSystemVersion": "13.5" } } } diff --git a/apps/desktop/src-tauri/tauri.windows.conf.json b/apps/desktop/src-tauri/tauri.windows.conf.json index 6e63c8a..ac2af6d 100644 --- a/apps/desktop/src-tauri/tauri.windows.conf.json +++ b/apps/desktop/src-tauri/tauri.windows.conf.json @@ -1,3 +1,12 @@ { - "bundle": { "targets": ["nsis"] } + "bundle": { + "targets": ["nsis"], + "windows": { + "nsis": { + "installMode": "currentUser", + "languages": ["English", "SimpChinese"], + "displayLanguageSelector": true + } + } + } } diff --git a/docs/INSTALL.md b/docs/INSTALL.md new file mode 100644 index 0000000..e29690a --- /dev/null +++ b/docs/INSTALL.md @@ -0,0 +1,40 @@ +# 安装 Git View 预览包 + +在 [GitHub Releases](https://github.com/XSY-28/git-view/releases) 选择同一版本的安装包与 `.sha256` 文件。当前目标平台为 macOS 13.5 及以上的 Apple silicon(arm64)和 Windows x64,尚不提供 Intel Mac、Windows ARM 或 Linux 安装包。macOS 下限由包内 [Node 24 的平台要求](https://github.com/nodejs/node/blob/v24.19.0/BUILDING.md#platform-list)决定。 + +## macOS Apple silicon + +1. 确认系统 Git 可用,在终端执行 `git --version`。如果系统提示安装 Command Line Tools,按提示完成;使用安装包不需要 Node、pnpm、Rust 或完整 Xcode。 +2. 下载 `Git-View_0.2.0_macos-arm64.dmg`。可在下载目录执行 `shasum -a 256 -c Git-View_0.2.0_macos-arm64.dmg.sha256` 核对文件。 +3. 退出正在运行的 Git View,打开 DMG,双击 `Install Git View.command`。安装无需管理员权限,固定安装到 `~/Applications/Git View.app`,完成后自动打开应用。 +4. 安装完成后弹出 DMG。不要把另一份 Git View.app 放在 `/Applications`,也不要保留解压的备份应用;这样能避免多个同名搜索入口。 + +安装程序从磁盘映像中解压到临时目录,用包内 Node 启动项目现有安装脚本。它校验应用身份和资源哈希、备份旧安装、检查运行状态、安装到固定路径、回收临时应用、清理注册并刷新搜索服务。磁盘映像中只保留压缩的应用,不会出现可被 Spotlight 注册的第二份 `.app`。 + +已有版本备份到 `~/Library/Application Support/Git View/backups`,格式为 ZIP。安装中发生错误时保留现有恢复机制;错误会显示在终端中。注册与搜索进程检查不能证明 Spotlight 面板结果正确,需要重新搜索确认。安装程序不会自动停止你正在使用的应用。 + +当前包未做 Apple Developer ID 签名或公证。系统拦截时,先确认下载来源,再用“系统设置 → 隐私与安全性 → 仍要打开”。安装命令和应用可能分别触发检查;不要通过关闭 Gatekeeper 绕过。 + +卸载时先退出应用,再删除 `~/Applications/Git View.app`。若也想删除设置和备份,另行删除 `~/.local/share/git-view` 和上述备份目录;不要删除你自己的 Git 仓库。 + +## Windows x64 + +1. 安装 [Git for Windows](https://gitforwindows.org/),确保新开的终端中 `git --version` 能成功。 +2. 下载 `Git-View_0.2.0_windows-x64-setup.exe`。用 PowerShell 执行 `Get-FileHash .\Git-View_0.2.0_windows-x64-setup.exe -Algorithm SHA256`,与同版本 `.sha256` 文件中的哈希比较。 +3. 运行安装向导。应用安装到当前用户目录,不要求管理员权限;缺少 Microsoft WebView2 时安装程序会下载运行时,因此首次安装可能需要联网。 +4. 从开始菜单打开 Git View,点击顶部打开仓库按钮选择本地仓库,或展开“手动输入路径”填入仓库的完整路径。 + +Windows 当前只支持查看,包括当前改动、历史、版本比较和历史调查。暂存、取消暂存、提交、创建和切换分支均由后台明确拒绝。浏览器 HTTP 备用入口暂不支持 Windows,请使用桌面应用。 + +当前 EXE 未做代码签名,Windows 可能显示未知发布者或 SmartScreen 提示。仅在核对 GitHub 下载来源和哈希后使用系统提供的“更多信息 → 仍要运行”入口,不要关闭 SmartScreen。若设备的组织策略禁止未知发布者,需要遵循该设备的策略。 + +卸载使用“设置 → 应用 → 已安装的应用 → Git View → 卸载”。应用设置默认在用户主目录的 `.local\share\git-view`,卸载不会自动删除你的仓库。 + +## 构建与验收边界 + +包构建工作流分别在 macOS ARM 与 Windows x64 runner 上执行类型检查、适用的真实 Git 测试和 Rust 宿主测试,再构建安装包。 + +- macOS:验证 DMG 完整性和挂载、执行包内安装程序 dry-run,并对从 DMG 解压出的内容验证内置 Node、中文/空格路径、仓库只读指纹和 stdio 退出。原生 GUI 在本机固定安装路径验收。 +- Windows:运行真正的 NSIS 静默安装,对安装结果做包内运行检查;启动安装后的 release 应用,用 WebView2 调试连接驱动真实窗口和 Tauri IPC,检查打开中文路径仓库、暂存/未暂存差异、历史差异、正常关闭与 Node 子进程回收,最后卸载。 + +这些检查的通过与失败以该版本对应的 GitHub Actions 和随包验证报告为准。Windows runner 是 CI 系统,不能替代所有 Windows 10/11 实体电脑上的验收;SmartScreen、Gatekeeper、人工安装向导和原生系统目录选择对话框也需要目标设备复测。当前安装包没有自动更新机制。 diff --git a/package.json b/package.json index 3a9621f..4fe1b45 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "inspect": "node dist/cli.mjs inspect --repo \"$PWD\" --json", "stop": "node dist/cli.mjs shutdown --json", "build:desktop": "node scripts/build-desktop.mjs", + "package:desktop": "node scripts/package-desktop.mjs", "update:desktop": "node scripts/install-desktop.mjs --check-ready && pnpm build && pnpm build:desktop && pnpm install:desktop", "install:desktop": "node scripts/install-desktop.mjs", "repair:desktop-search": "node scripts/install-desktop.mjs --repair-search", diff --git a/scripts/macos-install.command b/scripts/macos-install.command new file mode 100644 index 0000000..52684fb --- /dev/null +++ b/scripts/macos-install.command @@ -0,0 +1,21 @@ +#!/bin/bash +set -euo pipefail +package_dir="$(cd "$(dirname "$0")" && pwd)" +work_dir="$(/usr/bin/mktemp -d /tmp/git-view-package.XXXXXX)" +trap '/bin/rm -rf "$work_dir"' EXIT +bundle_dir="$work_dir/apps/desktop/src-tauri/target/release/bundle/macos" +/bin/mkdir -p "$bundle_dir" "$work_dir/scripts" +/usr/bin/ditto -x -k "$package_dir/Git-View.app.zip" "$bundle_dir" +/bin/cp "$package_dir/.installer/install-desktop.mjs" "$work_dir/scripts/" +/bin/cp "$package_dir/.installer/verify-installed.mjs" "$work_dir/scripts/" +/bin/cp "$package_dir/.installer/tauri.conf.json" "$work_dir/apps/desktop/src-tauri/" +runtime="$bundle_dir/Git View.app/Contents/Resources/runtime/node" +if [[ "${1:-}" == "--verify-only" ]]; then + "$runtime" "$work_dir/scripts/verify-installed.mjs" --app "$bundle_dir/Git View.app" +else + "$runtime" "$work_dir/scripts/install-desktop.mjs" "$@" + if [[ "${1:-}" != "--dry-run" && "${1:-}" != "--check-ready" ]]; then + /usr/bin/open "$HOME/Applications/Git View.app" + printf '\nInstalled to ~/Applications/Git View.app. You can close this window and eject the disk image.\n' + fi +fi diff --git a/scripts/package-desktop.mjs b/scripts/package-desktop.mjs new file mode 100644 index 0000000..6df44b5 --- /dev/null +++ b/scripts/package-desktop.mjs @@ -0,0 +1,41 @@ +import { execFile } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { chmod, copyFile, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +const exec = promisify(execFile); +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const host = join(root, 'apps/desktop/src-tauri'); +const config = JSON.parse(await readFile(join(host, 'tauri.conf.json'), 'utf8')); +const output = join(root, 'dist/installers'); +await mkdir(output, { recursive: true }); +let artifact; +if (process.platform === 'darwin') { + const stage = await mkdtemp(join(tmpdir(), 'git-view-dmg-')); + try { + const source = join(host, 'target/release/bundle/macos/Git View.app'); + await exec('/usr/bin/ditto', ['-c', '-k', '--sequesterRsrc', '--keepParent', source, join(stage, 'Git-View.app.zip')]); + await mkdir(join(stage, '.installer')); + for (const name of ['install-desktop.mjs', 'verify-installed.mjs']) await copyFile(join(root, 'scripts', name), join(stage, '.installer', name)); + await copyFile(join(host, 'tauri.conf.json'), join(stage, '.installer/tauri.conf.json')); + await copyFile(join(root, 'LICENSE'), join(stage, 'LICENSE.txt')); + await copyFile(join(root, 'scripts/macos-install.command'), join(stage, 'Install Git View.command')); + await chmod(join(stage, 'Install Git View.command'), 0o755); + await writeFile(join(stage, 'READ ME.txt'), `Git View ${config.version} — macOS ${process.arch}\n\nQuit Git View, then double-click Install Git View.command.\nThe installer uses the bundled Node runtime; no Node or Rust installation is needed.\nIt installs only to ~/Applications/Git View.app and keeps previous versions as ZIP backups.\nSystem Git is required. This preview has no Developer ID signature or Apple notarization.\nIf macOS blocks it, inspect the source and use System Settings > Privacy & Security > Open Anyway.\nDo not disable Gatekeeper. Close the installer disk image after installation.\n\n退出 Git View 后,双击 Install Git View.command。安装位置固定为 ~/Applications/Git View.app。\n旧版本备份为 ZIP。需要系统 Git,无需另装 Node 或 Rust。当前预览包尚未签名或公证。\n`); + artifact = `Git-View_${config.version}_macos-${process.arch}.dmg`; + await exec('/usr/bin/hdiutil', ['create', '-volname', 'Git View Installer', '-srcfolder', stage, '-ov', '-format', 'UDZO', join(output, artifact)], { timeout: 180_000 }); + await exec('/usr/bin/hdiutil', ['verify', join(output, artifact)], { timeout: 120_000 }); + } finally { await rm(stage, { recursive: true, force: true }); } +} else if (process.platform === 'win32') { + const folder = join(host, 'target/release/bundle/nsis'); + const candidates = (await readdir(folder)).filter(name => name.endsWith('-setup.exe')); + if (candidates.length !== 1) throw new Error(`Expected one NSIS installer, found ${candidates.length}.`); + artifact = `Git-View_${config.version}_windows-${process.arch}-setup.exe`; + await copyFile(join(folder, candidates[0]), join(output, artifact)); +} else { throw new Error('Installer packaging supports macOS and Windows only.'); } +const hash = createHash('sha256').update(await readFile(join(output, artifact))).digest('hex'); +await writeFile(join(output, `${artifact}.sha256`), `${hash} ${artifact}\n`); +console.log(JSON.stringify({ artifact: join(output, artifact), sha256: hash })); diff --git a/scripts/verify-macos-package.mjs b/scripts/verify-macos-package.mjs new file mode 100644 index 0000000..64458b2 --- /dev/null +++ b/scripts/verify-macos-package.mjs @@ -0,0 +1,29 @@ +import { execFile } from 'node:child_process'; +import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { promisify } from 'node:util'; + +const exec = promisify(execFile); +if (process.platform !== 'darwin') throw new Error('macOS verification must run on macOS.'); +const folder = resolve('dist/installers'); +const images = (await readdir(folder)).filter(name => name.endsWith('.dmg')); +if (images.length !== 1) throw new Error('Expected exactly one DMG.'); +const temporary = await mkdtemp(join(tmpdir(), 'git-view-mounted-')); +const mount = join(temporary, 'image'); +await mkdir(mount); +let mounted = false; +try { + await exec('/usr/bin/hdiutil', ['attach', '-nobrowse', '-readonly', '-mountpoint', mount, join(folder, images[0])]); + mounted = true; + const command = join(mount, 'Install Git View.command'); + await exec('/bin/bash', [command, '--dry-run'], { timeout: 90_000 }); + const report = JSON.parse((await exec('/bin/bash', [command, '--verify-only'], { timeout: 120_000 })).stdout); + report.checks.dmgMountedAndInstallerDryRun = true; + report.checks.extractedPayloadVerified = true; + await writeFile(join(folder, 'macos-package-verification.json'), `${JSON.stringify(report, null, 2)}\n`); + console.log('DMG mount, installer dry-run and extracted payload checks passed.'); +} finally { + if (mounted) await exec('/usr/bin/hdiutil', ['detach', mount]); + await rm(temporary, { recursive: true, force: true }); +} diff --git a/scripts/verify-windows-package.ps1 b/scripts/verify-windows-package.ps1 new file mode 100644 index 0000000..9ddfdae --- /dev/null +++ b/scripts/verify-windows-package.ps1 @@ -0,0 +1,29 @@ +$ErrorActionPreference = 'Stop' +$install = Join-Path $env:RUNNER_TEMP 'GitViewPackageInstall' +if (Test-Path $install) { throw "Verification directory already exists: $install" } +$packages = @(Get-ChildItem 'dist/installers/*-setup.exe') +if ($packages.Count -ne 1) { throw 'Expected exactly one NSIS installer' } +try { + $installer = Start-Process -FilePath $packages[0].FullName -ArgumentList "/S /D=$install" -PassThru -Wait + if ($installer.ExitCode -ne 0) { throw "NSIS installer failed: $($installer.ExitCode)" } + $app = Join-Path $install 'git-view-desktop.exe' + if (-not (Test-Path $app)) { throw 'NSIS did not install the desktop executable' } + node scripts/verify-installed.mjs --app $install --output dist/installers/windows-package-verification.json + if ($LASTEXITCODE -ne 0) { throw 'Installed payload verification failed' } + node scripts/verify-windows-ui.mjs $app + if ($LASTEXITCODE -ne 0) { throw 'Installed native window verification failed' } +} finally { + $uninstaller = Join-Path $install 'uninstall.exe' + if (Test-Path $uninstaller) { + $process = Start-Process -FilePath $uninstaller -ArgumentList '/S' -Wait -PassThru + if ($process.ExitCode -ne 0) { throw "NSIS uninstall failed: $($process.ExitCode)" } + # NSIS uninstall may hand off to a temporary process before returning. + for ($i = 0; $i -lt 30 -and (Test-Path (Join-Path $install 'git-view-desktop.exe')); $i++) { Start-Sleep -Milliseconds 500 } + if (Test-Path (Join-Path $install 'git-view-desktop.exe')) { throw 'Uninstall left the desktop executable installed' } + } + if (Test-Path $install) { Remove-Item $install -Recurse -Force } +} +$reportPath = 'dist/installers/windows-package-verification.json' +$report = Get-Content $reportPath -Raw | ConvertFrom-Json +$report.checks | Add-Member -NotePropertyName nsisInstalledAndUninstalled -NotePropertyValue $true +$report | ConvertTo-Json -Depth 20 | Set-Content $reportPath -Encoding utf8NoBOM diff --git a/scripts/verify-windows-ui.mjs b/scripts/verify-windows-ui.mjs new file mode 100644 index 0000000..62b50d8 --- /dev/null +++ b/scripts/verify-windows-ui.mjs @@ -0,0 +1,107 @@ +import assert from 'node:assert/strict'; +import { execFile, spawn } from 'node:child_process'; +import { createHash } from 'node:crypto'; +import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'; +import { createServer } from 'node:net'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { promisify } from 'node:util'; +import { setTimeout as wait } from 'node:timers/promises'; +import { chromium, expect } from '@playwright/test'; + +// Drive the actual installed WebView2 window; no HTTP fallback or mocked IPC. +if (process.platform !== 'win32') throw new Error('Native UI verification must run on Windows.'); +const executable = resolve(process.argv[2] || ''); +const output = resolve('dist/installers'); +const temporary = await mkdtemp(join(tmpdir(), 'git-view-native-ui-')); +const fixture = join(temporary, '中文 仓库'); +const exec = promisify(execFile); +const env = { ...process.env, GIT_VIEW_HOME: join(temporary, 'state'), GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: 'NUL' }; +for (const name of Object.keys(env)) if (name.startsWith('GIT_') && !['GIT_VIEW_HOME', 'GIT_CONFIG_NOSYSTEM', 'GIT_CONFIG_GLOBAL'].includes(name)) delete env[name]; +let child; let browser; let exited; +const report = { platform: process.platform, arch: process.arch, executable, checks: {}, environment: 'GitHub Windows runner; installed release WebView2 window', physicalWindows10Or11: 'unverified' }; +try { + await mkdir(output, { recursive: true }); + await mkdir(fixture); + const git = (...args) => exec('git', ['-C', fixture, ...args], { env }); + await git('init', '-b', 'main'); + await writeFile(join(fixture, '中文 文件.txt'), 'version one\n'); + await git('add', '--', '中文 文件.txt'); + await git('-c', 'user.name=Installer Test', '-c', 'user.email=test@example.invalid', 'commit', '-m', 'native fixture commit'); + await writeFile(join(fixture, '中文 文件.txt'), 'version two staged\n'); + await git('add', '--', '中文 文件.txt'); + await writeFile(join(fixture, '中文 文件.txt'), 'version three working\n'); + await writeFile(join(fixture, '未跟踪.txt'), 'untracked content\n'); + const fingerprint = async () => { + const hash = createHash('sha256'); + const walk = async path => { + for (const item of (await readdir(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) { + const file = join(path, item.name); hash.update(file.slice(fixture.length)); + if (item.isDirectory()) await walk(file); else hash.update(await readFile(file)); + } + }; + await walk(fixture); return hash.digest('hex'); + }; + const before = await fingerprint(); + const server = createServer(); + await new Promise(resolveListen => server.listen(0, '127.0.0.1', resolveListen)); + const port = server.address().port; + await new Promise(resolveClose => server.close(resolveClose)); + // This debugging endpoint exists only in the disposable CI process. + child = spawn(executable, [], { env: { ...env, WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: `--remote-debugging-port=${port} --remote-debugging-address=127.0.0.1`, WEBVIEW2_USER_DATA_FOLDER: join(temporary, 'webview') }, stdio: ['ignore', 'pipe', 'pipe'] }); + let diagnostic = ''; + child.stdout.on('data', chunk => { diagnostic += chunk; }); + child.stderr.on('data', chunk => { diagnostic += chunk; }); + exited = new Promise((resolveExit, reject) => { child.once('exit', (code, signal) => resolveExit({ code, signal })); child.once('error', reject); }); + await expect.poll(async () => { + if (child.exitCode !== null) throw new Error(`Native app exited before WebView initialization: ${diagnostic}`); + return fetch(`http://127.0.0.1:${port}/json/version`).then(result => result.ok, () => false); + }, { timeout: 60_000, message: 'Installed WebView2 must expose its real window' }).toBe(true); + browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`); + await expect.poll(() => browser.contexts().flatMap(context => context.pages()).length, { timeout: 30_000 }).toBeGreaterThan(0); + const page = browser.contexts().flatMap(context => context.pages())[0]; + const errors = []; page.on('pageerror', error => errors.push(error.message)); + await expect(page.locator('.repository-switcher-trigger')).toBeEnabled({ timeout: 30_000 }); + assert.equal(await page.evaluate(() => typeof window.__TAURI__?.core?.invoke), 'function'); + report.checks.realTauriIpcAndWebView2 = true; + await page.locator('.repository-switcher-trigger').click(); + await page.locator('.repository-switcher-manual summary').click(); + await page.locator('#repo-path').fill(fixture); + await page.locator('.repository-switcher-manual button').click(); + await expect(page.locator('.group-staged .file-row').filter({ hasText: '中文 文件.txt' })).toBeVisible({ timeout: 30_000 }); + report.checks.manualOpenUnicodeRepository = true; + await page.locator('.group-staged .file-row').filter({ hasText: '中文 文件.txt' }).click(); + await expect(page.getByTestId('diff-scroll')).toContainText('+version two staged'); + await expect(page.getByTestId('diff-scroll')).toContainText('-version one'); + report.checks.stagedDiff = true; + await page.locator('.group-unstaged .file-row').filter({ hasText: '中文 文件.txt' }).click(); + await expect(page.getByTestId('diff-scroll')).toContainText('+version three working'); + await expect(page.getByTestId('diff-scroll')).toContainText('-version two staged'); + report.checks.unstagedDiff = true; + await page.screenshot({ path: join(output, 'windows-native-changes.png') }); + await page.getByRole('navigation', { name: /^(Main view|主视图)$/ }).getByRole('button', { name: /^(History|历史)$/ }).click(); + await page.getByTestId('commit-row').filter({ hasText: 'native fixture commit' }).click(); + await expect(page.getByTestId('diff-scroll')).toContainText('+version one'); + report.checks.historyAndCommitDiff = true; + await page.screenshot({ path: join(output, 'windows-native-history.png') }); + assert.deepEqual(errors, []); + report.checks.noRendererErrors = true; + assert.equal(await fingerprint(), before); + report.checks.repositoryFingerprintUnchanged = true; + const nodes = JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', `ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process -Filter "ParentProcessId = ${child.pid}" | Where-Object Name -eq 'node.exe' | Select-Object -ExpandProperty ProcessId)`])).stdout); + assert.ok(nodes.length > 0, 'The real packaged Node sidecar must be running.'); + await exec('powershell.exe', ['-NoProfile', '-Command', `$p = Get-Process -Id ${child.pid}; if (-not $p.CloseMainWindow()) { throw 'Native window did not accept close' }`]); + const result = await Promise.race([exited, wait(15_000).then(() => { throw new Error('Native host did not exit after window close.'); })]); + assert.equal(result.code, 0); + await expect.poll(async () => (await exec('powershell.exe', ['-NoProfile', '-Command', `@(Get-Process -Id ${nodes.join(',')} -ErrorAction SilentlyContinue).Count`])).stdout.trim(), { timeout: 15_000 }).toBe('0'); + report.checks.normalWindowCloseAndSidecarCleanup = true; + await writeFile(join(output, 'windows-native-verification.json'), `${JSON.stringify(report, null, 2)}\n`); + console.log('Installed Windows window, repository opening, staged/unstaged diff, history and normal exit passed.'); +} catch (error) { + if (browser) for (const page of browser.contexts().flatMap(context => context.pages())) await page.screenshot({ path: join(output, 'windows-native-failure.png') }).catch(() => {}); + throw error; +} finally { + await browser?.close().catch(() => {}); + if (child && child.exitCode === null) { await exec('taskkill.exe', ['/PID', String(child.pid), '/T', '/F']).catch(() => {}); await exited.catch(() => {}); } + await rm(temporary, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 }); +} From f479038f55af0175bd1e3bce225ab74e170b86be Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 17:14:42 +0800 Subject: [PATCH 02/10] fix: execute and verify the packaged macOS installer --- scripts/install-desktop.mjs | 7 ++++++- scripts/macos-install.command | 2 ++ scripts/verify-macos-package.mjs | 5 ++++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/install-desktop.mjs b/scripts/install-desktop.mjs index 442f75e..ec8a988 100644 --- a/scripts/install-desktop.mjs +++ b/scripts/install-desktop.mjs @@ -72,7 +72,12 @@ async function validateBundle(path, run, expectedVersion) { async function requireStopped(run, paths) { const output = (await run('/bin/ps', ['-axo', 'pid=,comm='])).stdout; for (const line of output.split('\n')) { - const command = line.trim().replace(/^\d+\s+/, ''); + const entry = line.trim().match(/^(\d+)\s+(.+)$/); + if (!entry) continue; + const command = entry[2]; + // A distribution installer runs this script with the incoming bundle's + // own Node. Exempt only this exact process, never another app or sidecar. + if (Number(entry[1]) === process.pid && command === process.execPath) continue; if (command.endsWith('/Contents/MacOS/git-view-desktop') || paths.some(path => command.startsWith(`${path}/Contents/`))) { throw new Error('Git View is running. Quit Git View, then rerun the desktop update/install command. No process was stopped.'); } diff --git a/scripts/macos-install.command b/scripts/macos-install.command index 52684fb..21080fa 100644 --- a/scripts/macos-install.command +++ b/scripts/macos-install.command @@ -2,6 +2,8 @@ set -euo pipefail package_dir="$(cd "$(dirname "$0")" && pwd)" work_dir="$(/usr/bin/mktemp -d /tmp/git-view-package.XXXXXX)" +# Node resolves the script URL to a physical path; keep argv on the same path. +work_dir="$(cd "$work_dir" && pwd -P)" trap '/bin/rm -rf "$work_dir"' EXIT bundle_dir="$work_dir/apps/desktop/src-tauri/target/release/bundle/macos" /bin/mkdir -p "$bundle_dir" "$work_dir/scripts" diff --git a/scripts/verify-macos-package.mjs b/scripts/verify-macos-package.mjs index 64458b2..3a6cd61 100644 --- a/scripts/verify-macos-package.mjs +++ b/scripts/verify-macos-package.mjs @@ -3,6 +3,7 @@ import { mkdir, mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join, resolve } from 'node:path'; import { promisify } from 'node:util'; +import assert from 'node:assert/strict'; const exec = promisify(execFile); if (process.platform !== 'darwin') throw new Error('macOS verification must run on macOS.'); @@ -17,7 +18,9 @@ try { await exec('/usr/bin/hdiutil', ['attach', '-nobrowse', '-readonly', '-mountpoint', mount, join(folder, images[0])]); mounted = true; const command = join(mount, 'Install Git View.command'); - await exec('/bin/bash', [command, '--dry-run'], { timeout: 90_000 }); + const plan = JSON.parse((await exec('/bin/bash', [command, '--dry-run'], { timeout: 90_000 })).stdout); + assert.equal(plan.dryRun, true); + assert.equal(plan.searchRefresh.status, 'planned'); const report = JSON.parse((await exec('/bin/bash', [command, '--verify-only'], { timeout: 120_000 })).stdout); report.checks.dmgMountedAndInstallerDryRun = true; report.checks.extractedPayloadVerified = true; From 1a6a640705b9f68fc47a37790be3851767a6254b Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 17:25:21 +0800 Subject: [PATCH 03/10] ci: diagnose and retry installed Windows window checks --- .github/workflows/package.yml | 42 +++++++++++++++++++++++++++++++++++ scripts/verify-windows-ui.mjs | 8 +++++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index c13fdc3..3df6178 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -1,10 +1,17 @@ name: Unsigned desktop packages on: workflow_dispatch: + inputs: + verify_package_run: + description: Optional existing package run for a Windows verification-only retry + required: false + default: '' + type: string permissions: contents: read jobs: package: + if: inputs.verify_package_run == '' strategy: fail-fast: false matrix: @@ -24,6 +31,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: workspaces: apps/desktop/src-tauri + cache-on-failure: true - run: pnpm install --frozen-lockfile - run: pnpm typecheck - name: macOS full tests including HTTP transport @@ -51,3 +59,37 @@ jobs: name: unsigned-desktop-${{ matrix.os }} path: dist/installers/ if-no-files-found: error + verify_windows: + if: inputs.verify_package_run != '' + runs-on: windows-2025 + timeout-minutes: 10 + permissions: + contents: read + actions: read + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + with: + version: 11.7.0 + - uses: actions/setup-node@v4 + with: + node-version: 24.19.0 + cache: pnpm + - run: pnpm install --frozen-lockfile + - name: Download the existing installer without rebuilding + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + PACKAGE_RUN: ${{ inputs.verify_package_run }} + run: | + if ($env:PACKAGE_RUN -notmatch '^\d+$') { throw 'Package run must be a numeric GitHub Actions run ID' } + gh run download $env:PACKAGE_RUN --name unsigned-desktop-windows-2025 --dir dist/installers + if ($LASTEXITCODE -ne 0) { throw 'Could not download the installer' } + - shell: pwsh + run: ./scripts/verify-windows-package.ps1 + - uses: actions/upload-artifact@v4 + if: always() + with: + name: windows-installer-verification + path: dist/installers/ + if-no-files-found: error diff --git a/scripts/verify-windows-ui.mjs b/scripts/verify-windows-ui.mjs index 62b50d8..ae928dc 100644 --- a/scripts/verify-windows-ui.mjs +++ b/scripts/verify-windows-ui.mjs @@ -19,6 +19,7 @@ const exec = promisify(execFile); const env = { ...process.env, GIT_VIEW_HOME: join(temporary, 'state'), GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: 'NUL' }; for (const name of Object.keys(env)) if (name.startsWith('GIT_') && !['GIT_VIEW_HOME', 'GIT_CONFIG_NOSYSTEM', 'GIT_CONFIG_GLOBAL'].includes(name)) delete env[name]; let child; let browser; let exited; +let diagnostic = ''; let probeFailure; const report = { platform: process.platform, arch: process.arch, executable, checks: {}, environment: 'GitHub Windows runner; installed release WebView2 window', physicalWindows10Or11: 'unverified' }; try { await mkdir(output, { recursive: true }); @@ -49,13 +50,12 @@ try { await new Promise(resolveClose => server.close(resolveClose)); // This debugging endpoint exists only in the disposable CI process. child = spawn(executable, [], { env: { ...env, WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: `--remote-debugging-port=${port} --remote-debugging-address=127.0.0.1`, WEBVIEW2_USER_DATA_FOLDER: join(temporary, 'webview') }, stdio: ['ignore', 'pipe', 'pipe'] }); - let diagnostic = ''; child.stdout.on('data', chunk => { diagnostic += chunk; }); child.stderr.on('data', chunk => { diagnostic += chunk; }); exited = new Promise((resolveExit, reject) => { child.once('exit', (code, signal) => resolveExit({ code, signal })); child.once('error', reject); }); await expect.poll(async () => { if (child.exitCode !== null) throw new Error(`Native app exited before WebView initialization: ${diagnostic}`); - return fetch(`http://127.0.0.1:${port}/json/version`).then(result => result.ok, () => false); + return fetch(`http://127.0.0.1:${port}/json/version`).then(result => { probeFailure = `HTTP ${result.status}`; return result.ok; }, error => { probeFailure = String(error.cause || error); return false; }); }, { timeout: 60_000, message: 'Installed WebView2 must expose its real window' }).toBe(true); browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`); await expect.poll(() => browser.contexts().flatMap(context => context.pages()).length, { timeout: 30_000 }).toBeGreaterThan(0); @@ -98,6 +98,10 @@ try { await writeFile(join(output, 'windows-native-verification.json'), `${JSON.stringify(report, null, 2)}\n`); console.log('Installed Windows window, repository opening, staged/unstaged diff, history and normal exit passed.'); } catch (error) { + const processes = child ? await exec('powershell.exe', ['-NoProfile', '-Command', `ConvertTo-Json -Depth 4 -InputObject @{ app = @(Get-Process -Id ${child.pid} -ErrorAction SilentlyContinue | Select-Object Id,MainWindowTitle,Responding,SessionId,@{Name='WindowHandle';Expression={ [string]$_.MainWindowHandle }}); webviews = @(Get-CimInstance Win32_Process | Where-Object { $_.Name -eq 'msedgewebview2.exe' -or $_.ProcessId -eq ${child.pid} -or $_.ParentProcessId -eq ${child.pid} } | Select-Object Name,ProcessId,ParentProcessId,CommandLine) }`]).then(result => result.stdout, failure => String(failure)) : ''; + await writeFile(join(output, 'windows-native-diagnostics.json'), `${JSON.stringify({ error: String(error), diagnostic, probeFailure, processes }, null, 2)}\n`); + const screenshot = join(output, 'windows-native-desktop-failure.png').replaceAll("'", "''"); + await exec('powershell.exe', ['-NoProfile', '-Command', `Add-Type -AssemblyName System.Windows.Forms; Add-Type -AssemblyName System.Drawing; $r=[System.Windows.Forms.SystemInformation]::VirtualScreen; $b=New-Object System.Drawing.Bitmap($r.Width,$r.Height); $g=[System.Drawing.Graphics]::FromImage($b); try { $g.CopyFromScreen($r.Left,$r.Top,0,0,$b.Size); $b.Save('${screenshot}',[System.Drawing.Imaging.ImageFormat]::Png) } finally { $g.Dispose(); $b.Dispose() }`]).catch(failure => console.log(`Desktop screenshot unavailable: ${failure.message}`)); if (browser) for (const page of browser.contexts().flatMap(context => context.pages())) await page.screenshot({ path: join(output, 'windows-native-failure.png') }).catch(() => {}); throw error; } finally { From 631ad08b6e1732ba2af2cae485f397cf4b304122 Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 18:45:28 +0800 Subject: [PATCH 04/10] fix(ci): connect to elevated WebView2 using scoped policy --- scripts/verify-windows-ui.mjs | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/scripts/verify-windows-ui.mjs b/scripts/verify-windows-ui.mjs index ae928dc..4342eb5 100644 --- a/scripts/verify-windows-ui.mjs +++ b/scripts/verify-windows-ui.mjs @@ -4,7 +4,7 @@ import { createHash } from 'node:crypto'; import { mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises'; import { createServer } from 'node:net'; import { tmpdir } from 'node:os'; -import { join, resolve } from 'node:path'; +import { basename, join, resolve } from 'node:path'; import { promisify } from 'node:util'; import { setTimeout as wait } from 'node:timers/promises'; import { chromium, expect } from '@playwright/test'; @@ -19,7 +19,7 @@ const exec = promisify(execFile); const env = { ...process.env, GIT_VIEW_HOME: join(temporary, 'state'), GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: 'NUL' }; for (const name of Object.keys(env)) if (name.startsWith('GIT_') && !['GIT_VIEW_HOME', 'GIT_CONFIG_NOSYSTEM', 'GIT_CONFIG_GLOBAL'].includes(name)) delete env[name]; let child; let browser; let exited; -let diagnostic = ''; let probeFailure; +let diagnostic = ''; let probeFailure; let policyOverride; const report = { platform: process.platform, arch: process.arch, executable, checks: {}, environment: 'GitHub Windows runner; installed release WebView2 window', physicalWindows10Or11: 'unverified' }; try { await mkdir(output, { recursive: true }); @@ -48,6 +48,17 @@ try { await new Promise(resolveListen => server.listen(0, '127.0.0.1', resolveListen)); const port = server.address().port; await new Promise(resolveClose => server.close(resolveClose)); + // WebView2 150+ ignores environment debug flags in elevated hosts. + // Scope its documented HKLM override to this executable on the disposable runner, + // and restore any previous value even when verification fails. + const policyKey = 'HKLM:\\SOFTWARE\\Policies\\Microsoft\\Edge\\WebView2\\AdditionalBrowserArguments'; + const policyName = basename(executable).replaceAll("'", "''"); + const elevated = (await exec('powershell.exe', ['-NoProfile', '-Command', "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"])).stdout.trim() === 'True'; + report.elevatedHost = elevated; + if (elevated) { + policyOverride = { key: policyKey, name: policyName, previous: JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', `$ErrorActionPreference='Stop'; $k='${policyKey}'; $n='${policyName}'; $v=Get-ItemPropertyValue -LiteralPath $k -Name $n -ErrorAction SilentlyContinue; ConvertTo-Json -Compress -InputObject $v`])).stdout) }; + await exec('powershell.exe', ['-NoProfile', '-Command', `$ErrorActionPreference='Stop'; New-Item -Path '${policyKey}' -Force | Out-Null; New-ItemProperty -LiteralPath '${policyKey}' -Name '${policyName}' -PropertyType String -Value '--remote-debugging-port=${port} --remote-debugging-address=127.0.0.1' -Force | Out-Null`]); + } // This debugging endpoint exists only in the disposable CI process. child = spawn(executable, [], { env: { ...env, WEBVIEW2_ADDITIONAL_BROWSER_ARGUMENTS: `--remote-debugging-port=${port} --remote-debugging-address=127.0.0.1`, WEBVIEW2_USER_DATA_FOLDER: join(temporary, 'webview') }, stdio: ['ignore', 'pipe', 'pipe'] }); child.stdout.on('data', chunk => { diagnostic += chunk; }); @@ -107,5 +118,12 @@ try { } finally { await browser?.close().catch(() => {}); if (child && child.exitCode === null) { await exec('taskkill.exe', ['/PID', String(child.pid), '/T', '/F']).catch(() => {}); await exited.catch(() => {}); } + if (policyOverride) { + const { key, name, previous } = policyOverride; + const restore = previous === null + ? `Remove-ItemProperty -LiteralPath '${key}' -Name '${name}' -ErrorAction SilentlyContinue` + : `New-ItemProperty -LiteralPath '${key}' -Name '${name}' -PropertyType String -Value '${String(previous).replaceAll("'", "''")}' -Force | Out-Null`; + await exec('powershell.exe', ['-NoProfile', '-Command', restore]); + } await rm(temporary, { recursive: true, force: true, maxRetries: 10, retryDelay: 500 }); } From 839daef5400972e032cbf424194bc06d130673cc Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 18:46:35 +0800 Subject: [PATCH 05/10] test(windows): reject console allocation by desktop sidecar --- scripts/verify-windows-ui.mjs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/verify-windows-ui.mjs b/scripts/verify-windows-ui.mjs index 4342eb5..791a535 100644 --- a/scripts/verify-windows-ui.mjs +++ b/scripts/verify-windows-ui.mjs @@ -101,6 +101,16 @@ try { report.checks.repositoryFingerprintUnchanged = true; const nodes = JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', `ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process -Filter "ParentProcessId = ${child.pid}" | Where-Object Name -eq 'node.exe' | Select-Object -ExpandProperty ProcessId)`])).stdout); assert.ok(nodes.length > 0, 'The real packaged Node sidecar must be running.'); + const consoleHandles = JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', ` + Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class ConsoleProbe { [DllImport("kernel32.dll")] public static extern bool FreeConsole(); [DllImport("kernel32.dll")] public static extern bool AttachConsole(uint pid); [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow(); }'; + $handles = @(${nodes.join(',')} | ForEach-Object { + [ConsoleProbe]::FreeConsole() | Out-Null; + if ([ConsoleProbe]::AttachConsole($_)) { [ConsoleProbe]::GetConsoleWindow().ToInt64(); [ConsoleProbe]::FreeConsole() | Out-Null } else { 0 } + }); + ConvertTo-Json -Compress -InputObject $handles + `])).stdout); + assert.ok(consoleHandles.every(handle => handle === 0), 'The background Node sidecar must not allocate a console window.'); + report.checks.noSidecarConsoleWindow = true; await exec('powershell.exe', ['-NoProfile', '-Command', `$p = Get-Process -Id ${child.pid}; if (-not $p.CloseMainWindow()) { throw 'Native window did not accept close' }`]); const result = await Promise.race([exited, wait(15_000).then(() => { throw new Error('Native host did not exit after window close.'); })]); assert.equal(result.code, 0); From d9f03ee0aeccca9cc6e05ccec8d889fc70d87ba8 Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 18:47:27 +0800 Subject: [PATCH 06/10] fix(ci): handle absent PowerShell registry value --- scripts/verify-windows-ui.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/verify-windows-ui.mjs b/scripts/verify-windows-ui.mjs index 791a535..1be08af 100644 --- a/scripts/verify-windows-ui.mjs +++ b/scripts/verify-windows-ui.mjs @@ -56,7 +56,7 @@ try { const elevated = (await exec('powershell.exe', ['-NoProfile', '-Command', "([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)"])).stdout.trim() === 'True'; report.elevatedHost = elevated; if (elevated) { - policyOverride = { key: policyKey, name: policyName, previous: JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', `$ErrorActionPreference='Stop'; $k='${policyKey}'; $n='${policyName}'; $v=Get-ItemPropertyValue -LiteralPath $k -Name $n -ErrorAction SilentlyContinue; ConvertTo-Json -Compress -InputObject $v`])).stdout) }; + policyOverride = { key: policyKey, name: policyName, previous: JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', `$ErrorActionPreference='Stop'; $k='${policyKey}'; $n='${policyName}'; $v=Get-ItemPropertyValue -LiteralPath $k -Name $n -ErrorAction SilentlyContinue; ConvertTo-Json -Compress -InputObject $v`])).stdout.trim() || 'null') }; await exec('powershell.exe', ['-NoProfile', '-Command', `$ErrorActionPreference='Stop'; New-Item -Path '${policyKey}' -Force | Out-Null; New-ItemProperty -LiteralPath '${policyKey}' -Name '${policyName}' -PropertyType String -Value '--remote-debugging-port=${port} --remote-debugging-address=127.0.0.1' -Force | Out-Null`]); } // This debugging endpoint exists only in the disposable CI process. From f98a880779d55d2507c8c5068b8b07088f6df519 Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 18:50:16 +0800 Subject: [PATCH 07/10] fix(ci): respect initially open repository menu --- scripts/verify-windows-ui.mjs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/verify-windows-ui.mjs b/scripts/verify-windows-ui.mjs index 1be08af..12d0ba2 100644 --- a/scripts/verify-windows-ui.mjs +++ b/scripts/verify-windows-ui.mjs @@ -75,7 +75,20 @@ try { await expect(page.locator('.repository-switcher-trigger')).toBeEnabled({ timeout: 30_000 }); assert.equal(await page.evaluate(() => typeof window.__TAURI__?.core?.invoke), 'function'); report.checks.realTauriIpcAndWebView2 = true; - await page.locator('.repository-switcher-trigger').click(); + const nodes = JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', `ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process -Filter "ParentProcessId = ${child.pid}" | Where-Object Name -eq 'node.exe' | Select-Object -ExpandProperty ProcessId)`])).stdout); + assert.ok(nodes.length > 0, 'The real packaged Node sidecar must be running.'); + const consoleHandles = JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', ` + Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class ConsoleProbe { [DllImport("kernel32.dll")] public static extern bool FreeConsole(); [DllImport("kernel32.dll")] public static extern bool AttachConsole(uint pid); [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow(); }'; + $handles = @(${nodes.join(',')} | ForEach-Object { + [ConsoleProbe]::FreeConsole() | Out-Null; + if ([ConsoleProbe]::AttachConsole($_)) { [ConsoleProbe]::GetConsoleWindow().ToInt64(); [ConsoleProbe]::FreeConsole() | Out-Null } else { 0 } + }); + ConvertTo-Json -Compress -InputObject $handles + `])).stdout); + assert.ok(consoleHandles.every(handle => handle === 0), 'The background Node sidecar must not allocate a console window.'); + report.checks.noSidecarConsoleWindow = true; + const switcher = page.locator('.repository-switcher-trigger'); + if (await switcher.getAttribute('aria-expanded') !== 'true') await switcher.click(); await page.locator('.repository-switcher-manual summary').click(); await page.locator('#repo-path').fill(fixture); await page.locator('.repository-switcher-manual button').click(); @@ -99,18 +112,6 @@ try { report.checks.noRendererErrors = true; assert.equal(await fingerprint(), before); report.checks.repositoryFingerprintUnchanged = true; - const nodes = JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', `ConvertTo-Json -Compress -InputObject @(Get-CimInstance Win32_Process -Filter "ParentProcessId = ${child.pid}" | Where-Object Name -eq 'node.exe' | Select-Object -ExpandProperty ProcessId)`])).stdout); - assert.ok(nodes.length > 0, 'The real packaged Node sidecar must be running.'); - const consoleHandles = JSON.parse((await exec('powershell.exe', ['-NoProfile', '-Command', ` - Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class ConsoleProbe { [DllImport("kernel32.dll")] public static extern bool FreeConsole(); [DllImport("kernel32.dll")] public static extern bool AttachConsole(uint pid); [DllImport("kernel32.dll")] public static extern IntPtr GetConsoleWindow(); }'; - $handles = @(${nodes.join(',')} | ForEach-Object { - [ConsoleProbe]::FreeConsole() | Out-Null; - if ([ConsoleProbe]::AttachConsole($_)) { [ConsoleProbe]::GetConsoleWindow().ToInt64(); [ConsoleProbe]::FreeConsole() | Out-Null } else { 0 } - }); - ConvertTo-Json -Compress -InputObject $handles - `])).stdout); - assert.ok(consoleHandles.every(handle => handle === 0), 'The background Node sidecar must not allocate a console window.'); - report.checks.noSidecarConsoleWindow = true; await exec('powershell.exe', ['-NoProfile', '-Command', `$p = Get-Process -Id ${child.pid}; if (-not $p.CloseMainWindow()) { throw 'Native window did not accept close' }`]); const result = await Promise.race([exited, wait(15_000).then(() => { throw new Error('Native host did not exit after window close.'); })]); assert.equal(result.code, 0); From 727be42df4ed0fcecb69d38348d61dd1831e02ef Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 18:54:04 +0800 Subject: [PATCH 08/10] fix(windows): keep desktop background processes without console windows --- README.md | 2 +- apps/desktop/src-tauri/src/sidecar.rs | 6 ++++++ docs/INSTALL.md | 4 ++-- packages/git-cli/src/runner.ts | 2 +- scripts/verify-windows-package.ps1 | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2f115ad..4518828 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Git View 是一个本地 Git 图形工具,用于查看文件差异、浏览提 ## 安装 -从 [Releases](https://github.com/XSY-28/git-view/releases) 下载对应平台的预览安装包。两种包都自带 Node 运行时,使用时只需系统 Git,无需另装 Node、pnpm 或 Rust。 +预览安装包可在 [打包工作流](https://github.com/XSY-28/git-view/actions/workflows/package.yml) 对应成功运行的 Artifacts 中下载(需要登录 GitHub);正式发布后也会提供于 [Releases](https://github.com/XSY-28/git-view/releases)。两种包都自带 Node 运行时,使用时只需系统 Git,无需另装 Node、pnpm 或 Rust。 - **macOS Apple silicon**:下载 `macos-arm64.dmg`,打开后先退出 Git View,再双击 **Install Git View.command**。安装位置固定为 `~/Applications/Git View.app`,旧版本保留为 ZIP;完成后弹出磁盘映像。不要再复制一份到 `/Applications`。 - **Windows x64**:下载 `windows-x64-setup.exe`,运行安装向导,再从开始菜单打开。需要 Git for Windows 与 WebView2;缺少 WebView2 时安装程序会下载运行时。Windows 仅支持查看,不开放暂存、提交和分支写入。 diff --git a/apps/desktop/src-tauri/src/sidecar.rs b/apps/desktop/src-tauri/src/sidecar.rs index 4d9979e..72fcdaf 100644 --- a/apps/desktop/src-tauri/src/sidecar.rs +++ b/apps/desktop/src-tauri/src/sidecar.rs @@ -66,6 +66,12 @@ impl Sidecar { use std::os::unix::process::CommandExt; command.process_group(0); } + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + // The GUI host has no console; keep its piped background runtime quiet. + command.creation_flags(windows_sys::Win32::System::Threading::CREATE_NO_WINDOW); + } let mut child = command .spawn() .map_err(|_| "Bundled query process could not start")?; diff --git a/docs/INSTALL.md b/docs/INSTALL.md index e29690a..8cbd8fd 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,6 +1,6 @@ # 安装 Git View 预览包 -在 [GitHub Releases](https://github.com/XSY-28/git-view/releases) 选择同一版本的安装包与 `.sha256` 文件。当前目标平台为 macOS 13.5 及以上的 Apple silicon(arm64)和 Windows x64,尚不提供 Intel Mac、Windows ARM 或 Linux 安装包。macOS 下限由包内 [Node 24 的平台要求](https://github.com/nodejs/node/blob/v24.19.0/BUILDING.md#platform-list)决定。 +在 [打包工作流](https://github.com/XSY-28/git-view/actions/workflows/package.yml) 的成功运行中下载对应平台的 Artifacts ZIP(需要登录 GitHub),解压后取出安装包与 `.sha256` 文件。正式发布后也可在 [GitHub Releases](https://github.com/XSY-28/git-view/releases) 下载同版本文件。当前目标平台为 macOS 13.5 及以上的 Apple silicon(arm64)和 Windows x64,尚不提供 Intel Mac、Windows ARM 或 Linux 安装包。macOS 下限由包内 [Node 24 的平台要求](https://github.com/nodejs/node/blob/v24.19.0/BUILDING.md#platform-list)决定。 ## macOS Apple silicon @@ -35,6 +35,6 @@ Windows 当前只支持查看,包括当前改动、历史、版本比较和历 包构建工作流分别在 macOS ARM 与 Windows x64 runner 上执行类型检查、适用的真实 Git 测试和 Rust 宿主测试,再构建安装包。 - macOS:验证 DMG 完整性和挂载、执行包内安装程序 dry-run,并对从 DMG 解压出的内容验证内置 Node、中文/空格路径、仓库只读指纹和 stdio 退出。原生 GUI 在本机固定安装路径验收。 -- Windows:运行真正的 NSIS 静默安装,对安装结果做包内运行检查;启动安装后的 release 应用,用 WebView2 调试连接驱动真实窗口和 Tauri IPC,检查打开中文路径仓库、暂存/未暂存差异、历史差异、正常关闭与 Node 子进程回收,最后卸载。 +- Windows:运行真正的 NSIS 静默安装到包含中文和空格的目录,对安装结果做包内运行检查;启动安装后的 release 应用,用 WebView2 调试连接驱动真实窗口和 Tauri IPC,检查打开中文路径仓库、暂存/未暂存差异、历史差异、正常关闭与 Node 子进程回收,最后卸载。 这些检查的通过与失败以该版本对应的 GitHub Actions 和随包验证报告为准。Windows runner 是 CI 系统,不能替代所有 Windows 10/11 实体电脑上的验收;SmartScreen、Gatekeeper、人工安装向导和原生系统目录选择对话框也需要目标设备复测。当前安装包没有自动更新机制。 diff --git a/packages/git-cli/src/runner.ts b/packages/git-cli/src/runner.ts index 0edd90c..e8d6f2a 100644 --- a/packages/git-cli/src/runner.ts +++ b/packages/git-cli/src/runner.ts @@ -45,7 +45,7 @@ export function runGit(cwd: string, args: string[], options: RunOptions = {}): P '-c', 'i18n.logOutputEncoding=utf-8', '-c', 'protocol.allow=never', ]; return new Promise((resolve, reject) => { - const child = spawn('git', [...globalArgs, ...args], { cwd, env, shell: false, stdio: ['pipe', 'pipe', 'pipe'] }); + const child = spawn('git', [...globalArgs, ...args], { cwd, env, shell: false, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }); const stdout: Buffer[] = []; const stderr: Buffer[] = []; let bytes = 0; diff --git a/scripts/verify-windows-package.ps1 b/scripts/verify-windows-package.ps1 index 9ddfdae..a637219 100644 --- a/scripts/verify-windows-package.ps1 +++ b/scripts/verify-windows-package.ps1 @@ -1,5 +1,5 @@ $ErrorActionPreference = 'Stop' -$install = Join-Path $env:RUNNER_TEMP 'GitViewPackageInstall' +$install = Join-Path $env:RUNNER_TEMP 'Git View 中文 安装' if (Test-Path $install) { throw "Verification directory already exists: $install" } $packages = @(Get-ChildItem 'dist/installers/*-setup.exe') if ($packages.Count -ne 1) { throw 'Expected exactly one NSIS installer' } From 13210c9ba5aa822cc52a2f3d2478444c787740f4 Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 19:06:46 +0800 Subject: [PATCH 09/10] docs: record verified desktop installer results --- README.md | 2 +- .../2026-10-07-desktop-installers.md | 39 ++++++++++++++++++ .../desktop-installers-windows-changes.png | Bin 0 -> 36138 bytes .../desktop-installers-windows-history.png | Bin 0 -> 48348 bytes 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 docs/verification/2026-10-07-desktop-installers.md create mode 100644 docs/verification/screenshots/desktop-installers-windows-changes.png create mode 100644 docs/verification/screenshots/desktop-installers-windows-history.png diff --git a/README.md b/README.md index 4518828..09d6b50 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ open "$HOME/Applications/Git View.app" ## 支持范围 -macOS Apple silicon 已有桌面实机验证。Windows 暂时仅开放查看,源码与桌面宿主由 CI 检查,安装和原生界面尚未实机验证。 +macOS Apple silicon 已有桌面实机验证。Windows 暂时仅开放查看,已通过 Windows CI 中的实际安装、原生窗口差异与历史走查、正常退出和卸载验证;Windows 10/11 实体电脑尚未复测。详见[安装包验收记录](docs/verification/2026-10-07-desktop-installers.md)。 - 暂不提供 push、merge、rebase 或历史重写。远程跟踪引用来自本地仓库,不会自动获取远端更新。 - 暂存按整个文件操作,不支持选择差异行或片段。写入限常规仓库和 index;冲突、进行中的 Git 操作、submodule、特殊 index 或外部内容过滤器会被拒绝。暂不支持 `text`、`eol`、`ident`、`working-tree-encoding` 和 autocrlf 转换。 diff --git a/docs/verification/2026-10-07-desktop-installers.md b/docs/verification/2026-10-07-desktop-installers.md new file mode 100644 index 0000000..5bbb9a4 --- /dev/null +++ b/docs/verification/2026-10-07-desktop-installers.md @@ -0,0 +1,39 @@ +# 桌面安装包验收(2026-10-07) + +本轮为桌面预览版 0.2.0 提供 macOS Apple silicon DMG 和 Windows x64 NSIS EXE。构建源码为 `727be42df4ed0fcecb69d38348d61dd1831e02ef`,安装步骤见[安装说明](../INSTALL.md)。安装包没有 Developer ID、公证或 Windows 代码签名。 + +## 已验证 + +| 项目 | 结果与证据 | +| --- | --- | +| 本机源码检查 | `pnpm check` 通过:232 个测试、32 个文件,以及类型检查和构建。 | +| 本机宿主检查 | `cargo test --locked` 的 4 个实际 sidecar 生命周期测试通过,包括正常退出、协议错误、崩溃重启和 Git 子进程回收。 | +| macOS 安装与恢复脚本 | `pnpm test:install-desktop` 的 27 个测试通过。 | +| 本地 macOS DMG | 完整性、挂载、包内安装程序真实 dry-run 及解压内容验证通过;实际安装到 `~/Applications/Git View.app`。 | +| 本机 macOS 原生窗口 | 暂存差异显示 `-version one / +version two staged`,工作区差异显示 `-version two staged / +version three working`,首次提交显示 `+version one`;正常退出后主进程与 Node 子进程均结束。 | +| macOS 清理 | 用既有安装脚本移除本轮原始构建副本;旧版本保存为 ZIP,注册唯一检查及搜索服务刷新通过。最近打开的仓库恢复为项目目录,测试临时仓库已移除。 | +| 云端 macOS 包 | [打包运行](https://github.com/XSY-28/git-view/actions/runs/37610447140)的 macOS job 成功。CI 包验证报告与本机同源码构建的 DMG 原生窗口走查是分别记录的证据。 | +| Windows 安装与窗口 | 真实 NSIS 安装到中文/空格目录、包内运行、打开中文仓库、暂存/工作区差异、历史、仓库只读指纹、后台 Node 无控制台、正常关闭与子进程回收、卸载全部通过,见同一打包运行的 Windows job 与随包两份验证报告。 | + +本地 DMG 的 SHA256 为 `42708befe15e289747273e4bb92c694c6e33c0b6062e2eba4bc8398a00b6b49c`。安装后的原生可执行文件和资源哈希,与从该 DMG 解压验证的内容一致。本地验收报告保存在忽略的 `dist/installers/`,CI 的平台包与报告保存在对应工作流 Artifacts。 + +## Windows 问题与回归检查 + +最初的安装包可完成构建、NSIS 安装和包内 CLI / stdio 验证,但原生窗口测试未能接入 WebView2。进程诊断和桌面截图确认窗口、WebView2 与 Node 已运行;WebView2 命令行缺少测试请求的调试参数。 + +微软文档明确指出,[管理员宿主会忽略环境变量中的 WebView2 调试参数](https://learn.microsoft.com/en-us/microsoft-edge/webview2/concepts/webview-features-flags)。验证脚本在一次性 CI 机器中仅为 `git-view-desktop.exe` 设置对应 HKLM 参数,保留并在 `finally` 中恢复旧值。应用的正常启动没有调试端口配置。 + +截图同时暴露了后台 Node 额外分配控制台窗口的问题。[旧包回归运行](https://github.com/XSY-28/git-view/actions/runs/37610032540)在实际进程控制台检查中失败。修复在 Windows 宿主中以 `CREATE_NO_WINDOW` 启动后台 Node,并为 Node 发起的 Git 查询设置 `windowsHide`;保留原有管道通信和 Windows Job 退出清理机制。新包已通过同一进程检查及完整窗口走查。 + +Windows 安装包的 SHA256 为 `ea2cfd34635e5fc47a447bc62fa439822104cf31b6bc8b9da98b436ec0e8cb37`,下载到本机后已与随包校验文件核对。CLI 验证报告中的 `gui: unverified` 只表示 CLI 脚本不执行 GUI 检查;原生窗口的独立报告列出上述窗口检查全部通过。 + +![Windows 安装后的工作区差异](screenshots/desktop-installers-windows-changes.png) + +![Windows 安装后的历史提交差异](screenshots/desktop-installers-windows-history.png) + +## 验证边界 + +- Windows 仍只支持查看,暂存、取消暂存、提交与分支写入尚未开放。 +- Windows CI 环境中的实际窗口验证不等于所有 Windows 10/11 实体电脑验收;人工安装向导、原生目录选择器与 SmartScreen 仍需目标设备复测。 +- macOS 首次互联网下载后的 Gatekeeper 流程、不同 macOS 版本及 Spotlight 搜索面板尚未复测。注册唯一、搜索服务刷新和新 PID 不能替代搜索面板证据。 +- 暂无 Intel Mac、Windows ARM 和 Linux 安装包,也没有自动更新机制。 diff --git a/docs/verification/screenshots/desktop-installers-windows-changes.png b/docs/verification/screenshots/desktop-installers-windows-changes.png new file mode 100644 index 0000000000000000000000000000000000000000..731156fe98604f2188e07d31ab0d1e9ef19f44dd GIT binary patch literal 36138 zcmce-XH-*B*DeYc5Cl;XDGCTkuTrH1kRsAMNEZQ--a8~9MWy!^I?{UyodiL8?^2`m z9tZ>of#k$@eBT-4eq)>;=Z-sW{_O0s_L{5gx#oPHXMNYwP$DO#BPAdpAXk2;phG}# z=MDkEt;2h_@K*$Km<9+49ug=kywUT`I#?kwP4-&rKglghRVqpsFf|8%G$6X?yMe7z zle?`rJj(3_Fi@Q17OL?)ozn~J4Mn}oK{9KTP$GT;|JQktVnVuE+^)d$<>d#}9h~hY32R)12pmU?QZzBo|*H6b~a<`ef|6SH{3PQ~qS^&{SrUOGm z_07#7VXu&>&0{KeV=T$fmWojll9>k^9W?Oa%yc6F7OB zgDm$E0YPVy@}ETt!@W7suRl`-=eNBSW-m@bh~*A9b!aJ33|$g20RV%Vx1gYx@ci2y z&Ir0b$^Xh=KUvT#G%lsv@M*07R@&3M1UuQ4VsMLwCNnD~?M~AhX?btY8Bj|IK^}Xm zYSbrd>p*-L8P=FXfHPxb)d3er=jZ2(_8E|;rs@*eSLh=~U1$YLEA(w#fN;=>=7r30 zo>$1oW$3PwOsA7t6{b|)YJ7~(>>59QDP7r1Vm7(KrQUmhnXdFqH5vlX{k7gGzr^6= z?ChHrAGlGs?1I`0sEr!hzb6q)n?@HPt55l5 zR-ng|23E_|w+Mn&WJPlg<0jwu4N&U?ie(8}B)7-18(MFsid0iK>vw4fq?3*@IG}^1 zy#YYUu$8XacMWn{Qgz=Hjr<)X;s60RDtJ?0pHF97dGwoN&bdP`8vZk8;w^6L5u=AP z*Tl3e^JLlBHS zS#p6bEMrf{#e*VjD2W&XXI|hyyGfv%i@sTeX1_Tgpgy*ZwM3Q$lM5UedHwwKbaa3B zYXRtrX*luuze%!8X(OaI@bfgI5d=#1<}k(%%wp{4+=HYi+JaTn(yCuFpuI2NqFD?b zW`o{2Kcf0`0e2~8!(E+%AdpOoxhll6+D=zpvxMfA-nZ0s>E-K^J#Q~E{EL$l>Pc>2 z%=+i8>?2IYz0MBiQX;%@^9My($i7U0G_w7n$Oa>Yug3YxcfZ9yfxxs3;>i2M3NvVse z2QC^v0-=}-IMC^`MJcCS#6d<2UKC!cEuUQY0AT(w7sw@ThN^i*uQKxiLD0?NAOOHw4t440g;>~43N=eD|L&N1Bn7>jK`Abz-DOSu_`I>#_pp1zCfUn0x-y)#G}l(4w- zBI6F-b$(2fBHywPXf&C>KnHiDjYXo!s&go*Q!E(-yy!N3qz^V@E$5z*Iw8bR>;q$8 zDRSSRx|deMqc)Y`O_Pv#A&z%Tn{_9WVo>AWtGj|S{sGwSg}smak5u~`sW!hLz>kL) zgfF_oHb62y9H!cZXpko1aea2wS-=*EiHjQbnrLHy6Uqx}GG;>uc?O=9M9x zi=6bsC2V2T<8i6;-0F#~BWFWfDgrZCVeeekh-A@$Su3IkcCOo!>7Qp%!WeRZGcy zNU;cQ%n^mXn1Me`3z>^~G^C_bUUjMs^c&lhUSgKnNxt>{P(4>poWC{Df?uV``{vnF ze^%#AnazCi*)qjzz@fkA=pG^mxK07NzY1{1VumS<=bp3mdUCh01=w6%jl`ZJ^uwXT zL)fRAad)u`b-H4fv`l(~!uyl(Beyu*4FcrXg!4sT_XgpTvq?~9IIZ8&nP+2HttVb0kRzhp?~jh9{@zBtS=4C@G5lshI*C8dF)TCiNI(WlKodP}xk%;j2NauK z{MR#*KRe0vjM&QKesuFf?Fz&VF=_BuA)IIWcpBn0354W3yAer_Kk;0oQbh~>= zh=iHBLz~mcJdOeTmHL2G^2_uNbs9?hR`X-BEq1D9I1r^%FW+CDc1OW4j4(*r;;y|NcwEx<1<2U-+A1#9GJhL(bNQjKVrvT*9m>dULt0T|d+p&0*Fwq8 zd5}HFI7v-8dY61Bmcw{H7?qT{+@B4V1lju?1>R8ff`7j-_nma|gIMWcOpJx+a){mxp+|6s`n`syM6B<0msJ*@Q{AGVny&5vvl%^UHakO!TAAN??Omv2!=fBrfb5Bk|y*w$NC$_|3=pMqCc zUQ}QO2^@bnDmzam23J(<d7UFR-a^bTFegqIC9fpe;_=D4_TnUEvUIlB54<~-xC;U%FcvBm@90TuPxTk^4CVIEZh zv5hFh%od>`=@%nw9Ce07|QvE5x#Za*DZso-b#7^(Y8eQ z7+Md$kG~Y>PS(4vtf51xb4&s{mD&zj_xrMWRs@3{mvIn?ddFipgB@#NF8uAti_nb|H1KJ=APi| z;kL!LlErjE3$&{NHiR3bYi|jY;JiRcx*gi8y6pJckXx*UbNZlQr1Etl)A*6AmGv=acU;-bBBUKH}E-M=Mk3#h&HZ7z)w#;LGeg5o5 zZf8TYH)M86$!BLfy)Gz5lsK=;fm9r8H$5qnAxVsUz&drs>~5CG(Nlk1Q>HSq(`I^3 zU~T%;XSm91|1XG^^|b5fxOBV6_Sm;ct08fcd#q&EQOqXYJapcNIj4gT)#^i6rLVyP z3xdgI5ChQp^U@$&j!_wsaIw`E@>jDWfU7%!9c;S$cU|w-?p;F~KP-sbW{g??Fm`i0 zKCd-&7*q+J?wVMJ>v$8g%#ucSMn%2Tdtfp}^uzx~bXWy;^(rB-V)y#Za7Pt?V%|u? zUJ+keHbI8cdRVgq)arB6_t{l)WRV66ydlpIgjz9gv4awc@DOvt?1=)UyZjUKJ?w^r1mU2rkuJd>8@w;hS=HdPX4 zJ(R=7j<(9}E=)%b(yfxY)kKiAs*`Bj-beuED`t=G?-rnY?>i?Mex~x<-u7?h1=yUv z9vjWfy?=zA+0`%MO~D%I%FQ~yV0;|FSNwfUQA|dHhRk8!g$BDVQ^HL?U0;7x?|jp~ z7@r@&WI22p#%sTE!t&$4v4B-woV_(=-1QEPm|xPar5L2!3Gg1ysq6crXmI1hRg|uD zk?tv@nC-m+WSMy^sQ(-=`;-w?*tfD*_%kPW?brPa;7*0y5lRr%B6(&3F{2|g;bogZLL)&ku&tM&C zPRHXI;H%J?q0VatI3&Qsr36)%_7>K{>r=5jNt?#jOGZ~>c4wDw@!*KTGVh}&jS#j@ zGi5Bq4(B+xZlxM@<7{oJ!*)mNTtU}_!u-nkP_wq}Tm0;Y{Ez0TdMu}1wU1bu9an{~ z`3r|-NZvVh8rNVheYaGL9A<~b_V<%pB#{_S7o|M3JbY$Qt1C96_?~vto+KwD2RXRu zp;FSmh(6an-Vm zB!|d@g=_w>w8d)J<|XIf1yMK1b*AAqS!p(c(WVJfo@;cqbBg1&?PDdqm_)bVoq136kMxv6 zIYNmPi#-3HiJ$1b8sZfCp7yOrHwSr7=hj;fw=^=MesYBK^XtXD%nLYwQgAeJL3E3- zU829ZA}zgjYG&fv+TxEtfElRdX3dn~A88kvIKMA72f_M$8!sB)KG%M?6zs>BG1rjE zHWU#W8q`$e^bQMLlr#dZRxpYfz?G05{QKqUG7Hz8ZPwRrn_eVnm6U{dRkT8R2N^jy z5>Y)6_jC!zS8cBHRNXA9)Wdlj17hg9G{R9@$! zr^=}Bf3cnaWO8`k5*@91l{@Iw3yVF4tOoa)Ls3m>Eu+=KLxN;8)fZYZ7zb?Nencr2 zX<9FYyR+8u9K-t@SLe8e6ZI^p%pC5+Hmg3r8_TJCS8bR-dQ2qX{V;(#__g=tbV8vE zSk&GsM#T+YH|MlCV%GRx?@7@s;LmFa-`4%mmyV{~5wV`$&c32d+KnhJu5TpW(AsnB zqPAM)LmvaEyJ#PT(O6a}|KLeeP?(3kmqArDoAB9=bB%#NK5>iQe#ixo%Yy4={l!j((h4B5-t(}}O=5sN{w}FL ztx8&XTm~aLy-2>`$eY@K{zr`MJw5-+U-`AH+hMt*AMH$5VFIpN+qS8dz*qj^2ShFV z0xxE;gc-y~HGUvkHo`K$)2F$NmwGc=Z;44nNKDlBi(UM#`=2-%_@y#uS32AvnVw-@~j( zC@-&*&r>~mB)4^2I;?BCq%xW?9yIVY)6IUq;l{%Z5OsO$__+kD*J|R)dei$^9w+G} zN3iKfhg)^vcj;2V?A?XaIq}8wy*AtL#t<(Dvv=V$e~T7arurRFW?mgy#T*uPoMPcq zotIc`Z6HsN_h==QUVS9gtte;9M`U|Sw#k4hmGGB%a?aJ4*y!>eL1_i$pavz525FuO zBs^6dklEDevrvFtxBqNw(ZwO*#G|FL@7PnlQpKphmrW&ME8EescgAd@?^^_fdq;oO z8FE2BY1SmAVLQvBK};H;qa8$8{Sz8EEPc$NJ*`h%lFgN8%1jImZt#INqh#QF4y*Xg zC;psF-1etG6K5Z&_K&<1yWzzLtw{bkbEh+H>|>ePk3ad8(-qbE}U`ObAkE$R0!)<_`zxSN@VPp88y@C{j8|sL5 z$7hT%4C}nPp9I92t%?K#-F4t^;ylVXjsb(pU=rM#cC8kmYa15`kg-`t&6Asl##ij{ zbx$I5^+8VVfk2xH3+qoEA@Fib@Y}zJEr{I(%RTF!zHU78mWE~t4!SFErZ?Tnr9NJ!PNI=0UCs-6D`;6`fugMn0 z>$7J;9+*rMal_|BULs7WDfx@zPu8-VDG7(h|W zcMzx|9GB-mHX6wj$bI+`xq1P>N;H4NkZtJd(bSqU3HxMA+m2{5HPZ?A!xJgH}*WOra>zbl}{V}y>dxy*CkxhRf@mjU0Gfia-T6=$gi2k;tElIy2 zbsVNe^L+#TXqmYBrIc& z6~!!bE{Nim#%1hGtZ7OS%aw=eM(*fs@O<1j?eq_a57X4RmqW7%7>;B7Z(42?&-;e+ z+pLF_c+P`jQ%J2;C(o8(B0peRD9^8S&|cSD8STO1oc3Jq-IMKwuOVa zHZ;yJybc(6<8b%!I4sif>Ga~TgbVC1lDewE^Ur>ZJSET)z|$tOh$9%RxKbG2>NTC7dF}9uPu37UTd8k? zPv@ywEiws$znst#5(Fm_@5wC7V($@5Bt^Do{-bz;Rl)`E2ZEg^|KWE24tcpEOv?r0Zb0ErDzw+37ze=ncJolo5sM0*K+7X7lmM@9FU;&A0A{2-FWfXFm8$YEf4*) zfu0%3GjfT#5Q15fU;;#~npm|3fB5ENwyiJ*^r!p!y-e6B8GRO5FGR z($oDM_`>3x`HlelO&}ZD$^Q+j_bR!J}rsSO(+6x#@l!+v9`YPz<9h9=J7rMLD zkDIVru0Jk%EHf=%hCI@IvSOY|zhw954KpJRyL} zd*Dnw*ZVJ&?caOJpc${ffh>DyTWq`3-t1G0ich>#-!_Ivq?Y6093v>C2lz2jL`009 zMR)26iQqO5ePL%Cgb|X9PW0|**q@Uae^0lU*Ez*nL7!}x`46AATWGO?lzcr$HWW%f zHMZpCeZSi0r@Fd|a0Hm(dsREtxNC zqrJ(=LzTTXF;IT@tyaIDQrLy%-lERK_W0X-zLW4}j3TylQ@LQF)I#`xOezN)ToPcg4TiartGc1*UZ4V6XNm$=g= zvu$srQ$Eyz9Lz-(L^<4*|1o)d8vVil?d+`TUiTy|f)OGeh7|Jz1|ZRc&}~R;jyaLG zyEoK@_{zQ~>{D(Qx(T!Lyl~m~L3W~q%itHc-210_d4UIJNWJ}(<3IV14L`pFh@VDSZ2jqH!*W_!59Lw}@^jKNq}WfHToUcz-23`4fl^jsy3vWI>GDBk5oXDbVhvOdLtsv|Dv*asJK0XamxWm_`BNf#S!D4t-5Zdhf zJG&eiYKYA6R;W>iUlVl}VPs;ew|?1?EwGVI#WOn}Rjsyb{O32L&9b(JOtjxJUb;}a zMt4DX^kON>gYUY=T>Tw6NygGBWUevWg3;;khCY-Rs%!~ewBrzCp5Pj(7hDFNou~-B z$XAOX@$&)CD58b%9^obkZU~ZI$k90iB5Ra5P0#*d|E2cHG8BE~#LaEs5H!>0LhPDaq%0}QN0GITiXks+u2t`G-BNZ)oD0P>^I)?<$eSv zmd*_ANZ(!+B62!iaWSi!kGOjB#3%W$kwc?IP$#f(-^D^#fx-1?I$(-+x|Vqu5|P;^ zI>%tlMG6~!(+LD4KAykz9ytIZCYfW#r&HE-m;b{s4}p(*p#(BhG+5UQd|79Z2TMD4RRLGA35}o=LX8I*n5fR zt=R**_gm~5<2F;E!V-TrZXKrQjC(<3(mq?s#J#fYc9$MHbte|auM(8<7@Jpzr%p~H ze8saZQcKqE$4#r;xcd4uzlvGjyY8u{&IdN;uMp`C+H)V8QIAq&@p%&${5Xv{y_&~! zUWky?{q*b1^7`!6EV*gFi zzbH^EFszyoy33ACoS*iW+vZAwaW$aL?Y)aQNaXUS}Mqlr&R!aKq(9H*q-y!4oUxy4gbHy zw*U7*aobnR7ePw|E{yyYxr9qon_Dc?9;v+vv_taKagjR8a0x92k3VysAv%I@$4N4& zZ16%Tf>I)TQa$s&(u@WQ>*2d!s~fiN(ERGOJqAODH2?Lsam%)4-s~@c$(5v$(@x_H zQD8{$!;A!N=GPy?M+i+hwkP zg6{Iwr;`l&!(YFjte=|zjJs$VGLD3tVRkO?iQjj#6<)o*6HBH=EvTy-_EXa0aqZc& zZ*x7&Zv)CQEaf|zD$3Y@(g4^^eQCVuFW)+!(RB<-ihf2JwSJxt$b8JJQB4wgO3Nnd z5CEZVVE%2kU^B*tD(xP-I5Y}Da;Utrg(u&BRj^+vZRfETXb^KYf;%uUQTsX+bbZ}B zVuEk1tIP7KK>U-bX-NgMAzioeex1AX2aGa*%C1A0d^V+qSx5=b~7s(A-W*tYjoS&~YSEpJg@@ zvnO&=@9j)`?`5+B>z#7qMWUj&ap3;U*b+a&f^*Xp8TeJ?-H5J zTqx9e{Tz984H(wL{k zXv}Im99ECWQRdxLnc7v|ds{+iGLwj8Zv@YJ>+3Xp*v!|m@Vcns+scZV+N+UzB3`EN zHa^h^s7_zG#U@r|m2`R#Xo%1>`?xBPdk55&wvLp{o8>My#+#JY4%PDpp`(-Mo- zPa*p&CQd;<-fj<_I%aPjNjnKn>!>S^61yP|_v2H_4_U0bN=Z~0zo1`q56cMn(7Cxm zFrOf^MzquQyf$QnA?6Fk@)>c6%@qkr*3rIL{J^!?9^D8q4S&n-F0#;b*i|0B-I+>l zx-+Giuh-t6`Cw;ea&Lh5rkz6_tDz3&{Mj|)PoN3aJvH=kxw7Vdl$5+L9C!mckj?Y2d8?2J zrVV{y&mR0!d^<2&JK@BeFJkTRzbA!pV-iTL4g}M0Z?6(3%DxWj#nRM)vMzug8CXRI z@lPF^(Hv|gwl^kx7C`d@Mg#=bV*CF6wmcIVVa>gTz-kQ$imroeyeN!}@U-v8-O-$f zg@8T2Ujue!4Nr+;79F`{rW)Rlud&9)WY)ehvIp?U?fUr^U95`lSuXiP_#9>AjVDS; zx{(Lndx1PcaWTCM_z;KSLWQbrT($&#lFvL(X7)^i@7sNjTru8}W32#tRA{L9zDDxS zn9-wj#J$r>1`(HYve;UMySab9O)5&21u|a*4*TVH2J`+`{crJWtcqk(!>20ZFpUg8 zM!gxYcrISlRBrE~e5ry?;&|idLngeT<=K+W)lpMrX3Ysr4ptIi%%B zCE{h??o(6Nq~L)ScMANoD@j5Rk%c&-9OYd?f|UuNddU!s_!8Y2?|4%>fP*z$4<5Smr! z=HrS##j~u;VTs1lr`F{O3w`O{lRwM+MvWn){DA;T)lc~rz-rSsr3Qz@HtBx9#>@#) z7%cH#qI|D^A3k4((dtnmFQ7{(EB<>MNp%BUU%c>+p})_`ne;rCE_YkSzp*Zn>shPf*i)jVU^|m(sY(VMHD#jWr^S5|hsZnboG5DpXa!$GwfKbo> z>>pO8e}g>LOsVBEW!u>2azECz!Ha~&L3x6BTnzvF2QHoMPRu|NMbu_{U3hhj;p!KZ07ar>3FY!-vJ?se!{^AAPF66A~5Z=^UOB^fsCBeTawmEy` z@?07t!P7VSLAttUQ^ zzmj{2GRB)TQP`an{E{)n=HBhiua>Lrei`UAo&kRjF1v>9WXByr4or$4X!>#ClscC$_b_&f2N~z96-DTc zwD#svK%_nsO#ak`=h5W0)`GmDlv$cj*SGA9TbUQrnC1mm5(1GqgwVWiX?-6t*# zL4>=B&-&YllziAW=Z`RdN1a{8bz_%{aAv5LeSuR3eU z#gE`W4UE!&Hy3BqPhksBnTZf|=t$%V{8L=%Yb5uJfti?PdzCX8VsV*<4bOrW>MRS) zcGoEuvGbEf*c5IE#}R=_>N-8LrVK z9floR0_6Vli9qFauc(oHDpl|W*H-hX5CF_iJ__;pow!AqssdaLpiC16O==qH$#s_} zjy%;e=DvHS`rlXpTaFG9O9tlQ3|j$Sv*jT{nGC#LL@R$tl3>A``rzRw_S>7kTC6Tj zB>BMe){id>dNM7&3w~HXei!8Jplhg|nnTmqYGyZx(amcren+`~8BQ$;*U z!4sXL0PM_OxH$}mXx7|{<`y7%H@eg$;WM3GQi8XA=e0mMQ=U zRffX^KMV>+D&LH`GPJPx*S{CkCb}(RS87I{O>MWHy<8mlBk1SEHi6zZ+b^oiOnH9p zZx=l8J?Wm?$)o(s5)rKr>b7z^NWA*1hng%)plywN(SECu9=`05JdAtI2mJkNKJcN9 zaKh$fR>^9Gl;HqLtK&j7*;~3gE_1ysmsLC1Mf!o*XP5!D!$WU{%`oNB8)SYD?h)<% z#TxnsfRGnP6b(ZC9ibqqnjHc+-?Mp`;2jpHuxs`@Wn-?%@5rg%reBJAKyU;VQ<~N9 z(Fa}Za9p`tWXWL&POu5ohSn9^N9l~^kKDEj^Hv5_iQ6^L3W3E|Z)VROE6fl~PCxTw z0!k)zH@hbx2aF#83s;yE00AkCA-bViNl_L zY>U-8G24^}*Oz*J-jXQ%e5z(MEw%nw zYCU$gWa-L}N85X&TS4}2LeMP_vEtGwmOkevYq(D}fepSmf*2gF^dFr_zfqALG^$3= z*P}6+0loDgtrh%*K0K4upcivzlT=XV;5YBnYSZl>(XSVQ%&s5Xv*=|ymYt4@WtFC{ zRG4^r!mJqgS$P!z-!$JBy<4%#2yb409|2ni05K0HgDg3T`BU`z zzQpA$cr05C5^z(X5Wg3C8#_mz^@o4Xy;kxP*531;p0g6VFr|@n*Hp?c{xU9o3B%H3 zJDq;^_8Zb0dMB6tktt|w4nDdbQ=+!;QwFl4tH$PCS@x6b6*-M>7!pvEpC=8LTGq;f zN~kmVl+zIPUjHpVQyGO|E_ug;1YA4dQxoG;#GP9|JPo1-F_k%ryI1DfEi4wH2~!qs zmPl#Tz9v!d=ab~7SB-~CC9B%}!TF|-+AEDSm)q_(;ZDOkrY7%ga%aCnLJ@#mpr{m`;+Bp6=_vE^=|U4YyWjRlW-l+;E&{ z5vDjc;ogy4Z72T)v)*j;fR8UYM=6wtioTLA8__XFIA zQQR1=gbzjOfM%DZ!~=9cD)&Y^8CKKN7pp~$cO`-)TrLR-SMq%s%@GG;RT6F1_xP16 z`k$fbG4;q-cCJp$AB8H}C?#8I1XqI`-tAe4-o?cs;oZx0B!Q))j7 z*-`GDV7)Z#^eNiyb- z`7Bnh*Wc@g?Hnf-J`Pt=ZN1a`!fVQPNh;Y+Te?u& zT9j=2Ll%RW%!F})&A#^GuJ)^{YVma?etL}-!DwL#w>sDdqQey`;3`s)sZsqFGUYc}HWm)Zgbne9ViP_1uh2pD zzYzM5%(~xXeJ%F3NZi=I_;{9>b`_};__Q?FsFnZI-b`id?`$7n!(7nG_!eTbts%ru zz-ANo@|G5#qb-5MA+CdOhmURxlJRTEH4n5r`iPAG!i(k+EhnF?H>08R$}Q>!5B)O} zQP)R4CD=b^H&y=5f-)STi!gPS``Y7TQjL9|Vg!#@*!H)oCeVuVQ}f3SBs#@wg`2sRy$T6*XhUdX)Fv5 z#JbdZdjp{*G&nSqB{U$;)XeSgM49IDV(hS?LFYZ+Q`e$2S^Vy!3t!_m{B7?R8(k{M zvprxHDQzj$WZhlzSF*&&O}(s6eE+4(8LaH5MzDeY~)4~S-`NEnxUkK)l! z#h1iUX9E;Mw=A5uMckI_9!cuhfWA0To+jHtrn{tyIe?g0$DY@r5BoZlKF|OCUh8uq z?hQ84sL`=Q$#fik-WEt0PIF4PU%HVY>tPWvQAU3s-dRV2i88k(^!IkLa}6NddX zeaTEqCa6&Mm4x1ZeKv}+@#^C$SBoGUQ2958G6&CfmvTO}AJJTwE3ql** zU7n+ueTM{?ik|&!)Xq8AI~gkwy!wdV?VIh?RXQ_n+hqzJ4pDSj?nu-=Fw4%}XJw1X zM69x8fkomT-1l;rb&$ittMCzl3#iopE^MXP@Fl5lyNaCX6zkm#BKjg2u5Ww1Q|MSO z#|P5(l`yYOV8v6dHMc9#lp?+Flw><}4aF z+n%3!JoG8*>BtEsKHOcv*3K#By^I8oQ>?bLm)_8}UiLf3t`%?;jUl3!3Pr_94xy=Y z5N(^si#bO?h9UM=cocB$OVgyVrrKv-bw7%ox$CpA5~<{+55}4=b>Gr{&p|_B5@Iqd z=bRMz^eWO2zqSg4gjph%!C2>MA=vr2euQ&?0mav?-!B-aH5m9OV^z$q zP0ymXxQPMN)BLaT5!tq5;JmaDP$m8R6Iac^$oZ1~<7_zko!xic?(nI1CBl81eumr- zx@H$^(ma*HcX%oPqC7$H5o@k`WoGWz-2@j6)gWlUSK&|MxZOI9N12L$vDlmQ*(={qB_wI+HPgRRv zdvi6qHQ1YrX?;A}tL4U3dc>O41!1h7slt8xS)_PKymzl_XdKt+aoQ(>em0e-ZG|Kt zcw$ktNHG*QVUO|WnZ*#+k`QHgIgReJS!mn3KFD6)! z7YcZYy9(PwZfzA=8tZ)HTeT{$X!;X}Pr~bKtOx9yORYCJ2PuZulvw+OVPC;-UMpZt-th!lL~)5~lQQ-UPRP_Le!W#2M;SiH}XxC}dL z5E{*{oiQLym~p3Z`ZiZ9Dd}w(7AX{TizDlr+)|o^Ao(3WnYWHX_dyWcmf@GxgPDyZ zqDZenLqlszqrb&tOA zP=ub#Wl8-*MF_qPdDu-F+-%4CF4}iu_EnKjwFTwdERH_*CZ%A5dYS-@6%X4f!25jv z9(K(o)KfPqgOlJ92>b2)h`2~nJ>?$RIjD`QP#Z7p++4@snYJ>?#Esmg)y(?CI>M-k z7843S`&mO?&fx=}J>(8GG0f?=&2r;W3u$&cr*y*<6UI&YTdrS`32FqVZy%w7tJ83HK1}C8(@P>YkCRU9QnM+M`7T}@X zx;$ye*)4;P8WSm$@$@1=dE}6UZQi|-wIn=lBrd}tID-@Oor6_zWN@F9RGW2jVaP|G z(a|+1nA9|pr#c5XSo*I@-@zBmTP$t!AVS8iwqo|f7QZpCSXfM%?RumOLVmpY$7__* zmG7wcFZx5;&U?*u4`m$S#c>V4R_u8s2@?yf)QB(Lr`!0$-8Tz@hJLPm9i^X8F;#{! z;|m7cmyn{!gy$lT(%BwTJWGSVb{v|aM-tk!xpJ~yF8Y9iWcy51OR!2t+Mjqh#%FtW ztq%VTC9hdVKfyOhRzz?8JVs3$i>Fsi8qXif<|mG`NwhU_)Tdv2`8U?R!PlTC31Q$l zWTkYNclyB_78dqI@6+0^?~8I&^lblWS>ylScOHCQUqH?81>vD(hXiwXMZWv zA$Uv0&Hw23(>4k`)uHE2VJ;5+>zWNi`6La)vLYkZ5ZF)LO!k1un{jWEYz6e71av)q zeTIXNCc3w zM7qK;W%Tb-qw1XK(Z|KfMCQeF6RAITUixPi!XsvO=@jVtY`gw1oQ#aIHN(j8p?hGK z|HcAZicO&gl4=HhR7y$#sIyc>i(xM|TzU|e2e7H#@c@@*=&OVn$TLv;_@lioG*wcWl+;w1NModN2ha3B2lpd4U$ z>Ox46M*vxDbf~{jsCRW>I?B32+GD!dnD-}S=72d^-6~t0{gr=bQpaiMz?sFbSqn4V zlNrMUvuiJm80ItNtVXQzvJMf3-_XjD|g7*bAUk!mBrk%4k*9PIR}d$7+!N>wX#tVy2!U%d+BDwfHmo} zZGq?gjt}B~vnioNg|`nj(Ycq#EixyY{;VT-hIXpgp{eb~!A&tAQg=uun^N3bbANWn z6Q+3o>+ps3-uI;2Io?-mE1t0Ms9dd{o`A{2HftX4v^(IfgwP`T0d=4u$M ziQ%@O%f<;7HXy@4bGf0T26D~QpYTj~f7j=ZvyE}D^w=pMfVS;}zxp?~xmV>|L9fo^ zzKSwBygC@Q^tShSk7ZFidVXdQwWF9jltXw*Zv#Hdx&GvWw!MP(ViLoaD9`=yV^GOY zfm>wy{C4{bHzp1CzLR$WwLn z>AY$7aSYrH6BH0IlfuXyfz?4l%)3Kq8(j)}hpHLk!`z$G>0!Cm4(4Zz_gjS^MKR0P zCi#A{5`K!p8kz$-c%Lt35$O#waF{w@xiRKzD^xDvVhlOqemq63dt{%b6NMl z+WX3=I-+z-B!S=%AV6>kZo%CH1b4R}7lONc5?m83IKiFZa`B)Scemi~Zc{hiJ^i}p z&0GCu-kLQ(-XB=pOI4k!I_J~9_mQ_FGz1A7C^X#atIQy48$MhNl{482O%$z8M&#Ii z!xr+}4!x~3YLRadWTttT=ol_rq5RJOyHroG!lHN|qx@!XG6gLq#Q3LQtzG!w(|5n^ zb`2KP7w48*{jP;6biu9%Hm8Y?ifWfUO3Fr=w#`2iR=5?!tq**&)G7Q&Kc&6O0Tj3_z;z9u{^kYv?r_lvETw_{X*&2 zEk@%_JH*)iVM>(VLSGEP@Wbu+prPil;6&c=K#OA2$i|u5R=K_P*zt6+OFHpY z{;Mbqs%7&^ zgI*Zh``c5;hWw8ekL@X~Q!hDFW_W_Tdo(1xJtfMfQ@Ni;P$)n1>)lLO!V7a*&{mdl z>jsXVn%m(2z~^wI^(GM#Dik@3cX)#=O{4dlk~V zQ3RWbc|=M+%@l@7aJz>I7M&i;%qZjYxo zbI}tB_|%Ybi4f|mX)_rVtF11UF1ZzLfC$qNQ5x{^f|RH*w^{lqKqpb85eg}Q8KLTc z{>oGZy_KW=8-CXkLWWvj>#hfbOG|YV0eme|$SMaM^Y=g$h)dPS6y0nAKMzDE=iI~$ zSRF7$Z@dzPp-oE5t)bGrEVzF<<{y<`{;R>iFh!Y!!lohgd-?ozq$R4Fz-ZT1_u7$5 z-;Df6Fp(n2v)^l@<#Mb#Q>mW}k@x1c)chl|*KX5f{RWtveWc8RP!mRtMm@`%%Jeory=)N`|=`rbB_!-%3g8tvyqrKYZXl=k8>I)4VlZq7qk;GcQocH z-;A`S3%t6eD9651H^Z;BYoAY6xhD^#3~%{q3k%G|E&D* zz49I7R}kYb-)dzM9XbLoGjO4-rYqXh$Nsuq(S)}=1p^)kQttF=@Niux4^r3|-u9x} zr%4yP_=@9G5d{T1dv*-6Y4gqeH>}E`vqF8`0ji3hd{VmVTNDG4VBTRItSt`|zO zVGgb$ywW@u0Z!mR*ytjL=;!Eo!BPE?^SZBldk>Qf$%nkx7{9B+sTzo@Q1PD@R;Mn? z)54pBOTdZ|!#@ok`XgJOuEkj&ZDs@}kAyvsrWl^w4XohpQQa4|MmC-Sv+gj|3m~~b z;Tirf4_4W|wh^o3vp{M6@C4I8#gt!sqnpl*Zp7YHQBaNX{3e);)RheP1@^14_ug-q#7@aq@8QVcj(1 z3R*qj3-7PYGuZ#i72Se!x+_~L<2Q|cB$(mHt+GQjqpa2*L93fz^d_}u8Mo4Im7glg z&Gu?~sE*AP<8w1Gn{mtN_2g+UZEV`2#YzX~otfW(5k4vm`^OJ6f0TMVD51gOYMXU$ zv`aAJ%Q%g}nW)GgC;75X(D9(s{v79of9&xmS!G&u!Pa(`^&k%oUmc$1+HG;AOKYeT zyt4MJz~p|luF$Y>!p|Epb(Mks+orPi($jPRj>f0NdC2}jZwzEohPIjD8)yONAO*Ua zcUl9lTOK9Uh8*rThhMB2$=>wrGW-gD22nCwF9~E# z+WozYB${>cGi1~N?#5If%xE>-GaRzT&u6CJv~a&F0@@plsP>Hd{TqFiXXx?nOLe2X z9!?$SMY;s9gy>|}dXkX zCUzZn>9U=$r5xl#_!GN+r5AEnPxj;O*3{tz%H#jM(!uwbR_^D>e4q5@c*g@0 zZvJimWVNRs<}^CpY#zQ5P{)ZYrypg{96+tT5m{T>3%(qpyM!S6FfkB&&CfjOh0NKl zOmvt1r0l=9|CtRM?6D*$a8Dd>QRH!x2%#qEVNVu#s&_w1uH-z>k?e@nx?xClIf-$K z4a|Dk?^1-GHeMMYiDzrtJBgB2b$#icY(fT5m|*I;i#mxzWH%sXtB~i?lceK|2nZI$ zC?cTE)1N$UgJ(D0-4}?i^$+b^BcVGPya=9LA1Q22-MLO0ju+3bNPKh8;#;4nvd>n& zEt-pf%L>@&ZbpzAlzubBT2AAtOOh@=uCSacW#HN~k)`DC9&URxh;!^M=#?#P3+QJRtuxfoMD?4sVLZQ;$7a(rHTJBmj(1XL7b>tYA|yb9-S|zZ9>7{!ppcW3KaWr1 zamcDwdhkMs`|*ye4DXqmYjjOw>CPshBNZbjXIp>Ery1VO?&~kZ8k)al&*ufydoH7l zAC$)zA(g*16Yrxe0IRpJ7Fmo0>$%R`u*ZOnanF1FrE;uc5u8^#%z{tCDR`W*E3?)r z83yU!ES&RVO1+rPcsd=%h>LG5zyB;N4GUwBq5fnfR^aQ*26ge<04@Vq%P?xs{u&qj zM_#+j2SJHSaG_UOO&bGBs+vDsXk@0x6olVkAord~eV+|I6MY6See50OM-;ZSo!|!Q zlD={7H#6G}BXgKa4(Z^Vv{)qTA6cB;6IE|q*I;vnv=>WyhNbFr`I+!$CSHiUwL8KA z;SNkeWzpl?AAn7N2HsjSjR=#oSd`q4{{QuQHu!%3W|GEv&@F_w&ZrZDMnTr0lxA6sqBVcg;1W1Phc$5>33hY*RWb|MQUB9 zzbsf?0IE*1W-p7yEeIv7lEn40wgzTFh73vVeR`W~f1+I6yhwDhn^r2DI zx@LJ)frvVwy+HDveb;}oF)iF9h}iq(-)aFnDU;ZrCZ0RtiEdUV0>!0}6 zraYk`-0i1TNDB9lWb08zIr02xh8UYa{C-5gsb6nOcUmYqI=0re7H|uG()55vPmMT zxi+cX)J(nA%C1-i0dI2mO1^fG-OoqN5gi^7=yfndqufkq%Y~M&bTat>lI)pM0Dv6h znN?rq$8_Q(2lD%vWmzB2AOEz5+lWJc&}a%}a6b%ACwd#qJ%i$FyzIa+&csypNhg-R|G^Y$zXu$QonVqnDNC~Yh-u~eQfSJbtYX>!y5$C zyo(O;$x(zfEc7t0sMYvr+D5RGwo@vntKc1+tDTxMUzc2+{S;z2irSL|hjuJK8`nlU zst}j*fIJ3APn=;f1lv{n>Q7NI*~t!t4_-L)B31~&?oqw~s#78tV)!scI6Wka9)9k8 ztl~UHleaVVw`rc78Vr^B`detkxz=(0h{Bg|Cr2Ym(_fthl{u*~)PRRC@DCDBPIlrF z4RsP#iKnCH9hPpRWF($`rP}1JR2U2*oc>4xdMc&*A@xpD6O>v}+HHj`m1V&yzZ5*Q zr>2<@OYI^C6Z{~tIPK-=+}ziv&YTYrD8 z%5{jo5%;21zu#lAGXK6uB*E$K8Kxfi{HxF(uzD_M4d<5tBf73gWIDs_*UnaC+s$y zIQk9TC+E6#1}V1SQCd_JC+^%tekp#nJcv-{e*xtbv?jl#C@06Dj$L zOzr%$Z|>y@qkX^s=5l)&ae+jGIzI2)ye%8>+ECh9Uo)PJL>lE~2k5~CLI7;6hzuj+ zmcg7P-ezeN(lB4(G0Sf5Yf5rrsO8lZtT<+|NCI7CnhgWB`a=2LVHN&lFM0+z}NZRf^u;ro1OFd6c+?O3fHa+NKT6q zffi!irCA9yit$H|`{v!PP1`yKIa_2U0Xd#XM zN16Ly+Y#}5RPvVA%UB+Fmuc(krYRV8A4jun7x^A;5s**7P)DIu0mz2|x@Vc9zorcc z_1-5H6wqpy7%m%RW#!lr*H_La+@v8hRN9D?mw#GAW-{sjx+AozC}DeWWP0~~b+DFs z`+L=GkXB=tnX;OC5@G_={dKZ_v_n-R*DK)c>LR@8*o{ti3L+vRs~}TYZ8szDp z(I7L=U9)_Pon^^`b-OvSs9j4^7;}XTNULf@3BoVqzUA9K4C@QJx{`WkcvR(PjY0FZ z0<@vqPePFLwl`agf9UvitMK$IyW&ZJ;?a(Wc#bR$0nb0CFOet2Ys6Y}-1L|EJKUWe zQg`2VY7q;{?HKXAZfQ#LOVyKq5)1mobgHNiAI?6yw#HiEeekJKE*16&kAM6gPF4veYfWe`|G;z8+%68y=H``;4CqX27h`Uu~ znUh}tA!=+9l5N2{LRzOTek{+wS`=zZ10GzwVtgZ7(U17|4Yaz4<1ZD3e=in2#f9llCH@!R zE@_goL+Gq~IgIH%hC$RM>XU#$7-K&6Mq_{Ba z)TPn=3yJ1^i3TT(xkHjSN<>YXK(URQ@h&J51vn{9GU3IbH>{9jVe=*8F8{w9pcG>-L?m6 zCA{znw!!#J0<=j#cA%7-6w6gn*VhrPJamqQ?%7+XXEFf>Lj4<0y7CK7SE^Y1bMB$n zu}J~G=#aTS{gX11plgl*$Q!Gp98hqhR%Lsi`a@OuEeV!RIS^-12c?KbWDFh?gqR#Bcn^m>WxS?LpDStN!tW5zop2aKF7{(`;2@-G-1E zY<|%J;nb1*SFnBep@jHEzSYz1Z?dCLij%j`_%4?@mN?L5)5T|$$-{VHZ#Gju+D+1t z8=8E_r#4L6=z8^Fp#9M<*%O^>xfpKgRq>f-^=l~_B$!NrCb>X^!Hk!B0x+PQoPCOUMjfIJGC||1vaRj&uFZ6Fls+FWl z`Fy?S6L+x~p&?cx%VnLQ9IZu9*JDa8n1OTO<_@kIg-`t^r=R)Cut{35MIb=k;m|R= zB)q@uoBWzQ0kyRo7_L!1HJL}kyw`B${6ajcI@Ubh;lwgl!SW@~^?Ap^rr!Cjr_}h) zEDtO;6;g=9JYF5caq~;AQXz&H%RcG<1Hu0c_~E797pT@rl%g`o<| z%)^2mTjgE6o9FnK$YpvInqSo3_cJRXaT!5Gu|i2dqp&8aM6_9IxdX@&tWTZI`}cc? z>x>FS(w57HNH9AmhGsd2X;ViiXG5jyQFAQs?;ds3bqkY=3HOOp4A8tD2E`x3i9F+c zd7IS@oswMBbV6+^`4y1hqaK0_wZ(uKOJ?{RN|vckdIt<^8uQ&iyX_!vp7vp0sWkO{u(?Qg%|k|vS=wy6l_s?oWfu489z=N z6a=)-ZX(gL)iM!M=-$@3v7D0XDcki=_V47;z`K-vQsGK( zJmim?|ET*Z|Bklrhv}LPAM=clgMZyH54pFv37&JMP^LidX!D%YMtYugdS;0{8*`|w zj;46SG4gRpgv^WcDBPuNP)&dJuKTmLwk7$P+r%ucZ+8-XZRVMYqo~3vn6fazUP(WZyc#&J+W#M2x#8wdlO z#c(Rj=SkSLwISrV5LK2|YDC4Hp0xS};WiHYu0n_IS~M?NwsvuXy~=&<9$A9thVziZ zP}MQ?@j%Y1OIumT{jsvnMdhI=`Of$VaH>a^^TFN6ztsX#MYNXs-RKV<_!ABohNv3W z=N)g}Pi&YSq^71alk)y>epi6Bdd)R{xC73P<`+Bwm}kw93@D`He#;2T#xd-c0AJq6U_m$F{AHoZ2$zvpvLhir_9qMgC*PQ znG0$<&XQ^hfpxM-iQH=gvW&bZ{@6e^+{5GCbOiP1n~G|?>;C^Csj>$`<74Hgz*(j_qO*)CV3+4dn%@E;bAU#4G~@V z+cfE*#Km~s<;~|Y#g=Yv-sWB%2R`7*do2otHUGSPj4i&kVaI4vPM)bTOiZO~*o}Ep zn$hm(@znn?0PncYdX&evM=~Qk=WHA(e_12V53AbWm@6()n5?K{Mt}`U|75M(J(@$E zwjbyO9!hMP8Ldt`Wtg$rtq-?Lc1t`ma!}~I*C0|e5DO~3f?OIr5Mp=;np=vVc-aB* zIvJauzKX7S^wzb8OtMDdG@$cz0{`X=zR7l~}bI9|UV5 z)0T#S9U=H#T$xg22gKhh($YfXBCecj+N}m)gQ|8)xXE<3*lR~WCk@s?F*9^lm`o)X z%ZZo&O9&0AFwX=6Q)Ol4z~wJmuVT91J$HQl6Xe@sGv|YVOi#+6>E9!FMey@s=Kh+e zAA2K(k1@j`9LOtk4QpQy#HIU605aXQz&*$T@$C5?VZD!3j`g)7^9so?CY|aSX^~gh zzujPACL_E?_Gp3_tMt$czl#IapaPK%KJ%3;Umq{r{O>USyS-l5c?ZyyRt)eM??x|xil06G?se&Owpp1xCN2%rT2`B2Jf;x6 z*faSv7bw@eKHoA7IT4rLODsZs8mO_VOe@S_vV58Tp!`~Di4@CQ`}%g!XLnn-zpNiE zXgYX+aJxhxuEj3TFN@Bz`t&<^(J0#7IknQ!C;#bF8YH1M^nvR@j*S#DZMR6FXv1pK zFLO3}&nF?#&$hHWF6E*^^34Z@!K7Lx`h1qZHaK?Dq5zSALFvWt$_vF@ClO#kD@#Sk z>9C)dX>+A{&e~FyEnmhIe#ptEZ4Q^xEOK${t7lXpejbNuBn{VvgDvPJoBUG-Zg(`x zY3$ScyZQ_&q!*lOO&PQ$-~2r>i=hu*B-aJQ6M+bBtKFB1^ zw^QN>Fu4a$o)8K0Y6P!QQ|k^g45vb6B=j;FO%a9gmbJ?r!Rnp~)7;xx6Zu%1G^ z@ zvLu+URpK)Fm-dIO%=w6!Cv8znd0kSsO-r4-J3;3bpjl;I-_Dw(#=FbZU`8UuDB?XU z9j+#lYe)8gPjs=88WLK*hej`dzUT8YYhN}yyq~Vk@O!jdrHYh72MLcF@PeeC8gvZX z8y(Q^DAoe)Eq=Ri9wU`Te009iD$$cme6pMFqnjr8ovF}`P<2h6*W5=5AL#WfaBbh~ z7A%X9n#)l;2dQ26fI!->wD!wgiWb@ipWpXrooVRRHcENdl*G35p{L4uu9)LNEr!6 z-c$Z#rPm$OYO;fvGf9g1f?sxvNlg@-)RTeYK2Ji{;Fmrh+G723#6gPgK3K z3=6cBoK(YlK(pTecM+darkJ)Ny&A~1>(tjJ=YJ{6G6}&*WW=iqO{_amtXX2 z#+nEsv%7&h3V^?1;)3bfQXG`{&Kk}Hb^0QpNC{PEx8c7g8 zY$elR_VYFBUy0;;2wh$ijQ>&~_U;VDTOjmbXJCNn&YKij)ufBA;smp^VDt;RRwH4*uE!8mS+ z8JWVQBy8NB4xrW+@NzPjYX+ubwco;h<_}f#ilZ1V>9*X^-p59t@_RS)5f^d(9k#MUw99Q^RE4uEq2jtq>KQ>rvsl{v2SapJE#k#k7zm|<_M5^tA9ZjyKhgZIitC%z0 zub7Gi*AGf+;|@u~@v5)i590z2!|9?^CR8BHw7MF3=UWuVy>7U0GVj^PsiM%pkMnWs zx{JL0hEiPRT>}qEBx2Dkk@1SuBkz26BQu%oV^*{v*k$P96nFjg6@EpBY(ahnebKK} z|B1im?PbmDA@ALar-A7NBUt2VlzH*Cv}S2Be{gq3ts#aYWgWiSvNX|c4~-5YW+~%Z zJ7!=z*E6K9QBMr_xp62}(C9M|$WWFNb`e8;{=kz!ttW!%Dtg*}M`Th5J|Rmg{|m%K zg+Faw@=HRNj1M>Uf4Y`xeO%9;c@p!O#i2kf5Or!gd&-kB*ZADyBWpeQm&q<_XbUOD zLJ6i2<5;x!aRV#P&_+CI#v<4_0i}!f`@CF8K@3GpXa=S!^IW&HF);X%(jq*BQ>ESd zj`?nQo9=~&5Y>Kr5J!`d=gQibredFI% zBZ@KTs&EDnIV6uBs10FnWNZ#Lt$PKI+oi`X3d*PdD38db9HtU9)P6Jc#|6fLn+dr> zeF21M%akZv5qxCTNS$-YO<)t}c zJdWnat+O-fctK;azrYHeE&qw20JMP%4&zXMBugRoZTkIbe?yRsGAqweqmH35w8-ol zdMEJve{PxguVg#_uI&Tz4x2Rw{q-mKU)<2d2}T`J5dJ@T=HzgBz`XutPr-l5!sgL( z15#X0NBI?Qz^+VzD0nunJ}xzBry*M2w62`BF8e}XCtzuLg8=5yK8eeV7cXdYao9Whcg2v_%w%XJe7mG8FXUksr)}&6wd&@muTP4i6 z?08oWP=^8_U!s0}@x47N2dH4u&|Gz6tbTYoKWx_SZ~%vD-1dGG4;|gWd#qbL$$^zw z9}o<^Ir*3ZUGM(Lnn1In504oK{9Tb1nObs4?>PGiGW|Ggpm(#~JQ9h4HiI>jhkq9u zd-i}wJW!LHF=^Rz8Z5DuiNzvd zIqPiQRpci;XAa4v37@$tW|CD9VhT_5^BZFRk)Ro!u(*$a_mFKIA zH^80Hnrz@BK$fxx-$^E8UUsufV+c-`{9}t^{%(dy)S1HBdDkCkQ5=T!NiEPco z7{sIZw3dHWV$_GCWT)qr0p<=bcAYrf)Gp zJtIG4A>FZAX<(4-LjF#Rc(}E#+m*|#>zgX6p=U5IN-?>b?e$(MB@e>Pi3-;GYiyjG zM~7X>>>qYqucKd)#0{)8-VL@}rBxr3@uQjLpf3ri-rHPnn&!3{`b}NT_q3RE^&yuy@`)Ra04SUHg*lmM9hbvK0?B&_2)Y zvNI&-rV8RaTe~RKf7FNwtGPl^VCPETKg;_|wO^skaBpHGdPgksRd#S=xUmYD_!J#` zx5mB7{mc$r?(P<%n_;rti1ixU<@m{%SWt-}nRG8h!41ozqbIKKp{b$iJ=l>$4v$HqIPD zu}cuScw~5$6!WfuQna2{wSDEQB9s)wJG)w)VL@{;^9F~EBz?)@yZTs_J|SMeYtiM4 zaI`L7U+~G9@B#PJ7Zt0-i&Fw)%ZS|D^rXk@`89oEA9e||Aa1-wU!IsB)27R<5CxcA z#7wO%4KYO_o2NoPmF;4dE);meP^LMvnuKL@$Z|Rdli%(6iz^m4V$bTUwIM?H9$vB*i(5NGJZr}?ewD5tfIoaWV{33Ph$GwNd zeZdeZ?UaH9fB^bEVlp_QWccJuMQpeBu(_=II5+H8i|Lp8`YaE80lOjkTWB(!;fe!v z_6%h6%|Y6EqVYAPD|T4GW93*=pH!HRg!IkcabD?c_g?9uJi9kxU*N~=x38;6y&7|C zyN(NJy*Wm&lC*z=tNc>3OJ`kbg;ug7NpPVi)ZZ%;d63}!tujw2cvWca=sB90#lqIn z8jAb&SdyA{JRVg-vMtUb!u)WqoT|obDto5E1+8KNCI!_+*vCZnM;hA`LlcARyJU2S z?BL)=ok0Nwp%iKvVY&C@d$-!7mEtQq=^BIwsL`o{H|oPQlj#AmhDg80J7O%Z4idMz zV%acKg>TXr4QE72U4G*EZJ*W!Fbxv=UQal-l@EC{Jbilxyn1?__&owzDM;MHQ-S;) zg!5CJ`s~=U@%nN&es~68)!w|-KUS@V9l{yydO#uOciiuiJ1of(arE$*O650SSRyb5SR$#w z?K!a1wr5R6>xGFQ?QW*xrT0cZRbG04)FX?#cQ78p<8qk#F6`6DE$d&NBoO0MHP>kv z99><-KVhIQ$C%#@ZLeiZM$V+%UDx3Kn+hf?J>GG{KU6Tom*og7^`*AZJ9AIh z4lRgz%(n#wsXYzJ^u zK^Z*{YTVje@+Um+OW)UEv%y^yJmxvZktUBSK8%TPU5{7u# z#KQ(YfuUdUfqvu9{9nxhpso2qN`xcw{Fe2vtc8CCK=3aNqA)pYqRcE83M_a9*XR(2FukRgZtEJJx+ja zk;7I>|Lv0&2H#jC&Z*sW`bhP31f`|23r@_%EAoL~VH2?pZ(_pD?K?|E07?`Pc4({` z{heYk0cAr#nGs{EaZ9a@v>!DcQT} zz-}nvDa+DCX@|u10{PpI4wjBF(wXK%eG!HVSAQ0Jw$}*z3MP+vS5X~!4(U(B#|aLS z7s*qoZLeqTtyfT9>G)V2-T?^IU60s8bm~T~>j8<&)3^n;Hfd)aGQ5e5MQq)^U?AvG zzoA^Ue5rKxP1viOtI9L^^LYGHP(rNhux6B-sa?dqUva2c+_$f>>*U`qeZQtZb{SA1 zRVU*RWH`wHH!A{CRb3zq^7p`k?P6Nk@6#~$Q~6f1ibAt@AfK{P;(DfObtexR{obMb zTb>GcO8z>*1~1-0kcsBG+r(5FA03L0qWG926U{RWr3{{jF$ou|_lifD#guxtG7J-j z{9xnMm%vr*G1Q=CLr$x_`wHO&4o_j9JsD7rLUDd_*ccC+I5n*5UGo%sreKoe#999@ z`Drq~N+$Cmkz(l|GG|YZCv(-$^795KN_B?)FRMWQbea3h#qwnDh3gZc5t2Woz*0}A zI=hu`iN?YslaE1@Fwf4ylO+rUSHYBLq9gxuJ4Z07G4Yt0nikDUg1SuQ$3eC zxXk2OR}rg&yQjP?jqQZ&nrxSBomoK=@rL)puqd_fW}_LwR{p5IZ7R-R0foXTis&|~ z)t}R4?ERMw^Xf$4gl&S#e@Arx!9wLpSYZIGSLxty1(L|>z?hhrPY}6O9%bEvB>;X2 z9H&Iq0sQ>eY9jyI&IvLQd`_poVWf-dld~#gfjyoOD@*JCP=VT%&J8RAu(F%qX|q2Y z2!8=JG$QeuH^2NFPKkkP3hNPW`yuoZiD(zx8@0V6K;`NzmNodS8F{Xzp;~?T>C3Tl zibQK!_koOgN02f$h1Z{g#gd}X>v^MynV+{MAVZsM*`t#0`W;Z6)oxDFx$Hm82Qd=u zk6mAQ8mK?!pOx?qk@kN6=z~tz7k zllEK&a=N&ynW)U)CLcmZA^2~!Lk&}Y(q~$&@6TL+O*f!mcifug@>v&0+C1ykoS7H$ zGA|irX|rdWqQ!7!V;>nN7do^iJQ{^Qi~?t1DDUO_hN2AdtNW`hx030;i2rF>(pcMX z^I7GH|M03KF|*9M@&@!Q?M?D(S(Ww#-{yhL)6)Rpc45iqs?n9Tji1Le(yO45$YE>Z zkAm;%{8qmT*=Y3SLD(buPmxa*^{GAwoK;*;5lcd;Uzw+c$-lnLMbLfjgJ)BFG3~9} zjIc#8HvS`aF}40#wpG@)Af&jr!yqL(ZNM3egcx;HHF7w2~{K zuXoTgOOUHS&+dEh+T~EhY`y4Rd7UPzlOQHZDc2=oDoXvri1zG2`CCbe?s<=ftwYJT zJo8;gocdhn>&f;tIqi$$wy44sVX8Cz;0tbk%HM(BuEFSOmFwC_ntx%jNvi*Z^0%zC zAkS>PqsAmM-+c;%VxszXTiH5=DaL#>GTT+T#VI)TrW^-EBx>C7MhQ(O}I z*T0f6|7+_HI2%mb|8XVY9}g_bqm|;UJql^^xl?d-tQ-v1t^TN7XPf5h10Q&;54}=> z7LZDszWg|nxe9En#r)U&yv$A|3@}U}sei2c^v1BbUpW zZi6I+8rbYTRE9p6QvU%Zs^@LwEY4v;^_p440O$wQ%8RJ00{QykLJKw~rjk2>HqZ-> z4=6e5>FGL5no!Z-d-V#Bu>9eRYriVBoGlN}um1U-elPC`Ox86(D~7auM+%y`D}mxn zhTGhpN1!)?jdMRt?%Iv@n>TJIMGZ$D;1gtAQT_mTqXhY7XKn0`vWEw%~hHY+58VH(rI}9t`&7k!L?}x zo=qDeHsG3vNsejkO482Bq@>)-O)4Ln1pq&i_!`SwJaCTv?J~#ZR&NAs;%S@(FP*!u z_mlUq${+#??+k%vd1y5L0hJ7u(5}DHK$wTjr0~jfXqXJihUm-Fy7Z(v@UuzGT5-}j zs}ut+8BYLK1PS+RtWIAlS=AR*+|5zZybJ;<>w`Chcg;=ki8N`3OR`K{_PUKqV5}xY zTlqSi?+$OE0^S+v{lg3%;=IW_qh5WJ!u4(%rp_Ksv(T4{TI4tE&(_0DJ37T+(1`)&W{ z6y6@}rFdDYdQUQdh7IWWj5}fu_sD#aH|CF6@k`8o@8xt{+JRreaTOw=29DvPuR<_I z9YyJ&ULeT*rsoS;d3%-KRZzGxz@cI60p!G!su*;6HI6K%=An*_yhs7RcY)V(G(pYA zW3-(wKbbzfq<&MxM+<-N!cJlcJBoDRbYdYrb}C>VCPL$P8Ot;_s$6Nb(x z^AD!(?p38Id^!Lwla4YZxo`7+W9v*Hb|*``Yr?{J#$Xmc^W9e&fu>$|ce(w6Z8?^+ z>&L$#xXK18LU!Nw^vJ#=Vn=Z1N&0wPsmKYTs5t4MoDryNvsyGA_4wqL)cpjec@f%+EQ+}$s*!9rN%J`+Y_vYiD#Td3?L69&%$3?m~`op;b!HRLYS zv8Izq{aTJH`uy6fVp&L&k)N(=On7##9k=pq z!`0u>lG^MztW0DXZOw-=645i)NZseuA-m})UMp!IUU znRz|9o19h-j7#MC_^)TiHLI;#jz0zvV{jkh#W?m_IS;p;?J^u{S*UUxULQw2`5Oog zikYlUrav#HgWcKLQNa5J%!rg*BGX{;Yt;l>SSlArJP$t7AwL&o61Hi&NbL60-tlHc z+r>2YPqaJdRLT}XNQFdjp9RdyWN^ebVW9}6JjFv4*ak2BY*!@B43W=HmXINN2O}N|dwNpLO_XI*XN3GNS^X8rkoQ4Dd zuyVe={M!WelY!y$qjYzNCcZ>&AKhzTEcQpVlc*7y{T%u;P@(6?lXxHi3?wHT;pErVKYz`uRg#X$ zA#;;dILO3x?Vg*8yOeIpB09xQM?xINJJ;iL{7RneCd0y;rCuvds>VF5) z+02!wk?!8SL(aD&9AS7vmp`8AKaZMXXSmW&9KxXU&-(=Yv)0uQqF-&7#9%@|$>CZ) zBs(jsOdmW4m|s{NLL=bvYMKCl9^x=0(%KR*@AeK7^t29l$hwdv5#H#s*J~_x|3?5x zt;8`+B)bol~WT(Bjl=XSR5T4t(1fEO<{`2xw*Mh=2JMW%(SWhpbjTvqNAgmn|;zF zBMG@LFE49Lrl8Ib(D=UtcIBJYG!*o{ws&+qjTU~q9udc=`8fJJ$vpk*JQL`jDW3n! z<^akkDD3|zdh)Lr_LL}S*fOc)?0lZYZhmU#cYo3Y?3G##`cpqpLi4#hsFScY|ASCe zLUY+3`O$mu=e(J(Pft%B9US_qlx>0W760W!PtTp)M9aw{kam2(W4Ozo(kqOT;AE-p z)vS3&O$h|l(A5Zo9Rgg`-*T^yCSw2sX>{IE8PAj7a7>p%f-V*jm&^OJVZ8IdGQ0mN s>Dhe(j(vKX5DK3L{ucq7ionBQGN}_irzC5S-v{!978Py9al7cXx+i!QI^nu0d9CcXxMpSg_!b?6c3qz2AAc z|HFNn{q^kXuCCS^qdHt(RvZZ)4;}yjAW2GyC;|Y`&;S7B87$;`&X;V`K>*+rKvG0d z**)`g4MBVC;qBvPOzQ@_Xv$1f-7cOEb%H^nV|c6e`A<-kdMyQglLn(C{$0sSa%+?Y zzcqQps!6brX(Eks3BF0fNX?TU-^Rs*Huu`B@&cDPb8fd70iO(VW|Gol+pAi2g!Y~5=BNHd|9 z0{yc2II+K+bP)q_?@#6ajlm{cS$bLkb`0?pFyrv}iX#IT2j~t`FX~Phh+3cCLk~UX zuvk7EQ+?y{k@}CoU0J$*t|Y!N=pjq^?fJR7+e>W%d%a~fkxoI`{MXy9ukC$W&qTy) zV*dbNM-EjC!GFXWOVe3Np;q`bN|25F2B!IM_wkYN(mOd6TLUG5c~2?6V51EzQ0Th# zVkHFw>D=btNd9O8_QdPH9h>=dWrzd!XUK!(Z$6&EpQJB%SNKY~vD^Pv>`$x0*7XM; z34g_>iHrZ~G>Uiuf4O7D_V7`RVSW4DLsEhlB)6k#Q%O1g#9*jWaZeZpYB^SSDQNp- zei5|QZ4-w5H_iPOcP}zLsF`Ja>z{UQE>OObZ_AscVEuaN$LC63 zGaq<+DhTB?+ey3tJv`mypSFe2s4Y96%sFgR-L03(m4C=W&y53|ftp$CcctNd<&zvy6Wm<_Cs|S3OaBIi$5T%FjBj;>!r%&CZKrzw#Fe8dVbhl zsh4K^&OjX>_^7jf|Ku?e+Gwpy^6+PRZvdfXESjjRzA{{OldiD)IzRaX;aCAa7Ze!0 zrUwo$KwXH&x9+jDgmX}v#@hHLd9CHm_B0iKZqF}*>>^vVV-IRY6*Mh>i($NP%6q%w zh%eKGePkO3Z3#E$DsTNrvKu`Q$GrIc0QcK7Tse)n$;JhrxljhbFR;Vw;Tol{S2mL; zO*w=rhPb4p#ALlgx>;s)baad%o!zRqm6q^>dM!}CC68YqBd7n=CU&ptY9-h!H1)eO zQLBCqe_Hx@XorWQ@KeMdHDdd4Z-ylx(Ra#p-NFkKJn3Xc5(9?#;4CD$D&uMzX` ziq~SVerH*~f*{dUGZJk8g}aXXwGOT^b*=M#xs zEU{S^c+-DXVzZDIiFaZ1u~`uOfxJQBN}@t;f$%Jau+^r7`jOy}BkiR^;Yzgh&mDyf zXV9hpfjTifB3Qm>>+8Gz(_)61nfZNSxGZmR6ggSZzWLrta62Ik@SUQXt&in$d_*KrHsleo4$=Jv zW?lTWQ2#r~`~0N1;8%2hFn+{zbK|t~vtr9>o1|}3tHX1cnSB_9f;VmXx$uU|?Tx(J zeD{Qgu#k}6#!kp}D;Qk;b>e&SBR7xcYosOXWW&a86StWTH(LRiWNljWOFHLch64Sj zX+YQYY&_Kg!bKtOZ53)9U`p%@i5e6c#Cpa)q4aO*&e;}vs6C^bV=I^lafJ9zVR~U< z+gFE6a$L7v-PQ11zFPLlc4GlIR~!K z{I0c6q1O!7BtbB3wxDw$(_0a~5Rd)0{mrSCZ!s}us5aaDHkdnfO*1@j53OnXbcn}; zO%^3Af!_{^*K&l+P{j5aHv~KnFZg|fd>V1Aa1g%NEcsQoIvv*jWFkPz^iO)fOQg!#j@g~j2*!GbJfOde5_Zl7ZjwRP(c{Z=n64`m|W zvEwT}q5k5%>26}iZGAWrd8#9gfF&_k-4v`rp&*S6^~r&rHwO=M`qIf-&p~VO;B+V* zYdb5Pq-syeQT@5EGW-ykMgyD&FoMtMYszZLQYkc|WVa3-nx{8WxqCaz`0Go?;G+5! zp>~ZP7Z0mDmRI=$1t(oMurPH8blzpH2L%qKW`vRGbnodUiUTBu|H$&VeJeQiYYwO{ z;r0L@YIkXG^BQhs=3*4J238Up=>#o>M$hT1Ifjj@$igcYLiJ5FUl}eOkf{28biIOl zI_i$r4p-3l=x4X-rNv>Fuj7XYuwMS1lzbWIUsiV11_s4R6*xh%q8+X_()rapEi{`` zk?=Ce=qi2&S;)tV@+;pa@o|Qb!W6+l_Z_x*U)L1;h;H*fk9$K*@gcLmy;FET-Q(+0 zf7Yl&3(C{`fowvomsW@7%9mnu=-|Y(UPH(nxw14rZ%Oy&K<=fqx4(~i?i8Md6F0`P zOSBj}X>@!!@*L7+Z7M@|@1EBmm(6u`dp5+Lb@VnO%TQ?P*2K|h=X)^h!d&VN;VXv? zb?rmyQ)DH~o`V$x5gXUey?YiL%bz$6o(Ktj5q!hlRp}mvm{9nn{%MOP~-Tw@dof)#wHA*)HhE-7}Em(*FBsEtxovnMa3> zg=7P@T6)Qbk+KK3XNdR`H9{norQ=wgU821fKBKZktH_IxP%FxLU)M_szs>Q%Zr*A~h{@dRnn{1PePKT_rmJ{jQZnA zu}J0G47}rMb$lYtkr|WNi1p;;JE4Ht-ok`U>2iCCkAoSPCT)$B2y{N;5zsZIB>qB8 zPEq9S{4jePuM6@3zFx}b)tz6WY6JAGEB#`v()b6Uo>@MQX7=06RNJ6!?VQmOO9gYu z_OvHR1*SRR;$nNi!S(jntD8mUe7YpZZy`E>zZgjx@t_M`I z(S4naeGC_rHG9zx8tY*?bKEWn6yC>M8Cph-KgV}c$u<|^2Pg*3$EQ}L{9Mw_yGnq{ z8vd5VZgb0SvvmA*hYSz!eeXK3P(y>Vn9@04fYaeH>HQ=dnSdKiOHI$_b}Am#_wQ5> zAzW*XOW8Y3$n?RhQH#L0a=S-y*tZ;lAvAx;D!Q^4M!a=;gi(kLv^EX7CPrOV6Q}|{ z8}cg}rps$QUixRtI%ZeZp0;v)nI221gFs!L`_bPM`y*PF^=$9FPtPFB#3EGttDs2k+aI)Qt9Vr3}jIXpApzb1G&19~2 z=jWUE_nm-*foNWj>t3UMdIoXG;15tA3P-ybII{w5BT$qy?BegmhoZ>!JZP1~JKwCi zMwh&%+uf9jQ^f60N7IJ?`s%C^7X9R94mlEmUb*xwu`} zY4*`LkL}?IbrI4zza(?h;74~@2adusOymO-Wmm951FUB7}V20MGOpxf7qeid_H zXz%Q!Co+C6Or%#HABn#<=lQbM<22Cpi{+I?={XKyVzc3}(-pj$mxNzR*EG}te)=yK z5KEWVjt5FA;A^IOCcvh|m0J9&21{D5ia>Ucn>RBbm?>kmp6qga_w#V0E;6@_QFD5< z6g?%)2vm#;o~OVx+|h3fT6ENN8G4RJE0};A!Il;8|XEE^7*9r(D)_k zPcWh<`dwX;S>O?q+S9Y{A>W-a2l5|=aGOaoW_^`kO%fDq!SO!vGk*#k_uY$yui0j$|THrNV8VJ|H37e6aGoNKkJwn+Tp&i=4+}`fbf!o&pE#R?o23#L+#f`uGgNIxkIZbSLA(U%!M33300rY zmaSlR;^&q#czLMoZJWizo#5KkYApFQ?cfP|I6iaqo%ryPuc*XO4QblGR^cEkbm`&Q zJ>^EbMzCBnX&iCZtDrS+sNj@4{T#iD;r4VQ5co!KZmO>cbU;^(R56CA6;S8=WAlc} zY>8KP=)zB!Ldyk8SGUZjFXcUK?mjSnf?s0&nlWGUHYtd0XUneP_eWrbnQz9XJuop^j3^f8<2-?R4!qqnsLLD%g;`yUCN#9}RxB$nVmj&n-}u;P5#hUp}BW>s_!90 zMSO|7Cz-g_Jp{p%)|6R=f3CGx!HuJCc16uKFD&mEA#5%q9&5MgvP1^9ic!<35r?(M zxMZTwM_5L@RK^ms$QHI^!S#A`e9xvl=;4}srm&h$ihaktuNAayw44Vq8>@sdUd$;Vbd zvezd%UVD~5mwlqe;X-a8U<)#z3DN@UfiLQ8iJ6k$#sySVR8>3mNRq*-0Q?dTgoEah z@s+7|Q-OK%Eb33b_Ws;A%&Oe6s*RUw=8I?dH9ckBI$a z^bxC<5)OT&NfmGoDMX%8{9-Wj;>Ko$8qSs16uH(kmnu0GCX65E%Ny`h$cwR{1sV=H z_Vbb67N0v=I)QPzWL#n3-TRPWq`b74bqDxc3pG$6B&dU6NwQ@)=Q5$c_;f`zZn)y{ zT1G{aMWF3b2SaZ-;zMuuLWg2_J`2g))YrQ;JyFb$P_uIv$!{(PG52I8c=xB(+P*sj zZzDkO`NOO(yCxs*=hZ3x89V3fYm1Yrdl9P$W?LN)kOHfRl8wVw&2&#(qj@I3dBqz| zzW)LdLrL`u2~eoiGDkP=Mrl|M3HG_X5>u6z5nFcEAWrPcIajI#4gw(Hpn`)~GXnqN zN=??V;I$eo9)gMph&AI}X#Swb?FBwEx~J>4Yp7+n&)E7>sD%0YXfLC5hY)6g&2RH| zpV8JSz49i^;*-FE2OCN~{M~P)oeT->@LtQ#O$IhqB{8XZPCqv!T+~TeX}OQd47`t~ z^c$g!t4@!EsC4UN&{HOH6>!}5tAG63lZLzhAxg|4Qv{Fe;%!FQitx>Je<0GL%4 zOK{ao7XjqLSQsx3k+mdzM2#uW!QJF-e-;;%VkXRZJ6KZc42FSX__?An0SdI`rvtHU zooJXsi8m3xKundVFcD8XxxZG9Mj+v_RUKx&1F2!LuG7Jm(4+6jbZiqN!}9d04M@B- zSPuPnqZ?fgrHHg9j@27_i7s#B44_mr8)Ma~d_@f35B)C)E3{5BSG2Z?qKVU(^y+!o zKYr&|yz#}EpmdIa)SpX#;uKC3g#Q<;HD{73C3o4I&Iimvus{LW!2%y1kZ|viYs9Oz9v2hWWqP z{&Ej4iZiBT#2C8^72@g5)+!wP9}hGC6ojG>VS<;W)M5bsgZifL#c~Z_pAPyG#6{(Z zusf&+=8i2pVUY0IZ07SVZm{l7jKV$%IRBTrP`=ZVfqj<&04y>7|Kd0P4@tr{I4(i< zPZ<(EBxh$Wbbt*leGAjqMv}jo@;xN~QT6{5@lpo!PAt9KrvERE5)(#hEa!m6Rbq?iNI=|0L zm@?#lW!KrU1j>+VGhSJaH>Y3 z!}IR%!1=Qqm;!RSUY@s~fO}!k)S995FW73TfBB~~;V%Gr!griAMD^tF^l67IgcD?N zqFMDCKr&ddw0sB4!5p03RKtfieZoi*V=CE_0mHLQ)tSK)#jA;$e-Z63SP|C`V>}pX z^WLjX58^oS|6ta5oH2rzF=}4Lk-dHfy;@-G#ysKqLs2~+1i+um-zPrlo!?X}^X2Jk=+4&V78w6I$P)@nZ7}grt^#_=L}Nn$HrbSp zht-hg&O4_A#Yp}Gv9P(1wD_Yl zAG`tt})zF?o@k-c95cq9h9R8_S>fX*xSSHvutZ7OF>+kmSfEI)SFGR?e@=xu6E z-2ime9ePFQ)0I>V+?pg(?A;AhCq5CJh0w54vbC?0S3?WhF_c_Lb@6qwG%ILNS;2`fs4Tx!@Jd4DMl!1w6pN zyf`zhfVp`5$4l!4)6-5+RpmqMYDOOXaum(Ar`44L<`5&n8xP zl(e5>yul5Jw|>??&r+EhXK)RCbz7rV2(FYtE!fjD3Wo^l{3biv(Nv9ZB;j~Jn)WGM z7Ubt+T}ud2Dx{$i@*9~~`jl^vP%n=4Yq0BPOV#?sknj?;(0M>kxS2#L-^ScZ`5=wd z5}v`Whj|S|Go*pAw%ojeQksYUp2$X+UAibFCAu)y%H@jm&QGB8IJ};(_6FIb^f|S{ zQ14oLJ9F7>Ky+NNa!Er*CnLv5c_LnUe7m|3;nt1{$(sEkOMb)p<|W%!!TjX5PGm-$ zMIFslW`mN&X}wDWyBN1)kw>p(OTSvMmlrG(!9uHE?ax7@R(yOWStO+FKA={+kA~1P zE1$^EusSTz`G@Bqv`5EtnY!7} z#6-B#3xHvak`KoT?b=v-o|Um}k?OjAIfNyz=komyX+lB|UM^ zN7N|lbsuqVYp>;g#%};RP)gn)uMr#Yo=+t zKMglKa@i@^m|9|$&e^wK^9{d}KaQ7w!;zOmA6`1)q!F5b^5d{?QwcuZzY-OWnTaPu z@GMh9XO2$PLLk?p=MjXdT%ENj(9DjPijS|m8Tm1-;~oLBJm?|TF&FuwzZXEkt3o`o zNp``&)yKB`wBsj^SI(NC=!7j_($z_N=_6TcqmO&~ zWGPV~{GszWlYolM$w?;R(DL1gc3SZGnv3C*gojK9;U*S3Lb!k#*v~@i6kcfcc8{zZ z^gy`yhG&qUOD^Q>aDULi&3b>hAZoQhR_9Q&R?D^@-&RcWOI@PO=%t+3@t*Q)EeG`j zH&P162f$=G_puKMOKcT8>eiDq3tb-LfZV$+fSL*;YkD;si!sY>i&;ytTa`KR-@ zxweviX_%t(rOVO>M;kqvLzU@Z+YmWFyGi*aVo?swEUAe$V{Fxq5=91H>v&5vh5r38 z^q1s-gh)>AfF3iaV4s+pf@b1fWk+)P>i}b_GSDaU#O+opwRe;^()c1y3l*JjJ%fxp zm>z9&!ejiL0CKta+7CX4QICki&!t5=GTH7xnt!BEO8QlqNE2aG%aZvJc)a2QHjsk+&y2kirG?<=!QSLEw z@ah(&*?Pp>G$tMk@;1{z5~{Kue^D`bol*0}x!wgoH>06P{OGJpM^l*^MJ^fpyv2aK zPzb>T;a;2?D9N>4-iJ@oG1CHF4}Oj}p6gB_6iEIA5STL1G#%mmrgP> z-z@3K!hn`5-i)q63kTDF1{>8Q3f753g=K$Hu+)?nCqGv=pA8u%Fn_x1xkgs2h`xZi z3YnaqtXK=CqY-q}zi;Cy#_$&h>B6ol+ByXuOfrForuRDeye;~a+MPpDNKrfaJnGn& zGV-)9SfR|`CWfvtg*tIFth!4+00bzg4=(M6A9=If7-R66=#=Rf*_Z z311(L>Ulqk>y; z4w?L)L<)Nu~ZXNv?Mi}^NNg+q1nQdqM-hdFVTn~ zeLskYrf32*zkfM0QWQkWU?p>r`}G=P^g5j{aR&jIEI$3&u)JX7{)_hMr^St>&<`@Q zCa)%J+r(Ijgan7?QP=M>Nj>?xK^sMW8^5z@c46;`XDb$oIsDU$7T z)wIVloz!M77)^{x6iV5ZWVxm@At00b8OH@0TAcqneY9dDDOg2Uf8wA9qAzuvk|mTX zg)X}2wIxtiQU|dw)A;e)9s{$3U?zcb1aO;9!7x@tKTECvyysu}@HicIy=$TNPM5s0 z7m>~8Coqe+KZu>|F@ zM4^D(pX>Nniw@0Hwo9NQD#U4v^Y5(MXXR7QcAkhCC%2~!-Zd=!WK)IPlAA1T#Mgo% z&3HVUO#m!vNuR`taqBT>31#A*>a*=yMCuo6A>Ay!U=sX&{zm)QZH0y$wkS~=-V%>SeoR;C zCq`}g(t8&BcRG!7`Kliu=@on+8giWQd#IavQEvrXY!a)s3C z(*lmvh<76gPW5XB+-14t9;D&>A9up*dKS&bujY$2r~!`ezT#4lSF0zQ(sg;cjcG0| z+yd0u8|t*z&Q#J6ovXqzKnY&!lmbo>x>&_qT-qa*0M{a>#=t z98IB*_9#kX;5gi*#O6gwjLWj}af+4Y(dWKn^v!MS=6JzQz^geX^3~Ix z>7g(309tA?S5O#_Y3krT2%r$n@*o|eRHerBZ{b|i@+7Ojoq11Q{}%}OzsE3^C+lir z0BB90-LVywWR^Wr-{tzfXU%Lb~ATzIWe$xO9|l^@s<`03h~UJD9)GkAOz)52i4$9#;f$ zfKQA>|0kCK>(gH^81O#}*KEK+-0kz;X{OQeC_sznV0_uvCz{G0$w;pJIYjlhdO*HH zfF22>%4?TO9u>Svvoi?9Xixg@k9?4d%9lURIK-P%I!8?_W`f5m>Z2qsD!(XAJwnxd zGtRKSHGceS06?j&I?F#15=9kGmR!pB{e9E=Aopm#E^s#r!L4Z8&yDeF<>?-pouDdF zd)9znEnz;K<#=JgcZlB^YD(mG#}q)5m5Cy3vH3ABt>!J;@t% ze)6a4U*kJHspLv%v;5olh_M-;;;PHaOrAWBZwnZ-*|tY8%_zKR|0zyeN*ffQJU?>K zWSph1GEWqMw#S+`lCkI-Thn1|^3ymDf6d!I@W-bBrO30_O%eJlB*e*so}VhAQNg-* z#A;z~@+1iO*M`XPF_3}5ub{3GTW=P?Z_pW-?Z0by z9fVvp9K#hoNHiF;!59}kaE$O*d3P7h@94Cl+ZOD5ZIBka(Z!MejusNmih*OaHK!zs z6cH1SEk0Pg{iY}{SgNeAG?*z*!uW{k<2D1ys?7tp0LGz+AByRUNq_4C3~#Y9k`(u> zpva0KbiB2v{xSjo^v}QEJDdq2w4VLAtPxByO1srD=m#*gsehI4eJ5*mutS`u>>JWDZ{dU84-5tLDc`IRAY40h8 zLiswt&49BNwd{7&p5-RfXPP2BG+@1`72`~AQnRL<;o z?ub7@N3zPDTWLP3a?)(_Uu^9e($SVutat^l?;l7!-t68aCK4q!@;>$3Z9z3jx``Ca zM>*JrE4ioWXbngXSH==@+T1p#0b#zyFEcS&YRD&hUD8`AqVK-LWN~cd#_34!tJ}YB zj&4wv!ECNO@EMEn3h5a%TS!bH(dHm2-t59jH{%xFI~&+ebkKQt<`ulraJycA1_d{j zk4K1DqkHXpuBifo;?v`q^;gFHi!YLJNosjFQywASKDenp33XtcckKK)>(#tt3*Nvj zU8=HPa~EBl%_R6x^zC7fyTfNF{ZVhi(XYLV;peQaY?6^c`zN?bVYw$gnvV%TR}>A& z<>x!A16|_K-($t_2!!5Rr2QB-wlkLp|19Y}!jALS%yNGYf1d@3N}kKVeE2UsK(`jH zmM6yv1z%GVr5$&W_m56uHTUlrNq*Eh=U4(IUArivh7{!Uo%BYzxy)r=7Ookaccy>2 zkkTC_8Jl3bI;V`msC$iney=lkKgaKt*XMe7udtBx{QRERYpz1AdiEHZm+PD#XU~)@ zgJ4ivf`lCzBwjD^D^NeTcxTk&JKle>07e36OgF~;u%ZW|u?|0(R^g?$46ftlO19k@ z5Ah=gJkinLyf)6TT0=HRJ!Y?y9qi5;EW4ZSlNsIyZAcgPNkh5%zNtAAknrS@Erc=A z=Y+H?YFUcu^7nE|{XbU8Ujl~fFDkyU&$ptwkSu8Oa))K??KWYw3a?^Nsm)IX$-U-L ze2fY`d5;dv=U%;#HbZNqKcog$W{m~op(k}z7XR>EE+^)fbHT<>LhEB(4dC(AUUqNy zk8YVX!acoNO3iOlOMVuqi{r*(a86*<8K)hY(33!~>lnOfnOkwAYp&joo3C5adOsaZ zQHc3oEC!5) zUd)Le?!*sjpO~2Y7Hrwx!C}Lm&k)Jny?t;E&wQTYY4k=1hb12a{dn6FDUL^-e(%(u zEYzbhN~qr|0U3zvY@dip4U8w2+~Md5p2tb5m=`v&g)x;r(SImiOSuSkp_xDnIUN)w zwF&ZDqm0p{t+D%n^Otjk)!g#QJQ+iH$?|YAp&0~zjQmuNP+&(}x&C32A2YeDPs3#_UZTN6x z&o?R&y*O;Cx+V1v>6WgXuea8_y2J`!tlRed;ILcQcB0T~hImf7kajHIVDnH=pdnm4 za+?=j$~yImaEA`H*VA?W9`TmFXRA&5+C>VZO?a<@Nso`JMO4%Yn}?@A1_(psF!~}r z|KNIi-Nb|DB-_Gvsl`WY?~E_9PBa~J5+yylOzY~qVuraOo^nfQC<%kKr)9Cy7vIh- z2_#Jx_pb1b6Zi@`j|>tPqB_1nxz5XBYYomAqo>#B@HrxGYWk)m6=_rAfj7Wl7UFEo zD@TjJHi>)KPqnSrA|lQl5mo7SyBT_aM@ZldU#EN^9(mE7gqY&8ij*1Z{En2x(s9R9 zEtqf|S`^4}Enj=#;-6siCr1S_Ot~Bm`IsJOq{dFGuegnJcS1_PvkSSFwRRO4T=4zH z9|rvuhIc?i*mj3>Qs#@f8O{oicy)xIZyzlP
  • 1Ki?ZT!G>tG7B0Wj5y#Zac{$Bn z%J(0ME$={rNmNhzCrc?-JRdtr-N^`P7d0jlk|YyaUF&+lwDDV z9W$uJS_SW>Sbty=0c!U5AP5Tx)c(^1!(a?C%xlt5ol7m!h2Q(!6le?_I?OwTkA5By8Kea?(V+r`v3sbL>lj zo6CQ7fYP~=xI7fJklTNRLyRH{tWoTq$2h`o+0{F^amkQ8fy^$-3NqIfFf2*2^l#8Q z_cWmCam}#SSB{jLo|+~mq$x0$nqO|$uuCv?dV1;};ck6SO(Kz>pYND}hPc{F2z8RVjeKwjfneQ-Ufv;Q zJ>v84N5)|c?`INql`ohBbbrF(J~&A!lb-0u#NOh{TA?nUBP;bRox+&n^JLlS9pXrM zkNLz^3`=`iVB`nBOX+R!d1o}?!dB{mImTwIo;PD0BN}gwuZRDhOeK~=S_nnqb1)80 z6cY+UeiqvR+}a3iAihJd?-%&i_Y3^g>&AxPTUOT906&xNr%x3iBs^BzRp_q21q2A? zYuTWpIRBLfSn_NCPsqOr@HjwVtk$O;0kv6@8|CjE!e8po=-+k#{u2NDJWgTKdwqVn zNO|kb&4BK$(Cy&BYoDrdcqiJ1TZIj}e8S5B47y@3vbIrF(yCZ#8tP(t`*GFqbji9pBsSxO9LiY$`YY zV$YTx35`Lxcg~_C!Qgmd3pw>bpwwXfuttEb6P1(lgOM?EXv+kD`YZo?OQE%V&XCO% z4vcz!9o^(TknG4iO@&_A^uq)lF0xG5w1;KqU$qn!nSdz^AT=v2z%XT0&fV)By>0!4 z=EPHn(b>^;M&+1H13jN++@2%s}Ahk~n_za=q4chgH7Q zzJUNCU^X+#HRsuWm?_(+g{I9ZvS8wl$W+L(jaGw29#zt8a9f6KN&Iw`zq z

    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 ND61w@BK7vue*l^U!HfU^ literal 0 HcmV?d00001 From e0fa11d6eb3a102c2cdbc327842450c273212089 Mon Sep 17 00:00:00 2001 From: Sunyi Xu <94rbmyftn9@privaterelay.appleid.com> Date: Wed, 7 Oct 2026 19:17:33 +0800 Subject: [PATCH 10/10] Point installer downloads to GitHub Releases --- README.md | 2 +- docs/INSTALL.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 09d6b50..77682ed 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Git View 是一个本地 Git 图形工具,用于查看文件差异、浏览提 ## 安装 -预览安装包可在 [打包工作流](https://github.com/XSY-28/git-view/actions/workflows/package.yml) 对应成功运行的 Artifacts 中下载(需要登录 GitHub);正式发布后也会提供于 [Releases](https://github.com/XSY-28/git-view/releases)。两种包都自带 Node 运行时,使用时只需系统 Git,无需另装 Node、pnpm 或 Rust。 +在 [GitHub Releases](https://github.com/XSY-28/git-view/releases/latest) 下载当前版本的安装包和 `.sha256` 校验文件,无需登录 GitHub。两种包都自带 Node 运行时,使用时只需系统 Git,无需另装 Node、pnpm 或 Rust。 - **macOS Apple silicon**:下载 `macos-arm64.dmg`,打开后先退出 Git View,再双击 **Install Git View.command**。安装位置固定为 `~/Applications/Git View.app`,旧版本保留为 ZIP;完成后弹出磁盘映像。不要再复制一份到 `/Applications`。 - **Windows x64**:下载 `windows-x64-setup.exe`,运行安装向导,再从开始菜单打开。需要 Git for Windows 与 WebView2;缺少 WebView2 时安装程序会下载运行时。Windows 仅支持查看,不开放暂存、提交和分支写入。 diff --git a/docs/INSTALL.md b/docs/INSTALL.md index 8cbd8fd..1a32567 100644 --- a/docs/INSTALL.md +++ b/docs/INSTALL.md @@ -1,6 +1,6 @@ # 安装 Git View 预览包 -在 [打包工作流](https://github.com/XSY-28/git-view/actions/workflows/package.yml) 的成功运行中下载对应平台的 Artifacts ZIP(需要登录 GitHub),解压后取出安装包与 `.sha256` 文件。正式发布后也可在 [GitHub Releases](https://github.com/XSY-28/git-view/releases) 下载同版本文件。当前目标平台为 macOS 13.5 及以上的 Apple silicon(arm64)和 Windows x64,尚不提供 Intel Mac、Windows ARM 或 Linux 安装包。macOS 下限由包内 [Node 24 的平台要求](https://github.com/nodejs/node/blob/v24.19.0/BUILDING.md#platform-list)决定。 +在 [GitHub Releases](https://github.com/XSY-28/git-view/releases/latest) 下载对应平台的安装包与同版本 `.sha256` 文件,无需登录 GitHub。开发构建也可从 [打包工作流](https://github.com/XSY-28/git-view/actions/workflows/package.yml) 的成功运行中下载 Artifacts ZIP(需要登录 GitHub)。当前目标平台为 macOS 13.5 及以上的 Apple silicon(arm64)和 Windows x64,尚不提供 Intel Mac、Windows ARM 或 Linux 安装包。macOS 下限由包内 [Node 24 的平台要求](https://github.com/nodejs/node/blob/v24.19.0/BUILDING.md#platform-list)决定。 ## macOS Apple silicon