chore(watch): automate SentrySPM integration via post-prebuild script
This commit is contained in:
@@ -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
@@ -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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.");
|
||||||
@@ -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?.[
|
||||||
|
|||||||
Reference in New Issue
Block a user