mirror of
https://github.com/Llewellynvdm/nativefier.git
synced 2024-11-14 09:04:04 +00:00
632 lines
20 KiB
JavaScript
Executable File
632 lines
20 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
import 'source-map-support/register';
|
|
|
|
import electronPackager = require('electron-packager');
|
|
import * as log from 'loglevel';
|
|
import * as yargs from 'yargs';
|
|
|
|
import { DEFAULT_ELECTRON_VERSION } from './constants';
|
|
import {
|
|
checkInternet,
|
|
getProcessEnvs,
|
|
isArgFormatInvalid,
|
|
} from './helpers/helpers';
|
|
import { supportedArchs, supportedPlatforms } from './infer/inferOs';
|
|
import { buildNativefierApp } from './main';
|
|
import { RawOptions } from './options/model';
|
|
import { parseJson } from './utils/parseUtils';
|
|
|
|
export function initArgs(argv: string[]): yargs.Argv<RawOptions> {
|
|
const sanitizedArgs = sanitizeArgs(argv);
|
|
const args = yargs(sanitizedArgs)
|
|
.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',
|
|
})
|
|
.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',
|
|
})
|
|
.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: 'string',
|
|
})
|
|
.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',
|
|
})
|
|
.option('inject', {
|
|
description:
|
|
'path to a CSS/JS file to be injected; pass multiple times to inject multiple files',
|
|
string: true,
|
|
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: (value: string) =>
|
|
parseJson<electronPackager.Win32MetadataOptions>(value),
|
|
description:
|
|
'(windows only) a JSON string of key/value pairs (ProductName, InternalName, FileDescription) to embed as executable metadata',
|
|
})
|
|
.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<RawOptions>): RawOptions {
|
|
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() : undefined;
|
|
parsed.out = parsed._.length > 1 ? (parsed._[1] as string) : undefined;
|
|
|
|
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 = undefined;
|
|
}
|
|
} 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;
|
|
}
|
|
|
|
function sanitizeArgs(argv: string[]): string[] {
|
|
const sanitizedArgs: string[] = [];
|
|
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`,
|
|
);
|
|
}
|
|
const isLastArg = sanitizedArgs.length + 1 === argv.length;
|
|
if (sanitizedArgs.length > 0) {
|
|
const previousArg = sanitizedArgs[sanitizedArgs.length - 1];
|
|
|
|
log.debug({ arg, previousArg, isLastArg });
|
|
|
|
// 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);
|
|
|
|
if (arg === '--tray' && isLastArg) {
|
|
// Add a true if --tray is last so it gets enabled
|
|
sanitizedArgs.push('true');
|
|
}
|
|
});
|
|
|
|
return sanitizedArgs;
|
|
}
|
|
|
|
if (require.main === module) {
|
|
let args: yargs.Argv<RawOptions> | undefined = undefined;
|
|
let parsedArgs: RawOptions;
|
|
try {
|
|
args = initArgs(process.argv.slice(2));
|
|
parsedArgs = parseArgs(args);
|
|
} catch (err: unknown) {
|
|
if (args) {
|
|
log.error(err);
|
|
args.showHelp();
|
|
} else {
|
|
log.error('Failed to parse command-line arguments. Aborting.', err);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
const options: RawOptions = {
|
|
...parsedArgs,
|
|
};
|
|
|
|
checkInternet();
|
|
|
|
buildNativefierApp(options).catch((error) => {
|
|
log.error('Error during build. Run with --verbose for details.', error);
|
|
});
|
|
}
|