diff --git a/context/skills/error-tracking-upload-source-maps/description.md b/context/skills/error-tracking-upload-source-maps/description.md index 310e615e..5bbaaa3e 100644 --- a/context/skills/error-tracking-upload-source-maps/description.md +++ b/context/skills/error-tracking-upload-source-maps/description.md @@ -37,6 +37,13 @@ Wire source map generation, chunk-ID injection, and upload into your **productio #### Examples - **Node / tsc** Emit maps with embedded sources by setting both in `tsconfig.json`: `"sourceMap": true` and `"inlineSources": true`. Then run `posthog-cli sourcemap process` against the build output dir as a post-build step — it injects chunk IDs and uploads in one pass, and needs the upload credentials (see "Make credentials available at build time"). - **Vite / Webpack / Rollup** Prefer the bundler plugin from the reference over hand-rolling the CLI — it injects and uploads in one pass. Make sure the bundler is configured to emit source maps. +- **iOS (Xcode)** iOS uploads **dSYM debug symbols**, not source maps. Required target changes: + 1. `DEBUG_INFORMATION_FORMAT = dwarf-with-dsym` for Release. + 2. `ENABLE_USER_SCRIPT_SANDBOXING = NO`. + 3. A Run Script phase, ordered last, with `$(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Resources/DWARF/$(EXECUTABLE_NAME)` in its Input Files, calling the SDK's bundled script — do not hand-roll the upload: + - SPM: `POSTHOG_INCLUDE_SOURCE=1 POSTHOG_CLI_DOTENV_FILE="${SRCROOT}/.env" "${BUILD_DIR%/Build/*}/SourcePackages/checkouts/posthog-ios/build-tools/upload-symbols.sh"` + - CocoaPods: `POSTHOG_INCLUDE_SOURCE=1 POSTHOG_CLI_DOTENV_FILE="${SRCROOT}/.env" "${PODS_ROOT}/PostHog/build-tools/upload-symbols.sh"` + Copy the invocation verbatim — the `POSTHOG_INCLUDE_SOURCE=1` and `POSTHOG_CLI_DOTENV_FILE` prefixes HAVE to be there. This needs a recent `posthog-cli` (older ones silently ignore `POSTHOG_CLI_DOTENV_FILE`); the PostHog wizard installs it for you, so do not run `npm install -g` yourself. - **Next.js / Nuxt / Angular** Use the framework's documented source-map upload integration from the reference; these own their build pipeline, so configure upload there rather than bolting on a separate CLI step. - **React Native / Android / iOS / Flutter** You upload platform debug symbols (Hermes maps, ProGuard/R8 mappings, dSYMs) rather than plain `.js.map` files — follow the platform reference for the exact build hook. @@ -50,6 +57,7 @@ The upload credentials must be readable **by the build pipeline at build time**, - **Does NOT auto-load `.env`**: Rollup, plain webpack, and plain Node scripts. Load it explicitly — add `dotenv` (`require('dotenv').config()`, or `import 'dotenv/config'` for ESM) at the top of the bundler/config file. - **Separate-process gotcha**: if `posthog-cli sourcemap process` runs as its own `package.json` step (after the bundler), the CLI call is a **separate child process** and will *not* see env vars a loader set inside the bundler config. Point the CLI at the file directly: `posthog-cli --dotenv-file sourcemap process …` (the flag goes before the subcommand). - **`process` authenticates from the start.** `posthog-cli sourcemap process` resolves credentials before it injects chunk IDs — the inject phase needs them too, not just the upload — and fails without them. Always pass `--dotenv-file` to the `process` invocation. (It can still appear to work if the developer once ran `posthog-cli login`, which leaves credentials in `~/.posthog` — that won't exist in CI or on a teammate's machine.) +- **iOS / Xcode** No loader — the Run Script phase's `POSTHOG_CLI_DOTENV_FILE="${SRCROOT}/.env"` prefix points posthog-cli at the gitignored `.env`. `POSTHOG_CLI_HOST` is the API host (`https://us.posthog.com`), never the `*.i.posthog.com` ingestion host. #### Examples - **Next.js / Nuxt** Auto-load `.env` at build time; put the vars there and you're done. @@ -70,6 +78,7 @@ The upload credentials must be readable **by the build pipeline at build time**, ```json "build": "tsc && posthog-cli --dotenv-file .env sourcemap process --directory ./dist --release-name my-app" ``` +- **iOS (Xcode / posthog-cli)** A gitignored `.env` next to the `.xcodeproj` — the Run Script invocation's `POSTHOG_CLI_DOTENV_FILE="${SRCROOT}/.env"` prefix hands it to posthog-cli. No Xcode project wiring beyond the Run Script phase. In CI, set the `POSTHOG_CLI_*` values as job secrets instead — no `.env` on the runner. ### Write credentials to the env file @@ -96,7 +105,7 @@ Resolve two concrete commands for this project: the production **build** command - **Vite** Build: `npm run build`. Run: `npm run preview`. - **Plain Node** Build: `npm run build`. Run: `node ` — read package.json `main`/`bin` and the build output dir to name the real file (e.g. `node dist/index.js`). - **Android** Build: `./gradlew assembleRelease`. Run: launch on a device/emulator (Android Studio, or `./gradlew installRelease`). -- **iOS** Build: `xcodebuild …`. Run: launch the scheme on a simulator/device (Xcode). +- **iOS** Local build + run are one step: Xcode Run with Build Configuration = Release. `xcodebuild` is CI-only. - **Flutter** Build: `flutter build apk` / `flutter build ios`. Run: `flutter run`. - **React Native** Run: `npx react-native run-ios` / `npx react-native run-android`. @@ -267,7 +276,7 @@ Optionally add a temporary, clearly-labeled affordance that captures one test ex - The handler must call the SDK's exception-capture method **directly** — do **not** `throw`. Throwing depends on the global error handler and shows a dev overlay; a direct capture is deterministic across platforms. - Pass a single Error (or platform-equivalent throwable). No custom message beyond the Error, no extra properties, no second argument — the Error's stack trace is what gets resolved. - Use distinctive copy on the trigger (button label / route path) so the resulting event is easy to find in the UI. -- Read any file before editing it and capture its exact contents; after testing, restore every touched file and re-read to confirm nothing is left behind. Never leave the affordance in place — even if the test "didn't work", revert first. +- Read any file before editing it and capture its exact contents; after testing, restore every file the affordance touched — the affordance only, leave the upload and credential wiring in place — and re-read to confirm nothing is left behind. Never leave the affordance in place — even if the test "didn't work", revert first. - The upload only happens on the *production build*: build, run, trigger the error, then confirm the stack trace in Error Tracking points at real source files, not minified bundle paths. #### Examples @@ -275,7 +284,16 @@ Optionally add a temporary, clearly-labeled affordance that captures one test ex - **Node.js** Add a temporary route (e.g. `GET /__posthog-test-error`) on the existing server that calls `posthog.captureException(new Error("PostHog source maps test"))` and returns 200. With no HTTP layer, add the capture to the existing entry script where the client is initialised rather than creating a new file. Tell the user the exact command/URL to hit. - **React Native** Add a visible `Button` on the main screen whose onPress calls `posthog.captureException(new Error("PostHog source maps test"))`. - **Android (Kotlin)** Add a `Button` on the launcher Activity whose onClick captures a `Throwable` via the PostHog SDK, per the reference. -- **iOS (Swift)** Add a `UIButton` on the root view controller whose action captures an `NSError` via the PostHog SDK, per the reference. +- **iOS (Swift)** `Button` on the root view (SwiftUI) or `UIButton` on the root view controller (UIKit), handler: + ```swift + do { + throw NSError(domain: "PostHogSourceMapTest", code: 1, + userInfo: [NSLocalizedDescriptionKey: "Source map upload test error"]) + } catch { + PostHogSDK.shared.captureException(error) + } + ``` + (`capture()` takes an event-name String, not an Error.) Test flow — give the user these steps verbatim, everything happens in Xcode (no `xcodebuild`): 1) In Xcode: Edit Scheme ▸ Run ▸ Build Configuration ▸ Release, then Run — the Release build uploads dSYMs automatically. 2) Tap the "" button in the app. It's an event, not a crash — no debugger-detach or relaunch steps. - **Flutter** Add an `ElevatedButton` on the home widget whose onPressed calls `Posthog().captureException(Exception("PostHog source maps test"))`. ### Verify and hand off diff --git a/scripts/lib/tests/error-tracking-upload-source-maps.test.js b/scripts/lib/tests/error-tracking-upload-source-maps.test.js new file mode 100644 index 00000000..987afd96 --- /dev/null +++ b/scripts/lib/tests/error-tracking-upload-source-maps.test.js @@ -0,0 +1,30 @@ +import { describe, expect, it } from 'vitest'; +import { join } from 'path'; + +import { expandSkillGroups, loadSkillsConfig } from '../skill-generator.js'; + +const CONFIG_DIR = join(process.cwd(), 'context'); + +describe('error-tracking-upload-source-maps iOS variant', () => { + it('expands to the skill contract consumed by the wizard', () => { + const config = loadSkillsConfig(CONFIG_DIR); + const skills = expandSkillGroups(config, CONFIG_DIR); + const ios = skills.find((skill) => skill.id === 'error-tracking-upload-source-maps-ios'); + + expect(ios).toMatchObject({ + id: 'error-tracking-upload-source-maps-ios', + _shortId: 'ios', + _category: 'error-tracking-upload-source-maps', + _group: 'error-tracking-upload-source-maps', + display_name: 'iOS', + description: 'Upload dSYM debug symbols to PostHog Error Tracking for iOS', + tags: ['error-tracking', 'source-maps', 'ios', 'swift'], + docs_urls: ['https://posthog.com/docs/error-tracking/upload-source-maps/ios.md'], + _sharedDocs: [ + 'https://posthog.com/docs/error-tracking/upload-source-maps.md', + 'https://posthog.com/docs/error-tracking/upload-source-maps/cli.md', + ], + _cli: null, + }); + }); +});