@nwutils/packager 0.6.0 → 0.8.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 +115 -6
- package/package.json +3 -3
- package/src/appImage.js +10 -87
- package/src/dmg.js +277 -0
- package/src/index.js +8 -6
- package/src/nsis.js +105 -23
- package/src/util.js +127 -8
- package/types/dmg.d.ts +80 -0
- package/types/index.d.ts +7 -7
- package/types/nsis.d.ts +12 -2
- package/types/util.d.ts +41 -7
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ Supported output formats, selected via `format`:
|
|
|
13
13
|
| `rpm` | Linux | Planned |
|
|
14
14
|
| `msix` | Windows | Planned |
|
|
15
15
|
| `nsis` | Windows | Implemented |
|
|
16
|
-
| `dmg` | MacOS |
|
|
16
|
+
| `dmg` | MacOS | Implemented |
|
|
17
17
|
|
|
18
18
|
## Getting Started
|
|
19
19
|
|
|
@@ -65,7 +65,7 @@ keyring. If the key is protected by a passphrase, set it in the
|
|
|
65
65
|
### Self updating AppImages
|
|
66
66
|
|
|
67
67
|
Set `publish` to where releases are uploaded, and `version` to the app's
|
|
68
|
-
version.
|
|
68
|
+
version.
|
|
69
69
|
|
|
70
70
|
- an `app-update.yml` describing `publish` is embedded next to the NW.js
|
|
71
71
|
executable inside the AppImage, so the running app knows where to look
|
|
@@ -143,14 +143,102 @@ It needs the NSIS compiler, `makensis`, installed on the host. It runs on
|
|
|
143
143
|
Linux (`apt install nsis`) and MacOS (`brew install makensis`) too, so a Windows
|
|
144
144
|
installer can be built from any platform. On Windows, `choco install nsis`.
|
|
145
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
|
+
|
|
177
|
+
### DMG
|
|
178
|
+
|
|
179
|
+
Build the application with `platform: "osx"` first, then, on a MacOS host:
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
const dmgPath = await packager({
|
|
183
|
+
format: "dmg",
|
|
184
|
+
appDir: "./out",
|
|
185
|
+
appName: "Demo",
|
|
186
|
+
arch: "arm64",
|
|
187
|
+
outDir: "./dist",
|
|
188
|
+
signIdentity: "Developer ID Application: ACME (TEAMID)",
|
|
189
|
+
});
|
|
190
|
+
// ./dist/Demo-arm64.dmg
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The DMG holds the app next to a link to `/Applications`, so users install it by
|
|
194
|
+
dragging the app onto the link. It is made with `hdiutil`, and the app is signed
|
|
195
|
+
with `codesign` when `signIdentity` is set (`"-"` signs ad-hoc). Both are part
|
|
196
|
+
of MacOS, so DMGs are built on a MacOS host. Notarize the DMG afterwards, eg.
|
|
197
|
+
with `xcrun notarytool submit --wait` and `xcrun stapler staple`, so Gatekeeper
|
|
198
|
+
lets users open it.
|
|
199
|
+
|
|
200
|
+
### Self updating DMGs
|
|
201
|
+
|
|
202
|
+
Set `publish` and `version`. Updates don't use the DMG:
|
|
203
|
+
|
|
204
|
+
- an `app-update.yml` describing `publish` is added to the app's
|
|
205
|
+
`Contents/Resources`
|
|
206
|
+
- a `<appName>-<arch>-mac.zip` of the app, made with `ditto` so permissions,
|
|
207
|
+
symbolic links and the signature survive, is written next to the DMG
|
|
208
|
+
- a `latest-mac.yml` update info file (`latest-mac-<arch>.yml` for
|
|
209
|
+
architectures other than `x64`) describing the zip is written next to it
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
await packager({
|
|
213
|
+
format: "dmg",
|
|
214
|
+
appDir: "./out",
|
|
215
|
+
appName: "Demo",
|
|
216
|
+
arch: "arm64",
|
|
217
|
+
outDir: "./dist",
|
|
218
|
+
version: "1.2.0",
|
|
219
|
+
signIdentity: "Developer ID Application: ACME (TEAMID)",
|
|
220
|
+
publish: { provider: "github", owner: "nwutils", repo: "demo" },
|
|
221
|
+
});
|
|
222
|
+
// ./dist/Demo-arm64.dmg
|
|
223
|
+
// ./dist/Demo-arm64-mac.zip
|
|
224
|
+
// ./dist/latest-mac-arm64.yml
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Upload all three to every release.
|
|
228
|
+
[`@nwutils/updater`](https://www.npmjs.com/package/@nwutils/updater)'s
|
|
229
|
+
`MacUpdater` downloads the zip, checks it, swaps it in for the running app and
|
|
230
|
+
restarts it. Adding `app-update.yml` changes the app, so sign it here with
|
|
231
|
+
`signIdentity` rather than before packaging. A signed app only accepts updates
|
|
232
|
+
signed by the same team.
|
|
233
|
+
|
|
146
234
|
## API Reference
|
|
147
235
|
|
|
148
236
|
### `packager({ format, ...options })`
|
|
149
237
|
|
|
150
|
-
| Name | Type | Description
|
|
151
|
-
| ------ | ----------------------------------------------------------- |
|
|
152
|
-
| format | `"AppImage" \| "deb" \| "rpm" \| "msix" \| "nsis" \| "dmg"` | Which packager to run. `"AppImage"` and `"
|
|
153
|
-
| ... | - | The remaining options are passed through to the packager for `format` as-is.
|
|
238
|
+
| Name | Type | Description |
|
|
239
|
+
| ------ | ----------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
|
240
|
+
| format | `"AppImage" \| "deb" \| "rpm" \| "msix" \| "nsis" \| "dmg"` | Which packager to run. `"AppImage"`, `"nsis"` and `"dmg"` are implemented today. |
|
|
241
|
+
| ... | - | The remaining options are passed through to the packager for `format` as-is. |
|
|
154
242
|
|
|
155
243
|
Resolves with the path to the resulting packaged artifact.
|
|
156
244
|
|
|
@@ -195,9 +283,30 @@ Options
|
|
|
195
283
|
| desktopShortcut | `boolean` | If true, create a desktop shortcut. Defaults to `true` |
|
|
196
284
|
| startMenuShortcut | `boolean` | If true, create a Start Menu shortcut. Defaults to `true` |
|
|
197
285
|
| makensisPath | `string` | Path to the NSIS compiler. Defaults to `makensis` on the `PATH` |
|
|
286
|
+
| publish | `PublishOptions` | Where releases are published. Installs `app-update.yml` and writes `latest[-<arch>].yml` to `outDir` |
|
|
198
287
|
|
|
199
288
|
Resolves with the path to the resulting installer `.exe` file.
|
|
200
289
|
|
|
290
|
+
### `dmg(options)`
|
|
291
|
+
|
|
292
|
+
Options
|
|
293
|
+
|
|
294
|
+
| Name | Type | Description |
|
|
295
|
+
| ------------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
|
|
296
|
+
| appDir | `string` | Path to a built NW.js MacOS application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "osx"` |
|
|
297
|
+
| appName | `string` | Name of the application. Must match the `app.name` value used to build `appDir`: the bundle is expected at `<appDir>/<appName>.app` |
|
|
298
|
+
| arch | `"x64" \| "arm64"` | Architecture `appDir` was built for. Defaults to the host architecture |
|
|
299
|
+
| outDir | `string` | Directory the resulting `<appName>-<arch>.dmg` file is written to. Defaults to the parent directory of `appDir` |
|
|
300
|
+
| volumeName | `string` | Name of the volume the DMG mounts as. Defaults to `appName` |
|
|
301
|
+
| signIdentity | `string` | Code signing identity to sign the app with, or `"-"` to sign ad-hoc. Not signed by default |
|
|
302
|
+
| version | `string` | Version of the application. Required with `publish` |
|
|
303
|
+
| publish | `PublishOptions` | Where releases are published. Adds `app-update.yml` and writes `<appName>-<arch>-mac.zip` and `latest-mac[-<arch>].yml` to `outDir` |
|
|
304
|
+
| hdiutilPath | `string` | Path to `hdiutil`. Defaults to `hdiutil` on the `PATH` |
|
|
305
|
+
| dittoPath | `string` | Path to `ditto`. Defaults to `ditto` on the `PATH` |
|
|
306
|
+
| codesignPath | `string` | Path to `codesign`. Defaults to `codesign` on the `PATH` |
|
|
307
|
+
|
|
308
|
+
Resolves with the path to the resulting `.dmg` file.
|
|
309
|
+
|
|
201
310
|
## Contributing
|
|
202
311
|
|
|
203
312
|
### External contributor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwutils/packager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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/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\""
|
|
46
|
+
"test": "node --test --test-concurrency=1 \"../../tests/specs/packager/appImage.test.js\" \"../../tests/specs/packager/dmg.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/dmg.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/dmg.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import child_process from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
import util from "./util.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} DmgOptions
|
|
11
|
+
* @property {string} appDir Path to a built NW.js MacOS application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "osx"`.
|
|
12
|
+
* @property {string} appName Name of the application. Must match the `app.name` value used to build `appDir` - the bundle is expected at `<appDir>/<appName>.app`.
|
|
13
|
+
* @property {"x64" | "arm64"} [arch] Architecture `appDir` was built for. Defaults to the host architecture.
|
|
14
|
+
* @property {string} [outDir] Directory the resulting `<appName>-<arch>.dmg` file is written to. Defaults to the parent directory of `appDir`.
|
|
15
|
+
* @property {string} [volumeName] Name of the volume the DMG mounts as. Defaults to `appName`.
|
|
16
|
+
* @property {string} [signIdentity] Code signing identity, eg. `"Developer ID Application: ACME (TEAMID)"`, or `"-"` to sign ad-hoc. When set, the app is signed with `codesign` before it is put in the DMG. Sign here rather than before packaging when `publish` is set, since adding `app-update.yml` changes the bundle.
|
|
17
|
+
* @property {string} [version] Version of the application, eg. `"1.2.0"`. Required when `publish` is set: it is written to the update info file `@nwutils/updater` compares against.
|
|
18
|
+
* @property {import("./appImage.js").PublishOptions} [publish] Where releases are published. When set, an `app-update.yml` pointing at it is added to the app's `Contents/Resources`, and a `<appName>-<arch>-mac.zip` update archive and a `latest-mac[-<arch>].yml` update info file are written next to the DMG, for `@nwutils/updater` to self update from.
|
|
19
|
+
* @property {string} [hdiutilPath="hdiutil"] Path to `hdiutil`, which creates the DMG.
|
|
20
|
+
* @property {string} [dittoPath="ditto"] Path to `ditto`, which creates the update archive.
|
|
21
|
+
* @property {string} [codesignPath="codesign"] Path to `codesign`, which signs the app.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Package a built NW.js MacOS application as a DMG disk image.
|
|
26
|
+
*
|
|
27
|
+
* The DMG holds the app next to a link to `/Applications`, so users install it
|
|
28
|
+
* by dragging the app onto the link. Updates don't use the DMG: with
|
|
29
|
+
* `publish` set, a zip of the app is written next to it, which
|
|
30
|
+
* `@nwutils/updater`'s `MacUpdater` downloads and swaps in for the running
|
|
31
|
+
* app.
|
|
32
|
+
*
|
|
33
|
+
* This composes with `@nwutils/builder`: run it against the `outDir` (and
|
|
34
|
+
* `app.name`) that `@nwutils/builder` produced for `platform: "osx"`.
|
|
35
|
+
* `hdiutil`, `ditto` and `codesign` are part of MacOS, so this runs on a MacOS
|
|
36
|
+
* host.
|
|
37
|
+
* @async
|
|
38
|
+
* @function
|
|
39
|
+
* @param {DmgOptions} options
|
|
40
|
+
* @returns {Promise<string>} Path to the resulting `.dmg` file.
|
|
41
|
+
*/
|
|
42
|
+
async function dmg({
|
|
43
|
+
appDir,
|
|
44
|
+
appName,
|
|
45
|
+
arch,
|
|
46
|
+
outDir,
|
|
47
|
+
volumeName,
|
|
48
|
+
signIdentity,
|
|
49
|
+
version,
|
|
50
|
+
publish,
|
|
51
|
+
hdiutilPath = "hdiutil",
|
|
52
|
+
dittoPath = "ditto",
|
|
53
|
+
codesignPath = "codesign",
|
|
54
|
+
}) {
|
|
55
|
+
if (typeof appDir !== "string" || appDir === "") {
|
|
56
|
+
throw new Error(
|
|
57
|
+
`Expected "options.appDir" to be a non-empty string. Received: ${JSON.stringify(appDir)}`,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ((await util.fileExists(appDir)) === false) {
|
|
62
|
+
throw new Error(`"options.appDir" does not exist: ${appDir}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* `appName` names the bundle and the files written to `outDir`. */
|
|
66
|
+
if (
|
|
67
|
+
typeof appName !== "string" ||
|
|
68
|
+
appName === "" ||
|
|
69
|
+
appName.includes("/") ||
|
|
70
|
+
appName.startsWith(".") ||
|
|
71
|
+
hasControlCharacter(appName)
|
|
72
|
+
) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`Expected "options.appName" to be a non-empty file name without "/" or control characters, not starting with ".". Received: ${JSON.stringify(appName)}`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const resolvedArch =
|
|
79
|
+
/** @type {"x64" | "arm64" | undefined} */ (arch) ??
|
|
80
|
+
/** @type {"x64" | "arm64"} */ (/** @type {unknown} */ (process.arch));
|
|
81
|
+
if (resolvedArch !== "x64" && resolvedArch !== "arm64") {
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Expected "options.arch" to be "x64" or "arm64". Received: ${JSON.stringify(arch ?? process.arch)}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const resolvedVolumeName = volumeName ?? appName;
|
|
88
|
+
if (
|
|
89
|
+
typeof resolvedVolumeName !== "string" ||
|
|
90
|
+
resolvedVolumeName === "" ||
|
|
91
|
+
hasControlCharacter(resolvedVolumeName)
|
|
92
|
+
) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Expected "options.volumeName" to be a non-empty string without control characters. Received: ${JSON.stringify(volumeName)}`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (
|
|
99
|
+
signIdentity !== undefined &&
|
|
100
|
+
(typeof signIdentity !== "string" || signIdentity === "")
|
|
101
|
+
) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Expected "options.signIdentity" to be a non-empty string. Received: ${JSON.stringify(signIdentity)}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (publish !== undefined) {
|
|
108
|
+
util.validatePublish(publish);
|
|
109
|
+
if (typeof version !== "string" || version === "") {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Expected "options.version" to be a non-empty string when "options.publish" is set. Received: ${JSON.stringify(version)}`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const [key, value] of Object.entries({
|
|
117
|
+
hdiutilPath,
|
|
118
|
+
dittoPath,
|
|
119
|
+
codesignPath,
|
|
120
|
+
})) {
|
|
121
|
+
if (typeof value !== "string" || value === "") {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Expected "options.${key}" to be a non-empty string. Received: ${JSON.stringify(value)}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const resolvedAppDir = path.resolve(appDir);
|
|
129
|
+
const appBundleName = `${appName}.app`;
|
|
130
|
+
const appBundlePath = path.resolve(resolvedAppDir, appBundleName);
|
|
131
|
+
if ((await util.fileExists(appBundlePath)) === false) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Expected the app bundle to exist at ${appBundlePath}. Does "options.appName" match the "app.name" used to build "options.appDir" for "platform: \\"osx\\""?`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const resolvedOutDir = path.resolve(outDir ?? path.dirname(resolvedAppDir));
|
|
138
|
+
await fs.promises.mkdir(resolvedOutDir, { recursive: true });
|
|
139
|
+
const dmgPath = path.resolve(
|
|
140
|
+
resolvedOutDir,
|
|
141
|
+
`${appName}-${resolvedArch}.dmg`,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const stagingDir = await fs.promises.mkdtemp(
|
|
145
|
+
path.join(os.tmpdir(), "nwutils-packager-dmg-"),
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
/* NW.js' frameworks are full of symbolic links, which must stay links for the signature to hold. */
|
|
150
|
+
const stagedAppPath = path.resolve(stagingDir, appBundleName);
|
|
151
|
+
await fs.promises.cp(appBundlePath, stagedAppPath, {
|
|
152
|
+
recursive: true,
|
|
153
|
+
verbatimSymlinks: true,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
* `Contents/Resources` is where `@nwutils/updater` looks for it.
|
|
158
|
+
* It's inside the signed bundle, so
|
|
159
|
+
* signing comes after.
|
|
160
|
+
*/
|
|
161
|
+
if (publish !== undefined) {
|
|
162
|
+
await fs.promises.writeFile(
|
|
163
|
+
path.resolve(stagedAppPath, "Contents", "Resources", "app-update.yml"),
|
|
164
|
+
util.appUpdateYaml(publish),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (signIdentity !== undefined) {
|
|
169
|
+
run(
|
|
170
|
+
codesignPath,
|
|
171
|
+
[
|
|
172
|
+
"--force",
|
|
173
|
+
"--deep",
|
|
174
|
+
"--sign",
|
|
175
|
+
signIdentity,
|
|
176
|
+
/* Notarization requires the hardened runtime and a secure timestamp, which ad-hoc signatures can't have. */
|
|
177
|
+
...(signIdentity === "-"
|
|
178
|
+
? []
|
|
179
|
+
: ["--options", "runtime", "--timestamp"]),
|
|
180
|
+
stagedAppPath,
|
|
181
|
+
],
|
|
182
|
+
"codesign",
|
|
183
|
+
"codesignPath",
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/* Lets users install by dragging the app onto it. */
|
|
188
|
+
await fs.promises.symlink(
|
|
189
|
+
"/Applications",
|
|
190
|
+
path.resolve(stagingDir, "Applications"),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
run(
|
|
194
|
+
hdiutilPath,
|
|
195
|
+
[
|
|
196
|
+
"create",
|
|
197
|
+
"-volname",
|
|
198
|
+
resolvedVolumeName,
|
|
199
|
+
"-srcfolder",
|
|
200
|
+
stagingDir,
|
|
201
|
+
"-fs",
|
|
202
|
+
"HFS+",
|
|
203
|
+
"-format",
|
|
204
|
+
"UDZO",
|
|
205
|
+
"-ov",
|
|
206
|
+
dmgPath,
|
|
207
|
+
],
|
|
208
|
+
"hdiutil",
|
|
209
|
+
"hdiutilPath",
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
if (publish !== undefined) {
|
|
213
|
+
/*
|
|
214
|
+
* `ditto` keeps the permissions, symbolic links and extended attributes
|
|
215
|
+
* a signed app bundle needs, unlike most zip tools. `--keepParent` puts
|
|
216
|
+
* `<appName>.app` itself at the archive's root.
|
|
217
|
+
*/
|
|
218
|
+
const zipPath = path.resolve(
|
|
219
|
+
resolvedOutDir,
|
|
220
|
+
`${appName}-${resolvedArch}-mac.zip`,
|
|
221
|
+
);
|
|
222
|
+
run(
|
|
223
|
+
dittoPath,
|
|
224
|
+
["-c", "-k", "--sequesterRsrc", "--keepParent", stagedAppPath, zipPath],
|
|
225
|
+
"ditto",
|
|
226
|
+
"dittoPath",
|
|
227
|
+
);
|
|
228
|
+
await util.writeUpdateInfo({
|
|
229
|
+
filePath: zipPath,
|
|
230
|
+
updateInfoPath: path.resolve(
|
|
231
|
+
resolvedOutDir,
|
|
232
|
+
util.updateInfoFileName(resolvedArch, "osx"),
|
|
233
|
+
),
|
|
234
|
+
version: /** @type {string} */ (version),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return dmgPath;
|
|
239
|
+
} finally {
|
|
240
|
+
await fs.promises.rm(stagingDir, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* @param {string} value
|
|
246
|
+
* @returns {boolean}
|
|
247
|
+
*/
|
|
248
|
+
function hasControlCharacter(value) {
|
|
249
|
+
return [...value].some((character) => {
|
|
250
|
+
const code = character.charCodeAt(0);
|
|
251
|
+
return code < 0x20 || code === 0x7f;
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Run a MacOS tool, turning a missing tool into an error that says so.
|
|
257
|
+
* @param {string} toolPath Path or name of the tool.
|
|
258
|
+
* @param {string[]} args
|
|
259
|
+
* @param {string} toolName eg. `"hdiutil"`.
|
|
260
|
+
* @param {string} optionName Option that overrides `toolPath`, eg. `"hdiutilPath"`.
|
|
261
|
+
* @returns {void}
|
|
262
|
+
*/
|
|
263
|
+
function run(toolPath, args, toolName, optionName) {
|
|
264
|
+
try {
|
|
265
|
+
child_process.execFileSync(toolPath, args, { stdio: "inherit" });
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ENOENT") {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Could not find ${JSON.stringify(toolPath)}. "${toolName}" is part of MacOS, so DMGs are built on a MacOS host. Otherwise pass its path as "options.${optionName}".`,
|
|
270
|
+
{ cause: error },
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export default dmg;
|
package/src/index.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import appImage from "./appImage.js";
|
|
2
|
+
import dmg from "./dmg.js";
|
|
2
3
|
import nsis from "./nsis.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Supported packaged output formats.
|
|
6
7
|
*
|
|
7
8
|
* `AppImage`, `deb` and `rpm` package a Linux build. `MSIX` and `NSIS`
|
|
8
|
-
* package a Windows build. `
|
|
9
|
-
*
|
|
10
|
-
* they land.
|
|
9
|
+
* package a Windows build. `dmg` packages a MacOS build. `"AppImage"`,
|
|
10
|
+
* `"nsis"` and `"dmg"` are implemented today - the others are reserved so
|
|
11
|
+
* this option doesn't need to change shape once they land.
|
|
11
12
|
* @typedef {"AppImage" | "deb" | "rpm" | "msix" | "nsis" | "dmg"} PackageFormat
|
|
12
13
|
*/
|
|
13
14
|
|
|
@@ -20,7 +21,7 @@ import nsis from "./nsis.js";
|
|
|
20
21
|
* Package a built NW.js application.
|
|
21
22
|
* @async
|
|
22
23
|
* @function
|
|
23
|
-
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage` or `
|
|
24
|
+
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage`, `nsis` or `dmg`) expects.
|
|
24
25
|
* @returns {Promise<string>} Path to the resulting packaged artifact.
|
|
25
26
|
*/
|
|
26
27
|
async function packager({ format, ...options }) {
|
|
@@ -29,12 +30,13 @@ async function packager({ format, ...options }) {
|
|
|
29
30
|
return appImage(/** @type {any} */ (options));
|
|
30
31
|
case "nsis":
|
|
31
32
|
return nsis(/** @type {any} */ (options));
|
|
33
|
+
case "dmg":
|
|
34
|
+
return dmg(/** @type {any} */ (options));
|
|
32
35
|
case "deb":
|
|
33
36
|
case "rpm":
|
|
34
37
|
case "msix":
|
|
35
|
-
case "dmg":
|
|
36
38
|
throw new Error(
|
|
37
|
-
`options.format ${JSON.stringify(format)} is not implemented yet. Currently only "AppImage" and "
|
|
39
|
+
`options.format ${JSON.stringify(format)} is not implemented yet. Currently only "AppImage", "nsis" and "dmg" are supported.`,
|
|
38
40
|
);
|
|
39
41
|
default:
|
|
40
42
|
throw new Error(
|
package/src/nsis.js
CHANGED
|
@@ -21,6 +21,7 @@ import util from "./util.js";
|
|
|
21
21
|
* @property {boolean} [desktopShortcut=true] If true, create a desktop shortcut.
|
|
22
22
|
* @property {boolean} [startMenuShortcut=true] If true, create a Start Menu shortcut.
|
|
23
23
|
* @property {string} [makensisPath="makensis"] Path to the NSIS compiler. Defaults to `makensis` on the `PATH`.
|
|
24
|
+
* @property {import("./appImage.js").PublishOptions} [publish] Where releases are published. When set, an `app-update.yml` pointing at it is installed next to the executable and a `latest[-<arch>].yml` update info file is written next to the installer, for `@nwutils/updater` to self update from.
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -46,6 +47,12 @@ const ARCH_CHECK_KV = {
|
|
|
46
47
|
arm64: [["${IsNativeARM64}"]],
|
|
47
48
|
};
|
|
48
49
|
|
|
50
|
+
/**
|
|
51
|
+
* How long an update waits for the running application to exit.
|
|
52
|
+
* @type {number}
|
|
53
|
+
*/
|
|
54
|
+
const UPDATE_WAIT_SECONDS = 60;
|
|
55
|
+
|
|
49
56
|
/**
|
|
50
57
|
* Package a built NW.js Windows application as an NSIS installer.
|
|
51
58
|
*
|
|
@@ -55,6 +62,10 @@ const ARCH_CHECK_KV = {
|
|
|
55
62
|
* installing a new version first uninstalls the previous one. Both wait for
|
|
56
63
|
* the application to be closed before touching its files.
|
|
57
64
|
*
|
|
65
|
+
* Run with `/S /UPDATE`, as `@nwutils/updater` does, the installer instead
|
|
66
|
+
* waits for the running application to exit, updates it silently and starts
|
|
67
|
+
* the new version.
|
|
68
|
+
*
|
|
58
69
|
* This composes with `@nwutils/builder`: run it against the `outDir` (and
|
|
59
70
|
* `app.name`) that `@nwutils/builder` produced for `platform: "win"`.
|
|
60
71
|
* `makensis` runs on Linux and macOS too, so the installer can be built on
|
|
@@ -78,6 +89,7 @@ async function nsis({
|
|
|
78
89
|
desktopShortcut = true,
|
|
79
90
|
startMenuShortcut = true,
|
|
80
91
|
makensisPath = "makensis",
|
|
92
|
+
publish,
|
|
81
93
|
}) {
|
|
82
94
|
if (typeof appDir !== "string" || appDir === "") {
|
|
83
95
|
throw new Error(
|
|
@@ -169,6 +181,10 @@ async function nsis({
|
|
|
169
181
|
);
|
|
170
182
|
}
|
|
171
183
|
|
|
184
|
+
if (publish !== undefined) {
|
|
185
|
+
util.validatePublish(publish);
|
|
186
|
+
}
|
|
187
|
+
|
|
172
188
|
const resolvedIconPath =
|
|
173
189
|
icon === undefined
|
|
174
190
|
? undefined
|
|
@@ -193,28 +209,45 @@ async function nsis({
|
|
|
193
209
|
`${appName}-${resolvedArch}-Setup.exe`,
|
|
194
210
|
);
|
|
195
211
|
|
|
196
|
-
const
|
|
197
|
-
appDir: resolvedAppDir,
|
|
198
|
-
appFiles: await listAppFiles(resolvedAppDir),
|
|
199
|
-
appName,
|
|
200
|
-
appId: resolvedAppId,
|
|
201
|
-
version,
|
|
202
|
-
fileVersion,
|
|
203
|
-
arch: resolvedArch,
|
|
204
|
-
installerPath,
|
|
205
|
-
iconPath: resolvedIconPath,
|
|
206
|
-
licensePath: resolvedLicensePath,
|
|
207
|
-
publisher,
|
|
208
|
-
copyright,
|
|
209
|
-
desktopShortcut,
|
|
210
|
-
startMenuShortcut,
|
|
211
|
-
});
|
|
212
|
+
const appFiles = await listAppFiles(resolvedAppDir);
|
|
212
213
|
|
|
213
214
|
const scriptDir = await fs.promises.mkdtemp(
|
|
214
215
|
path.join(os.tmpdir(), "nwutils-packager-nsis-"),
|
|
215
216
|
);
|
|
216
217
|
|
|
217
218
|
try {
|
|
219
|
+
/*
|
|
220
|
+
* Installed next to the NW.js executable, ie. at
|
|
221
|
+
* `path.dirname(process.execPath)` at runtime, which is where
|
|
222
|
+
* `@nwutils/updater` looks for it.
|
|
223
|
+
*/
|
|
224
|
+
let appUpdateYamlPath;
|
|
225
|
+
if (publish !== undefined) {
|
|
226
|
+
appUpdateYamlPath = path.resolve(scriptDir, "app-update.yml");
|
|
227
|
+
await fs.promises.writeFile(
|
|
228
|
+
appUpdateYamlPath,
|
|
229
|
+
util.appUpdateYaml(publish),
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const script = nsisScript({
|
|
234
|
+
appDir: resolvedAppDir,
|
|
235
|
+
appFiles,
|
|
236
|
+
appName,
|
|
237
|
+
appId: resolvedAppId,
|
|
238
|
+
version,
|
|
239
|
+
fileVersion,
|
|
240
|
+
arch: resolvedArch,
|
|
241
|
+
installerPath,
|
|
242
|
+
iconPath: resolvedIconPath,
|
|
243
|
+
licensePath: resolvedLicensePath,
|
|
244
|
+
publisher,
|
|
245
|
+
copyright,
|
|
246
|
+
desktopShortcut,
|
|
247
|
+
startMenuShortcut,
|
|
248
|
+
appUpdateYamlPath,
|
|
249
|
+
});
|
|
250
|
+
|
|
218
251
|
const scriptPath = path.resolve(scriptDir, "installer.nsi");
|
|
219
252
|
await fs.promises.writeFile(scriptPath, script, "utf-8");
|
|
220
253
|
|
|
@@ -234,6 +267,17 @@ async function nsis({
|
|
|
234
267
|
throw error;
|
|
235
268
|
}
|
|
236
269
|
|
|
270
|
+
if (publish !== undefined) {
|
|
271
|
+
await util.writeUpdateInfo({
|
|
272
|
+
filePath: installerPath,
|
|
273
|
+
updateInfoPath: path.resolve(
|
|
274
|
+
resolvedOutDir,
|
|
275
|
+
util.updateInfoFileName(resolvedArch, "win"),
|
|
276
|
+
),
|
|
277
|
+
version,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
|
|
237
281
|
return installerPath;
|
|
238
282
|
} finally {
|
|
239
283
|
await fs.promises.rm(scriptDir, { recursive: true, force: true });
|
|
@@ -325,6 +369,7 @@ async function listAppFiles(appDir) {
|
|
|
325
369
|
* @param {string} options.copyright
|
|
326
370
|
* @param {boolean} options.desktopShortcut
|
|
327
371
|
* @param {boolean} options.startMenuShortcut
|
|
372
|
+
* @param {string} [options.appUpdateYamlPath] Absolute path to an `app-update.yml` file to install next to the executable.
|
|
328
373
|
* @returns {string}
|
|
329
374
|
*/
|
|
330
375
|
function nsisScript({
|
|
@@ -342,6 +387,7 @@ function nsisScript({
|
|
|
342
387
|
copyright,
|
|
343
388
|
desktopShortcut,
|
|
344
389
|
startMenuShortcut,
|
|
390
|
+
appUpdateYamlPath,
|
|
345
391
|
}) {
|
|
346
392
|
const e = util.nsisEscape;
|
|
347
393
|
const p = util.nsisPath;
|
|
@@ -363,6 +409,10 @@ function nsisScript({
|
|
|
363
409
|
'!include "LogicLib.nsh"',
|
|
364
410
|
'!include "x64.nsh"',
|
|
365
411
|
'!include "WinVer.nsh"',
|
|
412
|
+
'!include "FileFunc.nsh"',
|
|
413
|
+
"",
|
|
414
|
+
/* "1" when run with `/UPDATE`, ie. by `@nwutils/updater`. */
|
|
415
|
+
"Var IsUpdate",
|
|
366
416
|
"",
|
|
367
417
|
`Name "${e(appName)}"`,
|
|
368
418
|
`OutFile "${p(installerPath)}"`,
|
|
@@ -405,7 +455,17 @@ function nsisScript({
|
|
|
405
455
|
if (publisher !== undefined) {
|
|
406
456
|
lines.push(`VIAddVersionKey "CompanyName" "${e(publisher)}"`);
|
|
407
457
|
}
|
|
408
|
-
lines.push(
|
|
458
|
+
lines.push(
|
|
459
|
+
"",
|
|
460
|
+
"Function .onInit",
|
|
461
|
+
" SetShellVarContext current",
|
|
462
|
+
" ${GetParameters} $R0",
|
|
463
|
+
" ClearErrors",
|
|
464
|
+
' ${GetOptions} $R0 "/UPDATE" $R1',
|
|
465
|
+
" ${IfNot} ${Errors}",
|
|
466
|
+
' StrCpy $IsUpdate "1"',
|
|
467
|
+
" ${EndIf}",
|
|
468
|
+
);
|
|
409
469
|
if (archChecks.length > 0) {
|
|
410
470
|
/* Passing branches are left empty: only the final `${Else}` does anything. */
|
|
411
471
|
lines.push(
|
|
@@ -471,11 +531,11 @@ function nsisScript({
|
|
|
471
531
|
}
|
|
472
532
|
}
|
|
473
533
|
|
|
474
|
-
lines.push(
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
);
|
|
534
|
+
lines.push(' SetOutPath "$INSTDIR"');
|
|
535
|
+
if (appUpdateYamlPath !== undefined) {
|
|
536
|
+
lines.push(` File "/oname=app-update.yml" "${p(appUpdateYamlPath)}"`);
|
|
537
|
+
}
|
|
538
|
+
lines.push(' WriteUninstaller "$INSTDIR\\Uninstall.exe"', "");
|
|
479
539
|
if (startMenuShortcut) {
|
|
480
540
|
lines.push(` CreateShortcut "$SMPROGRAMS\\${e(appName)}.lnk" ${exe}`);
|
|
481
541
|
}
|
|
@@ -500,6 +560,11 @@ function nsisScript({
|
|
|
500
560
|
);
|
|
501
561
|
}
|
|
502
562
|
lines.push(
|
|
563
|
+
"",
|
|
564
|
+
/* An update runs silently, so the finish page's "Run" checkbox never shows. */
|
|
565
|
+
' ${If} $IsUpdate == "1"',
|
|
566
|
+
` Exec '${exe}'`,
|
|
567
|
+
" ${EndIf}",
|
|
503
568
|
"SectionEnd",
|
|
504
569
|
"",
|
|
505
570
|
"Function un.onInit",
|
|
@@ -516,6 +581,9 @@ function nsisScript({
|
|
|
516
581
|
for (const dir of [...appFiles.dirs].reverse()) {
|
|
517
582
|
lines.push(` RMDir ${installed(dir)}`);
|
|
518
583
|
}
|
|
584
|
+
if (appUpdateYamlPath !== undefined) {
|
|
585
|
+
lines.push(' Delete "$INSTDIR\\app-update.yml"');
|
|
586
|
+
}
|
|
519
587
|
lines.push(
|
|
520
588
|
' Delete "$INSTDIR\\Uninstall.exe"',
|
|
521
589
|
' RMDir "$INSTDIR"',
|
|
@@ -534,7 +602,12 @@ function nsisScript({
|
|
|
534
602
|
* the user to close it, so its files can be replaced or deleted. Windows
|
|
535
603
|
* refuses to open a running executable for writing, which is how a running
|
|
536
604
|
* copy is detected. Cancelling - which silent mode does straight away -
|
|
537
|
-
* aborts the installer or uninstaller.
|
|
605
|
+
* aborts the installer or uninstaller.
|
|
606
|
+
*
|
|
607
|
+
* During an update (`$IsUpdate` is "1") nobody is there to answer the prompt:
|
|
608
|
+
* `@nwutils/updater` starts the installer and then quits the application, so
|
|
609
|
+
* it waits for up to `UPDATE_WAIT_SECONDS` instead, then aborts. Uses `$2`
|
|
610
|
+
* and `$3`.
|
|
538
611
|
* @param {string} exePath Quoted NSIS string with the executable's path, eg. `"$INSTDIR\Demo.exe"`.
|
|
539
612
|
* @param {string} appName Application name, shown in the prompt.
|
|
540
613
|
* @param {string} indent Indentation of the generated lines.
|
|
@@ -542,6 +615,7 @@ function nsisScript({
|
|
|
542
615
|
*/
|
|
543
616
|
function waitForAppToClose(exePath, appName, indent) {
|
|
544
617
|
return [
|
|
618
|
+
"StrCpy $3 0",
|
|
545
619
|
"${Do}",
|
|
546
620
|
` \${IfNot} \${FileExists} ${exePath}`,
|
|
547
621
|
" ${Break}",
|
|
@@ -552,6 +626,14 @@ function waitForAppToClose(exePath, appName, indent) {
|
|
|
552
626
|
" FileClose $2",
|
|
553
627
|
" ${Break}",
|
|
554
628
|
" ${EndIf}",
|
|
629
|
+
' ${If} $IsUpdate == "1"',
|
|
630
|
+
" IntOp $3 $3 + 1",
|
|
631
|
+
` \${If} $3 > ${UPDATE_WAIT_SECONDS * 2}`,
|
|
632
|
+
" Abort",
|
|
633
|
+
" ${EndIf}",
|
|
634
|
+
" Sleep 500",
|
|
635
|
+
" ${Continue}",
|
|
636
|
+
" ${EndIf}",
|
|
555
637
|
` \${If} \${Cmd} \`MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "Close ${util.nsisEscape(appName)}, then select Retry." /SD IDCANCEL IDCANCEL\``,
|
|
556
638
|
" Abort",
|
|
557
639
|
" ${EndIf}",
|
package/src/util.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Map nw-builder's architecture identifiers to the identifiers used in
|
|
@@ -114,14 +115,22 @@ function desktopExecArg(value) {
|
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
/**
|
|
117
|
-
* Name of the update info file `@nwutils/updater` downloads
|
|
118
|
-
*
|
|
119
|
-
* `latest-linux.yml
|
|
120
|
-
*
|
|
118
|
+
* Name of the update info file `@nwutils/updater` downloads for `arch` on
|
|
119
|
+
* `platform`, gets the bare
|
|
120
|
+
* `latest-linux.yml` (Linux), `latest.yml` (Windows) or `latest-mac.yml`
|
|
121
|
+
* (MacOS), every other architecture gets an `-<arch>` suffix.
|
|
122
|
+
* @param {string} arch
|
|
123
|
+
* @param {"linux" | "win" | "osx"} [platform="linux"]
|
|
121
124
|
* @returns {string}
|
|
122
125
|
*/
|
|
123
|
-
function updateInfoFileName(arch) {
|
|
124
|
-
|
|
126
|
+
function updateInfoFileName(arch, platform = "linux") {
|
|
127
|
+
const base =
|
|
128
|
+
platform === "win"
|
|
129
|
+
? "latest"
|
|
130
|
+
: platform === "osx"
|
|
131
|
+
? "latest-mac"
|
|
132
|
+
: "latest-linux";
|
|
133
|
+
return arch === "x64" ? `${base}.yml` : `${base}-${arch}.yml`;
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
/**
|
|
@@ -165,8 +174,7 @@ function stringifyYaml(entries) {
|
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
/**
|
|
168
|
-
* Compute the base64 encoded SHA-512 digest of the file at `filePath
|
|
169
|
-
* same encoding electron-builder writes into its update info files.
|
|
177
|
+
* Compute the base64 encoded SHA-512 digest of the file at `filePath`.
|
|
170
178
|
* @async
|
|
171
179
|
* @function
|
|
172
180
|
* @param {string} filePath
|
|
@@ -269,8 +277,117 @@ function windowsVersion(version) {
|
|
|
269
277
|
return parts.join(".");
|
|
270
278
|
}
|
|
271
279
|
|
|
280
|
+
/**
|
|
281
|
+
* Throw if `publish` isn't a valid `PublishOptions` object.
|
|
282
|
+
* @param {unknown} publish
|
|
283
|
+
* @returns {void}
|
|
284
|
+
*/
|
|
285
|
+
function validatePublish(publish) {
|
|
286
|
+
if (typeof publish !== "object" || publish === null) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Expected "options.publish" to be an object. Received: ${JSON.stringify(publish)}`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { provider } = /** @type {{provider?: unknown}} */ (publish);
|
|
293
|
+
/** @type {string[]} */
|
|
294
|
+
let requiredKeys;
|
|
295
|
+
if (provider === "github") {
|
|
296
|
+
requiredKeys = ["owner", "repo"];
|
|
297
|
+
} else if (provider === "generic") {
|
|
298
|
+
requiredKeys = ["url"];
|
|
299
|
+
} else {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`Expected "options.publish.provider" to be "github" or "generic". Received: ${JSON.stringify(provider)}`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
for (const key of requiredKeys) {
|
|
306
|
+
const value = /** @type {Record<string, unknown>} */ (publish)[key];
|
|
307
|
+
if (typeof value !== "string" || value === "") {
|
|
308
|
+
throw new Error(
|
|
309
|
+
`Expected "options.publish.${key}" to be a non-empty string for provider "${provider}". Received: ${JSON.stringify(value)}`,
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (provider === "github") {
|
|
315
|
+
/*
|
|
316
|
+
* The characters GitHub allows in owner and repository names. Anything
|
|
317
|
+
* else - eg. "|", the update information's field separator - is rejected.
|
|
318
|
+
*/
|
|
319
|
+
for (const key of requiredKeys) {
|
|
320
|
+
const value = /** @type {Record<string, string>} */ (publish)[key];
|
|
321
|
+
if (/^[A-Za-z0-9._-]+$/.test(value) === false) {
|
|
322
|
+
throw new Error(
|
|
323
|
+
`Expected "options.publish.${key}" to be a GitHub ${key} name (letters, digits, ".", "-" and "_"). Received: ${JSON.stringify(value)}`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (provider === "generic") {
|
|
330
|
+
const { url } =
|
|
331
|
+
/** @type {import("./appImage.js").GenericPublishOptions} */ (publish);
|
|
332
|
+
let protocol;
|
|
333
|
+
try {
|
|
334
|
+
protocol = new URL(url).protocol;
|
|
335
|
+
} catch {
|
|
336
|
+
protocol = undefined;
|
|
337
|
+
}
|
|
338
|
+
if (protocol !== "https:" && protocol !== "http:") {
|
|
339
|
+
throw new Error(
|
|
340
|
+
`Expected "options.publish.url" to be an http(s) URL. Received: ${JSON.stringify(url)}`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Contents of the `app-update.yml` file `@nwutils/updater` reads to find out
|
|
348
|
+
* where releases are published. It is shipped next to the NW.js executable.
|
|
349
|
+
* @param {import("./appImage.js").PublishOptions} publish
|
|
350
|
+
* @returns {string}
|
|
351
|
+
*/
|
|
352
|
+
function appUpdateYaml(publish) {
|
|
353
|
+
return stringifyYaml(
|
|
354
|
+
publish.provider === "github"
|
|
355
|
+
? { provider: publish.provider, owner: publish.owner, repo: publish.repo }
|
|
356
|
+
: { provider: publish.provider, url: publish.url },
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Write the update info file `@nwutils/updater` compares the running version
|
|
362
|
+
* against, describing the packaged artifact at `filePath`. It must be uploaded
|
|
363
|
+
* to every release next to the artifact.
|
|
364
|
+
* @async
|
|
365
|
+
* @function
|
|
366
|
+
* @param {object} options
|
|
367
|
+
* @param {string} options.filePath Path to the packaged artifact, eg. the AppImage or installer.
|
|
368
|
+
* @param {string} options.updateInfoPath Path the update info file is written to.
|
|
369
|
+
* @param {string} options.version Version of the application.
|
|
370
|
+
* @returns {Promise<void>}
|
|
371
|
+
*/
|
|
372
|
+
async function writeUpdateInfo({ filePath, updateInfoPath, version }) {
|
|
373
|
+
const fileName = path.basename(filePath);
|
|
374
|
+
const sha512Digest = await sha512(filePath);
|
|
375
|
+
const { size } = await fs.promises.stat(filePath);
|
|
376
|
+
await fs.promises.writeFile(
|
|
377
|
+
updateInfoPath,
|
|
378
|
+
stringifyYaml({
|
|
379
|
+
version,
|
|
380
|
+
files: [{ url: fileName, sha512: sha512Digest, size }],
|
|
381
|
+
path: fileName,
|
|
382
|
+
sha512: sha512Digest,
|
|
383
|
+
releaseDate: new Date().toISOString(),
|
|
384
|
+
}),
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
|
|
272
388
|
export default {
|
|
273
389
|
APPIMAGE_ARCH_KV,
|
|
390
|
+
appUpdateYaml,
|
|
274
391
|
desktopExecArg,
|
|
275
392
|
fileExists,
|
|
276
393
|
isWindowsFileName,
|
|
@@ -282,5 +399,7 @@ export default {
|
|
|
282
399
|
stringifyDesktopEntry,
|
|
283
400
|
stringifyYaml,
|
|
284
401
|
updateInfoFileName,
|
|
402
|
+
validatePublish,
|
|
285
403
|
windowsVersion,
|
|
404
|
+
writeUpdateInfo,
|
|
286
405
|
};
|
package/types/dmg.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export default dmg;
|
|
2
|
+
export type DmgOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Path to a built NW.js MacOS application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "osx"`.
|
|
5
|
+
*/
|
|
6
|
+
appDir: string;
|
|
7
|
+
/**
|
|
8
|
+
* Name of the application. Must match the `app.name` value used to build `appDir` - the bundle is expected at `<appDir>/<appName>.app`.
|
|
9
|
+
*/
|
|
10
|
+
appName: string;
|
|
11
|
+
/**
|
|
12
|
+
* Architecture `appDir` was built for. Defaults to the host architecture.
|
|
13
|
+
*/
|
|
14
|
+
arch?: "x64" | "arm64" | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Directory the resulting `<appName>-<arch>.dmg` file is written to. Defaults to the parent directory of `appDir`.
|
|
17
|
+
*/
|
|
18
|
+
outDir?: string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Name of the volume the DMG mounts as. Defaults to `appName`.
|
|
21
|
+
*/
|
|
22
|
+
volumeName?: string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Code signing identity, eg. `"Developer ID Application: ACME (TEAMID)"`, or `"-"` to sign ad-hoc. When set, the app is signed with `codesign` before it is put in the DMG. Sign here rather than before packaging when `publish` is set, since adding `app-update.yml` changes the bundle.
|
|
25
|
+
*/
|
|
26
|
+
signIdentity?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Version of the application, eg. `"1.2.0"`. Required when `publish` is set: it is written to the update info file `@nwutils/updater` compares against.
|
|
29
|
+
*/
|
|
30
|
+
version?: string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Where releases are published. When set, an `app-update.yml` pointing at it is added to the app's `Contents/Resources`, and a `<appName>-<arch>-mac.zip` update archive and a `latest-mac[-<arch>].yml` update info file are written next to the DMG, for `@nwutils/updater` to self update from.
|
|
33
|
+
*/
|
|
34
|
+
publish?: import("./appImage.js").PublishOptions | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Path to `hdiutil`, which creates the DMG.
|
|
37
|
+
*/
|
|
38
|
+
hdiutilPath?: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Path to `ditto`, which creates the update archive.
|
|
41
|
+
*/
|
|
42
|
+
dittoPath?: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Path to `codesign`, which signs the app.
|
|
45
|
+
*/
|
|
46
|
+
codesignPath?: string | undefined;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} DmgOptions
|
|
50
|
+
* @property {string} appDir Path to a built NW.js MacOS application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "osx"`.
|
|
51
|
+
* @property {string} appName Name of the application. Must match the `app.name` value used to build `appDir` - the bundle is expected at `<appDir>/<appName>.app`.
|
|
52
|
+
* @property {"x64" | "arm64"} [arch] Architecture `appDir` was built for. Defaults to the host architecture.
|
|
53
|
+
* @property {string} [outDir] Directory the resulting `<appName>-<arch>.dmg` file is written to. Defaults to the parent directory of `appDir`.
|
|
54
|
+
* @property {string} [volumeName] Name of the volume the DMG mounts as. Defaults to `appName`.
|
|
55
|
+
* @property {string} [signIdentity] Code signing identity, eg. `"Developer ID Application: ACME (TEAMID)"`, or `"-"` to sign ad-hoc. When set, the app is signed with `codesign` before it is put in the DMG. Sign here rather than before packaging when `publish` is set, since adding `app-update.yml` changes the bundle.
|
|
56
|
+
* @property {string} [version] Version of the application, eg. `"1.2.0"`. Required when `publish` is set: it is written to the update info file `@nwutils/updater` compares against.
|
|
57
|
+
* @property {import("./appImage.js").PublishOptions} [publish] Where releases are published. When set, an `app-update.yml` pointing at it is added to the app's `Contents/Resources`, and a `<appName>-<arch>-mac.zip` update archive and a `latest-mac[-<arch>].yml` update info file are written next to the DMG, for `@nwutils/updater` to self update from.
|
|
58
|
+
* @property {string} [hdiutilPath="hdiutil"] Path to `hdiutil`, which creates the DMG.
|
|
59
|
+
* @property {string} [dittoPath="ditto"] Path to `ditto`, which creates the update archive.
|
|
60
|
+
* @property {string} [codesignPath="codesign"] Path to `codesign`, which signs the app.
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Package a built NW.js MacOS application as a DMG disk image.
|
|
64
|
+
*
|
|
65
|
+
* The DMG holds the app next to a link to `/Applications`, so users install it
|
|
66
|
+
* by dragging the app onto the link. Updates don't use the DMG: with
|
|
67
|
+
* `publish` set, a zip of the app is written next to it, which
|
|
68
|
+
* `@nwutils/updater`'s `MacUpdater` downloads and swaps in for the running
|
|
69
|
+
* app.
|
|
70
|
+
*
|
|
71
|
+
* This composes with `@nwutils/builder`: run it against the `outDir` (and
|
|
72
|
+
* `app.name`) that `@nwutils/builder` produced for `platform: "osx"`.
|
|
73
|
+
* `hdiutil`, `ditto` and `codesign` are part of MacOS, so this runs on a MacOS
|
|
74
|
+
* host.
|
|
75
|
+
* @async
|
|
76
|
+
* @function
|
|
77
|
+
* @param {DmgOptions} options
|
|
78
|
+
* @returns {Promise<string>} Path to the resulting `.dmg` file.
|
|
79
|
+
*/
|
|
80
|
+
declare function dmg({ appDir, appName, arch, outDir, volumeName, signIdentity, version, publish, hdiutilPath, dittoPath, codesignPath, }: DmgOptions): Promise<string>;
|
package/types/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@ export default packager;
|
|
|
3
3
|
* Supported packaged output formats.
|
|
4
4
|
*
|
|
5
5
|
* `AppImage`, `deb` and `rpm` package a Linux build. `MSIX` and `NSIS`
|
|
6
|
-
* package a Windows build. `
|
|
7
|
-
*
|
|
8
|
-
* they land.
|
|
6
|
+
* package a Windows build. `dmg` packages a MacOS build. `"AppImage"`,
|
|
7
|
+
* `"nsis"` and `"dmg"` are implemented today - the others are reserved so
|
|
8
|
+
* this option doesn't need to change shape once they land.
|
|
9
9
|
*/
|
|
10
10
|
export type PackageFormat = "AppImage" | "deb" | "rpm" | "msix" | "nsis" | "dmg";
|
|
11
11
|
/**
|
|
@@ -16,9 +16,9 @@ export type PublishOptions = import("./appImage.js").PublishOptions;
|
|
|
16
16
|
* Supported packaged output formats.
|
|
17
17
|
*
|
|
18
18
|
* `AppImage`, `deb` and `rpm` package a Linux build. `MSIX` and `NSIS`
|
|
19
|
-
* package a Windows build. `
|
|
20
|
-
*
|
|
21
|
-
* they land.
|
|
19
|
+
* package a Windows build. `dmg` packages a MacOS build. `"AppImage"`,
|
|
20
|
+
* `"nsis"` and `"dmg"` are implemented today - the others are reserved so
|
|
21
|
+
* this option doesn't need to change shape once they land.
|
|
22
22
|
* @typedef {"AppImage" | "deb" | "rpm" | "msix" | "nsis" | "dmg"} PackageFormat
|
|
23
23
|
*/
|
|
24
24
|
/**
|
|
@@ -29,7 +29,7 @@ export type PublishOptions = import("./appImage.js").PublishOptions;
|
|
|
29
29
|
* Package a built NW.js application.
|
|
30
30
|
* @async
|
|
31
31
|
* @function
|
|
32
|
-
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage` or `
|
|
32
|
+
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage`, `nsis` or `dmg`) expects.
|
|
33
33
|
* @returns {Promise<string>} Path to the resulting packaged artifact.
|
|
34
34
|
*/
|
|
35
35
|
declare function packager({ format, ...options }: {
|
package/types/nsis.d.ts
CHANGED
|
@@ -52,6 +52,10 @@ export type NsisOptions = {
|
|
|
52
52
|
* Path to the NSIS compiler. Defaults to `makensis` on the `PATH`.
|
|
53
53
|
*/
|
|
54
54
|
makensisPath?: string | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Where releases are published. When set, an `app-update.yml` pointing at it is installed next to the executable and a `latest[-<arch>].yml` update info file is written next to the installer, for `@nwutils/updater` to self update from.
|
|
57
|
+
*/
|
|
58
|
+
publish?: import("./appImage.js").PublishOptions | undefined;
|
|
55
59
|
};
|
|
56
60
|
/**
|
|
57
61
|
* The files and directories of a built application, relative to its root.
|
|
@@ -79,6 +83,10 @@ export type AppFiles = {
|
|
|
79
83
|
* installing a new version first uninstalls the previous one. Both wait for
|
|
80
84
|
* the application to be closed before touching its files.
|
|
81
85
|
*
|
|
86
|
+
* Run with `/S /UPDATE`, as `@nwutils/updater` does, the installer instead
|
|
87
|
+
* waits for the running application to exit, updates it silently and starts
|
|
88
|
+
* the new version.
|
|
89
|
+
*
|
|
82
90
|
* This composes with `@nwutils/builder`: run it against the `outDir` (and
|
|
83
91
|
* `app.name`) that `@nwutils/builder` produced for `platform: "win"`.
|
|
84
92
|
* `makensis` runs on Linux and macOS too, so the installer can be built on
|
|
@@ -88,7 +96,7 @@ export type AppFiles = {
|
|
|
88
96
|
* @param {NsisOptions} options
|
|
89
97
|
* @returns {Promise<string>} Path to the resulting installer `.exe` file.
|
|
90
98
|
*/
|
|
91
|
-
declare function nsis({ appDir, appName, version, arch, outDir, icon, appId, publisher, copyright, license, desktopShortcut, startMenuShortcut, makensisPath, }: NsisOptions): Promise<string>;
|
|
99
|
+
declare function nsis({ appDir, appName, version, arch, outDir, icon, appId, publisher, copyright, license, desktopShortcut, startMenuShortcut, makensisPath, publish, }: NsisOptions): Promise<string>;
|
|
92
100
|
/**
|
|
93
101
|
* List the files and directories under `appDir`. Symbolic links are followed,
|
|
94
102
|
* since NSIS copies what they point at.
|
|
@@ -120,9 +128,10 @@ export function listAppFiles(appDir: string): Promise<AppFiles>;
|
|
|
120
128
|
* @param {string} options.copyright
|
|
121
129
|
* @param {boolean} options.desktopShortcut
|
|
122
130
|
* @param {boolean} options.startMenuShortcut
|
|
131
|
+
* @param {string} [options.appUpdateYamlPath] Absolute path to an `app-update.yml` file to install next to the executable.
|
|
123
132
|
* @returns {string}
|
|
124
133
|
*/
|
|
125
|
-
export function nsisScript({ appDir, appFiles, appName, appId, version, fileVersion, arch, installerPath, iconPath, licensePath, publisher, copyright, desktopShortcut, startMenuShortcut, }: {
|
|
134
|
+
export function nsisScript({ appDir, appFiles, appName, appId, version, fileVersion, arch, installerPath, iconPath, licensePath, publisher, copyright, desktopShortcut, startMenuShortcut, appUpdateYamlPath, }: {
|
|
126
135
|
appDir: string;
|
|
127
136
|
appFiles: AppFiles;
|
|
128
137
|
appName: string;
|
|
@@ -137,4 +146,5 @@ export function nsisScript({ appDir, appFiles, appName, appId, version, fileVers
|
|
|
137
146
|
copyright: string;
|
|
138
147
|
desktopShortcut: boolean;
|
|
139
148
|
startMenuShortcut: boolean;
|
|
149
|
+
appUpdateYamlPath?: string | undefined;
|
|
140
150
|
}): string;
|
package/types/util.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
declare namespace _default {
|
|
2
2
|
export { APPIMAGE_ARCH_KV };
|
|
3
|
+
export { appUpdateYaml };
|
|
3
4
|
export { desktopExecArg };
|
|
4
5
|
export { fileExists };
|
|
5
6
|
export { isWindowsFileName };
|
|
@@ -11,7 +12,9 @@ declare namespace _default {
|
|
|
11
12
|
export { stringifyDesktopEntry };
|
|
12
13
|
export { stringifyYaml };
|
|
13
14
|
export { updateInfoFileName };
|
|
15
|
+
export { validatePublish };
|
|
14
16
|
export { windowsVersion };
|
|
17
|
+
export { writeUpdateInfo };
|
|
15
18
|
}
|
|
16
19
|
export default _default;
|
|
17
20
|
/**
|
|
@@ -20,6 +23,13 @@ export default _default;
|
|
|
20
23
|
* @type {Record<"ia32" | "x64" | "arm64", string>}
|
|
21
24
|
*/
|
|
22
25
|
declare const APPIMAGE_ARCH_KV: Record<"ia32" | "x64" | "arm64", string>;
|
|
26
|
+
/**
|
|
27
|
+
* Contents of the `app-update.yml` file `@nwutils/updater` reads to find out
|
|
28
|
+
* where releases are published. It is shipped next to the NW.js executable.
|
|
29
|
+
* @param {import("./appImage.js").PublishOptions} publish
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
declare function appUpdateYaml(publish: import("./appImage.js").PublishOptions): string;
|
|
23
33
|
/**
|
|
24
34
|
* Quote `value` as a single argument of a desktop entry's `Exec` key, per the
|
|
25
35
|
* Desktop Entry Specification: `%` is doubled so it isn't read as a field
|
|
@@ -75,8 +85,7 @@ declare function nsisPath(value: string): string;
|
|
|
75
85
|
*/
|
|
76
86
|
declare function parseDesktopEntry(contents: string): Record<string, string>;
|
|
77
87
|
/**
|
|
78
|
-
* Compute the base64 encoded SHA-512 digest of the file at `filePath
|
|
79
|
-
* same encoding electron-builder writes into its update info files.
|
|
88
|
+
* Compute the base64 encoded SHA-512 digest of the file at `filePath`.
|
|
80
89
|
* @async
|
|
81
90
|
* @function
|
|
82
91
|
* @param {string} filePath
|
|
@@ -105,13 +114,21 @@ declare function stringifyDesktopEntry(entries: Record<string, string | undefine
|
|
|
105
114
|
*/
|
|
106
115
|
declare function stringifyYaml(entries: Record<string, string | number | boolean | Record<string, string | number | boolean>[] | undefined>): string;
|
|
107
116
|
/**
|
|
108
|
-
* Name of the update info file `@nwutils/updater` downloads
|
|
109
|
-
*
|
|
110
|
-
* `latest-linux.yml
|
|
111
|
-
*
|
|
117
|
+
* Name of the update info file `@nwutils/updater` downloads for `arch` on
|
|
118
|
+
* `platform`, gets the bare
|
|
119
|
+
* `latest-linux.yml` (Linux), `latest.yml` (Windows) or `latest-mac.yml`
|
|
120
|
+
* (MacOS), every other architecture gets an `-<arch>` suffix.
|
|
121
|
+
* @param {string} arch
|
|
122
|
+
* @param {"linux" | "win" | "osx"} [platform="linux"]
|
|
112
123
|
* @returns {string}
|
|
113
124
|
*/
|
|
114
|
-
declare function updateInfoFileName(arch: "
|
|
125
|
+
declare function updateInfoFileName(arch: string, platform?: "linux" | "win" | "osx"): string;
|
|
126
|
+
/**
|
|
127
|
+
* Throw if `publish` isn't a valid `PublishOptions` object.
|
|
128
|
+
* @param {unknown} publish
|
|
129
|
+
* @returns {void}
|
|
130
|
+
*/
|
|
131
|
+
declare function validatePublish(publish: unknown): void;
|
|
115
132
|
/**
|
|
116
133
|
* Convert a version such as `"1.2.3"` or `"1.2.3-beta.1"` into the
|
|
117
134
|
* `X.X.X.X` form Windows version resources (and NSIS' `VIProductVersion`)
|
|
@@ -121,3 +138,20 @@ declare function updateInfoFileName(arch: "ia32" | "x64" | "arm64"): string;
|
|
|
121
138
|
* @returns {string}
|
|
122
139
|
*/
|
|
123
140
|
declare function windowsVersion(version: string): string;
|
|
141
|
+
/**
|
|
142
|
+
* Write the update info file `@nwutils/updater` compares the running version
|
|
143
|
+
* against, describing the packaged artifact at `filePath`. It must be uploaded
|
|
144
|
+
* to every release next to the artifact.
|
|
145
|
+
* @async
|
|
146
|
+
* @function
|
|
147
|
+
* @param {object} options
|
|
148
|
+
* @param {string} options.filePath Path to the packaged artifact, eg. the AppImage or installer.
|
|
149
|
+
* @param {string} options.updateInfoPath Path the update info file is written to.
|
|
150
|
+
* @param {string} options.version Version of the application.
|
|
151
|
+
* @returns {Promise<void>}
|
|
152
|
+
*/
|
|
153
|
+
declare function writeUpdateInfo({ filePath, updateInfoPath, version }: {
|
|
154
|
+
filePath: string;
|
|
155
|
+
updateInfoPath: string;
|
|
156
|
+
version: string;
|
|
157
|
+
}): Promise<void>;
|