mirror of
https://github.com/Llewellynvdm/nativefier.git
synced 2024-12-22 18:18:55 +00:00
Enable TypeScript strict:true, and more typescript-eslint rules (#1223)
* Catch promise errors better * Move subFunctions to bottom of createNewWindow * Use parents when creating child BrowserWindow instances * Some about:blank pages have an anchor (for some reason) * Inject browserWindowOptions better * Interim refactor to MainWindow object * Split up the window functions/helpers/events some * Further separate out window functions + tests * Add a mock for unit testing functions that access electron * Add unit tests for onWillPreventUnload * Improve windowEvents tests * Add the first test for windowHelpers * Move WebRequest event handling to node * insertCSS completely under test * clearAppData completely under test * Fix contextMenu require bug * More tests + fixes * Fix + add to createNewTab tests * Convert createMainWindow back to func + work out gremlins * Move setupWindow away from main since its shared * Make sure contextMenu is handling promises * Fix issues with fullscreen not working + menu refactor * Run jest against app/dist so that we can hit app unit tests as well * Requested PR changes * Add strict typing; tests currently failing * Fix failing unit tests * Add some more eslint warnings and fixes * More eslint fixes * Strict typing on (still issues with the lib dir) * Fix the package.json import/require * Fix some funky test errors * Warn -> Error for eslint rules * @ts-ignore -> @ts-expect-error * Add back the ext code I removed
This commit is contained in:
parent
7807bbb327
commit
7a08a2d676
38
.eslintrc.js
38
.eslintrc.js
@ -17,25 +17,29 @@ module.exports = {
|
|||||||
rules: {
|
rules: {
|
||||||
'no-console': 'error',
|
'no-console': 'error',
|
||||||
'prettier/prettier': [
|
'prettier/prettier': [
|
||||||
'error', {
|
'error',
|
||||||
'endOfLine': 'auto'
|
{
|
||||||
}
|
endOfLine: 'auto',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
// TODO remove when done killing `any`s and making tsc strict
|
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||||
'@typescript-eslint/ban-ts-comment': 'off',
|
'@typescript-eslint/no-confusing-non-null-assertion': 'error',
|
||||||
'@typescript-eslint/no-explicit-any': 'off',
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
'@typescript-eslint/no-extraneous-class': 'error',
|
||||||
'@typescript-eslint/no-unsafe-call': 'off',
|
'@typescript-eslint/no-implicit-any-catch': 'error',
|
||||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
'@typescript-eslint/no-invalid-void-type': 'error',
|
||||||
'@typescript-eslint/no-unsafe-return': 'off',
|
'@typescript-eslint/prefer-ts-expect-error': 'error',
|
||||||
|
'@typescript-eslint/type-annotation-spacing': 'error',
|
||||||
|
'@typescript-eslint/typedef': 'error',
|
||||||
|
'@typescript-eslint/unified-signatures': 'error',
|
||||||
},
|
},
|
||||||
// https://eslint.org/docs/user-guide/configuring/ignoring-code#ignorepatterns-in-config-files
|
// https://eslint.org/docs/user-guide/configuring/ignoring-code#ignorepatterns-in-config-files
|
||||||
ignorePatterns: [
|
ignorePatterns: [
|
||||||
"node_modules/**",
|
'node_modules/**',
|
||||||
"app/node_modules/**",
|
'app/node_modules/**',
|
||||||
"app/lib/**",
|
'app/lib/**',
|
||||||
"lib/**",
|
'lib/**',
|
||||||
"built-tests/**",
|
'built-tests/**',
|
||||||
"coverage/**",
|
'coverage/**',
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -60,3 +60,5 @@ nativefier*.tgz
|
|||||||
|
|
||||||
# https://github.com/nektos/act
|
# https://github.com/nektos/act
|
||||||
.actrc
|
.actrc
|
||||||
|
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
@ -25,14 +25,14 @@ export const APP_ARGS_FILE_PATH = path.join(__dirname, '..', 'nativefier.json');
|
|||||||
type SessionInteractionRequest = {
|
type SessionInteractionRequest = {
|
||||||
id?: string;
|
id?: string;
|
||||||
func?: string;
|
func?: string;
|
||||||
funcArgs?: any[];
|
funcArgs?: unknown[];
|
||||||
property?: string;
|
property?: string;
|
||||||
propertyValue?: any;
|
propertyValue?: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SessionInteractionResult = {
|
type SessionInteractionResult = {
|
||||||
id?: string;
|
id?: string;
|
||||||
value?: any;
|
value?: unknown;
|
||||||
error?: Error;
|
error?: Error;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -144,20 +144,17 @@ function createContextMenu(options, window: BrowserWindow): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function saveAppArgs(newAppArgs: any) {
|
export function saveAppArgs(newAppArgs: any): void {
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(APP_ARGS_FILE_PATH, JSON.stringify(newAppArgs));
|
fs.writeFileSync(APP_ARGS_FILE_PATH, JSON.stringify(newAppArgs));
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
log.warn(
|
log.warn(
|
||||||
`WARNING: Ignored nativefier.json rewrital (${(
|
`WARNING: Ignored nativefier.json rewrital (${(err as Error).message})`,
|
||||||
err as Error
|
|
||||||
).toString()})`,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupCloseEvent(options, window: BrowserWindow) {
|
function setupCloseEvent(options, window: BrowserWindow): void {
|
||||||
window.on('close', (event: IpcMainEvent) => {
|
window.on('close', (event: IpcMainEvent) => {
|
||||||
log.debug('mainWindow.close', event);
|
log.debug('mainWindow.close', event);
|
||||||
if (window.isFullScreen()) {
|
if (window.isFullScreen()) {
|
||||||
@ -181,7 +178,7 @@ function setupCounter(
|
|||||||
options,
|
options,
|
||||||
window: BrowserWindow,
|
window: BrowserWindow,
|
||||||
setDockBadge: (value: number | string, bounce?: boolean) => void,
|
setDockBadge: (value: number | string, bounce?: boolean) => void,
|
||||||
) {
|
): void {
|
||||||
window.on('page-title-updated', (event, title) => {
|
window.on('page-title-updated', (event, title) => {
|
||||||
log.debug('mainWindow.page-title-updated', { event, title });
|
log.debug('mainWindow.page-title-updated', { event, title });
|
||||||
const counterValue = getCounterValue(title);
|
const counterValue = getCounterValue(title);
|
||||||
@ -242,7 +239,7 @@ function setupSessionInteraction(options, window: BrowserWindow): void {
|
|||||||
typeof result.value['then'] === 'function'
|
typeof result.value['then'] === 'function'
|
||||||
) {
|
) {
|
||||||
// This is a promise. We'll resolve it here otherwise it will blow up trying to serialize it in the reply
|
// This is a promise. We'll resolve it here otherwise it will blow up trying to serialize it in the reply
|
||||||
result.value
|
(result.value as Promise<unknown>)
|
||||||
.then((trueResultValue) => {
|
.then((trueResultValue) => {
|
||||||
result.value = trueResultValue;
|
result.value = trueResultValue;
|
||||||
log.debug('ipcMain.session-interaction:result', result);
|
log.debug('ipcMain.session-interaction:result', result);
|
||||||
@ -274,9 +271,9 @@ function setupSessionInteraction(options, window: BrowserWindow): void {
|
|||||||
log.debug('session-interaction:result', result);
|
log.debug('session-interaction:result', result);
|
||||||
event.reply('session-interaction-reply', result);
|
event.reply('session-interaction-reply', result);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
log.error('session-interaction:error', error, event, request);
|
log.error('session-interaction:error', err, event, request);
|
||||||
result.error = error;
|
result.error = err as Error;
|
||||||
result.value = undefined; // Clear out the value in case serializing the value is what got us into this mess in the first place
|
result.value = undefined; // Clear out the value in case serializing the value is what got us into this mess in the first place
|
||||||
event.reply('session-interaction-reply', result);
|
event.reply('session-interaction-reply', result);
|
||||||
}
|
}
|
||||||
|
@ -89,9 +89,7 @@ export function generateMenu(
|
|||||||
{
|
{
|
||||||
label: 'Copy Current URL',
|
label: 'Copy Current URL',
|
||||||
accelerator: 'CmdOrCtrl+L',
|
accelerator: 'CmdOrCtrl+L',
|
||||||
click: () => {
|
click: (): void => clipboard.writeText(getCurrentURL()),
|
||||||
clipboard.writeText(getCurrentURL());
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Paste',
|
label: 'Paste',
|
||||||
@ -110,7 +108,7 @@ export function generateMenu(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Clear App Data',
|
label: 'Clear App Data',
|
||||||
click: (item: MenuItem, focusedWindow: BrowserWindow) => {
|
click: (item: MenuItem, focusedWindow: BrowserWindow): void => {
|
||||||
log.debug('Clear App Data.click', {
|
log.debug('Clear App Data.click', {
|
||||||
item,
|
item,
|
||||||
focusedWindow,
|
focusedWindow,
|
||||||
@ -166,7 +164,7 @@ export function generateMenu(
|
|||||||
accelerator: isOSX() ? 'Ctrl+Cmd+F' : 'F11',
|
accelerator: isOSX() ? 'Ctrl+Cmd+F' : 'F11',
|
||||||
enabled: mainWindow.isFullScreenable() || isOSX(),
|
enabled: mainWindow.isFullScreenable() || isOSX(),
|
||||||
visible: mainWindow.isFullScreenable() || isOSX(),
|
visible: mainWindow.isFullScreenable() || isOSX(),
|
||||||
click: (item: MenuItem, focusedWindow: BrowserWindow) => {
|
click: (item: MenuItem, focusedWindow: BrowserWindow): void => {
|
||||||
log.debug('Toggle Full Screen.click()', {
|
log.debug('Toggle Full Screen.click()', {
|
||||||
item,
|
item,
|
||||||
focusedWindow,
|
focusedWindow,
|
||||||
@ -266,9 +264,9 @@ export function generateMenu(
|
|||||||
submenu: [
|
submenu: [
|
||||||
{
|
{
|
||||||
label: `Built with Nativefier v${nativefierVersion}`,
|
label: `Built with Nativefier v${nativefierVersion}`,
|
||||||
click: () => {
|
click: (): void => {
|
||||||
openExternal('https://github.com/nativefier/nativefier').catch(
|
openExternal('https://github.com/nativefier/nativefier').catch(
|
||||||
(err) =>
|
(err: unknown): void =>
|
||||||
log.error(
|
log.error(
|
||||||
'Built with Nativefier v${nativefierVersion}.click ERROR',
|
'Built with Nativefier v${nativefierVersion}.click ERROR',
|
||||||
err,
|
err,
|
||||||
@ -278,9 +276,10 @@ export function generateMenu(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Report an Issue',
|
label: 'Report an Issue',
|
||||||
click: () => {
|
click: (): void => {
|
||||||
openExternal('https://github.com/nativefier/nativefier/issues').catch(
|
openExternal('https://github.com/nativefier/nativefier/issues').catch(
|
||||||
(err) => log.error('Report an Issue.click ERROR', err),
|
(err: unknown): void =>
|
||||||
|
log.error('Report an Issue.click ERROR', err),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -370,8 +369,8 @@ function injectBookmarks(menuTemplate: MenuItemConstructorOptions[]): void {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
label: bookmark.title,
|
label: bookmark.title,
|
||||||
click: () => {
|
click: (): void => {
|
||||||
goToURL(bookmark.url).catch((err) =>
|
goToURL(bookmark.url).catch((err: unknown): void =>
|
||||||
log.error(`${bookmark.title}.click ERROR`, err),
|
log.error(`${bookmark.title}.click ERROR`, err),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -390,7 +389,7 @@ function injectBookmarks(menuTemplate: MenuItemConstructorOptions[]): void {
|
|||||||
};
|
};
|
||||||
// Insert custom bookmarks menu between menus "View" and "Window"
|
// Insert custom bookmarks menu between menus "View" and "Window"
|
||||||
menuTemplate.splice(menuTemplate.length - 2, 0, bookmarksMenu);
|
menuTemplate.splice(menuTemplate.length - 2, 0, bookmarksMenu);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.error('Failed to load & parse bookmarks configuration JSON file.', err);
|
log.error('Failed to load & parse bookmarks configuration JSON file.', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ export function createTrayIcon(
|
|||||||
appIcon.setImage(nimage);
|
appIcon.setImage(nimage);
|
||||||
}
|
}
|
||||||
|
|
||||||
const onClick = () => {
|
const onClick = (): void => {
|
||||||
log.debug('onClick');
|
log.debug('onClick');
|
||||||
if (mainWindow.isVisible()) {
|
if (mainWindow.isVisible()) {
|
||||||
mainWindow.hide();
|
mainWindow.hide();
|
||||||
|
@ -141,7 +141,7 @@ export function linkIsInternal(
|
|||||||
// Only use the tld and the main domain for domain-ish test
|
// Only use the tld and the main domain for domain-ish test
|
||||||
// Enables domain-ish equality for blog.foo.com and shop.foo.com
|
// Enables domain-ish equality for blog.foo.com and shop.foo.com
|
||||||
return domainify(currentUrl) === domainify(newUrl);
|
return domainify(currentUrl) === domainify(newUrl);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.error(
|
log.error(
|
||||||
'Failed to parse domains as determining if link is internal. From:',
|
'Failed to parse domains as determining if link is internal. From:',
|
||||||
currentUrl,
|
currentUrl,
|
||||||
|
@ -4,6 +4,8 @@ import * as path from 'path';
|
|||||||
|
|
||||||
import { isOSX, isWindows, isLinux } from './helpers';
|
import { isOSX, isWindows, isLinux } from './helpers';
|
||||||
|
|
||||||
|
type fsError = Error & { code: string };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find a file or directory
|
* Find a file or directory
|
||||||
*/
|
*/
|
||||||
@ -14,12 +16,12 @@ function findSync(
|
|||||||
): string[] {
|
): string[] {
|
||||||
const matches: string[] = [];
|
const matches: string[] = [];
|
||||||
|
|
||||||
(function findSyncRecurse(base) {
|
(function findSyncRecurse(base): void {
|
||||||
let children: string[];
|
let children: string[];
|
||||||
try {
|
try {
|
||||||
children = fs.readdirSync(base);
|
children = fs.readdirSync(base);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (err.code === 'ENOENT') {
|
if ((err as fsError).code === 'ENOENT') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
throw err;
|
throw err;
|
||||||
@ -51,18 +53,18 @@ function findSync(
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFlashOnLinux() {
|
function findFlashOnLinux(): string {
|
||||||
return findSync(/libpepflashplayer\.so/, '/opt/google/chrome')[0];
|
return findSync(/libpepflashplayer\.so/, '/opt/google/chrome')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFlashOnWindows() {
|
function findFlashOnWindows(): string {
|
||||||
return findSync(
|
return findSync(
|
||||||
/pepflashplayer\.dll/,
|
/pepflashplayer\.dll/,
|
||||||
'C:\\Program Files (x86)\\Google\\Chrome',
|
'C:\\Program Files (x86)\\Google\\Chrome',
|
||||||
)[0];
|
)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFlashOnMac() {
|
function findFlashOnMac(): string {
|
||||||
return findSync(
|
return findSync(
|
||||||
/PepperFlashPlayer.plugin/,
|
/PepperFlashPlayer.plugin/,
|
||||||
'/Applications/Google Chrome.app/',
|
'/Applications/Google Chrome.app/',
|
||||||
@ -70,7 +72,7 @@ function findFlashOnMac() {
|
|||||||
)[0];
|
)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function inferFlashPath() {
|
export function inferFlashPath(): string {
|
||||||
if (isOSX()) {
|
if (isOSX()) {
|
||||||
return findFlashOnMac();
|
return findFlashOnMac();
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import { dialog, BrowserWindow, IpcMainEvent, WebContents } from 'electron';
|
import {
|
||||||
|
dialog,
|
||||||
|
BrowserWindow,
|
||||||
|
IpcMainEvent,
|
||||||
|
NewWindowWebContentsEvent,
|
||||||
|
WebContents,
|
||||||
|
} from 'electron';
|
||||||
import log from 'loglevel';
|
import log from 'loglevel';
|
||||||
|
|
||||||
import { linkIsInternal, nativeTabsSupported, openExternal } from './helpers';
|
import { linkIsInternal, nativeTabsSupported, openExternal } from './helpers';
|
||||||
@ -14,7 +20,7 @@ import {
|
|||||||
export function onNewWindow(
|
export function onNewWindow(
|
||||||
options,
|
options,
|
||||||
setupWindow: (...args) => void,
|
setupWindow: (...args) => void,
|
||||||
event: Event & { newGuest?: any },
|
event: NewWindowWebContentsEvent,
|
||||||
urlToGo: string,
|
urlToGo: string,
|
||||||
frameName: string,
|
frameName: string,
|
||||||
disposition:
|
disposition:
|
||||||
@ -33,7 +39,7 @@ export function onNewWindow(
|
|||||||
disposition,
|
disposition,
|
||||||
parent,
|
parent,
|
||||||
});
|
});
|
||||||
const preventDefault = (newGuest: any): void => {
|
const preventDefault = (newGuest: BrowserWindow): void => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (newGuest) {
|
if (newGuest) {
|
||||||
event.newGuest = newGuest;
|
event.newGuest = newGuest;
|
||||||
@ -96,7 +102,7 @@ export function onNewWindowHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
return Promise.reject(err);
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -153,7 +159,7 @@ export function setupNativefierWindow(options, window: BrowserWindow): void {
|
|||||||
|
|
||||||
window.webContents.on('will-navigate', (event: IpcMainEvent, url: string) => {
|
window.webContents.on('will-navigate', (event: IpcMainEvent, url: string) => {
|
||||||
onWillNavigate(options, event, url).catch((err) => {
|
onWillNavigate(options, event, url).catch((err) => {
|
||||||
log.error(' window.webContents.on.will-navigate ERROR', err);
|
log.error('window.webContents.on.will-navigate ERROR', err);
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -161,14 +167,14 @@ export function setupNativefierWindow(options, window: BrowserWindow): void {
|
|||||||
|
|
||||||
sendParamsOnDidFinishLoad(options, window);
|
sendParamsOnDidFinishLoad(options, window);
|
||||||
|
|
||||||
// @ts-ignore new-tab isn't in the type definition, but it does exist
|
// @ts-expect-error new-tab isn't in the type definition, but it does exist
|
||||||
window.on('new-tab', () => {
|
window.on('new-tab', () =>
|
||||||
createNewTab(
|
createNewTab(
|
||||||
options,
|
options,
|
||||||
setupNativefierWindow,
|
setupNativefierWindow,
|
||||||
options.targetUrl,
|
options.targetUrl,
|
||||||
true,
|
true,
|
||||||
window,
|
window,
|
||||||
).catch((err) => log.error('new-tab ERROR', err));
|
),
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ describe('injectCSS', () => {
|
|||||||
expect(mockGetCSSToInject).toHaveBeenCalled();
|
expect(mockGetCSSToInject).toHaveBeenCalled();
|
||||||
|
|
||||||
window.webContents.emit('did-navigate');
|
window.webContents.emit('did-navigate');
|
||||||
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
|
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
|
||||||
window.webContents.session.webRequest.send(
|
window.webContents.session.webRequest.send(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
{ responseHeaders, webContents: window.webContents },
|
{ responseHeaders, webContents: window.webContents },
|
||||||
@ -167,7 +167,7 @@ describe('injectCSS', () => {
|
|||||||
expect(mockGetCSSToInject).toHaveBeenCalled();
|
expect(mockGetCSSToInject).toHaveBeenCalled();
|
||||||
|
|
||||||
window.webContents.emit('did-navigate');
|
window.webContents.emit('did-navigate');
|
||||||
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
|
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
|
||||||
window.webContents.session.webRequest.send(
|
window.webContents.session.webRequest.send(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
{ responseHeaders, webContents: window.webContents },
|
{ responseHeaders, webContents: window.webContents },
|
||||||
@ -202,7 +202,7 @@ describe('injectCSS', () => {
|
|||||||
|
|
||||||
expect(window.webContents.emit('did-navigate')).toBe(true);
|
expect(window.webContents.emit('did-navigate')).toBe(true);
|
||||||
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
||||||
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
|
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
|
||||||
window.webContents.session.webRequest.send(
|
window.webContents.session.webRequest.send(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
{
|
{
|
||||||
@ -235,7 +235,7 @@ describe('injectCSS', () => {
|
|||||||
|
|
||||||
window.webContents.emit('did-navigate');
|
window.webContents.emit('did-navigate');
|
||||||
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
||||||
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
|
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
|
||||||
window.webContents.session.webRequest.send(
|
window.webContents.session.webRequest.send(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
{
|
{
|
||||||
@ -270,7 +270,7 @@ describe('injectCSS', () => {
|
|||||||
|
|
||||||
window.webContents.emit('did-navigate');
|
window.webContents.emit('did-navigate');
|
||||||
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
||||||
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
|
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
|
||||||
window.webContents.session.webRequest.send(
|
window.webContents.session.webRequest.send(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
{
|
{
|
||||||
@ -302,7 +302,7 @@ describe('injectCSS', () => {
|
|||||||
|
|
||||||
window.webContents.emit('did-navigate');
|
window.webContents.emit('did-navigate');
|
||||||
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
mockWebContentsInsertCSS.mockReset().mockResolvedValue(undefined);
|
||||||
// @ts-ignore this function doesn't exist in the actual electron version, but will in our mock
|
// @ts-expect-error this function doesn't exist in the actual electron version, but will in our mock
|
||||||
window.webContents.session.webRequest.send(
|
window.webContents.session.webRequest.send(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
{
|
{
|
||||||
|
@ -83,7 +83,7 @@ export function createNewTab(
|
|||||||
url: string,
|
url: string,
|
||||||
foreground: boolean,
|
foreground: boolean,
|
||||||
parent?: BrowserWindow,
|
parent?: BrowserWindow,
|
||||||
): Promise<BrowserWindow> {
|
): BrowserWindow {
|
||||||
log.debug('createNewTab', { url, foreground, parent });
|
log.debug('createNewTab', { url, foreground, parent });
|
||||||
return withFocusedWindow((focusedWindow) => {
|
return withFocusedWindow((focusedWindow) => {
|
||||||
const newTab = createNewWindow(options, setupWindow, url, parent);
|
const newTab = createNewWindow(options, setupWindow, url, parent);
|
||||||
@ -314,7 +314,7 @@ export function setProxyRules(window: BrowserWindow, proxyRules): void {
|
|||||||
.catch((err) => log.error('session.setProxy ERROR', err));
|
.catch((err) => log.error('session.setProxy ERROR', err));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function withFocusedWindow(block: (window: BrowserWindow) => any): any {
|
export function withFocusedWindow<T>(block: (window: BrowserWindow) => T): T {
|
||||||
const focusedWindow = BrowserWindow.getFocusedWindow();
|
const focusedWindow = BrowserWindow.getFocusedWindow();
|
||||||
if (focusedWindow) {
|
if (focusedWindow) {
|
||||||
return block(focusedWindow);
|
return block(focusedWindow);
|
||||||
|
@ -3,7 +3,7 @@ import 'source-map-support/register';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
|
|
||||||
import {
|
import electron, {
|
||||||
app,
|
app,
|
||||||
crashReporter,
|
crashReporter,
|
||||||
dialog,
|
dialog,
|
||||||
@ -67,10 +67,11 @@ if (process.argv.length > 1) {
|
|||||||
new URL(maybeUrl);
|
new URL(maybeUrl);
|
||||||
appArgs.targetUrl = maybeUrl;
|
appArgs.targetUrl = maybeUrl;
|
||||||
log.info('Loading override URL passed as argument:', maybeUrl);
|
log.info('Loading override URL passed as argument:', maybeUrl);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.error(
|
log.error(
|
||||||
'Not loading override URL passed as argument, because failed to parse:',
|
'Not loading override URL passed as argument, because failed to parse:',
|
||||||
maybeUrl,
|
maybeUrl,
|
||||||
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,12 +156,12 @@ if (appArgs.lang) {
|
|||||||
|
|
||||||
let currentBadgeCount = 0;
|
let currentBadgeCount = 0;
|
||||||
const setDockBadge = isOSX()
|
const setDockBadge = isOSX()
|
||||||
? (count: number, bounce = false) => {
|
? (count: number, bounce = false): void => {
|
||||||
app.dock.setBadge(count.toString());
|
app.dock.setBadge(count.toString());
|
||||||
if (bounce && count > currentBadgeCount) app.dock.bounce();
|
if (bounce && count > currentBadgeCount) app.dock.bounce();
|
||||||
currentBadgeCount = count;
|
currentBadgeCount = count;
|
||||||
}
|
}
|
||||||
: () => undefined;
|
: (): void => undefined;
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
log.debug('app.window-all-closed');
|
log.debug('app.window-all-closed');
|
||||||
@ -203,14 +204,14 @@ if (appArgs.crashReporter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (appArgs.widevine) {
|
if (appArgs.widevine) {
|
||||||
// @ts-ignore This event only appears on the widevine version of electron, which we'd see at runtime
|
// @ts-expect-error This event only appears on the widevine version of electron, which we'd see at runtime
|
||||||
app.on('widevine-ready', (version: string, lastVersion: string) => {
|
app.on('widevine-ready', (version: string, lastVersion: string) => {
|
||||||
log.debug('app.widevine-ready', { version, lastVersion });
|
log.debug('app.widevine-ready', { version, lastVersion });
|
||||||
onReady().catch((err) => log.error('onReady ERROR', err));
|
onReady().catch((err) => log.error('onReady ERROR', err));
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on(
|
app.on(
|
||||||
// @ts-ignore This event only appears on the widevine version of electron, which we'd see at runtime
|
// @ts-expect-error This event only appears on the widevine version of electron, which we'd see at runtime
|
||||||
'widevine-update-pending',
|
'widevine-update-pending',
|
||||||
(currentVersion: string, pendingVersion: string) => {
|
(currentVersion: string, pendingVersion: string) => {
|
||||||
log.debug('app.widevine-update-pending', {
|
log.debug('app.widevine-update-pending', {
|
||||||
@ -220,8 +221,8 @@ if (appArgs.widevine) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// @ts-ignore This event only appears on the widevine version of electron, which we'd see at runtime
|
// @ts-expect-error This event only appears on the widevine version of electron, which we'd see at runtime
|
||||||
app.on('widevine-error', (error: any) => {
|
app.on('widevine-error', (error: Error) => {
|
||||||
log.error('app.widevine-error', error);
|
log.error('app.widevine-error', error);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -231,7 +232,7 @@ if (appArgs.widevine) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
app.on('activate', (event, hasVisibleWindows) => {
|
app.on('activate', (event: electron.Event, hasVisibleWindows: boolean) => {
|
||||||
log.debug('app.activate', { event, hasVisibleWindows });
|
log.debug('app.activate', { event, hasVisibleWindows });
|
||||||
if (isOSX()) {
|
if (isOSX()) {
|
||||||
// this is called when the dock is clicked
|
// this is called when the dock is clicked
|
||||||
@ -374,7 +375,7 @@ app.on(
|
|||||||
|
|
||||||
app.on(
|
app.on(
|
||||||
'activity-was-continued',
|
'activity-was-continued',
|
||||||
(event: IpcMainEvent, type: string, userInfo: any) => {
|
(event: IpcMainEvent, type: string, userInfo: unknown) => {
|
||||||
log.debug('app.activity-was-continued', { event, type, userInfo });
|
log.debug('app.activity-was-continued', { event, type, userInfo });
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
||||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
@ -21,12 +22,12 @@ import { EventEmitter } from 'events';
|
|||||||
class MockBrowserWindow extends EventEmitter {
|
class MockBrowserWindow extends EventEmitter {
|
||||||
webContents: MockWebContents;
|
webContents: MockWebContents;
|
||||||
|
|
||||||
constructor(options?: any) {
|
constructor(options?: unknown) {
|
||||||
super(options);
|
super(options);
|
||||||
this.webContents = new MockWebContents();
|
this.webContents = new MockWebContents();
|
||||||
}
|
}
|
||||||
|
|
||||||
addTabbedWindow(tab: MockBrowserWindow) {
|
addTabbedWindow(tab: MockBrowserWindow): void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +55,7 @@ class MockBrowserWindow extends EventEmitter {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadURL(url: string, options?: any): Promise<void> {
|
loadURL(url: string, options?: unknown): Promise<void> {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,14 +71,14 @@ class MockBrowserWindow extends EventEmitter {
|
|||||||
class MockDialog {
|
class MockDialog {
|
||||||
static showMessageBox(
|
static showMessageBox(
|
||||||
browserWindow: MockBrowserWindow,
|
browserWindow: MockBrowserWindow,
|
||||||
options: any,
|
options: unknown,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
static showMessageBoxSync(
|
static showMessageBoxSync(
|
||||||
browserWindow: MockBrowserWindow,
|
browserWindow: MockBrowserWindow,
|
||||||
options: any,
|
options: unknown,
|
||||||
): number {
|
): number {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -112,7 +113,7 @@ class MockWebContents extends EventEmitter {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertCSS(css: string, options?: any): Promise<string> {
|
insertCSS(css: string, options?: unknown): Promise<string> {
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,22 +126,24 @@ class MockWebRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onHeadersReceived(
|
onHeadersReceived(
|
||||||
filter: any,
|
filter: unknown,
|
||||||
listener:
|
listener:
|
||||||
| ((
|
| ((
|
||||||
details: any,
|
details: unknown,
|
||||||
callback: (headersReceivedResponse: any) => void,
|
callback: (headersReceivedResponse: unknown) => void,
|
||||||
) => void)
|
) => void)
|
||||||
| null,
|
| null,
|
||||||
): void {
|
): void {
|
||||||
this.emitter.addListener(
|
this.emitter.addListener(
|
||||||
'onHeadersReceived',
|
'onHeadersReceived',
|
||||||
(details: any, callback: (headersReceivedResponse: any) => void) =>
|
(
|
||||||
listener(details, callback),
|
details: unknown,
|
||||||
|
callback: (headersReceivedResponse: unknown) => void,
|
||||||
|
) => listener(details, callback),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
send(event: string, ...args: any[]): void {
|
send(event: string, ...args: unknown[]): void {
|
||||||
this.emitter.emit(event, ...args);
|
this.emitter.emit(event, ...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,18 @@ export const INJECT_DIR = path.join(__dirname, '..', 'inject');
|
|||||||
* @param createCallback
|
* @param createCallback
|
||||||
* @param clickCallback
|
* @param clickCallback
|
||||||
*/
|
*/
|
||||||
function setNotificationCallback(createCallback, clickCallback) {
|
function setNotificationCallback(
|
||||||
|
createCallback: {
|
||||||
|
(title: string, opt: NotificationOptions): void;
|
||||||
|
(...args: unknown[]): void;
|
||||||
|
},
|
||||||
|
clickCallback: { (): void; (this: Notification, ev: Event): unknown },
|
||||||
|
): void {
|
||||||
const OldNotify = window.Notification;
|
const OldNotify = window.Notification;
|
||||||
const newNotify = function (title, opt) {
|
const newNotify = function (
|
||||||
|
title: string,
|
||||||
|
opt: NotificationOptions,
|
||||||
|
): Notification {
|
||||||
createCallback(title, opt);
|
createCallback(title, opt);
|
||||||
const instance = new OldNotify(title, opt);
|
const instance = new OldNotify(title, opt);
|
||||||
instance.addEventListener('click', clickCallback);
|
instance.addEventListener('click', clickCallback);
|
||||||
@ -47,11 +56,11 @@ function setNotificationCallback(createCallback, clickCallback) {
|
|||||||
get: () => OldNotify.permission,
|
get: () => OldNotify.permission,
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-expect-error
|
||||||
window.Notification = newNotify;
|
window.Notification = newNotify;
|
||||||
}
|
}
|
||||||
|
|
||||||
function injectScripts() {
|
function injectScripts(): void {
|
||||||
const needToInject = fs.existsSync(INJECT_DIR);
|
const needToInject = fs.existsSync(INJECT_DIR);
|
||||||
if (!needToInject) {
|
if (!needToInject) {
|
||||||
return;
|
return;
|
||||||
@ -68,15 +77,18 @@ function injectScripts() {
|
|||||||
log.debug('Injecting JS file', jsFile);
|
log.debug('Injecting JS file', jsFile);
|
||||||
require(jsFile);
|
require(jsFile);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
log.error('Error encoutered injecting JS files', error);
|
log.error('Error encoutered injecting JS files', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function notifyNotificationCreate(title, opt) {
|
function notifyNotificationCreate(
|
||||||
|
title: string,
|
||||||
|
opt: NotificationOptions,
|
||||||
|
): void {
|
||||||
ipcRenderer.send('notification', title, opt);
|
ipcRenderer.send('notification', title, opt);
|
||||||
}
|
}
|
||||||
function notifyNotificationClick() {
|
function notifyNotificationClick(): void {
|
||||||
ipcRenderer.send('notification-click');
|
ipcRenderer.send('notification-click');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@
|
|||||||
"build:watch": "npm run clean && tsc --build . app --watch",
|
"build:watch": "npm run clean && tsc --build . app --watch",
|
||||||
"changelog": "./.github/generate-changelog",
|
"changelog": "./.github/generate-changelog",
|
||||||
"ci": "npm run lint && npm test",
|
"ci": "npm run lint && npm test",
|
||||||
"clean": "rimraf lib/ app/lib/ app/dist/",
|
"clean": "rimraf coverage/ lib/ app/lib/ app/dist/",
|
||||||
"clean:full": "rimraf lib/ app/lib/ app/dist/ app/node_modules/ node_modules/",
|
"clean:full": "rimraf coverage/ lib/ app/lib/ app/dist/ app/node_modules/ node_modules/",
|
||||||
"lint:fix": "eslint . --ext .ts --fix",
|
"lint:fix": "eslint . --ext .ts --fix",
|
||||||
"lint:format": "prettier --write 'src/**/*.ts' 'app/src/**/*.ts'",
|
"lint:format": "prettier --write 'src/**/*.ts' 'app/src/**/*.ts'",
|
||||||
"lint": "eslint . --ext .ts",
|
"lint": "eslint . --ext .ts",
|
||||||
@ -66,7 +66,10 @@
|
|||||||
"yargs": "^17.0.1"
|
"yargs": "^17.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/electron-packager": "^15.0.1",
|
||||||
|
"@types/hasbin": "^1.2.0",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
|
"@types/loglevel": "^1.6.3",
|
||||||
"@types/ncp": "^2.0.4",
|
"@types/ncp": "^2.0.4",
|
||||||
"@types/node": "^12.20.15",
|
"@types/node": "^12.20.15",
|
||||||
"@types/page-icon": "^0.3.3",
|
"@types/page-icon": "^0.3.3",
|
||||||
|
@ -45,8 +45,8 @@ export function convertIconIfNecessary(options: AppOptions): void {
|
|||||||
const iconPath = convertToIco(options.packager.icon);
|
const iconPath = convertToIco(options.packager.icon);
|
||||||
options.packager.icon = iconPath;
|
options.packager.icon = iconPath;
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
log.warn('Failed to convert icon to .ico, skipping.', error);
|
log.warn('Failed to convert icon to .ico, skipping.', err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,8 +63,8 @@ export function convertIconIfNecessary(options: AppOptions): void {
|
|||||||
const iconPath = convertToPng(options.packager.icon);
|
const iconPath = convertToPng(options.packager.icon);
|
||||||
options.packager.icon = iconPath;
|
options.packager.icon = iconPath;
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
log.warn('Failed to convert icon to .png, skipping.', error);
|
log.warn('Failed to convert icon to .png, skipping.', err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -90,8 +90,8 @@ export function convertIconIfNecessary(options: AppOptions): void {
|
|||||||
if (options.nativefier.tray) {
|
if (options.nativefier.tray) {
|
||||||
convertToTrayIcon(options.packager.icon);
|
convertToTrayIcon(options.packager.icon);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
log.warn('Failed to convert icon to .icns, skipping.', error);
|
log.warn('Failed to convert icon to .icns, skipping.', err);
|
||||||
options.packager.icon = undefined;
|
options.packager.icon = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,18 @@ import * as path from 'path';
|
|||||||
|
|
||||||
import * as electronGet from '@electron/get';
|
import * as electronGet from '@electron/get';
|
||||||
import * as electronPackager from 'electron-packager';
|
import * as electronPackager from 'electron-packager';
|
||||||
import * as hasbin from 'hasbin';
|
|
||||||
import * as log from 'loglevel';
|
import * as log from 'loglevel';
|
||||||
|
|
||||||
import { convertIconIfNecessary } from './buildIcon';
|
import { convertIconIfNecessary } from './buildIcon';
|
||||||
import {
|
import {
|
||||||
copyFileOrDir,
|
copyFileOrDir,
|
||||||
getTempDir,
|
getTempDir,
|
||||||
|
hasWine,
|
||||||
isWindows,
|
isWindows,
|
||||||
isWindowsAdmin,
|
isWindowsAdmin,
|
||||||
} from '../helpers/helpers';
|
} from '../helpers/helpers';
|
||||||
import { useOldAppOptions, findUpgradeApp } from '../helpers/upgrade/upgrade';
|
import { useOldAppOptions, findUpgradeApp } from '../helpers/upgrade/upgrade';
|
||||||
import { AppOptions } from '../options/model';
|
import { AppOptions, RawOptions } from '../options/model';
|
||||||
import { getOptions } from '../options/optionsMain';
|
import { getOptions } from '../options/optionsMain';
|
||||||
import { prepareElectronApp } from './prepareElectronApp';
|
import { prepareElectronApp } from './prepareElectronApp';
|
||||||
|
|
||||||
@ -70,13 +70,13 @@ async function copyIconsIfNecessary(
|
|||||||
/**
|
/**
|
||||||
* Checks the app path array to determine if packaging completed successfully
|
* Checks the app path array to determine if packaging completed successfully
|
||||||
*/
|
*/
|
||||||
function getAppPath(appPath: string | string[]): string {
|
function getAppPath(appPath: string | string[]): string | undefined {
|
||||||
if (!Array.isArray(appPath)) {
|
if (!Array.isArray(appPath)) {
|
||||||
return appPath;
|
return appPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appPath.length === 0) {
|
if (appPath.length === 0) {
|
||||||
return null; // directory already exists and `--overwrite` not set
|
return undefined; // directory already exists and `--overwrite` not set
|
||||||
}
|
}
|
||||||
|
|
||||||
if (appPath.length > 1) {
|
if (appPath.length > 1) {
|
||||||
@ -89,20 +89,21 @@ function getAppPath(appPath: string | string[]): string {
|
|||||||
return appPath[0];
|
return appPath[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isUpgrade(rawOptions) {
|
function isUpgrade(rawOptions: RawOptions): boolean {
|
||||||
return (
|
if (
|
||||||
rawOptions.upgrade !== undefined &&
|
rawOptions.upgrade !== undefined &&
|
||||||
(rawOptions.upgrade === true ||
|
typeof rawOptions.upgrade === 'string' &&
|
||||||
(typeof rawOptions.upgrade === 'string' && rawOptions.upgrade !== ''))
|
rawOptions.upgrade !== ''
|
||||||
);
|
) {
|
||||||
|
rawOptions.upgradeFrom = rawOptions.upgrade;
|
||||||
|
rawOptions.upgrade = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function trimUnprocessableOptions(options: AppOptions): void {
|
function trimUnprocessableOptions(options: AppOptions): void {
|
||||||
if (
|
if (options.packager.platform === 'win32' && !isWindows() && !hasWine()) {
|
||||||
options.packager.platform === 'win32' &&
|
|
||||||
!isWindows() &&
|
|
||||||
!hasbin.sync('wine')
|
|
||||||
) {
|
|
||||||
const optionsPresent = Object.entries(options)
|
const optionsPresent = Object.entries(options)
|
||||||
.filter(
|
.filter(
|
||||||
([key, value]) =>
|
([key, value]) =>
|
||||||
@ -119,28 +120,30 @@ function trimUnprocessableOptions(options: AppOptions): void {
|
|||||||
'features, like a correct icon and process name. Do yourself a favor and install Wine, please.',
|
'features, like a correct icon and process name. Do yourself a favor and install Wine, please.',
|
||||||
);
|
);
|
||||||
for (const keyToUnset of optionsPresent) {
|
for (const keyToUnset of optionsPresent) {
|
||||||
options[keyToUnset] = null;
|
(options as unknown as Record<string, undefined>)[keyToUnset] = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export async function buildNativefierApp(rawOptions): Promise<string> {
|
export async function buildNativefierApp(
|
||||||
|
rawOptions: RawOptions,
|
||||||
|
): Promise<string | undefined> {
|
||||||
log.info('\nProcessing options...');
|
log.info('\nProcessing options...');
|
||||||
|
|
||||||
if (isUpgrade(rawOptions)) {
|
if (isUpgrade(rawOptions)) {
|
||||||
log.debug('Attempting to upgrade from', rawOptions.upgrade);
|
log.debug('Attempting to upgrade from', rawOptions.upgradeFrom);
|
||||||
const oldApp = findUpgradeApp(rawOptions.upgrade.toString());
|
const oldApp = findUpgradeApp(rawOptions.upgradeFrom as string);
|
||||||
if (oldApp === null) {
|
if (oldApp === null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not find an old Nativfier app in "${
|
`Could not find an old Nativfier app in "${
|
||||||
rawOptions.upgrade as string
|
rawOptions.upgradeFrom as string
|
||||||
}"`,
|
}"`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
rawOptions = useOldAppOptions(rawOptions, oldApp);
|
rawOptions = useOldAppOptions(rawOptions, oldApp);
|
||||||
if (rawOptions.out === undefined && rawOptions.overwrite) {
|
if (rawOptions.out === undefined && rawOptions.overwrite) {
|
||||||
rawOptions.out = path.dirname(rawOptions.upgrade);
|
rawOptions.out = path.dirname(rawOptions.upgradeFrom as string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.debug('rawOptions', rawOptions);
|
log.debug('rawOptions', rawOptions);
|
||||||
|
@ -6,14 +6,15 @@ import { promisify } from 'util';
|
|||||||
import * as log from 'loglevel';
|
import * as log from 'loglevel';
|
||||||
|
|
||||||
import { copyFileOrDir, generateRandomSuffix } from '../helpers/helpers';
|
import { copyFileOrDir, generateRandomSuffix } from '../helpers/helpers';
|
||||||
import { AppOptions } from '../options/model';
|
import { AppOptions, OutputOptions, PackageJSON } from '../options/model';
|
||||||
|
import { parseJson } from '../utils/parseUtils';
|
||||||
|
|
||||||
const writeFileAsync = promisify(fs.writeFile);
|
const writeFileAsync = promisify(fs.writeFile);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only picks certain app args to pass to nativefier.json
|
* Only picks certain app args to pass to nativefier.json
|
||||||
*/
|
*/
|
||||||
function pickElectronAppArgs(options: AppOptions): any {
|
function pickElectronAppArgs(options: AppOptions): OutputOptions {
|
||||||
return {
|
return {
|
||||||
accessibilityPrompt: options.nativefier.accessibilityPrompt,
|
accessibilityPrompt: options.nativefier.accessibilityPrompt,
|
||||||
alwaysOnTop: options.nativefier.alwaysOnTop,
|
alwaysOnTop: options.nativefier.alwaysOnTop,
|
||||||
@ -99,7 +100,10 @@ function pickElectronAppArgs(options: AppOptions): any {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function maybeCopyScripts(srcs: string[], dest: string): Promise<void> {
|
async function maybeCopyScripts(
|
||||||
|
srcs: string[] | undefined,
|
||||||
|
dest: string,
|
||||||
|
): Promise<void> {
|
||||||
if (!srcs || srcs.length === 0) {
|
if (!srcs || srcs.length === 0) {
|
||||||
log.debug('No files to inject, skipping copy.');
|
log.debug('No files to inject, skipping copy.');
|
||||||
return;
|
return;
|
||||||
@ -151,7 +155,12 @@ function changeAppPackageJsonName(
|
|||||||
url: string,
|
url: string,
|
||||||
): void {
|
): void {
|
||||||
const packageJsonPath = path.join(appPath, '/package.json');
|
const packageJsonPath = path.join(appPath, '/package.json');
|
||||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath).toString());
|
const packageJson = parseJson<PackageJSON>(
|
||||||
|
fs.readFileSync(packageJsonPath).toString(),
|
||||||
|
);
|
||||||
|
if (!packageJson) {
|
||||||
|
throw new Error(`Could not load package.json from ${packageJsonPath}`);
|
||||||
|
}
|
||||||
const normalizedAppName = normalizeAppName(name, url);
|
const normalizedAppName = normalizeAppName(name, url);
|
||||||
packageJson.name = normalizedAppName;
|
packageJson.name = normalizedAppName;
|
||||||
log.debug(`Updating ${packageJsonPath} 'name' field to ${normalizedAppName}`);
|
log.debug(`Updating ${packageJsonPath} 'name' field to ${normalizedAppName}`);
|
||||||
@ -171,10 +180,10 @@ export async function prepareElectronApp(
|
|||||||
log.debug(`Copying electron app from ${src} to ${dest}`);
|
log.debug(`Copying electron app from ${src} to ${dest}`);
|
||||||
try {
|
try {
|
||||||
await copyFileOrDir(src, dest);
|
await copyFileOrDir(src, dest);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
throw `Error copying electron app from ${src} to temp dir ${dest}. Error: ${(
|
throw `Error copying electron app from ${src} to temp dir ${dest}. Error: ${
|
||||||
err as Error
|
(err as Error).message
|
||||||
).toString()}`;
|
}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const appJsonPath = path.join(dest, '/nativefier.json');
|
const appJsonPath = path.join(dest, '/nativefier.json');
|
||||||
@ -188,19 +197,19 @@ export async function prepareElectronApp(
|
|||||||
const bookmarksJsonPath = path.join(dest, '/bookmarks.json');
|
const bookmarksJsonPath = path.join(dest, '/bookmarks.json');
|
||||||
try {
|
try {
|
||||||
await copyFileOrDir(options.nativefier.bookmarksMenu, bookmarksJsonPath);
|
await copyFileOrDir(options.nativefier.bookmarksMenu, bookmarksJsonPath);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.error('Error copying bookmarks menu config file.', err);
|
log.error('Error copying bookmarks menu config file.', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await maybeCopyScripts(options.nativefier.inject, dest);
|
await maybeCopyScripts(options.nativefier.inject, dest);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.error('Error copying injection files.', err);
|
log.error('Error copying injection files.', err);
|
||||||
}
|
}
|
||||||
changeAppPackageJsonName(
|
changeAppPackageJsonName(
|
||||||
dest,
|
dest,
|
||||||
options.packager.name,
|
options.packager.name as string,
|
||||||
options.packager.targetUrl,
|
options.packager.targetUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
164
src/cli.test.ts
164
src/cli.test.ts
@ -51,22 +51,22 @@ describe('initArgs + parseArgs', () => {
|
|||||||
test('upgrade arg', () => {
|
test('upgrade arg', () => {
|
||||||
const args = parseArgs(initArgs(['--upgrade', 'pathToUpgrade']));
|
const args = parseArgs(initArgs(['--upgrade', 'pathToUpgrade']));
|
||||||
expect(args.upgrade).toBe('pathToUpgrade');
|
expect(args.upgrade).toBe('pathToUpgrade');
|
||||||
expect(args.targetUrl).toBe('');
|
expect(args.targetUrl).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('upgrade arg with out dir', () => {
|
test('upgrade arg with out dir', () => {
|
||||||
const args = parseArgs(initArgs(['tmp', '--upgrade', 'pathToUpgrade']));
|
const args = parseArgs(initArgs(['tmp', '--upgrade', 'pathToUpgrade']));
|
||||||
expect(args.upgrade).toBe('pathToUpgrade');
|
expect(args.upgrade).toBe('pathToUpgrade');
|
||||||
expect(args.out).toBe('tmp');
|
expect(args.out).toBe('tmp');
|
||||||
expect(args.targetUrl).toBe('');
|
expect(args.targetUrl).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('upgrade arg with targetUrl', () => {
|
test('upgrade arg with targetUrl', () => {
|
||||||
expect(() => {
|
expect(() =>
|
||||||
parseArgs(
|
parseArgs(
|
||||||
initArgs(['https://www.google.com', '--upgrade', 'path/to/upgrade']),
|
initArgs(['https://www.google.com', '--upgrade', 'path/to/upgrade']),
|
||||||
);
|
),
|
||||||
}).toThrow();
|
).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('multi-inject', () => {
|
test('multi-inject', () => {
|
||||||
@ -92,53 +92,55 @@ describe('initArgs + parseArgs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
{ arg: 'app-copyright', shortArg: null, value: '(c) Nativefier' },
|
{ arg: 'app-copyright', shortArg: '', value: '(c) Nativefier' },
|
||||||
{ arg: 'app-version', shortArg: null, value: '2.0.0' },
|
{ arg: 'app-version', shortArg: '', value: '2.0.0' },
|
||||||
{ arg: 'background-color', shortArg: null, value: '#FFAA88' },
|
{ arg: 'background-color', shortArg: '', value: '#FFAA88' },
|
||||||
{ arg: 'basic-auth-username', shortArg: null, value: 'user' },
|
{ arg: 'basic-auth-username', shortArg: '', value: 'user' },
|
||||||
{ arg: 'basic-auth-password', shortArg: null, value: 'p@ssw0rd' },
|
{ arg: 'basic-auth-password', shortArg: '', value: 'p@ssw0rd' },
|
||||||
{ arg: 'bookmarks-menu', shortArg: null, value: 'bookmarks.json' },
|
{ arg: 'bookmarks-menu', shortArg: '', value: 'bookmarks.json' },
|
||||||
{
|
{
|
||||||
arg: 'browserwindow-options',
|
arg: 'browserwindow-options',
|
||||||
shortArg: null,
|
shortArg: '',
|
||||||
value: '{"test": 456}',
|
value: '{"test": 456}',
|
||||||
isJsonString: true,
|
isJsonString: true,
|
||||||
},
|
},
|
||||||
{ arg: 'build-version', shortArg: null, value: '3.0.0' },
|
{ arg: 'build-version', shortArg: '', value: '3.0.0' },
|
||||||
{
|
{
|
||||||
arg: 'crash-reporter',
|
arg: 'crash-reporter',
|
||||||
shortArg: null,
|
shortArg: '',
|
||||||
value: 'https://crash-reporter.com',
|
value: 'https://crash-reporter.com',
|
||||||
},
|
},
|
||||||
{ arg: 'electron-version', shortArg: 'e', value: '1.0.0' },
|
{ arg: 'electron-version', shortArg: 'e', value: '1.0.0' },
|
||||||
{
|
{
|
||||||
arg: 'file-download-options',
|
arg: 'file-download-options',
|
||||||
shortArg: null,
|
shortArg: '',
|
||||||
value: '{"test": 789}',
|
value: '{"test": 789}',
|
||||||
isJsonString: true,
|
isJsonString: true,
|
||||||
},
|
},
|
||||||
{ arg: 'flash-path', shortArg: null, value: 'pathToFlash' },
|
{ arg: 'flash-path', shortArg: '', value: 'pathToFlash' },
|
||||||
{ arg: 'global-shortcuts', shortArg: null, value: 'shortcuts.json' },
|
{ arg: 'global-shortcuts', shortArg: '', value: 'shortcuts.json' },
|
||||||
{ arg: 'icon', shortArg: 'i', value: 'icon.png' },
|
{ arg: 'icon', shortArg: 'i', value: 'icon.png' },
|
||||||
{ arg: 'internal-urls', shortArg: null, value: '.*' },
|
{ arg: 'internal-urls', shortArg: '', value: '.*' },
|
||||||
{ arg: 'lang', shortArg: null, value: 'fr' },
|
{ arg: 'lang', shortArg: '', value: 'fr' },
|
||||||
{ arg: 'name', shortArg: 'n', value: 'Google' },
|
{ arg: 'name', shortArg: 'n', value: 'Google' },
|
||||||
{
|
{
|
||||||
arg: 'process-envs',
|
arg: 'process-envs',
|
||||||
shortArg: null,
|
shortArg: '',
|
||||||
value: '{"test": 123}',
|
value: '{"test": 123}',
|
||||||
isJsonString: true,
|
isJsonString: true,
|
||||||
},
|
},
|
||||||
{ arg: 'proxy-rules', shortArg: null, value: 'RULE: PROXY' },
|
{ arg: 'proxy-rules', shortArg: '', value: 'RULE: PROXY' },
|
||||||
{ arg: 'user-agent', shortArg: 'u', value: 'FIREFOX' },
|
{ arg: 'user-agent', shortArg: 'u', value: 'FIREFOX' },
|
||||||
{
|
{
|
||||||
arg: 'win32metadata',
|
arg: 'win32metadata',
|
||||||
shortArg: null,
|
shortArg: '',
|
||||||
value: '{"ProductName": "Google"}',
|
value: '{"ProductName": "Google"}',
|
||||||
isJsonString: true,
|
isJsonString: true,
|
||||||
},
|
},
|
||||||
])('test string arg %s', ({ arg, shortArg, value, isJsonString }) => {
|
])('test string arg %s', ({ arg, shortArg, value, isJsonString }) => {
|
||||||
const args = parseArgs(initArgs(['https://google.com', `--${arg}`, value]));
|
const args = parseArgs(
|
||||||
|
initArgs(['https://google.com', `--${arg}`, value]),
|
||||||
|
) as Record<string, string>;
|
||||||
if (!isJsonString) {
|
if (!isJsonString) {
|
||||||
expect(args[arg]).toBe(value);
|
expect(args[arg]).toBe(value);
|
||||||
} else {
|
} else {
|
||||||
@ -147,8 +149,8 @@ describe('initArgs + parseArgs', () => {
|
|||||||
|
|
||||||
if (shortArg) {
|
if (shortArg) {
|
||||||
const argsShort = parseArgs(
|
const argsShort = parseArgs(
|
||||||
initArgs(['https://google.com', `-${shortArg as string}`, value]),
|
initArgs(['https://google.com', `-${shortArg}`, value]),
|
||||||
);
|
) as Record<string, string>;
|
||||||
if (!isJsonString) {
|
if (!isJsonString) {
|
||||||
expect(argsShort[arg]).toBe(value);
|
expect(argsShort[arg]).toBe(value);
|
||||||
} else {
|
} else {
|
||||||
@ -162,12 +164,14 @@ describe('initArgs + parseArgs', () => {
|
|||||||
{ arg: 'platform', shortArg: 'p', value: 'mac', badValue: 'os2' },
|
{ arg: 'platform', shortArg: 'p', value: 'mac', badValue: 'os2' },
|
||||||
{
|
{
|
||||||
arg: 'title-bar-style',
|
arg: 'title-bar-style',
|
||||||
shortArg: null,
|
shortArg: '',
|
||||||
value: 'hidden',
|
value: 'hidden',
|
||||||
badValue: 'cool',
|
badValue: 'cool',
|
||||||
},
|
},
|
||||||
])('limited choice arg %s', ({ arg, shortArg, value, badValue }) => {
|
])('limited choice arg %s', ({ arg, shortArg, value, badValue }) => {
|
||||||
const args = parseArgs(initArgs(['https://google.com', `--${arg}`, value]));
|
const args = parseArgs(
|
||||||
|
initArgs(['https://google.com', `--${arg}`, value]),
|
||||||
|
) as Record<string, string>;
|
||||||
expect(args[arg]).toBe(value);
|
expect(args[arg]).toBe(value);
|
||||||
|
|
||||||
// Mock console.error to not pollute the log with the yargs help text
|
// Mock console.error to not pollute the log with the yargs help text
|
||||||
@ -181,7 +185,7 @@ describe('initArgs + parseArgs', () => {
|
|||||||
if (shortArg) {
|
if (shortArg) {
|
||||||
const argsShort = parseArgs(
|
const argsShort = parseArgs(
|
||||||
initArgs(['https://google.com', `-${shortArg}`, value]),
|
initArgs(['https://google.com', `-${shortArg}`, value]),
|
||||||
);
|
) as Record<string, string>;
|
||||||
expect(argsShort[arg]).toBe(value);
|
expect(argsShort[arg]).toBe(value);
|
||||||
|
|
||||||
initArgs(['https://google.com', `-${shortArg}`, badValue]);
|
initArgs(['https://google.com', `-${shortArg}`, badValue]);
|
||||||
@ -192,64 +196,74 @@ describe('initArgs + parseArgs', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
{ arg: 'always-on-top', shortArg: null },
|
{ arg: 'always-on-top', shortArg: '' },
|
||||||
{ arg: 'block-external-urls', shortArg: null },
|
{ arg: 'block-external-urls', shortArg: '' },
|
||||||
{ arg: 'bounce', shortArg: null },
|
{ arg: 'bounce', shortArg: '' },
|
||||||
{ arg: 'clear-cache', shortArg: null },
|
{ arg: 'clear-cache', shortArg: '' },
|
||||||
{ arg: 'conceal', shortArg: 'c' },
|
{ arg: 'conceal', shortArg: 'c' },
|
||||||
{ arg: 'counter', shortArg: null },
|
{ arg: 'counter', shortArg: '' },
|
||||||
{ arg: 'darwin-dark-mode-support', shortArg: null },
|
{ arg: 'darwin-dark-mode-support', shortArg: '' },
|
||||||
{ arg: 'disable-context-menu', shortArg: null },
|
{ arg: 'disable-context-menu', shortArg: '' },
|
||||||
{ arg: 'disable-dev-tools', shortArg: null },
|
{ arg: 'disable-dev-tools', shortArg: '' },
|
||||||
{ arg: 'disable-gpu', shortArg: null },
|
{ arg: 'disable-gpu', shortArg: '' },
|
||||||
{ arg: 'disable-old-build-warning-yesiknowitisinsecure', shortArg: null },
|
{ arg: 'disable-old-build-warning-yesiknowitisinsecure', shortArg: '' },
|
||||||
{ arg: 'enable-es3-apis', shortArg: null },
|
{ arg: 'enable-es3-apis', shortArg: '' },
|
||||||
{ arg: 'fast-quit', shortArg: 'f' },
|
{ arg: 'fast-quit', shortArg: 'f' },
|
||||||
{ arg: 'flash', shortArg: null },
|
{ arg: 'flash', shortArg: '' },
|
||||||
{ arg: 'full-screen', shortArg: null },
|
{ arg: 'full-screen', shortArg: '' },
|
||||||
{ arg: 'hide-window-frame', shortArg: null },
|
{ arg: 'hide-window-frame', shortArg: '' },
|
||||||
{ arg: 'honest', shortArg: null },
|
{ arg: 'honest', shortArg: '' },
|
||||||
{ arg: 'ignore-certificate', shortArg: null },
|
{ arg: 'ignore-certificate', shortArg: '' },
|
||||||
{ arg: 'ignore-gpu-blacklist', shortArg: null },
|
{ arg: 'ignore-gpu-blacklist', shortArg: '' },
|
||||||
{ arg: 'insecure', shortArg: null },
|
{ arg: 'insecure', shortArg: '' },
|
||||||
{ arg: 'maximize', shortArg: null },
|
{ arg: 'maximize', shortArg: '' },
|
||||||
{ arg: 'portable', shortArg: null },
|
{ arg: 'portable', shortArg: '' },
|
||||||
{ arg: 'show-menu-bar', shortArg: 'm' },
|
{ arg: 'show-menu-bar', shortArg: 'm' },
|
||||||
{ arg: 'single-instance', shortArg: null },
|
{ arg: 'single-instance', shortArg: '' },
|
||||||
{ arg: 'tray', shortArg: null },
|
{ arg: 'tray', shortArg: '' },
|
||||||
{ arg: 'verbose', shortArg: null },
|
{ arg: 'verbose', shortArg: '' },
|
||||||
{ arg: 'widevine', shortArg: null },
|
{ arg: 'widevine', shortArg: '' },
|
||||||
])('test boolean arg %s', ({ arg, shortArg }) => {
|
])('test boolean arg %s', ({ arg, shortArg }) => {
|
||||||
const defaultArgs = parseArgs(initArgs(['https://google.com']));
|
const defaultArgs = parseArgs(initArgs(['https://google.com'])) as Record<
|
||||||
|
string,
|
||||||
|
boolean
|
||||||
|
>;
|
||||||
expect(defaultArgs[arg]).toBe(false);
|
expect(defaultArgs[arg]).toBe(false);
|
||||||
|
|
||||||
const args = parseArgs(initArgs(['https://google.com', `--${arg}`]));
|
const args = parseArgs(
|
||||||
|
initArgs(['https://google.com', `--${arg}`]),
|
||||||
|
) as Record<string, boolean>;
|
||||||
expect(args[arg]).toBe(true);
|
expect(args[arg]).toBe(true);
|
||||||
if (shortArg) {
|
if (shortArg) {
|
||||||
const argsShort = parseArgs(
|
const argsShort = parseArgs(
|
||||||
initArgs(['https://google.com', `-${shortArg}`]),
|
initArgs(['https://google.com', `-${shortArg}`]),
|
||||||
);
|
) as Record<string, boolean>;
|
||||||
expect(argsShort[arg]).toBe(true);
|
expect(argsShort[arg]).toBe(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
test.each([{ arg: 'no-overwrite', shortArg: null }])(
|
test.each([{ arg: 'no-overwrite', shortArg: '' }])(
|
||||||
'test inversible boolean arg %s',
|
'test inversible boolean arg %s',
|
||||||
({ arg, shortArg }) => {
|
({ arg, shortArg }) => {
|
||||||
const inverse = arg.startsWith('no-') ? arg.substr(3) : `no-${arg}`;
|
const inverse = arg.startsWith('no-') ? arg.substr(3) : `no-${arg}`;
|
||||||
|
|
||||||
const defaultArgs = parseArgs(initArgs(['https://google.com']));
|
const defaultArgs = parseArgs(initArgs(['https://google.com'])) as Record<
|
||||||
|
string,
|
||||||
|
boolean
|
||||||
|
>;
|
||||||
expect(defaultArgs[arg]).toBe(false);
|
expect(defaultArgs[arg]).toBe(false);
|
||||||
expect(defaultArgs[inverse]).toBe(true);
|
expect(defaultArgs[inverse]).toBe(true);
|
||||||
|
|
||||||
const args = parseArgs(initArgs(['https://google.com', `--${arg}`]));
|
const args = parseArgs(
|
||||||
|
initArgs(['https://google.com', `--${arg}`]),
|
||||||
|
) as Record<string, boolean>;
|
||||||
expect(args[arg]).toBe(true);
|
expect(args[arg]).toBe(true);
|
||||||
expect(args[inverse]).toBe(false);
|
expect(args[inverse]).toBe(false);
|
||||||
|
|
||||||
if (shortArg) {
|
if (shortArg) {
|
||||||
const argsShort = parseArgs(
|
const argsShort = parseArgs(
|
||||||
initArgs(['https://google.com', `-${shortArg as string}`]),
|
initArgs(['https://google.com', `-${shortArg}`]),
|
||||||
);
|
) as Record<string, boolean>;
|
||||||
expect(argsShort[arg]).toBe(true);
|
expect(argsShort[arg]).toBe(true);
|
||||||
expect(argsShort[inverse]).toBe(true);
|
expect(argsShort[inverse]).toBe(true);
|
||||||
}
|
}
|
||||||
@ -257,35 +271,35 @@ describe('initArgs + parseArgs', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
{ arg: 'disk-cache-size', shortArg: null, value: 100 },
|
{ arg: 'disk-cache-size', shortArg: '', value: 100 },
|
||||||
{ arg: 'height', shortArg: null, value: 200 },
|
{ arg: 'height', shortArg: '', value: 200 },
|
||||||
{ arg: 'max-height', shortArg: null, value: 300 },
|
{ arg: 'max-height', shortArg: '', value: 300 },
|
||||||
{ arg: 'max-width', shortArg: null, value: 400 },
|
{ arg: 'max-width', shortArg: '', value: 400 },
|
||||||
{ arg: 'min-height', shortArg: null, value: 500 },
|
{ arg: 'min-height', shortArg: '', value: 500 },
|
||||||
{ arg: 'min-width', shortArg: null, value: 600 },
|
{ arg: 'min-width', shortArg: '', value: 600 },
|
||||||
{ arg: 'width', shortArg: null, value: 700 },
|
{ arg: 'width', shortArg: '', value: 700 },
|
||||||
{ arg: 'x', shortArg: null, value: 800 },
|
{ arg: 'x', shortArg: '', value: 800 },
|
||||||
{ arg: 'y', shortArg: null, value: 900 },
|
{ arg: 'y', shortArg: '', value: 900 },
|
||||||
])('test numeric arg %s', ({ arg, shortArg, value }) => {
|
])('test numeric arg %s', ({ arg, shortArg, value }) => {
|
||||||
const args = parseArgs(
|
const args = parseArgs(
|
||||||
initArgs(['https://google.com', `--${arg}`, `${value}`]),
|
initArgs(['https://google.com', `--${arg}`, `${value}`]),
|
||||||
);
|
) as Record<string, number>;
|
||||||
expect(args[arg]).toBe(value);
|
expect(args[arg]).toBe(value);
|
||||||
|
|
||||||
const badArgs = parseArgs(
|
const badArgs = parseArgs(
|
||||||
initArgs(['https://google.com', `--${arg}`, 'abcd']),
|
initArgs(['https://google.com', `--${arg}`, 'abcd']),
|
||||||
);
|
) as Record<string, number>;
|
||||||
expect(badArgs[arg]).toBeNaN();
|
expect(badArgs[arg]).toBeNaN();
|
||||||
|
|
||||||
if (shortArg) {
|
if (shortArg) {
|
||||||
const shortArgs = parseArgs(
|
const shortArgs = parseArgs(
|
||||||
initArgs(['https://google.com', `-${shortArg as string}`, `${value}`]),
|
initArgs(['https://google.com', `-${shortArg}`, `${value}`]),
|
||||||
);
|
) as Record<string, number>;
|
||||||
expect(shortArgs[arg]).toBe(value);
|
expect(shortArgs[arg]).toBe(value);
|
||||||
|
|
||||||
const badShortArgs = parseArgs(
|
const badShortArgs = parseArgs(
|
||||||
initArgs(['https://google.com', `-${shortArg as string}`, 'abcd']),
|
initArgs(['https://google.com', `-${shortArg}`, 'abcd']),
|
||||||
);
|
) as Record<string, number>;
|
||||||
expect(badShortArgs[arg]).toBeNaN();
|
expect(badShortArgs[arg]).toBeNaN();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
36
src/cli.ts
36
src/cli.ts
@ -11,11 +11,12 @@ import {
|
|||||||
} from './helpers/helpers';
|
} from './helpers/helpers';
|
||||||
import { supportedArchs, supportedPlatforms } from './infer/inferOs';
|
import { supportedArchs, supportedPlatforms } from './infer/inferOs';
|
||||||
import { buildNativefierApp } from './main';
|
import { buildNativefierApp } from './main';
|
||||||
import { NativefierOptions } from './options/model';
|
import { RawOptions } from './options/model';
|
||||||
import { parseJson } from './utils/parseUtils';
|
import { parseJson } from './utils/parseUtils';
|
||||||
import { DEFAULT_ELECTRON_VERSION } from './constants';
|
import { DEFAULT_ELECTRON_VERSION } from './constants';
|
||||||
|
import electronPackager = require('electron-packager');
|
||||||
|
|
||||||
export function initArgs(argv: string[]): yargs.Argv<any> {
|
export function initArgs(argv: string[]): yargs.Argv<RawOptions> {
|
||||||
const args = yargs(argv)
|
const args = yargs(argv)
|
||||||
.scriptName('nativefier')
|
.scriptName('nativefier')
|
||||||
.usage(
|
.usage(
|
||||||
@ -160,7 +161,6 @@ export function initArgs(argv: string[]): yargs.Argv<any> {
|
|||||||
coerce: parseJson,
|
coerce: parseJson,
|
||||||
description:
|
description:
|
||||||
'override Electron BrowserWindow options (via JSON string); see https://github.com/nativefier/nativefier/blob/master/API.md#browserwindow-options',
|
'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', {
|
.option('disable-context-menu', {
|
||||||
default: false,
|
default: false,
|
||||||
@ -222,7 +222,6 @@ export function initArgs(argv: string[]): yargs.Argv<any> {
|
|||||||
coerce: getProcessEnvs,
|
coerce: getProcessEnvs,
|
||||||
description:
|
description:
|
||||||
'a JSON string of key/value pairs to be set as environment variables before any browser windows are opened',
|
'a JSON string of key/value pairs to be set as environment variables before any browser windows are opened',
|
||||||
type: 'string',
|
|
||||||
})
|
})
|
||||||
.option('single-instance', {
|
.option('single-instance', {
|
||||||
default: false,
|
default: false,
|
||||||
@ -285,11 +284,11 @@ export function initArgs(argv: string[]): yargs.Argv<any> {
|
|||||||
coerce: parseJson,
|
coerce: parseJson,
|
||||||
description:
|
description:
|
||||||
'a JSON string defining file download options; see https://github.com/sindresorhus/electron-dl',
|
'a JSON string defining file download options; see https://github.com/sindresorhus/electron-dl',
|
||||||
type: 'string',
|
|
||||||
})
|
})
|
||||||
.option('inject', {
|
.option('inject', {
|
||||||
description:
|
description:
|
||||||
'path to a CSS/JS file to be injected; pass multiple times to inject multiple files',
|
'path to a CSS/JS file to be injected; pass multiple times to inject multiple files',
|
||||||
|
string: true,
|
||||||
type: 'array',
|
type: 'array',
|
||||||
})
|
})
|
||||||
.option('lang', {
|
.option('lang', {
|
||||||
@ -477,10 +476,10 @@ export function initArgs(argv: string[]): yargs.Argv<any> {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
})
|
})
|
||||||
.option('win32metadata', {
|
.option('win32metadata', {
|
||||||
coerce: parseJson,
|
coerce: (value: string) =>
|
||||||
|
parseJson<electronPackager.Win32MetadataOptions>(value),
|
||||||
description:
|
description:
|
||||||
'(windows only) a JSON string of key/value pairs (ProductName, InternalName, FileDescription) to embed as executable metadata',
|
'(windows only) a JSON string of key/value pairs (ProductName, InternalName, FileDescription) to embed as executable metadata',
|
||||||
type: 'string',
|
|
||||||
})
|
})
|
||||||
.group(
|
.group(
|
||||||
[
|
[
|
||||||
@ -526,17 +525,17 @@ function decorateYargOptionGroup(value: string): string {
|
|||||||
return `====== ${value} ======`;
|
return `====== ${value} ======`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseArgs(args: yargs.Argv<any>): any {
|
export function parseArgs(args: yargs.Argv<RawOptions>): RawOptions {
|
||||||
const parsed = { ...args.argv };
|
const parsed = { ...args.argv };
|
||||||
// In yargs, the _ property of the parsed args is an array of the positional args
|
// 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_
|
// 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
|
// So try to extract the targetUrl and outputDirectory from these
|
||||||
parsed.targetUrl = parsed._.length > 0 ? parsed._[0].toString() : '';
|
parsed.targetUrl = parsed._.length > 0 ? parsed._[0].toString() : undefined;
|
||||||
parsed.out = parsed._.length > 1 ? parsed._[1] : '';
|
parsed.out = parsed._.length > 1 ? (parsed._[1] as string) : undefined;
|
||||||
|
|
||||||
if (parsed.upgrade && parsed.targetUrl !== '') {
|
if (parsed.upgrade && parsed.targetUrl) {
|
||||||
let targetAndUpgrade = false;
|
let targetAndUpgrade = false;
|
||||||
if (parsed.out === '') {
|
if (!parsed.out) {
|
||||||
// If we're upgrading, the first positional args might be the outputDirectory, so swap these if we can
|
// If we're upgrading, the first positional args might be the outputDirectory, so swap these if we can
|
||||||
try {
|
try {
|
||||||
// If this succeeds, we have a problem
|
// If this succeeds, we have a problem
|
||||||
@ -545,7 +544,7 @@ export function parseArgs(args: yargs.Argv<any>): any {
|
|||||||
} catch {
|
} catch {
|
||||||
// Cool, it's not a URL
|
// Cool, it's not a URL
|
||||||
parsed.out = parsed.targetUrl;
|
parsed.out = parsed.targetUrl;
|
||||||
parsed.targetUrl = '';
|
parsed.targetUrl = undefined;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Someone supplied a targetUrl, an outputDirectory, and --upgrade. That's not cool.
|
// Someone supplied a targetUrl, an outputDirectory, and --upgrade. That's not cool.
|
||||||
@ -559,7 +558,7 @@ export function parseArgs(args: yargs.Argv<any>): any {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parsed.targetUrl === '' && !parsed.upgrade) {
|
if (!parsed.targetUrl && !parsed.upgrade) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'ERROR: Nativefier must be called with either a targetUrl or the --upgrade option.\n',
|
'ERROR: Nativefier must be called with either a targetUrl or the --upgrade option.\n',
|
||||||
);
|
);
|
||||||
@ -572,7 +571,7 @@ export function parseArgs(args: yargs.Argv<any>): any {
|
|||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
// Not sure if we still need this with yargs. Keeping for now.
|
// Not sure if we still need this with yargs. Keeping for now.
|
||||||
const sanitizedArgs = [];
|
const sanitizedArgs: string[] = [];
|
||||||
process.argv.forEach((arg) => {
|
process.argv.forEach((arg) => {
|
||||||
if (isArgFormatInvalid(arg)) {
|
if (isArgFormatInvalid(arg)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -593,11 +592,12 @@ if (require.main === module) {
|
|||||||
sanitizedArgs.push(arg);
|
sanitizedArgs.push(arg);
|
||||||
});
|
});
|
||||||
|
|
||||||
let args, parsedArgs;
|
let args: yargs.Argv<RawOptions> | undefined = undefined;
|
||||||
|
let parsedArgs: RawOptions;
|
||||||
try {
|
try {
|
||||||
args = initArgs(sanitizedArgs.slice(2));
|
args = initArgs(sanitizedArgs.slice(2));
|
||||||
parsedArgs = parseArgs(args);
|
parsedArgs = parseArgs(args);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
if (args) {
|
if (args) {
|
||||||
log.error(err);
|
log.error(err);
|
||||||
args.showHelp();
|
args.showHelp();
|
||||||
@ -607,7 +607,7 @@ if (require.main === module) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const options: NativefierOptions = {
|
const options: RawOptions = {
|
||||||
...parsedArgs,
|
...parsedArgs,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -17,11 +17,17 @@ tmp.setGracefulCleanup(); // cleanup temp dirs even when an uncaught exception o
|
|||||||
const now = new Date();
|
const now = new Date();
|
||||||
const TMP_TIME = `${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`;
|
const TMP_TIME = `${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`;
|
||||||
|
|
||||||
type DownloadResult = {
|
export type DownloadResult = {
|
||||||
data: Buffer;
|
data: Buffer;
|
||||||
ext: string;
|
ext: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ProcessEnvs = Record<string, unknown>;
|
||||||
|
|
||||||
|
export function hasWine(): boolean {
|
||||||
|
return hasbin.sync('wine');
|
||||||
|
}
|
||||||
|
|
||||||
export function isOSX(): boolean {
|
export function isOSX(): boolean {
|
||||||
return os.platform() === 'darwin';
|
return os.platform() === 'darwin';
|
||||||
}
|
}
|
||||||
@ -57,7 +63,7 @@ export async function copyFileOrDir(
|
|||||||
dest: string,
|
dest: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
ncp(sourceFileOrDir, dest, (error: any) => {
|
ncp(sourceFileOrDir, dest, (error: Error[] | null): void => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
@ -66,15 +72,17 @@ export async function copyFileOrDir(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadFile(fileUrl: string): Promise<DownloadResult> {
|
export function downloadFile(
|
||||||
|
fileUrl: string,
|
||||||
|
): Promise<DownloadResult | undefined> {
|
||||||
log.debug(`Downloading ${fileUrl}`);
|
log.debug(`Downloading ${fileUrl}`);
|
||||||
return axios
|
return axios
|
||||||
.get(fileUrl, {
|
.get<Buffer>(fileUrl, {
|
||||||
responseType: 'arraybuffer',
|
responseType: 'arraybuffer',
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
data: response.data,
|
data: response.data,
|
||||||
@ -97,7 +105,7 @@ export function getAllowedIconFormats(platform: string): string[] {
|
|||||||
const icnsToPng = false;
|
const icnsToPng = false;
|
||||||
const icnsToIco = false;
|
const icnsToIco = false;
|
||||||
|
|
||||||
const formats = [];
|
const formats: string[] = [];
|
||||||
|
|
||||||
// Shell scripting is not supported on windows, temporary override
|
// Shell scripting is not supported on windows, temporary override
|
||||||
if (isWindows()) {
|
if (isWindows()) {
|
||||||
@ -175,11 +183,11 @@ export function generateRandomSuffix(length = 6): string {
|
|||||||
return hash.digest('hex').substring(0, length);
|
return hash.digest('hex').substring(0, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getProcessEnvs(val: string): any {
|
export function getProcessEnvs(val: string): ProcessEnvs | undefined {
|
||||||
if (!val) {
|
if (!val) {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
return parseJson(val);
|
return parseJson<ProcessEnvs>(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function checkInternet(): void {
|
export function checkInternet(): void {
|
||||||
|
@ -65,9 +65,9 @@ function getExecutableArch(
|
|||||||
function getExecutableInfo(
|
function getExecutableInfo(
|
||||||
executablePath: string,
|
executablePath: string,
|
||||||
platform: string,
|
platform: string,
|
||||||
): ExecutableInfo {
|
): ExecutableInfo | undefined {
|
||||||
if (!fileExists(executablePath)) {
|
if (!fileExists(executablePath)) {
|
||||||
return {};
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const exeBytes = getExecutableBytes(executablePath);
|
const exeBytes = getExecutableBytes(executablePath);
|
||||||
@ -81,6 +81,12 @@ export function getOptionsFromExecutable(
|
|||||||
priorOptions: NativefierOptions,
|
priorOptions: NativefierOptions,
|
||||||
): NativefierOptions {
|
): NativefierOptions {
|
||||||
const newOptions: NativefierOptions = { ...priorOptions };
|
const newOptions: NativefierOptions = { ...priorOptions };
|
||||||
|
if (!newOptions.name) {
|
||||||
|
throw new Error(
|
||||||
|
'Can not extract options from executable with no name specified.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const name: string = newOptions.name;
|
||||||
let executablePath: string | undefined = undefined;
|
let executablePath: string | undefined = undefined;
|
||||||
|
|
||||||
const appRoot = path.resolve(path.join(appResourcesDir, '..', '..'));
|
const appRoot = path.resolve(path.join(appResourcesDir, '..', '..'));
|
||||||
@ -118,8 +124,7 @@ export function getOptionsFromExecutable(
|
|||||||
appRoot,
|
appRoot,
|
||||||
children.filter(
|
children.filter(
|
||||||
(c) =>
|
(c) =>
|
||||||
c.name.toLowerCase() === `${newOptions.name.toLowerCase()}.exe` &&
|
c.name.toLowerCase() === `${name.toLowerCase()}.exe` && c.isFile(),
|
||||||
c.isFile(),
|
|
||||||
)[0].name,
|
)[0].name,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -130,7 +135,9 @@ export function getOptionsFromExecutable(
|
|||||||
'ProductVersion',
|
'ProductVersion',
|
||||||
);
|
);
|
||||||
log.debug(
|
log.debug(
|
||||||
`Extracted app version from executable: ${newOptions.appVersion}`,
|
`Extracted app version from executable: ${
|
||||||
|
newOptions.appVersion as string
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -142,7 +149,9 @@ export function getOptionsFromExecutable(
|
|||||||
newOptions.buildVersion = undefined;
|
newOptions.buildVersion = undefined;
|
||||||
} else {
|
} else {
|
||||||
log.debug(
|
log.debug(
|
||||||
`Extracted build version from executable: ${newOptions.buildVersion}`,
|
`Extracted build version from executable: ${
|
||||||
|
newOptions.buildVersion as string
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,7 +163,9 @@ export function getOptionsFromExecutable(
|
|||||||
'LegalCopyright',
|
'LegalCopyright',
|
||||||
);
|
);
|
||||||
log.debug(
|
log.debug(
|
||||||
`Extracted app copyright from executable: ${newOptions.appCopyright}`,
|
`Extracted app copyright from executable: ${
|
||||||
|
newOptions.appCopyright as string
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (looksLikeLinux) {
|
} else if (looksLikeLinux) {
|
||||||
@ -164,7 +175,13 @@ export function getOptionsFromExecutable(
|
|||||||
}
|
}
|
||||||
executablePath = path.join(
|
executablePath = path.join(
|
||||||
appRoot,
|
appRoot,
|
||||||
children.filter((c) => c.name == newOptions.name && c.isFile())[0].name,
|
children.filter((c) => c.name == name && c.isFile())[0].name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!executablePath || !newOptions.platform) {
|
||||||
|
throw Error(
|
||||||
|
`Could not find executablePath or platform of app in ${appRoot}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,8 +192,14 @@ export function getOptionsFromExecutable(
|
|||||||
executablePath,
|
executablePath,
|
||||||
newOptions.platform,
|
newOptions.platform,
|
||||||
);
|
);
|
||||||
|
if (!executableInfo) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not get executable info for executable path: ${executablePath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
newOptions.arch = executableInfo.arch;
|
newOptions.arch = executableInfo.arch;
|
||||||
log.debug(`Extracted arch from executable: ${newOptions.arch}`);
|
log.debug(`Extracted arch from executable: ${newOptions.arch as string}`);
|
||||||
}
|
}
|
||||||
if (newOptions.platform === undefined || newOptions.arch == undefined) {
|
if (newOptions.platform === undefined || newOptions.arch == undefined) {
|
||||||
throw Error(`Could not determine platform / arch of app in ${appRoot}`);
|
throw Error(`Could not determine platform / arch of app in ${appRoot}`);
|
||||||
|
@ -8,7 +8,7 @@ import { spawnSync } from 'child_process';
|
|||||||
export function getVersionString(
|
export function getVersionString(
|
||||||
executablePath: string,
|
executablePath: string,
|
||||||
versionString: string,
|
versionString: string,
|
||||||
): string {
|
): string | undefined {
|
||||||
let rcedit = path.resolve(
|
let rcedit = path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
'..',
|
'..',
|
||||||
|
@ -3,10 +3,11 @@ import * as path from 'path';
|
|||||||
|
|
||||||
import * as log from 'loglevel';
|
import * as log from 'loglevel';
|
||||||
|
|
||||||
import { NativefierOptions } from '../../options/model';
|
import { NativefierOptions, RawOptions } from '../../options/model';
|
||||||
import { dirExists, fileExists } from '../fsHelpers';
|
import { dirExists, fileExists } from '../fsHelpers';
|
||||||
import { extractBoolean, extractString } from './plistInfoXMLHelpers';
|
import { extractBoolean, extractString } from './plistInfoXMLHelpers';
|
||||||
import { getOptionsFromExecutable } from './executableHelpers';
|
import { getOptionsFromExecutable } from './executableHelpers';
|
||||||
|
import { parseJson } from '../../utils/parseUtils';
|
||||||
|
|
||||||
export type UpgradeAppInfo = {
|
export type UpgradeAppInfo = {
|
||||||
appResourcesDir: string;
|
appResourcesDir: string;
|
||||||
@ -79,7 +80,9 @@ function getInfoPListOptions(
|
|||||||
'NSHumanReadableCopyright',
|
'NSHumanReadableCopyright',
|
||||||
);
|
);
|
||||||
log.debug(
|
log.debug(
|
||||||
`Extracted app copyright from Info.plist: ${newOptions.appCopyright}`,
|
`Extracted app copyright from Info.plist: ${
|
||||||
|
newOptions.appCopyright as string
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +99,9 @@ function getInfoPListOptions(
|
|||||||
? undefined
|
? undefined
|
||||||
: newOptions.darwinDarkModeSupport === false),
|
: newOptions.darwinDarkModeSupport === false),
|
||||||
log.debug(
|
log.debug(
|
||||||
`Extracted app version from Info.plist: ${newOptions.appVersion}`,
|
`Extracted app version from Info.plist: ${
|
||||||
|
newOptions.appVersion as string
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,11 +157,19 @@ export function findUpgradeApp(upgradeFrom: string): UpgradeAppInfo | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
log.debug(`Loading ${path.join(appResourcesDir, 'nativefier.json')}`);
|
const nativefierJSONPath = path.join(appResourcesDir, 'nativefier.json');
|
||||||
const options: NativefierOptions = JSON.parse(
|
|
||||||
fs.readFileSync(path.join(appResourcesDir, 'nativefier.json'), 'utf8'),
|
log.debug(`Loading ${nativefierJSONPath}`);
|
||||||
|
const options = parseJson<NativefierOptions>(
|
||||||
|
fs.readFileSync(nativefierJSONPath, 'utf8'),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!options) {
|
||||||
|
throw new Error(
|
||||||
|
`Could not read Nativefier options from ${nativefierJSONPath}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
options.electronVersion = undefined;
|
options.electronVersion = undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -173,9 +186,9 @@ export function findUpgradeApp(upgradeFrom: string): UpgradeAppInfo | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useOldAppOptions(
|
export function useOldAppOptions(
|
||||||
rawOptions: NativefierOptions,
|
rawOptions: RawOptions,
|
||||||
oldApp: UpgradeAppInfo,
|
oldApp: UpgradeAppInfo,
|
||||||
): NativefierOptions {
|
): RawOptions {
|
||||||
if (rawOptions.targetUrl !== undefined && dirExists(rawOptions.targetUrl)) {
|
if (rawOptions.targetUrl !== undefined && dirExists(rawOptions.targetUrl)) {
|
||||||
// You got your ouput dir in my targetUrl!
|
// You got your ouput dir in my targetUrl!
|
||||||
rawOptions.out = rawOptions.targetUrl;
|
rawOptions.out = rawOptions.targetUrl;
|
||||||
|
@ -31,7 +31,7 @@ export async function getChromeVersionForElectronVersion(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
log.debug('Grabbing electron<->chrome versions file from', url);
|
log.debug('Grabbing electron<->chrome versions file from', url);
|
||||||
const response = await axios.get(url, { timeout: 5000 });
|
const response = await axios.get<ElectronRelease[]>(url, { timeout: 5000 });
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(`Bad request: Status code ${response.status}`);
|
throw new Error(`Bad request: Status code ${response.status}`);
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ export async function getChromeVersionForElectronVersion(
|
|||||||
`Associated electron v${electronVersion} to chrome v${chromeVersion}`,
|
`Associated electron v${electronVersion} to chrome v${chromeVersion}`,
|
||||||
);
|
);
|
||||||
return chromeVersion;
|
return chromeVersion;
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.error('getChromeVersionForElectronVersion ERROR', err);
|
log.error('getChromeVersionForElectronVersion ERROR', err);
|
||||||
log.debug('Falling back to default Chrome version', DEFAULT_CHROME_VERSION);
|
log.debug('Falling back to default Chrome version', DEFAULT_CHROME_VERSION);
|
||||||
return DEFAULT_CHROME_VERSION;
|
return DEFAULT_CHROME_VERSION;
|
||||||
|
@ -28,7 +28,7 @@ export async function getLatestFirefoxVersion(
|
|||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
try {
|
try {
|
||||||
log.debug('Grabbing Firefox version data from', url);
|
log.debug('Grabbing Firefox version data from', url);
|
||||||
const response = await axios.get(url, { timeout: 5000 });
|
const response = await axios.get<FirefoxVersions>(url, { timeout: 5000 });
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(`Bad request: Status code ${response.status}`);
|
throw new Error(`Bad request: Status code ${response.status}`);
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ export async function getLatestFirefoxVersion(
|
|||||||
`Got latest Firefox version ${firefoxVersions.LATEST_FIREFOX_VERSION}`,
|
`Got latest Firefox version ${firefoxVersions.LATEST_FIREFOX_VERSION}`,
|
||||||
);
|
);
|
||||||
return firefoxVersions.LATEST_FIREFOX_VERSION;
|
return firefoxVersions.LATEST_FIREFOX_VERSION;
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.error('getLatestFirefoxVersion ERROR', err);
|
log.error('getLatestFirefoxVersion ERROR', err);
|
||||||
log.debug(
|
log.debug(
|
||||||
'Falling back to default Firefox version',
|
'Falling back to default Firefox version',
|
||||||
|
@ -16,7 +16,7 @@ export async function getLatestSafariVersion(
|
|||||||
): Promise<SafariVersion> {
|
): Promise<SafariVersion> {
|
||||||
try {
|
try {
|
||||||
log.debug('Grabbing apple version data from', url);
|
log.debug('Grabbing apple version data from', url);
|
||||||
const response = await axios.get(url, { timeout: 5000 });
|
const response = await axios.get<string>(url, { timeout: 5000 });
|
||||||
if (response.status !== 200) {
|
if (response.status !== 200) {
|
||||||
throw new Error(`Bad request: Status code ${response.status}`);
|
throw new Error(`Bad request: Status code ${response.status}`);
|
||||||
}
|
}
|
||||||
@ -39,8 +39,8 @@ export async function getLatestSafariVersion(
|
|||||||
|
|
||||||
const versionRows = majorVersionTable.split('<tbody')[1].split('<tr');
|
const versionRows = majorVersionTable.split('<tbody')[1].split('<tr');
|
||||||
|
|
||||||
let version = null;
|
let version: string | undefined = undefined;
|
||||||
let webkitVersion: string = null;
|
let webkitVersion: string | undefined = undefined;
|
||||||
|
|
||||||
for (const versionRow of versionRows.reverse()) {
|
for (const versionRow of versionRows.reverse()) {
|
||||||
const versionMatch = [
|
const versionMatch = [
|
||||||
@ -61,12 +61,15 @@ export async function getLatestSafariVersion(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
if (version && webkitVersion) {
|
||||||
majorVersion,
|
return {
|
||||||
version,
|
majorVersion,
|
||||||
webkitVersion,
|
version,
|
||||||
};
|
webkitVersion,
|
||||||
} catch (err) {
|
};
|
||||||
|
}
|
||||||
|
return DEFAULT_SAFARI_VERSION;
|
||||||
|
} catch (err: unknown) {
|
||||||
log.error('getLatestSafariVersion ERROR', err);
|
log.error('getLatestSafariVersion ERROR', err);
|
||||||
log.debug('Falling back to default Safari version', DEFAULT_SAFARI_VERSION);
|
log.debug('Falling back to default Safari version', DEFAULT_SAFARI_VERSION);
|
||||||
return DEFAULT_SAFARI_VERSION;
|
return DEFAULT_SAFARI_VERSION;
|
||||||
|
@ -7,6 +7,7 @@ import * as pageIcon from 'page-icon';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
downloadFile,
|
downloadFile,
|
||||||
|
DownloadResult,
|
||||||
getAllowedIconFormats,
|
getAllowedIconFormats,
|
||||||
getTempDir,
|
getTempDir,
|
||||||
} from '../helpers/helpers';
|
} from '../helpers/helpers';
|
||||||
@ -17,10 +18,17 @@ const writeFileAsync = promisify(writeFile);
|
|||||||
const GITCLOUD_SPACE_DELIMITER = '-';
|
const GITCLOUD_SPACE_DELIMITER = '-';
|
||||||
const GITCLOUD_URL = 'https://nativefier.github.io/nativefier-icons/';
|
const GITCLOUD_URL = 'https://nativefier.github.io/nativefier-icons/';
|
||||||
|
|
||||||
function getMaxMatchScore(iconWithScores: any[]): number {
|
type GitCloudIcon = {
|
||||||
|
ext?: string;
|
||||||
|
name?: string;
|
||||||
|
score?: number;
|
||||||
|
url?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMaxMatchScore(iconWithScores: GitCloudIcon[]): number {
|
||||||
const score = iconWithScores.reduce((maxScore, currentIcon) => {
|
const score = iconWithScores.reduce((maxScore, currentIcon) => {
|
||||||
const currentScore = currentIcon.score;
|
const currentScore = currentIcon.score;
|
||||||
if (currentScore > maxScore) {
|
if (currentScore && currentScore > maxScore) {
|
||||||
return currentScore;
|
return currentScore;
|
||||||
}
|
}
|
||||||
return maxScore;
|
return maxScore;
|
||||||
@ -29,36 +37,42 @@ function getMaxMatchScore(iconWithScores: any[]): number {
|
|||||||
return score;
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMatchingIcons(iconsWithScores: any[], maxScore: number): any[] {
|
function getMatchingIcons(
|
||||||
return iconsWithScores
|
iconsWithScores: GitCloudIcon[],
|
||||||
.filter((item) => item.score === maxScore)
|
maxScore: number,
|
||||||
.map((item) => ({ ...item, ext: path.extname(item.url) }));
|
): GitCloudIcon[] {
|
||||||
|
return iconsWithScores.filter((item) => item.score === maxScore);
|
||||||
}
|
}
|
||||||
|
|
||||||
function mapIconWithMatchScore(
|
function mapIconWithMatchScore(
|
||||||
cloudIcons: { name: string; url: string }[],
|
cloudIcons: { name: string; url: string }[],
|
||||||
targetUrl: string,
|
targetUrl: string,
|
||||||
): any {
|
): GitCloudIcon[] {
|
||||||
const normalisedTargetUrl = targetUrl.toLowerCase();
|
const normalisedTargetUrl = targetUrl.toLowerCase();
|
||||||
return cloudIcons.map((item) => {
|
return cloudIcons.map((item) => {
|
||||||
const itemWords = item.name.split(GITCLOUD_SPACE_DELIMITER);
|
const itemWords = item.name.split(GITCLOUD_SPACE_DELIMITER);
|
||||||
const score = itemWords.reduce((currentScore: number, word: string) => {
|
const score: number = itemWords.reduce(
|
||||||
if (normalisedTargetUrl.includes(word)) {
|
(currentScore: number, word: string) => {
|
||||||
return currentScore + 1;
|
if (normalisedTargetUrl.includes(word)) {
|
||||||
}
|
return currentScore + 1;
|
||||||
return currentScore;
|
}
|
||||||
}, 0);
|
return currentScore;
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
return { ...item, score };
|
return { ...item, ext: path.extname(item.url), score };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function inferIconFromStore(
|
async function inferIconFromStore(
|
||||||
targetUrl: string,
|
targetUrl: string,
|
||||||
platform: string,
|
platform: string,
|
||||||
): Promise<any> {
|
): Promise<DownloadResult | undefined> {
|
||||||
log.debug(`Inferring icon from store for ${targetUrl} on ${platform}`);
|
log.debug(`Inferring icon from store for ${targetUrl} on ${platform}`);
|
||||||
const allowedFormats = new Set(getAllowedIconFormats(platform));
|
const allowedFormats = new Set<string | undefined>(
|
||||||
|
getAllowedIconFormats(platform),
|
||||||
|
);
|
||||||
|
|
||||||
const cloudIcons = await gitCloud(GITCLOUD_URL);
|
const cloudIcons = await gitCloud(GITCLOUD_URL);
|
||||||
log.debug(`Got ${cloudIcons.length} icons from gitcloud`);
|
log.debug(`Got ${cloudIcons.length} icons from gitcloud`);
|
||||||
@ -67,19 +81,19 @@ async function inferIconFromStore(
|
|||||||
|
|
||||||
if (maxScore === 0) {
|
if (maxScore === 0) {
|
||||||
log.debug('No relevant icon in store.');
|
log.debug('No relevant icon in store.');
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconsMatchingScore = getMatchingIcons(iconWithScores, maxScore);
|
const iconsMatchingScore = getMatchingIcons(iconWithScores, maxScore);
|
||||||
const iconsMatchingExt = iconsMatchingScore.filter((icon) =>
|
const iconsMatchingExt = iconsMatchingScore.filter((icon) =>
|
||||||
allowedFormats.has(icon.ext),
|
allowedFormats.has(icon.ext ?? path.extname(icon.url as string)),
|
||||||
);
|
);
|
||||||
const matchingIcon = iconsMatchingExt[0];
|
const matchingIcon = iconsMatchingExt[0];
|
||||||
const iconUrl = matchingIcon && matchingIcon.url;
|
const iconUrl = matchingIcon && matchingIcon.url;
|
||||||
|
|
||||||
if (!iconUrl) {
|
if (!iconUrl) {
|
||||||
log.debug('Could not infer icon from store');
|
log.debug('Could not infer icon from store');
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
return downloadFile(iconUrl);
|
return downloadFile(iconUrl);
|
||||||
}
|
}
|
||||||
@ -87,21 +101,19 @@ async function inferIconFromStore(
|
|||||||
export async function inferIcon(
|
export async function inferIcon(
|
||||||
targetUrl: string,
|
targetUrl: string,
|
||||||
platform: string,
|
platform: string,
|
||||||
): Promise<string> {
|
): Promise<string | undefined> {
|
||||||
log.debug(`Inferring icon for ${targetUrl} on ${platform}`);
|
log.debug(`Inferring icon for ${targetUrl} on ${platform}`);
|
||||||
const tmpDirPath = getTempDir('iconinfer');
|
const tmpDirPath = getTempDir('iconinfer');
|
||||||
|
|
||||||
let icon: { ext: string; data: Buffer } = await inferIconFromStore(
|
let icon: { ext: string; data: Buffer } | undefined =
|
||||||
targetUrl,
|
await inferIconFromStore(targetUrl, platform);
|
||||||
platform,
|
|
||||||
);
|
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
const ext = platform === 'win32' ? '.ico' : '.png';
|
const ext = platform === 'win32' ? '.ico' : '.png';
|
||||||
log.debug(`Trying to extract a ${ext} icon from the page.`);
|
log.debug(`Trying to extract a ${ext} icon from the page.`);
|
||||||
icon = await pageIcon(targetUrl, { ext });
|
icon = await pageIcon(targetUrl, { ext });
|
||||||
}
|
}
|
||||||
if (!icon) {
|
if (!icon) {
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
log.debug(`Got an icon from the page.`);
|
log.debug(`Got an icon from the page.`);
|
||||||
|
|
||||||
|
@ -19,13 +19,7 @@ export const supportedPlatforms = [
|
|||||||
|
|
||||||
export function inferPlatform(): string {
|
export function inferPlatform(): string {
|
||||||
const platform = os.platform();
|
const platform = os.platform();
|
||||||
if (
|
if (['darwin', 'linux', 'win32'].includes(platform)) {
|
||||||
platform === 'darwin' ||
|
|
||||||
// @ts-ignore
|
|
||||||
platform === 'mas' ||
|
|
||||||
platform === 'win32' ||
|
|
||||||
platform === 'linux'
|
|
||||||
) {
|
|
||||||
log.debug('Inferred platform', platform);
|
log.debug('Inferred platform', platform);
|
||||||
return platform;
|
return platform;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ const USER_AGENT =
|
|||||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36';
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.1 Safari/537.36';
|
||||||
|
|
||||||
export async function inferTitle(url: string): Promise<string> {
|
export async function inferTitle(url: string): Promise<string> {
|
||||||
const { data } = await axios.get(url, {
|
const { data } = await axios.get<string>(url, {
|
||||||
headers: {
|
headers: {
|
||||||
// Fake user agent for pages like http://messenger.com
|
// Fake user agent for pages like http://messenger.com
|
||||||
'User-Agent': USER_AGENT,
|
'User-Agent': USER_AGENT,
|
||||||
@ -13,7 +13,7 @@ export async function inferTitle(url: string): Promise<string> {
|
|||||||
});
|
});
|
||||||
log.debug(`Fetched ${(data.length / 1024).toFixed(1)} kb page at`, url);
|
log.debug(`Fetched ${(data.length / 1024).toFixed(1)} kb page at`, url);
|
||||||
const inferredTitle =
|
const inferredTitle =
|
||||||
/<\s*title.*?>(?<title>.+?)<\s*\/title\s*?>/i.exec(data).groups?.title ||
|
/<\s*title.*?>(?<title>.+?)<\s*\/title\s*?>/i.exec(data)?.groups?.title ??
|
||||||
'Webapp';
|
'Webapp';
|
||||||
log.debug('Inferred title:', inferredTitle);
|
log.debug('Inferred title:', inferredTitle);
|
||||||
return inferredTitle;
|
return inferredTitle;
|
||||||
|
@ -10,9 +10,14 @@ import { getLatestSafariVersion } from './infer/browsers/inferSafariVersion';
|
|||||||
import { inferArch } from './infer/inferOs';
|
import { inferArch } from './infer/inferOs';
|
||||||
import { buildNativefierApp } from './main';
|
import { buildNativefierApp } from './main';
|
||||||
import { userAgent } from './options/fields/userAgent';
|
import { userAgent } from './options/fields/userAgent';
|
||||||
|
import { NativefierOptions } from './options/model';
|
||||||
|
import { parseJson } from './utils/parseUtils';
|
||||||
|
|
||||||
async function checkApp(appRoot: string, inputOptions: any): Promise<void> {
|
async function checkApp(
|
||||||
const arch = (inputOptions.arch as string) || inferArch();
|
appRoot: string,
|
||||||
|
inputOptions: NativefierOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
const arch = inputOptions.arch ? (inputOptions.arch as string) : inferArch();
|
||||||
if (inputOptions.out !== undefined) {
|
if (inputOptions.out !== undefined) {
|
||||||
expect(
|
expect(
|
||||||
path.join(
|
path.join(
|
||||||
@ -43,28 +48,31 @@ async function checkApp(appRoot: string, inputOptions: any): Promise<void> {
|
|||||||
const appPath = path.join(appRoot, relativeAppFolder);
|
const appPath = path.join(appRoot, relativeAppFolder);
|
||||||
|
|
||||||
const configPath = path.join(appPath, 'nativefier.json');
|
const configPath = path.join(appPath, 'nativefier.json');
|
||||||
const nativefierConfig = JSON.parse(fs.readFileSync(configPath).toString());
|
const nativefierConfig: NativefierOptions | undefined =
|
||||||
expect(inputOptions.targetUrl).toBe(nativefierConfig.targetUrl);
|
parseJson<NativefierOptions>(fs.readFileSync(configPath).toString());
|
||||||
|
expect(nativefierConfig).not.toBeUndefined();
|
||||||
|
|
||||||
|
expect(inputOptions.targetUrl).toBe(nativefierConfig?.targetUrl);
|
||||||
|
|
||||||
// Test name inferring
|
// Test name inferring
|
||||||
expect(nativefierConfig.name).toBe('Google');
|
expect(nativefierConfig?.name).toBe('Google');
|
||||||
|
|
||||||
// Test icon writing
|
// Test icon writing
|
||||||
const iconFile =
|
const iconFile =
|
||||||
inputOptions.platform === 'darwin' ? '../electron.icns' : 'icon.png';
|
inputOptions.platform === 'darwin' ? '../electron.icns' : 'icon.png';
|
||||||
const iconPath = path.join(appPath, iconFile);
|
const iconPath = path.join(appPath, iconFile);
|
||||||
expect(fs.existsSync(iconPath)).toBe(true);
|
expect(fs.existsSync(iconPath)).toEqual(true);
|
||||||
expect(fs.statSync(iconPath).size).toBeGreaterThan(1000);
|
expect(fs.statSync(iconPath).size).toBeGreaterThan(1000);
|
||||||
|
|
||||||
// Test arch
|
// Test arch
|
||||||
if (inputOptions.arch !== undefined) {
|
if (inputOptions.arch !== undefined) {
|
||||||
expect(inputOptions.arch).toBe(nativefierConfig.arch);
|
expect(inputOptions.arch).toEqual(nativefierConfig?.arch);
|
||||||
} else {
|
} else {
|
||||||
expect(os.arch()).toBe(nativefierConfig.arch);
|
expect(os.arch()).toEqual(nativefierConfig?.arch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test electron version
|
// Test electron version
|
||||||
expect(nativefierConfig.electronVersionUsed).toBe(
|
expect(nativefierConfig?.electronVersionUsed).toBe(
|
||||||
inputOptions.electronVersion || DEFAULT_ELECTRON_VERSION,
|
inputOptions.electronVersion || DEFAULT_ELECTRON_VERSION,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -80,10 +88,11 @@ async function checkApp(appRoot: string, inputOptions: any): Promise<void> {
|
|||||||
});
|
});
|
||||||
inputOptions.userAgent = translatedUserAgent || inputOptions.userAgent;
|
inputOptions.userAgent = translatedUserAgent || inputOptions.userAgent;
|
||||||
}
|
}
|
||||||
expect(nativefierConfig.userAgent).toBe(inputOptions.userAgent);
|
|
||||||
|
expect(nativefierConfig?.userAgent).toEqual(inputOptions.userAgent);
|
||||||
|
|
||||||
// Test lang
|
// Test lang
|
||||||
expect(nativefierConfig.lang).toBe(inputOptions.lang);
|
expect(nativefierConfig?.lang).toEqual(inputOptions.lang);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Nativefier', () => {
|
describe('Nativefier', () => {
|
||||||
@ -101,7 +110,8 @@ describe('Nativefier', () => {
|
|||||||
lang: 'en-US',
|
lang: 'en-US',
|
||||||
};
|
};
|
||||||
const appPath = await buildNativefierApp(options);
|
const appPath = await buildNativefierApp(options);
|
||||||
await checkApp(appPath, options);
|
expect(appPath).not.toBeUndefined();
|
||||||
|
await checkApp(appPath as string, options);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -132,7 +142,8 @@ describe('Nativefier upgrade', () => {
|
|||||||
...baseAppOptions,
|
...baseAppOptions,
|
||||||
};
|
};
|
||||||
const appPath = await buildNativefierApp(options);
|
const appPath = await buildNativefierApp(options);
|
||||||
await checkApp(appPath, options);
|
expect(appPath).not.toBeUndefined();
|
||||||
|
await checkApp(appPath as string, options);
|
||||||
|
|
||||||
const upgradeOptions = {
|
const upgradeOptions = {
|
||||||
upgrade: appPath,
|
upgrade: appPath,
|
||||||
@ -142,7 +153,8 @@ describe('Nativefier upgrade', () => {
|
|||||||
const upgradeAppPath = await buildNativefierApp(upgradeOptions);
|
const upgradeAppPath = await buildNativefierApp(upgradeOptions);
|
||||||
options.electronVersion = DEFAULT_ELECTRON_VERSION;
|
options.electronVersion = DEFAULT_ELECTRON_VERSION;
|
||||||
options.userAgent = baseAppOptions.userAgent;
|
options.userAgent = baseAppOptions.userAgent;
|
||||||
await checkApp(upgradeAppPath, options);
|
expect(upgradeAppPath).not.toBeUndefined();
|
||||||
|
await checkApp(upgradeAppPath as string, options);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
|
|
||||||
import { buildNativefierApp } from './build/buildNativefierApp';
|
import { buildNativefierApp } from './build/buildNativefierApp';
|
||||||
|
import { RawOptions } from './options/model';
|
||||||
|
|
||||||
export { buildNativefierApp };
|
export { buildNativefierApp };
|
||||||
|
|
||||||
@ -9,12 +10,12 @@ export { buildNativefierApp };
|
|||||||
* Use the better, modern async `buildNativefierApp` instead if you can!
|
* Use the better, modern async `buildNativefierApp` instead if you can!
|
||||||
*/
|
*/
|
||||||
function buildNativefierAppOldCallbackStyle(
|
function buildNativefierAppOldCallbackStyle(
|
||||||
options: any, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types
|
options: RawOptions, // eslint-disable-line @typescript-eslint/explicit-module-boundary-types
|
||||||
callback: (err: any, result?: any) => void,
|
callback: (err?: Error, result?: string) => void,
|
||||||
): void {
|
): void {
|
||||||
buildNativefierApp(options)
|
buildNativefierApp(options)
|
||||||
.then((result) => callback(null, result))
|
.then((result) => callback(undefined, result))
|
||||||
.catch((err) => callback(err));
|
.catch((err: Error) => callback(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default buildNativefierAppOldCallbackStyle;
|
export default buildNativefierAppOldCallbackStyle;
|
||||||
|
@ -6,7 +6,7 @@ import { AppOptions } from './model';
|
|||||||
/**
|
/**
|
||||||
* Takes the options object and infers new values needing async work
|
* Takes the options object and infers new values needing async work
|
||||||
*/
|
*/
|
||||||
export async function asyncConfig(options: AppOptions): Promise<any> {
|
export async function asyncConfig(options: AppOptions): Promise<AppOptions> {
|
||||||
log.debug('\nPerforming async options post-processing.');
|
log.debug('\nPerforming async options post-processing.');
|
||||||
await processOptions(options);
|
return await processOptions(options);
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,103 @@
|
|||||||
|
import { AppOptions } from '../model';
|
||||||
import { processOptions } from './fields';
|
import { processOptions } from './fields';
|
||||||
|
describe('fields', () => {
|
||||||
|
let options: AppOptions;
|
||||||
|
|
||||||
test('fully-defined async options are returned as-is', async () => {
|
beforeEach(() => {
|
||||||
const options = {
|
options = {
|
||||||
packager: {
|
nativefier: {
|
||||||
icon: '/my/icon.png',
|
accessibilityPrompt: false,
|
||||||
name: 'my beautiful app ',
|
alwaysOnTop: false,
|
||||||
targetUrl: 'https://myurl.com',
|
backgroundColor: undefined,
|
||||||
dir: '/tmp/myapp',
|
basicAuthPassword: undefined,
|
||||||
},
|
basicAuthUsername: undefined,
|
||||||
nativefier: { userAgent: 'random user agent' },
|
blockExternalUrls: false,
|
||||||
};
|
bookmarksMenu: undefined,
|
||||||
// @ts-ignore
|
bounce: false,
|
||||||
await processOptions(options);
|
browserwindowOptions: undefined,
|
||||||
|
clearCache: false,
|
||||||
|
counter: false,
|
||||||
|
crashReporter: undefined,
|
||||||
|
disableContextMenu: false,
|
||||||
|
disableDevTools: false,
|
||||||
|
disableGpu: false,
|
||||||
|
disableOldBuildWarning: false,
|
||||||
|
diskCacheSize: undefined,
|
||||||
|
enableEs3Apis: false,
|
||||||
|
fastQuit: true,
|
||||||
|
fileDownloadOptions: undefined,
|
||||||
|
flashPluginDir: undefined,
|
||||||
|
fullScreen: false,
|
||||||
|
globalShortcuts: undefined,
|
||||||
|
height: undefined,
|
||||||
|
hideWindowFrame: false,
|
||||||
|
ignoreCertificate: false,
|
||||||
|
ignoreGpuBlacklist: false,
|
||||||
|
inject: [],
|
||||||
|
insecure: false,
|
||||||
|
internalUrls: undefined,
|
||||||
|
maximize: false,
|
||||||
|
maxHeight: undefined,
|
||||||
|
minWidth: undefined,
|
||||||
|
minHeight: undefined,
|
||||||
|
maxWidth: undefined,
|
||||||
|
nativefierVersion: '1.0.0',
|
||||||
|
processEnvs: undefined,
|
||||||
|
proxyRules: undefined,
|
||||||
|
showMenuBar: false,
|
||||||
|
singleInstance: false,
|
||||||
|
titleBarStyle: undefined,
|
||||||
|
tray: false,
|
||||||
|
userAgent: undefined,
|
||||||
|
userAgentHonest: false,
|
||||||
|
verbose: false,
|
||||||
|
versionString: '1.0.0',
|
||||||
|
width: undefined,
|
||||||
|
widevine: false,
|
||||||
|
x: undefined,
|
||||||
|
y: undefined,
|
||||||
|
zoom: 1,
|
||||||
|
},
|
||||||
|
packager: {
|
||||||
|
dir: '',
|
||||||
|
platform: process.platform,
|
||||||
|
portable: false,
|
||||||
|
targetUrl: '',
|
||||||
|
upgrade: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
expect(options.packager.icon).toEqual('/my/icon.png');
|
test('fully-defined async options are returned as-is', async () => {
|
||||||
expect(options.packager.name).toEqual('my beautiful app');
|
options.packager.icon = '/my/icon.png';
|
||||||
expect(options.nativefier.userAgent).toEqual('random user agent');
|
options.packager.name = 'my beautiful app ';
|
||||||
});
|
options.packager.platform = 'darwin';
|
||||||
|
options.nativefier.userAgent = 'random user agent';
|
||||||
test('user agent is ignored if not provided', async () => {
|
await processOptions(options);
|
||||||
const options = {
|
|
||||||
packager: {
|
expect(options.packager.icon).toEqual('/my/icon.png');
|
||||||
icon: '/my/icon.png',
|
expect(options.packager.name).toEqual('my beautiful app');
|
||||||
name: 'my beautiful app ',
|
expect(options.nativefier.userAgent).toEqual('random user agent');
|
||||||
targetUrl: 'https://myurl.com',
|
});
|
||||||
dir: '/tmp/myapp',
|
|
||||||
platform: 'linux',
|
test('name has spaces stripped in linux', async () => {
|
||||||
},
|
options.packager.name = 'my beautiful app ';
|
||||||
nativefier: { userAgent: undefined },
|
options.packager.platform = 'linux';
|
||||||
};
|
await processOptions(options);
|
||||||
// @ts-ignore
|
|
||||||
await processOptions(options);
|
expect(options.packager.name).toEqual('mybeautifulapp');
|
||||||
|
});
|
||||||
expect(options.nativefier.userAgent).toBeUndefined();
|
|
||||||
});
|
test('user agent is ignored if not provided', async () => {
|
||||||
|
await processOptions(options);
|
||||||
test('user agent short code is populated', async () => {
|
|
||||||
const options = {
|
expect(options.nativefier.userAgent).toBeUndefined();
|
||||||
packager: {
|
});
|
||||||
icon: '/my/icon.png',
|
|
||||||
name: 'my beautiful app ',
|
test('user agent short code is populated', async () => {
|
||||||
targetUrl: 'https://myurl.com',
|
options.nativefier.userAgent = 'edge';
|
||||||
dir: '/tmp/myapp',
|
await processOptions(options);
|
||||||
platform: 'linux',
|
|
||||||
},
|
expect(options.nativefier.userAgent).not.toBe('edge');
|
||||||
nativefier: { userAgent: 'edge' },
|
});
|
||||||
};
|
|
||||||
// @ts-ignore
|
|
||||||
await processOptions(options);
|
|
||||||
|
|
||||||
expect(options.nativefier.userAgent).not.toBe('edge');
|
|
||||||
});
|
});
|
||||||
|
@ -3,13 +3,19 @@ import { userAgent } from './userAgent';
|
|||||||
import { AppOptions } from '../model';
|
import { AppOptions } from '../model';
|
||||||
import { name } from './name';
|
import { name } from './name';
|
||||||
|
|
||||||
const OPTION_POSTPROCESSORS = [
|
type OptionPostprocessor = {
|
||||||
|
namespace: 'nativefier' | 'packager';
|
||||||
|
option: 'icon' | 'name' | 'userAgent';
|
||||||
|
processor: (options: AppOptions) => Promise<string | undefined>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPTION_POSTPROCESSORS: OptionPostprocessor[] = [
|
||||||
{ namespace: 'nativefier', option: 'userAgent', processor: userAgent },
|
{ namespace: 'nativefier', option: 'userAgent', processor: userAgent },
|
||||||
{ namespace: 'packager', option: 'icon', processor: icon },
|
{ namespace: 'packager', option: 'icon', processor: icon },
|
||||||
{ namespace: 'packager', option: 'name', processor: name },
|
{ namespace: 'packager', option: 'name', processor: name },
|
||||||
];
|
];
|
||||||
|
|
||||||
export async function processOptions(options: AppOptions): Promise<void> {
|
export async function processOptions(options: AppOptions): Promise<AppOptions> {
|
||||||
const processedOptions = await Promise.all(
|
const processedOptions = await Promise.all(
|
||||||
OPTION_POSTPROCESSORS.map(async ({ namespace, option, processor }) => {
|
OPTION_POSTPROCESSORS.map(async ({ namespace, option, processor }) => {
|
||||||
const result = await processor(options);
|
const result = await processor(options);
|
||||||
@ -22,8 +28,15 @@ export async function processOptions(options: AppOptions): Promise<void> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for (const { namespace, option, result } of processedOptions) {
|
for (const { namespace, option, result } of processedOptions) {
|
||||||
if (result !== null) {
|
if (
|
||||||
|
result &&
|
||||||
|
namespace in options &&
|
||||||
|
options[namespace] &&
|
||||||
|
option in options[namespace]
|
||||||
|
) {
|
||||||
|
// @ts-expect-error We're fiddling with objects at the string key level, which TS doesn't support well.
|
||||||
options[namespace][option] = result;
|
options[namespace][option] = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return options;
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ const ICON_PARAMS_NEEDS_INFER = {
|
|||||||
describe('when the icon parameter is passed', () => {
|
describe('when the icon parameter is passed', () => {
|
||||||
test('it should return the icon parameter', async () => {
|
test('it should return the icon parameter', async () => {
|
||||||
expect(inferIcon).toHaveBeenCalledTimes(0);
|
expect(inferIcon).toHaveBeenCalledTimes(0);
|
||||||
await expect(icon(ICON_PARAMS_PROVIDED)).resolves.toBe(null);
|
await expect(icon(ICON_PARAMS_PROVIDED)).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ describe('when the icon parameter is not passed', () => {
|
|||||||
);
|
);
|
||||||
const result = await icon(ICON_PARAMS_NEEDS_INFER);
|
const result = await icon(ICON_PARAMS_NEEDS_INFER);
|
||||||
|
|
||||||
expect(result).toBe(null);
|
expect(result).toBeUndefined();
|
||||||
expect(inferIcon).toHaveBeenCalledWith(
|
expect(inferIcon).toHaveBeenCalledWith(
|
||||||
ICON_PARAMS_NEEDS_INFER.packager.targetUrl,
|
ICON_PARAMS_NEEDS_INFER.packager.targetUrl,
|
||||||
ICON_PARAMS_NEEDS_INFER.packager.platform,
|
ICON_PARAMS_NEEDS_INFER.packager.platform,
|
||||||
|
@ -10,10 +10,15 @@ type IconParams = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function icon(options: IconParams): Promise<string> {
|
export async function icon(options: IconParams): Promise<string | undefined> {
|
||||||
if (options.packager.icon) {
|
if (options.packager.icon) {
|
||||||
log.debug('Got icon from options. Using it, no inferring needed');
|
log.debug('Got icon from options. Using it, no inferring needed');
|
||||||
return null;
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!options.packager.platform) {
|
||||||
|
log.error('No platform specified. Icon can not be inferrerd.');
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -21,11 +26,8 @@ export async function icon(options: IconParams): Promise<string> {
|
|||||||
options.packager.targetUrl,
|
options.packager.targetUrl,
|
||||||
options.packager.platform,
|
options.packager.platform,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (err: unknown) {
|
||||||
log.warn(
|
log.warn('Cannot automatically retrieve the app icon:', err);
|
||||||
'Cannot automatically retrieve the app icon:',
|
return undefined;
|
||||||
error.message || '',
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,24 +17,20 @@ async function tryToInferName(targetUrl: string): Promise<string> {
|
|||||||
log.debug('Inferring name for', targetUrl);
|
log.debug('Inferring name for', targetUrl);
|
||||||
const pageTitle = await inferTitle(targetUrl);
|
const pageTitle = await inferTitle(targetUrl);
|
||||||
return pageTitle || DEFAULT_APP_NAME;
|
return pageTitle || DEFAULT_APP_NAME;
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
log.warn(
|
log.warn(
|
||||||
`Unable to automatically determine app name, falling back to '${DEFAULT_APP_NAME}'. Reason: ${(
|
`Unable to automatically determine app name, falling back to '${DEFAULT_APP_NAME}'.`,
|
||||||
err as Error
|
err,
|
||||||
).toString()}`,
|
|
||||||
);
|
);
|
||||||
return DEFAULT_APP_NAME;
|
return DEFAULT_APP_NAME;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function name(options: NameParams): Promise<string> {
|
export async function name(options: NameParams): Promise<string> {
|
||||||
if (options.packager.name) {
|
let name: string | undefined = options.packager.name;
|
||||||
log.debug(
|
if (!name) {
|
||||||
`Got name ${options.packager.name} from options. No inferring needed`,
|
name = await tryToInferName(options.packager.targetUrl);
|
||||||
);
|
|
||||||
return sanitizeFilename(options.packager.platform, options.packager.name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const inferredName = await tryToInferName(options.packager.targetUrl);
|
return sanitizeFilename(options.packager.platform, name);
|
||||||
return sanitizeFilename(options.packager.platform, inferredName);
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ test('when a userAgent parameter is passed', async () => {
|
|||||||
packager: {},
|
packager: {},
|
||||||
nativefier: { userAgent: 'valid user agent' },
|
nativefier: { userAgent: 'valid user agent' },
|
||||||
};
|
};
|
||||||
await expect(userAgent(params)).resolves.toBeNull();
|
await expect(userAgent(params)).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('no userAgent parameter is passed', async () => {
|
test('no userAgent parameter is passed', async () => {
|
||||||
@ -20,7 +20,7 @@ test('no userAgent parameter is passed', async () => {
|
|||||||
packager: { platform: 'mac' },
|
packager: { platform: 'mac' },
|
||||||
nativefier: {},
|
nativefier: {},
|
||||||
};
|
};
|
||||||
await expect(userAgent(params)).resolves.toBeNull();
|
await expect(userAgent(params)).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('edge userAgent parameter is passed', async () => {
|
test('edge userAgent parameter is passed', async () => {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import * as log from 'loglevel';
|
import * as log from 'loglevel';
|
||||||
|
import { DEFAULT_ELECTRON_VERSION } from '../../constants';
|
||||||
|
|
||||||
import { getChromeVersionForElectronVersion } from '../../infer/browsers/inferChromeVersion';
|
import { getChromeVersionForElectronVersion } from '../../infer/browsers/inferChromeVersion';
|
||||||
import { getLatestFirefoxVersion } from '../../infer/browsers/inferFirefoxVersion';
|
import { getLatestFirefoxVersion } from '../../infer/browsers/inferFirefoxVersion';
|
||||||
import { getLatestSafariVersion } from '../../infer/browsers/inferSafariVersion';
|
import { getLatestSafariVersion } from '../../infer/browsers/inferSafariVersion';
|
||||||
import { normalizePlatform } from '../optionsMain';
|
import { normalizePlatform } from '../optionsMain';
|
||||||
|
|
||||||
type UserAgentOpts = {
|
export type UserAgentOpts = {
|
||||||
packager: {
|
packager: {
|
||||||
electronVersion?: string;
|
electronVersion?: string;
|
||||||
platform?: string;
|
platform?: string;
|
||||||
@ -15,22 +16,27 @@ type UserAgentOpts = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const USER_AGENT_PLATFORM_MAPS = {
|
const USER_AGENT_PLATFORM_MAPS: Record<string, string> = {
|
||||||
darwin: 'Macintosh; Intel Mac OS X 10_15_7',
|
darwin: 'Macintosh; Intel Mac OS X 10_15_7',
|
||||||
linux: 'X11; Linux x86_64',
|
linux: 'X11; Linux x86_64',
|
||||||
win32: 'Windows NT 10.0; Win64; x64',
|
win32: 'Windows NT 10.0; Win64; x64',
|
||||||
};
|
};
|
||||||
|
|
||||||
const USER_AGENT_SHORT_CODE_MAPS = {
|
const USER_AGENT_SHORT_CODE_MAPS: Record<
|
||||||
|
string,
|
||||||
|
(platform: string, electronVersion: string) => Promise<string>
|
||||||
|
> = {
|
||||||
edge: edgeUserAgent,
|
edge: edgeUserAgent,
|
||||||
firefox: firefoxUserAgent,
|
firefox: firefoxUserAgent,
|
||||||
safari: safariUserAgent,
|
safari: safariUserAgent,
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function userAgent(options: UserAgentOpts): Promise<string> {
|
export async function userAgent(
|
||||||
|
options: UserAgentOpts,
|
||||||
|
): Promise<string | undefined> {
|
||||||
if (!options.nativefier.userAgent) {
|
if (!options.nativefier.userAgent) {
|
||||||
// No user agent got passed. Let's handle it with the app.userAgentFallback
|
// No user agent got passed. Let's handle it with the app.userAgentFallback
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -43,19 +49,22 @@ export async function userAgent(options: UserAgentOpts): Promise<string> {
|
|||||||
`${options.nativefier.userAgent.toLowerCase()} not found in`,
|
`${options.nativefier.userAgent.toLowerCase()} not found in`,
|
||||||
Object.keys(USER_AGENT_SHORT_CODE_MAPS),
|
Object.keys(USER_AGENT_SHORT_CODE_MAPS),
|
||||||
);
|
);
|
||||||
return null;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.packager.platform = normalizePlatform(options.packager.platform);
|
options.packager.platform = normalizePlatform(options.packager.platform);
|
||||||
|
|
||||||
const userAgentPlatform =
|
const userAgentPlatform: string =
|
||||||
USER_AGENT_PLATFORM_MAPS[
|
USER_AGENT_PLATFORM_MAPS[
|
||||||
options.packager.platform === 'mas' ? 'darwin' : options.packager.platform
|
options.packager.platform === 'mas' ? 'darwin' : options.packager.platform
|
||||||
];
|
];
|
||||||
|
|
||||||
const mapFunction = USER_AGENT_SHORT_CODE_MAPS[options.nativefier.userAgent];
|
const mapFunction = USER_AGENT_SHORT_CODE_MAPS[options.nativefier.userAgent];
|
||||||
|
|
||||||
return await mapFunction(userAgentPlatform, options.packager.electronVersion);
|
return await mapFunction(
|
||||||
|
userAgentPlatform,
|
||||||
|
options.packager.electronVersion ?? DEFAULT_ELECTRON_VERSION,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function edgeUserAgent(
|
async function edgeUserAgent(
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
import { CreateOptions } from 'asar';
|
||||||
import * as electronPackager from 'electron-packager';
|
import * as electronPackager from 'electron-packager';
|
||||||
|
|
||||||
export interface ElectronPackagerOptions extends electronPackager.Options {
|
export interface ElectronPackagerOptions extends electronPackager.Options {
|
||||||
portable: boolean;
|
portable: boolean;
|
||||||
platform: string;
|
platform?: string;
|
||||||
targetUrl: string;
|
targetUrl: string;
|
||||||
upgrade: boolean;
|
upgrade: boolean;
|
||||||
upgradeFrom?: string;
|
upgradeFrom?: string;
|
||||||
@ -13,60 +14,152 @@ export interface AppOptions {
|
|||||||
nativefier: {
|
nativefier: {
|
||||||
accessibilityPrompt: boolean;
|
accessibilityPrompt: boolean;
|
||||||
alwaysOnTop: boolean;
|
alwaysOnTop: boolean;
|
||||||
backgroundColor: string;
|
backgroundColor?: string;
|
||||||
basicAuthPassword: string;
|
basicAuthPassword?: string;
|
||||||
basicAuthUsername: string;
|
basicAuthUsername?: string;
|
||||||
blockExternalUrls: boolean;
|
blockExternalUrls: boolean;
|
||||||
bookmarksMenu: string;
|
bookmarksMenu?: string;
|
||||||
bounce: boolean;
|
bounce: boolean;
|
||||||
browserwindowOptions: any;
|
browserwindowOptions?: BrowserWindowOptions;
|
||||||
clearCache: boolean;
|
clearCache: boolean;
|
||||||
counter: boolean;
|
counter: boolean;
|
||||||
crashReporter: string;
|
crashReporter?: string;
|
||||||
disableContextMenu: boolean;
|
disableContextMenu: boolean;
|
||||||
disableDevTools: boolean;
|
disableDevTools: boolean;
|
||||||
disableGpu: boolean;
|
disableGpu: boolean;
|
||||||
disableOldBuildWarning: boolean;
|
disableOldBuildWarning: boolean;
|
||||||
diskCacheSize: number;
|
diskCacheSize?: number;
|
||||||
electronVersionUsed?: string;
|
electronVersionUsed?: string;
|
||||||
enableEs3Apis: boolean;
|
enableEs3Apis: boolean;
|
||||||
fastQuit: boolean;
|
fastQuit: boolean;
|
||||||
fileDownloadOptions: any;
|
fileDownloadOptions: unknown;
|
||||||
flashPluginDir: string;
|
flashPluginDir?: string;
|
||||||
fullScreen: boolean;
|
fullScreen: boolean;
|
||||||
globalShortcuts: any;
|
globalShortcuts?: GlobalShortcut[];
|
||||||
hideWindowFrame: boolean;
|
hideWindowFrame: boolean;
|
||||||
ignoreCertificate: boolean;
|
ignoreCertificate: boolean;
|
||||||
ignoreGpuBlacklist: boolean;
|
ignoreGpuBlacklist: boolean;
|
||||||
inject: string[];
|
inject?: string[];
|
||||||
insecure: boolean;
|
insecure: boolean;
|
||||||
internalUrls: string;
|
internalUrls?: string;
|
||||||
lang?: string;
|
lang?: string;
|
||||||
maximize: boolean;
|
maximize: boolean;
|
||||||
nativefierVersion: string;
|
nativefierVersion?: string;
|
||||||
processEnvs: string;
|
processEnvs?: string;
|
||||||
proxyRules: string;
|
proxyRules?: string;
|
||||||
showMenuBar: boolean;
|
showMenuBar: boolean;
|
||||||
singleInstance: boolean;
|
singleInstance: boolean;
|
||||||
titleBarStyle: string;
|
titleBarStyle?: string;
|
||||||
tray: string | boolean;
|
tray: string | boolean;
|
||||||
userAgent: string;
|
userAgent?: string;
|
||||||
userAgentHonest: boolean;
|
userAgentHonest: boolean;
|
||||||
verbose: boolean;
|
verbose: boolean;
|
||||||
versionString: string;
|
versionString?: string;
|
||||||
width: number;
|
width?: number;
|
||||||
widevine: boolean;
|
widevine: boolean;
|
||||||
height: number;
|
height?: number;
|
||||||
minWidth: number;
|
minWidth?: number;
|
||||||
minHeight: number;
|
minHeight?: number;
|
||||||
maxWidth: number;
|
maxWidth?: number;
|
||||||
maxHeight: number;
|
maxHeight?: number;
|
||||||
x: number;
|
x?: number;
|
||||||
y: number;
|
y?: number;
|
||||||
zoom: number;
|
zoom: number;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type BrowserWindowOptions = Record<string, unknown>;
|
||||||
|
|
||||||
|
export type GlobalShortcut = {
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type NativefierOptions = Partial<
|
export type NativefierOptions = Partial<
|
||||||
AppOptions['packager'] & AppOptions['nativefier']
|
AppOptions['packager'] & AppOptions['nativefier']
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
export type OutputOptions = NativefierOptions & {
|
||||||
|
buildDate: number;
|
||||||
|
isUpgrade: boolean;
|
||||||
|
oldBuildWarningText: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PackageJSON = {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RawOptions = {
|
||||||
|
accessibilityPrompt?: boolean;
|
||||||
|
alwaysOnTop?: boolean;
|
||||||
|
appCopyright?: string;
|
||||||
|
appVersion?: string;
|
||||||
|
arch?: string | string[];
|
||||||
|
asar?: boolean | CreateOptions;
|
||||||
|
backgroundColor?: string;
|
||||||
|
basicAuthPassword?: string;
|
||||||
|
basicAuthUsername?: string;
|
||||||
|
blockExternalUrls?: boolean;
|
||||||
|
bookmarksMenu?: string;
|
||||||
|
bounce?: boolean;
|
||||||
|
browserwindowOptions?: BrowserWindowOptions;
|
||||||
|
buildVersion?: string;
|
||||||
|
clearCache?: boolean;
|
||||||
|
conceal?: boolean;
|
||||||
|
counter?: boolean;
|
||||||
|
crashReporter?: string;
|
||||||
|
darwinDarkModeSupport?: boolean;
|
||||||
|
disableContextMenu?: boolean;
|
||||||
|
disableDevTools?: boolean;
|
||||||
|
disableGpu?: boolean;
|
||||||
|
disableOldBuildWarning?: boolean;
|
||||||
|
disableOldBuildWarningYesiknowitisinsecure?: boolean;
|
||||||
|
diskCacheSize?: number;
|
||||||
|
electronVersion?: string;
|
||||||
|
electronVersionUsed?: string;
|
||||||
|
enableEs3Apis?: boolean;
|
||||||
|
fastQuit?: boolean;
|
||||||
|
fileDownloadOptions?: unknown;
|
||||||
|
flashPath?: string;
|
||||||
|
flashPluginDir?: string;
|
||||||
|
fullScreen?: boolean;
|
||||||
|
globalShortcuts?: string | GlobalShortcut[];
|
||||||
|
height?: number;
|
||||||
|
hideWindowFrame?: boolean;
|
||||||
|
icon?: string;
|
||||||
|
ignoreCertificate?: boolean;
|
||||||
|
ignoreGpuBlacklist?: boolean;
|
||||||
|
inject?: string[];
|
||||||
|
insecure?: boolean;
|
||||||
|
internalUrls?: string;
|
||||||
|
lang?: string;
|
||||||
|
maxHeight?: number;
|
||||||
|
maximize?: boolean;
|
||||||
|
maxWidth?: number;
|
||||||
|
minHeight?: number;
|
||||||
|
minWidth?: number;
|
||||||
|
name?: string;
|
||||||
|
nativefierVersion?: string;
|
||||||
|
out?: string;
|
||||||
|
overwrite?: boolean;
|
||||||
|
platform?: string;
|
||||||
|
portable?: boolean;
|
||||||
|
processEnvs?: string;
|
||||||
|
proxyRules?: string;
|
||||||
|
showMenuBar?: boolean;
|
||||||
|
singleInstance?: boolean;
|
||||||
|
targetUrl?: string;
|
||||||
|
titleBarStyle?: string;
|
||||||
|
tray?: string | boolean;
|
||||||
|
upgrade?: string | boolean;
|
||||||
|
upgradeFrom?: string;
|
||||||
|
userAgent?: string;
|
||||||
|
userAgentHonest?: boolean;
|
||||||
|
verbose?: boolean;
|
||||||
|
versionString?: string;
|
||||||
|
widevine?: boolean;
|
||||||
|
width?: number;
|
||||||
|
win32metadata?: electronPackager.Win32MetadataOptions;
|
||||||
|
x?: number;
|
||||||
|
y?: number;
|
||||||
|
zoom?: number;
|
||||||
|
};
|
||||||
|
@ -22,8 +22,9 @@ export function normalizeUrl(urlToNormalize: string): string {
|
|||||||
let parsedUrl: url.URL;
|
let parsedUrl: url.URL;
|
||||||
try {
|
try {
|
||||||
parsedUrl = new url.URL(urlWithProtocol);
|
parsedUrl = new url.URL(urlWithProtocol);
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
throw `Your url "${urlWithProtocol}" is invalid`;
|
log.error('normalizeUrl ERROR', err);
|
||||||
|
throw new Error(`Your url "${urlWithProtocol}" is invalid`);
|
||||||
}
|
}
|
||||||
const normalizedUrl = parsedUrl.toString();
|
const normalizedUrl = parsedUrl.toString();
|
||||||
log.debug(`Normalized URL ${urlToNormalize} to:`, normalizedUrl);
|
log.debug(`Normalized URL ${urlToNormalize} to:`, normalizedUrl);
|
||||||
|
@ -1,9 +1,71 @@
|
|||||||
import { getOptions, normalizePlatform } from './optionsMain';
|
import { getOptions, normalizePlatform } from './optionsMain';
|
||||||
import * as asyncConfig from './asyncConfig';
|
import * as asyncConfig from './asyncConfig';
|
||||||
import { inferPlatform } from '../infer/inferOs';
|
import { inferPlatform } from '../infer/inferOs';
|
||||||
|
import { AppOptions } from './model';
|
||||||
|
|
||||||
const mockedAsyncConfig = { some: 'options' };
|
|
||||||
let asyncConfigMock: jest.SpyInstance;
|
let asyncConfigMock: jest.SpyInstance;
|
||||||
|
const mockedAsyncConfig: AppOptions = {
|
||||||
|
nativefier: {
|
||||||
|
accessibilityPrompt: false,
|
||||||
|
alwaysOnTop: false,
|
||||||
|
backgroundColor: undefined,
|
||||||
|
basicAuthPassword: undefined,
|
||||||
|
basicAuthUsername: undefined,
|
||||||
|
blockExternalUrls: false,
|
||||||
|
bookmarksMenu: undefined,
|
||||||
|
bounce: false,
|
||||||
|
browserwindowOptions: undefined,
|
||||||
|
clearCache: false,
|
||||||
|
counter: false,
|
||||||
|
crashReporter: undefined,
|
||||||
|
disableContextMenu: false,
|
||||||
|
disableDevTools: false,
|
||||||
|
disableGpu: false,
|
||||||
|
disableOldBuildWarning: false,
|
||||||
|
diskCacheSize: undefined,
|
||||||
|
enableEs3Apis: false,
|
||||||
|
fastQuit: true,
|
||||||
|
fileDownloadOptions: undefined,
|
||||||
|
flashPluginDir: undefined,
|
||||||
|
fullScreen: false,
|
||||||
|
globalShortcuts: undefined,
|
||||||
|
height: undefined,
|
||||||
|
hideWindowFrame: false,
|
||||||
|
ignoreCertificate: false,
|
||||||
|
ignoreGpuBlacklist: false,
|
||||||
|
inject: [],
|
||||||
|
insecure: false,
|
||||||
|
internalUrls: undefined,
|
||||||
|
maximize: false,
|
||||||
|
maxHeight: undefined,
|
||||||
|
minWidth: undefined,
|
||||||
|
minHeight: undefined,
|
||||||
|
maxWidth: undefined,
|
||||||
|
nativefierVersion: '1.0.0',
|
||||||
|
processEnvs: undefined,
|
||||||
|
proxyRules: undefined,
|
||||||
|
showMenuBar: false,
|
||||||
|
singleInstance: false,
|
||||||
|
titleBarStyle: undefined,
|
||||||
|
tray: false,
|
||||||
|
userAgent: undefined,
|
||||||
|
userAgentHonest: false,
|
||||||
|
verbose: false,
|
||||||
|
versionString: '1.0.0',
|
||||||
|
width: undefined,
|
||||||
|
widevine: false,
|
||||||
|
x: undefined,
|
||||||
|
y: undefined,
|
||||||
|
zoom: 1,
|
||||||
|
},
|
||||||
|
packager: {
|
||||||
|
dir: '',
|
||||||
|
platform: process.platform,
|
||||||
|
portable: false,
|
||||||
|
targetUrl: '',
|
||||||
|
upgrade: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
asyncConfigMock = jest
|
asyncConfigMock = jest
|
||||||
@ -18,8 +80,8 @@ test('it should call the async config', async () => {
|
|||||||
const result = await getOptions(params);
|
const result = await getOptions(params);
|
||||||
expect(asyncConfigMock).toHaveBeenCalledWith(
|
expect(asyncConfigMock).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
packager: expect.anything(),
|
packager: expect.anything() as AppOptions['packager'],
|
||||||
nativefier: expect.anything(),
|
nativefier: expect.anything() as AppOptions['nativefier'],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(result.packager.targetUrl).toEqual(params.targetUrl);
|
expect(result.packager.targetUrl).toEqual(params.targetUrl);
|
||||||
@ -34,7 +96,7 @@ test('it should set the accessibility prompt option to true by default', async (
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
nativefier: expect.objectContaining({
|
nativefier: expect.objectContaining({
|
||||||
accessibilityPrompt: true,
|
accessibilityPrompt: true,
|
||||||
}),
|
}) as AppOptions['nativefier'],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
expect(result.nativefier.accessibilityPrompt).toEqual(true);
|
expect(result.nativefier.accessibilityPrompt).toEqual(true);
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import * as debug from 'debug';
|
||||||
import * as log from 'loglevel';
|
import * as log from 'loglevel';
|
||||||
|
|
||||||
// package.json is `require`d to let tsc strip the `src` folder by determining
|
// package.json is `require`d to let tsc strip the `src` folder by determining
|
||||||
// baseUrl=src. A static import would prevent that and cause an ugly extra `src` folder in `lib`
|
// baseUrl=src. A static import would prevent that and cause an ugly extra `src` folder in `lib`
|
||||||
const packageJson = require('../../package.json'); // eslint-disable-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const packageJson: {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
} = require('../../package.json');
|
||||||
import {
|
import {
|
||||||
DEFAULT_ELECTRON_VERSION,
|
DEFAULT_ELECTRON_VERSION,
|
||||||
PLACEHOLDER_APP_DIR,
|
PLACEHOLDER_APP_DIR,
|
||||||
@ -13,8 +19,9 @@ import {
|
|||||||
} from '../constants';
|
} from '../constants';
|
||||||
import { inferPlatform, inferArch } from '../infer/inferOs';
|
import { inferPlatform, inferArch } from '../infer/inferOs';
|
||||||
import { asyncConfig } from './asyncConfig';
|
import { asyncConfig } from './asyncConfig';
|
||||||
import { AppOptions } from './model';
|
import { AppOptions, GlobalShortcut, RawOptions } from './model';
|
||||||
import { normalizeUrl } from './normalizeUrl';
|
import { normalizeUrl } from './normalizeUrl';
|
||||||
|
import { parseJson } from '../utils/parseUtils';
|
||||||
|
|
||||||
const SEMVER_VERSION_NUMBER_REGEX = /\d+\.\d+\.\d+[-_\w\d.]*/;
|
const SEMVER_VERSION_NUMBER_REGEX = /\d+\.\d+\.\d+[-_\w\d.]*/;
|
||||||
|
|
||||||
@ -22,31 +29,33 @@ const SEMVER_VERSION_NUMBER_REGEX = /\d+\.\d+\.\d+[-_\w\d.]*/;
|
|||||||
* Process and validate raw user arguments
|
* Process and validate raw user arguments
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export async function getOptions(rawOptions: any): Promise<AppOptions> {
|
export async function getOptions(rawOptions: RawOptions): Promise<AppOptions> {
|
||||||
const options: AppOptions = {
|
const options: AppOptions = {
|
||||||
packager: {
|
packager: {
|
||||||
appCopyright: rawOptions.appCopyright,
|
appCopyright: rawOptions.appCopyright,
|
||||||
appVersion: rawOptions.appVersion,
|
appVersion: rawOptions.appVersion,
|
||||||
arch: rawOptions.arch || inferArch(),
|
arch: rawOptions.arch ?? inferArch(),
|
||||||
asar: rawOptions.asar || rawOptions.conceal || false,
|
asar: rawOptions.asar ?? rawOptions.conceal ?? false,
|
||||||
buildVersion: rawOptions.buildVersion,
|
buildVersion: rawOptions.buildVersion,
|
||||||
darwinDarkModeSupport: rawOptions.darwinDarkModeSupport || false,
|
darwinDarkModeSupport: rawOptions.darwinDarkModeSupport ?? false,
|
||||||
dir: PLACEHOLDER_APP_DIR,
|
dir: PLACEHOLDER_APP_DIR,
|
||||||
electronVersion: rawOptions.electronVersion || DEFAULT_ELECTRON_VERSION,
|
electronVersion: rawOptions.electronVersion ?? DEFAULT_ELECTRON_VERSION,
|
||||||
icon: rawOptions.icon,
|
icon: rawOptions.icon,
|
||||||
name: typeof rawOptions.name === 'string' ? rawOptions.name : '',
|
name: typeof rawOptions.name === 'string' ? rawOptions.name : '',
|
||||||
out: rawOptions.out || process.cwd(),
|
out: rawOptions.out ?? process.cwd(),
|
||||||
overwrite: rawOptions.overwrite,
|
overwrite: rawOptions.overwrite,
|
||||||
platform: rawOptions.platform,
|
platform: rawOptions.platform,
|
||||||
portable: rawOptions.portable || false,
|
portable: rawOptions.portable ?? false,
|
||||||
targetUrl:
|
targetUrl:
|
||||||
rawOptions.targetUrl === undefined
|
rawOptions.targetUrl === undefined
|
||||||
? '' // We'll plug this in later via upgrade
|
? '' // We'll plug this in later via upgrade
|
||||||
: normalizeUrl(rawOptions.targetUrl),
|
: normalizeUrl(rawOptions.targetUrl),
|
||||||
tmpdir: false, // workaround for electron-packager#375
|
tmpdir: false, // workaround for electron-packager#375
|
||||||
upgrade: rawOptions.upgrade !== undefined ? true : false,
|
upgrade: rawOptions.upgrade !== undefined ? true : false,
|
||||||
upgradeFrom: rawOptions.upgrade,
|
upgradeFrom:
|
||||||
win32metadata: rawOptions.win32metadata || {
|
(rawOptions.upgradeFrom as string) ??
|
||||||
|
((rawOptions.upgrade as string) || undefined),
|
||||||
|
win32metadata: rawOptions.win32metadata ?? {
|
||||||
ProductName: rawOptions.name,
|
ProductName: rawOptions.name,
|
||||||
InternalName: rawOptions.name,
|
InternalName: rawOptions.name,
|
||||||
FileDescription: rawOptions.name,
|
FileDescription: rawOptions.name,
|
||||||
@ -54,70 +63,70 @@ export async function getOptions(rawOptions: any): Promise<AppOptions> {
|
|||||||
},
|
},
|
||||||
nativefier: {
|
nativefier: {
|
||||||
accessibilityPrompt: true,
|
accessibilityPrompt: true,
|
||||||
alwaysOnTop: rawOptions.alwaysOnTop || false,
|
alwaysOnTop: rawOptions.alwaysOnTop ?? false,
|
||||||
backgroundColor: rawOptions.backgroundColor || null,
|
backgroundColor: rawOptions.backgroundColor,
|
||||||
basicAuthPassword: rawOptions.basicAuthPassword || null,
|
basicAuthPassword: rawOptions.basicAuthPassword,
|
||||||
basicAuthUsername: rawOptions.basicAuthUsername || null,
|
basicAuthUsername: rawOptions.basicAuthUsername,
|
||||||
blockExternalUrls: rawOptions.blockExternalUrls || false,
|
blockExternalUrls: rawOptions.blockExternalUrls ?? false,
|
||||||
bookmarksMenu: rawOptions.bookmarksMenu || null,
|
bookmarksMenu: rawOptions.bookmarksMenu,
|
||||||
bounce: rawOptions.bounce || false,
|
bounce: rawOptions.bounce ?? false,
|
||||||
browserwindowOptions: rawOptions.browserwindowOptions,
|
browserwindowOptions: rawOptions.browserwindowOptions,
|
||||||
clearCache: rawOptions.clearCache || false,
|
clearCache: rawOptions.clearCache ?? false,
|
||||||
counter: rawOptions.counter || false,
|
counter: rawOptions.counter ?? false,
|
||||||
crashReporter: rawOptions.crashReporter,
|
crashReporter: rawOptions.crashReporter,
|
||||||
disableContextMenu: rawOptions.disableContextMenu,
|
disableContextMenu: rawOptions.disableContextMenu ?? false,
|
||||||
disableDevTools: rawOptions.disableDevTools,
|
disableDevTools: rawOptions.disableDevTools ?? false,
|
||||||
disableGpu: rawOptions.disableGpu || false,
|
disableGpu: rawOptions.disableGpu ?? false,
|
||||||
diskCacheSize: rawOptions.diskCacheSize || null,
|
diskCacheSize: rawOptions.diskCacheSize,
|
||||||
disableOldBuildWarning:
|
disableOldBuildWarning:
|
||||||
rawOptions.disableOldBuildWarningYesiknowitisinsecure || false,
|
rawOptions.disableOldBuildWarningYesiknowitisinsecure ?? false,
|
||||||
enableEs3Apis: rawOptions.enableEs3Apis || false,
|
enableEs3Apis: rawOptions.enableEs3Apis ?? false,
|
||||||
fastQuit: rawOptions.fastQuit || false,
|
fastQuit: rawOptions.fastQuit ?? false,
|
||||||
fileDownloadOptions: rawOptions.fileDownloadOptions,
|
fileDownloadOptions: rawOptions.fileDownloadOptions,
|
||||||
flashPluginDir: rawOptions.flashPath || rawOptions.flash || null,
|
flashPluginDir: rawOptions.flashPath,
|
||||||
fullScreen: rawOptions.fullScreen || false,
|
fullScreen: rawOptions.fullScreen ?? false,
|
||||||
globalShortcuts: null,
|
globalShortcuts: undefined,
|
||||||
hideWindowFrame: rawOptions.hideWindowFrame,
|
hideWindowFrame: rawOptions.hideWindowFrame ?? false,
|
||||||
ignoreCertificate: rawOptions.ignoreCertificate || false,
|
ignoreCertificate: rawOptions.ignoreCertificate ?? false,
|
||||||
ignoreGpuBlacklist: rawOptions.ignoreGpuBlacklist || false,
|
ignoreGpuBlacklist: rawOptions.ignoreGpuBlacklist ?? false,
|
||||||
inject: rawOptions.inject || [],
|
inject: rawOptions.inject ?? [],
|
||||||
insecure: rawOptions.insecure || false,
|
insecure: rawOptions.insecure ?? false,
|
||||||
internalUrls: rawOptions.internalUrls || null,
|
internalUrls: rawOptions.internalUrls,
|
||||||
lang: rawOptions.lang || undefined,
|
lang: rawOptions.lang,
|
||||||
maximize: rawOptions.maximize || false,
|
maximize: rawOptions.maximize ?? false,
|
||||||
nativefierVersion: packageJson.version,
|
nativefierVersion: packageJson.version,
|
||||||
processEnvs: rawOptions.processEnvs,
|
processEnvs: rawOptions.processEnvs,
|
||||||
proxyRules: rawOptions.proxyRules || null,
|
proxyRules: rawOptions.proxyRules,
|
||||||
showMenuBar: rawOptions.showMenuBar || false,
|
showMenuBar: rawOptions.showMenuBar ?? false,
|
||||||
singleInstance: rawOptions.singleInstance || false,
|
singleInstance: rawOptions.singleInstance ?? false,
|
||||||
titleBarStyle: rawOptions.titleBarStyle || null,
|
titleBarStyle: rawOptions.titleBarStyle,
|
||||||
tray: rawOptions.tray || false,
|
tray: rawOptions.tray ?? false,
|
||||||
userAgent: rawOptions.userAgent,
|
userAgent: rawOptions.userAgent,
|
||||||
userAgentHonest: rawOptions.userAgentHonest || false,
|
userAgentHonest: rawOptions.userAgentHonest ?? false,
|
||||||
verbose: rawOptions.verbose,
|
verbose: rawOptions.verbose ?? false,
|
||||||
versionString: rawOptions.versionString,
|
versionString: rawOptions.versionString,
|
||||||
width: rawOptions.width || 1280,
|
width: rawOptions.width ?? 1280,
|
||||||
height: rawOptions.height || 800,
|
height: rawOptions.height ?? 800,
|
||||||
minWidth: rawOptions.minWidth,
|
minWidth: rawOptions.minWidth,
|
||||||
minHeight: rawOptions.minHeight,
|
minHeight: rawOptions.minHeight,
|
||||||
maxWidth: rawOptions.maxWidth,
|
maxWidth: rawOptions.maxWidth,
|
||||||
maxHeight: rawOptions.maxHeight,
|
maxHeight: rawOptions.maxHeight,
|
||||||
widevine: rawOptions.widevine || false,
|
widevine: rawOptions.widevine ?? false,
|
||||||
x: rawOptions.x,
|
x: rawOptions.x,
|
||||||
y: rawOptions.y,
|
y: rawOptions.y,
|
||||||
zoom: rawOptions.zoom || 1.0,
|
zoom: rawOptions.zoom ?? 1.0,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (options.nativefier.verbose) {
|
if (options.nativefier.verbose) {
|
||||||
log.setLevel('trace');
|
log.setLevel('trace');
|
||||||
try {
|
try {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
debug.enable('electron-packager');
|
||||||
require('debug').enable('electron-packager');
|
} catch (err: unknown) {
|
||||||
} catch (err) {
|
log.error(
|
||||||
log.debug(
|
|
||||||
'Failed to enable electron-packager debug output. This should not happen,',
|
'Failed to enable electron-packager debug output. This should not happen,',
|
||||||
'and suggests their internals changed. Please report an issue.',
|
'and suggests their internals changed. Please report an issue.',
|
||||||
|
err,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,13 +154,17 @@ export async function getOptions(rawOptions: any): Promise<AppOptions> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.nativefier.widevine) {
|
if (options.nativefier.widevine) {
|
||||||
const widevineElectronVersion = `${options.packager.electronVersion}-wvvmp`;
|
const widevineElectronVersion = `${
|
||||||
|
options.packager.electronVersion as string
|
||||||
|
}-wvvmp`;
|
||||||
try {
|
try {
|
||||||
await axios.get(
|
await axios.get(
|
||||||
`https://github.com/castlabs/electron-releases/releases/tag/v${widevineElectronVersion}`,
|
`https://github.com/castlabs/electron-releases/releases/tag/v${widevineElectronVersion}`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch {
|
||||||
throw `\nERROR: castLabs Electron version "${widevineElectronVersion}" does not exist. \nVerify versions at https://github.com/castlabs/electron-releases/releases. \nAborting.`;
|
throw new Error(
|
||||||
|
`\nERROR: castLabs Electron version "${widevineElectronVersion}" does not exist. \nVerify versions at https://github.com/castlabs/electron-releases/releases. \nAborting.`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
options.packager.electronVersion = widevineElectronVersion;
|
options.packager.electronVersion = widevineElectronVersion;
|
||||||
@ -173,7 +186,7 @@ export async function getOptions(rawOptions: any): Promise<AppOptions> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (options.nativefier.userAgentHonest && options.nativefier.userAgent) {
|
if (options.nativefier.userAgentHonest && options.nativefier.userAgent) {
|
||||||
options.nativefier.userAgent = null;
|
options.nativefier.userAgent = undefined;
|
||||||
log.warn(
|
log.warn(
|
||||||
`\nATTENTION: user-agent AND user-agent-honest/honest were provided. In this case, honesty wins. user-agent will be ignored`,
|
`\nATTENTION: user-agent AND user-agent-honest/honest were provided. In this case, honesty wins. user-agent will be ignored`,
|
||||||
);
|
);
|
||||||
@ -181,11 +194,19 @@ export async function getOptions(rawOptions: any): Promise<AppOptions> {
|
|||||||
|
|
||||||
options.packager.platform = normalizePlatform(options.packager.platform);
|
options.packager.platform = normalizePlatform(options.packager.platform);
|
||||||
|
|
||||||
if (options.nativefier.width > options.nativefier.maxWidth) {
|
if (
|
||||||
|
options.nativefier.maxWidth &&
|
||||||
|
options.nativefier.width &&
|
||||||
|
options.nativefier.width > options.nativefier.maxWidth
|
||||||
|
) {
|
||||||
options.nativefier.width = options.nativefier.maxWidth;
|
options.nativefier.width = options.nativefier.maxWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.nativefier.height > options.nativefier.maxHeight) {
|
if (
|
||||||
|
options.nativefier.maxHeight &&
|
||||||
|
options.nativefier.height &&
|
||||||
|
options.nativefier.height > options.nativefier.maxHeight
|
||||||
|
) {
|
||||||
options.nativefier.height = options.nativefier.maxHeight;
|
options.nativefier.height = options.nativefier.maxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,8 +223,8 @@ export async function getOptions(rawOptions: any): Promise<AppOptions> {
|
|||||||
|
|
||||||
if (rawOptions.globalShortcuts) {
|
if (rawOptions.globalShortcuts) {
|
||||||
log.debug('Using global shortcuts file at', rawOptions.globalShortcuts);
|
log.debug('Using global shortcuts file at', rawOptions.globalShortcuts);
|
||||||
const globalShortcuts = JSON.parse(
|
const globalShortcuts = parseJson<GlobalShortcut[]>(
|
||||||
fs.readFileSync(rawOptions.globalShortcuts).toString(),
|
fs.readFileSync(rawOptions.globalShortcuts as string).toString(),
|
||||||
);
|
);
|
||||||
options.nativefier.globalShortcuts = globalShortcuts;
|
options.nativefier.globalShortcuts = globalShortcuts;
|
||||||
}
|
}
|
||||||
@ -213,7 +234,7 @@ export async function getOptions(rawOptions: any): Promise<AppOptions> {
|
|||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function normalizePlatform(platform: string): string {
|
export function normalizePlatform(platform: string | undefined): string {
|
||||||
if (!platform) {
|
if (!platform) {
|
||||||
return inferPlatform();
|
return inferPlatform();
|
||||||
}
|
}
|
||||||
|
@ -14,8 +14,12 @@ test.each([
|
|||||||
[undefined, true, true],
|
[undefined, true, true],
|
||||||
[undefined, false, false],
|
[undefined, false, false],
|
||||||
])(
|
])(
|
||||||
'parseBoolean("%s") === %s',
|
'parseBoolean("%s") === %s (default = %s)',
|
||||||
(testString: string, expectedResult: boolean, _default: boolean) => {
|
(
|
||||||
expect(parseBoolean(testString, _default)).toBe(expectedResult);
|
testValue: boolean | string | number | undefined,
|
||||||
|
expectedResult: boolean,
|
||||||
|
_default: boolean,
|
||||||
|
) => {
|
||||||
|
expect(parseBoolean(testValue, _default)).toBe(expectedResult);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -3,9 +3,12 @@ import * as log from 'loglevel';
|
|||||||
import { isWindows } from '../helpers/helpers';
|
import { isWindows } from '../helpers/helpers';
|
||||||
|
|
||||||
export function parseBoolean(
|
export function parseBoolean(
|
||||||
val: boolean | string | number,
|
val: boolean | string | number | undefined,
|
||||||
_default: boolean,
|
_default: boolean,
|
||||||
): boolean {
|
): boolean {
|
||||||
|
if (val === undefined) {
|
||||||
|
return _default;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
if (typeof val === 'boolean') {
|
if (typeof val === 'boolean') {
|
||||||
return val;
|
return val;
|
||||||
@ -23,7 +26,7 @@ export function parseBoolean(
|
|||||||
default:
|
default:
|
||||||
return _default;
|
return _default;
|
||||||
}
|
}
|
||||||
} catch (_) {
|
} catch {
|
||||||
return _default;
|
return _default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,11 +42,11 @@ export function parseBooleanOrString(val: string): boolean | string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function parseJson(val: string): any {
|
export function parseJson<Type>(val: string): Type | undefined {
|
||||||
if (!val) return {};
|
if (!val) return undefined;
|
||||||
try {
|
try {
|
||||||
return JSON.parse(val);
|
return JSON.parse(val) as Type;
|
||||||
} catch (err) {
|
} catch (err: unknown) {
|
||||||
const windowsShellHint = isWindows()
|
const windowsShellHint = isWindows()
|
||||||
? `\n In particular, Windows cmd doesn't have single quotes, so you have to use only double-quotes plus escaping: "{\\"someKey\\": \\"someValue\\"}"`
|
? `\n In particular, Windows cmd doesn't have single quotes, so you have to use only double-quotes plus escaping: "{\\"someKey\\": \\"someValue\\"}"`
|
||||||
: '';
|
: '';
|
||||||
|
@ -4,7 +4,7 @@ import sanitize = require('sanitize-filename');
|
|||||||
import { DEFAULT_APP_NAME } from '../constants';
|
import { DEFAULT_APP_NAME } from '../constants';
|
||||||
|
|
||||||
export function sanitizeFilename(
|
export function sanitizeFilename(
|
||||||
platform: string,
|
platform: string | undefined,
|
||||||
filenameToSanitize: string,
|
filenameToSanitize: string,
|
||||||
): string {
|
): string {
|
||||||
let result: string = sanitize(filenameToSanitize);
|
let result: string = sanitize(filenameToSanitize);
|
||||||
|
@ -1,32 +1,36 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
// Bumping the minimum required Node version? You must bump:
|
"strict": true,
|
||||||
// 1. package.json -> engines.node
|
// Bumping the minimum required Node version? You must bump:
|
||||||
// 2. package.json -> devDependencies.@types/node
|
// 1. package.json -> engines.node
|
||||||
// 3. tsconfig.json -> {target, lib}
|
// 2. package.json -> devDependencies.@types/node
|
||||||
// 4. .github/workflows/ci.yml -> node-version
|
// 3. tsconfig.json -> {target, lib}
|
||||||
//
|
// 4. .github/workflows/ci.yml -> node-version
|
||||||
// Here in tsconfig.json, we want to set the `target` and `lib` keys
|
//
|
||||||
// to the "best" values for the minimum/required version of node.
|
// Here in tsconfig.json, we want to set the `target` and `lib` keys
|
||||||
// TS doesn't offer any easy "preset" for this, so the best we have is to
|
// to the "best" values for the minimum/required version of node.
|
||||||
// believe people who know which {syntax, library} parts of current EcmaScript
|
// TS doesn't offer any easy "preset" for this, so the best we have is to
|
||||||
// are supported for our version of Node, and use what they recommend.
|
// believe people who know which {syntax, library} parts of current EcmaScript
|
||||||
// For the current Node version, I followed
|
// are supported for our version of Node, and use what they recommend.
|
||||||
// https://stackoverflow.com/questions/59787574/typescript-tsconfig-settings-for-node-js-12
|
// For the current Node version, I followed
|
||||||
"target": "es2019",
|
// https://stackoverflow.com/questions/59787574/typescript-tsconfig-settings-for-node-js-12
|
||||||
// In `lib` we add `dom`, to tell tsc it's okay to use the URL object (which is in Node >= 7)
|
"target": "es2019",
|
||||||
"lib": ["es2020", "dom"]
|
// In `lib` we add `dom`, to tell tsc it's okay to use the URL object (which is in Node >= 7)
|
||||||
|
"lib": [
|
||||||
|
"es2020",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"./src/**/*"
|
"./src/**/*"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user