UNPKG

humpty

Version:

Makes sure your changelogs mention your breaking changes

180 lines (164 loc) 4.23 kB
// @flow import _ from "lodash"; import path from "path"; import simpleGit from "simple-git"; const babylon = require("babylon"); const reactDocs = require("react-docgen"); import babylonOptions from "./util/babylonOptions"; async function showFile(repo, apiRef, filePath) { return new Promise((resolve, reject) => { repo.show([`${apiRef}:${filePath}`], (err, results) => { if (err) { reject(err); return; } resolve(results); }); }); } async function loadFile( repo, apiRef, filePath, extensionsToTry = ["", ".js", ".jsx"] ) { for (let i = 0; i < extensionsToTry.length; i++) { const ext = extensionsToTry[i]; try { const contents = await showFile(repo, apiRef, `${filePath}${ext}`); return contents; } catch (error) { // trying next extension } } } function getDefaultExportSourceFilePath(ast) { const defaultExportAsNamed = ast.find( node => node.type === "ExportNamedDeclaration" && node.specifiers[0].exported.name === "default" ); if (defaultExportAsNamed) { return defaultExportAsNamed.source.value; } } async function getPropsOfDeepestFile({ repo, apiRef, sourceFile, currentDir = "" }) { const fullFilePath = path.join(currentDir, sourceFile); const source = await loadFile(repo, apiRef, fullFilePath); if (!source) { throw new Error(`Could not load file for named export ${sourceFile}`); } const indexAst = getAST(source); const nextSourceFile = getDefaultExportSourceFilePath(indexAst); if (nextSourceFile) { return await getPropsOfDeepestFile({ repo, apiRef, sourceFile: nextSourceFile, currentDir: path.dirname(fullFilePath) }); } return await detectProps(source); } async function detectNamedExports({ repo, apiRef, source: indexSource }) { const indexAst = getAST(indexSource); if (!indexAst) { throw new Error("Could not parse entry file"); } const exportsThatNeedLookup = indexAst .filter( ({ type }) => ["ExportNamedDeclaration"].includes(type) // "ExportDefaultDeclaration" ) .filter(({ source }) => Boolean(source)); const results = await Promise.all( exportsThatNeedLookup.map(async toImport => { const publicExportName = toImport.specifiers[0].exported.name; const sourceFile = toImport.source.value; const foundProps = await getPropsOfDeepestFile({ repo, apiRef, sourceFile }); return generateExportDefinition({ isDefaultExport: false, name: publicExportName, api: { props: foundProps } }); }) ); return results; } async function detectProps(sourceToParse) { const defaultExportApi = reactDocs.parse(sourceToParse); return _(defaultExportApi.props) .mapValues((value, name) => _.merge({}, value, { name })) .values() .value(); } function generateExportDefinition({ isDefaultExport, name, sourceContents, api }) { return { isDefaultExport, sourceContents, name, api }; } async function loadExport(repo, apiRef) { const source = await loadFile(repo, apiRef, "index.js"); const namedExports = await detectNamedExports({ repo, apiRef, source }); const results = [ generateExportDefinition({ isDefaultExport: true, sourceContents: source, api: { props: await detectProps(source) } }), ...namedExports ]; return results; } function getGitLog(repo, oldApi, newApi) { return new Promise((resolve, reject) => { repo.log( { from: oldApi, to: newApi }, (err, results) => { if (err) { reject(err); return; } resolve(results.all.map(log => log.message)); } ); }); } function getAST(source) { const ast = babylon.parse(source, babylonOptions); return ast ? ast.program.body : null; } export default async function getExportsAndCommits({ oldApi, newApi, gitRepo }) { const repo = simpleGit(gitRepo).silent(true); return { commitMessages: await getGitLog(repo, oldApi, newApi), newExports: await loadExport(repo, newApi), oldExports: await loadExport(repo, oldApi) }; }