Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: miscellaneous quality of life improvements #1296

Merged
merged 47 commits into from
Nov 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
0c9fb93
chore(docs): update roadmap
ayushmanchhabra Nov 8, 2024
8125b86
chore(ci): upgrade MacOS and Ubuntu image runners
ayushmanchhabra Nov 8, 2024
e305f3a
chore(test): start Windows fixture configuration
ayushmanchhabra Nov 8, 2024
ab3e4a6
chore(test): rename demo.js to demo.osx.js
ayushmanchhabra Nov 8, 2024
186a32d
fix: undeprecate options.version
ayushmanchhabra Nov 8, 2024
c1ccd81
chore(bld): update docs link
ayushmanchhabra Nov 8, 2024
523b445
chore(docs): update roadmap
ayushmanchhabra Nov 8, 2024
c444b69
chore(util): add in house log function
ayushmanchhabra Nov 8, 2024
56905ca
chore(util): check for invalid input
ayushmanchhabra Nov 8, 2024
67bc126
chore(util): check for invalid input
ayushmanchhabra Nov 8, 2024
e705ea0
chore(test): try out log function
ayushmanchhabra Nov 8, 2024
0ef2df3
fix(get): listen for Ctrl+C and delete partially downloaded file
ayushmanchhabra Nov 8, 2024
79696d3
fix(docs): correct default value of options.app.legalCopyright for Wi…
ayushmanchhabra Nov 8, 2024
0724887
chore(docs): clarify options.app.fileVersion default values
ayushmanchhabra Nov 8, 2024
92d8dab
chore(docs): clarify terminology regarding package.json
ayushmanchhabra Nov 8, 2024
2c774d8
chore(test): configure initial Windows build
ayushmanchhabra Nov 8, 2024
8bfbcea
chore: create CLI command to build and execute Windows NW.js demo app
ayushmanchhabra Nov 8, 2024
9aaa5d7
chore: create CLI command to build and execute Windows NW.js demo app
ayushmanchhabra Nov 8, 2024
3652910
fix(test): point to valid icon path
ayushmanchhabra Nov 8, 2024
60470f4
chore(get): correct spelling in code comments
ayushmanchhabra Nov 8, 2024
c0e1fed
chore(bld): improve readability
ayushmanchhabra Nov 8, 2024
ba081f5
chore(test): update title from NW.js Demo to Demo since it shows in T…
ayushmanchhabra Nov 8, 2024
8124ad5
chore: add some logs so user knows status of process
ayushmanchhabra Nov 9, 2024
1bee729
chore(test): update options.app.legalCopyright
ayushmanchhabra Nov 9, 2024
1963641
chore(test): add options.app.NSLocalNetworkUsageDescription to MacOS …
ayushmanchhabra Nov 9, 2024
506e4f2
fix(util): parse options.app.exec path correctly
ayushmanchhabra Nov 9, 2024
243256f
chore(test): create linux fixture
ayushmanchhabra Nov 9, 2024
75ae2a6
chore(test): switch demo configs to normal build flavor
ayushmanchhabra Nov 9, 2024
a2bd73d
chore(test): add command to build and execute demo linux app
ayushmanchhabra Nov 9, 2024
8b388ec
chore(test): switch demo config to sdk build flavor
ayushmanchhabra Nov 9, 2024
88f6104
chore(test): update demo app name in manifest
ayushmanchhabra Nov 9, 2024
e6881b8
fix(util): resolve path for icon
ayushmanchhabra Nov 9, 2024
9a212fa
fix(bld): correct options.app.exec assignment
ayushmanchhabra Nov 9, 2024
53f8b61
fix(util): point to icon in built app
ayushmanchhabra Nov 9, 2024
5c380b8
fix(bld): update icon for linux post parsing
ayushmanchhabra Nov 9, 2024
fd1f41a
fix(bld): update icon for linux post parsing
ayushmanchhabra Nov 9, 2024
6a73cc6
fix(bld): update icon for linux post parsing
ayushmanchhabra Nov 9, 2024
4ba840d
chore(docs): add note on how to build and execute demo applications
ayushmanchhabra Nov 9, 2024
7692205
chore(test): reference icon from manifest
ayushmanchhabra Nov 9, 2024
41993fe
chore(test): add comment about icon file path
ayushmanchhabra Nov 9, 2024
f15ec3a
chore(test): remove comment about icon file path
ayushmanchhabra Nov 9, 2024
29ca824
chore(test): add comment for how to execute built demo app
ayushmanchhabra Nov 9, 2024
c26c175
chore(test): update comment for how to execute built demo app
ayushmanchhabra Nov 9, 2024
6401d30
chore(test): fix outDir path
ayushmanchhabra Nov 9, 2024
1978370
chore(test): remove node prefix
ayushmanchhabra Nov 9, 2024
5a44161
chore(docs): mention a gotcha regarding icon
ayushmanchhabra Nov 9, 2024
582591d
chore(docs): mention a gotcha regarding icon
ayushmanchhabra Nov 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ jobs:
strategy:
matrix:
os:
- macos-14
- ubuntu-22.04
- macos-15
- ubuntu-24.04
- windows-2022
fail-fast: false
runs-on: ${{ matrix.os }}
Expand Down
27 changes: 14 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Every NW.js release includes a modified Node.js binary at a specific version. It

## Usage

> Using this package might feel overwhelming due to the abundence of options. There are demo applications built for Linux, MacOS and Windows which can be found in `/tests/fixtures/app`. You can build a Linux application by cloning this repo and running `npm run demo:bld:linux && npm run demo:exe:linux`. Commands for the other operating systems can be found inside `scripts` prefixed with `demo:*` in the `package.json`.

This package can be used via a command line interface, be imported as a JavaScript module, or configured via the Node manifest as a JSON object. If options are defined in Node manifest, then they will be used over options defined in CLI or JavaScript API.

CLI interface:
Expand Down Expand Up @@ -228,20 +230,20 @@ This object defines additional properties used for building for a specific platf

| Name | Type | Default | Description |
| ---- | ------- | --------- | ----------- |
| `icon` | `string` | `undefined` | The path to the icon file. It should be a .ico file. |
| `name` | `string` | Value of `name` in app's `package.json` | The name of the application |
| `version` | `string` | Value of `version` in app's `package.json` | (deprecated, Use `fileVersion` instead) The version of the application |
| `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) |
| `name` | `string` | Value of `name` in NW.js manifest | The name of the application |
| `version` | `string` | Value of `version` in NW.js manifest | The version of the application |
| `comments` | `string` | `undefined` | Additional information that should be displayed for diagnostic purposes. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `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. |
| `privateBuild` | `string` | `undefined` | Information about a private version of the file—for example, Built by TESTER1 on \\TESTBED. |
| `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. |
| `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. |
| `productName` | `string` | `name` in NW.js manifest | Name of the product with which the file is distributed. This string is required. |
| `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. |
| `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. |
| `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 |

Expand All @@ -253,7 +255,7 @@ This object defines additional properties used for building for a specific platf
| genericName | `string` | Generic name of the application |
| noDisplay | `boolean` | If true the application is not displayed |
| comment | `string` | Tooltip for the entry, for example "View sites on the Internet". |
| icon | `string` | Icon to display in file manager, menus, etc. |
| icon | `string` | Icon to display in file manager, menus, etc. (**WARNING**: Please define the icon in the NW.js manifest instead) |
| hidden | `boolean` | TBD |
| onlyShowIn | `string[]` | A list of strings identifying the desktop environments that should display a given desktop entry |
| notShowIn | `string[]` | A list of strings identifying the desktop environments that should not display a given desktop entry |
Expand All @@ -277,7 +279,7 @@ This object defines additional properties used for building for a specific platf
| Name | Type | Description |
| ---- | ------- | ----------- |
| name | `string` | The name of the application |
| icon | `string` | The path to the icon file. It should be a .icns file. |
| 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) |
| LSApplicationCategoryType | `string` | The category that best describes your app for the App Store. |
| CFBundleIdentifier | `string` | A unique identifier for a bundle usually in reverse DNS format. |
| CFBundleName | `string` | A user-visible short name for the bundle. |
Expand Down Expand Up @@ -385,7 +387,6 @@ nwbuild({

### Chores

- chore: add Linux, MacOS and Windows fixtures
- chore(docs): don't store JSDoc definitions in `typedef`s - get's hard to understand during development.
- chore: annotate file paths as `fs.PathLike` instead of `string`.
- chore(bld): factor out core build step
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@
"lint:fix": "eslint --fix ./src ./tests",
"test": "vitest run --coverage",
"test:cov": "vitest --coverage.enabled true",
"demo:bld": "node ./tests/fixtures/demo.js",
"demo:exe": "./tests/fixtures/out/Demo.app/Contents/MacOS/Demo",
"demo:bld:linux": "node ./tests/fixtures/demo.linux.js",
"demo:bld:osx": "node ./tests/fixtures/demo.osx.js",
"demo:bld:win": "node ./tests/fixtures/demo.win.js",
"demo:exe:linux": "./tests/fixtures/out/linux/Demo",
"demo:exe:osx": "./tests/fixtures/out/osx/Demo.app/Contents/MacOS/Demo",
"demo:exe:win": "./tests/fixtures/out/win/Demo.exe",
"demo:cli": "nwbuild --mode=run --flavor=sdk --glob=false --cacheDir=./node_modules/nw ./tests/fixtures/app"
},
"devDependencies": {
Expand Down
13 changes: 7 additions & 6 deletions src/bld.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ import setOsxConfig from './bld/osx.js';
* References:
* https://learn.microsoft.com/en-us/windows/win32/msi/version
* https://learn.microsoft.com/en-gb/windows/win32/sbscs/application-manifests
* https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2015/deployment/trustinfo-element-clickonce-application?view=vs-2015#requestedexecutionlevel
* https://learn.microsoft.com/en-us/visualstudio/deployment/trustinfo-element-clickonce-application?view=vs-2022#requestedexecutionlevel
* https://learn.microsoft.com/en-gb/windows/win32/menurc/versioninfo-resource
* @typedef {object} WinRc Windows configuration options. More info
* @property {string} name The name of the application
* @property {string} version @deprecated Use {@link fileVersion} instead. The version of the application
* @property {string} version The version of the application
* @property {string} comments Additional information that should be displayed for diagnostic purposes.
* @property {string} company Company that produced the file—for example, Microsoft Corporation or Standard Microsystems Corporation, Inc. This string is required.
* @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.
Expand Down Expand Up @@ -256,13 +256,13 @@ const setLinuxConfig = async ({ app, outDir }) => {
GenericName: app.genericName,
NoDisplay: app.noDisplay,
Comment: app.comment,
Icon: app.icon,
Icon: path.resolve(outDir, 'package.nw', path.basename(app.icon)),
Hidden: app.hidden,
OnlyShowIn: app.onlyShowIn,
NotShowIn: app.notShowIn,
DBusActivatable: app.dBusActivatable,
TryExec: app.tryExec,
Exec: app.name,
Exec: app.exec,
Path: app.path,
Terminal: app.terminal,
Actions: app.actions,
Expand Down Expand Up @@ -319,10 +319,11 @@ const setWinConfig = async ({ app, outDir }) => {
if (app.icon) {
const iconBuffer = await fs.promises.readFile(path.resolve(app.icon));
const iconFile = resedit.Data.IconFile.from(iconBuffer);
const iconGroupIDs = resedit.Resource.IconGroupEntry.fromEntries(res.entries).map((entry) => entry.id);
resedit.Resource.IconGroupEntry.replaceIconsForResource(
res.entries,
// This is the name of the icon group nw.js uses that gets shown in file exlorers
'IDR_MAINFRAME',
/* Should be `IDR_MAINFRAME` */
iconGroupIDs[0],
EN_US,
iconFile.icons.map(i => i.data)
);
Expand Down
11 changes: 11 additions & 0 deletions src/get/request.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'node:fs';
import process from 'node:process';
import stream from 'node:stream';

import axios from 'axios';
Expand All @@ -15,11 +16,21 @@ export default async function request(url, filePath) {

const writeStream = fs.createWriteStream(filePath);

/* Listen for SIGINT (Ctrl+C) */
process.on('SIGINT', function () {
/* Delete file if it exists. This prevents unnecessary `Central Directory not found` errors. */
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
process.exit();
});

const response = await axios({
method: 'get',
url: url,
responseType: 'stream'
});

await stream.promises.pipeline(response.data, writeStream);

}
2 changes: 1 addition & 1 deletion src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export type AppOptions<P extends SupportedPlatform> =
export interface WindowsAppOptions {
/** The name of the application */
name?: string,
/** @deprecated Use {@link fileVersion} instead. The version of the application */
/** The version of the application */
version?: string,
/** Additional information that should be displayed for diagnostic purposes. */
comments?: string,
Expand Down
22 changes: 16 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import console from 'node:console';
import fs from 'node:fs';
import path from 'node:path';

import bld from './bld.js';
import get from './get/index.js';
Expand Down Expand Up @@ -45,17 +46,19 @@ async function nwbuild(options) {
};

try {
// Parse options
/* Parse options */
options = await util.parse(options, manifest);
util.log('debug', 'info', 'Parse initial options');

util.log('debug', 'info', 'Get node manifest...');
manifest = await util.getNodeManifest({ srcDir: options.srcDir, glob: options.glob });
if (typeof manifest.json?.nwbuild === 'object') {
options = manifest.json.nwbuild;
}

util.log('info', options.logLevel, 'Parse final options using node manifest');
options = await util.parse(options, manifest.json);

//TODO: impl logging
util.log('debug', options.logLevel, 'Manifest: ', `${manifest.path}\n${manifest.json}\n`);

built = fs.existsSync(options.cacheDir);
if (built === false) {
Expand All @@ -69,21 +72,25 @@ async function nwbuild(options) {
}
}

// Validate options.version to get the version specific release info
/* Validate options.version to get the version specific release info */
util.log('info', options.logLevel, 'Get version specific release info...');
releaseInfo = await util.getReleaseInfo(
options.version,
options.platform,
options.arch,
options.cacheDir,
options.manifestUrl,
);
util.log('debug', options.logLevel, `Release info:\n${JSON.stringify(releaseInfo, null, 2)}\n`);

util.log('info', options.logLevel, 'Validate options.* ...');
await util.validate(options, releaseInfo);
util.log('debug', options.logLevel, `Options:\n${JSON.stringify(options, null, 2)}`);

// Remove leading "v" from version string
/* Remove leading "v" from version string */
options.version = releaseInfo.version.slice(1);

// Download binaries
util.log('info', options.logLevel, 'Getting NW.js and related binaries...');
await get({
version: options.version,
flavor: options.flavor,
Expand All @@ -102,6 +109,7 @@ async function nwbuild(options) {
}

if (options.mode === 'run') {
util.log('info', options.logLevel, 'Running NW.js in run mode...');
await run({
version: options.version,
flavor: options.flavor,
Expand All @@ -113,6 +121,7 @@ async function nwbuild(options) {
argv: options.argv,
});
} else if (options.mode === 'build') {
util.log('info', options.logLevel, `Build a NW.js application for ${options.platform} ${options.arch}...`);
await bld({
version: options.version,
flavor: options.flavor,
Expand All @@ -129,6 +138,7 @@ async function nwbuild(options) {
zip: options.zip,
releaseInfo: releaseInfo,
});
util.log('info', options.logLevel, `Appliction is available at ${path.resolve(options.outDir)}`);
}
} catch (error) {
console.error(error);
Expand Down
42 changes: 39 additions & 3 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ export const parse = async (options, pkg) => {
/* Remove special and control characters from app.name to mitigate potential path traversal. */
options.app.name = options.app.name.replace(/[<>:"/\\|?*\u0000-\u001F]/g, '');
}
options.app.icon = options.app.icon ?? undefined;
/* Path to where the icon currently is in the filesystem */
options.app.icon = path.resolve(options.app.icon) ?? undefined;

// TODO(#737): move this out
if (options.platform === 'linux') {
Expand All @@ -243,7 +244,7 @@ export const parse = async (options, pkg) => {
options.app.notShowIn = options.app.notShowIn ?? undefined;
options.app.dBusActivatable = options.app.dBusActivatable ?? undefined;
options.app.tryExec = options.app.tryExec ?? undefined;
options.app.exec = options.app.name ?? undefined;
options.app.exec = path.resolve(options.app.exec) ?? undefined;
options.app.path = options.app.path ?? undefined;
options.app.terminal = options.app.terminal ?? undefined;
options.app.actions = options.app.actions ?? undefined;
Expand Down Expand Up @@ -453,4 +454,39 @@ async function fileExists(filePath) {
return exists;
}

export default { fileExists, getReleaseInfo, getPath, PLATFORM_KV, ARCH_KV, EXE_NAME, globFiles, getNodeManifest, parse, validate };
/**
* Custom logging function
* @param {'debug' | 'info' | 'warn' | 'error'} severity - severity of message
* @param {'debug' | 'info' | 'warn' | 'error'} logLevel - log level requested by user
* @param {string} message - contents of message
* @throws {Error} - throw error on invalid input
* @returns {string} - stdout
*/
function log(severity, logLevel, message) {
if (!['debug', 'info', 'warn', 'error'].includes(severity)) {
throw new Error(`Expected debug, info, warn or error message severity. Got ${severity}`);
}
if (!['debug', 'info', 'warn', 'error'].includes(logLevel)) {
throw new Error(`Expected debug, info, warn or error user defined log level. Got ${logLevel}`);
}

const sev = {
'debug': 4,
'info': 3,
'warn': 2,
'error': 1,
};
let stdout = '';
const messageSeverity = sev[severity];
const userDefSeverity = sev[logLevel];

if (messageSeverity <= userDefSeverity) {
stdout = `[ ${severity.toUpperCase()} ] ${message}`;
}

console.log(stdout);

return stdout;
}

export default { fileExists, getReleaseInfo, getPath, PLATFORM_KV, ARCH_KV, EXE_NAME, globFiles, getNodeManifest, parse, validate, log };
2 changes: 1 addition & 1 deletion tests/fixtures/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<html>
<head>
<title>NW.js Demo</title>
<title>Demo</title>
</head>

<body>
Expand Down
7 changes: 5 additions & 2 deletions tests/fixtures/app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"name": "nwapp",
"name": "Demo",
"main": "index.html",
"version": "0.0.0",
"product_string": "nwapp"
"product_string": "Demo",
"window": {
"icon": "./icon.png"
}
}
30 changes: 30 additions & 0 deletions tests/fixtures/demo.linux.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import nwbuild from '../../src/index.js';

await nwbuild({
mode: 'build',
flavor: 'sdk',
platform: 'linux',
srcDir: './tests/fixtures/app',
cacheDir: './node_modules/nw',
outDir: './tests/fixtures/out/linux',
glob: false,
logLevel: 'debug',
app: {
name: 'Demo',
genericName: 'Demo',
noDisplay: false,
comment: 'Tooltip information',
/* File path of icon from where it is copied. */
icon: './tests/fixtures/app/icon.png',
hidden: false,
// TODO: test in different Linux desktop environments
// onlyShowIn: [],
// notShowIn: [],
dBusActivatable: true,
// TODO: test in Linux environment
// tryExec: '/path/to/exe?'
exec: './tests/fixtures/out/linux/Demo',
}
});

console.log('\nExecute `npm run demo:exe:linux` to run the application.');
Loading