diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 00000000..5a904716 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,23 @@ +export class ExitError extends Error { + 'cli-ux': { + exit: number + } + code?: string + + constructor(error?: string | Error | number, exitCode = 0) { + if (typeof error === 'number') { + exitCode = error + error = undefined + } + const addCode = (error: ExitError) => { + error['cli-ux'] = error['cli-ux'] || {} + error['cli-ux'].exit = exitCode + return error + } + if (error instanceof Error) { + return addCode(error as any) + } + super(error || `${exitCode}: ${status}`) + this.code = 'EEXIT' + } +} diff --git a/src/hooks.ts b/src/hooks.ts index 6f8a7815..a8a5872c 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -17,4 +17,12 @@ export interface Hooks { } } -export type Hook = (options: Hooks[K] & {config: Config.IConfig}) => any +export type Hook = (this: Hook.Context, options: Hooks[K] & {config: Config.IConfig}) => any + +export namespace Hook { + export interface Context { + exit(code?: number): void + error(message: string | Error, options?: {exit?: number}): void + log(message?: any): void + } +} diff --git a/src/plugin.ts b/src/plugin.ts index 4ae4c08c..e1bd0246 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -4,7 +4,8 @@ import {inspect} from 'util' import {Command} from './command' import Debug from './debug' -import {Hooks} from './hooks' +import {ExitError} from './errors' +import {Hook, Hooks} from './hooks' import {Manifest} from './manifest' import {PJSON} from './pjson' import {Topic} from './topic' @@ -190,18 +191,29 @@ export class Plugin implements IPlugin { } async runHook(event: K, opts: T[K]) { + const context: Hook.Context = { + exit(code) { + throw new ExitError(code) + }, + log(message) { + process.stdout.write((message || '') + '\n') + }, + error(message, options = {}) { + throw new ExitError(message, options.exit) + }, + } const promises = (this.hooks[event] || []) .map(async hook => { try { const p = tsPath(this.root, hook) debug('hook', event, p) - const search = (m: any) => { + const search = (m: any): Hook => { if (typeof m === 'function') return m if (m.default && typeof m.default === 'function') return m.default - return Object.values(m).find((m: any) => typeof m === 'function') + return Object.values(m).find((m: any) => typeof m === 'function') as Hook } - await search(require(p))(opts) + await search(require(p)).call(context, opts) } catch (err) { if (err && err['cli-ux'] && err['cli-ux'].exit !== undefined) throw err process.emitWarning(err)