diff --git a/.gitignore b/.gitignore index 31265353a0..9753ebfc40 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ yarn-error.log .DS_Store dist/ lib/ +# lib/ ignores metro-dev-helpers/lib so: +!metro-dev-helpers/lib .env* examples/SampleApp/ios/vendor/ vendor diff --git a/examples/ExpoMessaging/metro.config.js b/examples/ExpoMessaging/metro.config.js index dc82335281..63b02a09fc 100644 --- a/examples/ExpoMessaging/metro.config.js +++ b/examples/ExpoMessaging/metro.config.js @@ -1,152 +1,13 @@ /* eslint-env node */ -function resolvePath(...parts) { - const thisPath = PATH.resolve.apply(PATH, parts); - if (!FS.existsSync(thisPath)) return; - - return FS.realpathSync(thisPath); -} - -function isExternalModule(modulePath) { - return modulePath.substring(0, __dirname.length) !== __dirname; -} - -function listDirectories(rootPath, cb) { - FS.readdirSync(rootPath).forEach((fileName) => { - if (fileName.charAt(0) === '.') return; - - let fullFileName = PATH.join(rootPath, fileName), - stats = FS.lstatSync(fullFileName), - symbolic = false; - - if (stats.isSymbolicLink()) { - fullFileName = resolvePath(fullFileName); - if (!fullFileName) return; - - stats = FS.lstatSync(fullFileName); - - symbolic = true; - } - - if (!stats.isDirectory()) return; - - const external = isExternalModule(fullFileName); - cb({ rootPath, symbolic, external, fullFileName, fileName }); - }); -} - -function buildFullModuleMap( - moduleRoot, - mainModuleMap, - externalModuleMap, - _alreadyVisited, - _prefix, -) { - if (!moduleRoot) return; - - const alreadyVisited = _alreadyVisited || {}, - prefix = _prefix; - - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ fileName, fullFileName, symbolic, external }) => { - if (symbolic) - return buildFullModuleMap( - resolvePath(fullFileName, 'node_modules'), - mainModuleMap, - externalModuleMap, - alreadyVisited, - ); - - const moduleMap = external ? externalModuleMap : mainModuleMap, - moduleName = prefix ? PATH.join(prefix, fileName) : fileName; - - if (fileName.charAt(0) !== '@') moduleMap[moduleName] = fullFileName; - else - return buildFullModuleMap( - fullFileName, - mainModuleMap, - externalModuleMap, - alreadyVisited, - fileName, - ); - }); -} - -function buildModuleResolutionMap() { - const moduleMap = {}, - externalModuleMap = {}; - - buildFullModuleMap(baseModulePath, moduleMap, externalModuleMap); - - // Root project modules take precedence over external modules - return Object.assign({}, externalModuleMap, moduleMap); -} - -function findAlternateRoots(moduleRoot = baseModulePath, alternateRoots = [], _alreadyVisited) { - const alreadyVisited = _alreadyVisited || {}; - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ fullFileName, fileName, external }) => { - if (fileName.charAt(0) !== '@') { - if (external) alternateRoots.push(fullFileName); - } else { - findAlternateRoots(fullFileName, alternateRoots, alreadyVisited); - } - }); - - return alternateRoots; -} - -function getPolyfillHelper() { - let getPolyfills; - - // Get default react-native polyfills - try { - getPolyfills = require('react-native/rn-get-polyfills'); - } catch (e) { - getPolyfills = () => []; - } - - // See if project has custom polyfills, if so, include the PATH to them - try { - const customPolyfills = require.resolve('./polyfills.js'); - getPolyfills = (function (originalGetPolyfills) { - return () => originalGetPolyfills().concat(customPolyfills); - })(getPolyfills); - } catch (e) { - //ignore - } - - return getPolyfills; -} const PATH = require('path'); -const FS = require('fs'), - blacklist = require('metro-config/src/defaults/blacklist'); +const blacklist = require('metro-config/src/defaults/blackList'); -const repoDir = PATH.dirname(PATH.dirname(__dirname)); +const extractLinkedPackages = require('stream-chat-react-native-core/metro-dev-helpers/extract-linked-packages'); -const moduleBlacklist = [ - new RegExp(repoDir + '/examples/NativeMessaging/.*'), - new RegExp(repoDir + '/examples/SampleApp/.*'), - new RegExp(repoDir + '/examples/TypeScriptMessaging/.*'), - new RegExp(repoDir + '/native-package/.*'), - new RegExp(repoDir + '/expo-package/node_modules/.*'), - new RegExp(repoDir + '/node_modules/.*'), - ], - baseModulePath = resolvePath(__dirname, 'node_modules'), - // watch alternate roots (outside of project root) - alternateRoots = findAlternateRoots(), - // build full module map for proper - // resolution of modules in external roots - extraNodeModules = buildModuleResolutionMap(); +const projectRoot = PATH.resolve(__dirname); -if (alternateRoots && alternateRoots.length) - console.log('Found alternate project roots: ', alternateRoots); +const { alternateRoots, extraNodeModules, moduleBlacklist } = extractLinkedPackages(projectRoot); module.exports = { resolver: { @@ -154,11 +15,5 @@ module.exports = { extraNodeModules, useWatchman: false, }, - watchFolders: [PATH.resolve(__dirname)].concat(alternateRoots), - // transformer: { - // babelTransformerPath: require.resolve('./compiler/transformer'), - // }, - serializer: { - getPolyfills: getPolyfillHelper(), - }, + watchFolders: [projectRoot].concat(alternateRoots), }; diff --git a/examples/NativeMessaging/metro.config.js b/examples/NativeMessaging/metro.config.js index b2268a4c41..ca0a4a206f 100644 --- a/examples/NativeMessaging/metro.config.js +++ b/examples/NativeMessaging/metro.config.js @@ -1,164 +1,19 @@ /* eslint-env node */ -function resolvePath(...parts) { - const thisPath = PATH.resolve.apply(PATH, parts); - if (!FS.existsSync(thisPath)) return; - - return FS.realpathSync(thisPath); -} - -function isExternalModule(modulePath) { - return modulePath.substring(0, __dirname.length) !== __dirname; -} - -function listDirectories(rootPath, cb) { - FS.readdirSync(rootPath).forEach((fileName) => { - if (fileName.charAt(0) === '.') return; - - let fullFileName = PATH.join(rootPath, fileName), - stats = FS.lstatSync(fullFileName), - symbolic = false; - - if (stats.isSymbolicLink()) { - fullFileName = resolvePath(fullFileName); - if (!fullFileName) return; - - stats = FS.lstatSync(fullFileName); - - symbolic = true; - } - - if (!stats.isDirectory()) return; - - const external = isExternalModule(fullFileName); - cb({ rootPath, symbolic, external, fullFileName, fileName }); - }); -} - -function buildFullModuleMap( - moduleRoot, - mainModuleMap, - externalModuleMap, - _alreadyVisited, - _prefix, -) { - if (!moduleRoot) return; - - const alreadyVisited = _alreadyVisited || {}, - prefix = _prefix; - - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ fileName, fullFileName, symbolic, external }) => { - if (symbolic) - return buildFullModuleMap( - resolvePath(fullFileName, 'node_modules'), - mainModuleMap, - externalModuleMap, - alreadyVisited, - ); - - const moduleMap = external ? externalModuleMap : mainModuleMap, - moduleName = prefix ? PATH.join(prefix, fileName) : fileName; - - if (fileName.charAt(0) !== '@') moduleMap[moduleName] = fullFileName; - else - return buildFullModuleMap( - fullFileName, - mainModuleMap, - externalModuleMap, - alreadyVisited, - fileName, - ); - }); -} - -function buildModuleResolutionMap() { - const moduleMap = {}, - externalModuleMap = {}; - - buildFullModuleMap(baseModulePath, moduleMap, externalModuleMap); - - // Root project modules take precedence over external modules - return Object.assign({}, externalModuleMap, moduleMap); -} - -function findAlternateRoots(moduleRoot = baseModulePath, alternateRoots = [], _alreadyVisited) { - const alreadyVisited = _alreadyVisited || {}; - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ fullFileName, fileName, external }) => { - if (fileName.charAt(0) !== '@') { - if (external) alternateRoots.push(fullFileName); - } else { - findAlternateRoots(fullFileName, alternateRoots, alreadyVisited); - } - }); - - return alternateRoots; -} - -function getPolyfillHelper() { - let getPolyfills; - - // Get default react-native polyfills - try { - getPolyfills = require('react-native/rn-get-polyfills'); - } catch (e) { - getPolyfills = () => []; - } - - // See if project has custom polyfills, if so, include the PATH to them - try { - const customPolyfills = require.resolve('./polyfills.js'); - getPolyfills = (function (originalGetPolyfills) { - return () => originalGetPolyfills().concat(customPolyfills); - })(getPolyfills); - } catch (e) { - //ignore - } - - return getPolyfills; -} const PATH = require('path'); -const FS = require('fs'), - exclusionList = require('metro-config/src/defaults/exclusionList'); +const blacklist = require('metro-config/src/defaults/exclusionList'); -const repoDir = PATH.dirname(PATH.dirname(__dirname)); +const extractLinkedPackages = require('stream-chat-react-native-core/metro-dev-helpers/extract-linked-packages'); -const moduleExclusionList = [ - new RegExp(repoDir + '/examples/ExpoMessaging/.*'), - new RegExp(repoDir + '/examples/SampleApp/.*'), - new RegExp(repoDir + '/examples/TypeScriptMessaging/.*'), - new RegExp(repoDir + '/expo-package/.*'), - new RegExp(repoDir + '/native-package/node_modules/.*'), - new RegExp(repoDir + '/node_modules/.*'), - ], - baseModulePath = resolvePath(__dirname, 'node_modules'), - // watch alternate roots (outside of project root) - alternateRoots = findAlternateRoots(), - // build full module map for proper - // resolution of modules in external roots - extraNodeModules = buildModuleResolutionMap(); +const projectRoot = PATH.resolve(__dirname); -if (alternateRoots && alternateRoots.length) - console.log('Found alternate project roots: ', alternateRoots); +const { alternateRoots, extraNodeModules, moduleBlacklist } = extractLinkedPackages(projectRoot); module.exports = { resolver: { - blacklistRE: exclusionList(moduleExclusionList), + blacklistRE: blacklist(moduleBlacklist), extraNodeModules, useWatchman: false, }, - watchFolders: [PATH.resolve(__dirname)].concat(alternateRoots), - // transformer: { - // babelTransformerPath: require.resolve('./compiler/transformer'), - // }, - serializer: { - getPolyfills: getPolyfillHelper(), - }, + watchFolders: [projectRoot].concat(alternateRoots), }; diff --git a/examples/SampleApp/metro.config.js b/examples/SampleApp/metro.config.js index 2de8fae491..ca0a4a206f 100644 --- a/examples/SampleApp/metro.config.js +++ b/examples/SampleApp/metro.config.js @@ -1,186 +1,19 @@ -/* eslint-disable */ -function resolvePath(...parts) { - const thisPath = PATH.resolve.apply(PATH, parts); - if (!FS.existsSync(thisPath)) return; - - return FS.realpathSync(thisPath); -} - -function isExternalModule(modulePath) { - return modulePath.substring(0, __dirname.length) !== __dirname; -} - -function listDirectories(rootPath, cb) { - FS.readdirSync(rootPath).forEach((fileName) => { - if (fileName.charAt(0) === '.') return; - - let fullFileName = PATH.join(rootPath, fileName), - stats = FS.lstatSync(fullFileName), - symbolic = false; - - if (stats.isSymbolicLink()) { - fullFileName = resolvePath(fullFileName); - if (!fullFileName) return; - - stats = FS.lstatSync(fullFileName); - - symbolic = true; - } - - if (!stats.isDirectory()) return; - - const external = isExternalModule(fullFileName); - cb({ rootPath, symbolic, external, fullFileName, fileName }); - }); -} - -function buildFullModuleMap( - moduleRoot, - mainModuleMap, - externalModuleMap, - _alreadyVisited, - _prefix, -) { - if (!moduleRoot) return; - - const alreadyVisited = _alreadyVisited || {}, - prefix = _prefix; - - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ external, fileName, fullFileName, symbolic }) => { - if (symbolic) - return buildFullModuleMap( - resolvePath(fullFileName, 'node_modules'), - mainModuleMap, - externalModuleMap, - alreadyVisited, - ); - - const moduleMap = external ? externalModuleMap : mainModuleMap, - moduleName = prefix ? PATH.join(prefix, fileName) : fileName; - - if (fileName.charAt(0) !== '@') moduleMap[moduleName] = fullFileName; - else - return buildFullModuleMap( - fullFileName, - mainModuleMap, - externalModuleMap, - alreadyVisited, - fileName, - ); - }); -} - -function buildModuleResolutionMap() { - const moduleMap = {}, - externalModuleMap = {}; - - buildFullModuleMap(baseModulePath, moduleMap, externalModuleMap); - - // Root project modules take precedence over external modules - return Object.assign({}, externalModuleMap, moduleMap); -} - -function findAlternateRoots(moduleRoot = baseModulePath, alternateRoots = [], _alreadyVisited) { - const alreadyVisited = _alreadyVisited || {}; - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ external, fileName, fullFileName }) => { - if (fileName.charAt(0) !== '@') { - if (external) alternateRoots.push(fullFileName); - } else { - findAlternateRoots(fullFileName, alternateRoots, alreadyVisited); - } - }); - - return alternateRoots; -} - -function getPolyfillHelper() { - let getPolyfills; - - // Get default react-native polyfills - try { - getPolyfills = require('react-native/rn-get-polyfills'); - } catch (e) { - getPolyfills = () => []; - } - - // See if project has custom polyfills, if so, include the PATH to them - try { - const customPolyfills = require.resolve('./polyfills.js'); - getPolyfills = (function (originalGetPolyfills) { - return () => originalGetPolyfills().concat(customPolyfills); - })(getPolyfills); - } catch (e) { - //ignore - } - - return getPolyfills; -} +/* eslint-env node */ const PATH = require('path'); -const FS = require('fs'), - exclusionList = require('metro-config/src/defaults/exclusionList'); +const blacklist = require('metro-config/src/defaults/exclusionList'); -const repoDir = PATH.dirname(PATH.dirname(__dirname)); +const extractLinkedPackages = require('stream-chat-react-native-core/metro-dev-helpers/extract-linked-packages'); -const moduleExclusionList = [ - new RegExp(repoDir + '/examples/ExpoMessaging/.*'), - new RegExp(PATH.dirname(repoDir) + '/flat-list-mvcp/node_modules/.*'), - new RegExp(PATH.dirname(repoDir) + '/flat-list-mvcp/Example/.*'), - new RegExp(repoDir + '/examples/NativeMessaging/.*'), - new RegExp(repoDir + '/examples/TypeScriptMessaging/.*'), - // new RegExp(repoDir + '/native-example/(.*)'), - new RegExp(repoDir + '/expo-package/.*'), - new RegExp(repoDir + '/native-package/node_modules/.*'), - new RegExp(repoDir + '/node_modules/.*'), - ], - baseModulePath = resolvePath(__dirname, 'node_modules'), - // watch alternate roots (outside of project root) - alternateRoots = findAlternateRoots(), - // build full module map for proper - // resolution of modules in external roots - extraNodeModules = buildModuleResolutionMap(); +const projectRoot = PATH.resolve(__dirname); -if (alternateRoots && alternateRoots.length) - console.log('Found alternate project roots: ', alternateRoots); +const { alternateRoots, extraNodeModules, moduleBlacklist } = extractLinkedPackages(projectRoot); -console.log(moduleExclusionList); module.exports = { resolver: { - blacklistRE: exclusionList(moduleExclusionList), + blacklistRE: blacklist(moduleBlacklist), extraNodeModules, useWatchman: false, }, - watchFolders: [PATH.resolve(__dirname)].concat(alternateRoots), - // transformer: { - // babelTransformerPath: require.resolve('./compiler/transformer'), - // }, - serializer: { - getPolyfills: getPolyfillHelper(), - }, + watchFolders: [projectRoot].concat(alternateRoots), }; - -// /** -// * Metro configuration for React Native -// * https://github.com/facebook/react-native -// * -// * @format -// */ - -// module.exports = { -// transformer: { -// getTransformOptions: async () => ({ -// transform: { -// experimentalImportSupport: false, -// inlineRequires: false, -// }, -// }), -// }, -// }; diff --git a/examples/TypeScriptMessaging/metro.config.js b/examples/TypeScriptMessaging/metro.config.js index e1dc970758..ca0a4a206f 100644 --- a/examples/TypeScriptMessaging/metro.config.js +++ b/examples/TypeScriptMessaging/metro.config.js @@ -1,182 +1,19 @@ /* eslint-env node */ -function resolvePath(...parts) { - const thisPath = PATH.resolve.apply(PATH, parts); - if (!FS.existsSync(thisPath)) return; - - return FS.realpathSync(thisPath); -} - -function isExternalModule(modulePath) { - return modulePath.substring(0, __dirname.length) !== __dirname; -} - -function listDirectories(rootPath, cb) { - FS.readdirSync(rootPath).forEach((fileName) => { - if (fileName.charAt(0) === '.') return; - - let fullFileName = PATH.join(rootPath, fileName), - stats = FS.lstatSync(fullFileName), - symbolic = false; - - if (stats.isSymbolicLink()) { - fullFileName = resolvePath(fullFileName); - if (!fullFileName) return; - - stats = FS.lstatSync(fullFileName); - - symbolic = true; - } - - if (!stats.isDirectory()) return; - - const external = isExternalModule(fullFileName); - cb({ rootPath, symbolic, external, fullFileName, fileName }); - }); -} - -function buildFullModuleMap( - moduleRoot, - mainModuleMap, - externalModuleMap, - _alreadyVisited, - _prefix, -) { - if (!moduleRoot) return; - - const alreadyVisited = _alreadyVisited || {}, - prefix = _prefix; - - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ fileName, fullFileName, symbolic, external }) => { - if (symbolic) - return buildFullModuleMap( - resolvePath(fullFileName, 'node_modules'), - mainModuleMap, - externalModuleMap, - alreadyVisited, - ); - - const moduleMap = external ? externalModuleMap : mainModuleMap, - moduleName = prefix ? PATH.join(prefix, fileName) : fileName; - - if (fileName.charAt(0) !== '@') moduleMap[moduleName] = fullFileName; - else - return buildFullModuleMap( - fullFileName, - mainModuleMap, - externalModuleMap, - alreadyVisited, - fileName, - ); - }); -} - -function buildModuleResolutionMap() { - const moduleMap = {}, - externalModuleMap = {}; - - buildFullModuleMap(baseModulePath, moduleMap, externalModuleMap); - - // Root project modules take precedence over external modules - return Object.assign({}, externalModuleMap, moduleMap); -} - -function findAlternateRoots(moduleRoot = baseModulePath, alternateRoots = [], _alreadyVisited) { - const alreadyVisited = _alreadyVisited || {}; - if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) return; - - alreadyVisited[moduleRoot] = true; - - listDirectories(moduleRoot, ({ fullFileName, fileName, external }) => { - if (fileName.charAt(0) !== '@') { - if (external) alternateRoots.push(fullFileName); - } else { - findAlternateRoots(fullFileName, alternateRoots, alreadyVisited); - } - }); - - return alternateRoots; -} - -function getPolyfillHelper() { - let getPolyfills; - - // Get default react-native polyfills - try { - getPolyfills = require('react-native/rn-get-polyfills'); - } catch (e) { - getPolyfills = () => []; - } - - // See if project has custom polyfills, if so, include the PATH to them - try { - const customPolyfills = require.resolve('./polyfills.js'); - getPolyfills = (function (originalGetPolyfills) { - return () => originalGetPolyfills().concat(customPolyfills); - })(getPolyfills); - } catch (e) { - //ignore - } - - return getPolyfills; -} const PATH = require('path'); -const FS = require('fs'), - exclusionList = require('metro-config/src/defaults/exclusionList'); +const blacklist = require('metro-config/src/defaults/exclusionList'); -const repoDir = PATH.dirname(PATH.dirname(__dirname)); +const extractLinkedPackages = require('stream-chat-react-native-core/metro-dev-helpers/extract-linked-packages'); -const moduleExclusionList = [ - new RegExp(repoDir + '/examples/ExpoMessaging/.*'), - new RegExp(repoDir + '/examples/NativeMessaging/.*'), - new RegExp(repoDir + '/examples/SampleApp/.*'), - new RegExp(repoDir + '/expo-package/.*'), - new RegExp(repoDir + '/native-package/node_modules/.*'), - new RegExp(repoDir + '/node_modules/.*'), - ], - baseModulePath = resolvePath(__dirname, 'node_modules'), - // watch alternate roots (outside of project root) - alternateRoots = findAlternateRoots(), - // build full module map for proper - // resolution of modules in external roots - extraNodeModules = buildModuleResolutionMap(); +const projectRoot = PATH.resolve(__dirname); -if (alternateRoots && alternateRoots.length) - console.log('Found alternate project roots: ', alternateRoots); +const { alternateRoots, extraNodeModules, moduleBlacklist } = extractLinkedPackages(projectRoot); module.exports = { resolver: { - blacklistRE: exclusionList(moduleExclusionList), + blacklistRE: blacklist(moduleBlacklist), extraNodeModules, useWatchman: false, }, - watchFolders: [PATH.resolve(__dirname)].concat(alternateRoots), - // transformer: { - // babelTransformerPath: require.resolve('./compiler/transformer'), - // }, - serializer: { - getPolyfills: getPolyfillHelper(), - }, + watchFolders: [projectRoot].concat(alternateRoots), }; - -// /** -// * Metro configuration for React Native -// * https://github.com/facebook/react-native -// * -// * @format -// */ - -// module.exports = { -// transformer: { -// getTransformOptions: async () => ({ -// transform: { -// experimentalImportSupport: false, -// inlineRequires: false, -// }, -// }), -// }, -// }; diff --git a/metro-dev-helpers/extract-linked-packages.js b/metro-dev-helpers/extract-linked-packages.js new file mode 100644 index 0000000000..859b9d676d --- /dev/null +++ b/metro-dev-helpers/extract-linked-packages.js @@ -0,0 +1,56 @@ +/* eslint-env node */ +const PATH = require('path'); + +const { extractExtraNodeModules, findLinkedPackages } = require('./lib'); + +const sdkBlacklistedPaths = [ + '/examples/NativeMessaging', + '/examples/ExpoMessaging', + '/examples/TypeScriptMessaging', + '/examples/SampleApp', + '/native-package/node_modules', + '/expo-package/node_modules', + '/node_modules', +]; + +module.exports = function extractLinkedPackages(repoDir) { + // Map containing linked packages and their real paths + const linkedPackages = findLinkedPackages(repoDir); + + const sdkRootPackage = linkedPackages['stream-chat-react-native-core']; + const sdkNativePackage = linkedPackages['stream-chat-react-native']; + const sdkExpoPackage = linkedPackages['stream-chat-expo']; + + if (!sdkRootPackage) { + throw new Error('stream-chat-react-native-core is not linked!'); + } + + const alternateRoots = [sdkRootPackage]; + + if (sdkNativePackage) { + alternateRoots.push(sdkNativePackage); + } + + if (sdkExpoPackage) { + alternateRoots.push(sdkExpoPackage); + } + + if (!sdkNativePackage && !sdkExpoPackage) { + throw new Error( + 'stream-chat-react-native or stream-chat-expo is not linked! You need to link at least one.', + ); + } + + // Blacklisting samples and other packages folders so theyre not taken in consideration + // The filter operation checks if this helper is being used inside of one of the blacklisted + // folders and if so, removes it from the blacklist. + const moduleBlacklist = sdkBlacklistedPaths + .filter((item) => repoDir.slice(-item.length) !== item) + .map((item) => new RegExp(sdkRootPackage + item + '/.*')); + + // Recursively extract node_modules for repoDir and linked packages + const repoNodeModules = PATH.join(repoDir, 'node_modules'); + const extraNodeModules = extractExtraNodeModules(repoDir, repoNodeModules); + + return { alternateRoots, extraNodeModules, moduleBlacklist }; +}; diff --git a/metro-dev-helpers/lib/extract-extra-node-modules.js b/metro-dev-helpers/lib/extract-extra-node-modules.js new file mode 100644 index 0000000000..b97874c93b --- /dev/null +++ b/metro-dev-helpers/lib/extract-extra-node-modules.js @@ -0,0 +1,108 @@ +/* eslint-env node */ + +const PATH = require('path'); +const FS = require('fs'); + +function resolvePath(...parts) { + const thisPath = PATH.resolve.apply(PATH, parts); + if (!FS.existsSync(thisPath)) { + return; + } + + return FS.realpathSync(thisPath); +} + +function isExternalModule(repoDir, modulePath) { + return modulePath.substring(0, repoDir.length) !== repoDir; +} + +function listDirectories(repoDir, rootPath, cb) { + FS.readdirSync(rootPath).forEach((fileName) => { + if (fileName.charAt(0) === '.') { + return; + } + + let fullFileName = PATH.join(rootPath, fileName), + stats = FS.lstatSync(fullFileName), + symbolic = false; + + if (stats.isSymbolicLink()) { + fullFileName = resolvePath(fullFileName); + if (!fullFileName) { + return; + } + + stats = FS.lstatSync(fullFileName); + + symbolic = true; + } + + if (!stats.isDirectory()) { + return; + } + + const external = isExternalModule(repoDir, fullFileName); + cb({ external, fileName, fullFileName, rootPath, symbolic }); + }); +} + +function buildFullModuleMap( + repoDir, + moduleRoot, + mainModuleMap, + externalModuleMap, + _alreadyVisited, + _prefix, +) { + if (!moduleRoot) { + return; + } + + const alreadyVisited = _alreadyVisited || {}, + prefix = _prefix; + + // eslint-disable-next-line no-prototype-builtins + if (alreadyVisited && alreadyVisited.hasOwnProperty(moduleRoot)) { + return; + } + + alreadyVisited[moduleRoot] = true; + + listDirectories(repoDir, moduleRoot, ({ external, fileName, fullFileName, symbolic }) => { + if (symbolic) { + return buildFullModuleMap( + repoDir, + resolvePath(fullFileName, 'node_modules'), + mainModuleMap, + externalModuleMap, + alreadyVisited, + ); + } + + const moduleMap = external ? externalModuleMap : mainModuleMap, + moduleName = prefix ? PATH.join(prefix, fileName) : fileName; + + if (fileName.charAt(0) !== '@') { + moduleMap[moduleName] = fullFileName; + } else { + return buildFullModuleMap( + repoDir, + fullFileName, + mainModuleMap, + externalModuleMap, + alreadyVisited, + fileName, + ); + } + }); +} + +module.exports = function extractExtraNodeModules(repoDir, moduleRoot) { + const moduleMap = {}, + externalModuleMap = {}; + + buildFullModuleMap(repoDir, moduleRoot, moduleMap, externalModuleMap); + + // Root project modules take precedence over external modules + return Object.assign({}, externalModuleMap, moduleMap); +}; diff --git a/metro-dev-helpers/lib/find-linked-packages.js b/metro-dev-helpers/lib/find-linked-packages.js new file mode 100644 index 0000000000..01d7627f3c --- /dev/null +++ b/metro-dev-helpers/lib/find-linked-packages.js @@ -0,0 +1,20 @@ +/* eslint-env node */ + +const PATH = require('path'); + +const LINK_STRING = 'link:'; + +module.exports = function findLinkedPackages(repoDir) { + const pjson = require(PATH.join(repoDir, 'package.json')); + const dependencies = {...pjson.dependencies, ...pjson.devDependencies}; + const linkedDependencies = Object.entries(dependencies).filter(([,value]) => { + return value.slice(0, LINK_STRING.length) === LINK_STRING; + }); + + const linkedDependenciesPathMap = linkedDependencies.reduce((acc, [nextKey, nextValue]) => { + acc[nextKey] = PATH.resolve(nextValue.replace(LINK_STRING, '')); + return acc; + }, {}) + + return linkedDependenciesPathMap; +}; diff --git a/metro-dev-helpers/lib/index.js b/metro-dev-helpers/lib/index.js new file mode 100644 index 0000000000..db2e29ceb9 --- /dev/null +++ b/metro-dev-helpers/lib/index.js @@ -0,0 +1,9 @@ +/* eslint-env node */ + +const extractExtraNodeModules = require('./extract-extra-node-modules'); +const findLinkedPackages = require('./find-linked-packages'); + +module.exports = { + extractExtraNodeModules, + findLinkedPackages, +};