forked from nativefier/nativefier
/
cli.ts
executable file
·619 lines (601 loc) · 19 KB
/
cli.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
#!/usr/bin/env node
import 'source-map-support/register';
import * as log from 'loglevel';
import * as yargs from 'yargs';
import {
checkInternet,
getProcessEnvs,
isArgFormatInvalid,
} from './helpers/helpers';
import { supportedArchs, supportedPlatforms } from './infer/inferOs';
import { buildNativefierApp } from './main';
import { NativefierOptions } from './options/model';
import { parseJson } from './utils/parseUtils';
import { DEFAULT_ELECTRON_VERSION } from './constants';
export function initArgs(argv: string[]): yargs.Argv<any> {
const args = yargs(argv)
.scriptName('nativefier')
.usage(
'$0 <targetUrl> [outputDirectory] [other options]\nor\n$0 --upgrade <pathToExistingApp> [other options]',
)
.example(
'$0 <targetUrl> -n <name>',
'Make an app from <targetUrl> and set the application name to <name>',
)
.example(
'$0 --upgrade <pathToExistingApp>',
'Upgrade (in place) the existing Nativefier app at <pathToExistingApp>',
)
.example(
'$0 <targetUrl> -p <platform> -a <arch>',
'Make an app from <targetUrl> for the OS <platform> and CPU architecture <arch>',
)
.example(
'for more examples and help...',
'See https://github.com/nativefier/nativefier/blob/master/CATALOG.md',
)
.positional('targetUrl', {
description:
'the URL that you wish to to turn into a native app; required if not using --upgrade',
type: 'string',
})
.positional('outputDirectory', {
defaultDescription: 'current directory',
description: 'the directory to generate the app in',
normalize: true,
type: 'string',
})
// App Creation Options
.option('a', {
alias: 'arch',
choices: supportedArchs,
defaultDescription: "current Node's arch",
description: 'the CPU architecture to build for',
type: 'string',
})
.option('c', {
alias: 'conceal',
default: false,
description: 'package the app source code into an asar archive',
type: 'boolean',
})
.option('e', {
alias: 'electron-version',
defaultDescription: DEFAULT_ELECTRON_VERSION,
description:
"specify the electron version to use (without the 'v'); see https://github.com/electron/electron/releases",
})
.option('global-shortcuts', {
description:
'define global keyboard shortcuts via a JSON file; See https://github.com/nativefier/nativefier/blob/master/API.md#global-shortcuts',
normalize: true,
type: 'string',
})
.option('i', {
alias: 'icon',
description:
'the icon file to use as the icon for the app (.ico on Windows, .icns/.png on macOS, .png on Linux)',
normalize: true,
type: 'string',
})
.option('n', {
alias: 'name',
defaultDescription: 'the title of the page passed via targetUrl',
description: 'specify the name of the app',
type: 'string',
})
.option('no-overwrite', {
default: false,
description: 'do not overwrite output directory if it already exists',
type: 'boolean',
})
.option('overwrite', {
// This is needed to have the `no-overwrite` flag to work correctly
default: true,
hidden: true,
type: 'boolean',
})
.option('p', {
alias: 'platform',
choices: supportedPlatforms,
defaultDescription: 'current operating system',
description: 'the operating system platform to build for',
type: 'string',
})
.option('portable', {
default: false,
description:
'make the app store its user data in the app folder; WARNING: see https://github.com/nativefier/nativefier/blob/master/API.md#portable for security risks',
type: 'boolean',
})
.option('upgrade', {
description:
'upgrade an app built by an older version of Nativefier\nYou must pass the full path to the existing app executable (app will be overwritten with upgraded version by default)',
normalize: true,
type: 'string',
})
.option('widevine', {
default: false,
description:
"use a Widevine-enabled version of Electron for DRM playback (use at your own risk, it's unofficial, provided by CastLabs)",
type: 'boolean',
})
.group(
[
'arch',
'conceal',
'electron-version',
'global-shortcuts',
'icon',
'name',
'no-overwrite',
'platform',
'portable',
'upgrade',
'widevine',
],
decorateYargOptionGroup('App Creation Options'),
)
// App Window Options
.option('always-on-top', {
default: false,
description: 'enable always on top window',
type: 'boolean',
})
.option('background-color', {
description:
"set the app background color, for better integration while the app is loading. Example value: '#2e2c29'",
type: 'string',
})
.option('bookmarks-menu', {
description:
'create a bookmarks menu (via JSON file); See https://github.com/nativefier/nativefier/blob/master/API.md#bookmarks-menu',
normalize: true,
type: 'string',
})
.option('browserwindow-options', {
coerce: parseJson,
description:
'override Electron BrowserWindow options (via JSON string); see https://github.com/nativefier/nativefier/blob/master/API.md#browserwindow-options',
type: 'string',
})
.option('disable-context-menu', {
default: false,
description: 'disable the context menu (right click)',
type: 'boolean',
})
.option('disable-dev-tools', {
default: false,
description: 'disable developer tools (Ctrl+Shift+I / F12)',
type: 'boolean',
})
.option('full-screen', {
default: false,
description: 'always start the app full screen',
type: 'boolean',
})
.option('height', {
defaultDescription: '800',
description: 'set window default height in pixels',
type: 'number',
})
.option('hide-window-frame', {
default: false,
description: 'disable window frame and controls',
type: 'boolean',
})
.option('m', {
alias: 'show-menu-bar',
default: false,
description: 'set menu bar visible',
type: 'boolean',
})
.option('max-height', {
defaultDescription: 'unlimited',
description: 'set window maximum height in pixels',
type: 'number',
})
.option('max-width', {
defaultDescription: 'unlimited',
description: 'set window maximum width in pixels',
type: 'number',
})
.option('maximize', {
default: false,
description: 'always start the app maximized',
type: 'boolean',
})
.option('min-height', {
defaultDescription: '0',
description: 'set window minimum height in pixels',
type: 'number',
})
.option('min-width', {
defaultDescription: '0',
description: 'set window minimum width in pixels',
type: 'number',
})
.option('process-envs', {
coerce: getProcessEnvs,
description:
'a JSON string of key/value pairs to be set as environment variables before any browser windows are opened',
type: 'string',
})
.option('single-instance', {
default: false,
description: 'allow only a single instance of the app',
type: 'boolean',
})
.option('tray', {
default: false,
description:
"allow app to stay in system tray. If 'start-in-tray' is set as argument, don't show main window on first start",
type: 'boolean',
})
.option('width', {
defaultDescription: '1280',
description: 'app window default width in pixels',
type: 'number',
})
.option('x', {
description: 'set window x location in pixels from left',
type: 'number',
})
.option('y', {
description: 'set window y location in pixels from top',
type: 'number',
})
.option('zoom', {
default: 1.0,
description: 'set the default zoom factor for the app',
type: 'number',
})
.group(
[
'always-on-top',
'background-color',
'bookmarks-menu',
'browserwindow-options',
'disable-context-menu',
'disable-dev-tools',
'full-screen',
'height',
'hide-window-frame',
'm',
'max-width',
'max-height',
'maximize',
'min-height',
'min-width',
'process-envs',
'single-instance',
'tray',
'width',
'x',
'y',
'zoom',
],
decorateYargOptionGroup('App Window Options'),
)
// Internal Browser Options
.option('file-download-options', {
coerce: parseJson,
description:
'a JSON string defining file download options; see https://github.com/sindresorhus/electron-dl',
type: 'string',
})
.option('inject', {
description:
'path to a CSS/JS file to be injected; pass multiple times to inject multiple files',
type: 'array',
})
.option('lang', {
defaultDescription: 'os language at runtime of the app',
description:
'set the language or locale to render the web site as (e.g., "fr", "en-US", "es", etc.)',
type: 'string',
})
.option('u', {
alias: 'user-agent',
description:
"set the app's user agent string; may also use 'edge', 'firefox', or 'safari' to have one auto-generated",
type: 'string',
})
.option('user-agent-honest', {
alias: 'honest',
default: false,
description:
'prevent the normal changing of the user agent string to appear as a regular Chrome browser',
type: 'boolean',
})
.group(
[
'file-download-options',
'inject',
'lang',
'user-agent',
'user-agent-honest',
],
decorateYargOptionGroup('Internal Browser Options'),
)
// Internal Browser Cache Options
.option('clear-cache', {
default: false,
description: 'prevent the app from preserving cache between launches',
type: 'boolean',
})
.option('disk-cache-size', {
defaultDescription: 'chromium default',
description:
'set the maximum disk space (in bytes) to be used by the disk cache',
type: 'number',
})
.group(
['clear-cache', 'disk-cache-size'],
decorateYargOptionGroup('Internal Browser Cache Options'),
)
// URL Handling Options
.option('block-external-urls', {
default: false,
description: `forbid navigation to URLs not considered "internal" (see '--internal-urls'). Instead of opening in an external browser, attempts to navigate to external URLs will be blocked`,
type: 'boolean',
})
.option('internal-urls', {
defaultDescription: 'URLs sharing the same base domain',
description:
'regex of URLs to consider "internal"; all other URLs will be opened in an external browser',
type: 'string',
})
.option('proxy-rules', {
description:
'proxy rules; see https://www.electronjs.org/docs/api/session#sessetproxyconfig',
type: 'string',
})
.group(
['block-external-urls', 'internal-urls', 'proxy-rules'],
decorateYargOptionGroup('URL Handling Options'),
)
// Auth Options
.option('basic-auth-password', {
description: 'basic http(s) auth password',
type: 'string',
})
.option('basic-auth-username', {
description: 'basic http(s) auth username',
type: 'string',
})
.group(
['basic-auth-password', 'basic-auth-username'],
decorateYargOptionGroup('Auth Options'),
)
// Graphics Options
.option('disable-gpu', {
default: false,
description: 'disable hardware acceleration',
type: 'boolean',
})
.option('enable-es3-apis', {
default: false,
description: 'force activation of WebGL 2.0',
type: 'boolean',
})
.option('ignore-gpu-blacklist', {
default: false,
description: 'force WebGL apps to work on unsupported GPUs',
type: 'boolean',
})
.group(
['disable-gpu', 'enable-es3-apis', 'ignore-gpu-blacklist'],
decorateYargOptionGroup('Graphics Options'),
)
// (In)Security Options
.option('disable-old-build-warning-yesiknowitisinsecure', {
default: false,
description:
'disable warning shown when opening an app made too long ago; Nativefier uses the Chrome browser (through Electron), and it is dangerous to keep using an old version of it',
type: 'boolean',
})
.option('ignore-certificate', {
default: false,
description: 'ignore certificate-related errors',
type: 'boolean',
})
.option('insecure', {
default: false,
description: 'enable loading of insecure content',
type: 'boolean',
})
.group(
[
'disable-old-build-warning-yesiknowitisinsecure',
'ignore-certificate',
'insecure',
],
decorateYargOptionGroup('(In)Security Options'),
)
// Flash Options (DEPRECATED)
.option('flash', {
default: false,
deprecated: true,
description: 'enable Adobe Flash',
hidden: true,
type: 'boolean',
})
.option('flash-path', {
deprecated: true,
description: 'path to Chrome flash plugin; find it in `chrome://plugins`',
hidden: true,
normalize: true,
type: 'string',
})
// Platform Specific Options
.option('app-copyright', {
description:
'(macOS, windows only) set a human-readable copyright line for the app; maps to `LegalCopyright` metadata property on Windows, and `NSHumanReadableCopyright` on macOS',
type: 'string',
})
.option('app-version', {
description:
'(macOS, windows only) set the version of the app; maps to the `ProductVersion` metadata property on Windows, and `CFBundleShortVersionString` on macOS',
type: 'string',
})
.option('bounce', {
default: false,
description:
'(macOS only) make the dock icon bounce when the counter increases',
type: 'boolean',
})
.option('build-version', {
description:
'(macOS, windows only) set the build version of the app; maps to `FileVersion` metadata property on Windows, and `CFBundleVersion` on macOS',
type: 'string',
})
.option('counter', {
default: false,
description:
'(macOS only) set a dock count badge, determined by looking for a number in the window title',
type: 'boolean',
})
.option('darwin-dark-mode-support', {
default: false,
description: '(macOS only) enable Dark Mode support on macOS 10.14+',
type: 'boolean',
})
.option('f', {
alias: 'fast-quit',
default: false,
description: '(macOS only) quit app on window close',
type: 'boolean',
})
.option('title-bar-style', {
choices: ['hidden', 'hiddenInset'],
description:
'(macOS only) set title bar style; consider injecting custom CSS (via --inject) for better integration',
type: 'string',
})
.option('win32metadata', {
coerce: parseJson,
description:
'(windows only) a JSON string of key/value pairs (ProductName, InternalName, FileDescription) to embed as executable metadata',
type: 'string',
})
.group(
[
'app-copyright',
'app-version',
'bounce',
'build-version',
'counter',
'darwin-dark-mode-support',
'fast-quit',
'title-bar-style',
'win32metadata',
],
decorateYargOptionGroup('Platform-Specific Options'),
)
// Debug Options
.option('crash-reporter', {
description: 'remote server URL to send crash reports',
type: 'string',
})
.option('verbose', {
default: false,
description: 'enable verbose/debug/troubleshooting logs',
type: 'boolean',
})
.group(
['crash-reporter', 'verbose'],
decorateYargOptionGroup('Debug Options'),
)
.version()
.help()
.group(['version', 'help'], 'Other Options')
.wrap(yargs.terminalWidth());
// We must access argv in order to get yargs to actually process args
// Do this now to go ahead and get any errors out of the way
args.argv;
return args;
}
function decorateYargOptionGroup(value: string): string {
return `====== ${value} ======`;
}
export function parseArgs(args: yargs.Argv<any>): any {
const parsed = { ...args.argv };
// In yargs, the _ property of the parsed args is an array of the positional args
// https://github.com/yargs/yargs/blob/master/docs/examples.md#and-non-hyphenated-options-too-just-use-argv_
// So try to extract the targetUrl and outputDirectory from these
parsed.targetUrl = parsed._.length > 0 ? parsed._[0].toString() : '';
parsed.out = parsed._.length > 1 ? parsed._[1] : '';
if (parsed.upgrade && parsed.targetUrl !== '') {
let targetAndUpgrade = false;
if (parsed.out === '') {
// If we're upgrading, the first positional args might be the outputDirectory, so swap these if we can
try {
// If this succeeds, we have a problem
new URL(parsed.targetUrl);
targetAndUpgrade = true;
} catch {
// Cool, it's not a URL
parsed.out = parsed.targetUrl;
parsed.targetUrl = '';
}
} else {
// Someone supplied a targetUrl, an outputDirectory, and --upgrade. That's not cool.
targetAndUpgrade = true;
}
if (targetAndUpgrade) {
throw new Error(
'ERROR: Nativefier must be called with either a targetUrl or the --upgrade option, not both.\n',
);
}
}
if (parsed.targetUrl === '' && !parsed.upgrade) {
throw new Error(
'ERROR: Nativefier must be called with either a targetUrl or the --upgrade option.\n',
);
}
parsed.noOverwrite = parsed['no-overwrite'] = !parsed.overwrite;
return parsed;
}
if (require.main === module) {
// Not sure if we still need this with yargs. Keeping for now.
const sanitizedArgs = [];
process.argv.forEach((arg) => {
if (isArgFormatInvalid(arg)) {
throw new Error(
`Invalid argument passed: ${arg} .\nNativefier supports short options (like "-n") and long options (like "--name"), all lowercase. Run "nativefier --help" for help.\nAborting`,
);
}
if (sanitizedArgs.length > 0) {
const previousArg = sanitizedArgs[sanitizedArgs.length - 1];
// Work around commander.js not supporting default argument for options
if (
previousArg === '--tray' &&
!['true', 'false', 'start-in-tray'].includes(arg)
) {
sanitizedArgs.push('true');
}
}
sanitizedArgs.push(arg);
});
let args, parsedArgs;
try {
args = initArgs(sanitizedArgs.slice(2));
parsedArgs = parseArgs(args);
} catch (err) {
if (args) {
log.error(err);
args.showHelp();
} else {
log.error('Failed to parse command-line arguments. Aborting.', err);
}
process.exit(1);
}
const options: NativefierOptions = {
...parsedArgs,
};
checkInternet();
buildNativefierApp(options).catch((error) => {
log.error('Error during build. Run with --verbose for details.', error);
});
}