#!/usr/bin/env bash set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CONFIG="$ROOT/Shared/AppRemoteConfiguration.swift" CONTENT="$ROOT/App/ContentView.swift" MAP="$ROOT/App/MapHomeView.swift" SETUP="$ROOT/App/FirstSetupView.swift" SETTINGS="$ROOT/App/SettingsView.swift" BUG_REPORT="$ROOT/App/BugReportView.swift" GITHUB_SUBMISSION="$ROOT/Shared/GitHubSubmission.swift" DISCUSSION_FORM="$ROOT/.github/DISCUSSION_TEMPLATE/第三方配置分享.yml" ISSUE_FORM="$ROOT/.github/ISSUE_TEMPLATE/bug-report.yml" ISSUE_CONFIG="$ROOT/.github/ISSUE_TEMPLATE/config.yml" fail() { echo "FAIL: $1" >&2 exit 1 } python3 - "$ROOT/version.txt" <<'PY' || exit 1 import json import sys with open(sys.argv[1], encoding="utf-8") as handle: config = json.load(handle) assert config["latestVersion"] == "1.0.7" assert config["minimumSupportedVersion"] == "1.0.0" assert "shadowrocket" not in config["communityPromptClients"] assert set(config["communityPromptClients"]) == { "surge", "quantumultX", "loon", "stash", "egern" } PY LATEST_VERSION="$(python3 -c 'import json, sys; print(json.load(open(sys.argv[1], encoding="utf-8"))["latestVersion"])' "$ROOT/version.txt")" grep -Fq "latestVersion: \"$LATEST_VERSION\"" "$CONFIG" \ || fail "Shared/AppRemoteConfiguration.swift fallback latestVersion must match version.txt ($LATEST_VERSION)" grep -Fq "version-v$LATEST_VERSION" "$ROOT/README.md" \ || fail "README.md version badge must match version.txt ($LATEST_VERSION)" grep -Fq "version-v$LATEST_VERSION" "$ROOT/README.en.md" \ || fail "README.en.md version badge must match version.txt ($LATEST_VERSION)" grep -q 'static let fallback = AppRemoteConfiguration' "$CONFIG" \ || fail "the app must ship a built-in remote-configuration fallback" grep -q 'timeoutIntervalForRequest = 1.5' "$CONFIG" \ || fail "remote configuration requests must use a short timeout" grep -q 'timeoutIntervalForResource = 2' "$CONFIG" \ || fail "remote configuration resource loading must use a short timeout" grep -q 'gh-proxy.org/https://raw.githubusercontent.com/xweiba/location-spoofer/main/version.txt' "$CONFIG" \ || fail "update detection must prefer the domestic GitHub Raw mirror" grep -q 'static let configurationURLs = \[' "$CONFIG" \ || fail "update detection must retain multiple configuration sources" ! grep -q 'data.count' "$CONFIG" \ || fail "the client must not impose a remote configuration file-size limit" grep -q 'docs/releases/v\\(version).md' "$CONFIG" \ || fail "update notes must come from the archived release document" grep -q '/releases/latest' "$CONFIG" \ || fail "missing release notes must fall back to the latest Release page" grep -q '.task { await checkForUpdates() }' "$CONTENT" \ || fail "update detection must run asynchronously outside bootstrap" ! grep -A90 'private func bootstrap() async' "$CONTENT" | grep -q 'fetch()' \ || fail "remote configuration must not block the startup gate" grep -Fq 'Label("检查更新"' "$SETTINGS" \ || fail "Settings must expose a manual update check" grep -Fq 'title: Text("已是最新版本")' "$SETTINGS" \ || fail "manual update checks must report the current-version result" grep -Fq 'title: Text("检查更新失败")' "$SETTINGS" \ || fail "manual update checks must report network failures" grep -q '社区分享成功配置?' "$MAP" \ || fail "non-Shadowrocket success must offer community contribution" grep -q 'Button("去提交")' "$MAP" \ || fail "community contribution prompt must expose the submit action" grep -q 'Button("复制模板")' "$MAP" \ || fail "community contribution prompt must expose an explicit copy action" grep -q 'Button("取消", role: .cancel)' "$MAP" \ || fail "the first community prompts must expose cancel" grep -q 'Button("不再提示", role: .cancel)' "$MAP" \ || fail "the third community prompt must allow permanent suppression" grep -q '匿名收录,不在 README 展示投稿账号' "$GITHUB_SUBMISSION" \ || fail "the contribution template must offer anonymous README attribution" grep -Fq 'https://github.com/xweiba/location-spoofer/discussions/new?category=%E7%AC%AC%E4%B8%89%E6%96%B9%E9%85%8D%E7%BD%AE%E5%88%86%E4%BA%AB' "$GITHUB_SUBMISSION" \ || fail "community submission must use one fixed Discussions address" grep -q 'UIPasteboard.general.string = GitHubSubmission.communityContributionTemplate' "$MAP" \ || fail "the explicit copy action must copy the contribution template" grep -A8 'Button("去提交")' "$MAP" | grep -q 'UIPasteboard.general.string = GitHubSubmission.communityContributionTemplate' \ || fail "the submit action must copy the template before opening GitHub" grep -A8 'Button("去提交")' "$MAP" | grep -q 'openCommunityContributionPage()' \ || fail "the submit action must open the fixed Discussions page after copying" ! grep -A12 'private func openCommunityContributionPage' "$MAP" | grep -q 'URLComponents' \ || fail "the fixed community address must not be rebuilt from dynamic query items" grep -A4 'private func openCommunityContributionPage' "$MAP" | grep -q 'SafariDestination' \ || fail "community submission must open with the shared in-App Safari destination" ! grep -A4 'private func openCommunityContributionPage' "$MAP" | grep -q 'UIApplication.shared.open' \ || fail "community submission must not be intercepted by an external GitHub client" test -s "$DISCUSSION_FORM" \ || fail "the fixed Discussions category must provide a maintained submission form" grep -q 'label: 第三方客户端' "$DISCUSSION_FORM" \ || fail "the Discussions form must classify submissions by client" grep -q '匿名收录,不在 README 展示投稿账号' "$DISCUSSION_FORM" \ || fail "the Discussions form must retain anonymous README attribution" grep -q '请先移除账号、订阅、密码、设备标识、真实位置等敏感信息' "$DISCUSSION_FORM" \ || fail "the Discussions form must remind users to remove sensitive information" grep -q '截图除敏感信息遮挡外不要添加箭头、编号或说明文字' "$DISCUSSION_FORM" \ || fail "the Discussions form must explain the screenshot annotation rule" ! grep -q 'label: 隐私确认' "$DISCUSSION_FORM" \ || fail "the Discussions form must not require a separate privacy confirmation" for field in \ '## 第三方客户端' \ '## 第三方客户端版本' \ '## iOS 版本' \ '## 配置步骤' \ '## 截图与补充说明' \ '## README 收录署名'; do grep -Fq "$field" "$GITHUB_SUBMISSION" \ || fail "the App contribution template must match Discussions field: $field" done grep -Fq 'label: 第三方客户端版本' "$DISCUSSION_FORM" \ || fail "the Discussions form must clearly request the third-party client version" grep -Fq 'https://github.com/xweiba/location-spoofer/issues/new?template=bug-report.yml' "$GITHUB_SUBMISSION" \ || fail "the App bug report must open the dedicated Issue form" grep -Fq 'GitHubSubmission.bugReportURL' "$BUG_REPORT" \ || fail "the App bug report must use the shared GitHub destination" grep -Fq 'SafariView(url: destination.url)' "$BUG_REPORT" \ || fail "the App bug report must open in the shared in-App Safari view" ! grep -q 'UIApplication.shared.open' "$BUG_REPORT" \ || fail "the App bug report must not be handed to an external GitHub client" grep -Fq 'App 生成的诊断报告' "$BUG_REPORT" \ || fail "the App must tell users where to paste the generated report" grep -Fq 'String(localized: "第三方客户端")' "$BUG_REPORT" \ || fail "the generated report must identify the selected third-party client" grep -Fq 'Label("报告 Bug"' "$SETTINGS" \ || fail "Settings must identify the support action as a bug report" for action in '使用帮助' '功能建议' '分享第三方配置'; do grep -Fq "Label(\"$action\"" "$SETTINGS" \ || fail "Settings support must expose: $action" done grep -A8 'Label("分享第三方配置"' "$SETTINGS" >/dev/null \ || fail "Settings must retain the manual third-party sharing action" grep -q 'GitHubSubmission.communityContributionTemplate' "$SETTINGS" \ || fail "Settings manual sharing must copy the shared contribution template" grep -q 'SafariView(url: destination.url)' "$SETTINGS" \ || fail "Settings GitHub support actions must use the in-App Safari view" test -s "$ISSUE_FORM" \ || fail "the repository must provide a bug report Issue form" grep -Fq 'label: App 生成的诊断报告' "$ISSUE_FORM" \ || fail "the Issue form must expose the field named by the App" grep -Fq '设置 → 支持 → 报告 Bug' "$ISSUE_FORM" \ || fail "the Issue form must point users to the matching App workflow" grep -Fq 'label: 我已移除真实位置、认证信息、订阅地址、证书私钥和其他敏感信息' "$ISSUE_FORM" \ || fail "the Issue form must require privacy confirmation" grep -Fq 'blank_issues_enabled: false' "$ISSUE_CONFIG" \ || fail "blank Issues must be disabled so reports use the maintained form" for destination in '使用帮助' '功能建议' '第三方配置分享'; do grep -Fq "name: $destination" "$ISSUE_CONFIG" \ || fail "Issue chooser must route non-bug request to $destination" done for obsolete in \ '我已配置,开始检测' \ '确认完成,重新检测' \ '下一步:导入配置' \ '我已导入,检测接口连接'; do ! grep -q "$obsolete" "$SETUP" \ || fail "setup footer must not retain dynamic label: $obsolete" done test "$(grep -c 'actionLabel("完成")' "$SETUP")" -eq 4 \ || fail "all setup footer primary actions must use the fixed 完成 label" application_line="$(grep -n 'Section("应用")' "$SETTINGS" | head -n 1 | cut -d: -f1)" certificate_line="$(grep -n 'Section("证书")' "$SETTINGS" | head -n 1 | cut -d: -f1)" test "$application_line" -lt "$certificate_line" \ || fail "the APP certificate reset section must appear below the application section" echo "PASS: update and community contribution contract"