fix(ios): close final release review blockers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
27
.github/workflows/publish-app-store.yml
vendored
27
.github/workflows/publish-app-store.yml
vendored
@@ -7,7 +7,9 @@
|
|||||||
# ── HUMAN GATE (one-time, after Apple Developer Program enrollment) ──────────────
|
# ── HUMAN GATE (one-time, after Apple Developer Program enrollment) ──────────────
|
||||||
# Complete ALL of the following before releasing. Do NOT invent any of these IDs.
|
# Complete ALL of the following before releasing. Do NOT invent any of these IDs.
|
||||||
#
|
#
|
||||||
# 1. Fill and commit the eas.json placeholders (see eas.json.README.md for click paths):
|
# 1. Link the app to an Expo project and add its UUID as the GitHub Actions
|
||||||
|
# repository variable EAS_PROJECT_ID (see eas.json.README.md), then fill and
|
||||||
|
# commit the eas.json placeholders:
|
||||||
# submit.production.ios.ascAppId REPLACE_WITH_APP_STORE_CONNECT_APP_ID → numeric App Store Connect App ID
|
# submit.production.ios.ascAppId REPLACE_WITH_APP_STORE_CONNECT_APP_ID → numeric App Store Connect App ID
|
||||||
# submit.production.ios.appleTeamId REPLACE_WITH_APPLE_TEAM_ID → 10-char Apple Team ID
|
# submit.production.ios.appleTeamId REPLACE_WITH_APPLE_TEAM_ID → 10-char Apple Team ID
|
||||||
#
|
#
|
||||||
@@ -26,7 +28,8 @@
|
|||||||
# SENTRY_AUTH_TOKEN, SENTRY_ORG, and SENTRY_PROJECT in Expo before releasing.
|
# SENTRY_AUTH_TOKEN, SENTRY_ORG, and SENTRY_PROJECT in Expo before releasing.
|
||||||
#
|
#
|
||||||
# Build number is managed remotely by EAS (eas.json: cli.appVersionSource=remote,
|
# Build number is managed remotely by EAS (eas.json: cli.appVersionSource=remote,
|
||||||
# build.production.ios.autoIncrement=buildNumber). No app.json mutation happens here.
|
# build.production.ios.autoIncrement=buildNumber). The workflow only injects the
|
||||||
|
# EAS project linkage into its temporary runner copy of app.json.
|
||||||
|
|
||||||
name: Publish to App Store (TestFlight)
|
name: Publish to App Store (TestFlight)
|
||||||
|
|
||||||
@@ -54,6 +57,7 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
EAS_CLI_VERSION: "21.0.0"
|
EAS_CLI_VERSION: "21.0.0"
|
||||||
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
|
EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
|
||||||
|
EAS_PROJECT_ID: ${{ vars.EAS_PROJECT_ID }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@v6
|
||||||
|
|
||||||
@@ -80,6 +84,13 @@ jobs:
|
|||||||
need "APPLE_APP_STORE_CONNECT_API_KEY_ID" "${ASC_KEY_ID:-}"
|
need "APPLE_APP_STORE_CONNECT_API_KEY_ID" "${ASC_KEY_ID:-}"
|
||||||
need "APPLE_APP_STORE_CONNECT_ISSUER_ID" "${ASC_ISSUER_ID:-}"
|
need "APPLE_APP_STORE_CONNECT_ISSUER_ID" "${ASC_ISSUER_ID:-}"
|
||||||
need "APPLE_APP_STORE_CONNECT_API_KEY" "${ASC_KEY_B64:-}"
|
need "APPLE_APP_STORE_CONNECT_API_KEY" "${ASC_KEY_B64:-}"
|
||||||
|
if [ -z "${EAS_PROJECT_ID:-}" ]; then
|
||||||
|
echo "::error::Missing required repository variable: EAS_PROJECT_ID"
|
||||||
|
fail=1
|
||||||
|
elif ! printf '%s' "$EAS_PROJECT_ID" | grep -Eq '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'; then
|
||||||
|
echo "::error::EAS_PROJECT_ID must be an Expo project UUID"
|
||||||
|
fail=1
|
||||||
|
fi
|
||||||
asc_app_id=$(node -p "require('./eas.json').submit.production.ios.ascAppId || ''")
|
asc_app_id=$(node -p "require('./eas.json').submit.production.ios.ascAppId || ''")
|
||||||
team_id=$(node -p "require('./eas.json').submit.production.ios.appleTeamId || ''")
|
team_id=$(node -p "require('./eas.json').submit.production.ios.appleTeamId || ''")
|
||||||
case "$asc_app_id" in
|
case "$asc_app_id" in
|
||||||
@@ -113,6 +124,18 @@ jobs:
|
|||||||
- name: Install dependencies (deterministic)
|
- name: Install dependencies (deterministic)
|
||||||
run: npm ci --legacy-peer-deps
|
run: npm ci --legacy-peer-deps
|
||||||
|
|
||||||
|
- name: Configure EAS project linkage
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
node -e "
|
||||||
|
const fs = require('fs');
|
||||||
|
const j = require('./app.json');
|
||||||
|
j.expo.extra = { ...j.expo.extra, eas: { ...j.expo.extra?.eas, projectId: process.env.EAS_PROJECT_ID } };
|
||||||
|
fs.writeFileSync('app.json', JSON.stringify(j, null, 2) + '\n');
|
||||||
|
"
|
||||||
|
test "$(node -p "require('./app.json').expo.extra.eas.projectId")" = "$EAS_PROJECT_ID"
|
||||||
|
echo "Linked build to Expo project $EAS_PROJECT_ID."
|
||||||
|
|
||||||
- name: Configure App Store Connect API key
|
- name: Configure App Store Connect API key
|
||||||
env:
|
env:
|
||||||
ASC_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}
|
ASC_KEY_ID: ${{ secrets.APPLE_APP_STORE_CONNECT_API_KEY_ID }}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import {
|
|||||||
} from "../../src/lib/notifications"
|
} from "../../src/lib/notifications"
|
||||||
import type { Category } from "../../src/lib/notifications"
|
import type { Category } from "../../src/lib/notifications"
|
||||||
import { hasTelemetryConsent, setTelemetryConsent } from "../../src/lib/telemetry"
|
import { hasTelemetryConsent, setTelemetryConsent } from "../../src/lib/telemetry"
|
||||||
|
import { PRIVACY_POLICY_URL } from "../../src/lib/links"
|
||||||
|
|
||||||
function SettingRow({
|
function SettingRow({
|
||||||
icon,
|
icon,
|
||||||
@@ -215,7 +216,7 @@ export default function SettingsScreen() {
|
|||||||
label="Privacy Policy"
|
label="Privacy Policy"
|
||||||
description="What data we collect and how"
|
description="What data we collect and how"
|
||||||
isDark={isDark}
|
isDark={isDark}
|
||||||
onPress={() => Linking.openURL("https://agentlabs.cc/opencode/privacy")}
|
onPress={() => Linking.openURL(PRIVACY_POLICY_URL)}
|
||||||
right={<Ionicons name="open-outline" size={20} color={isDark ? "#666666" : "#999999"} />}
|
right={<Ionicons name="open-outline" size={20} color={isDark ? "#666666" : "#999999"} />}
|
||||||
/>
|
/>
|
||||||
</SettingSection>
|
</SettingSection>
|
||||||
|
|||||||
@@ -141,13 +141,15 @@ While waiting for Apple's verification call and approval:
|
|||||||
- Note: Key ID and Issuer ID
|
- Note: Key ID and Issuer ID
|
||||||
- Base64-encode the .p8 and store in GitHub secret `APPLE_APP_STORE_CONNECT_API_KEY`
|
- Base64-encode the .p8 and store in GitHub secret `APPLE_APP_STORE_CONNECT_API_KEY`
|
||||||
|
|
||||||
4. Configure the EAS `production` environment for optional crash reporting:
|
4. Run `eas init` once, then add the generated `extra.eas.projectId` UUID as the GitHub Actions repository variable `EAS_PROJECT_ID`.
|
||||||
|
|
||||||
|
5. Configure the EAS `production` environment for optional crash reporting:
|
||||||
- `EXPO_PUBLIC_SENTRY_DSN`
|
- `EXPO_PUBLIC_SENTRY_DSN`
|
||||||
- `SENTRY_AUTH_TOKEN` (secret visibility)
|
- `SENTRY_AUTH_TOKEN` (secret visibility)
|
||||||
- `SENTRY_ORG`
|
- `SENTRY_ORG`
|
||||||
- `SENTRY_PROJECT`
|
- `SENTRY_PROJECT`
|
||||||
|
|
||||||
5. Create an internal TestFlight group and add yourself as tester
|
6. Create an internal TestFlight group and add yourself as tester
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ Because the answer is "No", no ERN (Encryption Registration Number) is required
|
|||||||
| 9 | Export compliance | ✅ Done | `ITSAppUsesNonExemptEncryption: false` added to `app.json`. Answers + rationale in this doc (see Export Compliance section above) and `distribution/app-store-listing.md`. |
|
| 9 | Export compliance | ✅ Done | `ITSAppUsesNonExemptEncryption: false` added to `app.json`. Answers + rationale in this doc (see Export Compliance section above) and `distribution/app-store-listing.md`. |
|
||||||
| 10 | ATS justification in App Review notes | ✅ Done | Full justification text in `distribution/app-store-listing.md` under "App Review Notes — ATS Justification" |
|
| 10 | ATS justification in App Review notes | ✅ Done | Full justification text in `distribution/app-store-listing.md` under "App Review Notes — ATS Justification" |
|
||||||
| 11 | Reviewer test instructions | ✅ Done | Updated with correct command (`opencode serve --hostname 0.0.0.0`) in `distribution/app-store-listing.md` |
|
| 11 | Reviewer test instructions | ✅ Done | Updated with correct command (`opencode serve --hostname 0.0.0.0`) in `distribution/app-store-listing.md` |
|
||||||
| 12 | GitHub secrets: `EXPO_TOKEN`, `APPLE_APP_STORE_CONNECT_API_KEY_ID`, `APPLE_APP_STORE_CONNECT_ISSUER_ID`, `APPLE_APP_STORE_CONNECT_API_KEY` (base64 .p8) | User | 🟡 post-enrollment — see `.github/workflows/publish-app-store.yml` header |
|
| 12 | GitHub variable `EAS_PROJECT_ID`; secrets: `EXPO_TOKEN`, `APPLE_APP_STORE_CONNECT_API_KEY_ID`, `APPLE_APP_STORE_CONNECT_ISSUER_ID`, `APPLE_APP_STORE_CONNECT_API_KEY` (base64 .p8) | User | 🟡 post-enrollment — run `eas init`, then see `.github/workflows/publish-app-store.yml` |
|
||||||
| 13 | Update `eas.json` placeholders: `ascAppId` + `appleTeamId` | User | 🟡 post-enrollment — see `eas.json.README.md` for click paths |
|
| 13 | Update `eas.json` placeholders: `ascAppId` + `appleTeamId` | User | 🟡 post-enrollment — see `eas.json.README.md` for click paths |
|
||||||
| 14 | CI workflow validated | CI | 🟡 Linux checks pass; PR must prove the macOS Simulator build |
|
| 14 | CI workflow validated | CI | 🟡 Linux checks pass; PR must prove the macOS Simulator build |
|
||||||
| 15 | TestFlight release notes | ✅ Done | `distribution/whatsnew-ios/release-notes-en-US.txt` — polished, 1658 chars (limit 4000) |
|
| 15 | TestFlight release notes | ✅ Done | `distribution/whatsnew-ios/release-notes-en-US.txt` — polished, 1658 chars (limit 4000) |
|
||||||
@@ -102,7 +102,7 @@ Because the answer is "No", no ERN (Encryption Registration Number) is required
|
|||||||
1. (manual) Sign in to App Store Connect, create app with bundle id `cc.agentlabs.opencode`.
|
1. (manual) Sign in to App Store Connect, create app with bundle id `cc.agentlabs.opencode`.
|
||||||
2. (manual) Generate App Store Connect API key (App Manager role) → download `.p8` → base64 encode → add as GitHub secret.
|
2. (manual) Generate App Store Connect API key (App Manager role) → download `.p8` → base64 encode → add as GitHub secret.
|
||||||
3. (manual) Update `eas.json` placeholders (Team ID, ASC App ID).
|
3. (manual) Update `eas.json` placeholders (Team ID, ASC App ID).
|
||||||
4. (manual) `eas login` + `eas build:configure` for first-time setup (managed signing).
|
4. (manual) `eas login` + `eas init` + `eas build:configure`; add the generated project UUID as repository variable `EAS_PROJECT_ID`.
|
||||||
5. (automated) Publish a GitHub Release for the version tag → CI calls EAS Build → EAS Submit → IPA lands in TestFlight.
|
5. (automated) Publish a GitHub Release for the version tag → CI calls EAS Build → EAS Submit → IPA lands in TestFlight.
|
||||||
6. (manual, first time) Add internal testers in App Store Connect → distribute via TestFlight.
|
6. (manual, first time) Add internal testers in App Store Connect → distribute via TestFlight.
|
||||||
7. (manual) After internal testing OK → submit for App Store review (production).
|
7. (manual) After internal testing OK → submit for App Store review (production).
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ When the opencode AI agent requests a file-access permission, the notification b
|
|||||||
**Files:** `app.json:29`, `app.json:38-39`
|
**Files:** `app.json:29`, `app.json:38-39`
|
||||||
**Description:** Both platforms allow HTTP connections, which is required for local/LAN servers. This is intentional and correct for the use case. However, neither the Play Store listing, App Store listing, nor a privacy policy document currently explains that HTTP connections may be made to user-provided servers. Google Play's Data Safety section and Apple's App Privacy report will flag arbitrary network access if not documented.
|
**Description:** Both platforms allow HTTP connections, which is required for local/LAN servers. This is intentional and correct for the use case. However, neither the Play Store listing, App Store listing, nor a privacy policy document currently explains that HTTP connections may be made to user-provided servers. Google Play's Data Safety section and Apple's App Privacy report will flag arbitrary network access if not documented.
|
||||||
**Remediation:**
|
**Remediation:**
|
||||||
1. Update the privacy policy at `agentlabs.cc/opencode/privacy` to explain that the app connects to user-configured server addresses that may use HTTP.
|
1. Update the canonical privacy policy at `https://dzianisv.github.io/opencode-mobile/privacy/` to explain that the app connects to user-configured server addresses that may use HTTP.
|
||||||
2. In Play Store Data Safety: disclose "Other app performance data" collected (crash reports via Sentry — opt-in).
|
2. In Play Store Data Safety: disclose "Other app performance data" collected (crash reports via Sentry — opt-in).
|
||||||
**Status:** Open
|
**Status:** Open
|
||||||
|
|
||||||
|
|||||||
@@ -73,10 +73,12 @@ Alternatively, in App Store Connect:
|
|||||||
|
|
||||||
## After filling in the placeholders
|
## After filling in the placeholders
|
||||||
|
|
||||||
1. Commit the updated `eas.json` to the repo.
|
1. Run `eas init` once to create/link the Expo project.
|
||||||
2. Add the `EXPO_TOKEN` and App Store Connect API GitHub Actions secrets (see `.github/workflows/publish-app-store.yml` for the exact list).
|
2. Copy the generated `extra.eas.projectId` UUID and add it as the GitHub Actions repository variable `EAS_PROJECT_ID`. The release workflow injects it into `app.json` only on the runner.
|
||||||
3. Publish a GitHub Release for the version tag (or manually dispatch the App Store workflow).
|
3. Commit the updated `eas.json` to the repo.
|
||||||
4. The release event triggers CI to build the IPA via EAS and submit that exact build to TestFlight.
|
4. Add the `EXPO_TOKEN` and App Store Connect API GitHub Actions secrets (see `.github/workflows/publish-app-store.yml` for the exact list).
|
||||||
|
5. Publish a GitHub Release for the version tag (or manually dispatch the App Store workflow).
|
||||||
|
6. The release event triggers CI to build the IPA via EAS and submit that exact build to TestFlight.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Modal, Linking } from "react-native"
|
import { View, Text, TouchableOpacity, StyleSheet, useColorScheme, Modal, Linking } from "react-native"
|
||||||
import { Ionicons } from "@expo/vector-icons"
|
import { Ionicons } from "@expo/vector-icons"
|
||||||
|
import { PRIVACY_POLICY_URL } from "../lib/links"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
visible: boolean
|
visible: boolean
|
||||||
@@ -45,9 +46,7 @@ export function TelemetryConsentModal({ visible, onAllow, onDecline }: Props) {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Privacy policy link */}
|
{/* Privacy policy link */}
|
||||||
<TouchableOpacity
|
<TouchableOpacity onPress={() => Linking.openURL(PRIVACY_POLICY_URL)}>
|
||||||
onPress={() => Linking.openURL("https://agentlabs.cc/opencode/privacy")}
|
|
||||||
>
|
|
||||||
<Text style={styles.privacyLink}>Read our full privacy policy</Text>
|
<Text style={styles.privacyLink}>Read our full privacy policy</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
|||||||
1
src/lib/links.ts
Normal file
1
src/lib/links.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const PRIVACY_POLICY_URL = "https://dzianisv.github.io/opencode-mobile/privacy/"
|
||||||
@@ -232,7 +232,10 @@ export function createClient(config: ClientConfig) {
|
|||||||
try {
|
try {
|
||||||
yield JSON.parse(data)
|
yield JSON.parse(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn("[SSE] Failed to parse event:", data.slice(0, 200), err)
|
console.warn("[SSE] Failed to parse event", {
|
||||||
|
length: data.length,
|
||||||
|
error: err instanceof Error ? err.message : String(err),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ export function initSentry() {
|
|||||||
return scrubEvent(event)
|
return scrubEvent(event)
|
||||||
},
|
},
|
||||||
beforeBreadcrumb(crumb) {
|
beforeBreadcrumb(crumb) {
|
||||||
|
// Console output can contain malformed server payloads, prompts, or code.
|
||||||
|
if (crumb.category === "console") return null
|
||||||
if (crumb.data && typeof crumb.data === "object") {
|
if (crumb.data && typeof crumb.data === "object") {
|
||||||
crumb.data = redactObject(crumb.data as Record<string, unknown>)
|
crumb.data = redactObject(crumb.data as Record<string, unknown>)
|
||||||
}
|
}
|
||||||
@@ -149,6 +151,7 @@ function scrubEvent<T extends Sentry.Event>(event: T): T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (event.breadcrumbs) {
|
if (event.breadcrumbs) {
|
||||||
|
event.breadcrumbs = event.breadcrumbs.filter((crumb) => crumb.category !== "console")
|
||||||
for (const crumb of event.breadcrumbs) {
|
for (const crumb of event.breadcrumbs) {
|
||||||
if (typeof crumb.message === "string") crumb.message = redactString(crumb.message)
|
if (typeof crumb.message === "string") crumb.message = redactString(crumb.message)
|
||||||
if (crumb.data && typeof crumb.data === "object") {
|
if (crumb.data && typeof crumb.data === "object") {
|
||||||
|
|||||||
Reference in New Issue
Block a user