mirror of https://github.com/actions/toolkit
Refactor base middlewares
parent
36629a3962
commit
b45c9d3801
|
@ -1,9 +1,6 @@
|
|||
import * as exec from '@actions/exec'
|
||||
import {CommandRunnerBase} from './core'
|
||||
import {
|
||||
ErrorMatcher,
|
||||
ExitCodeMatcher,
|
||||
OutputMatcher,
|
||||
failAction,
|
||||
matchEvent,
|
||||
matchExitCode,
|
||||
|
@ -16,7 +13,10 @@ import {
|
|||
CommandRunnerActionType,
|
||||
CommandRunnerEventTypeExtended,
|
||||
CommandRunnerMiddleware,
|
||||
CommandRunnerOptions
|
||||
CommandRunnerOptions,
|
||||
ErrorMatcher,
|
||||
ExitCodeMatcher,
|
||||
OutputMatcher
|
||||
} from './types'
|
||||
|
||||
const commandRunnerActions = {
|
||||
|
|
|
@ -3,39 +3,12 @@ import {StringDecoder} from 'string_decoder'
|
|||
import {
|
||||
CommandRunnerContext,
|
||||
CommandRunnerMiddleware,
|
||||
CommandRunnerMiddlewarePromisified,
|
||||
CommandRunnerOptions
|
||||
} from './types'
|
||||
|
||||
export const promisifyCommandRunnerMiddleware =
|
||||
(middleware: CommandRunnerMiddleware): CommandRunnerMiddlewarePromisified =>
|
||||
async (ctx, next) => {
|
||||
return Promise.resolve(middleware(ctx, next))
|
||||
}
|
||||
|
||||
export const composeCommandRunnerMiddleware =
|
||||
(middleware: CommandRunnerMiddlewarePromisified[]) =>
|
||||
async (context: CommandRunnerContext, nextGlobal: () => Promise<void>) => {
|
||||
let index = 0
|
||||
|
||||
const nextLocal = async (): Promise<void> => {
|
||||
if (index < middleware.length) {
|
||||
const currentMiddleware = middleware[index++]
|
||||
if (middleware === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await currentMiddleware(context, nextLocal)
|
||||
}
|
||||
|
||||
await nextGlobal()
|
||||
}
|
||||
|
||||
await nextLocal()
|
||||
}
|
||||
import {PromisifiedFn, promisifyFn} from './utils'
|
||||
|
||||
export class CommandRunnerBase {
|
||||
private middleware: CommandRunnerMiddlewarePromisified[] = []
|
||||
private middleware: PromisifiedFn<CommandRunnerMiddleware>[] = []
|
||||
|
||||
constructor(
|
||||
private commandLine: string,
|
||||
|
@ -45,7 +18,7 @@ export class CommandRunnerBase {
|
|||
) {}
|
||||
|
||||
use(middleware: CommandRunnerMiddleware): this {
|
||||
this.middleware.push(promisifyCommandRunnerMiddleware(middleware))
|
||||
this.middleware.push(promisifyFn(middleware))
|
||||
return this
|
||||
}
|
||||
|
||||
|
@ -104,8 +77,36 @@ export class CommandRunnerBase {
|
|||
}
|
||||
|
||||
const next = async (): Promise<void> => Promise.resolve()
|
||||
await composeCommandRunnerMiddleware(this.middleware)(context, next)
|
||||
await composeMiddleware(this.middleware)(context, next)
|
||||
|
||||
return context
|
||||
}
|
||||
}
|
||||
|
||||
export function composeMiddleware(
|
||||
middleware: PromisifiedFn<CommandRunnerMiddleware>[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> {
|
||||
middleware = middleware.map(mw => promisifyFn(mw))
|
||||
|
||||
return async (
|
||||
context: CommandRunnerContext,
|
||||
nextGlobal: () => Promise<void>
|
||||
) => {
|
||||
let index = 0
|
||||
|
||||
const nextLocal = async (): Promise<void> => {
|
||||
if (index < middleware.length) {
|
||||
const currentMiddleware = middleware[index++]
|
||||
if (middleware === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
await currentMiddleware(context, nextLocal)
|
||||
}
|
||||
|
||||
await nextGlobal()
|
||||
}
|
||||
|
||||
await nextLocal()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import * as core from '@actions/core'
|
||||
import {
|
||||
CommandRunnerAction,
|
||||
CommandRunnerContext,
|
||||
CommandRunnerEventType,
|
||||
CommandRunnerEventTypeExtended,
|
||||
CommandRunnerMiddleware,
|
||||
CommandRunnerMiddlewarePromisified
|
||||
ErrorMatcher,
|
||||
ExitCodeMatcher,
|
||||
OutputMatcher
|
||||
} from './types'
|
||||
import {
|
||||
composeCommandRunnerMiddleware,
|
||||
promisifyCommandRunnerMiddleware
|
||||
} from './core'
|
||||
import {composeMiddleware} from './core'
|
||||
import {gte, gt, lte, lt, eq, PromisifiedFn} from './utils'
|
||||
|
||||
const getEventTypesFromContext = (
|
||||
ctx: CommandRunnerContext
|
||||
|
@ -39,16 +40,15 @@ const getEventTypesFromContext = (
|
|||
return [...eventTypes]
|
||||
}
|
||||
|
||||
type CommandRunnerAction = (
|
||||
message?:
|
||||
| string
|
||||
| ((ctx: CommandRunnerContext, events: CommandRunnerEventType[]) => string)
|
||||
) => CommandRunnerMiddlewarePromisified
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
@ -202,31 +202,6 @@ export const produceLog: CommandRunnerAction = message => async (ctx, next) => {
|
|||
next()
|
||||
}
|
||||
|
||||
/**
|
||||
* Filtering middleware
|
||||
*/
|
||||
|
||||
/** Calls next middleware */
|
||||
export const passThrough: () => CommandRunnerMiddlewarePromisified =
|
||||
() => async (_, next) =>
|
||||
next()
|
||||
|
||||
/**
|
||||
* Either calls next middleware or not depending on the result of the given condition.
|
||||
*/
|
||||
export const filter: (
|
||||
shouldPass:
|
||||
| boolean
|
||||
| ((ctx: CommandRunnerContext) => boolean | Promise<boolean>)
|
||||
) => CommandRunnerMiddlewarePromisified = shouldPass => async (ctx, next) => {
|
||||
if (typeof shouldPass === 'function') {
|
||||
if (await shouldPass(ctx)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching event has occured.
|
||||
* Will call the next middleware otherwise.
|
||||
|
@ -234,13 +209,9 @@ export const filter: (
|
|||
export const matchEvent = (
|
||||
eventType: CommandRunnerEventTypeExtended | CommandRunnerEventTypeExtended[],
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): CommandRunnerMiddlewarePromisified => {
|
||||
if (!middleware?.length) {
|
||||
middleware = [passThrough()]
|
||||
}
|
||||
|
||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
const expectedEventsPositiveArray = (
|
||||
|
@ -283,8 +254,6 @@ export const matchEvent = (
|
|||
}
|
||||
}
|
||||
|
||||
export type OutputMatcher = RegExp | string | ((output: string) => boolean)
|
||||
|
||||
/**
|
||||
* Will call passed middleware if matching event has occured.
|
||||
* Will call the next middleware otherwise.
|
||||
|
@ -292,13 +261,9 @@ export type OutputMatcher = RegExp | string | ((output: string) => boolean)
|
|||
export const matchOutput = (
|
||||
matcher: OutputMatcher,
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): CommandRunnerMiddlewarePromisified => {
|
||||
if (!middleware?.length) {
|
||||
middleware = [passThrough()]
|
||||
}
|
||||
|
||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
|
@ -328,30 +293,9 @@ export const matchOutput = (
|
|||
}
|
||||
}
|
||||
|
||||
export type ExitCodeMatcher = string | number
|
||||
const removeWhitespaces = (str: string): string => str.replace(/\s/g, '')
|
||||
|
||||
const lte =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b <= a
|
||||
const gte =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b >= a
|
||||
const lt =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b < a
|
||||
const gt =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b > a
|
||||
const eq =
|
||||
(a: number) =>
|
||||
(b: number): boolean =>
|
||||
b === a
|
||||
|
||||
const matchers = {
|
||||
const MATCHERS = {
|
||||
'>=': gte,
|
||||
'>': gt,
|
||||
'<=': lte,
|
||||
|
@ -359,11 +303,9 @@ const matchers = {
|
|||
'=': eq
|
||||
} as const
|
||||
|
||||
const removeWhitespaces = (str: string): string => str.replace(/\s/g, '')
|
||||
|
||||
const parseExitCodeMatcher = (
|
||||
code: ExitCodeMatcher
|
||||
): [keyof typeof matchers, number] => {
|
||||
): [keyof typeof MATCHERS, number] => {
|
||||
if (typeof code === 'number') {
|
||||
return ['=', code]
|
||||
}
|
||||
|
@ -382,14 +324,7 @@ const parseExitCodeMatcher = (
|
|||
}
|
||||
|
||||
const [, operator, number] = match
|
||||
return [operator as keyof typeof matchers, parseInt(number)]
|
||||
}
|
||||
|
||||
const matcherToMatcherFn = (
|
||||
matcher: ExitCodeMatcher
|
||||
): ((exitCode: number) => boolean) => {
|
||||
const [operator, number] = parseExitCodeMatcher(matcher)
|
||||
return matchers[operator](number)
|
||||
return [operator as keyof typeof MATCHERS, parseInt(number)]
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -399,20 +334,17 @@ const matcherToMatcherFn = (
|
|||
export const matchExitCode = (
|
||||
code: ExitCodeMatcher,
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): CommandRunnerMiddlewarePromisified => {
|
||||
const matcher = matcherToMatcherFn(code)
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const [operator, number] = parseExitCodeMatcher(code)
|
||||
const matcherFn = MATCHERS[operator](number)
|
||||
|
||||
if (!middleware?.length) {
|
||||
middleware = [passThrough()]
|
||||
}
|
||||
|
||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
// if exit code is undefined, NaN will not match anything
|
||||
if (matcher(ctx.exitCode ?? NaN)) {
|
||||
if (matcherFn(ctx.exitCode ?? NaN)) {
|
||||
composedMiddleware(ctx, next)
|
||||
return
|
||||
}
|
||||
|
@ -421,25 +353,14 @@ export const matchExitCode = (
|
|||
}
|
||||
}
|
||||
|
||||
export type ErrorMatcher =
|
||||
| RegExp
|
||||
| string
|
||||
| ((error: {
|
||||
type: 'stderr' | 'execerr'
|
||||
error: Error | null
|
||||
message: string
|
||||
}) => boolean)
|
||||
|
||||
export const matchSpecificError = (
|
||||
matcher: ErrorMatcher,
|
||||
middleware?: CommandRunnerMiddleware[]
|
||||
): CommandRunnerMiddlewarePromisified => {
|
||||
if (!middleware?.length) {
|
||||
middleware = [passThrough()]
|
||||
}
|
||||
|
||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
||||
middleware?:
|
||||
| CommandRunnerMiddleware[]
|
||||
| PromisifiedFn<CommandRunnerMiddleware>[]
|
||||
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||
const composedMiddleware = composeMiddleware(
|
||||
!middleware?.length ? [passThrough()] : middleware
|
||||
)
|
||||
|
||||
return async (ctx, next) => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import * as exec from '@actions/exec'
|
||||
import {PromisifiedFn} from './utils'
|
||||
|
||||
/* CommandRunner core */
|
||||
|
||||
|
@ -15,20 +16,22 @@ export interface CommandRunnerContext {
|
|||
exitCode: number | null
|
||||
}
|
||||
|
||||
/* Middlewares as used internally in CommandRunner */
|
||||
export type CommandRunnerMiddlewarePromisified = (
|
||||
ctx: CommandRunnerContext,
|
||||
next: () => Promise<void>
|
||||
) => Promise<void>
|
||||
|
||||
/* Middlewares as used by the user */
|
||||
export type CommandRunnerMiddleware = (
|
||||
type _CommandRunnerMiddleware = (
|
||||
ctx: CommandRunnerContext,
|
||||
next: () => Promise<void>
|
||||
) => void | Promise<void>
|
||||
|
||||
export type CommandRunnerMiddleware = PromisifiedFn<_CommandRunnerMiddleware>
|
||||
|
||||
/* Command runner events handling and command runner actions */
|
||||
|
||||
export type CommandRunnerAction = (
|
||||
message?:
|
||||
| string
|
||||
| ((ctx: CommandRunnerContext, events: CommandRunnerEventType[]) => string)
|
||||
) => PromisifiedFn<CommandRunnerMiddleware>
|
||||
|
||||
/* Command runner default actions types on which preset middleware exists */
|
||||
export type CommandRunnerActionType = 'throw' | 'fail' | 'log'
|
||||
|
||||
|
@ -49,3 +52,18 @@ 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)
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
* Promises
|
||||
*/
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type PromisifiedFn<T extends (...args: any[]) => any> = (
|
||||
...args: Parameters<T>
|
||||
) => ReturnType<T> extends Promise<unknown>
|
||||
? ReturnType<T>
|
||||
: Promise<ReturnType<T>>
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const promisifyFn = <T extends (...args: any[]) => any>(
|
||||
fn: T
|
||||
): PromisifiedFn<T> => {
|
||||
const result = async (...args: Parameters<T>): Promise<unknown> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
resolve(fn(...args))
|
||||
} catch (error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return result as PromisifiedFn<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
Loading…
Reference in New Issue