react-native-node-api
Version:
Node-API for React Native
294 lines (293 loc) • 14.6 kB
JavaScript
;
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 () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__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 });
exports.readAndParsePlist = readAndParsePlist;
exports.readXcframeworkInfo = readXcframeworkInfo;
exports.writeXcframeworkInfo = writeXcframeworkInfo;
exports.readFrameworkInfo = readFrameworkInfo;
exports.writeFrameworkInfo = writeFrameworkInfo;
exports.linkFramework = linkFramework;
exports.linkFlatFramework = linkFlatFramework;
exports.restoreFrameworkLinks = restoreFrameworkLinks;
exports.linkVersionedFramework = linkVersionedFramework;
exports.linkXcframework = linkXcframework;
const strict_1 = __importDefault(require("node:assert/strict"));
const node_path_1 = __importDefault(require("node:path"));
const node_fs_1 = __importDefault(require("node:fs"));
const plist_1 = __importDefault(require("@expo/plist"));
const zod = __importStar(require("zod"));
const cli_utils_1 = require("@react-native-node-api/cli-utils");
const path_utils_js_1 = require("../path-utils.js");
const link_modules_js_1 = require("./link-modules.js");
/**
* Reads and parses a plist file, converting it to XML format if needed.
*/
async function readAndParsePlist(plistPath) {
(0, strict_1.default)(node_fs_1.default.existsSync(plistPath), `Expected an Info.plist: ${plistPath}`);
// Try reading the file to see if it is already in XML format
try {
const contents = await node_fs_1.default.promises.readFile(plistPath, "utf-8");
if (contents.startsWith("<?xml")) {
return plist_1.default.parse(contents);
}
try {
// Convert to XML format if needed
(0, strict_1.default)(process.platform === "darwin", "Updating Info.plist files are not supported on this platform");
await (0, cli_utils_1.spawn)("plutil", ["-convert", "xml1", plistPath], {
outputMode: "inherit",
});
}
catch (cause) {
throw new Error(`Failed to convert plist to XML: ${plistPath}`, {
cause,
});
}
return plist_1.default.parse(
// Read it again now that it is in XML format
await node_fs_1.default.promises.readFile(plistPath, "utf-8"));
}
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(zod.looseObject({
BinaryPath: zod.string(),
LibraryIdentifier: zod.string(),
LibraryPath: zod.string(),
DebugSymbolsPath: zod.string().optional(),
})),
CFBundlePackageType: zod.literal("XFWK"),
XCFrameworkFormatVersion: zod.literal("1.0"),
});
async function readXcframeworkInfo(infoPlistPath) {
const infoPlist = await readAndParsePlist(infoPlistPath);
return XcframeworkInfoSchema.parse(infoPlist);
}
async function writeXcframeworkInfo(xcframeworkPath, info) {
const infoPlistPath = node_path_1.default.join(xcframeworkPath, "Info.plist");
const infoPlistXml = plist_1.default.build(info);
await node_fs_1.default.promises.writeFile(infoPlistPath, infoPlistXml, "utf-8");
}
const FrameworkInfoSchema = zod.looseObject({
CFBundlePackageType: zod.literal("FMWK"),
CFBundleInfoDictionaryVersion: zod.literal("6.0"),
CFBundleExecutable: zod.string(),
});
async function readFrameworkInfo(infoPlistPath) {
const infoPlist = await readAndParsePlist(infoPlistPath);
return FrameworkInfoSchema.parse(infoPlist);
}
async function writeFrameworkInfo(infoPlistPath, info) {
const infoPlistXml = plist_1.default.build(info);
await node_fs_1.default.promises.writeFile(infoPlistPath, infoPlistXml, "utf-8");
}
async function linkFramework(options) {
const { frameworkPath } = options;
strict_1.default.equal(process.platform, "darwin", "Linking Apple frameworks are only supported on macOS");
(0, strict_1.default)(node_fs_1.default.existsSync(frameworkPath), `Expected framework at '${frameworkPath}'`);
if (node_fs_1.default.existsSync(node_path_1.default.join(frameworkPath, "Versions"))) {
await linkVersionedFramework(options);
}
else {
await linkFlatFramework(options);
}
}
async function linkFlatFramework({ frameworkPath, debugSymbolsPath, newLibraryName, }) {
strict_1.default.equal(process.platform, "darwin", "Linking Apple addons are only supported on macOS");
const frameworkInfoPath = node_path_1.default.join(frameworkPath, "Info.plist");
const frameworkInfo = await readFrameworkInfo(frameworkInfoPath);
// Update install name
await (0, cli_utils_1.spawn)("install_name_tool", [
"-id",
`@rpath/${newLibraryName}.framework/${newLibraryName}`,
frameworkInfo.CFBundleExecutable,
], {
outputMode: "buffered",
cwd: frameworkPath,
});
await writeFrameworkInfo(frameworkInfoPath, {
...frameworkInfo,
CFBundleExecutable: newLibraryName,
});
// Rename the actual binary
await node_fs_1.default.promises.rename(node_path_1.default.join(frameworkPath, frameworkInfo.CFBundleExecutable), node_path_1.default.join(frameworkPath, newLibraryName));
// Rename the framework directory
const newFrameworkPath = node_path_1.default.join(node_path_1.default.dirname(frameworkPath), `${newLibraryName}.framework`);
await node_fs_1.default.promises.rename(frameworkPath, newFrameworkPath);
if (debugSymbolsPath) {
const frameworkDebugSymbolsPath = node_path_1.default.join(debugSymbolsPath, `${node_path_1.default.basename(frameworkPath)}.dSYM`);
if (node_fs_1.default.existsSync(frameworkDebugSymbolsPath)) {
// Remove existing DWARF data
await node_fs_1.default.promises.rm(frameworkDebugSymbolsPath, {
recursive: true,
force: true,
});
// Rebuild DWARF data
await (0, cli_utils_1.spawn)("dsymutil", [
node_path_1.default.join(newFrameworkPath, newLibraryName),
"-o",
node_path_1.default.join(debugSymbolsPath, newLibraryName + ".dSYM"),
], {
outputMode: "buffered",
});
}
}
}
/**
* NPM packages aren't preserving internal symlinks inside versioned frameworks.
* This function attempts to restore those.
*/
async function restoreFrameworkLinks(frameworkPath) {
// Reconstruct missing symbolic links if needed
const versionsPath = node_path_1.default.join(frameworkPath, "Versions");
const versionCurrentPath = node_path_1.default.join(versionsPath, "Current");
(0, strict_1.default)(node_fs_1.default.existsSync(versionsPath), `Expected "Versions" directory inside versioned framework '${frameworkPath}'`);
if (!node_fs_1.default.existsSync(versionCurrentPath)) {
const versionDirectoryEntries = await node_fs_1.default.promises.readdir(versionsPath, {
withFileTypes: true,
});
const versionDirectoryPaths = versionDirectoryEntries
.filter((dirent) => dirent.isDirectory())
.map((dirent) => node_path_1.default.join(dirent.parentPath, dirent.name));
strict_1.default.equal(versionDirectoryPaths.length, 1, `Expected a single directory in ${versionsPath}, found ${JSON.stringify(versionDirectoryPaths)}`);
const [versionDirectoryPath] = versionDirectoryPaths;
await node_fs_1.default.promises.symlink(node_path_1.default.relative(node_path_1.default.dirname(versionCurrentPath), versionDirectoryPath), versionCurrentPath);
}
const { CFBundleExecutable } = await readFrameworkInfo(node_path_1.default.join(versionCurrentPath, "Resources", "Info.plist"));
const libraryRealPath = node_path_1.default.join(versionCurrentPath, CFBundleExecutable);
const libraryLinkPath = node_path_1.default.join(frameworkPath, CFBundleExecutable);
// Reconstruct missing symbolic links if needed
if (node_fs_1.default.existsSync(libraryRealPath) && !node_fs_1.default.existsSync(libraryLinkPath)) {
await node_fs_1.default.promises.symlink(node_path_1.default.relative(node_path_1.default.dirname(libraryLinkPath), libraryRealPath), libraryLinkPath);
}
}
async function linkVersionedFramework({ frameworkPath, newLibraryName, }) {
strict_1.default.equal(process.platform, "darwin", "Linking Apple addons are only supported on macOS");
await restoreFrameworkLinks(frameworkPath);
const frameworkInfoPath = node_path_1.default.join(frameworkPath, "Versions", "Current", "Resources", "Info.plist");
const frameworkInfo = await readFrameworkInfo(frameworkInfoPath);
// Update install name
await (0, cli_utils_1.spawn)("install_name_tool", [
"-id",
`@rpath/${newLibraryName}.framework/${newLibraryName}`,
frameworkInfo.CFBundleExecutable,
], {
outputMode: "buffered",
cwd: frameworkPath,
});
await writeFrameworkInfo(frameworkInfoPath, {
...frameworkInfo,
CFBundleExecutable: newLibraryName,
});
// Rename the actual binary
const existingBinaryPath = node_path_1.default.join(frameworkPath, frameworkInfo.CFBundleExecutable);
const stat = await node_fs_1.default.promises.lstat(existingBinaryPath);
(0, strict_1.default)(stat.isSymbolicLink(), `Expected binary to be a symlink: ${existingBinaryPath}`);
const realBinaryPath = await node_fs_1.default.promises.realpath(existingBinaryPath);
const newRealBinaryPath = node_path_1.default.join(node_path_1.default.dirname(realBinaryPath), newLibraryName);
// Rename the real binary file
await node_fs_1.default.promises.rename(realBinaryPath, newRealBinaryPath);
// Remove the old binary symlink
await node_fs_1.default.promises.unlink(existingBinaryPath);
// Create a new symlink with the new name
const newBinarySymlinkTarget = node_path_1.default.join("Versions", "Current", newLibraryName);
(0, strict_1.default)(node_fs_1.default.existsSync(node_path_1.default.join(frameworkPath, newBinarySymlinkTarget)), "Expected new binary to exist");
await node_fs_1.default.promises.symlink(newBinarySymlinkTarget, node_path_1.default.join(frameworkPath, newLibraryName));
// Rename the framework directory
await node_fs_1.default.promises.rename(frameworkPath, node_path_1.default.join(node_path_1.default.dirname(frameworkPath), `${newLibraryName}.framework`));
}
async function linkXcframework({ platform, modulePath, incremental, naming, }) {
strict_1.default.equal(process.platform, "darwin", "Linking Apple addons are only supported on macOS");
// Copy the xcframework to the output directory and rename the framework and binary
const newLibraryName = (0, path_utils_js_1.getLibraryName)(modulePath, naming);
const outputPath = (0, link_modules_js_1.getLinkedModuleOutputPath)(platform, modulePath, naming);
if (incremental && node_fs_1.default.existsSync(outputPath)) {
const moduleModified = (0, path_utils_js_1.getLatestMtime)(modulePath);
const outputModified = (0, path_utils_js_1.getLatestMtime)(outputPath);
if (moduleModified < outputModified) {
return {
originalPath: modulePath,
libraryName: newLibraryName,
outputPath,
skipped: true,
};
}
}
// Delete any existing xcframework (or xcodebuild will try to amend it)
await node_fs_1.default.promises.rm(outputPath, { recursive: true, force: true });
// Copy the existing xcframework to the output path
await node_fs_1.default.promises.cp(modulePath, outputPath, {
recursive: true,
verbatimSymlinks: true,
});
const info = await readXcframeworkInfo(node_path_1.default.join(outputPath, "Info.plist"));
await Promise.all(info.AvailableLibraries.map(async (framework) => {
const frameworkPath = node_path_1.default.join(outputPath, framework.LibraryIdentifier, framework.LibraryPath);
await linkFramework({
frameworkPath,
newLibraryName,
debugSymbolsPath: framework.DebugSymbolsPath
? node_path_1.default.join(outputPath, framework.LibraryIdentifier, framework.DebugSymbolsPath)
: undefined,
});
}));
await writeXcframeworkInfo(outputPath, {
...info,
AvailableLibraries: info.AvailableLibraries.map((library) => {
return {
...library,
LibraryPath: `${newLibraryName}.framework`,
BinaryPath: `${newLibraryName}.framework/${newLibraryName}`,
};
}),
});
// Delete any leftover "magic file"
await node_fs_1.default.promises.rm(node_path_1.default.join(outputPath, "react-native-node-api-module"), {
force: true,
});
return {
originalPath: modulePath,
libraryName: newLibraryName,
outputPath,
skipped: false,
};
}