UNPKG

@knapsack-pro/vitest

Version:

Knapsack Pro Vitest splits Vitest tests across CI nodes and makes sure that tests will run in optimal time on each CI node.

106 lines (105 loc) 4.51 kB
#!/usr/bin/env node import { KnapsackProCore, KnapsackProLogger, } from '@knapsack-pro/core'; import { startVitest, parseCLI, createVitest, } from 'vitest/node'; import { v4 as uuidv4 } from 'uuid'; import { minimatch } from 'minimatch'; import { glob } from 'glob'; import { join, dirname } from 'path'; import fs from 'fs'; import { fileURLToPath } from 'url'; import * as Urls from './urls.js'; if (process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN_VITEST) { process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN = process.env.KNAPSACK_PRO_TEST_SUITE_TOKEN_VITEST; } const knapsackProLogger = new KnapsackProLogger(); const projectPath = process.cwd(); async function main() { const filePath = fileURLToPath(import.meta.url); const dirName = dirname(filePath); const pkg = JSON.parse(fs.readFileSync(join(dirName, '..', 'package.json'), 'utf8')); knapsackProLogger.debug(`Running ${pkg.name}@${pkg.version}`); const resolvedConfig = await getResolvedConfig(); const knapsackPro = new KnapsackProCore(pkg.name, pkg.version, makeGetAllTestFiles(resolvedConfig)); const onSuccess = async (testFiles) => { const vitest = await startVitest('test', undefined, { include: testFiles.map((testFile) => testFile.path), watch: false, ...generateCoverageConfig(resolvedConfig), }); if (!vitest) { throw new Error('[@knapsack-pro/vitest] Vitest failed to start'); } await vitest.close(); return getTestResults(vitest); }; const onError = () => { }; knapsackPro.runQueueMode(onSuccess, onError); } main(); function getTestResults(vitest) { const recordedTestFiles = vitest.state.getFiles().map((file) => { const testFile = vitest.state.getReportedEntity(file); if (testFile?.type !== 'module') { throw new Error('[@knapsack-pro/vitest] Vitest reported non-module file'); } const path = process.platform === 'win32' ? file.filepath.replace(`${projectPath}\\`, '').replace(/\\/g, '/') : file.filepath.replace(`${projectPath}/`, ''); const diagnostic = testFile.diagnostic(); return { path, time_execution: diagnostic.duration / 1000, }; }); const isTestSuiteGreen = process.exitCode !== 1; return { recordedTestFiles, isTestSuiteGreen, }; } async function getResolvedConfig() { const cliArguments = parseCLI(`vitest ${process.argv.slice(2)}`); knapsackProLogger.debug(`Vitest CLI options:\n${KnapsackProLogger.objectInspect(cliArguments)}`); if (cliArguments.filter.length > 0) { throw new Error('[@knapsack-pro/vitest] Passing list of files to test from command line is not supported'); } const resolvedConfig = (await createVitest('test', { ...cliArguments.options, watch: false })).config; return resolvedConfig; } function makeGetAllTestFiles(resolvedConfig) { return () => { const testFileIncludePattern = process.env.KNAPSACK_PRO_TEST_FILE_PATTERN || resolvedConfig.include || '**/*.{test,spec}.?(c|m)[jt]s?(x)'; const testFileExcludePattern = process.env .KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN ? [process.env.KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN] : resolvedConfig.exclude || []; const testFiles = glob .sync(testFileIncludePattern) .filter((testFilePath) => testFileExcludePattern.every((excludePattern) => !minimatch(testFilePath, excludePattern, { matchBase: true, }))) .filter((testFilePath) => !testFilePath.match(/node_modules/)) .map((testFilePath) => ({ path: testFilePath })); if (testFiles.length === 0) { const errorMessage = `[@knapsack-pro/vitest] Test files cannot be found.\nPlease set KNAPSACK_PRO_TEST_FILE_PATTERN matching your test directory structure.\nLearn more: ${Urls.NO_TESTS_FOUND}`; knapsackProLogger.error(errorMessage); throw new Error(errorMessage); } return testFiles; }; } function generateCoverageConfig(resolvedConfig) { const coverageDirectory = process.env.KNAPSACK_PRO_COVERAGE_DIRECTORY || resolvedConfig.coverage.reportsDirectory; if (resolvedConfig.coverage.enabled) { return { coverage: { reportsDirectory: join(coverageDirectory, uuidv4()), }, }; } return {}; }