From 011c96c60eef53cfe318bb84d43f6e520fd7b21c Mon Sep 17 00:00:00 2001
From: Ayushman Chhabra <14110965+ayushmanchhabra@users.noreply.github.com>
Date: Sun, 10 May 2026 01:12:47 +0530
Subject: [PATCH 1/9] chore(docs): update README
---
README.md | 56 ++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 45 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index b165f6b..4594113 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,26 @@ Update NW.js applications for Linux, MacOS and Windows platforms.
## Usage
```js
-import {}
+import Updater from "@nwutils/updater";
+const Manifest = nw.require("./package.json");
+
+const updater = new Updater(Manifest);
+
+let status = "";
+updater.checkNewVersion((err, newerVersionExists, remoteManifest) => {
+ if (err) {
+ status = `Error checking for updates: ${err.message}`;
+ return;
+ }
+ if (newerVersionExists) {
+ status = "A newer version is available.";
+ } else {
+ status = "No new version available.";
+ }
+});
+
+updater.download();
+
```
It gives you low-level API to:
@@ -26,19 +45,12 @@ It gives you low-level API to:
5. The new app (in temp) will copy itself to the original folder, overwriting the old app.
6. The new app will run itself from original folder and exit the process.
-You should build this logic by yourself though. As a reference you can use [example](app/index.html).
-
-Covered by tests and works for [linux](http://screencast.com/t/Je2ptbHhP), [windows](http://screencast.com/t/MSTKqVS3) and [mac](http://screencast.com/t/OXyC5xoA).
-
-## Examples
-
-- [Basic](examples/basic.js)
-
-
## API
+
#### new updater(manifest, options)
+
Creates new instance of updater. Manifest could be a `package.json` of project.
Note that compressed apps are assumed to be downloaded in the format produced by [nw-builder](https://github.com/nwutils/nw-builder) (or [grunt-nw-builder](https://github.com/nwjs/grunt-nw-builder)).
@@ -49,7 +61,9 @@ Note that compressed apps are assumed to be downloaded in the format produced by
- options `object` - Optional
+
#### updater.checkNewVersion(cb)
+
Will check the latest available version of the application by requesting the manifest specified in `manifestUrl`.
The callback will always be called; the second parameter indicates whether or not there's a newer version.
@@ -60,7 +74,9 @@ This function assumes you use [Semantic Versioning](http://semver.org) and enfor
- cb `function` - Callback arguments: error, newerVersionExists (`Boolean`), remoteManifest
+
#### updater.download(cb, newManifest)
+
Downloads the new app to a template folder
**Params**
@@ -70,17 +86,23 @@ Downloads the new app to a template folder
**Returns**: `Request` - Request - stream, the stream contains `manifest` property with new manifest and 'content-length' property with the size of package.
+
#### updater.getAppPath()
+
Returns executed application path
**Returns**: `string`
+
#### updater.getAppExec()
+
Returns current application executable
**Returns**: `string`
+
#### updater.unpack(filename, cb, manifest)
+
Will unpack the `filename` in temporary folder.
For Windows, [unzip](https://www.mkssoftware.com/docs/man1/unzip.1.asp) is used (which is [not signed](https://github.com/edjafarov/node-webkit-updater/issues/68)).
@@ -91,7 +113,9 @@ For Windows, [unzip](https://www.mkssoftware.com/docs/man1/unzip.1.asp) is used
- manifest `object`
+
#### updater.runInstaller(appPath, args, options)
+
Runs installer
**Params**
@@ -102,7 +126,9 @@ Runs installer
**Returns**: `function`
+
#### updater.install(copyPath, cb)
+
Installs the app (copies current application to `copyPath`)
**Params**
@@ -111,7 +137,9 @@ Installs the app (copies current application to `copyPath`)
- cb `function` - Callback arguments: error
+
#### updater.run(execPath, args, options)
+
Runs the app from original app executable path.
**Params**
@@ -122,7 +150,6 @@ Runs the app from original app executable path.
Note: if this doesn't work, try `gui.Shell.openItem(execPath)` (see [node-webkit Shell](https://github.com/rogerwang/node-webkit/wiki/Shell)).
-
---
## Manifest Schema
@@ -156,18 +183,23 @@ The manifest could be a `package.json` of project, but doesn't have to be.
The name of your app. From time, it is assumed your Mac app is called `.app`, your Windows executable is `.exe`, etc.
### manifest.version
+
[semver](http://semver.org) version of your app.
### manifest.manifestUrl
+
The URL where your latest manifest is hosted; where node-webkit-updater looks to check if there is a newer version of your app available.
### manifest.packages
+
An "object" containing an object for each OS your app (at least this version of your app) supports; `mac`, `win`, `linux32`, `linux64`.
### manifest.packages.{mac, win, linux32, linux64}.url
+
Each package has to contain a `url` property pointing to where the app (for the version & OS in question) can be downloaded.
### manifest.packages.{mac, win, linux32, linux64}.execPath (Optional)
+
It's assumed your app is stored at the root of your package, use this to override that and specify a path (relative to the root of your package).
This can also be used to override `manifest.name`; e.g. if your `manifest.name` is `helloWorld` (therefore `helloWorld.app` on Mac) but your Windows executable is named `nw.exe`. Then you'd set `execPath` to `nw.exe`
@@ -177,9 +209,11 @@ This can also be used to override `manifest.name`; e.g. if your `manifest.name`
## Troubleshooting
### Mac
+
If you get an error on Mac about too many files being open, run `ulimit -n 10240`
### Windows
+
On Windows, there is no "unzip" command built in by default. As a result, this project uses a third party "unzip.exe" in order to extract the downloaded update. On the NWJS site, in the "How to package and distribute your apps" file, one of the recommended methods of distribution is using EnigmaVirtualBox to package the app, nw.exe, and required DLLs into a single EXE file. This method works great for distribution, but unfortunately breaks node-webkit-updater, because it wraps the required unzip.exe file inside of the created EnigmaVirtualBox EXE. As a result, *it is not possible to use EnigmaVirtualBox to distribute your app if you plan on using node-webkit-updater*. Try using InnoSetup instead.
## Contributing
From 990cd9ac6f0723cdd059a4cb95ad12120b0041c2 Mon Sep 17 00:00:00 2001
From: Ayushman Chhabra <14110965+ayushmanchhabra@users.noreply.github.com>
Date: Sun, 10 May 2026 01:16:35 +0530
Subject: [PATCH 2/9] chore(test): use nw.App.manifest instead of
nw.require(package.json)
---
src/main.js | 49 +++++++++++++++++++++++++++++
tests/fixtures/app-current/index.js | 3 +-
2 files changed, 50 insertions(+), 2 deletions(-)
diff --git a/src/main.js b/src/main.js
index c1710aa..fa46138 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,4 +1,7 @@
+const fs = await import('node:fs');
const os = await import('node:os');
+const path = await import('node:path');
+const stream = await import('node:stream');
function semverGt(v1, v2) {
const [major1, minor1, patch1] = v1.replace(/^v/i, '').split('.').map(Number);
@@ -83,6 +86,52 @@ class Updater {
cb(error, false, null);
});
}
+
+ /**
+ * Downloads the new app to a temporary folder.
+ *
+ * @async
+ * @method
+ * @param {(error: Error|null, filepath: string|null) => void} cb
+ * @param {Manifest} newManifest
+ * @returns {void}
+ */
+ download(cb, newManifest) {
+ const manifest = newManifest ?? this.manifest;
+ const url = manifest.packages[platform].url;
+
+ const filename = decodeURI(path.basename(url));
+
+ const destinationPath = path.resolve(
+ this.options.temporaryDirectory,
+ filename
+ );
+
+ const writeStream = fs.createWriteStream(destinationPath);
+
+ fetch(url)
+ .then((response) => {
+ if (!response.ok) {
+ throw new Error(
+ `Failed to download update: ${response.status} ${response.statusText}`
+ );
+ }
+
+ if (!response.body) {
+ throw new Error('Response body is not readable');
+ }
+
+ // Web ReadableStream -> Node.js Readable
+ const readable = stream.Readable.fromWeb(response.body);
+ return stream.promises.pipeline(readable, writeStream);
+ })
+ .then(() => {
+ cb(null, destinationPath);
+ })
+ .catch((err) => {
+ cb(err, null);
+ });
+ }
}
export default Updater;
diff --git a/tests/fixtures/app-current/index.js b/tests/fixtures/app-current/index.js
index b06a155..092fe97 100644
--- a/tests/fixtures/app-current/index.js
+++ b/tests/fixtures/app-current/index.js
@@ -1,9 +1,8 @@
import Updater from "./updater.js";
-const manifest = nw.require("./package.json");
let updater;
document.addEventListener("DOMContentLoaded", () => {
- updater = new Updater(manifest);
+ updater = new Updater(nw.App.manifest);
document.getElementById("check-for-updates-button").addEventListener("click", handleCheckForUpdates);
});
From 5de57ea4528ea653a040568860752420422d0989 Mon Sep 17 00:00:00 2001
From: Ayushman Chhabra <14110965+ayushmanchhabra@users.noreply.github.com>
Date: Sun, 10 May 2026 01:17:35 +0530
Subject: [PATCH 3/9] chore(docs): update LICENSE year
---
LICENSE | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/LICENSE b/LICENSE
index 53298f1..c918d8b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2023 NW.js Utils
+Copyright (c) 2026 NW.js Utils
Copyright (c) 2014 Eldar Djafarov
Permission is hereby granted, free of charge, to any person obtaining a copy
From 4dd904525fe42bce147dd835345733715c3da2b1 Mon Sep 17 00:00:00 2001
From: Ayushman Chhabra <14110965+ayushmanchhabra@users.noreply.github.com>
Date: Sun, 10 May 2026 02:54:19 +0530
Subject: [PATCH 4/9] chore(test): add test case for updater.download function
---
README.md | 2 +-
src/main.js | 36 +++++++++++--
tests/fixtures/app-current/index.html | 33 +++++++++++-
tests/fixtures/app-current/index.js | 21 +++++++-
tests/fixtures/app-latest/index.html | 33 +++++++++++-
tests/fixtures/app-latest/index.js | 22 +++++++-
tests/fixtures/app-latest/updater.js | 77 +++++++++++++++++++++++++++
tests/fixtures/releases/manifest.json | 4 +-
tests/specs/main.test.js | 22 ++++++--
types/src/main.d.ts | 10 ++++
10 files changed, 241 insertions(+), 19 deletions(-)
diff --git a/README.md b/README.md
index 4594113..50df231 100644
--- a/README.md
+++ b/README.md
@@ -77,7 +77,7 @@ This function assumes you use [Semantic Versioning](http://semver.org) and enfor
#### updater.download(cb, newManifest)
-Downloads the new app to a template folder
+Downloads the new app to a temporary folder.
**Params**
diff --git a/src/main.js b/src/main.js
index fa46138..749691c 100644
--- a/src/main.js
+++ b/src/main.js
@@ -43,6 +43,32 @@ function semverGt(v1, v2) {
* @property {string} temporaryDirectory - The path to a directory to download the updates to and unpack them in. Defaults to [`os.tmpdir()`](https://nodejs.org/api/os.html#os_os_tmpdir)
*/
+function getHost() {
+ let platform;
+
+ switch (process.platform) {
+ case 'win32':
+ platform = 'windows';
+ break;
+
+ case 'darwin':
+ platform = 'macos';
+ break;
+
+ case 'linux':
+ platform = 'linux';
+ break;
+
+ default:
+ throw new Error(`Unsupported platform: ${process.platform}`);
+ }
+
+ const arch = process.arch;
+
+ return `${platform}-${arch}`;
+
+}
+
class Updater {
/**
@@ -98,10 +124,11 @@ class Updater {
*/
download(cb, newManifest) {
const manifest = newManifest ?? this.manifest;
- const url = manifest.packages[platform].url;
+ const url = manifest.packages[getHost()].url;
const filename = decodeURI(path.basename(url));
+ fs.mkdirSync(this.options.temporaryDirectory, { recursive: true });
const destinationPath = path.resolve(
this.options.temporaryDirectory,
filename
@@ -121,9 +148,10 @@ class Updater {
throw new Error('Response body is not readable');
}
- // Web ReadableStream -> Node.js Readable
- const readable = stream.Readable.fromWeb(response.body);
- return stream.promises.pipeline(readable, writeStream);
+ return stream.promises.pipeline(
+ response.body,
+ writeStream
+ );
})
.then(() => {
cb(null, destinationPath);
diff --git a/tests/fixtures/app-current/index.html b/tests/fixtures/app-current/index.html
index 97d4e5d..02d2480 100644
--- a/tests/fixtures/app-current/index.html
+++ b/tests/fixtures/app-current/index.html
@@ -1,13 +1,42 @@
+
Demo
+
-
-
+
+
+
+
Function
+
Action
+
Status
+
Data
+
+
+
+
+
+
checkNewVersion
+
+
+
+
+
+
+
download
+
+
+
+
+
+
+
+
+
diff --git a/tests/fixtures/app-current/index.js b/tests/fixtures/app-current/index.js
index 092fe97..18558a8 100644
--- a/tests/fixtures/app-current/index.js
+++ b/tests/fixtures/app-current/index.js
@@ -1,9 +1,13 @@
+const path = await import("node:path");
+const process = await import("node:process");
+
import Updater from "./updater.js";
let updater;
document.addEventListener("DOMContentLoaded", () => {
- updater = new Updater(nw.App.manifest);
+ updater = new Updater(nw.App.manifest, { temporaryDirectory: path.resolve(nw.App.dataPath, "tmpDir") });
document.getElementById("check-for-updates-button").addEventListener("click", handleCheckForUpdates);
+ document.getElementById("download-button").addEventListener("click", handleDownload);
});
function handleCheckForUpdates() {
@@ -16,8 +20,23 @@ function handleCheckForUpdates() {
}
if (newerVersionExists) {
updateStatus.textContent = "A newer version is available.";
+ document.getElementById("update-data").textContent = JSON.stringify(remoteManifest, null, 2);
} else {
updateStatus.textContent = "No new version available.";
}
});
}
+
+function handleDownload() {
+ const downloadStatus = document.getElementById("download-status");
+ const newManifest = document.getElementById("update-data").textContent ? JSON.parse(document.getElementById("update-data").textContent) : null;
+ downloadStatus.textContent = "Downloading update...";
+ updater.download((err, filePath) => {
+ if (err) {
+ downloadStatus.textContent = `Error downloading update: ${err.message}`;
+ return;
+ }
+ downloadStatus.textContent = "Update downloaded successfully at " + filePath;
+ document.getElementById("download-data").textContent = filePath;
+ }, newManifest);
+}
diff --git a/tests/fixtures/app-latest/index.html b/tests/fixtures/app-latest/index.html
index 97d4e5d..02d2480 100644
--- a/tests/fixtures/app-latest/index.html
+++ b/tests/fixtures/app-latest/index.html
@@ -1,13 +1,42 @@
+
Demo
+
-
-
+