diff --git a/package.json b/package.json index 6b2a2b948..667e65dc3 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "doc:dev": "concurrently --kill-others \"node .github/fswatch.config.js\" \"vitepress dev doc\"", "doc:bld": "node .github/jsdoc.config.cjs && vitepress build doc", "test:unit": "node --test test/unit/index.js", - "test:e2e": "node --test test/e2e/index.js", + "test:e2e": "node --test test/e2e/*.js", "demo": "cd test/fixture && node demo.js" }, "devDependencies": { diff --git a/src/build.js b/src/build.js index e7a2c7c60..a3ea23516 100644 --- a/src/build.js +++ b/src/build.js @@ -105,15 +105,24 @@ import { log } from "./log.js"; * mode: "build", * }); * - * @param {string | string[]} files Array of NW app files - * @param {string} nwDir Directory to hold NW binaries - * @param {string} outDir Directory to store build artifacts - * @param {"linux" | "osx" | "win"} platform Platform is the operating system type - * @param {"zip" | boolean} zip Specify if the build artifacts are to be zipped - * @param {LinuxRc | OsxRc | WinRc} app Multi platform configuration options + * @param {string | string[]} files Array of NW app files + * @param {string} nwDir Directory to hold NW binaries + * @param {string} outDir Directory to store build artifacts + * @param {"linux" | "osx" | "win"} platform Platform is the operating system type + * @param {"zip" | boolean} zip Specify if the build artifacts are to be zipped + * @param {LinuxRc | OsxRc | WinRc} app Multi platform configuration options + * @param {string} chromiumVer Chromium version * @return {Promise} */ -export async function build(files, nwDir, outDir, platform, zip, app) { +export async function build( + files, + nwDir, + outDir, + platform, + zip, + app, + chromiumVer, +) { log.debug(`Remove any files at ${outDir} directory`); await rm(outDir, { force: true, recursive: true }); log.debug(`Copy ${nwDir} files to ${outDir} directory`); @@ -218,6 +227,7 @@ export async function build(files, nwDir, outDir, platform, zip, app) { } }); + // TODO: Add support for more options as specified in rcedit v4 docs. const rcEditOptions = { "file-version": app.version, "product-version": app.version, @@ -246,33 +256,265 @@ export async function build(files, nwDir, outDir, platform, zip, app) { } try { - const outApp = resolve(outDir, `${app.name}.app`); - await rename(resolve(outDir, "nwjs.app"), outApp); + // Rename nwjs.app executable to ${options.app.name}.app + await rename( + resolve(outDir, "nwjs.app"), + resolve(outDir, `${app.name}.app`), + ); + + // Rename Contents/MacOS/nwjs to Contents/MacOS/${options.app.name} + await rename( + resolve(outDir, `${app.name}.app`, "Contents", "MacOS", "nwjs"), + resolve(outDir, `${app.name}.app`, "Contents", "MacOS", app.name), + ); + + // Rename main Helper app + await rename( + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper.app", + "Contents", + "MacOS", + "nwjs Helper", + ), + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper.app", + "Contents", + "MacOS", + `${app.name} Helper`, + ), + ); + + // Rename Alerts Helper app + await rename( + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Alerts).app", + "Contents", + "MacOS", + "nwjs Helper (Alerts)", + ), + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Alerts).app", + "Contents", + "MacOS", + `${app.name} Helper (Alerts)`, + ), + ); + + // Rename GPU Helper app + await rename( + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (GPU).app", + "Contents", + "MacOS", + "nwjs Helper (GPU)", + ), + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (GPU).app", + "Contents", + "MacOS", + `${app.name} Helper (GPU)`, + ), + ); + + // Rename Plugin Helper app + await rename( + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Plugin).app", + "Contents", + "MacOS", + "nwjs Helper (Plugin)", + ), + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Plugin).app", + "Contents", + "MacOS", + `${app.name} Helper (Plugin)`, + ), + ); + + // Rename Renderer Helper app + await rename( + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Renderer).app", + "Contents", + "MacOS", + "nwjs Helper (Renderer)", + ), + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Renderer).app", + "Contents", + "MacOS", + `${app.name} Helper (Renderer)`, + ), + ); + + // Overwrite's nwjs default icon with user specified icon if (app.icon !== undefined) { await copyFile( resolve(app.icon), - resolve(outApp, "Contents", "Resources", "app.icns"), + resolve( + outDir, + `${app.name}.app`, + "Contents", + "Resources", + "app.icns", + ), ); } - const infoPlistPath = resolve(outApp, "Contents", "Info.plist"); - const infoPlistJson = plist.parse(await readFile(infoPlistPath, "utf-8")); + let infoPlist = { + main: { + path: "", + json: {}, + }, + // TODO: update localised string values based on language requirements + strings: { + path: "", + list: [], + }, + helper_main: { + path: "", + json: {}, + }, + helper_Alerts: { + path: "", + json: {}, + }, + helper_GPU: { + path: "", + json: {}, + }, + helper_Plugin: { + path: "", + json: {}, + }, + helper_Renderer: { + path: "", + json: {}, + }, + }; - const infoPlistStringsPath = resolve( - outApp, + // Main Info.plist: read plist data into memory + infoPlist.main.path = resolve( + outDir, + `${app.name}.app`, + "Contents", + "Info.plist", + ); + infoPlist.main.json = plist.parse( + await readFile(infoPlist.main.path, "utf-8"), + ); + + // Main Info.plist: update plist data + infoPlist.main.json.LSApplicationCategoryType = + app.LSApplicationCategoryType; + infoPlist.main.json.CFBundleIdentifier = app.CFBundleIdentifier; + infoPlist.main.json.CFBundleName = app.CFBundleName; + infoPlist.main.json.CFBundleDisplayName = app.CFBundleDisplayName; + infoPlist.main.json.CFBundleSpokenName = app.CFBundleSpokenName; + infoPlist.main.json.CFBundleVersion = app.CFBundleVersion; + infoPlist.main.json.CFBundleShortVersionString = + app.CFBundleShortVersionString; + infoPlist.main.json.CFBundleExecutable = app.name; + + // Main Info.plist: write updated plist data back to file + await writeFile(infoPlist.main.path, plist.build(infoPlist.main.json)); + + // InfoPlist.strings: read file into memory + infoPlist.strings.path = resolve( + outDir, + `${app.name}.app`, "Contents", "Resources", "en.lproj", "InfoPlist.strings", ); - const infoPlistStringsData = await readFile( - infoPlistStringsPath, - "utf-8", - ); + infoPlist.strings.list = ( + await readFile(infoPlist.strings.path, "utf-8") + ).split("\n"); - let infoPlistStringsDataArray = infoPlistStringsData.split("\n"); - - infoPlistStringsDataArray.forEach((line, idx, arr) => { + // InfoPlist.strings: update localised InfoPlist.strings + infoPlist.strings.list.forEach((line, idx, arr) => { if (line.includes("NSHumanReadableCopyright")) { arr[ idx @@ -280,24 +522,170 @@ export async function build(files, nwDir, outDir, platform, zip, app) { } }); - infoPlistJson.LSApplicationCategoryType = app.LSApplicationCategoryType; - infoPlistJson.CFBundleIdentifier = app.CFBundleIdentifier; - infoPlistJson.CFBundleName = app.CFBundleName; - infoPlistJson.CFBundleDisplayName = app.CFBundleDisplayName; - infoPlistJson.CFBundleSpokenName = app.CFBundleSpokenName; - infoPlistJson.CFBundleVersion = app.CFBundleVersion; - infoPlistJson.CFBundleShortVersionString = app.CFBundleShortVersionString; - - Object.keys(infoPlistJson).forEach((option) => { - if (infoPlistJson[option] === undefined) { - delete infoPlistJson[option]; - } - }); + // InfoPlist.strings: write updated localised InfoPlist.strings back to file + await writeFile( + infoPlist.strings.path, + infoPlist.strings.list.toString().replace(/,/g, "\n"), + ); + + // Main Helper app: read file into memory + infoPlist.helper_main.path = resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper.app", + "Contents", + "Info.plist", + ); + infoPlist.helper_main.json = plist.parse( + await readFile(infoPlist.helper_main.path, "utf-8"), + ); + + // Main Helper app: update plist data + infoPlist.helper_main.json.CFBundleDisplayName = `${app.CFBundleDisplayName} Helper`; + infoPlist.helper_main.json.CFBundleExecutable = `${app.name} Helper`; + infoPlist.helper_main.json.CFBundleIdentifier = `${app.CFBundleIdentifier}.helper`; + infoPlist.helper_main.json.CFBundleName = `${app.name} Helper`; + infoPlist.helper_main.json.CFBundleShortVersionString = + app.CFBundleShortVersionString; + + // Main Helper app: write updated plist data back to file + await writeFile( + infoPlist.helper_main.path, + plist.build(infoPlist.helper_main.json), + ); + + // Alerts Helper app: read file into memory + infoPlist.helper_Alerts.path = resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Alerts).app", + "Contents", + "Info.plist", + ); + infoPlist.helper_Alerts.json = plist.parse( + await readFile(infoPlist.helper_Alerts.path, "utf-8"), + ); + + // Alerts Helper app: update plist data + infoPlist.helper_Alerts.json.CFBundleDisplayName = `${app.CFBundleDisplayName} Helper (Alerts)`; + infoPlist.helper_Alerts.json.CFBundleExecutable = `${app.name} Helper (Alerts)`; + infoPlist.helper_Alerts.json.CFBundleIdentifier = `${app.CFBundleIdentifier}.helper.Alerts`; + infoPlist.helper_Alerts.json.CFBundleName = `${app.name} Helper (Alerts)`; + infoPlist.helper_Alerts.json.CFBundleShortVersionString = + app.CFBundleShortVersionString; + + // Alerts Helper app: write updated plist data back to file + await writeFile( + infoPlist.helper_Alerts.path, + plist.build(infoPlist.helper_Alerts.json), + ); + + // GPU Helper app: read file into memory + infoPlist.helper_GPU.path = resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (GPU).app", + "Contents", + "Info.plist", + ); + infoPlist.helper_GPU.json = plist.parse( + await readFile(infoPlist.helper_GPU.path, "utf-8"), + ); + + // GPU Helper app: update plist data + infoPlist.helper_GPU.json.CFBundleDisplayName = `${app.CFBundleDisplayName} Helper (GPU)`; + infoPlist.helper_GPU.json.CFBundleExecutable = `${app.name} Helper (GPU)`; + infoPlist.helper_GPU.json.CFBundleIdentifier = `${app.CFBundleIdentifier}.helper.GPU`; + infoPlist.helper_GPU.json.CFBundleName = `${app.name} Helper (GPU)`; + infoPlist.helper_GPU.json.CFBundleShortVersionString = + app.CFBundleShortVersionString; + + // GPU Helper app: write updated plist data back to file + await writeFile( + infoPlist.helper_GPU.path, + plist.build(infoPlist.helper_GPU.json), + ); + + // Plugin Helper app: read file into memory + infoPlist.helper_Plugin.path = resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Plugin).app", + "Contents", + "Info.plist", + ); + infoPlist.helper_Plugin.json = plist.parse( + await readFile(infoPlist.helper_Plugin.path, "utf-8"), + ); + + // Plugin Helper app: update plist data + infoPlist.helper_Plugin.json.CFBundleDisplayName = `${app.CFBundleDisplayName} Helper (Plugin)`; + infoPlist.helper_Plugin.json.CFBundleExecutable = `${app.name} Helper (Plugin)`; + infoPlist.helper_Plugin.json.CFBundleIdentifier = `${app.CFBundleIdentifier}.helper.Plugin`; + infoPlist.helper_Plugin.json.CFBundleName = `${app.name} Helper (Plugin)`; + infoPlist.helper_Plugin.json.CFBundleShortVersionString = + app.CFBundleShortVersionString; + + // Plugin Helper app: write updated plist data back to file + await writeFile( + infoPlist.helper_Plugin.path, + plist.build(infoPlist.helper_Plugin.json), + ); + + // Renderer Helper app: read file into memory + infoPlist.helper_Renderer.path = resolve( + outDir, + `${app.name}.app`, + "Contents", + "Frameworks", + "nwjs Framework.framework", + "Versions", + chromiumVer, + "Helpers", + "nwjs Helper (Renderer).app", + "Contents", + "Info.plist", + ); + infoPlist.helper_Renderer.json = plist.parse( + await readFile(infoPlist.helper_Renderer.path, "utf-8"), + ); + + // Renderer Helper app: update plist data + infoPlist.helper_Renderer.json.CFBundleDisplayName = `${app.CFBundleDisplayName} Helper (Renderer)`; + infoPlist.helper_Renderer.json.CFBundleExecutable = `${app.name} Helper (Renderer)`; + infoPlist.helper_Renderer.json.CFBundleIdentifier = `${app.CFBundleIdentifier}.helper.Renderer`; + infoPlist.helper_Renderer.json.CFBundleName = `${app.name} Helper (Renderer)`; + infoPlist.helper_Renderer.json.CFBundleShortVersionString = + app.CFBundleShortVersionString; - await writeFile(infoPlistPath, plist.build(infoPlistJson)); + // Renderer Helper app: write updated plist data back to file await writeFile( - infoPlistStringsPath, - infoPlistStringsDataArray.toString().replace(/,/g, "\n"), + infoPlist.helper_Renderer.path, + plist.build(infoPlist.helper_Renderer.json), ); } catch (error) { log.error(error); diff --git a/src/index.js b/src/index.js index 182fec031..a3e812d5a 100644 --- a/src/index.js +++ b/src/index.js @@ -181,6 +181,7 @@ const nwbuild = async (options) => { options.platform, options.zip, options.app, + releaseInfo.components.chromium, ); } } catch (error) { diff --git a/test/e2e/index.js b/test/e2e/index.js deleted file mode 100644 index 34782ef6e..000000000 --- a/test/e2e/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import * as modeTests from "./mode.js"; - -modeTests; diff --git a/test/e2e/osx.js b/test/e2e/osx.js new file mode 100644 index 000000000..a4b7915eb --- /dev/null +++ b/test/e2e/osx.js @@ -0,0 +1,52 @@ +import { equal } from "node:assert"; +import { basename, resolve } from "node:path"; +import { before, describe, it } from "node:test"; + +import nwbuild from "nw-builder"; + +describe("osx specific tests", () => { + let nwOptions = {}; + + before(async () => { + nwOptions = { + srcDir: "test/fixture/app", + mode: "build", + version: "0.80.0", + platform: "osx", + arch: "x64", + outDir: "test/fixture/out", + cacheDir: "test/fixture/cache", + glob: false, + app: { + name: "demo", + icon: "app/icon.icns", + CFBundleIdentifier: "io.nwutils.demo", + CFBundleName: "demo", + CFBundleDisplayName: "demo", + CFBundleVersion: "0.0.0", + CFBundleShortVersionString: "0.0.0", + NSHumanReadableCopyright: "Copyright (c) 2023 NW.js Utilities", + }, + }; + + await nwbuild(nwOptions); + }); + + it("renames nwjs.app to `options.app.name`.app", () => { + const appName = basename( + resolve(nwOptions.outDir, `${nwOptions.app.name}.app`), + ".app", + ); + + equal(appName, "demo"); + }); + + it("renames Contents/MacOS/nwjs to Contents/MacOS/${options.app.name}", () => { + const appName = basename( + resolve(nwOptions.outDir, `${nwOptions.app.name}.app`, "Contents", "MacOS", nwOptions.app.name) + ); + + equal(appName, "demo"); + }); + +}); diff --git a/test/fixture/app/package.json b/test/fixture/app/package.json index 955b4056e..85ade08b6 100644 --- a/test/fixture/app/package.json +++ b/test/fixture/app/package.json @@ -1,5 +1,6 @@ { - "name": "nwapp", + "name": "demo", "main": "index.html", - "version": "0.0.0" + "version": "0.0.0", + "product_string": "demo" } diff --git a/test/fixture/demo.js b/test/fixture/demo.js index b0c5d070e..63b5f48a4 100644 --- a/test/fixture/demo.js +++ b/test/fixture/demo.js @@ -1,20 +1,19 @@ import nwbuild from "../../src/index.js"; await nwbuild({ + mode: "build", + version: "0.80.0", + platform: "osx", srcDir: "app", - mode: "get", - version: "stable", - flavor: "normal", - platform: "linux", - arch: "x64", - cache: true, - ffmpeg: true, glob: false, app: { - company: "Some Company", - fileDescription: "Process Name", - productName: "Some Product", - legalCopyright: "Copyright (c) 2023", + name: "demo", + icon: "app/icon.icns", + CFBundleIdentifier: "io.nwutils.demo", + CFBundleName: "demo", + CFBundleDisplayName: "demo", + CFBundleVersion: "0.0.0", + CFBundleShortVersionString: "0.0.0", + NSHumanReadableCopyright: "Copyright (c) 2023 NW.js Utilities", }, - logLevel: "debug", });