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

Make public improved API interface for Node.js #260

Merged
merged 9 commits into from
Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Added

- Make public improved API interface for Node.js ([#260](https://github.com/marp-team/marp-cli/pull/260))
- Added info about [Chocolatey](https://chocolatey.org/packages/marp-cli) and [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/marp.json) packages into Readme ([#263](https://github.com/marp-team/marp-cli/pull/263) by [@zverev-iv](https://github.com/zverev-iv))

### Fixed
Expand Down
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,55 @@ For example, the below configuration will set constructor option for Marp Core a

> :warning: Some options may be overridden by used template.

## API _(EXPERIMENTAL)_

You can use Marp CLI through Node.js [if installed Marp CLI into your local project](#local-installation).

```js
const { marpCli } = require('@marp-team/marp-cli')

marpCli(['test.md', '--pdf'])
.then((exitStatus) => {
if (exitStatus > 0) {
console.error(`Failure (Exit status: ${exitStatus})`)
} else {
console.log('Success')
}
})
.catch(console.error)
```

`marpCli()` accepts an argument of CLI options as array, and returns `Promise` to resolve an expected exit status in CLI. It would be rejected with the instance of `Error` if CLI met an error to suspend the conversion process.

### Error handling

We have exported [`CLIError` class and `CLIErrorCode` enum](https://github.com/marp-team/marp-cli/blob/master/src/error.ts) from `@marp-team/marp-cli`, to allow handling for specific errors that have already known by Marp CLI.

If `CLIError` instance was thrown, you can identify the reason why CLI throwed error by checking `errorCode` member.

### Wait for observation

`marpCli()` would not be resolved initiatively if started some observation: Watch mode, server mode, and preview window.

`waitForObservation()` is helpful to handle them. It returns `Promise` that would be resolved with helper object when ready to observe resources in `marpCli()`.

```javascript
const { marpCli, waitForObservation } = require('@marp-team/marp-cli')

marpCli(['--server', './slides/'])
.then((exitCode) => console.log(`Done with exit code ${exitCode}`))
.catch(console.error)

waitForObservation().then(({ stop }) => {
console.log('Observed')

// Stop observations to resolve marpCli()'s Promise
stop()
})
```

The resolved helper has `stop()` method for telling Marp CLI to stop observation and resolve `Promise`.

## Contributing

Are you interested in contributing? Please see [CONTRIBUTING.md](.github/CONTRIBUTING.md) and [the common contributing guideline for Marp team](https://github.com/marp-team/.github/blob/master/CONTRIBUTING.md).
Expand Down
2 changes: 1 addition & 1 deletion marp-cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@

require('v8-compile-cache')
require('./lib/marp-cli.js')
.default(process.argv.slice(2))
.cliInterface(process.argv.slice(2))
.then((exitCode) => process.on('exit', () => process.exit(exitCode)))
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
"engines": {
"node": ">=10"
},
"main": "lib/marp-cli.js",
"types": "types/src/marp-cli.d.ts",
"main": "lib/index.js",
"types": "types/src/index.d.ts",
"files": [
"marp-cli.js",
"lib/",
Expand Down
17 changes: 11 additions & 6 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import pugPlugin from 'rollup-plugin-pug'
import { terser } from 'rollup-plugin-terser'
import { dependencies } from './package.json'

const compact = !process.env.ROLLUP_WATCH

const external = (deps) => (id) =>
deps.some((dep) => dep === id || id.startsWith(`${dep}/`))

Expand All @@ -40,7 +42,10 @@ const plugins = (opts = {}) => [
}),
pugPlugin({ pugRuntime: 'pug-runtime' }),
url({ sourceDir: path.join(__dirname, 'lib') }),
!process.env.ROLLUP_WATCH && terser(),
compact &&
terser({
keep_classnames: /^CLIError$/,
}),
]

const browser = {
Expand All @@ -57,21 +62,21 @@ export default [
{
...browser,
input: 'src/templates/bespoke.js',
output: { file: 'lib/bespoke.js', format: 'iife' },
output: { compact, file: 'lib/bespoke.js', format: 'iife' },
},
{
...browser,
input: 'src/templates/watch.js',
output: { file: 'lib/watch.js', format: 'iife' },
output: { compact, file: 'lib/watch.js', format: 'iife' },
},
{
...browser,
input: 'src/server/server-index.js',
output: { file: 'lib/server/server-index.js', format: 'iife' },
output: { compact, file: 'lib/server/server-index.js', format: 'iife' },
},
{
...cli,
input: 'src/marp-cli.ts',
output: { exports: 'named', file: 'lib/marp-cli.js', format: 'cjs' },
input: ['src/marp-cli.ts', 'src/index.ts'],
output: { compact, dir: 'lib', exports: 'named', format: 'cjs' },
},
]
19 changes: 8 additions & 11 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import osLocale from 'os-locale'
import { info, warn } from './cli'
import { ConverterOption, ConvertType } from './converter'
import resolveEngine, { ResolvableEngine, ResolvedEngine } from './engine'
import { CLIError } from './error'
import { error } from './error'
import { TemplateOption } from './templates'
import { Theme, ThemeSet } from './theme'

Expand Down Expand Up @@ -213,10 +213,9 @@ export class MarpCLIConfig {
// Fallback to input arguments in server mode
if (this.args.server || this.conf.server) {
if (Array.isArray(this.args._)) {
if (this.args._.length > 1)
throw new CLIError(
'Server mode have to specify just one directory.'
)
if (this.args._.length > 1) {
error('Server mode have to specify just one directory.')
}
if (this.args._.length === 1) return path.resolve(this.args._[0])
}
}
Expand All @@ -229,10 +228,10 @@ export class MarpCLIConfig {
stat = await lstat(dir)
} catch (e) {
if (e.code !== 'ENOENT') throw e
throw new CLIError(`Input directory "${dir}" is not found.`)
error(`Input directory "${dir}" is not found.`)
}

if (!stat.isDirectory()) throw new CLIError(`"${dir}" is not directory.`)
if (!stat.isDirectory()) error(`"${dir}" is not directory.`)

return dir
}
Expand All @@ -250,7 +249,7 @@ export class MarpCLIConfig {
this.conf = ret.config
}
} catch (e) {
throw new CLIError(
error(
[
'Could not find or parse configuration file.',
e.name !== 'Error' && `(${e.name})`,
Expand Down Expand Up @@ -289,9 +288,7 @@ export class MarpCLIConfig {
theme.advice.insteadOf
} to make theme CSS available from directory.`
)
throw new CLIError(
`Directory cannot pass to theme option. (${theme.path})`
)
error(`Directory cannot pass to theme option. (${theme.path})`)
}

if (e.code !== 'ENOENT') throw e
Expand Down
4 changes: 2 additions & 2 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import path from 'path'
import { Marpit } from '@marp-team/marpit'
import importFrom from 'import-from'
import pkgUp from 'pkg-up'
import { CLIError } from './error'
import { error } from './error'

export type Engine = typeof Marpit
export type ResolvableEngine = Engine | string
Expand Down Expand Up @@ -41,7 +41,7 @@ export class ResolvedEngine {
return resolved
})

if (!resolved) throw new CLIError(`The specified engine has not resolved.`)
if (!resolved) error(`The specified engine has not resolved.`)
return resolved
}

Expand Down
14 changes: 12 additions & 2 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export class CLIError implements Error {
export class CLIError extends Error {
readonly errorCode: number
readonly message: string
readonly name = 'CLIError'

constructor(message: string, errorCode = 1) {
super()
this.message = message
this.errorCode = errorCode
}
Expand All @@ -13,6 +14,15 @@ export class CLIError implements Error {
}
}

export function error(msg: string, errorCode = 1): never {
export enum CLIErrorCode {
GENERAL_ERROR = 1,
NOT_FOUND_CHROMIUM = 2,
LISTEN_PORT_IS_ALREADY_USED = 3,
}

export function error(
msg: string,
errorCode = CLIErrorCode.GENERAL_ERROR
): never {
throw new CLIError(msg, errorCode)
}
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { apiInterface } from './marp-cli'

export { ObservationHelper, waitForObservation } from './marp-cli'
export { CLIError, CLIErrorCode } from './error'

export const marpCli = apiInterface
export default apiInterface
Loading