@nwutils/packager 0.5.0 → 0.6.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 +64 -5
- package/package.json +3 -3
- package/src/index.js +8 -6
- package/src/nsis.js +563 -0
- package/src/util.js +93 -0
- package/types/appImage.d.ts +22 -14
- package/types/index.d.ts +16 -5
- package/types/nsis.d.ts +140 -0
- package/types/util.d.ts +85 -37
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,51 @@ 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
|
+
|
|
109
146
|
## API Reference
|
|
110
147
|
|
|
111
148
|
### `packager({ format, ...options })`
|
|
112
149
|
|
|
113
|
-
| Name | Type
|
|
114
|
-
| ------ |
|
|
115
|
-
| format | `"AppImage" \| "deb" \| "rpm" \| "
|
|
116
|
-
| ... | -
|
|
150
|
+
| Name | Type | Description |
|
|
151
|
+
| ------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
152
|
+
| format | `"AppImage" \| "deb" \| "rpm" \| "msix" \| "nsis" \| "dmg"` | Which packager to run. `"AppImage"` and `"nsis"` are implemented today. |
|
|
153
|
+
| ... | - | The remaining options are passed through to the packager for `format` as-is. |
|
|
117
154
|
|
|
118
155
|
Resolves with the path to the resulting packaged artifact.
|
|
119
156
|
|
|
@@ -139,6 +176,28 @@ Options
|
|
|
139
176
|
|
|
140
177
|
Resolves with the path to the resulting `.AppImage` file.
|
|
141
178
|
|
|
179
|
+
### `nsis(options)`
|
|
180
|
+
|
|
181
|
+
Options
|
|
182
|
+
|
|
183
|
+
| Name | Type | Description |
|
|
184
|
+
| ----------------- | ---------------------------- | --------------------------------------------------------------------------------------------------------------------- |
|
|
185
|
+
| appDir | `string` | Path to a built NW.js Windows application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "win"` |
|
|
186
|
+
| appName | `string` | Name of the application. Must match the `app.name` value used to build `appDir`. Must be a valid Windows file name |
|
|
187
|
+
| version | `string` | Version of the application, eg. `"1.2.0"`. Required |
|
|
188
|
+
| arch | `"ia32" \| "x64" \| "arm64"` | Architecture `appDir` was built for. Defaults to the host architecture |
|
|
189
|
+
| outDir | `string` | Directory the resulting `<appName>-<arch>-Setup.exe` file is written to. Defaults to the parent directory of `appDir` |
|
|
190
|
+
| icon | `string` | Path to a `.ico` icon for the installer and uninstaller. Defaults to NSIS' own icon |
|
|
191
|
+
| appId | `string` | Name of the registry key the installation is recorded under. Defaults to `appName`. Keep it the same across releases |
|
|
192
|
+
| publisher | `string` | Publisher shown in Windows' installed apps list |
|
|
193
|
+
| copyright | `string` | Copyright notice written into the installer's version information |
|
|
194
|
+
| license | `string` | Path to a `.txt` or `.rtf` license file the user has to accept |
|
|
195
|
+
| desktopShortcut | `boolean` | If true, create a desktop shortcut. Defaults to `true` |
|
|
196
|
+
| startMenuShortcut | `boolean` | If true, create a Start Menu shortcut. Defaults to `true` |
|
|
197
|
+
| makensisPath | `string` | Path to the NSIS compiler. Defaults to `makensis` on the `PATH` |
|
|
198
|
+
|
|
199
|
+
Resolves with the path to the resulting installer `.exe` file.
|
|
200
|
+
|
|
142
201
|
## Contributing
|
|
143
202
|
|
|
144
203
|
### External contributor
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nwutils/packager",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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/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(
|
package/src/nsis.js
ADDED
|
@@ -0,0 +1,563 @@
|
|
|
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} NsisOptions
|
|
11
|
+
* @property {string} appDir Path to a built NW.js Windows application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "win"`.
|
|
12
|
+
* @property {string} appName Name of the application. Must match the `app.name` value used to build `appDir` - the executable is expected at `<appDir>/<appName>.exe`. Must be a valid Windows file name.
|
|
13
|
+
* @property {string} version Version of the application, eg. `"1.2.0"`. Shown in Windows' installed apps list, and converted to `X.X.X.X` for the installer's own version resource.
|
|
14
|
+
* @property {"ia32" | "x64" | "arm64"} [arch] Architecture `appDir` was built for. Defaults to the host architecture. The installer refuses to run on Windows machines that can't run that architecture.
|
|
15
|
+
* @property {string} [outDir] Directory the resulting `<appName>-<arch>-Setup.exe` file is written to. Defaults to the parent directory of `appDir`.
|
|
16
|
+
* @property {string} [icon] Path to a `.ico` icon for the installer and uninstaller. Defaults to NSIS' own icon.
|
|
17
|
+
* @property {string} [appId] Name of the registry key the installation is recorded under. Defaults to `appName`. Keep it the same across releases, so installing a new version replaces the previous one.
|
|
18
|
+
* @property {string} [publisher] Publisher shown in Windows' installed apps list.
|
|
19
|
+
* @property {string} [copyright] Copyright notice written into the installer's version information.
|
|
20
|
+
* @property {string} [license] Path to a `.txt` or `.rtf` license file. When set, the installer shows it on a page the user has to accept.
|
|
21
|
+
* @property {boolean} [desktopShortcut=true] If true, create a desktop shortcut.
|
|
22
|
+
* @property {boolean} [startMenuShortcut=true] If true, create a Start Menu shortcut.
|
|
23
|
+
* @property {string} [makensisPath="makensis"] Path to the NSIS compiler. Defaults to `makensis` on the `PATH`.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The files and directories of a built application, relative to its root.
|
|
28
|
+
* @typedef {object} AppFiles
|
|
29
|
+
* @property {string[]} files Relative file paths, using the host's path separator.
|
|
30
|
+
* @property {string[]} dirs Relative directory paths, parents before children.
|
|
31
|
+
* @property {number} size Total size of `files` in bytes.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Map nw-builder's architecture identifiers to the `x64.nsh` and `WinVer.nsh`
|
|
36
|
+
* checks for whether the Windows machine running the installer can run that
|
|
37
|
+
* architecture. Each inner list is one way to pass, and all of its checks
|
|
38
|
+
* must pass. x64 builds run natively on x64 and, emulated, on ARM64 - but
|
|
39
|
+
* only from Windows 11, as ARM64 Windows 10 only emulates ia32. Every Windows
|
|
40
|
+
* machine runs ia32 builds.
|
|
41
|
+
* @type {Record<"ia32" | "x64" | "arm64", string[][]>}
|
|
42
|
+
*/
|
|
43
|
+
const ARCH_CHECK_KV = {
|
|
44
|
+
ia32: [],
|
|
45
|
+
x64: [["${IsNativeAMD64}"], ["${IsNativeARM64}", "${AtLeastWin11}"]],
|
|
46
|
+
arm64: [["${IsNativeARM64}"]],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Package a built NW.js Windows application as an NSIS installer.
|
|
51
|
+
*
|
|
52
|
+
* The installer installs for the current user only, into
|
|
53
|
+
* `%LOCALAPPDATA%\Programs\<appName>`, so it never asks for administrator
|
|
54
|
+
* rights. It registers an uninstaller in Windows' installed apps list, and
|
|
55
|
+
* installing a new version first uninstalls the previous one. Both wait for
|
|
56
|
+
* the application to be closed before touching its files.
|
|
57
|
+
*
|
|
58
|
+
* This composes with `@nwutils/builder`: run it against the `outDir` (and
|
|
59
|
+
* `app.name`) that `@nwutils/builder` produced for `platform: "win"`.
|
|
60
|
+
* `makensis` runs on Linux and macOS too, so the installer can be built on
|
|
61
|
+
* any host.
|
|
62
|
+
* @async
|
|
63
|
+
* @function
|
|
64
|
+
* @param {NsisOptions} options
|
|
65
|
+
* @returns {Promise<string>} Path to the resulting installer `.exe` file.
|
|
66
|
+
*/
|
|
67
|
+
async function nsis({
|
|
68
|
+
appDir,
|
|
69
|
+
appName,
|
|
70
|
+
version,
|
|
71
|
+
arch,
|
|
72
|
+
outDir,
|
|
73
|
+
icon,
|
|
74
|
+
appId,
|
|
75
|
+
publisher,
|
|
76
|
+
copyright = "",
|
|
77
|
+
license,
|
|
78
|
+
desktopShortcut = true,
|
|
79
|
+
startMenuShortcut = true,
|
|
80
|
+
makensisPath = "makensis",
|
|
81
|
+
}) {
|
|
82
|
+
if (typeof appDir !== "string" || appDir === "") {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Expected "options.appDir" to be a non-empty string. Received: ${JSON.stringify(appDir)}`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if ((await util.fileExists(appDir)) === false) {
|
|
89
|
+
throw new Error(`"options.appDir" does not exist: ${appDir}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/*
|
|
93
|
+
* `appName` names files inside `appDir`, `outDir` and the install
|
|
94
|
+
* directory, and is written into the installer script.
|
|
95
|
+
*/
|
|
96
|
+
if (
|
|
97
|
+
typeof appName !== "string" ||
|
|
98
|
+
util.isWindowsFileName(appName) === false
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Expected "options.appName" to be a valid Windows file name. Received: ${JSON.stringify(appName)}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (typeof version !== "string" || version === "") {
|
|
106
|
+
throw new Error(
|
|
107
|
+
`Expected "options.version" to be a non-empty string. Received: ${JSON.stringify(version)}`,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
const fileVersion = util.windowsVersion(version);
|
|
111
|
+
|
|
112
|
+
const resolvedArch =
|
|
113
|
+
/** @type {"ia32" | "x64" | "arm64" | undefined} */ (arch) ??
|
|
114
|
+
/** @type {"ia32" | "x64" | "arm64"} */ (
|
|
115
|
+
/** @type {unknown} */ (process.arch)
|
|
116
|
+
);
|
|
117
|
+
/* `Object.hasOwn` so inherited keys such as "constructor" aren't mistaken for architectures. */
|
|
118
|
+
if (Object.hasOwn(ARCH_CHECK_KV, resolvedArch) === false) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
`Expected "options.arch" to be "ia32", "x64" or "arm64". Received: ${JSON.stringify(arch)}`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const resolvedAppId = appId ?? appName;
|
|
125
|
+
/* The installation is recorded under a registry key named after `appId`, and `\` separates registry keys. */
|
|
126
|
+
if (
|
|
127
|
+
typeof resolvedAppId !== "string" ||
|
|
128
|
+
resolvedAppId === "" ||
|
|
129
|
+
resolvedAppId.includes("\\") ||
|
|
130
|
+
[...resolvedAppId].some((character) => {
|
|
131
|
+
const code = character.charCodeAt(0);
|
|
132
|
+
return code < 0x20 || code === 0x7f;
|
|
133
|
+
})
|
|
134
|
+
) {
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Expected "options.appId" to be a non-empty string without "\\" or control characters. Received: ${JSON.stringify(appId)}`,
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (
|
|
141
|
+
publisher !== undefined &&
|
|
142
|
+
(typeof publisher !== "string" || publisher === "")
|
|
143
|
+
) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Expected "options.publisher" to be a non-empty string. Received: ${JSON.stringify(publisher)}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (typeof copyright !== "string") {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`Expected "options.copyright" to be a string. Received: ${JSON.stringify(copyright)}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
for (const [key, value] of Object.entries({
|
|
156
|
+
desktopShortcut,
|
|
157
|
+
startMenuShortcut,
|
|
158
|
+
})) {
|
|
159
|
+
if (typeof value !== "boolean") {
|
|
160
|
+
throw new Error(
|
|
161
|
+
`Expected "options.${key}" to be a boolean. Received: ${JSON.stringify(value)}`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (typeof makensisPath !== "string" || makensisPath === "") {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`Expected "options.makensisPath" to be a non-empty string. Received: ${JSON.stringify(makensisPath)}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const resolvedIconPath =
|
|
173
|
+
icon === undefined
|
|
174
|
+
? undefined
|
|
175
|
+
: await resolveInputFile("icon", icon, [".ico"]);
|
|
176
|
+
const resolvedLicensePath =
|
|
177
|
+
license === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: await resolveInputFile("license", license, [".txt", ".rtf"]);
|
|
180
|
+
|
|
181
|
+
const resolvedAppDir = path.resolve(appDir);
|
|
182
|
+
const executablePath = path.resolve(resolvedAppDir, `${appName}.exe`);
|
|
183
|
+
if ((await util.fileExists(executablePath)) === false) {
|
|
184
|
+
throw new Error(
|
|
185
|
+
`Expected the NW.js executable to exist at ${executablePath}. Does "options.appName" match the "app.name" used to build "options.appDir" for "platform: \\"win\\""?`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const resolvedOutDir = path.resolve(outDir ?? path.dirname(resolvedAppDir));
|
|
190
|
+
await fs.promises.mkdir(resolvedOutDir, { recursive: true });
|
|
191
|
+
const installerPath = path.resolve(
|
|
192
|
+
resolvedOutDir,
|
|
193
|
+
`${appName}-${resolvedArch}-Setup.exe`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const script = nsisScript({
|
|
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
|
+
|
|
213
|
+
const scriptDir = await fs.promises.mkdtemp(
|
|
214
|
+
path.join(os.tmpdir(), "nwutils-packager-nsis-"),
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
const scriptPath = path.resolve(scriptDir, "installer.nsi");
|
|
219
|
+
await fs.promises.writeFile(scriptPath, script, "utf-8");
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
child_process.execFileSync(
|
|
223
|
+
makensisPath,
|
|
224
|
+
["-INPUTCHARSET", "UTF8", "-V2", scriptPath],
|
|
225
|
+
{ stdio: "inherit" },
|
|
226
|
+
);
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (/** @type {NodeJS.ErrnoException} */ (error).code === "ENOENT") {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Could not find the NSIS compiler at ${JSON.stringify(makensisPath)}. Install NSIS (eg. "apt install nsis", "brew install makensis" or "choco install nsis"), or pass its path as "options.makensisPath".`,
|
|
231
|
+
{ cause: error },
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return installerPath;
|
|
238
|
+
} finally {
|
|
239
|
+
await fs.promises.rm(scriptDir, { recursive: true, force: true });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Resolve an input file option, and throw unless it exists and has one of
|
|
245
|
+
* `extensions`.
|
|
246
|
+
* @async
|
|
247
|
+
* @function
|
|
248
|
+
* @param {string} key Option name, for error messages.
|
|
249
|
+
* @param {unknown} value Option value.
|
|
250
|
+
* @param {string[]} extensions Accepted lowercase extensions, eg. `[".ico"]`.
|
|
251
|
+
* @returns {Promise<string>} Absolute path to the file.
|
|
252
|
+
*/
|
|
253
|
+
async function resolveInputFile(key, value, extensions) {
|
|
254
|
+
if (typeof value !== "string" || value === "") {
|
|
255
|
+
throw new Error(
|
|
256
|
+
`Expected "options.${key}" to be a non-empty string. Received: ${JSON.stringify(value)}`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const resolvedPath = path.resolve(value);
|
|
260
|
+
if (extensions.includes(path.extname(resolvedPath).toLowerCase()) === false) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
`Expected "options.${key}" to be a ${extensions.join(" or ")} file. Received: ${resolvedPath}`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if ((await util.fileExists(resolvedPath)) === false) {
|
|
266
|
+
throw new Error(`"options.${key}" does not exist: ${resolvedPath}`);
|
|
267
|
+
}
|
|
268
|
+
return resolvedPath;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* List the files and directories under `appDir`. Symbolic links are followed,
|
|
273
|
+
* since NSIS copies what they point at.
|
|
274
|
+
* @async
|
|
275
|
+
* @function
|
|
276
|
+
* @param {string} appDir Absolute path to the built application.
|
|
277
|
+
* @returns {Promise<AppFiles>}
|
|
278
|
+
*/
|
|
279
|
+
async function listAppFiles(appDir) {
|
|
280
|
+
/** @type {AppFiles} */
|
|
281
|
+
const appFiles = { files: [], dirs: [], size: 0 };
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* @param {string} relativeDir
|
|
285
|
+
* @returns {Promise<void>}
|
|
286
|
+
*/
|
|
287
|
+
async function walk(relativeDir) {
|
|
288
|
+
const entries = await fs.promises.readdir(path.join(appDir, relativeDir));
|
|
289
|
+
for (const entry of entries.sort()) {
|
|
290
|
+
const relativePath = path.join(relativeDir, entry);
|
|
291
|
+
const stats = await fs.promises.stat(path.join(appDir, relativePath));
|
|
292
|
+
if (stats.isDirectory()) {
|
|
293
|
+
appFiles.dirs.push(relativePath);
|
|
294
|
+
await walk(relativePath);
|
|
295
|
+
} else {
|
|
296
|
+
appFiles.files.push(relativePath);
|
|
297
|
+
appFiles.size += stats.size;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
await walk("");
|
|
303
|
+
return appFiles;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Generate the NSIS script for an installer.
|
|
308
|
+
*
|
|
309
|
+
* Every file is listed explicitly rather than copied with `File /r`, so the
|
|
310
|
+
* uninstaller deletes exactly the files that were installed. The user can
|
|
311
|
+
* choose any install directory - including one such as `C:\` that holds
|
|
312
|
+
* other files - so the uninstaller never deletes directories recursively.
|
|
313
|
+
* @param {object} options
|
|
314
|
+
* @param {string} options.appDir Absolute path to the built application.
|
|
315
|
+
* @param {AppFiles} options.appFiles Files and directories under `appDir`.
|
|
316
|
+
* @param {string} options.appName
|
|
317
|
+
* @param {string} options.appId
|
|
318
|
+
* @param {string} options.version
|
|
319
|
+
* @param {string} options.fileVersion `version` in `X.X.X.X` form.
|
|
320
|
+
* @param {"ia32" | "x64" | "arm64"} options.arch
|
|
321
|
+
* @param {string} options.installerPath Absolute path the installer is written to.
|
|
322
|
+
* @param {string} [options.iconPath] Absolute path to a `.ico` file.
|
|
323
|
+
* @param {string} [options.licensePath] Absolute path to a `.txt` or `.rtf` file.
|
|
324
|
+
* @param {string} [options.publisher]
|
|
325
|
+
* @param {string} options.copyright
|
|
326
|
+
* @param {boolean} options.desktopShortcut
|
|
327
|
+
* @param {boolean} options.startMenuShortcut
|
|
328
|
+
* @returns {string}
|
|
329
|
+
*/
|
|
330
|
+
function nsisScript({
|
|
331
|
+
appDir,
|
|
332
|
+
appFiles,
|
|
333
|
+
appName,
|
|
334
|
+
appId,
|
|
335
|
+
version,
|
|
336
|
+
fileVersion,
|
|
337
|
+
arch,
|
|
338
|
+
installerPath,
|
|
339
|
+
iconPath,
|
|
340
|
+
licensePath,
|
|
341
|
+
publisher,
|
|
342
|
+
copyright,
|
|
343
|
+
desktopShortcut,
|
|
344
|
+
startMenuShortcut,
|
|
345
|
+
}) {
|
|
346
|
+
const e = util.nsisEscape;
|
|
347
|
+
const p = util.nsisPath;
|
|
348
|
+
/* Paths inside the install directory always use `\`, whichever host builds the installer. */
|
|
349
|
+
const installed = (/** @type {string} */ relativePath) =>
|
|
350
|
+
`"$INSTDIR\\${e(relativePath.split(path.sep).join("\\"))}"`;
|
|
351
|
+
const uninstallKey = `"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${e(appId)}"`;
|
|
352
|
+
const exe = installed(`${appName}.exe`);
|
|
353
|
+
const archChecks = ARCH_CHECK_KV[arch];
|
|
354
|
+
|
|
355
|
+
/** @type {string[]} */
|
|
356
|
+
const lines = [
|
|
357
|
+
"Unicode true",
|
|
358
|
+
"ManifestDPIAware true",
|
|
359
|
+
"RequestExecutionLevel user",
|
|
360
|
+
"SetCompressor /SOLID lzma",
|
|
361
|
+
"",
|
|
362
|
+
'!include "MUI2.nsh"',
|
|
363
|
+
'!include "LogicLib.nsh"',
|
|
364
|
+
'!include "x64.nsh"',
|
|
365
|
+
'!include "WinVer.nsh"',
|
|
366
|
+
"",
|
|
367
|
+
`Name "${e(appName)}"`,
|
|
368
|
+
`OutFile "${p(installerPath)}"`,
|
|
369
|
+
`InstallDir "$LOCALAPPDATA\\Programs\\${e(appName)}"`,
|
|
370
|
+
`InstallDirRegKey HKCU ${uninstallKey} "InstallLocation"`,
|
|
371
|
+
"",
|
|
372
|
+
];
|
|
373
|
+
|
|
374
|
+
if (iconPath !== undefined) {
|
|
375
|
+
lines.push(
|
|
376
|
+
`!define MUI_ICON "${p(iconPath)}"`,
|
|
377
|
+
`!define MUI_UNICON "${p(iconPath)}"`,
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
lines.push(
|
|
381
|
+
"!define MUI_ABORTWARNING",
|
|
382
|
+
`!define MUI_FINISHPAGE_RUN ${exe}`,
|
|
383
|
+
"",
|
|
384
|
+
"!insertmacro MUI_PAGE_WELCOME",
|
|
385
|
+
);
|
|
386
|
+
if (licensePath !== undefined) {
|
|
387
|
+
lines.push(`!insertmacro MUI_PAGE_LICENSE "${p(licensePath)}"`);
|
|
388
|
+
}
|
|
389
|
+
lines.push(
|
|
390
|
+
"!insertmacro MUI_PAGE_DIRECTORY",
|
|
391
|
+
"!insertmacro MUI_PAGE_INSTFILES",
|
|
392
|
+
"!insertmacro MUI_PAGE_FINISH",
|
|
393
|
+
"!insertmacro MUI_UNPAGE_CONFIRM",
|
|
394
|
+
"!insertmacro MUI_UNPAGE_INSTFILES",
|
|
395
|
+
'!insertmacro MUI_LANGUAGE "English"',
|
|
396
|
+
"",
|
|
397
|
+
`VIProductVersion "${fileVersion}"`,
|
|
398
|
+
`VIFileVersion "${fileVersion}"`,
|
|
399
|
+
`VIAddVersionKey "ProductName" "${e(appName)}"`,
|
|
400
|
+
`VIAddVersionKey "ProductVersion" "${e(version)}"`,
|
|
401
|
+
`VIAddVersionKey "FileVersion" "${e(version)}"`,
|
|
402
|
+
`VIAddVersionKey "FileDescription" "${e(appName)} Setup"`,
|
|
403
|
+
`VIAddVersionKey "LegalCopyright" "${e(copyright)}"`,
|
|
404
|
+
);
|
|
405
|
+
if (publisher !== undefined) {
|
|
406
|
+
lines.push(`VIAddVersionKey "CompanyName" "${e(publisher)}"`);
|
|
407
|
+
}
|
|
408
|
+
lines.push("", "Function .onInit", " SetShellVarContext current");
|
|
409
|
+
if (archChecks.length > 0) {
|
|
410
|
+
/* Passing branches are left empty: only the final `${Else}` does anything. */
|
|
411
|
+
lines.push(
|
|
412
|
+
...archChecks.flatMap((checks, index) =>
|
|
413
|
+
checks.map(
|
|
414
|
+
(check, checkIndex) =>
|
|
415
|
+
` \${${checkIndex > 0 ? "AndIf" : index === 0 ? "If" : "ElseIf"}} ${check}`,
|
|
416
|
+
),
|
|
417
|
+
),
|
|
418
|
+
" ${Else}",
|
|
419
|
+
` MessageBox MB_ICONSTOP "This installer is for ${arch} Windows, which this computer can't run." /SD IDOK`,
|
|
420
|
+
" Abort",
|
|
421
|
+
" ${EndIf}",
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
lines.push(
|
|
425
|
+
"FunctionEnd",
|
|
426
|
+
"",
|
|
427
|
+
'Section "Install"',
|
|
428
|
+
` ReadRegStr $0 HKCU ${uninstallKey} "InstallLocation"`,
|
|
429
|
+
' ${If} $0 != ""',
|
|
430
|
+
...waitForAppToClose(`"$0\\${e(appName)}.exe"`, appName, " "),
|
|
431
|
+
" ${EndIf}",
|
|
432
|
+
...waitForAppToClose(exe, appName, " "),
|
|
433
|
+
/*
|
|
434
|
+
* Uninstall the previous version first, so files it had that this
|
|
435
|
+
* version doesn't are removed. `_?=` runs its uninstaller in place and
|
|
436
|
+
* waits for it, which leaves the uninstaller itself behind.
|
|
437
|
+
*/
|
|
438
|
+
' ${If} $0 != ""',
|
|
439
|
+
' ${AndIf} ${FileExists} "$0\\Uninstall.exe"',
|
|
440
|
+
" ClearErrors",
|
|
441
|
+
` ExecWait '"$0\\Uninstall.exe" /S _?=$0' $1`,
|
|
442
|
+
" ${If} ${Errors}",
|
|
443
|
+
" ${OrIf} $1 != 0",
|
|
444
|
+
` MessageBox MB_ICONSTOP "The previous version of ${e(appName)} couldn't be uninstalled." /SD IDOK`,
|
|
445
|
+
" Abort",
|
|
446
|
+
" ${EndIf}",
|
|
447
|
+
' Delete "$0\\Uninstall.exe"',
|
|
448
|
+
' RMDir "$0"',
|
|
449
|
+
" ${EndIf}",
|
|
450
|
+
"",
|
|
451
|
+
' SetOutPath "$INSTDIR"',
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
/* Group files by directory: `File` writes into the current `SetOutPath`. */
|
|
455
|
+
/** @type {Map<string, string[]>} */
|
|
456
|
+
const filesByDir = new Map([["", []]]);
|
|
457
|
+
for (const dir of appFiles.dirs) {
|
|
458
|
+
filesByDir.set(dir, []);
|
|
459
|
+
}
|
|
460
|
+
for (const file of appFiles.files) {
|
|
461
|
+
/** @type {string[]} */ (
|
|
462
|
+
filesByDir.get(path.dirname(file).replace(/^\.$/, ""))
|
|
463
|
+
).push(file);
|
|
464
|
+
}
|
|
465
|
+
for (const [dir, files] of filesByDir) {
|
|
466
|
+
if (dir !== "") {
|
|
467
|
+
lines.push(` SetOutPath ${installed(dir)}`);
|
|
468
|
+
}
|
|
469
|
+
for (const file of files) {
|
|
470
|
+
lines.push(` File "${p(path.join(appDir, file))}"`);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
lines.push(
|
|
475
|
+
' SetOutPath "$INSTDIR"',
|
|
476
|
+
' WriteUninstaller "$INSTDIR\\Uninstall.exe"',
|
|
477
|
+
"",
|
|
478
|
+
);
|
|
479
|
+
if (startMenuShortcut) {
|
|
480
|
+
lines.push(` CreateShortcut "$SMPROGRAMS\\${e(appName)}.lnk" ${exe}`);
|
|
481
|
+
}
|
|
482
|
+
if (desktopShortcut) {
|
|
483
|
+
lines.push(` CreateShortcut "$DESKTOP\\${e(appName)}.lnk" ${exe}`);
|
|
484
|
+
}
|
|
485
|
+
lines.push(
|
|
486
|
+
"",
|
|
487
|
+
` WriteRegStr HKCU ${uninstallKey} "DisplayName" "${e(appName)}"`,
|
|
488
|
+
` WriteRegStr HKCU ${uninstallKey} "DisplayVersion" "${e(version)}"`,
|
|
489
|
+
` WriteRegStr HKCU ${uninstallKey} "DisplayIcon" ${exe}`,
|
|
490
|
+
` WriteRegStr HKCU ${uninstallKey} "InstallLocation" "$INSTDIR"`,
|
|
491
|
+
` WriteRegStr HKCU ${uninstallKey} "UninstallString" '"$INSTDIR\\Uninstall.exe"'`,
|
|
492
|
+
` WriteRegStr HKCU ${uninstallKey} "QuietUninstallString" '"$INSTDIR\\Uninstall.exe" /S'`,
|
|
493
|
+
` WriteRegDWORD HKCU ${uninstallKey} "EstimatedSize" ${Math.ceil(appFiles.size / 1024)}`,
|
|
494
|
+
` WriteRegDWORD HKCU ${uninstallKey} "NoModify" 1`,
|
|
495
|
+
` WriteRegDWORD HKCU ${uninstallKey} "NoRepair" 1`,
|
|
496
|
+
);
|
|
497
|
+
if (publisher !== undefined) {
|
|
498
|
+
lines.push(
|
|
499
|
+
` WriteRegStr HKCU ${uninstallKey} "Publisher" "${e(publisher)}"`,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
lines.push(
|
|
503
|
+
"SectionEnd",
|
|
504
|
+
"",
|
|
505
|
+
"Function un.onInit",
|
|
506
|
+
" SetShellVarContext current",
|
|
507
|
+
"FunctionEnd",
|
|
508
|
+
"",
|
|
509
|
+
'Section "Uninstall"',
|
|
510
|
+
...waitForAppToClose(exe, appName, " "),
|
|
511
|
+
);
|
|
512
|
+
for (const file of appFiles.files) {
|
|
513
|
+
lines.push(` Delete ${installed(file)}`);
|
|
514
|
+
}
|
|
515
|
+
/* Children before parents, and `RMDir` without `/r` only removes empty directories. */
|
|
516
|
+
for (const dir of [...appFiles.dirs].reverse()) {
|
|
517
|
+
lines.push(` RMDir ${installed(dir)}`);
|
|
518
|
+
}
|
|
519
|
+
lines.push(
|
|
520
|
+
' Delete "$INSTDIR\\Uninstall.exe"',
|
|
521
|
+
' RMDir "$INSTDIR"',
|
|
522
|
+
` Delete "$SMPROGRAMS\\${e(appName)}.lnk"`,
|
|
523
|
+
` Delete "$DESKTOP\\${e(appName)}.lnk"`,
|
|
524
|
+
` DeleteRegKey HKCU ${uninstallKey}`,
|
|
525
|
+
"SectionEnd",
|
|
526
|
+
"",
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
return lines.join("\n");
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Generate NSIS lines that wait until the application isn't running, asking
|
|
534
|
+
* the user to close it, so its files can be replaced or deleted. Windows
|
|
535
|
+
* refuses to open a running executable for writing, which is how a running
|
|
536
|
+
* copy is detected. Cancelling - which silent mode does straight away -
|
|
537
|
+
* aborts the installer or uninstaller. Uses `$2`.
|
|
538
|
+
* @param {string} exePath Quoted NSIS string with the executable's path, eg. `"$INSTDIR\Demo.exe"`.
|
|
539
|
+
* @param {string} appName Application name, shown in the prompt.
|
|
540
|
+
* @param {string} indent Indentation of the generated lines.
|
|
541
|
+
* @returns {string[]}
|
|
542
|
+
*/
|
|
543
|
+
function waitForAppToClose(exePath, appName, indent) {
|
|
544
|
+
return [
|
|
545
|
+
"${Do}",
|
|
546
|
+
` \${IfNot} \${FileExists} ${exePath}`,
|
|
547
|
+
" ${Break}",
|
|
548
|
+
" ${EndIf}",
|
|
549
|
+
" ClearErrors",
|
|
550
|
+
` FileOpen $2 ${exePath} a`,
|
|
551
|
+
" ${IfNot} ${Errors}",
|
|
552
|
+
" FileClose $2",
|
|
553
|
+
" ${Break}",
|
|
554
|
+
" ${EndIf}",
|
|
555
|
+
` \${If} \${Cmd} \`MessageBox MB_RETRYCANCEL|MB_ICONEXCLAMATION "Close ${util.nsisEscape(appName)}, then select Retry." /SD IDCANCEL IDCANCEL\``,
|
|
556
|
+
" Abort",
|
|
557
|
+
" ${EndIf}",
|
|
558
|
+
"${Loop}",
|
|
559
|
+
].map((line) => `${indent}${line}`);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
export default nsis;
|
|
563
|
+
export { listAppFiles, nsisScript };
|
package/src/util.js
CHANGED
|
@@ -180,14 +180,107 @@ async function sha512(filePath) {
|
|
|
180
180
|
return hash.digest("base64");
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Escape `value` for use inside a double quoted NSIS string. NSIS expands `$`
|
|
185
|
+
* variables and `$\` escapes there, so `$` is doubled and `"` is escaped.
|
|
186
|
+
* Line breaks are rejected rather than escaped: a raw one would end the
|
|
187
|
+
* command and let the value inject further script lines. The result is not
|
|
188
|
+
* quoted, so it can be joined with NSIS variables, eg. `"$INSTDIR\<value>"`.
|
|
189
|
+
* @param {string} value
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
function nsisEscape(value) {
|
|
193
|
+
if (/[\r\n]/.test(value)) {
|
|
194
|
+
throw new Error(
|
|
195
|
+
`NSIS strings must not contain line breaks. Received: ${JSON.stringify(value)}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
/* In a replacement string `$$` is a literal `$`, so "$$$$" writes "$$". */
|
|
199
|
+
return value.replaceAll("$", "$$$$").replaceAll('"', '$\\"');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Check a path on the host for use inside a double quoted NSIS string that
|
|
204
|
+
* `makensis` reads at compile time, eg. a `File` source or `OutFile`. Unlike
|
|
205
|
+
* runtime strings, those keep `$` literally, so nothing is escaped. Instead,
|
|
206
|
+
* the sequences `makensis` would still interpret are rejected: `${define}`
|
|
207
|
+
* expansions and `$\` escapes, and `"` - an escaped `$\"` doesn't survive
|
|
208
|
+
* being passed through a `!define`, as the icon and license paths are.
|
|
209
|
+
* @param {string} value
|
|
210
|
+
* @returns {string} `value`, unchanged.
|
|
211
|
+
*/
|
|
212
|
+
function nsisPath(value) {
|
|
213
|
+
if (/[\r\n"]/.test(value) || value.includes("${") || value.includes("$\\")) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Paths passed to NSIS must not contain line breaks, '"', "\${" or "$\\". Received: ${JSON.stringify(value)}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Names Windows reserves for devices, with or without an extension.
|
|
223
|
+
* @type {RegExp}
|
|
224
|
+
*/
|
|
225
|
+
const WINDOWS_RESERVED_NAME = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check that `value` can be used as a single Windows file name: no path
|
|
229
|
+
* separators, characters Windows forbids in file names, control characters,
|
|
230
|
+
* trailing dots or spaces, or reserved device names.
|
|
231
|
+
* @param {string} value
|
|
232
|
+
* @returns {boolean}
|
|
233
|
+
*/
|
|
234
|
+
function isWindowsFileName(value) {
|
|
235
|
+
const hasControlCharacter = [...value].some((character) => {
|
|
236
|
+
const code = character.charCodeAt(0);
|
|
237
|
+
return code < 0x20 || code === 0x7f;
|
|
238
|
+
});
|
|
239
|
+
return (
|
|
240
|
+
value !== "" &&
|
|
241
|
+
/[<>:"/\\|?*]/.test(value) === false &&
|
|
242
|
+
hasControlCharacter === false &&
|
|
243
|
+
/[. ]$/.test(value) === false &&
|
|
244
|
+
WINDOWS_RESERVED_NAME.test(value) === false
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Convert a version such as `"1.2.3"` or `"1.2.3-beta.1"` into the
|
|
250
|
+
* `X.X.X.X` form Windows version resources (and NSIS' `VIProductVersion`)
|
|
251
|
+
* require. Up to four leading numeric parts are kept, missing ones are
|
|
252
|
+
* filled with `0`, and any prerelease or build suffix is dropped.
|
|
253
|
+
* @param {string} version
|
|
254
|
+
* @returns {string}
|
|
255
|
+
*/
|
|
256
|
+
function windowsVersion(version) {
|
|
257
|
+
const match = version.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:\.(\d+))?/);
|
|
258
|
+
if (match === null) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Expected a version starting with a number, eg. "1.2.3". Received: ${JSON.stringify(version)}`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
const parts = match.slice(1).map((part) => Number(part ?? 0));
|
|
264
|
+
if (parts.some((part) => part > 65535)) {
|
|
265
|
+
throw new Error(
|
|
266
|
+
`Expected each part of the version to be at most 65535. Received: ${JSON.stringify(version)}`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return parts.join(".");
|
|
270
|
+
}
|
|
271
|
+
|
|
183
272
|
export default {
|
|
184
273
|
APPIMAGE_ARCH_KV,
|
|
185
274
|
desktopExecArg,
|
|
186
275
|
fileExists,
|
|
276
|
+
isWindowsFileName,
|
|
277
|
+
nsisEscape,
|
|
278
|
+
nsisPath,
|
|
187
279
|
parseDesktopEntry,
|
|
188
280
|
sha512,
|
|
189
281
|
shellQuote,
|
|
190
282
|
stringifyDesktopEntry,
|
|
191
283
|
stringifyYaml,
|
|
192
284
|
updateInfoFileName,
|
|
285
|
+
windowsVersion,
|
|
193
286
|
};
|
package/types/appImage.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export default appImage;
|
|
1
2
|
export type AppImageOptions = {
|
|
2
3
|
/**
|
|
3
4
|
* Path to a built NW.js Linux application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "linux"`.
|
|
@@ -10,48 +11,53 @@ export type AppImageOptions = {
|
|
|
10
11
|
/**
|
|
11
12
|
* Path to a `.png` or `.svg` icon. Defaults to the `Icon` value read from `<appDir>/<appName>.desktop`.
|
|
12
13
|
*/
|
|
13
|
-
icon?: string;
|
|
14
|
+
icon?: string | undefined;
|
|
14
15
|
/**
|
|
15
16
|
* Target architecture. Defaults to the host architecture. `appimagetool` runs as a native binary, so this must match the host unless the host has emulation (eg. QEMU/binfmt) configured for the target architecture.
|
|
16
17
|
*/
|
|
17
|
-
arch?: "ia32" | "x64" | "arm64";
|
|
18
|
+
arch?: "ia32" | "x64" | "arm64" | undefined;
|
|
18
19
|
/**
|
|
19
20
|
* Directory the resulting `.AppImage` file is written to. Defaults to the parent directory of `appDir`.
|
|
20
21
|
*/
|
|
21
|
-
outDir?: string;
|
|
22
|
+
outDir?: string | undefined;
|
|
22
23
|
/**
|
|
23
24
|
* Directory used to cache the downloaded `appimagetool` binary.
|
|
24
25
|
*/
|
|
25
|
-
cacheDir?: string;
|
|
26
|
+
cacheDir?: string | undefined;
|
|
26
27
|
/**
|
|
27
28
|
* If true, reuse a cached `appimagetool` binary. Otherwise redownload it.
|
|
28
29
|
*/
|
|
29
|
-
cache?: boolean;
|
|
30
|
+
cache?: boolean | undefined;
|
|
30
31
|
/**
|
|
31
32
|
* Base URL `appimagetool-<arch>.AppImage` is downloaded from. Defaults to the `AppImage/appimagetool` "continuous" GitHub release. Must be https, except for localhost.
|
|
32
33
|
*/
|
|
33
|
-
appImageToolUrl?: string;
|
|
34
|
+
appImageToolUrl?: string | undefined;
|
|
34
35
|
/**
|
|
35
36
|
* If true, embed a GPG signature in the AppImage. Requires `gpg` and a usable secret key on the host. A passphrase-protected key reads its passphrase from the `APPIMAGETOOL_SIGN_PASSPHRASE` environment variable.
|
|
36
37
|
*/
|
|
37
|
-
sign?: boolean;
|
|
38
|
+
sign?: boolean | undefined;
|
|
38
39
|
/**
|
|
39
40
|
* ID of the GPG key to sign with. Defaults to `gpg`'s default secret key. Requires `sign` to be true.
|
|
40
41
|
*/
|
|
41
|
-
signKey?: string;
|
|
42
|
+
signKey?: string | undefined;
|
|
42
43
|
/**
|
|
43
44
|
* 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.
|
|
44
45
|
*/
|
|
45
|
-
version?: string;
|
|
46
|
+
version?: string | undefined;
|
|
46
47
|
/**
|
|
47
48
|
* Where releases are published. When set, an `app-update.yml` pointing at it is embedded in the AppImage and a `latest-linux[-<arch>].yml` update info file is written next to it, for `@nwutils/updater` to self update from.
|
|
48
49
|
*/
|
|
49
|
-
publish?: PublishOptions;
|
|
50
|
+
publish?: PublishOptions | undefined;
|
|
50
51
|
/**
|
|
51
52
|
* AppImage update information for zsync based updaters (eg. AppImageUpdate, `appimageupdatetool`, AppImage managers). `true` derives it from `publish`. A string is embedded as-is, eg. `"gh-releases-zsync|owner|repo|latest|Demo-x86_64.AppImage.zsync"`. Requires `zsyncmake` on the host to produce the `.zsync` file.
|
|
52
53
|
*/
|
|
53
|
-
updateInformation?: string | boolean;
|
|
54
|
+
updateInformation?: string | boolean | undefined;
|
|
54
55
|
};
|
|
56
|
+
/**
|
|
57
|
+
* Publish to a GitHub repository's releases. The AppImage and update info
|
|
58
|
+
* file must be uploaded as assets of the latest (non draft, non prerelease)
|
|
59
|
+
* release.
|
|
60
|
+
*/
|
|
55
61
|
export type GitHubPublishOptions = {
|
|
56
62
|
provider: "github";
|
|
57
63
|
/**
|
|
@@ -63,6 +69,10 @@ export type GitHubPublishOptions = {
|
|
|
63
69
|
*/
|
|
64
70
|
repo: string;
|
|
65
71
|
};
|
|
72
|
+
/**
|
|
73
|
+
* Publish to any static file server. The AppImage and update info file must
|
|
74
|
+
* be served from `url`.
|
|
75
|
+
*/
|
|
66
76
|
export type GenericPublishOptions = {
|
|
67
77
|
provider: "generic";
|
|
68
78
|
/**
|
|
@@ -128,11 +138,9 @@ declare function appImage({ appDir, appName, icon, arch, outDir, cacheDir, cache
|
|
|
128
138
|
* @param {boolean} options.cache If true, reuse a cached binary. Otherwise redownload it.
|
|
129
139
|
* @returns {Promise<string>} Path to an executable `appimagetool` binary.
|
|
130
140
|
*/
|
|
131
|
-
|
|
141
|
+
export function getAppImageTool({ appImageToolArch, appImageToolUrl, cacheDir, cache, }: {
|
|
132
142
|
appImageToolArch: string;
|
|
133
143
|
appImageToolUrl: string;
|
|
134
144
|
cacheDir: string;
|
|
135
145
|
cache: boolean;
|
|
136
146
|
}): Promise<string>;
|
|
137
|
-
export default appImage;
|
|
138
|
-
export { getAppImageTool };
|
package/types/index.d.ts
CHANGED
|
@@ -1,12 +1,24 @@
|
|
|
1
|
+
export default packager;
|
|
2
|
+
/**
|
|
3
|
+
* Supported packaged output formats.
|
|
4
|
+
*
|
|
5
|
+
* `AppImage`, `deb` and `rpm` package a Linux build. `MSIX` and `NSIS`
|
|
6
|
+
* package a Windows build. `"AppImage"` and `"nsis"` are implemented today -
|
|
7
|
+
* the others are reserved so this option doesn't need to change shape once
|
|
8
|
+
* they land.
|
|
9
|
+
*/
|
|
1
10
|
export type PackageFormat = "AppImage" | "deb" | "rpm" | "msix" | "nsis" | "dmg";
|
|
11
|
+
/**
|
|
12
|
+
* Where releases are published, for packagers that support self updating.
|
|
13
|
+
*/
|
|
2
14
|
export type PublishOptions = import("./appImage.js").PublishOptions;
|
|
3
15
|
/**
|
|
4
16
|
* Supported packaged output formats.
|
|
5
17
|
*
|
|
6
18
|
* `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.
|
|
19
|
+
* package a Windows build. `"AppImage"` and `"nsis"` are implemented today -
|
|
20
|
+
* the others are reserved so this option doesn't need to change shape once
|
|
21
|
+
* they land.
|
|
10
22
|
* @typedef {"AppImage" | "deb" | "rpm" | "msix" | "nsis" | "dmg"} PackageFormat
|
|
11
23
|
*/
|
|
12
24
|
/**
|
|
@@ -17,10 +29,9 @@ export type PublishOptions = import("./appImage.js").PublishOptions;
|
|
|
17
29
|
* Package a built NW.js application.
|
|
18
30
|
* @async
|
|
19
31
|
* @function
|
|
20
|
-
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage`) expects.
|
|
32
|
+
* @param {{format: PackageFormat} & Record<string, any>} options `format`, plus the options the matching packager (eg. `appImage` or `nsis`) expects.
|
|
21
33
|
* @returns {Promise<string>} Path to the resulting packaged artifact.
|
|
22
34
|
*/
|
|
23
35
|
declare function packager({ format, ...options }: {
|
|
24
36
|
format: PackageFormat;
|
|
25
37
|
} & Record<string, any>): Promise<string>;
|
|
26
|
-
export default packager;
|
package/types/nsis.d.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
export default nsis;
|
|
2
|
+
export type NsisOptions = {
|
|
3
|
+
/**
|
|
4
|
+
* Path to a built NW.js Windows application, ie. the `outDir` produced by `@nwutils/builder` for `platform: "win"`.
|
|
5
|
+
*/
|
|
6
|
+
appDir: string;
|
|
7
|
+
/**
|
|
8
|
+
* Name of the application. Must match the `app.name` value used to build `appDir` - the executable is expected at `<appDir>/<appName>.exe`. Must be a valid Windows file name.
|
|
9
|
+
*/
|
|
10
|
+
appName: string;
|
|
11
|
+
/**
|
|
12
|
+
* Version of the application, eg. `"1.2.0"`. Shown in Windows' installed apps list, and converted to `X.X.X.X` for the installer's own version resource.
|
|
13
|
+
*/
|
|
14
|
+
version: string;
|
|
15
|
+
/**
|
|
16
|
+
* Architecture `appDir` was built for. Defaults to the host architecture. The installer refuses to run on Windows machines that can't run that architecture.
|
|
17
|
+
*/
|
|
18
|
+
arch?: "ia32" | "x64" | "arm64" | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Directory the resulting `<appName>-<arch>-Setup.exe` file is written to. Defaults to the parent directory of `appDir`.
|
|
21
|
+
*/
|
|
22
|
+
outDir?: string | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Path to a `.ico` icon for the installer and uninstaller. Defaults to NSIS' own icon.
|
|
25
|
+
*/
|
|
26
|
+
icon?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Name of the registry key the installation is recorded under. Defaults to `appName`. Keep it the same across releases, so installing a new version replaces the previous one.
|
|
29
|
+
*/
|
|
30
|
+
appId?: string | undefined;
|
|
31
|
+
/**
|
|
32
|
+
* Publisher shown in Windows' installed apps list.
|
|
33
|
+
*/
|
|
34
|
+
publisher?: string | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Copyright notice written into the installer's version information.
|
|
37
|
+
*/
|
|
38
|
+
copyright?: string | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* Path to a `.txt` or `.rtf` license file. When set, the installer shows it on a page the user has to accept.
|
|
41
|
+
*/
|
|
42
|
+
license?: string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* If true, create a desktop shortcut.
|
|
45
|
+
*/
|
|
46
|
+
desktopShortcut?: boolean | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* If true, create a Start Menu shortcut.
|
|
49
|
+
*/
|
|
50
|
+
startMenuShortcut?: boolean | undefined;
|
|
51
|
+
/**
|
|
52
|
+
* Path to the NSIS compiler. Defaults to `makensis` on the `PATH`.
|
|
53
|
+
*/
|
|
54
|
+
makensisPath?: string | undefined;
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* The files and directories of a built application, relative to its root.
|
|
58
|
+
*/
|
|
59
|
+
export type AppFiles = {
|
|
60
|
+
/**
|
|
61
|
+
* Relative file paths, using the host's path separator.
|
|
62
|
+
*/
|
|
63
|
+
files: string[];
|
|
64
|
+
/**
|
|
65
|
+
* Relative directory paths, parents before children.
|
|
66
|
+
*/
|
|
67
|
+
dirs: string[];
|
|
68
|
+
/**
|
|
69
|
+
* Total size of `files` in bytes.
|
|
70
|
+
*/
|
|
71
|
+
size: number;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Package a built NW.js Windows application as an NSIS installer.
|
|
75
|
+
*
|
|
76
|
+
* The installer installs for the current user only, into
|
|
77
|
+
* `%LOCALAPPDATA%\Programs\<appName>`, so it never asks for administrator
|
|
78
|
+
* rights. It registers an uninstaller in Windows' installed apps list, and
|
|
79
|
+
* installing a new version first uninstalls the previous one. Both wait for
|
|
80
|
+
* the application to be closed before touching its files.
|
|
81
|
+
*
|
|
82
|
+
* This composes with `@nwutils/builder`: run it against the `outDir` (and
|
|
83
|
+
* `app.name`) that `@nwutils/builder` produced for `platform: "win"`.
|
|
84
|
+
* `makensis` runs on Linux and macOS too, so the installer can be built on
|
|
85
|
+
* any host.
|
|
86
|
+
* @async
|
|
87
|
+
* @function
|
|
88
|
+
* @param {NsisOptions} options
|
|
89
|
+
* @returns {Promise<string>} Path to the resulting installer `.exe` file.
|
|
90
|
+
*/
|
|
91
|
+
declare function nsis({ appDir, appName, version, arch, outDir, icon, appId, publisher, copyright, license, desktopShortcut, startMenuShortcut, makensisPath, }: NsisOptions): Promise<string>;
|
|
92
|
+
/**
|
|
93
|
+
* List the files and directories under `appDir`. Symbolic links are followed,
|
|
94
|
+
* since NSIS copies what they point at.
|
|
95
|
+
* @async
|
|
96
|
+
* @function
|
|
97
|
+
* @param {string} appDir Absolute path to the built application.
|
|
98
|
+
* @returns {Promise<AppFiles>}
|
|
99
|
+
*/
|
|
100
|
+
export function listAppFiles(appDir: string): Promise<AppFiles>;
|
|
101
|
+
/**
|
|
102
|
+
* Generate the NSIS script for an installer.
|
|
103
|
+
*
|
|
104
|
+
* Every file is listed explicitly rather than copied with `File /r`, so the
|
|
105
|
+
* uninstaller deletes exactly the files that were installed. The user can
|
|
106
|
+
* choose any install directory - including one such as `C:\` that holds
|
|
107
|
+
* other files - so the uninstaller never deletes directories recursively.
|
|
108
|
+
* @param {object} options
|
|
109
|
+
* @param {string} options.appDir Absolute path to the built application.
|
|
110
|
+
* @param {AppFiles} options.appFiles Files and directories under `appDir`.
|
|
111
|
+
* @param {string} options.appName
|
|
112
|
+
* @param {string} options.appId
|
|
113
|
+
* @param {string} options.version
|
|
114
|
+
* @param {string} options.fileVersion `version` in `X.X.X.X` form.
|
|
115
|
+
* @param {"ia32" | "x64" | "arm64"} options.arch
|
|
116
|
+
* @param {string} options.installerPath Absolute path the installer is written to.
|
|
117
|
+
* @param {string} [options.iconPath] Absolute path to a `.ico` file.
|
|
118
|
+
* @param {string} [options.licensePath] Absolute path to a `.txt` or `.rtf` file.
|
|
119
|
+
* @param {string} [options.publisher]
|
|
120
|
+
* @param {string} options.copyright
|
|
121
|
+
* @param {boolean} options.desktopShortcut
|
|
122
|
+
* @param {boolean} options.startMenuShortcut
|
|
123
|
+
* @returns {string}
|
|
124
|
+
*/
|
|
125
|
+
export function nsisScript({ appDir, appFiles, appName, appId, version, fileVersion, arch, installerPath, iconPath, licensePath, publisher, copyright, desktopShortcut, startMenuShortcut, }: {
|
|
126
|
+
appDir: string;
|
|
127
|
+
appFiles: AppFiles;
|
|
128
|
+
appName: string;
|
|
129
|
+
appId: string;
|
|
130
|
+
version: string;
|
|
131
|
+
fileVersion: string;
|
|
132
|
+
arch: "ia32" | "x64" | "arm64";
|
|
133
|
+
installerPath: string;
|
|
134
|
+
iconPath?: string | undefined;
|
|
135
|
+
licensePath?: string | undefined;
|
|
136
|
+
publisher?: string | undefined;
|
|
137
|
+
copyright: string;
|
|
138
|
+
desktopShortcut: boolean;
|
|
139
|
+
startMenuShortcut: boolean;
|
|
140
|
+
}): string;
|
package/types/util.d.ts
CHANGED
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
declare namespace _default {
|
|
2
|
+
export { APPIMAGE_ARCH_KV };
|
|
3
|
+
export { desktopExecArg };
|
|
4
|
+
export { fileExists };
|
|
5
|
+
export { isWindowsFileName };
|
|
6
|
+
export { nsisEscape };
|
|
7
|
+
export { nsisPath };
|
|
8
|
+
export { parseDesktopEntry };
|
|
9
|
+
export { sha512 };
|
|
10
|
+
export { shellQuote };
|
|
11
|
+
export { stringifyDesktopEntry };
|
|
12
|
+
export { stringifyYaml };
|
|
13
|
+
export { updateInfoFileName };
|
|
14
|
+
export { windowsVersion };
|
|
15
|
+
}
|
|
16
|
+
export default _default;
|
|
17
|
+
/**
|
|
18
|
+
* Map nw-builder's architecture identifiers to the identifiers used in
|
|
19
|
+
* `appimagetool` release asset names and its `ARCH` environment variable.
|
|
20
|
+
* @type {Record<"ia32" | "x64" | "arm64", string>}
|
|
21
|
+
*/
|
|
22
|
+
declare const APPIMAGE_ARCH_KV: Record<"ia32" | "x64" | "arm64", string>;
|
|
23
|
+
/**
|
|
24
|
+
* Quote `value` as a single argument of a desktop entry's `Exec` key, per the
|
|
25
|
+
* Desktop Entry Specification: `%` is doubled so it isn't read as a field
|
|
26
|
+
* code, an argument with reserved characters is double quoted with `"`, `` ` ``,
|
|
27
|
+
* `$` and `\` backslash-escaped, and finally backslashes are escaped again as
|
|
28
|
+
* required for every string value.
|
|
29
|
+
* @param {string} value
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
declare function desktopExecArg(value: string): string;
|
|
1
33
|
/**
|
|
2
34
|
* Check if a file or directory exists.
|
|
3
35
|
* @async
|
|
@@ -6,6 +38,35 @@
|
|
|
6
38
|
* @returns {Promise<boolean>}
|
|
7
39
|
*/
|
|
8
40
|
declare function fileExists(filePath: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Check that `value` can be used as a single Windows file name: no path
|
|
43
|
+
* separators, characters Windows forbids in file names, control characters,
|
|
44
|
+
* trailing dots or spaces, or reserved device names.
|
|
45
|
+
* @param {string} value
|
|
46
|
+
* @returns {boolean}
|
|
47
|
+
*/
|
|
48
|
+
declare function isWindowsFileName(value: string): boolean;
|
|
49
|
+
/**
|
|
50
|
+
* Escape `value` for use inside a double quoted NSIS string. NSIS expands `$`
|
|
51
|
+
* variables and `$\` escapes there, so `$` is doubled and `"` is escaped.
|
|
52
|
+
* Line breaks are rejected rather than escaped: a raw one would end the
|
|
53
|
+
* command and let the value inject further script lines. The result is not
|
|
54
|
+
* quoted, so it can be joined with NSIS variables, eg. `"$INSTDIR\<value>"`.
|
|
55
|
+
* @param {string} value
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
declare function nsisEscape(value: string): string;
|
|
59
|
+
/**
|
|
60
|
+
* Check a path on the host for use inside a double quoted NSIS string that
|
|
61
|
+
* `makensis` reads at compile time, eg. a `File` source or `OutFile`. Unlike
|
|
62
|
+
* runtime strings, those keep `$` literally, so nothing is escaped. Instead,
|
|
63
|
+
* the sequences `makensis` would still interpret are rejected: `${define}`
|
|
64
|
+
* expansions and `$\` escapes, and `"` - an escaped `$\"` doesn't survive
|
|
65
|
+
* being passed through a `!define`, as the icon and license paths are.
|
|
66
|
+
* @param {string} value
|
|
67
|
+
* @returns {string} `value`, unchanged.
|
|
68
|
+
*/
|
|
69
|
+
declare function nsisPath(value: string): string;
|
|
9
70
|
/**
|
|
10
71
|
* Parse a freedesktop.org desktop entry file's `[Desktop Entry]` group into
|
|
11
72
|
* a key/value map. Other groups (eg. `[Desktop Action ...]`) are ignored.
|
|
@@ -14,11 +75,14 @@ declare function fileExists(filePath: string): Promise<boolean>;
|
|
|
14
75
|
*/
|
|
15
76
|
declare function parseDesktopEntry(contents: string): Record<string, string>;
|
|
16
77
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* @
|
|
78
|
+
* Compute the base64 encoded SHA-512 digest of the file at `filePath`, the
|
|
79
|
+
* same encoding electron-builder writes into its update info files.
|
|
80
|
+
* @async
|
|
81
|
+
* @function
|
|
82
|
+
* @param {string} filePath
|
|
83
|
+
* @returns {Promise<string>}
|
|
20
84
|
*/
|
|
21
|
-
declare function
|
|
85
|
+
declare function sha512(filePath: string): Promise<string>;
|
|
22
86
|
/**
|
|
23
87
|
* Quote `value` as a single POSIX shell word. Inside single quotes nothing is
|
|
24
88
|
* expanded, so only single quotes themselves need escaping.
|
|
@@ -27,15 +91,19 @@ declare function stringifyDesktopEntry(entries: Record<string, string | undefine
|
|
|
27
91
|
*/
|
|
28
92
|
declare function shellQuote(value: string): string;
|
|
29
93
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* code, an argument with reserved characters is double quoted with `"`, `` ` ``,
|
|
33
|
-
* `$` and `\` backslash-escaped, and finally backslashes are escaped again as
|
|
34
|
-
* required for every string value.
|
|
35
|
-
* @param {string} value
|
|
94
|
+
* Serialise a `[Desktop Entry]` key/value map back into a desktop entry file.
|
|
95
|
+
* @param {Record<string, string | undefined>} entries
|
|
36
96
|
* @returns {string}
|
|
37
97
|
*/
|
|
38
|
-
declare function
|
|
98
|
+
declare function stringifyDesktopEntry(entries: Record<string, string | undefined>): string;
|
|
99
|
+
/**
|
|
100
|
+
* Serialise a map of scalars, and lists of maps of scalars, as YAML. This is
|
|
101
|
+
* the only shape the update info and `app-update.yml` files use, so it avoids
|
|
102
|
+
* pulling in a YAML library for a handful of lines.
|
|
103
|
+
* @param {Record<string, string | number | boolean | Record<string, string | number | boolean>[] | undefined>} entries
|
|
104
|
+
* @returns {string}
|
|
105
|
+
*/
|
|
106
|
+
declare function stringifyYaml(entries: Record<string, string | number | boolean | Record<string, string | number | boolean>[] | undefined>): string;
|
|
39
107
|
/**
|
|
40
108
|
* Name of the update info file `@nwutils/updater` downloads to find the
|
|
41
109
|
* latest release, following electron-builder's naming: `x64` gets the bare
|
|
@@ -45,31 +113,11 @@ declare function desktopExecArg(value: string): string;
|
|
|
45
113
|
*/
|
|
46
114
|
declare function updateInfoFileName(arch: "ia32" | "x64" | "arm64"): string;
|
|
47
115
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
116
|
+
* Convert a version such as `"1.2.3"` or `"1.2.3-beta.1"` into the
|
|
117
|
+
* `X.X.X.X` form Windows version resources (and NSIS' `VIProductVersion`)
|
|
118
|
+
* require. Up to four leading numeric parts are kept, missing ones are
|
|
119
|
+
* filled with `0`, and any prerelease or build suffix is dropped.
|
|
120
|
+
* @param {string} version
|
|
52
121
|
* @returns {string}
|
|
53
122
|
*/
|
|
54
|
-
declare function
|
|
55
|
-
/**
|
|
56
|
-
* Compute the base64 encoded SHA-512 digest of the file at `filePath`, the
|
|
57
|
-
* same encoding electron-builder writes into its update info files.
|
|
58
|
-
* @async
|
|
59
|
-
* @function
|
|
60
|
-
* @param {string} filePath
|
|
61
|
-
* @returns {Promise<string>}
|
|
62
|
-
*/
|
|
63
|
-
declare function sha512(filePath: string): Promise<string>;
|
|
64
|
-
declare const _default: {
|
|
65
|
-
APPIMAGE_ARCH_KV: Record<"arm64" | "ia32" | "x64", string>;
|
|
66
|
-
desktopExecArg: typeof desktopExecArg;
|
|
67
|
-
fileExists: typeof fileExists;
|
|
68
|
-
parseDesktopEntry: typeof parseDesktopEntry;
|
|
69
|
-
sha512: typeof sha512;
|
|
70
|
-
shellQuote: typeof shellQuote;
|
|
71
|
-
stringifyDesktopEntry: typeof stringifyDesktopEntry;
|
|
72
|
-
stringifyYaml: typeof stringifyYaml;
|
|
73
|
-
updateInfoFileName: typeof updateInfoFileName;
|
|
74
|
-
};
|
|
75
|
-
export default _default;
|
|
123
|
+
declare function windowsVersion(version: string): string;
|