Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shared-read-and-parse-plist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-native-node-api": patch
---

Export `readAndParsePlist`, which reads an XML or binary plist, from the package's Node.js entrypoint.
4 changes: 2 additions & 2 deletions packages/host/src/node/cli/apple.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import cp from "node:child_process";

import {
linkFlatFramework,
readAndParsePlist,
readFrameworkInfo,
readXcframeworkInfo,
restoreVersionedFrameworkSymlinks,
} from "./apple";
import { readAndParsePlist } from "../plist";
import { setupTempDirectory } from "../test-utils";

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

await assert.rejects(
() => readAndParsePlist(infoPlistPath),
/Expected an Info.plist/,
/Expected a plist/,
);
});

Expand Down
32 changes: 6 additions & 26 deletions packages/host/src/node/cli/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import assert from "node:assert/strict";
import path from "node:path";
import fs from "node:fs";

import plist from "@expo/plist";
import * as bplist from "bplist-parser";
import * as xcode from "@bacons/xcode";
import * as xcodeJson from "@bacons/xcode/json";
import * as zod from "zod";

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

import { getLibraryName } from "../path-utils.js";
import { readAndParsePlist, writePlist } from "../plist.js";
import {
LinkModuleOptions,
LinkModuleResult,
Expand Down Expand Up @@ -112,25 +111,6 @@ export async function ensureXcodeBuildPhase(fromPath: string) {
}
}

/**
* Reads and parses a plist file in either XML or binary format.
*/
export async function readAndParsePlist(plistPath: string): Promise<unknown> {
assert(fs.existsSync(plistPath), `Expected an Info.plist: ${plistPath}`);
try {
const contents = await fs.promises.readFile(plistPath);
if (contents.subarray(0, 6).toString("ascii") === "bplist") {
const [result] = bplist.parseBuffer<unknown>(contents);
return result;
}
return plist.parse(contents.toString("utf-8")) as unknown;
} catch (cause) {
throw new Error(`Failed to read and parse plist at path "${plistPath}"`, {
cause,
});
}
}

// Using a looseObject to allow additional fields that we don't know about
const XcframeworkInfoSchema = zod.looseObject({
AvailableLibraries: zod.array(
Expand All @@ -157,9 +137,10 @@ export async function writeXcframeworkInfo(
xcframeworkPath: string,
info: zod.infer<typeof XcframeworkInfoSchema>,
) {
const infoPlistPath = path.join(xcframeworkPath, "Info.plist");
const infoPlistXml = plist.build(info);
await fs.promises.writeFile(infoPlistPath, infoPlistXml, "utf-8");
await writePlist({
path: path.join(xcframeworkPath, "Info.plist"),
plist: info,
});
}

const FrameworkInfoSchema = zod.looseObject({
Expand All @@ -177,8 +158,7 @@ export async function writeFrameworkInfo(
infoPlistPath: string,
info: zod.infer<typeof FrameworkInfoSchema>,
) {
const infoPlistXml = plist.build(info);
await fs.promises.writeFile(infoPlistPath, infoPlistXml, "utf-8");
await writePlist({ path: infoPlistPath, plist: info });
}

type LinkFrameworkOptions = {
Expand Down
2 changes: 2 additions & 0 deletions packages/host/src/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export {
determineLibraryBasename,
dereferenceDirectory,
} from "./path-utils.js";

export { readAndParsePlist } from "./plist.js";
35 changes: 35 additions & 0 deletions packages/host/src/node/plist.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import assert from "node:assert/strict";
import fs from "node:fs";

import plist from "@expo/plist";
import * as bplist from "bplist-parser";

/**
* Reads and parses a plist file in either XML or binary format.
*/
export async function readAndParsePlist(plistPath: string): Promise<unknown> {
assert(fs.existsSync(plistPath), `Expected a plist: ${plistPath}`);
try {
const contents = await fs.promises.readFile(plistPath);
if (contents.subarray(0, 6).toString("ascii") === "bplist") {
const [result] = bplist.parseBuffer<unknown>(contents);
return result;
}
return plist.parse(contents.toString("utf-8")) as unknown;
} catch (cause) {
throw new Error(`Failed to read and parse plist at path "${plistPath}"`, {
cause,
});
}
}

/** Serialize a plist object as XML and write it to the given path. */
export async function writePlist({
path: plistPath,
plist: plistDict,
}: {
path: string;
plist: Record<string, unknown>;
}) {
await fs.promises.writeFile(plistPath, plist.build(plistDict), "utf8");
}
15 changes: 2 additions & 13 deletions packages/host/src/node/prebuilds/apple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ 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 { determineLibraryBasename } from "../path-utils.js";
import { writePlist } from "../plist.js";

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

/** Serialize a plist object and write it to the given path. */
async function writeInfoPlist({
path: infoPlistPath,
plist: plistDict,
}: {
path: string;
plist: Record<string, unknown>;
}) {
await fs.promises.writeFile(infoPlistPath, plist.build(plistDict), "utf8");
}

/** Build and write the framework Info.plist to the given path. */
async function writeFrameworkInfoPlist({
path: infoPlistPath,
Expand All @@ -43,7 +32,7 @@ async function writeFrameworkInfoPlist({
libraryName: string;
bundleIdentifier?: string;
}) {
await writeInfoPlist({
await writePlist({
path: infoPlistPath,
plist: {
CFBundleDevelopmentRegion: "en",
Expand Down
2 changes: 0 additions & 2 deletions packages/node-addon-examples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@
"bootstrap": "node --run copy-and-build"
},
"devDependencies": {
"@expo/plist": "0.4.7",
"bplist-parser": "^0.5.0",
"cmake-rn": "workspace:*",
"gyp-to-cmake": "workspace:*",
"node-addon-examples": "github:nodejs/node-addon-examples#4b7dd86a85644610e6de80154df9acac9329b509",
Expand Down
22 changes: 4 additions & 18 deletions packages/node-addon-examples/scripts/verify-prebuilds.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,11 @@ import fs from "node:fs";
import assert from "node:assert/strict";
import path from "node:path";

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

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

// @expo/plist is CJS with an `export default`; under this genuine ESM
// (.mts) module's interop, the default import binds to the whole
// `module.exports`, nesting the real API one `.default` deeper.
const plist = plistModule.default;

const FrameworkInfoPlistSchema = z.object({
CFBundleExecutable: z.string(),
});
Expand Down Expand Up @@ -52,20 +46,12 @@ async function verifyAndroidPrebuild(dirent: fs.Dirent) {
}
}

async function readPlist(plistPath: string): Promise<unknown> {
const contents = await fs.promises.readFile(plistPath);
// xcodebuild emits binary plists, while the host writes XML ones
if (contents.subarray(0, 6).toString("ascii") === "bplist") {
const [parsed] = parseBuffer<unknown>(contents);
return parsed;
}
return plist.parse(contents.toString("utf8"));
}

async function verifyFrameworkInfoPlist(frameworkDir: string) {
const libraryName = path.basename(frameworkDir, ".framework");
const infoPlistPath = path.join(frameworkDir, "Info.plist");
const parsed = FrameworkInfoPlistSchema.parse(await readPlist(infoPlistPath));
const parsed = FrameworkInfoPlistSchema.parse(
await readAndParsePlist(infoPlistPath),
);
assert.equal(
parsed.CFBundleExecutable,
libraryName,
Expand Down
6 changes: 0 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading