1 | "use strict";
|
2 |
|
3 | Object.defineProperty(exports, "__esModule", {
|
4 | value: true
|
5 | });
|
6 | exports.createStub = createStub;
|
7 | exports.glob = glob;
|
8 | exports.pkgHasFlowFiles = pkgHasFlowFiles;
|
9 |
|
10 | var _safe = _interopRequireDefault(require("colors/safe"));
|
11 |
|
12 | var _got = _interopRequireDefault(require("got"));
|
13 |
|
14 | var _flowgen = _interopRequireDefault(require("flowgen"));
|
15 |
|
16 | var _prettier = _interopRequireDefault(require("prettier"));
|
17 |
|
18 | var _npmProjectUtils = require("./npm/npmProjectUtils");
|
19 |
|
20 | var _util = require("util");
|
21 |
|
22 | var _node = require("./node");
|
23 |
|
24 | var _glob = _interopRequireDefault(require("glob"));
|
25 |
|
26 | var _fileUtils = require("./fileUtils");
|
27 |
|
28 | var _codeSign = require("./codeSign");
|
29 |
|
30 | var _semver = require("./semver");
|
31 |
|
32 | var _logger = require("./logger");
|
33 |
|
34 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
35 |
|
36 | function 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 |
|
46 | async 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 |
|
77 | const moduleStubTemplate = `
|
78 | declare module '%s' {
|
79 | declare module.exports: any;
|
80 | }`.trim();
|
81 | const aliasTemplate = `
|
82 | declare module '%s%s' {
|
83 | declare module.exports: $Exports<'%s'>;
|
84 | }`.trim();
|
85 | const guessedModuleStubTemplate = `
|
86 | declare module '%s' {
|
87 | declare module.exports: %s;
|
88 | }`.trim();
|
89 |
|
90 | function 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 |
|
101 | const functionTemplate = '(%s) => any';
|
102 |
|
103 | const spaceByI = i => ' '.repeat(i);
|
104 |
|
105 | const keyTypeTemplate = ` %s: %s,\n`;
|
106 |
|
107 | function 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 |
|
127 | function 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 |
|
139 | function 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 |
|
150 | function objectToType(obj, maxDepth, currentDepth = 0, deep = true) {
|
151 | if (obj === null) return 'null';
|
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 |
|
163 | function 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 |
|
170 | async function writeStub(projectRoot, packageName, packageVersion, packageFolder, overwrite, files, libdefDir, maxDepth, typescriptTypingsPath, typescriptTypingsContent) {
|
171 | let flowgenOutput = null;
|
172 | const flowgenTemplate = `
|
173 | declare 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';
|
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 |
|
296 |
|
297 |
|
298 |
|
299 |
|
300 |
|
301 | async 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 |
|
342 | async 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 |
|
353 |
|
354 |
|
355 |
|
356 |
|
357 |
|
358 |
|
359 |
|
360 | async 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 | }
|
378 |
|
379 |
|
380 | if (version == null) {
|
381 |
|
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 |
|
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 | }
|
401 |
|
402 |
|
403 | if (typescriptTypingsPath == null) {
|
404 |
|
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 | }
|
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 |