flow-typed
Version:
A repository of high quality flow type definitions
450 lines (359 loc) • 14.7 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.createStub = createStub;
exports.glob = glob;
exports.pkgHasFlowFiles = pkgHasFlowFiles;
var _safe = _interopRequireDefault(require("colors/safe"));
var _got = _interopRequireDefault(require("got"));
var _flowgen = _interopRequireDefault(require("flowgen"));
var _prettier = _interopRequireDefault(require("prettier"));
var _npmProjectUtils = require("./npm/npmProjectUtils");
var _util = require("util");
var _node = require("./node");
var _glob = _interopRequireDefault(require("glob"));
var _fileUtils = require("./fileUtils");
var _codeSign = require("./codeSign");
var _semver = require("./semver");
var _logger = require("./logger");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function glob(pattern, options) {
return new Promise((resolve, reject) => (0, _glob.default)(pattern, options, (err, files) => {
if (err) {
reject(err);
} else {
resolve(files);
}
}));
}
async function resolvePkgDirPath(pkgName, pkgJsonDirPath, pnpjs) {
if (pnpjs != null) {
const pnpResolvedDirPath = pnpjs.resolveToUnqualified(pkgName, pkgJsonDirPath);
if (pnpResolvedDirPath != null) {
return pnpResolvedDirPath;
}
}
let prevNodeModulesDirPath;
let nodeModulesDirPath = _node.path.resolve(pkgJsonDirPath, 'node_modules');
while (true) {
const pkgDirPath = _node.path.resolve(nodeModulesDirPath, pkgName);
if (await _node.fs.exists(pkgDirPath)) {
return pkgDirPath;
}
prevNodeModulesDirPath = nodeModulesDirPath;
nodeModulesDirPath = _node.path.resolve(nodeModulesDirPath, '..', '..', 'node_modules');
if (prevNodeModulesDirPath === nodeModulesDirPath) {
break;
}
}
throw new Error('Unable to find `' + pkgName + '/` install directory! ' + 'Did you forget to run `npm install` before running `flow-typed install`?');
}
const moduleStubTemplate = `
declare module '%s' {
declare module.exports: any;
}`.trim();
const aliasTemplate = `
declare module '%s%s' {
declare module.exports: $Exports<'%s'>;
}`.trim();
const guessedModuleStubTemplate = `
declare module '%s' {
declare module.exports: %s;
}`.trim();
function stubFor(moduleName, fileExt) {
const moduleStub = (0, _util.format)(moduleStubTemplate, moduleName);
if (fileExt !== undefined) {
const aliasStub = (0, _util.format)(aliasTemplate, moduleName, fileExt, moduleName);
return `${moduleStub}\n${aliasStub}`;
}
return moduleStub;
}
const functionTemplate = '(%s) => any';
const spaceByI = i => ' '.repeat(i);
const keyTypeTemplate = ` %s: %s,\n`;
function objectToTypedTemplate(obj, currentDepth, maxDepth, functionHeader) {
const thisDepth = currentDepth + 1;
let formatedEntries = [];
if (functionHeader) {
formatedEntries.push(spaceByI(thisDepth + 1) + functionHeader);
}
for (let entrie of Object.entries(obj)) {
const toTypeResult = objectToType(entrie[1], maxDepth, thisDepth, thisDepth <= maxDepth);
formatedEntries.push((0, _util.format)(`${spaceByI(thisDepth)}${keyTypeTemplate}`, entrie[0], toTypeResult));
}
if (formatedEntries.length > 0) {
return `{\n${formatedEntries.join('')}${spaceByI(thisDepth)}}`;
} else {
return '{}';
}
}
function guessFunctionArguments(fun) {
let raw = fun.toString();
let args = raw.slice(raw.indexOf('(') + 1, raw.indexOf(')'));
args = args.replace(/ /g, '');
if (args.length > 0) {
args = args.split(',').map(el => `${el}: any`).join(', ');
}
return (0, _util.format)(functionTemplate, args);
}
function functionToType(fun, currentDepth, maxDepth) {
let output = guessFunctionArguments(fun);
let functionEntries = Object.entries(fun);
if (functionEntries.length > 0) {
return objectToTypedTemplate(fun, currentDepth, maxDepth, output.length > 0 ? output + ',\n' : undefined);
}
return output;
}
function objectToType(obj, maxDepth, currentDepth = 0, deep = true) {
if (obj === null) return 'null'; // Every function that depends on objectToTypedTemplate need to check deep first
if (deep) {
if (typeof obj === 'object') return objectToTypedTemplate(obj, currentDepth, maxDepth);
if (typeof obj === 'function') return functionToType(obj, currentDepth, maxDepth);
}
if (typeof obj === 'object') return 'any';
if (typeof obj === 'function') return guessFunctionArguments(obj);
return typeof obj;
}
function guessedStubFor(moduleName, packagePath, maxDepth = 1) {
const module = require(packagePath);
const formattedTemplate = (0, _util.format)(guessedModuleStubTemplate, moduleName, objectToType(module, maxDepth));
return formattedTemplate;
}
async function writeStub(projectRoot, packageName, packageVersion, packageFolder, overwrite, files, libdefDir, maxDepth, typescriptTypingsPath, typescriptTypingsContent) {
let flowgenOutput = null;
const flowgenTemplate = `
declare module '%s' {
%s
}
`.trim();
if (typescriptTypingsPath) {
const code = _flowgen.default.compiler.compileDefinitionFile(typescriptTypingsPath);
try {
flowgenOutput = _prettier.default.format((0, _util.format)(flowgenTemplate, packageName, code), {
parser: 'babel-flow',
singleQuote: true,
semi: true
});
} catch (e) {
if (e.message.includes('`declare module` cannot be used inside another `declare module`')) {
flowgenOutput = _prettier.default.format(code, {
parser: 'babel-flow',
singleQuote: true,
semi: true
});
} else {
flowgenOutput = (0, _util.format)(flowgenTemplate, packageName, code);
}
}
} else if (typescriptTypingsContent) {
const code = _flowgen.default.compiler.compileDefinitionString(typescriptTypingsContent);
try {
flowgenOutput = _prettier.default.format((0, _util.format)(flowgenTemplate, packageName, code), {
parser: 'babel-flow',
singleQuote: true,
semi: true
});
} catch (e) {
if (e.message.includes('`declare module` cannot be used inside another `declare module`')) {
flowgenOutput = _prettier.default.format(code, {
parser: 'babel-flow',
singleQuote: true,
semi: true
});
} else {
flowgenOutput = (0, _util.format)(flowgenTemplate, packageName, code);
}
}
}
let output = ['/**', ' * This is an autogenerated libdef stub for:', ' *', ` * '${packageName}'`, ' *', ' * Fill this stub out by replacing all the `any` types.', ' *', ' * Once filled out, we encourage you to share your work with the', ' * community by sending a pull request to:', ' * https://github.com/flowtype/flow-typed', ' */\n\n'].join('\n');
if (packageFolder !== null && false) {
try {
output += guessedStubFor(packageName, packageFolder, maxDepth);
} catch (e) {
output += stubFor(packageName);
}
} else if (flowgenOutput) {
output += flowgenOutput;
} else {
output += stubFor(packageName);
}
if (files.length > 0) {
output += `
/**
* We include stubs for each file inside this npm package in case you need to
* require those files directly. Feel free to delete any files that aren't
* needed.
*/
`;
const [fileDecls, aliases] = files.reduce(([fileDecls, aliases], file) => {
const ext = _node.path.extname(file);
const name = file.substr(0, file.length - ext.length);
const moduleName = `${packageName}/${name}`;
if (name === 'index') {
aliases.push((0, _util.format)(aliasTemplate, moduleName, '', packageName));
aliases.push((0, _util.format)(aliasTemplate, moduleName, ext, packageName));
} else if (_node.path.basename(name) === 'index') {
const dirModuleName = packageName + '/' + _node.path.dirname(file);
fileDecls.push((0, _util.format)(moduleStubTemplate, dirModuleName));
aliases.push((0, _util.format)(aliasTemplate, moduleName, '', dirModuleName));
aliases.push((0, _util.format)(aliasTemplate, moduleName, ext, dirModuleName));
} else {
fileDecls.push((0, _util.format)(moduleStubTemplate, moduleName));
aliases.push((0, _util.format)(aliasTemplate, moduleName, ext, moduleName));
}
return [fileDecls, aliases];
}, [[], []]);
output += fileDecls.join('\n\n');
output += '\n\n// Filename aliases\n';
output += aliases.join('\n');
}
output += '\n'; // File should end with a newline
const filename = _node.path.join(projectRoot, libdefDir, 'npm', (0, _util.format)('%s_vx.x.x.js', packageName));
await (0, _fileUtils.mkdirp)(_node.path.dirname(filename));
if (!overwrite) {
const exists = await _node.fs.exists(filename);
if (exists) {
const existingStub = await _node.fs.readFile(filename, 'utf8');
if (!(0, _codeSign.verifySignedCode)(existingStub)) {
throw new Error('Stub already exists and has been modified. ' + 'Use --overwrite to overwrite');
}
}
}
const flowVersionRaw = await (0, _npmProjectUtils.determineFlowVersion)(projectRoot);
const flowVersion = flowVersionRaw ? `/flow_${(0, _semver.versionToString)(flowVersionRaw)}` : '';
const stubVersion = `<<STUB>>/${packageName}_v${packageVersion}${flowVersion}`;
await _node.fs.writeFile(filename, (0, _codeSign.signCode)(output, stubVersion));
return filename;
}
/**
* Look across a project root node_modules as well as
* each workspace's node_modules to find the dependency
* to determine if it is flow typed.
*/
async function pkgHasFlowFiles(projectRoot, packageName, pnpjs, workspacesPkgJsonData) {
const findTypedFiles = async path => {
try {
let pathToPackage = await resolvePkgDirPath(packageName, path, pnpjs);
const typedFiles = await glob('**/*.flow', {
cwd: pathToPackage,
ignore: 'node_modules/**'
});
if (typedFiles.length > 0) {
return pathToPackage;
}
} catch (e) {
return undefined;
}
};
const rootTypedPath = await findTypedFiles(projectRoot);
if (rootTypedPath) {
return {
isFlowTyped: true,
pkgPath: rootTypedPath
};
}
const typedWorkspacePaths = await Promise.all(workspacesPkgJsonData.map(async pkgJson => findTypedFiles(_node.path.dirname(pkgJson.pathStr))));
const workspacePath = typedWorkspacePaths.find(o => !!o);
if (workspacePath) {
return {
isFlowTyped: true,
pkgPath: workspacePath
};
}
return {
isFlowTyped: false
};
}
async function getDefinitelyTypedPackage(packageName, explicitVersion) {
const parts = packageName.split('/');
const typesPackageName = packageName.startsWith('@') ? parts[0].slice(1) + '__' + parts[1] : packageName;
const version = explicitVersion ? `@${explicitVersion}` : '';
const typing = `https://unpkg.com/@types/${typesPackageName}${version}/index.d.ts`;
return (0, _got.default)(typing, {
method: 'GET'
});
}
/**
* createStub("/path/to/root", "foo") will create a file
* /path/to/root/flow-typed/npm/foo.js that contains a stub for the module foo.
*
* If foo is installed, it will read the directory that require("foo") resolves
* to and include definitions for "foo/FILE", for every FILE in the foo package
*/
async function createStub(projectRoot, packageName, explicitVersion, overwrite, pnpjs, typescript, libdefDir, maxDepth) {
let files = [];
let resolutionError = null;
let pathToPackage = null;
let version = explicitVersion || null;
let typescriptTypingsPath = null;
let typescriptTypingsContent = null;
const typedefDir = libdefDir || 'flow-typed';
try {
pathToPackage = await resolvePkgDirPath(packageName, projectRoot, pnpjs);
files = await glob('**/*.{js,jsx}', {
cwd: pathToPackage,
ignore: 'node_modules/**'
});
} catch (e) {
resolutionError = e;
} // Try to deduce a version if one isn't provided
if (version == null) {
// Look at the package.json for the installed module
if (pathToPackage != null) {
try {
version = require(_node.path.join(pathToPackage, 'package.json')).version;
} catch (e) {}
}
}
if (typescript) {
if (typescriptTypingsPath == null) {
// Look at the package.json for the installed module
if (pathToPackage != null) {
try {
const pkg = require(_node.path.join(pathToPackage, 'package.json'));
const typing = pkg.typings || pkg.types;
if (typing) typescriptTypingsPath = _node.path.join(pathToPackage, typing);
} catch (e) {}
}
} // If that failed, try looking for index.d.ts file
if (typescriptTypingsPath == null) {
// Look at the package.json for the installed module
if (pathToPackage != null) {
const typing = _node.path.join(pathToPackage, 'index.d.ts');
if (await _node.fs.exists(typing)) {
typescriptTypingsPath = typing;
}
}
}
if (typescriptTypingsPath == null) {
try {
const response = await getDefinitelyTypedPackage(packageName, explicitVersion);
typescriptTypingsContent = response.body;
} catch (e) {
console.log(_safe.default.red("❌\t%s%s': %s"), packageName, version ? '@' + version : '', e.message);
return false;
}
}
} // If that failed, try looking for a package.json in the root
if (version == null) {
try {
const pkgJsonPathStr = await (0, _npmProjectUtils.findPackageJsonPath)(projectRoot);
const pkgJsonData = await (0, _npmProjectUtils.getPackageJsonData)(pkgJsonPathStr);
const rootDependencies = await (0, _npmProjectUtils.getPackageJsonDependencies)(pkgJsonData, [], []);
version = rootDependencies[packageName] || null;
} catch (e) {}
}
try {
if (version === null) {
throw new Error('Could not deduce version from node_modules or package.json. ' + 'Please provide an explicit version');
}
const filename = await writeStub(projectRoot, packageName, version, pathToPackage, overwrite, files, typedefDir, maxDepth, typescriptTypingsPath, typescriptTypingsContent);
const terseFilename = _node.path.relative(projectRoot, filename);
(0, _logger.listItem)(`${packageName}@${version}`, _safe.default.red(terseFilename), resolutionError ? _safe.default.yellow(`Unable to stub all files in ${packageName}, so only created a stub for the main module (${resolutionError.message})`) : undefined);
return true;
} catch (e) {
console.log(_safe.default.red("❌\t%s%s': %s"), packageName, version ? '@' + version : '', e.message);
return false;
}
}
;