nw-builder
Version:
Build NW.js desktop applications for MacOS, Windows and Linux.
223 lines (200 loc) • 7.1 kB
JavaScript
import console from "node:console";
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { build, parse } from "plist";
import semver from "semver";
/**
* Function to update Helper App Plist Files
* @param {string} plistPath - Path to Helper App Plist File
* @param {string} helperName - Helper App Name
* @param {string} helperId - Helper App ID
* @param {string} appCFBundleIdentifier - options.app.CFBundleIdentifier
*/
async function updateHelperPlist(
plistPath,
helperName,
helperId,
appCFBundleIdentifier,
) {
const plistFullPath = path.resolve(plistPath, "Contents/Info.plist");
const plistJson = parse(await fs.promises.readFile(plistFullPath, "utf-8"));
plistJson.CFBundleDisplayName = helperName;
plistJson.CFBundleName = helperName;
plistJson.CFBundleExecutable = helperName;
plistJson.CFBundleIdentifier = `${appCFBundleIdentifier}.${helperId}`;
await fs.promises.writeFile(plistFullPath, build(plistJson));
}
/**
*
* @param {object} options - Options.
* @param {string} options.version - NW.js version.
* @param {object} options.app - Application configuration.
* @param {string} options.outDir - Output directory.
* @param {string} options.releaseInfo - Release information.
* @returns {Promise<void>} - Promise.
*/
export default async function setOsxConfig({
version,
app,
outDir,
releaseInfo,
}) {
if (process.platform === "win32") {
console.warn(
"MacOS apps built on Windows platform do not preserve all file permissions. See https://github.com/nwutils/nw-builder/issues/716",
);
}
try {
/**
* @type {string | null}
*/
const chromiumVersion = releaseInfo?.components?.chromium;
if (!chromiumVersion) {
throw new Error("Chromium version is missing.");
}
/**
* Path to MacOS application.
* @type {string}
*/
const nwjsApp = path.resolve(outDir, "nwjs.app");
/**
* Path to renamed MacOS application.
* @type {string}
*/
const outApp = path.resolve(outDir, `${app.name}.app`);
/* Rename `nwjs.app` to `${app.name}.app` */
await fs.promises.rename(nwjsApp, outApp);
/* Rename `Contents/MacOS/nwjs` to `Contents/MacOS/${app.name}` */
await fs.promises.rename(
path.resolve(outApp, "Contents", "MacOS", "nwjs"),
path.resolve(outApp, "Contents", "MacOS", app.name),
);
/* Rename all Helper apps */
const helperBaseDir = path.resolve(
outApp,
"Contents/Frameworks/nwjs Framework.framework/Versions",
/* TODO: Version mismatch tracked in https://github.com/nwjs/nw.js/issues/8371 */
"Current",
"Helpers",
);
const helperApps = [
{ name: "nwjs Helper (Alerts).app", id: "helper.alert" },
{ name: "nwjs Helper (GPU).app", id: "helper.gpu" },
{ name: "nwjs Helper (Renderer).app", id: "helper.renderer" },
{ name: "nwjs Helper.app", id: "helper" },
];
/* MacOS Plugin Helper is removed in NW.js v0.111.0 (Chromium M148) */
if (semver.lt(version, "0.111.0")) {
helperApps.push({
name: "nwjs Helper (Plugin).app",
id: "helper.plugin",
});
}
for (const helperApp of helperApps) {
const newHelperAppName = helperApp.name.replace(/^nwjs/, app.name);
const oldPath = path.resolve(helperBaseDir, helperApp.name);
const newPath = path.resolve(helperBaseDir, newHelperAppName);
// Rename Helper base directory
await fs.promises.rename(oldPath, newPath);
// Rename Helper sub-directory
const helperBaseName = helperApp.name.replace(/.app$/, "");
const subPathBase = path.resolve(newPath, "Contents/MacOS/");
const oldSubPath = path.resolve(subPathBase, helperBaseName);
const newSubPath = path.resolve(
subPathBase,
helperBaseName.replace(/^nwjs/, app.name),
);
await fs.promises.rename(oldSubPath, newSubPath);
// Update Helper Plist file
await updateHelperPlist(
newPath,
newHelperAppName.replace(/.app$/, ""),
helperApp.id,
app.CFBundleIdentifier,
);
}
/* Replace default icon with user defined icon if specified. */
if (app.icon !== undefined) {
await fs.promises.copyFile(
path.resolve(app.icon),
path.resolve(outApp, "Contents", "Resources", "app.icns"),
);
}
/**
* Path to `nwjs.app/Contents/Info.plist`
* @type {string}
*/
const contentsInfoPlistPath = path.resolve(
outApp,
"Contents",
"Info.plist",
);
/**
* Path to `nwjs.app/Contents/Resources/en.lproj/InfoPlist.settings`
* @type {string}
*/
const contentsResourcesEnLprojInfoPlistStringsPath = path.resolve(
outApp,
"Contents",
"Resources",
"en.lproj",
"InfoPlist.strings",
);
/**
* JSON from `nwjs.app/Contents/Info.plist`
* @type {object}
*/
const contentsInfoPlistJson = parse(
await fs.promises.readFile(contentsInfoPlistPath, "utf-8"),
);
/* Update Plist with user defined values. */
contentsInfoPlistJson.LSApplicationCategoryType =
app.LSApplicationCategoryType;
contentsInfoPlistJson.CFBundleIdentifier = app.CFBundleIdentifier;
contentsInfoPlistJson.CFBundleName = app.CFBundleName;
contentsInfoPlistJson.CFBundleDisplayName = app.CFBundleDisplayName;
contentsInfoPlistJson.CFBundleSpokenName = app.CFBundleSpokenName;
contentsInfoPlistJson.CFBundleVersion = app.CFBundleVersion;
contentsInfoPlistJson.CFBundleShortVersionString =
app.CFBundleShortVersionString;
contentsInfoPlistJson.CFBundleExecutable = app.name;
contentsInfoPlistJson.NSLocalNetworkUsageDescription =
app.NSLocalNetworkUsageDescription;
/* Remove properties that were not updated by the user. */
Object.keys(contentsInfoPlistJson).forEach((option) => {
if (contentsInfoPlistJson[option] === undefined) {
delete contentsInfoPlistJson[option];
}
});
/**
* Data from `nwjs.app/Contents/Resources/en.lproj/InfoPlist.settings`
* @type {string[]}
*/
const contentsResourcesEnLprojInfoPlistStringsArray = (
await fs.promises.readFile(
contentsResourcesEnLprojInfoPlistStringsPath,
"utf-8",
)
).split("\n");
contentsResourcesEnLprojInfoPlistStringsArray.forEach((line, idx, arr) => {
if (line.includes("NSHumanReadableCopyright")) {
arr[idx] =
`NSHumanReadableCopyright = "${app.NSHumanReadableCopyright}";`;
}
});
/* Write the updated values to their config files. */
await fs.promises.writeFile(
contentsInfoPlistPath,
build(contentsInfoPlistJson),
);
await fs.promises.writeFile(
contentsResourcesEnLprojInfoPlistStringsPath,
contentsResourcesEnLprojInfoPlistStringsArray
.toString()
.replace(/,/g, "\n"),
);
} catch (error) {
console.error(error);
}
}