From de0e12d0394c68f6c297280b8615736d55645d39 Mon Sep 17 00:00:00 2001 From: "Michael Bradley, Jr" Date: Mon, 16 Sep 2019 15:39:22 -0500 Subject: [PATCH] feat(@embark/utils): "inside monorepo" APIs --- packages/core/inside-monorepo/index.js | 1 + packages/core/inside-monorepo/package.json | 11 ++ packages/core/utils/index.d.ts | 6 + packages/core/utils/package.json | 4 +- packages/core/utils/src/index.js | 15 ++ packages/core/utils/src/monorepo.js | 185 +++++++++++++++++++++ 6 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 packages/core/inside-monorepo/index.js create mode 100644 packages/core/inside-monorepo/package.json create mode 100644 packages/core/utils/src/monorepo.js diff --git a/packages/core/inside-monorepo/index.js b/packages/core/inside-monorepo/index.js new file mode 100644 index 000000000..4c309bf61 --- /dev/null +++ b/packages/core/inside-monorepo/index.js @@ -0,0 +1 @@ +// does nothing on purpose, do not delete this .js file diff --git a/packages/core/inside-monorepo/package.json b/packages/core/inside-monorepo/package.json new file mode 100644 index 000000000..eace31d37 --- /dev/null +++ b/packages/core/inside-monorepo/package.json @@ -0,0 +1,11 @@ +{ + "name": "embark-inside-monorepo", + "private": true, + "version": "4.1.1", + "author": "Michael Bradley (https://github.com/michaelsbradleyjr/)", + "description": "If a package from the monorepo can resolve this package then the first package is inside the monorepo", + "keywords": [], + "license": "MIT", + "main": "index.js", + "scripts": {} +} diff --git a/packages/core/utils/index.d.ts b/packages/core/utils/index.d.ts index 76fbed138..7d17f532c 100644 --- a/packages/core/utils/index.d.ts +++ b/packages/core/utils/index.d.ts @@ -18,8 +18,14 @@ declare module "embark-utils" { function escapeHtml(message: any): string; function embarkPath(...names: string[]): string; function exit(code?: any): void; + function findMonorepoPackageFromRoot(pkgName: string, prefilter?: null | ((pkgName: string) => (pkgJsonPath: string) => boolean)): Promise; + function findMonorepoPackageFromRootSync(pkgName: string, prefilter?: null | ((pkgName: string) => (pkgJsonPath: string) => boolean)): string; function findNextPort(port: number): Promise; function isEs6Module(module: any): boolean; + function isInsideMonorepo(): Promise; + function isInsideMonorepoSync(): boolean; + function monorepoRootPath(): Promise; + function monorepoRootPathSync(): string; function jsonFunctionReplacer(key: any, value: any): any; function fuzzySearch(text: string, list: any, filter: any): any; function getExternalContractUrl(file: string, provideUrl: string): string; diff --git a/packages/core/utils/package.json b/packages/core/utils/package.json index 02d0e3f5c..729fce16a 100644 --- a/packages/core/utils/package.json +++ b/packages/core/utils/package.json @@ -45,7 +45,7 @@ "extends": "../../../.eslintrc.json" }, "dependencies": { - "@babel/runtime-corejs2": "7.3.1", + "@babel/runtime-corejs2": "7.6.0", "bip39": "3.0.2", "clipboardy": "1.2.3", "colors": "1.3.2", @@ -55,6 +55,7 @@ "follow-redirects": "1.8.0", "fs-extra": "7.0.1", "fuzzy": "0.1.3", + "glob": "7.1.4", "globule": "1.2.1", "merge": "1.2.1", "multihashes": "0.4.14", @@ -73,6 +74,7 @@ "@types/node": "10.11.7", "@types/pretty-ms": "5.0.1", "cross-env": "5.2.0", + "embark-inside-monorepo": "^4.1.1", "eslint": "5.7.0", "npm-run-all": "4.1.5", "rimraf": "3.0.0", diff --git a/packages/core/utils/src/index.js b/packages/core/utils/src/index.js index bbfc7fa59..3719eccc0 100644 --- a/packages/core/utils/src/index.js +++ b/packages/core/utils/src/index.js @@ -50,6 +50,15 @@ import { compact, last, recursiveMerge, groupBy } from './collections'; import { prepareForCompilation } from './solidity/remapImports'; import { File, getExternalContractUrl, Types } from './file'; +import { + findMonorepoPackageFromRoot, + findMonorepoPackageFromRootSync, + isInsideMonorepo, + isInsideMonorepoSync, + monorepoRootPath, + monorepoRootPathSync +} from './monorepo'; + function timer(ms) { const then = Date.now(); return new Promise(resolve => ( @@ -312,6 +321,8 @@ const Utils = { soliditySha3, recursiveMerge, prepareContractsConfig, + findMonorepoPackageFromRoot, + findMonorepoPackageFromRootSync, getWeiBalanceFromString, getHexBalanceFromString, getExternalContractUrl, @@ -321,6 +332,10 @@ const Utils = { httpsGet, httpGetJson, httpsGetJson, + isInsideMonorepo, + isInsideMonorepoSync, + monorepoRootPath, + monorepoRootPathSync, pingEndpoint, setUpEnv, sha512, diff --git a/packages/core/utils/src/monorepo.js b/packages/core/utils/src/monorepo.js new file mode 100644 index 000000000..ecad8f438 --- /dev/null +++ b/packages/core/utils/src/monorepo.js @@ -0,0 +1,185 @@ +/* global __dirname module require */ + +const findUp = require('find-up'); +const {readJson, readJsonSync} = require('fs-extra'); + +const {promisify} = require('util'); +const glob = require('glob'); +const globP = promisify(glob); + +const {basename, dirname, join} = require('path'); + +let _isInsideMonorepo = null; +let _monorepoRootPath = null; + +const embarkInsidePkg = 'embark-inside-monorepo'; +const lernaJson = 'lerna.json'; + +const couldNotFindRootErrorMsg = `could not find embark's monorepo's root starting from ${__dirname}`; +const notInsideErrorMsg = function (dir) { + return `package ${dir} is not inside embark's monorepo`; +}; + +async function isInsideMonorepo() { + if (_isInsideMonorepo === null) { + try { + _isInsideMonorepo = !!( + await findUp(`node_modules/${embarkInsidePkg}`, {cwd: __dirname}) + ); + } catch (err) { + _isInsideMonorepo = false; + } + } + + return _isInsideMonorepo; +} + +function isInsideMonorepoSync() { + if (_isInsideMonorepo === null) { + try { + _isInsideMonorepo = !!require.resolve( + embarkInsidePkg, {paths: [__dirname]} + ); + } catch (err) { + _isInsideMonorepo = false; + } + } + + return _isInsideMonorepo; +} + +async function monorepoRootPath() { + if (!(await isInsideMonorepo())) { + throw new Error( + notInsideErrorMsg(dirname(await findUp('package.json', {cwd: __dirname}))) + ); + } + + if (_monorepoRootPath === null) { + try { + _monorepoRootPath = dirname(await findUp(lernaJson, {cwd: __dirname})); + } catch (err) { + _monorepoRootPath = false; + throw new Error(couldNotFindRootErrorMsg); + } + } + + if (_monorepoRootPath) { + return _monorepoRootPath; + } + + throw new Error(couldNotFindRootErrorMsg); +} + +function monorepoRootPathSync() { + if (!isInsideMonorepoSync()) { + throw new Error( + notInsideErrorMsg(dirname(findUp.sync('package.json', {cwd: __dirname}))) + ); + } + + if (_monorepoRootPath === null) { + try { + _monorepoRootPath = dirname(findUp.sync(lernaJson, {cwd: __dirname})); + } catch (err) { + _monorepoRootPath = false; + throw new Error(couldNotFindRootErrorMsg); + } + } + + if (_monorepoRootPath) { + return _monorepoRootPath; + } + + throw new Error(couldNotFindRootErrorMsg); +} + +const globArgs = function(monorepoRootPath) { + return [ + '**/package.json', + { + cwd: monorepoRootPath, + ignore: [ + '**/node_modules/**', + 'package.json', + 'scripts/**', + 'site/**' + ] + } + ]; +}; + +const couldNotFindPkgErrorMsg = function(pkgName, monorepoRootPath) { + return `could not find any package named ${pkgName} inside the embark monorepo at ${monorepoRootPath}, if it is known to exist try disabling the prefilter by passing null as the second argument`; +}; + +const partialMatch = function(pkgName) { + if (pkgName.startsWith('embark-')) { + pkgName = pkgName.slice(7); + } + return function (pkgJsonPath) { + let dir = basename(dirname(pkgJsonPath)); + return dir.includes(pkgName); + }; +}; + +async function findMonorepoPackageFromRoot(pkgName, prefilter = partialMatch) { + const rootPath = await monorepoRootPath(); + const pkgJsonPaths = (await globP(...globArgs(rootPath))); + + prefilter = prefilter ? prefilter(pkgName) : () => true; + const jsons = function *() { + for (let path of pkgJsonPaths) { + if (!prefilter(path)) continue; + path = join(rootPath, path); + yield Promise.all([readJson(path), path]); + } + }; + + let pkgPath; + for await (const [json, path] of jsons()) { + if (json.name === pkgName) { + pkgPath = dirname(path); + break; + } + } + + if (pkgPath) return pkgPath; + + throw new Error(couldNotFindPkgErrorMsg(pkgName, rootPath)); +} + +function findMonorepoPackageFromRootSync(pkgName, prefilter = partialMatch) { + const rootPath = monorepoRootPathSync(); + const pkgJsonPaths = glob.sync(...globArgs(rootPath)); + + prefilter = prefilter ? prefilter(pkgName) : () => true; + const jsons = function *() { + for (let path of pkgJsonPaths) { + if (!prefilter(path)) continue; + path = join(rootPath, path); + yield [readJsonSync(path), path]; + } + }; + + let pkgPath; + for (const [json, path] of jsons()) { + if (json.name === pkgName) { + pkgPath = dirname(path); + break; + } + } + + if (pkgPath) return pkgPath; + + throw new Error(couldNotFindPkgErrorMsg(pkgName, rootPath)); +} + +module.exports = { + findMonorepoPackageFromRoot, + findMonorepoPackageFromRootSync, + isInsideMonorepo, + isInsideMonorepoSync, + monorepoRootPath, + monorepoRootPathSync +};