@nwutils/packager 0.5.0 → 0.7.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 +96 -5
- package/package.json +3 -3
- package/src/appImage.js +10 -87
- package/src/index.js +8 -6
- package/src/nsis.js +645 -0
- package/src/util.js +214 -6
- package/types/appImage.d.ts +22 -14
- package/types/index.d.ts +16 -5
- package/types/nsis.d.ts +150 -0
- package/types/util.d.ts +119 -36
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Supported output formats, selected via `format`:
|
|
|
12
12
|
| `deb` | Linux | Planned |
|
|
13
13
|
| `rpm` | Linux | Planned |
|
|
14
14
|
| `msix` | Windows | Planned |
|
|
15
|
-
| `nsis` | Windows |
|
|
15
|
+
| `nsis` | Windows | Implemented |
|
|
16
16
|
| `dmg` | MacOS | Planned |
|
|
17
17
|
|
|
18
18
|
## Getting Started
|
|
@@ -106,14 +106,82 @@ managers. `true` derives it from `publish`
|
|
|
106
106
|
`zsyncmake` (usually packaged as `zsync`) installed on the host, and the
|
|
107
107
|
`.zsync` file must be uploaded alongside the AppImage.
|
|
108
108
|
|
|
109
|
+
### NSIS installer
|
|
110
|
+
|
|
111
|
+
Build the application with `platform: "win"` first, then:
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
const installerPath = await packager({
|
|
115
|
+
format: "nsis",
|
|
116
|
+
appDir: "./out",
|
|
117
|
+
appName: "Demo",
|
|
118
|
+
version: "1.2.0",
|
|
119
|
+
outDir: "./dist",
|
|
120
|
+
icon: "./icon.ico",
|
|
121
|
+
publisher: "ACME",
|
|
122
|
+
license: "./LICENSE.txt",
|
|
123
|
+
});
|
|
124
|
+
// ./dist/Demo-x64-Setup.exe
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The installer walks the user through a wizard and installs for the current
|
|
128
|
+
user only, into `%LOCALAPPDATA%\Programs\<appName>`, so it never asks for
|
|
129
|
+
administrator rights. It adds Start Menu and desktop shortcuts, and registers
|
|
130
|
+
an uninstaller in Windows' installed apps list. The uninstaller removes exactly
|
|
131
|
+
the files that were installed. Installing a new version first uninstalls the
|
|
132
|
+
previous one, matched by `appId`, and stops if that fails. If the application
|
|
133
|
+
is running, the installer and uninstaller ask the user to close it first;
|
|
134
|
+
in silent mode (`/S`) they exit with an error instead.
|
|
135
|
+
|
|
136
|
+
The installer refuses to run on Windows machines that can't run `arch`: x64
|
|
137
|
+
builds need x64 Windows, or ARM64 Windows 11, which emulates x64.
|
|
138
|
+
|
|
139
|
+
Run the installer with `/S` to install silently, and `/D=<dir>` (last, unquoted)
|
|
140
|
+
to choose the install directory.
|
|
141
|
+
|
|
142
|
+
It needs the NSIS compiler, `makensis`, installed on the host. It runs on
|
|
143
|
+
Linux (`apt install nsis`) and MacOS (`brew install makensis`) too, so a Windows
|
|
144
|
+
installer can be built from any platform. On Windows, `choco install nsis`.
|
|
145
|
+
|
|
146
|
+
### Self updating NSIS installs
|
|
147
|
+
|
|
148
|
+
Set `publish`, the same way as for [AppImages](#self-updating-appimages):
|
|
149
|
+
|
|
150
|
+
- an `app-update.yml` describing `publish` is installed next to
|
|
151
|
+
`<appName>.exe`, and removed by the uninstaller
|
|
152
|
+
- a `latest.yml` update info file (`latest-<arch>.yml` for architectures other
|
|
153
|
+
than `x64`) with the version, file name, size and SHA-512 of the installer is
|
|
154
|
+
written next to it in `outDir`
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
await packager({
|
|
158
|
+
format: "nsis",
|
|
159
|
+
appDir: "./out",
|
|
160
|
+
appName: "Demo",
|
|
161
|
+
version: "1.2.0",
|
|
162
|
+
outDir: "./dist",
|
|
163
|
+
publish: { provider: "github", owner: "nwutils", repo: "demo" },
|
|
164
|
+
});
|
|
165
|
+
// ./dist/Demo-x64-Setup.exe
|
|
166
|
+
// ./dist/latest.yml
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Upload both the installer and `latest.yml` to every release.
|
|
170
|
+
[`@nwutils/updater`](https://www.npmjs.com/package/@nwutils/updater)'s
|
|
171
|
+
`NsisUpdater` downloads the new installer, checks it, and runs it with
|
|
172
|
+
`/S /UPDATE`. With `/UPDATE`, the installer waits up to 60 seconds for the
|
|
173
|
+
running application to exit instead of asking the user to close it, updates it
|
|
174
|
+
silently, and starts the new version. Because it installs for the current user,
|
|
175
|
+
updating never asks for administrator rights.
|
|
176
|
+
|
|
109
177
|
## API Reference
|
|
110
178
|
|
|
111
179
|
### `packager({ format, ...options })`
|
|
112
180
|
|
|
113
|
-
| Name | Type
|
|
114
|
-
| ------ |
|
|
115
|
-
| format | `"AppImage" \| "deb" \| "rpm" \| "
|
|
116
|
-
| ... | -
|
|
181
|
+
| Name | Type | Description |
|
|
182
|
+
| ------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
183
|
+
| format | `"AppImage" \| "deb" \| "rpm" \| "msix" \| "nsis" \| "dmg"` | Which packager to run. `"AppImage"` and `"nsis"` are implemented today. |
|
|
184
|
+
| ... | - | The remaining options are passed through to the packager for `format` as-is. |
|
|
117
185
|
|
|
118
186
|
Resolves with the path to the resulting packaged artifact.
|
|
119
187
|
|
|
@@ -139,6 +207,29 @@ Options
|
|
|
139
207
|
|
|
140
208
|
Resolves with the path to the resulting `.AppImage` file.
|
|
141
209
|
|
|
210
|
+
### `nsis(options)`
|
|
211
|
+
|
|
212
|
+
Options
|
|
213
|
+
|
|
214
|
+
| Name | Type | Description |
|
|
215
|
+
| ----------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
216
|
+
| appDir | `string` | Path to a built NW.js Windows application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "win"` |
|
|
217
|
+
| appName | `string` | Name of the application. Must match the `app.name` value used to build `appDir`. Must be a valid Windows file name |
|
|
218
|
+
| version | `string` | Version of the application, eg. `"1.2.0"`. Required |
|
|
219
|
+
| arch | `"ia32" \| "x64" \| "arm64"` | Architecture `appDir` was built for. Defaults to the host architecture |
|
|
220
|
+
| outDir | `string` | Directory the resulting `<appName>-<arch>-Setup.exe` file is written to. Defaults to the parent directory of `appDir` |
|
|
221
|
+
| icon | `string` | Path to a `.ico` icon for the installer and uninstaller. Defaults to NSIS' own icon |
|
|
222
|
+
| appId | `string` | Name of the registry key the installation is recorded under. Defaults to `appName`. Keep it the same across releases |
|
|
223
|
+
| publisher | `string` | Publisher shown in Windows' installed apps list |
|
|
224
|
+
| copyright | `string` | Copyright notice written into the installer's version information |
|
|
225
|
+
| license | `string` | Path to a `.txt` or `.rtf` license file the user has to accept |
|
|
226
|
+
| desktopShortcut | `boolean` | If true, create a desktop shortcut. Defaults to `true` |
|
|
227
|
+
| startMenuShortcut | `boolean` | If true, create a Start Menu shortcut. Defaults to `true` |
|
|
228
|
+
| makensisPath | `string` | Path to the NSIS compiler. Defaults to `makensis` on the `PATH` |
|
|
229
|
+
| publish | `PublishOptions` | Where releases are published. Installs `app-update.yml` and writes `latest[-<arch>].yml` to `outDir` |
|
|
230
|
+
|
|
231
|
+
Resolves with the path to the resulting installer `.exe` file.
|
|
232
|
+
|
|
142
233
|
## Contributing
|
|
143
234
|
|
|
144
235
|
### External contributor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwutils/packager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Package NW.js applications for Linux, MacOS and Windows.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"NW.js",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"lint": "eslint . ../../tests/specs/packager ../../tests/fixtures/packager",
|
|
44
44
|
"lint:fix": "eslint . ../../tests/specs/packager ../../tests/fixtures/packager --fix",
|
|
45
45
|
"type": "tsc",
|
|
46
|
-
"test": "node --test --test-concurrency=1 \"../../tests/specs/packager/appImage.test.js\" \"../../tests/specs/packager/request.test.js\" \"../../tests/specs/packager/util.test.js\"",
|
|
47
|
-
"test:real": "node --test \"../../tests/specs/packager/appImage.real.test.js\""
|
|
46
|
+
"test": "node --test --test-concurrency=1 \"../../tests/specs/packager/appImage.test.js\" \"../../tests/specs/packager/nsis.test.js\" \"../../tests/specs/packager/request.test.js\" \"../../tests/specs/packager/util.test.js\"",
|
|
47
|
+
"test:real": "node --test --test-concurrency=1 \"../../tests/specs/packager/appImage.real.test.js\" \"../../tests/specs/packager/nsis.real.test.js\""
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {},
|
|
50
50
|
"devEngines": {
|
package/src/appImage.js
CHANGED
|
@@ -141,7 +141,7 @@ async function appImage({
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
if (publish !== undefined) {
|
|
144
|
-
validatePublish(publish);
|
|
144
|
+
util.validatePublish(publish);
|
|
145
145
|
if (typeof version !== "string" || version === "") {
|
|
146
146
|
throw new Error(
|
|
147
147
|
`Expected "options.version" to be a non-empty string when "options.publish" is set. Received: ${JSON.stringify(version)}`,
|
|
@@ -280,15 +280,7 @@ async function appImage({
|
|
|
280
280
|
if (publish !== undefined) {
|
|
281
281
|
await fs.promises.writeFile(
|
|
282
282
|
path.resolve(appImageDir, "app-update.yml"),
|
|
283
|
-
util.
|
|
284
|
-
publish.provider === "github"
|
|
285
|
-
? {
|
|
286
|
-
provider: publish.provider,
|
|
287
|
-
owner: publish.owner,
|
|
288
|
-
repo: publish.repo,
|
|
289
|
-
}
|
|
290
|
-
: { provider: publish.provider, url: publish.url },
|
|
291
|
-
),
|
|
283
|
+
util.appUpdateYaml(publish),
|
|
292
284
|
);
|
|
293
285
|
}
|
|
294
286
|
|
|
@@ -367,18 +359,14 @@ async function appImage({
|
|
|
367
359
|
}
|
|
368
360
|
|
|
369
361
|
if (publish !== undefined) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
sha512: appImageSha512,
|
|
379
|
-
releaseDate: new Date().toISOString(),
|
|
380
|
-
}),
|
|
381
|
-
);
|
|
362
|
+
await util.writeUpdateInfo({
|
|
363
|
+
filePath: appImageFilePath,
|
|
364
|
+
updateInfoPath: path.resolve(
|
|
365
|
+
resolvedOutDir,
|
|
366
|
+
util.updateInfoFileName(resolvedArch, "linux"),
|
|
367
|
+
),
|
|
368
|
+
version: /** @type {string} */ (version),
|
|
369
|
+
});
|
|
382
370
|
}
|
|
383
371
|
|
|
384
372
|
return appImageFilePath;
|
|
@@ -387,71 +375,6 @@ async function appImage({
|
|
|
387
375
|
}
|
|
388
376
|
}
|
|
389
377
|
|
|
390
|
-
/**
|
|
391
|
-
* Throw if `publish` isn't a valid `PublishOptions` object.
|
|
392
|
-
* @param {unknown} publish
|
|
393
|
-
* @returns {asserts publish is PublishOptions}
|
|
394
|
-
*/
|
|
395
|
-
function validatePublish(publish) {
|
|
396
|
-
if (typeof publish !== "object" || publish === null) {
|
|
397
|
-
throw new Error(
|
|
398
|
-
`Expected "options.publish" to be an object. Received: ${JSON.stringify(publish)}`,
|
|
399
|
-
);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const { provider } = /** @type {{provider?: unknown}} */ (publish);
|
|
403
|
-
/** @type {string[]} */
|
|
404
|
-
let requiredKeys;
|
|
405
|
-
if (provider === "github") {
|
|
406
|
-
requiredKeys = ["owner", "repo"];
|
|
407
|
-
} else if (provider === "generic") {
|
|
408
|
-
requiredKeys = ["url"];
|
|
409
|
-
} else {
|
|
410
|
-
throw new Error(
|
|
411
|
-
`Expected "options.publish.provider" to be "github" or "generic". Received: ${JSON.stringify(provider)}`,
|
|
412
|
-
);
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
for (const key of requiredKeys) {
|
|
416
|
-
const value = /** @type {Record<string, unknown>} */ (publish)[key];
|
|
417
|
-
if (typeof value !== "string" || value === "") {
|
|
418
|
-
throw new Error(
|
|
419
|
-
`Expected "options.publish.${key}" to be a non-empty string for provider "${provider}". Received: ${JSON.stringify(value)}`,
|
|
420
|
-
);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
if (provider === "github") {
|
|
425
|
-
/*
|
|
426
|
-
* The characters GitHub allows in owner and repository names. Anything
|
|
427
|
-
* else - eg. "|", the update information's field separator - is rejected.
|
|
428
|
-
*/
|
|
429
|
-
for (const key of requiredKeys) {
|
|
430
|
-
const value = /** @type {Record<string, string>} */ (publish)[key];
|
|
431
|
-
if (/^[A-Za-z0-9._-]+$/.test(value) === false) {
|
|
432
|
-
throw new Error(
|
|
433
|
-
`Expected "options.publish.${key}" to be a GitHub ${key} name (letters, digits, ".", "-" and "_"). Received: ${JSON.stringify(value)}`,
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
if (provider === "generic") {
|
|
440
|
-
const { url } = /** @type {GenericPublishOptions} */ (publish);
|
|
441
|
-
let protocol;
|
|
442
|
-
try {
|
|
443
|
-
protocol = new URL(url).protocol;
|
|
444
|
-
} catch {
|
|
445
|
-
protocol = undefined;
|
|
446
|
-
}
|
|
447
|
-
if (protocol !== "https:" && protocol !== "http:") {
|
|
448
|
-
throw new Error(
|
|
449
|
-
`Expected "options.publish.url" to be an http(s) URL. Received: ${JSON.stringify(url)}`,
|
|
450
|
-
);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
378
|
/**
|
|
456
379
|
* Build the AppImage update information string for `publish`, in the format
|
|
457
380
|
* zsync based AppImage updaters understand.
|
package/src/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import appImage from "./appImage.js";
|
|
2
|
+
import nsis from "./nsis.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Supported packaged output formats.
|
|
5
6
|
*
|
|
6
7
|
* `AppImage`, `deb` and `rpm` package a Linux build. `MSIX` and `NSIS`
|
|
7
|
-
* package a Windows build.
|
|
8
|
-
* others are reserved so this option doesn't need to change shape once
|
|
9
|
-
* land.
|
|
8
|
+
* package a Windows build. `"AppImage"` and `"nsis"` are implemented today -
|
|
9
|
+
* the others are reserved so this option doesn't need to change shape once
|
|
10
|
+
* they land.
|
|
10
11
|
* @typedef {"AppImage" | "deb" | "rpm" | "msix" | "nsis" | "dmg"} PackageFormat
|
|
11
12
|
*/
|
|
12
13
|
|
|
@@ -19,20 +20,21 @@ import appImage from "./appImage.js";
|
|
|
19
20
|
* Package a built NW.js application.
|
|
20
21
|
* @async
|
|
21
22
|
* @function
|
|
22
|
-
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage`) expects.
|
|
23
|
+
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage` or `nsis`) expects.
|
|
23
24
|
* @returns {Promise<string>} Path to the resulting packaged artifact.
|
|
24
25
|
*/
|
|
25
26
|
async function packager({ format, ...options }) {
|
|
26
27
|
switch (format) {
|
|
27
28
|
case "AppImage":
|
|
28
29
|
return appImage(/** @type {any} */ (options));
|
|
30
|
+
case "nsis":
|
|
31
|
+
return nsis(/** @type {any} */ (options));
|
|
29
32
|
case "deb":
|
|
30
33
|
case "rpm":
|
|
31
34
|
case "msix":
|
|
32
|
-
case "nsis":
|
|
33
35
|
case "dmg":
|
|
34
36
|
throw new Error(
|
|
35
|
-
`options.format ${JSON.stringify(format)} is not implemented yet. Currently only "AppImage"
|
|
37
|
+
`options.format ${JSON.stringify(format)} is not implemented yet. Currently only "AppImage" and "nsis" are supported.`,
|
|
36
38
|
);
|
|
37
39
|
default:
|
|
38
40
|
throw new Error(
|