Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add browser options --browser and several sub options #603

Merged
merged 14 commits into from
Oct 3, 2024
8 changes: 4 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,18 @@
- Upgrade Marp Core to [v4.0.0](https://github.com/marp-team/marp-core/releases/v4.0.0) ([#591](https://github.com/marp-team/marp-cli/pull/591))
- The slide container of built-in themes became the block element and adopted safe centering
- Relax HTML allowlist: Allowed a lot of HTML elements and attributes by default
- Use [the new headless mode of Chrome](https://developer.chrome.com/docs/chromium/headless) while converting by default ([#593](https://github.com/marp-team/marp-cli/pull/593))
- You can get back to the old headless mode by setting `PUPPETEER_HEADLESS_MODE=old` env.

### Added

<!-- Allow using Firefox / WebDriver BiDi protocol during conversion ([#597](https://github.com/marp-team/marp-cli/pull/597)) -->

- Initial support for Firefox / WebDriver BiDi protocol during conversion ([#565](https://github.com/marp-team/marp-cli/issues/565), [#597](https://github.com/marp-team/marp-cli/pull/597))
- `--browser` and some related options to control the browser for conversion ([#603](https://github.com/marp-team/marp-cli/pull/603))
- `--debug` (`-d`) option to CLI interface ([#599](https://github.com/marp-team/marp-cli/pull/599))
- CI testing against Node.js v22 ([#591](https://github.com/marp-team/marp-cli/pull/591))

### Changed

- Use [the new headless mode of Chrome](https://developer.chrome.com/docs/chromium/headless) while converting by default ([#593](https://github.com/marp-team/marp-cli/pull/593))
- You can get back to the old headless mode by setting `PUPPETEER_HEADLESS_MODE=old` env.
- Upgrade Marpit to [v3.1.1](https://github.com/marp-team/marpit/releases/tag/v3.1.1) ([#591](https://github.com/marp-team/marp-cli/pull/591))
- Support for CSS nesting
- Upgrade development Node.js LTS to v20.17.0 ([#591](https://github.com/marp-team/marp-cli/pull/591))
Expand Down
133 changes: 97 additions & 36 deletions README.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions src/browser/browsers/chrome.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import { isInsideContainer } from '../../utils/container'
import { debugBrowser } from '../../utils/debug'
import {
isWSL,
resolveWindowsEnv,
resolveWSLPathToGuestSync,
getWindowsEnv,
translateWindowsPathToWSL,
} from '../../utils/wsl'
import { Browser } from '../browser'
import type { BrowserKind, BrowserProtocol, BrowserOptions } from '../browser'
Expand Down Expand Up @@ -128,15 +128,15 @@ export class ChromeBrowser extends Browser {

private async puppeteerDataDir() {
if (this._puppeteerDataDir === undefined) {
let requiredResolveWSLPath = false
let needToTranslateWindowsPathToWSL = false

this._puppeteerDataDir = await (async () => {
// In WSL environment, Marp CLI may use Chrome on Windows. If Chrome has
// located in host OS (Windows), we have to specify Windows path.
if (await this.browserInWSLHost()) {
if (wslTmp === undefined) wslTmp = await resolveWindowsEnv('TMP')
if (wslTmp === undefined) wslTmp = await getWindowsEnv('TMP')
if (wslTmp !== undefined) {
requiredResolveWSLPath = true
needToTranslateWindowsPathToWSL = true
return path.win32.resolve(wslTmp, this.#dataDirName)
}
}
Expand All @@ -146,8 +146,8 @@ export class ChromeBrowser extends Browser {
debugBrowser(`Chrome data directory: %s`, this._puppeteerDataDir)

// Ensure the data directory is created
const mkdirPath = requiredResolveWSLPath
? resolveWSLPathToGuestSync(this._puppeteerDataDir)
const mkdirPath = needToTranslateWindowsPathToWSL
? await translateWindowsPathToWSL(this._puppeteerDataDir)
: this._puppeteerDataDir

await fs.promises.mkdir(mkdirPath, { recursive: true })
Expand Down
16 changes: 12 additions & 4 deletions src/browser/finder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,19 @@ export type BrowserFinder = (
opts: BrowserFinderOptions
) => Promise<BrowserFinderResult>

const finderMap = { chrome, edge, firefox } as const
export type FinderName = keyof typeof availableFindersMap

export type FinderName = keyof typeof finderMap
const availableFindersMap = { chrome, edge, firefox } as const

export const defaultFinders = ['chrome', 'edge', 'firefox'] as const
export const availableFinders = Object.keys(
availableFindersMap
) as readonly FinderName[]

export const defaultFinders = [
'chrome',
'edge',
'firefox',
] as const satisfies readonly FinderName[]

export const findBrowser = async (
finders: readonly FinderName[] = defaultFinders,
Expand Down Expand Up @@ -68,7 +76,7 @@ export const findBrowser = async (
const resolved = Array<boolean | undefined>(finderCount)

finders.forEach((finderName, index) => {
const finder = finderMap[finderName]
const finder = availableFindersMap[finderName]

finder(normalizedOpts)
.then((ret) => {
Expand Down
11 changes: 5 additions & 6 deletions src/browser/finders/edge.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import path from 'node:path'
import { error, CLIErrorCode } from '../../error'
import {
resolveWSLPathToGuestSync,
resolveWindowsEnvSync,
} from '../../utils/wsl'
import { translateWindowsPathToWSL, getWindowsEnv } from '../../utils/wsl'
import { ChromeBrowser } from '../browsers/chrome'
import { ChromeCdpBrowser } from '../browsers/chrome-cdp'
import type { BrowserFinder, BrowserFinderResult } from '../finder'
Expand Down Expand Up @@ -83,12 +80,14 @@ const edgeFinderWin32 = async ({
}

const edgeFinderWSL1 = async () => {
const localAppData = resolveWindowsEnvSync('LOCALAPPDATA')
const localAppData = await getWindowsEnv('LOCALAPPDATA')

return await edgeFinderWin32({
programFiles: '/mnt/c/Program Files',
programFilesX86: '/mnt/c/Program Files (x86)',
localAppData: localAppData ? resolveWSLPathToGuestSync(localAppData) : '',
localAppData: localAppData
? await translateWindowsPathToWSL(localAppData)
: '',
join: path.posix.join,
})
}
64 changes: 62 additions & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import path from 'node:path'
import chalk from 'chalk'
import { cosmiconfig, cosmiconfigSync } from 'cosmiconfig'
import { osLocale } from 'os-locale'
import { availableFinders } from './browser/finder'
import type { FinderName } from './browser/finder'
import type { BrowserManagerConfig } from './browser/manager'
import { info, warn, error as cliError } from './cli'
import { ConvertType, type ConverterOption } from './converter'
Expand All @@ -24,6 +26,10 @@ interface IMarpCLIArguments {
'bespoke.osc'?: boolean
'bespoke.progress'?: boolean
'bespoke.transition'?: boolean
browser?: string | string[]
browserPath?: string
browserProtocol?: string
browserTimeout?: number
configFile?: string | false
description?: string
engine?: string
Expand Down Expand Up @@ -71,6 +77,8 @@ export type IMarpCLIConfig = Overwrite<
progress?: boolean
transition?: boolean
}
browser?: 'auto' | FinderName | FinderName[]
browserProtocol?: 'cdp' | 'webdriver-bidi'
engine?: ResolvableEngine
html?: ConverterOption['html']
keywords?: string | string[]
Expand Down Expand Up @@ -118,8 +126,58 @@ export class MarpCLIConfig {
private constructor() {} // eslint-disable-line @typescript-eslint/no-empty-function

browserManagerOption() {
const finders = (() => {
const isAvailableFinder = (f: string): f is FinderName =>
(availableFinders as string[]).includes(f)

const browser = this.args.browser ?? this.conf.browser ?? 'auto'

if (typeof browser === 'string') {
const normalized = browser.toLowerCase()

if (normalized === 'auto') return undefined
if (isAvailableFinder(normalized)) return normalized

error(`Unknown browser: ${browser}`)
}

const filtered = browser.filter(isAvailableFinder)

if (filtered.length === 0 && browser.length > 0)
error(`No available browsers: ${browser.join(', ')}`)

return filtered
})()

const browserPath = (() => {
if (this.args.browserPath) return path.resolve(this.args.browserPath)
if (this.conf.browserPath)
return path.resolve(path.dirname(this.confPath!), this.conf.browserPath)

// Replacement for CHROME_PATH environment variable
if (process.env.BROWSER_PATH) return process.env.BROWSER_PATH

return undefined
})()

const protocol = (() => {
const target =
this.args.browserProtocol ?? this.conf.browserProtocol ?? 'cdp'

if (target === 'cdp') return 'cdp'
if (target === 'webdriver-bidi') return 'webDriverBiDi'

error(`Unknown browser protocol: ${target}`)
})()

const timeout = (() => {
// TODO: Resolve timeout from args and configuration file
if (this.args.browserTimeout !== undefined)
return Math.floor(this.args.browserTimeout * 1000)

if (this.conf.browserTimeout !== undefined)
return Math.floor(this.conf.browserTimeout * 1000)

// Fallback to classic way until v3 (PUPPETEER_TIMEOUT environment variable)
if (process.env.PUPPETEER_TIMEOUT) {
const envTimeout = Number.parseInt(process.env.PUPPETEER_TIMEOUT, 10)
if (!Number.isNaN(envTimeout)) return envTimeout
Expand All @@ -128,7 +186,9 @@ export class MarpCLIConfig {
})()

return {
protocol: 'cdp',
finders,
path: browserPath,
protocol,
timeout,
} as const satisfies BrowserManagerConfig
}
Expand Down
6 changes: 3 additions & 3 deletions src/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { ThemeSet } from './theme'
import { isOfficialDockerImage } from './utils/container'
import { debug } from './utils/debug'
import { pdfLib, setOutline } from './utils/pdf'
import { resolveWSLPathToHost } from './utils/wsl'
import { translateWSLPathToWindows } from './utils/wsl'
import { notifier } from './watcher'

const CREATED_BY_MARP = 'Created by Marp'
Expand Down Expand Up @@ -161,7 +161,7 @@ export class Converter {
const browser = await this.browser

return (await browser.browserInWSLHost())
? `file:${await resolveWSLPathToHost(f.absolutePath)}`
? `file:${await translateWSLPathToWindows(f.absolutePath)}`
: f.absoluteFileScheme
}

Expand Down Expand Up @@ -643,7 +643,7 @@ export class Converter {

if (tmpFile) {
if (await browser.browserInWSLHost()) {
uri = `file:${await resolveWSLPathToHost(tmpFile.path)}`
uri = `file:${await translateWSLPathToWindows(tmpFile.path)}`
} else {
uri = `file://${tmpFile.path}`
}
Expand Down
1 change: 1 addition & 0 deletions src/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class CLIError extends Error {
}

export const CLIErrorCode = {
INVALID_OPTIONS: -1,
GENERAL_ERROR: 1,
NOT_FOUND_BROWSER: 2,
LISTEN_PORT_IS_ALREADY_USED: 3,
Expand Down
Loading