@nwutils/packager 0.7.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 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 | Planned |
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. This follows electron-builder's approach:
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
@@ -174,14 +174,71 @@ running application to exit instead of asking the user to close it, updates it
174
174
  silently, and starts the new version. Because it installs for the current user,
175
175
  updating never asks for administrator rights.
176
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
+
177
234
  ## API Reference
178
235
 
179
236
  ### `packager({ format, ...options })`
180
237
 
181
- | Name | Type | Description |
182
- | ------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------- |
183
- | format | `"AppImage" \| "deb" \| "rpm" \| "msix" \| "nsis" \| "dmg"` | Which packager to run. `"AppImage"` and `"nsis"` are implemented today. |
184
- | ... | - | The remaining options are passed through to the packager for `format` as-is. |
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. |
185
242
 
186
243
  Resolves with the path to the resulting packaged artifact.
187
244
 
@@ -230,6 +287,26 @@ Options
230
287
 
231
288
  Resolves with the path to the resulting installer `.exe` file.
232
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
+
233
310
  ## Contributing
234
311
 
235
312
  ### External contributor
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nwutils/packager",
3
- "version": "0.7.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/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. `"AppImage"` and `"nsis"` are implemented today -
9
- * the others are reserved so this option doesn't need to change shape once
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 `nsis`) expects.
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 "nsis" are supported.`,
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/util.js CHANGED
@@ -116,15 +116,20 @@ function desktopExecArg(value) {
116
116
 
117
117
  /**
118
118
  * Name of the update info file `@nwutils/updater` downloads for `arch` on
119
- * `platform`, matching electron-builder: `x64` gets the bare
120
- * `latest-linux.yml` (Linux) or `latest.yml` (Windows), every other
121
- * architecture gets an `-<arch>` suffix.
122
- * @param {string} arch
123
- * @param {"linux" | "win"} [platform="linux"]
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"]
124
124
  * @returns {string}
125
125
  */
126
126
  function updateInfoFileName(arch, platform = "linux") {
127
- const base = platform === "win" ? "latest" : "latest-linux";
127
+ const base =
128
+ platform === "win"
129
+ ? "latest"
130
+ : platform === "osx"
131
+ ? "latest-mac"
132
+ : "latest-linux";
128
133
  return arch === "x64" ? `${base}.yml` : `${base}-${arch}.yml`;
129
134
  }
130
135
 
@@ -169,8 +174,7 @@ function stringifyYaml(entries) {
169
174
  }
170
175
 
171
176
  /**
172
- * Compute the base64 encoded SHA-512 digest of the file at `filePath`, the
173
- * same encoding electron-builder writes into its update info files.
177
+ * Compute the base64 encoded SHA-512 digest of the file at `filePath`.
174
178
  * @async
175
179
  * @function
176
180
  * @param {string} filePath
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. `"AppImage"` and `"nsis"` are implemented today -
7
- * the others are reserved so this option doesn't need to change shape once
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. `"AppImage"` and `"nsis"` are implemented today -
20
- * the others are reserved so this option doesn't need to change shape once
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 `nsis`) expects.
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/util.d.ts CHANGED
@@ -85,8 +85,7 @@ declare function nsisPath(value: string): string;
85
85
  */
86
86
  declare function parseDesktopEntry(contents: string): Record<string, string>;
87
87
  /**
88
- * Compute the base64 encoded SHA-512 digest of the file at `filePath`, the
89
- * same encoding electron-builder writes into its update info files.
88
+ * Compute the base64 encoded SHA-512 digest of the file at `filePath`.
90
89
  * @async
91
90
  * @function
92
91
  * @param {string} filePath
@@ -116,14 +115,14 @@ declare function stringifyDesktopEntry(entries: Record<string, string | undefine
116
115
  declare function stringifyYaml(entries: Record<string, string | number | boolean | Record<string, string | number | boolean>[] | undefined>): string;
117
116
  /**
118
117
  * Name of the update info file `@nwutils/updater` downloads for `arch` on
119
- * `platform`, matching electron-builder: `x64` gets the bare
120
- * `latest-linux.yml` (Linux) or `latest.yml` (Windows), every other
121
- * architecture gets an `-<arch>` suffix.
122
- * @param {string} arch
123
- * @param {"linux" | "win"} [platform="linux"]
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"]
124
123
  * @returns {string}
125
124
  */
126
- declare function updateInfoFileName(arch: string, platform?: "linux" | "win"): string;
125
+ declare function updateInfoFileName(arch: string, platform?: "linux" | "win" | "osx"): string;
127
126
  /**
128
127
  * Throw if `publish` isn't a valid `PublishOptions` object.
129
128
  * @param {unknown} publish