UNPKG

react-native-node-api

Version:
294 lines (293 loc) 14.6 kB
"use strict"; 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, }; }