Skip to content

Commit 5724d29

Browse files
grabbouthymikee
authored andcommitted
feat: support autolinking in monorepos (#768)
* Improve handling of files in monorepo * Fix tests with my poor Ruby haha * Remove note about monorepo from the docs * wat * Okay, this is how you do tests in Ruby * Fix Ruby tests * Always resolve correct root * Move files around * Add tests - one is failing, need to check why * Fix tests * Assert it works from the same level * Warn if the root is passed We do not need root anymore. However, we still accept one argument (config) for testing purposes. Without this check, users that pass custom root would accidentially break their projects as CLI would take it as a config value. * Implement monorepo support for Android * Note custom paths * Add quiet flag * Improve handling of files in monorepo * Fix tests with my poor Ruby haha * Remove note about monorepo from the docs * wat * Okay, this is how you do tests in Ruby * Fix Ruby tests * Always resolve correct root * Move files around * Add tests - one is failing, need to check why * Fix tests * Assert it works from the same level * Warn if the root is passed We do not need root anymore. However, we still accept one argument (config) for testing purposes. Without this check, users that pass custom root would accidentially break their projects as CLI would take it as a config value. * Implement monorepo support for Android * Note custom paths * Add quiet flag * update locfile * Fix init - make detachedCommands and remove ugly setProjectDir hack * Resolve conflicts * Fix remainig uses or root and get rid of cwd * Update paths * Remove unused func * Update templates.ts * Fix unit tests * Better logger * Debug * Add deprecations * Fix a typo * Better deprecation message
1 parent 71f109e commit 5724d29

File tree

24 files changed

+297
-229
lines changed

24 files changed

+297
-229
lines changed

docs/autolinking.md

Lines changed: 11 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,6 @@ The implementation ensures that a library is imported only once. If you need to
3636

3737
See example usage in React Native template's [Podfile](https://github.com/facebook/react-native/blob/0.60-stable/template/ios/Podfile).
3838

39-
### Custom root (monorepos)
40-
41-
The project root is where `node_modules` with `react-native` is. Autolinking script assume your project root to be `".."`, relative to `ios` directory. If you're in a project with custom structure, like this:
42-
43-
```
44-
root/
45-
node_modules
46-
example/
47-
ios/
48-
```
49-
50-
you'll need to set a custom root. Pass it as an argument to `use_native_modules!` function inside the targets and adjust the relatively required `native_modules` path accordingly:
51-
52-
```rb
53-
# example/ios/Podfile
54-
require_relative '../../node_modules/@react-native-community/cli-platform-ios/native_modules'
55-
target 'RNapp' do
56-
# React pods and custom pods here...
57-
use_native_modules!("../..")
58-
end
59-
```
60-
6139
## Platform Android
6240

6341
The [native_modules.gradle](https://github.com/react-native-community/cli/blob/master/packages/platform-android/native_modules.gradle) script is included in your project's `settings.gradle` and `app/build.gradle` files and:
@@ -76,31 +54,6 @@ See example usage in React Native template:
7654
- [app/build.gradle](https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle#L185)
7755
- [MainApplication.java](https://github.com/facebook/react-native/blob/769e35ba5f4c31ef913035a5cc8bc0e88546ca55/template/android/app/src/main/java/com/helloworld/MainApplication.java#L22-L28)
7856

79-
### Custom root (monorepos)
80-
81-
The project root is where `node_modules` with `react-native` is. Autolinking scripts assume your project root to be `".."`, relative to `android` directory. If you're in a project with custom structure, like this:
82-
83-
```
84-
root/
85-
node_modules
86-
example/
87-
android/
88-
```
89-
90-
you'll need to set a custom root. Pass it as a second argument to `applyNativeModulesSettingsGradle` and `applyNativeModulesAppBuildGradle` methods and adjust the `native_modules.gradle` path accordingly:
91-
92-
```groovy
93-
// example/android/settings.gradle
94-
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
95-
applyNativeModulesSettingsGradle(settings, "../..")
96-
```
97-
98-
```groovy
99-
// example/android/app/build.gradle
100-
apply from: file("../../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
101-
applyNativeModulesAppBuildGradle(project, "../..")
102-
```
103-
10457
## What do I need to have in my package to make it work?
10558

10659
You’re already using Gradle, so Android support will work by default.
@@ -142,3 +95,14 @@ module.exports = {
14295
},
14396
};
14497
```
98+
99+
## How can I use autolinking in a monorepo?
100+
101+
There is nothing extra you need to do - monorepos are supported by default.
102+
103+
Please note that in certain scenarios, such as when using Yarn workspaces, your packages might be hoisted to the root of the repository. If that is the case, please make sure that the following paths are pointing to the
104+
correct location and update them accordingly:
105+
106+
- path to `native_modules.rb` in your `ios/Podfile`
107+
- path to `native_modules.gradle` in your `android/settings.gradle`
108+
- path to `native_modules.gradle` in your `android/app/build.gradle`

packages/cli-types/src/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ export interface Command<Args = Object> {
3939
}>;
4040
}
4141

42+
export type DetachedCommandFunction<Args = Object> = (
43+
argv: Array<string>,
44+
args: Args,
45+
) => Promise<void> | void;
46+
47+
export type DetachedCommand<Args = Object> = Command<Args> & {
48+
detached: true;
49+
func: DetachedCommandFunction<Args>;
50+
};
51+
4252
interface PlatformConfig<
4353
ProjectConfig,
4454
ProjectParams,

packages/cli/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"envinfo": "^7.1.0",
4343
"errorhandler": "^1.5.0",
4444
"execa": "^1.0.0",
45+
"find-up": "^4.1.0",
4546
"fs-extra": "^7.0.1",
4647
"glob": "^7.1.1",
4748
"graceful-fs": "^4.1.3",
@@ -71,8 +72,8 @@
7172
},
7273
"devDependencies": {
7374
"@types/command-exists": "^1.2.0",
74-
"@types/graceful-fs": "^4.1.3",
7575
"@types/cosmiconfig": "^5.0.3",
76+
"@types/graceful-fs": "^4.1.3",
7677
"@types/hapi__joi": "^15.0.4",
7778
"@types/minimist": "^1.2.0",
7879
"@types/mkdirp": "^0.5.2",

packages/cli/src/cliEntry.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@ import path from 'path';
1414

1515
import type {CommandT, ConfigT} from 'types';
1616
// $FlowFixMe - converted to TS
17-
import commands from './commands';
17+
import {detachedCommands, projectCommands} from './commands';
1818
// $FlowFixMe - converted to TS
1919
import init from './commands/init/initCompat';
2020
// $FlowFixMe - converted to TS
2121
import assertRequiredOptions from './tools/assertRequiredOptions';
2222
import {logger} from '@react-native-community/cli-tools';
23-
// $FlowFixMe - converted to TS
24-
import {setProjectDir} from './tools/packageManager';
2523
import pkgJson from '../package.json';
2624
// $FlowFixMe - converted to TS
2725
import loadConfig from './tools/config';
@@ -117,7 +115,11 @@ const addCommand = (command: CommandT, ctx: ConfigT) => {
117115

118116
try {
119117
assertRequiredOptions(options, passedOptions);
120-
await command.func(argv, ctx, passedOptions);
118+
if (command.detached) {
119+
await command.func(argv, passedOptions);
120+
} else {
121+
await command.func(argv, ctx, passedOptions);
122+
}
121123
} catch (error) {
122124
handleError(error);
123125
}
@@ -149,6 +151,9 @@ async function run() {
149151
}
150152

151153
async function setupAndRun() {
154+
// Commander is not available yet
155+
logger.setVerbose(process.argv.includes('--verbose'));
156+
152157
// We only have a setup script for UNIX envs currently
153158
if (process.platform !== 'win32') {
154159
const scriptName = 'setup_env.sh';
@@ -168,19 +173,29 @@ async function setupAndRun() {
168173
}
169174
}
170175

171-
// when we run `config`, we don't want to output anything to the console. We
172-
// expect it to return valid JSON
173-
if (process.argv.includes('config')) {
174-
logger.disable();
175-
}
176+
detachedCommands.forEach(addCommand);
176177

177-
const ctx = loadConfig();
178+
try {
179+
// when we run `config`, we don't want to output anything to the console. We
180+
// expect it to return valid JSON
181+
if (process.argv.includes('config')) {
182+
logger.disable();
183+
}
178184

179-
logger.enable();
185+
const ctx = loadConfig();
180186

181-
setProjectDir(ctx.root);
187+
logger.enable();
182188

183-
[...commands, ...ctx.commands].forEach(command => addCommand(command, ctx));
189+
[...projectCommands, ...ctx.commands].forEach(command =>
190+
addCommand(command, ctx),
191+
);
192+
} catch (e) {
193+
logger.enable();
194+
logger.debug(e.message);
195+
logger.debug(
196+
'Failed to load configuration of your project. Only a subset of commands will be available.',
197+
);
198+
}
184199

185200
commander.parse(process.argv);
186201

@@ -194,8 +209,6 @@ async function setupAndRun() {
194209
if (commander.args.length === 0 && commander.rawArgs.includes('--version')) {
195210
console.log(pkgJson.version);
196211
}
197-
198-
logger.setVerbose(commander.verbose);
199212
}
200213

201214
export default {

packages/cli/src/commands/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {Command} from '@react-native-community/cli-types';
1+
import {DetachedCommand, Command} from '@react-native-community/cli-types';
22

33
// @ts-ignore - JS file
44
import server from './server/server';
@@ -15,7 +15,7 @@ import init from './init';
1515
// @ts-ignore - JS file
1616
import doctor from './doctor';
1717

18-
export default [
18+
export const projectCommands = [
1919
server,
2020
bundle,
2121
ramBundle,
@@ -26,6 +26,7 @@ export default [
2626
upgrade,
2727
info,
2828
config,
29-
init,
3029
doctor,
3130
] as Command[];
31+
32+
export const detachedCommands = [init] as DetachedCommand[];

packages/cli/src/commands/init/__tests__/template.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ test('installTemplatePackage', async () => {
2626
expect(PackageManger.install).toHaveBeenCalledWith([TEMPLATE_NAME], {
2727
preferYarn: false,
2828
silent: true,
29-
cwd: TEMPLATE_SOURCE_DIR,
29+
root: TEMPLATE_SOURCE_DIR,
3030
});
3131
});
3232

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import init from './init';
22

33
export default {
44
func: init,
5+
detached: true,
56
name: 'init <projectName>',
67
description:
78
'Initialize a new React Native project named <projectName> in a directory of the same name.',

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

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import {validateProjectName} from './validate';
1111
import DirectoryAlreadyExistsError from './errors/DirectoryAlreadyExistsError';
1212
import printRunInstructions from './printRunInstructions';
1313
import {CLIError, logger} from '@react-native-community/cli-tools';
14-
import {Config} from '@react-native-community/cli-types';
1514
import {
1615
installTemplatePackage,
1716
getTemplateConfig,
@@ -47,16 +46,6 @@ function doesDirectoryExist(dir: string) {
4746
return fs.existsSync(dir);
4847
}
4948

50-
function getProjectDirectory({
51-
projectName,
52-
directory,
53-
}: {
54-
projectName: string;
55-
directory: string;
56-
}): string {
57-
return path.relative(process.cwd(), directory || projectName);
58-
}
59-
6049
async function setProjectDirectory(directory: string) {
6150
const directoryExists = doesDirectoryExist(directory);
6251
if (directoryExists) {
@@ -87,6 +76,8 @@ async function setProjectDirectory(directory: string) {
8776
error,
8877
);
8978
}
79+
80+
return process.cwd();
9081
}
9182

9283
function adjustNameIfUrl(name: string, cwd: string) {
@@ -112,7 +103,7 @@ async function createFromTemplate({
112103
logger.debug('Initializing new project');
113104
logger.log(banner);
114105

115-
await setProjectDirectory(directory);
106+
const projectDirectory = await setProjectDirectory(directory);
116107

117108
const Loader = getLoader();
118109
const loader = new Loader({text: 'Downloading template'});
@@ -152,7 +143,12 @@ async function createFromTemplate({
152143
loader.succeed();
153144
}
154145

155-
await installDependencies({projectName, npm, loader});
146+
await installDependencies({
147+
projectName,
148+
npm,
149+
loader,
150+
root: projectDirectory,
151+
});
156152
} catch (e) {
157153
loader.fail();
158154
throw new Error(e);
@@ -165,16 +161,19 @@ async function installDependencies({
165161
projectName,
166162
npm,
167163
loader,
164+
root,
168165
}: {
169166
projectName: string;
170167
npm?: boolean;
171168
loader: ora.Ora;
169+
root: string;
172170
}) {
173171
loader.start('Installing dependencies');
174172

175173
await PackageManager.installAll({
176174
preferYarn: !npm,
177175
silent: true,
176+
root,
178177
});
179178

180179
if (process.platform === 'darwin') {
@@ -213,10 +212,9 @@ async function createProject(
213212

214213
export default (async function initialize(
215214
[projectName]: Array<string>,
216-
context: Config,
217215
options: Options,
218216
) {
219-
const rootFolder = context.root;
217+
const root = process.cwd();
220218

221219
validateProjectName(projectName);
222220

@@ -226,15 +224,12 @@ export default (async function initialize(
226224
*/
227225
const version: string = minimist(process.argv).version || DEFAULT_VERSION;
228226

229-
const directoryName = getProjectDirectory({
230-
projectName,
231-
directory: options.directory || projectName,
232-
});
227+
const directoryName = path.relative(root, options.directory || projectName);
233228

234229
try {
235230
await createProject(projectName, directoryName, version, options);
236231

237-
const projectFolder = path.join(rootFolder, projectName);
232+
const projectFolder = path.join(root, projectName);
238233
printRunInstructions(projectFolder, projectName);
239234
} catch (e) {
240235
logger.error(e.message);

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

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -56,27 +56,33 @@ async function generateProject(
5656
const pkgJson = require('react-native/package.json');
5757
const reactVersion = pkgJson.peerDependencies.react;
5858

59-
PackageManager.setProjectDir(destinationRoot);
6059
await createProjectFromTemplate(
6160
destinationRoot,
6261
newProjectName,
6362
options.template,
6463
);
6564

6665
logger.info('Adding required dependencies');
67-
await PackageManager.install([`react@${reactVersion}`]);
66+
await PackageManager.install([`react@${reactVersion}`], {
67+
root: destinationRoot,
68+
});
6869

6970
logger.info('Adding required dev dependencies');
70-
await PackageManager.installDev([
71-
'@babel/core',
72-
'@babel/runtime',
73-
'@react-native-community/eslint-config',
74-
'eslint',
75-
'jest',
76-
'babel-jest',
77-
'metro-react-native-babel-preset',
78-
`react-test-renderer@${reactVersion}`,
79-
]);
71+
await PackageManager.installDev(
72+
[
73+
'@babel/core',
74+
'@babel/runtime',
75+
'@react-native-community/eslint-config',
76+
'eslint',
77+
'jest',
78+
'babel-jest',
79+
'metro-react-native-babel-preset',
80+
`react-test-renderer@${reactVersion}`,
81+
],
82+
{
83+
root: destinationRoot,
84+
},
85+
);
8086

8187
addJestToPackageJson(destinationRoot);
8288

0 commit comments

Comments
 (0)