@sentry/wizard
Version:
Sentry wizard helping you to configure your project
331 lines • 13.7 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.XcodeProject = void 0;
/* 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
const prompts_1 = __importDefault(require("@clack/prompts"));
const fs = __importStar(require("node:fs"));
const path = __importStar(require("node:path"));
const templates = __importStar(require("./templates"));
const xcode_1 = require("xcode");
function setDebugInformationFormatAndSandbox(proj, targetName) {
const xcObjects = proj.hash.project.objects;
if (!xcObjects.PBXNativeTarget) {
xcObjects.PBXNativeTarget = {};
}
const targetKey = Object.keys(xcObjects.PBXNativeTarget).filter((key) => {
const value = xcObjects.PBXNativeTarget?.[key];
return (!key.endsWith('_comment') &&
typeof value !== 'string' &&
value?.name === targetName);
})[0];
const target = xcObjects.PBXNativeTarget[targetKey];
if (!xcObjects.XCBuildConfiguration) {
xcObjects.XCBuildConfiguration = {};
}
if (!xcObjects.XCConfigurationList) {
xcObjects.XCConfigurationList = {};
}
const buildConfigurationListId = target?.buildConfigurationList ?? '';
const configurationList = xcObjects.XCConfigurationList?.[buildConfigurationListId];
const buildListConfigurationIds = configurationList?.buildConfigurations ?? [];
for (const buildListConfigId of buildListConfigurationIds) {
const config = xcObjects.XCBuildConfiguration[buildListConfigId.value] ?? {};
if (typeof config === 'string') {
// Ignore comments
continue;
}
const buildSettings = config.buildSettings ?? {};
buildSettings.DEBUG_INFORMATION_FORMAT = '"dwarf-with-dsym"';
buildSettings.ENABLE_USER_SCRIPT_SANDBOXING = '"NO"';
config.buildSettings = buildSettings;
xcObjects.XCBuildConfiguration[buildListConfigId.value] = config;
}
}
function addSentrySPM(proj, targetName) {
const xcObjects = proj.hash.project.objects;
const sentryFrameworkUUID = proj.generateUuid();
const sentrySPMUUID = proj.generateUuid();
// Check whether xcObjects already have sentry framework
if (xcObjects.PBXFrameworksBuildPhase) {
for (const key in xcObjects.PBXFrameworksBuildPhase || {}) {
const frameworkBuildPhase = xcObjects.PBXFrameworksBuildPhase[key];
if (key.endsWith('_comment') || typeof frameworkBuildPhase === 'string') {
// Ignore comments
continue;
}
for (const framework of frameworkBuildPhase.files ?? []) {
// We identify the Sentry framework by the comment "Sentry in Frameworks",
// which is set by this manager in previous runs.
if (framework.comment === 'Sentry in Frameworks') {
return;
}
}
}
}
if (!xcObjects.PBXBuildFile) {
xcObjects.PBXBuildFile = {};
}
xcObjects.PBXBuildFile[sentryFrameworkUUID] = {
isa: 'PBXBuildFile',
productRef: sentrySPMUUID,
productRef_comment: 'Sentry',
};
xcObjects.PBXBuildFile[`${sentryFrameworkUUID}_comment`] =
'Sentry in Frameworks';
if (!xcObjects.PBXFrameworksBuildPhase) {
xcObjects.PBXFrameworksBuildPhase = {};
}
for (const key in xcObjects.PBXFrameworksBuildPhase) {
const value = xcObjects.PBXFrameworksBuildPhase[key];
if (key.endsWith('_comment') || typeof value === 'string') {
// Ignore comments
continue;
}
const frameworks = value.files ?? [];
frameworks.push({
value: sentryFrameworkUUID,
comment: 'Sentry in Frameworks',
});
value.files = frameworks;
xcObjects.PBXFrameworksBuildPhase[key] = value;
}
if (!xcObjects.PBXNativeTarget) {
xcObjects.PBXNativeTarget = {};
}
const targetKey = Object.keys(xcObjects.PBXNativeTarget || {}).filter((key) => {
const value = xcObjects.PBXNativeTarget?.[key];
return (!key.endsWith('_comment') &&
typeof value !== 'string' &&
value?.name === targetName);
})[0];
const target = xcObjects.PBXNativeTarget[targetKey];
if (!target.packageProductDependencies) {
target.packageProductDependencies = [];
}
target.packageProductDependencies.push({
value: sentrySPMUUID,
comment: 'Sentry',
});
const sentrySwiftPackageUUID = proj.generateUuid();
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';
prompts_1.default.log.step('Added Sentry SPM dependency to your project');
}
function addUploadSymbolsScript(xcodeProject, sentryProject, targetName, uploadSource) {
const xcObjects = xcodeProject.hash.project.objects;
if (!xcObjects.PBXNativeTarget) {
xcObjects.PBXNativeTarget = {};
}
const targetKey = Object.keys(xcObjects.PBXNativeTarget).filter((key) => {
const value = xcObjects.PBXNativeTarget?.[key];
return (!key.endsWith('_comment') &&
typeof value !== 'string' &&
value?.name === targetName);
})[0];
if (!xcObjects.PBXShellScriptBuildPhase) {
xcObjects.PBXShellScriptBuildPhase = {};
}
for (const key in xcObjects.PBXShellScriptBuildPhase) {
const value = xcObjects.PBXShellScriptBuildPhase[key] ?? {};
if (typeof value === 'string') {
// Ignore comments
continue;
}
// Sentry script already exists, update it
if (value.shellScript?.includes('sentry-cli')) {
delete xcObjects.PBXShellScriptBuildPhase?.[key];
delete xcObjects.PBXShellScriptBuildPhase?.[`${key}_comment`];
break;
}
xcObjects.PBXShellScriptBuildPhase[key] = value;
}
const isHomebrewInstalled = fs.existsSync('/opt/homebrew/bin/sentry-cli');
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, isHomebrewInstalled),
});
prompts_1.default.log.step(`Added Sentry upload script to "${targetName}" build phase`);
}
class XcodeProject {
projectPath;
project;
objects;
files;
/**
* Creates a new XcodeProject instance, a wrapper around the Xcode project file `<PROJECT>.xcodeproj/project.pbxproj`.
*
* @param projectPath - The path to the Xcode project file
*/
constructor(projectPath) {
this.projectPath = projectPath;
this.project = (0, xcode_1.project)(projectPath);
this.project.parseSync();
this.objects = this.project.hash.project.objects;
}
getAllTargets() {
const targets = this.objects.PBXNativeTarget ?? {};
return Object.keys(targets)
.filter((key) => {
const value = targets[key];
return (!key.endsWith('_comment') &&
typeof value !== 'string' &&
value.productType.startsWith('"com.apple.product-type.application'));
})
.map((key) => {
return targets[key].name;
});
}
updateXcodeProject(sentryProject, target, addSPMReference, uploadSource = true) {
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);
}
filesForTarget(target) {
const files = this.projectFiles();
const fileDictionary = {};
files.forEach((file) => {
fileDictionary[file.key] = file.path;
});
const targets = this.objects.PBXNativeTarget || {};
const nativeTarget = Object.keys(targets).filter((key) => {
const value = targets[key];
return (!key.endsWith('_comment') &&
typeof value !== 'string' &&
value.name === target);
})[0];
if (nativeTarget === undefined) {
return undefined;
}
const buildPhaseKey = targets[nativeTarget].buildPhases?.filter((phase) => {
return this.objects.PBXSourcesBuildPhase?.[phase.value] !== undefined;
})[0];
if (buildPhaseKey === undefined) {
return undefined;
}
const buildPhase = this.objects.PBXSourcesBuildPhase?.[buildPhaseKey.value];
const buildPhaseFiles = buildPhase?.files ?? [];
const baseDir = path.dirname(path.dirname(this.projectPath));
return buildPhaseFiles
.map((file) => {
const fileRef = this.objects.PBXBuildFile?.[file.value]?.fileRef;
if (!fileRef) {
return '';
}
const buildFile = fileDictionary[fileRef];
if (!buildFile) {
return '';
}
return path.join(baseDir, buildFile);
})
.filter((f) => f.length > 0);
}
projectFiles() {
if (this.files === undefined) {
const proj = this.project.getFirstProject();
const mainGroupKey = proj.firstProject.mainGroup;
const mainGroup = this.objects.PBXGroup?.[mainGroupKey];
if (!mainGroup || typeof mainGroup === 'string') {
return [];
}
this.files = this.buildGroup(mainGroup);
}
return this.files;
}
buildGroup(group, path = '') {
const result = [];
for (const child of group.children ?? []) {
const fileReference = this.objects.PBXFileReference?.[child.value];
const groupReference = this.objects.PBXGroup?.[child.value];
if (fileReference) {
if (typeof fileReference === 'string') {
continue;
}
result.push({
key: child.value,
path: `${path}${fileReference.path.replace(/"/g, '')}`,
});
}
else if (groupReference) {
if (typeof groupReference === 'string') {
continue;
}
const groupChildren = this.buildGroup(groupReference, groupReference.path
? `${path}${groupReference.path.replace(/"/g, '')}/`
: path);
result.push(...groupChildren);
}
}
return result;
}
}
exports.XcodeProject = XcodeProject;
//# sourceMappingURL=xcode-manager.js.map