UNPKG

@sentry/wizard

Version:

Sentry wizard helping you to configure your project

316 lines (283 loc) 9.58 kB
/* eslint-disable max-lines */ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ // @ts-ignore - clack is ESM and TS complains about that. It works though import clack from '@clack/prompts'; import * as fs from 'fs'; import { SentryProjectData } from '../utils/types'; import * as templates from './templates'; import * as path from 'path'; const xcode = require('xcode'); interface ProjetFile { key: string; path: string; } function setDebugInformationFormatAndSandbox( proj: any, targetName: string, ): void { const xcObjects = proj.hash.project.objects; const targetKey: string = Object.keys(xcObjects.PBXNativeTarget || {}).filter( (key) => { return ( !key.endsWith('_comment') && xcObjects.PBXNativeTarget[key].name === targetName ); }, )[0]; const target = xcObjects.PBXNativeTarget[targetKey]; xcObjects.XCConfigurationList[ target.buildConfigurationList ].buildConfigurations.forEach((buildConfig: { value: string }) => { const buildSettings = xcObjects.XCBuildConfiguration[buildConfig.value].buildSettings; buildSettings.DEBUG_INFORMATION_FORMAT = '"dwarf-with-dsym"'; buildSettings.ENABLE_USER_SCRIPT_SANDBOXING = '"NO"'; }); } function addSentrySPM(proj: any, targetName: string): void { const xcObjects = proj.hash.project.objects; const sentryFrameworkUUID = proj.generateUuid() as string; const sentrySPMUUID = proj.generateUuid() as string; //Check whether xcObjects already have sentry framework if (xcObjects.PBXFrameworksBuildPhase) { for (const key in xcObjects.PBXFrameworksBuildPhase || {}) { if (!key.endsWith('_comment')) { const frameworks = xcObjects.PBXFrameworksBuildPhase[key].files; for (const framework of frameworks) { if (framework.comment === 'Sentry in Frameworks') { return; } } } } } xcObjects.PBXBuildFile[sentryFrameworkUUID] = { isa: 'PBXBuildFile', productRef: sentrySPMUUID, productRef_comment: 'Sentry', }; xcObjects.PBXBuildFile[sentryFrameworkUUID + '_comment'] = 'Sentry in Frameworks'; for (const key in xcObjects.PBXFrameworksBuildPhase || {}) { if (!key.endsWith('_comment')) { const frameworks = xcObjects.PBXFrameworksBuildPhase[key].files; frameworks.push({ value: sentryFrameworkUUID, comment: 'Sentry in Frameworks', }); } } const targetKey: string = Object.keys(xcObjects.PBXNativeTarget || {}).filter( (key) => { return ( !key.endsWith('_comment') && xcObjects.PBXNativeTarget[key].name === targetName ); }, )[0]; const target = xcObjects.PBXNativeTarget[targetKey]; if (!target.packageProductDependencies) { target.packageProductDependencies = []; } target.packageProductDependencies.push({ value: sentrySPMUUID, comment: 'Sentry', }); const sentrySwiftPackageUUID = proj.generateUuid() as string; const xcProject = proj.getFirstProject().firstProject; if (!xcProject.packageReferences) { xcProject.packageReferences = []; } xcProject.packageReferences.push({ value: sentrySwiftPackageUUID, comment: 'XCRemoteSwiftPackageReference "sentry-cocoa"', }); if (!xcObjects.XCRemoteSwiftPackageReference) { xcObjects.XCRemoteSwiftPackageReference = {}; } xcObjects.XCRemoteSwiftPackageReference[sentrySwiftPackageUUID] = { isa: 'XCRemoteSwiftPackageReference', repositoryURL: '"https://github.com/getsentry/sentry-cocoa/"', requirement: { kind: 'upToNextMajorVersion', minimumVersion: '8.0.0', }, }; xcObjects.XCRemoteSwiftPackageReference[sentrySwiftPackageUUID + '_comment'] = 'XCRemoteSwiftPackageReference "sentry-cocoa"'; if (!xcObjects.XCSwiftPackageProductDependency) { xcObjects.XCSwiftPackageProductDependency = {}; } xcObjects.XCSwiftPackageProductDependency[sentrySPMUUID] = { isa: 'XCSwiftPackageProductDependency', package: sentrySwiftPackageUUID, package_comment: 'XCRemoteSwiftPackageReference "sentry-cocoa"', productName: 'Sentry', }; xcObjects.XCSwiftPackageProductDependency[sentrySPMUUID + '_comment'] = 'Sentry'; clack.log.step('Added Sentry SPM dependency to your project'); } function addUploadSymbolsScript( xcodeProject: any, sentryProject: SentryProjectData, targetName: string, uploadSource = true, ): void { const xcObjects = xcodeProject.hash.project.objects; const targetKey: string = Object.keys(xcObjects.PBXNativeTarget || {}).filter( (key) => { return ( !key.endsWith('_comment') && xcObjects.PBXNativeTarget[key].name === targetName ); }, )[0]; for (const scriptKey in xcObjects.PBXShellScriptBuildPhase || {}) { if (!scriptKey.endsWith('_comment')) { const script = xcObjects.PBXShellScriptBuildPhase[scriptKey].shellScript; //Sentry script already exists, update it if (script.includes('sentry-cli')) { delete xcObjects.PBXShellScriptBuildPhase[scriptKey]; delete xcObjects.PBXShellScriptBuildPhase[scriptKey + '_comment']; break; } } } xcodeProject.addBuildPhase( [], 'PBXShellScriptBuildPhase', 'Upload Debug Symbols to Sentry', targetKey, { inputFileListPaths: [], outputFileListPaths: [], inputPaths: [templates.scriptInputPath], shellPath: '/bin/sh', shellScript: templates.getRunScriptTemplate( sentryProject.organization.slug, sentryProject.slug, uploadSource, ), }, ); clack.log.step(`Added Sentry upload script to "${targetName}" build phase`); } export class XcodeProject { projectPath: string; project: any; objects: any; files: ProjetFile[] | undefined; public constructor(projectPath: string) { this.projectPath = projectPath; this.project = xcode.project(projectPath); this.project.parseSync(); this.objects = this.project.hash.project.objects; } public getAllTargets(): string[] { return Object.keys(this.objects.PBXNativeTarget || {}) .filter((key) => { return ( !key.endsWith('_comment') && this.objects.PBXNativeTarget[key].productType.startsWith( '"com.apple.product-type.application', ) ); }) .map((key) => { return this.objects.PBXNativeTarget[key].name as string; }); } public updateXcodeProject( sentryProject: SentryProjectData, target: string, apiKeys: { token: string }, addSPMReference: boolean, uploadSource = true, ): void { addUploadSymbolsScript(this.project, sentryProject, target, uploadSource); if (uploadSource) { setDebugInformationFormatAndSandbox(this.project, target); } if (addSPMReference) { addSentrySPM(this.project, target); } const newContent = this.project.writeSync(); fs.writeFileSync(this.projectPath, newContent); } public filesForTarget(target: string): string[] | undefined { const files = this.projectFiles(); const fileDictionary: any = {}; files.forEach((file) => { fileDictionary[file.key] = file.path; }); const nativeTarget = Object.keys(this.objects.PBXNativeTarget || {}).filter( (key) => { return ( !key.endsWith('_comment') && this.objects.PBXNativeTarget[key].name === target ); }, )[0]; if (nativeTarget === undefined) { return undefined; } const buildPhaseKey = this.objects.PBXNativeTarget[ nativeTarget ].buildPhases.filter((phase: any) => { return this.objects.PBXSourcesBuildPhase[phase.value] !== undefined; })[0]; if (buildPhaseKey === undefined) { return undefined; } const buildPhases = this.objects.PBXSourcesBuildPhase[buildPhaseKey.value]; if (buildPhases === undefined) { return undefined; } const baseDir = path.dirname(path.dirname(this.projectPath)); return buildPhases.files .map((file: any) => { const buildFile = fileDictionary[ this.objects.PBXBuildFile[file.value].fileRef ] as string; if (!buildFile) { return ''; } return path.join(baseDir, buildFile); }) .filter((f: string) => f.length > 0) as string[]; } projectFiles(): ProjetFile[] { if (this.files === undefined) { const proj = this.project.getFirstProject(); const mainGroupKey = proj.firstProject.mainGroup; const mainGroup = this.objects.PBXGroup[mainGroupKey]; this.files = this.buildGroup(mainGroup); } return this.files; } buildGroup(group: any, path = ''): ProjetFile[] { const result: ProjetFile[] = []; for (const child of group.children) { if (this.objects.PBXFileReference[child.value]) { const fileReference = this.objects.PBXFileReference[child.value]; result.push({ key: child.value, path: `${path}${fileReference.path.replace(/"/g, '')}`, }); } else if (this.objects.PBXGroup[child.value]) { const groupReference = this.objects.PBXGroup[child.value]; const groupChildren = this.buildGroup( groupReference, groupReference.path ? `${path}${groupReference.path.replace(/"/g, '')}/` : path, ); result.push(...groupChildren); } } return result; } }