mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
Merge pull request #2 from jjbrunton/staging
chore(release): promote mobile build hardening
This commit is contained in:
12 files changed
+160
-39
No files matched your search
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: 2026-08-28-maestro-stable-login-readiness
|
||||
date: 2026-08-28
|
||||
status: promoted
|
||||
scope: native Auth-shell acceptance flows
|
||||
evidence: iOS simulator login-shell acceptance on current main
|
||||
destination: e2e/maestro
|
||||
review_after: 2027-02-28
|
||||
---
|
||||
|
||||
## Symptom
|
||||
|
||||
The credential-free native smoke failed even though the current app reached the
|
||||
login screen and displayed its required controls.
|
||||
|
||||
## Reproduction
|
||||
|
||||
Run `e2e/maestro/login-screen-smoke.yaml` against current main. The flow waited
|
||||
for the retired tagline `Explore intimacy together`; the app displayed updated
|
||||
marketing copy while `Sauci` and `Password` were present.
|
||||
|
||||
## Cause
|
||||
|
||||
Login readiness was coupled to mutable marketing copy rather than a stable
|
||||
required control.
|
||||
|
||||
## Remediation
|
||||
|
||||
Wait for the password control that defines login readiness, while retaining the
|
||||
brand and password assertions. Apply the same readiness condition to the
|
||||
authenticated and credential-free flows.
|
||||
|
||||
## Evidence
|
||||
|
||||
Simulator read-back confirmed the current login shell exposes `Sauci` and
|
||||
`Password`. The maintained Maestro flows now wait for that stable control rather
|
||||
than the tagline.
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: 2026-08-28-mobile-release-source-integrity
|
||||
date: 2026-08-28
|
||||
status: promoted
|
||||
scope: mobile release build and upload workflow
|
||||
evidence: clean-source local EAS builds for iOS 43 and Android 46
|
||||
destination: scripts/lint-harness.mjs
|
||||
review_after: 2027-02-28
|
||||
---
|
||||
|
||||
## Symptom
|
||||
|
||||
A build-only request initially regenerated tracked native projects. The generated
|
||||
Android widget provider replaced the checked-in implementation, and the Android
|
||||
release script would also have uploaded after building.
|
||||
|
||||
## Reproduction
|
||||
|
||||
Run the former mobile release scripts or run `expo prebuild` before a local EAS
|
||||
build, then inspect `git diff`. The reproduced diff included the Android widget
|
||||
provider, widget XML, and Xcode project rather than version metadata alone.
|
||||
|
||||
## Cause
|
||||
|
||||
The release scripts combined three independently reviewed operations: native
|
||||
regeneration, artifact creation, and store upload. Sauci already tracks native
|
||||
projects, and EAS skips prebuild when those directories exist.
|
||||
|
||||
## Remediation
|
||||
|
||||
Keep native regeneration explicit, make release scripts artifact-only, expose
|
||||
upload separately, and lint the scripts so those responsibilities cannot be
|
||||
silently recombined.
|
||||
|
||||
## Evidence
|
||||
|
||||
Corrected local EAS logs reported `PREBUILD` skipped. Both production artifacts
|
||||
built successfully from the clean tracked sources; the post-build worktree
|
||||
contained only the four expected build-number files. `npm run lint:harness`
|
||||
enforces the release-script boundary.
|
||||
@@ -9,7 +9,15 @@ Read `docs/releasing.md`, `apps/mobile/RELEASING.md` when present, and the mobil
|
||||
scoped instructions. Confirm target platform and release profile. Run repository
|
||||
verification plus a native acceptance pass before starting a release build.
|
||||
|
||||
Use the package scripts in `apps/mobile/package.json`; preserve platform signing
|
||||
and versioning. Building or uploading to a store is an external mutation: obtain
|
||||
explicit authorization for the requested target, then report the build artifact,
|
||||
version/build number, upload result, and read-back state.
|
||||
Before a build from the current upstream branch, fetch, require a clean worktree,
|
||||
and prove `HEAD` matches its upstream. Sauci tracks its native iOS and Android
|
||||
projects, so routine release builds must package those checked-in sources. Do not
|
||||
run `expo prebuild` first; native regeneration is a separate reviewed change.
|
||||
|
||||
Use the package scripts in `apps/mobile/package.json`; they create artifacts only.
|
||||
Never infer upload or submission authority from build authorization. Store upload
|
||||
uses a separately authorized command. Preserve platform signing and local EAS
|
||||
versioning. After a build, verify that tracked changes are limited to the expected
|
||||
version metadata, then independently read the artifact version and signature.
|
||||
Report the artifact, version/build number, upload result when authorized, and
|
||||
read-back state.
|
||||
@@ -91,7 +91,7 @@ android {
|
||||
applicationId 'com.sauci.app'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 45
|
||||
versionCode 46
|
||||
versionName "1.0.5"
|
||||
}
|
||||
signingConfigs {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"group.com.sauci.app"
|
||||
]
|
||||
},
|
||||
"buildNumber": "42",
|
||||
"buildNumber": "43",
|
||||
"usesAppleSignIn": true,
|
||||
"googleServicesFile": "./GoogleService-Info.plist",
|
||||
"infoPlist": {
|
||||
@@ -183,7 +183,7 @@
|
||||
"backgroundColor": "#1a1a2e"
|
||||
},
|
||||
"package": "com.sauci.app",
|
||||
"versionCode": 45,
|
||||
"versionCode": 46,
|
||||
"googleServicesFile": "./google-services.json",
|
||||
"permissions": [
|
||||
"RECEIVE_BOOT_COMPLETED",
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>42</string>
|
||||
<string>43</string>
|
||||
<key>GOOGLE_ANALYTICS_INAPURCHASE_COLLECTION_ENABLED</key>
|
||||
<false/>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -13,10 +13,11 @@
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:ci": "jest --runInBand --ci",
|
||||
"release:ios": "dotenv -e .env -- npx expo prebuild --platform ios && dotenv -e .env -- eas build --platform ios --profile production --local",
|
||||
"release:ios:preview": "dotenv -e .env -- npx expo prebuild --platform ios && dotenv -e .env -- eas build --platform ios --profile preview --local",
|
||||
"release:android": "dotenv -e .env -- npx expo prebuild --platform android && dotenv -e .env -- eas build --platform android --profile production --local && bundle exec fastlane android upload",
|
||||
"release:android:preview": "dotenv -e .env -- npx expo prebuild --platform android && dotenv -e .env -- eas build --platform android --profile preview --local"
|
||||
"release:ios": "dotenv -e .env -- eas build --platform ios --profile production --local",
|
||||
"release:ios:preview": "dotenv -e .env -- eas build --platform ios --profile preview --local",
|
||||
"release:android": "dotenv -e .env -- eas build --platform android --profile production --local",
|
||||
"release:android:preview": "dotenv -e .env -- eas build --platform android --profile preview --local",
|
||||
"release:android:upload": "bundle exec fastlane android upload"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bacons/apple-targets": "^0.1.6",
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<string>com.apple.widgetkit-extension</string>
|
||||
</dict>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>42</string>
|
||||
<string>43</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.5</string>
|
||||
</dict>
|
||||
|
||||
+38
-24
@@ -22,14 +22,30 @@ The `--local` flag runs the EAS build process on your machine instead of Expo's
|
||||
cd apps/mobile
|
||||
|
||||
# Android - produces .aab file (for Play Store)
|
||||
eas build --platform android --profile production --local
|
||||
npm run release:android
|
||||
|
||||
# iOS - produces .ipa file (for App Store)
|
||||
eas build --platform ios --profile production --local
|
||||
npm run release:ios
|
||||
```
|
||||
|
||||
Output files are placed in the current directory by default.
|
||||
|
||||
These scripts create signed artifacts only. They do not upload or submit them.
|
||||
Store mutation always requires a separate, explicitly authorized command.
|
||||
|
||||
### Source integrity
|
||||
|
||||
Sauci commits its native `ios/` and `android/` projects. Start a release from a
|
||||
clean worktree whose `HEAD` matches its fetched upstream, and do not run
|
||||
`expo prebuild` before a routine release build. EAS detects the native projects
|
||||
and packages the checked-in sources directly.
|
||||
|
||||
After the build, inspect `git diff`. Only the expected local EAS version fields
|
||||
may change: `app.json`, the platform native version file, and the iOS widget
|
||||
`Info.plist` for an iOS build. Any widget implementation, resource, or Xcode
|
||||
project rewrite means the source was regenerated and the artifact must be rebuilt
|
||||
from the clean tracked sources.
|
||||
|
||||
### Building an APK (for Testing)
|
||||
|
||||
The production profile creates an AAB (Android App Bundle), which can't be directly installed on devices. To build an APK for testing:
|
||||
@@ -116,13 +132,11 @@ export JAVA_HOME=$(/usr/libexec/java_home -v 17)
|
||||
cd android && ./gradlew --stop && cd ..
|
||||
```
|
||||
|
||||
**Clean build after dependency changes:**
|
||||
```bash
|
||||
# Remove native folders and rebuild
|
||||
rm -rf android ios
|
||||
npx expo prebuild --clean
|
||||
eas build --platform android --profile production --local
|
||||
```
|
||||
**Native regeneration after dependency changes:**
|
||||
|
||||
Treat `expo prebuild` as a source change, not a release step. Run it separately
|
||||
only when native dependencies intentionally changed, review the full native diff,
|
||||
verify it, and commit it before starting the release from a clean worktree.
|
||||
|
||||
**Out of memory:**
|
||||
```bash
|
||||
@@ -163,13 +177,10 @@ MYAPP_UPLOAD_KEY_PASSWORD=<password from EAS>
|
||||
|
||||
**Important:** Never commit these credentials to git.
|
||||
|
||||
### Step 3: Prebuild (if needed)
|
||||
### Step 3: Confirm tracked native sources
|
||||
|
||||
If the `android/` folder doesn't exist or native dependencies changed:
|
||||
|
||||
```bash
|
||||
npx expo prebuild --platform android --clean
|
||||
```
|
||||
The committed `android/` project is the release source. If native dependencies
|
||||
changed, regenerate and review that project as a separate change before release.
|
||||
|
||||
### Step 4: Build the Release Bundle
|
||||
|
||||
@@ -206,11 +217,10 @@ Expected SHA1: `C3:70:C5:8C:85:E5:B5:92:97:82:A9:B0:47:98:40:99:AC:C8:47:43`
|
||||
|
||||
## iOS Release Build
|
||||
|
||||
### Step 1: Prebuild (if needed)
|
||||
### Step 1: Confirm tracked native sources
|
||||
|
||||
```bash
|
||||
npx expo prebuild --platform ios --clean
|
||||
```
|
||||
The committed `ios/` project is the release source. If native dependencies
|
||||
changed, regenerate and review that project as a separate change before release.
|
||||
|
||||
### Step 2: Install Pods
|
||||
|
||||
@@ -245,9 +255,11 @@ cd ..
|
||||
|
||||
## Version Management
|
||||
|
||||
Version is managed by EAS with `appVersionSource: "remote"` in `eas.json`.
|
||||
Version is managed locally by EAS with `appVersionSource: "local"` in
|
||||
`apps/mobile/eas.json`. Production builds auto-increment the platform build
|
||||
number and synchronize the tracked native version file.
|
||||
|
||||
To sync or bump versions:
|
||||
To inspect or deliberately set versions:
|
||||
|
||||
```bash
|
||||
# Check current version
|
||||
@@ -258,7 +270,7 @@ eas build:version:set --platform android --version 1.0.1
|
||||
eas build:version:set --platform ios --version 1.0.1
|
||||
```
|
||||
|
||||
For local builds, you may need to update `app.json` version manually:
|
||||
The user-facing version remains in `app.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -291,12 +303,14 @@ If Play Store rejects with "signed with wrong key":
|
||||
### Build Failures After Dependency Changes
|
||||
|
||||
```bash
|
||||
# Clean and rebuild
|
||||
# Clean the existing tracked Android project and rebuild it
|
||||
cd android && ./gradlew clean && cd ..
|
||||
npx expo prebuild --platform android --clean
|
||||
cd android && ./gradlew bundleRelease
|
||||
```
|
||||
|
||||
If native regeneration is genuinely required, do it as a separate reviewed and
|
||||
committed source change before running the release build.
|
||||
|
||||
### iOS Signing Issues
|
||||
|
||||
Ensure your Apple Developer certificates and provisioning profiles are up to date in Xcode:
|
||||
|
||||
@@ -12,7 +12,7 @@ name: Standalone-backend build reaches the Auth login screen
|
||||
- tapOn:
|
||||
point: "94%,44%"
|
||||
- extendedWaitUntil:
|
||||
visible: "Explore intimacy together"
|
||||
visible: "Password"
|
||||
timeout: 30000
|
||||
- assertVisible: "Sauci"
|
||||
- assertVisible: "Password"
|
||||
|
||||
@@ -12,7 +12,7 @@ name: Hosted Auth fixture reaches the standalone API-backed app
|
||||
- tapOn:
|
||||
point: "94%,44%"
|
||||
- extendedWaitUntil:
|
||||
visible: "Explore intimacy together"
|
||||
visible: "Password"
|
||||
timeout: 30000
|
||||
- tapOn: "Password"
|
||||
- tapOn: "Enter your email"
|
||||
|
||||
@@ -37,6 +37,27 @@ for (const file of allFiles) {
|
||||
|
||||
if (!scoped) {
|
||||
const packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
|
||||
const mobilePackageFile = path.join(root, 'apps', 'mobile', 'package.json');
|
||||
const mobilePackageJson = JSON.parse(await readFile(mobilePackageFile, 'utf8'));
|
||||
const releaseBuildScripts = Object.entries(mobilePackageJson.scripts ?? {})
|
||||
.filter(([name]) => /^release:(ios|android)(:preview)?$/.test(name));
|
||||
for (const [name, command] of releaseBuildScripts) {
|
||||
if (/\bexpo\s+prebuild\b/.test(command)) {
|
||||
fail(
|
||||
mobilePackageFile,
|
||||
`${name} regenerates tracked native projects before packaging`,
|
||||
'keep native regeneration as an explicit reviewed change and let the release script build the checked-in native sources',
|
||||
);
|
||||
}
|
||||
if (/\b(?:fastlane|eas)\b[^\n]*(?:upload|submit)|\bupload_to_/.test(command)) {
|
||||
fail(
|
||||
mobilePackageFile,
|
||||
`${name} combines build creation with store mutation`,
|
||||
'keep build scripts artifact-only and expose upload or submission as a separately authorized command',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
for (const app of (await readdir(path.join(root, 'apps'), { withFileTypes: true })).filter((entry) => entry.isDirectory())) {
|
||||
const packageFile = path.join(root, 'apps', app.name, 'package.json');
|
||||
if (!(await exists(packageFile))) continue;
|
||||
|
||||
Reference in new issue
Block a user