From 5c5377c9902e06244b35d9d84f32856923a6d160 Mon Sep 17 00:00:00 2001
From: Tommy Nguyen <4123478+tido64@users.noreply.github.com>
Date: Wed, 19 Mar 2025 15:44:01 +0100
Subject: [PATCH] refactor(apple): port `info_plist.rb` to JS
---
ios/entitlements.mjs | 4 +-
ios/infoPlist.mjs | 98 ++++++
ios/privacyManifest.mjs | 4 +-
ios/utils.mjs | 52 ++-
test/ios/entitlements.test.ts | 2 +-
test/ios/infoPlist.test.ts | 619 ++++++++++++++++++++++++++++++++++
test/pack.test.ts | 1 +
7 files changed, 768 insertions(+), 12 deletions(-)
create mode 100644 ios/infoPlist.mjs
create mode 100644 test/ios/infoPlist.test.ts
diff --git a/ios/entitlements.mjs b/ios/entitlements.mjs
index 8f43d2cb1..e9d11ea72 100644
--- a/ios/entitlements.mjs
+++ b/ios/entitlements.mjs
@@ -1,7 +1,7 @@
// @ts-check
import * as nodefs from "node:fs";
import * as path from "node:path";
-import { isObject, toPlist } from "./utils.mjs";
+import { isObject, plistFromJSON } from "./utils.mjs";
/** @import { ApplePlatform, JSONObject } from "../scripts/types.js"; */
@@ -56,7 +56,7 @@ export function generateEntitlements(
}
const filename = "App.entitlements";
- const entitlements = toPlist(
+ const entitlements = plistFromJSON(
{
...(targetPlatform === "macos"
? DEFAULT_MACOS_ENTITLEMENTS
diff --git a/ios/infoPlist.mjs b/ios/infoPlist.mjs
new file mode 100644
index 000000000..5387bcf9b
--- /dev/null
+++ b/ios/infoPlist.mjs
@@ -0,0 +1,98 @@
+// @ts-check
+import * as nodefs from "node:fs";
+import * as path from "node:path";
+import {
+ isObject,
+ jsonFromPlist,
+ plistFromJSON,
+ projectPath,
+ resolveResources,
+} from "./utils.mjs";
+
+/** @import { ApplePlatform, JSONArray, JSONObject } from "../scripts/types.js"; */
+
+/**
+ * @param {JSONObject} appConfig
+ * @param {ApplePlatform} targetPlatform
+ * @param {JSONObject} info
+ */
+function setMacProperties(appConfig, targetPlatform, info) {
+ if (targetPlatform !== "macos") {
+ return;
+ }
+
+ const config = appConfig[targetPlatform];
+ if (!isObject(config)) {
+ return;
+ }
+
+ const category = config["applicationCategoryType"];
+ if (typeof category === "string") {
+ info["LSApplicationCategoryType"] = category;
+ }
+
+ const copyright = config["humanReadableCopyright"];
+ if (typeof copyright === "string") {
+ info["NSHumanReadableCopyright"] = copyright;
+ }
+}
+
+/**
+ * @param {JSONObject} appConfig
+ * @param {ApplePlatform} targetPlatform
+ * @param {string} destination
+ * @returns {void}
+ */
+export function generateInfoPlist(
+ appConfig,
+ targetPlatform,
+ destination,
+ fs = nodefs
+) {
+ const filename = "ReactTestApp/Info.plist";
+ const infoPlistSrc = projectPath(filename, targetPlatform);
+
+ const info = jsonFromPlist(infoPlistSrc);
+
+ const resources = resolveResources(appConfig, targetPlatform);
+ registerFonts(resources, targetPlatform, info);
+ setMacProperties(appConfig, targetPlatform, info);
+
+ const infoPlistDst = path.join(destination, path.basename(infoPlistSrc));
+ fs.writeFileSync(infoPlistDst, plistFromJSON(info, filename));
+}
+
+/**
+ * @param {JSONArray | undefined} resources
+ * @param {ApplePlatform} targetPlatform
+ * @param {JSONObject} info
+ */
+export function registerFonts(resources, targetPlatform, info) {
+ if (!resources) {
+ return;
+ }
+
+ const fontFiles = [".otf", ".ttf"];
+ const fonts = [];
+ for (const filename of resources) {
+ if (typeof filename === "string") {
+ const extname = path.extname(filename);
+ if (fontFiles.includes(extname)) {
+ fonts.push(path.basename(filename));
+ }
+ }
+ }
+
+ if (fonts.length > 0) {
+ switch (targetPlatform) {
+ case "macos":
+ // https://developer.apple.com/documentation/bundleresources/information_property_list/atsapplicationfontspath
+ info["ATSApplicationFontsPath"] = ".";
+ break;
+ default:
+ // https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app
+ info["UIAppFonts"] = fonts;
+ break;
+ }
+ }
+}
diff --git a/ios/privacyManifest.mjs b/ios/privacyManifest.mjs
index 04e9d9441..da236b607 100644
--- a/ios/privacyManifest.mjs
+++ b/ios/privacyManifest.mjs
@@ -1,7 +1,7 @@
// @ts-check
import * as nodefs from "node:fs";
import * as path from "node:path";
-import { isObject, toPlist } from "./utils.mjs";
+import { isObject, plistFromJSON } from "./utils.mjs";
/**
* @import { ApplePlatform, JSONObject, JSONValue } from "../scripts/types.js";
@@ -107,6 +107,6 @@ export function generatePrivacyManifest(
}
const filename = "PrivacyInfo.xcprivacy";
- const xcprivacy = toPlist(manifest, filename);
+ const xcprivacy = plistFromJSON(manifest, filename);
fs.writeFileSync(path.join(destination, filename), xcprivacy);
}
diff --git a/ios/utils.mjs b/ios/utils.mjs
index d6be217f1..fcff70b38 100644
--- a/ios/utils.mjs
+++ b/ios/utils.mjs
@@ -18,13 +18,20 @@ export function isObject(obj) {
}
/**
- * @param {string} p
- * @param {ApplePlatform} targetPlatform
- * @returns {string}
+ * @param {string} filename
+ * @returns {JSONObject}
*/
-export function projectPath(p, targetPlatform) {
- const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
- return path.join(packageDir, targetPlatform, p);
+export function jsonFromPlist(filename) {
+ const args = ["-convert", "json", "-o", "-", filename];
+ const plutil = spawnSync("/usr/bin/plutil", args, {
+ stdio: ["ignore", "pipe", "inherit"],
+ });
+
+ if (plutil.status !== 0) {
+ throw new Error(`Failed to read '${filename}'`);
+ }
+
+ return JSON.parse(plutil.stdout.toString());
}
/**
@@ -32,7 +39,7 @@ export function projectPath(p, targetPlatform) {
* @param {string} filename
* @returns {string}
*/
-export function toPlist(source, filename) {
+export function plistFromJSON(source, filename) {
const args = ["-convert", "xml1", "-r", "-o", "-", "--", "-"];
const plutil = spawnSync("/usr/bin/plutil", args, {
stdio: ["pipe", "pipe", "inherit"],
@@ -45,3 +52,34 @@ export function toPlist(source, filename) {
return plutil.stdout.toString();
}
+
+/**
+ * @param {string} p
+ * @param {ApplePlatform} targetPlatform
+ * @returns {string}
+ */
+export function projectPath(p, targetPlatform) {
+ const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
+ return path.join(packageDir, targetPlatform, p);
+}
+
+/**
+ * @param {JSONObject} appConfig
+ * @param {ApplePlatform} targetPlatform
+ * @returns {JSONValue[] | undefined}
+ */
+export function resolveResources(appConfig, targetPlatform) {
+ const resources = appConfig["resources"];
+ if (resources && typeof resources === "object") {
+ if (Array.isArray(resources)) {
+ return resources;
+ }
+
+ const res = resources[targetPlatform];
+ if (Array.isArray(res)) {
+ return res;
+ }
+ }
+
+ return undefined;
+}
diff --git a/test/ios/entitlements.test.ts b/test/ios/entitlements.test.ts
index 99ef5c10c..a8585a790 100644
--- a/test/ios/entitlements.test.ts
+++ b/test/ios/entitlements.test.ts
@@ -6,7 +6,7 @@ import { fs, setMockFiles } from "../fs.mock.ts";
const macosOnly = { skip: process.platform === "win32" };
-describe("generatePrivacyManifest()", macosOnly, () => {
+describe("generateEntitlements()", macosOnly, () => {
const targetPlatforms = ["ios", "macos", "visionos"] as const;
function generateEntitlements(
diff --git a/test/ios/infoPlist.test.ts b/test/ios/infoPlist.test.ts
new file mode 100644
index 000000000..b7d62f29f
--- /dev/null
+++ b/test/ios/infoPlist.test.ts
@@ -0,0 +1,619 @@
+import { deepEqual } from "node:assert/strict";
+import { afterEach, describe, it } from "node:test";
+import { generateInfoPlist as generateInfoPlistActual } from "../../ios/infoPlist.mjs";
+import type { JSONObject } from "../../scripts/types.ts";
+import { fs, setMockFiles } from "../fs.mock.ts";
+
+const macosOnly = { skip: process.platform === "win32" };
+
+describe("generateInfoPlist()", macosOnly, () => {
+ const targetPlatforms = ["ios", "macos", "visionos"] as const;
+
+ function generateInfoPlist(
+ config: JSONObject,
+ platform: (typeof targetPlatforms)[number]
+ ): void {
+ const destination = ".";
+ fs.mkdirSync(destination, { recursive: true, mode: 0o755 });
+ generateInfoPlistActual(config, platform, destination, fs);
+ }
+
+ function readInfoPlist() {
+ return fs.readFileSync("Info.plist", { encoding: "utf-8" }).split("\n");
+ }
+
+ afterEach(() => {
+ setMockFiles();
+ });
+
+ for (const platform of targetPlatforms) {
+ it(`[${platform}] generates a default 'Info.plist'`, () => {
+ generateInfoPlist({}, platform);
+
+ deepEqual(readInfoPlist(), DEFAULT_INFO_PLIST[platform]);
+ });
+
+ it(`[${platform}] registers font files`, () => {
+ generateInfoPlist(
+ {
+ resources: ["OpenType.otf", "TrueType.ttf"],
+ },
+ platform
+ );
+
+ deepEqual(readInfoPlist(), INFO_PLIST_WITH_FONTS[platform]);
+ });
+ }
+
+ it("[macos] sets application category type", () => {
+ generateInfoPlist(
+ { macos: { applicationCategoryType: "public.app-category.games" } },
+ "macos"
+ );
+
+ deepEqual(readInfoPlist(), INFO_PLIST_MACOS.applicationCategoryType);
+ });
+
+ it("[macos] sets human-readable copyright", () => {
+ generateInfoPlist(
+ {
+ macos: {
+ humanReadableCopyright:
+ "Copyright © 2025 Contoso. All rights reserved.",
+ },
+ },
+ "macos"
+ );
+
+ deepEqual(readInfoPlist(), INFO_PLIST_MACOS.humanReadableCopyright);
+ });
+});
+
+const DEFAULT_INFO_PLIST = {
+ ios: [
+ '',
+ '',
+ '',
+ "",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleURLTypes",
+ " ",
+ " ",
+ " CFBundleURLSchemes",
+ " ",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " msauth.$(PRODUCT_BUNDLE_IDENTIFIER)",
+ " ",
+ " ",
+ " ",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationQueriesSchemes",
+ " ",
+ " linkedin",
+ " ms-outlook",
+ " ms-sfb",
+ " msauth",
+ " msauthv2",
+ " msauthv3",
+ " ",
+ " LSRequiresIPhoneOS",
+ " ",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " NSCameraUsageDescription",
+ " The camera is needed to scan QR codes for JS bundle URLs",
+ " UIApplicationSceneManifest",
+ " ",
+ " UIApplicationSupportsMultipleScenes",
+ " ",
+ " UISceneConfigurations",
+ " ",
+ " UIWindowSceneSessionRoleApplication",
+ " ",
+ " ",
+ " UISceneConfigurationName",
+ " Default Configuration",
+ " UISceneDelegateClassName",
+ " $(PRODUCT_MODULE_NAME).SceneDelegate",
+ " ",
+ " ",
+ " ",
+ " ",
+ " UILaunchScreen",
+ " ",
+ " UIRequiredDeviceCapabilities",
+ " ",
+ " arm64",
+ " ",
+ " UISupportedInterfaceOrientations",
+ " ",
+ " UIInterfaceOrientationPortrait",
+ " UIInterfaceOrientationPortraitUpsideDown",
+ " UIInterfaceOrientationLandscapeLeft",
+ " UIInterfaceOrientationLandscapeRight",
+ " ",
+ " UIViewControllerBasedStatusBarAppearance",
+ " ",
+ "",
+ "",
+ "",
+ ],
+ macos: [
+ '',
+ '',
+ '',
+ "",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationCategoryType",
+ " public.app-category.developer-tools",
+ " LSMinimumSystemVersion",
+ " $(MACOSX_DEPLOYMENT_TARGET)",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " NSHumanReadableCopyright",
+ " Copyright © 2020 Microsoft. All rights reserved.",
+ " NSMainStoryboardFile",
+ " Main",
+ " NSPrincipalClass",
+ " NSApplication",
+ " NSSupportsAutomaticTermination",
+ " ",
+ " NSSupportsSuddenTermination",
+ " ",
+ "",
+ "",
+ "",
+ ],
+ visionos: [
+ '',
+ '',
+ '',
+ "",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleURLTypes",
+ " ",
+ " ",
+ " CFBundleURLSchemes",
+ " ",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " msauth.$(PRODUCT_BUNDLE_IDENTIFIER)",
+ " ",
+ " ",
+ " ",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationQueriesSchemes",
+ " ",
+ " linkedin",
+ " ms-outlook",
+ " ms-sfb",
+ " msauth",
+ " msauthv2",
+ " msauthv3",
+ " ",
+ " LSRequiresIPhoneOS",
+ " ",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " UIApplicationSceneManifest",
+ " ",
+ " UIApplicationSupportsMultipleScenes",
+ " ",
+ " UISceneConfigurations",
+ " ",
+ " UIWindowSceneSessionRoleApplication",
+ " ",
+ " ",
+ " UISceneConfigurationName",
+ " Default Configuration",
+ " UISceneDelegateClassName",
+ " $(PRODUCT_MODULE_NAME).SceneDelegate",
+ " ",
+ " ",
+ " ",
+ " ",
+ " UILaunchScreen",
+ " ",
+ " UIRequiredDeviceCapabilities",
+ " ",
+ " arm64",
+ " ",
+ " UISupportedInterfaceOrientations",
+ " ",
+ " UIInterfaceOrientationPortrait",
+ " UIInterfaceOrientationPortraitUpsideDown",
+ " UIInterfaceOrientationLandscapeLeft",
+ " UIInterfaceOrientationLandscapeRight",
+ " ",
+ " UIViewControllerBasedStatusBarAppearance",
+ " ",
+ "",
+ "",
+ "",
+ ],
+};
+
+const INFO_PLIST_WITH_FONTS = {
+ ios: [
+ '',
+ '',
+ '',
+ "",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleURLTypes",
+ " ",
+ " ",
+ " CFBundleURLSchemes",
+ " ",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " msauth.$(PRODUCT_BUNDLE_IDENTIFIER)",
+ " ",
+ " ",
+ " ",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationQueriesSchemes",
+ " ",
+ " linkedin",
+ " ms-outlook",
+ " ms-sfb",
+ " msauth",
+ " msauthv2",
+ " msauthv3",
+ " ",
+ " LSRequiresIPhoneOS",
+ " ",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " NSCameraUsageDescription",
+ " The camera is needed to scan QR codes for JS bundle URLs",
+ " UIAppFonts",
+ " ",
+ " OpenType.otf",
+ " TrueType.ttf",
+ " ",
+ " UIApplicationSceneManifest",
+ " ",
+ " UIApplicationSupportsMultipleScenes",
+ " ",
+ " UISceneConfigurations",
+ " ",
+ " UIWindowSceneSessionRoleApplication",
+ " ",
+ " ",
+ " UISceneConfigurationName",
+ " Default Configuration",
+ " UISceneDelegateClassName",
+ " $(PRODUCT_MODULE_NAME).SceneDelegate",
+ " ",
+ " ",
+ " ",
+ " ",
+ " UILaunchScreen",
+ " ",
+ " UIRequiredDeviceCapabilities",
+ " ",
+ " arm64",
+ " ",
+ " UISupportedInterfaceOrientations",
+ " ",
+ " UIInterfaceOrientationPortrait",
+ " UIInterfaceOrientationPortraitUpsideDown",
+ " UIInterfaceOrientationLandscapeLeft",
+ " UIInterfaceOrientationLandscapeRight",
+ " ",
+ " UIViewControllerBasedStatusBarAppearance",
+ " ",
+ "",
+ "",
+ "",
+ ],
+ macos: [
+ '',
+ '',
+ '',
+ "",
+ " ATSApplicationFontsPath",
+ " .",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationCategoryType",
+ " public.app-category.developer-tools",
+ " LSMinimumSystemVersion",
+ " $(MACOSX_DEPLOYMENT_TARGET)",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " NSHumanReadableCopyright",
+ " Copyright © 2020 Microsoft. All rights reserved.",
+ " NSMainStoryboardFile",
+ " Main",
+ " NSPrincipalClass",
+ " NSApplication",
+ " NSSupportsAutomaticTermination",
+ " ",
+ " NSSupportsSuddenTermination",
+ " ",
+ "",
+ "",
+ "",
+ ],
+ visionos: [
+ '',
+ '',
+ '',
+ "",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleURLTypes",
+ " ",
+ " ",
+ " CFBundleURLSchemes",
+ " ",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " msauth.$(PRODUCT_BUNDLE_IDENTIFIER)",
+ " ",
+ " ",
+ " ",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationQueriesSchemes",
+ " ",
+ " linkedin",
+ " ms-outlook",
+ " ms-sfb",
+ " msauth",
+ " msauthv2",
+ " msauthv3",
+ " ",
+ " LSRequiresIPhoneOS",
+ " ",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " UIAppFonts",
+ " ",
+ " OpenType.otf",
+ " TrueType.ttf",
+ " ",
+ " UIApplicationSceneManifest",
+ " ",
+ " UIApplicationSupportsMultipleScenes",
+ " ",
+ " UISceneConfigurations",
+ " ",
+ " UIWindowSceneSessionRoleApplication",
+ " ",
+ " ",
+ " UISceneConfigurationName",
+ " Default Configuration",
+ " UISceneDelegateClassName",
+ " $(PRODUCT_MODULE_NAME).SceneDelegate",
+ " ",
+ " ",
+ " ",
+ " ",
+ " UILaunchScreen",
+ " ",
+ " UIRequiredDeviceCapabilities",
+ " ",
+ " arm64",
+ " ",
+ " UISupportedInterfaceOrientations",
+ " ",
+ " UIInterfaceOrientationPortrait",
+ " UIInterfaceOrientationPortraitUpsideDown",
+ " UIInterfaceOrientationLandscapeLeft",
+ " UIInterfaceOrientationLandscapeRight",
+ " ",
+ " UIViewControllerBasedStatusBarAppearance",
+ " ",
+ "",
+ "",
+ "",
+ ],
+};
+
+const INFO_PLIST_MACOS = {
+ applicationCategoryType: [
+ '',
+ '',
+ '',
+ "",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationCategoryType",
+ " public.app-category.games",
+ " LSMinimumSystemVersion",
+ " $(MACOSX_DEPLOYMENT_TARGET)",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " NSHumanReadableCopyright",
+ " Copyright © 2020 Microsoft. All rights reserved.",
+ " NSMainStoryboardFile",
+ " Main",
+ " NSPrincipalClass",
+ " NSApplication",
+ " NSSupportsAutomaticTermination",
+ " ",
+ " NSSupportsSuddenTermination",
+ " ",
+ "",
+ "",
+ "",
+ ],
+ humanReadableCopyright: [
+ '',
+ '',
+ '',
+ "",
+ " CFBundleDevelopmentRegion",
+ " $(DEVELOPMENT_LANGUAGE)",
+ " CFBundleDisplayName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundleExecutable",
+ " $(EXECUTABLE_NAME)",
+ " CFBundleIdentifier",
+ " $(PRODUCT_BUNDLE_IDENTIFIER)",
+ " CFBundleInfoDictionaryVersion",
+ " 6.0",
+ " CFBundleName",
+ " $(PRODUCT_DISPLAY_NAME)",
+ " CFBundlePackageType",
+ " $(PRODUCT_BUNDLE_PACKAGE_TYPE)",
+ " CFBundleShortVersionString",
+ " $(PRODUCT_VERSION)",
+ " CFBundleVersion",
+ " $(PRODUCT_BUILD_NUMBER)",
+ " LSApplicationCategoryType",
+ " public.app-category.developer-tools",
+ " LSMinimumSystemVersion",
+ " $(MACOSX_DEPLOYMENT_TARGET)",
+ " NSAppTransportSecurity",
+ " ",
+ " NSAllowsLocalNetworking",
+ " ",
+ " ",
+ " NSHumanReadableCopyright",
+ " Copyright © 2025 Contoso. All rights reserved.",
+ " NSMainStoryboardFile",
+ " Main",
+ " NSPrincipalClass",
+ " NSApplication",
+ " NSSupportsAutomaticTermination",
+ " ",
+ " NSSupportsSuddenTermination",
+ " ",
+ "",
+ "",
+ "",
+ ],
+};
diff --git a/test/pack.test.ts b/test/pack.test.ts
index 02e016e0a..b29dd1cc3 100644
--- a/test/pack.test.ts
+++ b/test/pack.test.ts
@@ -155,6 +155,7 @@ describe("npm pack", () => {
"ios/assets_catalog.rb",
"ios/entitlements.mjs",
"ios/entitlements.rb",
+ "ios/infoPlist.mjs",
"ios/info_plist.rb",
"ios/node.rb",
"ios/pod_helpers.rb",