Skip to content

Commit 4a5b4b2

Browse files
committed
refactor transitive deps mechanism
1 parent 9c38ba3 commit 4a5b4b2

File tree

9 files changed

+96
-177
lines changed

9 files changed

+96
-177
lines changed
Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,43 @@
1+
import {DependencyMap} from '@react-native-community/cli-types';
2+
import {findDependencyPath} from '@react-native-community/cli-tools';
13
import path from 'path';
24
import fs from 'fs';
35

46
/**
57
* Returns an array of dependencies from project's package.json
68
*/
7-
export default function findDependencies(root: string): Array<string> {
8-
let pjson;
9+
export default function findDependencies(root: string): DependencyMap {
10+
const dependencies: DependencyMap = new Map();
11+
12+
const checkDependency = (dependencyPath: string) => {
13+
let pjson: {[key: string]: any};
914

10-
try {
1115
pjson = JSON.parse(
12-
fs.readFileSync(path.join(root, 'package.json'), 'utf8'),
16+
fs.readFileSync(path.join(dependencyPath, 'package.json'), 'utf8'),
1317
);
14-
} catch (e) {
15-
return [];
16-
}
1718

18-
const deps = [
19-
...Object.keys(pjson.dependencies || {}),
20-
...Object.keys(pjson.devDependencies || {}),
21-
];
19+
if (dependencies.has(pjson.name)) {
20+
return;
21+
}
22+
23+
dependencies.set(pjson.name, {
24+
version: pjson.version,
25+
peerDependencies: pjson.peerDependencies || {},
26+
path: dependencyPath,
27+
});
28+
29+
for (const dependency in {
30+
...pjson.dependencies,
31+
...pjson.devDependencies,
32+
}) {
33+
const depPath = findDependencyPath(dependency, root, dependencyPath);
34+
if (depPath) {
35+
checkDependency(depPath);
36+
}
37+
}
38+
};
39+
40+
checkDependency(root);
2241

23-
return deps;
42+
return dependencies;
2443
}
File renamed without changes.

packages/cli-tools/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ export {default as handlePortUnavailable} from './handlePortUnavailable';
1717
export * from './port';
1818
export {default as cacheManager} from './cacheManager';
1919
export {default as runSudo} from './runSudo';
20-
export {default as checkTransitiveDependencies} from './resolveTransitiveDeps';
20+
export * from './resolveTransitiveDeps';
21+
export * from './npm';
22+
export * from './bun';
23+
export * from './yarn';
2124

2225
export * from './errors';
File renamed without changes.

packages/cli-tools/src/resolveTransitiveDeps.ts

Lines changed: 40 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,18 @@
11
import fs from 'fs-extra';
22
import path from 'path';
3-
import * as fetch from 'npm-registry-fetch';
3+
import * as npmRegistryFetch from 'npm-registry-fetch';
44
import chalk from 'chalk';
55
import {prompt} from 'prompts';
66
import execa from 'execa';
77
import semver from 'semver';
8-
import generateFileHash from './generateFileHash';
9-
import {getLoader} from './loader';
10-
import logger from './logger';
11-
import {CLIError} from './errors';
12-
13-
export interface DependencyData {
14-
path: string;
15-
version: string;
16-
peerDependencies: {[key: string]: string};
17-
duplicates?: DependencyData[];
18-
}
19-
20-
function isUsingYarn(root: string) {
21-
return fs.existsSync(path.join(root, 'yarn.lock'));
22-
}
23-
24-
async function podInstall() {
25-
process.chdir('ios');
26-
const loader = getLoader();
27-
try {
28-
loader.start('Installing pods...');
29-
await execa('bundle', ['exec', 'pod', 'install']);
30-
loader.succeed();
31-
} catch (error) {
32-
const stderr = (error as any).stderr || (error as any).stdout;
33-
loader.fail();
34-
logger.error(stderr);
35-
36-
throw new CLIError(
37-
'Could not install pods. Try running pod installation manually.',
38-
);
39-
} finally {
40-
process.chdir('..');
41-
}
42-
}
43-
44-
function writeFile(filePath: string, content: string) {
45-
fs.writeFileSync(filePath, content, {encoding: 'utf8'});
46-
}
8+
import {getLoader, logger} from '@react-native-community/cli-tools';
9+
import {DependencyMap} from '@react-native-community/cli-types';
10+
import {isProjectUsingYarn} from './yarn';
4711

4812
export async function fetchAvailableVersions(
4913
packageName: string,
5014
): Promise<string[]> {
51-
const response = await fetch.json(`/${packageName}`);
15+
const response = await npmRegistryFetch.json(`/${packageName}`);
5216

5317
return Object.keys(response.versions || {});
5418
}
@@ -84,64 +48,9 @@ export function findDependencyPath(
8448
return dependencyPath;
8549
}
8650

87-
export function collectDependencies(root: string): Map<string, DependencyData> {
88-
const dependencies = new Map<string, DependencyData>();
89-
90-
const checkDependency = (dependencyPath: string) => {
91-
const packageJsonPath = path.join(dependencyPath, 'package.json');
92-
const packageJson = require(packageJsonPath);
93-
94-
if (dependencies.has(packageJson.name)) {
95-
const dependency = dependencies.get(packageJson.name) as DependencyData;
96-
97-
if (
98-
dependencyPath !== dependency.path &&
99-
dependency.duplicates?.every(
100-
(duplicate) => duplicate.path !== dependencyPath,
101-
)
102-
) {
103-
dependencies.set(packageJson.name, {
104-
...dependency,
105-
duplicates: [
106-
...dependency.duplicates,
107-
{
108-
path: dependencyPath,
109-
version: packageJson.version,
110-
peerDependencies: packageJson.peerDependencies,
111-
},
112-
],
113-
});
114-
}
115-
return;
116-
}
117-
118-
dependencies.set(packageJson.name, {
119-
path: dependencyPath,
120-
version: packageJson.version,
121-
peerDependencies: packageJson.peerDependencies,
122-
duplicates: [],
123-
});
124-
125-
for (const dependency in {
126-
...packageJson.dependencies,
127-
...(root === dependencyPath ? packageJson.devDependencies : {}),
128-
}) {
129-
const depPath = findDependencyPath(dependency, root, dependencyPath);
130-
131-
if (depPath) {
132-
checkDependency(depPath);
133-
}
134-
}
135-
};
136-
137-
checkDependency(root);
138-
139-
return dependencies;
140-
}
141-
14251
export function filterNativeDependencies(
14352
root: string,
144-
dependencies: Map<string, DependencyData>,
53+
dependencies: DependencyMap,
14554
) {
14655
const depsWithNativePeers = new Map<string, Map<string, string>>();
14756

@@ -198,15 +107,15 @@ export function filterInstalledPeers(
198107

199108
export function findPeerDepsToInstall(
200109
root: string,
201-
dependencies: Map<string, DependencyData>,
110+
dependencies: DependencyMap,
202111
) {
203112
const rootPackageJson = require(path.join(root, 'package.json'));
204113
const dependencyList = {
205114
...rootPackageJson.dependencies,
206115
...rootPackageJson.devDependencies,
207116
};
208117
const peerDependencies = new Set<string>();
209-
dependencies.forEach((value) => {
118+
Array.from(dependencies.entries()).forEach(([_, value]) => {
210119
if (value.peerDependencies) {
211120
Object.keys(value.peerDependencies).forEach((name) => {
212121
if (!Object.keys(dependencyList).includes(name)) {
@@ -218,16 +127,24 @@ export function findPeerDepsToInstall(
218127

219128
return peerDependencies;
220129
}
221-
export function getMissingPeerDepsForYarn(root: string) {
222-
const dependencies = collectDependencies(root);
130+
export function getMissingPeerDepsForYarn(
131+
root: string,
132+
dependencies: DependencyMap,
133+
) {
223134
const depsToInstall = findPeerDepsToInstall(root, dependencies);
224-
225135
return depsToInstall;
226136
}
227137

228138
// install peer deps with yarn without making any changes to package.json and yarn.lock
229-
export function yarnSilentInstallPeerDeps(root: string) {
230-
const dependenciesToInstall = getMissingPeerDepsForYarn(root);
139+
export function yarnSilentInstallPeerDeps(
140+
root: string,
141+
missingPeerDependencies: DependencyMap,
142+
) {
143+
const dependenciesToInstall = getMissingPeerDepsForYarn(
144+
root,
145+
missingPeerDependencies,
146+
);
147+
231148
const packageJsonPath = path.join(root, 'package.json');
232149
const lockfilePath = path.join(root, 'yarn.lock');
233150

@@ -259,19 +176,11 @@ export function yarnSilentInstallPeerDeps(root: string) {
259176
return;
260177
}
261178

262-
writeFile(packageJsonPath, binPackageJson);
263-
writeFile(lockfilePath, binLockfile);
179+
fs.writeFileSync(packageJsonPath, binPackageJson, {encoding: 'utf8'});
180+
fs.writeFileSync(lockfilePath, binLockfile, {encoding: 'utf8'});
264181
}
265182
}
266183

267-
function findPeerDepsForAutolinking(root: string) {
268-
const deps = collectDependencies(root);
269-
const nonEmptyPeers = filterNativeDependencies(root, deps);
270-
const nonInstalledPeers = filterInstalledPeers(root, nonEmptyPeers);
271-
272-
return nonInstalledPeers;
273-
}
274-
275184
export async function promptForMissingPeerDependencies(
276185
dependencies: Record<string, Record<string, string>>,
277186
): Promise<boolean> {
@@ -302,7 +211,7 @@ export async function promptForMissingPeerDependencies(
302211
return install;
303212
}
304213

305-
async function getPackagesVersion(
214+
export async function getPackagesVersion(
306215
missingDependencies: Record<string, Record<string, string>>,
307216
) {
308217
const packageToRanges: {[pkg: string]: string[]} = {};
@@ -339,7 +248,7 @@ async function getPackagesVersion(
339248
return workingVersions;
340249
}
341250

342-
function installMissingPackages(
251+
export function installMissingPackages(
343252
packages: Record<string, string | null>,
344253
yarn = true,
345254
) {
@@ -359,63 +268,37 @@ function installMissingPackages(
359268
}
360269
loader.succeed();
361270

362-
return true;
271+
return deps;
363272
} catch (error) {
364273
loader.fail();
365274

366-
return false;
275+
return [];
367276
}
368277
}
369278

370-
export async function resolveTransitiveDeps(root: string) {
371-
const isYarn = isUsingYarn(root);
279+
export async function resolveTransitiveDeps(
280+
root: string,
281+
dependencyMap: DependencyMap,
282+
) {
283+
const isYarn = !!isProjectUsingYarn(root);
284+
372285
if (isYarn) {
373-
yarnSilentInstallPeerDeps(root);
286+
yarnSilentInstallPeerDeps(root, dependencyMap);
374287
}
288+
const nonEmptyPeers = filterNativeDependencies(root, dependencyMap);
289+
const nonInstalledPeers = filterInstalledPeers(root, nonEmptyPeers);
375290

376-
const missingPeerDependencies = findPeerDepsForAutolinking(root);
377-
if (Object.keys(missingPeerDependencies).length > 0) {
291+
if (Object.keys(nonInstalledPeers).length > 0) {
378292
const installDeps = await promptForMissingPeerDependencies(
379-
missingPeerDependencies,
293+
nonInstalledPeers,
380294
);
381295

382296
if (installDeps) {
383-
const packagesVersions = await getPackagesVersion(
384-
missingPeerDependencies,
385-
);
297+
const packagesVersions = await getPackagesVersion(nonInstalledPeers);
386298

387299
return installMissingPackages(packagesVersions, isYarn);
388300
}
389301
}
390302

391-
return false;
392-
}
393-
394-
async function resolvePodsInstallation() {
395-
const {install} = await prompt({
396-
type: 'confirm',
397-
name: 'install',
398-
message:
399-
'Do you want to install pods? This will make sure your transitive dependencies are linked properly.',
400-
});
401-
402-
if (install) {
403-
await podInstall();
404-
}
405-
}
406-
407-
export default async function checkTransitiveDependencies() {
408-
const root = process.cwd();
409-
const packageJsonPath = path.join(process.cwd(), 'package.json');
410-
const preInstallHash = generateFileHash(packageJsonPath);
411-
const areTransitiveDepsInstalled = await resolveTransitiveDeps(root);
412-
const postInstallHash = generateFileHash(packageJsonPath);
413-
414-
if (
415-
process.platform === 'darwin' &&
416-
areTransitiveDepsInstalled &&
417-
preInstallHash !== postInstallHash
418-
) {
419-
await resolvePodsInstallation();
420-
}
303+
return [];
421304
}
File renamed without changes.

packages/cli-types/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,15 @@ export type UserDependencyConfig = {
148148
healthChecks: [];
149149
};
150150

151+
export type DependencyMap = Map<
152+
string,
153+
{
154+
version: string;
155+
peerDependencies: Record<string, string>;
156+
path: string;
157+
}
158+
>;
159+
151160
export {
152161
IOSProjectConfig,
153162
IOSProjectParams,

packages/cli/src/commands/init/init.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ import {
1212
getLoader,
1313
Loader,
1414
cacheManager,
15+
getBunVersionIfAvailable,
16+
getNpmVersionIfAvailable,
17+
getYarnVersionIfAvailable,
1518
} from '@react-native-community/cli-tools';
1619
import {installPods} from '@react-native-community/cli-platform-ios';
1720
import {
@@ -24,9 +27,6 @@ import {changePlaceholderInTemplate} from './editTemplate';
2427
import * as PackageManager from '../../tools/packageManager';
2528
import banner from './banner';
2629
import TemplateAndVersionError from './errors/TemplateAndVersionError';
27-
import {getBunVersionIfAvailable} from '../../tools/bun';
28-
import {getNpmVersionIfAvailable} from '../../tools/npm';
29-
import {getYarnVersionIfAvailable} from '../../tools/yarn';
3030
import {createHash} from 'crypto';
3131

3232
const DEFAULT_VERSION = 'latest';

0 commit comments

Comments
 (0)