chore(watch): automate SentrySPM integration via post-prebuild script

This commit is contained in:
2026-08-08 23:21:55 +02:00
parent d6543cd8ca
commit 284fd22c13
4 changed files with 220 additions and 68 deletions
+4 -2
View File
@@ -8,13 +8,14 @@ Swimming workout tracker with Apple Watch and WearOS companion apps. Expo 57 + R
npx expo start # Dev server npx expo start # Dev server
npx expo run:ios # Build & run on iOS npx expo run:ios # Build & run on iOS
npx expo prebuild -p ios # Regenerate ios/ dir (safe — targets/ persists) npx expo prebuild -p ios # Regenerate ios/ dir (safe — targets/ persists)
npx expo prebuild -p ios && node scripts/add-sentry-watch.js # Prebuild + add SentrySPM to watch target
npx drizzle-kit generate # Regenerate SQL migrations after schema changes npx drizzle-kit generate # Regenerate SQL migrations after schema changes
``` ```
**WearOS:** **WearOS:**
```bash ```bash
./scripts/build-wearos.sh debug # Build debug APK ./scripts/build-wearos.sh debug # Build debug APK (reads version from app.json)
./scripts/build-wearos.sh release # Build release APK ./scripts/build-wearos.sh release # Build release APK (reads version from app.json)
./scripts/generate-wearos-icon.sh assets/icon.png # Generate launcher icons ./scripts/generate-wearos-icon.sh assets/icon.png # Generate launcher icons
``` ```
@@ -58,6 +59,7 @@ targets/wearos/ # WearOS app (Kotlin/Compose) — survives prebuil
- WCSession for real-time messaging, `transferUserInfo` for reliable offline sync - WCSession for real-time messaging, `transferUserInfo` for reliable offline sync
- HealthKit for swim workout tracking (indoor swimming) - HealthKit for swim workout tracking (indoor swimming)
- Swift types `StoredWorkout`, `StoredSet`, `CompletedLap` in `WorkoutManager.swift` - Swift types `StoredWorkout`, `StoredSet`, `CompletedLap` in `WorkoutManager.swift`
- SentrySPM: `@bacons/apple-targets` doesn't support `swiftPackages` yet. Run `node scripts/add-sentry-watch.js` after `expo prebuild` to add it automatically. For EAS builds, this runs automatically via the `eas-build-post-install` hook in package.json.
## WearOS Target ## WearOS Target
+59 -58
View File
@@ -1,60 +1,61 @@
{ {
"name": "swimbuddy", "name": "swimbuddy",
"version": "1.0.0", "version": "1.0.0",
"main": "index.ts", "main": "index.ts",
"dependencies": { "dependencies": {
"@bacons/apple-targets": "^5.0.0", "@bacons/apple-targets": "^5.0.0",
"@expo/vector-icons": "^15.0.2", "@expo/vector-icons": "^15.0.2",
"@react-navigation/bottom-tabs": "^7.18.14", "@react-navigation/bottom-tabs": "^7.18.14",
"@react-navigation/native": "^7.3.14", "@react-navigation/native": "^7.3.14",
"@react-navigation/native-stack": "^7.18.6", "@react-navigation/native-stack": "^7.18.6",
"@sentry/react-native": "^8.22.0", "@sentry/react-native": "^8.22.0",
"@tailwindcss/postcss": "^4.3.3", "@tailwindcss/postcss": "^4.3.3",
"drizzle-orm": "^0.45.2", "drizzle-orm": "^0.45.2",
"expo": "~57.0.10", "expo": "~57.0.10",
"expo-application": "~57.0.2", "expo-application": "~57.0.2",
"expo-localization": "~57.0.1", "expo-localization": "~57.0.1",
"expo-sqlite": "~57.0.1", "expo-sqlite": "~57.0.1",
"expo-status-bar": "~57.0.1", "expo-status-bar": "~57.0.1",
"expo-system-ui": "~57.0.2", "expo-system-ui": "~57.0.2",
"expo-updates": "^57.0.12", "expo-updates": "^57.0.12",
"i18next": "^26.3.6", "i18next": "^26.3.6",
"nativewind": "^5.0.0-preview.4", "nativewind": "^5.0.0-preview.4",
"react": "19.2.3", "react": "19.2.3",
"react-i18next": "^17.0.11", "react-i18next": "^17.0.11",
"react-native": "0.86.2", "react-native": "0.86.2",
"react-native-css": "^3.0.7", "react-native-css": "^3.0.7",
"react-native-gesture-handler": "~2.32.0", "react-native-gesture-handler": "~2.32.0",
"react-native-mmkv": "^4.3.2", "react-native-mmkv": "^4.3.2",
"react-native-reanimated": "4.5.1", "react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0", "react-native-safe-area-context": "~5.7.0",
"react-native-screens": "~4.26.0" "react-native-screens": "~4.26.0"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "~19.2.2", "@types/react": "~19.2.2",
"babel-plugin-inline-import": "^3.0.0", "babel-plugin-inline-import": "^3.0.0",
"drizzle-kit": "^0.31.10", "drizzle-kit": "^0.31.10",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "~6.0.3" "typescript": "~6.0.3"
}, },
"scripts": { "scripts": {
"start": "expo start", "start": "expo start",
"android": "expo run:android", "android": "expo run:android",
"ios": "expo run:ios", "ios": "expo run:ios",
"web": "expo start --web", "web": "expo start --web",
"db:generate": "drizzle-kit generate", "db:generate": "drizzle-kit generate",
"prebuild": "expo prebuild", "prebuild": "expo prebuild && npm run eas-build-post-install",
"prebuild:clean": "expo prebuild --clean", "prebuild:clean": "expo prebuild --clean && npm run eas-build-post-install",
"build:wearos:debug": "./scripts/build-wearos.sh debug", "eas-build-post-install": "node scripts/add-sentry-watch.js",
"build:wearos:release": "./scripts/build-wearos.sh release", "build:wearos:debug": "./scripts/build-wearos.sh debug",
"deploy:ios": "eas build --platform ios --profile ${1:-preview} && eas submit --platform ios --profile ${1:-preview}", "build:wearos:release": "./scripts/build-wearos.sh release",
"deploy:android": "eas build --platform android --profile ${1:-preview} && eas submit --platform android --profile ${1:-preview}", "deploy:ios": "eas build --platform ios --profile ${1:-preview} && eas submit --platform ios --profile ${1:-preview}",
"deploy:wearos": "./scripts/build-wearos.sh release ${2:-}", "deploy:android": "eas build --platform android --profile ${1:-preview} && eas submit --platform android --profile ${1:-preview}",
"update": "eas update --branch ${1:-preview}", "deploy:wearos": "./scripts/build-wearos.sh release ${2:-}",
"update:production": "eas update --branch production" "update": "eas update --branch ${1:-preview}",
}, "update:production": "eas update --branch production"
"private": true, },
"overrides": { "private": true,
"lightningcss": "1.30.1" "overrides": {
} "lightningcss": "1.30.1"
}
} }
+157
View File
@@ -0,0 +1,157 @@
#!/usr/bin/env node
/**
* Post-prebuild script: adds SentrySPM Swift Package to the watch target.
*
* Run after `expo prebuild`:
* node scripts/add-sentry-watch.js
*/
const fs = require("fs");
const path = require("path");
const SENTRY_REPO_URL = "https://github.com/getsentry/sentry-cocoa.git";
const SENTRY_MIN_VERSION = "9.25.0";
const SENTRY_PRODUCT = "Sentry";
function genUuid() {
const hex = "0123456789ABCDEF";
let id = "";
for (let i = 0; i < 24; i++) id += hex[Math.floor(Math.random() * 16)];
return id;
}
const projectRoot = path.resolve(__dirname, "..");
const pbxprojPath = path.join(
projectRoot,
"ios",
"SwimBuddy.xcodeproj",
"project.pbxproj"
);
if (!fs.existsSync(pbxprojPath)) {
console.error("project.pbxproj not found. Run expo prebuild first.");
process.exit(1);
}
let content = fs.readFileSync(pbxprojPath, "utf-8");
// Already added?
if (content.includes(SENTRY_REPO_URL)) {
console.log("SentrySPM already present.");
process.exit(0);
}
// ── Find watch target UUID ──
const watchTargetMatch = content.match(
/(\w{24})\s*\/\* SwimBuddy \*\/\s*=\s*\{\s*\n\s*isa = PBXNativeTarget;\s*\n\s*buildConfigurationList = XX792EB48D81CD7C6C71DCXX/
);
if (!watchTargetMatch) {
console.error("Watch target not found");
process.exit(1);
}
const watchTargetUuid = watchTargetMatch[1];
console.log(`Watch target: ${watchTargetUuid}`);
// ── Find watch target's Frameworks build phase UUID ──
const watchTargetBlock = content.match(
new RegExp(
`${watchTargetUuid}\\s*\\/\\* SwimBuddy \\*\\/\\s*=\\s*\\{[\\s\\S]*?buildPhases\\s*=\\s*\\(([\\s\\S]*?)\\);`
)
);
if (!watchTargetBlock) {
console.error("Cannot parse watch target buildPhases");
process.exit(1);
}
const fwPhaseMatch = watchTargetBlock[1].match(/(\w{24})\s*\/\* Frameworks \*\//);
if (!fwPhaseMatch) {
console.error("Watch Frameworks phase not found");
process.exit(1);
}
const watchFwPhaseId = fwPhaseMatch[1];
console.log(`Watch Frameworks phase: ${watchFwPhaseId}`);
// ── Generate UUIDs ──
const pkgRefId = genUuid();
const prodDepId = genUuid();
const buildFileId = genUuid();
// ── 1. PBXBuildFile (single-line format) ──
const buildFileLine = `\t\t${buildFileId} /* ${SENTRY_PRODUCT} in Frameworks */ = {isa = PBXBuildFile; productRef = ${prodDepId} /* ${SENTRY_PRODUCT} */; };`;
content = content.replace(
/\/\* End PBXBuildFile section \*\//,
`${buildFileLine}\n/* End PBXBuildFile section */`
);
// ── 2. XCRemoteSwiftPackageReference section ──
const pkgRefLine = `\t\t${pkgRefId} /* XCRemoteSwiftPackageReference "sentry-cocoa" */ = {isa = XCRemoteSwiftPackageReference; repositoryURL = "${SENTRY_REPO_URL}"; requirement = {kind = "upToNextMajorVersion"; minimumVersion = "${SENTRY_MIN_VERSION}"; }; };`;
if (!content.includes("/* Begin XCRemoteSwiftPackageReference section */")) {
content = content.replace(
/\/\* End PBXProject section \*\//,
`/* Begin XCRemoteSwiftPackageReference section */\n${pkgRefLine}\n/* End XCRemoteSwiftPackageReference section */\n/* End PBXProject section */`
);
} else {
content = content.replace(
/\/\* End XCRemoteSwiftPackageReference section \*\//,
`${pkgRefLine}\n/* End XCRemoteSwiftPackageReference section */`
);
}
// ── 3. XCSwiftPackageProductDependency section ──
const prodDepLine = `\t\t${prodDepId} /* ${SENTRY_PRODUCT} */ = {isa = XCSwiftPackageProductDependency; package = ${pkgRefId} /* XCRemoteSwiftPackageReference "sentry-cocoa" */; productName = ${SENTRY_PRODUCT}; };`;
if (!content.includes("/* Begin XCSwiftPackageProductDependency section */")) {
content = content.replace(
/\/\* End XCRemoteSwiftPackageReference section \*\//,
`/* Begin XCSwiftPackageProductDependency section */\n${prodDepLine}\n/* End XCSwiftPackageProductDependency section */\n/* End XCRemoteSwiftPackageReference section */`
);
} else {
content = content.replace(
/\/\* End XCSwiftPackageProductDependency section \*\//,
`${prodDepLine}\n/* End XCSwiftPackageProductDependency section */`
);
}
// ── 4. Add to watch Frameworks build phase ──
const fwPhasePattern = new RegExp(
`(${watchFwPhaseId}\\s*\\/\\* Frameworks \\*\\/ = \\{[\\s\\S]*?files = \\()[\\s\\S]*?(\\);[\\s\\S]*?runOnlyForDeploymentPostprocessing)`
);
const fwMatch = content.match(fwPhasePattern);
if (fwMatch) {
const insertLine = `\n\t\t\t\t${buildFileId} /* ${SENTRY_PRODUCT} in Frameworks */,`;
content = content.replace(fwPhasePattern, `$1${fwMatch[0].includes(buildFileId) ? "" : insertLine}$2`);
} else {
console.error("Cannot insert into watch Frameworks build phase");
process.exit(1);
}
// ── 5. packageReferences in PBXProject ──
const pkgRefComment = `${pkgRefId} /* XCRemoteSwiftPackageReference "sentry-cocoa" */`;
if (!content.includes("packageReferences = (")) {
content = content.replace(
/(targets\s*=\s*\([^)]*\);)/,
`$1\n\t\t\tpackageReferences = (\n\t\t\t\t${pkgRefComment},\n\t\t\t);`
);
} else {
content = content.replace(
/(packageReferences\s*=\s*\()/,
`$1\n\t\t\t\t${pkgRefComment},`
);
}
// ── 6. packageProductDependencies on watch target ──
const prodDepComment = `${prodDepId} /* ${SENTRY_PRODUCT} */`;
// Find the watch target block and check if packageProductDependencies exists
const wtPattern = new RegExp(
`(${watchTargetUuid}\\s*\\/\\* SwimBuddy \\*\\/ = \\{[\\s\\S]*?fileSystemSynchronizedGroups = \\([\\s\\S]*?\\);[\\s\\S]*?)(name = )`
);
const wtMatch = content.match(wtPattern);
if (wtMatch && !content.includes("packageProductDependencies")) {
content = content.replace(
wtPattern,
`$1\n\t\t\tpackageProductDependencies = (\n\t\t\t\t${prodDepComment},\n\t\t\t);\n\t\t$2`
);
}
fs.writeFileSync(pbxprojPath, content);
console.log("SentrySPM added to watch target successfully.");
-8
View File
@@ -5,14 +5,6 @@ module.exports = (config) => ({
icon: "../../assets/icon.png", icon: "../../assets/icon.png",
deploymentTarget: "10.0", deploymentTarget: "10.0",
frameworks: ["SwiftUI"], frameworks: ["SwiftUI"],
swiftPackages: [
{
name: "SentrySPM",
repositoryURL: "https://github.com/getsentry/sentry-cocoa.git",
kind: "upToNextMajorVersion",
minimumVersion: "9.25.0",
},
],
entitlements: { entitlements: {
"com.apple.security.application-groups": "com.apple.security.application-groups":
config.ios?.entitlements?.[ config.ios?.entitlements?.[