Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react-native): add all flag to sync-deps #21821

Merged
merged 1 commit into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/generated/packages/expo/executors/sync-deps.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"items": { "type": "string" },
"default": [],
"description": "An array of npm packages to exclude."
},
"all": {
"type": "boolean",
"description": "Copy all dependencies and devDependencies from the workspace root package.json.",
"default": false
}
},
"presets": []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
"items": { "type": "string" },
"default": [],
"description": "An array of npm packages to exclude."
},
"all": {
"type": "boolean",
"description": "Copy all dependencies and devDependencies from the workspace root package.json.",
"default": false
}
},
"presets": []
Expand Down
22 changes: 19 additions & 3 deletions e2e/react-native/src/react-native-legacy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,18 +215,34 @@ describe('@nx/react-native (legacy)', () => {
return `import AsyncStorage from '@react-native-async-storage/async-storage';${content}`;
});

await runCLIAsync(`sync-deps ${appName}`);
let result = readJson(join('apps', appName, 'package.json'));
expect(result).toMatchObject({
dependencies: {
'@react-native-async-storage/async-storage': '*',
},
});

await runCLIAsync(
`sync-deps ${appName} --include=react-native-image-picker`
);
result = readJson(join('apps', appName, 'package.json'));
expect(result).toMatchObject({
dependencies: {
'@react-native-async-storage/async-storage': '*',
'react-native-image-picker': '*',
},
});

const result = readJson(join('apps', appName, 'package.json'));
await runCLIAsync(`sync-deps ${appName} --all`);
result = readJson(join('apps', appName, 'package.json'));
expect(result).toMatchObject({
dependencies: {
'@react-native-async-storage/async-storage': '*',
'react-native-image-picker': '*',
'react-native': '*',
},
devDependencies: {
'@react-native-async-storage/async-storage': '*',
'@nx/react-native': '*',
},
});
});
Expand Down
1 change: 1 addition & 0 deletions packages/expo/src/executors/sync-deps/schema.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface ExpoSyncDepsOptions {
include: string[] | string; // default is an empty array []
exclude: string[] | string; // default is an empty array []
all: boolean; // default is false
}
5 changes: 5 additions & 0 deletions packages/expo/src/executors/sync-deps/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
},
"default": [],
"description": "An array of npm packages to exclude."
},
"all": {
"type": "boolean",
"description": "Copy all dependencies and devDependencies from the workspace root package.json.",
"default": false
}
}
}
74 changes: 51 additions & 23 deletions packages/expo/src/executors/sync-deps/sync-deps.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@ import { join } from 'path';
import * as chalk from 'chalk';
import {
ExecutorContext,
ProjectGraph,
logger,
readCachedProjectGraph,
readJsonFile,
writeJsonFile,
} from '@nx/devkit';

import { ExpoSyncDepsOptions } from './schema';
import { findAllNpmDependencies } from '../../utils/find-all-npm-dependencies';
import { PackageJson } from 'nx/src/utils/package-json';

export interface ReactNativeSyncDepsOutput {
success: boolean;
Expand All @@ -19,41 +23,59 @@ export default async function* syncDepsExecutor(
): AsyncGenerator<ReactNativeSyncDepsOutput> {
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;

const workspacePackageJsonPath = join(context.root, 'package.json');
const projectPackageJsonPath = join(
context.root,
projectRoot,
'package.json'
);

const workspacePackageJson = readJsonFile(workspacePackageJsonPath);
const projectPackageJson = readJsonFile(projectPackageJsonPath);
displayNewlyAddedDepsMessage(
context.projectName,
await syncDeps(
projectRoot,
context.root,
context.projectName,
projectPackageJson,
projectPackageJsonPath,
workspacePackageJson,
context.projectGraph,
typeof options.include === 'string'
? options.include.split(',')
: options.include,
typeof options.exclude === 'string'
? options.exclude.split(',')
: options.exclude
: options.exclude,
options.all
)
);

yield { success: true };
}

export async function syncDeps(
projectRoot: string,
workspaceRoot: string,
projectName: string,
projectPackageJson: PackageJson,
projectPackageJsonPath: string,
workspacePackageJson: PackageJson,
projectGraph: ProjectGraph = readCachedProjectGraph(),
include: string[] = [],
exclude: string[] = []
exclude: string[] = [],
all: boolean = false
): Promise<string[]> {
const workspacePackageJsonPath = join(workspaceRoot, 'package.json');
const workspacePackageJson = readJsonFile(workspacePackageJsonPath);
let npmDeps = Object.keys(workspacePackageJson.dependencies || {});
let npmDevdeps = Object.keys(workspacePackageJson.devDependencies || {});
let npmDeps = all
? Object.keys(workspacePackageJson.dependencies || {})
: findAllNpmDependencies(projectGraph, projectName);
let npmDevdeps = all
? Object.keys(workspacePackageJson.devDependencies || {})
: [];

const packageJsonPath = join(workspaceRoot, projectRoot, 'package.json');
const packageJson = readJsonFile(packageJsonPath);
const newDeps = [];
let updated = false;

if (!packageJson.dependencies) {
packageJson.dependencies = {};
if (!projectPackageJson.dependencies) {
projectPackageJson.dependencies = {};
updated = true;
}

Expand All @@ -64,30 +86,36 @@ export async function syncDeps(
npmDeps = npmDeps.filter((dep) => !exclude.includes(dep));
}

if (!packageJson.devDependencies) {
packageJson.devDependencies = {};
if (!projectPackageJson.devDependencies) {
projectPackageJson.devDependencies = {};
}
if (!packageJson.dependencies) {
packageJson.dependencies = {};
if (!projectPackageJson.dependencies) {
projectPackageJson.dependencies = {};
}

npmDeps.forEach((dep) => {
if (!packageJson.dependencies[dep] && !packageJson.devDependencies[dep]) {
packageJson.dependencies[dep] = '*';
if (
!projectPackageJson.dependencies[dep] &&
!projectPackageJson.devDependencies[dep]
) {
projectPackageJson.dependencies[dep] = '*';
newDeps.push(dep);
updated = true;
}
});
npmDevdeps.forEach((dep) => {
if (!packageJson.dependencies[dep] && !packageJson.devDependencies[dep]) {
packageJson.devDependencies[dep] = '*';
if (
!projectPackageJson.dependencies[dep] &&
!projectPackageJson.devDependencies[dep]
) {
projectPackageJson.devDependencies[dep] = '*';
newDeps.push(dep);
updated = true;
}
});

if (updated) {
writeJsonFile(packageJsonPath, packageJson);
writeJsonFile(projectPackageJsonPath, projectPackageJson);
}

return newDeps;
Expand Down
23 changes: 20 additions & 3 deletions packages/expo/src/executors/update/update.impl.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ExecutorContext, names } from '@nx/devkit';
import { resolve as pathResolve } from 'path';
import { ExecutorContext, names, readJsonFile } from '@nx/devkit';
import { join, resolve as pathResolve } from 'path';
import { ChildProcess, fork } from 'child_process';

import { resolveEas } from '../../utils/resolve-eas';
Expand All @@ -23,10 +23,27 @@ export default async function* buildExecutor(
): AsyncGenerator<ReactNativeUpdateOutput> {
const projectRoot =
context.projectsConfigurations.projects[context.projectName].root;
const workspacePackageJsonPath = join(context.root, 'package.json');
const projectPackageJsonPath = join(
context.root,
projectRoot,
'package.json'
);

const workspacePackageJson = readJsonFile(workspacePackageJsonPath);
const projectPackageJson = readJsonFile(projectPackageJsonPath);

await installAsync(context.root, { packages: ['expo-updates'] });
displayNewlyAddedDepsMessage(
context.projectName,
await syncDeps(projectRoot, context.root, ['expo-updates'])
await syncDeps(
context.projectName,
projectPackageJson,
projectPackageJsonPath,
workspacePackageJson,
context.projectGraph,
['expo-updates']
)
);

try {
Expand Down
103 changes: 103 additions & 0 deletions packages/expo/src/utils/find-all-npm-dependencies.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { findAllNpmDependencies } from './find-all-npm-dependencies';
import { DependencyType, ProjectGraph } from '@nx/devkit';

test('findAllNpmDependencies', () => {
const graph: ProjectGraph = {
nodes: {
myapp: {
type: 'app',
name: 'myapp',
data: { files: [] },
},
lib1: {
type: 'lib',
name: 'lib1',
data: { files: [] },
},
lib2: {
type: 'lib',
name: 'lib2',
data: { files: [] },
},
lib3: {
type: 'lib',
name: 'lib3',
data: { files: [] },
},
} as any,
externalNodes: {
'npm:react-native-image-picker': {
type: 'npm',
name: 'npm:react-native-image-picker',
data: {
version: '1',
packageName: 'react-native-image-picker',
},
},
'npm:react-native-dialog': {
type: 'npm',
name: 'npm:react-native-dialog',
data: {
version: '1',
packageName: 'react-native-dialog',
},
},
'npm:react-native-snackbar': {
type: 'npm',
name: 'npm:react-native-snackbar',
data: {
version: '1',
packageName: 'react-native-snackbar',
},
},
'npm:@nx/react-native': {
type: 'npm',
name: 'npm:@nx/react-native',
data: {
version: '1',
packageName: '@nx/react-native',
},
},
},
dependencies: {
myapp: [
{ type: DependencyType.static, source: 'myapp', target: 'lib1' },
{ type: DependencyType.static, source: 'myapp', target: 'lib2' },
{
type: DependencyType.static,
source: 'myapp',
target: 'npm:react-native-image-picker',
},
{
type: DependencyType.static,
source: 'myapp',
target: 'npm:@nx/react-native',
},
],
lib1: [
{ type: DependencyType.static, source: 'lib1', target: 'lib2' },
{
type: DependencyType.static,
source: 'lib3',
target: 'npm:react-native-snackbar',
},
],
lib2: [{ type: DependencyType.static, source: 'lib2', target: 'lib3' }],
lib3: [
{
type: DependencyType.static,
source: 'lib3',
target: 'npm:react-native-dialog',
},
],
},
};

const result = findAllNpmDependencies(graph, 'myapp');

expect(result).toEqual([
'react-native-dialog',
'react-native-snackbar',
'react-native-image-picker',
]);
});
35 changes: 35 additions & 0 deletions packages/expo/src/utils/find-all-npm-dependencies.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ProjectGraph } from '@nx/devkit';

export function findAllNpmDependencies(
graph: ProjectGraph,
projectName: string,
list: string[] = [],
seen = new Set<string>()
) {
// In case of bad circular dependencies
if (seen.has(projectName)) {
return list;
}
seen.add(projectName);

const node = graph.externalNodes[projectName];

// Don't want to include '@nx/react-native' and '@nx/expo' because React Native
// autolink will warn that the package has no podspec file for iOS.
if (node) {
if (
node.name !== `npm:@nx/react-native` &&
node.name !== `npm:@nrwl/react-native` &&
node.name !== `npm:@nx/expo` &&
node.name !== `npm:@nrwl/expo`
) {
list.push(node.data.packageName);
}
} else {
// it's workspace project, search for it's dependencies
graph.dependencies[projectName]?.forEach((dep) =>
findAllNpmDependencies(graph, dep.target, list, seen)
);
}
return list;
}
Loading