UNPKG

flow-typed

Version:

A repository of high quality flow type definitions

450 lines (359 loc) 14.7 kB
"use strict"; 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; } }