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
+177
-56
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
|
scoped instructions. Confirm target platform and release profile. Run repository
|
||||||
verification plus a native acceptance pass before starting a release build.
|
verification plus a native acceptance pass before starting a release build.
|
||||||
|
|
||||||
Use the package scripts in `apps/mobile/package.json`; preserve platform signing
|
Before a build from the current upstream branch, fetch, require a clean worktree,
|
||||||
and versioning. Building or uploading to a store is an external mutation: obtain
|
and prove `HEAD` matches its upstream. Sauci tracks its native iOS and Android
|
||||||
explicit authorization for the requested target, then report the build artifact,
|
projects, so routine release builds must package those checked-in sources. Do not
|
||||||
version/build number, upload result, and read-back state.
|
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'
|
applicationId 'com.sauci.app'
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 45
|
versionCode 46
|
||||||
versionName "1.0.5"
|
versionName "1.0.5"
|
||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
"group.com.sauci.app"
|
"group.com.sauci.app"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"buildNumber": "42",
|
"buildNumber": "43",
|
||||||
"usesAppleSignIn": true,
|
"usesAppleSignIn": true,
|
||||||
"googleServicesFile": "./GoogleService-Info.plist",
|
"googleServicesFile": "./GoogleService-Info.plist",
|
||||||
"infoPlist": {
|
"infoPlist": {
|
||||||
@@ -183,7 +183,7 @@
|
|||||||
"backgroundColor": "#1a1a2e"
|
"backgroundColor": "#1a1a2e"
|
||||||
},
|
},
|
||||||
"package": "com.sauci.app",
|
"package": "com.sauci.app",
|
||||||
"versionCode": 45,
|
"versionCode": 46,
|
||||||
"googleServicesFile": "./google-services.json",
|
"googleServicesFile": "./google-services.json",
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"RECEIVE_BOOT_COMPLETED",
|
"RECEIVE_BOOT_COMPLETED",
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>42</string>
|
<string>43</string>
|
||||||
<key>GOOGLE_ANALYTICS_INAPURCHASE_COLLECTION_ENABLED</key>
|
<key>GOOGLE_ANALYTICS_INAPURCHASE_COLLECTION_ENABLED</key>
|
||||||
<false/>
|
<false/>
|
||||||
<key>LSMinimumSystemVersion</key>
|
<key>LSMinimumSystemVersion</key>
|
||||||
|
|||||||
@@ -13,10 +13,11 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
"test:ci": "jest --runInBand --ci",
|
"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": "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:ios:preview": "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": "dotenv -e .env -- eas build --platform android --profile production --local",
|
||||||
"release:android:preview": "dotenv -e .env -- npx expo prebuild --platform android && dotenv -e .env -- eas build --platform android --profile preview --local"
|
"release:android:preview": "dotenv -e .env -- eas build --platform android --profile preview --local",
|
||||||
|
"release:android:upload": "bundle exec fastlane android upload"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bacons/apple-targets": "^0.1.6",
|
"@bacons/apple-targets": "^0.1.6",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<string>com.apple.widgetkit-extension</string>
|
<string>com.apple.widgetkit-extension</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>42</string>
|
<string>43</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.0.5</string>
|
<string>1.0.5</string>
|
||||||
</dict>
|
</dict>
|
||||||
|
|||||||
+55
-41
@@ -18,17 +18,33 @@ The `--local` flag runs the EAS build process on your machine instead of Expo's
|
|||||||
|
|
||||||
### Quick Start
|
### Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd apps/mobile
|
cd apps/mobile
|
||||||
|
|
||||||
# Android - produces .aab file (for Play Store)
|
# 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)
|
# 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.
|
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)
|
### Building an APK (for Testing)
|
||||||
|
|
||||||
@@ -116,13 +132,11 @@ export JAVA_HOME=$(/usr/libexec/java_home -v 17)
|
|||||||
cd android && ./gradlew --stop && cd ..
|
cd android && ./gradlew --stop && cd ..
|
||||||
```
|
```
|
||||||
|
|
||||||
**Clean build after dependency changes:**
|
**Native regeneration after dependency changes:**
|
||||||
```bash
|
|
||||||
# Remove native folders and rebuild
|
Treat `expo prebuild` as a source change, not a release step. Run it separately
|
||||||
rm -rf android ios
|
only when native dependencies intentionally changed, review the full native diff,
|
||||||
npx expo prebuild --clean
|
verify it, and commit it before starting the release from a clean worktree.
|
||||||
eas build --platform android --profile production --local
|
|
||||||
```
|
|
||||||
|
|
||||||
**Out of memory:**
|
**Out of memory:**
|
||||||
```bash
|
```bash
|
||||||
@@ -163,13 +177,10 @@ MYAPP_UPLOAD_KEY_PASSWORD=<password from EAS>
|
|||||||
|
|
||||||
**Important:** Never commit these credentials to git.
|
**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:
|
The committed `android/` project is the release source. If native dependencies
|
||||||
|
changed, regenerate and review that project as a separate change before release.
|
||||||
```bash
|
|
||||||
npx expo prebuild --platform android --clean
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Build the Release Bundle
|
### 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
|
## iOS Release Build
|
||||||
|
|
||||||
### Step 1: Prebuild (if needed)
|
### Step 1: Confirm tracked native sources
|
||||||
|
|
||||||
```bash
|
The committed `ios/` project is the release source. If native dependencies
|
||||||
npx expo prebuild --platform ios --clean
|
changed, regenerate and review that project as a separate change before release.
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Install Pods
|
### Step 2: Install Pods
|
||||||
|
|
||||||
@@ -245,9 +255,11 @@ cd ..
|
|||||||
|
|
||||||
## Version Management
|
## 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
|
```bash
|
||||||
# Check current version
|
# 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
|
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
|
```json
|
||||||
{
|
{
|
||||||
@@ -288,14 +300,16 @@ If Play Store rejects with "signed with wrong key":
|
|||||||
3. Verify fingerprint matches expected SHA1
|
3. Verify fingerprint matches expected SHA1
|
||||||
4. Replace your local keystore file
|
4. Replace your local keystore file
|
||||||
|
|
||||||
### Build Failures After Dependency Changes
|
### Build Failures After Dependency Changes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clean and rebuild
|
# Clean the existing tracked Android project and rebuild it
|
||||||
cd android && ./gradlew clean && cd ..
|
cd android && ./gradlew clean && cd ..
|
||||||
npx expo prebuild --platform android --clean
|
cd android && ./gradlew bundleRelease
|
||||||
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
|
### iOS Signing Issues
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ name: Standalone-backend build reaches the Auth login screen
|
|||||||
- tapOn:
|
- tapOn:
|
||||||
point: "94%,44%"
|
point: "94%,44%"
|
||||||
- extendedWaitUntil:
|
- extendedWaitUntil:
|
||||||
visible: "Explore intimacy together"
|
visible: "Password"
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
- assertVisible: "Sauci"
|
- assertVisible: "Sauci"
|
||||||
- assertVisible: "Password"
|
- assertVisible: "Password"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ name: Hosted Auth fixture reaches the standalone API-backed app
|
|||||||
- tapOn:
|
- tapOn:
|
||||||
point: "94%,44%"
|
point: "94%,44%"
|
||||||
- extendedWaitUntil:
|
- extendedWaitUntil:
|
||||||
visible: "Explore intimacy together"
|
visible: "Password"
|
||||||
timeout: 30000
|
timeout: 30000
|
||||||
- tapOn: "Password"
|
- tapOn: "Password"
|
||||||
- tapOn: "Enter your email"
|
- tapOn: "Enter your email"
|
||||||
|
|||||||
@@ -37,6 +37,27 @@ for (const file of allFiles) {
|
|||||||
|
|
||||||
if (!scoped) {
|
if (!scoped) {
|
||||||
const packageJson = JSON.parse(await readFile(path.join(root, 'package.json'), 'utf8'));
|
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())) {
|
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');
|
const packageFile = path.join(root, 'apps', app.name, 'package.json');
|
||||||
if (!(await exists(packageFile))) continue;
|
if (!(await exists(packageFile))) continue;
|
||||||
|
|||||||
Reference in new issue
Block a user