diff --git a/__e2e__/__snapshots__/config.test.ts.snap b/__e2e__/__snapshots__/config.test.ts.snap index 0249bc706..ddb819206 100644 --- a/__e2e__/__snapshots__/config.test.ts.snap +++ b/__e2e__/__snapshots__/config.test.ts.snap @@ -26,7 +26,7 @@ exports[`shows up current config without unnecessary output 1`] = ` }, { "name": "build-ios", - "description": "builds your app for iOS platform", + "description": "builds your app on iOS simulator", "examples": [ "<>" ], @@ -65,9 +65,7 @@ exports[`shows up current config without unnecessary output 1`] = ` "android": { "sourceDir": "<>/TestProject/android", "appName": "app", - "packageName": "com.testproject", - "applicationId": "com.testproject", - "mainActivity": ".MainActivity" + "packageName": "com.testproject" } } } diff --git a/packages/cli-config/src/__tests__/__snapshots__/index-test.ts.snap b/packages/cli-config/src/__tests__/__snapshots__/index-test.ts.snap index 453523b62..505caac8e 100644 --- a/packages/cli-config/src/__tests__/__snapshots__/index-test.ts.snap +++ b/packages/cli-config/src/__tests__/__snapshots__/index-test.ts.snap @@ -11,7 +11,7 @@ Object { ], "podspecPath": "<>/node_modules/react-native-test/ReactNativeTest.podspec", "scriptPhases": Array [], - "version": "unresolved", + "version": "0.0.1", }, }, "root": "<>/node_modules/react-native-test", @@ -58,7 +58,7 @@ Object { "path": "./phase.sh", }, ], - "version": "unresolved", + "version": "0.0.1", }, }, "root": "<>/node_modules/react-native-test", @@ -109,7 +109,7 @@ Object { "show_env_vars_in_log": false, }, ], - "version": "unresolved", + "version": "0.0.1", }, }, "root": "<>/node_modules/react-native-test", @@ -134,7 +134,7 @@ Object { "configurations": Array [], "podspecPath": "<>/node_modules/react-native-test/ReactNativeTest.podspec", "scriptPhases": Array [], - "version": "unresolved", + "version": "0.0.1", }, }, "root": "<>/node_modules/react-native-test", @@ -153,7 +153,7 @@ Object { ], "podspecPath": "<>/node_modules/react-native-test/ReactNativeTest.podspec", "scriptPhases": Array [], - "version": "unresolved", + "version": "0.0.1", }, }, "root": "<>/node_modules/react-native-test", diff --git a/packages/cli-config/src/__tests__/findDependencies-test.ts b/packages/cli-config/src/__tests__/findDependencies-test.ts index 90a366faf..cb2af37bf 100644 --- a/packages/cli-config/src/__tests__/findDependencies-test.ts +++ b/packages/cli-config/src/__tests__/findDependencies-test.ts @@ -14,10 +14,24 @@ test('returns plugins from both dependencies and dev dependencies', () => { writeFiles(DIR, { 'package.json': ` { + "name": "plugin", + "version": "1.0.0", "dependencies": {"rnpm-plugin-test": "*"}, "devDependencies": {"rnpm-plugin-test-2": "*"} } `, + 'node_modules/rnpm-plugin-test/package.json': ` + { + "name": "rnpm-plugin-test", + "version": "1.0.0" + + }`, + 'node_modules/rnpm-plugin-test-2/package.json': ` + { + "name": "rnpm-plugin-test-2", + "version": "1.0.0" + }`, }); - expect(findDependencies(DIR)).toHaveLength(2); + + expect(findDependencies(DIR).size).toBe(3); }); diff --git a/packages/cli-config/src/__tests__/index-test.ts b/packages/cli-config/src/__tests__/index-test.ts index 104ed7de0..b7157e310 100644 --- a/packages/cli-config/src/__tests__/index-test.ts +++ b/packages/cli-config/src/__tests__/index-test.ts @@ -17,7 +17,12 @@ const androidPath = slash( ); const REACT_NATIVE_MOCK = { - 'node_modules/react-native/package.json': '{}', + 'node_modules/react-native/package.json': ` + { + "name": "react-native", + "version": "0.0.1" + } + `, 'node_modules/react-native/react-native.config.js': ` const ios = require("${iosPath}"); const android = require("${androidPath}"); @@ -59,22 +64,27 @@ beforeEach(async () => { afterEach(async () => await cleanup(DIR)); -test('should have a valid structure by default', () => { +test('should have a valid structure by default', async () => { DIR = getTempDirectory('config_test_structure'); writeFiles(DIR, { 'react-native.config.js': `module.exports = { reactNativePath: "." }`, }); - const config = loadConfig(DIR); + const config = await loadConfig(DIR); expect(removeString(config, DIR)).toMatchSnapshot(); }); -test('should return dependencies from package.json', () => { +test('should return dependencies from package.json', async () => { DIR = getTempDirectory('config_test_deps'); writeFiles(DIR, { ...REACT_NATIVE_MOCK, - 'node_modules/react-native-test/package.json': '{}', + 'node_modules/react-native-test/package.json': ` + { + "name": "react-native-test", + "version": "0.0.1" + } + `, 'node_modules/react-native-test/ReactNativeTest.podspec': '', 'package.json': `{ "dependencies": { @@ -83,15 +93,20 @@ test('should return dependencies from package.json', () => { } }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect(removeString(dependencies, DIR)).toMatchSnapshot(); }); -test('should read a config of a dependency and use it to load other settings', () => { +test('should read a config of a dependency and use it to load other settings', async () => { DIR = getTempDirectory('config_test_settings'); writeFiles(DIR, { ...REACT_NATIVE_MOCK, - 'node_modules/react-native-test/package.json': '{}', + 'node_modules/react-native-test/package.json': ` + { + "name": "react-native-test", + "version": "0.0.1" + } + `, 'node_modules/react-native-test/ReactNativeTest.podspec': '', 'node_modules/react-native-test/react-native.config.js': `module.exports = { dependency: { @@ -122,17 +137,22 @@ test('should read a config of a dependency and use it to load other settings', ( } }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect( removeString(dependencies['react-native-test'], DIR), ).toMatchSnapshot(); }); -test('should merge project configuration with default values', () => { +test('should merge project configuration with default values', async () => { DIR = getTempDirectory('config_test_merge'); writeFiles(DIR, { ...REACT_NATIVE_MOCK, - 'node_modules/react-native-test/package.json': '{}', + 'node_modules/react-native-test/package.json': ` + { + "name": "react-native-test", + "version": "0.0.1" + } + `, 'node_modules/react-native-test/ReactNativeTest.podspec': '', 'package.json': `{ "dependencies": { @@ -153,17 +173,22 @@ test('should merge project configuration with default values', () => { } }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect(removeString(dependencies['react-native-test'], DIR)).toMatchSnapshot( 'snapshoting `react-native-test` config', ); }); -test('should load commands from "react-native-foo" and "react-native-bar" packages', () => { +test('should load commands from "react-native-foo" and "react-native-bar" packages', async () => { DIR = getTempDirectory('config_test_packages'); writeFiles(DIR, { 'react-native.config.js': 'module.exports = { reactNativePath: "." }', - 'node_modules/react-native-foo/package.json': '{}', + 'node_modules/react-native-foo/package.json': ` + { + "name": "react-native-foo", + "version": "0.0.1" + } + `, 'node_modules/react-native-foo/react-native.config.js': `module.exports = { commands: [ { @@ -172,7 +197,12 @@ test('should load commands from "react-native-foo" and "react-native-bar" packag } ] }`, - 'node_modules/react-native-bar/package.json': '{}', + 'node_modules/react-native-bar/package.json': ` + { + "name": "react-native-bar", + "version": "0.0.1" + } + `, 'node_modules/react-native-bar/react-native.config.js': `module.exports = { commands: [ { @@ -188,15 +218,20 @@ test('should load commands from "react-native-foo" and "react-native-bar" packag } }`, }); - const {commands} = loadConfig(DIR); + const {commands} = await loadConfig(DIR); expect(commands).toMatchSnapshot(); }); -test('should not skip packages that have invalid configuration (to avoid breaking users)', () => { +test('should not skip packages that have invalid configuration (to avoid breaking users)', async () => { process.env.FORCE_COLOR = '0'; // To disable chalk DIR = getTempDirectory('config_test_skip'); writeFiles(DIR, { - 'node_modules/react-native/package.json': '{}', + 'node_modules/react-native/package.json': ` + { + "name": "react-native", + "version": "0.0.1" + } + `, 'node_modules/react-native/react-native.config.js': `module.exports = { dependency: { invalidProperty: 5 @@ -208,18 +243,20 @@ test('should not skip packages that have invalid configuration (to avoid breakin } }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect(removeString(dependencies, DIR)).toMatchSnapshot( 'dependencies config', ); expect(spy.mock.calls[0][0]).toMatchSnapshot('logged warning'); }); -test('does not use restricted "react-native" key to resolve config from package.json', () => { +test('does not use restricted "react-native" key to resolve config from package.json', async () => { DIR = getTempDirectory('config_test_restricted'); writeFiles(DIR, { 'node_modules/react-native-netinfo/package.json': `{ - "react-native": "src/index.js" + "react-native": "src/index.js", + "name": "react-native-netinfo", + "version": "0.0.1" }`, 'react-native.config.js': 'module.exports = { reactNativePath: "." }', 'package.json': `{ @@ -228,12 +265,13 @@ test('does not use restricted "react-native" key to resolve config from package. } }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); + expect(dependencies).toHaveProperty('react-native-netinfo'); expect(spy).not.toHaveBeenCalled(); }); -test('supports dependencies from user configuration with custom root and properties', () => { +test('supports dependencies from user configuration with custom root and properties', async () => { DIR = getTempDirectory('config_test_custom_root'); const escapePathSeparator = (value: string) => path.sep === '\\' ? value.replace(/(\/|\\)/g, '\\\\') : value; @@ -274,7 +312,7 @@ module.exports = { }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect(removeString(dependencies['local-lib'], DIR)).toMatchInlineSnapshot(` Object { "name": "local-lib", @@ -292,11 +330,14 @@ module.exports = { `); }); -test('should apply build types from dependency config', () => { +test('should apply build types from dependency config', async () => { DIR = getTempDirectory('config_test_apply_dependency_config'); writeFiles(DIR, { ...REACT_NATIVE_MOCK, - 'node_modules/react-native-test/package.json': '{}', + 'node_modules/react-native-test/package.json': `{ + "name": "react-native-test", + "version": "0.0.1" + }`, 'node_modules/react-native-test/ReactNativeTest.podspec': '', 'node_modules/react-native-test/react-native.config.js': `module.exports = { dependency: { @@ -314,13 +355,13 @@ test('should apply build types from dependency config', () => { } }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect( removeString(dependencies['react-native-test'], DIR), ).toMatchSnapshot(); }); -test('supports dependencies from user configuration with custom build type', () => { +test('supports dependencies from user configuration with custom build type', async () => { DIR = getTempDirectory('config_test_apply_custom_build_config'); writeFiles(DIR, { ...REACT_NATIVE_MOCK, @@ -335,7 +376,12 @@ test('supports dependencies from user configuration with custom build type', () }, } }`, - 'node_modules/react-native-test/package.json': '{}', + 'node_modules/react-native-test/package.json': ` + { + "name": "react-native-test", + "version": "0.0.1" + } + `, 'node_modules/react-native-test/ReactNativeTest.podspec': '', 'node_modules/react-native-test/react-native.config.js': 'module.exports = {}', @@ -347,17 +393,20 @@ test('supports dependencies from user configuration with custom build type', () }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect( removeString(dependencies['react-native-test'], DIR), ).toMatchSnapshot(); }); -test('supports disabling dependency for ios platform', () => { +test('supports disabling dependency for ios platform', async () => { DIR = getTempDirectory('config_test_disable_dependency_platform'); writeFiles(DIR, { ...REACT_NATIVE_MOCK, - 'node_modules/react-native-test/package.json': '{}', + 'node_modules/react-native-test/package.json': `{ + "name": "react-native-test", + "version": "0.0.1" + }`, 'node_modules/react-native-test/ReactNativeTest.podspec': '', 'node_modules/react-native-test/react-native.config.js': ` module.exports = { @@ -376,13 +425,13 @@ test('supports disabling dependency for ios platform', () => { }`, }); - const {dependencies} = loadConfig(DIR); + const {dependencies} = await loadConfig(DIR); expect( removeString(dependencies['react-native-test'], DIR), ).toMatchSnapshot(); }); -test('should convert project sourceDir relative path to absolute', () => { +test('should convert project sourceDir relative path to absolute', async () => { DIR = getTempDirectory('config_test_absolute_project_source_dir'); const iosProjectDir = './ios2'; const androidProjectDir = './android2'; @@ -441,7 +490,7 @@ test('should convert project sourceDir relative path to absolute', () => { `, }); - const config = loadConfig(DIR); + const config = await loadConfig(DIR); expect(config.project.ios?.sourceDir).toBe(path.join(DIR, iosProjectDir)); expect(config.project.android?.sourceDir).toBe( diff --git a/packages/cli-config/src/findDependencies.ts b/packages/cli-config/src/findDependencies.ts index cb0f5ecd0..e05114355 100644 --- a/packages/cli-config/src/findDependencies.ts +++ b/packages/cli-config/src/findDependencies.ts @@ -1,24 +1,49 @@ +import {DependencyMap} from '@react-native-community/cli-types'; +import {findDependencyPath} from '@react-native-community/cli-tools'; import path from 'path'; import fs from 'fs'; /** * Returns an array of dependencies from project's package.json */ -export default function findDependencies(root: string): Array { - let pjson; +export default function findDependencies(root: string): DependencyMap { + const dependencies: DependencyMap = new Map(); + + const checkDependency = (dependencyPath: string) => { + let pjson: {[key: string]: any}; + + const packageJsonPath = path.join(dependencyPath, 'package.json'); + + if (!fs.existsSync(packageJsonPath)) { + return; + } - try { pjson = JSON.parse( - fs.readFileSync(path.join(root, 'package.json'), 'utf8'), + fs.readFileSync(path.join(dependencyPath, 'package.json'), 'utf8'), ); - } catch (e) { - return []; - } - const deps = [ - ...Object.keys(pjson.dependencies || {}), - ...Object.keys(pjson.devDependencies || {}), - ]; + if (dependencies.has(pjson.name)) { + return; + } + + dependencies.set(pjson.name, { + version: pjson.version, + peerDependencies: pjson.peerDependencies || {}, + path: dependencyPath, + }); + + for (const dependency in { + ...pjson.dependencies, + ...pjson.devDependencies, + }) { + const depPath = findDependencyPath(dependency, root, dependencyPath); + if (depPath) { + checkDependency(depPath); + } + } + }; + + checkDependency(root); - return deps; + return dependencies; } diff --git a/packages/cli-config/src/loadConfig.ts b/packages/cli-config/src/loadConfig.ts index ffd3533de..66af3d4bf 100644 --- a/packages/cli-config/src/loadConfig.ts +++ b/packages/cli-config/src/loadConfig.ts @@ -11,6 +11,7 @@ import { version, resolveNodeModuleDir, UnknownProjectError, + resolveTransitiveDeps, } from '@react-native-community/cli-tools'; import findDependencies from './findDependencies'; import resolveReactNativePath from './resolveReactNativePath'; @@ -75,7 +76,9 @@ function getReactNativeVersion(reactNativePath: string) { /** * Loads CLI configuration */ -function loadConfig(projectRoot: string = findProjectRoot()): Config { +async function loadConfig( + projectRoot: string = findProjectRoot(), +): Promise { let lazyProject: ProjectConfig; const userConfig = readConfigFromDisk(projectRoot); @@ -113,11 +116,22 @@ function loadConfig(projectRoot: string = findProjectRoot()): Config { }, }; + const dependencyMap = findDependencies(projectRoot); + let dependencies = Array.from(dependencyMap.keys()); + + if ( + userConfig.unstable_autolinkPeerDependencies && + !process.argv.includes('config') + ) { + const installedDependencies = await resolveTransitiveDeps( + projectRoot, + dependencyMap, + ); + dependencies = [...dependencies, ...installedDependencies]; + } + const finalConfig = Array.from( - new Set([ - ...Object.keys(userConfig.dependencies), - ...findDependencies(projectRoot), - ]), + new Set([...Object.keys(userConfig.dependencies), ...dependencies]), ).reduce((acc: Config, dependencyName) => { const localDependencyRoot = userConfig.dependencies[dependencyName] && diff --git a/packages/cli-config/src/schema.ts b/packages/cli-config/src/schema.ts index ee4ed7074..91c759633 100644 --- a/packages/cli-config/src/schema.ts +++ b/packages/cli-config/src/schema.ts @@ -186,6 +186,7 @@ export const projectConfig = t linkConfig: t.func(), }), ).default({}), + unstable_autolinkPeerDependencies: t.bool(), }) .unknown(true) .default(); diff --git a/packages/cli-doctor/src/commands/doctor.ts b/packages/cli-doctor/src/commands/doctor.ts index 7271aa1f6..667999456 100644 --- a/packages/cli-doctor/src/commands/doctor.ts +++ b/packages/cli-doctor/src/commands/doctor.ts @@ -175,7 +175,7 @@ const doctorCommand = (async (_, options, config) => { Promise.all(categories.map(iterateOverHealthChecks)); const healthchecksPerCategory = await iterateOverCategories( - Object.values(getHealthchecks(options)).filter( + Object.values(await getHealthchecks(options)).filter( (category) => category !== undefined, ) as HealthCheckCategory[], ); diff --git a/packages/cli-doctor/src/tools/healthchecks/index.ts b/packages/cli-doctor/src/tools/healthchecks/index.ts index cd8b8fb37..8bca0c348 100644 --- a/packages/cli-doctor/src/tools/healthchecks/index.ts +++ b/packages/cli-doctor/src/tools/healthchecks/index.ts @@ -1,3 +1,5 @@ +import loadConfig from '@react-native-community/cli-config'; +import {logger} from '@react-native-community/cli-tools'; import nodeJS from './nodeJS'; import {yarn, npm} from './packageManagers'; import adb from './adb'; @@ -12,11 +14,9 @@ import xcode from './xcode'; import cocoaPods from './cocoaPods'; import iosDeploy from './iosDeploy'; import {Healthchecks, HealthCheckCategory} from '../../types'; -import loadConfig from '@react-native-community/cli-config'; import xcodeEnv from './xcodeEnv'; import packager from './packager'; import deepmerge from 'deepmerge'; -import {logger} from '@react-native-community/cli-tools'; export const HEALTHCHECK_TYPES = { ERROR: 'ERROR', @@ -28,14 +28,16 @@ type Options = { contributor: boolean | void; }; -export const getHealthchecks = ({contributor}: Options): Healthchecks => { +export const getHealthchecks = async ({ + contributor, +}: Options): Promise => { let additionalChecks: HealthCheckCategory[] = []; let projectSpecificHealthchecks = {}; let config; // Doctor can run in a detached mode, where there isn't a config so this can fail try { - config = loadConfig(); + config = await loadConfig(); additionalChecks = config.healthChecks; if (config.reactNativePath) { diff --git a/packages/cli/src/tools/__tests__/packageManager-test.ts b/packages/cli-tools/src/__tests__/packageManager-test.ts similarity index 99% rename from packages/cli/src/tools/__tests__/packageManager-test.ts rename to packages/cli-tools/src/__tests__/packageManager-test.ts index 78c31a652..03a3d2b27 100644 --- a/packages/cli/src/tools/__tests__/packageManager-test.ts +++ b/packages/cli-tools/src/__tests__/packageManager-test.ts @@ -2,7 +2,7 @@ jest.mock('execa', () => jest.fn()); import execa from 'execa'; import * as yarn from '../yarn'; import * as bun from '../bun'; -import {logger} from '@react-native-community/cli-tools'; +import logger from '../logger'; import * as PackageManager from '../packageManager'; const PACKAGES = ['react', 'react-native']; diff --git a/packages/cli-tools/src/__tests__/resolveTransitiveDeps.test.ts b/packages/cli-tools/src/__tests__/resolveTransitiveDeps.test.ts new file mode 100644 index 000000000..7696efda1 --- /dev/null +++ b/packages/cli-tools/src/__tests__/resolveTransitiveDeps.test.ts @@ -0,0 +1,241 @@ +import path from 'path'; +import prompts from 'prompts'; +import {cleanup, getTempDirectory, writeFiles} from '../../../../jest/helpers'; +import { + calculateWorkingVersion, + filterInstalledPeers, + filterNativeDependencies, + findDependencyPath, + getMissingPeerDeps, + resolveTransitiveDeps, +} from '../resolveTransitiveDeps'; +import logger from '../logger'; +import findDependencies from '../../../cli-config/src/findDependencies'; + +jest.mock('execa', () => jest.fn()); +jest.mock('prompts', () => ({prompt: jest.fn()})); + +jest.mock('../logger', () => ({ + isVerbose: jest.fn(), + warn: jest.fn(), + log: jest.fn(), +})); + +const mockFetchJson = jest.fn(); + +jest.mock('npm-registry-fetch', () => ({ + json: mockFetchJson, +})); + +const rootPackageJson = { + name: 'App', + version: '1.0.0', + dependencies: { + 'react-native': '0.72.4', + '@react-navigation/stack': '^6.3.17', + }, +}; + +const stackPackageJson = { + name: '@react-navigation/stack', + version: '6.3.17', + dependencies: { + '@react-navigation/elements': '^1.3.18', + 'react-native-gesture-handler': '^1.10.3', + }, + peerDependencies: { + react: '*', + 'react-native': '*', + 'react-native-gesture-handler': '>= 1.0.0', + }, +}; + +const elementsPackageJson = { + name: '@react-navigation/elements', + version: '1.3.18', + peerDependencies: { + react: '*', + 'react-native': '*', + 'react-native-safe-area-view': '*', + }, +}; + +const gestureHandlerPackageJson = { + name: 'react-native-gesture-handler', + version: '2.1.0', +}; + +const DIR = getTempDirectory('root_test'); + +const createTempFiles = (rest?: Record) => { + writeFiles(DIR, { + 'package.json': JSON.stringify(rootPackageJson), + 'node_modules/@react-navigation/stack/package.json': JSON.stringify( + stackPackageJson, + ), + 'node_modules/@react-navigation/elements/package.json': JSON.stringify( + elementsPackageJson, + ), + 'node_modules/react-native-gesture-handler/package.json': JSON.stringify( + gestureHandlerPackageJson, + ), + 'node_modules/react-native-gesture-handler/ios/Podfile': '', + ...rest, + }); +}; + +beforeEach(async () => { + await cleanup(DIR); + jest.resetAllMocks(); +}); + +describe('calculateWorkingVersion', () => { + it('should return the highest matching version for all ranges', () => { + const workingVersion = calculateWorkingVersion( + ['*', '>=2.2.0', '>=2.0.0'], + ['1.9.0', '2.0.0', '2.2.0', '3.0.0'], + ); + + expect(workingVersion).toBe('3.0.0'); + }); + + it('should return null if no version matches all ranges', () => { + const workingVersion = calculateWorkingVersion( + ['*', '>=2.2.0', '^1.0.0-alpha'], + ['1.9.0', '2.0.0', '2.1.0'], + ); + + expect(workingVersion).toBe(null); + }); +}); + +describe('findDependencyPath', () => { + it('should return the path to the dependency if it is in top-level node_modules', () => { + writeFiles(DIR, { + 'package.json': JSON.stringify(rootPackageJson), + 'node_modules/@react-navigation/stack/package.json': JSON.stringify( + stackPackageJson, + ), + }); + + const dependencyPath = findDependencyPath( + '@react-navigation/stack', + DIR, + path.join(DIR, 'node_modules', '@react-navigation/stack'), + ); + + expect(dependencyPath).toBe( + path.join(DIR, 'node_modules', '@react-navigation/stack'), + ); + }); + + it('should return the path to the nested node_modules if package is installed here', () => { + writeFiles(DIR, { + 'package.json': JSON.stringify(rootPackageJson), + 'node_modules/@react-navigation/stack/node_modules/react-native-gesture-handler/package.json': + '{}', + }); + + const dependencyPath = findDependencyPath( + 'react-native-gesture-handler', + DIR, + path.join(DIR, 'node_modules', '@react-navigation/stack'), + ); + + expect(dependencyPath).toBe( + path.join( + DIR, + 'node_modules', + '@react-navigation/stack', + 'node_modules', + 'react-native-gesture-handler', + ), + ); + }); +}); + +describe('filterNativeDependencies', () => { + it('should return only dependencies with peer dependencies containing native code', () => { + createTempFiles({ + 'node_modules/react-native-safe-area-view/ios/Podfile': '{}', + }); + const dependencies = findDependencies(DIR); + const filtered = filterNativeDependencies(DIR, dependencies); + expect(filtered.keys()).toContain('@react-navigation/elements'); + }); +}); + +describe('filterInstalledPeers', () => { + it('should return only dependencies with peer dependencies that are installed', () => { + createTempFiles(); + const dependencies = findDependencies(DIR); + const libsWithNativeDeps = filterNativeDependencies(DIR, dependencies); + const nonInstalledPeers = filterInstalledPeers(DIR, libsWithNativeDeps); + + expect(Object.keys(nonInstalledPeers)).toContain('@react-navigation/stack'); + expect(Object.keys(nonInstalledPeers['@react-navigation/stack'])).toContain( + 'react-native-gesture-handler', + ); + }); +}); + +describe('getMissingPeerDepsForYarn', () => { + it('should return an array of peer dependencies to install', () => { + createTempFiles(); + const dependencies = findDependencies(DIR); + const missingDeps = getMissingPeerDeps(DIR, dependencies); + expect(missingDeps.values()).toContain('react'); + expect(missingDeps.values()).toContain('react-native-gesture-handler'); + expect(missingDeps.values()).toContain('react-native-safe-area-view'); + }); +}); + +describe('resolveTransitiveDeps', () => { + it('should display list of missing peer dependencies if there are any', async () => { + createTempFiles(); + prompts.prompt.mockReturnValue({}); + const dependencies = findDependencies(DIR); + await resolveTransitiveDeps(DIR, dependencies); + expect(logger.warn).toHaveBeenCalledWith( + 'Looks like you are missing some of the peer dependencies of your libraries:\n', + ); + }); + + it('should not display list if there are no missing peer dependencies', async () => { + writeFiles(DIR, { + 'package.json': JSON.stringify(rootPackageJson), + }); + const dependencies = findDependencies(DIR); + await resolveTransitiveDeps(DIR, dependencies); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + it('should prompt user to install missing peer dependencies', async () => { + createTempFiles(); + prompts.prompt.mockReturnValue({}); + const dependencies = findDependencies(DIR); + await resolveTransitiveDeps(DIR, dependencies); + expect(prompts.prompt).toHaveBeenCalledWith({ + type: 'confirm', + name: 'installDependencies', + message: + 'Do you want to install them now? The matching versions will be added as project dependencies and become visible for autolinking.', + }); + }); + + it('should install missing peer dependencies if user confirms', async () => { + createTempFiles(); + const dependencies = findDependencies(DIR); + prompts.prompt.mockReturnValue({installDependencies: true}); + mockFetchJson.mockReturnValueOnce({ + versions: { + '2.0.0': {}, + '2.1.0': {}, + }, + }); + + const resolveDeps = await resolveTransitiveDeps(DIR, dependencies); + + expect(resolveDeps).toEqual(['react-native-gesture-handler@^2.1.0']); + }); +}); diff --git a/packages/cli/src/tools/bun.ts b/packages/cli-tools/src/bun.ts similarity index 91% rename from packages/cli/src/tools/bun.ts rename to packages/cli-tools/src/bun.ts index 121be7515..081e6b6eb 100644 --- a/packages/cli/src/tools/bun.ts +++ b/packages/cli-tools/src/bun.ts @@ -1,7 +1,7 @@ -import {logger} from '@react-native-community/cli-tools'; import {execSync} from 'child_process'; import findUp from 'find-up'; import semver from 'semver'; +import logger from './logger'; export function getBunVersionIfAvailable() { let bunVersion; diff --git a/packages/cli-tools/src/generateFileHash.ts b/packages/cli-tools/src/generateFileHash.ts new file mode 100644 index 000000000..3cf26760c --- /dev/null +++ b/packages/cli-tools/src/generateFileHash.ts @@ -0,0 +1,14 @@ +import fs from 'fs-extra'; +import {createHash} from 'crypto'; +import {CLIError} from './errors'; + +export default function generateFileHash(filePath: string) { + try { + const file = fs.readFileSync(filePath, {encoding: 'utf8'}); + const hash = createHash('md5').update(file).digest('hex'); + + return hash; + } catch { + throw new CLIError('Failed to generate file hash.'); + } +} diff --git a/packages/cli-tools/src/index.ts b/packages/cli-tools/src/index.ts index d0e1b92ba..b77a52b43 100644 --- a/packages/cli-tools/src/index.ts +++ b/packages/cli-tools/src/index.ts @@ -17,5 +17,10 @@ export {default as handlePortUnavailable} from './handlePortUnavailable'; export * from './port'; export {default as cacheManager} from './cacheManager'; export {default as runSudo} from './runSudo'; +export * from './resolveTransitiveDeps'; +export * from './npm'; +export * from './bun'; +export * from './yarn'; +export * as PackageManager from './packageManager'; export * from './errors'; diff --git a/packages/cli/src/tools/npm.ts b/packages/cli-tools/src/npm.ts similarity index 100% rename from packages/cli/src/tools/npm.ts rename to packages/cli-tools/src/npm.ts diff --git a/packages/cli/src/tools/packageManager.ts b/packages/cli-tools/src/packageManager.ts similarity index 93% rename from packages/cli/src/tools/packageManager.ts rename to packages/cli-tools/src/packageManager.ts index b465f1858..776f56f60 100644 --- a/packages/cli/src/tools/packageManager.ts +++ b/packages/cli-tools/src/packageManager.ts @@ -1,8 +1,8 @@ import execa from 'execa'; -import {logger} from '@react-native-community/cli-tools'; import {getYarnVersionIfAvailable, isProjectUsingYarn} from './yarn'; import {getBunVersionIfAvailable, isProjectUsingBun} from './bun'; import {getNpmVersionIfAvailable, isProjectUsingNpm} from './npm'; +import logger from './logger'; export type PackageManager = keyof typeof packageManagers; @@ -85,6 +85,17 @@ export function shouldUseNpm(options: Options) { return isProjectUsingNpm(options.root) && getNpmVersionIfAvailable(); } +export function getProjectPackageManager(root: string) { + if (isProjectUsingYarn(root)) { + return 'yarn'; + } + if (isProjectUsingBun(root)) { + return 'bun'; + } + + return 'npm'; +} + export function init(options: Options) { return configurePackageManager([], 'init', options); } diff --git a/packages/cli-tools/src/resolveTransitiveDeps.ts b/packages/cli-tools/src/resolveTransitiveDeps.ts new file mode 100644 index 000000000..d3b0ce829 --- /dev/null +++ b/packages/cli-tools/src/resolveTransitiveDeps.ts @@ -0,0 +1,310 @@ +import fs from 'fs-extra'; +import path from 'path'; +import * as npmRegistryFetch from 'npm-registry-fetch'; +import chalk from 'chalk'; +import {prompt} from 'prompts'; +import execa from 'execa'; +import semver from 'semver'; +import {DependencyMap} from '@react-native-community/cli-types'; +import {getLoader} from './loader'; +import logger from './logger'; +import { + PackageManager, + getProjectPackageManager, + install, +} from './packageManager'; + +export async function fetchAvailableVersions( + packageName: string, +): Promise { + const response = await npmRegistryFetch.json(`/${packageName}`); + + return Object.keys(response.versions || {}); +} + +export function calculateWorkingVersion( + ranges: string[], + availableVersions: string[], +): string | null { + const sortedVersions = availableVersions + .filter((version) => + ranges.every((range) => semver.satisfies(version, range)), + ) + .sort(semver.rcompare); + + return sortedVersions.length > 0 ? sortedVersions[0] : null; +} + +export function findDependencyPath( + dependencyName: string, + rootPath: string, + parentPath: string, +) { + let dependencyPath; + const topLevelPath = path.join(rootPath, 'node_modules', dependencyName); + const nestedPath = path.join(parentPath, 'node_modules', dependencyName); + + if (fs.existsSync(topLevelPath)) { + dependencyPath = topLevelPath; + } else if (fs.existsSync(nestedPath)) { + dependencyPath = nestedPath; + } + + return dependencyPath; +} + +export function filterNativeDependencies( + root: string, + dependencies: DependencyMap, +) { + const depsWithNativePeers = new Map>(); + + dependencies.forEach((value, key) => { + if (value.peerDependencies) { + const nativeDependencies = new Map(); + + Object.entries(value.peerDependencies).forEach(([name, versions]) => { + const dependencyPath = findDependencyPath(name, root, value.path); + + if (dependencyPath) { + const iosPath = path.join(dependencyPath, 'ios'); + const androidPath = path.join(dependencyPath, 'android'); + + if (fs.existsSync(iosPath) || fs.existsSync(androidPath)) { + nativeDependencies.set(name, versions); + } + } + }); + + if (nativeDependencies.size > 0) { + depsWithNativePeers.set(key, nativeDependencies); + } + } + }); + + return depsWithNativePeers; +} + +export function filterInstalledPeers( + root: string, + peers: Map>, +) { + const data: Record> = {}; + const packageJsonPath = path.join(root, 'package.json'); + const packageJson = require(packageJsonPath); + const dependencyList = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + }; + peers.forEach((peerDependencies, dependency) => { + peerDependencies.forEach((version, name) => { + if (!Object.keys(dependencyList).includes(name)) { + data[dependency] = { + ...data[dependency], + [name]: version, + }; + } + }); + }); + + return data; +} + +export function findPeerDepsToInstall( + root: string, + dependencies: DependencyMap, +) { + const rootPackageJson = require(path.join(root, 'package.json')); + const dependencyList = { + ...rootPackageJson.dependencies, + ...rootPackageJson.devDependencies, + }; + const peerDependencies = new Set(); + Array.from(dependencies.entries()).forEach(([_, value]) => { + if (value.peerDependencies) { + Object.keys(value.peerDependencies).forEach((name) => { + if (!Object.keys(dependencyList).includes(name)) { + peerDependencies.add(name); + } + }); + } + }); + + return peerDependencies; +} + +export function getMissingPeerDeps(root: string, dependencies: DependencyMap) { + const depsToInstall = findPeerDepsToInstall(root, dependencies); + return depsToInstall; +} + +// install peer deps with yarn/bun without making any changes to package.json and yarn.lock/bun.lockb +export function silentInstallPeerDeps( + root: string, + missingPeerDependencies: DependencyMap, + pkgManager: PackageManager, +) { + const dependenciesToInstall = getMissingPeerDeps( + root, + missingPeerDependencies, + ); + const lockfile = pkgManager === 'yarn' ? 'yarn.lock' : 'bun.lockb'; + + const packageJsonPath = path.join(root, 'package.json'); + const lockfilePath = path.join(root, lockfile); + + if (dependenciesToInstall.size > 0) { + const binPackageJson = fs.readFileSync(packageJsonPath, { + encoding: 'utf8', + }); + const binLockfile = fs.readFileSync(lockfilePath, { + encoding: 'utf8', + }); + + if (!binPackageJson) { + logger.error('package.json is missing'); + return; + } + + if (!binLockfile) { + logger.error(`${lockfile} is missing`); + return; + } + const loader = getLoader({text: 'Looking for peer dependencies...'}); + + loader.start(); + try { + execa.sync(pkgManager, ['add', ...dependenciesToInstall]); + loader.succeed(); + } catch { + loader.fail('Failed to verify peer dependencies'); + return; + } + + fs.writeFileSync(packageJsonPath, binPackageJson, {encoding: 'utf8'}); + fs.writeFileSync(lockfilePath, binLockfile, {encoding: 'utf8'}); + } +} + +export async function promptForMissingPeerDependencies( + dependencies: Record>, +): Promise { + logger.warn( + 'Looks like you are missing some of the peer dependencies of your libraries:\n', + ); + logger.log( + Object.entries(dependencies) + .map( + ([dependencyName, peerDependencies]) => + `\t${chalk.bold(dependencyName)}:\n ${Object.entries( + peerDependencies, + ).map( + ([peerDependency, peerDependencyVersion]) => + `\t- ${peerDependency} ${peerDependencyVersion}\n`, + )}`, + ) + .join('\n') + .replace(/,/g, ''), + ); + + const {installDependencies} = await prompt({ + type: 'confirm', + name: 'installDependencies', + message: + 'Do you want to install them now? The matching versions will be added as project dependencies and become visible for autolinking.', + }); + + return installDependencies; +} + +export async function getPackagesVersion( + missingDependencies: Record>, +) { + const packageToRanges: {[pkg: string]: string[]} = {}; + + for (const dependency in missingDependencies) { + const packages = missingDependencies[dependency]; + + for (const packageName in packages) { + if (!packageToRanges[packageName]) { + packageToRanges[packageName] = []; + } + packageToRanges[packageName].push(packages[packageName]); + } + } + + const workingVersions: {[pkg: string]: string | null} = {}; + + for (const packageName in packageToRanges) { + const ranges = packageToRanges[packageName]; + const availableVersions = await fetchAvailableVersions(packageName); + const workingVersion = calculateWorkingVersion(ranges, availableVersions); + + if (workingVersion !== null) { + workingVersions[packageName] = workingVersion; + } else { + logger.warn( + `Could not find a version that matches all ranges for ${chalk.bold( + packageName, + )}. Please resolve this issue manually.`, + ); + } + } + + return workingVersions; +} + +export async function installMissingPackages( + packages: Record, + pkgManager: PackageManager, +) { + const packageVersions = Object.entries(packages).map( + ([name, version]) => `${name}@^${version}`, + ); + const flattenList = ([] as string[]).concat(...packageVersions); + + const loader = getLoader({text: 'Installing peer dependencies...'}); + loader.start(); + + try { + const deps = flattenList.map((dep) => dep); + install(deps, { + packageManager: pkgManager, + root: process.cwd(), + silent: true, + }); + loader.succeed(); + + return deps; + } catch (error) { + loader.fail(); + + return []; + } +} + +export async function resolveTransitiveDeps( + root: string, + dependencyMap: DependencyMap, +) { + const packageManager = getProjectPackageManager(root); + + if (packageManager !== 'npm') { + silentInstallPeerDeps(root, dependencyMap, packageManager); + } + const nonEmptyPeers = filterNativeDependencies(root, dependencyMap); + const nonInstalledPeers = filterInstalledPeers(root, nonEmptyPeers); + if (Object.keys(nonInstalledPeers).length > 0) { + const installDeps = await promptForMissingPeerDependencies( + nonInstalledPeers, + ); + + if (installDeps) { + const packagesVersions = await getPackagesVersion(nonInstalledPeers); + + return installMissingPackages(packagesVersions, packageManager); + } + } + + return []; +} diff --git a/packages/cli/src/tools/yarn.ts b/packages/cli-tools/src/yarn.ts similarity index 95% rename from packages/cli/src/tools/yarn.ts rename to packages/cli-tools/src/yarn.ts index 45a8f463a..178184d20 100644 --- a/packages/cli/src/tools/yarn.ts +++ b/packages/cli-tools/src/yarn.ts @@ -8,8 +8,8 @@ import {execSync} from 'child_process'; import semver from 'semver'; -import {logger} from '@react-native-community/cli-tools'; import findUp from 'find-up'; +import logger from './logger'; /** * Use Yarn if available, it's much faster than the npm client. diff --git a/packages/cli-types/src/index.ts b/packages/cli-types/src/index.ts index b20e4378a..f8cfc4a64 100644 --- a/packages/cli-types/src/index.ts +++ b/packages/cli-types/src/index.ts @@ -135,6 +135,7 @@ export type UserConfig = Omit & { ios?: IOSProjectParams; [key: string]: any; }; + unstable_autolinkPeerDependencies?: boolean; }; export type UserDependencyConfig = { @@ -148,6 +149,15 @@ export type UserDependencyConfig = { healthChecks: []; }; +export type DependencyMap = Map< + string, + { + version: string; + peerDependencies: Record; + path: string; + } +>; + export { IOSProjectConfig, IOSProjectParams, diff --git a/packages/cli/package.json b/packages/cli/package.json index b3d934ff5..5e337a201 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,6 +39,7 @@ "find-up": "^4.1.0", "fs-extra": "^8.1.0", "graceful-fs": "^4.1.3", + "npm-registry-fetch": "16.0.0", "prompts": "^2.4.2", "semver": "^7.5.2" }, @@ -46,6 +47,7 @@ "@types/fs-extra": "^8.1.0", "@types/graceful-fs": "^4.1.3", "@types/hapi__joi": "^17.1.6", + "@types/npm-registry-fetch": "8.0.4", "@types/prompts": "^2.4.4", "@types/semver": "^6.0.2", "deepmerge": "^4.3.0", diff --git a/packages/cli/src/commands/init/__tests__/template.test.ts b/packages/cli/src/commands/init/__tests__/template.test.ts index f30e51dc2..b2369bc5c 100644 --- a/packages/cli/src/commands/init/__tests__/template.test.ts +++ b/packages/cli/src/commands/init/__tests__/template.test.ts @@ -2,7 +2,7 @@ jest.mock('execa', () => jest.fn()); import execa from 'execa'; import path from 'path'; import fs from 'fs'; -import * as PackageManger from '../../../tools/packageManager'; +import {PackageManager} from '@react-native-community/cli-tools'; import { installTemplatePackage, getTemplateConfig, @@ -20,11 +20,11 @@ afterEach(() => { }); test('installTemplatePackage', async () => { - jest.spyOn(PackageManger, 'install').mockImplementationOnce(() => null); + jest.spyOn(PackageManager, 'install').mockImplementationOnce(jest.fn()); await installTemplatePackage(TEMPLATE_NAME, TEMPLATE_SOURCE_DIR, 'npm'); - expect(PackageManger.install).toHaveBeenCalledWith([TEMPLATE_NAME], { + expect(PackageManager.install).toHaveBeenCalledWith([TEMPLATE_NAME], { packageManager: 'npm', silent: true, root: TEMPLATE_SOURCE_DIR, diff --git a/packages/cli/src/commands/init/init.ts b/packages/cli/src/commands/init/init.ts index b6351cb19..a09dd5be9 100644 --- a/packages/cli/src/commands/init/init.ts +++ b/packages/cli/src/commands/init/init.ts @@ -12,6 +12,10 @@ import { getLoader, Loader, cacheManager, + getBunVersionIfAvailable, + getNpmVersionIfAvailable, + getYarnVersionIfAvailable, + PackageManager, } from '@react-native-community/cli-tools'; import {installPods} from '@react-native-community/cli-platform-ios'; import { @@ -21,12 +25,8 @@ import { executePostInitScript, } from './template'; import {changePlaceholderInTemplate} from './editTemplate'; -import * as PackageManager from '../../tools/packageManager'; import banner from './banner'; import TemplateAndVersionError from './errors/TemplateAndVersionError'; -import {getBunVersionIfAvailable} from '../../tools/bun'; -import {getNpmVersionIfAvailable} from '../../tools/npm'; -import {getYarnVersionIfAvailable} from '../../tools/yarn'; import {createHash} from 'crypto'; const DEFAULT_VERSION = 'latest'; diff --git a/packages/cli/src/commands/init/template.ts b/packages/cli/src/commands/init/template.ts index c5966fe82..b7333ed00 100644 --- a/packages/cli/src/commands/init/template.ts +++ b/packages/cli/src/commands/init/template.ts @@ -1,7 +1,10 @@ import execa from 'execa'; import path from 'path'; -import {logger, CLIError} from '@react-native-community/cli-tools'; -import * as PackageManager from '../../tools/packageManager'; +import { + logger, + CLIError, + PackageManager, +} from '@react-native-community/cli-tools'; import copyFiles from '../../tools/copyFiles'; import replacePathSepForRegex from '../../tools/replacePathSepForRegex'; import fs from 'fs'; @@ -21,7 +24,7 @@ export async function installTemplatePackage( ) { logger.debug(`Installing template from ${templateName}`); - await PackageManager.init({ + PackageManager.init({ packageManager, silent: true, root, diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index de74e82df..5d97a3856 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -174,7 +174,7 @@ async function setupAndRun() { let config: Config | undefined; try { - config = loadConfig(); + config = await loadConfig(); logger.enable(); diff --git a/yarn.lock b/yarn.lock index c2e043e05..42cdd7ad0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1629,6 +1629,14 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@npmcli/agent@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@npmcli/agent/-/agent-2.0.0.tgz#d8c4246c30c1ab55bb02970433acfeba85906ee7" + integrity sha512-RpRbD6PnaQIUl+p8MoH7sl2CHyMofCO0abOV+0VulqKW84+0nRWnj0bYFQELTN5HpNvzWAV8pRN6Fjx9ZLOS0g== + dependencies: + lru-cache "^10.0.1" + socks "^2.7.1" + "@npmcli/fs@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@npmcli/fs/-/fs-3.1.0.tgz#233d43a25a91d68c3a863ba0da6a3f00924a173e" @@ -2230,6 +2238,14 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.0.tgz#69a23a3ad29caf0097f06eda59b361ee2f0639f6" integrity sha1-aaI6OtKcrwCX8G7aWbNh7i8GOfY= +"@types/node-fetch@*": + version "2.6.4" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.4.tgz#1bc3a26de814f6bf466b25aeb1473fa1afe6a660" + integrity sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg== + dependencies: + "@types/node" "*" + form-data "^3.0.0" + "@types/node-fetch@^2.3.7", "@types/node-fetch@^2.5.5": version "2.5.5" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.5.tgz#cd264e20a81f4600a6c52864d38e7fef72485e92" @@ -2248,6 +2264,27 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/npm-package-arg@*": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/npm-package-arg/-/npm-package-arg-6.1.1.tgz#9e2d8adc04d39824a3d9f36f738010a3f7da3c1a" + integrity sha512-452/1Kp9IdM/oR10AyqAgZOxUt7eLbm+EMJ194L6oarMYdZNiFIFAOJ7IIr0OrZXTySgfHjJezh2oiyk2kc3ag== + +"@types/npm-registry-fetch@8.0.4": + version "8.0.4" + resolved "https://registry.yarnpkg.com/@types/npm-registry-fetch/-/npm-registry-fetch-8.0.4.tgz#77b2737cde22314ccda1dfdb9568fd7769e95b90" + integrity sha512-R9yEj6+NDmXLpKNS19cIaMyaHfV0aHjy/1qbo8K9jiHyjyaYg0CEmuOV/L0Q91DZDi3SuxlYY+2XYwh9TbB+eQ== + dependencies: + "@types/node" "*" + "@types/node-fetch" "*" + "@types/npm-package-arg" "*" + "@types/npmlog" "*" + "@types/ssri" "*" + +"@types/npmlog@*": + version "4.1.4" + resolved "https://registry.yarnpkg.com/@types/npmlog/-/npmlog-4.1.4.tgz#30eb872153c7ead3e8688c476054ddca004115f6" + integrity sha512-WKG4gTr8przEZBiJ5r3s8ZIAoMXNbOgQ+j/d5O4X3x6kZJRLNvyUJuUK/KoG3+8BaOHPhp2m7WC6JKKeovDSzQ== + "@types/prettier@^1.19.0": version "1.19.1" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" @@ -2315,6 +2352,13 @@ resolved "https://registry.yarnpkg.com/@types/shell-quote/-/shell-quote-1.7.1.tgz#2d059091214a02c29f003f591032172b2aff77e8" integrity sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw== +"@types/ssri@*": + version "7.1.1" + resolved "https://registry.yarnpkg.com/@types/ssri/-/ssri-7.1.1.tgz#2a2c94abf0d3a8c3b07bb4ff08142dd571407bb5" + integrity sha512-DPP/jkDaqGiyU75MyMURxLWyYLwKSjnAuGe9ZCsLp9QZOpXmDfuevk769F0BS86TmRuD5krnp06qw9nSoNO+0g== + dependencies: + "@types/node" "*" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -3295,6 +3339,24 @@ cacache@^17.0.0: tar "^6.1.11" unique-filename "^3.0.0" +cacache@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-18.0.0.tgz#17a9ecd6e1be2564ebe6cdca5f7cfed2bfeb6ddc" + integrity sha512-I7mVOPl3PUCeRub1U8YoGz2Lqv9WOBpobZ8RyWFXmReuILz+3OAyTa5oH3QPdtKZD7N0Yk00aLfzn0qvp8dZ1w== + dependencies: + "@npmcli/fs" "^3.1.0" + fs-minipass "^3.0.0" + glob "^10.2.2" + lru-cache "^10.0.1" + minipass "^7.0.3" + minipass-collect "^1.0.2" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + p-map "^4.0.0" + ssri "^10.0.0" + tar "^6.1.11" + unique-filename "^3.0.0" + cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -6018,6 +6080,13 @@ hosted-git-info@^6.0.0: dependencies: lru-cache "^7.5.1" +hosted-git-info@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-7.0.0.tgz#276330b8ad9f4566e82c8ccb16050decc096076b" + integrity sha512-ICclEpTLhHj+zCuSb2/usoNXSVkxUSIopre+b1w8NDY9Dntp9LO4vLdHYI336TH8sAqwrRgnSfdkBG2/YpisHA== + dependencies: + lru-cache "^10.0.1" + hsl-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e" @@ -7936,6 +8005,11 @@ loose-envify@^1.0.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.0.1.tgz#0a3be479df549cca0e5d693ac402ff19537a6b7a" + integrity sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g== + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -8003,6 +8077,23 @@ make-fetch-happen@^11.0.0, make-fetch-happen@^11.0.1, make-fetch-happen@^11.0.3, socks-proxy-agent "^7.0.0" ssri "^10.0.0" +make-fetch-happen@^13.0.0: + version "13.0.0" + resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-13.0.0.tgz#705d6f6cbd7faecb8eac2432f551e49475bfedf0" + integrity sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A== + dependencies: + "@npmcli/agent" "^2.0.0" + cacache "^18.0.0" + http-cache-semantics "^4.1.1" + is-lambda "^1.0.1" + minipass "^7.0.2" + minipass-fetch "^3.0.0" + minipass-flush "^1.0.5" + minipass-pipeline "^1.2.4" + negotiator "^0.6.3" + promise-retry "^2.0.1" + ssri "^10.0.0" + makeerror@1.0.12, makeerror@1.0.x: version "1.0.12" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" @@ -8310,6 +8401,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.2.tgz#58a82b7d81c7010da5bd4b2c0c85ac4b4ec5131e" integrity sha512-eL79dXrE1q9dBbDCLg7xfn/vl7MS4F1gvJAgjJrQli/jbQWdUttuVawphqpffoIYfRdq78LHx6GP4bU/EQ2ATA== +minipass@^7.0.2, minipass@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.0.3.tgz#05ea638da44e475037ed94d1c7efcc76a25e1974" + integrity sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg== + minizlib@^2.1.1, minizlib@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -8657,6 +8753,16 @@ npm-package-arg@^10.0.0, npm-package-arg@^10.1.0: semver "^7.3.5" validate-npm-package-name "^5.0.0" +npm-package-arg@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-11.0.0.tgz#0f09cba4a2c7e1bcba9c6520cd1775fa18eda82d" + integrity sha512-D8sItaQ8n6VlBUFed3DLz2sCpkabRAjaiLkTamDppvh8lmmAPirzNfBuhJd/2rlmoxZ2S9mOHmIEvzV2z2jOeA== + dependencies: + hosted-git-info "^7.0.0" + proc-log "^3.0.0" + semver "^7.3.5" + validate-npm-package-name "^5.0.0" + npm-packlist@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-5.1.1.tgz#79bcaf22a26b6c30aa4dd66b976d69cc286800e0" @@ -8684,6 +8790,19 @@ npm-pick-manifest@^8.0.0: npm-package-arg "^10.0.0" semver "^7.3.5" +npm-registry-fetch@16.0.0: + version "16.0.0" + resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-16.0.0.tgz#7529dd7c64c16a1bc8af72f99df73dfe98bb9549" + integrity sha512-JFCpAPUpvpwfSydv99u85yhP68rNIxSFmDpNbNnRWKSe3gpjHnWL8v320gATwRzjtgmZ9Jfe37+ZPOLZPwz6BQ== + dependencies: + make-fetch-happen "^13.0.0" + minipass "^7.0.2" + minipass-fetch "^3.0.0" + minipass-json-stream "^1.0.1" + minizlib "^2.1.2" + npm-package-arg "^11.0.0" + proc-log "^3.0.0" + npm-registry-fetch@^14.0.0, npm-registry-fetch@^14.0.3, npm-registry-fetch@^14.0.5: version "14.0.5" resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-14.0.5.tgz#fe7169957ba4986a4853a650278ee02e568d115d" @@ -10925,7 +11044,7 @@ socks-proxy-agent@^7.0.0: debug "^4.3.3" socks "^2.6.2" -socks@^2.6.2: +socks@^2.6.2, socks@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==