// @flow import path from 'path'; import semver from 'semver'; import {getCacheRepoDir} from './cacheRepoUtils'; import {fs} from './node'; import {ValidationError} from './ValidationError'; import { disjointVersionsAll as disjointFlowVersionsAll, parseDirString as parseFlowDirString, toSemverString as flowVersionToSemver, type FlowVersion, } from './flowVersion'; import {isExcludedFile} from './fileUtils'; import {TEST_FILE_NAME_RE} from './libDefs'; import {findLatestFileCommitHash} from './git'; type EnvLibDef = { name: string, flowVersion: FlowVersion, path: string, testFilePaths: Array, }; export const getEnvDefs = async (): Promise> => { const definitionsDir = path.join(getCacheRepoDir(), 'definitions'); const envDefsDirPath = path.join(definitionsDir, 'environments'); const dirItems = await fs.readdir(envDefsDirPath); const errors = []; const proms = dirItems.map(async itemName => { if (isExcludedFile(itemName)) return; try { return await getSingleEnvDef(itemName, envDefsDirPath); } catch (e) { errors.push(e); } }); const settled = await Promise.all(proms); if (errors.length) { throw errors; } return [...settled].filter(Boolean).flat(); }; const getSingleEnvDef = async (defName: string, envDefPath: string) => { const itemPath = path.join(envDefPath, defName); const itemStat = await fs.stat(itemPath); if (itemStat.isDirectory()) { // itemPath must be an env dir return await extractEnvDefs(itemPath, defName); } else { throw new ValidationError( `Expected only directories to be present in this directory.`, ); } }; async function extractEnvDefs( envDirPath: string, defName: string, ): Promise> { const envDefFileName = `${defName}.js`; const envDirItems = await fs.readdir(envDirPath); const commonTestFiles = []; const parsedFlowDirs: Array<[string, FlowVersion]> = []; envDirItems.forEach(envDirItem => { const envDirItemPath = path.join(envDirPath, envDirItem); const envDirItemStat = fs.statSync(envDirItemPath); if (envDirItemStat.isFile()) { const isValidTestFile = TEST_FILE_NAME_RE.test(envDirItem); if (isValidTestFile) commonTestFiles.push(envDirItemPath); } else if (envDirItemStat.isDirectory()) { const parsedFlowDir = parseFlowDirString(envDirItem); parsedFlowDirs.push([envDirItemPath, parsedFlowDir]); } else { throw new ValidationError('Unexpected directory item'); } }); if (!disjointFlowVersionsAll(parsedFlowDirs.map(([_, ver]) => ver))) { throw new ValidationError(`Flow versions not disjoint on ${defName}!`); } if (parsedFlowDirs.length === 0) { throw new ValidationError('No libdef files found!'); } const envDefs = []; await Promise.all( parsedFlowDirs.map(async ([flowDirPath, flowVersion]) => { const testFilePaths = [...commonTestFiles]; let libDefFilePath: null | string = null; (await fs.readdir(flowDirPath)).forEach(flowDirItem => { const flowDirItemPath = path.join(flowDirPath, flowDirItem); const flowDirItemStat = fs.statSync(flowDirItemPath); if (flowDirItemStat.isFile()) { if (path.extname(flowDirItem) === '.swp') { return; } // Is this the env def file? if (flowDirItem === envDefFileName) { libDefFilePath = path.join(flowDirPath, flowDirItem); return; } // Is this a test file? const isValidTestFile = TEST_FILE_NAME_RE.test(flowDirItem); if (isValidTestFile) { testFilePaths.push(flowDirItemPath); return; } throw new ValidationError( `Unexpected file: ${envDefFileName}. This directory can only contain test files ` + `or a env def file named \`${envDefFileName}\`.`, ); } else { throw new ValidationError( `Unexpected sub-directory. This directory can only contain test ` + `files or a env def file named \`${envDefFileName}\`.`, ); } }); if (libDefFilePath === null) { libDefFilePath = path.join(flowDirPath, envDefFileName); throw new ValidationError( `No env def file found. Looking for a file named ${envDefFileName}`, ); } envDefs.push({ name: defName, flowVersion, path: libDefFilePath, testFilePaths, }); }), ); return envDefs; } export const findEnvDef = ( defName: string, flowVersion: FlowVersion, useCacheUntil: number, envDefs: Array, ): EnvLibDef | void => { return envDefs.filter(def => { let filterMatch = def.name === defName; if (!filterMatch) { return false; } switch (def.flowVersion.kind) { case 'all': return true; case 'ranged': case 'specific': return semver.satisfies( flowVersionToSemver(flowVersion), flowVersionToSemver(def.flowVersion), ); default: return true; } })[0]; }; export async function getEnvDefVersionHash( repoDirPath: string, libDef: EnvLibDef, ): Promise { const latestCommitHash = await findLatestFileCommitHash( repoDirPath, path.relative(repoDirPath, libDef.path), ); return `${latestCommitHash.substr(0, 10)}/${ libDef.name }/flow_${flowVersionToSemver(libDef.flowVersion)}`; }