Skip to content

Commit

Permalink
Merge pull request #599 from marp-team/debug-option
Browse files Browse the repository at this point in the history
Add `--debug` (`-d`) option to CLI interface
  • Loading branch information
yhatt authored Sep 27, 2024
2 parents c04622e + 70023c0 commit 79ab9d9
Show file tree
Hide file tree
Showing 12 changed files with 348 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

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

- `--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
Expand Down
13 changes: 10 additions & 3 deletions marp-cli.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
#!/usr/bin/env node

'use strict'
{
const prepare = require('./lib/prepare.js')
const cli = prepare.cliPrepare()

require('./lib/marp-cli.js')
.cliInterface(process.argv.slice(2))
.then((exitCode) => process.on('exit', () => process.exit(exitCode)))
if (cli.debug)
process.env.DEBUG = `${process.env.DEBUG ? `${process.env.DEBUG},` : ''}${cli.debug}`

require('./lib/marp-cli.js')
.cliInterface(cli.args)
.then((exitCode) => process.on('exit', () => process.exit(exitCode)))
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,18 +52,18 @@
"check:audit": "yarn audit",
"check:format": "yarn -s format -c",
"check:ts": "tsc --noEmit",
"clean": "node -e 'fs.rmSync(`lib`,{recursive:true,force:true})'",
"clean": "node -e \"fs.rmSync('lib',{recursive:true,force:true})\"",
"format": "prettier \"**/*.{css,js,jsx,json,md,mjs,scss,ts,tsx,yaml,yml}\"",
"format:write": "yarn -s format --write",
"lint:js": "ESLINT_USE_FLAT_CONFIG=false eslint --ext .js,.mjs,.jsx,.ts,.tsx --report-unused-disable-directives --cache .",
"lint:css": "stylelint \"src/**/*.{css,scss}\"",
"prepack": "npm-run-all --parallel check:* lint:* test:coverage --parallel build types",
"preversion": "run-p check:* lint:* test:coverage",
"standalone": "node -e 'fs.rmSync(`bin`,{recursive:true,force:true})' && pkg --options \"icu-data-dir=$(node ./scripts/icu.mjs)\" -C gzip --out-path ./bin .",
"standalone": "node ./scripts/standalone.mjs",
"standalone:pack": "node ./scripts/pack.js",
"test": "jest",
"test:coverage": "jest --coverage",
"types": "node -e 'fs.rmSync(`types`,{recursive:true,force:true})' && tsc --declaration --emitDeclarationOnly --outDir types",
"types": "node -e \"fs.rmSync('types',{recursive:true,force:true})\" && tsc --declaration --emitDeclarationOnly --outDir types",
"version": "curl https://raw.githubusercontent.com/marp-team/actions/v1/lib/scripts/version.js | node && git add -A CHANGELOG.md",
"watch": "rollup -w -c"
},
Expand Down
6 changes: 5 additions & 1 deletion rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ export default [
},
{
...cli,
input: ['src/marp-cli.ts', 'src/index.ts'],
input: [
'src/index.ts', // Node.js entrypoint
'src/marp-cli.ts', // CLI entrypoint
'src/prepare.ts', // CLI preparation
],
output: { compact, dir: 'lib', exports: 'named', format: 'cjs' },
},
]
77 changes: 41 additions & 36 deletions scripts/icu.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,47 +7,52 @@ import yauzl from 'yauzl'

const __dirname = path.dirname(fileURLToPath(import.meta.url))
const icuDir = path.join(__dirname, '../tmp/icu')
await fs.promises.mkdir(icuDir, { recursive: true })

const zipFromBuffer = promisify(yauzl.fromBuffer)

// Get the ICU version and endianness
const [icuMajor, icuMinor] = process.versions.icu.split('.')
const icuEndianness = process.config.variables.icu_endianness.toLowerCase()
export const fetchIcu = async () => {
await fs.promises.mkdir(icuDir, { recursive: true })

// Download the ICU data
const response = await fetch(
`https://github.com/unicode-org/icu/releases/download/release-${icuMajor}-${icuMinor}/icu4c-${icuMajor}_${icuMinor}-data-bin-${icuEndianness}.zip`
)
// Get the ICU version and endianness
const [icuMajor, icuMinor] = process.versions.icu.split('.')
const icuEndianness = process.config.variables.icu_endianness.toLowerCase()

if (!response.ok) {
throw new Error(`Failed to download ICU data: ${response.statusText}`)
}
// Download the ICU data
const response = await fetch(
`https://github.com/unicode-org/icu/releases/download/release-${icuMajor}-${icuMinor}/icu4c-${icuMajor}_${icuMinor}-data-bin-${icuEndianness}.zip`
)

if (!response.ok) {
throw new Error(`Failed to download ICU data: ${response.statusText}`)
}

// Extract the ICU data
const zip = await zipFromBuffer(Buffer.from(await response.arrayBuffer()), {
lazyEntries: true,
})

const icuDat = await new Promise((res, rej) => {
zip.on('error', (err) => rej(err))
zip.on('entry', async (entry) => {
if (/icudt\d+.\.dat/.test(entry.fileName)) {
zip.openReadStream(entry, (err, readStream) => {
if (err) return rej(err)

const output = path.join(icuDir, entry.fileName)

readStream.pipe(fs.createWriteStream(output))
res(output)
})
} else {
zip.readEntry()
}
// Extract the ICU data
const zip = await zipFromBuffer(Buffer.from(await response.arrayBuffer()), {
lazyEntries: true,
})
zip.on('end', () => rej(new Error('Failed to find ICU data in the archive')))
zip.readEntry()
})

// Print the relative path to the ICU data from the project root
console.log(path.relative(path.join(__dirname, '../'), icuDat))
const icuDat = await new Promise((res, rej) => {
zip.on('error', (err) => rej(err))
zip.on('entry', async (entry) => {
if (/icudt\d+.\.dat/.test(entry.fileName)) {
zip.openReadStream(entry, (err, readStream) => {
if (err) return rej(err)

const output = path.join(icuDir, entry.fileName)

readStream.pipe(fs.createWriteStream(output))
res(output)
})
} else {
zip.readEntry()
}
})
zip.on('end', () =>
rej(new Error('Failed to find ICU data in the archive'))
)
zip.readEntry()
})

// Return the relative path to the ICU data from the project root
return `./${path.relative(path.join(__dirname, '../'), icuDat).replace(/\\/g, '/')}`
}
25 changes: 25 additions & 0 deletions scripts/standalone.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import fs from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { exec } from '@yao-pkg/pkg'
import { fetchIcu } from './icu.mjs'

// Clean up bin directory
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const binDir = path.join(__dirname, '../bin')

await fs.promises.rm(binDir, { recursive: true, force: true })

// Fetch the ICU data
const icuDat = await fetchIcu()

// Run pkg
await exec([
'--options',
`icu-data-dir=${icuDat}`,
'-C',
'gzip',
'--out-path',
binDir,
path.join(__dirname, '../'),
])
6 changes: 5 additions & 1 deletion src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,11 @@ export class ResolvedEngine<T extends Engine = Engine> {
if (resolved) break
}

if (!resolved) error(`The specified engine has not resolved.`)
if (!resolved)
error(
`The specified engine has not resolved. (Try "--debug=true" to see more details)`
)

return resolved
}

Expand Down
32 changes: 24 additions & 8 deletions src/marp-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,17 @@ enum OptionGroup {
Marp = 'Marp / Marpit Options:',
}

export interface MarpCLIInternalOptions {
export interface MarpCLIOptions {
baseUrl?: string
stdin: boolean
throwErrorAlways: boolean
}

export type MarpCLIAPIOptions = Pick<MarpCLIInternalOptions, 'baseUrl'>
export type MarpCLIAPIOptions = Pick<MarpCLIOptions, 'baseUrl'>

export interface MarpCLICLIOptions {
debug?: string | boolean
}

export interface ObservationHelper {
stop: () => void
Expand All @@ -44,7 +48,7 @@ Usage:

export const marpCli = async (
argv: string[],
{ baseUrl, stdin: defaultStdin, throwErrorAlways }: MarpCLIInternalOptions
{ baseUrl, stdin: defaultStdin, throwErrorAlways }: MarpCLIOptions
): Promise<number> => {
let browserManager: BrowserManager | undefined
let server: Server | undefined
Expand All @@ -69,6 +73,14 @@ export const marpCli = async (
group: OptionGroup.Basic,
type: 'boolean',
},
debug: {
// NOTE: This option will not be parsed by yargs because it has been pre-parsed by prepare.ts
alias: 'd',
describe: 'Show debug logs (bool or filter pattern)',
defaultDescription: 'false',
group: OptionGroup.Basic,
type: 'string',
},
output: {
alias: 'o',
describe: 'Output file path (or directory when input-dir is passed)',
Expand Down Expand Up @@ -181,7 +193,7 @@ export const marpCli = async (
},
template: {
describe: 'Choose template',
defaultDescription: 'bespoke',
defaultDescription: '"bespoke"',
group: OptionGroup.Template,
choices: Object.keys(templates),
type: 'string',
Expand Down Expand Up @@ -469,7 +481,11 @@ export const waitForObservation = () =>
export const apiInterface = (argv: string[], opts: MarpCLIAPIOptions = {}) =>
marpCli(argv, { ...opts, stdin: false, throwErrorAlways: true })

export const cliInterface = (argv: string[] = []) =>
marpCli(argv, { stdin: true, throwErrorAlways: false })

export default cliInterface
export const cliInterface = (argv: string[]) => {
if (process.env.DEBUG) {
cli.info(
`Debug logging is enabled. (Filter pattern: ${chalk.yellow(process.env.DEBUG)})`
)
}
return marpCli(argv, { stdin: true, throwErrorAlways: false })
}
47 changes: 47 additions & 0 deletions src/prepare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
export const defaultDebug = 'marp-cli*'

const debugOptionMatcher = /^(?:-d|--debug)(?:$|=)/

const enableValues = ['true', '1']
const falseValues = ['false', '0']
const starValues = ['all', 'full']

const parseDebugValue = (value: string) => {
const trimmedValue = value.trim()
const normalizedValue = trimmedValue.toLowerCase()

if (starValues.includes(normalizedValue)) return '*'
if (enableValues.includes(normalizedValue)) return defaultDebug
if (falseValues.includes(normalizedValue)) return false

return trimmedValue || defaultDebug
}

export const cliPrepare = (args = process.argv.slice(2)) => {
// Parse debug option
let debug: string | false = false

const normalizedArgs = [...args]
const dbgIdx = normalizedArgs.findIndex((arg) => debugOptionMatcher.test(arg))

if (dbgIdx >= 0) {
const dbgOption = normalizedArgs[dbgIdx]

if (dbgOption.startsWith('-d=') || dbgOption.startsWith('--debug=')) {
debug = parseDebugValue(dbgOption.slice(dbgOption.indexOf('=') + 1))
normalizedArgs.splice(dbgIdx, 1)
} else {
const nextArg = normalizedArgs[dbgIdx + 1]

if (!nextArg || nextArg.startsWith('-')) {
debug = defaultDebug
normalizedArgs.splice(dbgIdx, 1)
} else {
debug = parseDebugValue(nextArg)
normalizedArgs.splice(dbgIdx, 2)
}
}
}

return { args: normalizedArgs, debug }
}
2 changes: 1 addition & 1 deletion test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ describe('Marp CLI API interface', () => {
expect(ret).toBe(0)
expect(marpCliSpy).toHaveBeenCalled()

const opts: marpCli.MarpCLIInternalOptions = marpCliSpy.mock.calls[0][1]
const opts: marpCli.MarpCLIOptions = marpCliSpy.mock.calls[0][1]

expect(opts.baseUrl).toBe('https://example.com/')
expect(opts.stdin).toBe(false)
Expand Down
30 changes: 29 additions & 1 deletion test/marp-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,34 @@ describe('Marp CLI', () => {
})
}

describe('with DEBUG env', () => {
it('shows enabling debug logging and set pattern', async () => {
process.env.DEBUG = 'debug-pattern,debug-pattern:*'

try {
const warn = jest.spyOn(console, 'warn').mockImplementation()
const log = jest.spyOn(console, 'log').mockImplementation()

try {
expect(await marpCli(['-v'])).toBe(0)
expect(warn).toHaveBeenCalledWith(
expect.stringContaining('Debug logging is enabled')
)
expect(warn).toHaveBeenCalledWith(
expect.stringContaining(
'Filter pattern: debug-pattern,debug-pattern:*'
)
)
} finally {
warn.mockRestore()
log.mockRestore()
}
} finally {
delete process.env.DEBUG
}
})
})

describe('when passed file is not found', () => {
it('outputs warning and help with exit code 1', async () => {
const warn = jest.spyOn(console, 'warn').mockImplementation()
Expand Down Expand Up @@ -1523,7 +1551,7 @@ describe('Marp CLI', () => {
})

it('converts markdown came from stdin and outputs to stdout', async () => {
expect(await marpCli()).toBe(0)
expect(await marpCli([])).toBe(0)
expect(cliInfo.mock.calls.map(([m]) => m)).toContainEqual(
expect.stringContaining('<stdin> => <stdout>')
)
Expand Down
Loading

0 comments on commit 79ab9d9

Please sign in to comment.