// @flow import {mkdirp} from './fileUtils'; import {cloneInto, rebaseRepoMainline} from './git'; import {fs, os, path} from './node'; import semver from 'semver'; export const CACHE_REPO_EXPIRY = 1000 * 60; // 1 minute const REMOTE_REPO_URL = 'https://github.com/flowtype/flow-typed.git'; async function cloneCacheRepo() { await mkdirp(getCacheRepoDir()); try { await cloneInto(REMOTE_REPO_URL, getCacheRepoDir()); } catch (e) { console.error('ERROR: Unable to clone local cache repo!'); throw e; } await fs.writeFile(getLastUpdatedFile(), String(Date.now())); } let customCacheDir = null; function getCacheDir() { return customCacheDir === null ? path.join(os.homedir(), '.flow-typed') : customCacheDir; } function clearCustomCacheDir(): void { customCacheDir = null; } function setCustomCacheDir(dir: string): void { customCacheDir = dir; } function getCacheRepoGitDir(): string { return path.join(getCacheRepoDir(), '.git'); } function getLastUpdatedFile(): string { return path.join(getCacheRepoDir(), 'lastUpdated'); } async function rebaseCacheRepo() { if ( (await fs.exists(getCacheRepoDir())) && (await fs.exists(getCacheRepoGitDir())) ) { try { await rebaseRepoMainline(getCacheRepoDir()); } catch (e) { console.error( 'ERROR: Unable to rebase the local cache repo. ' + e.message, ); return false; } await fs.writeFile(getLastUpdatedFile(), String(Date.now())); return true; } else { await cloneCacheRepo(); return true; } } /** * Ensure that the CACHE_REPO_DIR exists and is recently rebased. * (else: create/rebase it) */ const cacheRepoEnsureToken: { lastEnsured: number, pendingEnsurance: Promise<*>, } = { lastEnsured: 0, pendingEnsurance: Promise.resolve(), }; export async function ensureCacheRepo( cacheRepoExpiry: number = CACHE_REPO_EXPIRY, ): Promise { // Only re-run rebase checks if a check hasn't been run in the last 5 minutes if (cacheRepoEnsureToken.lastEnsured + 5 * 1000 * 60 >= Date.now()) { return cacheRepoEnsureToken.pendingEnsurance; } cacheRepoEnsureToken.lastEnsured = Date.now(); const prevEnsurance = cacheRepoEnsureToken.pendingEnsurance; return (cacheRepoEnsureToken.pendingEnsurance = prevEnsurance.then(() => (async function() { const repoDirExists = fs.exists(getCacheRepoDir()); const repoGitDirExists = fs.exists(getCacheRepoGitDir()); if (!(await repoDirExists) || !(await repoGitDirExists)) { console.log(`• flow-typed cache not found, fetching from GitHub...`); await cloneCacheRepo(); } else { let lastUpdated = 0; if (await fs.exists(getLastUpdatedFile())) { // If the LAST_UPDATED_FILE has anything other than just a number in // it, just assume we need to update. const lastUpdatedRaw = await fs.readFile(getLastUpdatedFile()); const lastUpdatedNum = parseInt(lastUpdatedRaw, 10); if (String(lastUpdatedNum) === String(lastUpdatedRaw)) { lastUpdated = lastUpdatedNum; } } if (lastUpdated + cacheRepoExpiry < Date.now()) { console.log('• rebasing flow-typed cache...'); const rebaseSuccessful = await rebaseCacheRepo(); if (!rebaseSuccessful) { console.log( "\nNOTE: Unable to rebase local cache! If you don't currently " + "have internet connectivity, no worries -- we'll update the " + 'local cache the next time you do.\n', ); } } } })(), )); } export function getCacheRepoDir(): string { return path.join(getCacheDir(), 'repo'); } export async function verifyCLIVersion(): Promise { const metadataPath = path.join( getCacheRepoDir(), 'definitions', '.cli-metadata.json', ); const metadata = await fs.readJson(metadataPath); const compatibleCLIRange = metadata.compatibleCLIRange; if (!compatibleCLIRange) { throw new Error( `Unable to find the 'compatibleCLIRange' property in ${metadataPath}. ` + `You might need to update your flow-typed CLI to the latest version.`, ); } const thisCLIPkgJsonPath = path.join(__dirname, '..', '..', 'package.json'); const thisCLIPkgJson = await fs.readJson(thisCLIPkgJsonPath); const thisCLIVersion = thisCLIPkgJson.version; if ( !semver.satisfies( semver.coerce(thisCLIVersion) ?? thisCLIVersion, compatibleCLIRange, ) ) { throw new Error( `Please upgrade your flow-typed CLI! This CLI is version ` + `${thisCLIVersion}, but the latest flow-typed definitions are only ` + `compatible with flow-typed@${compatibleCLIRange}`, ); } } export { CACHE_REPO_EXPIRY as _CACHE_REPO_EXPIRY, cacheRepoEnsureToken as _cacheRepoEnsureToken, clearCustomCacheDir as _clearCustomCacheDir, getCacheRepoGitDir as _getCacheRepoGitDir, getLastUpdatedFile as _getLastUpdatedFile, REMOTE_REPO_URL as _REMOTE_REPO_URL, setCustomCacheDir as _setCustomCacheDir, };