From 86a67695d6358de4b1f051baaede8e21950801db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 21 Oct 2025 10:23:54 +0200 Subject: [PATCH 1/3] Convert binary plist files to xml and use a proper parser when linking --- package-lock.json | 30 +++++ packages/host/package.json | 1 + packages/host/src/node/cli/apple.test.ts | 127 +++++++++++++++++++--- packages/host/src/node/cli/apple.ts | 50 +++++---- packages/host/src/node/prebuilds/apple.ts | 18 +-- 5 files changed, 171 insertions(+), 55 deletions(-) diff --git a/package-lock.json b/package-lock.json index 16c9a3d5..48911022 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2914,6 +2914,17 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@expo/plist": { + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.4.7.tgz", + "integrity": "sha512-dGxqHPvCZKeRKDU1sJZMmuyVtcASuSYh1LPFVaM1DuffqPL36n6FMEL0iUqq2Tx3xhWk8wCnWl34IKplUjJDdA==", + "license": "MIT", + "dependencies": { + "@xmldom/xmldom": "^0.8.8", + "base64-js": "^1.2.3", + "xmlbuilder": "^15.1.1" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", @@ -6879,6 +6890,15 @@ "integrity": "sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA==", "license": "MIT" }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.11", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", + "integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -14474,6 +14494,15 @@ "async-limiter": "~1.0.0" } }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", @@ -14853,6 +14882,7 @@ "version": "0.5.1", "license": "MIT", "dependencies": { + "@expo/plist": "^0.4.7", "@react-native-node-api/cli-utils": "0.1.0", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1" diff --git a/packages/host/package.json b/packages/host/package.json index 8031a554..caab011d 100644 --- a/packages/host/package.json +++ b/packages/host/package.json @@ -80,6 +80,7 @@ ], "license": "MIT", "dependencies": { + "@expo/plist": "^0.4.7", "@react-native-node-api/cli-utils": "0.1.0", "pkg-dir": "^8.0.0", "read-pkg": "^9.0.1" diff --git a/packages/host/src/node/cli/apple.test.ts b/packages/host/src/node/cli/apple.test.ts index 191baafe..2bf6cc66 100644 --- a/packages/host/src/node/cli/apple.test.ts +++ b/packages/host/src/node/cli/apple.test.ts @@ -1,49 +1,146 @@ import assert from "node:assert/strict"; import { describe, it } from "node:test"; import path from "node:path"; -import { readInfoPlist } from "./apple"; +import fs from "node:fs"; + +import { + determineInfoPlistPath, + readInfoPlist, + updateInfoPlist, +} from "./apple"; import { setupTempDirectory } from "../test-utils"; describe("apple", () => { - describe("Info.plist lookup", () => { - it("should find Info.plist files in unversioned frameworks", async (context) => { + describe("determineInfoPlistPath", () => { + it("should find Info.plist files in unversioned frameworks", (context) => { const infoPlistContents = `...`; const infoPlistSubPath = "Info.plist"; const tempDirectoryPath = setupTempDirectory(context, { [infoPlistSubPath]: infoPlistContents, }); - const result = await readInfoPlist(tempDirectoryPath); - - assert.strictEqual(result.contents, infoPlistContents); assert.strictEqual( - result.infoPlistPath, + determineInfoPlistPath(tempDirectoryPath), path.join(tempDirectoryPath, infoPlistSubPath), ); }); - it("should find Info.plist files in versioned frameworks", async (context) => { + it("should find Info.plist files in versioned frameworks", (context) => { const infoPlistContents = `...`; const infoPlistSubPath = "Versions/Current/Resources/Info.plist"; const tempDirectoryPath = setupTempDirectory(context, { [infoPlistSubPath]: infoPlistContents, }); - const result = await readInfoPlist(tempDirectoryPath); - - assert.strictEqual(result.contents, infoPlistContents); assert.strictEqual( - result.infoPlistPath, + determineInfoPlistPath(tempDirectoryPath), path.join(tempDirectoryPath, infoPlistSubPath), ); }); - it("should throw if Info.plist is missing from framework", async (context) => { + it("should throw if Info.plist is missing from framework", (context) => { const tempDirectoryPath = setupTempDirectory(context, {}); + assert.throws( + () => determineInfoPlistPath(tempDirectoryPath), + /Unable to locate an Info.plist file within framework./, + ); + }); + }); + + describe("readInfoPlist", () => { + it("should read Info.plist contents", async (context) => { + const infoPlistContents = ` + + + + + CFBundleExecutable + ExecutableFileName + CFBundleIconFile + AppIcon + + + `; + const infoPlistSubPath = "Info.plist"; + const tempDirectoryPath = setupTempDirectory(context, { + [infoPlistSubPath]: infoPlistContents, + }); + const infoPlistPath = path.join(tempDirectoryPath, infoPlistSubPath); + + const contents = await readInfoPlist(infoPlistPath); + assert.deepEqual(contents, { + CFBundleExecutable: "ExecutableFileName", + CFBundleIconFile: "AppIcon", + }); + }); + + it("should throw if Info.plist doesn't exist", async (context) => { + const tempDirectoryPath = setupTempDirectory(context, {}); + const infoPlistPath = path.join(tempDirectoryPath, "Info.plist"); + await assert.rejects( - async () => readInfoPlist(tempDirectoryPath), - /Unable to read Info.plist for framework at path ".*?", as an Info.plist file couldn't be found./, + () => readInfoPlist(infoPlistPath), + /Unable to read Info.plist at path/, + ); + }); + }); + + describe("updateInfoPlist", () => { + it("updates an xml plist", async (context) => { + const infoPlistSubPath = "Info.plist"; + const tempDirectoryPath = setupTempDirectory(context, { + [infoPlistSubPath]: ` + + + + + CFBundleExecutable + addon + + + `, + }); + + await updateInfoPlist({ + frameworkPath: tempDirectoryPath, + oldLibraryName: "addon", + newLibraryName: "new-addon-name", + }); + + const contents = await fs.promises.readFile( + path.join(tempDirectoryPath, infoPlistSubPath), + "utf-8", + ); + assert.match(contents, /<\?xml version="1.0" encoding="UTF-8"\?>/); + assert.match( + contents, + /CFBundleExecutable<\/key>\s*new-addon-name<\/string>/, + ); + }); + + it("converts a binary plist to xml", async (context) => { + const tempDirectoryPath = setupTempDirectory(context, {}); + // Write a binary plist file + const binaryPlistContents = Buffer.from( + // Generated running "base64 -i " on a plist file from a framework in the node-examples package + "YnBsaXN0MDDfEBUBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4cICEiIyQiJSYnJChfEBNCdWlsZE1hY2hpbmVPU0J1aWxkXxAZQ0ZCdW5kbGVEZXZlbG9wbWVudFJlZ2lvbl8QEkNGQnVuZGxlRXhlY3V0YWJsZV8QEkNGQnVuZGxlSWRlbnRpZmllcl8QHUNGQnVuZGxlSW5mb0RpY3Rpb25hcnlWZXJzaW9uXxATQ0ZCdW5kbGVQYWNrYWdlVHlwZV8QGkNGQnVuZGxlU2hvcnRWZXJzaW9uU3RyaW5nXxARQ0ZCdW5kbGVTaWduYXR1cmVfEBpDRkJ1bmRsZVN1cHBvcnRlZFBsYXRmb3Jtc18QD0NGQnVuZGxlVmVyc2lvbl8QFUNTUmVzb3VyY2VzRmlsZU1hcHBlZFpEVENvbXBpbGVyXxAPRFRQbGF0Zm9ybUJ1aWxkXkRUUGxhdGZvcm1OYW1lXxARRFRQbGF0Zm9ybVZlcnNpb25aRFRTREtCdWlsZFlEVFNES05hbWVXRFRYY29kZVxEVFhjb2RlQnVpbGRfEBBNaW5pbXVtT1NWZXJzaW9uXlVJRGV2aWNlRmFtaWx5VjI0RzIzMVdFbmdsaXNoVWFkZG9uXxAPZXhhbXBsZV82LmFkZG9uUzYuMFRGTVdLUzEuMFQ/Pz8/oR9fEA9pUGhvbmVTaW11bGF0b3IJXxAiY29tLmFwcGxlLmNvbXBpbGVycy5sbHZtLmNsYW5nLjFfMFYyMkMxNDZfEA9pcGhvbmVzaW11bGF0b3JUMTguMl8QE2lwaG9uZXNpbXVsYXRvcjE4LjJUMTYyMFgxNkM1MDMyYaEpEAEACAA1AEsAZwB8AJEAsQDHAOQA+AEVAScBPwFKAVwBawF/AYoBlAGcAakBvAHLAdIB2gHgAfIB9gH7Af8CBAIGAhgCGQI+AkUCVwJcAnICdwKAAoIAAAAAAAACAQAAAAAAAAAqAAAAAAAAAAAAAAAAAAAChA==", + "base64", + ); + const binaryPlistPath = path.join(tempDirectoryPath, "Info.plist"); + await fs.promises.writeFile(binaryPlistPath, binaryPlistContents); + + await updateInfoPlist({ + frameworkPath: tempDirectoryPath, + oldLibraryName: "addon", + newLibraryName: "new-addon-name", + }); + + const contents = await fs.promises.readFile(binaryPlistPath, "utf-8"); + assert.match(contents, /<\?xml version="1.0" encoding="UTF-8"\?>/); + assert.match( + contents, + /CFBundleExecutable<\/key>\s*new-addon-name<\/string>/, ); }); }); diff --git a/packages/host/src/node/cli/apple.ts b/packages/host/src/node/cli/apple.ts index 20a9065c..bb268de3 100644 --- a/packages/host/src/node/cli/apple.ts +++ b/packages/host/src/node/cli/apple.ts @@ -3,6 +3,7 @@ import path from "node:path"; import fs from "node:fs"; import os from "node:os"; +import plist from "@expo/plist"; import { spawn } from "@react-native-node-api/cli-utils"; import { getLatestMtime, getLibraryName } from "../path-utils.js"; @@ -12,7 +13,7 @@ import { LinkModuleResult, } from "./link-modules.js"; -function determineInfoPlistPath(frameworkPath: string) { +export function determineInfoPlistPath(frameworkPath: string) { const checkedPaths = new Array(); // First, assume it is an "unversioned" framework that keeps its Info.plist in @@ -47,28 +48,15 @@ function determineInfoPlistPath(frameworkPath: string) { /** * Resolves the Info.plist file within a framework and reads its contents. */ -export async function readInfoPlist(frameworkPath: string) { - let infoPlistPath: string; +export async function readInfoPlist(infoPlistPath: string) { try { - infoPlistPath = determineInfoPlistPath(frameworkPath); + const contents = await fs.promises.readFile(infoPlistPath, "utf-8"); + return plist.parse(contents) as Record; } catch (cause) { - throw new Error( - `Unable to read Info.plist for framework at path "${frameworkPath}", as an Info.plist file couldn't be found.`, - { cause }, - ); - } - - let contents: string; - try { - contents = await fs.promises.readFile(infoPlistPath, "utf-8"); - } catch (cause) { - throw new Error( - `Unable to read Info.plist for framework at path "${frameworkPath}", due to a file system error.`, - { cause }, - ); + throw new Error(`Unable to read Info.plist at path "${infoPlistPath}"`, { + cause, + }); } - - return { infoPlistPath, contents }; } type UpdateInfoPlistOptions = { @@ -85,11 +73,25 @@ export async function updateInfoPlist({ oldLibraryName, newLibraryName, }: UpdateInfoPlistOptions) { - const { infoPlistPath, contents } = await readInfoPlist(frameworkPath); + const infoPlistPath = determineInfoPlistPath(frameworkPath); - // TODO: Use a proper plist parser - const updatedContents = contents.replaceAll(oldLibraryName, newLibraryName); - await fs.promises.writeFile(infoPlistPath, updatedContents, "utf-8"); + // Convert to XML format if needed + try { + await spawn("plutil", ["-convert", "xml1", infoPlistPath], { + outputMode: "inherit", + }); + } catch (error) { + throw new Error( + `Failed to convert Info.plist at path "${infoPlistPath}" to XML format`, + { cause: error }, + ); + } + + const contents = await readInfoPlist(infoPlistPath); + if (contents.CFBundleExecutable === oldLibraryName) { + contents.CFBundleExecutable = newLibraryName; + } + await fs.promises.writeFile(infoPlistPath, plist.build(contents), "utf-8"); } export async function linkXcframework({ diff --git a/packages/host/src/node/prebuilds/apple.ts b/packages/host/src/node/prebuilds/apple.ts index f3b1efde..33422b85 100644 --- a/packages/host/src/node/prebuilds/apple.ts +++ b/packages/host/src/node/prebuilds/apple.ts @@ -3,6 +3,7 @@ import fs from "node:fs"; import path from "node:path"; import os from "node:os"; +import plist from "@expo/plist"; import { spawn } from "@react-native-node-api/cli-utils"; import { AppleTriplet } from "./triplets.js"; @@ -23,21 +24,6 @@ export const APPLE_ARCHITECTURES = { "arm64-apple-visionos-sim": "arm64", } satisfies Record; -export function createPlistContent(values: Record) { - return [ - '', - '', - '', - " ", - ...Object.entries(values).flatMap(([key, value]) => [ - ` ${key}`, - ` ${value}`, - ]), - " ", - "", - ].join("\n"); -} - type XCframeworkOptions = { frameworkPaths: string[]; outputPath: string; @@ -59,7 +45,7 @@ export async function createAppleFramework(libraryPath: string) { // Create an empty Info.plist file await fs.promises.writeFile( path.join(frameworkPath, "Info.plist"), - createPlistContent({ + plist.build({ CFBundleDevelopmentRegion: "en", CFBundleExecutable: libraryName, CFBundleIdentifier: `com.callstackincubator.node-api.${libraryName}`, From ad2a2c52e54b28104bc6cd34f0bcc8f4baca8a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 21 Oct 2025 11:44:58 +0200 Subject: [PATCH 2/3] Add assert for platform in updateInfoPlist --- packages/host/src/node/cli/apple.test.ts | 131 +++++++++++++++-------- packages/host/src/node/cli/apple.ts | 4 + 2 files changed, 88 insertions(+), 47 deletions(-) diff --git a/packages/host/src/node/cli/apple.test.ts b/packages/host/src/node/cli/apple.test.ts index 2bf6cc66..051e7cee 100644 --- a/packages/host/src/node/cli/apple.test.ts +++ b/packages/host/src/node/cli/apple.test.ts @@ -87,10 +87,13 @@ describe("apple", () => { }); describe("updateInfoPlist", () => { - it("updates an xml plist", async (context) => { - const infoPlistSubPath = "Info.plist"; - const tempDirectoryPath = setupTempDirectory(context, { - [infoPlistSubPath]: ` + it( + "updates an xml plist", + { skip: process.platform !== "darwin" }, + async (context) => { + const infoPlistSubPath = "Info.plist"; + const tempDirectoryPath = setupTempDirectory(context, { + [infoPlistSubPath]: ` @@ -100,48 +103,82 @@ describe("apple", () => { `, - }); - - await updateInfoPlist({ - frameworkPath: tempDirectoryPath, - oldLibraryName: "addon", - newLibraryName: "new-addon-name", - }); - - const contents = await fs.promises.readFile( - path.join(tempDirectoryPath, infoPlistSubPath), - "utf-8", - ); - assert.match(contents, /<\?xml version="1.0" encoding="UTF-8"\?>/); - assert.match( - contents, - /CFBundleExecutable<\/key>\s*new-addon-name<\/string>/, - ); - }); - - it("converts a binary plist to xml", async (context) => { - const tempDirectoryPath = setupTempDirectory(context, {}); - // Write a binary plist file - const binaryPlistContents = Buffer.from( - // Generated running "base64 -i " on a plist file from a framework in the node-examples package - "YnBsaXN0MDDfEBUBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4cICEiIyQiJSYnJChfEBNCdWlsZE1hY2hpbmVPU0J1aWxkXxAZQ0ZCdW5kbGVEZXZlbG9wbWVudFJlZ2lvbl8QEkNGQnVuZGxlRXhlY3V0YWJsZV8QEkNGQnVuZGxlSWRlbnRpZmllcl8QHUNGQnVuZGxlSW5mb0RpY3Rpb25hcnlWZXJzaW9uXxATQ0ZCdW5kbGVQYWNrYWdlVHlwZV8QGkNGQnVuZGxlU2hvcnRWZXJzaW9uU3RyaW5nXxARQ0ZCdW5kbGVTaWduYXR1cmVfEBpDRkJ1bmRsZVN1cHBvcnRlZFBsYXRmb3Jtc18QD0NGQnVuZGxlVmVyc2lvbl8QFUNTUmVzb3VyY2VzRmlsZU1hcHBlZFpEVENvbXBpbGVyXxAPRFRQbGF0Zm9ybUJ1aWxkXkRUUGxhdGZvcm1OYW1lXxARRFRQbGF0Zm9ybVZlcnNpb25aRFRTREtCdWlsZFlEVFNES05hbWVXRFRYY29kZVxEVFhjb2RlQnVpbGRfEBBNaW5pbXVtT1NWZXJzaW9uXlVJRGV2aWNlRmFtaWx5VjI0RzIzMVdFbmdsaXNoVWFkZG9uXxAPZXhhbXBsZV82LmFkZG9uUzYuMFRGTVdLUzEuMFQ/Pz8/oR9fEA9pUGhvbmVTaW11bGF0b3IJXxAiY29tLmFwcGxlLmNvbXBpbGVycy5sbHZtLmNsYW5nLjFfMFYyMkMxNDZfEA9pcGhvbmVzaW11bGF0b3JUMTguMl8QE2lwaG9uZXNpbXVsYXRvcjE4LjJUMTYyMFgxNkM1MDMyYaEpEAEACAA1AEsAZwB8AJEAsQDHAOQA+AEVAScBPwFKAVwBawF/AYoBlAGcAakBvAHLAdIB2gHgAfIB9gH7Af8CBAIGAhgCGQI+AkUCVwJcAnICdwKAAoIAAAAAAAACAQAAAAAAAAAqAAAAAAAAAAAAAAAAAAAChA==", - "base64", - ); - const binaryPlistPath = path.join(tempDirectoryPath, "Info.plist"); - await fs.promises.writeFile(binaryPlistPath, binaryPlistContents); - - await updateInfoPlist({ - frameworkPath: tempDirectoryPath, - oldLibraryName: "addon", - newLibraryName: "new-addon-name", - }); - - const contents = await fs.promises.readFile(binaryPlistPath, "utf-8"); - assert.match(contents, /<\?xml version="1.0" encoding="UTF-8"\?>/); - assert.match( - contents, - /CFBundleExecutable<\/key>\s*new-addon-name<\/string>/, - ); - }); + }); + + await updateInfoPlist({ + frameworkPath: tempDirectoryPath, + oldLibraryName: "addon", + newLibraryName: "new-addon-name", + }); + + const contents = await fs.promises.readFile( + path.join(tempDirectoryPath, infoPlistSubPath), + "utf-8", + ); + assert.match(contents, /<\?xml version="1.0" encoding="UTF-8"\?>/); + assert.match( + contents, + /CFBundleExecutable<\/key>\s*new-addon-name<\/string>/, + ); + }, + ); + + it( + "converts a binary plist to xml", + { skip: process.platform !== "darwin" }, + async (context) => { + const tempDirectoryPath = setupTempDirectory(context, {}); + // Write a binary plist file + const binaryPlistContents = Buffer.from( + // Generated running "base64 -i " on a plist file from a framework in the node-examples package + "YnBsaXN0MDDfEBUBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4cICEiIyQiJSYnJChfEBNCdWlsZE1hY2hpbmVPU0J1aWxkXxAZQ0ZCdW5kbGVEZXZlbG9wbWVudFJlZ2lvbl8QEkNGQnVuZGxlRXhlY3V0YWJsZV8QEkNGQnVuZGxlSWRlbnRpZmllcl8QHUNGQnVuZGxlSW5mb0RpY3Rpb25hcnlWZXJzaW9uXxATQ0ZCdW5kbGVQYWNrYWdlVHlwZV8QGkNGQnVuZGxlU2hvcnRWZXJzaW9uU3RyaW5nXxARQ0ZCdW5kbGVTaWduYXR1cmVfEBpDRkJ1bmRsZVN1cHBvcnRlZFBsYXRmb3Jtc18QD0NGQnVuZGxlVmVyc2lvbl8QFUNTUmVzb3VyY2VzRmlsZU1hcHBlZFpEVENvbXBpbGVyXxAPRFRQbGF0Zm9ybUJ1aWxkXkRUUGxhdGZvcm1OYW1lXxARRFRQbGF0Zm9ybVZlcnNpb25aRFRTREtCdWlsZFlEVFNES05hbWVXRFRYY29kZVxEVFhjb2RlQnVpbGRfEBBNaW5pbXVtT1NWZXJzaW9uXlVJRGV2aWNlRmFtaWx5VjI0RzIzMVdFbmdsaXNoVWFkZG9uXxAPZXhhbXBsZV82LmFkZG9uUzYuMFRGTVdLUzEuMFQ/Pz8/oR9fEA9pUGhvbmVTaW11bGF0b3IJXxAiY29tLmFwcGxlLmNvbXBpbGVycy5sbHZtLmNsYW5nLjFfMFYyMkMxNDZfEA9pcGhvbmVzaW11bGF0b3JUMTguMl8QE2lwaG9uZXNpbXVsYXRvcjE4LjJUMTYyMFgxNkM1MDMyYaEpEAEACAA1AEsAZwB8AJEAsQDHAOQA+AEVAScBPwFKAVwBawF/AYoBlAGcAakBvAHLAdIB2gHgAfIB9gH7Af8CBAIGAhgCGQI+AkUCVwJcAnICdwKAAoIAAAAAAAACAQAAAAAAAAAqAAAAAAAAAAAAAAAAAAAChA==", + "base64", + ); + const binaryPlistPath = path.join(tempDirectoryPath, "Info.plist"); + await fs.promises.writeFile(binaryPlistPath, binaryPlistContents); + + await updateInfoPlist({ + frameworkPath: tempDirectoryPath, + oldLibraryName: "addon", + newLibraryName: "new-addon-name", + }); + + const contents = await fs.promises.readFile(binaryPlistPath, "utf-8"); + assert.match(contents, /<\?xml version="1.0" encoding="UTF-8"\?>/); + assert.match( + contents, + /CFBundleExecutable<\/key>\s*new-addon-name<\/string>/, + ); + }, + ); + + it( + "throws when not on darwin", + { skip: process.platform === "darwin" }, + async (context) => { + const tempDirectoryPath = setupTempDirectory(context, { + ["Info.plist"]: '', + }); + + await assert.rejects( + () => + updateInfoPlist({ + frameworkPath: tempDirectoryPath, + oldLibraryName: "addon", + newLibraryName: "new-addon-name", + }), + (err) => { + assert(err instanceof Error); + assert.match(err.message, /Failed to convert Info.plist at path/); + assert(err.cause instanceof Error); + assert.match( + err.cause.message, + /Updating Info.plist files are not supported on this platform/, + ); + return true; + }, + ); + }, + ); }); }); diff --git a/packages/host/src/node/cli/apple.ts b/packages/host/src/node/cli/apple.ts index bb268de3..90ff6064 100644 --- a/packages/host/src/node/cli/apple.ts +++ b/packages/host/src/node/cli/apple.ts @@ -77,6 +77,10 @@ export async function updateInfoPlist({ // Convert to XML format if needed try { + assert( + process.platform === "darwin", + "Updating Info.plist files are not supported on this platform", + ); await spawn("plutil", ["-convert", "xml1", infoPlistPath], { outputMode: "inherit", }); From eb8ac71407a26b1e0470313f4fc7ddd61d9de295 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Tue, 21 Oct 2025 13:07:38 +0200 Subject: [PATCH 3/3] Changed updateInfoPlist to assert old library name --- packages/host/src/node/cli/apple.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/host/src/node/cli/apple.ts b/packages/host/src/node/cli/apple.ts index 90ff6064..687b8ccf 100644 --- a/packages/host/src/node/cli/apple.ts +++ b/packages/host/src/node/cli/apple.ts @@ -92,9 +92,12 @@ export async function updateInfoPlist({ } const contents = await readInfoPlist(infoPlistPath); - if (contents.CFBundleExecutable === oldLibraryName) { - contents.CFBundleExecutable = newLibraryName; - } + assert.equal( + contents.CFBundleExecutable, + oldLibraryName, + "Unexpected CFBundleExecutable value in Info.plist", + ); + contents.CFBundleExecutable = newLibraryName; await fs.promises.writeFile(infoPlistPath, plist.build(contents), "utf-8"); }