fava
Version:
A wannabe tiny largely-drop-in replacement for ava that works in the browser too.
125 lines (97 loc) • 3.87 kB
text/typescript
/* IMPORT */
import {execFileSync} from 'node:child_process';
import delay from 'promise-resolve-timeout';
import color from 'tiny-colors';
import readdir from 'tiny-readdir';
import Watcher from 'watcher';
import zeptomatch from 'zeptomatch';
import Env from './env';
import Utils from './utils';
import type {Options} from './types';
/* MAIN */
const CLI = {
/* API */
execute: async ( globs: string[], options: Options ): Promise<void> => {
//TODO: Implement this better
//TODO: Add support for executing different test files in parallel
const filterGlobs = globs;
const filter = ( targetPath: string ): boolean => zeptomatch ( filterGlobs, targetPath );
const includeGlobs = ['**/test.{js,mjs}', '**/src/test.{js,mjs}', '**/source/test.{js,mjs}', '**/test-*.{js,mjs}', '**/*.spec.{js,mjs}', '**/*.test.{js,mjs}', '**/test/**/*.{js,mjs}', '**/tests/**/*.{js,mjs}', '**/__tests__/**/*.{js,mjs}'];
const include = ( targetPath: string ): boolean => zeptomatch ( includeGlobs, targetPath );
const ignoreGlobs = ['**/.git/**', '**/node_modules/**', '**/dist/**', '**/out/**', '**/_[!_]*', '**/fixtures.{js,mjs}', '**/__tests__/**/__helper__/**/*', '**/__tests__/**/__helpers__/**/*', '**/__tests__/**/__fixture__/**/*', '**/__tests__/**/__fixtures__/**/*', '**/test/**/helper/**/*', '**/test/**/helpers/**/*', '**/test/**/fixture/**/*', '**/test/**/fixtures/**/*', '**/tests/**/helper/**/*', '**/tests/**/helpers/**/*', '**/tests/**/fixture/**/*', '**/tests/**/fixtures/**/*'];
const ignore = ( targetPath: string ): boolean => zeptomatch ( ignoreGlobs, targetPath );
const spawn = ( filePath: string ): boolean => {
try {
execFileSync ( process.execPath, [filePath], {
stdio: ['ignore', 'inherit', 'inherit'],
env: {
...process.env,
FAVA_CLI: '1',
FAVA_FAIL_FAST: options.failFast ? '1' : '0',
FAVA_MATCH: options.match?.length ? options.match.join ( ',' ) : '',
FAVA_TIMEOUT: options.timeout ? String ( options.timeout ) : '0',
FAVA_VERBOSE: options.verbose ? '1' : '0'
}
});
return true;
} catch {
return false;
}
};
const divider = (() => {
let count = 0;
return (): void => {
if ( count ) {
const width = Utils.getStdoutColumns ();
const divider = color.dim ( '-'.repeat ( width ) );
console.log ( divider );
}
count += 1;
};
})();
const execute = ( filePath: string ): void => {
divider ();
const isSuccess = spawn ( filePath );
if ( ( Env.options.failFast || options.failFast ) && !isSuccess && !options.watch ) {
process.exit ( 1 );
}
};
const watch = (): void => {
const watcherOptions = {
native: true,
recursive: true,
ignoreInitial: false,
debounce: 500,
ignore
};
new Watcher ( process.cwd (), watcherOptions, ( event, targetPath ) => onChange ( targetPath ) );
};
const input = (): void => {
process.stdin.on ( 'data', ( data: Buffer ): void => {
if ( data.toString ().trim ().toLowerCase () !== 'rs' ) return;
find ();
});
};
const find = async (): Promise<void> => {
const {files} = await readdir ( process.cwd (), { ignore } );
for ( const file of files ) {
onChange ( file );
await delay ( 0 );
}
};
const onChange = ( targetPath: string ): void => {
if ( !include ( targetPath ) ) return;
if ( globs.length && !filter ( targetPath ) ) return;
execute ( targetPath );
};
if ( options.watch ) {
watch ();
input ();
await Utils.lang.hang ();
} else {
await find ();
}
}
};
/* EXPORT */
export default CLI;