@sentry/wizard
Version:
Sentry wizard helping you to configure your project
853 lines (852 loc) • 92.5 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 });
const fs = __importStar(require("node:fs"));
const os = __importStar(require("node:os"));
const path = __importStar(require("node:path"));
const node_process_1 = __importDefault(require("node:process"));
const vitest_1 = require("vitest");
const templates_1 = require("../../src/apple/templates");
const xcode_manager_1 = require("../../src/apple/xcode-manager");
vitest_1.vi.mock('node:fs', async () => ({
__esModule: true,
...(await vitest_1.vi.importActual('node:fs')),
}));
vitest_1.vi.mock('@clack/prompts', () => ({
log: {
info: vitest_1.vi.fn(),
success: vitest_1.vi.fn(),
step: vitest_1.vi.fn(),
},
}));
const appleProjectsPath = path.resolve(__dirname, '../../fixtures/test-applications/apple');
const damagedProjectPath = path.join(appleProjectsPath, 'damaged-missing-configuration-list/Project.xcodeproj/project.pbxproj');
const noTargetsProjectPath = path.join(appleProjectsPath, 'no-targets/Project.xcodeproj/project.pbxproj');
const noFilesInTargetProjectPath = path.join(appleProjectsPath, 'no-files-in-target/Project.xcodeproj/project.pbxproj');
const projectWithSynchronizedFolders = path.join(appleProjectsPath, 'project-with-synchronized-folders/Project.xcodeproj/project.pbxproj');
const singleTargetProjectPath = path.join(appleProjectsPath, 'spm-swiftui-single-target/Project.xcodeproj/project.pbxproj');
const multiTargetProjectPath = path.join(appleProjectsPath, 'spm-swiftui-multi-targets/Project.xcodeproj/project.pbxproj');
const projectData = {
id: '1234567890',
slug: 'project',
organization: {
id: '1234567890',
name: 'Sentry',
slug: 'sentry',
},
keys: [{ dsn: { public: 'https://sentry.io/1234567890' } }],
};
(0, vitest_1.describe)('XcodeManager', () => {
(0, vitest_1.beforeEach)(() => {
if (node_process_1.default.platform !== 'darwin') {
// The macOS system helpers are only available on macOS
// As the test suite is also run on non-macOS platforms, we need to mock the system helpers
// The path to the Xcode.app can be different on different machines, so we allow overwriting it using environment variables
vitest_1.vi.mock('../../src/apple/macos-system-helper', () => ({
MacOSSystemHelpers: {
findSDKRootDirectoryPath: vitest_1.vi.fn(() => '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk'),
findDeveloperDirectoryPath: vitest_1.vi.fn(() => '/Applications/Xcode.app/Contents/Developer'),
readXcodeBuildSettings: vitest_1.vi.fn(() => ({
CONFIGURATION_BUILD_DIR: path.join(appleProjectsPath, 'project-with-synchronized-folders/build/Release-unknown'),
})),
},
}));
}
});
(0, vitest_1.afterEach)(() => {
vitest_1.vi.clearAllMocks();
});
(0, vitest_1.describe)('XcodeProject', () => {
(0, vitest_1.describe)('getAllTargets', () => {
(0, vitest_1.describe)('single target', () => {
(0, vitest_1.it)('should return all targets', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
// -- Act --
const targets = xcodeProject.getAllTargets();
// -- Assert --
(0, vitest_1.expect)(targets).toEqual(['Project']);
});
});
(0, vitest_1.describe)('multiple targets', () => {
(0, vitest_1.it)('should return all targets', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(multiTargetProjectPath);
// -- Act --
const targets = xcodeProject.getAllTargets();
// -- Assert --
(0, vitest_1.expect)(targets).toEqual(['Project1', 'Project2']);
});
});
(0, vitest_1.describe)('no targets', () => {
(0, vitest_1.it)('should return an empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(noTargetsProjectPath);
// -- Act --
const targets = xcodeProject.getAllTargets();
// -- Assert --
(0, vitest_1.expect)(targets).toEqual([]);
});
});
(0, vitest_1.describe)('project with missing configuration list', () => {
(0, vitest_1.it)('should return an empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(damagedProjectPath);
// -- Act --
const targets = xcodeProject.getAllTargets();
// -- Assert --
(0, vitest_1.expect)(targets).toEqual([]);
});
});
});
(0, vitest_1.describe)('updateXcodeProject', () => {
let sourceProjectPath;
let tempProjectPath;
let xcodeProject;
(0, vitest_1.beforeEach)(() => {
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'update-xcode-project'));
sourceProjectPath = singleTargetProjectPath;
tempProjectPath = path.resolve(tempDir, 'project.pbxproj');
fs.copyFileSync(sourceProjectPath, tempProjectPath);
xcodeProject = new xcode_manager_1.XcodeProject(tempProjectPath);
});
(0, vitest_1.describe)('upload symbols script', () => {
const scriptVariants = [
{
uploadSource: true,
includeHomebrewPath: true,
},
{
uploadSource: true,
includeHomebrewPath: false,
},
{
uploadSource: false,
includeHomebrewPath: true,
},
{
uploadSource: false,
includeHomebrewPath: false,
},
];
for (const variant of scriptVariants) {
(0, vitest_1.describe)(`upload source = ${variant.uploadSource?.toString()} and include homebrew path = ${variant.includeHomebrewPath.toString()}`, () => {
(0, vitest_1.beforeEach)(() => {
vitest_1.vi.spyOn(fs, 'existsSync').mockReturnValue(variant.includeHomebrewPath);
});
(0, vitest_1.afterEach)(() => {
vitest_1.vi.restoreAllMocks();
});
(0, vitest_1.it)('should add the upload symbols script to the target', () => {
// -- Arrange --
const generatedShellScript = (0, templates_1.getRunScriptTemplate)(projectData.organization.slug, projectData.slug, variant.uploadSource, variant.includeHomebrewPath);
const expectedShellScript = `"${generatedShellScript.replace(/"/g, '\\"')}"`;
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Project', false, // Ignore SPM reference
variant.uploadSource);
// -- Assert --
const updatedXcodeProject = new xcode_manager_1.XcodeProject(tempProjectPath);
// Expect the upload symbols script to be added
const scriptObjects = updatedXcodeProject.objects.PBXShellScriptBuildPhase;
(0, vitest_1.expect)(scriptObjects).toBeDefined();
if (!scriptObjects) {
throw new Error('Script objects not found');
}
const scriptKeys = Object.keys(scriptObjects);
(0, vitest_1.expect)(scriptKeys).toHaveLength(2);
// Find the script ID
const scriptId = scriptKeys.find((key) => !key.endsWith('_comment'));
(0, vitest_1.expect)(scriptId).toBeDefined();
if (!scriptId) {
throw new Error('Script ID not found');
}
(0, vitest_1.expect)(scriptId).toMatch(/^[A-F0-9]{24}$/i);
// Expect the script to be added
const script = scriptObjects[scriptId];
(0, vitest_1.expect)(script).toBeDefined();
(0, vitest_1.expect)(typeof script).not.toBe('string');
(0, vitest_1.expect)(script.inputPaths).toEqual([
'"${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME}"',
]);
(0, vitest_1.expect)(script.outputPaths).toEqual([]);
(0, vitest_1.expect)(script.shellPath).toBe('/bin/sh');
(0, vitest_1.expect)(script.shellScript).toEqual(expectedShellScript);
const commentKey = `${scriptId}_comment`;
(0, vitest_1.expect)(scriptKeys).toContain(commentKey);
(0, vitest_1.expect)(scriptObjects[commentKey]).toBe('Upload Debug Symbols to Sentry');
});
});
}
});
(0, vitest_1.describe)('debug information format and sandbox', () => {
(0, vitest_1.describe)('upload source is false', () => {
(0, vitest_1.it)('should not update the Xcode project', () => {
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Project', false, // Ignore SPM reference
false);
// -- Assert --
const expectedXcodeProject = new xcode_manager_1.XcodeProject(sourceProjectPath);
(0, vitest_1.expect)(xcodeProject.objects.XCBuildConfiguration).toEqual(expectedXcodeProject.objects.XCBuildConfiguration);
});
});
(0, vitest_1.describe)('upload source is true', () => {
const uploadSource = true;
(0, vitest_1.describe)('named target not found', () => {
(0, vitest_1.it)('should not update the flags in the Xcode project', () => {
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Invalid Target Name', false, // Ignore SPM reference
uploadSource);
// -- Assert --
const originalXcodeProject = new xcode_manager_1.XcodeProject(sourceProjectPath);
(0, vitest_1.expect)(xcodeProject.objects.XCBuildConfiguration).toEqual(originalXcodeProject.objects.XCBuildConfiguration);
});
});
(0, vitest_1.describe)('named target found', () => {
(0, vitest_1.describe)('build configurations is undefined', () => {
(0, vitest_1.it)('should not update the Xcode project', () => {
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Invalid Target Name', false, // Ignore SPM reference
uploadSource);
// -- Assert --
const originalXcodeProject = new xcode_manager_1.XcodeProject(sourceProjectPath);
(0, vitest_1.expect)(xcodeProject.objects.XCBuildConfiguration).toEqual(originalXcodeProject.objects.XCBuildConfiguration);
});
});
(0, vitest_1.describe)('no build configurations found', () => {
(0, vitest_1.it)('should not update the Xcode project', () => {
// -- Arrange --
xcodeProject.objects.XCBuildConfiguration = {};
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Invalid Target Name', false, // Ignore SPM reference
uploadSource);
// -- Assert --
(0, vitest_1.expect)(xcodeProject.objects.XCBuildConfiguration).toEqual({});
});
});
(0, vitest_1.describe)('build configurations found', () => {
const debugProjectBuildConfigurationListId = 'D4E604DA2D50CEEE00CAB00F';
const releaseProjectBuildConfigurationListId = 'D4E604DB2D50CEEE00CAB00F';
const debugTargetBuildConfigurationListId = 'D4E604DD2D50CEEE00CAB00F';
const releaseTargetBuildConfigurationListId = 'D4E604DE2D50CEEE00CAB00F';
(0, vitest_1.it)('should update the target configuration lists', () => {
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Project', false, // Ignore SPM reference
uploadSource);
// -- Assert --
(0, vitest_1.expect)(xcodeProject.objects.XCBuildConfiguration).toBeDefined();
// Both Debug and Release are configured equally
const expectedConfigKeys = [
debugTargetBuildConfigurationListId,
releaseTargetBuildConfigurationListId, // Release
];
for (const key of expectedConfigKeys) {
const buildConfiguration = xcodeProject.objects
.XCBuildConfiguration?.[key];
(0, vitest_1.expect)(buildConfiguration).toBeDefined();
(0, vitest_1.expect)(typeof buildConfiguration).not.toBe('string');
const buildSettings = buildConfiguration.buildSettings ?? {};
(0, vitest_1.expect)(buildSettings.DEBUG_INFORMATION_FORMAT).toBe('"dwarf-with-dsym"');
(0, vitest_1.expect)(buildSettings.ENABLE_USER_SCRIPT_SANDBOXING).toBe('"NO"');
}
});
(0, vitest_1.it)('should not update the project configuration lists', () => {
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Project', false, // Ignore SPM reference
uploadSource);
// -- Assert --
(0, vitest_1.expect)(xcodeProject.objects.XCBuildConfiguration).toBeDefined();
// Check project build configurations 'Debug'
const debugBuildConfiguration = xcodeProject.objects
.XCBuildConfiguration?.[debugProjectBuildConfigurationListId];
(0, vitest_1.expect)(debugBuildConfiguration).toBeDefined();
(0, vitest_1.expect)(typeof debugBuildConfiguration).not.toBe('string');
(0, vitest_1.expect)(debugBuildConfiguration.buildSettings
?.DEBUG_INFORMATION_FORMAT).toBe('dwarf');
(0, vitest_1.expect)(debugBuildConfiguration.buildSettings
?.ENABLE_USER_SCRIPT_SANDBOXING).toBe('YES');
// Check project build configurations 'Release'
const releaseBuildConfiguration = xcodeProject.objects
.XCBuildConfiguration?.[releaseProjectBuildConfigurationListId];
(0, vitest_1.expect)(releaseBuildConfiguration).toBeDefined();
(0, vitest_1.expect)(typeof releaseBuildConfiguration).not.toBe('string');
(0, vitest_1.expect)(releaseBuildConfiguration.buildSettings
?.DEBUG_INFORMATION_FORMAT).toBe('"dwarf-with-dsym"');
(0, vitest_1.expect)(releaseBuildConfiguration.buildSettings
?.ENABLE_USER_SCRIPT_SANDBOXING).toBe('YES');
});
});
});
});
});
(0, vitest_1.describe)('add SPM reference', () => {
const addSPMReference = true;
(0, vitest_1.describe)('framework build phase already contains Sentry', () => {
(0, vitest_1.it)('should not update the Xcode project', () => {
// -- Arrange --
xcodeProject.objects.PBXFrameworksBuildPhase = {
'framework-id': {
isa: 'PBXFrameworksBuildPhase',
files: [
{
value: '123',
comment: 'Sentry in Frameworks',
},
],
},
};
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Project', addSPMReference);
// -- Assert --
const expectedXcodeProject = new xcode_manager_1.XcodeProject(sourceProjectPath);
expectedXcodeProject.objects.PBXFrameworksBuildPhase = {
'framework-id': {
isa: 'PBXFrameworksBuildPhase',
files: [
{
value: '123',
comment: 'Sentry in Frameworks',
},
],
},
};
(0, vitest_1.expect)(xcodeProject.objects.PBXFrameworksBuildPhase).toEqual(expectedXcodeProject.objects.PBXFrameworksBuildPhase);
(0, vitest_1.expect)(xcodeProject.objects.XCRemoteSwiftPackageReference).toEqual(expectedXcodeProject.objects.XCRemoteSwiftPackageReference);
(0, vitest_1.expect)(xcodeProject.objects.XCSwiftPackageProductDependency).toEqual(expectedXcodeProject.objects.XCSwiftPackageProductDependency);
});
});
(0, vitest_1.it)('should add the SPM reference to the target', () => {
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Project', addSPMReference);
// -- Assert --
// Get the target
const target = xcodeProject.objects.PBXNativeTarget?.['D4E604CC2D50CEEC00CAB00F'];
(0, vitest_1.expect)(target).toBeDefined();
if (!target) {
throw new Error('Target is undefined');
}
// Check the SPM dependency is added to the target
(0, vitest_1.expect)(target.packageProductDependencies).toEqual([
vitest_1.expect.objectContaining({
value: vitest_1.expect.any(String),
comment: 'Sentry',
}),
]);
// Check the SPM package reference object is added to the project
const remoteSwiftPackageReferences = xcodeProject.objects.XCRemoteSwiftPackageReference;
(0, vitest_1.expect)(remoteSwiftPackageReferences).toBeDefined();
if (!remoteSwiftPackageReferences) {
throw new Error('XCRemoteSwiftPackageReference is undefined');
}
const rspRefKeys = Object.keys(remoteSwiftPackageReferences);
(0, vitest_1.expect)(rspRefKeys).toHaveLength(2);
// First key is expected to be the UUID of the SPM package reference
(0, vitest_1.expect)(rspRefKeys[0]).toMatch(/^[A-F0-9]{24}$/i);
// Second key is expected to be the UUID of the SPM package reference with _comment suffix
(0, vitest_1.expect)(rspRefKeys[1]).toMatch(/^[A-F0-9]{24}_comment$/i);
(0, vitest_1.expect)(remoteSwiftPackageReferences?.[rspRefKeys[0]]).toEqual({
isa: 'XCRemoteSwiftPackageReference',
repositoryURL: '"https://github.com/getsentry/sentry-cocoa/"',
requirement: {
kind: 'upToNextMajorVersion',
minimumVersion: '8.0.0',
},
});
(0, vitest_1.expect)(remoteSwiftPackageReferences?.[rspRefKeys[1]]).toBe('XCRemoteSwiftPackageReference "sentry-cocoa"');
// Check the SPM package is a dependency of the target
const packageProductDependencies = xcodeProject.objects.XCSwiftPackageProductDependency;
(0, vitest_1.expect)(packageProductDependencies).toBeDefined();
if (!packageProductDependencies) {
throw new Error('XCSwiftPackageProductDependency is undefined');
}
const ppDepKeys = Object.keys(packageProductDependencies);
(0, vitest_1.expect)(ppDepKeys).toHaveLength(2);
// First key is expected to be the UUID of the SPM package dependency
(0, vitest_1.expect)(ppDepKeys[0]).toMatch(/^[A-F0-9]{24}$/i);
// Second key is expected to be the UUID of the SPM package dependency with _comment suffix
(0, vitest_1.expect)(ppDepKeys[1]).toMatch(/^[A-F0-9]{24}_comment$/i);
(0, vitest_1.expect)(packageProductDependencies?.[ppDepKeys[0]]).toEqual({
isa: 'XCSwiftPackageProductDependency',
package: rspRefKeys[0],
package_comment: 'XCRemoteSwiftPackageReference "sentry-cocoa"',
productName: 'Sentry',
});
});
(0, vitest_1.it)('should initialize packageProductDependencies if not present', () => {
// -- Arrange --
// Ensure the target exists but has no packageProductDependencies initially
const targetKey = 'D4E604CC2D50CEEC00CAB00F';
const target = xcodeProject.objects.PBXNativeTarget?.[targetKey];
if (target) {
// Remove packageProductDependencies to test initialization
delete target.packageProductDependencies;
}
// -- Act --
xcodeProject.updateXcodeProject(projectData, 'Project', addSPMReference);
// -- Assert --
const updatedTarget = xcodeProject.objects.PBXNativeTarget?.[targetKey];
(0, vitest_1.expect)(updatedTarget.packageProductDependencies).toBeDefined();
(0, vitest_1.expect)(updatedTarget.packageProductDependencies).toEqual([
vitest_1.expect.objectContaining({
value: vitest_1.expect.any(String),
comment: 'Sentry',
}),
]);
});
});
});
(0, vitest_1.describe)('getSourceFilesForTarget', () => {
(0, vitest_1.describe)('targets are undefined', () => {
(0, vitest_1.it)('should return undefined', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = undefined;
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
(0, vitest_1.expect)(files).toBeUndefined();
});
});
(0, vitest_1.describe)('target not found', () => {
(0, vitest_1.it)('should return undefined', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('NonExistentTarget');
// -- Assert --
(0, vitest_1.expect)(files).toBeUndefined();
});
});
(0, vitest_1.describe)('target build phases are undefined', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
Project: {
isa: 'PBXNativeTarget',
name: 'Project',
buildPhases: undefined,
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('build phases are undefined', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
Project: {
isa: 'PBXNativeTarget',
name: 'Project',
buildPhases: undefined,
},
};
xcodeProject.objects.PBXSourcesBuildPhase = undefined;
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('referenced build phase is undefined', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
Project: {
isa: 'PBXNativeTarget',
name: 'Project',
buildPhases: [
{
value: 'random-build-phase',
},
],
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('build phase files are undefined', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
Project: {
isa: 'PBXNativeTarget',
name: 'Project',
buildPhases: [
{
value: 'build-phase-key',
},
],
},
};
xcodeProject.objects.PBXSourcesBuildPhase = {
'build-phase-key': {
isa: 'PBXSourcesBuildPhase',
files: undefined,
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('build phase has no files', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(noFilesInTargetProjectPath);
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('build phase with files', () => {
let xcodeProject;
(0, vitest_1.beforeEach)(() => {
xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
'some-target': {
isa: 'PBXNativeTarget',
name: 'some-target',
buildPhases: [
{
value: 'build-phase-key',
},
],
},
};
xcodeProject.objects.PBXSourcesBuildPhase = {
'build-phase-key': {
isa: 'PBXSourcesBuildPhase',
files: [
{
value: 'file-key',
},
],
},
};
xcodeProject.objects.PBXBuildFile = {
'file-key': {
isa: 'PBXBuildFile',
fileRef: 'file-ref-key',
},
};
});
(0, vitest_1.describe)('build file objects are not defined', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
xcodeProject.objects.PBXBuildFile = undefined;
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('build file object is not found', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
xcodeProject.objects.PBXBuildFile = {};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('build file object is invalid', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
xcodeProject.objects.PBXBuildFile = {
'file-key': 'invalid-object',
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('file reference is missing', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
xcodeProject.objects.PBXBuildFile = {
'file-key': {
isa: 'PBXBuildFile',
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('file reference is invalid', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
xcodeProject.objects.PBXFileReference = {
'file-ref-key': 'invalid-object',
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('file reference path is missing', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
xcodeProject.objects.PBXFileReference = {
'file-ref-key': {
isa: 'PBXFileReference',
path: undefined,
sourceTree: 'SOURCE_ROOT',
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
});
(0, vitest_1.describe)('valid file reference', () => {
(0, vitest_1.it)('should return array with file path', () => {
// -- Arrange --
xcodeProject.objects.PBXFileReference = {
'file-ref-key': {
isa: 'PBXFileReference',
path: 'test.swift',
sourceTree: 'SOURCE_ROOT',
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([
path.join(xcodeProject.baseDir, 'test.swift'),
]);
});
});
});
(0, vitest_1.describe)('synchronized root groups', () => {
(0, vitest_1.it)('should handle missing fileSystemSynchronizedGroups', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
'some-target': {
isa: 'PBXNativeTarget',
name: 'some-target',
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
(0, vitest_1.it)('should handle empty fileSystemSynchronizedGroups', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
'some-target': {
isa: 'PBXNativeTarget',
name: 'some-target',
fileSystemSynchronizedGroups: [],
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
(0, vitest_1.it)('should handle invalid synchronized root group', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
'some-target': {
isa: 'PBXNativeTarget',
name: 'some-target',
fileSystemSynchronizedGroups: [
{
value: 'invalid-group',
},
],
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
(0, vitest_1.it)('should handle synchronized root group with missing path', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
xcodeProject.objects.PBXNativeTarget = {
'some-target': {
isa: 'PBXNativeTarget',
name: 'some-target',
fileSystemSynchronizedGroups: [
{
value: 'group-key',
},
],
},
};
xcodeProject.objects.PBXFileSystemSynchronizedRootGroup = {
'group-key': {
isa: 'PBXFileSystemSynchronizedRootGroup',
path: undefined,
sourceTree: 'SOURCE_ROOT',
},
};
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('some-target');
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);
});
(0, vitest_1.it)('should exclude files in membership exceptions', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(projectWithSynchronizedFolders);
// The subfolder 1-1-1 is a synchronized root group containing two files:
// - File-1-1-1-1.swift
// - File-1-1-1-2.swift
//
// The membership exceptions are:
// - File-1-1-1-2.swift
//
// The expected result is that File-1-1-1-1.swift is excluded from the build, but
// included due to the membership exception.
// The File-1-1-1-2.swift is excluded from the build due to the membership exception.
// Pre-condition: File-1-1-1-1.swift exists
const file1111 = path.join(xcodeProject.baseDir, 'Group 1', 'Subgroup 1-1', 'Subfolder 1-1-1', 'File-1-1-1-1.swift');
(0, vitest_1.expect)(fs.existsSync(file1111)).toBe(true);
// Pre-condition: File-1-1-1-2.swift exists
const file1112 = path.join(xcodeProject.baseDir, 'Group 1', 'Subgroup 1-1', 'Subfolder 1-1-1', 'File-1-1-1-2.swift');
(0, vitest_1.expect)(fs.existsSync(file1112)).toBe(true);
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
// Known Issue:
// The file `File-1-1-1-1.swift` is included in the source build phase, but not in the list of files.
//
// This is the group structure:
// <main group> / Group 1 / Subgroup 1-1 / Subfolder 1-1-1 / File-1-1-1-1.swift
//
// - <main group> is the root group
// - Group 1 is a group
// - Subgroup 1-1 is a nested group
// - Subfolder 1-1-1 is a synchronized root group
// - File-1-1-1-1.swift is a file in the synchronized root group Subfolder 1-1-1
//
// For no apparent reason, Xcode is picking up the file, but Group 1 is not mentioned anywhere other then the main group.
// This would require us to consider every root group as a potential source of files, which seems excessive if a project has multiple targets.
// expect(files).toContain(file1111);
(0, vitest_1.expect)(files).not.toContain(file1112);
});
(0, vitest_1.it)('should return synchronized files and files in main group', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(projectWithSynchronizedFolders);
// -- Act --
const files = xcodeProject.getSourceFilesForTarget('Project');
// -- Assert --
// The order is not guaranteed, so we need to check for each file individually
// The order in this test case is the one displayed in the Xcode UI
const group1DirPath = path.join(xcodeProject.baseDir, 'Group 1');
const subgroup1_1DirPath = path.join(group1DirPath, 'Subgroup 1-1');
const subgroup1_2DirPath = path.join(group1DirPath, 'Subgroup 1-2');
const subgroup1_1_2DirPath = path.join(subgroup1_1DirPath, 'Subgroup 1-1-2');
const sourcesDirPath = path.join(xcodeProject.baseDir, 'Sources');
const subfolder1DirPath = path.join(sourcesDirPath, 'Subfolder 1');
const subfolder2DirPath = path.join(sourcesDirPath, 'Subfolder 2');
const groupRef1_3DirPath = path.join(xcodeProject.baseDir, 'Group Reference 1-3');
(0, vitest_1.expect)(files).toContain(path.join(subgroup1_2DirPath, 'File-1-2-2.swift'));
(0, vitest_1.expect)(files).toContain(path.join(subfolder1DirPath, 'ContentView.swift'));
(0, vitest_1.expect)(files).toContain(path.join(subgroup1_2DirPath, 'File-1-2-3--relative-to-group.swift'));
(0, vitest_1.expect)(files).toContain(path.join(sourcesDirPath, 'MainApp.swift'));
(0, vitest_1.expect)(files).toContain(path.join(subfolder2DirPath, 'File.swift'));
// Absolute path
(0, vitest_1.expect)(files).toContain('/System/Library/CoreServices/SystemVersion.plist');
// Path relative to the SDK
(0, vitest_1.expect)(files).toContain('/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/SDKSettings.plist');
// Path relative to the developer directory
(0, vitest_1.expect)(files).toContain('/Applications/Xcode.app/Contents/Developer/usr/bin/git');
// Path relative to the build products directory
// NOT SUPPORTED YET
// Path relative to the project
(0, vitest_1.expect)(files).toContain(path.join(subgroup1_2DirPath, 'File-1-2-1.swift'));
(0, vitest_1.expect)(files).toContain(path.join(groupRef1_3DirPath, 'File-1-3-1.swift'));
(0, vitest_1.expect)(files).toContain(path.join(subgroup1_2DirPath, 'File-1-2-3--relative-to-project.swift'));
(0, vitest_1.expect)(files).toContain(path.join(subgroup1_1_2DirPath, 'File-1-1-2-1.swift'));
// Known Issue:
// The file `File-1-1-1-1.swift` is included in the source build phase, but not in the list of files.
//
// This is the group structure:
// <main group> / Group 1 / Subgroup 1-1 / Subfolder 1-1-1 / File-1-1-1-1.swift
//
// - <main group> is the root group
// - Group 1 is a group
// - Subgroup 1-1 is a nested group
// - Subfolder 1-1-1 is a synchronized root group
// - File-1-1-1-1.swift is a file in the synchronized root group Subfolder 1-1-1
//
// For no apparent reason, Xcode is picking up the file, but Group 1 is not mentioned anywhere other then the main group.
// This would require us to consider every root group as a potential source of files, which seems excessive if a project has multiple targets.
// expect(files).toContain(
// path.join(
// xcodeProject.baseDir,
// 'Group 1',
// 'Subgroup 1-1',
// 'Subfolder 1-1-1',
// 'File-1-1-1-1.swift',
// ),
// );
// Assert that there are no other file paths in the list
(0, vitest_1.expect)(files).toHaveLength(12);
});
});
});
(0, vitest_1.describe)('findFilesInSourceBuildPhase', () => {
(0, vitest_1.describe)('when build phase is not found', () => {
(0, vitest_1.it)('should return empty array', () => {
// -- Arrange --
const xcodeProject = new xcode_manager_1.XcodeProject(singleTargetProjectPath);
const nativeTargetId = 'D4E604CC2D50CEEC00CAB00F';
const nativeTarget = xcodeProject.objects.PBXNativeTarget?.[nativeTargetId];
nativeTarget.buildPhases = undefined;
// -- Act --
const files = xcodeProject.findFilesInSourceBuildPhase({
id: nativeTargetId,
obj: nativeTarget,
});
// -- Assert --
(0, vitest_1.expect)(files).toEqual([]);