flow-typed
Version:
A repository of high quality flow type definitions
539 lines (421 loc) • 17 kB
JavaScript
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}`;
}
;