UNPKG

flow-typed

Version:

A repository of high quality flow type definitions

539 lines (421 loc) 17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports._extractLibDefsFromNpmPkgDir = extractLibDefsFromNpmPkgDir; exports._parsePkgNameVer = parsePkgNameVer; exports._validateVersionNumPart = validateVersionNumPart; exports._validateVersionPart = validateVersionPart; exports.findNpmLibDef = findNpmLibDef; exports.getCacheNpmLibDefs = getCacheNpmLibDefs; exports.getInstalledNpmLibDefs = getInstalledNpmLibDefs; exports.getNpmLibDefDirFromNested = void 0; exports.getNpmLibDefVersionHash = getNpmLibDefVersionHash; exports.getNpmLibDefs = getNpmLibDefs; exports.getScopedPackageName = getScopedPackageName; exports.parseSignedCodeVersion = parseSignedCodeVersion; exports.pkgVersionMatch = pkgVersionMatch; var _cacheRepoUtils = require("../cacheRepoUtils"); var _codeSign = require("../codeSign"); var _fileUtils = require("../fileUtils"); var _flowVersion = require("../flowVersion"); var _git = require("../git"); var _node = require("../node"); var _semver = require("../semver"); var _semver2 = _interopRequireDefault(require("semver")); var _got = _interopRequireDefault(require("got")); var _ValidationError = require("../ValidationError"); var _libDefs = require("../libDefs"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const P = Promise; /** * When in a nested directory of npm libdefs such as package/libdef dir * find and return the root npm dir */ const getNpmLibDefDirFromNested = path => { const npmDefsDir = '/npm/'; return path.substring(0, path.indexOf(npmDefsDir) + npmDefsDir.length); }; exports.getNpmLibDefDirFromNested = getNpmLibDefDirFromNested; async function extractLibDefsFromNpmPkgDir(pkgDirPath, scope, pkgNameVer, validating) { const parsedPkgNameVer = parsePkgNameVer(pkgNameVer); if (parsedPkgNameVer === null) { return []; } const { pkgName, pkgVersion } = parsedPkgNameVer; const pkgVersionStr = (0, _semver.versionToString)(pkgVersion); const libDefFileName = `${pkgName}_${pkgVersionStr}.js`; const pkgDirItems = await _node.fs.readdir(pkgDirPath); if (validating) { const fullPkgName = `${scope === null ? '' : scope + '/'}${pkgName}`; try { await _npmExists(fullPkgName); } catch (e) { throw new _ValidationError.ValidationError(`Package \`${fullPkgName}\` does not exist on npm, is this meant to be an environment instead?`); } } const commonTestFiles = []; const parsedFlowDirs = []; pkgDirItems.forEach(pkgDirItem => { const pkgDirItemPath = _node.path.join(pkgDirPath, pkgDirItem); const pkgDirItemStat = _node.fs.statSync(pkgDirItemPath); if (pkgDirItemStat.isFile()) { const isValidTestFile = _libDefs.TEST_FILE_NAME_RE.test(pkgDirItem); if (isValidTestFile) commonTestFiles.push(pkgDirItemPath); } else if (pkgDirItemStat.isDirectory()) { const parsedFlowDir = (0, _flowVersion.parseDirString)(pkgDirItem); parsedFlowDirs.push([pkgDirItemPath, parsedFlowDir]); } else { throw new _ValidationError.ValidationError('Unexpected directory item'); } }); if (!(0, _flowVersion.disjointVersionsAll)(parsedFlowDirs.map(([_, ver]) => ver))) { throw new _ValidationError.ValidationError('Flow versions not disjoint!'); } if (parsedFlowDirs.length === 0) { throw new _ValidationError.ValidationError('No libdef files found!'); } const libDefs = []; await P.all(parsedFlowDirs.map(async ([flowDirPath, flowVersion]) => { const testFilePaths = [].concat(commonTestFiles); let libDefFilePath = null; const depVersions = {}; (await _node.fs.readdir(flowDirPath)).forEach(flowDirItem => { const flowDirItemPath = _node.path.join(flowDirPath, flowDirItem); const flowDirItemStat = _node.fs.statSync(flowDirItemPath); if (flowDirItemStat.isFile()) { if (_node.path.extname(flowDirItem) === '.swp') { return; } // Is this the libdef file? if (flowDirItem === libDefFileName) { libDefFilePath = _node.path.join(flowDirPath, flowDirItem); return; } // Is this a test file? const isValidTestFile = _libDefs.TEST_FILE_NAME_RE.test(flowDirItem); if (isValidTestFile) { testFilePaths.push(flowDirItemPath); return; } // Here we need to look at the deps and add it to the npmLibDef. // Later if installing this libdef // try to install the dependencies if not already installed elsewhere if (flowDirItem === 'config.json') { const deps = JSON.parse(_node.fs.readFileSync(_node.path.join(flowDirPath, flowDirItem), 'utf-8')).deps; if (deps) { Object.keys(deps).forEach(dep => { depVersions[dep] = [...deps[dep]]; }); } return; } throw new _ValidationError.ValidationError(`Unexpected file: ${libDefFileName}. This directory can only contain test files ` + `or a libdef file named ${'`' + libDefFileName + '`'}.`); } else { throw new _ValidationError.ValidationError(`Unexpected sub-directory. This directory can only contain test ` + `files or a libdef file named ${'`' + libDefFileName + '`'}.`); } }); if (libDefFilePath === null) { libDefFilePath = _node.path.join(flowDirPath, libDefFileName); throw new _ValidationError.ValidationError(`No libdef file found. Looking for a file named ${libDefFileName}`); } libDefs.push({ scope, name: pkgName, version: pkgVersionStr, flowVersion, path: libDefFilePath, testFilePaths, depVersions: Object.keys(depVersions).length > 0 ? depVersions : null }); })); return libDefs; } async function getCacheNpmLibDefs(cacheExpiry, skipCache = false) { if (!skipCache) { await (0, _cacheRepoUtils.ensureCacheRepo)(cacheExpiry); } await (0, _cacheRepoUtils.verifyCLIVersion)(); return await getNpmLibDefs(_node.path.join((0, _cacheRepoUtils.getCacheRepoDir)(), 'definitions')); } const PKG_NAMEVER_RE = /^(.*)_v\^?([0-9]+)\.([0-9]+|x)\.([0-9]+|x)(-.*)?$/; const PKG_GIT_RE = /^([\w\-]+)@([\w\.]+):([\w\-]+)\/([\w\-]+)(?:\.git)$/; function parsePkgNameVer(pkgNameVer) { const pkgNameVerMatches = pkgNameVer.match(PKG_NAMEVER_RE); const pkgNameGitMatches = pkgNameVer.match(PKG_GIT_RE); if (pkgNameGitMatches != null) { return { pkgName: pkgNameGitMatches[4], pkgVersion: { major: 0, minor: 0, patch: 0, prerel: '' } }; } if (pkgNameVerMatches == null) { throw new _ValidationError.ValidationError(`Malformed npm package name! ` + `Expected the name to be formatted as <PKGNAME>_v<MAJOR>.<MINOR>.<PATCH> but instead got ${pkgNameVer}`); } const [_, pkgName, majorStr, minorStr, patchStr, prerel] = pkgNameVerMatches; const major = validateVersionNumPart(majorStr, 'major'); const minor = validateVersionPart(minorStr, 'minor'); const patch = validateVersionPart(patchStr, 'patch'); return { pkgName, pkgVersion: { major, minor, patch, prerel: prerel != null ? prerel.substr(1) : prerel } }; } /** * Given a number-or-wildcard part of a version string (i.e. a `minor` or * `patch` part), parse the string into either a number or 'x'. */ function validateVersionPart(part, partName) { if (part === 'x') { return part; } return validateVersionNumPart(part, partName); } /** * Given a number-only part of a version string (i.e. the `major` part), parse * the string into a number. */ function validateVersionNumPart(part, partName) { const num = parseInt(part, 10); if (String(num) !== part) { throw new _ValidationError.ValidationError(`Invalid ${partName} number: '${part}'. Expected a number.`); } return num; } function pkgVersionMatch(pkgSemverRaw, libDefSemverRaw) { var _semver$coerce$versio, _semver$coerce; // The package version should be treated as a semver implicitly prefixed by // `^` or `~`. Depending on whether or not the minor value is defined. // i.e.: "foo_v2.2.x" is the same range as "~2.2.x" // and "foo_v2.x.x" is the same range as "^2.x.x" // UNLESS it is prefixed by the equals character (i.e. "foo_=v2.2.x") const libDefSemver = (() => { const versionSplit = libDefSemverRaw.split('.'); if (libDefSemverRaw[0] !== '=' && libDefSemverRaw[0] !== '^') { if (versionSplit[1] !== 'x') { return '~' + libDefSemverRaw; } return '^' + libDefSemverRaw; } return libDefSemverRaw; })(); const pkgSemver = (_semver$coerce$versio = (_semver$coerce = _semver2.default.coerce(pkgSemverRaw)) === null || _semver$coerce === void 0 ? void 0 : _semver$coerce.version) !== null && _semver$coerce$versio !== void 0 ? _semver$coerce$versio : pkgSemverRaw; if (_semver2.default.valid(pkgSemver)) { // Test the single package version against the LibDef range return _semver2.default.satisfies(pkgSemver, libDefSemver); } if (_semver2.default.valid(libDefSemver)) { // Test the single LibDef version against the package range return _semver2.default.satisfies(libDefSemver, pkgSemver); } if (!(_semver2.default.validRange(pkgSemver) && _semver2.default.validRange(libDefSemver))) { return false; } const pkgRange = new _semver2.default.Range(pkgSemver); const libDefRange = new _semver2.default.Range(libDefSemver); if (libDefRange.set[0].length !== 2) { throw new Error('Invalid npm libdef version! It appears to be a non-contiguous range.'); } const libDefLower = (0, _semver.getRangeLowerBound)(libDefRange); const libDefUpper = (0, _semver.getRangeUpperBound)(libDefRange); const pkgBelowLower = _semver2.default.gtr(libDefLower, pkgSemver); const pkgAboveUpper = _semver2.default.ltr(libDefUpper, pkgSemver); if (pkgBelowLower || pkgAboveUpper) { return false; } const pkgLower = pkgRange.set[0][0].semver.version; return libDefRange.test(pkgLower); } function filterLibDefs(defs, filter) { return defs.filter(def => { let filterMatch = false; switch (filter.type) { case 'exact': const fullName = def.scope ? `${def.scope}/${def.name}` : def.name; filterMatch = filter.pkgName.toLowerCase() === fullName.toLowerCase() && pkgVersionMatch(filter.pkgVersion, def.version); break; default: filter; } if (!filterMatch) { return false; } const filterFlowVersion = filter.flowVersion; if (filterFlowVersion !== undefined) { const { flowVersion } = def; switch (flowVersion.kind) { case 'all': return true; case 'ranged': case 'specific': return _semver2.default.satisfies((0, _flowVersion.toSemverString)(filterFlowVersion), (0, _flowVersion.toSemverString)(def.flowVersion)); default: flowVersion; } } return true; }).sort((a, b) => { const aZeroed = a.version.replace(/x/g, '0'); const bZeroed = b.version.replace(/x/g, '0'); return _semver2.default.gt(aZeroed, bZeroed) ? -1 : 1; }); } async function _npmExists(pkgName) { const pkgUrl = `https://registry.npmjs.org/${encodeURIComponent(pkgName)}`; return (0, _got.default)(pkgUrl, { method: 'HEAD' }); } async function findNpmLibDef(pkgName, pkgVersion, flowVersion, useCacheUntil = _cacheRepoUtils.CACHE_REPO_EXPIRY, skipCache = false, extLibDefs) { const libDefs = extLibDefs !== null && extLibDefs !== void 0 ? extLibDefs : await getCacheNpmLibDefs(useCacheUntil, skipCache); const filteredLibDefs = filterLibDefs(libDefs, { type: 'exact', pkgName, pkgVersion, flowVersion }); return filteredLibDefs.length === 0 ? null : filteredLibDefs[0]; } function parseSignedCodeVersion(signedCodeVer) { if (signedCodeVer === null) { return null; } if (signedCodeVer.startsWith('<<STUB>>/')) { return { kind: 'Stub', name: signedCodeVer.substring('<<STUB>>/'.length) }; } const matches = signedCodeVer.match(/([^\/]+)\/(@[^\/]+\/)?([^\/]+)\/([^\/]+)/); if (matches == null) { return null; } const scope = matches[2] == null ? null : matches[2].substr(0, matches[2].length - 1); const nameVer = matches[3]; if (nameVer === null) { return null; } const pkgNameVer = parsePkgNameVer(nameVer); if (pkgNameVer === null) { return null; } const { pkgName, pkgVersion } = pkgNameVer; const flowVerMatches = matches[4].match(/^flow_(>=|<=)?(v[^ ]+) ?(<=(v.+))?$/); const flowVerStr = flowVerMatches == null ? matches[3] : flowVerMatches[3] == null ? flowVerMatches[2] : `${flowVerMatches[2]}-${flowVerMatches[4]}`; const flowDirStr = `flow_${flowVerStr}`; const flowVer = flowVerMatches == null ? (0, _flowVersion.parseDirString)(flowDirStr) : (0, _flowVersion.parseDirString)(flowDirStr); return { kind: 'LibDef', libDef: { scope, name: pkgName, version: (0, _semver.versionToString)(pkgVersion), flowVersion: flowVer, testFilePaths: [], depVersions: null } }; } async function getInstalledNpmLibDef(flowProjectRootDir, fullFilePath) { const terseFilePath = _node.path.relative(flowProjectRootDir, fullFilePath); const fileStat = await _node.fs.stat(fullFilePath); if (fileStat.isFile()) { const fileContent = await _node.fs.readFile(fullFilePath, 'utf8'); if ((0, _codeSign.verifySignedCode)(fileContent)) { const signedCodeVer = (0, _codeSign.getSignedCodeVersion)(fileContent); if (signedCodeVer === null) { return null; } const parsed = parseSignedCodeVersion(signedCodeVer); if (!parsed) { return null; } return [terseFilePath, parsed.kind === 'LibDef' ? { kind: 'LibDef', libDef: { ...parsed.libDef, path: terseFilePath } } : parsed]; } } } async function getInstalledNpmLibDefs(flowProjectRootDir, libdefDir) { const typedefDir = libdefDir || 'flow-typed'; const libDefDirPath = _node.path.join(flowProjectRootDir, typedefDir, 'npm'); if (!(await _node.fs.exists(libDefDirPath))) return new Map(); const filesInNpmDir = await (0, _fileUtils.getFilesInDir)(libDefDirPath, true); return new Map((await P.all([...filesInNpmDir].map(fileName => getInstalledNpmLibDef(flowProjectRootDir, _node.path.join(libDefDirPath, fileName))))).filter(Boolean)); } /** * Retrieve single libdef. */ async function getSingleLibdef(itemName, npmDefsDirPath, validating) { const itemPath = _node.path.join(npmDefsDirPath, itemName); const itemStat = await _node.fs.stat(itemPath); if (itemStat.isDirectory()) { if (itemName[0] === '@') { // This must be a scoped npm package, so go one directory deeper const scope = itemName; const scopeDirItems = await _node.fs.readdir(itemPath); const settled = await P.all(scopeDirItems.filter(item => !(0, _fileUtils.isExcludedFile)(item)).map(async itemName => { const itemPath = _node.path.join(npmDefsDirPath, scope, itemName); const itemStat = await _node.fs.stat(itemPath); if (itemStat.isDirectory()) { return await extractLibDefsFromNpmPkgDir(itemPath, scope, itemName, validating); } else { throw new _ValidationError.ValidationError(`Expected only sub-directories in this dir!`); } })); return [].concat(...settled); } else { // itemPath must be a package dir return await extractLibDefsFromNpmPkgDir(itemPath, null, // No scope itemName, validating); } } else { throw new _ValidationError.ValidationError(`Expected only directories to be present in this directory.`); } } /** * Retrieve a list of *all* npm libdefs. */ async function getNpmLibDefs(defsDirPath, validating) { const npmDefsDirPath = _node.path.join(defsDirPath, 'npm'); const dirItems = await _node.fs.readdir(npmDefsDirPath); const errors = []; const proms = dirItems.map(async itemName => { if ((0, _fileUtils.isExcludedFile)(itemName)) return; try { return await getSingleLibdef(itemName, npmDefsDirPath, validating); } catch (e) { errors.push(e); } }); const settled = await P.all(proms); if (errors.length) { throw errors; } return [].concat(...settled).filter(Boolean); } async function getNpmLibDefVersionHash(repoDirPath, libDef) { const latestCommitHash = await (0, _git.findLatestFileCommitHash)(repoDirPath, _node.path.relative(repoDirPath, libDef.path)); return `${latestCommitHash.substr(0, 10)}/` + (libDef.scope === null ? '' : `${libDef.scope}/`) + `${libDef.name}_${libDef.version}/` + `flow_${(0, _flowVersion.toSemverString)(libDef.flowVersion)}`; } function getScopedPackageName(libDef) { return (libDef.scope === null ? '' : `${libDef.scope}/`) + `${libDef.name}`; }