mirror of https://github.com/actions/toolkit
Rearrange code and add comprehensive comments
parent
b7fcb99778
commit
bfa86cc586
|
@ -1,6 +1,9 @@
|
|||
import * as exec from '../exec'
|
||||
import {CommandRunnerBase} from './core'
|
||||
import {
|
||||
ErrorMatcher,
|
||||
ExitCodeMatcher,
|
||||
OutputMatcher,
|
||||
failAction,
|
||||
matchEvent,
|
||||
matchExitCode,
|
||||
|
@ -8,15 +11,12 @@ import {
|
|||
matchSpecificError,
|
||||
produceLog,
|
||||
throwError
|
||||
} from './middleware'
|
||||
} from './middlware'
|
||||
import {
|
||||
CommandRunnerActionType,
|
||||
CommandRunnerEventTypeExtended,
|
||||
CommandRunnerMiddleware,
|
||||
CommandRunnerOptions,
|
||||
ErrorMatcher,
|
||||
ExitCodeMatcher,
|
||||
OutputMatcher
|
||||
CommandRunnerOptions
|
||||
} from './types'
|
||||
|
||||
const commandRunnerActions = {
|
||||
|
@ -26,6 +26,30 @@ const commandRunnerActions = {
|
|||
} as const
|
||||
|
||||
export class CommandRunner extends CommandRunnerBase {
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed on command runner run
|
||||
* @param event allows to set middleware on certain event
|
||||
* - `execerr` - when error happens during command execution
|
||||
* - `stderr` - when stderr is not empty
|
||||
* - `stdout` - when stdout is not empty
|
||||
* - `exitcode` - when exit code is not 0
|
||||
* - `ok` - when exit code is 0 and stderr is empty
|
||||
* Each event can also be negated by prepending `!` to it, e.g. `!ok`
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* const runner = createCommandRunner('echo', ['hello'])
|
||||
* await runner
|
||||
* .on('ok', 'log', 'Command executed successfully')
|
||||
* .on('!ok', 'throw')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
on(
|
||||
event: CommandRunnerEventTypeExtended | CommandRunnerEventTypeExtended[],
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
|
@ -40,6 +64,23 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command executed
|
||||
* with empty stdout.
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* const runner = createCommandRunner('echo', ['hello'])
|
||||
* await runner
|
||||
* .onEmptyOutput('throw', 'Command did not produce an output')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onEmptyOutput(
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
message?: string
|
||||
|
@ -48,6 +89,23 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command failed
|
||||
* to execute (either did not find such command or failed to spawn it).
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* const runner = createCommandRunner('echo', ['hello'])
|
||||
* await runner
|
||||
* .onExecutionError('throw', 'Command failed to execute')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onExecutionError(
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
message?: string
|
||||
|
@ -62,6 +120,23 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command produced
|
||||
* non-empty stderr.
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* const runner = createCommandRunner('echo', ['hello'])
|
||||
* await runner
|
||||
* .onStdError('throw', 'Command produced an error')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onStdError(
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
message?: string
|
||||
|
@ -76,6 +151,23 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command produced
|
||||
* non-empty stderr or failed to execute (either did not find such command or failed to spawn it).
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* const runner = createCommandRunner('echo', ['hello'])
|
||||
* await runner
|
||||
* .onError('throw', 'Command produced an error or failed to execute')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onError(
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
message?: string
|
||||
|
@ -83,6 +175,28 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this.on(['execerr', 'stderr'], action, message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command produced
|
||||
* an error that matches provided matcher.
|
||||
* @param matcher allows to match specific error, can be either a string (to match error message exactly),
|
||||
* a regular expression (to match error message with it) or a function (to match error object with it)
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* await createCommandRunner()
|
||||
* .setCommand('curl')
|
||||
* .setArgs(['-f', 'http://example.com/'])
|
||||
* .onSpecificError('Failed to connect to example.com port 80: Connection refused', 'throw', 'Failed to connect to example.com')
|
||||
* .onSpecificError(/429/, log, 'Too many requests, retrying in 4 seconds')
|
||||
* .onSpecificError(/429/, () => retryIn(4000))
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onSpecificError(
|
||||
matcher: ErrorMatcher,
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
|
@ -98,6 +212,23 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command produced
|
||||
* zero exit code and empty stderr.
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* const runner = createCommandRunner('echo', ['hello'])
|
||||
* await runner
|
||||
* .onSuccess('log', 'Command executed successfully')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onSuccess(
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
message?: string
|
||||
|
@ -105,6 +236,27 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this.on('ok', action, message)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command produced an
|
||||
* exit code that matches provided matcher.
|
||||
* @param matcher allows to match specific exit code, can be either a number (to match exit code exactly)
|
||||
* or a string to match exit code against operator and number, e.g. `'>= 0'`
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from event type)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from event type)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from event type)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* await createCommandRunner()
|
||||
* .setCommand('curl')
|
||||
* .setArgs(['-f', 'http://example.com/'])
|
||||
* .onExitCode(0, 'log', 'Command executed successfully')
|
||||
* .onExitCode('>= 400', 'throw', 'Command failed to execute')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onExitCode(
|
||||
matcher: ExitCodeMatcher,
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
|
@ -120,6 +272,27 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets middleware (default or custom) to be executed when command produced
|
||||
* the stdout that matches provided matcher.
|
||||
* @param matcher allows to match specific stdout, can be either a string (to match stdout exactly),
|
||||
* a regular expression (to match stdout with it) or a function (to match stdout with it)
|
||||
* @param action allows to set action to be executed on event, it can be
|
||||
* either default action (passed as string) or a custom middleware, default
|
||||
* actions are:
|
||||
* - `throw` - throws an error with message passed as second argument or a default one (inferred from matcher)
|
||||
* - `fail` - fails the command with message passed as second argument or a default one (inferred from matcher)
|
||||
* - `log` - logs the message passed as second argument or a default one (inferred from matcher)
|
||||
* @param message optional message to be passed to action, is not relevant when action is a custom middleware
|
||||
* @example ```typescript
|
||||
* const runner = createCommandRunner('echo', ['hello'])
|
||||
* await runner
|
||||
* .onOutput('hello', 'log', 'Command executed successfully')
|
||||
* .onOutput(/hello\S+/, 'log', 'What?')
|
||||
* .onOutput(stdout => stdout.includes('world'), 'log', 'Huh')
|
||||
* .run()
|
||||
* ```
|
||||
*/
|
||||
onOutput(
|
||||
matcher: OutputMatcher,
|
||||
action: CommandRunnerActionType | CommandRunnerMiddleware,
|
||||
|
@ -136,6 +309,13 @@ export class CommandRunner extends CommandRunnerBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a command runner with provided command line, arguments and options
|
||||
* @param commandLine command line to execute
|
||||
* @param args arguments to pass to command
|
||||
* @param options options to pass to command executor
|
||||
* @returns command runner instance
|
||||
*/
|
||||
export const createCommandRunner = (
|
||||
commandLine = '',
|
||||
args: string[] = [],
|
||||
|
|
|
@ -17,6 +17,10 @@ export class CommandRunnerBase {
|
|||
private executor: typeof exec.exec = exec.exec
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Sets command to be executed, passing a callback
|
||||
* allows to modify command based on currently set command
|
||||
*/
|
||||
setCommand(commandLine: string | ((commandLine: string) => string)): this {
|
||||
this.commandLine =
|
||||
typeof commandLine === 'function'
|
||||
|
@ -26,6 +30,10 @@ export class CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets command arguments, passing a callback
|
||||
* allows to modify arguments based on currently set arguments
|
||||
*/
|
||||
setArgs(args: string[] | ((args: string[]) => string[])): this {
|
||||
this.args =
|
||||
typeof args === 'function' ? args(this.args) : [...this.args, ...args]
|
||||
|
@ -33,6 +41,10 @@ export class CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets options for command executor (exec.exec by default), passing a callback
|
||||
* allows to modify options based on currently set options
|
||||
*/
|
||||
setOptions(
|
||||
options:
|
||||
| CommandRunnerOptions
|
||||
|
@ -44,11 +56,37 @@ export class CommandRunnerBase {
|
|||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets arbitrary middleware to be executed on command runner run
|
||||
* middleware is executed in the order it was added
|
||||
* @param middleware middleware to be executed
|
||||
* @example
|
||||
* ```ts
|
||||
* const runner = new CommandRunner()
|
||||
* runner.use(async (ctx, next) => {
|
||||
* console.log('before')
|
||||
* const {
|
||||
* exitCode // exit code of the command
|
||||
* stdout // stdout of the command
|
||||
* stderr // stderr of the command
|
||||
* execerr // error thrown by the command executor
|
||||
* commandLine // command line that was executed
|
||||
* args // arguments that were passed to the command
|
||||
* options // options that were passed to the command
|
||||
* } = ctx
|
||||
* await next()
|
||||
* console.log('after')
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
use(middleware: CommandRunnerMiddleware): this {
|
||||
this.middleware.push(promisifyFn(middleware))
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs command with currently set options and arguments
|
||||
*/
|
||||
async run(
|
||||
/* overrides command for this specific execution if not undefined */
|
||||
commandLine?: string,
|
||||
|
@ -117,9 +155,21 @@ export class CommandRunnerBase {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes multiple middleware into a single middleware
|
||||
* implements a chain of responsibility pattern
|
||||
* with next function passed to each middleware
|
||||
* and each middleware being able to call next() to pass control to the next middleware
|
||||
* or not call next() to stop the chain,
|
||||
* it is also possible to run code after the next was called by using async/await
|
||||
* for a cleanup or other purposes.
|
||||
* This behavior is mostly implemented to be similar to express, koa or other middleware based frameworks
|
||||
* in order to avoid confusion. Executing code after next() usually would not be needed.
|
||||
*/
|
||||
export function composeMiddleware(
|
||||
middleware: CommandRunnerMiddleware[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> {
|
||||
// promisify all passed middleware
|
||||
middleware = middleware.map(mw => promisifyFn(mw))
|
||||
|
||||
return async (
|
||||
|
@ -128,6 +178,12 @@ export function composeMiddleware(
|
|||
) => {
|
||||
let index = 0
|
||||
|
||||
/**
|
||||
* Picks the first not-yet-executed middleware from the list and
|
||||
* runs it, passing itself as next function for
|
||||
* that middleware to call, therefore would be called
|
||||
* by each middleware in the chain
|
||||
*/
|
||||
const nextLocal = async (): Promise<void> => {
|
||||
if (index < middleware.length) {
|
||||
const currentMiddleware = middleware[index++]
|
||||
|
@ -138,9 +194,18 @@ export function composeMiddleware(
|
|||
await currentMiddleware(context, nextLocal)
|
||||
}
|
||||
|
||||
/**
|
||||
* If no middlware left to be executed
|
||||
* will call the next funtion passed to the
|
||||
* composed middleware
|
||||
*/
|
||||
await nextGlobal()
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the chain of middleware execution by
|
||||
* calling nextLocal directly
|
||||
*/
|
||||
await nextLocal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import {CommandRunnerContext, CommandRunnerEventType} from './types'
|
||||
|
||||
/**
|
||||
* Keeps track of already computed events for context
|
||||
* to avoid recomputing them
|
||||
*/
|
||||
let contextEvents: WeakMap<
|
||||
CommandRunnerContext,
|
||||
CommandRunnerEventType[]
|
||||
> | null = null
|
||||
|
||||
/**
|
||||
* Returns event types that were triggered by the command execution
|
||||
*/
|
||||
export const getEvents = (
|
||||
ctx: CommandRunnerContext
|
||||
): CommandRunnerEventType[] => {
|
||||
const existingEvents = contextEvents?.get(ctx)
|
||||
|
||||
if (existingEvents) {
|
||||
return existingEvents
|
||||
}
|
||||
|
||||
const eventTypes = new Set<CommandRunnerEventType>()
|
||||
|
||||
if (ctx.execerr) {
|
||||
eventTypes.add('execerr')
|
||||
}
|
||||
|
||||
if (ctx.stderr) {
|
||||
eventTypes.add('stderr')
|
||||
}
|
||||
|
||||
if (ctx.exitCode) {
|
||||
eventTypes.add('exitcode')
|
||||
}
|
||||
|
||||
if (ctx.stdout) {
|
||||
eventTypes.add('stdout')
|
||||
}
|
||||
|
||||
if (!ctx.stderr && !ctx.execerr && ctx.stdout !== null && !ctx.exitCode) {
|
||||
eventTypes.add('ok')
|
||||
}
|
||||
|
||||
const result = [...eventTypes]
|
||||
|
||||
if (!contextEvents) {
|
||||
contextEvents = new WeakMap()
|
||||
}
|
||||
|
||||
contextEvents.set(ctx, result)
|
||||
|
||||
return result
|
||||
}
|
|
@ -1,401 +0,0 @@
|
|||
import * as core from '@actions/core'
|
||||
import {
|
||||
CommandRunnerAction,
|
||||
CommandRunnerContext,
|
||||
CommandRunnerEventType,
|
||||
CommandRunnerEventTypeExtended,
|
||||
CommandRunnerMiddleware,
|
||||
ErrorMatcher,
|
||||
ExitCodeMatcher,
|
||||
OutputMatcher
|
||||
} from './types'
|
||||
import {composeMiddleware} from './core'
|
||||
import {gte, gt, lte, lt, eq, PromisifiedFn} from './utils'
|
||||
|
||||
const getEventTypesFromContext = (
|
||||
ctx: CommandRunnerContext
|
||||
): CommandRunnerEventType[] => {
|
||||
const eventTypes = new Set<CommandRunnerEventType>()
|
||||
|
||||
if (ctx.execerr) {
|
||||
eventTypes.add('execerr')
|
||||
}
|
||||
|
||||
if (ctx.stderr) {
|
||||
eventTypes.add('stderr')
|
||||
}
|
||||
|
||||
if (ctx.exitCode) {
|
||||
eventTypes.add('exitcode')
|
||||
}
|
||||
|
||||
if (ctx.stdout) {
|
||||
eventTypes.add('stdout')
|
||||
}
|
||||
|
||||
if (!ctx.stderr && !ctx.execerr && ctx.stdout !== null && !ctx.exitCode) {
|
||||
eventTypes.add('ok')
|
||||
}
|
||||
|
||||
return [...eventTypes]
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic middleware
|
||||
*/
|
||||
|
||||
/** Calls next middleware */
|
||||
export const passThrough: () => PromisifiedFn<CommandRunnerMiddleware> =
|
||||
() => async (_, next) =>
|
||||
next()
|
||||
|
||||
/**
|
||||
* Fails Github Action with the given message or with a default one depending on execution conditions.
|
||||
*/
|
||||
export const failAction: CommandRunnerAction = message => async ctx => {
|
||||
const events = getEventTypesFromContext(ctx)
|
||||
|
||||
if (message !== undefined) {
|
||||
core.setFailed(typeof message === 'string' ? message : message(ctx, events))
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error with the given message or with a default one depending on execution conditions.
|
||||
*/
|
||||
export const throwError: CommandRunnerAction = message => {
|
||||
return async ctx => {
|
||||
const events = getEventTypesFromContext(ctx)
|
||||
|
||||
if (message !== undefined) {
|
||||
throw new Error(
|
||||
typeof message === 'string' ? message : message(ctx, events)
|
||||
)
|
||||
}
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}`
|
||||
)
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}`
|
||||
)
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.`
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message with the given message or with a default one depending on execution conditions.
|
||||
*/
|
||||
export const produceLog: CommandRunnerAction = message => async (ctx, next) => {
|
||||
const events = getEventTypesFromContext(ctx)
|
||||
|
||||
if (message !== undefined) {
|
||||
// core.info(typeof message === 'string' ? message : message(ctx, []))
|
||||
const messageText =
|
||||
typeof message === 'string' ? message : message(ctx, events)
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
core.error(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
core.error(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
core.warning(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('ok')) {
|
||||
core.notice(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
core.info(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
core.error(
|
||||
`The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
core.error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
core.warning(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('ok')) {
|
||||
core.notice(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
core.info(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching event has occured.
|
||||
* Will call the next middleware otherwise.
|
||||
*/
|
||||
export const matchEvent = (
|
||||
eventType: CommandRunnerEventTypeExtended | CommandRunnerEventTypeExtended[],
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
const expectedEventsPositiveArray = (
|
||||
Array.isArray(eventType) ? eventType : [eventType]
|
||||
).filter(e => !e.startsWith('!')) as CommandRunnerEventType[]
|
||||
|
||||
const expectedEventsNegativeArray = (
|
||||
Array.isArray(eventType) ? eventType : [eventType]
|
||||
)
|
||||
.filter(e => e.startsWith('!'))
|
||||
.map(e => e.slice(1)) as CommandRunnerEventType[]
|
||||
|
||||
const expectedEventsPositive = new Set(expectedEventsPositiveArray)
|
||||
const expectedEventsNegative = new Set(expectedEventsNegativeArray)
|
||||
|
||||
return async (ctx, next) => {
|
||||
const currentEvents = getEventTypesFromContext(ctx)
|
||||
let shouldRun = false
|
||||
|
||||
if (
|
||||
expectedEventsPositive.size &&
|
||||
currentEvents.some(e => expectedEventsPositive.has(e))
|
||||
) {
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
if (
|
||||
expectedEventsNegative.size &&
|
||||
currentEvents.every(e => !expectedEventsNegative.has(e))
|
||||
) {
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
if (shouldRun) {
|
||||
await composedMiddleware(ctx, next)
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching event has occured.
|
||||
* Will call the next middleware otherwise.
|
||||
*/
|
||||
export const matchOutput = (
|
||||
matcher: OutputMatcher,
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
const output = ctx.stdout
|
||||
|
||||
if (output === null) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof matcher === 'function' && !matcher(output)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof matcher === 'string' && output !== matcher) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (matcher instanceof RegExp && !matcher.test(output)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
await composedMiddleware(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
const removeWhitespaces = (str: string): string => str.replace(/\s/g, '')
|
||||
|
||||
const MATCHERS = {
|
||||
'>=': gte,
|
||||
'>': gt,
|
||||
'<=': lte,
|
||||
'<': lt,
|
||||
'=': eq
|
||||
} as const
|
||||
|
||||
const parseExitCodeMatcher = (
|
||||
code: ExitCodeMatcher
|
||||
): [keyof typeof MATCHERS, number] => {
|
||||
if (typeof code === 'number') {
|
||||
return ['=', code]
|
||||
}
|
||||
|
||||
code = removeWhitespaces(code)
|
||||
|
||||
// just shortcuts for the most common cases
|
||||
if (code.startsWith('=')) return ['=', Number(code.slice(1))]
|
||||
if (code === '>0') return ['>', 0]
|
||||
if (code === '<1') return ['<', 1]
|
||||
|
||||
const match = code.match(/^([><]=?)(\d+)$/)
|
||||
|
||||
if (match === null) {
|
||||
throw new Error(`Invalid exit code matcher: ${code}`)
|
||||
}
|
||||
|
||||
const [, operator, number] = match
|
||||
return [operator as keyof typeof MATCHERS, parseInt(number)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching exit code was returned.
|
||||
* Will call the next middleware otherwise.
|
||||
*/
|
||||
export const matchExitCode = (
|
||||
code: ExitCodeMatcher,
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const [operator, number] = parseExitCodeMatcher(code)
|
||||
const matcherFn = MATCHERS[operator](number)
|
||||
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
// if exit code is undefined, NaN will not match anything
|
||||
if (matcherFn(ctx.exitCode ?? NaN)) {
|
||||
await composedMiddleware(ctx, next)
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
||||
|
||||
export const matchSpecificError = (
|
||||
matcher: ErrorMatcher,
|
||||
middleware?:
|
||||
| CommandRunnerMiddleware[]
|
||||
| PromisifiedFn<CommandRunnerMiddleware>[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
if (ctx.execerr === null && ctx.stderr === null) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const error: {
|
||||
type: 'stderr' | 'execerr'
|
||||
error: Error | null
|
||||
message: string
|
||||
} = {
|
||||
type: ctx.execerr ? 'execerr' : 'stderr',
|
||||
error: ctx.execerr ? ctx.execerr : null,
|
||||
message: ctx.execerr ? ctx.execerr.message : ctx.stderr ?? ''
|
||||
}
|
||||
|
||||
if (typeof matcher === 'function' && !matcher(error)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof matcher === 'string' && error.message !== matcher) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (matcher instanceof RegExp && !matcher.test(error.message)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
await composedMiddleware(ctx, next)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,158 @@
|
|||
import * as core from '@actions/core'
|
||||
import {CommandRunnerAction} from '../types'
|
||||
import {getEvents} from '../get-events'
|
||||
|
||||
/**
|
||||
* Fails Github Action with the given message or with a default one depending on execution conditions.
|
||||
*/
|
||||
export const failAction: CommandRunnerAction = message => async ctx => {
|
||||
const events = getEvents(ctx)
|
||||
|
||||
if (message !== undefined) {
|
||||
core.setFailed(typeof message === 'string' ? message : message(ctx, events))
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
core.setFailed(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws an error with the given message or with a default one depending on execution conditions.
|
||||
*/
|
||||
export const throwError: CommandRunnerAction = message => {
|
||||
return async ctx => {
|
||||
const events = getEvents(ctx)
|
||||
|
||||
if (message !== undefined) {
|
||||
throw new Error(
|
||||
typeof message === 'string' ? message : message(ctx, events)
|
||||
)
|
||||
}
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}`
|
||||
)
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}`
|
||||
)
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.`
|
||||
)
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs a message with the given message or with a default one depending on execution conditions.
|
||||
*/
|
||||
export const produceLog: CommandRunnerAction = message => async (ctx, next) => {
|
||||
const events = getEvents(ctx)
|
||||
|
||||
if (message !== undefined) {
|
||||
// core.info(typeof message === 'string' ? message : message(ctx, []))
|
||||
const messageText =
|
||||
typeof message === 'string' ? message : message(ctx, events)
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
core.error(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
core.error(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
core.warning(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('ok')) {
|
||||
core.notice(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
core.info(messageText)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('execerr')) {
|
||||
core.error(
|
||||
`The command "${ctx.commandLine}" failed to run: ${ctx.execerr?.message}`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('stderr')) {
|
||||
core.error(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an error: ${ctx.stderr}`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (!events.includes('stdout')) {
|
||||
core.warning(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced an empty output.`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (events.includes('ok')) {
|
||||
core.notice(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
core.info(
|
||||
`The command "${ctx.commandLine}" finished with exit code ${ctx.exitCode} and produced the following output: ${ctx.stdout}`
|
||||
)
|
||||
next()
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export * from './action-middleware'
|
||||
export * from './match-error'
|
||||
export * from './match-event'
|
||||
export * from './match-exitcode'
|
||||
export * from './match-output'
|
||||
export * from './pass-through'
|
|
@ -0,0 +1,71 @@
|
|||
import {composeMiddleware} from '../core'
|
||||
import {passThrough} from './pass-through'
|
||||
import {CommandRunnerMiddleware} from '../types'
|
||||
import {PromisifiedFn} from '../utils'
|
||||
|
||||
/**
|
||||
* Matcher types that are available to user to match error against
|
||||
* and set middleware on
|
||||
*/
|
||||
export type ErrorMatcher =
|
||||
| RegExp
|
||||
| string
|
||||
| ((error: {
|
||||
type: 'stderr' | 'execerr'
|
||||
error: Error | null
|
||||
message: string
|
||||
}) => boolean)
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching error has occured.
|
||||
* If matching error has occured will call passed middleware. Will call the next middleware otherwise.
|
||||
*/
|
||||
export const matchSpecificError = (
|
||||
matcher: ErrorMatcher,
|
||||
middleware?:
|
||||
| CommandRunnerMiddleware[]
|
||||
| PromisifiedFn<CommandRunnerMiddleware>[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
/**
|
||||
* Composes passed middleware if any or replaces them
|
||||
* with passThrough middleware if none were passed
|
||||
* to avoid errors when calling composed middleware
|
||||
*/
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
if (ctx.execerr === null && ctx.stderr === null) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
const error: {
|
||||
type: 'stderr' | 'execerr'
|
||||
error: Error | null
|
||||
message: string
|
||||
} = {
|
||||
type: ctx.execerr ? 'execerr' : 'stderr',
|
||||
error: ctx.execerr ? ctx.execerr : null,
|
||||
message: ctx.execerr ? ctx.execerr.message : ctx.stderr ?? ''
|
||||
}
|
||||
|
||||
if (typeof matcher === 'function' && !matcher(error)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof matcher === 'string' && error.message !== matcher) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (matcher instanceof RegExp && !matcher.test(error.message)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
await composedMiddleware(ctx, next)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import {composeMiddleware} from '../core'
|
||||
import {getEvents} from '../get-events'
|
||||
import {
|
||||
CommandRunnerEventTypeExtended,
|
||||
CommandRunnerMiddleware,
|
||||
CommandRunnerEventType
|
||||
} from '../types'
|
||||
import {PromisifiedFn} from '../utils'
|
||||
import {passThrough} from './pass-through'
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching event has occured.
|
||||
* Will call the next middleware otherwise.
|
||||
*/
|
||||
export const matchEvent = (
|
||||
eventType: CommandRunnerEventTypeExtended | CommandRunnerEventTypeExtended[],
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
/**
|
||||
* Composes passed middleware if any or replaces them
|
||||
* with passThrough middleware if none were passed
|
||||
* to avoid errors when calling composed middleware
|
||||
*/
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
const expectedEventsPositiveArray = (
|
||||
Array.isArray(eventType) ? eventType : [eventType]
|
||||
).filter(e => !e.startsWith('!')) as CommandRunnerEventType[]
|
||||
|
||||
const expectedEventsNegativeArray = (
|
||||
Array.isArray(eventType) ? eventType : [eventType]
|
||||
)
|
||||
.filter(e => e.startsWith('!'))
|
||||
.map(e => e.slice(1)) as CommandRunnerEventType[]
|
||||
|
||||
const expectedEventsPositive = new Set(expectedEventsPositiveArray)
|
||||
const expectedEventsNegative = new Set(expectedEventsNegativeArray)
|
||||
|
||||
return async (ctx, next) => {
|
||||
const currentEvents = getEvents(ctx)
|
||||
let shouldRun = false
|
||||
|
||||
if (
|
||||
expectedEventsPositive.size &&
|
||||
currentEvents.some(e => expectedEventsPositive.has(e))
|
||||
) {
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
if (
|
||||
expectedEventsNegative.size &&
|
||||
currentEvents.every(e => !expectedEventsNegative.has(e))
|
||||
) {
|
||||
shouldRun = true
|
||||
}
|
||||
|
||||
if (shouldRun) {
|
||||
await composedMiddleware(ctx, next)
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
import {composeMiddleware} from '../core'
|
||||
import {passThrough} from './pass-through'
|
||||
import {CommandRunnerMiddleware} from '../types'
|
||||
import {PromisifiedFn, removeWhitespaces} from '../utils'
|
||||
|
||||
/**
|
||||
* Matcher types that are available to user to match exit code against
|
||||
* and set middleware on
|
||||
*/
|
||||
export type ExitCodeMatcher = string | number
|
||||
|
||||
/**
|
||||
* Comparators
|
||||
*/
|
||||
export const lte =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b <= a
|
||||
export const gte =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b >= a
|
||||
export const lt =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b < a
|
||||
export const gt =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b > a
|
||||
export const eq =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b === a
|
||||
|
||||
const MATCHERS = {
|
||||
'>=': gte,
|
||||
'>': gt,
|
||||
'<=': lte,
|
||||
'<': lt,
|
||||
'=': eq
|
||||
} as const
|
||||
|
||||
const parseExitCodeMatcher = (
|
||||
code: ExitCodeMatcher
|
||||
): [keyof typeof MATCHERS, number] => {
|
||||
if (typeof code === 'number') {
|
||||
return ['=', code]
|
||||
}
|
||||
|
||||
code = removeWhitespaces(code)
|
||||
|
||||
// just shortcuts for the most common cases
|
||||
if (code.startsWith('=')) return ['=', Number(code.slice(1))]
|
||||
if (code === '>0') return ['>', 0]
|
||||
if (code === '<1') return ['<', 1]
|
||||
|
||||
const match = code.match(/^([><]=?)(\d+)$/)
|
||||
|
||||
if (match === null) {
|
||||
throw new Error(`Invalid exit code matcher: ${code}`)
|
||||
}
|
||||
|
||||
const [, operator, number] = match
|
||||
return [operator as keyof typeof MATCHERS, parseInt(number)]
|
||||
}
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching exit code was returned.
|
||||
* Will call the next middleware otherwise. Will also call next middleware
|
||||
* if exit code is null (command did not run).
|
||||
*/
|
||||
export const matchExitCode = (
|
||||
code: ExitCodeMatcher,
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const [operator, number] = parseExitCodeMatcher(code)
|
||||
|
||||
// sets appropriate matching function
|
||||
const matcherFn = MATCHERS[operator](number)
|
||||
|
||||
/**
|
||||
* Composes passed middleware if any or replaces them
|
||||
* with passThrough middleware if none were passed
|
||||
* to avoid errors when calling composed middleware
|
||||
*/
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
// if exit code is undefined, NaN will not match anything
|
||||
if (matcherFn(ctx.exitCode ?? NaN)) {
|
||||
await composedMiddleware(ctx, next)
|
||||
return
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import {composeMiddleware} from '../core'
|
||||
import {passThrough} from './pass-through'
|
||||
import {CommandRunnerMiddleware} from '../types'
|
||||
import {PromisifiedFn} from '../utils'
|
||||
|
||||
/**
|
||||
* Matcher types that are available to user to match output against
|
||||
* and set middleware on
|
||||
*/
|
||||
export type OutputMatcher = RegExp | string | ((output: string) => boolean)
|
||||
|
||||
/**
|
||||
* Will call passed middleware if command produced a matching stdout.
|
||||
* Will call the next middleware otherwise. Will also call the next middleware
|
||||
* if stdout is null (command did not run).
|
||||
*/
|
||||
export const matchOutput = (
|
||||
matcher: OutputMatcher,
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
/**
|
||||
* Composes passed middleware if any or replaces them
|
||||
* with passThrough middleware if none were passed
|
||||
* to avoid errors when calling composed middleware
|
||||
*/
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
const output = ctx.stdout
|
||||
|
||||
if (output === null) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof matcher === 'function' && !matcher(output)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof matcher === 'string' && output !== matcher) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
if (matcher instanceof RegExp && !matcher.test(output)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
|
||||
await composedMiddleware(ctx, next)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import {CommandRunnerMiddleware} from '../types'
|
||||
import {PromisifiedFn} from '../utils'
|
||||
|
||||
/** Calls next middleware */
|
||||
export const passThrough: () => PromisifiedFn<CommandRunnerMiddleware> =
|
||||
() => async (_, next) =>
|
||||
next()
|
|
@ -1,41 +1,64 @@
|
|||
import * as exec from '../exec'
|
||||
import {PromisifiedFn} from './utils'
|
||||
|
||||
/* CommandRunner core */
|
||||
|
||||
/**
|
||||
* CommandRunner.prototype.run() outpout and context
|
||||
* that is passed to each middleware
|
||||
*/
|
||||
export interface CommandRunnerContext {
|
||||
/* Inputs with which command was executed */
|
||||
/** Command that was executed */
|
||||
commandLine: string
|
||||
|
||||
/** Arguments with which command was executed */
|
||||
args: string[]
|
||||
|
||||
/** Command options with which command executor was ran */
|
||||
options: exec.ExecOptions
|
||||
|
||||
/* Results of the execution */
|
||||
/** Error that was thrown when attempting to execute command */
|
||||
execerr: Error | null
|
||||
|
||||
/** Command's output that was passed to stderr if command did run, null otherwise */
|
||||
stderr: string | null
|
||||
|
||||
/** Command's output that was passed to stdout if command did run, null otherwise */
|
||||
stdout: string | null
|
||||
|
||||
/** Command's exit code if command did run, null otherwise */
|
||||
exitCode: number | null
|
||||
}
|
||||
|
||||
/* Middlewares as used by the user */
|
||||
/**
|
||||
* Base middleware shape
|
||||
*/
|
||||
type _CommandRunnerMiddleware = (
|
||||
ctx: CommandRunnerContext,
|
||||
next: () => Promise<void>
|
||||
) => void | Promise<void>
|
||||
|
||||
/**
|
||||
* Normalized middleware shape that is always promisified
|
||||
*/
|
||||
export type CommandRunnerMiddleware = PromisifiedFn<_CommandRunnerMiddleware>
|
||||
|
||||
/* Command runner events handling and command runner actions */
|
||||
|
||||
/**
|
||||
* Shape for the command runner default middleware creators
|
||||
*/
|
||||
export type CommandRunnerAction = (
|
||||
message?:
|
||||
| string
|
||||
| ((ctx: CommandRunnerContext, events: CommandRunnerEventType[]) => string)
|
||||
) => PromisifiedFn<CommandRunnerMiddleware>
|
||||
|
||||
/* Command runner default actions types on which preset middleware exists */
|
||||
/**
|
||||
* Default middleware identifires that can be uset to set respective action
|
||||
* in copmposing middleware
|
||||
*/
|
||||
export type CommandRunnerActionType = 'throw' | 'fail' | 'log'
|
||||
|
||||
/* Command runner event types as used internally passed to middleware for the user */
|
||||
/**
|
||||
* Command runner event types on which middleware can be set
|
||||
*/
|
||||
export type CommandRunnerEventType =
|
||||
| 'execerr'
|
||||
| 'stderr'
|
||||
|
@ -43,27 +66,19 @@ export type CommandRunnerEventType =
|
|||
| 'exitcode'
|
||||
| 'ok'
|
||||
|
||||
/* Command runner event types as used by the user for filtering results */
|
||||
/**
|
||||
* Extended event type that can be used to set middleware on event not happening
|
||||
*/
|
||||
export type CommandRunnerEventTypeExtended =
|
||||
| CommandRunnerEventType
|
||||
| `!${CommandRunnerEventType}`
|
||||
|
||||
/**
|
||||
* options that would be passed to the command executor (exec.exec by default)
|
||||
* failOnStdErr and ignoreReturnCode are excluded as they are
|
||||
* handled by the CommandRunner itself
|
||||
*/
|
||||
export type CommandRunnerOptions = Omit<
|
||||
exec.ExecOptions,
|
||||
'failOnStdErr' | 'ignoreReturnCode'
|
||||
>
|
||||
|
||||
/* Matchers */
|
||||
|
||||
export type OutputMatcher = RegExp | string | ((output: string) => boolean)
|
||||
|
||||
export type ExitCodeMatcher = string | number
|
||||
|
||||
export type ErrorMatcher =
|
||||
| RegExp
|
||||
| string
|
||||
| ((error: {
|
||||
type: 'stderr' | 'execerr'
|
||||
error: Error | null
|
||||
message: string
|
||||
}) => boolean)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
/**
|
||||
* Promises
|
||||
* Promisifies a a function type
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type PromisifiedFn<T extends (...args: any[]) => any> = (
|
||||
...args: Parameters<T>
|
||||
|
@ -9,6 +8,9 @@ export type PromisifiedFn<T extends (...args: any[]) => any> = (
|
|||
? ReturnType<T>
|
||||
: Promise<ReturnType<T>>
|
||||
|
||||
/**
|
||||
* Promisifies a function
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const promisifyFn = <T extends (...args: any[]) => any>(
|
||||
fn: T
|
||||
|
@ -27,26 +29,6 @@ export const promisifyFn = <T extends (...args: any[]) => any>(
|
|||
}
|
||||
|
||||
/**
|
||||
* Comparators
|
||||
* Removes all whitespaces from a string
|
||||
*/
|
||||
|
||||
export const lte =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b <= a
|
||||
export const gte =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b >= a
|
||||
export const lt =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b < a
|
||||
export const gt =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b > a
|
||||
export const eq =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b === a
|
||||
export const removeWhitespaces = (str: string): string => str.replace(/\s/g, '')
|
||||
|
|
|
@ -2,6 +2,8 @@ import {StringDecoder} from 'string_decoder'
|
|||
import {ExecOptions, ExecOutput, ExecListeners} from './interfaces'
|
||||
import * as tr from './toolrunner'
|
||||
|
||||
export {CommandRunner, createCommandRunner} from './command-runner'
|
||||
|
||||
export {ExecOptions, ExecOutput, ExecListeners}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue