-
-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #595 from marp-team/prepare-new-browser-finder
Internal: Prepare new browser manager interface and recreated finder
- Loading branch information
Showing
40 changed files
with
1,889 additions
and
104 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
export type BrowserKind = 'chrome' | 'firefox' | ||
export type BrowserProtocol = 'webdriver-bidi' | 'cdp' | ||
export type BrowserPurpose = 'convert' | 'preview' | ||
|
||
export interface BrowserOptions { | ||
purpose: BrowserPurpose | ||
} | ||
|
||
export abstract class Browser { | ||
static readonly kind: BrowserKind | ||
static readonly protocol: BrowserProtocol | ||
|
||
// --- | ||
|
||
purpose: BrowserPurpose | ||
|
||
constructor(opts: BrowserOptions) { | ||
this.purpose = opts.purpose | ||
} | ||
|
||
get kind() { | ||
return (this.constructor as typeof Browser).kind | ||
} | ||
|
||
get protocol() { | ||
return (this.constructor as typeof Browser).protocol | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Browser } from '../browser' | ||
|
||
export class ChromeCdpBrowser extends Browser { | ||
static readonly kind = 'chrome' as const | ||
static readonly protocol = 'cdp' as const | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Browser } from '../browser' | ||
|
||
export class ChromeBrowser extends Browser { | ||
static readonly kind = 'chrome' as const | ||
static readonly protocol = 'webdriver-bidi' as const | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import { Browser } from '../browser' | ||
|
||
export class FirefoxBrowser extends Browser { | ||
static readonly kind = 'firefox' as const | ||
static readonly protocol = 'webdriver-bidi' as const | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { CLIError, CLIErrorCode } from '../error' | ||
import { debugBrowserFinder } from '../utils/debug' | ||
import type { Browser } from './browser' | ||
import { chromeFinder as chrome } from './finders/chrome' | ||
import { edgeFinder as edge } from './finders/edge' | ||
import { firefoxFinder as firefox } from './finders/firefox' | ||
import { isExecutable, normalizeDarwinAppPath } from './finders/utils' | ||
|
||
export interface BrowserFinderResult { | ||
path: string | ||
acceptedBrowsers: (typeof Browser)[] | ||
} | ||
|
||
export interface BrowserFinderOptions { | ||
preferredPath?: string | ||
} | ||
|
||
export type BrowserFinder = ( | ||
opts: BrowserFinderOptions | ||
) => Promise<BrowserFinderResult> | ||
|
||
const finderMap = { chrome, edge, firefox } as const | ||
|
||
export const autoFinders = ['chrome', 'edge', 'firefox'] as const | ||
|
||
export const findBrowser = async ( | ||
finders: readonly (keyof typeof finderMap)[] = autoFinders, | ||
opts: BrowserFinderOptions = {} | ||
) => { | ||
const finderCount = finders.length | ||
const normalizedOpts = { | ||
preferredPath: await (async () => { | ||
if (opts.preferredPath) { | ||
const normalized = await normalizeDarwinAppPath(opts.preferredPath) | ||
if (await isExecutable(normalized)) return normalized | ||
} | ||
return undefined | ||
})(), | ||
} | ||
|
||
if (finderCount === 0) { | ||
debugBrowserFinder('No browser finder specified.') | ||
|
||
if (normalizedOpts.preferredPath) { | ||
debugBrowserFinder( | ||
'Use preferred path as Chrome: %s', | ||
normalizedOpts.preferredPath | ||
) | ||
|
||
return await chrome(normalizedOpts) | ||
} | ||
|
||
throw new CLIError( | ||
'No suitable browser found.', | ||
CLIErrorCode.NOT_FOUND_BROWSER | ||
) | ||
} | ||
|
||
debugBrowserFinder( | ||
`Start finding browser from ${finders.join(', ')} (%o)`, | ||
normalizedOpts | ||
) | ||
|
||
return new Promise<BrowserFinderResult>((res, rej) => { | ||
const results = Array<BrowserFinderResult>(finderCount) | ||
const resolved = Array<boolean | undefined>(finderCount) | ||
|
||
finders.forEach((finderName, index) => { | ||
const finder = finderMap[finderName] | ||
|
||
finder(normalizedOpts) | ||
.then((ret) => { | ||
debugBrowserFinder(`Found ${finderName}: %o`, ret) | ||
results[index] = ret | ||
resolved[index] = true | ||
}) | ||
.catch((e) => { | ||
debugBrowserFinder(`Finder ${finderName} was failed: %o`, e) | ||
resolved[index] = false | ||
}) | ||
.finally(() => { | ||
let target: number | undefined | ||
|
||
for (let i = finderCount - 1; i >= 0; i -= 1) { | ||
if (resolved[i] !== false) target = i | ||
} | ||
|
||
if (target === undefined) { | ||
rej( | ||
new CLIError( | ||
`No suitable browser found. Please ensure one of the following browsers is installed: ${finders.join(', ')}`, | ||
CLIErrorCode.NOT_FOUND_BROWSER | ||
) | ||
) | ||
} else if (resolved[target]) { | ||
res(results[target]) | ||
} | ||
}) | ||
}) | ||
}).then((result) => { | ||
debugBrowserFinder('Use browser: %o', result) | ||
return result | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { | ||
darwinFast, | ||
linux, | ||
win32, | ||
wsl, | ||
} from 'chrome-launcher/dist/chrome-finder' | ||
import { error, CLIErrorCode } from '../../error' | ||
import { ChromeBrowser } from '../browsers/chrome' | ||
import { ChromeCdpBrowser } from '../browsers/chrome-cdp' | ||
import type { BrowserFinder, BrowserFinderResult } from '../finder' | ||
import { findExecutableBinary, getPlatform } from './utils' | ||
|
||
const chrome = (path: string): BrowserFinderResult => ({ | ||
path, | ||
acceptedBrowsers: [ChromeBrowser, ChromeCdpBrowser], | ||
}) | ||
|
||
export const chromeFinder: BrowserFinder = async ({ preferredPath } = {}) => { | ||
if (preferredPath) return chrome(preferredPath) | ||
|
||
const platform = await getPlatform() | ||
const installation = await (async () => { | ||
switch (platform) { | ||
case 'darwin': | ||
return darwinFast() | ||
case 'linux': | ||
return linux()[0] | ||
case 'win32': | ||
return win32()[0] | ||
case 'wsl1': | ||
return wsl()[0] | ||
} | ||
return await fallback() | ||
})() | ||
|
||
if (installation) return chrome(installation) | ||
|
||
error('Chrome browser could not be found.', CLIErrorCode.NOT_FOUND_BROWSER) | ||
} | ||
|
||
const fallback = async () => | ||
await findExecutableBinary([ | ||
'google-chrome-stable', | ||
'google-chrome', | ||
'chrome', // FreeBSD Chromium | ||
'chromium-browser', | ||
'chromium', | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import path from 'node:path' | ||
import { error, CLIErrorCode } from '../../error' | ||
import { | ||
resolveWSLPathToGuestSync, | ||
resolveWindowsEnvSync, | ||
} from '../../utils/wsl' | ||
import { ChromeBrowser } from '../browsers/chrome' | ||
import { ChromeCdpBrowser } from '../browsers/chrome-cdp' | ||
import type { BrowserFinder, BrowserFinderResult } from '../finder' | ||
import { findExecutable, getPlatform } from './utils' | ||
|
||
const edge = (path: string): BrowserFinderResult => ({ | ||
path, | ||
acceptedBrowsers: [ChromeBrowser, ChromeCdpBrowser], | ||
}) | ||
|
||
export const edgeFinder: BrowserFinder = async ({ preferredPath } = {}) => { | ||
if (preferredPath) return edge(preferredPath) | ||
|
||
const platform = await getPlatform() | ||
const installation = await (async () => { | ||
switch (platform) { | ||
case 'darwin': | ||
return await edgeFinderDarwin() | ||
case 'linux': | ||
return await edgeFinderLinux() | ||
case 'win32': | ||
return await edgeFinderWin32() | ||
case 'wsl1': | ||
return await edgeFinderWSL1() | ||
} | ||
return undefined | ||
})() | ||
|
||
if (installation) return edge(installation) | ||
|
||
error('Edge browser could not be found.', CLIErrorCode.NOT_FOUND_BROWSER) | ||
} | ||
|
||
const edgeFinderDarwin = async () => | ||
await findExecutable([ | ||
'/Applications/Microsoft Edge Canary.app/Contents/MacOS/Microsoft Edge Canary', | ||
'/Applications/Microsoft Edge Dev.app/Contents/MacOS/Microsoft Edge Dev', | ||
'/Applications/Microsoft Edge Beta.app/Contents/MacOS/Microsoft Edge Beta', | ||
'/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge', | ||
]) | ||
|
||
const edgeFinderLinux = async () => | ||
await findExecutable([ | ||
'/opt/microsoft/msedge-canary/msedge', | ||
'/opt/microsoft/msedge-dev/msedge', | ||
'/opt/microsoft/msedge-beta/msedge', | ||
'/opt/microsoft/msedge/msedge', | ||
]) | ||
|
||
const edgeFinderWin32 = async ({ | ||
programFiles = process.env.PROGRAMFILES, | ||
programFilesX86 = process.env['PROGRAMFILES(X86)'], | ||
localAppData = process.env.LOCALAPPDATA, | ||
join = path.join, | ||
}: { | ||
programFiles?: string | ||
programFilesX86?: string | ||
localAppData?: string | ||
join?: typeof path.join | ||
} = {}): Promise<string | undefined> => { | ||
const paths: string[] = [] | ||
|
||
const suffixes = [ | ||
['Microsoft', 'Edge SxS', 'Application', 'msedge.exe'], | ||
['Microsoft', 'Edge Dev', 'Application', 'msedge.exe'], | ||
['Microsoft', 'Edge Beta', 'Application', 'msedge.exe'], | ||
['Microsoft', 'Edge', 'Application', 'msedge.exe'], | ||
] | ||
|
||
for (const suffix of suffixes) { | ||
for (const prefix of [localAppData, programFiles, programFilesX86]) { | ||
if (prefix) paths.push(join(prefix, ...suffix)) | ||
} | ||
} | ||
|
||
return await findExecutable(paths) | ||
} | ||
|
||
const edgeFinderWSL1 = async () => { | ||
const localAppData = resolveWindowsEnvSync('LOCALAPPDATA') | ||
|
||
return await edgeFinderWin32({ | ||
programFiles: '/mnt/c/Program Files', | ||
programFilesX86: '/mnt/c/Program Files (x86)', | ||
localAppData: localAppData ? resolveWSLPathToGuestSync(localAppData) : '', | ||
join: path.posix.join, | ||
}) | ||
} |
Oops, something went wrong.