Skip to content

Commit 1f2901b

Browse files
authored
refactor(apple): port info_plist.rb to JS (#2421)
1 parent 93ae62e commit 1f2901b

File tree

7 files changed

+768
-12
lines changed

7 files changed

+768
-12
lines changed

ios/entitlements.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @ts-check
22
import * as nodefs from "node:fs";
33
import * as path from "node:path";
4-
import { isObject, toPlist } from "./utils.mjs";
4+
import { isObject, plistFromJSON } from "./utils.mjs";
55

66
/** @import { ApplePlatform, JSONObject } from "../scripts/types.js"; */
77

@@ -56,7 +56,7 @@ export function generateEntitlements(
5656
}
5757

5858
const filename = "App.entitlements";
59-
const entitlements = toPlist(
59+
const entitlements = plistFromJSON(
6060
{
6161
...(targetPlatform === "macos"
6262
? DEFAULT_MACOS_ENTITLEMENTS

ios/infoPlist.mjs

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// @ts-check
2+
import * as nodefs from "node:fs";
3+
import * as path from "node:path";
4+
import {
5+
isObject,
6+
jsonFromPlist,
7+
plistFromJSON,
8+
projectPath,
9+
resolveResources,
10+
} from "./utils.mjs";
11+
12+
/** @import { ApplePlatform, JSONArray, JSONObject } from "../scripts/types.js"; */
13+
14+
/**
15+
* @param {JSONObject} appConfig
16+
* @param {ApplePlatform} targetPlatform
17+
* @param {JSONObject} info
18+
*/
19+
function setMacProperties(appConfig, targetPlatform, info) {
20+
if (targetPlatform !== "macos") {
21+
return;
22+
}
23+
24+
const config = appConfig[targetPlatform];
25+
if (!isObject(config)) {
26+
return;
27+
}
28+
29+
const category = config["applicationCategoryType"];
30+
if (typeof category === "string") {
31+
info["LSApplicationCategoryType"] = category;
32+
}
33+
34+
const copyright = config["humanReadableCopyright"];
35+
if (typeof copyright === "string") {
36+
info["NSHumanReadableCopyright"] = copyright;
37+
}
38+
}
39+
40+
/**
41+
* @param {JSONObject} appConfig
42+
* @param {ApplePlatform} targetPlatform
43+
* @param {string} destination
44+
* @returns {void}
45+
*/
46+
export function generateInfoPlist(
47+
appConfig,
48+
targetPlatform,
49+
destination,
50+
fs = nodefs
51+
) {
52+
const filename = "ReactTestApp/Info.plist";
53+
const infoPlistSrc = projectPath(filename, targetPlatform);
54+
55+
const info = jsonFromPlist(infoPlistSrc);
56+
57+
const resources = resolveResources(appConfig, targetPlatform);
58+
registerFonts(resources, targetPlatform, info);
59+
setMacProperties(appConfig, targetPlatform, info);
60+
61+
const infoPlistDst = path.join(destination, path.basename(infoPlistSrc));
62+
fs.writeFileSync(infoPlistDst, plistFromJSON(info, filename));
63+
}
64+
65+
/**
66+
* @param {JSONArray | undefined} resources
67+
* @param {ApplePlatform} targetPlatform
68+
* @param {JSONObject} info
69+
*/
70+
export function registerFonts(resources, targetPlatform, info) {
71+
if (!resources) {
72+
return;
73+
}
74+
75+
const fontFiles = [".otf", ".ttf"];
76+
const fonts = [];
77+
for (const filename of resources) {
78+
if (typeof filename === "string") {
79+
const extname = path.extname(filename);
80+
if (fontFiles.includes(extname)) {
81+
fonts.push(path.basename(filename));
82+
}
83+
}
84+
}
85+
86+
if (fonts.length > 0) {
87+
switch (targetPlatform) {
88+
case "macos":
89+
// https://developer.apple.com/documentation/bundleresources/information_property_list/atsapplicationfontspath
90+
info["ATSApplicationFontsPath"] = ".";
91+
break;
92+
default:
93+
// https://developer.apple.com/documentation/uikit/text_display_and_fonts/adding_a_custom_font_to_your_app
94+
info["UIAppFonts"] = fonts;
95+
break;
96+
}
97+
}
98+
}

ios/privacyManifest.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// @ts-check
22
import * as nodefs from "node:fs";
33
import * as path from "node:path";
4-
import { isObject, toPlist } from "./utils.mjs";
4+
import { isObject, plistFromJSON } from "./utils.mjs";
55

66
/**
77
* @import { ApplePlatform, JSONObject, JSONValue } from "../scripts/types.js";
@@ -107,6 +107,6 @@ export function generatePrivacyManifest(
107107
}
108108

109109
const filename = "PrivacyInfo.xcprivacy";
110-
const xcprivacy = toPlist(manifest, filename);
110+
const xcprivacy = plistFromJSON(manifest, filename);
111111
fs.writeFileSync(path.join(destination, filename), xcprivacy);
112112
}

ios/utils.mjs

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,28 @@ export function isObject(obj) {
1818
}
1919

2020
/**
21-
* @param {string} p
22-
* @param {ApplePlatform} targetPlatform
23-
* @returns {string}
21+
* @param {string} filename
22+
* @returns {JSONObject}
2423
*/
25-
export function projectPath(p, targetPlatform) {
26-
const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
27-
return path.join(packageDir, targetPlatform, p);
24+
export function jsonFromPlist(filename) {
25+
const args = ["-convert", "json", "-o", "-", filename];
26+
const plutil = spawnSync("/usr/bin/plutil", args, {
27+
stdio: ["ignore", "pipe", "inherit"],
28+
});
29+
30+
if (plutil.status !== 0) {
31+
throw new Error(`Failed to read '${filename}'`);
32+
}
33+
34+
return JSON.parse(plutil.stdout.toString());
2835
}
2936

3037
/**
3138
* @param {JSONObject} source
3239
* @param {string} filename
3340
* @returns {string}
3441
*/
35-
export function toPlist(source, filename) {
42+
export function plistFromJSON(source, filename) {
3643
const args = ["-convert", "xml1", "-r", "-o", "-", "--", "-"];
3744
const plutil = spawnSync("/usr/bin/plutil", args, {
3845
stdio: ["pipe", "pipe", "inherit"],
@@ -45,3 +52,34 @@ export function toPlist(source, filename) {
4552

4653
return plutil.stdout.toString();
4754
}
55+
56+
/**
57+
* @param {string} p
58+
* @param {ApplePlatform} targetPlatform
59+
* @returns {string}
60+
*/
61+
export function projectPath(p, targetPlatform) {
62+
const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
63+
return path.join(packageDir, targetPlatform, p);
64+
}
65+
66+
/**
67+
* @param {JSONObject} appConfig
68+
* @param {ApplePlatform} targetPlatform
69+
* @returns {JSONValue[] | undefined}
70+
*/
71+
export function resolveResources(appConfig, targetPlatform) {
72+
const resources = appConfig["resources"];
73+
if (resources && typeof resources === "object") {
74+
if (Array.isArray(resources)) {
75+
return resources;
76+
}
77+
78+
const res = resources[targetPlatform];
79+
if (Array.isArray(res)) {
80+
return res;
81+
}
82+
}
83+
84+
return undefined;
85+
}

test/ios/entitlements.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { fs, setMockFiles } from "../fs.mock.ts";
66

77
const macosOnly = { skip: process.platform === "win32" };
88

9-
describe("generatePrivacyManifest()", macosOnly, () => {
9+
describe("generateEntitlements()", macosOnly, () => {
1010
const targetPlatforms = ["ios", "macos", "visionos"] as const;
1111

1212
function generateEntitlements(

0 commit comments

Comments
 (0)