nw-builder 4.11.6 → 4.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -84,7 +84,14 @@ Node manifest usage:
84
84
  }
85
85
  ```
86
86
 
87
- > From here on we will show `nw-builder` functionality by using the JavaScript module. Please note that the same functionality applies when using a command line or Node manifest.
87
+ See `nw-builder` in action by building the demo application.
88
+
89
+ 1. `git clone https://github.com/nwutils/nw-builder`
90
+ 1. Run `npm run demo:bld:linux && npm run demo:exe:linux` to build and execute a Linux application.
91
+ 1. Run `npm run demo:bld:osx && npm run demo:exe:osx` to build and execute a MacOS application.
92
+ 1. Run `npm run demo:bld:win && npm run demo:exe:win` to build and execute a Windows application.
93
+
94
+ > From here on we will show `nw-builder` functionality by using the JavaScript module. Please note that the same functionality applies when using a command line or manifest file.
88
95
 
89
96
  ## Concepts
90
97
 
@@ -92,7 +99,7 @@ Node manifest usage:
92
99
 
93
100
  ### Get Mode
94
101
 
95
- > Deprecation warning: From v4.6.4 onward, run mode is deprecated. This logic has been ported over to `nwjs/npm-installer` repo and will be removed in the next major release.
102
+ > Deprecation warning: From v4.6.4 onward, get mode is deprecated. This logic has been ported over to `nwjs/npm-installer` repo and will be removed in the next major release.
96
103
 
97
104
  By default you get the normal build of the latest NW.js release for your specific platform and arch. For more information, please refer to the API reference.
98
105
 
@@ -125,13 +132,15 @@ nwbuild({
125
132
  > Deprecation warning: From v4.6.0 onward, run mode is deprecated. This logic has been ported over to `nwjs/npm-installer` repo and will be removed in the next major release.
126
133
 
127
134
  ```javascript
128
- nwbuild({
135
+ const nwProcess = await nwbuild({
129
136
  mode: "run",
130
137
  srcDir: "./app",
131
138
  glob: false,
132
139
  });
133
140
  ```
134
141
 
142
+ Note: The `nwProcess` is a [Node.js process](https://nodejs.org/api/process.html#process)
143
+
135
144
  ### Build Mode
136
145
 
137
146
  Build with defaults:
@@ -142,7 +151,7 @@ nwbuild({
142
151
  });
143
152
  ```
144
153
 
145
- Managed Manifest
154
+ #### Managed Manifest
146
155
 
147
156
  You can let `nw-builder` manage your node modules. The `managedManifest` options accepts a `boolean`, `string` or `object` type. It will then remove `devDependencies`, autodetect and download `dependencies` via the relevant `packageManager`. If none is specified, it uses `npm` as default.
148
157
 
@@ -176,7 +185,7 @@ nwbuild({
176
185
  });
177
186
  ```
178
187
 
179
- Rebuild Node addons
188
+ #### Rebuild Node addons
180
189
 
181
190
  Currently this feature is quite limited. It only builds node addons which have a `binding.gyp` file in the `srcDir`. There are plans to support nan, cmake, ffi and gn and auto rebuild native addons which are installed as node modules.
182
191
 
@@ -201,7 +210,6 @@ Options
201
210
 
202
211
  | Name | Type | Default | Description |
203
212
  | ---- | ------- | --------- | ----------- |
204
- | app | `LinuxRc \| WinRc \| OsxRc` | Additional options for each platform. (See below.)
205
213
  | mode | `"get" \| "run" \| "build"` | `"build"` | Choose between get, run or build mode |
206
214
  | version | `string \| "latest" \| "stable"` | `"latest"` | Runtime version |
207
215
  | flavor | `"normal" \| "sdk"` | `"normal"` | Runtime flavor |
@@ -210,15 +218,17 @@ Options
210
218
  | downloadUrl | `"https://dl.nwjs.io" \| "https://npm.taobao.org/mirrors/nwjs" \| https://npmmirror.com/mirrors/nwjs \| "https://github.com/corwin-of-amber/nw.js/releases/"` | `"https://dl.nwjs.io"` | Download server. Supports file systems too (for example `file:///home/localghost/nwjs_mirror`) |
211
219
  | manifestUrl | `"https://nwjs.io/versions" \| "https://raw.githubusercontent.com/nwutils/nw-builder/main/src/util/osx.arm.versions.json"` | `"https://nwjs.io/versions"` | Versions manifest |
212
220
  | cacheDir | `string` | `"./cache"` | Directory to cache NW binaries |
221
+ | cache | `boolean` | `true`| If true the existing cache is used. Otherwise it removes and redownloads it. |
222
+ | ffmpeg | `boolean` | `false`| If true the chromium ffmpeg is replaced by community version with proprietary codecs. |
223
+ | logLevel | `"error" \| "warn" \| "info" \| "debug"` | `"info"`| Specify level of logging. |
213
224
  | srcDir | `string` | `"./"` | File paths to application code |
225
+ | argv | `string[]` | `[]` | Command line arguments to pass to NW executable in run mode. You can also define these in `chromium-args` in NW.js manifest. |
226
+ | glob | `boolean` | `true`| If true file globbing is enabled when parsing `srcDir`. |
214
227
  | outDir | `string` | `"./out"` | Directory to store build artifacts |
215
228
  | managedManifest | `boolean \| string \| object` | `false` | Managed manifest |
216
229
  | nodeAddon | `false \| "gyp"` | `false` | Rebuild Node native addons |
217
- | cache | `boolean` | `true`| If true the existing cache is used. Otherwise it removes and redownloads it. |
218
- | ffmpeg | `boolean` | `false`| If true the chromium ffmpeg is replaced by community version with proprietary codecs. |
219
- | glob | `boolean` | `true`| If true file globbing is enabled when parsing `srcDir`. |
220
- | logLevel | `"error" \| "warn" \| "info" \| "debug"` | `"info"`| Specify level of logging. |
221
230
  | zip | `boolean \| "zip" \| "tar" \| "tgz"` | `false`| If true, "zip", "tar" or "tgz" the `outDir` directory is compressed. |
231
+ | app | `LinuxRc \| WinRc \| OsxRc` | Additional options for each platform. (See below.)
222
232
 
223
233
  ### `app` configuration object
224
234
 
@@ -228,20 +238,20 @@ This object defines additional properties used for building for a specific platf
228
238
 
229
239
  | Name | Type | Default | Description |
230
240
  | ---- | ------- | --------- | ----------- |
231
- | `icon` | `string` | `undefined` | The path to the icon file. It should be a .ico file. |
232
- | `name` | `string` | Value of `name` in app's `package.json` | The name of the application |
233
- | `version` | `string` | Value of `version` in app's `package.json` | (deprecated, Use `fileVersion` instead) The version of the application |
241
+ | `icon` | `string` | `undefined` | The path to the icon file. It should be a .ico file. (**WARNING**: Please define the icon in the NW.js manifest instead) |
242
+ | `name` | `string` | Value of `name` in NW.js manifest | The name of the application |
243
+ | `version` | `string` | Value of `version` in NW.js manifest | The version of the application |
234
244
  | `comments` | `string` | `undefined` | Additional information that should be displayed for diagnostic purposes. |
235
- | `company` | `string` | Value of `author` in app's `package.json` | Company that produced the file—for example, Microsoft Corporation or Standard Microsystems Corporation, Inc. This string is required. |
236
- | `fileDescription` | `string` | Value of `description` in app's `package.json` | File description to be presented to users. This string may be displayed in a list box when the user is choosing files to install. For example, Keyboard Driver for AT-Style Keyboards. This string is required. |
237
- | `fileVersion` | `string` | Value of `version` option or value of `version` in app's `package.json` | Version number of the file. For example, 3.10 or 5.00.RC2. This string is required. |
238
- | `internalName` | `string` | Value of `name` in app's `package.json` |Internal name of the file, if one exists—for example, a module name if the file is a dynamic-link library. If the file has no internal name, this string should be the original filename, without extension. This string is required. |
239
- | `legalCopyright` | `string` | NW.js' copyright info | Copyright notices that apply to the file. This should include the full text of all notices, legal symbols, copyright dates, and so on. This string is optional. |
245
+ | `company` | `string` | Value of `author` in NW.js manifest | Company that produced the file—for example, Microsoft Corporation or Standard Microsystems Corporation, Inc. This string is required. |
246
+ | `fileDescription` | `string` | Value of `description` in NW.js manifest | File description to be presented to users. This string may be displayed in a list box when the user is choosing files to install. For example, Keyboard Driver for AT-Style Keyboards. This string is required. |
247
+ | `fileVersion` | `string` | Value of `version` or value of `version` in NW.js manifest | Version number of the file. For example, 3.10 or 5.00.RC2. This string is required. |
248
+ | `internalName` | `string` | Value of `name` in NW.js manifest |Internal name of the file, if one exists—for example, a module name if the file is a dynamic-link library. If the file has no internal name, this string should be the original filename, without extension. This string is required. |
249
+ | `legalCopyright` | `string` | `undefined` | Copyright notices that apply to the file. This should include the full text of all notices, legal symbols, copyright dates, and so on. This string is optional. |
240
250
  | `legalTrademark` | `string` | `undefined` | Trademarks and registered trademarks that apply to the file. This should include the full text of all notices, legal symbols, trademark numbers, and so on. This string is optional. |
241
251
  | `originalFilename` | `string` | Value of `name` option | Original name of the file, not including a path. This information enables an application to determine whether a file has been renamed by a user. The format of the name depends on the file system for which the file was created. This string is required. |
242
252
  | `privateBuild` | `string` | `undefined` | Information about a private version of the file—for example, Built by TESTER1 on \\TESTBED. |
243
- | `productName` | `string` | Matches the package name defined in app's `package.json` | Name of the product with which the file is distributed. This string is required. |
244
- | `productVersion` | `string` | Value of `version` in app's `package.json` | Version of the product with which the file is distributed—for example, 3.10 or 5.00.RC2. |
253
+ | `productName` | `string` | `name` in NW.js manifest | Name of the product with which the file is distributed. This string is required. |
254
+ | `productVersion` | `string` | Value of `version` in NW.js manifest | Version of the product with which the file is distributed—for example, 3.10 or 5.00.RC2. |
245
255
  | `specialBuild` | `string` | `undefined` | Text that specifies how this version of the file differs from the standard version—for example, Private build for TESTER1 solving mouse problems on M250 and M250E computers. |
246
256
  | `languageCode` | `number` | `1033` | Language of the file, defined by Microsoft, see: https://learn.microsoft.com/en-us/openspecs/office_standards/ms-oe376/6c085406-a698-4e12-9d4d-c3b0ee3dbc4a |
247
257
 
@@ -253,7 +263,7 @@ This object defines additional properties used for building for a specific platf
253
263
  | genericName | `string` | Generic name of the application |
254
264
  | noDisplay | `boolean` | If true the application is not displayed |
255
265
  | comment | `string` | Tooltip for the entry, for example "View sites on the Internet". |
256
- | icon | `string` | Icon to display in file manager, menus, etc. |
266
+ | icon | `string` | Icon to display in file manager, menus, etc. (**WARNING**: Please define the icon in the NW.js manifest instead) |
257
267
  | hidden | `boolean` | TBD |
258
268
  | onlyShowIn | `string[]` | A list of strings identifying the desktop environments that should display a given desktop entry |
259
269
  | notShowIn | `string[]` | A list of strings identifying the desktop environments that should not display a given desktop entry |
@@ -277,7 +287,7 @@ This object defines additional properties used for building for a specific platf
277
287
  | Name | Type | Description |
278
288
  | ---- | ------- | ----------- |
279
289
  | name | `string` | The name of the application |
280
- | icon | `string` | The path to the icon file. It should be a .icns file. |
290
+ | icon | `string` | The path to the icon file. It should be a .icns file. (**WARNING**: Please define the icon in the NW.js manifest instead) |
281
291
  | LSApplicationCategoryType | `string` | The category that best describes your app for the App Store. |
282
292
  | CFBundleIdentifier | `string` | A unique identifier for a bundle usually in reverse DNS format. |
283
293
  | CFBundleName | `string` | A user-visible short name for the bundle. |
@@ -385,7 +395,6 @@ nwbuild({
385
395
 
386
396
  ### Chores
387
397
 
388
- - chore: add Linux, MacOS and Windows fixtures
389
398
  - chore(docs): don't store JSDoc definitions in `typedef`s - get's hard to understand during development.
390
399
  - chore: annotate file paths as `fs.PathLike` instead of `string`.
391
400
  - chore(bld): factor out core build step
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nw-builder",
3
- "version": "4.11.6",
3
+ "version": "4.12.0",
4
4
  "description": "Build NW.js desktop applications for MacOS, Windows and Linux.",
5
5
  "keywords": [
6
6
  "NW.js",
@@ -46,17 +46,21 @@
46
46
  "lint:fix": "eslint --fix ./src ./tests",
47
47
  "test": "vitest run --coverage",
48
48
  "test:cov": "vitest --coverage.enabled true",
49
- "demo:bld": "node ./tests/fixtures/demo.js",
50
- "demo:exe": "./tests/fixtures/out/Demo.app/Contents/MacOS/Demo",
49
+ "demo:bld:linux": "node ./tests/fixtures/demo.linux.js",
50
+ "demo:bld:osx": "node ./tests/fixtures/demo.osx.js",
51
+ "demo:bld:win": "node ./tests/fixtures/demo.win.js",
52
+ "demo:exe:linux": "./tests/fixtures/out/linux/Demo",
53
+ "demo:exe:osx": "./tests/fixtures/out/osx/Demo.app/Contents/MacOS/Demo",
54
+ "demo:exe:win": "./tests/fixtures/out/win/Demo.exe",
51
55
  "demo:cli": "nwbuild --mode=run --flavor=sdk --glob=false --cacheDir=./node_modules/nw ./tests/fixtures/app"
52
56
  },
53
57
  "devDependencies": {
54
- "@eslint/js": "^9.12.0",
55
- "@vitest/coverage-v8": "^2.1.2",
58
+ "@eslint/js": "^9.15.0",
59
+ "@vitest/coverage-v8": "^2.1.5",
56
60
  "base-volta-off-of-nwjs": "^1.0.5",
57
- "eslint": "^9.12.0",
58
- "eslint-plugin-jsdoc": "^50.3.2",
59
- "globals": "^15.11.0",
61
+ "eslint": "^9.15.0",
62
+ "eslint-plugin-jsdoc": "^50.5.0",
63
+ "globals": "^15.12.0",
60
64
  "nw": "^0.93.0",
61
65
  "selenium-webdriver": "^4.25.0",
62
66
  "vitest": "^2.0.4"
package/src/bld.js CHANGED
@@ -62,11 +62,11 @@ import setOsxConfig from './bld/osx.js';
62
62
  * References:
63
63
  * https://learn.microsoft.com/en-us/windows/win32/msi/version
64
64
  * https://learn.microsoft.com/en-gb/windows/win32/sbscs/application-manifests
65
- * https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/deployment/trustinfo-element-clickonce-application?view=vs-2015#requestedexecutionlevel
65
+ * https://learn.microsoft.com/en-us/visualstudio/deployment/trustinfo-element-clickonce-application?view=vs-2022#requestedexecutionlevel
66
66
  * https://learn.microsoft.com/en-gb/windows/win32/menurc/versioninfo-resource
67
67
  * @typedef {object} WinRc Windows configuration options. More info
68
68
  * @property {string} name The name of the application
69
- * @property {string} version @deprecated Use {@link fileVersion} instead. The version of the application
69
+ * @property {string} version The version of the application
70
70
  * @property {string} comments Additional information that should be displayed for diagnostic purposes.
71
71
  * @property {string} company Company that produced the file—for example, Microsoft Corporation or Standard Microsystems Corporation, Inc. This string is required.
72
72
  * @property {string} fileDescription File description to be presented to users. This string may be displayed in a list box when the user is choosing files to install. For example, Keyboard Driver for AT-Style Keyboards. This string is required.
@@ -256,13 +256,13 @@ const setLinuxConfig = async ({ app, outDir }) => {
256
256
  GenericName: app.genericName,
257
257
  NoDisplay: app.noDisplay,
258
258
  Comment: app.comment,
259
- Icon: app.icon,
259
+ Icon: path.resolve(outDir, 'package.nw', path.basename(app.icon)),
260
260
  Hidden: app.hidden,
261
261
  OnlyShowIn: app.onlyShowIn,
262
262
  NotShowIn: app.notShowIn,
263
263
  DBusActivatable: app.dBusActivatable,
264
264
  TryExec: app.tryExec,
265
- Exec: app.name,
265
+ Exec: app.exec,
266
266
  Path: app.path,
267
267
  Terminal: app.terminal,
268
268
  Actions: app.actions,
@@ -319,10 +319,11 @@ const setWinConfig = async ({ app, outDir }) => {
319
319
  if (app.icon) {
320
320
  const iconBuffer = await fs.promises.readFile(path.resolve(app.icon));
321
321
  const iconFile = resedit.Data.IconFile.from(iconBuffer);
322
+ const iconGroupIDs = resedit.Resource.IconGroupEntry.fromEntries(res.entries).map((entry) => entry.id);
322
323
  resedit.Resource.IconGroupEntry.replaceIconsForResource(
323
324
  res.entries,
324
- // This is the name of the icon group nw.js uses that gets shown in file exlorers
325
- 'IDR_MAINFRAME',
325
+ /* Should be `IDR_MAINFRAME` */
326
+ iconGroupIDs[0],
326
327
  EN_US,
327
328
  iconFile.icons.map(i => i.data)
328
329
  );
@@ -1,4 +1,5 @@
1
1
  import fs from 'node:fs';
2
+ import process from 'node:process';
2
3
  import stream from 'node:stream';
3
4
 
4
5
  import axios from 'axios';
@@ -15,6 +16,15 @@ export default async function request(url, filePath) {
15
16
 
16
17
  const writeStream = fs.createWriteStream(filePath);
17
18
 
19
+ /* Listen for SIGINT (Ctrl+C) */
20
+ process.on('SIGINT', function () {
21
+ /* Delete file if it exists. This prevents unnecessary `Central Directory not found` errors. */
22
+ if (fs.existsSync(filePath)) {
23
+ fs.unlinkSync(filePath);
24
+ }
25
+ process.exit();
26
+ });
27
+
18
28
  const response = await axios({
19
29
  method: 'get',
20
30
  url: url,
@@ -22,4 +32,5 @@ export default async function request(url, filePath) {
22
32
  });
23
33
 
24
34
  await stream.promises.pipeline(response.data, writeStream);
35
+
25
36
  }
package/src/index.d.ts CHANGED
@@ -61,7 +61,7 @@ export type AppOptions<P extends SupportedPlatform> =
61
61
  export interface WindowsAppOptions {
62
62
  /** The name of the application */
63
63
  name?: string,
64
- /** @deprecated Use {@link fileVersion} instead. The version of the application */
64
+ /** The version of the application */
65
65
  version?: string,
66
66
  /** Additional information that should be displayed for diagnostic purposes. */
67
67
  comments?: string,
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import console from 'node:console';
2
2
  import fs from 'node:fs';
3
+ import path from 'node:path';
3
4
 
4
5
  import bld from './bld.js';
5
6
  import get from './get/index.js';
@@ -45,17 +46,19 @@ async function nwbuild(options) {
45
46
  };
46
47
 
47
48
  try {
48
- // Parse options
49
+ /* Parse options */
49
50
  options = await util.parse(options, manifest);
51
+ util.log('debug', 'info', 'Parse initial options');
50
52
 
53
+ util.log('debug', 'info', 'Get node manifest...');
51
54
  manifest = await util.getNodeManifest({ srcDir: options.srcDir, glob: options.glob });
52
55
  if (typeof manifest.json?.nwbuild === 'object') {
53
56
  options = manifest.json.nwbuild;
54
57
  }
55
58
 
59
+ util.log('info', options.logLevel, 'Parse final options using node manifest');
56
60
  options = await util.parse(options, manifest.json);
57
-
58
- //TODO: impl logging
61
+ util.log('debug', options.logLevel, 'Manifest: ', `${manifest.path}\n${manifest.json}\n`);
59
62
 
60
63
  built = fs.existsSync(options.cacheDir);
61
64
  if (built === false) {
@@ -69,7 +72,8 @@ async function nwbuild(options) {
69
72
  }
70
73
  }
71
74
 
72
- // Validate options.version to get the version specific release info
75
+ /* Validate options.version to get the version specific release info */
76
+ util.log('info', options.logLevel, 'Get version specific release info...');
73
77
  releaseInfo = await util.getReleaseInfo(
74
78
  options.version,
75
79
  options.platform,
@@ -77,13 +81,16 @@ async function nwbuild(options) {
77
81
  options.cacheDir,
78
82
  options.manifestUrl,
79
83
  );
84
+ util.log('debug', options.logLevel, `Release info:\n${JSON.stringify(releaseInfo, null, 2)}\n`);
80
85
 
86
+ util.log('info', options.logLevel, 'Validate options.* ...');
81
87
  await util.validate(options, releaseInfo);
88
+ util.log('debug', options.logLevel, `Options:\n${JSON.stringify(options, null, 2)}`);
82
89
 
83
- // Remove leading "v" from version string
90
+ /* Remove leading "v" from version string */
84
91
  options.version = releaseInfo.version.slice(1);
85
92
 
86
- // Download binaries
93
+ util.log('info', options.logLevel, 'Getting NW.js and related binaries...');
87
94
  await get({
88
95
  version: options.version,
89
96
  flavor: options.flavor,
@@ -102,6 +109,7 @@ async function nwbuild(options) {
102
109
  }
103
110
 
104
111
  if (options.mode === 'run') {
112
+ util.log('info', options.logLevel, 'Running NW.js in run mode...');
105
113
  await run({
106
114
  version: options.version,
107
115
  flavor: options.flavor,
@@ -113,6 +121,7 @@ async function nwbuild(options) {
113
121
  argv: options.argv,
114
122
  });
115
123
  } else if (options.mode === 'build') {
124
+ util.log('info', options.logLevel, `Build a NW.js application for ${options.platform} ${options.arch}...`);
116
125
  await bld({
117
126
  version: options.version,
118
127
  flavor: options.flavor,
@@ -129,6 +138,7 @@ async function nwbuild(options) {
129
138
  zip: options.zip,
130
139
  releaseInfo: releaseInfo,
131
140
  });
141
+ util.log('info', options.logLevel, `Appliction is available at ${path.resolve(options.outDir)}`);
132
142
  }
133
143
  } catch (error) {
134
144
  console.error(error);
package/src/run.js CHANGED
@@ -23,7 +23,7 @@ import util from './util.js';
23
23
  * @async
24
24
  * @function
25
25
  * @param {RunOptions} options Run mode options
26
- * @returns {Promise<void>}
26
+ * @returns {Promise<child_process.ChildProcess | null>} - A Node.js process object
27
27
  */
28
28
  async function run({
29
29
  version = 'latest',
@@ -35,6 +35,11 @@ async function run({
35
35
  glob = false,
36
36
  argv = [],
37
37
  }) {
38
+ /**
39
+ * @type {child_process.ChildProcess | null}
40
+ */
41
+ let nwProcess = null;
42
+
38
43
  try {
39
44
  if (util.EXE_NAME[platform] === undefined) {
40
45
  throw new Error('Unsupported platform.');
@@ -49,8 +54,8 @@ async function run({
49
54
  );
50
55
 
51
56
  return new Promise((res, rej) => {
52
- // It is assumed that the package.json is located at srcDir/package.json
53
- const nwProcess = child_process.spawn(
57
+ /* It is assumed that the package.json is located at `${options.srcDir}/package.json` */
58
+ nwProcess = child_process.spawn(
54
59
  path.resolve(nwDir, util.EXE_NAME[platform]),
55
60
  [...[srcDir], ...argv],
56
61
  { stdio: 'inherit' },
@@ -68,6 +73,7 @@ async function run({
68
73
  } catch (error) {
69
74
  console.error(error);
70
75
  }
76
+ return nwProcess;
71
77
  }
72
78
 
73
79
  export default run;
package/src/util.js CHANGED
@@ -172,16 +172,16 @@ async function getNodeManifest({
172
172
  * @param {any} option - a boolean type option
173
173
  * @returns {any} Usually `undefined`, `true` or `false`. if not then it is validated later on.
174
174
  */
175
- function str2Bool (option) {
175
+ function str2Bool(option) {
176
176
  if (typeof option === 'string') {
177
177
  if (option === 'true') {
178
178
  return true;
179
179
  } else if (option === 'false') {
180
180
  return false;
181
- }
181
+ }
182
182
  } else {
183
183
  return option;
184
- }
184
+ }
185
185
  }
186
186
 
187
187
  /**
@@ -230,7 +230,8 @@ export const parse = async (options, pkg) => {
230
230
  /* Remove special and control characters from app.name to mitigate potential path traversal. */
231
231
  options.app.name = options.app.name.replace(/[<>:"/\\|?*\u0000-\u001F]/g, '');
232
232
  }
233
- options.app.icon = options.app.icon ?? undefined;
233
+ /* Path to where the icon currently is in the filesystem */
234
+ options.app.icon = path.resolve(options.app.icon) ?? undefined;
234
235
 
235
236
  // TODO(#737): move this out
236
237
  if (options.platform === 'linux') {
@@ -243,7 +244,7 @@ export const parse = async (options, pkg) => {
243
244
  options.app.notShowIn = options.app.notShowIn ?? undefined;
244
245
  options.app.dBusActivatable = options.app.dBusActivatable ?? undefined;
245
246
  options.app.tryExec = options.app.tryExec ?? undefined;
246
- options.app.exec = options.app.name ?? undefined;
247
+ options.app.exec = path.resolve(options.app.exec) ?? undefined;
247
248
  options.app.path = options.app.path ?? undefined;
248
249
  options.app.terminal = options.app.terminal ?? undefined;
249
250
  options.app.actions = options.app.actions ?? undefined;
@@ -306,7 +307,11 @@ export const parse = async (options, pkg) => {
306
307
  * @throws {Error} Throw error if options are invalid
307
308
  */
308
309
  export const validate = async (options, releaseInfo) => {
309
- if (!['get', 'run', 'build'].includes(options.mode)) {
310
+ if (
311
+ options.mode !== 'get' &&
312
+ options.mode !== 'run' &&
313
+ options.mode !== 'build'
314
+ ) {
310
315
  throw new Error(
311
316
  `Unknown mode ${options.mode}. Expected "get", "run" or "build".`,
312
317
  );
@@ -330,16 +335,23 @@ export const validate = async (options, releaseInfo) => {
330
335
  `Platform ${options.platform} and architecture ${options.arch} is not supported by this download server.`,
331
336
  );
332
337
  }
333
- // if (typeof options.cacheDir !== "string") {
334
- // throw new Error("Expected options.cacheDir to be a string. Got " + typeof options.cacheDir);
335
- // }
338
+ if (typeof options.downloadUrl === 'string' && !options.downloadUrl.startsWith('http') && !options.downloadUrl.startsWith('file')) {
339
+ throw new Error('Expected options.downloadUrl to be a string and starts with `http` or `file`.');
340
+ }
341
+ if (typeof options.manifestUrl === 'string' && !options.manifestUrl.startsWith('http') && !options.manifestUrl.startsWith('file')) {
342
+ throw new Error('Expected options.manifestUrl to be a string and starts with `http` or `file`.');
343
+ }
344
+ if (typeof options.cacheDir !== 'string') {
345
+ throw new Error('Expected options.cacheDir to be a string. Got ' + typeof options.cacheDir);
346
+ }
347
+
336
348
  if (typeof options.cache !== 'boolean') {
337
- return new Error(
349
+ throw new Error(
338
350
  'Expected options.cache to be a boolean. Got ' + typeof options.cache,
339
351
  );
340
352
  }
341
353
  if (typeof options.ffmpeg !== 'boolean') {
342
- return new Error(
354
+ throw new Error(
343
355
  'Expected options.ffmpeg to be a boolean. Got ' + typeof options.ffmpeg,
344
356
  );
345
357
  }
@@ -359,27 +371,26 @@ export const validate = async (options, releaseInfo) => {
359
371
  if (options.mode === 'get') {
360
372
  return undefined;
361
373
  }
374
+ if (typeof options.srcDir !== 'string') {
375
+ throw new Error('Expected options.srcDir to be a string. Got ' + typeof options.srcDir);
376
+ }
362
377
  if (Array.isArray(options.argv)) {
363
- return new Error(
378
+ throw new Error(
364
379
  'Expected options.argv to be an array. Got ' + typeof options.argv,
365
380
  );
366
381
  }
367
382
  if (typeof options.glob !== 'boolean') {
368
- return new Error(
383
+ throw new Error(
369
384
  'Expected options.glob to be a boolean. Got ' + typeof options.glob,
370
385
  );
371
386
  }
372
387
 
373
- if (options.srcDir) {
374
- await fs.promises.readdir(options.srcDir);
375
- }
376
-
377
388
  if (options.mode === 'run') {
378
389
  return undefined;
379
390
  }
380
391
 
381
- if (options.outDir) {
382
- await fs.promises.readdir(options.outDir);
392
+ if (typeof options.outDir !== 'string') {
393
+ throw new Error('Expected options.outDir to be a string. Got ' + typeof options.outDir);
383
394
  }
384
395
 
385
396
  if (
@@ -387,7 +398,7 @@ export const validate = async (options, releaseInfo) => {
387
398
  typeof options.managedManifest !== 'object' &&
388
399
  typeof options.managedManifest !== 'string'
389
400
  ) {
390
- return new Error(
401
+ throw new Error(
391
402
  'Expected options.managedManifest to be a boolean, object or string. Got ' +
392
403
  typeof options.managedManifest,
393
404
  );
@@ -395,24 +406,184 @@ export const validate = async (options, releaseInfo) => {
395
406
 
396
407
  if (typeof options.managedManifest === 'object') {
397
408
  if (options.managedManifest.name === undefined) {
398
- return new Error('Expected NW.js Manifest to have a `name` property.');
409
+ throw new Error('Expected NW.js Manifest to have a `name` property.');
399
410
  }
400
411
  if (options.managedManifest.main === undefined) {
401
- return new Error('Expected NW.js Manifest to have a `main` property.');
412
+ throw new Error('Expected NW.js Manifest to have a `main` property.');
402
413
  }
403
414
  }
404
415
 
405
416
  if (typeof options.nativeAddon !== 'boolean') {
406
417
  if (typeof options.nativeAddon !== 'boolean' && typeof options.nativeAddon !== 'string') {
407
- return new Error('Expected options.nativeAddon to be a boolean or string type. Got ' + typeof options.nativeAddon);
418
+ throw new Error('Expected options.nativeAddon to be a boolean or string type. Got ' + typeof options.nativeAddon);
408
419
  }
409
420
 
410
421
  if (semver.parse(options.version).minor >= '83' && options.nativeAddon !== false) {
411
- return new Error('Native addons are not supported for NW.js v0.82.0 and below');
422
+ throw new Error('Native addons are not supported for NW.js v0.82.0 and below');
423
+ }
424
+
425
+ if (typeof options.zip !== 'boolean' &
426
+ options.zip !== 'zip' &&
427
+ options.zip !== 'tar' &&
428
+ options.zip !== 'tgz') {
429
+ throw new Error('Expected options.zip to be a boolean, `zip`, `tar` or `tgz`. Got ' + typeof options.zip);
412
430
  }
413
431
  }
414
432
 
415
- // TODO: Validate app options
433
+ if (options.platform === 'linux') {
434
+ if (options.app.name && typeof options.app.name !== 'string') {
435
+ throw new Error('Expected options.app.name to be a string. Got ' + options.app.name);
436
+ }
437
+ if (options.app.genericName && typeof options.app.genericName !== 'string') {
438
+ throw new Error('Expected options.app.genericName to be a string. Got ' + options.app.genericName);
439
+ }
440
+ if (options.app.noDisplay && typeof options.app.noDisplay !== 'boolean') {
441
+ throw new Error('Expected options.app.noDisplay to be a boolean. Got ' + options.app.noDisplay);
442
+ }
443
+ if (options.app.comment && typeof options.app.comment !== 'string') {
444
+ throw new Error('Expected options.app.comment to be a string. Got ' + options.app.comment);
445
+ }
446
+ if (options.app.icon && typeof options.app.icon !== 'string') {
447
+ throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon);
448
+ }
449
+ if (options.app.hidden && typeof options.app.hidden !== 'string') {
450
+ throw new Error('Expected options.app.hidden to be a string. Got ' + options.app.hidden);
451
+ }
452
+ if (options.app.onlyShowIn && Array.isArray(options.app.onlyShowIn)) {
453
+ throw new Error('Expected options.app.onlyShowIn to be an Array<string>. Got ' + options.app.onlyShowIn);
454
+ }
455
+ if (options.app.notShowIn && Array.isArray(options.app.notShowIn)) {
456
+ throw new Error('Expected options.app.notShowIn to be an Array<string>. Got ' + options.app.notShowIn);
457
+ }
458
+ if (options.app.dBusActivatable && typeof options.app.dBusActivatable !== 'boolean') {
459
+ throw new Error('Expected options.app.dBusActivatable to be a boolean. Got ' + options.app.dBusActivatable);
460
+ }
461
+ if (options.app.tryExec && typeof options.app.tryExec !== 'string') {
462
+ throw new Error('Expected options.app.tryExec to be a string. Got ' + options.app.tryExec);
463
+ }
464
+ if (options.app.exec && typeof options.app.exec !== 'string') {
465
+ throw new Error('Expected options.app.exec to be a string. Got ' + options.app.exec);
466
+ }
467
+ if (options.app.path && typeof options.app.path !== 'string') {
468
+ throw new Error('Expected options.app.path to be a string. Got ' + options.app.path);
469
+ }
470
+ if (options.app.terminal && typeof options.app.terminal !== 'boolean') {
471
+ throw new Error('Expected options.app.terminal to be a boolean. Got ' + options.app.terminal);
472
+ }
473
+ if (options.app.actions && Array.isArray(options.app.actions)) {
474
+ throw new Error('Expected options.app.actions to be a Array<string>. Got ' + options.app.actions);
475
+ }
476
+ if (options.app.mimeType && Array.isArray(options.app.mimeType)) {
477
+ throw new Error('Expected options.app.mimeType to be a Array<string>. Got ' + options.app.mimeType);
478
+ }
479
+ if (options.app.categories && Array.isArray(options.app.categories)) {
480
+ throw new Error('Expected options.app.categories to be a Array<string>. Got ' + options.app.categories);
481
+ }
482
+ if (options.app.implements && Array.isArray(options.app.implements)) {
483
+ throw new Error('Expected options.app.implements to be a Array<string>. Got ' + options.app.implements);
484
+ }
485
+ if (options.app.keywords && Array.isArray(options.app.keywords)) {
486
+ throw new Error('Expected options.app.keywords to be a Array<string>. Got ' + options.app.keywords);
487
+ }
488
+ if (options.app.startupNotify && typeof options.app.startupNotify !== 'boolean') {
489
+ throw new Error('Expected options.app.startupNotify to be a boolean. Got ' + options.app.startupNotify);
490
+ }
491
+ if (options.app.startupWMClass && typeof options.app.startupWMClass !== 'string') {
492
+ throw new Error('Expected options.app.startupWMClass to be a string. Got ' + options.app.startupWMClass);
493
+ }
494
+ if (options.app.prefersNonDefaultGPU && typeof options.app.prefersNonDefaultGPU !== 'boolean') {
495
+ throw new Error('Expected options.app.prefersNonDefaultGPU to be a boolean. Got ' + options.app.prefersNonDefaultGPU);
496
+ }
497
+ if (options.app.singleMainWindow && typeof options.app.singleMainWindow !== 'string') {
498
+ throw new Error('Expected options.app.singleMainWindow to be a string. Got ' + options.app.singleMainWindow);
499
+ }
500
+ } else if (options.platform === 'osx') {
501
+ if (typeof options.app.name !== 'string') {
502
+ throw new Error('Expected options.app.name to be a string. Got ' + options.app.name);
503
+ }
504
+ if (typeof options.app.icon !== 'string') {
505
+ throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon);
506
+ }
507
+ if (typeof options.app.LSApplicationCategoryType !== 'string') {
508
+ throw new Error('Expected options.app.LSApplicationCategoryType to be a string. Got ' + options.app.LSApplicationCategoryType);
509
+ }
510
+ if (typeof options.app.CFBundleIdentifier !== 'string') {
511
+ throw new Error('Expected options.app.CFBundleIdentifier to be a string. Got ' + options.app.CFBundleIdentifier);
512
+ }
513
+ if (typeof options.app.CFBundleName !== 'string') {
514
+ throw new Error('Expected options.app.CFBundleName to be a string. Got ' + options.app.CFBundleName);
515
+ }
516
+ if (typeof options.app.CFBundleDisplayName !== 'string') {
517
+ throw new Error('Expected options.app.CFBundleDisplayName to be a string. Got ' + options.app.CFBundleDisplayName);
518
+ }
519
+ if (typeof options.app.CFBundleSpokenName !== 'string') {
520
+ throw new Error('Expected options.app.CFBundleSpokenName to be a string. Got ' + options.app.CFBundleSpokenName);
521
+ }
522
+ if (typeof options.app.CFBundleVersion !== 'string') {
523
+ throw new Error('Expected options.app.CFBundleVersion to be a string. Got ' + options.app.CFBundleVersion);
524
+ }
525
+ if (typeof options.app.CFBundleShortVersionString !== 'string') {
526
+ throw new Error('Expected options.app.CFBundleShortVersionString to be a string. Got ' + options.app.CFBundleShortVersionString);
527
+ }
528
+ if (typeof options.app.NSHumanReadableCopyright !== 'string') {
529
+ throw new Error('Expected options.app.NSHumanReadableCopyright to be a string. Got ' + options.app.NSHumanReadableCopyright);
530
+ }
531
+ if (typeof options.app.NSLocalNetworkUsageDescription !== 'string') {
532
+ throw new Error('Expected options.app.NSLocalNetworkUsageDescription to be a string. Got ' + options.app.NSLocalNetworkUsageDescription);
533
+ }
534
+ } else {
535
+ if (typeof options.app.name !== 'string') {
536
+ throw new Error('Expected options.app.name to be a string. Got ' + options.app.name);
537
+ }
538
+ if (typeof options.app.icon !== 'string') {
539
+ throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon);
540
+ }
541
+ if (typeof options.app.version !== 'string') {
542
+ throw new Error('Expected options.app.version to be a string. Got ' + options.app.version);
543
+ }
544
+ if (options.app.comments && typeof options.app.comments !== 'string') {
545
+ throw new Error('Expected options.app.comments to be a string. Got ' + options.app.comments);
546
+ }
547
+ if (typeof options.app.company !== 'string') {
548
+ throw new Error('Expected options.app.company to be a string. Got ' + options.app.company);
549
+ }
550
+ if (typeof options.app.fileDescription !== 'string') {
551
+ throw new Error('Expected options.app.fileDescription to be a string. Got ' + options.app.fileDescription);
552
+ }
553
+ if (typeof options.app.fileVersion !== 'string') {
554
+ throw new Error('Expected options.app.fileVersion to be a string. Got ' + options.app.fileVersion);
555
+ }
556
+ if (typeof options.app.internalName !== 'string') {
557
+ throw new Error('Expected options.app.internalName to be a string. Got ' + options.app.internalName);
558
+ }
559
+ if (options.app.legalCopyright && typeof options.app.legalCopyright !== 'string') {
560
+ throw new Error('Expected options.app.legalCopyright to be a string. Got ' + options.app.legalCopyright);
561
+ }
562
+ if (options.app.legalTrademark && typeof options.app.legalTrademark !== 'string') {
563
+ throw new Error('Expected options.app.legalTrademark to be a string. Got ' + options.app.legalTrademark);
564
+ }
565
+ if (typeof options.app.originalFilename !== 'string') {
566
+ throw new Error('Expected options.app.originalFilename to be a string. Got ' + options.app.originalFilename);
567
+ }
568
+ if (typeof options.app.privateBuild !== 'string') {
569
+ throw new Error('Expected options.app.privateBuild to be a string. Got ' + options.app.privateBuild);
570
+ }
571
+ if (typeof options.app.productName !== 'string') {
572
+ throw new Error('Expected options.app.productName to be a string. Got ' + options.app.productName);
573
+ }
574
+ if (typeof options.app.productVersion !== 'string') {
575
+ throw new Error('Expected options.app.productVersion to be a string. Got ' + options.app.productVersion);
576
+ }
577
+ if (options.app.specialBuild && typeof options.app.specialBuild !== 'string') {
578
+ throw new Error('Expected options.app.specialBuild to be a string. Got ' + options.app.specialBuild);
579
+ }
580
+ if (typeof options.app.legalCopyright !== 'string') {
581
+ throw new Error('Expected options.app.legalCopyright to be a string. Got ' + options.app.legalCopyright);
582
+ }
583
+ if (typeof options.app.languageCode !== 'number') {
584
+ throw new Error('Expected options.app.languageCode to be a number. Got ' + options.app.languageCode);
585
+ }
586
+ }
416
587
  return undefined;
417
588
  };
418
589
 
@@ -453,4 +624,39 @@ async function fileExists(filePath) {
453
624
  return exists;
454
625
  }
455
626
 
456
- export default { fileExists, getReleaseInfo, getPath, PLATFORM_KV, ARCH_KV, EXE_NAME, globFiles, getNodeManifest, parse, validate };
627
+ /**
628
+ * Custom logging function
629
+ * @param {'debug' | 'info' | 'warn' | 'error'} severity - severity of message
630
+ * @param {'debug' | 'info' | 'warn' | 'error'} logLevel - log level requested by user
631
+ * @param {string} message - contents of message
632
+ * @throws {Error} - throw error on invalid input
633
+ * @returns {string} - stdout
634
+ */
635
+ function log(severity, logLevel, message) {
636
+ if (!['debug', 'info', 'warn', 'error'].includes(severity)) {
637
+ throw new Error(`Expected debug, info, warn or error message severity. Got ${severity}`);
638
+ }
639
+ if (!['debug', 'info', 'warn', 'error'].includes(logLevel)) {
640
+ throw new Error(`Expected debug, info, warn or error user defined log level. Got ${logLevel}`);
641
+ }
642
+
643
+ const sev = {
644
+ 'debug': 4,
645
+ 'info': 3,
646
+ 'warn': 2,
647
+ 'error': 1,
648
+ };
649
+ let stdout = '';
650
+ const messageSeverity = sev[severity];
651
+ const userDefSeverity = sev[logLevel];
652
+
653
+ if (messageSeverity <= userDefSeverity) {
654
+ stdout = `[ ${severity.toUpperCase()} ] ${message}`;
655
+ }
656
+
657
+ console.log(stdout);
658
+
659
+ return stdout;
660
+ }
661
+
662
+ export default { fileExists, getReleaseInfo, getPath, PLATFORM_KV, ARCH_KV, EXE_NAME, globFiles, getNodeManifest, parse, validate, log };