Skip to content

Commit 62f421b

Browse files
kraenhansenclaude
andauthored
Isolate plist reading and writing in the host's plist module (#474)
* Share readAndParsePlist with verify-prebuilds Move readAndParsePlist into its own module and export it from the Node.js entrypoint, so verify-prebuilds can use it instead of its own copy. Keeping it out of cli/apple.ts avoids loading @bacons/xcode and the CLI utilities from the package's main entrypoint. Closes #472 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * Move plist writing into the plist module Moves the XML write helper out of prebuilds/apple.ts into plist.ts as writePlist, and uses it for the two Info.plist writers in cli/apple.ts, so plist.ts is the only module importing @expo/plist or bplist-parser. readAndParsePlist's assertion now says "a plist", as it reads any plist. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 726829a commit 62f421b

9 files changed

Lines changed: 56 additions & 67 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"react-native-node-api": patch
3+
---
4+
5+
Export `readAndParsePlist`, which reads an XML or binary plist, from the package's Node.js entrypoint.

‎packages/host/src/node/cli/apple.test.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import cp from "node:child_process";
66

77
import {
88
linkFlatFramework,
9-
readAndParsePlist,
109
readFrameworkInfo,
1110
readXcframeworkInfo,
1211
restoreVersionedFrameworkSymlinks,
1312
} from "./apple";
13+
import { readAndParsePlist } from "../plist";
1414
import { setupTempDirectory } from "../test-utils";
1515

1616
// Generated running "base64 -i <path-to-binary-plist>" on a plist file from a framework in the node-examples package
@@ -55,7 +55,7 @@ describe("readAndParsePlist", () => {
5555

5656
await assert.rejects(
5757
() => readAndParsePlist(infoPlistPath),
58-
/Expected an Info.plist/,
58+
/Expected a plist/,
5959
);
6060
});
6161

‎packages/host/src/node/cli/apple.ts‎

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import assert from "node:assert/strict";
22
import path from "node:path";
33
import fs from "node:fs";
44

5-
import plist from "@expo/plist";
6-
import * as bplist from "bplist-parser";
75
import * as xcode from "@bacons/xcode";
86
import * as xcodeJson from "@bacons/xcode/json";
97
import * as zod from "zod";
108

119
import { chalk, prettyPath, spawn } from "@react-native-node-api/cli-utils";
1210

1311
import { getLibraryName } from "../path-utils.js";
12+
import { readAndParsePlist, writePlist } from "../plist.js";
1413
import {
1514
LinkModuleOptions,
1615
LinkModuleResult,
@@ -112,25 +111,6 @@ export async function ensureXcodeBuildPhase(fromPath: string) {
112111
}
113112
}
114113

115-
/**
116-
* Reads and parses a plist file in either XML or binary format.
117-
*/
118-
export async function readAndParsePlist(plistPath: string): Promise<unknown> {
119-
assert(fs.existsSync(plistPath), `Expected an Info.plist: ${plistPath}`);
120-
try {
121-
const contents = await fs.promises.readFile(plistPath);
122-
if (contents.subarray(0, 6).toString("ascii") === "bplist") {
123-
const [result] = bplist.parseBuffer<unknown>(contents);
124-
return result;
125-
}
126-
return plist.parse(contents.toString("utf-8")) as unknown;
127-
} catch (cause) {
128-
throw new Error(`Failed to read and parse plist at path "${plistPath}"`, {
129-
cause,
130-
});
131-
}
132-
}
133-
134114
// Using a looseObject to allow additional fields that we don't know about
135115
const XcframeworkInfoSchema = zod.looseObject({
136116
AvailableLibraries: zod.array(
@@ -157,9 +137,10 @@ export async function writeXcframeworkInfo(
157137
xcframeworkPath: string,
158138
info: zod.infer<typeof XcframeworkInfoSchema>,
159139
) {
160-
const infoPlistPath = path.join(xcframeworkPath, "Info.plist");
161-
const infoPlistXml = plist.build(info);
162-
await fs.promises.writeFile(infoPlistPath, infoPlistXml, "utf-8");
140+
await writePlist({
141+
path: path.join(xcframeworkPath, "Info.plist"),
142+
plist: info,
143+
});
163144
}
164145

165146
const FrameworkInfoSchema = zod.looseObject({
@@ -177,8 +158,7 @@ export async function writeFrameworkInfo(
177158
infoPlistPath: string,
178159
info: zod.infer<typeof FrameworkInfoSchema>,
179160
) {
180-
const infoPlistXml = plist.build(info);
181-
await fs.promises.writeFile(infoPlistPath, infoPlistXml, "utf-8");
161+
await writePlist({ path: infoPlistPath, plist: info });
182162
}
183163

184164
type LinkFrameworkOptions = {

‎packages/host/src/node/index.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,5 @@ export {
2626
determineLibraryBasename,
2727
dereferenceDirectory,
2828
} from "./path-utils.js";
29+
30+
export { readAndParsePlist } from "./plist.js";

‎packages/host/src/node/plist.ts‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs";
3+
4+
import plist from "@expo/plist";
5+
import * as bplist from "bplist-parser";
6+
7+
/**
8+
* Reads and parses a plist file in either XML or binary format.
9+
*/
10+
export async function readAndParsePlist(plistPath: string): Promise<unknown> {
11+
assert(fs.existsSync(plistPath), `Expected a plist: ${plistPath}`);
12+
try {
13+
const contents = await fs.promises.readFile(plistPath);
14+
if (contents.subarray(0, 6).toString("ascii") === "bplist") {
15+
const [result] = bplist.parseBuffer<unknown>(contents);
16+
return result;
17+
}
18+
return plist.parse(contents.toString("utf-8")) as unknown;
19+
} catch (cause) {
20+
throw new Error(`Failed to read and parse plist at path "${plistPath}"`, {
21+
cause,
22+
});
23+
}
24+
}
25+
26+
/** Serialize a plist object as XML and write it to the given path. */
27+
export async function writePlist({
28+
path: plistPath,
29+
plist: plistDict,
30+
}: {
31+
path: string;
32+
plist: Record<string, unknown>;
33+
}) {
34+
await fs.promises.writeFile(plistPath, plist.build(plistDict), "utf8");
35+
}

‎packages/host/src/node/prebuilds/apple.ts‎

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import fs from "node:fs";
33
import path from "node:path";
44
import os from "node:os";
55

6-
import plist from "@expo/plist";
76
import { spawn } from "@react-native-node-api/cli-utils";
87

98
import { determineLibraryBasename } from "../path-utils.js";
9+
import { writePlist } from "../plist.js";
1010

1111
type XCframeworkOptions = {
1212
frameworkPaths: string[];
@@ -22,17 +22,6 @@ export function escapeBundleIdentifier(input: string) {
2222
return input.replace(/[^A-Za-z0-9-.]/g, "-");
2323
}
2424

25-
/** Serialize a plist object and write it to the given path. */
26-
async function writeInfoPlist({
27-
path: infoPlistPath,
28-
plist: plistDict,
29-
}: {
30-
path: string;
31-
plist: Record<string, unknown>;
32-
}) {
33-
await fs.promises.writeFile(infoPlistPath, plist.build(plistDict), "utf8");
34-
}
35-
3625
/** Build and write the framework Info.plist to the given path. */
3726
async function writeFrameworkInfoPlist({
3827
path: infoPlistPath,
@@ -43,7 +32,7 @@ async function writeFrameworkInfoPlist({
4332
libraryName: string;
4433
bundleIdentifier?: string;
4534
}) {
46-
await writeInfoPlist({
35+
await writePlist({
4736
path: infoPlistPath,
4837
plist: {
4938
CFBundleDevelopmentRegion: "en",

‎packages/node-addon-examples/package.json‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@
3030
"bootstrap": "node --run copy-and-build"
3131
},
3232
"devDependencies": {
33-
"@expo/plist": "0.4.7",
34-
"bplist-parser": "^0.5.0",
3533
"cmake-rn": "workspace:*",
3634
"gyp-to-cmake": "workspace:*",
3735
"node-addon-examples": "github:nodejs/node-addon-examples#4b7dd86a85644610e6de80154df9acac9329b509",

‎packages/node-addon-examples/scripts/verify-prebuilds.mts‎

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,11 @@ import fs from "node:fs";
22
import assert from "node:assert/strict";
33
import path from "node:path";
44

5-
import plistModule from "@expo/plist";
6-
import { parseBuffer } from "bplist-parser";
5+
import { readAndParsePlist } from "react-native-node-api";
76
import { z } from "zod";
87

98
import { DIRS } from "./cmake-projects.mjs";
109

11-
// @expo/plist is CJS with an `export default`; under this genuine ESM
12-
// (.mts) module's interop, the default import binds to the whole
13-
// `module.exports`, nesting the real API one `.default` deeper.
14-
const plist = plistModule.default;
15-
1610
const FrameworkInfoPlistSchema = z.object({
1711
CFBundleExecutable: z.string(),
1812
});
@@ -52,20 +46,12 @@ async function verifyAndroidPrebuild(dirent: fs.Dirent) {
5246
}
5347
}
5448

55-
async function readPlist(plistPath: string): Promise<unknown> {
56-
const contents = await fs.promises.readFile(plistPath);
57-
// xcodebuild emits binary plists, while the host writes XML ones
58-
if (contents.subarray(0, 6).toString("ascii") === "bplist") {
59-
const [parsed] = parseBuffer<unknown>(contents);
60-
return parsed;
61-
}
62-
return plist.parse(contents.toString("utf8"));
63-
}
64-
6549
async function verifyFrameworkInfoPlist(frameworkDir: string) {
6650
const libraryName = path.basename(frameworkDir, ".framework");
6751
const infoPlistPath = path.join(frameworkDir, "Info.plist");
68-
const parsed = FrameworkInfoPlistSchema.parse(await readPlist(infoPlistPath));
52+
const parsed = FrameworkInfoPlistSchema.parse(
53+
await readAndParsePlist(infoPlistPath),
54+
);
6955
assert.equal(
7056
parsed.CFBundleExecutable,
7157
libraryName,

‎pnpm-lock.yaml‎

Lines changed: 0 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)