Skip to content

Commit a8bf057

Browse files
committed
Require Node.js 14
1 parent 228d8f8 commit a8bf057

File tree

6 files changed

+133
-116
lines changed

6 files changed

+133
-116
lines changed

cli.js

+33-27
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
#!/usr/bin/env node
2-
'use strict';
3-
const path = require('path');
4-
const fs = require('fs');
5-
const meow = require('meow');
6-
const appdmg = require('appdmg');
7-
const plist = require('plist');
8-
const Ora = require('ora');
9-
const execa = require('execa');
10-
const addLicenseAgreementIfNeeded = require('./sla');
11-
const composeIcon = require('./compose-icon');
2+
import process from 'node:process';
3+
import path from 'node:path';
4+
import fs from 'node:fs';
5+
import {fileURLToPath} from 'node:url';
6+
import meow from 'meow';
7+
import appdmg from 'appdmg';
8+
import plist from 'plist';
9+
import Ora from 'ora';
10+
import {execa} from 'execa';
11+
import addLicenseAgreementIfNeeded from './sla.js';
12+
import composeIcon from './compose-icon.js';
13+
14+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
1215

1316
if (process.platform !== 'darwin') {
1417
console.error('macOS only');
@@ -28,17 +31,18 @@ const cli = meow(`
2831
$ create-dmg 'Lungo.app'
2932
$ create-dmg 'Lungo.app' Build/Releases
3033
`, {
34+
importMeta: import.meta,
3135
flags: {
3236
overwrite: {
33-
type: 'boolean'
37+
type: 'boolean',
3438
},
3539
identity: {
36-
type: 'string'
40+
type: 'string',
3741
},
3842
dmgTitle: {
39-
type: 'string'
40-
}
41-
}
43+
type: 'string',
44+
},
45+
},
4246
});
4347

4448
let [appPath, destinationPath] = cli.input;
@@ -73,7 +77,7 @@ async function init() {
7377
let appInfo;
7478
try {
7579
appInfo = plist.parse(infoPlist);
76-
} catch (_) {
80+
} catch {
7781
const {stdout} = await execa('/usr/bin/plutil', ['-convert', 'xml1', '-o', '-', infoPlistPath]);
7882
appInfo = plist.parse(stdout);
7983
}
@@ -95,7 +99,7 @@ async function init() {
9599
if (cli.flags.overwrite) {
96100
try {
97101
fs.unlinkSync(dmgPath);
98-
} catch (_) {}
102+
} catch {}
99103
}
100104

101105
const hasAppIcon = appInfo.CFBundleIconFile;
@@ -126,24 +130,24 @@ async function init() {
126130
window: {
127131
size: {
128132
width: 660,
129-
height: 400
130-
}
133+
height: 400,
134+
},
131135
},
132136
contents: [
133137
{
134138
x: 180,
135139
y: 170,
136140
type: 'file',
137-
path: appPath
141+
path: appPath,
138142
},
139143
{
140144
x: 480,
141145
y: 170,
142146
type: 'link',
143-
path: '/Applications'
144-
}
145-
]
146-
}
147+
path: '/Applications',
148+
},
149+
],
150+
},
147151
});
148152

149153
ee.on('progress', info => {
@@ -177,7 +181,7 @@ async function init() {
177181
}
178182

179183
if (!identity) {
180-
const error = new Error();
184+
const error = new Error(); // eslint-disable-line unicorn/error-message
181185
error.stderr = 'No suitable code signing identity found';
182186
throw error;
183187
}
@@ -205,7 +209,9 @@ async function init() {
205209
});
206210
}
207211

208-
init().catch(error => {
212+
try {
213+
await init();
214+
} catch (error) {
209215
ora.fail((error && error.stack) || error);
210216
process.exit(1);
211-
});
217+
}

compose-icon.js

+26-20
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
1-
const fs = require('fs');
2-
const {promisify} = require('util');
3-
const execa = require('execa');
4-
const tempy = require('tempy');
5-
const gm = require('gm').subClass({imageMagick: true});
6-
const icns = require('icns-lib');
7-
1+
import {Buffer} from 'node:buffer';
2+
import fs from 'node:fs';
3+
import {promisify} from 'node:util';
4+
import path from 'node:path';
5+
import {fileURLToPath} from 'node:url';
6+
import {execa} from 'execa';
7+
import {temporaryFile} from 'tempy';
8+
import baseGm from 'gm';
9+
import icns from 'icns-lib';
10+
11+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
12+
13+
const gm = baseGm.subClass({imageMagick: true});
814
const readFile = promisify(fs.readFile);
915
const writeFile = promisify(fs.writeFile);
1016

11-
const filterMap = (map, filterFn) => Object.entries(map).filter(filterFn).reduce((out, [key, item]) => ({...out, [key]: item}), {});
17+
const filterMap = (map, filterFunction) => Object.fromEntries(Object.entries(map).filter(element => filterFunction(element)).map(([key, item]) => [key, item]));
1218

1319
// Drive icon from `/System/Library/Extensions/IOStorageFamily.kext/Contents/Resources/Removable.icns``
1420
const baseDiskIconPath = `${__dirname}/disk-icon.icns`;
1521

1622
const biggestPossibleIconType = 'ic10';
1723

18-
async function composeIcon(type, appIcon, mountIcon, composedIcon) {
24+
async function baseComposeIcon(type, appIcon, mountIcon, composedIcon) {
1925
mountIcon = gm(mountIcon);
2026
appIcon = gm(appIcon);
2127

2228
const [appIconSize, mountIconSize] = await Promise.all([
2329
promisify(appIcon.size.bind(appIcon))(),
24-
promisify(appIcon.size.bind(mountIcon))()
30+
promisify(appIcon.size.bind(mountIcon))(),
2531
]);
2632

2733
// Change the perspective of the app icon to match the mount drive icon
@@ -30,12 +36,12 @@ async function composeIcon(type, appIcon, mountIcon, composedIcon) {
3036
// Resize the app icon to fit it inside the mount icon, aspect ration should not be kept to create the perspective illution
3137
appIcon = appIcon.resize(mountIconSize.width / 1.58, mountIconSize.height / 1.82, '!');
3238

33-
const tempAppIconPath = tempy.file({extension: 'png'});
34-
await promisify(appIcon.write.bind(appIcon))(tempAppIconPath);
39+
const temporaryAppIconPath = temporaryFile({extension: 'png'});
40+
await promisify(appIcon.write.bind(appIcon))(temporaryAppIconPath);
3541

3642
// Compose the two icons
3743
const iconGravityFactor = mountIconSize.height * 0.063;
38-
mountIcon = mountIcon.composite(tempAppIconPath).gravity('Center').geometry(`+0-${iconGravityFactor}`);
44+
mountIcon = mountIcon.composite(temporaryAppIconPath).gravity('Center').geometry(`+0-${iconGravityFactor}`);
3945

4046
composedIcon[type] = await promisify(mountIcon.toBuffer.bind(mountIcon))();
4147
}
@@ -53,7 +59,7 @@ const hasGm = async () => {
5359
}
5460
};
5561

56-
module.exports = async appIconPath => {
62+
export default async function composeIcon(appIconPath) {
5763
if (!await hasGm()) {
5864
return baseDiskIconPath;
5965
}
@@ -64,7 +70,7 @@ module.exports = async appIconPath => {
6470
const composedIcon = {};
6571
await Promise.all(Object.entries(appIcon).map(async ([type, icon]) => {
6672
if (baseDiskIcons[type]) {
67-
return composeIcon(type, icon, baseDiskIcons[type], composedIcon);
73+
return baseComposeIcon(type, icon, baseDiskIcons[type], composedIcon);
6874
}
6975

7076
console.warn('There is no base image for this type', type);
@@ -73,12 +79,12 @@ module.exports = async appIconPath => {
7379
if (!composedIcon[biggestPossibleIconType]) {
7480
// Make sure the highest-resolution variant is generated
7581
const largestAppIcon = Object.values(appIcon).sort((a, b) => Buffer.byteLength(b) - Buffer.byteLength(a))[0];
76-
await composeIcon(biggestPossibleIconType, largestAppIcon, baseDiskIcons[biggestPossibleIconType], composedIcon);
82+
await baseComposeIcon(biggestPossibleIconType, largestAppIcon, baseDiskIcons[biggestPossibleIconType], composedIcon);
7783
}
7884

79-
const tempComposedIcon = tempy.file({extension: 'icns'});
85+
const temporaryComposedIcon = temporaryFile({extension: 'icns'});
8086

81-
await writeFile(tempComposedIcon, icns.format(composedIcon));
87+
await writeFile(temporaryComposedIcon, icns.format(composedIcon));
8288

83-
return tempComposedIcon;
84-
};
89+
return temporaryComposedIcon;
90+
}

package.json

+12-11
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
"email": "[email protected]",
1111
"url": "https://sindresorhus.com"
1212
},
13+
"type": "module",
1314
"bin": {
14-
"create-dmg": "cli.js"
15+
"create-dmg": "./cli.js"
1516
},
1617
"engines": {
17-
"node": ">=8"
18+
"node": ">=14.16"
1819
},
1920
"scripts": {
20-
"test": "xo && ava"
21+
"test": "ava"
2122
},
2223
"files": [
2324
"cli.js",
@@ -43,16 +44,16 @@
4344
],
4445
"dependencies": {
4546
"appdmg": "^0.6.6",
46-
"execa": "^1.0.0",
47-
"gm": "^1.23.1",
47+
"execa": "^6.1.0",
48+
"gm": "^1.25.0",
4849
"icns-lib": "^1.0.1",
49-
"meow": "^5.0.0",
50-
"ora": "^4.0.3",
51-
"plist": "^3.0.1",
52-
"tempy": "^0.3.0"
50+
"meow": "^11.0.0",
51+
"ora": "^6.1.2",
52+
"plist": "^3.0.6",
53+
"tempy": "^3.0.0"
5354
},
5455
"devDependencies": {
55-
"ava": "^2.4.0",
56-
"xo": "^0.25.3"
56+
"ava": "^5.2.0",
57+
"xo": "^0.53.1"
5758
}
5859
}

readme.md

+7-11
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,10 @@ Imagine you have finished a macOS app, exported it from Xcode, and now want to d
1010

1111
## Install
1212

13-
Ensure you have [Node.js](https://nodejs.org) 8 or later installed. Then run the following:
13+
Ensure you have [Node.js](https://nodejs.org) 14 or later installed. Then run the following:
1414

15-
```
16-
$ npm install --global create-dmg
15+
```sh
16+
npm install --global create-dmg
1717
```
1818

1919
## Usage
@@ -36,7 +36,7 @@ $ create-dmg --help
3636

3737
## DMG
3838

39-
The DMG requires macOS 10.13 or later and has the filename `App Name 0.0.0.dmg`, for example `Lungo 1.0.0.dmg`.
39+
The DMG requires macOS 10.13 or later and has the filename `App Name 0.0.0.dmg`. For example, `Lungo 1.0.0.dmg`.
4040

4141
It will try to code sign the DMG, but the DMG is still created and fine even if the code signing fails, for example if you don't have a developer certificate.
4242

@@ -56,8 +56,8 @@ If either `license.txt`, `license.rtf`, or `sla.r` ([raw SLAResources file](http
5656

5757
#### Steps using [Homebrew](https://brew.sh)
5858

59-
```
60-
$ brew install graphicsmagick imagemagick
59+
```sh
60+
brew install graphicsmagick imagemagick
6161
```
6262

6363
#### Icon example
@@ -66,13 +66,9 @@ Original icon → DMG icon
6666

6767
<img src="icon-example-app.png" width="300"><img src="icon-example.png" width="300">
6868

69-
## Links
70-
71-
- [Product Hunt post](https://www.producthunt.com/posts/create-dmg)
72-
7369
## Related
7470

7571
- [Defaults](https://github.com/sindresorhus/Defaults) - Swifty and modern UserDefaults
7672
- [LaunchAtLogin](https://github.com/sindresorhus/LaunchAtLogin) - Add “Launch at Login” functionality to your macOS
77-
- [Preferences](https://github.com/sindresorhus/Preferences) - Add a preferences window to your macOS app
73+
- [My apps](https://sindresorhus.com/apps)
7874
- [More…](https://github.com/search?q=user%3Asindresorhus+language%3Aswift)

0 commit comments

Comments
 (0)