diff --git a/.gitignore b/.gitignore index cab80ae..bb05c2b 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json # Finder (MacOS) folder config .DS_Store + +# comparison output from scripts/compare-output.ts +.compare-output diff --git a/scripts/compare-output.ts b/scripts/compare-output.ts new file mode 100644 index 0000000..0d88f78 --- /dev/null +++ b/scripts/compare-output.ts @@ -0,0 +1,94 @@ +/** + * Compare script: generates projects for each platform and validates the + * pbxproj output from the Object API migration. + * + * Usage: bun scripts/compare-output.ts + */ + +import { join } from "path"; +import { rmSync, readdirSync, statSync } from "fs"; + +const ROOT = join(import.meta.dir, ".."); +const OUT_DIR = join(ROOT, ".compare-output"); + +async function runCLI(platform: string, name: string): Promise { + const outDir = join(OUT_DIR, platform); + const proc = Bun.spawnSync( + ["bun", "src/cli.ts", name, "--platform", platform, "--org", "com.example", "--org-name", "Example Inc", "-y", "--output", outDir], + { cwd: ROOT, stderr: "pipe", stdout: "pipe" } + ); + const stdout = new TextDecoder().decode(proc.stdout as unknown as ArrayBuffer); + const stderr = new TextDecoder().decode(proc.stderr as unknown as ArrayBuffer); + if (proc.exitCode !== 0) { + throw new Error(`CLI failed for ${platform}:\n${stderr}\n${stdout}`); + } + return join(outDir, name); +} + +async function main() { + rmSync(OUT_DIR, { recursive: true, force: true }); + + const platforms = ["ios", "macos", "tvos", "watchos", "visionos", "multiplatform"]; + let allPass = true; + + for (const platform of platforms) { + console.log(`\n--- ${platform} ---`); + try { + const projectDir = await runCLI(platform, "TestApp"); + const pbxprojPath = join(projectDir, "TestApp.xcodeproj", "project.pbxproj"); + const pbxproj = await Bun.file(pbxprojPath).text(); + + // Structural checks + const checks: Record = { + hasPBXProject: pbxproj.includes("isa = PBXProject"), + hasPBXNativeTarget: pbxproj.includes("isa = PBXNativeTarget"), + hasSourcesBuildPhase: pbxproj.includes("isa = PBXSourcesBuildPhase"), + hasFrameworksBuildPhase: pbxproj.includes("isa = PBXFrameworksBuildPhase"), + hasResourcesBuildPhase: pbxproj.includes("isa = PBXResourcesBuildPhase"), + hasXCBuildConfiguration: pbxproj.includes("isa = XCBuildConfiguration"), + hasXCConfigurationList: pbxproj.includes("isa = XCConfigurationList"), + hasPBXGroup: pbxproj.includes("isa = PBXGroup"), + hasPBXFileReference: pbxproj.includes("isa = PBXFileReference"), + hasPBXBuildFile: pbxproj.includes("isa = PBXBuildFile"), + hasProductApp: pbxproj.includes("TestApp.app"), + hasBundleId: pbxproj.includes("com.example."), + hasSwiftVersion: pbxproj.includes("SWIFT_VERSION"), + hasDebugConfig: pbxproj.includes("name = Debug"), + hasReleaseConfig: pbxproj.includes("name = Release"), + hasArchiveVersion: pbxproj.includes("archiveVersion = 1"), + hasObjectVersion: pbxproj.includes("objectVersion = 56"), + hasCompatVersion: pbxproj.includes("Xcode 14.0"), + hasUpgradeCheck: pbxproj.includes("1620"), + }; + + const platformPass = Object.values(checks).every(Boolean); + if (!platformPass) allPass = false; + console.log(` Checks: ${platformPass ? "ALL PASS" : "FAILURES"}`); + for (const [key, value] of Object.entries(checks)) { + if (!value) console.log(` FAIL: ${key}`); + } + + // Count objects + const isaCounts: Record = {}; + const isaRegex = /isa = (\w+)/g; + let match; + while ((match = isaRegex.exec(pbxproj)) !== null) { + const isa = match[1]!; + isaCounts[isa] = (isaCounts[isa] || 0) + 1; + } + console.log(" Objects:", JSON.stringify(isaCounts)); + + // Save for manual diff + await Bun.write(join(OUT_DIR, `${platform}.pbxproj`), pbxproj); + } catch (err: any) { + allPass = false; + console.log(` ERROR: ${err.message}`); + } + } + + console.log(`\n${"=".repeat(50)}`); + console.log(allPass ? "ALL PLATFORMS PASS" : "SOME PLATFORMS FAILED"); + console.log(`pbxproj files saved in ${OUT_DIR}/ for manual inspection`); +} + +main().catch(console.error); diff --git a/src/generator.ts b/src/generator.ts index 74709cf..8e09c76 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -10,10 +10,14 @@ import { join, extname, basename } from "path"; import { build as buildPbxproj } from "@bacons/xcode/json"; import { build as buildWorkspace } from "@bacons/xcode/workspace"; import { build as buildScheme } from "@bacons/xcode/scheme"; -import type { - ResolvedConfig, - TemplateVariables, -} from "./resolver"; +import { + XcodeProject, + PBXGroup, + PBXNativeTarget, + XCBuildConfiguration, + XCConfigurationList, +} from "@bacons/xcode"; +import type { ResolvedConfig, TemplateVariables } from "./resolver"; import { substituteVariables, toIdentifier, toRFC1034 } from "./resolver"; /** Options passed to the generator */ @@ -32,87 +36,42 @@ export interface GenerateOptions { templateFilesBase: string; } -const FILE_TYPES: Record = { - swift: "sourcecode.swift", - m: "sourcecode.c.objc", - mm: "sourcecode.cpp.objcpp", - c: "sourcecode.c.c", - cpp: "sourcecode.cpp.cpp", - h: "sourcecode.c.h", - hpp: "sourcecode.cpp.h", - metal: "sourcecode.metal", - storyboard: "file.storyboard", - xib: "file.xib", - plist: "text.plist.xml", - entitlements: "text.plist.entitlements", - xcassets: "folder.assetcatalog", - xcdatamodeld: "wrapper.xcdatamodeld", - xcdatamodel: "wrapper.xcdatamodel", - strings: "text.plist.strings", - stringsdict: "text.plist.stringsdict", - json: "text.json", - js: "sourcecode.javascript", - png: "image.png", - jpg: "image.jpeg", - gif: "image.gif", - pdf: "image.pdf", - ttf: "file", - otf: "file", -}; - -function getFileType(filename: string): string | undefined { - const ext = extname(filename).slice(1).toLowerCase(); - return FILE_TYPES[ext]; +/** Check if a file type string represents a source file that belongs in the Sources build phase. */ +function isSourceFileType(fileType: string | undefined): boolean { + return fileType?.startsWith("sourcecode.") ?? false; } -function isSourceFile(filename: string): boolean { - const ext = extname(filename).slice(1).toLowerCase(); - return ["swift", "m", "mm", "c", "cpp", "metal"].includes(ext); -} - -function isResourceFile(filename: string): boolean { - const ext = extname(filename).slice(1).toLowerCase(); - return [ - "xcassets", - "storyboard", - "xib", - "xcdatamodeld", - "strings", - "stringsdict", - "json", - "png", - "jpg", - "gif", - "pdf", - "ttf", - "otf", - "sks", - "scnassets", - "usdz", - ].includes(ext); -} - -function generateUUID(seed: string): string { - const hash = new Bun.CryptoHasher("md5").update(seed).digest("hex"); - return hash.slice(0, 24).toUpperCase(); -} - -let uuidCounter = 0; -function nextUUID(prefix: string): string { - return generateUUID(`${prefix}_${uuidCounter++}_${Date.now()}`); +/** Check if a file type string represents a resource that belongs in the Resources build phase. */ +function isResourceFileType(fileType: string | undefined): boolean { + if (!fileType) return false; + return ( + fileType.startsWith("image.") || + fileType.startsWith("audio.") || + fileType.startsWith("video.") || + fileType.startsWith("text.plist.strings") || + fileType.startsWith("text.plist.stringsdict") || + fileType.startsWith("text.json") || + fileType === "folder.assetcatalog" || + fileType === "file.storyboard" || + fileType === "file.xib" || + fileType === "file" || + fileType.startsWith("wrapper.xcdatamodel") + ); } function getUserName(): string { try { const proc = Bun.spawnSync(["id", "-F"]); - return new TextDecoder().decode(proc.stdout as unknown as ArrayBuffer).trim() || "Unknown"; + return ( + new TextDecoder().decode(proc.stdout as unknown as ArrayBuffer).trim() || + "Unknown" + ); } catch { return "Unknown"; } } export async function generateProject(opts: GenerateOptions): Promise { - uuidCounter = 0; const { name, outputDir, @@ -151,11 +110,16 @@ export async function generateProject(opts: GenerateOptions): Promise { config, vars, sourcesDir, - templateFilesBase + templateFilesBase, ); // Generate project.pbxproj - const { pbxproj, targetUUID } = buildProjectFile(name, config, vars, writtenFiles); + const { pbxproj, targetUUID } = buildProjectFile( + name, + config, + vars, + writtenFiles, + ); await Bun.write(join(xcodeprojDir, "project.pbxproj"), pbxproj); // Generate workspace @@ -163,7 +127,10 @@ export async function generateProject(opts: GenerateOptions): Promise { version: "1.0", children: [{ type: "FileRef", location: `self:` }], } as any); - await Bun.write(join(xcworkspaceDir, "contents.xcworkspacedata"), workspaceData); + await Bun.write( + join(xcworkspaceDir, "contents.xcworkspacedata"), + workspaceData, + ); // Generate shared scheme const schemeDir = join(xcodeprojDir, "xcshareddata", "xcschemes"); @@ -188,7 +155,7 @@ async function copyTemplateFiles( config: ResolvedConfig, vars: TemplateVariables, sourcesDir: string, - templateFilesBase: string + templateFilesBase: string, ): Promise { const writtenFiles: WrittenFile[] = []; @@ -223,11 +190,11 @@ async function copyTemplateFiles( processed = processed.replace(/___FILENAME___/g, destName); processed = processed.replace( /___FILEBASENAME___/g, - basename(destName, extname(destName)) + basename(destName, extname(destName)), ); processed = processed.replace( /___FILEBASENAMEASIDENTIFIER___/g, - toIdentifier(basename(destName, extname(destName))) + toIdentifier(basename(destName, extname(destName))), ); await Bun.write(destPath, processed); @@ -245,11 +212,7 @@ async function copyTemplateFiles( mkdirSync(assetsPath, { recursive: true }); await Bun.write( join(assetsPath, "Contents.json"), - JSON.stringify( - { info: { author: "xcode", version: 1 } }, - null, - 2 - ) + JSON.stringify({ info: { author: "xcode", version: 1 } }, null, 2), ); // AppIcon - always generate (templates don't include it, Xcode generates dynamically) @@ -257,7 +220,7 @@ async function copyTemplateFiles( mkdirSync(appIconDir, { recursive: true }); await Bun.write( join(appIconDir, "Contents.json"), - JSON.stringify(buildAppIconContents(config.platform), null, 2) + JSON.stringify(buildAppIconContents(config.platform), null, 2), ); // AccentColor @@ -271,8 +234,8 @@ async function copyTemplateFiles( info: { author: "xcode", version: 1 }, }, null, - 2 - ) + 2, + ), ); if (!writtenFiles.some((f) => f.filename === "Assets.xcassets")) { @@ -292,9 +255,7 @@ function buildAppIconContents(platform: string): any { switch (platform) { case "ios": return { - images: [ - { idiom: "universal", platform: "ios", size: "1024x1024" }, - ], + images: [{ idiom: "universal", platform: "ios", size: "1024x1024" }], info, }; case "macos": @@ -348,51 +309,14 @@ function buildProjectFile( name: string, config: ResolvedConfig, vars: TemplateVariables, - writtenFiles: WrittenFile[] + writtenFiles: WrittenFile[], ): { pbxproj: string; targetUUID: string } { - const identifier = toIdentifier(name); const bundleId = `${toRFC1034(vars.bundleIdentifierPrefix)}.${toRFC1034(name)}`; - // Generate UUIDs for all objects - const rootProjectUUID = nextUUID("rootProject"); - const mainGroupUUID = nextUUID("mainGroup"); - const sourcesGroupUUID = nextUUID("sourcesGroup"); - const productsGroupUUID = nextUUID("productsGroup"); - const productRefUUID = nextUUID("productRef"); - const targetUUID = nextUUID("target"); - const projectConfigListUUID = nextUUID("projectConfigList"); - const targetConfigListUUID = nextUUID("targetConfigList"); - const projectDebugConfigUUID = nextUUID("projectDebugConfig"); - const projectReleaseConfigUUID = nextUUID("projectReleaseConfig"); - const targetDebugConfigUUID = nextUUID("targetDebugConfig"); - const targetReleaseConfigUUID = nextUUID("targetReleaseConfig"); - const sourcesBuildPhaseUUID = nextUUID("sourcesBuildPhase"); - const frameworksBuildPhaseUUID = nextUUID("frameworksBuildPhase"); - const resourcesBuildPhaseUUID = nextUUID("resourcesBuildPhase"); - // Determine deployment target settings based on platform const platformSettings = getPlatformSettings(config.platform); - // Build file references and build files - const fileRefUUIDs: Record = {}; - const buildFileUUIDs: Record = {}; - const sourcesBuildFiles: string[] = []; - const resourcesBuildFiles: string[] = []; - - for (const file of writtenFiles) { - const refUUID = nextUUID(`ref_${file.filename}`); - const buildUUID = nextUUID(`build_${file.filename}`); - fileRefUUIDs[file.filename] = refUUID; - buildFileUUIDs[file.filename] = buildUUID; - - if (file.isDirectory || isResourceFile(file.filename)) { - resourcesBuildFiles.push(file.filename); - } else if (isSourceFile(file.filename)) { - sourcesBuildFiles.push(file.filename); - } - } - - // Merge project build settings from template + // Merge build settings const projectShared: Record = { ...config.projectSharedSettings, LOCALIZATION_PREFERS_STRING_CATALOGS: "YES", @@ -440,7 +364,8 @@ function buildProjectFile( ALWAYS_SEARCH_USER_PATHS: "NO", }; - const projectDebug: Record = { + const projectDebugSettings: Record = { + ...projectShared, ...config.projectDebugSettings, DEBUG_INFORMATION_FORMAT: "dwarf", ENABLE_TESTABILITY: "YES", @@ -453,7 +378,8 @@ function buildProjectFile( SWIFT_OPTIMIZATION_LEVEL: "-Onone", }; - const projectRelease: Record = { + const projectReleaseSettings: Record = { + ...projectShared, ...config.projectReleaseSettings, DEBUG_INFORMATION_FORMAT: "dwarf-with-dsym", ENABLE_NS_ASSERTIONS: "NO", @@ -478,192 +404,153 @@ function buildProjectFile( SWIFT_APPROACHABLE_CONCURRENCY: "YES", SWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY: "YES", SWIFT_DEFAULT_ACTOR_ISOLATION: "MainActor", + ...platformSettings.deploymentTarget, }; - // Add platform-specific deployment target & SDKROOT - Object.assign(targetShared, platformSettings.deploymentTarget); - - const targetDebug: Record = { + const targetDebugSettings: Record = { + ...targetShared, ...config.targetDebugSettings, }; - const targetRelease: Record = { + const targetReleaseSettings: Record = { + ...targetShared, ...config.targetReleaseSettings, VALIDATE_PRODUCT: "YES", }; - // Build the pbxproj using @bacons/xcode/json's build() - const objects: Record = {}; - - // Project configuration list - objects[projectDebugConfigUUID] = { - isa: "XCBuildConfiguration", - buildSettings: { ...projectShared, ...projectDebug }, - name: "Debug", - }; - objects[projectReleaseConfigUUID] = { - isa: "XCBuildConfiguration", - buildSettings: { ...projectShared, ...projectRelease }, - name: "Release", - }; - objects[projectConfigListUUID] = { - isa: "XCConfigurationList", - buildConfigurations: [projectDebugConfigUUID, projectReleaseConfigUUID], - defaultConfigurationIsVisible: 0, - defaultConfigurationName: "Release", - }; - - // Target configuration list - objects[targetDebugConfigUUID] = { - isa: "XCBuildConfiguration", - buildSettings: { ...targetShared, ...targetDebug }, - name: "Debug", - }; - objects[targetReleaseConfigUUID] = { - isa: "XCBuildConfiguration", - buildSettings: { ...targetShared, ...targetRelease }, - name: "Release", - }; - objects[targetConfigListUUID] = { - isa: "XCConfigurationList", - buildConfigurations: [targetDebugConfigUUID, targetReleaseConfigUUID], - defaultConfigurationIsVisible: 0, - defaultConfigurationName: "Release", - }; - - // File references - for (const file of writtenFiles) { - const refUUID = fileRefUUIDs[file.filename]!; - const fileType = getFileType(file.filename); - objects[refUUID] = { - isa: "PBXFileReference", - lastKnownFileType: fileType, - path: file.filename, - sourceTree: "", - }; - if (file.filename.endsWith(".swift")) { - objects[refUUID].lastKnownFileType = "sourcecode.swift"; - } - } - - // Product reference - objects[productRefUUID] = { - isa: "PBXFileReference", - explicitFileType: "wrapper.application", - includeInIndex: 0, - path: `${name}.app`, - sourceTree: "BUILT_PRODUCTS_DIR", - }; + // Create XcodeProject with a minimal bootstrap (rootObject must exist in objects) + const bootstrapRootUUID = "00000000000000000000ROOT"; + const xcproj = new XcodeProject("project.pbxproj", { + archiveVersion: 1, + objectVersion: 56, + classes: {}, + objects: { + [bootstrapRootUUID]: { + isa: "PBXProject", + buildConfigurationList: "PLACEHOLDER", + mainGroup: "PLACEHOLDER", + targets: [], + // These will be overridden by setupDefaults + our explicit values + compatibilityVersion: "Xcode 14.0", + attributes: { + BuildIndependentTargetsInParallel: "YES", + LastSwiftUpdateCheck: "1620", + LastUpgradeCheck: "1620", + TargetAttributes: {}, + }, + } as any, + }, + rootObject: bootstrapRootUUID, + }); - // Build files (for build phases) - for (const filename of sourcesBuildFiles) { - objects[buildFileUUIDs[filename]!] = { - isa: "PBXBuildFile", - fileRef: fileRefUUIDs[filename]!, - }; - } - for (const filename of resourcesBuildFiles) { - objects[buildFileUUIDs[filename]!] = { - isa: "PBXBuildFile", - fileRef: fileRefUUIDs[filename]!, - }; - } + // Now build the real structure using the Object API - // Build phases - objects[sourcesBuildPhaseUUID] = { - isa: "PBXSourcesBuildPhase", - buildActionMask: 2147483647, - files: sourcesBuildFiles.map((f) => buildFileUUIDs[f]), - runOnlyForDeploymentPostprocessing: 0, - }; - objects[frameworksBuildPhaseUUID] = { - isa: "PBXFrameworksBuildPhase", - buildActionMask: 2147483647, - files: [], - runOnlyForDeploymentPostprocessing: 0, - }; - objects[resourcesBuildPhaseUUID] = { - isa: "PBXResourcesBuildPhase", - buildActionMask: 2147483647, - files: resourcesBuildFiles.map((f) => buildFileUUIDs[f]), - runOnlyForDeploymentPostprocessing: 0, - }; + // Main group (root of the file tree) + const mainGroup = PBXGroup.create(xcproj, { + sourceTree: "", + }); - // Sources group (contains all project files) - objects[sourcesGroupUUID] = { - isa: "PBXGroup", - children: writtenFiles.map((f) => fileRefUUIDs[f.filename]), + // Sources group (contains project files) + const sourcesGroup = mainGroup.createGroup({ path: name, sourceTree: "", - }; + }); // Products group - objects[productsGroupUUID] = { - isa: "PBXGroup", - children: [productRefUUID], + const productsGroup = mainGroup.createGroup({ name: "Products", sourceTree: "", - }; + }); - // Main group - objects[mainGroupUUID] = { - isa: "PBXGroup", - children: [sourcesGroupUUID, productsGroupUUID], - sourceTree: "", - }; + // Product reference (.app bundle) + const productRef = productsGroup.createFile({ + explicitFileType: "wrapper.application", + includeInIndex: 0, + path: `${name}.app`, + sourceTree: "BUILT_PRODUCTS_DIR", + }); // Native target - objects[targetUUID] = { - isa: "PBXNativeTarget", - buildConfigurationList: targetConfigListUUID, - buildPhases: [ - sourcesBuildPhaseUUID, - frameworksBuildPhaseUUID, - resourcesBuildPhaseUUID, - ], - buildRules: [], - dependencies: [], + const target = PBXNativeTarget.create(xcproj, { name, productName: name, - productReference: productRefUUID, - productType: config.productType, - }; + productType: config.productType as any, + buildConfigurationList: XCConfigurationList.create(xcproj, { + buildConfigurations: [ + XCBuildConfiguration.create(xcproj, { + name: "Debug", + buildSettings: targetDebugSettings as any, + }), + XCBuildConfiguration.create(xcproj, { + name: "Release", + buildSettings: targetReleaseSettings as any, + }), + ], + defaultConfigurationName: "Release", + }), + productReference: productRef, + }); - // Root project - objects[rootProjectUUID] = { - isa: "PBXProject", - attributes: { - BuildIndependentTargetsInParallel: 1, - LastSwiftUpdateCheck: "1620", - LastUpgradeCheck: "1620", - TargetAttributes: { - [targetUUID]: { - CreatedOnToolsVersion: "16.2", - }, + // Build phases — get-or-create via the target (sets defaults automatically) + const sourcesBuildPhase = target.getSourcesBuildPhase(); + target.getFrameworksBuildPhase(); + const resourcesBuildPhase = target.getResourcesBuildPhase(); + + // Create file references and add to appropriate build phases + for (const file of writtenFiles) { + // createFile infers lastKnownFileType from extension and adds to group + const fileRef = sourcesGroup.createFile({ + path: file.filename, + sourceTree: "", + }); + + const fileType = fileRef.props.lastKnownFileType; + + if (file.isDirectory || isResourceFileType(fileType)) { + resourcesBuildPhase.createFile({ fileRef }); + } else if (isSourceFileType(fileType)) { + sourcesBuildPhase.createFile({ fileRef }); + } + } + + + // Update the root project object with real references + const rootProject = xcproj.rootObject; + + rootProject.props.buildConfigurationList = XCConfigurationList.create( + xcproj, + { + buildConfigurations: [ + XCBuildConfiguration.create(xcproj, { + name: "Debug", + buildSettings: projectDebugSettings as any, + }), + XCBuildConfiguration.create(xcproj, { + name: "Release", + buildSettings: projectReleaseSettings as any, + }), + ], + defaultConfigurationName: "Release", + }, + ); + rootProject.props.mainGroup = mainGroup; + rootProject.props.productRefGroup = productsGroup; + rootProject.props.targets = [target]; + rootProject.props.attributes = { + BuildIndependentTargetsInParallel: "YES", + LastSwiftUpdateCheck: "1620", + LastUpgradeCheck: "1620", + TargetAttributes: { + [target.uuid]: { + CreatedOnToolsVersion: "16.2", }, }, - buildConfigurationList: projectConfigListUUID, - compatibilityVersion: "Xcode 14.0", - developmentRegion: "en", - hasScannedForEncodings: 0, - knownRegions: ["en", "Base"], - mainGroup: mainGroupUUID, - productRefGroup: productsGroupUUID, - projectDirPath: "", - projectRoot: "", - targets: [targetUUID], }; - // Use @bacons/xcode to serialize + // Serialize via the Object API → JSON → pbxproj string return { - pbxproj: buildPbxproj({ - archiveVersion: 1, - objectVersion: 56, - classes: {}, - objects, - rootObject: rootProjectUUID, - }), - targetUUID, + pbxproj: buildPbxproj(xcproj.toJSON()), + targetUUID: target.uuid, }; } @@ -686,8 +573,7 @@ function getPlatformSettings(platform: string): PlatformConfig { "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight", INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad: "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight", - LD_RUNPATH_SEARCH_PATHS: - "$(inherited) @executable_path/Frameworks", + LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks", ENABLE_PREVIEWS: "YES", }, deploymentTarget: { @@ -713,8 +599,7 @@ function getPlatformSettings(platform: string): PlatformConfig { targetSettings: { SDKROOT: "appletvos", TARGETED_DEVICE_FAMILY: "3", - LD_RUNPATH_SEARCH_PATHS: - "$(inherited) @executable_path/Frameworks", + LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks", ENABLE_PREVIEWS: "YES", }, deploymentTarget: { @@ -726,8 +611,7 @@ function getPlatformSettings(platform: string): PlatformConfig { targetSettings: { SDKROOT: "watchos", TARGETED_DEVICE_FAMILY: "4", - LD_RUNPATH_SEARCH_PATHS: - "$(inherited) @executable_path/Frameworks", + LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks", ENABLE_PREVIEWS: "YES", SKIP_INSTALL: "YES", }, @@ -740,8 +624,7 @@ function getPlatformSettings(platform: string): PlatformConfig { targetSettings: { SDKROOT: "xros", TARGETED_DEVICE_FAMILY: "7", - LD_RUNPATH_SEARCH_PATHS: - "$(inherited) @executable_path/Frameworks", + LD_RUNPATH_SEARCH_PATHS: "$(inherited) @executable_path/Frameworks", ENABLE_PREVIEWS: "YES", }, deploymentTarget: { @@ -773,7 +656,11 @@ function getPlatformSettings(platform: string): PlatformConfig { } } -function buildSchemeXml(name: string, config: ResolvedConfig, targetUUID: string): string { +function buildSchemeXml( + name: string, + config: ResolvedConfig, + targetUUID: string, +): string { const ref = { buildableIdentifier: "primary", blueprintIdentifier: targetUUID,