UNPKG

14.7 kBJavaScriptView Raw
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6exports.createStub = createStub;
7exports.glob = glob;
8exports.pkgHasFlowFiles = pkgHasFlowFiles;
9
10var _safe = _interopRequireDefault(require("colors/safe"));
11
12var _got = _interopRequireDefault(require("got"));
13
14var _flowgen = _interopRequireDefault(require("flowgen"));
15
16var _prettier = _interopRequireDefault(require("prettier"));
17
18var _npmProjectUtils = require("./npm/npmProjectUtils");
19
20var _util = require("util");
21
22var _node = require("./node");
23
24var _glob = _interopRequireDefault(require("glob"));
25
26var _fileUtils = require("./fileUtils");
27
28var _codeSign = require("./codeSign");
29
30var _semver = require("./semver");
31
32var _logger = require("./logger");
33
34function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
35
36function glob(pattern, options) {
37 return new Promise((resolve, reject) => (0, _glob.default)(pattern, options, (err, files) => {
38 if (err) {
39 reject(err);
40 } else {
41 resolve(files);
42 }
43 }));
44}
45
46async function resolvePkgDirPath(pkgName, pkgJsonDirPath, pnpjs) {
47 if (pnpjs != null) {
48 const pnpResolvedDirPath = pnpjs.resolveToUnqualified(pkgName, pkgJsonDirPath);
49
50 if (pnpResolvedDirPath != null) {
51 return pnpResolvedDirPath;
52 }
53 }
54
55 let prevNodeModulesDirPath;
56
57 let nodeModulesDirPath = _node.path.resolve(pkgJsonDirPath, 'node_modules');
58
59 while (true) {
60 const pkgDirPath = _node.path.resolve(nodeModulesDirPath, pkgName);
61
62 if (await _node.fs.exists(pkgDirPath)) {
63 return pkgDirPath;
64 }
65
66 prevNodeModulesDirPath = nodeModulesDirPath;
67 nodeModulesDirPath = _node.path.resolve(nodeModulesDirPath, '..', '..', 'node_modules');
68
69 if (prevNodeModulesDirPath === nodeModulesDirPath) {
70 break;
71 }
72 }
73
74 throw new Error('Unable to find `' + pkgName + '/` install directory! ' + 'Did you forget to run `npm install` before running `flow-typed install`?');
75}
76
77const moduleStubTemplate = `
78declare module '%s' {
79 declare module.exports: any;
80}`.trim();
81const aliasTemplate = `
82declare module '%s%s' {
83 declare module.exports: $Exports<'%s'>;
84}`.trim();
85const guessedModuleStubTemplate = `
86declare module '%s' {
87 declare module.exports: %s;
88}`.trim();
89
90function stubFor(moduleName, fileExt) {
91 const moduleStub = (0, _util.format)(moduleStubTemplate, moduleName);
92
93 if (fileExt !== undefined) {
94 const aliasStub = (0, _util.format)(aliasTemplate, moduleName, fileExt, moduleName);
95 return `${moduleStub}\n${aliasStub}`;
96 }
97
98 return moduleStub;
99}
100
101const functionTemplate = '(%s) => any';
102
103const spaceByI = i => ' '.repeat(i);
104
105const keyTypeTemplate = ` %s: %s,\n`;
106
107function objectToTypedTemplate(obj, currentDepth, maxDepth, functionHeader) {
108 const thisDepth = currentDepth + 1;
109 let formatedEntries = [];
110
111 if (functionHeader) {
112 formatedEntries.push(spaceByI(thisDepth + 1) + functionHeader);
113 }
114
115 for (let entrie of Object.entries(obj)) {
116 const toTypeResult = objectToType(entrie[1], maxDepth, thisDepth, thisDepth <= maxDepth);
117 formatedEntries.push((0, _util.format)(`${spaceByI(thisDepth)}${keyTypeTemplate}`, entrie[0], toTypeResult));
118 }
119
120 if (formatedEntries.length > 0) {
121 return `{\n${formatedEntries.join('')}${spaceByI(thisDepth)}}`;
122 } else {
123 return '{}';
124 }
125}
126
127function guessFunctionArguments(fun) {
128 let raw = fun.toString();
129 let args = raw.slice(raw.indexOf('(') + 1, raw.indexOf(')'));
130 args = args.replace(/ /g, '');
131
132 if (args.length > 0) {
133 args = args.split(',').map(el => `${el}: any`).join(', ');
134 }
135
136 return (0, _util.format)(functionTemplate, args);
137}
138
139function functionToType(fun, currentDepth, maxDepth) {
140 let output = guessFunctionArguments(fun);
141 let functionEntries = Object.entries(fun);
142
143 if (functionEntries.length > 0) {
144 return objectToTypedTemplate(fun, currentDepth, maxDepth, output.length > 0 ? output + ',\n' : undefined);
145 }
146
147 return output;
148}
149
150function objectToType(obj, maxDepth, currentDepth = 0, deep = true) {
151 if (obj === null) return 'null'; // Every function that depends on objectToTypedTemplate need to check deep first
152
153 if (deep) {
154 if (typeof obj === 'object') return objectToTypedTemplate(obj, currentDepth, maxDepth);
155 if (typeof obj === 'function') return functionToType(obj, currentDepth, maxDepth);
156 }
157
158 if (typeof obj === 'object') return 'any';
159 if (typeof obj === 'function') return guessFunctionArguments(obj);
160 return typeof obj;
161}
162
163function guessedStubFor(moduleName, packagePath, maxDepth = 1) {
164 const module = require(packagePath);
165
166 const formattedTemplate = (0, _util.format)(guessedModuleStubTemplate, moduleName, objectToType(module, maxDepth));
167 return formattedTemplate;
168}
169
170async function writeStub(projectRoot, packageName, packageVersion, packageFolder, overwrite, files, libdefDir, maxDepth, typescriptTypingsPath, typescriptTypingsContent) {
171 let flowgenOutput = null;
172 const flowgenTemplate = `
173declare module '%s' {
174 %s
175}
176`.trim();
177
178 if (typescriptTypingsPath) {
179 const code = _flowgen.default.compiler.compileDefinitionFile(typescriptTypingsPath);
180
181 try {
182 flowgenOutput = _prettier.default.format((0, _util.format)(flowgenTemplate, packageName, code), {
183 parser: 'babel-flow',
184 singleQuote: true,
185 semi: true
186 });
187 } catch (e) {
188 if (e.message.includes('`declare module` cannot be used inside another `declare module`')) {
189 flowgenOutput = _prettier.default.format(code, {
190 parser: 'babel-flow',
191 singleQuote: true,
192 semi: true
193 });
194 } else {
195 flowgenOutput = (0, _util.format)(flowgenTemplate, packageName, code);
196 }
197 }
198 } else if (typescriptTypingsContent) {
199 const code = _flowgen.default.compiler.compileDefinitionString(typescriptTypingsContent);
200
201 try {
202 flowgenOutput = _prettier.default.format((0, _util.format)(flowgenTemplate, packageName, code), {
203 parser: 'babel-flow',
204 singleQuote: true,
205 semi: true
206 });
207 } catch (e) {
208 if (e.message.includes('`declare module` cannot be used inside another `declare module`')) {
209 flowgenOutput = _prettier.default.format(code, {
210 parser: 'babel-flow',
211 singleQuote: true,
212 semi: true
213 });
214 } else {
215 flowgenOutput = (0, _util.format)(flowgenTemplate, packageName, code);
216 }
217 }
218 }
219
220 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');
221
222 if (packageFolder !== null && false) {
223 try {
224 output += guessedStubFor(packageName, packageFolder, maxDepth);
225 } catch (e) {
226 output += stubFor(packageName);
227 }
228 } else if (flowgenOutput) {
229 output += flowgenOutput;
230 } else {
231 output += stubFor(packageName);
232 }
233
234 if (files.length > 0) {
235 output += `
236
237/**
238 * We include stubs for each file inside this npm package in case you need to
239 * require those files directly. Feel free to delete any files that aren't
240 * needed.
241 */
242`;
243 const [fileDecls, aliases] = files.reduce(([fileDecls, aliases], file) => {
244 const ext = _node.path.extname(file);
245
246 const name = file.substr(0, file.length - ext.length);
247 const moduleName = `${packageName}/${name}`;
248
249 if (name === 'index') {
250 aliases.push((0, _util.format)(aliasTemplate, moduleName, '', packageName));
251 aliases.push((0, _util.format)(aliasTemplate, moduleName, ext, packageName));
252 } else if (_node.path.basename(name) === 'index') {
253 const dirModuleName = packageName + '/' + _node.path.dirname(file);
254
255 fileDecls.push((0, _util.format)(moduleStubTemplate, dirModuleName));
256 aliases.push((0, _util.format)(aliasTemplate, moduleName, '', dirModuleName));
257 aliases.push((0, _util.format)(aliasTemplate, moduleName, ext, dirModuleName));
258 } else {
259 fileDecls.push((0, _util.format)(moduleStubTemplate, moduleName));
260 aliases.push((0, _util.format)(aliasTemplate, moduleName, ext, moduleName));
261 }
262
263 return [fileDecls, aliases];
264 }, [[], []]);
265 output += fileDecls.join('\n\n');
266 output += '\n\n// Filename aliases\n';
267 output += aliases.join('\n');
268 }
269
270 output += '\n'; // File should end with a newline
271
272 const filename = _node.path.join(projectRoot, libdefDir, 'npm', (0, _util.format)('%s_vx.x.x.js', packageName));
273
274 await (0, _fileUtils.mkdirp)(_node.path.dirname(filename));
275
276 if (!overwrite) {
277 const exists = await _node.fs.exists(filename);
278
279 if (exists) {
280 const existingStub = await _node.fs.readFile(filename, 'utf8');
281
282 if (!(0, _codeSign.verifySignedCode)(existingStub)) {
283 throw new Error('Stub already exists and has been modified. ' + 'Use --overwrite to overwrite');
284 }
285 }
286 }
287
288 const flowVersionRaw = await (0, _npmProjectUtils.determineFlowVersion)(projectRoot);
289 const flowVersion = flowVersionRaw ? `/flow_${(0, _semver.versionToString)(flowVersionRaw)}` : '';
290 const stubVersion = `<<STUB>>/${packageName}_v${packageVersion}${flowVersion}`;
291 await _node.fs.writeFile(filename, (0, _codeSign.signCode)(output, stubVersion));
292 return filename;
293}
294/**
295 * Look across a project root node_modules as well as
296 * each workspace's node_modules to find the dependency
297 * to determine if it is flow typed.
298 */
299
300
301async function pkgHasFlowFiles(projectRoot, packageName, pnpjs, workspacesPkgJsonData) {
302 const findTypedFiles = async path => {
303 try {
304 let pathToPackage = await resolvePkgDirPath(packageName, path, pnpjs);
305 const typedFiles = await glob('**/*.flow', {
306 cwd: pathToPackage,
307 ignore: 'node_modules/**'
308 });
309
310 if (typedFiles.length > 0) {
311 return pathToPackage;
312 }
313 } catch (e) {
314 return undefined;
315 }
316 };
317
318 const rootTypedPath = await findTypedFiles(projectRoot);
319
320 if (rootTypedPath) {
321 return {
322 isFlowTyped: true,
323 pkgPath: rootTypedPath
324 };
325 }
326
327 const typedWorkspacePaths = await Promise.all(workspacesPkgJsonData.map(async pkgJson => findTypedFiles(_node.path.dirname(pkgJson.pathStr))));
328 const workspacePath = typedWorkspacePaths.find(o => !!o);
329
330 if (workspacePath) {
331 return {
332 isFlowTyped: true,
333 pkgPath: workspacePath
334 };
335 }
336
337 return {
338 isFlowTyped: false
339 };
340}
341
342async function getDefinitelyTypedPackage(packageName, explicitVersion) {
343 const parts = packageName.split('/');
344 const typesPackageName = packageName.startsWith('@') ? parts[0].slice(1) + '__' + parts[1] : packageName;
345 const version = explicitVersion ? `@${explicitVersion}` : '';
346 const typing = `https://unpkg.com/@types/${typesPackageName}${version}/index.d.ts`;
347 return (0, _got.default)(typing, {
348 method: 'GET'
349 });
350}
351/**
352 * createStub("/path/to/root", "foo") will create a file
353 * /path/to/root/flow-typed/npm/foo.js that contains a stub for the module foo.
354 *
355 * If foo is installed, it will read the directory that require("foo") resolves
356 * to and include definitions for "foo/FILE", for every FILE in the foo package
357 */
358
359
360async function createStub(projectRoot, packageName, explicitVersion, overwrite, pnpjs, typescript, libdefDir, maxDepth) {
361 let files = [];
362 let resolutionError = null;
363 let pathToPackage = null;
364 let version = explicitVersion || null;
365 let typescriptTypingsPath = null;
366 let typescriptTypingsContent = null;
367 const typedefDir = libdefDir || 'flow-typed';
368
369 try {
370 pathToPackage = await resolvePkgDirPath(packageName, projectRoot, pnpjs);
371 files = await glob('**/*.{js,jsx}', {
372 cwd: pathToPackage,
373 ignore: 'node_modules/**'
374 });
375 } catch (e) {
376 resolutionError = e;
377 } // Try to deduce a version if one isn't provided
378
379
380 if (version == null) {
381 // Look at the package.json for the installed module
382 if (pathToPackage != null) {
383 try {
384 version = require(_node.path.join(pathToPackage, 'package.json')).version;
385 } catch (e) {}
386 }
387 }
388
389 if (typescript) {
390 if (typescriptTypingsPath == null) {
391 // Look at the package.json for the installed module
392 if (pathToPackage != null) {
393 try {
394 const pkg = require(_node.path.join(pathToPackage, 'package.json'));
395
396 const typing = pkg.typings || pkg.types;
397 if (typing) typescriptTypingsPath = _node.path.join(pathToPackage, typing);
398 } catch (e) {}
399 }
400 } // If that failed, try looking for index.d.ts file
401
402
403 if (typescriptTypingsPath == null) {
404 // Look at the package.json for the installed module
405 if (pathToPackage != null) {
406 const typing = _node.path.join(pathToPackage, 'index.d.ts');
407
408 if (await _node.fs.exists(typing)) {
409 typescriptTypingsPath = typing;
410 }
411 }
412 }
413
414 if (typescriptTypingsPath == null) {
415 try {
416 const response = await getDefinitelyTypedPackage(packageName, explicitVersion);
417 typescriptTypingsContent = response.body;
418 } catch (e) {
419 console.log(_safe.default.red("❌\t%s%s': %s"), packageName, version ? '@' + version : '', e.message);
420 return false;
421 }
422 }
423 } // If that failed, try looking for a package.json in the root
424
425
426 if (version == null) {
427 try {
428 const pkgJsonPathStr = await (0, _npmProjectUtils.findPackageJsonPath)(projectRoot);
429 const pkgJsonData = await (0, _npmProjectUtils.getPackageJsonData)(pkgJsonPathStr);
430 const rootDependencies = await (0, _npmProjectUtils.getPackageJsonDependencies)(pkgJsonData, [], []);
431 version = rootDependencies[packageName] || null;
432 } catch (e) {}
433 }
434
435 try {
436 if (version === null) {
437 throw new Error('Could not deduce version from node_modules or package.json. ' + 'Please provide an explicit version');
438 }
439
440 const filename = await writeStub(projectRoot, packageName, version, pathToPackage, overwrite, files, typedefDir, maxDepth, typescriptTypingsPath, typescriptTypingsContent);
441
442 const terseFilename = _node.path.relative(projectRoot, filename);
443
444 (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);
445 return true;
446 } catch (e) {
447 console.log(_safe.default.red("❌\t%s%s': %s"), packageName, version ? '@' + version : '', e.message);
448 return false;
449 }
450}
\No newline at end of file