import type { Transform } from 'node:stream'; import through2 from 'through2'; export type TExecutionMode = 'forEach' | 'forFirst' | 'atEnd'; export type TGulpFunctionEncoding = BufferEncoding | string | null; export type TGulpFunctionResult = PromiseLike | unknown; export interface IPromiseFunction { (file?: TFile | null, enc?: TGulpFunctionEncoding): TGulpFunctionResult; } type TTransformCallback = (errorArg?: Error | null, dataArg?: unknown) => void; type TFlushCallback = (errorArg?: Error | null) => void; const normalizeError = (errorArg: unknown): Error => { return errorArg instanceof Error ? errorArg : new Error(String(errorArg)); }; const defaultExport = ( functionsToExecuteArg: IPromiseFunction | Array>, executionModeArg: TExecutionMode = 'forEach' ): Transform => { const runFunction = async ( functionArg: IPromiseFunction, fileArg: TFile | null, encArg: TGulpFunctionEncoding ): Promise => { await functionArg(fileArg, encArg); }; const checkAndRunFunction = async ( fileArg: TFile | null, encArg: TGulpFunctionEncoding ): Promise => { if (typeof functionsToExecuteArg === 'function') { await runFunction(functionsToExecuteArg, fileArg, encArg); } else if (Array.isArray(functionsToExecuteArg)) { await Promise.all( functionsToExecuteArg.map(async (functionArg) => runFunction(functionArg, fileArg, encArg)) ); } else { throw new Error('gulp-callfunction: something is strange with the given arguments'); } }; let hasExecutedOnce = false; const transformFunction = (fileArg: TFile, encArg: BufferEncoding, cbArg: TTransformCallback) => { switch (executionModeArg) { case 'forEach': checkAndRunFunction(fileArg, encArg).then( () => cbArg(null, fileArg), (errorArg) => cbArg(normalizeError(errorArg)) ); break; case 'forFirst': if (!hasExecutedOnce) { hasExecutedOnce = true; checkAndRunFunction(fileArg, encArg).then( () => cbArg(null, fileArg), (errorArg) => cbArg(normalizeError(errorArg)) ); } else { cbArg(null, fileArg); } break; case 'atEnd': cbArg(null, fileArg); break; default: cbArg(null, fileArg); break; } }; const flushFunction = (cbArg: TFlushCallback) => { if (executionModeArg === 'atEnd') { checkAndRunFunction(null, null).then( () => cbArg(), (errorArg) => cbArg(normalizeError(errorArg)) ); } else { cbArg(); } }; return through2.obj(transformFunction, flushFunction) as Transform; }; export const forEach = (funcArg: IPromiseFunction): Transform => { return defaultExport(funcArg, 'forEach'); }; export const forFirst = (funcArg: IPromiseFunction): Transform => { return defaultExport(funcArg, 'forFirst'); }; export const atEnd = (funcArg: IPromiseFunction): Transform => { return defaultExport(funcArg, 'atEnd'); }; export default defaultExport;