diff --git a/CHANGELOG.md b/CHANGELOG.md index 73e3d841..c465df6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## Unreleased + +### Features + +#### Apple + +- feat(apple): Dynamically fetch latest Sentry Cocoa SDK version for SPM instead of hardcoding +- feat(apple): Add `--fix` flag for diagnosing and repairing Apple integrations +- fix(apple): Remove `.experimental` namespace from `enableLogs` option in code snippets +- ref(apple): Add CI/CD guidance and `SENTRY_SKIP_DSYM_UPLOAD` option for dSYM upload build phase +- ref(apple): Add info message about expected Sentry.framework dSYM warning in App Store uploads + ## 6.12.0 ### Features diff --git a/bin.ts b/bin.ts index 6692c1e4..d66763fb 100644 --- a/bin.ts +++ b/bin.ts @@ -154,6 +154,12 @@ const argv = yargs(hideBin(process.argv), process.cwd()) describe: 'Ignore git changes in the project', type: 'boolean', }, + fix: { + default: false, + describe: + 'Run diagnostic checks and fix issues with your Sentry integration', + type: 'boolean', + }, spotlight: { default: false, describe: diff --git a/src/apple/apple-wizard.ts b/src/apple/apple-wizard.ts index 69e5c6ba..0a599fea 100644 --- a/src/apple/apple-wizard.ts +++ b/src/apple/apple-wizard.ts @@ -10,6 +10,7 @@ import { printWelcome, } from '../utils/clack'; import { offerProjectScopedMcpConfig } from '../utils/clack/mcp-config'; +import { fetchSdkVersion } from '../utils/release-registry'; import { checkInstalledCLI } from './check-installed-cli'; import { configureFastlane } from './configure-fastlane'; import { configurePackageManager } from './configure-package-manager'; @@ -81,12 +82,18 @@ async function runAppleWizardWithTelementry( projectDir, }); + // Step - Fetch latest SDK version for SPM + const sdkVersion = shouldUseSPM + ? await fetchSdkVersion('sentry.cocoa') + : undefined; + // Step - Configure Xcode Project configureXcodeProject({ xcProject, project: selectedProject, target, shouldUseSPM, + sdkVersion, }); // Step - Feature Selection @@ -121,6 +128,12 @@ async function runAppleWizardWithTelementry( selectedProject.slug, ); + clack.log.info( + `When uploading to the App Store, you may see a warning about missing dSYMs for ${chalk.cyan( + 'Sentry.framework', + )}. This is expected for pre-compiled SPM frameworks and does not affect Sentry's crash reporting.`, + ); + clack.log.success( 'Sentry was successfully added to your project! Run your project to send your first event to Sentry. Go to Sentry.io to see whether everything is working fine.', ); diff --git a/src/apple/configure-sentry-cli.ts b/src/apple/configure-sentry-cli.ts index a5e5af74..75326bf7 100644 --- a/src/apple/configure-sentry-cli.ts +++ b/src/apple/configure-sentry-cli.ts @@ -19,11 +19,15 @@ export function configureSentryCLI({ `Created a ${chalk.cyan( '.sentryclirc', )} file in your project directory to provide an auth token for Sentry CLI. - + It was also added to your ${chalk.cyan('.gitignore')} file. Set the ${chalk.cyan( 'SENTRY_AUTH_TOKEN', - )} environment variable in your CI environment. See https://docs.sentry.io/cli/configuration/#auth-token for more information.`, + )} environment variable in your CI environment to upload debug symbols during App Store builds. See https://docs.sentry.io/cli/configuration/#auth-token for more information. + +Set ${chalk.cyan( + 'SENTRY_SKIP_DSYM_UPLOAD=1', + )} in Xcode build settings to skip uploads for specific configurations (e.g., Debug).`, ); Sentry.setTag('sentry-cli-configured', true); debug(`Sentry CLI configured: ${chalk.cyan(true.toString())}`); diff --git a/src/apple/configure-xcode-project.ts b/src/apple/configure-xcode-project.ts index ee596aeb..f99685b7 100644 --- a/src/apple/configure-xcode-project.ts +++ b/src/apple/configure-xcode-project.ts @@ -7,13 +7,21 @@ export function configureXcodeProject({ project, target, shouldUseSPM, + sdkVersion, }: { xcProject: XcodeProject; project: SentryProjectData; target: string; shouldUseSPM: boolean; + sdkVersion?: string; }) { traceStep('Update Xcode project', () => { - xcProject.updateXcodeProject(project, target, shouldUseSPM, true); + xcProject.updateXcodeProject( + project, + target, + shouldUseSPM, + true, + sdkVersion, + ); }); } diff --git a/src/apple/doctor/apple-doctor.ts b/src/apple/doctor/apple-doctor.ts new file mode 100644 index 00000000..8b593e2f --- /dev/null +++ b/src/apple/doctor/apple-doctor.ts @@ -0,0 +1,95 @@ +// @ts-expect-error - clack is ESM and TS complains about that. It works though +import clack from '@clack/prompts'; +import chalk from 'chalk'; + +import { withTelemetry } from '../../telemetry'; +import { abortIfCancelled, printWelcome } from '../../utils/clack'; +import { lookupXcodeProject } from '../lookup-xcode-project'; +import type { AppleWizardOptions } from '../options'; +import { checkBuildPhase } from './checks/check-build-phase'; +import { checkCodeInit } from './checks/check-code-init'; +import { checkSdkVersion } from './checks/check-sdk-version'; +import { checkSentryCli } from './checks/check-sentry-cli'; +import { checkSentryCliRc } from './checks/check-sentryclirc'; +import type { DiagnosticResult } from './types'; + +export async function runAppleDoctorWizard( + options: AppleWizardOptions, +): Promise { + return withTelemetry( + { + enabled: options.telemetryEnabled, + integration: 'ios', + wizardOptions: options, + }, + () => runAppleDoctorWithTelemetry(options), + ); +} + +async function runAppleDoctorWithTelemetry( + options: AppleWizardOptions, +): Promise { + const projectDir = options.projectDir ?? process.cwd(); + + printWelcome({ wizardName: 'Sentry Apple Doctor' }); + + const { xcProject, target } = await lookupXcodeProject({ projectDir }); + + clack.log.info('Running diagnostic checks...\n'); + + const results: DiagnosticResult[] = [ + checkSentryCli(), + checkSentryCliRc({ projectDir }), + await checkSdkVersion({ xcProject }), + checkBuildPhase({ xcProject, target }), + checkCodeInit({ xcProject, target }), + ]; + + let hasFailures = false; + let hasFixable = false; + + for (const result of results) { + if (result.status === 'pass') { + clack.log.success( + `${chalk.green('PASS')} ${result.name}: ${result.message}`, + ); + } else if (result.status === 'warn') { + clack.log.warn( + `${chalk.yellow('WARN')} ${result.name}: ${result.message}`, + ); + } else { + clack.log.error(`${chalk.red('FAIL')} ${result.name}: ${result.message}`); + hasFailures = true; + } + + if (result.fixAvailable && result.status !== 'pass') { + hasFixable = true; + } + } + + if (hasFixable) { + const shouldFix = await abortIfCancelled( + clack.confirm({ + message: 'Would you like to attempt to fix the issues found?', + }), + ); + + if (shouldFix) { + for (const result of results) { + if (result.fixAvailable && result.status !== 'pass' && result.fix) { + clack.log.step(`Fixing: ${result.name}...`); + const fixed = await result.fix(); + if (fixed) { + clack.log.success(`Fixed: ${result.name}`); + } else { + clack.log.warn(`Could not automatically fix: ${result.name}`); + } + } + } + } + } else if (!hasFailures) { + clack.log.success( + 'All checks passed! Your Sentry integration looks healthy.', + ); + } +} diff --git a/src/apple/doctor/checks/check-build-phase.ts b/src/apple/doctor/checks/check-build-phase.ts new file mode 100644 index 00000000..08d312e6 --- /dev/null +++ b/src/apple/doctor/checks/check-build-phase.ts @@ -0,0 +1,80 @@ +import type { PBXNativeTarget, PBXShellScriptBuildPhase } from 'xcode'; +import type { XcodeProject } from '../../xcode-manager'; +import type { DiagnosticResult } from '../types'; + +export function checkBuildPhase({ + xcProject, + target, +}: { + xcProject: XcodeProject; + target: string; +}): DiagnosticResult { + const xcObjects = xcProject.objects; + + const targetKey = Object.keys(xcObjects.PBXNativeTarget ?? {}).find((key) => { + const value = xcObjects.PBXNativeTarget?.[key]; + return ( + !key.endsWith('_comment') && + typeof value !== 'string' && + value?.name === target + ); + }); + + if (!targetKey) { + return { + name: 'dSYM Upload Build Phase', + status: 'fail', + message: `Target "${target}" not found in project.`, + fixAvailable: false, + }; + } + + const nativeTarget = xcObjects.PBXNativeTarget?.[ + targetKey + ] as PBXNativeTarget; + + let sentryBuildPhase: PBXShellScriptBuildPhase | undefined; + for (const phase of nativeTarget.buildPhases ?? []) { + const bp = xcObjects.PBXShellScriptBuildPhase?.[phase.value]; + if (typeof bp !== 'string' && bp?.shellScript?.includes('sentry-cli')) { + sentryBuildPhase = bp; + break; + } + } + + if (!sentryBuildPhase) { + return { + name: 'dSYM Upload Build Phase', + status: 'fail', + message: + 'No Sentry dSYM upload build phase found in target. Re-run the wizard to add it.', + fixAvailable: false, + }; + } + + const issues: string[] = []; + const script = sentryBuildPhase.shellScript ?? ''; + + if (!script.includes('SENTRY_ORG')) { + issues.push('Missing SENTRY_ORG'); + } + if (!script.includes('SENTRY_PROJECT')) { + issues.push('Missing SENTRY_PROJECT'); + } + + if (issues.length === 0) { + return { + name: 'dSYM Upload Build Phase', + status: 'pass', + message: 'Sentry dSYM upload build phase is correctly configured.', + fixAvailable: false, + }; + } + + return { + name: 'dSYM Upload Build Phase', + status: 'warn', + message: `Issues found: ${issues.join('; ')}`, + fixAvailable: false, + }; +} diff --git a/src/apple/doctor/checks/check-code-init.ts b/src/apple/doctor/checks/check-code-init.ts new file mode 100644 index 00000000..873d1249 --- /dev/null +++ b/src/apple/doctor/checks/check-code-init.ts @@ -0,0 +1,60 @@ +import * as fs from 'node:fs'; +import type { XcodeProject } from '../../xcode-manager'; +import type { DiagnosticResult } from '../types'; + +export function checkCodeInit({ + xcProject, + target, +}: { + xcProject: XcodeProject; + target: string; +}): DiagnosticResult { + const files = xcProject.getSourceFilesForTarget(target); + + if (!files || files.length === 0) { + return { + name: 'Sentry Initialization Code', + status: 'warn', + message: + 'Could not resolve source files for the target to check for initialization code.', + fixAvailable: false, + }; + } + + for (const filePath of files) { + if (!fs.existsSync(filePath)) continue; + + let content: string; + try { + content = fs.readFileSync(filePath, 'utf8'); + } catch { + continue; + } + + // Check for active (non-commented) Sentry initialization + const lines = content.split('\n'); + for (const line of lines) { + const trimmed = line.trim(); + if (trimmed.startsWith('//') || trimmed.startsWith('/*')) continue; + if ( + trimmed.includes('SentrySDK.start') || + trimmed.includes('[SentrySDK start') + ) { + return { + name: 'Sentry Initialization Code', + status: 'pass', + message: `Sentry initialization found in ${filePath}.`, + fixAvailable: false, + }; + } + } + } + + return { + name: 'Sentry Initialization Code', + status: 'fail', + message: + 'No active Sentry initialization code found in source files. SDK will not start.', + fixAvailable: false, + }; +} diff --git a/src/apple/doctor/checks/check-sdk-version.ts b/src/apple/doctor/checks/check-sdk-version.ts new file mode 100644 index 00000000..5f8d4d99 --- /dev/null +++ b/src/apple/doctor/checks/check-sdk-version.ts @@ -0,0 +1,82 @@ +import * as fs from 'node:fs'; +import { fetchSdkVersion } from '../../../utils/release-registry'; +import type { XcodeProject } from '../../xcode-manager'; +import type { DiagnosticResult } from '../types'; + +interface SpmPackageRef { + repositoryURL?: string; + requirement?: { minimumVersion: string }; +} + +export async function checkSdkVersion({ + xcProject, +}: { + xcProject: XcodeProject; +}): Promise { + const refs = xcProject.objects.XCRemoteSwiftPackageReference ?? {}; + let currentMinVersion: string | undefined; + let packageRefKey: string | undefined; + + for (const [key, value] of Object.entries(refs)) { + if (key.endsWith('_comment') || typeof value === 'string') continue; + const ref = value as SpmPackageRef; + if (ref.repositoryURL?.includes('sentry-cocoa')) { + currentMinVersion = ref.requirement?.minimumVersion; + packageRefKey = key; + break; + } + } + + if (!currentMinVersion) { + return { + name: 'SDK Version (SPM)', + status: 'warn', + message: + 'No Sentry SPM package reference found. SDK may be installed via CocoaPods or not installed.', + fixAvailable: false, + }; + } + + const latestVersion = await fetchSdkVersion('sentry.cocoa'); + if (!latestVersion) { + return { + name: 'SDK Version (SPM)', + status: 'warn', + message: `Current minimum version: ${currentMinVersion}. Could not fetch latest version to compare.`, + fixAvailable: false, + }; + } + + const currentMajor = parseInt(currentMinVersion.split('.')[0], 10); + const latestMajor = parseInt(latestVersion.split('.')[0], 10); + + if (currentMajor >= latestMajor) { + return { + name: 'SDK Version (SPM)', + status: 'pass', + message: `Minimum version ${currentMinVersion} is on the latest major (${latestMajor}.x).`, + fixAvailable: false, + }; + } + + return { + name: 'SDK Version (SPM)', + status: 'fail', + message: `Minimum version is ${currentMinVersion} but latest major is ${latestMajor}.x. Consider updating to ${latestMajor}.0.0.`, + fixAvailable: true, + fix: () => { + if (packageRefKey && xcProject.objects.XCRemoteSwiftPackageReference) { + const ref = xcProject.objects.XCRemoteSwiftPackageReference[ + packageRefKey + ] as SpmPackageRef | string | undefined; + if (typeof ref !== 'string' && ref?.requirement) { + ref.requirement.minimumVersion = `${latestMajor}.0.0`; + const newContent = xcProject.project.writeSync(); + fs.writeFileSync(xcProject.pbxprojPath, newContent); + return Promise.resolve(true); + } + } + return Promise.resolve(false); + }, + }; +} diff --git a/src/apple/doctor/checks/check-sentry-cli.ts b/src/apple/doctor/checks/check-sentry-cli.ts new file mode 100644 index 00000000..22b9d3de --- /dev/null +++ b/src/apple/doctor/checks/check-sentry-cli.ts @@ -0,0 +1,30 @@ +import * as bash from '../../../utils/bash'; +import type { DiagnosticResult } from '../types'; + +export function checkSentryCli(): DiagnosticResult { + const hasCli = bash.hasSentryCLI(); + + if (hasCli) { + return { + name: 'sentry-cli', + status: 'pass', + message: 'sentry-cli is installed and available.', + fixAvailable: false, + }; + } + + return { + name: 'sentry-cli', + status: 'fail', + message: 'sentry-cli is not installed. dSYM uploads will fail.', + fixAvailable: true, + fix: async () => { + try { + await bash.installSentryCLI(); + return true; + } catch { + return false; + } + }, + }; +} diff --git a/src/apple/doctor/checks/check-sentryclirc.ts b/src/apple/doctor/checks/check-sentryclirc.ts new file mode 100644 index 00000000..31c841bd --- /dev/null +++ b/src/apple/doctor/checks/check-sentryclirc.ts @@ -0,0 +1,53 @@ +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import type { DiagnosticResult } from '../types'; + +export function checkSentryCliRc({ + projectDir, +}: { + projectDir: string; +}): DiagnosticResult { + const rcPath = path.join(projectDir, '.sentryclirc'); + + if (!fs.existsSync(rcPath)) { + if (process.env.SENTRY_AUTH_TOKEN) { + return { + name: '.sentryclirc / Auth Token', + status: 'pass', + message: + 'No .sentryclirc found, but SENTRY_AUTH_TOKEN environment variable is set.', + fixAvailable: false, + }; + } + + return { + name: '.sentryclirc / Auth Token', + status: 'fail', + message: + 'No .sentryclirc found and SENTRY_AUTH_TOKEN is not set. dSYM uploads will fail.', + fixAvailable: false, + }; + } + + const content = fs.readFileSync(rcPath, 'utf8'); + if ( + !content.includes('token=') || + content.includes('token=\n') || + content.includes('token=_YOUR') + ) { + return { + name: '.sentryclirc / Auth Token', + status: 'warn', + message: + '.sentryclirc exists but appears to have an invalid or placeholder auth token.', + fixAvailable: false, + }; + } + + return { + name: '.sentryclirc / Auth Token', + status: 'pass', + message: '.sentryclirc exists and contains an auth token.', + fixAvailable: false, + }; +} diff --git a/src/apple/doctor/types.ts b/src/apple/doctor/types.ts new file mode 100644 index 00000000..c62890c9 --- /dev/null +++ b/src/apple/doctor/types.ts @@ -0,0 +1,9 @@ +export type DiagnosticStatus = 'pass' | 'warn' | 'fail'; + +export interface DiagnosticResult { + name: string; + status: DiagnosticStatus; + message: string; + fixAvailable: boolean; + fix?: () => Promise; +} diff --git a/src/apple/templates.ts b/src/apple/templates.ts index 7c22e5b5..b181586d 100644 --- a/src/apple/templates.ts +++ b/src/apple/templates.ts @@ -13,6 +13,10 @@ fi ` : ''; return `# This script is responsible for uploading debug symbols and source context for Sentry.${includeHomebrew} +if [ "$SENTRY_SKIP_DSYM_UPLOAD" = "1" ]; then + echo "SENTRY_SKIP_DSYM_UPLOAD is set, skipping dSYM upload." + exit 0 +fi if which sentry-cli >/dev/null; then export SENTRY_ORG=${orgSlug} export SENTRY_PROJECT=${projectSlug} @@ -56,8 +60,8 @@ export function getSwiftSnippet(dsn: string, enableLogs: boolean): string { if (enableLogs) { snippet += ` - // Enable experimental logging features - options.experimental.enableLogs = true`; + // Enable logging features + options.enableLogs = true`; } snippet += ` @@ -93,8 +97,8 @@ export function getObjcSnippet(dsn: string, enableLogs: boolean): string { if (enableLogs) { snippet += ` - // Enable experimental logging features - options.experimental.enableLogs = YES;`; + // Enable logging features + options.enableLogs = YES;`; } snippet += ` diff --git a/src/apple/xcode-manager.ts b/src/apple/xcode-manager.ts index 996413e8..6ce8bce5 100644 --- a/src/apple/xcode-manager.ts +++ b/src/apple/xcode-manager.ts @@ -84,7 +84,13 @@ function setDebugInformationFormatAndSandbox( } } -function addSentrySPM(proj: Project, targetName: string): void { +const FALLBACK_SENTRY_COCOA_VERSION = '8.0.0'; + +function addSentrySPM( + proj: Project, + targetName: string, + sdkVersion?: string, +): void { const xcObjects = proj.hash.project.objects; const sentryFrameworkUUID = proj.generateUuid(); @@ -176,12 +182,20 @@ function addSentrySPM(proj: Project, targetName: string): void { xcObjects.XCRemoteSwiftPackageReference = {}; } + let minimumVersion = FALLBACK_SENTRY_COCOA_VERSION; + if (sdkVersion) { + const majorMatch = sdkVersion.match(/^(\d+)\./); + if (majorMatch) { + minimumVersion = `${majorMatch[1]}.0.0`; + } + } + xcObjects.XCRemoteSwiftPackageReference[sentrySwiftPackageUUID] = { isa: 'XCRemoteSwiftPackageReference', repositoryURL: '"https://github.com/getsentry/sentry-cocoa/"', requirement: { kind: 'upToNextMajorVersion', - minimumVersion: '8.0.0', + minimumVersion, }, }; xcObjects.XCRemoteSwiftPackageReference[`${sentrySwiftPackageUUID}_comment`] = @@ -261,6 +275,7 @@ export class XcodeProject { target: string, addSPMReference: boolean, uploadSource = true, + sdkVersion?: string, ): void { this.addUploadSymbolsScript({ sentryProject, @@ -271,7 +286,7 @@ export class XcodeProject { setDebugInformationFormatAndSandbox(this.project, target); } if (addSPMReference) { - addSentrySPM(this.project, target); + addSentrySPM(this.project, target, sdkVersion); } const newContent = this.project.writeSync(); fs.writeFileSync(this.pbxprojPath, newContent); diff --git a/src/run.ts b/src/run.ts index c3304b08..e3af84a2 100644 --- a/src/run.ts +++ b/src/run.ts @@ -9,6 +9,7 @@ import { run as legacyRun } from '../lib/Setup'; import { runAndroidWizard } from './android/android-wizard'; import { runAngularWizard } from './angular/angular-wizard'; import { runAppleWizard } from './apple/apple-wizard'; +import { runAppleDoctorWizard } from './apple/doctor/apple-doctor'; import { runFlutterWizard } from './flutter/flutter-wizard'; import { runNextjsWizard } from './nextjs/nextjs-wizard'; import { runNuxtWizard } from './nuxt/nuxt-wizard'; @@ -68,6 +69,7 @@ type Args = { comingFrom?: string; ignoreGitChanges?: boolean; xcodeProjectDir?: string; + fix?: boolean; }; function preSelectedProjectArgsToObject( @@ -156,6 +158,7 @@ export async function run(argv: Args) { comingFrom: finalArgs.comingFrom, ignoreGitChanges: finalArgs.ignoreGitChanges, spotlight: finalArgs.spotlight, + fix: finalArgs.fix, }; switch (integration) { @@ -168,10 +171,17 @@ export async function run(argv: Args) { break; case 'ios': - await runAppleWizard({ - ...wizardOptions, - projectDir: finalArgs.xcodeProjectDir, - }); + if (finalArgs.fix) { + await runAppleDoctorWizard({ + ...wizardOptions, + projectDir: finalArgs.xcodeProjectDir, + }); + } else { + await runAppleWizard({ + ...wizardOptions, + projectDir: finalArgs.xcodeProjectDir, + }); + } break; case 'android': diff --git a/src/utils/types.ts b/src/utils/types.ts index 9f4e2dd4..66934423 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -89,6 +89,12 @@ export type WizardOptions = { * This can be passed via the `--spotlight` arg. */ spotlight?: boolean; + + /** + * Run diagnostic checks and fix issues with an existing Sentry integration. + * This can be passed via the `--fix` arg. + */ + fix?: boolean; }; export interface Feature { diff --git a/test/apple/code-tools.test.ts b/test/apple/code-tools.test.ts index 36b2f0a6..78290c82 100644 --- a/test/apple/code-tools.test.ts +++ b/test/apple/code-tools.test.ts @@ -1194,7 +1194,7 @@ describe('code-tools', () => { // -- Assert -- expect(result).toBeTruthy(); const fileContent = fs.readFileSync(filePath, 'utf8'); - expect(fileContent).toContain('options.experimental.enableLogs = true'); + expect(fileContent).toContain('options.enableLogs = true'); }); it('should add logs option to Objective-C file', () => { @@ -1212,7 +1212,7 @@ describe('code-tools', () => { // -- Assert -- expect(result).toBeTruthy(); const fileContent = fs.readFileSync(filePath, 'utf8'); - expect(fileContent).toContain('options.experimental.enableLogs = YES;'); + expect(fileContent).toContain('options.enableLogs = YES;'); }); }); @@ -1233,7 +1233,7 @@ describe('code-tools', () => { expect(result).toBeTruthy(); const fileContent = fs.readFileSync(filePath, 'utf8'); expect(fileContent).not.toContain( - 'options.experimental.enableLogs = true', + 'options.enableLogs = true', ); }); @@ -1253,7 +1253,7 @@ describe('code-tools', () => { expect(result).toBeTruthy(); const fileContent = fs.readFileSync(filePath, 'utf8'); expect(fileContent).not.toContain( - 'options.experimental.enableLogs = YES;', + 'options.enableLogs = YES;', ); }); }); diff --git a/test/apple/templates.test.ts b/test/apple/templates.test.ts index d496d604..7dd09c2d 100644 --- a/test/apple/templates.test.ts +++ b/test/apple/templates.test.ts @@ -26,6 +26,10 @@ if [[ "$(uname -m)" == arm64 ]]; then export PATH="/opt/homebrew/bin:$PATH" fi +if [ "$SENTRY_SKIP_DSYM_UPLOAD" = "1" ]; then + echo "SENTRY_SKIP_DSYM_UPLOAD is set, skipping dSYM upload." + exit 0 +fi if which sentry-cli >/dev/null; then export SENTRY_ORG=test-org export SENTRY_PROJECT=test-project @@ -42,6 +46,10 @@ fi uploadSource: true, includeHomebrewPath: false, expectedScript: `# This script is responsible for uploading debug symbols and source context for Sentry. +if [ "$SENTRY_SKIP_DSYM_UPLOAD" = "1" ]; then + echo "SENTRY_SKIP_DSYM_UPLOAD is set, skipping dSYM upload." + exit 0 +fi if which sentry-cli >/dev/null; then export SENTRY_ORG=test-org export SENTRY_PROJECT=test-project @@ -62,6 +70,10 @@ if [[ "$(uname -m)" == arm64 ]]; then export PATH="/opt/homebrew/bin:$PATH" fi +if [ "$SENTRY_SKIP_DSYM_UPLOAD" = "1" ]; then + echo "SENTRY_SKIP_DSYM_UPLOAD is set, skipping dSYM upload." + exit 0 +fi if which sentry-cli >/dev/null; then export SENTRY_ORG=test-org export SENTRY_PROJECT=test-project @@ -78,6 +90,10 @@ fi uploadSource: false, includeHomebrewPath: false, expectedScript: `# This script is responsible for uploading debug symbols and source context for Sentry. +if [ "$SENTRY_SKIP_DSYM_UPLOAD" = "1" ]; then + echo "SENTRY_SKIP_DSYM_UPLOAD is set, skipping dSYM upload." + exit 0 +fi if which sentry-cli >/dev/null; then export SENTRY_ORG=test-org export SENTRY_PROJECT=test-project @@ -179,8 +195,8 @@ fi // options.attachScreenshot = true // This adds a screenshot to the error events // options.attachViewHierarchy = true // This adds the view hierarchy to the error events - // Enable experimental logging features - options.experimental.enableLogs = true + // Enable logging features + options.enableLogs = true } // Remove the next line after confirming that your Sentry integration is working. SentrySDK.capture(message: "This app uses Sentry! :)") @@ -250,8 +266,8 @@ fi //options.attachScreenshot = YES; //This will add a screenshot to the error events //options.attachViewHierarchy = YES; //This will add the view hierarchy to the error events - // Enable experimental logging features - options.experimental.enableLogs = YES; + // Enable logging features + options.enableLogs = YES; }]; //Remove the next line after confirming that your Sentry integration is working. [SentrySDK captureMessage:@"This app uses Sentry!"]; diff --git a/test/apple/xcode-manager.test.ts b/test/apple/xcode-manager.test.ts index 7b9c50ab..98022c66 100644 --- a/test/apple/xcode-manager.test.ts +++ b/test/apple/xcode-manager.test.ts @@ -594,6 +594,85 @@ describe('XcodeManager', () => { }), ]); }); + + it('should use dynamic SDK version when provided', () => { + // -- Act -- + xcodeProject.updateXcodeProject( + projectData, + 'Project', + addSPMReference, + true, + '9.8.0', + ); + + // -- Assert -- + const remoteSwiftPackageReferences = + xcodeProject.objects.XCRemoteSwiftPackageReference; + expect(remoteSwiftPackageReferences).toBeDefined(); + const rspRefKeys = Object.keys( + remoteSwiftPackageReferences ?? {}, + ).filter((k) => !k.endsWith('_comment')); + expect(remoteSwiftPackageReferences?.[rspRefKeys[0]]).toEqual({ + isa: 'XCRemoteSwiftPackageReference', + repositoryURL: '"https://github.com/getsentry/sentry-cocoa/"', + requirement: { + kind: 'upToNextMajorVersion', + minimumVersion: '9.0.0', + }, + }); + }); + + it('should fall back to 8.0.0 when sdkVersion is undefined', () => { + // -- Act -- + xcodeProject.updateXcodeProject( + projectData, + 'Project', + addSPMReference, + true, + undefined, + ); + + // -- Assert -- + const remoteSwiftPackageReferences = + xcodeProject.objects.XCRemoteSwiftPackageReference; + const rspRefKeys = Object.keys( + remoteSwiftPackageReferences ?? {}, + ).filter((k) => !k.endsWith('_comment')); + expect(remoteSwiftPackageReferences?.[rspRefKeys[0]]).toEqual( + expect.objectContaining({ + requirement: { + kind: 'upToNextMajorVersion', + minimumVersion: '8.0.0', + }, + }), + ); + }); + + it('should fall back to 8.0.0 when sdkVersion is malformed', () => { + // -- Act -- + xcodeProject.updateXcodeProject( + projectData, + 'Project', + addSPMReference, + true, + 'not-a-version', + ); + + // -- Assert -- + const remoteSwiftPackageReferences = + xcodeProject.objects.XCRemoteSwiftPackageReference; + const rspRefKeys = Object.keys( + remoteSwiftPackageReferences ?? {}, + ).filter((k) => !k.endsWith('_comment')); + expect(remoteSwiftPackageReferences?.[rspRefKeys[0]]).toEqual( + expect.objectContaining({ + requirement: { + kind: 'upToNextMajorVersion', + minimumVersion: '8.0.0', + }, + }), + ); + }); }); });