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 * as exec from '@actions/exec'
|
||||||
import {CommandRunnerBase} from './core'
|
import {CommandRunnerBase} from './core'
|
||||||
import {
|
import {
|
||||||
ErrorMatcher,
|
|
||||||
ExitCodeMatcher,
|
|
||||||
OutputMatcher,
|
|
||||||
failAction,
|
failAction,
|
||||||
matchEvent,
|
matchEvent,
|
||||||
matchExitCode,
|
matchExitCode,
|
||||||
|
@ -16,7 +13,10 @@ import {
|
||||||
CommandRunnerActionType,
|
CommandRunnerActionType,
|
||||||
CommandRunnerEventTypeExtended,
|
CommandRunnerEventTypeExtended,
|
||||||
CommandRunnerMiddleware,
|
CommandRunnerMiddleware,
|
||||||
CommandRunnerOptions
|
CommandRunnerOptions,
|
||||||
|
ErrorMatcher,
|
||||||
|
ExitCodeMatcher,
|
||||||
|
OutputMatcher
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
const commandRunnerActions = {
|
const commandRunnerActions = {
|
||||||
|
|
|
@ -3,39 +3,12 @@ import {StringDecoder} from 'string_decoder'
|
||||||
import {
|
import {
|
||||||
CommandRunnerContext,
|
CommandRunnerContext,
|
||||||
CommandRunnerMiddleware,
|
CommandRunnerMiddleware,
|
||||||
CommandRunnerMiddlewarePromisified,
|
|
||||||
CommandRunnerOptions
|
CommandRunnerOptions
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import {PromisifiedFn, promisifyFn} from './utils'
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
export class CommandRunnerBase {
|
export class CommandRunnerBase {
|
||||||
private middleware: CommandRunnerMiddlewarePromisified[] = []
|
private middleware: PromisifiedFn<CommandRunnerMiddleware>[] = []
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private commandLine: string,
|
private commandLine: string,
|
||||||
|
@ -45,7 +18,7 @@ export class CommandRunnerBase {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
use(middleware: CommandRunnerMiddleware): this {
|
use(middleware: CommandRunnerMiddleware): this {
|
||||||
this.middleware.push(promisifyCommandRunnerMiddleware(middleware))
|
this.middleware.push(promisifyFn(middleware))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,8 +77,36 @@ export class CommandRunnerBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
const next = async (): Promise<void> => Promise.resolve()
|
const next = async (): Promise<void> => Promise.resolve()
|
||||||
await composeCommandRunnerMiddleware(this.middleware)(context, next)
|
await composeMiddleware(this.middleware)(context, next)
|
||||||
|
|
||||||
return context
|
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 * as core from '@actions/core'
|
||||||
import {
|
import {
|
||||||
|
CommandRunnerAction,
|
||||||
CommandRunnerContext,
|
CommandRunnerContext,
|
||||||
CommandRunnerEventType,
|
CommandRunnerEventType,
|
||||||
CommandRunnerEventTypeExtended,
|
CommandRunnerEventTypeExtended,
|
||||||
CommandRunnerMiddleware,
|
CommandRunnerMiddleware,
|
||||||
CommandRunnerMiddlewarePromisified
|
ErrorMatcher,
|
||||||
|
ExitCodeMatcher,
|
||||||
|
OutputMatcher
|
||||||
} from './types'
|
} from './types'
|
||||||
import {
|
import {composeMiddleware} from './core'
|
||||||
composeCommandRunnerMiddleware,
|
import {gte, gt, lte, lt, eq, PromisifiedFn} from './utils'
|
||||||
promisifyCommandRunnerMiddleware
|
|
||||||
} from './core'
|
|
||||||
|
|
||||||
const getEventTypesFromContext = (
|
const getEventTypesFromContext = (
|
||||||
ctx: CommandRunnerContext
|
ctx: CommandRunnerContext
|
||||||
|
@ -39,16 +40,15 @@ const getEventTypesFromContext = (
|
||||||
return [...eventTypes]
|
return [...eventTypes]
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommandRunnerAction = (
|
|
||||||
message?:
|
|
||||||
| string
|
|
||||||
| ((ctx: CommandRunnerContext, events: CommandRunnerEventType[]) => string)
|
|
||||||
) => CommandRunnerMiddlewarePromisified
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Basic middleware
|
* 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.
|
* 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()
|
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 passed middleware if matching event has occured.
|
||||||
* Will call the next middleware otherwise.
|
* Will call the next middleware otherwise.
|
||||||
|
@ -234,13 +209,9 @@ export const filter: (
|
||||||
export const matchEvent = (
|
export const matchEvent = (
|
||||||
eventType: CommandRunnerEventTypeExtended | CommandRunnerEventTypeExtended[],
|
eventType: CommandRunnerEventTypeExtended | CommandRunnerEventTypeExtended[],
|
||||||
middleware?: CommandRunnerMiddleware[]
|
middleware?: CommandRunnerMiddleware[]
|
||||||
): CommandRunnerMiddlewarePromisified => {
|
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||||
if (!middleware?.length) {
|
const composedMiddleware = composeMiddleware(
|
||||||
middleware = [passThrough()]
|
!middleware?.length ? [passThrough()] : middleware
|
||||||
}
|
|
||||||
|
|
||||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
|
||||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const expectedEventsPositiveArray = (
|
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 passed middleware if matching event has occured.
|
||||||
* Will call the next middleware otherwise.
|
* Will call the next middleware otherwise.
|
||||||
|
@ -292,13 +261,9 @@ export type OutputMatcher = RegExp | string | ((output: string) => boolean)
|
||||||
export const matchOutput = (
|
export const matchOutput = (
|
||||||
matcher: OutputMatcher,
|
matcher: OutputMatcher,
|
||||||
middleware?: CommandRunnerMiddleware[]
|
middleware?: CommandRunnerMiddleware[]
|
||||||
): CommandRunnerMiddlewarePromisified => {
|
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||||
if (!middleware?.length) {
|
const composedMiddleware = composeMiddleware(
|
||||||
middleware = [passThrough()]
|
!middleware?.length ? [passThrough()] : middleware
|
||||||
}
|
|
||||||
|
|
||||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
|
||||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return async (ctx, next) => {
|
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 =
|
const MATCHERS = {
|
||||||
(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 = {
|
|
||||||
'>=': gte,
|
'>=': gte,
|
||||||
'>': gt,
|
'>': gt,
|
||||||
'<=': lte,
|
'<=': lte,
|
||||||
|
@ -359,11 +303,9 @@ const matchers = {
|
||||||
'=': eq
|
'=': eq
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
const removeWhitespaces = (str: string): string => str.replace(/\s/g, '')
|
|
||||||
|
|
||||||
const parseExitCodeMatcher = (
|
const parseExitCodeMatcher = (
|
||||||
code: ExitCodeMatcher
|
code: ExitCodeMatcher
|
||||||
): [keyof typeof matchers, number] => {
|
): [keyof typeof MATCHERS, number] => {
|
||||||
if (typeof code === 'number') {
|
if (typeof code === 'number') {
|
||||||
return ['=', code]
|
return ['=', code]
|
||||||
}
|
}
|
||||||
|
@ -382,14 +324,7 @@ const parseExitCodeMatcher = (
|
||||||
}
|
}
|
||||||
|
|
||||||
const [, operator, number] = match
|
const [, operator, number] = match
|
||||||
return [operator as keyof typeof matchers, parseInt(number)]
|
return [operator as keyof typeof MATCHERS, parseInt(number)]
|
||||||
}
|
|
||||||
|
|
||||||
const matcherToMatcherFn = (
|
|
||||||
matcher: ExitCodeMatcher
|
|
||||||
): ((exitCode: number) => boolean) => {
|
|
||||||
const [operator, number] = parseExitCodeMatcher(matcher)
|
|
||||||
return matchers[operator](number)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -399,20 +334,17 @@ const matcherToMatcherFn = (
|
||||||
export const matchExitCode = (
|
export const matchExitCode = (
|
||||||
code: ExitCodeMatcher,
|
code: ExitCodeMatcher,
|
||||||
middleware?: CommandRunnerMiddleware[]
|
middleware?: CommandRunnerMiddleware[]
|
||||||
): CommandRunnerMiddlewarePromisified => {
|
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||||
const matcher = matcherToMatcherFn(code)
|
const [operator, number] = parseExitCodeMatcher(code)
|
||||||
|
const matcherFn = MATCHERS[operator](number)
|
||||||
|
|
||||||
if (!middleware?.length) {
|
const composedMiddleware = composeMiddleware(
|
||||||
middleware = [passThrough()]
|
!middleware?.length ? [passThrough()] : middleware
|
||||||
}
|
|
||||||
|
|
||||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
|
||||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
// if exit code is undefined, NaN will not match anything
|
// if exit code is undefined, NaN will not match anything
|
||||||
if (matcher(ctx.exitCode ?? NaN)) {
|
if (matcherFn(ctx.exitCode ?? NaN)) {
|
||||||
composedMiddleware(ctx, next)
|
composedMiddleware(ctx, next)
|
||||||
return
|
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 = (
|
export const matchSpecificError = (
|
||||||
matcher: ErrorMatcher,
|
matcher: ErrorMatcher,
|
||||||
middleware?: CommandRunnerMiddleware[]
|
middleware?:
|
||||||
): CommandRunnerMiddlewarePromisified => {
|
| CommandRunnerMiddleware[]
|
||||||
if (!middleware?.length) {
|
| PromisifiedFn<CommandRunnerMiddleware>[]
|
||||||
middleware = [passThrough()]
|
): PromisifiedFn<CommandRunnerMiddleware> => {
|
||||||
}
|
const composedMiddleware = composeMiddleware(
|
||||||
|
!middleware?.length ? [passThrough()] : middleware
|
||||||
const composedMiddleware = composeCommandRunnerMiddleware(
|
|
||||||
middleware.map(mw => promisifyCommandRunnerMiddleware(mw))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return async (ctx, next) => {
|
return async (ctx, next) => {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as exec from '@actions/exec'
|
import * as exec from '@actions/exec'
|
||||||
|
import {PromisifiedFn} from './utils'
|
||||||
|
|
||||||
/* CommandRunner core */
|
/* CommandRunner core */
|
||||||
|
|
||||||
|
@ -15,20 +16,22 @@ export interface CommandRunnerContext {
|
||||||
exitCode: number | null
|
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 */
|
/* Middlewares as used by the user */
|
||||||
export type CommandRunnerMiddleware = (
|
type _CommandRunnerMiddleware = (
|
||||||
ctx: CommandRunnerContext,
|
ctx: CommandRunnerContext,
|
||||||
next: () => Promise<void>
|
next: () => Promise<void>
|
||||||
) => void | Promise<void>
|
) => void | Promise<void>
|
||||||
|
|
||||||
|
export type CommandRunnerMiddleware = PromisifiedFn<_CommandRunnerMiddleware>
|
||||||
|
|
||||||
/* Command runner events handling and command runner actions */
|
/* 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 */
|
/* Command runner default actions types on which preset middleware exists */
|
||||||
export type CommandRunnerActionType = 'throw' | 'fail' | 'log'
|
export type CommandRunnerActionType = 'throw' | 'fail' | 'log'
|
||||||
|
|
||||||
|
@ -49,3 +52,18 @@ export type CommandRunnerOptions = Omit<
|
||||||
exec.ExecOptions,
|
exec.ExecOptions,
|
||||||
'failOnStdErr' | 'ignoreReturnCode'
|
'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