| 事件 | 工作流与行为 |
|---|---|
| PR、推送 main / develop、手动 CI | ci.yml:质量检查 |
推送 v* tag |
release.yml:并行构建签名 APK 与 Windows ZIP,二者通过后统一创建 / 更新 GitHub Release |
日常 CI 负责质量检查,tag 工作流负责应用构建与发布。
正式标签为 vX.Y.Z,beta 标签为 vX.Y.Z-beta.N。三端共用版本预检:标签基础版本与 pubspec 的 X.Y.Z 一致,包内使用 X.Y.Z+build;每个版本的 beta 序号从 1 开始,内部构建号独立递增。beta 的 APK / ZIP 标记为 GitHub 预发布,Latest 保持正式发布;iOS 上传 TestFlight。准备与发布命令见发布操作。
固定 Flutter 3.38.10,应用、tools/source_probe、tool/db_codegen 三份依赖严格按 lockfile 安装。统一 PUB_HOSTED_URL=https://pub.flutter-io.cn,缓存只加速安装,不能替代锁文件校验。
CI 检查工作流结构、锁文件变化、数据库生成与新 schema、gen-l10n 生成一致性、Dart 格式、静态分析,以及独立 Source 调查包的格式 / 分析 / 样本完整性预检。根分析会扫描工具包,必须提前完成其依赖安装。
按用户约定,应用单元 / 组件测试、调查包 UT、Python 发布工具 UT 均在提交前本地执行,日常 CI(含 PR 和手动运行)不再重复运行。测试代码仍保留,格式和静态分析仍覆盖测试文件;远端 CI 绿色不代表 UT 已通过,应在提交或 PR 中记录实际本地验证。未添加强制 Git hook。
本地命令见开发说明。工作流结构校验入口为 check_ci_yaml.dart。样本哈希按原始字节计算,.gitattributes 固定 LF;哈希失败先修样本完整性,不放宽校验或把网络请求数为零误判为网络故障。
Windows 构建 job 仅在 tag 发布工作流运行,使用 windows-2022 / Visual Studio 2022、同一固定 Flutter 版本及严格锁文件安装;构建前检查 VS 主版本。Git 长路径配置覆盖检出和 Pub 子进程,以支持固定提交的 WebView fork;仅缓存 SDK 和 Pub 依赖,不复用本机构建目录。构建入口固定为 lib/main.dart,检查 EXE、Flutter / WebView DLL、AOT 与资源文件是否存在且非空。CI 不启动应用或执行在线探针,编译成功不代表运行验收通过。
tool/package_windows.ps1 查找 Visual Studio 2022 的 x64 VC++ 运行库,调用标准库脚本 tool/release_windows.py 打包。脚本核对 tag / pubspec、EXE 的 x64 架构、产品身份、版本与非 Debug 标志,验证必需 DLL / AOT / 资源文件,保留完整 data 目录及许可声明。ZIP 根目录直接包含 shiori.exe;不带 PDB 或包含构建机路径的 native_assets.json。
Windows 产物为 shiori-reader-<tag>-windows-x64.zip、SHA256SUMS-windows-x64.txt 和 release-info-windows-x64.json(版本、commit、摘要及未签名状态),与 Android 文件名分开。tag 构建产物保留 14 天;publish job 等待两个平台构建成功,下载当前运行的两个 artifact、核对两份 SHA-256 后统一上传同一 GitHub Release。只有该 job 获得 Release 写权限。任一构建或校验失败,都不进入发布。
本地可在正式入口 Release 构建后调用 tool/package_windows.ps1 -Tag <tag> -Commit <完整提交 SHA> 验包。Windows 使用方式与分发边界见发布说明。
APK 校验固定使用 runner 预装的 Build Tools 35.0.0,发布构建前执行 release_android.py tools 预检,不依赖 PATH 中的 sdkmanager,也不自动选 runner 上最高版本:更高预装版本的 apksigner --print-certs 输出 V2 Signer: certificate SHA-256 digest,与校验器预期的 Signer #1 certificate SHA-256 digest 格式不同,会导致校验失败。固定工具版本不替代签名检查。
已知校验错误输出受控原因(版本、证书、工具),不会输出工具参数、原始 stderr 或签名秘密;未知异常仍使用通用提示。
按用户约定,tag 发布直接并行进入 android-release 与 windows-release,不查询或等待 CI、不重复执行格式 / 分析 / UT。develop 日常 CI 保持独立;master 仅作为发布中间分支,不因推送触发 CI 或 Release。发布脚本优先快进 master,保留最终提交与标签的可追溯性。Android 构建仍保留工具预检、版本一致性、签名和 APK 校验;失败时不上传 Release。签名需要四个仓库 Actions secrets:
| Secret | 内容 |
|---|---|
| ANDROID_KEYSTORE_BASE64 | 发布密钥库文件的 Base64 |
| ANDROID_STORE_PASSWORD | 密钥库密码 |
| ANDROID_KEY_ALIAS | 签名别名 |
| ANDROID_KEY_PASSWORD | 私钥密码 |
工作流检查四项 Secret 非空、Base64 有效并还原 android/app/release-keystore.jks 与 android/key.properties。密码按 Java Properties 规则转义,支持空格、反斜杠等字符;密钥库密码与别名通过 keytool 导出证书验证,私钥密码在签名构建时验证。密钥只在签名步骤注入,不回显;无论构建成功与否都清理签名文件。
本地缺少 key.properties 时构建配置可回退 Debug 签名,因此 Release 编译不等于正式签名。密钥和密码需自行安全备份,不提交 Git。换电脑需要恢复同一密钥才能保持 Android 更新身份。
标签不会自动修改包内版本;发版时在 pubspec.yaml 准备版本和构建号,并对齐 tag。iOS Runner 与 ShareExtension 从 Flutter 生成配置继承版本,不在 Xcode 工程中维护数字。发布准备与许可边界见发布说明。iOS 发布链路见iOS TestFlight 发布。
发布工具为 tool/release_android.py(Python 3 标准库),离线测试入口为 tool/test_release_android.py。
正式入口明确为 lib/main.dart。构建后用 Android SDK apksigner 验证签名有效,再对比配置密钥导出的证书 SHA-256;aapt 检查 applicationId=dev.shiori.reader、versionName / versionCode 与 pubspec 一致且不可调试。检查通过才生成并上传:
shiori-reader-<tag>-android.apkSHA256SUMS.txt(APK 校验值)release-info.json(tag、commit、包版本、APK 哈希、证书指纹,不含私钥 / 密码)
产物先保存为 14 天的 Actions artifact。发布 job 签署更新清单、核对历史构建号和已有资产摘要,通过草稿完成上传后再公开 Release;重跑只补齐内容一致的缺失附件。beta 标签标为 prerelease,Latest 指向正式版本。更新签名配置见发布操作。
签名构建显式选择 runner 上的 Xcode 26.3,并在安装依赖前验证 iOS SDK 主版本至少为 26,避免默认 Xcode 变化或旧 SDK 到上传阶段才失败。最低运行系统仍由 deployment target 决定,不随构建 SDK 提高到 iOS 26。要求来源:Apple 上传要求。
工作流:.github/workflows/ios-release.yml。推送 v* tag 时与 Android 发布并行,在 macOS runner 上构建签名 IPA 并上传 App Store Connect(TestFlight)。手动入口只做签名构建冒烟(不上传 ASC,且无标签上下文时跳过版本预检)。质量检查不在发布工作流重复;tag 与 pubspec 版本一致性复用 release_android.py version 预检(仅 tag 触发时执行)。
签名链路:分发证书 p12 导入临时钥匙串;tool/ios_release_signing.py 用 App Store Connect API 密钥按该证书查找或创建 Runner 和 ShareExtension 各自的 App Store profile,核对 Bundle ID、App Group、证书和有效期后安装到 runner。脚本仅在 CI 工作副本中将两个 target 的 Release archive 改为手动 Apple Distribution 签名,并生成含两份 profile 映射的导出配置;archive / export 不启用 Xcode 自动更新签名,因此不会请求 Development 证书。仓库工程的 Debug 和本地 Xcode 自动开发签名保持原样。导出配置基础文件为 ios/ExportOptions.plist,teamID 与工程 DEVELOPMENT_TEAM 一致(个人团队)。共 5 个 secrets:
| Secret | 内容 |
|---|---|
ASC_KEY_ID / ASC_ISSUER_ID / ASC_KEY_P8 |
App Store Connect API 团队密钥(个人团队上下文生成,Admin 角色) |
IOS_DIST_CERT_P12 |
Apple Distribution 证书 p12 的 base64 |
IOS_DIST_CERT_PASSWORD |
p12 口令(可为空) |
Apple 侧一次性准备(都在个人团队上下文操作,注意右上角团队切换器):
- 注册 App ID
dev.shiori.reader与dev.shiori.reader.ShareExtension,均启用 App Groups 并分配group.dev.shiori.reader.import; - App Store Connect 新建 App(Bundle ID 选
dev.shiori.reader); - 生成 App Store Connect API 密钥(
.p8仅能下载一次); - 生成分发证书:在有 Xcode 的机器上创建 Apple Distribution 并从钥匙串导出 p12,base64 后存入 secret。工作流不在 runner 上自动建证——证书产物含未加密私钥,而公开仓库的 Actions 产物任何登录用户都能下载。
发版与安装:与 Android 相同,人工对齐版本后推 tag(ASC 要求同一 versionName 下 CFBundleVersion 严格递增);构建经数分钟至一小时处理后出现在 TestFlight,内部测试不走 Beta 审核,构建 90 天未安装会过期。CI 构建成功不代表设备运行通过;对外分发前仍需按发布操作完成验收。