From 387af0cf3f92bb9dd20f39eccb349d766bd613bb Mon Sep 17 00:00:00 2001 From: Emily Xiong Date: Wed, 25 Aug 2021 15:54:23 -0400 Subject: [PATCH] feat(core): add react native to main repo (#6493) --- .github/workflows/e2e-matrix.yml | 8 + .gitignore | 2 +- docs/angular/api-detox/executors/build.md | 23 + docs/angular/api-detox/executors/test.md | 177 ++++++ .../api-detox/generators/application.md | 81 +++ .../executors/build-android.md | 13 + .../api-react-native/executors/bundle.md | 45 ++ .../executors/ensure-symlink.md | 5 + .../api-react-native/executors/run-android.md | 85 +++ .../api-react-native/executors/run-ios.md | 73 +++ .../api-react-native/executors/start.md | 23 + .../api-react-native/executors/sync-deps.md | 13 + .../generators/application.md | 125 +++++ .../api-react-native/generators/component.md | 119 ++++ .../api-react-native/generators/library.md | 157 ++++++ docs/angular/executors.json | 2 + docs/angular/generators.json | 2 + docs/map.json | 231 ++++++++ docs/node/api-detox/executors/build.md | 24 + docs/node/api-detox/executors/test.md | 178 ++++++ docs/node/api-detox/generators/application.md | 81 +++ .../executors/build-android.md | 14 + .../node/api-react-native/executors/bundle.md | 46 ++ .../executors/ensure-symlink.md | 6 + .../api-react-native/executors/run-android.md | 86 +++ .../api-react-native/executors/run-ios.md | 74 +++ docs/node/api-react-native/executors/start.md | 24 + .../api-react-native/executors/sync-deps.md | 14 + .../generators/application.md | 125 +++++ .../api-react-native/generators/component.md | 119 ++++ .../api-react-native/generators/library.md | 157 ++++++ docs/node/executors.json | 2 + docs/node/generators.json | 2 + docs/react/api-detox/executors/build.md | 24 + docs/react/api-detox/executors/test.md | 178 ++++++ .../react/api-detox/generators/application.md | 81 +++ .../executors/build-android.md | 14 + .../api-react-native/executors/bundle.md | 46 ++ .../executors/ensure-symlink.md | 6 + .../api-react-native/executors/run-android.md | 86 +++ .../api-react-native/executors/run-ios.md | 74 +++ .../react/api-react-native/executors/start.md | 24 + .../api-react-native/executors/sync-deps.md | 14 + .../generators/application.md | 125 +++++ .../api-react-native/generators/component.md | 119 ++++ .../api-react-native/generators/library.md | 157 ++++++ docs/react/executors.json | 2 + docs/react/generators.json | 2 + e2e/detox/jest.config.js | 11 + e2e/detox/project.json | 34 ++ e2e/detox/src/detox.test.ts | 56 ++ e2e/detox/tsconfig.json | 13 + e2e/detox/tsconfig.spec.json | 16 + e2e/react-native/jest.config.js | 11 + e2e/react-native/project.json | 34 ++ e2e/react-native/src/react-native.test.ts | 96 ++++ e2e/react-native/tsconfig.json | 13 + e2e/react-native/tsconfig.spec.json | 16 + e2e/utils/index.ts | 1 + package.json | 2 + .../bin/create-nx-workspace.ts | 9 +- packages/detox/.eslintrc.json | 1 + packages/detox/README.md | 80 +++ packages/detox/executors.json | 14 + packages/detox/generators.json | 33 ++ packages/detox/index.ts | 2 + packages/detox/jest.config.js | 12 + packages/detox/migrations.json | 13 + packages/detox/package.json | 42 ++ packages/detox/project.json | 82 +++ .../detox/src/executors/build/build.impl.ts | 71 +++ packages/detox/src/executors/build/compat.ts | 5 + .../detox/src/executors/build/schema.d.ts | 5 + .../detox/src/executors/build/schema.json | 19 + packages/detox/src/executors/test/compat.ts | 5 + packages/detox/src/executors/test/schema.d.ts | 30 + packages/detox/src/executors/test/schema.json | 120 ++++ .../detox/src/executors/test/test.impl.ts | 77 +++ .../src/generators/application/application.ts | 34 ++ .../application/files/app/.babelrc.template | 11 + .../files/app/.detoxrc.json.template | 58 ++ .../files/app/environment.js.template | 23 + .../application/files/app/jest.config.json | 12 + .../files/app/src/app.spec.ts.template | 16 + .../files/app/test-setup.ts.template | 6 + .../application/files/app/tsconfig.e2e.json | 10 + .../application/files/app/tsconfig.json | 10 + .../application/lib/add-git-ignore-entry.ts | 12 + .../application/lib/add-linting.spec.ts | 71 +++ .../generators/application/lib/add-linting.ts | 49 ++ .../application/lib/add-project.spec.ts | 60 ++ .../generators/application/lib/add-project.ts | 71 +++ .../application/lib/create-files.spec.ts | 29 + .../application/lib/create-files.ts | 13 + .../application/lib/normalize-options.spec.ts | 77 +++ .../application/lib/normalize-options.ts | 50 ++ .../src/generators/application/schema.d.ts | 11 + .../src/generators/application/schema.json | 50 ++ .../detox/src/generators/init/init.spec.ts | 19 + packages/detox/src/generators/init/init.ts | 47 ++ .../detox/src/generators/init/schema.d.ts | 3 + .../detox/src/generators/init/schema.json | 13 + packages/detox/src/utils/versions.ts | 5 + packages/detox/tsconfig.json | 13 + packages/detox/tsconfig.lib.json | 11 + packages/detox/tsconfig.spec.json | 15 + packages/react-native/.eslintrc.json | 1 + packages/react-native/README.md | 193 +++++++ packages/react-native/executors.json | 76 +++ packages/react-native/generators.json | 57 ++ packages/react-native/index.ts | 5 + packages/react-native/jest.config.js | 12 + packages/react-native/migrations.json | 209 +++++++ packages/react-native/nx_post_install.rb | 15 + packages/react-native/package.json | 47 ++ .../react-native/plugins/metro-resolver.ts | 98 ++++ .../react-native/plugins/with-nx-metro.ts | 24 + packages/react-native/project.json | 75 +++ .../build-android/build-android.impl.ts | 58 ++ .../src/executors/build-android/compat.ts | 5 + .../src/executors/build-android/schema.d.ts | 3 + .../src/executors/build-android/schema.json | 15 + .../src/executors/bundle/bundle.impl.ts | 74 +++ .../src/executors/bundle/compat.ts | 5 + .../src/executors/bundle/schema.d.ts | 8 + .../src/executors/bundle/schema.json | 36 ++ .../src/executors/ensure-symlink/compat.ts | 5 + .../ensure-symlink/ensure-symlink.impl.ts | 17 + .../src/executors/ensure-symlink/schema.json | 9 + .../src/executors/run-android/compat.ts | 5 + .../executors/run-android/run-android.impl.ts | 106 ++++ .../src/executors/run-android/schema.d.ts | 14 + .../src/executors/run-android/schema.json | 60 ++ .../src/executors/run-ios/compat.ts | 5 + .../src/executors/run-ios/run-ios.impl.ts | 106 ++++ .../src/executors/run-ios/schema.d.ts | 12 + .../src/executors/run-ios/schema.json | 52 ++ .../src/executors/start/compat.ts | 5 + .../start/lib/is-packager-running.ts | 13 + .../src/executors/start/schema.d.ts | 4 + .../src/executors/start/schema.json | 20 + .../src/executors/start/start.impl.ts | 115 ++++ .../src/executors/sync-deps/compat.ts | 5 + .../src/executors/sync-deps/schema.d.ts | 3 + .../src/executors/sync-deps/schema.json | 14 + .../src/executors/sync-deps/sync-deps.impl.ts | 82 +++ .../application/application.spec.ts | 68 +++ .../src/generators/application/application.ts | 77 +++ .../application/files/app/.babelrc.template | 3 + .../files/app/android/app/_BUCK.template | 55 ++ .../app/android/app/build.gradle.template | 236 ++++++++ .../files/app/android/app/build_defs.bzl | 19 + .../files/app/android/app/debug.keystore | Bin 0 -> 2257 bytes .../files/app/android/app/proguard-rules.pro | 10 + .../__lowerCaseName__/DetoxTest.java.template | 36 ++ .../src/debug/AndroidManifest.xml.template | 13 + .../ReactNativeFlipper.java.template | 72 +++ .../app/src/main/AndroidManifest.xml.template | 47 ++ .../MainActivity.java.template | 15 + .../MainApplication.java.template | 80 +++ .../src/main/res/drawable/splashscreen.xml | 7 + .../main/res/drawable/splashscreen_image.png | Bin 0 -> 9306 bytes .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3056 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5024 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2096 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2858 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4569 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7098 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6464 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10676 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9250 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15523 bytes .../app/src/main/res/values/colors.xml | 5 + .../src/main/res/values/strings.xml.template | 3 + .../app/src/main/res/values/styles.xml | 12 + .../main/res/xml/network_security_config.xml | 7 + .../files/app/android/build.gradle.template | 46 ++ .../files/app/android/gradle.properties | 35 ++ .../android/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + .../application/files/app/android/gradlew.bat | 89 +++ .../files/app/android/gradlew.template | 185 +++++++ .../app/android/settings.gradle.template | 6 + .../application/files/app/app.json.template | 4 + .../files/app/ios/Podfile.template | 26 + .../project.pbxproj.template | 512 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcschemes/__className__.xcscheme.template | 88 +++ .../contents.xcworkspacedata.template | 10 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../files/app/ios/__className__/AppDelegate.h | 8 + .../ios/__className__/AppDelegate.m.template | 62 +++ .../Base.lproj/LaunchScreen.xib.template | 42 ++ .../AppIcon.appiconset/Contents.json | 38 ++ .../Images.xcassets/Contents.json | 6 + .../app/ios/__className__/Info.plist.template | 55 ++ .../LaunchScreen.storyboard.template | 47 ++ .../ios/__className__/Supporting/Expo.plist | 10 + .../files/app/ios/__className__/main.m | 10 + .../files/app/metro.config.js.template | 21 + .../files/app/package.json.template | 11 + .../files/app/src/app/App.spec.tsx.template | 10 + .../files/app/src/app/App.tsx.template | 131 +++++ .../application/files/app/src/app/logo.png | Bin 0 -> 28693 bytes .../files/app/src/main.tsx.template | 4 + .../files/app/test-setup.ts.template | 1 + .../files/app/tsconfig.app.json.template | 9 + .../files/app/tsconfig.json.template | 22 + .../generators/application/lib/add-detox.ts | 18 + .../generators/application/lib/add-project.ts | 104 ++++ .../lib/create-application-files.ts | 24 + .../application/lib/nomalize-options.spec.ts | 106 ++++ .../application/lib/normalize-options.ts | 62 +++ .../src/generators/application/schema.d.ts | 17 + .../src/generators/application/schema.json | 76 +++ .../generators/component/component.spec.ts | 135 +++++ .../src/generators/component/component.ts | 88 +++ .../files/__fileName__.spec.tsx__tmpl__ | 11 + .../component/files/__fileName__.tsx__tmpl__ | 32 ++ .../generators/component/lib/add-import.ts | 28 + .../component/lib/normalize-options.ts | 83 +++ .../src/generators/component/schema.d.ts | 14 + .../src/generators/component/schema.json | 77 +++ .../src/generators/init/init.spec.ts | 59 ++ .../react-native/src/generators/init/init.ts | 89 +++ .../init/lib/add-git-ignore-entry.ts | 26 + .../generators/init/lib/gitignore-entries.ts | 53 ++ .../src/generators/init/schema.d.ts | 5 + .../src/generators/init/schema.json | 27 + .../library/files/lib/.babelrc__tmpl__ | 3 + .../generators/library/files/lib/README.md | 7 + .../library/files/lib/package.json__tmpl__ | 4 + .../library/files/lib/src/index.ts__tmpl__ | 0 .../library/files/lib/test-setup.ts.template | 1 + .../library/files/lib/tsconfig.json__tmpl__ | 16 + .../files/lib/tsconfig.lib.json__tmpl__ | 9 + .../library/lib/normalize-options.ts | 52 ++ .../src/generators/library/library.spec.ts | 341 ++++++++++++ .../src/generators/library/library.ts | 193 +++++++ .../src/generators/library/schema.d.ts | 22 + .../src/generators/library/schema.json | 97 ++++ .../update-jest-for-react-native.spec.ts | 100 ++++ .../update-jest-for-react-native.ts | 81 +++ packages/react-native/src/utils/add-jest.ts | 40 ++ .../src/utils/add-linting.spec.ts | 60 ++ .../react-native/src/utils/add-linting.ts | 85 +++ packages/react-native/src/utils/chmod-task.ts | 16 + .../utils/ensure-node-modules-symlink.spec.ts | 188 +++++++ .../src/utils/ensure-node-modules-symlink.ts | 144 +++++ .../utils/find-all-npm-dependencies.spec.ts | 93 ++++ .../src/utils/find-all-npm-dependencies.ts | 25 + .../src/utils/pod-install-task.ts | 54 ++ .../react-native/src/utils/symlink-task.ts | 17 + .../src/utils/testing-generators.ts | 28 + packages/react-native/src/utils/versions.ts | 23 + packages/react-native/tsconfig.json | 13 + packages/react-native/tsconfig.lib.json | 11 + packages/react-native/tsconfig.spec.json | 15 + packages/tao/src/commands/migrate.ts | 2 + packages/workspace/src/generators/new/new.ts | 6 + .../src/generators/preset/preset.spec.ts | 15 + .../workspace/src/generators/preset/preset.ts | 10 + .../workspace/src/generators/utils/presets.ts | 1 + scripts/check-versions.ts | 1 - scripts/e2e-build-package-publish.ts | 4 + scripts/nx-release.js | 2 + scripts/package.sh | 12 +- tsconfig.base.json | 2 + workspace.json | 4 + yarn.lock | 12 + 271 files changed, 11693 insertions(+), 9 deletions(-) create mode 100644 docs/angular/api-detox/executors/build.md create mode 100644 docs/angular/api-detox/executors/test.md create mode 100644 docs/angular/api-detox/generators/application.md create mode 100644 docs/angular/api-react-native/executors/build-android.md create mode 100644 docs/angular/api-react-native/executors/bundle.md create mode 100644 docs/angular/api-react-native/executors/ensure-symlink.md create mode 100644 docs/angular/api-react-native/executors/run-android.md create mode 100644 docs/angular/api-react-native/executors/run-ios.md create mode 100644 docs/angular/api-react-native/executors/start.md create mode 100644 docs/angular/api-react-native/executors/sync-deps.md create mode 100644 docs/angular/api-react-native/generators/application.md create mode 100644 docs/angular/api-react-native/generators/component.md create mode 100644 docs/angular/api-react-native/generators/library.md create mode 100644 docs/node/api-detox/executors/build.md create mode 100644 docs/node/api-detox/executors/test.md create mode 100644 docs/node/api-detox/generators/application.md create mode 100644 docs/node/api-react-native/executors/build-android.md create mode 100644 docs/node/api-react-native/executors/bundle.md create mode 100644 docs/node/api-react-native/executors/ensure-symlink.md create mode 100644 docs/node/api-react-native/executors/run-android.md create mode 100644 docs/node/api-react-native/executors/run-ios.md create mode 100644 docs/node/api-react-native/executors/start.md create mode 100644 docs/node/api-react-native/executors/sync-deps.md create mode 100644 docs/node/api-react-native/generators/application.md create mode 100644 docs/node/api-react-native/generators/component.md create mode 100644 docs/node/api-react-native/generators/library.md create mode 100644 docs/react/api-detox/executors/build.md create mode 100644 docs/react/api-detox/executors/test.md create mode 100644 docs/react/api-detox/generators/application.md create mode 100644 docs/react/api-react-native/executors/build-android.md create mode 100644 docs/react/api-react-native/executors/bundle.md create mode 100644 docs/react/api-react-native/executors/ensure-symlink.md create mode 100644 docs/react/api-react-native/executors/run-android.md create mode 100644 docs/react/api-react-native/executors/run-ios.md create mode 100644 docs/react/api-react-native/executors/start.md create mode 100644 docs/react/api-react-native/executors/sync-deps.md create mode 100644 docs/react/api-react-native/generators/application.md create mode 100644 docs/react/api-react-native/generators/component.md create mode 100644 docs/react/api-react-native/generators/library.md create mode 100644 e2e/detox/jest.config.js create mode 100644 e2e/detox/project.json create mode 100644 e2e/detox/src/detox.test.ts create mode 100644 e2e/detox/tsconfig.json create mode 100644 e2e/detox/tsconfig.spec.json create mode 100644 e2e/react-native/jest.config.js create mode 100644 e2e/react-native/project.json create mode 100644 e2e/react-native/src/react-native.test.ts create mode 100644 e2e/react-native/tsconfig.json create mode 100644 e2e/react-native/tsconfig.spec.json create mode 100644 packages/detox/.eslintrc.json create mode 100644 packages/detox/README.md create mode 100644 packages/detox/executors.json create mode 100644 packages/detox/generators.json create mode 100644 packages/detox/index.ts create mode 100644 packages/detox/jest.config.js create mode 100644 packages/detox/migrations.json create mode 100644 packages/detox/package.json create mode 100644 packages/detox/project.json create mode 100644 packages/detox/src/executors/build/build.impl.ts create mode 100644 packages/detox/src/executors/build/compat.ts create mode 100644 packages/detox/src/executors/build/schema.d.ts create mode 100644 packages/detox/src/executors/build/schema.json create mode 100644 packages/detox/src/executors/test/compat.ts create mode 100644 packages/detox/src/executors/test/schema.d.ts create mode 100644 packages/detox/src/executors/test/schema.json create mode 100644 packages/detox/src/executors/test/test.impl.ts create mode 100644 packages/detox/src/generators/application/application.ts create mode 100644 packages/detox/src/generators/application/files/app/.babelrc.template create mode 100644 packages/detox/src/generators/application/files/app/.detoxrc.json.template create mode 100644 packages/detox/src/generators/application/files/app/environment.js.template create mode 100644 packages/detox/src/generators/application/files/app/jest.config.json create mode 100644 packages/detox/src/generators/application/files/app/src/app.spec.ts.template create mode 100644 packages/detox/src/generators/application/files/app/test-setup.ts.template create mode 100644 packages/detox/src/generators/application/files/app/tsconfig.e2e.json create mode 100644 packages/detox/src/generators/application/files/app/tsconfig.json create mode 100644 packages/detox/src/generators/application/lib/add-git-ignore-entry.ts create mode 100644 packages/detox/src/generators/application/lib/add-linting.spec.ts create mode 100644 packages/detox/src/generators/application/lib/add-linting.ts create mode 100644 packages/detox/src/generators/application/lib/add-project.spec.ts create mode 100644 packages/detox/src/generators/application/lib/add-project.ts create mode 100644 packages/detox/src/generators/application/lib/create-files.spec.ts create mode 100644 packages/detox/src/generators/application/lib/create-files.ts create mode 100644 packages/detox/src/generators/application/lib/normalize-options.spec.ts create mode 100644 packages/detox/src/generators/application/lib/normalize-options.ts create mode 100644 packages/detox/src/generators/application/schema.d.ts create mode 100644 packages/detox/src/generators/application/schema.json create mode 100644 packages/detox/src/generators/init/init.spec.ts create mode 100644 packages/detox/src/generators/init/init.ts create mode 100644 packages/detox/src/generators/init/schema.d.ts create mode 100644 packages/detox/src/generators/init/schema.json create mode 100644 packages/detox/src/utils/versions.ts create mode 100644 packages/detox/tsconfig.json create mode 100644 packages/detox/tsconfig.lib.json create mode 100644 packages/detox/tsconfig.spec.json create mode 100644 packages/react-native/.eslintrc.json create mode 100644 packages/react-native/README.md create mode 100644 packages/react-native/executors.json create mode 100644 packages/react-native/generators.json create mode 100644 packages/react-native/index.ts create mode 100644 packages/react-native/jest.config.js create mode 100644 packages/react-native/migrations.json create mode 100644 packages/react-native/nx_post_install.rb create mode 100644 packages/react-native/package.json create mode 100644 packages/react-native/plugins/metro-resolver.ts create mode 100644 packages/react-native/plugins/with-nx-metro.ts create mode 100644 packages/react-native/project.json create mode 100644 packages/react-native/src/executors/build-android/build-android.impl.ts create mode 100644 packages/react-native/src/executors/build-android/compat.ts create mode 100644 packages/react-native/src/executors/build-android/schema.d.ts create mode 100644 packages/react-native/src/executors/build-android/schema.json create mode 100644 packages/react-native/src/executors/bundle/bundle.impl.ts create mode 100644 packages/react-native/src/executors/bundle/compat.ts create mode 100644 packages/react-native/src/executors/bundle/schema.d.ts create mode 100644 packages/react-native/src/executors/bundle/schema.json create mode 100644 packages/react-native/src/executors/ensure-symlink/compat.ts create mode 100644 packages/react-native/src/executors/ensure-symlink/ensure-symlink.impl.ts create mode 100644 packages/react-native/src/executors/ensure-symlink/schema.json create mode 100644 packages/react-native/src/executors/run-android/compat.ts create mode 100644 packages/react-native/src/executors/run-android/run-android.impl.ts create mode 100644 packages/react-native/src/executors/run-android/schema.d.ts create mode 100644 packages/react-native/src/executors/run-android/schema.json create mode 100644 packages/react-native/src/executors/run-ios/compat.ts create mode 100644 packages/react-native/src/executors/run-ios/run-ios.impl.ts create mode 100644 packages/react-native/src/executors/run-ios/schema.d.ts create mode 100644 packages/react-native/src/executors/run-ios/schema.json create mode 100644 packages/react-native/src/executors/start/compat.ts create mode 100644 packages/react-native/src/executors/start/lib/is-packager-running.ts create mode 100644 packages/react-native/src/executors/start/schema.d.ts create mode 100644 packages/react-native/src/executors/start/schema.json create mode 100644 packages/react-native/src/executors/start/start.impl.ts create mode 100644 packages/react-native/src/executors/sync-deps/compat.ts create mode 100644 packages/react-native/src/executors/sync-deps/schema.d.ts create mode 100644 packages/react-native/src/executors/sync-deps/schema.json create mode 100644 packages/react-native/src/executors/sync-deps/sync-deps.impl.ts create mode 100644 packages/react-native/src/generators/application/application.spec.ts create mode 100644 packages/react-native/src/generators/application/application.ts create mode 100644 packages/react-native/src/generators/application/files/app/.babelrc.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/_BUCK.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/build.gradle.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/build_defs.bzl create mode 100644 packages/react-native/src/generators/application/files/app/android/app/debug.keystore create mode 100644 packages/react-native/src/generators/application/files/app/android/app/proguard-rules.pro create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/androidTest/java/com/__lowerCaseName__/DetoxTest.java.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/debug/AndroidManifest.xml.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/debug/java/com/__lowerCaseName__/ReactNativeFlipper.java.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/AndroidManifest.xml.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainActivity.java.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainApplication.java.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/drawable/splashscreen.xml create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/drawable/splashscreen_image.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/colors.xml create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/strings.xml.template create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/styles.xml create mode 100644 packages/react-native/src/generators/application/files/app/android/app/src/main/res/xml/network_security_config.xml create mode 100644 packages/react-native/src/generators/application/files/app/android/build.gradle.template create mode 100644 packages/react-native/src/generators/application/files/app/android/gradle.properties create mode 100644 packages/react-native/src/generators/application/files/app/android/gradle/wrapper/gradle-wrapper.jar create mode 100644 packages/react-native/src/generators/application/files/app/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/react-native/src/generators/application/files/app/android/gradlew.bat create mode 100755 packages/react-native/src/generators/application/files/app/android/gradlew.template create mode 100644 packages/react-native/src/generators/application/files/app/android/settings.gradle.template create mode 100644 packages/react-native/src/generators/application/files/app/app.json.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/Podfile.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.pbxproj.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/xcshareddata/xcschemes/__className__.xcscheme.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/contents.xcworkspacedata.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.h create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.m.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/Base.lproj/LaunchScreen.xib.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/Contents.json create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/Info.plist.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/LaunchScreen.storyboard.template create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/Supporting/Expo.plist create mode 100644 packages/react-native/src/generators/application/files/app/ios/__className__/main.m create mode 100644 packages/react-native/src/generators/application/files/app/metro.config.js.template create mode 100644 packages/react-native/src/generators/application/files/app/package.json.template create mode 100644 packages/react-native/src/generators/application/files/app/src/app/App.spec.tsx.template create mode 100644 packages/react-native/src/generators/application/files/app/src/app/App.tsx.template create mode 100644 packages/react-native/src/generators/application/files/app/src/app/logo.png create mode 100644 packages/react-native/src/generators/application/files/app/src/main.tsx.template create mode 100644 packages/react-native/src/generators/application/files/app/test-setup.ts.template create mode 100644 packages/react-native/src/generators/application/files/app/tsconfig.app.json.template create mode 100644 packages/react-native/src/generators/application/files/app/tsconfig.json.template create mode 100644 packages/react-native/src/generators/application/lib/add-detox.ts create mode 100644 packages/react-native/src/generators/application/lib/add-project.ts create mode 100644 packages/react-native/src/generators/application/lib/create-application-files.ts create mode 100644 packages/react-native/src/generators/application/lib/nomalize-options.spec.ts create mode 100644 packages/react-native/src/generators/application/lib/normalize-options.ts create mode 100644 packages/react-native/src/generators/application/schema.d.ts create mode 100644 packages/react-native/src/generators/application/schema.json create mode 100644 packages/react-native/src/generators/component/component.spec.ts create mode 100644 packages/react-native/src/generators/component/component.ts create mode 100644 packages/react-native/src/generators/component/files/__fileName__.spec.tsx__tmpl__ create mode 100644 packages/react-native/src/generators/component/files/__fileName__.tsx__tmpl__ create mode 100644 packages/react-native/src/generators/component/lib/add-import.ts create mode 100644 packages/react-native/src/generators/component/lib/normalize-options.ts create mode 100644 packages/react-native/src/generators/component/schema.d.ts create mode 100644 packages/react-native/src/generators/component/schema.json create mode 100644 packages/react-native/src/generators/init/init.spec.ts create mode 100644 packages/react-native/src/generators/init/init.ts create mode 100644 packages/react-native/src/generators/init/lib/add-git-ignore-entry.ts create mode 100644 packages/react-native/src/generators/init/lib/gitignore-entries.ts create mode 100644 packages/react-native/src/generators/init/schema.d.ts create mode 100644 packages/react-native/src/generators/init/schema.json create mode 100644 packages/react-native/src/generators/library/files/lib/.babelrc__tmpl__ create mode 100644 packages/react-native/src/generators/library/files/lib/README.md create mode 100644 packages/react-native/src/generators/library/files/lib/package.json__tmpl__ create mode 100644 packages/react-native/src/generators/library/files/lib/src/index.ts__tmpl__ create mode 100644 packages/react-native/src/generators/library/files/lib/test-setup.ts.template create mode 100644 packages/react-native/src/generators/library/files/lib/tsconfig.json__tmpl__ create mode 100644 packages/react-native/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ create mode 100644 packages/react-native/src/generators/library/lib/normalize-options.ts create mode 100644 packages/react-native/src/generators/library/library.spec.ts create mode 100644 packages/react-native/src/generators/library/library.ts create mode 100644 packages/react-native/src/generators/library/schema.d.ts create mode 100644 packages/react-native/src/generators/library/schema.json create mode 100644 packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.spec.ts create mode 100644 packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.ts create mode 100644 packages/react-native/src/utils/add-jest.ts create mode 100644 packages/react-native/src/utils/add-linting.spec.ts create mode 100644 packages/react-native/src/utils/add-linting.ts create mode 100644 packages/react-native/src/utils/chmod-task.ts create mode 100644 packages/react-native/src/utils/ensure-node-modules-symlink.spec.ts create mode 100644 packages/react-native/src/utils/ensure-node-modules-symlink.ts create mode 100644 packages/react-native/src/utils/find-all-npm-dependencies.spec.ts create mode 100644 packages/react-native/src/utils/find-all-npm-dependencies.ts create mode 100644 packages/react-native/src/utils/pod-install-task.ts create mode 100644 packages/react-native/src/utils/symlink-task.ts create mode 100644 packages/react-native/src/utils/testing-generators.ts create mode 100644 packages/react-native/src/utils/versions.ts create mode 100644 packages/react-native/tsconfig.json create mode 100644 packages/react-native/tsconfig.lib.json create mode 100644 packages/react-native/tsconfig.spec.json diff --git a/.github/workflows/e2e-matrix.yml b/.github/workflows/e2e-matrix.yml index ad62f150168c4..8267273cac8f5 100644 --- a/.github/workflows/e2e-matrix.yml +++ b/.github/workflows/e2e-matrix.yml @@ -44,6 +44,7 @@ jobs: - e2e-web - e2e-storybook - e2e-workspace + - e2e-react-native,e2e-detox exclude: - os: ubuntu-latest node_version: '14' @@ -121,6 +122,13 @@ jobs: with: version: 6.9.1 + - name: Install applesimutils, reset ios simulators + if: ${{ matrix.os == 'macos-latest' }} + run: | + HOMEBREW_NO_AUTO_UPDATE=1 brew tap wix/brew >/dev/null + brew install applesimutils + xcrun simctl shutdown all && xcrun simctl erase all + - name: Run e2e tests run: yarn nx run-many --target=e2e --projects="${{ join(matrix.packages) }}" env: diff --git a/.gitignore b/.gitignore index 685c5170b1f4c..859b504507f8f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ node_modules dist /build /coverage -test +./test .DS_Store tmp *.log diff --git a/docs/angular/api-detox/executors/build.md b/docs/angular/api-detox/executors/build.md new file mode 100644 index 0000000000000..bfe22ee60d1d7 --- /dev/null +++ b/docs/angular/api-detox/executors/build.md @@ -0,0 +1,23 @@ +# @nrwl/detox:build + +Run the command defined in build property of the specified configuration. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### configPath + +Alias(es): cp + +Type: `string` + +Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or "detox" section in package.json + +### detoxConfiguration + +Alias(es): C + +Type: `string` + +Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it diff --git a/docs/angular/api-detox/executors/test.md b/docs/angular/api-detox/executors/test.md new file mode 100644 index 0000000000000..ab6f91b34736e --- /dev/null +++ b/docs/angular/api-detox/executors/test.md @@ -0,0 +1,177 @@ +# @nrwl/detox:test + +Initiating your detox test suite. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### appLaunchArgs + +Type: `number` + +Custom arguments to pass (through) onto the app every time it is launched. + +### artifactsLocation + +Alias(es): a + +Type: `string` + +Artifacts (logs, screenshots, etc) root directory. + +### captureViewHierarchy + +Type: `string` + +[iOS Only] Capture \*.uihierarchy snapshots on view action errors and device.captureViewHierarchy() calls. + +### cleanup + +Type: `boolean` + +Shutdown simulator when test is over, useful for CI scripts, to make sure detox exists cleanly with no residue + +### configPath + +Alias(es): cp + +Type: `string` + +Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or "detox" section in package.json + +### debugSynchronization + +Alias(es): d + +Type: `string` + +Customize how long an action/expectation can take to complete before Detox starts querying the app why it is busy. By default, the app status will be printed if the action takes more than 10s to complete. + +### detoxConfiguration + +Alias(es): C + +Type: `string` + +Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it + +### deviceLaunchArgs + +Type: `string` + +A list of passthrough-arguments to use when (if) devices (Android emulator / iOS simulator) are launched by Detox. + +### deviceName + +Alias(es): n + +Type: `string` + +Override the device name specified in a configuration. Useful for running a single build configuration on multiple devices. + +### forceAdbInstall + +Type: `boolean` + +Due to problems with the adb install command on Android, Detox resorts to a different scheme for install APK's. Setting true will disable that and force usage of adb install, instead. + +### gpu + +Type: `boolean` + +[Android Only] Launch Emulator with the specific -gpu [gpu mode] parameter. + +### headless + +Type: `boolean` + +Android Only] Launch Emulator in headless mode. Useful when running on CI. + +### inspectBrk + +Type: `boolean` + +Uses node's --inspect-brk flag to let users debug the jest/mocha test runner + +### jestReportSpecs + +Type: `boolean` + +[Jest Only] Whether to output logs per each running spec, in real-time. By default, disabled with multiple workers. + +### loglevel + +Alias(es): l + +Type: `string` + +Log level: fatal, error, warn, info, verbose, trace + +### noColor + +Type: `boolean` + +Disable colors in log output + +### recordLogs + +Type: `string` + +Save logs during each test to artifacts directory. Pass "failing" to save logs of failing tests only. + +### recordPerformance + +Type: `string` + +[iOS Only] Save Detox Instruments performance recordings of each test to artifacts directory. + +### recordTimeline + +Type: `string` + +[Jest Only] Record tests and events timeline, for visual display on the chrome://tracing tool. + +### recordVideos + +Type: `string` + +Save screen recordings of each test to artifacts directory. Pass "failing" to save recordings of failing tests only. + +### retries + +Type: `number` + +[Jest Circus Only] Re-spawn the test runner for individual failing suite files until they pass, or times at least. + +### reuse + +Type: `boolean` + +Reuse existing installed app (do not delete + reinstall) for a faster run. + +### runnerConfig + +Alias(es): o + +Type: `string` + +Test runner config file, defaults to 'e2e/mocha.opts' for mocha and 'e2e/config.json' for jest. + +### takeScreenshots + +Type: `string` + +Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only. + +### useCustomLogger + +Type: `boolean` + +Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation (e.g. Jest / Mocha). + +### workers + +Type: `number` + +Specifies number of workers the test runner should spawn, requires a test runner with parallel execution support (Detox CLI currently supports Jest). diff --git a/docs/angular/api-detox/generators/application.md b/docs/angular/api-detox/generators/application.md new file mode 100644 index 0000000000000..c99ef6852f99f --- /dev/null +++ b/docs/angular/api-detox/generators/application.md @@ -0,0 +1,81 @@ +# @nrwl/detox:application + +Create a detox application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/detox:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Name of the E2E Project + +### project (_**required**_) + +Type: `string` + +The name of the frontend project to test. + +### directory + +Type: `string` + +A directory where the project is placed + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint`, `none` + +The tool to use for running lint checks. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files diff --git a/docs/angular/api-react-native/executors/build-android.md b/docs/angular/api-react-native/executors/build-android.md new file mode 100644 index 0000000000000..a01624528f3a7 --- /dev/null +++ b/docs/angular/api-react-native/executors/build-android.md @@ -0,0 +1,13 @@ +# @nrwl/react-native:build-android + +Release Build for Android. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### apk + +Type: `boolean` + +Generate apk file(s) rather than a bundle (.aab). diff --git a/docs/angular/api-react-native/executors/bundle.md b/docs/angular/api-react-native/executors/bundle.md new file mode 100644 index 0000000000000..41c62f7ea631f --- /dev/null +++ b/docs/angular/api-react-native/executors/bundle.md @@ -0,0 +1,45 @@ +# @nrwl/react-native:bundle + +Builds the JS bundle. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### bundleOutput (_**required**_) + +Type: `string` + +The output path of the generated files. + +### entryFile (_**required**_) + +Type: `string` + +The entry file relative to project root. + +### platform (_**required**_) + +Type: `string` + +Platform to build for (ios, android). + +### dev + +Default: `true` + +Type: `boolean` + +Generate a development build. + +### maxWorkers + +Type: `number` + +The number of workers we should parallelize the transformer on. + +### sourceMap + +Type: `boolean` + +Whether source maps should be generated or not. diff --git a/docs/angular/api-react-native/executors/ensure-symlink.md b/docs/angular/api-react-native/executors/ensure-symlink.md new file mode 100644 index 0000000000000..a4cf66f158fd9 --- /dev/null +++ b/docs/angular/api-react-native/executors/ensure-symlink.md @@ -0,0 +1,5 @@ +# @nrwl/react-native:ensure-symlink + +Ensure workspace node_modules is symlink under app's node_modules folder. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. diff --git a/docs/angular/api-react-native/executors/run-android.md b/docs/angular/api-react-native/executors/run-android.md new file mode 100644 index 0000000000000..d6fd62b95328a --- /dev/null +++ b/docs/angular/api-react-native/executors/run-android.md @@ -0,0 +1,85 @@ +# @nrwl/react-native:run-android + +Runs Android application. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### appId + +Type: `string` + +Specify an applicationId to launch after build. If not specified, 'package' from AndroidManifest.xml will be used. + +### appIdSuffix + +Type: `string` + +Specify an applicationIdSuffix to launch after build. + +### deviceId + +Type: `string` + +Builds your app and starts it on a specific device/simulator with the given device id (listed by running "adb devices" on the command line). + +### jetifier + +Default: `true` + +Type: `boolean` + +Run jetifier – the AndroidX transition tool. By default it runs before Gradle to ease working with libraries that don't support AndroidX yet. + +### mainActivity + +Default: `MainActivity` + +Type: `string` + +Name of the activity to start. + +### packager + +Default: `true` + +Type: `boolean` + +Starts the packager server + +### port + +Default: `8081` + +Type: `number` + +The port where the packager server is listening on. + +### sync + +Default: `true` + +Type: `boolean` + +Syncs npm dependencies to package.json (for React Native autolink). + +### tasks + +Type: `string` + +Run custom gradle tasks. If this argument is provided, then --variant option is ignored. Example: yarn react-native run-android --tasks clean,installDebug. + +### terminal + +Type: `string` + +Launches the Metro Bundler in a new window using the specified terminal path. + +### variant + +Default: `debug` + +Type: `string` + +Specify your app's build variant (e.g. debug, release). diff --git a/docs/angular/api-react-native/executors/run-ios.md b/docs/angular/api-react-native/executors/run-ios.md new file mode 100644 index 0000000000000..4d6a9381bc1c2 --- /dev/null +++ b/docs/angular/api-react-native/executors/run-ios.md @@ -0,0 +1,73 @@ +# @nrwl/react-native:run-ios + +Runs iOS application. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### device + +Type: `string` + +Explicitly set device to use by name. The value is not required if you have a single device connected. + +### install + +Default: `true` + +Type: `boolean` + +Runs 'pod install' for native modules before building iOS app. + +### packager + +Default: `true` + +Type: `boolean` + +Starts the packager server + +### port + +Default: `8081` + +Type: `number` + +The port where the packager server is listening on. + +### scheme + +Type: `string` + +Explicitly set the Xcode scheme to use + +### simulator + +Default: `iPhone X` + +Type: `string` + +Explicitly set simulator to use. Optionally include iOS version between parenthesis at the end to match an exact version: "iPhone X (12.1)" + +### sync + +Default: `true` + +Type: `boolean` + +Syncs npm dependencies to package.json (for React Native autolink). Always true when --install is used. + +### terminal + +Type: `string` + +Launches the Metro Bundler in a new window using the specified terminal path. + +### xcodeConfiguration + +Default: `Debug` + +Type: `string` + +Explicitly set the Xcode configuration to use diff --git a/docs/angular/api-react-native/executors/start.md b/docs/angular/api-react-native/executors/start.md new file mode 100644 index 0000000000000..bd550d0972786 --- /dev/null +++ b/docs/angular/api-react-native/executors/start.md @@ -0,0 +1,23 @@ +# @nrwl/react-native:start + +Starts the dev server for JS bundle. + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### port + +Default: `8081` + +Type: `number` + +The port to listen on. + +### resetCache + +Default: `false` + +Type: `boolean` + +Resets metro cache. diff --git a/docs/angular/api-react-native/executors/sync-deps.md b/docs/angular/api-react-native/executors/sync-deps.md new file mode 100644 index 0000000000000..8ac77c9a37d40 --- /dev/null +++ b/docs/angular/api-react-native/executors/sync-deps.md @@ -0,0 +1,13 @@ +# @nrwl/react-native:sync-deps + +Syncs dependencies to package.json (required for autolinking). + +Options can be configured in `angular.json` when defining the executor, or when invoking it. + +## Options + +### include + +Type: `string` + +A comma-separated list of additional npm packages to include. e.g. 'nx sync-deps --include=react-native-gesture-handler,react-native-safe-area-context' diff --git a/docs/angular/api-react-native/generators/application.md b/docs/angular/api-react-native/generators/application.md new file mode 100644 index 0000000000000..df252a83c6397 --- /dev/null +++ b/docs/angular/api-react-native/generators/application.md @@ -0,0 +1,125 @@ +# @nrwl/react-native:application + +Create an application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +### Examples + +Generate apps/nested/myapp: + +```bash +nx g app myapp --directory=nested +``` + +Use class components instead of functional components: + +```bash +nx g app myapp --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +The directory of the new application. + +### displayName + +Type: `string` + +The display name to show in the application. Defaults to name. + +### e2eTestRunner + +Default: `detox` + +Type: `string` + +Possible values: `detox`, `none` + +Adds the specified e2e test runner + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint` + +The tool to use for running lint checks. + +### name + +Type: `string` + +The name of the application. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the application (used for linting) + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/angular/api-react-native/generators/component.md b/docs/angular/api-react-native/generators/component.md new file mode 100644 index 0000000000000..f01e543de29e6 --- /dev/null +++ b/docs/angular/api-react-native/generators/component.md @@ -0,0 +1,119 @@ +# @nrwl/react-native:component + +Create a component + +## Usage + +```bash +nx generate component ... +``` + +```bash +nx g c ... # same +``` + +By default, Nx will search for `component` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:component ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g component ... --dry-run +``` + +### Examples + +Generate a component in the mylib library: + +```bash +nx g component my-component --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g component my-component --project=mylib --classComponent +``` + +## Options + +### name (_**required**_) + +Type: `string` + +The name of the component. + +### project (_**required**_) + +Alias(es): p + +Type: `string` + +The name of the project. + +### classComponent + +Alias(es): C + +Default: `false` + +Type: `boolean` + +Use class components instead of functional component. + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case component file name (e.g. App.tsx). + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. diff --git a/docs/angular/api-react-native/generators/library.md b/docs/angular/api-react-native/generators/library.md new file mode 100644 index 0000000000000..c7a2c5e11e5bc --- /dev/null +++ b/docs/angular/api-react-native/generators/library.md @@ -0,0 +1,157 @@ +# @nrwl/react-native:library + +Create a library + +## Usage + +```bash +nx generate library ... +``` + +```bash +nx g lib ... # same +``` + +By default, Nx will search for `library` in the default collection provisioned in `angular.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:library ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g library ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + +### directory + +Alias(es): d + +Type: `string` + +A directory where the lib is placed. + +### globalCss + +Default: `false` + +Type: `boolean` + +When true, the stylesheet is generated using global CSS instead of CSS modules (e.g. file is '_.css' rather than '_.module.css'). + +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case component file name (e.g. App.tsx). + +### publishable + +Type: `boolean` + +Create a publishable library. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files. + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the library (used for linting). + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests. diff --git a/docs/angular/executors.json b/docs/angular/executors.json index acf6274462afd..20e4d08631c8b 100644 --- a/docs/angular/executors.json +++ b/docs/angular/executors.json @@ -1,12 +1,14 @@ [ "angular", "cypress", + "detox", "gatsby", "jest", "linter", "next", "node", "nx-plugin", + "react-native", "storybook", "web", "workspace" diff --git a/docs/angular/generators.json b/docs/angular/generators.json index 46a738dc38252..0ae1fbf35a4b1 100644 --- a/docs/angular/generators.json +++ b/docs/angular/generators.json @@ -1,6 +1,7 @@ [ "angular", "cypress", + "detox", "express", "gatsby", "jest", @@ -9,6 +10,7 @@ "node", "nx-plugin", "react", + "react-native", "storybook", "web", "workspace" diff --git a/docs/map.json b/docs/map.json index 505a804dbb7af..bd3e1f7314737 100644 --- a/docs/map.json +++ b/docs/map.json @@ -908,6 +908,83 @@ } ] }, + { + "name": "detox", + "id": "detox", + "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "angular/api-detox/generators/application" + }, + { + "name": "build executor", + "id": "build", + "file": "angular/api-detox/executors/build" + }, + { + "name": "test executor", + "id": "test", + "file": "angular/api-detox/executors/test" + } + ] + }, + { + "name": "react native", + "id": "react-native", + "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "angular/api-react-native/generators/application" + }, + { + "name": "component generator", + "id": "component", + "file": "angular/api-react-native/generators/component" + }, + { + "name": "library generator", + "id": "library", + "file": "angular/api-react-native/generators/library" + }, + { + "name": "build android executor", + "id": "build-android", + "file": "angular/api-react-native/executors/build-android" + }, + { + "name": "bundle executor", + "id": "bundle", + "file": "angular/api-react-native/executors/bundle" + }, + { + "name": "ensure symlink executor", + "id": "ensure-symlink", + "file": "angular/api-react-native/executors/ensure-symlink" + }, + { + "name": "run android executor", + "id": "run-android", + "file": "angular/api-react-native/executors/run-android" + }, + { + "name": "run ios executor", + "id": "run-ios", + "file": "angular/api-react-native/executors/run-ios" + }, + { + "name": "start executor", + "id": "start", + "file": "angular/api-react-native/executors/start" + }, + { + "name": "sync deps executor", + "id": "sync-deps", + "file": "angular/api-react-native/executors/sync-deps" + } + ] + }, { "name": "Nx Plugin", "id": "nx-plugin", @@ -2120,6 +2197,104 @@ } ] }, + { + "name": "detox", + "id": "detox", + "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "react/api-detox/generators/application" + }, + { + "name": "build executor", + "id": "build", + "file": "react/api-detox/executors/build" + }, + { + "name": "test executor", + "id": "test", + "file": "react/api-detox/executors/test" + } + ] + }, + { + "name": "detox", + "id": "detox", + "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "node/api-detox/generators/application" + }, + { + "name": "build executor", + "id": "build", + "file": "node/api-detox/executors/build" + }, + { + "name": "test executor", + "id": "test", + "file": "node/api-detox/executors/test" + } + ] + }, + { + "name": "react native", + "id": "react-native", + "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "react/api-react-native/generators/application" + }, + { + "name": "component generator", + "id": "component", + "file": "react/api-react-native/generators/component" + }, + { + "name": "library generator", + "id": "library", + "file": "react/api-react-native/generators/library" + }, + { + "name": "build android executor", + "id": "build-android", + "file": "react/api-react-native/executors/build-android" + }, + { + "name": "bundle executor", + "id": "bundle", + "file": "react/api-react-native/executors/bundle" + }, + { + "name": "ensure symlink executor", + "id": "ensure-symlink", + "file": "react/api-react-native/executors/ensure-symlink" + }, + { + "name": "run android executor", + "id": "run-android", + "file": "react/api-react-native/executors/run-android" + }, + { + "name": "run ios executor", + "id": "run-ios", + "file": "react/api-react-native/executors/run-ios" + }, + { + "name": "start executor", + "id": "start", + "file": "react/api-react-native/executors/start" + }, + { + "name": "sync deps executor", + "id": "sync-deps", + "file": "react/api-react-native/executors/sync-deps" + } + ] + }, { "name": "Nx Plugin", "id": "nx-plugin", @@ -3273,6 +3448,62 @@ } ] }, + { + "name": "react native", + "id": "react-native", + "itemList": [ + { + "name": "application generator", + "id": "application", + "file": "node/api-react-native/generators/application" + }, + { + "name": "component generator", + "id": "component", + "file": "node/api-react-native/generators/component" + }, + { + "name": "library generator", + "id": "library", + "file": "node/api-react-native/generators/library" + }, + { + "name": "build android executor", + "id": "build-android", + "file": "node/api-react-native/executors/build-android" + }, + { + "name": "bundle executor", + "id": "bundle", + "file": "node/api-react-native/executors/bundle" + }, + { + "name": "ensure symlink executor", + "id": "ensure-symlink", + "file": "node/api-react-native/executors/ensure-symlink" + }, + { + "name": "run android executor", + "id": "run-android", + "file": "node/api-react-native/executors/run-android" + }, + { + "name": "run ios executor", + "id": "run-ios", + "file": "node/api-react-native/executors/run-ios" + }, + { + "name": "start executor", + "id": "start", + "file": "node/api-react-native/executors/start" + }, + { + "name": "sync deps executor", + "id": "sync-deps", + "file": "node/api-react-native/executors/sync-deps" + } + ] + }, { "name": "Nx Plugin", "id": "nx-plugin", diff --git a/docs/node/api-detox/executors/build.md b/docs/node/api-detox/executors/build.md new file mode 100644 index 0000000000000..913b5c1f68c21 --- /dev/null +++ b/docs/node/api-detox/executors/build.md @@ -0,0 +1,24 @@ +# @nrwl/detox:build + +Run the command defined in build property of the specified configuration. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### configPath + +Alias(es): cp + +Type: `string` + +Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or "detox" section in package.json + +### detoxConfiguration + +Alias(es): C + +Type: `string` + +Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it diff --git a/docs/node/api-detox/executors/test.md b/docs/node/api-detox/executors/test.md new file mode 100644 index 0000000000000..8e1e87066e756 --- /dev/null +++ b/docs/node/api-detox/executors/test.md @@ -0,0 +1,178 @@ +# @nrwl/detox:test + +Initiating your detox test suite. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### appLaunchArgs + +Type: `number` + +Custom arguments to pass (through) onto the app every time it is launched. + +### artifactsLocation + +Alias(es): a + +Type: `string` + +Artifacts (logs, screenshots, etc) root directory. + +### captureViewHierarchy + +Type: `string` + +[iOS Only] Capture \*.uihierarchy snapshots on view action errors and device.captureViewHierarchy() calls. + +### cleanup + +Type: `boolean` + +Shutdown simulator when test is over, useful for CI scripts, to make sure detox exists cleanly with no residue + +### configPath + +Alias(es): cp + +Type: `string` + +Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or "detox" section in package.json + +### debugSynchronization + +Alias(es): d + +Type: `string` + +Customize how long an action/expectation can take to complete before Detox starts querying the app why it is busy. By default, the app status will be printed if the action takes more than 10s to complete. + +### detoxConfiguration + +Alias(es): C + +Type: `string` + +Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it + +### deviceLaunchArgs + +Type: `string` + +A list of passthrough-arguments to use when (if) devices (Android emulator / iOS simulator) are launched by Detox. + +### deviceName + +Alias(es): n + +Type: `string` + +Override the device name specified in a configuration. Useful for running a single build configuration on multiple devices. + +### forceAdbInstall + +Type: `boolean` + +Due to problems with the adb install command on Android, Detox resorts to a different scheme for install APK's. Setting true will disable that and force usage of adb install, instead. + +### gpu + +Type: `boolean` + +[Android Only] Launch Emulator with the specific -gpu [gpu mode] parameter. + +### headless + +Type: `boolean` + +Android Only] Launch Emulator in headless mode. Useful when running on CI. + +### inspectBrk + +Type: `boolean` + +Uses node's --inspect-brk flag to let users debug the jest/mocha test runner + +### jestReportSpecs + +Type: `boolean` + +[Jest Only] Whether to output logs per each running spec, in real-time. By default, disabled with multiple workers. + +### loglevel + +Alias(es): l + +Type: `string` + +Log level: fatal, error, warn, info, verbose, trace + +### noColor + +Type: `boolean` + +Disable colors in log output + +### recordLogs + +Type: `string` + +Save logs during each test to artifacts directory. Pass "failing" to save logs of failing tests only. + +### recordPerformance + +Type: `string` + +[iOS Only] Save Detox Instruments performance recordings of each test to artifacts directory. + +### recordTimeline + +Type: `string` + +[Jest Only] Record tests and events timeline, for visual display on the chrome://tracing tool. + +### recordVideos + +Type: `string` + +Save screen recordings of each test to artifacts directory. Pass "failing" to save recordings of failing tests only. + +### retries + +Type: `number` + +[Jest Circus Only] Re-spawn the test runner for individual failing suite files until they pass, or times at least. + +### reuse + +Type: `boolean` + +Reuse existing installed app (do not delete + reinstall) for a faster run. + +### runnerConfig + +Alias(es): o + +Type: `string` + +Test runner config file, defaults to 'e2e/mocha.opts' for mocha and 'e2e/config.json' for jest. + +### takeScreenshots + +Type: `string` + +Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only. + +### useCustomLogger + +Type: `boolean` + +Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation (e.g. Jest / Mocha). + +### workers + +Type: `number` + +Specifies number of workers the test runner should spawn, requires a test runner with parallel execution support (Detox CLI currently supports Jest). diff --git a/docs/node/api-detox/generators/application.md b/docs/node/api-detox/generators/application.md new file mode 100644 index 0000000000000..d4623cead290e --- /dev/null +++ b/docs/node/api-detox/generators/application.md @@ -0,0 +1,81 @@ +# @nrwl/detox:application + +Create a detox application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/detox:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Name of the E2E Project + +### project (_**required**_) + +Type: `string` + +The name of the frontend project to test. + +### directory + +Type: `string` + +A directory where the project is placed + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint`, `none` + +The tool to use for running lint checks. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files diff --git a/docs/node/api-react-native/executors/build-android.md b/docs/node/api-react-native/executors/build-android.md new file mode 100644 index 0000000000000..270dbe5d42b4d --- /dev/null +++ b/docs/node/api-react-native/executors/build-android.md @@ -0,0 +1,14 @@ +# @nrwl/react-native:build-android + +Release Build for Android. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### apk + +Type: `boolean` + +Generate apk file(s) rather than a bundle (.aab). diff --git a/docs/node/api-react-native/executors/bundle.md b/docs/node/api-react-native/executors/bundle.md new file mode 100644 index 0000000000000..b289379ad6b94 --- /dev/null +++ b/docs/node/api-react-native/executors/bundle.md @@ -0,0 +1,46 @@ +# @nrwl/react-native:bundle + +Builds the JS bundle. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### bundleOutput (_**required**_) + +Type: `string` + +The output path of the generated files. + +### entryFile (_**required**_) + +Type: `string` + +The entry file relative to project root. + +### platform (_**required**_) + +Type: `string` + +Platform to build for (ios, android). + +### dev + +Default: `true` + +Type: `boolean` + +Generate a development build. + +### maxWorkers + +Type: `number` + +The number of workers we should parallelize the transformer on. + +### sourceMap + +Type: `boolean` + +Whether source maps should be generated or not. diff --git a/docs/node/api-react-native/executors/ensure-symlink.md b/docs/node/api-react-native/executors/ensure-symlink.md new file mode 100644 index 0000000000000..f05ec6a696279 --- /dev/null +++ b/docs/node/api-react-native/executors/ensure-symlink.md @@ -0,0 +1,6 @@ +# @nrwl/react-native:ensure-symlink + +Ensure workspace node_modules is symlink under app's node_modules folder. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. diff --git a/docs/node/api-react-native/executors/run-android.md b/docs/node/api-react-native/executors/run-android.md new file mode 100644 index 0000000000000..edbda005cf5fa --- /dev/null +++ b/docs/node/api-react-native/executors/run-android.md @@ -0,0 +1,86 @@ +# @nrwl/react-native:run-android + +Runs Android application. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### appId + +Type: `string` + +Specify an applicationId to launch after build. If not specified, 'package' from AndroidManifest.xml will be used. + +### appIdSuffix + +Type: `string` + +Specify an applicationIdSuffix to launch after build. + +### deviceId + +Type: `string` + +Builds your app and starts it on a specific device/simulator with the given device id (listed by running "adb devices" on the command line). + +### jetifier + +Default: `true` + +Type: `boolean` + +Run jetifier – the AndroidX transition tool. By default it runs before Gradle to ease working with libraries that don't support AndroidX yet. + +### mainActivity + +Default: `MainActivity` + +Type: `string` + +Name of the activity to start. + +### packager + +Default: `true` + +Type: `boolean` + +Starts the packager server + +### port + +Default: `8081` + +Type: `number` + +The port where the packager server is listening on. + +### sync + +Default: `true` + +Type: `boolean` + +Syncs npm dependencies to package.json (for React Native autolink). + +### tasks + +Type: `string` + +Run custom gradle tasks. If this argument is provided, then --variant option is ignored. Example: yarn react-native run-android --tasks clean,installDebug. + +### terminal + +Type: `string` + +Launches the Metro Bundler in a new window using the specified terminal path. + +### variant + +Default: `debug` + +Type: `string` + +Specify your app's build variant (e.g. debug, release). diff --git a/docs/node/api-react-native/executors/run-ios.md b/docs/node/api-react-native/executors/run-ios.md new file mode 100644 index 0000000000000..620b6cf2e45bd --- /dev/null +++ b/docs/node/api-react-native/executors/run-ios.md @@ -0,0 +1,74 @@ +# @nrwl/react-native:run-ios + +Runs iOS application. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### device + +Type: `string` + +Explicitly set device to use by name. The value is not required if you have a single device connected. + +### install + +Default: `true` + +Type: `boolean` + +Runs 'pod install' for native modules before building iOS app. + +### packager + +Default: `true` + +Type: `boolean` + +Starts the packager server + +### port + +Default: `8081` + +Type: `number` + +The port where the packager server is listening on. + +### scheme + +Type: `string` + +Explicitly set the Xcode scheme to use + +### simulator + +Default: `iPhone X` + +Type: `string` + +Explicitly set simulator to use. Optionally include iOS version between parenthesis at the end to match an exact version: "iPhone X (12.1)" + +### sync + +Default: `true` + +Type: `boolean` + +Syncs npm dependencies to package.json (for React Native autolink). Always true when --install is used. + +### terminal + +Type: `string` + +Launches the Metro Bundler in a new window using the specified terminal path. + +### xcodeConfiguration + +Default: `Debug` + +Type: `string` + +Explicitly set the Xcode configuration to use diff --git a/docs/node/api-react-native/executors/start.md b/docs/node/api-react-native/executors/start.md new file mode 100644 index 0000000000000..a8c4b864521b0 --- /dev/null +++ b/docs/node/api-react-native/executors/start.md @@ -0,0 +1,24 @@ +# @nrwl/react-native:start + +Starts the dev server for JS bundle. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### port + +Default: `8081` + +Type: `number` + +The port to listen on. + +### resetCache + +Default: `false` + +Type: `boolean` + +Resets metro cache. diff --git a/docs/node/api-react-native/executors/sync-deps.md b/docs/node/api-react-native/executors/sync-deps.md new file mode 100644 index 0000000000000..f569fc5e4016c --- /dev/null +++ b/docs/node/api-react-native/executors/sync-deps.md @@ -0,0 +1,14 @@ +# @nrwl/react-native:sync-deps + +Syncs dependencies to package.json (required for autolinking). + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### include + +Type: `string` + +A comma-separated list of additional npm packages to include. e.g. 'nx sync-deps --include=react-native-gesture-handler,react-native-safe-area-context' diff --git a/docs/node/api-react-native/generators/application.md b/docs/node/api-react-native/generators/application.md new file mode 100644 index 0000000000000..9d531048d8c34 --- /dev/null +++ b/docs/node/api-react-native/generators/application.md @@ -0,0 +1,125 @@ +# @nrwl/react-native:application + +Create an application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +### Examples + +Generate apps/nested/myapp: + +```bash +nx g app myapp --directory=nested +``` + +Use class components instead of functional components: + +```bash +nx g app myapp --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +The directory of the new application. + +### displayName + +Type: `string` + +The display name to show in the application. Defaults to name. + +### e2eTestRunner + +Default: `detox` + +Type: `string` + +Possible values: `detox`, `none` + +Adds the specified e2e test runner + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint` + +The tool to use for running lint checks. + +### name + +Type: `string` + +The name of the application. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the application (used for linting) + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/node/api-react-native/generators/component.md b/docs/node/api-react-native/generators/component.md new file mode 100644 index 0000000000000..4070af5e98d3e --- /dev/null +++ b/docs/node/api-react-native/generators/component.md @@ -0,0 +1,119 @@ +# @nrwl/react-native:component + +Create a component + +## Usage + +```bash +nx generate component ... +``` + +```bash +nx g c ... # same +``` + +By default, Nx will search for `component` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:component ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g component ... --dry-run +``` + +### Examples + +Generate a component in the mylib library: + +```bash +nx g component my-component --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g component my-component --project=mylib --classComponent +``` + +## Options + +### name (_**required**_) + +Type: `string` + +The name of the component. + +### project (_**required**_) + +Alias(es): p + +Type: `string` + +The name of the project. + +### classComponent + +Alias(es): C + +Default: `false` + +Type: `boolean` + +Use class components instead of functional component. + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case component file name (e.g. App.tsx). + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. diff --git a/docs/node/api-react-native/generators/library.md b/docs/node/api-react-native/generators/library.md new file mode 100644 index 0000000000000..2f26c9872b83f --- /dev/null +++ b/docs/node/api-react-native/generators/library.md @@ -0,0 +1,157 @@ +# @nrwl/react-native:library + +Create a library + +## Usage + +```bash +nx generate library ... +``` + +```bash +nx g lib ... # same +``` + +By default, Nx will search for `library` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:library ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g library ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + +### directory + +Alias(es): d + +Type: `string` + +A directory where the lib is placed. + +### globalCss + +Default: `false` + +Type: `boolean` + +When true, the stylesheet is generated using global CSS instead of CSS modules (e.g. file is '_.css' rather than '_.module.css'). + +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case component file name (e.g. App.tsx). + +### publishable + +Type: `boolean` + +Create a publishable library. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files. + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the library (used for linting). + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests. diff --git a/docs/node/executors.json b/docs/node/executors.json index acf6274462afd..20e4d08631c8b 100644 --- a/docs/node/executors.json +++ b/docs/node/executors.json @@ -1,12 +1,14 @@ [ "angular", "cypress", + "detox", "gatsby", "jest", "linter", "next", "node", "nx-plugin", + "react-native", "storybook", "web", "workspace" diff --git a/docs/node/generators.json b/docs/node/generators.json index 46a738dc38252..0ae1fbf35a4b1 100644 --- a/docs/node/generators.json +++ b/docs/node/generators.json @@ -1,6 +1,7 @@ [ "angular", "cypress", + "detox", "express", "gatsby", "jest", @@ -9,6 +10,7 @@ "node", "nx-plugin", "react", + "react-native", "storybook", "web", "workspace" diff --git a/docs/react/api-detox/executors/build.md b/docs/react/api-detox/executors/build.md new file mode 100644 index 0000000000000..913b5c1f68c21 --- /dev/null +++ b/docs/react/api-detox/executors/build.md @@ -0,0 +1,24 @@ +# @nrwl/detox:build + +Run the command defined in build property of the specified configuration. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### configPath + +Alias(es): cp + +Type: `string` + +Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or "detox" section in package.json + +### detoxConfiguration + +Alias(es): C + +Type: `string` + +Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it diff --git a/docs/react/api-detox/executors/test.md b/docs/react/api-detox/executors/test.md new file mode 100644 index 0000000000000..8e1e87066e756 --- /dev/null +++ b/docs/react/api-detox/executors/test.md @@ -0,0 +1,178 @@ +# @nrwl/detox:test + +Initiating your detox test suite. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### appLaunchArgs + +Type: `number` + +Custom arguments to pass (through) onto the app every time it is launched. + +### artifactsLocation + +Alias(es): a + +Type: `string` + +Artifacts (logs, screenshots, etc) root directory. + +### captureViewHierarchy + +Type: `string` + +[iOS Only] Capture \*.uihierarchy snapshots on view action errors and device.captureViewHierarchy() calls. + +### cleanup + +Type: `boolean` + +Shutdown simulator when test is over, useful for CI scripts, to make sure detox exists cleanly with no residue + +### configPath + +Alias(es): cp + +Type: `string` + +Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or "detox" section in package.json + +### debugSynchronization + +Alias(es): d + +Type: `string` + +Customize how long an action/expectation can take to complete before Detox starts querying the app why it is busy. By default, the app status will be printed if the action takes more than 10s to complete. + +### detoxConfiguration + +Alias(es): C + +Type: `string` + +Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it + +### deviceLaunchArgs + +Type: `string` + +A list of passthrough-arguments to use when (if) devices (Android emulator / iOS simulator) are launched by Detox. + +### deviceName + +Alias(es): n + +Type: `string` + +Override the device name specified in a configuration. Useful for running a single build configuration on multiple devices. + +### forceAdbInstall + +Type: `boolean` + +Due to problems with the adb install command on Android, Detox resorts to a different scheme for install APK's. Setting true will disable that and force usage of adb install, instead. + +### gpu + +Type: `boolean` + +[Android Only] Launch Emulator with the specific -gpu [gpu mode] parameter. + +### headless + +Type: `boolean` + +Android Only] Launch Emulator in headless mode. Useful when running on CI. + +### inspectBrk + +Type: `boolean` + +Uses node's --inspect-brk flag to let users debug the jest/mocha test runner + +### jestReportSpecs + +Type: `boolean` + +[Jest Only] Whether to output logs per each running spec, in real-time. By default, disabled with multiple workers. + +### loglevel + +Alias(es): l + +Type: `string` + +Log level: fatal, error, warn, info, verbose, trace + +### noColor + +Type: `boolean` + +Disable colors in log output + +### recordLogs + +Type: `string` + +Save logs during each test to artifacts directory. Pass "failing" to save logs of failing tests only. + +### recordPerformance + +Type: `string` + +[iOS Only] Save Detox Instruments performance recordings of each test to artifacts directory. + +### recordTimeline + +Type: `string` + +[Jest Only] Record tests and events timeline, for visual display on the chrome://tracing tool. + +### recordVideos + +Type: `string` + +Save screen recordings of each test to artifacts directory. Pass "failing" to save recordings of failing tests only. + +### retries + +Type: `number` + +[Jest Circus Only] Re-spawn the test runner for individual failing suite files until they pass, or times at least. + +### reuse + +Type: `boolean` + +Reuse existing installed app (do not delete + reinstall) for a faster run. + +### runnerConfig + +Alias(es): o + +Type: `string` + +Test runner config file, defaults to 'e2e/mocha.opts' for mocha and 'e2e/config.json' for jest. + +### takeScreenshots + +Type: `string` + +Save screenshots before and after each test to artifacts directory. Pass "failing" to save screenshots of failing tests only. + +### useCustomLogger + +Type: `boolean` + +Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation (e.g. Jest / Mocha). + +### workers + +Type: `number` + +Specifies number of workers the test runner should spawn, requires a test runner with parallel execution support (Detox CLI currently supports Jest). diff --git a/docs/react/api-detox/generators/application.md b/docs/react/api-detox/generators/application.md new file mode 100644 index 0000000000000..d4623cead290e --- /dev/null +++ b/docs/react/api-detox/generators/application.md @@ -0,0 +1,81 @@ +# @nrwl/detox:application + +Create a detox application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/detox:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Name of the E2E Project + +### project (_**required**_) + +Type: `string` + +The name of the frontend project to test. + +### directory + +Type: `string` + +A directory where the project is placed + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint`, `none` + +The tool to use for running lint checks. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files diff --git a/docs/react/api-react-native/executors/build-android.md b/docs/react/api-react-native/executors/build-android.md new file mode 100644 index 0000000000000..270dbe5d42b4d --- /dev/null +++ b/docs/react/api-react-native/executors/build-android.md @@ -0,0 +1,14 @@ +# @nrwl/react-native:build-android + +Release Build for Android. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### apk + +Type: `boolean` + +Generate apk file(s) rather than a bundle (.aab). diff --git a/docs/react/api-react-native/executors/bundle.md b/docs/react/api-react-native/executors/bundle.md new file mode 100644 index 0000000000000..b289379ad6b94 --- /dev/null +++ b/docs/react/api-react-native/executors/bundle.md @@ -0,0 +1,46 @@ +# @nrwl/react-native:bundle + +Builds the JS bundle. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### bundleOutput (_**required**_) + +Type: `string` + +The output path of the generated files. + +### entryFile (_**required**_) + +Type: `string` + +The entry file relative to project root. + +### platform (_**required**_) + +Type: `string` + +Platform to build for (ios, android). + +### dev + +Default: `true` + +Type: `boolean` + +Generate a development build. + +### maxWorkers + +Type: `number` + +The number of workers we should parallelize the transformer on. + +### sourceMap + +Type: `boolean` + +Whether source maps should be generated or not. diff --git a/docs/react/api-react-native/executors/ensure-symlink.md b/docs/react/api-react-native/executors/ensure-symlink.md new file mode 100644 index 0000000000000..f05ec6a696279 --- /dev/null +++ b/docs/react/api-react-native/executors/ensure-symlink.md @@ -0,0 +1,6 @@ +# @nrwl/react-native:ensure-symlink + +Ensure workspace node_modules is symlink under app's node_modules folder. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. diff --git a/docs/react/api-react-native/executors/run-android.md b/docs/react/api-react-native/executors/run-android.md new file mode 100644 index 0000000000000..edbda005cf5fa --- /dev/null +++ b/docs/react/api-react-native/executors/run-android.md @@ -0,0 +1,86 @@ +# @nrwl/react-native:run-android + +Runs Android application. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### appId + +Type: `string` + +Specify an applicationId to launch after build. If not specified, 'package' from AndroidManifest.xml will be used. + +### appIdSuffix + +Type: `string` + +Specify an applicationIdSuffix to launch after build. + +### deviceId + +Type: `string` + +Builds your app and starts it on a specific device/simulator with the given device id (listed by running "adb devices" on the command line). + +### jetifier + +Default: `true` + +Type: `boolean` + +Run jetifier – the AndroidX transition tool. By default it runs before Gradle to ease working with libraries that don't support AndroidX yet. + +### mainActivity + +Default: `MainActivity` + +Type: `string` + +Name of the activity to start. + +### packager + +Default: `true` + +Type: `boolean` + +Starts the packager server + +### port + +Default: `8081` + +Type: `number` + +The port where the packager server is listening on. + +### sync + +Default: `true` + +Type: `boolean` + +Syncs npm dependencies to package.json (for React Native autolink). + +### tasks + +Type: `string` + +Run custom gradle tasks. If this argument is provided, then --variant option is ignored. Example: yarn react-native run-android --tasks clean,installDebug. + +### terminal + +Type: `string` + +Launches the Metro Bundler in a new window using the specified terminal path. + +### variant + +Default: `debug` + +Type: `string` + +Specify your app's build variant (e.g. debug, release). diff --git a/docs/react/api-react-native/executors/run-ios.md b/docs/react/api-react-native/executors/run-ios.md new file mode 100644 index 0000000000000..620b6cf2e45bd --- /dev/null +++ b/docs/react/api-react-native/executors/run-ios.md @@ -0,0 +1,74 @@ +# @nrwl/react-native:run-ios + +Runs iOS application. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### device + +Type: `string` + +Explicitly set device to use by name. The value is not required if you have a single device connected. + +### install + +Default: `true` + +Type: `boolean` + +Runs 'pod install' for native modules before building iOS app. + +### packager + +Default: `true` + +Type: `boolean` + +Starts the packager server + +### port + +Default: `8081` + +Type: `number` + +The port where the packager server is listening on. + +### scheme + +Type: `string` + +Explicitly set the Xcode scheme to use + +### simulator + +Default: `iPhone X` + +Type: `string` + +Explicitly set simulator to use. Optionally include iOS version between parenthesis at the end to match an exact version: "iPhone X (12.1)" + +### sync + +Default: `true` + +Type: `boolean` + +Syncs npm dependencies to package.json (for React Native autolink). Always true when --install is used. + +### terminal + +Type: `string` + +Launches the Metro Bundler in a new window using the specified terminal path. + +### xcodeConfiguration + +Default: `Debug` + +Type: `string` + +Explicitly set the Xcode configuration to use diff --git a/docs/react/api-react-native/executors/start.md b/docs/react/api-react-native/executors/start.md new file mode 100644 index 0000000000000..a8c4b864521b0 --- /dev/null +++ b/docs/react/api-react-native/executors/start.md @@ -0,0 +1,24 @@ +# @nrwl/react-native:start + +Starts the dev server for JS bundle. + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### port + +Default: `8081` + +Type: `number` + +The port to listen on. + +### resetCache + +Default: `false` + +Type: `boolean` + +Resets metro cache. diff --git a/docs/react/api-react-native/executors/sync-deps.md b/docs/react/api-react-native/executors/sync-deps.md new file mode 100644 index 0000000000000..f569fc5e4016c --- /dev/null +++ b/docs/react/api-react-native/executors/sync-deps.md @@ -0,0 +1,14 @@ +# @nrwl/react-native:sync-deps + +Syncs dependencies to package.json (required for autolinking). + +Options can be configured in `workspace.json` when defining the executor, or when invoking it. +Read more about how to use executors and the CLI here: https://nx.dev/getting-started/nx-cli#common-commands. + +## Options + +### include + +Type: `string` + +A comma-separated list of additional npm packages to include. e.g. 'nx sync-deps --include=react-native-gesture-handler,react-native-safe-area-context' diff --git a/docs/react/api-react-native/generators/application.md b/docs/react/api-react-native/generators/application.md new file mode 100644 index 0000000000000..9d531048d8c34 --- /dev/null +++ b/docs/react/api-react-native/generators/application.md @@ -0,0 +1,125 @@ +# @nrwl/react-native:application + +Create an application + +## Usage + +```bash +nx generate application ... +``` + +```bash +nx g app ... # same +``` + +By default, Nx will search for `application` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:application ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g application ... --dry-run +``` + +### Examples + +Generate apps/nested/myapp: + +```bash +nx g app myapp --directory=nested +``` + +Use class components instead of functional components: + +```bash +nx g app myapp --classComponent +``` + +## Options + +### directory + +Alias(es): d + +Type: `string` + +The directory of the new application. + +### displayName + +Type: `string` + +The display name to show in the application. Defaults to name. + +### e2eTestRunner + +Default: `detox` + +Type: `string` + +Possible values: `detox`, `none` + +Adds the specified e2e test runner + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint` + +The tool to use for running lint checks. + +### name + +Type: `string` + +The name of the application. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the application (used for linting) + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests diff --git a/docs/react/api-react-native/generators/component.md b/docs/react/api-react-native/generators/component.md new file mode 100644 index 0000000000000..4070af5e98d3e --- /dev/null +++ b/docs/react/api-react-native/generators/component.md @@ -0,0 +1,119 @@ +# @nrwl/react-native:component + +Create a component + +## Usage + +```bash +nx generate component ... +``` + +```bash +nx g c ... # same +``` + +By default, Nx will search for `component` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:component ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g component ... --dry-run +``` + +### Examples + +Generate a component in the mylib library: + +```bash +nx g component my-component --project=mylib +``` + +Generate a class component in the mylib library: + +```bash +nx g component my-component --project=mylib --classComponent +``` + +## Options + +### name (_**required**_) + +Type: `string` + +The name of the component. + +### project (_**required**_) + +Alias(es): p + +Type: `string` + +The name of the project. + +### classComponent + +Alias(es): C + +Default: `false` + +Type: `boolean` + +Use class components instead of functional component. + +### directory + +Alias(es): d + +Type: `string` + +Create the component under this directory (can be nested). + +### export + +Alias(es): e + +Default: `false` + +Type: `boolean` + +When true, the component is exported from the project index.ts (if it exists). + +### flat + +Default: `false` + +Type: `boolean` + +Create component at the source root rather than its own directory. + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case component file name (e.g. App.tsx). + +### skipTests + +Default: `false` + +Type: `boolean` + +When true, does not create "spec.ts" test files for the new component. diff --git a/docs/react/api-react-native/generators/library.md b/docs/react/api-react-native/generators/library.md new file mode 100644 index 0000000000000..2f26c9872b83f --- /dev/null +++ b/docs/react/api-react-native/generators/library.md @@ -0,0 +1,157 @@ +# @nrwl/react-native:library + +Create a library + +## Usage + +```bash +nx generate library ... +``` + +```bash +nx g lib ... # same +``` + +By default, Nx will search for `library` in the default collection provisioned in `workspace.json`. + +You can specify the collection explicitly as follows: + +```bash +nx g @nrwl/react-native:library ... +``` + +Show what will be generated without writing to disk: + +```bash +nx g library ... --dry-run +``` + +### Examples + +Generate libs/myapp/mylib: + +```bash +nx g lib mylib --directory=myapp +``` + +## Options + +### name (_**required**_) + +Type: `string` + +Library name + +### buildable + +Default: `false` + +Type: `boolean` + +Generate a buildable library. + +### directory + +Alias(es): d + +Type: `string` + +A directory where the lib is placed. + +### globalCss + +Default: `false` + +Type: `boolean` + +When true, the stylesheet is generated using global CSS instead of CSS modules (e.g. file is '_.css' rather than '_.module.css'). + +### importPath + +Type: `string` + +The library name used to import it, like @myorg/my-awesome-lib + +### js + +Default: `false` + +Type: `boolean` + +Generate JavaScript files rather than TypeScript files. + +### linter + +Default: `eslint` + +Type: `string` + +Possible values: `eslint`, `tslint` + +The tool to use for running lint checks. + +### pascalCaseFiles + +Alias(es): P + +Default: `false` + +Type: `boolean` + +Use pascal case component file name (e.g. App.tsx). + +### publishable + +Type: `boolean` + +Create a publishable library. + +### setParserOptionsProject + +Default: `false` + +Type: `boolean` + +Whether or not to configure the ESLint "parserOptions.project" option. We do not do this by default for lint performance reasons. + +### skipFormat + +Default: `false` + +Type: `boolean` + +Skip formatting files. + +### skipTsConfig + +Default: `false` + +Type: `boolean` + +Do not update tsconfig.json for development experience. + +### strict + +Default: `true` + +Type: `boolean` + +Whether to enable tsconfig strict mode or not. + +### tags + +Alias(es): t + +Type: `string` + +Add tags to the library (used for linting). + +### unitTestRunner + +Default: `jest` + +Type: `string` + +Possible values: `jest`, `none` + +Test runner to use for unit tests. diff --git a/docs/react/executors.json b/docs/react/executors.json index acf6274462afd..20e4d08631c8b 100644 --- a/docs/react/executors.json +++ b/docs/react/executors.json @@ -1,12 +1,14 @@ [ "angular", "cypress", + "detox", "gatsby", "jest", "linter", "next", "node", "nx-plugin", + "react-native", "storybook", "web", "workspace" diff --git a/docs/react/generators.json b/docs/react/generators.json index 46a738dc38252..0ae1fbf35a4b1 100644 --- a/docs/react/generators.json +++ b/docs/react/generators.json @@ -1,6 +1,7 @@ [ "angular", "cypress", + "detox", "express", "gatsby", "jest", @@ -9,6 +10,7 @@ "node", "nx-plugin", "react", + "react-native", "storybook", "web", "workspace" diff --git a/e2e/detox/jest.config.js b/e2e/detox/jest.config.js new file mode 100644 index 0000000000000..40033364a9ca6 --- /dev/null +++ b/e2e/detox/jest.config.js @@ -0,0 +1,11 @@ +module.exports = { + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + maxWorkers: 1, + globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json' } }, + displayName: 'e2e-detox', + testTimeout: 600000, +}; diff --git a/e2e/detox/project.json b/e2e/detox/project.json new file mode 100644 index 0000000000000..00dec6f198046 --- /dev/null +++ b/e2e/detox/project.json @@ -0,0 +1,34 @@ +{ + "root": "e2e/detox", + "sourceRoot": "e2e/detox", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/workspace:run-commands", + "options": { + "commands": [ + { + "command": "yarn e2e-start-local-registry" + }, + { + "command": "yarn e2e-build-package-publish" + }, + { + "command": "nx run-e2e-tests e2e-detox" + } + ], + "parallel": false + } + }, + "run-e2e-tests": { + "executor": "@nrwl/jest:jest", + "options": { + "jestConfig": "e2e/detox/jest.config.js", + "passWithNoTests": true, + "runInBand": true + }, + "outputs": ["coverage/e2e/detox"] + } + }, + "implicitDependencies": ["detox"] +} diff --git a/e2e/detox/src/detox.test.ts b/e2e/detox/src/detox.test.ts new file mode 100644 index 0000000000000..350980b7111f8 --- /dev/null +++ b/e2e/detox/src/detox.test.ts @@ -0,0 +1,56 @@ +import { + checkFilesExist, + isOSX, + newProject, + runCLI, + runCLIAsync, + uniq, + getSelectedPackageManager, +} from '@nrwl/e2e/utils'; + +describe('Detox', () => { + beforeEach(() => newProject()); + + it('should create files and run lint command', async () => { + // currently react native does not support pnpm: https://github.com/pnpm/pnpm/issues/3321 + if (getSelectedPackageManager() === 'pnpm') return; + const appName = uniq('myapp'); + runCLI( + `generate @nrwl/react-native:app ${appName} --e2eTestRunner=detox --linter=eslint` + ); + + checkFilesExist(`apps/${appName}-e2e/.detoxrc.json`); + checkFilesExist(`apps/${appName}-e2e/tsconfig.json`); + checkFilesExist(`apps/${appName}-e2e/tsconfig.e2e.json`); + checkFilesExist(`apps/${appName}-e2e/test-setup.ts`); + checkFilesExist(`apps/${appName}-e2e/src/app.spec.ts`); + + const lintResults = await runCLIAsync(`lint ${appName}-e2e`); + expect(lintResults.combinedOutput).toContain('All files pass linting'); + }); + it('should build and test ios', async () => { + if (!isOSX()) return; + + const appName = uniq('myapp'); + runCLI( + `generate @nrwl/react-native:app ${appName} --e2eTestRunner=detox --linter=eslint` + ); + + expect(runCLI(`build-ios ${appName}-e2e`)).toContain( + 'Running target "build-ios" succeeded' + ); + + // comment out due to github issue that unable to build xcode error 12.5 https://github.com/facebook/react-native/issues/31480 + /* expect(runCLI(`build-ios ${appName}-e2e --pod`)).toContain( + 'Running target "build-ios" succeeded' + ); + expect( + runCLI( + `test-ios ${appName}-e2e --prod --debugSynchronization=true --loglevel=trace` + ) + ).toContain('Running target "test-ios" succeeded'); + + await killPorts(8081); // kill the port for the serve command + */ + }, 1000000); +}); diff --git a/e2e/detox/tsconfig.json b/e2e/detox/tsconfig.json new file mode 100644 index 0000000000000..6d5abf8483200 --- /dev/null +++ b/e2e/detox/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/detox/tsconfig.spec.json b/e2e/detox/tsconfig.spec.json new file mode 100644 index 0000000000000..af4ac638d6709 --- /dev/null +++ b/e2e/detox/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/e2e/react-native/jest.config.js b/e2e/react-native/jest.config.js new file mode 100644 index 0000000000000..8e170507a93c1 --- /dev/null +++ b/e2e/react-native/jest.config.js @@ -0,0 +1,11 @@ +module.exports = { + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html'], + maxWorkers: 1, + globals: { 'ts-jest': { tsconfig: '/tsconfig.spec.json' } }, + displayName: 'e2e-react-native', + testTimeout: 600000, +}; diff --git a/e2e/react-native/project.json b/e2e/react-native/project.json new file mode 100644 index 0000000000000..c0c943f776c63 --- /dev/null +++ b/e2e/react-native/project.json @@ -0,0 +1,34 @@ +{ + "root": "e2e/react-native", + "sourceRoot": "e2e/react-native", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/workspace:run-commands", + "options": { + "commands": [ + { + "command": "yarn e2e-start-local-registry" + }, + { + "command": "yarn e2e-build-package-publish" + }, + { + "command": "nx run-e2e-tests e2e-react-native" + } + ], + "parallel": false + } + }, + "run-e2e-tests": { + "executor": "@nrwl/jest:jest", + "options": { + "jestConfig": "e2e/react-native/jest.config.js", + "passWithNoTests": true, + "runInBand": true + }, + "outputs": ["coverage/e2e/react-native"] + } + }, + "implicitDependencies": ["react-native"] +} diff --git a/e2e/react-native/src/react-native.test.ts b/e2e/react-native/src/react-native.test.ts new file mode 100644 index 0000000000000..6e7e2fcc2def1 --- /dev/null +++ b/e2e/react-native/src/react-native.test.ts @@ -0,0 +1,96 @@ +import { + checkFilesExist, + getSelectedPackageManager, + newProject, + readJson, + runCLI, + runCLIAsync, + uniq, + updateFile, +} from '@nrwl/e2e/utils'; +import { join } from 'path'; + +describe('react native', () => { + let proj: string; + + beforeEach(() => (proj = newProject())); + + it('should test, create ios and android JS bundles', async () => { + // currently react native does not support pnpm: https://github.com/pnpm/pnpm/issues/3321 + if (getSelectedPackageManager() === 'pnpm') return; + + const appName = uniq('my-app'); + const libName = uniq('lib'); + const componentName = uniq('component'); + + runCLI(`generate @nrwl/react-native:application ${appName}`); + runCLI(`generate @nrwl/react-native:library ${libName}`); + runCLI( + `generate @nrwl/react-native:component ${componentName} --project=${libName} --export` + ); + + updateFile(`apps/${appName}/src/app/App.tsx`, (content) => { + let updated = `import ${componentName} from '${proj}/${libName}';\n${content}`; + return updated; + }); + + const appTestResults = await runCLIAsync(`test ${appName}`); + expect(appTestResults.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); + + const libTestResults = await runCLIAsync(`test ${libName}`); + expect(libTestResults.combinedOutput).toContain( + 'Test Suites: 1 passed, 1 total' + ); + + const iosBundleResult = await runCLIAsync(`bundle-ios ${appName}`); + expect(iosBundleResult.combinedOutput).toContain( + 'Done writing bundle output' + ); + expect(() => + checkFilesExist(`dist/apps/${appName}/ios/main.jsbundle`) + ).not.toThrow(); + + const androidBundleResult = await runCLIAsync(`bundle-android ${appName}`); + expect(androidBundleResult.combinedOutput).toContain( + 'Done writing bundle output' + ); + expect(() => + checkFilesExist(`dist/apps/${appName}/android/main.jsbundle`) + ).not.toThrow(); + }); + + it('sync npm dependencies for autolink', async () => { + // currently react native does not support pnpm: https://github.com/pnpm/pnpm/issues/3321 + if (getSelectedPackageManager() === 'pnpm') return; + + const appName = uniq('my-app'); + runCLI(`generate @nrwl/react-native:application ${appName}`); + // Add npm package with native modules + updateFile(join('package.json'), (content) => { + const json = JSON.parse(content); + json.dependencies['react-native-image-picker'] = '1.0.0'; + json.dependencies['react-native-gesture-handler'] = '1.0.0'; + json.dependencies['react-native-safe-area-contex'] = '1.0.0'; + return JSON.stringify(json, null, 2); + }); + // Add import for Nx to pick up + updateFile(join('apps', appName, 'src/app/App.tsx'), (content) => { + return `import { launchImageLibrary } from 'react-native-image-picker';\n${content}`; + }); + + await runCLIAsync( + `sync-deps ${appName} --include=react-native-gesture-handler,react-native-safe-area-context` + ); + + const result = readJson(join('apps', appName, 'package.json')); + expect(result).toMatchObject({ + dependencies: { + 'react-native-image-picker': '*', + 'react-native-gesture-handler': '*', + 'react-native-safe-area-context': '*', + }, + }); + }); +}); diff --git a/e2e/react-native/tsconfig.json b/e2e/react-native/tsconfig.json new file mode 100644 index 0000000000000..6d5abf8483200 --- /dev/null +++ b/e2e/react-native/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": [], + "files": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/react-native/tsconfig.spec.json b/e2e/react-native/tsconfig.spec.json new file mode 100644 index 0000000000000..af4ac638d6709 --- /dev/null +++ b/e2e/react-native/tsconfig.spec.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/e2e/utils/index.ts b/e2e/utils/index.ts index c8de0370cbcd3..6989a90f2152c 100644 --- a/e2e/utils/index.ts +++ b/e2e/utils/index.ts @@ -188,6 +188,7 @@ export function newProject({ name = uniq('proj') } = {}): string { `@nrwl/react`, `@nrwl/storybook`, `@nrwl/web`, + `@nrwl/react-native`, ]; packageInstall(packages.join(` `), projScope); diff --git a/package.json b/package.json index dc2282194d4ad..5926adb888201 100644 --- a/package.json +++ b/package.json @@ -175,6 +175,7 @@ "license-webpack-plugin": "2.3.15", "loader-utils": "1.2.3", "memfs": "^3.0.1", + "metro-resolver": "^0.66.2", "mime": "2.4.4", "mini-css-extract-plugin": "0.8.0", "minimatch": "3.0.4", @@ -182,6 +183,7 @@ "next-sitemap": "^1.6.108", "ng-packagr": "~12.2.0", "ngrx-store-freeze": "0.2.4", + "node-fetch": "^2.6.1", "npm-run-all": "^4.1.5", "open": "^7.4.2", "parse-markdown-links": "^1.0.4", diff --git a/packages/create-nx-workspace/bin/create-nx-workspace.ts b/packages/create-nx-workspace/bin/create-nx-workspace.ts index 28f285e23d6f7..7f5d18bd9e073 100644 --- a/packages/create-nx-workspace/bin/create-nx-workspace.ts +++ b/packages/create-nx-workspace/bin/create-nx-workspace.ts @@ -25,6 +25,7 @@ export enum Preset { AngularWithNest = 'angular-nest', React = 'react', ReactWithExpress = 'react-express', + ReactNative = 'react-native', NextJs = 'next', Gatsby = 'gatsby', Nest = 'nest', @@ -74,6 +75,11 @@ const presetOptions: { name: Preset; message: string }[] = [ message: 'web components [a workspace with a single app built using web components]', }, + { + name: Preset.ReactNative, + message: + 'react-native [a workspace with a single React Native application]', + }, { name: Preset.ReactWithExpress, message: @@ -339,7 +345,8 @@ function determineStyle(preset: Preset, parsedArgs: any) { preset === Preset.Empty || preset === Preset.NPM || preset === Preset.Nest || - preset === Preset.Express + preset === Preset.Express || + preset === Preset.ReactNative ) { return Promise.resolve(null); } diff --git a/packages/detox/.eslintrc.json b/packages/detox/.eslintrc.json new file mode 100644 index 0000000000000..ab8f38339cde4 --- /dev/null +++ b/packages/detox/.eslintrc.json @@ -0,0 +1 @@ +{ "extends": "../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] } diff --git a/packages/detox/README.md b/packages/detox/README.md new file mode 100644 index 0000000000000..360d75222f376 --- /dev/null +++ b/packages/detox/README.md @@ -0,0 +1,80 @@ +# Detox Plugin for Nx + +[![License](https://img.shields.io/npm/l/@nrwl/workspace.svg?style=flat-square)]() +[![NPM Version](https://badge.fury.io/js/%40nrwl%2Fdetox.svg)](https://www.npmjs.com/@nrwl/detox) +[![Join the chat at https://gitter.im/nrwl-nx/community](https://badges.gitter.im/nrwl-nx/community.svg)](https://gitter.im/nrwl-nx/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join us @nrwl/community on slack](https://img.shields.io/badge/slack-%40nrwl%2Fcommunity-brightgreen)](https://join.slack.com/t/nrwlcommunity/shared_invite/enQtNzU5MTE4OTQwOTk0LTgxY2E0ZWYzMWE0YzA5ZDA2MWM1NDVhNmI2ZWMyYmZhNWJiODk3MjkxZjY3MzU5ZjRmM2NmNWU1OTgyZmE4Mzc) + + + +## Table of Contents + + + + +- [Setup](#setup) + - [Install applesimutils (Mac only)](#install-applesimutils-mac-only) + - [Install Jest Globally](#install-jest-globally) + - [Commands](#commands) + - [Manually Add E2E Folder](#manually-add-e2e-folder) + - [Change Testing Simulator/Emulator](#change-testing-simulatoremulator) +- [Learn more](#learn-more) + + + +## Setup + +#### Install applesimutils (Mac only) + +[applesimutils](https://github.com/wix/AppleSimulatorUtils) is a collection of utils for Apple simulators. + +```sh +brew tap wix/brew +brew install applesimutils +``` + +#### Install Jest Globally + +```sh +npm install -g jest +``` + +### Commands + +Note: For e2e tests to work, the app must be running (`nx start `). A built app must exist before run test commands. + +- `nx build-ios `: build the iOS app (Mac only) +- `nx test-ios `: run e2e tests on the built iOS app (Mac only) +- `nx build-ios --prod` and `nx test-ios --prod`: build and run release version of iOS app. Note: you might need open the xcode project under iOS and choose a team under "Sign & Capabilities". +- `nx build-android `: build the android app +- `nx test-android `: run e2e tests on the built android app +- `nx build-android --prod` and `nx test-android --prod`: build and run release version of android app. + +### Manually Add E2E Folder + +A `` folder is automatically generated when you create a react native app. However, if you want to add e2e folder manually, you need to: + +- Install @nrwl/detox + + ```sh + # Using npm + npm install --save-dev @nrwl/detox + + # Using yarn + yarn add -D @nrwl/detox + ``` + +- Run `nx generate @nrwl/detox:app ` +- Follow instructions https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md to manully change android files. + +### Change Testing Simulator/Emulator + +For iOS, in terminal, run `xcrun simctl list` to view a list of simulators on your Mac. To open your active simulator, `run open -a simulator`. In `/.detoxrc.json`, you could change the simulator under `devices.simulator.device`. + +For Android: in terminal, run `emulator -list-avds` to view a list of emulators installed. To open your emulator, run `emulator -avd `. In `/.detoxrc.json`, you could change the simulator under `devices.emulator.device`. + +To override the device name specified in a configuration, you could use `--device-name` option: `nx test-ios --device-name "iPhone 11"`. + +## Learn more + +Visit the [Nx Documentation](https://nx.dev) to learn more. diff --git a/packages/detox/executors.json b/packages/detox/executors.json new file mode 100644 index 0000000000000..634159315029d --- /dev/null +++ b/packages/detox/executors.json @@ -0,0 +1,14 @@ +{ + "executors": { + "build": { + "implementation": "./src/executors/build/build.impl", + "schema": "./src/executors/build/schema.json", + "description": "Run the command defined in build property of the specified configuration." + }, + "test": { + "implementation": "./src/executors/test/test.impl", + "schema": "./src/executors/test/schema.json", + "description": "Initiating your detox test suite." + } + } +} diff --git a/packages/detox/generators.json b/packages/detox/generators.json new file mode 100644 index 0000000000000..514ee4055257c --- /dev/null +++ b/packages/detox/generators.json @@ -0,0 +1,33 @@ +{ + "name": "Nx Detox", + "version": "0.1", + "extends": ["@nrwl/workspace"], + "schematics": { + "init": { + "factory": "./src/generators/init/init#detoxInitSchematic", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the @nrwl/detox plugin", + "hidden": true + }, + "application": { + "factory": "./src/generators/application/application#detoxApplicationSchematic", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "description": "Create a detox application" + } + }, + "generators": { + "init": { + "factory": "./src/generators/init/init#detoxInitGenerator", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the @nrwl/detox plugin", + "hidden": true + }, + "application": { + "factory": "./src/generators/application/application#detoxApplicationGenerator", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "description": "Create a detox application" + } + } +} diff --git a/packages/detox/index.ts b/packages/detox/index.ts new file mode 100644 index 0000000000000..7edbccfae606e --- /dev/null +++ b/packages/detox/index.ts @@ -0,0 +1,2 @@ +export { detoxInitGenerator } from './src/generators/init/init'; +export { detoxApplicationGenerator } from './src/generators/application/application'; diff --git a/packages/detox/jest.config.js b/packages/detox/jest.config.js new file mode 100644 index 0000000000000..32aef31aa6b1f --- /dev/null +++ b/packages/detox/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html', 'json'], + globals: { + 'ts-jest': { tsconfig: '/tsconfig.spec.json' }, + }, + displayName: 'react-native', + testEnvironment: 'node', +}; diff --git a/packages/detox/migrations.json b/packages/detox/migrations.json new file mode 100644 index 0000000000000..0fab2b02ee094 --- /dev/null +++ b/packages/detox/migrations.json @@ -0,0 +1,13 @@ +{ + "packageJsonUpdates": { + "12.8.0": { + "version": "12.8.0-beta.0", + "packages": { + "detox": { + "version": "18.20.2", + "alwaysAddToPackageJson": false + } + } + } + } +} diff --git a/packages/detox/package.json b/packages/detox/package.json new file mode 100644 index 0000000000000..4765524ea6c1c --- /dev/null +++ b/packages/detox/package.json @@ -0,0 +1,42 @@ +{ + "name": "@nrwl/detox", + "version": "0.0.1", + "description": "Detox Plugin for Nx", + "keywords": [ + "Monorepo", + "React", + "Web", + "Native", + "CLI", + "Detox" + ], + "homepage": "https://nx.dev", + "bugs": { + "url": "https://github.com/nrwl/nx/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nrwl/nx.git" + }, + "license": "MIT", + "author": "Victor Savkin", + "main": "index.js", + "types": "index.d.ts", + "dependencies": { + "@nrwl/devkit": "*", + "@nrwl/jest": "*", + "@nrwl/linter": "*", + "@nrwl/react": "*", + "chalk": "^4.1.0" + }, + "peerDependencies": { + "@nrwl/workspace": "*", + "detox": "18.18.0" + }, + "builders": "./executors.json", + "ng-update": { + "requirements": {}, + "migrations": "./migrations.json" + }, + "schematics": "./generators.json" +} diff --git a/packages/detox/project.json b/packages/detox/project.json new file mode 100644 index 0000000000000..75c1c07f6a834 --- /dev/null +++ b/packages/detox/project.json @@ -0,0 +1,82 @@ +{ + "root": "packages/detox", + "sourceRoot": "packages/detox/src", + "projectType": "library", + "targets": { + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "packages/detox/**/*.ts", + "packages/detox/**/*.spec.ts", + "packages/detox/**/*.spec.tsx", + "packages/detox/**/*.spec.js", + "packages/detox/**/*.spec.jsx", + "packages/detox/**/*.d.ts" + ] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "options": { + "jestConfig": "packages/detox/jest.config.js", + "passWithNoTests": true + }, + "outputs": ["coverage/packages/detox"] + }, + "build-base": { + "builder": "@nrwl/node:package", + "options": { + "outputPath": "build/packages/detox", + "tsConfig": "packages/detox/tsconfig.lib.json", + "packageJson": "packages/detox/package.json", + "main": "packages/detox/index.ts", + "updateBuildableProjectDepsInPackageJson": false, + "assets": [ + { + "input": "packages/detox", + "glob": "**/files/**", + "output": "/" + }, + { + "input": "packages/detox", + "glob": "**/files/**/.gitkeep", + "output": "/" + }, + { + "input": "packages/detox", + "glob": "**/files/**/.babelrc.template", + "output": "/" + }, + { + "input": "packages/detox", + "glob": "**/files/**/.detoxrc.json.template", + "output": "/" + }, + { + "input": "./packages/detox", + "glob": "**/*.json", + "ignore": ["**/tsconfig*.json"], + "output": "/" + }, + "LICENSE" + ] + }, + "build": { + "executor": "@nrwl/workspace:run-commands", + "outputs": ["build/packages/detox"], + "options": { + "command": "node ./scripts/copy-readme.js detox" + } + }, + "outputs": ["{options.outputPath}"] + }, + "build": { + "executor": "@nrwl/workspace:run-commands", + "outputs": ["build/packages/detox"], + "options": { + "command": "node ./scripts/copy-readme.js detox" + } + } + } +} diff --git a/packages/detox/src/executors/build/build.impl.ts b/packages/detox/src/executors/build/build.impl.ts new file mode 100644 index 0000000000000..330bd9dce03b0 --- /dev/null +++ b/packages/detox/src/executors/build/build.impl.ts @@ -0,0 +1,71 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { join } from 'path'; +import { ChildProcess, fork } from 'child_process'; + +import { DetoxBuildOptions } from './schema'; + +export interface DetoxBuildOutput { + success: boolean; +} + +let childProcess: ChildProcess; + +export default async function* detoxBuildExecutor( + options: DetoxBuildOptions, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + + try { + await runCliBuild(context.root, projectRoot, options); + + yield { success: true }; + } finally { + if (childProcess) { + childProcess.kill(); + } + } +} + +function runCliBuild( + workspaceRoot: string, + projectRoot: string, + options: DetoxBuildOptions +) { + return new Promise((resolve, reject) => { + childProcess = fork( + join(workspaceRoot, './node_modules/detox/local-cli/cli.js'), + ['build', ...createDetoxBuildOptions(options)], + { + cwd: projectRoot, + } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + +function createDetoxBuildOptions(options) { + return Object.keys(options).reduce((acc, k) => { + const v = options[k]; + if (k === 'detoxConfiguration') { + acc.push('--configuration', v); + } else if (k === 'configPath') { + acc.push('--config-path', v); + } else acc.push(`--${k}`, options[k]); + return acc; + }, []); +} diff --git a/packages/detox/src/executors/build/compat.ts b/packages/detox/src/executors/build/compat.ts new file mode 100644 index 0000000000000..87700b88bc7aa --- /dev/null +++ b/packages/detox/src/executors/build/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import detoxBuildExecutor from './build.impl'; + +export default convertNxExecutor(detoxBuildExecutor); diff --git a/packages/detox/src/executors/build/schema.d.ts b/packages/detox/src/executors/build/schema.d.ts new file mode 100644 index 0000000000000..7a8231e7f50a4 --- /dev/null +++ b/packages/detox/src/executors/build/schema.d.ts @@ -0,0 +1,5 @@ +// options from https://github.com/wix/Detox/blob/master/docs/APIRef.DetoxCLI.md#build +export interface DetoxBuildOptions { + detoxConfiguration?: string; + configPath?: string; +} diff --git a/packages/detox/src/executors/build/schema.json b/packages/detox/src/executors/build/schema.json new file mode 100644 index 0000000000000..c5b9c60a274f6 --- /dev/null +++ b/packages/detox/src/executors/build/schema.json @@ -0,0 +1,19 @@ +{ + "title": "Run detox build", + "description": "Run detox build options", + "type": "object", + "cli": "nx", + "properties": { + "detoxConfiguration": { + "type": "string", + "description": "Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it", + "alias": "C" + }, + "configPath": { + "type": "string", + "description": "Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or \"detox\" section in package.json", + "alias": "cp" + } + }, + "required": [] +} diff --git a/packages/detox/src/executors/test/compat.ts b/packages/detox/src/executors/test/compat.ts new file mode 100644 index 0000000000000..1409e42e14935 --- /dev/null +++ b/packages/detox/src/executors/test/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import detoxTestExecutor from './test.impl'; + +export default convertNxExecutor(detoxTestExecutor); diff --git a/packages/detox/src/executors/test/schema.d.ts b/packages/detox/src/executors/test/schema.d.ts new file mode 100644 index 0000000000000..3ca658eeee52a --- /dev/null +++ b/packages/detox/src/executors/test/schema.d.ts @@ -0,0 +1,30 @@ +// options from https://github.com/wix/Detox/blob/master/docs/APIRef.DetoxCLI.md#test +// detox test args: https://github.com/wix/Detox/blob/master/detox/local-cli/utils/testCommandArgs.js +export interface DetoxTestOptions { + configPath: string; + detoxConfiguration: string; + runnerConfig: string; + deviceName: string; + loglevel: string; + debugSynchronization: string; + artifactsLocation: string; + recordLogs: 'failing' | 'all' | 'none'; + takeScreenshots: 'manual' | 'failing' | 'all' | 'none'; + recordVideos: 'failing' | 'all' | 'none'; + recordPerformance: 'all' | 'none'; + recordTimeline: 'all' | 'none'; + captureViewHierarchy: 'enabled' | 'disabled'; + retries: number; + resuse: boolean; + cleanup: boolean; + workers: number; + jestReportSpecs: boolean; + headeless: boolean; + gpu: boolean; + deviceLauchArgs: string; + appLaunchArgs: string; + noColor: boolean; + useCutsomeLogger: boolean; + forceAdbInstall: boolean; + inspectBrk: boolean; +} diff --git a/packages/detox/src/executors/test/schema.json b/packages/detox/src/executors/test/schema.json new file mode 100644 index 0000000000000..599366fab495d --- /dev/null +++ b/packages/detox/src/executors/test/schema.json @@ -0,0 +1,120 @@ +{ + "title": "Run detox test", + "description": "Run detox test options", + "type": "object", + "cli": "nx", + "properties": { + "detoxConfiguration": { + "type": "string", + "description": "Select a device configuration from your defined configurations, if not supplied, and there's only one configuration, detox will default to it", + "alias": "C" + }, + "configPath": { + "type": "string", + "description": "Specify Detox config file path. If not supplied, detox searches for .detoxrc[.js] or \"detox\" section in package.json", + "alias": "cp" + }, + "runnerConfig": { + "type": "string", + "description": "Test runner config file, defaults to 'e2e/mocha.opts' for mocha and 'e2e/config.json' for jest.", + "alias": "o" + }, + "deviceName": { + "type": "string", + "description": "Override the device name specified in a configuration. Useful for running a single build configuration on multiple devices.", + "alias": "n" + }, + "loglevel": { + "type": "string", + "description": "Log level: fatal, error, warn, info, verbose, trace", + "alias": "l" + }, + "debugSynchronization": { + "type": "string", + "description": "Customize how long an action/expectation can take to complete before Detox starts querying the app why it is busy. By default, the app status will be printed if the action takes more than 10s to complete.", + "alias": "d" + }, + "artifactsLocation": { + "type": "string", + "description": "Artifacts (logs, screenshots, etc) root directory.", + "alias": "a" + }, + "recordLogs": { + "type": "string", + "description": "Save logs during each test to artifacts directory. Pass \"failing\" to save logs of failing tests only." + }, + "takeScreenshots": { + "type": "string", + "description": "Save screenshots before and after each test to artifacts directory. Pass \"failing\" to save screenshots of failing tests only. " + }, + "recordVideos": { + "type": "string", + "description": "Save screen recordings of each test to artifacts directory. Pass \"failing\" to save recordings of failing tests only." + }, + "recordPerformance": { + "type": "string", + "description": "[iOS Only] Save Detox Instruments performance recordings of each test to artifacts directory." + }, + "recordTimeline": { + "type": "string", + "description": "[Jest Only] Record tests and events timeline, for visual display on the chrome://tracing tool." + }, + "captureViewHierarchy": { + "type": "string", + "description": "[iOS Only] Capture *.uihierarchy snapshots on view action errors and device.captureViewHierarchy() calls." + }, + "retries": { + "type": "number", + "description": "[Jest Circus Only] Re-spawn the test runner for individual failing suite files until they pass, or times at least." + }, + "reuse": { + "type": "boolean", + "description": "Reuse existing installed app (do not delete + reinstall) for a faster run." + }, + "cleanup": { + "type": "boolean", + "description": "Shutdown simulator when test is over, useful for CI scripts, to make sure detox exists cleanly with no residue" + }, + "workers": { + "type": "number", + "description": "Specifies number of workers the test runner should spawn, requires a test runner with parallel execution support (Detox CLI currently supports Jest)." + }, + "jestReportSpecs": { + "type": "boolean", + "description": "[Jest Only] Whether to output logs per each running spec, in real-time. By default, disabled with multiple workers." + }, + "headless": { + "type": "boolean", + "description": "Android Only] Launch Emulator in headless mode. Useful when running on CI." + }, + "gpu": { + "type": "boolean", + "description": "[Android Only] Launch Emulator with the specific -gpu [gpu mode] parameter." + }, + "deviceLaunchArgs": { + "type": "string", + "description": "A list of passthrough-arguments to use when (if) devices (Android emulator / iOS simulator) are launched by Detox." + }, + "appLaunchArgs": { + "type": "number", + "description": "Custom arguments to pass (through) onto the app every time it is launched." + }, + "noColor": { + "type": "boolean", + "description": "Disable colors in log output" + }, + "useCustomLogger": { + "type": "boolean", + "description": "Use Detox' custom console-logging implementation, for logging Detox (non-device) logs. Disabling will fallback to node.js / test-runner's implementation (e.g. Jest / Mocha)." + }, + "forceAdbInstall": { + "type": "boolean", + "description": "Due to problems with the adb install command on Android, Detox resorts to a different scheme for install APK's. Setting true will disable that and force usage of adb install, instead." + }, + "inspectBrk": { + "type": "boolean", + "description": "Uses node's --inspect-brk flag to let users debug the jest/mocha test runner" + } + }, + "required": [] +} diff --git a/packages/detox/src/executors/test/test.impl.ts b/packages/detox/src/executors/test/test.impl.ts new file mode 100644 index 0000000000000..f06d29cdbe708 --- /dev/null +++ b/packages/detox/src/executors/test/test.impl.ts @@ -0,0 +1,77 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { toFileName } from '@nrwl/workspace/src/devkit-reexport'; +import { join } from 'path'; +import { ChildProcess, fork } from 'child_process'; + +import { DetoxTestOptions } from './schema'; + +export interface DetoxTestOutput { + success: boolean; +} + +let childProcess: ChildProcess; + +export default async function* detoxTestExecutor( + options: DetoxTestOptions, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + + try { + await runCliTest(context.root, projectRoot, options); + + yield { success: true }; + } finally { + if (childProcess) { + childProcess.kill(); + } + } +} + +function runCliTest( + workspaceRoot: string, + projectRoot: string, + options: DetoxTestOptions +) { + return new Promise((resolve, reject) => { + childProcess = fork( + join(workspaceRoot, './node_modules/detox/local-cli/cli.js'), + ['test', ...createDetoxTestOptions(options)], + { + cwd: projectRoot, + } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + +function createDetoxTestOptions(options: DetoxTestOptions): string[] { + return Object.keys(options).reduce((acc, k) => { + const propertyName = toFileName(k); // convert camelCase to kebab-case + const propertyValue = options[k]; + if (k === 'detoxConfiguration') { + acc.push('--configuration', propertyValue); + } else if (k === 'deviceLaunchArgs') { + acc.push(`--device-launch-args="${propertyValue}"`); // the value must be specified after an equal sign (=) and inside quotes. + } else if (k === 'appLaunchArgs') { + acc.push(`--app-launch-argss="${propertyValue}"`); // the value must be specified after an equal sign (=) and inside quotes. + } else { + acc.push(`--${propertyName}`, propertyValue); + } + return acc; + }, []); +} diff --git a/packages/detox/src/generators/application/application.ts b/packages/detox/src/generators/application/application.ts new file mode 100644 index 0000000000000..cc1c60a318332 --- /dev/null +++ b/packages/detox/src/generators/application/application.ts @@ -0,0 +1,34 @@ +import { convertNxGenerator, formatFiles, Tree } from '@nrwl/devkit'; + +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import detoxInitGenerator from '../init/init'; +import { addGitIgnoreEntry } from './lib/add-git-ignore-entry'; +import { addLinting } from './lib/add-linting'; +import { addProject } from './lib/add-project'; +import { createFiles } from './lib/create-files'; +import { normalizeOptions } from './lib/normalize-options'; +import { Schema } from './schema'; + +export async function detoxApplicationGenerator(host: Tree, schema: Schema) { + const options = normalizeOptions(host, schema); + + const initTask = await detoxInitGenerator(host, { + skipFormat: true, + }); + createFiles(host, options); + addProject(host, options); + addGitIgnoreEntry(host, options); + + const lintingTask = await addLinting(host, options); + + if (!options.skipFormat) { + await formatFiles(host); + } + + return runTasksInSerial(initTask, lintingTask); +} + +export default detoxApplicationGenerator; +export const detoxApplicationSchematic = convertNxGenerator( + detoxApplicationGenerator +); diff --git a/packages/detox/src/generators/application/files/app/.babelrc.template b/packages/detox/src/generators/application/files/app/.babelrc.template new file mode 100644 index 0000000000000..61641ec8ac365 --- /dev/null +++ b/packages/detox/src/generators/application/files/app/.babelrc.template @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/packages/detox/src/generators/application/files/app/.detoxrc.json.template b/packages/detox/src/generators/application/files/app/.detoxrc.json.template new file mode 100644 index 0000000000000..7555e808cb227 --- /dev/null +++ b/packages/detox/src/generators/application/files/app/.detoxrc.json.template @@ -0,0 +1,58 @@ +{ + "testRunner": "jest", + "runnerConfig": "jest.config.json", + "apps": { + "ios.debug": { + "type": "ios.app", + "build": "cd ../<%= appFileName %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 12' -derivedDataPath ./build -quiet", + "binaryPath": "../<%= appFileName %>/ios/build/Build/Products/Debug-iphonesimulator/<%= appClassName %>.app" + }, + "ios.release": { + "type": "ios.app", + "build": "cd ../<%= appFileName %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 12' -derivedDataPath ./build -quiet", + "binaryPath": "../<%= appFileName %>/ios/build/Build/Products/Release-iphonesimulator/<%= appClassName %>.app" + }, + "android.debug": { + "type": "android.apk", + "build": "cd ../<%= appFileName %>/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug", + "binaryPath": "../<%= appFileName %>/android/app/build/outputs/apk/debug/app-debug.apk" + }, + "android.release": { + "type": "android.apk", + "build": "cd ../<%= appFileName %>/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release", + "binaryPath": "../<%= appFileName %>/android/app/build/outputs/apk/release/app-release.apk" + } + }, + "devices": { + "simulator": { + "type": "ios.simulator", + "device": { + "type": "iPhone 12" + } + }, + "emulator": { + "type": "android.emulator", + "device": { + "avdName": "Pixel_4a_API_30" + } + } + }, + "configurations": { + "ios.sim.release": { + "device": "simulator", + "app": "ios.release" + }, + "ios.sim.debug": { + "device": "simulator", + "app": "ios.debug" + }, + "android.emu.release": { + "device": "emulator", + "app": "android.release" + }, + "android.emu.debug": { + "device": "emulator", + "app": "android.debug" + } + } +} diff --git a/packages/detox/src/generators/application/files/app/environment.js.template b/packages/detox/src/generators/application/files/app/environment.js.template new file mode 100644 index 0000000000000..7f4fc942fc497 --- /dev/null +++ b/packages/detox/src/generators/application/files/app/environment.js.template @@ -0,0 +1,23 @@ +const { + DetoxCircusEnvironment, + SpecReporter, + WorkerAssignReporter, +} = require('detox/runners/jest-circus'); + +class CustomDetoxEnvironment extends DetoxCircusEnvironment { + constructor(config, context) { + super(config, context); + + // Can be safely removed, if you are content with the default value (=300000ms) + this.initTimeout = 300000; + + // This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level. + // This is strictly optional. + this.registerListeners({ + SpecReporter, + WorkerAssignReporter, + }); + } +} + +module.exports = CustomDetoxEnvironment; diff --git a/packages/detox/src/generators/application/files/app/jest.config.json b/packages/detox/src/generators/application/files/app/jest.config.json new file mode 100644 index 0000000000000..1d9956fad702f --- /dev/null +++ b/packages/detox/src/generators/application/files/app/jest.config.json @@ -0,0 +1,12 @@ +{ + "preset": "../../jest.preset", + "testEnvironment": "./environment", + "testRunner": "jest-circus/runner", + "testTimeout": 120000, + "reporters": ["detox/runners/jest/streamlineReporter"], + "setupFilesAfterEnv": ["/test-setup.ts"], + "transform": { + "^(?!.*\\.(js|jsx|ts|tsx|css|json)$)": "@nrwl/react/plugins/jest", + "^.+\\.[tj]sx?$": "babel-jest" + } +} diff --git a/packages/detox/src/generators/application/files/app/src/app.spec.ts.template b/packages/detox/src/generators/application/files/app/src/app.spec.ts.template new file mode 100644 index 0000000000000..30bc1f0aa9671 --- /dev/null +++ b/packages/detox/src/generators/application/files/app/src/app.spec.ts.template @@ -0,0 +1,16 @@ +import { device, element, by, expect } from 'detox'; + +describe('<%= appClassName %>', () => { + beforeEach(async () => { + await device.reloadReactNative(); + }); + + it('should display welcome message', async () => { + await expect(element(by.id('heading'))).toHaveText('Welcome to <%= appClassName %>'); + }); + + it('should open nx link', async () => { + await expect(element(by.id('nx-link'))).toBeVisible(); + element(by.id('nx-link')).tap(); + }); +}); diff --git a/packages/detox/src/generators/application/files/app/test-setup.ts.template b/packages/detox/src/generators/application/files/app/test-setup.ts.template new file mode 100644 index 0000000000000..004486f6bf0af --- /dev/null +++ b/packages/detox/src/generators/application/files/app/test-setup.ts.template @@ -0,0 +1,6 @@ +import { device } from 'detox'; + +beforeAll(async () => { + await device.launchApp(); + await device.disableSynchronization(); +}); diff --git a/packages/detox/src/generators/application/files/app/tsconfig.e2e.json b/packages/detox/src/generators/application/files/app/tsconfig.e2e.json new file mode 100644 index 0000000000000..8d2d1651b7bb8 --- /dev/null +++ b/packages/detox/src/generators/application/files/app/tsconfig.e2e.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "<%= offsetFromRoot %>dist/out-tsc", + "allowJs": true, + "types": ["node", "jest", "detox"] + }, + "include": ["src/**/*.ts", "src/**/*.js"] +} diff --git a/packages/detox/src/generators/application/files/app/tsconfig.json b/packages/detox/src/generators/application/files/app/tsconfig.json new file mode 100644 index 0000000000000..c31c52e04ce1a --- /dev/null +++ b/packages/detox/src/generators/application/files/app/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.e2e.json" + } + ] +} diff --git a/packages/detox/src/generators/application/lib/add-git-ignore-entry.ts b/packages/detox/src/generators/application/lib/add-git-ignore-entry.ts new file mode 100644 index 0000000000000..57cbe9b42a5fa --- /dev/null +++ b/packages/detox/src/generators/application/lib/add-git-ignore-entry.ts @@ -0,0 +1,12 @@ +import { logger, Tree } from '@nrwl/devkit'; +import { NormalizedSchema } from './normalize-options'; + +export function addGitIgnoreEntry(host: Tree, options: NormalizedSchema) { + if (host.exists('.gitignore')) { + let content = host.read('.gitignore', 'utf-8'); + content = `${content}\n${options.projectRoot}/artifacts\n`; + host.write('.gitignore', content); + } else { + logger.warn(`Couldn't find .gitignore file to update`); + } +} diff --git a/packages/detox/src/generators/application/lib/add-linting.spec.ts b/packages/detox/src/generators/application/lib/add-linting.spec.ts new file mode 100644 index 0000000000000..dede28035e1be --- /dev/null +++ b/packages/detox/src/generators/application/lib/add-linting.spec.ts @@ -0,0 +1,71 @@ +import { readProjectConfiguration, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import { addLinting } from './add-linting'; +import { addProject } from './add-project'; + +describe('Add Linting', () => { + let tree: Tree; + + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace(); + addProject(tree, { + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.EsLint, + }); + }); + + it('should add update `workspace.json` file properly when eslint is passed', () => { + addLinting(tree, { + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.EsLint, + }); + const project = readProjectConfiguration(tree, 'my-app-e2e'); + + expect(project.targets.lint).toBeDefined(); + expect(project.targets.lint.executor).toEqual('@nrwl/linter:eslint'); + }); + + it('should add update `workspace.json` file properly when tslint is passed', () => { + addLinting(tree, { + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.TsLint, + }); + const project = readProjectConfiguration(tree, 'my-app-e2e'); + + expect(project.targets.lint).toBeDefined(); + expect(project.targets.lint.executor).toEqual( + '@angular-devkit/build-angular:tslint' + ); + }); + + it('should not add lint target when "none" is passed', async () => { + addLinting(tree, { + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.None, + }); + const project = readProjectConfiguration(tree, 'my-app-e2e'); + + expect(project.targets.lint).toBeUndefined(); + }); +}); diff --git a/packages/detox/src/generators/application/lib/add-linting.ts b/packages/detox/src/generators/application/lib/add-linting.ts new file mode 100644 index 0000000000000..b02cad2609a0b --- /dev/null +++ b/packages/detox/src/generators/application/lib/add-linting.ts @@ -0,0 +1,49 @@ +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { Linter, lintProjectGenerator } from '@nrwl/linter'; +import { + addDependenciesToPackageJson, + joinPathFragments, + updateJson, + Tree, +} from '@nrwl/devkit'; +import { extraEslintDependencies, createReactEslintJson } from '@nrwl/react'; +import { NormalizedSchema } from './normalize-options'; + +export async function addLinting(host: Tree, options: NormalizedSchema) { + if (options.linter === Linter.None) { + return () => {}; + } + + const lintTask = await lintProjectGenerator(host, { + linter: options.linter, + project: options.projectName, + tsConfigPaths: [ + joinPathFragments(options.projectRoot, 'tsconfig.app.json'), + ], + eslintFilePatterns: [`${options.projectRoot}/**/*.{ts,tsx,js,jsx}`], + skipFormat: true, + }); + + if (options.linter === Linter.TsLint) { + return () => {}; + } + + const reactEslintJson = createReactEslintJson( + options.projectRoot, + options.setParserOptionsProject + ); + + updateJson( + host, + joinPathFragments(options.projectRoot, '.eslintrc.json'), + () => reactEslintJson + ); + + const installTask = await addDependenciesToPackageJson( + host, + extraEslintDependencies.dependencies, + extraEslintDependencies.devDependencies + ); + + return runTasksInSerial(lintTask, installTask); +} diff --git a/packages/detox/src/generators/application/lib/add-project.spec.ts b/packages/detox/src/generators/application/lib/add-project.spec.ts new file mode 100644 index 0000000000000..ef28dd7cb11ce --- /dev/null +++ b/packages/detox/src/generators/application/lib/add-project.spec.ts @@ -0,0 +1,60 @@ +import { + addProjectConfiguration, + readProjectConfiguration, + Tree, +} from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import { addProject } from './add-project'; + +describe('Add Project', () => { + let tree: Tree; + + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace(); + addProjectConfiguration(tree, 'my-app', { + root: 'my-app', + targets: { + serve: { + executor: 'serve-executor', + options: {}, + configurations: { + production: {}, + }, + }, + }, + }); + }); + + it('should update workspace.json', async () => { + addProject(tree, { + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.EsLint, + }); + const project = readProjectConfiguration(tree, 'my-app-e2e'); + + expect(project.root).toEqual('apps/my-app-e2e'); + expect(project.sourceRoot).toEqual('apps/my-app-e2e/src'); + }); + + it('should update nx.json', async () => { + addProject(tree, { + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.EsLint, + }); + + const project = readProjectConfiguration(tree, 'my-app-e2e'); + expect(project.tags).toEqual([]); + expect(project.implicitDependencies).toEqual(['my-app']); + }); +}); diff --git a/packages/detox/src/generators/application/lib/add-project.ts b/packages/detox/src/generators/application/lib/add-project.ts new file mode 100644 index 0000000000000..5a783c7d84e02 --- /dev/null +++ b/packages/detox/src/generators/application/lib/add-project.ts @@ -0,0 +1,71 @@ +import { + addProjectConfiguration, + TargetConfiguration, + Tree, +} from '@nrwl/devkit'; +import { NormalizedSchema } from './normalize-options'; + +export function addProject(host: Tree, options: NormalizedSchema) { + addProjectConfiguration(host, options.projectName, { + root: options.projectRoot, + sourceRoot: `${options.projectRoot}/src`, + projectType: 'application', + targets: { ...getTargets(options) }, + tags: [], + implicitDependencies: options.project ? [options.project] : undefined, + }); +} + +function getTargets(options: NormalizedSchema) { + const architect: { [key: string]: TargetConfiguration } = {}; + + architect['build-ios'] = { + executor: '@nrwl/detox:build', + options: { + detoxConfiguration: 'ios.sim.debug', + }, + configurations: { + production: { + detoxConfiguration: 'ios.sim.release', + }, + }, + }; + + architect['test-ios'] = { + executor: '@nrwl/detox:test', + options: { + detoxConfiguration: 'ios.sim.debug', + }, + configurations: { + production: { + detoxConfiguration: 'ios.sim.release', + }, + }, + }; + + architect['build-android'] = { + executor: '@nrwl/detox:build', + options: { + detoxConfiguration: 'android.emu.debug', + }, + configurations: { + production: { + detoxConfiguration: 'android.emu.release', + }, + }, + }; + + architect['test-android'] = { + executor: '@nrwl/detox:test', + options: { + detoxConfiguration: 'android.emu.debug', + }, + configurations: { + production: { + detoxConfiguration: 'android.emu.release', + }, + }, + }; + + return architect; +} diff --git a/packages/detox/src/generators/application/lib/create-files.spec.ts b/packages/detox/src/generators/application/lib/create-files.spec.ts new file mode 100644 index 0000000000000..e945319f380fc --- /dev/null +++ b/packages/detox/src/generators/application/lib/create-files.spec.ts @@ -0,0 +1,29 @@ +import { Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import { createFiles } from './create-files'; + +describe('Create Files', () => { + let tree: Tree; + + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should generate files', () => { + createFiles(tree, { + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.EsLint, + }); + + expect(tree.exists('apps/my-app-e2e/.detoxrc.json')).toBeTruthy(); + expect(tree.exists('apps/my-app-e2e/tsconfig.json')).toBeTruthy(); + expect(tree.exists('apps/my-app-e2e/tsconfig.e2e.json')).toBeTruthy(); + expect(tree.exists('apps/my-app-e2e/test-setup.ts')).toBeTruthy(); + }); +}); diff --git a/packages/detox/src/generators/application/lib/create-files.ts b/packages/detox/src/generators/application/lib/create-files.ts new file mode 100644 index 0000000000000..c625f987cbf02 --- /dev/null +++ b/packages/detox/src/generators/application/lib/create-files.ts @@ -0,0 +1,13 @@ +import { generateFiles, offsetFromRoot, toJS, Tree } from '@nrwl/devkit'; +import { join } from 'path'; +import { NormalizedSchema } from './normalize-options'; + +export function createFiles(host: Tree, options: NormalizedSchema) { + generateFiles(host, join(__dirname, '../files/app'), options.projectRoot, { + ...options, + offsetFromRoot: offsetFromRoot(options.projectRoot), + }); + if (options.js) { + toJS(host); + } +} diff --git a/packages/detox/src/generators/application/lib/normalize-options.spec.ts b/packages/detox/src/generators/application/lib/normalize-options.spec.ts new file mode 100644 index 0000000000000..d4ae9e7a75d2f --- /dev/null +++ b/packages/detox/src/generators/application/lib/normalize-options.spec.ts @@ -0,0 +1,77 @@ +import { addProjectConfiguration, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import { Schema } from '../schema'; +import { normalizeOptions } from './normalize-options'; + +describe('Normalize Options', () => { + let appTree: Tree; + + beforeEach(() => { + appTree = createTreeWithEmptyWorkspace(); + }); + + it('should normalize options with name in kebab case', () => { + addProjectConfiguration(appTree, 'my-app', { + root: 'apps/my-app', + targets: {}, + }); + const schema: Schema = { + name: 'my-app-e2e', + project: 'my-app', + linter: Linter.EsLint, + }; + const options = normalizeOptions(appTree, schema); + expect(options).toEqual({ + name: 'my-app-e2e', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + project: 'my-app', + appFileName: 'my-app', + appClassName: 'MyApp', + linter: Linter.EsLint, + }); + }); + + it('should normalize options with name in camel case', () => { + addProjectConfiguration(appTree, 'my-app', { + root: 'apps/my-app', + targets: {}, + }); + const schema: Schema = { + name: 'myAppE2e', + project: 'myApp', + }; + const options = normalizeOptions(appTree, schema); + expect(options).toEqual({ + appClassName: 'MyApp', + appFileName: 'my-app', + name: 'my-app-e2e', + project: 'myApp', + projectName: 'my-app-e2e', + projectRoot: 'apps/my-app-e2e', + }); + }); + + it('should normalize options with directory', () => { + addProjectConfiguration(appTree, 'my-app', { + root: 'apps/my-app', + targets: {}, + }); + const schema: Schema = { + name: 'my-app-e2e', + project: 'my-app', + directory: 'directory', + }; + const options = normalizeOptions(appTree, schema); + expect(options).toEqual({ + project: 'my-app', + appClassName: 'MyApp', + appFileName: 'my-app', + projectRoot: 'apps/directory/my-app-e2e', + name: 'my-app-e2e', + directory: 'directory', + projectName: 'directory-my-app-e2e', + }); + }); +}); diff --git a/packages/detox/src/generators/application/lib/normalize-options.ts b/packages/detox/src/generators/application/lib/normalize-options.ts new file mode 100644 index 0000000000000..37cde3db93a00 --- /dev/null +++ b/packages/detox/src/generators/application/lib/normalize-options.ts @@ -0,0 +1,50 @@ +import { + getWorkspaceLayout, + joinPathFragments, + names, + Tree, +} from '@nrwl/devkit'; +import { Schema } from '../schema'; + +export interface NormalizedSchema extends Schema { + appFileName: string; // the file name of app to be tested + appClassName: string; // the class name of app to be tested + projectName: string; // the name of e2e project + projectRoot: string; // the root path of e2e project +} + +/** + * if options.name = 'my-app-e2e' with no options.directory + * projectName = 'my-app', projectRoot = 'apps/my-app' + * if options.name = 'my-app' with options.directory = 'my-dir' + * projectName = 'my-dir-my-app', projectRoot = 'apps/my-dir/my-apps' + */ +export function normalizeOptions( + host: Tree, + options: Schema +): NormalizedSchema { + const { appsDir } = getWorkspaceLayout(host); + const fileName = names(options.name).fileName; + const directoryFileName = options.directory + ? names(options.directory).fileName + : ''; + const projectName = directoryFileName + ? `${directoryFileName.replace(new RegExp('/', 'g'), '-')}-${fileName}` + : fileName; + const projectRoot = directoryFileName + ? joinPathFragments(appsDir, directoryFileName, fileName) + : joinPathFragments(appsDir, fileName); + + const { fileName: appFileName, className: appClassName } = names( + options.project + ); + + return { + ...options, + appFileName, + appClassName, + name: fileName, + projectName, + projectRoot, + }; +} diff --git a/packages/detox/src/generators/application/schema.d.ts b/packages/detox/src/generators/application/schema.d.ts new file mode 100644 index 0000000000000..c8f9019d2683d --- /dev/null +++ b/packages/detox/src/generators/application/schema.d.ts @@ -0,0 +1,11 @@ +import { Linter } from '@nrwl/linter'; + +export interface Schema { + project: string; + name: string; + directory?: string; + linter?: Linter; + js?: boolean; + skipFormat?: boolean; + setParserOptionsProject?: boolean; +} diff --git a/packages/detox/src/generators/application/schema.json b/packages/detox/src/generators/application/schema.json new file mode 100644 index 0000000000000..2fb2512be7f97 --- /dev/null +++ b/packages/detox/src/generators/application/schema.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "Create Detox Configuration for the workspace", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "The name of the frontend project to test.", + "$default": { + "$source": "projectName" + }, + "x-prompt": "What is the name of the frontend project to test?" + }, + "name": { + "type": "string", + "description": "Name of the E2E Project", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the e2e project?" + }, + "directory": { + "type": "string", + "description": "A directory where the project is placed" + }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "tslint", "none"], + "default": "eslint" + }, + "js": { + "description": "Generate JavaScript files rather than TypeScript files", + "type": "boolean", + "default": false + }, + "skipFormat": { + "description": "Skip formatting files", + "type": "boolean", + "default": false + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", + "default": false + } + }, + "required": ["name", "project"] +} diff --git a/packages/detox/src/generators/init/init.spec.ts b/packages/detox/src/generators/init/init.spec.ts new file mode 100644 index 0000000000000..ec9d65d27d74f --- /dev/null +++ b/packages/detox/src/generators/init/init.spec.ts @@ -0,0 +1,19 @@ +import { Tree, readJson } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { detoxInitGenerator } from './init'; + +describe('init', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it('should add detox dependencies', async () => { + await detoxInitGenerator(tree, {}); + const packageJson = readJson(tree, 'package.json'); + expect(packageJson.devDependencies['@nrwl/detox']).toBeDefined(); + expect(packageJson.devDependencies['@types/detox']).toBeDefined(); + expect(packageJson.devDependencies['detox']).toBeDefined(); + }); +}); diff --git a/packages/detox/src/generators/init/init.ts b/packages/detox/src/generators/init/init.ts new file mode 100644 index 0000000000000..8460d76b45e41 --- /dev/null +++ b/packages/detox/src/generators/init/init.ts @@ -0,0 +1,47 @@ +import { + addDependenciesToPackageJson, + convertNxGenerator, + formatFiles, + removeDependenciesFromPackageJson, + Tree, +} from '@nrwl/devkit'; +import { jestVersion } from '@nrwl/jest/src/utils/versions'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { Schema } from './schema'; +import { + detoxVersion, + nxVersion, + testingLibraryJestDom, + typesDetoxVersion, +} from '../../utils/versions'; + +export async function detoxInitGenerator(host: Tree, schema: Schema) { + const tasks = [moveDependency(host), updateDependencies(host)]; + + if (!schema.skipFormat) { + await formatFiles(host); + } + + return runTasksInSerial(...tasks); +} + +export function updateDependencies(host: Tree) { + return addDependenciesToPackageJson( + host, + {}, + { + '@nrwl/detox': nxVersion, + detox: detoxVersion, + '@types/detox': typesDetoxVersion, + '@testing-library/jest-dom': testingLibraryJestDom, + 'jest-circus': jestVersion, + } + ); +} + +function moveDependency(host: Tree) { + return removeDependenciesFromPackageJson(host, ['@nrwl/detox'], []); +} + +export default detoxInitGenerator; +export const detoxInitSchematic = convertNxGenerator(detoxInitGenerator); diff --git a/packages/detox/src/generators/init/schema.d.ts b/packages/detox/src/generators/init/schema.d.ts new file mode 100644 index 0000000000000..e5fe924e01d2f --- /dev/null +++ b/packages/detox/src/generators/init/schema.d.ts @@ -0,0 +1,3 @@ +export interface Schema { + skipFormat?: boolean; +} diff --git a/packages/detox/src/generators/init/schema.json b/packages/detox/src/generators/init/schema.json new file mode 100644 index 0000000000000..fb4b7ce92ac46 --- /dev/null +++ b/packages/detox/src/generators/init/schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "Add Detox Schematics", + "type": "object", + "properties": { + "skipFormat": { + "description": "Skip formatting files", + "type": "boolean", + "default": false + } + }, + "required": [] +} diff --git a/packages/detox/src/utils/versions.ts b/packages/detox/src/utils/versions.ts new file mode 100644 index 0000000000000..18b2c8023f3ce --- /dev/null +++ b/packages/detox/src/utils/versions.ts @@ -0,0 +1,5 @@ +export const nxVersion = '*'; + +export const detoxVersion = '18.20.2'; +export const typesDetoxVersion = '17.14.2'; +export const testingLibraryJestDom = '5.14.1'; diff --git a/packages/detox/tsconfig.json b/packages/detox/tsconfig.json new file mode 100644 index 0000000000000..62ebbd946474c --- /dev/null +++ b/packages/detox/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/detox/tsconfig.lib.json b/packages/detox/tsconfig.lib.json new file mode 100644 index 0000000000000..037d796f28623 --- /dev/null +++ b/packages/detox/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["**/*.spec.ts"], + "include": ["**/*.ts"] +} diff --git a/packages/detox/tsconfig.spec.json b/packages/detox/tsconfig.spec.json new file mode 100644 index 0000000000000..559410b96af67 --- /dev/null +++ b/packages/detox/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/packages/react-native/.eslintrc.json b/packages/react-native/.eslintrc.json new file mode 100644 index 0000000000000..ab8f38339cde4 --- /dev/null +++ b/packages/react-native/.eslintrc.json @@ -0,0 +1 @@ +{ "extends": "../../.eslintrc", "rules": {}, "ignorePatterns": ["!**/*"] } diff --git a/packages/react-native/README.md b/packages/react-native/README.md new file mode 100644 index 0000000000000..63a87102e44cd --- /dev/null +++ b/packages/react-native/README.md @@ -0,0 +1,193 @@ +

Nx - Smart, Extensible Build Framework

+ +{{links}} + +
+ +# React Native Plugin for Nx + +{{what-is-nx}} + +{{getting-started}} + +## Table of Contents + + + + +- [Getting started](#getting-started) + - [Create a new Nx workspace:](#create-a-new-nx-workspace) + - [Install React Native plugin](#install-react-native-plugin) + - [Create an app](#create-an-app) + - [Start the JavaScript bundler](#start-the-javascript-bundler) + - [Run on devices](#run-on-devices) + - [Release build](#release-build) + - [Test/lint the app](#testlint-the-app) + - [E2e test the app](#e2e-test-the-app) + - [Setup](#setup) + - [Install applesimutils (Mac only)](#install-applesimutils-mac-only) + - [Install Jest Globally](#install-jest-globally) + - [Commands](#commands) + - [Manually Add E2E Folder](#manually-add-e2e-folder) + - [Change Testing Simulator/Emulator](#change-testing-simulatoremulator) +- [Using components from React library](#using-components-from-react-library) +- [CLI Commands and Options](#cli-commands-and-options) + - [`start`](#start) + - [`--port [number]`](#--port-number) + - [`run-ios`](#run-ios) + - [`--port [number]`](#--port-number-1) + - [`--install`](#--install) + - [`--sync`](#--sync) + - [`run-android`](#run-android) + - [`--port [number]`](#--port-number-2) + - [`--sync`](#--sync-1) + - [`sync-deps`](#sync-deps) + - [`--include [string]`](#--include-string) +- [Learn more](#learn-more) +- [Contributing](#contributing) +- [Debugging](#debugging) + + + +## Getting started + +### Create a new Nx workspace: + +```sh +npx create-nx-workspace --cli=nx --preset=empty +``` + +### Install React Native plugin + +```sh +# Using npm +npm install --save-dev @nrwl/react-native + +# Using yarn +yarn add -D @nrwl/react-native +``` + +### Create an app + +```sh +npx nx g @nrwl/react-native:app +``` + +When using Nx, you can create multiple applications and themes in the same workspace. If you don't want to prefix your commands with npx, install `@nrwl/cli` globally. + +### Start the JavaScript bundler + +```sh +npx nx start +``` + +This will start the bundler at `http://localhost:8081`. + +### Run on devices + +Make sure the bundler server is running. + +**Android:** + +```sh +npx nx run-android +``` + +**iOS:** (Mac only) + +```sh +npx nx run-ios --install +``` + +Note: The `--install` flag installs Xcode dependencies before building the iOS app. This option keeps dependencies up to date. + +### Release build + +**Android:** + +```sh +npx nx build-android +``` + +**iOS:** (Mac only) + +No CLI support yet. Run in the Xcode project. See: https://reactnative.dev/docs/running-on-device + +### Test/lint the app + +```sh +npx nx test +npx nx lint +``` + +## Using components from React library + +You can use a component from React library generated using Nx package for React. Once you run: + +```sh +npx nx g @nrwl/react-native:lib ui-button +``` + +This will generate the `UiButton` component, which you can use in your app. + +```jsx +import { UiButton } from '@myorg/ui-button'; +``` + +## CLI Commands and Options + +Usage: + +```sh +npx nx [command] [app] [...options] +``` + +### `start` + +Starts the JS bundler that communicates with connected devices. + +#### `--port [number]` + +The port to listen on. + +### `run-ios` + +Builds your app and starts it on iOS simulator. + +#### `--port [number]` + +The port of the JS bundler. + +#### `--install` + +Install dependencies for the Xcode project before building iOS app. + +#### `--sync` + +Sync app dependencies to its `package.json`. On by default, use `--no-sync` to turn it off. + +### `run-android` + +Builds your app and starts it on iOS simulator. + +#### `--port [number]` + +The port of the JS bundler. + +#### `--sync` + +Sync app dependencies to its `package.json`. On by default, use `--no-sync` to turn it off. + +### `sync-deps` + +Sync app dependencies to its `package.json`. + +#### `--include [string]` + +A comma-separate list of additional packages to include. + +e.g. `nx sync-deps [app] --include react-native-gesture,react-native-safe-area-context` + +## Learn more + +Visit the [Nx Documentation](https://nx.dev) to learn more. diff --git a/packages/react-native/executors.json b/packages/react-native/executors.json new file mode 100644 index 0000000000000..079e1dd26a4fc --- /dev/null +++ b/packages/react-native/executors.json @@ -0,0 +1,76 @@ +{ + "executors": { + "run-android": { + "implementation": "./src/executors/run-android/run-android.impl", + "schema": "./src/executors/run-android/schema.json", + "description": "Runs Android application." + }, + "run-ios": { + "implementation": "./src/executors/run-ios/run-ios.impl", + "schema": "./src/executors/run-ios/schema.json", + "description": "Runs iOS application." + }, + "bundle": { + "implementation": "./src/executors/bundle/bundle.impl", + "schema": "./src/executors/bundle/schema.json", + "description": "Builds the JS bundle." + }, + "build-android": { + "implementation": "./src/executors/build-android/build-android.impl", + "schema": "./src/executors/build-android/schema.json", + "description": "Release Build for Android." + }, + "start": { + "implementation": "./src/executors/start/start.impl", + "schema": "./src/executors/start/schema.json", + "description": "Starts the dev server for JS bundle." + }, + "sync-deps": { + "implementation": "./src/executors/sync-deps/sync-deps.impl", + "schema": "./src/executors/sync-deps/schema.json", + "description": "Syncs dependencies to package.json (required for autolinking)." + }, + "ensure-symlink": { + "implementation": "./src/executors/ensure-symlink/ensure-symlink.impl", + "schema": "./src/executors/ensure-symlink//schema.json", + "description": "Ensure workspace node_modules is symlink under app's node_modules folder." + } + }, + "builders": { + "run-android": { + "implementation": "./src/executors/run-android/compat", + "schema": "./src/executors/run-android/schema.json", + "description": "Runs Android application." + }, + "run-ios": { + "implementation": "./src/executors/run-ios/compat", + "schema": "./src/executors/run-ios/schema.json", + "description": "Runs iOS application." + }, + "bundle": { + "implementation": "./src/executors/bundle/compat", + "schema": "./src/executors/bundle/schema.json", + "description": "Builds the JS bundle." + }, + "build-android": { + "implementation": "./src/executors/build-android/compat", + "schema": "./src/executors/build-android/schema.json", + "description": "Release Build for Android." + }, + "start": { + "implementation": "./src/executors/start/compat", + "schema": "./src/executors/start/schema.json", + "description": "Starts the dev server for JS bundle." + }, + "sync-deps": { + "implementation": "./src/executors/sync-deps/compat", + "schema": "./src/executors/sync-deps/schema.json", + "description": "Syncs dependencies to package.json (required for autolinking)." + }, + "ensure-symlink": { + "implementation": "./src/executors/ensure-symlink/compat", + "schema": "./src/executors/ensure-symlink//schema.json", + "description": "Ensure workspace node_modules is symlink under app's node_modules folder." + } + } +} diff --git a/packages/react-native/generators.json b/packages/react-native/generators.json new file mode 100644 index 0000000000000..e790ff2738e6b --- /dev/null +++ b/packages/react-native/generators.json @@ -0,0 +1,57 @@ +{ + "name": "Nx React Native", + "version": "0.1", + "extends": ["@nrwl/workspace"], + "schematics": { + "init": { + "factory": "./src/generators/init/init#reactNativeInitSchematic", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the @nrwl/react-native plugin", + "hidden": true + }, + "application": { + "factory": "./src/generators/application/application#reactNativeApplicationSchematic", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "description": "Create an application" + }, + "library": { + "factory": "./src/generators/library/library#reactNativeLibrarySchematic", + "schema": "./src/generators/library/schema.json", + "aliases": ["lib"], + "description": "Create a library" + }, + "component": { + "factory": "./src/generators/component/component#reactNativeComponentSchematic", + "schema": "./src/generators/component/schema.json", + "description": "Create a component", + "aliases": ["c"] + } + }, + "generators": { + "init": { + "factory": "./src/generators/init/init#reactNativeInitGenerator", + "schema": "./src/generators/init/schema.json", + "description": "Initialize the @nrwl/react-native plugin", + "hidden": true + }, + "application": { + "factory": "./src/generators/application/application#reactNativeApplicationGenerator", + "schema": "./src/generators/application/schema.json", + "aliases": ["app"], + "description": "Create an application" + }, + "library": { + "factory": "./src/generators/library/library#reactNativeLibraryGenerator", + "schema": "./src/generators/library/schema.json", + "aliases": ["lib"], + "description": "Create a library" + }, + "component": { + "factory": "./src/generators/component/component#reactNativeComponentGenerator", + "schema": "./src/generators/component/schema.json", + "description": "Create a component", + "aliases": ["c"] + } + } +} diff --git a/packages/react-native/index.ts b/packages/react-native/index.ts new file mode 100644 index 0000000000000..18ca544f227fe --- /dev/null +++ b/packages/react-native/index.ts @@ -0,0 +1,5 @@ +export { reactNativeInitGenerator } from './src/generators/init/init'; +export { reactNativeApplicationGenerator } from './src/generators/application/application'; +export { reactNativeLibraryGenerator } from './src/generators/library/library'; +export { reactNativeComponentGenerator } from './src/generators/component/component'; +export { withNxMetro } from './plugins/with-nx-metro'; diff --git a/packages/react-native/jest.config.js b/packages/react-native/jest.config.js new file mode 100644 index 0000000000000..32aef31aa6b1f --- /dev/null +++ b/packages/react-native/jest.config.js @@ -0,0 +1,12 @@ +module.exports = { + preset: '../../jest.preset.js', + transform: { + '^.+\\.[tj]sx?$': 'ts-jest', + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'html', 'json'], + globals: { + 'ts-jest': { tsconfig: '/tsconfig.spec.json' }, + }, + displayName: 'react-native', + testEnvironment: 'node', +}; diff --git a/packages/react-native/migrations.json b/packages/react-native/migrations.json new file mode 100644 index 0000000000000..7fbb7f7110b51 --- /dev/null +++ b/packages/react-native/migrations.json @@ -0,0 +1,209 @@ +{ + "schematics": { + "update-jest-for-react-native": { + "version": "12.5.0-beta.0", + "cli": "nx", + "description": "Update jest for react native", + "factory": "./src/migrations/update-12-5-0/update-jest-for-react-native" + } + }, + "packageJsonUpdates": { + "11.4.0": { + "version": "11.4.0-beta.0", + "packages": { + "metro": { + "version": "0.59.0", + "addToPackageJson": "devDependencies", + "alwaysAddToPackageJson": false + }, + "metro-resolver": { + "version": "0.59.0", + "addToPackageJson": "devDependencies", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli": { + "version": "4.14.0", + "addToPackageJson": "devDependencies", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli-platform-android": { + "version": "4.14.0", + "addToPackageJson": "devDependencies", + "alwaysAddToPackageJson": false + }, + "react": { + "version": "17.0.1", + "alwaysAddToPackageJson": false + }, + "@types/react": { + "version": "16.9.56", + "alwaysAddToPackageJson": false + }, + "react-native": { + "version": "0.63.4", + "alwaysAddToPackageJson": false + }, + "@types/react-native": { + "version": "0.63.18", + "alwaysAddToPackageJson": false + } + } + }, + "12.3.6": { + "version": "12.3.6-beta.0", + "packages": { + "react-native": { + "version": "0.64.1", + "alwaysAddToPackageJson": false + }, + "@nrwl/react": { + "version": "12.3.6", + "alwaysAddToPackageJson": false + }, + "@nrwl/jest": { + "version": "12.3.6", + "alwaysAddToPackageJson": false + }, + "@nrwl/linter": { + "version": "12.3.6", + "alwaysAddToPackageJson": false + }, + "@types/react": { + "version": "17.0.6", + "alwaysAddToPackageJson": false + }, + "@types/react-native": { + "version": "0.64.5", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli": { + "version": "5.0.1-alpha.2", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli-platform-android": { + "version": "5.0.1-alpha.1", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli-platform-ios": { + "version": "5.0.1-alpha.2", + "alwaysAddToPackageJson": false + }, + "metro-react-native-babel-preset": { + "version": "0.66.0", + "alwaysAddToPackageJson": false + }, + "@testing-library/react-native": { + "version": "7.2.0", + "alwaysAddToPackageJson": false + }, + "@testing-library/jest-native": { + "version": "4.0.1", + "alwaysAddToPackageJson": false + }, + "metro": { + "version": "0.66.0", + "alwaysAddToPackageJson": false + }, + "metro-resolver": { + "version": "0.66.0", + "alwaysAddToPackageJson": false + } + } + }, + "12.5.0": { + "version": "12.5.0-beta.0", + "packages": { + "@nrwl/jest": { + "version": "12.5.7", + "alwaysAddToPackageJson": false + }, + "@nrwl/linter": { + "version": "12.5.7", + "alwaysAddToPackageJson": false + }, + "react": { + "version": "17.0.2", + "alwaysAddToPackageJson": false + }, + "react-native": { + "version": "0.65.0-rc.2", + "alwaysAddToPackageJson": false + }, + "react-test-renderer": { + "version": "17.0.2", + "alwaysAddToPackageJson": false + }, + "react-native-codegen": { + "version": "0.0.7", + "addToPackageJson": "devDependencies", + "alwaysAddToPackageJson": false + }, + "@types/react-native": { + "version": "0.64.10", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli": { + "version": "6.0.0-rc.0", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli-platform-android": { + "version": "6.0.0-rc.0", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli-platform-ios": { + "version": "6.0.0-rc.0", + "alwaysAddToPackageJson": false + }, + "@testing-library/react-native": { + "version": "8.0.0-rc.0", + "alwaysAddToPackageJson": false + } + } + }, + "12.8.0": { + "version": "12.8.0-beta.0", + "packages": { + "react-native": { + "version": "0.65.1", + "alwaysAddToPackageJson": false + }, + "@types/react-native": { + "version": "0.64.13", + "alwaysAddToPackageJson": false + }, + "@types/react": { + "version": "17.0.19", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli": { + "version": "6.0.0", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli-platform-android": { + "version": "6.0.0", + "alwaysAddToPackageJson": false + }, + "@react-native-community/cli-platform-ios": { + "version": "6.0.0", + "alwaysAddToPackageJson": false + }, + "@testing-library/jest-native": { + "version": "4.0.2", + "alwaysAddToPackageJson": false + }, + "metro": { + "version": "0.66.2", + "alwaysAddToPackageJson": false + }, + "metro-resolver": { + "version": "0.66.2", + "alwaysAddToPackageJson": false + }, + "metro-react-native-babel-preset": { + "version": "0.66.2", + "alwaysAddToPackageJson": false + } + } + } + } +} diff --git a/packages/react-native/nx_post_install.rb b/packages/react-native/nx_post_install.rb new file mode 100644 index 0000000000000..62bce6ae0a99c --- /dev/null +++ b/packages/react-native/nx_post_install.rb @@ -0,0 +1,15 @@ +def nx_post_install (installer) + Pod::UI.info("[Nx] Updating build settings to support custom port") + installer.pods_project.targets.each do |target| + if ['React', 'React-Core'].include?(target.name) + target.build_configurations.each do |build_configuration| + if build_configuration.name == 'Debug' + gcc_preprocessor_defs = build_configuration.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] + gcc_preprocessor_defs ||= %w($(inherited) COCOAPODS=1 DEBUG=1) + gcc_preprocessor_defs << 'RCT_METRO_PORT=${RCT_METRO_PORT}' unless gcc_preprocessor_defs.include?('RCT_METRO_PORT') + build_configuration.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = gcc_preprocessor_defs + end + end + end + end + end diff --git a/packages/react-native/package.json b/packages/react-native/package.json new file mode 100644 index 0000000000000..cb4ed3235740b --- /dev/null +++ b/packages/react-native/package.json @@ -0,0 +1,47 @@ +{ + "name": "@nrwl/react-native", + "version": "0.0.1", + "description": "React Native Plugin for Nx", + "keywords": [ + "Monorepo", + "React", + "Web", + "Jest", + "Native", + "CLI" + ], + "homepage": "https://nx.dev", + "bugs": { + "url": "https://github.com/nrwl/nx/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/nrwl/nx.git" + }, + "license": "MIT", + "author": "Victor Savkin", + "main": "index.js", + "types": "index.d.ts", + "dependencies": { + "@nrwl/detox": "*", + "@nrwl/devkit": "*", + "@nrwl/jest": "*", + "@nrwl/linter": "*", + "@nrwl/react": "*", + "chalk": "^4.1.0", + "ignore": "^5.0.4", + "metro-resolver": "^0.66.2", + "node-fetch": "^2.6.1", + "tsconfig-paths": "^3.9.0" + }, + "peerDependencies": { + "@nrwl/workspace": "*", + "react-native": "0.65.1" + }, + "builders": "./executors.json", + "ng-update": { + "requirements": {}, + "migrations": "./migrations.json" + }, + "schematics": "./generators.json" +} diff --git a/packages/react-native/plugins/metro-resolver.ts b/packages/react-native/plugins/metro-resolver.ts new file mode 100644 index 0000000000000..979d2b0a113e8 --- /dev/null +++ b/packages/react-native/plugins/metro-resolver.ts @@ -0,0 +1,98 @@ +import * as metroResolver from 'metro-resolver'; +import type { MatchPath } from 'tsconfig-paths'; +import { createMatchPath, loadConfig } from 'tsconfig-paths'; +import * as chalk from 'chalk'; + +/* + * Use tsconfig to resolve additional workspace libs. + * + * This resolve function requires projectRoot to be set to + * workspace root in order modules and assets to be registered and watched. + */ +export function getResolveRequest(extensions: string[]) { + return function ( + _context: any, + realModuleName: string, + platform: string | null, + moduleName: string + ) { + const DEBUG = process.env.NX_REACT_NATIVE_DEBUG === 'true'; + + if (DEBUG) console.log(chalk.cyan(`[Nx] Resolving: ${moduleName}`)); + + const { resolveRequest, ...context } = _context; + try { + return metroResolver.resolve(context, moduleName, platform); + } catch { + if (DEBUG) + console.log( + chalk.cyan( + `[Nx] Unable to resolve with default Metro resolver: ${moduleName}` + ) + ); + } + const matcher = getMatcher(); + let match; + const matchExtension = extensions.find((extension) => { + match = matcher(realModuleName, undefined, undefined, ['.' + extension]); + return !!match; + }); + + if (match) { + return { + type: 'sourceFile', + filePath: + !matchExtension || match.endsWith(`.${matchExtension}`) + ? match + : `${match}.${matchExtension}`, + }; + } else { + if (DEBUG) { + console.log( + chalk.red(`[Nx] Failed to resolve ${chalk.bold(moduleName)}`) + ); + console.log( + chalk.cyan( + `[Nx] The following tsconfig paths was used:\n:${chalk.bold( + JSON.stringify(paths, null, 2) + )}` + ) + ); + } + throw new Error(`Cannot resolve ${chalk.bold(moduleName)}`); + } + }; +} + +let matcher: MatchPath; +let absoluteBaseUrl: string; +let paths: Record; + +function getMatcher() { + const DEBUG = process.env.NX_REACT_NATIVE_DEBUG === 'true'; + + if (!matcher) { + const result = loadConfig(); + if (result.resultType === 'success') { + absoluteBaseUrl = result.absoluteBaseUrl; + paths = result.paths; + if (DEBUG) { + console.log( + chalk.cyan(`[Nx] Located tsconfig at ${chalk.bold(absoluteBaseUrl)}`) + ); + console.log( + chalk.cyan( + `[Nx] Found the following paths:\n:${chalk.bold( + JSON.stringify(paths, null, 2) + )}` + ) + ); + } + matcher = createMatchPath(absoluteBaseUrl, paths); + } else { + console.log(chalk.cyan(`[Nx] Failed to locate tsconfig}`)); + throw new Error(`Could not load tsconfig for project`); + } + } + return matcher; +} diff --git a/packages/react-native/plugins/with-nx-metro.ts b/packages/react-native/plugins/with-nx-metro.ts new file mode 100644 index 0000000000000..09d17575bb277 --- /dev/null +++ b/packages/react-native/plugins/with-nx-metro.ts @@ -0,0 +1,24 @@ +import { appRootPath } from '@nrwl/workspace/src/utils/app-root'; +import { getResolveRequest } from './metro-resolver'; + +interface WithNxOptions { + debug?: boolean; + extensions?: string[]; +} + +export function withNxMetro(config: any, opts: WithNxOptions = {}) { + const extensions = ['', 'ts', 'tsx', 'js', 'jsx']; + if (opts.debug) process.env.NX_REACT_NATIVE_DEBUG = 'true'; + if (opts.extensions) extensions.push(...opts.extensions); + + // Set the root to workspace root so we can resolve modules and assets + config.projectRoot = appRootPath; + + // Add support for paths specified by tsconfig + config.resolver = { + ...config.resolver, + resolveRequest: getResolveRequest(extensions), + }; + + return config; +} diff --git a/packages/react-native/project.json b/packages/react-native/project.json new file mode 100644 index 0000000000000..550e08adfef48 --- /dev/null +++ b/packages/react-native/project.json @@ -0,0 +1,75 @@ +{ + "root": "packages/react-native", + "sourceRoot": "packages/react-native/src", + "projectType": "library", + "targets": { + "lint": { + "builder": "@nrwl/linter:eslint", + "options": { + "lintFilePatterns": [ + "packages/react-native/**/*.ts", + "packages/react-native/**/*.spec.ts", + "packages/react-native/**/*.spec.tsx", + "packages/react-native/**/*.spec.js", + "packages/react-native/**/*.spec.jsx", + "packages/react-native/**/*.d.ts" + ] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "options": { + "jestConfig": "packages/react-native/jest.config.js", + "passWithNoTests": true + }, + "outputs": ["coverage/packages/react-native"] + }, + "build-base": { + "builder": "@nrwl/node:package", + "options": { + "outputPath": "build/packages/react-native", + "tsConfig": "packages/react-native/tsconfig.lib.json", + "packageJson": "packages/react-native/package.json", + "main": "packages/react-native/index.ts", + "updateBuildableProjectDepsInPackageJson": false, + "assets": [ + "packages/react-native/*.md", + { + "input": "packages/react-native", + "glob": "**/files/**/.babelrc__tmpl__", + "output": "/" + }, + { + "input": "packages/react-native", + "glob": "**/files/**/.babelrc.template", + "output": "/" + }, + { + "input": "./packages/react-native/src", + "glob": "**/*.!(ts)", + "output": "./src" + }, + { + "input": "./packages/react-native", + "glob": "*.rb", + "output": "." + }, + { + "input": "./packages/react-native", + "glob": "**/*.json", + "ignore": ["**/tsconfig*.json"], + "output": "/" + } + ] + }, + "outputs": ["{options.outputPath}"] + }, + "build": { + "executor": "@nrwl/workspace:run-commands", + "outputs": ["build/packages/react-native"], + "options": { + "command": "node ./scripts/copy-readme.js react-native" + } + } + } +} diff --git a/packages/react-native/src/executors/build-android/build-android.impl.ts b/packages/react-native/src/executors/build-android/build-android.impl.ts new file mode 100644 index 0000000000000..86cb684d1c73d --- /dev/null +++ b/packages/react-native/src/executors/build-android/build-android.impl.ts @@ -0,0 +1,58 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { join } from 'path'; +import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; +import { ChildProcess, spawn } from 'child_process'; +import { chmodSync } from 'fs'; +import { ReactNativeBuildOptions } from './schema'; +export interface ReactNativeBuildOutput { + success: boolean; +} + +let childProcess: ChildProcess; + +export default async function* buildAndroidExecutor( + options: ReactNativeBuildOptions, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + ensureNodeModulesSymlink(context.root, projectRoot); + chmodSync(join(projectRoot, 'android', 'gradlew'), 0o775); + chmodSync(join(projectRoot, 'android', 'gradlew.bat'), 0o775); + + try { + await runCliBuild(projectRoot, options); + yield { success: true }; + } finally { + if (childProcess) { + childProcess.kill(); + } + } +} + +function runCliBuild(projectRoot: string, options: ReactNativeBuildOptions) { + return new Promise((resolve, reject) => { + childProcess = spawn( + process.platform === 'win32' ? 'gradlew.bat' : './gradlew', + [options.apk ? 'assembleRelease' : 'bundleRelease'], + { + cwd: join(projectRoot, 'android'), + stdio: [0, 1, 2], + } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} diff --git a/packages/react-native/src/executors/build-android/compat.ts b/packages/react-native/src/executors/build-android/compat.ts new file mode 100644 index 0000000000000..12465b9fe895c --- /dev/null +++ b/packages/react-native/src/executors/build-android/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import buildAndroidExecutor from './build-android.impl'; + +export default convertNxExecutor(buildAndroidExecutor); diff --git a/packages/react-native/src/executors/build-android/schema.d.ts b/packages/react-native/src/executors/build-android/schema.d.ts new file mode 100644 index 0000000000000..5f42ee8024b30 --- /dev/null +++ b/packages/react-native/src/executors/build-android/schema.d.ts @@ -0,0 +1,3 @@ +export interface ReactNativeBuildOptions { + apk?: boolean; +} diff --git a/packages/react-native/src/executors/build-android/schema.json b/packages/react-native/src/executors/build-android/schema.json new file mode 100644 index 0000000000000..034fae138c6a6 --- /dev/null +++ b/packages/react-native/src/executors/build-android/schema.json @@ -0,0 +1,15 @@ +{ + "cli": "nx", + "$id": "NxReactNativeBuildAndroid", + "$schema": "http://json-schema.org/schema", + "title": "Release Build for Android", + "description": "Build target options", + "type": "object", + "properties": { + "apk": { + "type": "boolean", + "description": "Generate apk file(s) rather than a bundle (.aab)." + } + }, + "required": [] +} diff --git a/packages/react-native/src/executors/bundle/bundle.impl.ts b/packages/react-native/src/executors/bundle/bundle.impl.ts new file mode 100644 index 0000000000000..de0534f5ac63a --- /dev/null +++ b/packages/react-native/src/executors/bundle/bundle.impl.ts @@ -0,0 +1,74 @@ +import { createDirectory } from '@nrwl/workspace/src/utilities/fileutils'; +import { toFileName } from '@nrwl/workspace/src/devkit-reexport'; +import { dirname, join, relative, sep } from 'path'; +import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; +import { ChildProcess, fork } from 'child_process'; +import { ExecutorContext } from '@nrwl/devkit'; +import { ReactNativeBundleOptions } from './schema'; + +export interface ReactNativeBundleOutput { + success: boolean; +} + +let childProcess: ChildProcess; + +export default async function* bundleExecutor( + options: ReactNativeBundleOptions, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + + options.bundleOutput = relative(context.root, projectRoot) + .split(sep) + .map(() => '..') + .concat(options.bundleOutput) + .join(sep); + + createDirectory(dirname(join(projectRoot, options.bundleOutput))); + ensureNodeModulesSymlink(context.root, projectRoot); + + try { + await runCliBuild(context.root, projectRoot, options); + yield { success: true }; + } finally { + if (childProcess) { + childProcess.kill(); + } + } +} + +function runCliBuild(workspaceRoot, projectRoot, options) { + return new Promise((resolve, reject) => { + const cliOptions = createBundleOptions(options); + childProcess = fork( + join(workspaceRoot, './node_modules/react-native/cli.js'), + ['bundle', ...cliOptions], + { cwd: projectRoot } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + +function createBundleOptions(options) { + return Object.keys(options).reduce((acc, _k) => { + const v = options[_k]; + const k = toFileName(_k); + if (v === undefined) return acc; + acc.push(`--${k}`, v); + return acc; + }, []); +} diff --git a/packages/react-native/src/executors/bundle/compat.ts b/packages/react-native/src/executors/bundle/compat.ts new file mode 100644 index 0000000000000..5ee72ffcf7032 --- /dev/null +++ b/packages/react-native/src/executors/bundle/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import bundleExecutor from './bundle.impl'; + +export default convertNxExecutor(bundleExecutor); diff --git a/packages/react-native/src/executors/bundle/schema.d.ts b/packages/react-native/src/executors/bundle/schema.d.ts new file mode 100644 index 0000000000000..dfaa8ba589e57 --- /dev/null +++ b/packages/react-native/src/executors/bundle/schema.d.ts @@ -0,0 +1,8 @@ +export interface ReactNativeBundleOptions { + dev: boolean; + platform: string; + entryFile: string; + bundleOutput: string; + maxWorkers: number; + sourceMap: boolean; +} diff --git a/packages/react-native/src/executors/bundle/schema.json b/packages/react-native/src/executors/bundle/schema.json new file mode 100644 index 0000000000000..8792e54f68de6 --- /dev/null +++ b/packages/react-native/src/executors/bundle/schema.json @@ -0,0 +1,36 @@ +{ + "cli": "nx", + "$id": "NxReactNativeBundle", + "$schema": "http://json-schema.org/schema", + "title": "Offline JS Bundle for React Native", + "description": "JS Bundle target options", + "type": "object", + "properties": { + "dev": { + "type": "boolean", + "description": "Generate a development build.", + "default": true + }, + "entryFile": { + "type": "string", + "description": "The entry file relative to project root." + }, + "bundleOutput": { + "type": "string", + "description": "The output path of the generated files." + }, + "maxWorkers": { + "type": "number", + "description": "The number of workers we should parallelize the transformer on." + }, + "sourceMap": { + "type": "boolean", + "description": "Whether source maps should be generated or not." + }, + "platform": { + "description": "Platform to build for (ios, android).", + "type": "string" + } + }, + "required": ["platform", "entryFile", "bundleOutput"] +} diff --git a/packages/react-native/src/executors/ensure-symlink/compat.ts b/packages/react-native/src/executors/ensure-symlink/compat.ts new file mode 100644 index 0000000000000..94c2df8239aa6 --- /dev/null +++ b/packages/react-native/src/executors/ensure-symlink/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import ensureSymlinkExecutor from './ensure-symlink.impl'; + +export default convertNxExecutor(ensureSymlinkExecutor); diff --git a/packages/react-native/src/executors/ensure-symlink/ensure-symlink.impl.ts b/packages/react-native/src/executors/ensure-symlink/ensure-symlink.impl.ts new file mode 100644 index 0000000000000..b07fce099eae2 --- /dev/null +++ b/packages/react-native/src/executors/ensure-symlink/ensure-symlink.impl.ts @@ -0,0 +1,17 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; + +export interface ReactNativeEnsureSymlinkOutput { + success: boolean; +} + +export default async function* ensureSymlinkExecutor( + _, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + + ensureNodeModulesSymlink(context.root, projectRoot); + + yield { success: true }; +} diff --git a/packages/react-native/src/executors/ensure-symlink/schema.json b/packages/react-native/src/executors/ensure-symlink/schema.json new file mode 100644 index 0000000000000..2b90bb8a1c8e4 --- /dev/null +++ b/packages/react-native/src/executors/ensure-symlink/schema.json @@ -0,0 +1,9 @@ +{ + "cli": "nx", + "$id": "NxReactNativeEnsureSymlink", + "$schema": "http://json-schema.org/schema", + "title": "Ensure Symlink for React Native", + "description": "Ensure workspace node_modules is symlink under app's node_modules folder.", + "type": "object", + "properties": {} +} diff --git a/packages/react-native/src/executors/run-android/compat.ts b/packages/react-native/src/executors/run-android/compat.ts new file mode 100644 index 0000000000000..42e734a8a7adc --- /dev/null +++ b/packages/react-native/src/executors/run-android/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import runAndroidExecutor from './run-android.impl'; + +export default convertNxExecutor(runAndroidExecutor); diff --git a/packages/react-native/src/executors/run-android/run-android.impl.ts b/packages/react-native/src/executors/run-android/run-android.impl.ts new file mode 100644 index 0000000000000..5aa2821d7c2a4 --- /dev/null +++ b/packages/react-native/src/executors/run-android/run-android.impl.ts @@ -0,0 +1,106 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { join } from 'path'; +import { ChildProcess, fork } from 'child_process'; + +import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; +import { + displayNewlyAddedDepsMessage, + syncDeps, +} from '../sync-deps/sync-deps.impl'; +import { chmodSync } from 'fs'; +import { ReactNativeRunAndroidOptions } from './schema'; +import { runCliStart } from '../start/start.impl'; + +export interface ReactNativeRunAndroidOutput { + success: boolean; +} + +let childProcess: ChildProcess; + +export default async function* runAndroidExecutor( + options: ReactNativeRunAndroidOptions, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + ensureNodeModulesSymlink(context.root, projectRoot); + chmodSync(join(projectRoot, 'android', 'gradlew'), 0o775); + chmodSync(join(projectRoot, 'android', 'gradlew.bat'), 0o775); + + if (options.sync) { + displayNewlyAddedDepsMessage( + context.projectName, + await syncDeps(context.projectName, projectRoot) + ); + } + + try { + const tasks = [runCliRunAndroid(context.root, projectRoot, options)]; + if (options.packager) { + tasks.push( + runCliStart(context.root, projectRoot, { port: options.port }) + ); + } + + await Promise.all(tasks); + + yield { success: true }; + } finally { + if (childProcess) { + childProcess.kill(); + } + } +} + +function runCliRunAndroid( + workspaceRoot: string, + projectRoot: string, + options: ReactNativeRunAndroidOptions +) { + return new Promise((resolve, reject) => { + /** + * Call the react native cli with option `--no-packager` + * Not passing '--packager' due to cli will launch start command from the project root + */ + childProcess = fork( + join(workspaceRoot, './node_modules/react-native/cli.js'), + ['run-android', ...createRunAndroidOptions(options), '--no-packager'], + { + cwd: projectRoot, + env: { ...process.env, RCT_METRO_PORT: options.port.toString() }, + } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + +const nxOptions = ['sync', 'install', 'packager']; + +function createRunAndroidOptions(options) { + return Object.keys(options).reduce((acc, k) => { + const v = options[k]; + if (k === 'mainActivity') { + acc.push(`--main-activity`, v); + } else if (k === 'jetifier') { + if (!v) { + acc.push(`--no-jetifier`); + } + } else if (v && !nxOptions.includes(k)) { + acc.push(`--${k}`, v); + } + return acc; + }, []); +} diff --git a/packages/react-native/src/executors/run-android/schema.d.ts b/packages/react-native/src/executors/run-android/schema.d.ts new file mode 100644 index 0000000000000..a12ec80c92417 --- /dev/null +++ b/packages/react-native/src/executors/run-android/schema.d.ts @@ -0,0 +1,14 @@ +// part of options from https://github.com/react-native-community/cli/blob/master/packages/platform-android/src/commands/runAndroid/index.ts#L314 +export interface ReactNativeRunAndroidOptions { + variant: string; + appId: string; + appIdSuffix: string; + mainActiviy: string; + deviceId: string; + tasks?: string; + jetifier: boolean; + sync: boolean; + port: number; + terminal?: string; + packager: boolean; +} diff --git a/packages/react-native/src/executors/run-android/schema.json b/packages/react-native/src/executors/run-android/schema.json new file mode 100644 index 0000000000000..4bc3743519932 --- /dev/null +++ b/packages/react-native/src/executors/run-android/schema.json @@ -0,0 +1,60 @@ +{ + "cli": "nx", + "$id": "NxReactNativeRunAndroid", + "$schema": "http://json-schema.org/schema", + "title": "Run Android application", + "description": "Run Android target options", + "type": "object", + "properties": { + "variant": { + "type": "string", + "description": "Specify your app's build variant (e.g. debug, release).", + "default": "debug" + }, + "appId": { + "type": "string", + "description": "Specify an applicationId to launch after build. If not specified, 'package' from AndroidManifest.xml will be used." + }, + "appIdSuffix": { + "type": "string", + "description": "Specify an applicationIdSuffix to launch after build." + }, + "mainActivity": { + "type": "string", + "description": "Name of the activity to start.", + "default": "MainActivity" + }, + "deviceId": { + "type": "string", + "description": "Builds your app and starts it on a specific device/simulator with the given device id (listed by running \"adb devices\" on the command line)." + }, + "tasks": { + "type": "string", + "description": "Run custom gradle tasks. If this argument is provided, then --variant option is ignored. Example: yarn react-native run-android --tasks clean,installDebug." + }, + "jetifier": { + "type": "boolean", + "description": "Run jetifier – the AndroidX transition tool. By default it runs before Gradle to ease working with libraries that don't support AndroidX yet.", + "default": true + }, + "sync": { + "type": "boolean", + "description": "Syncs npm dependencies to package.json (for React Native autolink).", + "default": true + }, + "port": { + "type": "number", + "description": "The port where the packager server is listening on.", + "default": 8081 + }, + "terminal": { + "type": "string", + "description": "Launches the Metro Bundler in a new window using the specified terminal path." + }, + "packager": { + "type": "boolean", + "description": "Starts the packager server", + "default": true + } + } +} diff --git a/packages/react-native/src/executors/run-ios/compat.ts b/packages/react-native/src/executors/run-ios/compat.ts new file mode 100644 index 0000000000000..df1ef7090fb7b --- /dev/null +++ b/packages/react-native/src/executors/run-ios/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import runIosExecutor from './run-ios.impl'; + +export default convertNxExecutor(runIosExecutor); diff --git a/packages/react-native/src/executors/run-ios/run-ios.impl.ts b/packages/react-native/src/executors/run-ios/run-ios.impl.ts new file mode 100644 index 0000000000000..934d4dd7a439c --- /dev/null +++ b/packages/react-native/src/executors/run-ios/run-ios.impl.ts @@ -0,0 +1,106 @@ +import { ExecutorContext } from '@nrwl/devkit'; +import { join } from 'path'; +import { ChildProcess, fork } from 'child_process'; +import { platform } from 'os'; + +import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; +import { + displayNewlyAddedDepsMessage, + syncDeps, +} from '../sync-deps/sync-deps.impl'; +import { podInstall } from '../../utils/pod-install-task'; +import { ReactNativeRunIosOptions } from './schema'; +import { runCliStart } from '../start/start.impl'; + +export interface ReactNativeRunIosOutput { + success: boolean; +} + +let childProcess: ChildProcess; + +export default async function* runIosExecutor( + options: ReactNativeRunIosOptions, + context: ExecutorContext +): AsyncGenerator { + if (platform() !== 'darwin') { + throw new Error(`The run-ios build requires Mac to run`); + } + const projectRoot = context.workspace.projects[context.projectName].root; + ensureNodeModulesSymlink(context.root, projectRoot); + if (options.sync) { + displayNewlyAddedDepsMessage( + context.projectName, + await syncDeps(context.projectName, projectRoot) + ); + } + if (options.install) { + await podInstall(join(projectRoot, 'ios')); + } + + try { + const tasks = [runCliRunIOS(context.root, projectRoot, options)]; + if (options.packager && options.xcodeConfiguration !== 'Release') { + tasks.push( + runCliStart(context.root, projectRoot, { port: options.port }) + ); + } + + await Promise.all(tasks); + + yield { success: true }; + } finally { + if (childProcess) { + childProcess.kill(); + } + } +} + +function runCliRunIOS( + workspaceRoot: string, + projectRoot: string, + options: ReactNativeRunIosOptions +) { + return new Promise((resolve, reject) => { + /** + * Call the react native cli with option `--no-packager` + * Not passing '--packager' due to cli will launch start command from the project root + */ + childProcess = fork( + join(workspaceRoot, './node_modules/react-native/cli.js'), + ['run-ios', ...createRunIOSOptions(options), '--no-packager'], + { + cwd: projectRoot, + env: { ...process.env, RCT_METRO_PORT: options.port.toString() }, + } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + +const nxOptions = ['sync', 'install', 'packager']; + +function createRunIOSOptions(options) { + return Object.keys(options).reduce((acc, k) => { + const v = options[k]; + if (k === 'xcodeConfiguration') { + acc.push('--configuration', v); + } else if (v && !nxOptions.includes(k)) { + acc.push(`--${k}`, options[k]); + } + return acc; + }, []); +} diff --git a/packages/react-native/src/executors/run-ios/schema.d.ts b/packages/react-native/src/executors/run-ios/schema.d.ts new file mode 100644 index 0000000000000..61aff0f3a71eb --- /dev/null +++ b/packages/react-native/src/executors/run-ios/schema.d.ts @@ -0,0 +1,12 @@ +// part of options form https://github.com/react-native-community/cli/blob/master/packages/platform-ios/src/commands/runIOS/index.ts#L541 +export interface ReactNativeRunIosOptions { + xcodeConfiguration: string; + port: number; + scheme: string; + simulator: string; + device: string; + packager: boolean; + install?: boolean; + sync?: boolean; + terminal?: string; +} diff --git a/packages/react-native/src/executors/run-ios/schema.json b/packages/react-native/src/executors/run-ios/schema.json new file mode 100644 index 0000000000000..9dd7bd99eebb0 --- /dev/null +++ b/packages/react-native/src/executors/run-ios/schema.json @@ -0,0 +1,52 @@ +{ + "cli": "nx", + "$id": "NxReactNativeRunIos", + "$schema": "http://json-schema.org/schema", + "title": "Run iOS application", + "description": "Run iOS target options", + "type": "object", + "properties": { + "xcodeConfiguration": { + "type": "string", + "description": "Explicitly set the Xcode configuration to use", + "default": "Debug" + }, + "scheme": { + "type": "string", + "description": "Explicitly set the Xcode scheme to use" + }, + "simulator": { + "type": "string", + "description": "Explicitly set simulator to use. Optionally include iOS version between parenthesis at the end to match an exact version: \"iPhone X (12.1)\"", + "default": "iPhone X" + }, + "device": { + "type": "string", + "description": "Explicitly set device to use by name. The value is not required if you have a single device connected." + }, + "install": { + "type": "boolean", + "description": "Runs 'pod install' for native modules before building iOS app.", + "default": true + }, + "sync": { + "type": "boolean", + "description": "Syncs npm dependencies to package.json (for React Native autolink). Always true when --install is used.", + "default": true + }, + "port": { + "type": "number", + "description": "The port where the packager server is listening on.", + "default": 8081 + }, + "terminal": { + "type": "string", + "description": "Launches the Metro Bundler in a new window using the specified terminal path." + }, + "packager": { + "type": "boolean", + "description": "Starts the packager server", + "default": true + } + } +} diff --git a/packages/react-native/src/executors/start/compat.ts b/packages/react-native/src/executors/start/compat.ts new file mode 100644 index 0000000000000..dfc3aa7e71e14 --- /dev/null +++ b/packages/react-native/src/executors/start/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import startExecutor from './start.impl'; + +export default convertNxExecutor(startExecutor); diff --git a/packages/react-native/src/executors/start/lib/is-packager-running.ts b/packages/react-native/src/executors/start/lib/is-packager-running.ts new file mode 100644 index 0000000000000..4ea543fb7a014 --- /dev/null +++ b/packages/react-native/src/executors/start/lib/is-packager-running.ts @@ -0,0 +1,13 @@ +import fetch from 'node-fetch'; + +export async function isPackagerRunning( + packagerPort: number +): Promise<'running' | 'not_running' | 'unrecognized'> { + try { + const resp = await fetch(`http://localhost:${packagerPort}/status`); + const data = await resp.text(); + return data === 'packager-status:running' ? 'running' : 'unrecognized'; + } catch { + return 'not_running'; + } +} diff --git a/packages/react-native/src/executors/start/schema.d.ts b/packages/react-native/src/executors/start/schema.d.ts new file mode 100644 index 0000000000000..4175cc66e5e0b --- /dev/null +++ b/packages/react-native/src/executors/start/schema.d.ts @@ -0,0 +1,4 @@ +export interface ReactNativeStartOptions { + port: number; + resetCache?: boolean; +} diff --git a/packages/react-native/src/executors/start/schema.json b/packages/react-native/src/executors/start/schema.json new file mode 100644 index 0000000000000..776548e76f9b1 --- /dev/null +++ b/packages/react-native/src/executors/start/schema.json @@ -0,0 +1,20 @@ +{ + "cli": "nx", + "$id": "NxReactNativeStart", + "$schema": "http://json-schema.org/schema", + "title": "Packager Server for React Native", + "description": "Packager Server target options", + "type": "object", + "properties": { + "port": { + "type": "number", + "description": "The port to listen on.", + "default": 8081 + }, + "resetCache": { + "type": "boolean", + "description": "Resets metro cache.", + "default": false + } + } +} diff --git a/packages/react-native/src/executors/start/start.impl.ts b/packages/react-native/src/executors/start/start.impl.ts new file mode 100644 index 0000000000000..4bdf8f8ab60b4 --- /dev/null +++ b/packages/react-native/src/executors/start/start.impl.ts @@ -0,0 +1,115 @@ +import * as chalk from 'chalk'; +import { ExecutorContext, logger } from '@nrwl/devkit'; +import { ChildProcess, fork } from 'child_process'; +import { join } from 'path'; +import { ensureNodeModulesSymlink } from '../../utils/ensure-node-modules-symlink'; +import { isPackagerRunning } from './lib/is-packager-running'; +import { ReactNativeStartOptions } from './schema'; + +export interface ReactNativeStartOutput { + baseUrl?: string; + success: boolean; +} + +let childProcess: ChildProcess; + +export default async function* startExecutor( + options: ReactNativeStartOptions, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + ensureNodeModulesSymlink(context.root, projectRoot); + + try { + const baseUrl = `http://localhost:${options.port}`; + const appName = context.projectName; + logger.info(chalk.cyan(`Packager is ready at ${baseUrl}`)); + logger.info( + `Use ${chalk.bold(`nx run-android ${appName}`)} or ${chalk.bold( + `nx run-ios ${appName}` + )} to run the native app.` + ); + + await runCliStart(context.root, projectRoot, options); + + yield { + baseUrl, + success: true, + }; + } finally { + if (childProcess) { + childProcess.kill(); + } + } +} + +/* + * Starts the JS bundler and checks for "running" status before notifying + * that packager has started. + */ +export async function runCliStart( + workspaceRoot: string, + projectRoot: string, + options: ReactNativeStartOptions +): Promise { + const result = await isPackagerRunning(options.port); + if (result === 'running') { + logger.info('JS server already running.'); + } else if (result === 'unrecognized') { + logger.warn('JS server not recognized.'); + } else { + // result === 'not_running' + logger.info('Starting JS server...'); + + try { + await startAsync(workspaceRoot, projectRoot, options); + } catch (error) { + logger.error( + `Failed to start the packager server. Error details: ${error.message}` + ); + throw error; + } + } +} + +function startAsync( + workspaceRoot: string, + projectRoot: string, + options: ReactNativeStartOptions +): Promise { + return new Promise((resolve, reject) => { + childProcess = fork( + join(workspaceRoot, './node_modules/react-native/cli.js'), + ['start', ...createStartOptions(options)], + { cwd: projectRoot } + ); + + // Ensure the child process is killed when the parent exits + process.on('exit', () => childProcess.kill()); + process.on('SIGTERM', () => childProcess.kill()); + + childProcess.on('error', (err) => { + reject(err); + }); + childProcess.on('exit', (code) => { + if (code === 0) { + resolve(code); + } else { + reject(code); + } + }); + }); +} + +function createStartOptions(options) { + return Object.keys(options).reduce((acc, k) => { + if (k === 'resetCache') { + if (options[k] === true) { + acc.push(`--reset-cache`); + } + } else { + acc.push(`--${k}`, options[k]); + } + return acc; + }, []); +} diff --git a/packages/react-native/src/executors/sync-deps/compat.ts b/packages/react-native/src/executors/sync-deps/compat.ts new file mode 100644 index 0000000000000..91634e4ee6cba --- /dev/null +++ b/packages/react-native/src/executors/sync-deps/compat.ts @@ -0,0 +1,5 @@ +import { convertNxExecutor } from '@nrwl/devkit'; + +import syncDepsExecutor from './sync-deps.impl'; + +export default convertNxExecutor(syncDepsExecutor); diff --git a/packages/react-native/src/executors/sync-deps/schema.d.ts b/packages/react-native/src/executors/sync-deps/schema.d.ts new file mode 100644 index 0000000000000..be1e6ae66759f --- /dev/null +++ b/packages/react-native/src/executors/sync-deps/schema.d.ts @@ -0,0 +1,3 @@ +export interface ReactNativeSyncDepsOptions { + include: string; +} diff --git a/packages/react-native/src/executors/sync-deps/schema.json b/packages/react-native/src/executors/sync-deps/schema.json new file mode 100644 index 0000000000000..bf7efb1518037 --- /dev/null +++ b/packages/react-native/src/executors/sync-deps/schema.json @@ -0,0 +1,14 @@ +{ + "cli": "nx", + "$id": "NxReactNativeSyncDeps", + "$schema": "http://json-schema.org/schema", + "title": "Sync Deps for React Native", + "description": "Updates package.json with project dependencies", + "type": "object", + "properties": { + "include": { + "type": "string", + "description": "A comma-separated list of additional npm packages to include. e.g. 'nx sync-deps --include=react-native-gesture-handler,react-native-safe-area-context'" + } + } +} diff --git a/packages/react-native/src/executors/sync-deps/sync-deps.impl.ts b/packages/react-native/src/executors/sync-deps/sync-deps.impl.ts new file mode 100644 index 0000000000000..4478bccfa3f38 --- /dev/null +++ b/packages/react-native/src/executors/sync-deps/sync-deps.impl.ts @@ -0,0 +1,82 @@ +import { join } from 'path'; +import { findAllNpmDependencies } from '../../utils/find-all-npm-dependencies'; +import { + readJsonFile, + writeJsonFile, +} from '@nrwl/workspace/src/utilities/fileutils'; +import * as chalk from 'chalk'; +import { ExecutorContext, logger } from '@nrwl/devkit'; +import { ReactNativeSyncDepsOptions } from './schema'; +import { createProjectGraphAsync } from '@nrwl/workspace/src/core/project-graph'; + +export interface ReactNativeSyncDepsOutput { + success: boolean; +} + +export default async function* syncDepsExecutor( + options: ReactNativeSyncDepsOptions, + context: ExecutorContext +): AsyncGenerator { + const projectRoot = context.workspace.projects[context.projectName].root; + displayNewlyAddedDepsMessage( + context.projectName, + await syncDeps(context.projectName, projectRoot, options.include) + ); + + yield { success: true }; +} + +export async function syncDeps( + projectName: string, + projectRoot: string, + include?: string +): Promise { + const graph = await createProjectGraphAsync(); + const npmDeps = findAllNpmDependencies(graph, projectName); + const packageJsonPath = join(projectRoot, 'package.json'); + const packageJson = readJsonFile(packageJsonPath); + const newDeps = []; + const includeDeps = include?.split(','); + let updated = false; + + if (!packageJson.dependencies) { + packageJson.dependencies = {}; + updated = true; + } + + if (includeDeps) { + npmDeps.push(...includeDeps); + } + + npmDeps.forEach((dep) => { + if (!packageJson.dependencies[dep]) { + packageJson.dependencies[dep] = '*'; + newDeps.push(dep); + updated = true; + } + }); + + if (updated) { + writeJsonFile(packageJsonPath, packageJson); + } + + return newDeps; +} + +export function displayNewlyAddedDepsMessage( + projectName: string, + deps: string[] +) { + if (deps.length > 0) { + logger.info(`${chalk.bold.cyan( + 'info' + )} Added entries to 'package.json' for '${projectName}' (for autolink): + ${deps.map((d) => chalk.bold.cyan(`"${d}": "*"`)).join('\n ')}`); + } else { + logger.info( + `${chalk.bold.cyan( + 'info' + )} Dependencies for '${projectName}' are up to date! No changes made.` + ); + } +} diff --git a/packages/react-native/src/generators/application/application.spec.ts b/packages/react-native/src/generators/application/application.spec.ts new file mode 100644 index 0000000000000..90a192a1bb911 --- /dev/null +++ b/packages/react-native/src/generators/application/application.spec.ts @@ -0,0 +1,68 @@ +import { + Tree, + readWorkspaceConfiguration, + getProjects, + readJson, +} from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import { reactNativeApplicationGenerator } from './application'; + +describe('app', () => { + let appTree: Tree; + + beforeEach(() => { + appTree = createTreeWithEmptyWorkspace(); + appTree.write('.gitignore', ''); + }); + + it('should update workspace.json', async () => { + await reactNativeApplicationGenerator(appTree, { + name: 'myApp', + displayName: 'myApp', + linter: Linter.EsLint, + e2eTestRunner: 'none', + }); + const workspaceJson = readWorkspaceConfiguration(appTree); + const projects = getProjects(appTree); + + expect(projects.get('my-app').root).toEqual('apps/my-app'); + expect(workspaceJson.defaultProject).toEqual('my-app'); + }); + + it('should update nx.json', async () => { + await reactNativeApplicationGenerator(appTree, { + name: 'myApp', + displayName: 'myApp', + tags: 'one,two', + linter: Linter.EsLint, + e2eTestRunner: 'none', + }); + + const nxJson = readJson(appTree, '/nx.json'); + expect(nxJson).toMatchObject({ + npmScope: 'proj', + projects: { + 'my-app': { + tags: ['one', 'two'], + }, + }, + }); + }); + + it('should generate files', async () => { + await reactNativeApplicationGenerator(appTree, { + name: 'myApp', + displayName: 'myApp', + linter: Linter.EsLint, + e2eTestRunner: 'none', + }); + expect(appTree.exists('apps/my-app/src/app/App.tsx')).toBeTruthy(); + expect(appTree.exists('apps/my-app/src/main.tsx')).toBeTruthy(); + + const tsconfig = readJson(appTree, 'apps/my-app/tsconfig.json'); + expect(tsconfig.extends).toEqual('../../tsconfig.base.json'); + + expect(appTree.exists('apps/my-app/.eslintrc.json')).toBe(true); + }); +}); diff --git a/packages/react-native/src/generators/application/application.ts b/packages/react-native/src/generators/application/application.ts new file mode 100644 index 0000000000000..0e0502711fcf8 --- /dev/null +++ b/packages/react-native/src/generators/application/application.ts @@ -0,0 +1,77 @@ +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { Schema } from './schema'; +import { runPodInstall } from '../../utils/pod-install-task'; +import { runChmod } from '../../utils/chmod-task'; +import { runSymlink } from '../../utils/symlink-task'; +import { addLinting } from '../../utils/add-linting'; +import { addJest } from '../../utils/add-jest'; +import { + convertNxGenerator, + Tree, + formatFiles, + joinPathFragments, + GeneratorCallback, +} from '@nrwl/devkit'; +import { normalizeOptions } from './lib/normalize-options'; +import initGenerator from '../init/init'; +import { join } from 'path'; +import { addProject } from './lib/add-project'; +import { createApplicationFiles } from './lib/create-application-files'; +import { addDetox } from './lib/add-detox'; + +export async function reactNativeApplicationGenerator( + host: Tree, + schema: Schema +): Promise { + const options = normalizeOptions(host, schema); + + createApplicationFiles(host, options); + addProject(host, options); + + const initTask = await initGenerator(host, { ...options, skipFormat: true }); + const lintTask = await addLinting( + host, + options.projectName, + options.appProjectRoot, + [joinPathFragments(options.appProjectRoot, 'tsconfig.app.json')], + options.linter, + options.setParserOptionsProject + ); + const jestTask = await addJest( + host, + options.unitTestRunner, + options.projectName, + options.appProjectRoot + ); + const detoxTask = await addDetox(host, options); + const symlinkTask = runSymlink(options.appProjectRoot); + const podInstallTask = runPodInstall(options.iosProjectRoot); + const chmodTaskGradlew = runChmod( + join(options.androidProjectRoot, 'gradlew'), + 0o775 + ); + const chmodTaskGradlewBat = runChmod( + join(options.androidProjectRoot, 'gradlew.bat'), + 0o775 + ); + + if (!options.skipFormat) { + await formatFiles(host); + } + + return runTasksInSerial( + initTask, + lintTask, + jestTask, + detoxTask, + symlinkTask, + podInstallTask, + chmodTaskGradlew, + chmodTaskGradlewBat + ); +} + +export default reactNativeApplicationGenerator; +export const reactNativeApplicationSchematic = convertNxGenerator( + reactNativeApplicationGenerator +); diff --git a/packages/react-native/src/generators/application/files/app/.babelrc.template b/packages/react-native/src/generators/application/files/app/.babelrc.template new file mode 100644 index 0000000000000..d4b74b5be7b43 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/.babelrc.template @@ -0,0 +1,3 @@ +{ + "presets": ["module:metro-react-native-babel-preset"] +} diff --git a/packages/react-native/src/generators/application/files/app/android/app/_BUCK.template b/packages/react-native/src/generators/application/files/app/android/app/_BUCK.template new file mode 100644 index 0000000000000..9359ab06b6661 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/_BUCK.template @@ -0,0 +1,55 @@ +# To learn about Buck see [Docs](https://buckbuild.com/). +# To run your application with Buck: +# - install Buck +# - `npm start` - to start the packager +# - `cd android` +# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` +# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck +# - `buck install -r android/app` - compile, install and run application +# + +load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") + +lib_deps = [] + +create_aar_targets(glob(["libs/*.aar"])) + +create_jar_targets(glob(["libs/*.jar"])) + +android_library( + name = "all-libs", + exported_deps = lib_deps, +) + +android_library( + name = "app-code", + srcs = glob([ + "src/main/java/**/*.java", + ]), + deps = [ + ":all-libs", + ":build_config", + ":res", + ], +) + +android_build_config( + name = "build_config", + package = "com.<%= lowerCaseName %>", +) + +android_resource( + name = "res", + package = "com.<%= lowerCaseName %>", + res = "src/main/res", +) + +android_binary( + name = "app", + keystore = "//android/keystores:debug", + manifest = "src/main/AndroidManifest.xml", + package_type = "debug", + deps = [ + ":app-code", + ], +) diff --git a/packages/react-native/src/generators/application/files/app/android/app/build.gradle.template b/packages/react-native/src/generators/application/files/app/android/app/build.gradle.template new file mode 100644 index 0000000000000..2c8ecdffb08b6 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/build.gradle.template @@ -0,0 +1,236 @@ +apply plugin: "com.android.application" + +import com.android.build.OutputFile + +/** + * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets + * and bundleReleaseJsAndAssets). + * These basically call `react-native bundle` with the correct arguments during the Android build + * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the + * bundle directly from the development server. Below you can see all the possible configurations + * and their defaults. If you decide to add a configuration block, make sure to add it before the + * `apply from: "../../node_modules/react-native/react.gradle"` line. + * + * project.ext.react = [ + * // the name of the generated asset file containing your JS bundle + * bundleAssetName: "index.android.bundle", + * + * // the entry file for bundle generation. If none specified and + * // "index.android.js" exists, it will be used. Otherwise "index.js" is + * // default. Can be overridden with ENTRY_FILE environment variable. + * entryFile: "index.android.js", + * + * // https://facebook.github.io/react-native/docs/performance#enable-the-ram-format + * bundleCommand: "ram-bundle", + * + * // whether to bundle JS and assets in debug mode + * bundleInDebug: false, + * + * // whether to bundle JS and assets in release mode + * bundleInRelease: true, + * + * // whether to bundle JS and assets in another build variant (if configured). + * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants + * // The configuration property can be in the following formats + * // 'bundleIn${productFlavor}${buildType}' + * // 'bundleIn${buildType}' + * // bundleInFreeDebug: true, + * // bundleInPaidRelease: true, + * // bundleInBeta: true, + * + * // whether to disable dev mode in custom build variants (by default only disabled in release) + * // for example: to disable dev mode in the staging build type (if configured) + * devDisabledInStaging: true, + * // The configuration property can be in the following formats + * // 'devDisabledIn${productFlavor}${buildType}' + * // 'devDisabledIn${buildType}' + * + * // the root of your project, i.e. where "package.json" lives + * root: "../../", + * + * // where to put the JS bundle asset in debug mode + * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", + * + * // where to put the JS bundle asset in release mode + * jsBundleDirRelease: "$buildDir/intermediates/assets/release", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in debug mode + * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", + * + * // where to put drawable resources / React Native assets, e.g. the ones you use via + * // require('./image.png')), in release mode + * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", + * + * // by default the gradle tasks are skipped if none of the JS files or assets change; this means + * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to + * // date; if you have any other folders that you want to ignore for performance reasons (gradle + * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ + * // for example, you might want to remove it from here. + * inputExcludes: ["android/**", "ios/**"], + * + * // override which node gets called and with what additional arguments + * nodeExecutableAndArgs: ["node"], + * + * // supply additional arguments to the packager + * extraPackagerArgs: [] + * ] + */ + +project.ext.react = [ + entryFile: "<%= appProjectRoot %>/src/main.tsx", + enableHermes: false, // clean and rebuild if changing +] + +apply from: "../../node_modules/react-native/react.gradle" + +/** + * Set this to true to create two separate APKs instead of one: + * - An APK that only works on ARM devices + * - An APK that only works on x86 devices + * The advantage is the size of the APK is reduced by about 4MB. + * Upload all the APKs to the Play Store and people will download + * the correct one based on the CPU architecture of their device. + */ +def enableSeparateBuildPerCPUArchitecture = false + +/** + * Run Proguard to shrink the Java bytecode in release builds. + */ +def enableProguardInReleaseBuilds = false + +/** + * The preferred build flavor of JavaScriptCore. + * + * For example, to use the international variant, you can use: + * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'org.webkit:android-jsc:+' + +/** + * Whether to enable the Hermes VM. + * + * This should be set on project.ext.react and mirrored here. If it is not set + * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode + * and the benefits of using Hermes will therefore be sharply reduced. + */ +def enableHermes = project.ext.react.get("enableHermes", false); + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + + defaultConfig { + applicationId "com.<%= lowerCaseName %>" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + <% if (e2eTestRunner === 'detox') { %> + testBuildType System.getProperty('testBuildType', 'debug') // This will later be used to control the test apk build type + testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' + <% } %> + } + splits { + abi { + reset() + enable enableSeparateBuildPerCPUArchitecture + universalApk false // If true, also generate a universal APK + include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" + } + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + // In production, you need to generate your own keystore file. + // See: https://reactnative.dev/docs/signed-apk-android + // release { + // if (project.hasProperty('MYAPP_UPLOAD_STORE_FILE')) { + // storeFile file(MYAPP_UPLOAD_STORE_FILE) + // storePassword MYAPP_UPLOAD_STORE_PASSWORD + // keyAlias MYAPP_UPLOAD_KEY_ALIAS + // keyPassword MYAPP_UPLOAD_KEY_PASSWORD + // } + // } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + // Caution! In production, you need to generate your own keystore file. + // See: https://reactnative.dev/docs/signed-apk-android + signingConfig signingConfigs.debug + // signingConfig signingConfigs.release + minifyEnabled enableProguardInReleaseBuilds + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + + packagingOptions { + pickFirst "lib/armeabi-v7a/libc++_shared.so" + pickFirst "lib/arm64-v8a/libc++_shared.so" + pickFirst "lib/x86/libc++_shared.so" + pickFirst "lib/x86_64/libc++_shared.so" + } + + // applicationVariants are e.g. debug, release + applicationVariants.all { variant -> + variant.outputs.each { output -> + // For each separate APK per architecture, set a unique version code as described here: + // https://developer.android.com/studio/build/configure-apk-splits.html + def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] + def abi = output.getFilter(OutputFile.ABI) + if (abi != null) { // null for the universal-debug, universal-release variants + output.versionCodeOverride = + versionCodes.get(abi) * 1048576 + defaultConfig.versionCode + } + + } + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" // From node_modules + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" + debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { + exclude group:'com.facebook.fbjni' + } + debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + } + debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { + exclude group:'com.facebook.flipper' + } + + if (enableHermes) { + def hermesPath = "../../node_modules/hermes-engine/android/"; + debugImplementation files(hermesPath + "hermes-debug.aar") + releaseImplementation files(hermesPath + "hermes-release.aar") + } else { + implementation jscFlavor + } + + <% if (e2eTestRunner === 'detox') { %> + androidTestImplementation('com.wix:detox:+') + <% } %> +} + +// Run this once to be able to run the application with BUCK +// puts all compile dependencies into folder libs for BUCK to use +task copyDownloadableDepsToLibs(type: Copy) { + from configurations.compile + into 'libs' +} + +apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) diff --git a/packages/react-native/src/generators/application/files/app/android/app/build_defs.bzl b/packages/react-native/src/generators/application/files/app/android/app/build_defs.bzl new file mode 100644 index 0000000000000..fff270f8d1d48 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/build_defs.bzl @@ -0,0 +1,19 @@ +"""Helper definitions to glob .aar and .jar targets""" + +def create_aar_targets(aarfiles): + for aarfile in aarfiles: + name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] + lib_deps.append(":" + name) + android_prebuilt_aar( + name = name, + aar = aarfile, + ) + +def create_jar_targets(jarfiles): + for jarfile in jarfiles: + name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] + lib_deps.append(":" + name) + prebuilt_jar( + name = name, + binary_jar = jarfile, + ) diff --git a/packages/react-native/src/generators/application/files/app/android/app/debug.keystore b/packages/react-native/src/generators/application/files/app/android/app/debug.keystore new file mode 100644 index 0000000000000000000000000000000000000000..364e105ed39fbfd62001429a68140672b06ec0de GIT binary patch literal 2257 zcmchYXEfYt8;7T1^dLH$VOTZ%2NOdOH5j5LYLtZ0q7x-V8_6gU5)#7dkq{HTmsfNq zB3ZqcAxeY^G10@?efK?Q&)M(qInVv!xjx+IKEL}p*K@LYvIzo#AZG>st5|P)KF1_Z;y){W{<7K{nl!CPuE z_^(!C(Ol0n8 zK13*rzAtW>(wULKPRYLd7G18F8#1P`V*9`(Poj26eOXYyBVZPno~Cvvhx7vPjAuZo zF?VD!zB~QG(!zbw#qsxT8%BSpqMZ4f70ZPn-3y$L8{EVbbN9$H`B&Z1quk9tgp5FM zuxp3pJ0b8u|3+#5bkJ4SRnCF2l7#DyLYXYY8*?OuAwK4E6J{0N=O3QNVzQ$L#FKkR zi-c@&!nDvezOV$i$Lr}iF$XEcwnybQ6WZrMKuw8gCL^U#D;q3t&HpTbqyD%vG=TeDlzCT~MXUPC|Leb-Uk+ z=vnMd(|>ld?Fh>V8poP;q;;nc@en$|rnP0ytzD&fFkCeUE^kG9Kx4wUh!!rpjwKDP zyw_e|a^x_w3E zP}}@$g>*LLJ4i0`Gx)qltL}@;mDv}D*xR^oeWcWdPkW@Uu)B^X&4W1$p6}ze!zudJ zyiLg@uggoMIArBr*27EZV7djDg@W1MaL+rcZ-lrANJQ%%>u8)ZMWU@R2qtnmG(acP z0d_^!t>}5W zpT`*2NR+0+SpTHb+6Js4b;%LJB;B_-ChhnU5py}iJtku*hm5F0!iql8Hrpcy1aYbT z1*dKC5ua6pMX@@iONI?Hpr%h;&YaXp9n!ND7-=a%BD7v&g zOO41M6EbE24mJ#S$Ui0-brR5ML%@|ndz^)YLMMV1atna{Fw<;TF@>d&F|!Z>8eg>>hkFrV)W+uv=`^F9^e zzzM2*oOjT9%gLoub%(R57p-`TXFe#oh1_{&N-YN z<}artH|m=d8TQuKSWE)Z%puU|g|^^NFwC#N=@dPhasyYjoy(fdEVfKR@cXKHZV-`06HsP`|Ftx;8(YD$fFXumLWbGnu$GMqRncXYY9mwz9$ap zQtfZB^_BeNYITh^hA7+(XNFox5WMeG_LtJ%*Q}$8VKDI_p8^pqX)}NMb`0e|wgF7D zuQACY_Ua<1ri{;Jwt@_1sW9zzdgnyh_O#8y+C;LcZq6=4e^cs6KvmK@$vVpKFGbQ= z$)Eux5C|Fx;Gtmv9^#Y-g@7Rt7*eLp5n!gJmn7&B_L$G?NCN`AP>cXQEz}%F%K;vUs{+l4Q{}eWW;ATe2 zqvXzxoIDy(u;F2q1JH7Sf;{jy_j})F+cKlIOmNfjBGHoG^CN zM|Ho&&X|L-36f}Q-obEACz`sI%2f&k>z5c$2TyTSj~vmO)BW~+N^kt`Jt@R|s!){H ze1_eCrlNaPkJQhL$WG&iRvF*YG=gXd1IyYQ9ew|iYn7r~g!wOnw;@n42>enAxBv*A zEmV*N#sxdicyNM=A4|yaOC5MByts}s_Hpfj|y<6G=o=!3S@eIFKDdpR7|FY>L&Wat&oW&cm&X~ z5Bt>Fcq(fgnvlvLSYg&o6>&fY`ODg4`V^lWWD=%oJ#Kbad2u~! zLECFS*??>|vDsNR&pH=Ze0Eo`sC_G`OjoEKVHY|wmwlX&(XBE<@sx3Hd^gtd-fNwUHsylg06p`U2y_={u}Bc; + +import com.wix.detox.Detox; +import com.wix.detox.config.DetoxConfig; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +import androidx.test.rule.ActivityTestRule; + +@RunWith(AndroidJUnit4.class) +@LargeTest +public class DetoxTest { + // Replace 'MainActivity' with the value of android:name entry in + // in AndroidManifest.xml + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); + + @Test + public void runDetoxTests() { + // This is optional - in case you've decided to integrate TestButler + // See https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md#8-test-butler-support-optional + // TestButlerProbe.assertReadyIfInstalled(); + + DetoxConfig detoxConfig = new DetoxConfig(); + detoxConfig.idlePolicyConfig.masterTimeoutSec = 90; + detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 60; + detoxConfig.rnContextLoadTimeoutSec = (com.<%= lowerCaseName %>.BuildConfig.DEBUG ? 180 : 60); + + Detox.runTests(mActivityRule, detoxConfig); + } +} \ No newline at end of file diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/debug/AndroidManifest.xml.template b/packages/react-native/src/generators/application/files/app/android/app/src/debug/AndroidManifest.xml.template new file mode 100644 index 0000000000000..b2f3ad9fce923 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/debug/AndroidManifest.xml.template @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/debug/java/com/__lowerCaseName__/ReactNativeFlipper.java.template b/packages/react-native/src/generators/application/files/app/android/app/src/debug/java/com/__lowerCaseName__/ReactNativeFlipper.java.template new file mode 100644 index 0000000000000..cb0a2f1b3a551 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/debug/java/com/__lowerCaseName__/ReactNativeFlipper.java.template @@ -0,0 +1,72 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + *

This source code is licensed under the MIT license found in the LICENSE file in the root + * directory of this source tree. + */ +package com.<%= lowerCaseName %>; + +import android.content.Context; +import com.facebook.flipper.android.AndroidFlipperClient; +import com.facebook.flipper.android.utils.FlipperUtils; +import com.facebook.flipper.core.FlipperClient; +import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; +import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; +import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; +import com.facebook.flipper.plugins.inspector.DescriptorMapping; +import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; +import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; +import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; +import com.facebook.flipper.plugins.react.ReactFlipperPlugin; +import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.modules.network.NetworkingModule; +import okhttp3.OkHttpClient; + +public class ReactNativeFlipper { + public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { + if (FlipperUtils.shouldEnableFlipper(context)) { + final FlipperClient client = AndroidFlipperClient.getInstance(context); + + client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); + client.addPlugin(new ReactFlipperPlugin()); + client.addPlugin(new DatabasesFlipperPlugin(context)); + client.addPlugin(new SharedPreferencesFlipperPlugin(context)); + client.addPlugin(CrashReporterPlugin.getInstance()); + + NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); + NetworkingModule.setCustomClientBuilder( + new NetworkingModule.CustomClientBuilder() { + @Override + public void apply(OkHttpClient.Builder builder) { + builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); + } + }); + client.addPlugin(networkFlipperPlugin); + client.start(); + + // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized + // Hence we run if after all native modules have been initialized + ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); + if (reactContext == null) { + reactInstanceManager.addReactInstanceEventListener( + new ReactInstanceManager.ReactInstanceEventListener() { + @Override + public void onReactContextInitialized(ReactContext reactContext) { + reactInstanceManager.removeReactInstanceEventListener(this); + reactContext.runOnNativeModulesQueueThread( + new Runnable() { + @Override + public void run() { + client.addPlugin(new FrescoFlipperPlugin()); + } + }); + } + }); + } else { + client.addPlugin(new FrescoFlipperPlugin()); + } + } + } +} diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/AndroidManifest.xml.template b/packages/react-native/src/generators/application/files/app/android/app/src/main/AndroidManifest.xml.template new file mode 100644 index 0000000000000..6d6608836ad40 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/AndroidManifest.xml.template @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + > + + + + + + + + diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainActivity.java.template b/packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainActivity.java.template new file mode 100644 index 0000000000000..17b6581101b9b --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainActivity.java.template @@ -0,0 +1,15 @@ +package com.<%= lowerCaseName %>; + +import com.facebook.react.ReactActivity; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "main"; + } +} diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainApplication.java.template b/packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainApplication.java.template new file mode 100644 index 0000000000000..252db0e7613e0 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/java/com/__lowerCaseName__/MainApplication.java.template @@ -0,0 +1,80 @@ +package com.<%= lowerCaseName %>; + +import android.app.Application; +import android.content.Context; +import com.facebook.react.PackageList; +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.soloader.SoLoader; +import java.lang.reflect.InvocationTargetException; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = + new ReactNativeHost(this) { + @Override + public boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + @SuppressWarnings("UnnecessaryLocalVariable") + List packages = new PackageList(this).getPackages(); + // Packages that cannot be autolinked yet can be added manually here, for example: + // packages.add(new MyReactNativePackage()); + return packages; + } + + @Override + protected String getJSMainModuleName() { + return "<%= appProjectRoot %>/src/main"; + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } + + @Override + public void onCreate() { + super.onCreate(); + SoLoader.init(this, /* native exopackage */ false); + initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); + } + + /** + * Loads Flipper in React Native templates. Call this in the onCreate method with something like + * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); + * + * @param context + * @param reactInstanceManager + */ + private static void initializeFlipper( + Context context, ReactInstanceManager reactInstanceManager) { + if (BuildConfig.DEBUG) { + try { + /* + We use reflection here to pick up the class that initializes Flipper, + since Flipper library is not available in release mode + */ + Class aClass = Class.forName("com.<%= lowerCaseName %>.ReactNativeFlipper"); + aClass + .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) + .invoke(null, context, reactInstanceManager); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } + } +} diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/drawable/splashscreen.xml b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/drawable/splashscreen.xml new file mode 100644 index 0000000000000..9f19c56e5410d --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/drawable/splashscreen.xml @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/drawable/splashscreen_image.png b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/drawable/splashscreen_image.png new file mode 100644 index 0000000000000000000000000000000000000000..cc94f379de325e1292ad7843f958a74fb7547d76 GIT binary patch literal 9306 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 literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a2f5908281d070150700378b64a84c7db1f97aa1 GIT binary patch literal 3056 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 axPAxe%7rDusX+w*00006jj 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⁣ zwiG+x#%j}{0gU~J8;<|!B1@-XaB@{KORFwrYg_8rOv({b0EO#DbeQRm;B6_9=mXGf z-x|VL{zd`)#@yN}HkCSJbjbNlE|zL3Wm9Q8HY`sV)}3%pgN>cL^67{Z;PPL(*wT8N zUjXU{@|*hvm}({wsAC=x0^ok0%UAz0;sogW{B!nDqk|JJ5x~4NfTDgP49^zeu`csl?5mY@JdQdISc zFs!E{^grmkLnUk9 zny~m)1vws@5BFI<-0Tuo2JWX(0v`W|t(wg;s--L47WTvTMz-8l#TL^=OJNRS2?_Qj z3AKT+gvbyBi#H*-tJ%tWD|>EV3wy|8qxfzS!5RW;Jpl5*zo&^UBU=fG#2}UvRyNkK zA06Dy9;K1ca@r2T>yThYgI!ont$(G{6q#2QT+00r_x0(b)gsE`lBB?2gr55gq^D3Fi&p%E(p9>U%bv zkg1Jco(RbyTX7FDHOnl7-O@ zI$AaIl?9NJKPm(WiBP`1-#CB1QzU>&hKm)fpa5DKE{2$X0hGz-0uZ?cyTk(YC!Y&| zL=1VrNERSA5NA2jq7FACfX4JfPyj5XXl1yv0>~s;eF7L2$>&oMqeTFT2m$y7FlkON z_yurD1yIOvA;5C6016pyxBznGUt0kJ&k5r#;&>Jow`r)sp9R~PmK~lz$3xH%LT*1U zJdOyABZ3!FvNoR*vN$5ykHS8f`jA4zV+|L}i1C4`B2c{R0;UdYxaU|H)2avz@ z=mEYc|2S<+(B2Tj+FkX+2D+yFI!k9lWMA61DJ{)e;lum$(;O87?vGJJe!KtK04+N_ zI*P~t@dUb>9Xh{dbyl{-ZQ(UMgz7$|QfL5XSPkskt^NgctYC#;4WcZB1@%@wy@2t3 z2z0DI7&%b$*Aw~abe?GxE`ez@+6hOh-6*8fHRV{1os$EL@}uUZeG4h1&Be`98q*7j z=3-v+lhIjfWVo12!<>%V^a6lTgW3+_#W6n|p*~==zOH7z$0{LSZk(Tpd7EaD04hnA zL;#fxS0aD{`5^&D`}>0Uq?byDD-l2=!wm_bLcUl4gc(% za1p|itVANvFF>hghAS07Im1;IK;|b*W)}VDyI;BIp2=K*yu2a)j?B|f<44NI$NbmJ z#dE0>jI$fMr&@>4kN8MLFb4&2O9fEKaQg%(QO$4_1rVQywG^CmBLh#}_7gKW3vd?| z2?1^&KWq8}8I^_S0|)MowU_pw$q@nl@Nkn$z>BQq_KA^9yaR`(R3u{{Ig;cwt z@AJ^{ODQCm^neroM9nKNUAXi9RCK`OsP_LuR0PUR(YZCCX5dNF6VzcoK&=b^r`W?ltt|*F zpkoae%ZT{C1h~EcFui~b7fF`vb<<~j_VquuUA$}QqIKYELPp#;{u?q8Dz}WAG-(3; zjrm$i%7UbyZMM(Y{>!uJ#vNB?R~B{6Htp=>e*<{fQQ5W7V(1coCWlOON!MzZxhum| ztZBQpGR z;~#ur^&PockKdV{Q6R>o`Pl{0x!DEbpZ7y9Y;*ZvE!*gU`V1W3znva{f=?WO5I&>B z&hw6}tjECtaghm5z|C#%M;Yf_*pI^};h}Vl=^r9EN=tVDj86D;C$jIJ?K7VP+00000NkvXXu0mjf D5i!M* literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..459ca609d3ae0d3943ab44cdc27feef9256dc6d7 GIT binary patch literal 7098 zcmV;r8%5-aP)U(QdAI7f)tS=AhH53iU?Q%B}x&gA$2B`o|*LCD1jhW zSQpS0{*?u3iXtkY?&2<)$@#zc%$?qDlF1T~d7k&lWaiv^&wbx>zVm(GIrof<%iY)A zm%|rhEg~Z$Te<*wd9Cb1SB{RkOI$-=MBtc%k*xtvYC~Uito}R@3fRUqJvco z|Bt2r9pSOcJocAEd)UN^Tz-82GUZlqsU;wb|2Q_1!4Rms&HO1Xyquft~#6lJoR z`$|}VSy@{k6U652FJ~bnD9(X%>CS6Wp6U>sn;f}te}%WL`rg)qE4Q=4OOhk^@ykw( ziKr^LHnAd4M?#&SQhw8zaC05q#Mc66K^mxY!dZ=W+#Bq1B}cQ6Y8FWd(n>#%{8Di_8$CHibtvP z-x#-g;~Q?y0vJA*8TW>ZxF?fAy1DuFy7%O1ylLF(t=ah7LjZ$=p!;8(ZLjXAhwEkCR{wF`L=hwm>|vLK2=gR&KM1ZEG9R~53yNCZdabQoQ%VsolX zS#WlesPcpJ)7XLo6>Ly$im38oxyiizP&&>***e@KqUk3q3y+LQN^-v?ZmO>9O{Oq@ z{{He$*Z=Kf_FPR>El3iB*FULYFMnLa#Fl^l&|bFg$Omlh{xVVJ7uHm=4WE6)NflH6 z=>z4w{GV&8#MNnEY3*B7pXU!$9v-tZvdjO}9O=9r{3Wxq2QB}(n%%YI$)pS~NEd}U z)n#nv-V)K}kz9M0$hogDLsa<(OS0Hf5^WUKO-%WbR1W1ID$NpAegxHH;em?U$Eyn1 zU{&J2@WqSUn0tav=jR&&taR9XbV+Izb*PwFn|?cv0mksBdOWeGxNb~oR;`~>#w3bp zrOrEQ+BiW_*f&GARyW|nE}~oh0R>>AOH^>NHNKe%%sXLgWRu1Sy3yW0Q#L{8Y6=3d zKd=By=Nb8?#W6|LrpZm>8Ro)`@cLmU;D`d64nKT~6Z!aLOS{m`@oYwD`9yily@}%yr0A>P!6O4G|ImNbBzI`LJ0@=TfLt^f`M07vw_PvXvN{nx%4 zD8vS>8*2N}`lD>M{`v?2!nYnf%+`GRK3`_i+yq#1a1Yx~_1o~-$2@{=r~q11r0oR* zqBhFFVZFx!U0!2CcItqLs)C;|hZ|9zt3k^(2g32!KB-|(RhKbq-vh|uT>jT@tX8dN zH`TT5iytrZT#&8u=9qt=oV`NjC)2gWl%KJ;n63WwAe%-)iz&bK{k`lTSAP`hr)H$Q`Yq8-A4PBBuP*-G#hSKrnmduy6}G zrc+mcVrrxM0WZ__Y#*1$mVa2y=2I`TQ%3Vhk&=y!-?<4~iq8`XxeRG!q?@l&cG8;X zQ(qH=@6{T$$qk~l?Z0@I4HGeTG?fWL67KN#-&&CWpW0fUm}{sBGUm)Xe#=*#W{h_i zohQ=S{=n3jDc1b{h6oTy=gI!(N%ni~O$!nBUig}9u1b^uI8SJ9GS7L#s!j;Xy*CO>N(o6z){ND5WTew%1lr? znp&*SAdJb5{L}y7q#NHbY;N_1vn!a^3TGRzCKjw?i_%$0d2%AR73CwHf z`h4QFmE-7G=psYnw)B!_Cw^{=!UNZeR{(s47|V$`3;-*gneX=;O+eN@+Efd_Zt=@H3T@v&o^%H z7QgDF8g>X~$4t9pv35G{a_8Io>#>uGRHV{2PSk#Ea~^V8!n@9C)ZH#87~ z#{~PUaRR~4K*m4*PI16)rvzdaP|7sE8SyMQYI6!t(%JNebR%?lc$={$s?VBI0Qk!A zvrE4|#asTZA|5tB{>!7BcxOezR?QIo4U_LU?&9Im-liGSc|TrJ>;1=;W?gG)0pQaw z|6o7&I&PH!*Z=c7pNPkp)1(4W`9Z01*QKv44FkvF^2Kdz3gDNpV=A6R;Q}~V-_sZY zB9DB)F8%iFEjK?Gf4$Cwu_hA$98&pkrJM!7{l+}osR_aU2PEx!1CRCKsS`0v$LlKq z{Pg#ZeoBMv@6BcmK$-*|S9nv50or*2&EV`L7PfW$2J7R1!9Q(1SSe42eSWZ5sYU?g z2v{_QB^^jfh$)L?+|M`u-E7D=Hb?7@9O89!bRUSI7uD?Mxh63j5!4e(v)Kc&TUEqy z8;f`#(hwrIeW);FA0CK%YHz6;(WfJz^<&W#y0N3O2&Qh_yxHu?*8z1y9Ua}rECL!5 z7L1AEXx83h^}+)cY*Ko{`^0g3GtTuMP>b$kq;Aqo+2d&+48mc#DP;Sv z*UL^nR*K7J968xR0_eTaZ`N`u_c#9bFUjTj-}0+_57(gtEJT|7PA12W=2Z>#_a z&Wg@_b=$d~wonN3h~?)gS`qxx<4J&`dI*rH9!mTSiQj(0rF-{YoNJRnOqd5IbP7p} ztDaPu$A;#osxf=z2zVe4>tpa(knS_Mp67nKcE<>Cj$G2orP(Z$Oc4;4DPwbXYZsS^ z;b>59s(LgYmx|tkRD?U{+9VZ$T}{S}L6>lQNR^a|&5joAFXtOrI07Do!vk(e$mu@Y zNdN!djB`Hq1*T8mrC@S)MLwZ`&8aM8YYtVj7i)IY{g&D1sJaY`3e=1DSFnjO+jEHH zj+|@r$$4RtpuJ!8=C`n5X;5BjU2slP9VV&m0gr+{O(I}9pYF32AMU?n$k$=x;X^E# zOb-x}p1_`@IOXAj3>HFxnmvBV9M^^9CfD7UlfuH*y^aOD?X6D82p_r*c>DF)m=9>o zgv_SDeSF6WkoVOI<_mX};FlW9rk3WgQP|vr-eVo8!wH!TiX)aiw+I|dBWJX=H6zxx z_tSI2$ChOM+?XlJwEz3!juYU6Z_b+vP-Y|m1!|ahw>Kpjrii-M_wmO@f@7;aK(I;p zqWgn+X^onc-*f)V9Vfu?AHLHHK!p2|M`R&@4H0x4hD5#l1##Plb8KsgqGZ{`d+1Ns zQ7N(V#t49wYIm9drzw`;WSa|+W+VW8Zbbx*Z+aXHSoa!c!@3F_yVww58NPH2->~Ls z2++`lSrKF(rBZLZ5_ts6_LbZG-W-3fDq^qI>|rzbc@21?)H>!?7O*!D?dKlL z6J@yulp7;Yk6Bdytq*J1JaR1!pXZz4aXQ{qfLu0;TyPWebr3|*EzCk5%ImpjUI4cP z7A$bJvo4(n2km-2JTfRKBjI9$mnJG@)LjjE9dnG&O=S;fC)@nq9K&eUHAL%yAPX7OFuD$pb_H9nhd{iE0OiI4#F-);A|&YT z|A3tvFLfR`5NYUkE?Rfr&PyUeFX-VHzcss2i*w06vn4{k1R%1_1+Ygx2oFt*HwfT> zd=PFdfFtrP1+YRs0AVr{YVp4Bnw2HQX-|P$M^9&P7pY6XSC-8;O2Ia4c{=t{NRD=z z0DeYUO3n;p%k zNEmBntbNac&5o#&fkY1QSYA4tKqBb=w~c6yktzjyk_Po)A|?nn8>HdA31amaOf7jX z2qillM8t8V#qv5>19Cg_X`mlU*O5|C#X-kfAXAHAD*q%6+z%IK(*H6olm-N4%Ic)5 zL`?wQgXfD&qQRxWskoO^Ylb>`jelq;*~ZIwKw|#BQjOSLkgc2uy7|oFEVhC?pcnU+ z^7qz}Z2%F!WOp%JO3y*&_7t;uRfU>)drR1q)c7lX?;A1-TuLTR zyr(`7O19`eW{ev;L%`;BvOzh?m|)Rh?W8&I$KVvUTo?@f@K!du&vf=o6kKb?hA z%e6$T0jWS7doVkN%^_k3QOksfV?aC$Ge$a)z(!C@UVs*@qzDw*OFd*JfX#>5LCXjE z_vfUrLF7D`K$U2Ld#OCnh9U!;r7%GlKo$e__Il-oba06ER{H&f#J&W@x^^5j;y$0` zs2`m6pf+{UiDb{Mjsb$rH+MCM6G_wX92so96`ODFYKD>!Xz^0y@U7Tc1uON4L<>2f-oPe%FRPEZ@S#-yd7Md-i?v z)$Kgtq;%4g@>Kap3Nl2I&jnCIfGmRmcF4CXfF1H}3SfhLg8=!a0ucGaUk&c3*Ykgl z2X_L84cs+FD#cjf-nMJkVDH%XzOoh5!X-Q$K5VZx-hGF7MQ=XKBjhZZQ@1Sh zO^vY`WQ`zi21z-+01na%<^niMFIWm-n|!?hm4X2HEHkba4YS|+HRoIR=`#Xck@PFXaPjnP z=hC4A*0lumS+gpK=TUN!G;{WqICbMz-V=-lTP^@a#C|E!qH;T00SZh7u#?+?08g0< zV1s%-U-`T@8wGh!3pO^`zUIY{nAED7kBqg!qi&GfOp>57f2PGTV19m z0qU@1PYkf%4z_%;Sq4IY94rS+ie~pwT@O3+tg?#k_=5PIk6tV@< zwLoqM0wBVLkI#`|1w=eYMnc^aRR!t?lnUng>WekR#X!!9mYXL3g^gC7`)S7mmo{y} z9*N!d$s32Nu{cZp#O|UxEZK7eY<7hGcI=lc;HrSVL|HA|S$rhhu_DBT&l+`75d`Sj3LaM~H)P zZuk2&jor6yipafklSsPL-vMo?0yAYXpH3=LveBhkno-3{4VLWL16I-@!RM$Po>&}} zm&PX3-$i>$*yx-THZmvK2q`8Qm7B`(NMR;>VSgoGw}W|G6Xd6v04Zf;HIZ0DZU?@- z39vPe0N8w(9kl$2?eG4T?tLgY5V&aFl%~g;2)aSpi!dl?{hDgsz|3<-M(gPtwP_!n z2aB4tV?d0k+>X`+(HMYfK@qtfDK|mIJeg+A<_i-n+5wkrexFs#V0N&~+{+qJ(wggC*52o2daaRwcu7r;S!!KwguB3!Ei7?IEY ze4V$m{8B4Q^(VK4~Ea!V@@}Gs0HGbR5 zy~WI*21hZuoiK`=O$2a|Uce-Zi2%A*pB|?{gv)n8+_B+i&u8Ys)ePY+UwhBDlzbC& z+N00*-?a8DTC26*(3pKgeMO`fOau^-+c6Qqq}3-dpTsEEH}ds! zT^}8XAWO>c5%+qF%#M8#x_0gC+N%q8h6-%w;qidS%gai<T)vpfYuCHXRx6O-TbC|fnj87X zBESvn(9XlXFMj6%{&BaNQ&;xixaKP)+jJ|%u&?HXvYficY}{%hf?0rNDS-X-0_Jcr zjfj~n?T;~RL#sd4ZED2Jf{*Vj+*1eP9-H+~8X^#Jb?HHabLY)EH{QD@Yh-$M`XXt@3_f-L8nBo~*C?L4~n6M92PCuzX=KFgM*j!B66er$F! z+*M(Wkk`UI@uhrL#IUz-C{K@@xtd&n-PQz%kc}7YeE{{&$?}-*yW$eG*E4jp>B_U!2`2oZuvvitN& z%RN>tE$+Yhtqb1q+xQHbp=W4uKSiIj_LZppR0=hEiVj>P0^Vcr^hu2+#Hqum+}zzo znqZ|M4oD|qd=y&JX-qob`=uqt?o%FJPIVY2w0M7BH>#sx>s#OM#9JF1(3LxMAe-vi ztJeU*G)aksP`5sP9_%|~>Pp{NmMMcay>&D+cI%H}$uSx{Su(yz$)2e$*pS%*+!Zo>DNp(P7 zI%w^D2ceEFUGCtQPKfsKr`x%^dy;Rh>lMKuhA^btz=071W=vV`_xz&m;cvd0`|!3+ z2M6uga6CNvy)%Pjw_X}5+xf###jc+?=>6chZI{BMH=haH^7ipT>(?9{weF3apk<4; z_nZFsi`@oFBXCZE^k9B1x+cH2)~9d(MnfEm;GJxG*IB zU@ly{cOTWk*K1ryX+T7m!6A>VwB-*qfH;b>`AUP19lLSA9HbfppW!={L0K)??SymOCA^V>=tOBLn2c5e ksm9QK-qMKdW>5J419kFO%DdQj-T(jq07*qoM6N<$f+5oB`~Uy| literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..8ca12fe024be86e868d14e91120a6902f8e88ac6 GIT binary patch literal 6464 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 B0*L?s literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..8e19b410a1b15ff180f3dacac19395fe3046cdec GIT binary patch literal 10676 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 literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4c19a13c239cb67b8a2134ddd5f325db1d2d5bee GIT binary patch literal 15523 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 BIP(Ai literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/colors.xml b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000000..7e2bd59b12479 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/colors.xml @@ -0,0 +1,5 @@ + + + + #FFFFFF + diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/strings.xml.template b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/strings.xml.template new file mode 100644 index 0000000000000..c4cd4a4629855 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/strings.xml.template @@ -0,0 +1,3 @@ + + <%= displayName %> + diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/styles.xml b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000000..248d47aae2501 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/values/styles.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/packages/react-native/src/generators/application/files/app/android/app/src/main/res/xml/network_security_config.xml b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000000000..c7755e76fa0e6 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,7 @@ + + + + 10.0.2.2 + localhost + + \ No newline at end of file diff --git a/packages/react-native/src/generators/application/files/app/android/build.gradle.template b/packages/react-native/src/generators/application/files/app/android/build.gradle.template new file mode 100644 index 0000000000000..4264860ecc8cd --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/build.gradle.template @@ -0,0 +1,46 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext { + buildToolsVersion = "30.0.2" + minSdkVersion = 21 + compileSdkVersion = 30 + targetSdkVersion = 30 + ndkVersion = "20.1.5948944" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:4.2.1") + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + mavenCentral() + mavenLocal() + maven { + // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm + url("$rootDir/../node_modules/react-native/android") + } + maven { + // Android JSC is installed from npm + url("$rootDir/../node_modules/jsc-android/dist") + } + + google() + + + <% if (e2eTestRunner === 'detox') { %> + maven { + // All of the Detox artifacts are provided via the npm module + url("$rootDir/../../../node_modules/detox/Detox-android") + } + <% } %> + + } +} diff --git a/packages/react-native/src/generators/application/files/app/android/gradle.properties b/packages/react-native/src/generators/application/files/app/android/gradle.properties new file mode 100644 index 0000000000000..6fbe418b48283 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/gradle.properties @@ -0,0 +1,35 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true + +# Version of flipper SDK to use with React Native +FLIPPER_VERSION=0.93.0 + +# Production release must be signed +# See: https://reactnative.dev/docs/signed-apk-android +#MYAPP_UPLOAD_STORE_FILE=my-upload-key.keystore +#MYAPP_UPLOAD_KEY_ALIAS=my-key-alias +#MYAPP_UPLOAD_STORE_PASSWORD=***** +#MYAPP_UPLOAD_KEY_PASSWORD=***** diff --git a/packages/react-native/src/generators/application/files/app/android/gradle/wrapper/gradle-wrapper.jar b/packages/react-native/src/generators/application/files/app/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..e708b1c023ec8b20f512888fe07c5bd3ff77bb8f GIT binary patch literal 59203 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

K7UpUK$>l@ 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 E2O(~oYXATM literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/android/gradle/wrapper/gradle-wrapper.properties b/packages/react-native/src/generators/application/files/app/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000000..7665b0fa93ae7 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/packages/react-native/src/generators/application/files/app/android/gradlew.bat b/packages/react-native/src/generators/application/files/app/android/gradlew.bat new file mode 100644 index 0000000000000..107acd32c4e68 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/packages/react-native/src/generators/application/files/app/android/gradlew.template b/packages/react-native/src/generators/application/files/app/android/gradlew.template new file mode 100755 index 0000000000000..4f906e0c811fc --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/gradlew.template @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/packages/react-native/src/generators/application/files/app/android/settings.gradle.template b/packages/react-native/src/generators/application/files/app/android/settings.gradle.template new file mode 100644 index 0000000000000..a70ca3656495b --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/android/settings.gradle.template @@ -0,0 +1,6 @@ +rootProject.name = '<%= className %>' + +apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); +applyNativeModulesSettingsGradle(settings) + +include ':app' diff --git a/packages/react-native/src/generators/application/files/app/app.json.template b/packages/react-native/src/generators/application/files/app/app.json.template new file mode 100644 index 0000000000000..4e3ababec0323 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/app.json.template @@ -0,0 +1,4 @@ +{ + "name": "<%= className %>", + "displayName": "<%= displayName %>" +} diff --git a/packages/react-native/src/generators/application/files/app/ios/Podfile.template b/packages/react-native/src/generators/application/files/app/ios/Podfile.template new file mode 100644 index 0000000000000..5cf9353234fc8 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/Podfile.template @@ -0,0 +1,26 @@ +require_relative '../node_modules/react-native/scripts/react_native_pods' +require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' +require_relative '../node_modules/@nrwl/react-native/nx_post_install' + +platform :ios, '11.0' + +target '<%= className %>' do + config = use_native_modules! + + use_react_native!( + :path => config[:reactNativePath], + # to enable hermes on iOS, change `false` to `true` and then install pods + :hermes_enabled => false + ) + + # Enables Flipper. + # + # Note that if you have use_frameworks! enabled, Flipper will not work and + # you should disable the next line. + use_flipper!() + + post_install do |installer| + react_native_post_install(installer) + nx_post_install(installer) + end +end \ No newline at end of file diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.pbxproj.template b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.pbxproj.template new file mode 100644 index 0000000000000..4839d3e5b79da --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.pbxproj.template @@ -0,0 +1,512 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + B3356142AC64179FFC014881 /* libPods-<%= className %>.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 81E830AF565B82692D619A27 /* libPods-<%= className %>.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = <%= className %>; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* <%= className %>.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = <%= className %>.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = <%= className %>/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = <%= className %>/AppDelegate.m; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = <%= className %>/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = <%= className %>/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = <%= className %>/main.m; sourceTree = ""; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = <%= className %>/LaunchScreen.storyboard; sourceTree = ""; }; + 81E830AF565B82692D619A27 /* libPods-<%= className %>.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-<%= className %>.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 95971177ADC3B43EA1915EFA /* Pods-<%= className %>.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-<%= className %>.release.xcconfig"; path = "Target Support Files/Pods-<%= className %>/Pods-<%= className %>.release.xcconfig"; sourceTree = ""; }; + AE0E2B88164A638A5DDEB356 /* Pods-<%= className %>.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-<%= className %>.debug.xcconfig"; path = "Target Support Files/Pods-<%= className %>/Pods-<%= className %>.debug.xcconfig"; sourceTree = ""; }; + ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B3356142AC64179FFC014881 /* libPods-<%= className %>.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* <%= className %> */ = { + isa = PBXGroup; + children = ( + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = <%= className %>; + sourceTree = ""; + }; + 2D16E6871FA4F8E400B85C8A /* Frameworks */ = { + isa = PBXGroup; + children = ( + ED297162215061F000B7C4FE /* JavaScriptCore.framework */, + 81E830AF565B82692D619A27 /* libPods-<%= className %>.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* <%= className %> */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 83CBBA001A601CBA00E9B192 /* Products */, + 2D16E6871FA4F8E400B85C8A /* Frameworks */, + C2004278D84265DB429CF917 /* Pods */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* <%= className %>.app */, + ); + name = Products; + sourceTree = ""; + }; + C2004278D84265DB429CF917 /* Pods */ = { + isa = PBXGroup; + children = ( + AE0E2B88164A638A5DDEB356 /* Pods-<%= className %>.debug.xcconfig */, + 95971177ADC3B43EA1915EFA /* Pods-<%= className %>.release.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 13B07F861A680F5B00A75B9A /* <%= className %> */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "<%= className %>" */; + buildPhases = ( + 6C62348392EC1AF6F3F93830 /* [CP] Check Pods Manifest.lock */, + FD10A7F022414F080027D42C /* Start Packager */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + A84F2DC47AE20FA20EBF9D3E /* [CP] Embed Pods Frameworks */, + 5C52E8E471D125E776E8B51C /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = <%= className %>; + productName = <%= className %>; + productReference = 13B07F961A680F5B00A75B9A /* <%= className %>.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1210; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1120; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "<%= className %>" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* <%= className %> */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nexport NODE_BINARY=node\n../node_modules/react-native/scripts/react-native-xcode.sh <%= entryFile %>\n"; + }; + 5C52E8E471D125E776E8B51C /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-<%= className %>/Pods-<%= className %>-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-<%= className %>/Pods-<%= className %>-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-<%= className %>/Pods-<%= className %>-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 6C62348392EC1AF6F3F93830 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-<%= className %>-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + A84F2DC47AE20FA20EBF9D3E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-<%= className %>/Pods-<%= className %>-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-<%= className %>/Pods-<%= className %>-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-<%= className %>/Pods-<%= className %>-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + FD10A7F022414F080027D42C /* Start Packager */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Start Packager"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* <%= className %> */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AE0E2B88164A638A5DDEB356 /* Pods-<%= className %>.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = <%= className %>/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = <%= className %>; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 95971177ADC3B43EA1915EFA /* Pods-<%= className %>.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = 1; + INFOPLIST_FILE = <%= className %>/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = "org.$(PRODUCT_NAME:rfc1034identifier)"; + PRODUCT_NAME = <%= className %>; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "<%= className %>" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "<%= className %>" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000000..919434a6254f0 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/xcshareddata/xcschemes/__className__.xcscheme.template b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/xcshareddata/xcschemes/__className__.xcscheme.template new file mode 100644 index 0000000000000..088b0c30b2db6 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__.xcodeproj/xcshareddata/xcschemes/__className__.xcscheme.template @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/contents.xcworkspacedata.template b/packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/contents.xcworkspacedata.template new file mode 100644 index 0000000000000..b711c275d9110 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/contents.xcworkspacedata.template @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000000..18d981003d68d --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.h b/packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.h new file mode 100644 index 0000000000000..ef1de86a2a80a --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.h @@ -0,0 +1,8 @@ +#import +#import + +@interface AppDelegate : UIResponder + +@property (nonatomic, strong) UIWindow *window; + +@end diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.m.template b/packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.m.template new file mode 100644 index 0000000000000..aff47c84e3712 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/AppDelegate.m.template @@ -0,0 +1,62 @@ +#import "AppDelegate.h" + +#import +#import +#import + +#ifdef FB_SONARKIT_ENABLED +#import +#import +#import +#import +#import +#import + +static void InitializeFlipper(UIApplication *application) { + FlipperClient *client = [FlipperClient sharedClient]; + SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; + [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; + [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; + [client addPlugin:[FlipperKitReactPlugin new]]; + [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; + [client start]; +} +#endif + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions +{ +#ifdef FB_SONARKIT_ENABLED + InitializeFlipper(application); +#endif + + RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; + RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge + moduleName:@"main" + initialProperties:nil]; + + if (@available(iOS 13.0, *)) { + rootView.backgroundColor = [UIColor systemBackgroundColor]; + } else { + rootView.backgroundColor = [UIColor whiteColor]; + } + + self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; + UIViewController *rootViewController = [UIViewController new]; + rootViewController.view = rootView; + self.window.rootViewController = rootViewController; + [self.window makeKeyAndVisible]; + return YES; +} + +- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge +{ +#if DEBUG + return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"<%= appProjectRoot %>/src/main" fallbackResource:nil]; +#else + return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; +#endif +} + +@end diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/Base.lproj/LaunchScreen.xib.template b/packages/react-native/src/generators/application/files/app/ios/__className__/Base.lproj/LaunchScreen.xib.template new file mode 100644 index 0000000000000..0880e22d26ced --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/Base.lproj/LaunchScreen.xib.template @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/AppIcon.appiconset/Contents.json b/packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000000..48e64ae8a3efa --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,38 @@ +{ + "images": [ + { + "idiom": "iphone", + "size": "29x29", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "29x29", + "scale": "3x" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "40x40", + "scale": "3x" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "2x" + }, + { + "idiom": "iphone", + "size": "60x60", + "scale": "3x" + } + ], + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/Contents.json b/packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/Contents.json new file mode 100644 index 0000000000000..97a8662ebdb43 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info": { + "version": 1, + "author": "xcode" + } +} diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/Info.plist.template b/packages/react-native/src/generators/application/files/app/ios/__className__/Info.plist.template new file mode 100644 index 0000000000000..f943833b04fcd --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/Info.plist.template @@ -0,0 +1,55 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + <%= displayName %> + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSExceptionDomains + + localhost + + NSExceptionAllowsInsecureHTTPLoads + + + + + NSLocationWhenInUseUsageDescription + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/LaunchScreen.storyboard.template b/packages/react-native/src/generators/application/files/app/ios/__className__/LaunchScreen.storyboard.template new file mode 100644 index 0000000000000..2015153029df9 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/LaunchScreen.storyboard.template @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/Supporting/Expo.plist b/packages/react-native/src/generators/application/files/app/ios/__className__/Supporting/Expo.plist new file mode 100644 index 0000000000000..03410dc8b39a8 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/Supporting/Expo.plist @@ -0,0 +1,10 @@ + + + + + EXUpdatesSDKVersion + YOUR-APP-SDK-VERSION-HERE + EXUpdatesURL + YOUR-APP-URL-HERE + + diff --git a/packages/react-native/src/generators/application/files/app/ios/__className__/main.m b/packages/react-native/src/generators/application/files/app/ios/__className__/main.m new file mode 100644 index 0000000000000..25181b6ccb54b --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/ios/__className__/main.m @@ -0,0 +1,10 @@ +#import + +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} + diff --git a/packages/react-native/src/generators/application/files/app/metro.config.js.template b/packages/react-native/src/generators/application/files/app/metro.config.js.template new file mode 100644 index 0000000000000..b72d4cbe6b506 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/metro.config.js.template @@ -0,0 +1,21 @@ +const { withNxMetro } = require('@nrwl/react-native'); + +module.exports = withNxMetro( + { + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, + }, + { + // Change this to true to see debugging info. + // Useful if you have issues resolving modules + debug: false, + // all the file extensions used for imports other than 'ts', 'tsx', 'js', 'jsx' + extensions: [], + } +); diff --git a/packages/react-native/src/generators/application/files/app/package.json.template b/packages/react-native/src/generators/application/files/app/package.json.template new file mode 100644 index 0000000000000..7e58c06fc2675 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/package.json.template @@ -0,0 +1,11 @@ +{ + "name": "<%= projectName %>", + "version": "0.0.1", + "private": true, + "dependencies": { + "react-native": "*", + "react": "*", + "@testing-library/react-native": "*", + "@testing-library/jest-native": "*" + } +} diff --git a/packages/react-native/src/generators/application/files/app/src/app/App.spec.tsx.template b/packages/react-native/src/generators/application/files/app/src/app/App.spec.tsx.template new file mode 100644 index 0000000000000..36d84f9b09728 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/src/app/App.spec.tsx.template @@ -0,0 +1,10 @@ +import 'react-native'; +import React from 'react'; +import { render } from '@testing-library/react-native'; + +import App from './App'; + +it('renders correctly', () => { + const { getByTestId } = render(); + expect(getByTestId('heading')).toHaveTextContent('Welcome'); +}); diff --git a/packages/react-native/src/generators/application/files/app/src/app/App.tsx.template b/packages/react-native/src/generators/application/files/app/src/app/App.tsx.template new file mode 100644 index 0000000000000..87cfabd1741e1 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/src/app/App.tsx.template @@ -0,0 +1,131 @@ +import React from 'react'; +import { + SafeAreaView, + StyleSheet, + ScrollView, + Image, + View, + Text, + StatusBar, + TouchableOpacity, +} from 'react-native'; + +import { + Colors, + DebugInstructions, + ReloadInstructions, +} from 'react-native/Libraries/NewAppScreen'; +// @ts-ignore +import openURLInBrowser from 'react-native/Libraries/Core/Devtools/openURLInBrowser'; + +const App = () => { + return ( + <> + + + + + + Welcome to <%= displayName %> + + + + Step One + + Edit <%= appProjectRoot %>/App.tsx to change this + screen and then come back to see your edits. + + + + See Your Changes + + Alternatively, press{' '} + R in the bundler + terminal window. + + + + Debug + + + + + + Learn More + openURLInBrowser('https://nx.dev')} + testID="nx-link" + > + + Visit nx.dev for more info + about Nx. + + + + + + + + ); +}; + +const styles = StyleSheet.create({ + scrollView: { + backgroundColor: Colors.lighter, + }, + header: { + backgroundColor: '#143055', + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 24, + }, + logo: { + width: 200, + height: 180, + resizeMode: 'contain', + }, + heading: { + fontSize: 24, + fontWeight: '600', + color: Colors.lighter, + }, + body: { + backgroundColor: Colors.white, + }, + sectionContainer: { + marginTop: 32, + paddingHorizontal: 24, + }, + sectionTitle: { + fontSize: 24, + fontWeight: '600', + color: Colors.black, + }, + sectionDescription: { + marginTop: 8, + fontSize: 18, + fontWeight: '400', + color: Colors.dark, + }, + highlight: { + fontWeight: '700', + }, + footer: { + color: Colors.dark, + fontSize: 12, + fontWeight: '600', + padding: 4, + paddingRight: 12, + textAlign: 'right', + }, + link: { + color: '#45bc98', + }, +}); + +export default App; diff --git a/packages/react-native/src/generators/application/files/app/src/app/logo.png b/packages/react-native/src/generators/application/files/app/src/app/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e9b9b6eb620ffe5c4ee48dec55e9a6392a136cf9 GIT binary patch literal 28693 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 LH03MgEJOYWSW^Zd literal 0 HcmV?d00001 diff --git a/packages/react-native/src/generators/application/files/app/src/main.tsx.template b/packages/react-native/src/generators/application/files/app/src/main.tsx.template new file mode 100644 index 0000000000000..f0b77bd794d33 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/src/main.tsx.template @@ -0,0 +1,4 @@ +import { AppRegistry } from 'react-native'; +import App from './app/App'; + +AppRegistry.registerComponent('main', () => App); diff --git a/packages/react-native/src/generators/application/files/app/test-setup.ts.template b/packages/react-native/src/generators/application/files/app/test-setup.ts.template new file mode 100644 index 0000000000000..9f28ad211b736 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/test-setup.ts.template @@ -0,0 +1 @@ +import '@testing-library/jest-native/extend-expect'; diff --git a/packages/react-native/src/generators/application/files/app/tsconfig.app.json.template b/packages/react-native/src/generators/application/files/app/tsconfig.app.json.template new file mode 100644 index 0000000000000..f3ac07d2b4b9f --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/tsconfig.app.json.template @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "<%= offsetFromRoot %>dist/out-tsc", + "types": ["node"] + }, + "exclude": ["**/*.spec.ts", "**/*.spec.tsx"], + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/packages/react-native/src/generators/application/files/app/tsconfig.json.template b/packages/react-native/src/generators/application/files/app/tsconfig.json.template new file mode 100644 index 0000000000000..6e3eddab36ae4 --- /dev/null +++ b/packages/react-native/src/generators/application/files/app/tsconfig.json.template @@ -0,0 +1,22 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.base.json", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "jsx": "react-native", + "lib": ["dom", "esnext"], + "moduleResolution": "node", + "noEmit": true, + "skipLibCheck": true, + "resolveJsonModule": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + } + ], + "exclude": [ + "node_modules" + ] +} diff --git a/packages/react-native/src/generators/application/lib/add-detox.ts b/packages/react-native/src/generators/application/lib/add-detox.ts new file mode 100644 index 0000000000000..811c56ad97bba --- /dev/null +++ b/packages/react-native/src/generators/application/lib/add-detox.ts @@ -0,0 +1,18 @@ +import { detoxApplicationGenerator } from '@nrwl/detox'; +import { Tree } from '@nrwl/devkit'; +import { NormalizedSchema } from './normalize-options'; +import { Linter } from '@nrwl/linter'; + +export async function addDetox(host: Tree, options: NormalizedSchema) { + if (options?.e2eTestRunner !== 'detox') { + return () => {}; + } + + return detoxApplicationGenerator(host, { + ...options, + linter: Linter.EsLint, + name: `${options.name}-e2e`, + directory: options.directory, + project: options.name, + }); +} diff --git a/packages/react-native/src/generators/application/lib/add-project.ts b/packages/react-native/src/generators/application/lib/add-project.ts new file mode 100644 index 0000000000000..fe58b60c5fc0f --- /dev/null +++ b/packages/react-native/src/generators/application/lib/add-project.ts @@ -0,0 +1,104 @@ +import { + addProjectConfiguration, + NxJsonProjectConfiguration, + ProjectConfiguration, + readWorkspaceConfiguration, + TargetConfiguration, + Tree, + updateWorkspaceConfiguration, +} from '@nrwl/devkit'; +import { NormalizedSchema } from './normalize-options'; + +export function addProject(host: Tree, options: NormalizedSchema) { + const nxConfig: NxJsonProjectConfiguration = { + tags: options.parsedTags, + }; + + const project: ProjectConfiguration = { + root: options.appProjectRoot, + sourceRoot: `${options.appProjectRoot}/src`, + projectType: 'application', + targets: { ...getTargets(options) }, + }; + + addProjectConfiguration(host, options.projectName, { + ...project, + ...nxConfig, + }); + + const workspace = readWorkspaceConfiguration(host); + + if (!workspace.defaultProject) { + workspace.defaultProject = options.projectName; + + updateWorkspaceConfiguration(host, workspace); + } +} + +function getTargets(options: NormalizedSchema) { + const architect: { [key: string]: TargetConfiguration } = {}; + + architect.start = { + executor: '@nrwl/react-native:start', + options: { + port: 8081, + }, + }; + + architect.serve = { + executor: '@nrwl/workspace:run-commands', + options: { + command: `nx start ${options.name}`, + }, + }; + + architect['run-ios'] = { + executor: '@nrwl/react-native:run-ios', + options: {}, + }; + + architect['bundle-ios'] = { + executor: '@nrwl/react-native:bundle', + outputs: [`${options.appProjectRoot}/build`], + options: { + entryFile: `${options.appProjectRoot}/src/main.tsx`, + platform: 'ios', + bundleOutput: `dist/${options.appProjectRoot}/ios/main.jsbundle`, + }, + }; + + architect['run-android'] = { + executor: '@nrwl/react-native:run-android', + options: {}, + }; + + architect['build-android'] = { + executor: '@nrwl/react-native:build-android', + outputs: [ + `${options.appProjectRoot}/android/app/build/outputs/bundle`, + `${options.appProjectRoot}/android/app/build/outputs/apk`, + ], + options: {}, + }; + + architect['bundle-android'] = { + executor: '@nrwl/react-native:bundle', + options: { + entryFile: `${options.appProjectRoot}/src/main.tsx`, + platform: 'android', + bundleOutput: `dist/${options.appProjectRoot}/android/main.jsbundle`, + }, + }; + + architect['sync-deps'] = { + executor: '@nrwl/react-native:sync-deps', + options: {}, + }; + + architect['ensure-symlink'] = { + executor: '@nrwl/react-native:ensure-symlink', + options: {}, + }; + + return architect; +} diff --git a/packages/react-native/src/generators/application/lib/create-application-files.ts b/packages/react-native/src/generators/application/lib/create-application-files.ts new file mode 100644 index 0000000000000..22a1fe04f3eea --- /dev/null +++ b/packages/react-native/src/generators/application/lib/create-application-files.ts @@ -0,0 +1,24 @@ +import { generateFiles, offsetFromRoot, toJS, Tree } from '@nrwl/devkit'; +import { join } from 'path'; +import { NormalizedSchema } from './normalize-options'; + +export function createApplicationFiles(host: Tree, options: NormalizedSchema) { + generateFiles(host, join(__dirname, '../files/app'), options.appProjectRoot, { + ...options, + offsetFromRoot: offsetFromRoot(options.appProjectRoot), + }); + if (options.unitTestRunner === 'none') { + host.delete(join(options.appProjectRoot, `/src/app/App.spec.tsx`)); + } + if (options.e2eTestRunner === 'none') { + host.delete( + join( + options.androidProjectRoot, + `/app/src/androidTest/java/com/${options.lowerCaseName}/DetoxTest.java` + ) + ); + } + if (options.js) { + toJS(host); + } +} diff --git a/packages/react-native/src/generators/application/lib/nomalize-options.spec.ts b/packages/react-native/src/generators/application/lib/nomalize-options.spec.ts new file mode 100644 index 0000000000000..d6e2ef8230127 --- /dev/null +++ b/packages/react-native/src/generators/application/lib/nomalize-options.spec.ts @@ -0,0 +1,106 @@ +import { Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import { Schema } from '../schema'; +import { normalizeOptions } from './normalize-options'; + +describe('Normalize Options', () => { + let appTree: Tree; + + beforeEach(() => { + appTree = createTreeWithEmptyWorkspace(); + }); + + it('should normalize options with name in kebab case', () => { + const schema: Schema = { + name: 'my-app', + linter: Linter.EsLint, + e2eTestRunner: 'none', + }; + const options = normalizeOptions(appTree, schema); + expect(options).toEqual({ + androidProjectRoot: 'apps/my-app/android', + appProjectRoot: 'apps/my-app', + className: 'MyApp', + displayName: 'MyApp', + iosProjectRoot: 'apps/my-app/ios', + lowerCaseName: 'myapp', + name: 'my-app', + parsedTags: [], + projectName: 'my-app', + linter: Linter.EsLint, + entryFile: '/virtual/apps/my-app/src/main.tsx', + e2eTestRunner: 'none', + unitTestRunner: 'jest', + }); + }); + + it('should normalize options with name in camel case', () => { + const schema: Schema = { + name: 'myApp', + e2eTestRunner: 'none', + }; + const options = normalizeOptions(appTree, schema); + expect(options).toEqual({ + androidProjectRoot: 'apps/my-app/android', + appProjectRoot: 'apps/my-app', + className: 'MyApp', + displayName: 'MyApp', + iosProjectRoot: 'apps/my-app/ios', + lowerCaseName: 'myapp', + name: 'my-app', + parsedTags: [], + projectName: 'my-app', + entryFile: '/virtual/apps/my-app/src/main.tsx', + e2eTestRunner: 'none', + unitTestRunner: 'jest', + }); + }); + + it('should normalize options with directory', () => { + const schema: Schema = { + name: 'my-app', + directory: 'directory', + e2eTestRunner: 'none', + }; + const options = normalizeOptions(appTree, schema); + expect(options).toEqual({ + androidProjectRoot: 'apps/directory/my-app/android', + appProjectRoot: 'apps/directory/my-app', + className: 'MyApp', + displayName: 'MyApp', + iosProjectRoot: 'apps/directory/my-app/ios', + lowerCaseName: 'myapp', + name: 'my-app', + directory: 'directory', + parsedTags: [], + projectName: 'directory-my-app', + entryFile: '/virtual/apps/directory/my-app/src/main.tsx', + e2eTestRunner: 'none', + unitTestRunner: 'jest', + }); + }); + + it('should normalize options with display name', () => { + const schema: Schema = { + name: 'my-app', + displayName: 'My App', + e2eTestRunner: 'none', + }; + const options = normalizeOptions(appTree, schema); + expect(options).toEqual({ + androidProjectRoot: 'apps/my-app/android', + appProjectRoot: 'apps/my-app', + className: 'MyApp', + displayName: 'My App', + iosProjectRoot: 'apps/my-app/ios', + lowerCaseName: 'myapp', + name: 'my-app', + parsedTags: [], + projectName: 'my-app', + entryFile: '/virtual/apps/my-app/src/main.tsx', + e2eTestRunner: 'none', + unitTestRunner: 'jest', + }); + }); +}); diff --git a/packages/react-native/src/generators/application/lib/normalize-options.ts b/packages/react-native/src/generators/application/lib/normalize-options.ts new file mode 100644 index 0000000000000..2c09d927a7a02 --- /dev/null +++ b/packages/react-native/src/generators/application/lib/normalize-options.ts @@ -0,0 +1,62 @@ +import { names, Tree } from '@nrwl/devkit'; +import { join } from 'path'; +import { Schema } from '../schema'; + +export interface NormalizedSchema extends Schema { + className: string; + projectName: string; + appProjectRoot: string; + lowerCaseName: string; + iosProjectRoot: string; + androidProjectRoot: string; + parsedTags: string[]; + entryFile: string; +} + +export function normalizeOptions( + host: Tree, + options: Schema +): NormalizedSchema { + const { fileName, className } = names(options.name); + + const directoryName = options.directory + ? names(options.directory).fileName + : ''; + const projectDirectory = directoryName + ? `${directoryName}/${fileName}` + : fileName; + + const appProjectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); + + const appProjectRoot = `apps/${projectDirectory}`; + const iosProjectRoot = join(appProjectRoot, 'ios'); + const androidProjectRoot = join(appProjectRoot, 'android'); + + const parsedTags = options.tags + ? options.tags.split(',').map((s) => s.trim()) + : []; + + const entryFile = join(host.root, appProjectRoot, '/src/main.tsx'); + + /** + * if options.name is "my-app" + * name: "my-app", className: 'MyApp', lowerCaseName: 'myapp', displayName: 'MyApp', projectName: 'my-app', appProjectRoot: 'apps/my-app', androidProjectRoot: 'apps/my-app/android', iosProjectRoot: 'apps/my-app/ios' + * if options.name is "myApp" + * name: "my-app", className: 'MyApp', lowerCaseName: 'myapp', displayName: 'MyApp', projectName: 'my-app', appProjectRoot: 'apps/my-app', androidProjectRoot: 'apps/my-app/android', iosProjectRoot: 'apps/my-app/ios' + */ + return { + ...options, + unitTestRunner: options.unitTestRunner || 'jest', + e2eTestRunner: options.e2eTestRunner || 'detox', + name: fileName, + className, + lowerCaseName: className.toLowerCase(), + displayName: options.displayName || className, + projectName: appProjectName, + appProjectRoot, + iosProjectRoot, + androidProjectRoot, + parsedTags, + entryFile, + }; +} diff --git a/packages/react-native/src/generators/application/schema.d.ts b/packages/react-native/src/generators/application/schema.d.ts new file mode 100644 index 0000000000000..1a0426eecbec3 --- /dev/null +++ b/packages/react-native/src/generators/application/schema.d.ts @@ -0,0 +1,17 @@ +import { Linter } from '@nrwl/linter'; + +export interface Schema { + name: string; + displayName?: string; + style?: string; + skipFormat?: boolean; + directory?: string; + tags?: string; + unitTestRunner?: 'jest' | 'none'; + pascalCaseFiles?: boolean; + classComponent?: boolean; + js?: boolean; + linter?: Linter; + setParserOptionsProject?: boolean; + e2eTestRunner?: 'detox' | 'none'; +} diff --git a/packages/react-native/src/generators/application/schema.json b/packages/react-native/src/generators/application/schema.json new file mode 100644 index 0000000000000..4a6a4f4054454 --- /dev/null +++ b/packages/react-native/src/generators/application/schema.json @@ -0,0 +1,76 @@ +{ + "cli": "nx", + "$id": "NxReactNativeApplication", + "$schema": "http://json-schema.org/schema", + "title": "Create a React Application for Nx", + "examples": [ + { + "command": "g app myapp --directory=nested", + "description": "Generate apps/nested/myapp" + }, + { + "command": "g app myapp --classComponent", + "description": "Use class components instead of functional components" + } + ], + "type": "object", + "properties": { + "name": { + "description": "The name of the application.", + "type": "string", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the application?" + }, + "displayName": { + "description": "The display name to show in the application. Defaults to name.", + "type": "string" + }, + "directory": { + "description": "The directory of the new application.", + "type": "string", + "alias": "d" + }, + "skipFormat": { + "description": "Skip formatting files", + "type": "boolean", + "default": false + }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "tslint"], + "default": "eslint" + }, + "unitTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for unit tests", + "default": "jest" + }, + "tags": { + "type": "string", + "description": "Add tags to the application (used for linting)", + "alias": "t" + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files", + "default": false + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", + "default": false + }, + "e2eTestRunner": { + "description": "Adds the specified e2e test runner", + "type": "string", + "enum": ["detox", "none"], + "default": "detox" + } + }, + "required": [] +} diff --git a/packages/react-native/src/generators/component/component.spec.ts b/packages/react-native/src/generators/component/component.spec.ts new file mode 100644 index 0000000000000..63060ad9144dc --- /dev/null +++ b/packages/react-native/src/generators/component/component.spec.ts @@ -0,0 +1,135 @@ +import { logger, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { createApp, createLib } from '../../utils/testing-generators'; +import { reactNativeComponentGenerator } from './component'; + +describe('component', () => { + let appTree: Tree; + let projectName: string; + + beforeEach(async () => { + projectName = 'my-lib'; + appTree = createTreeWithEmptyWorkspace(); + appTree.write('.gitignore', ''); + await createApp(appTree, 'my-app'); + await createLib(appTree, projectName); + jest.spyOn(logger, 'warn').mockImplementation(() => {}); + jest.spyOn(logger, 'debug').mockImplementation(() => {}); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should generate files', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: projectName, + }); + + expect(appTree.exists('libs/my-lib/src/lib/hello/hello.tsx')).toBeTruthy(); + expect( + appTree.exists('libs/my-lib/src/lib/hello/hello.spec.tsx') + ).toBeTruthy(); + }); + + it('should generate files for an app', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: 'my-app', + }); + + expect(appTree.exists('apps/my-app/src/app/hello/hello.tsx')).toBeTruthy(); + expect( + appTree.exists('apps/my-app/src/app/hello/hello.spec.tsx') + ).toBeTruthy(); + }); + + describe('--export', () => { + it('should add to index.ts barrel', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: projectName, + export: true, + }); + + const indexContent = appTree.read('libs/my-lib/src/index.ts', 'utf-8'); + + expect(indexContent).toMatch(/lib\/hello/); + }); + + it('should not export from an app', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: 'my-app', + export: true, + }); + + const indexContent = appTree.read('libs/my-lib/src/index.ts', 'utf-8'); + + expect(indexContent).not.toMatch(/lib\/hello/); + }); + }); + + describe('--pascalCaseFiles', () => { + it('should generate component files with upper case names', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: projectName, + pascalCaseFiles: true, + }); + expect( + appTree.exists('libs/my-lib/src/lib/hello/Hello.tsx') + ).toBeTruthy(); + expect( + appTree.exists('libs/my-lib/src/lib/hello/Hello.spec.tsx') + ).toBeTruthy(); + }); + }); + + describe('--directory', () => { + it('should create component under the directory', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: projectName, + directory: 'components', + }); + + expect(appTree.exists('/libs/my-lib/src/components/hello/hello.tsx')); + }); + + it('should create with nested directories', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'helloWorld', + project: projectName, + directory: 'lib/foo', + }); + + expect( + appTree.exists('/libs/my-lib/src/lib/foo/hello-world/hello-world.tsx') + ); + }); + }); + + describe('--flat', () => { + it('should create in project directory rather than in its own folder', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: projectName, + flat: true, + }); + + expect(appTree.exists('/libs/my-lib/src/lib/hello.tsx')); + }); + it('should work with custom directory path', async () => { + await reactNativeComponentGenerator(appTree, { + name: 'hello', + project: projectName, + flat: true, + directory: 'components', + }); + + expect(appTree.exists('/libs/my-lib/src/components/hello.tsx')); + }); + }); +}); diff --git a/packages/react-native/src/generators/component/component.ts b/packages/react-native/src/generators/component/component.ts new file mode 100644 index 0000000000000..9ba5280263ffd --- /dev/null +++ b/packages/react-native/src/generators/component/component.ts @@ -0,0 +1,88 @@ +import * as ts from 'typescript'; +import { Schema } from './schema'; +import { + applyChangesToString, + convertNxGenerator, + formatFiles, + generateFiles, + getProjects, + joinPathFragments, + toJS, + Tree, +} from '@nrwl/devkit'; +import { NormalizedSchema, normalizeOptions } from './lib/normalize-options'; +import { addImport } from './lib/add-import'; + +export async function reactNativeComponentGenerator( + host: Tree, + schema: Schema +) { + const options = await normalizeOptions(host, schema); + createComponentFiles(host, options); + + addExportsToBarrel(host, options); + + await formatFiles(host); +} + +function createComponentFiles(host: Tree, options: NormalizedSchema) { + const componentDir = joinPathFragments( + options.projectSourceRoot, + options.directory + ); + + generateFiles(host, joinPathFragments(__dirname, './files'), componentDir, { + ...options, + tmpl: '', + }); + + for (const c of host.listChanges()) { + let deleteFile = false; + + if (options.skipTests && /.*spec.tsx/.test(c.path)) { + deleteFile = true; + } + + if (deleteFile) { + host.delete(c.path); + } + } + + if (options.js) { + toJS(host); + } +} + +function addExportsToBarrel(host: Tree, options: NormalizedSchema) { + const workspace = getProjects(host); + const isApp = workspace.get(options.project).projectType === 'application'; + + if (options.export && !isApp) { + const indexFilePath = joinPathFragments( + options.projectSourceRoot, + options.js ? 'index.js' : 'index.ts' + ); + const indexSource = host.read(indexFilePath, 'utf-8'); + if (indexSource !== null) { + const indexSourceFile = ts.createSourceFile( + indexFilePath, + indexSource, + ts.ScriptTarget.Latest, + true + ); + const changes = applyChangesToString( + indexSource, + addImport( + indexSourceFile, + `export * from './${options.directory}/${options.fileName}';` + ) + ); + host.write(indexFilePath, changes); + } + } +} + +export default reactNativeComponentGenerator; +export const reactNativeComponentSchematic = convertNxGenerator( + reactNativeComponentGenerator +); diff --git a/packages/react-native/src/generators/component/files/__fileName__.spec.tsx__tmpl__ b/packages/react-native/src/generators/component/files/__fileName__.spec.tsx__tmpl__ new file mode 100644 index 0000000000000..d42366c2ff40d --- /dev/null +++ b/packages/react-native/src/generators/component/files/__fileName__.spec.tsx__tmpl__ @@ -0,0 +1,11 @@ +import React from 'react'; +import { render } from '@testing-library/react-native'; + +import <%= className %> from './<%= fileName %>'; + +describe('<%= className %>', () => { + it('should render successfully', () => { + const { container } = render(< <%= className %> />); + expect(container).toBeTruthy(); + }); +}); diff --git a/packages/react-native/src/generators/component/files/__fileName__.tsx__tmpl__ b/packages/react-native/src/generators/component/files/__fileName__.tsx__tmpl__ new file mode 100644 index 0000000000000..130b2fe9266f6 --- /dev/null +++ b/packages/react-native/src/generators/component/files/__fileName__.tsx__tmpl__ @@ -0,0 +1,32 @@ +<% if (classComponent) { %> +import { Component } from 'react'; +<% } else { %> +import React from 'react'; +<% } %> +import { View, Text } from 'react-native'; + +/* eslint-disable-next-line */ +export interface <%= className %>Props { +} + +<% if (classComponent) { %> +export class <%= className %> extends Component<<%= className %>Props> { + render() { + return ( + + Welcome to <%= name %>! + + ); + } +} +<% } else { %> +export function <%= className %>(props: <%= className %>Props) { + return ( + + Welcome to <%= name %>! + + ); +}; +<% } %> + +export default <%= className %>; diff --git a/packages/react-native/src/generators/component/lib/add-import.ts b/packages/react-native/src/generators/component/lib/add-import.ts new file mode 100644 index 0000000000000..b151ee22261a0 --- /dev/null +++ b/packages/react-native/src/generators/component/lib/add-import.ts @@ -0,0 +1,28 @@ +import { findNodes } from '@nrwl/workspace/src/utilities/typescript'; +import * as ts from 'typescript'; +import { ChangeType, StringChange } from '@nrwl/devkit'; + +export function addImport( + source: ts.SourceFile, + statement: string +): StringChange[] { + const allImports = findNodes(source, ts.SyntaxKind.ImportDeclaration); + if (allImports.length > 0) { + const lastImport = allImports[allImports.length - 1]; + return [ + { + type: ChangeType.Insert, + index: lastImport.end + 1, + text: `\n${statement}\n`, + }, + ]; + } else { + return [ + { + type: ChangeType.Insert, + index: 0, + text: `\n${statement}\n`, + }, + ]; + } +} diff --git a/packages/react-native/src/generators/component/lib/normalize-options.ts b/packages/react-native/src/generators/component/lib/normalize-options.ts new file mode 100644 index 0000000000000..23bc23c724f43 --- /dev/null +++ b/packages/react-native/src/generators/component/lib/normalize-options.ts @@ -0,0 +1,83 @@ +import { + getProjects, + joinPathFragments, + logger, + names, + Tree, +} from '@nrwl/devkit'; +import { Schema } from '../schema'; + +export interface NormalizedSchema extends Schema { + projectSourceRoot: string; + fileName: string; + className: string; +} + +export async function normalizeOptions( + host: Tree, + options: Schema +): Promise { + assertValidOptions(options); + + const { className, fileName } = names(options.name); + const componentFileName = options.pascalCaseFiles ? className : fileName; + const project = getProjects(host).get(options.project); + + if (!project) { + logger.error( + `Cannot find the ${options.project} project. Please double check the project name.` + ); + throw new Error(); + } + + const { sourceRoot: projectSourceRoot, projectType } = project; + + const directory = await getDirectory(host, options); + + if (options.export && projectType === 'application') { + logger.warn( + `The "--export" option should not be used with applications and will do nothing.` + ); + } + + options.classComponent = options.classComponent ?? false; + + return { + ...options, + directory, + className, + fileName: componentFileName, + projectSourceRoot, + }; +} + +async function getDirectory(host: Tree, options: Schema) { + const fileName = names(options.name).fileName; + const workspace = getProjects(host); + let baseDir: string; + if (options.directory) { + baseDir = options.directory; + } else { + baseDir = + workspace.get(options.project).projectType === 'application' + ? 'app' + : 'lib'; + } + return options.flat ? baseDir : joinPathFragments(baseDir, fileName); +} + +function assertValidOptions(options: Schema) { + const slashes = ['/', '\\']; + slashes.forEach((s) => { + if (options.name.indexOf(s) !== -1) { + const [name, ...rest] = options.name.split(s).reverse(); + let suggestion = rest.map((x) => x.toLowerCase()).join(s); + if (options.directory) { + suggestion = `${options.directory}${s}${suggestion}`; + } + throw new Error( + `Found "${s}" in the component name. Did you mean to use the --directory option (e.g. \`nx g c ${name} --directory ${suggestion}\`)?` + ); + } + }); +} diff --git a/packages/react-native/src/generators/component/schema.d.ts b/packages/react-native/src/generators/component/schema.d.ts new file mode 100644 index 0000000000000..17e23f569d756 --- /dev/null +++ b/packages/react-native/src/generators/component/schema.d.ts @@ -0,0 +1,14 @@ +/** + * Same as the @nrwl/react library schema, except it removes keys: style, routing, globalCss + */ +export interface Schema { + name: string; + project: string; + skipTests?: boolean; + directory?: string; + export?: boolean; + pascalCaseFiles?: boolean; + classComponent?: boolean; + js?: boolean; + flat?: boolean; +} diff --git a/packages/react-native/src/generators/component/schema.json b/packages/react-native/src/generators/component/schema.json new file mode 100644 index 0000000000000..c2dd5864dcb9e --- /dev/null +++ b/packages/react-native/src/generators/component/schema.json @@ -0,0 +1,77 @@ +{ + "cli": "nx", + "$id": "NxReactNativeApplication", + "$schema": "http://json-schema.org/schema", + "title": "Create a React Component for Nx", + "type": "object", + "examples": [ + { + "command": "g component my-component --project=mylib", + "description": "Generate a component in the mylib library" + }, + { + "command": "g component my-component --project=mylib --classComponent", + "description": "Generate a class component in the mylib library" + } + ], + "properties": { + "project": { + "type": "string", + "description": "The name of the project.", + "alias": "p", + "$default": { + "$source": "projectName" + }, + "x-prompt": "What is the name of the project for this component?" + }, + "name": { + "type": "string", + "description": "The name of the component.", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the component?" + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files.", + "default": false + }, + "skipTests": { + "type": "boolean", + "description": "When true, does not create \"spec.ts\" test files for the new component.", + "default": false + }, + "directory": { + "type": "string", + "description": "Create the component under this directory (can be nested).", + "alias": "d" + }, + "flat": { + "type": "boolean", + "description": "Create component at the source root rather than its own directory.", + "default": false + }, + "export": { + "type": "boolean", + "description": "When true, the component is exported from the project index.ts (if it exists).", + "alias": "e", + "default": false, + "x-prompt": "Should this component be exported in the project?" + }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case component file name (e.g. App.tsx).", + "alias": "P", + "default": false + }, + "classComponent": { + "type": "boolean", + "alias": "C", + "description": "Use class components instead of functional component.", + "default": false + } + }, + "required": ["name", "project"] +} diff --git a/packages/react-native/src/generators/init/init.spec.ts b/packages/react-native/src/generators/init/init.spec.ts new file mode 100644 index 0000000000000..786aa7bd60b0e --- /dev/null +++ b/packages/react-native/src/generators/init/init.spec.ts @@ -0,0 +1,59 @@ +import { Tree, readJson, updateJson } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { reactNativeInitGenerator } from './init'; + +describe('init', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + tree.write('.gitignore', ''); + }); + + it('should add react native dependencies', async () => { + await reactNativeInitGenerator(tree, { e2eTestRunner: 'none' }); + const packageJson = readJson(tree, 'package.json'); + expect(packageJson.dependencies['react']).toBeDefined(); + expect(packageJson.dependencies['react-native']).toBeDefined(); + expect(packageJson.devDependencies['@types/react']).toBeDefined(); + expect(packageJson.devDependencies['@types/react-native']).toBeDefined(); + }); + + it('should add .gitignore entries for React native files and directories', async () => { + tree.write( + '/.gitignore', + ` +/node_modules +` + ); + await reactNativeInitGenerator(tree, { e2eTestRunner: 'none' }); + + const content = tree.read('/.gitignore').toString(); + + expect(content).toMatch(/# React Native/); + expect(content).toMatch(/# Nested node_modules/); + }); + + describe('defaultCollection', () => { + it('should be set if none was set before', async () => { + await reactNativeInitGenerator(tree, { e2eTestRunner: 'none' }); + const workspaceJson = readJson(tree, 'workspace.json'); + expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/react-native'); + }); + + it('should not be set if something else was set before', async () => { + updateJson(tree, 'workspace.json', (json) => { + json.cli = { + defaultCollection: '@nrwl/react', + }; + + json.targets = {}; + + return json; + }); + await reactNativeInitGenerator(tree, { e2eTestRunner: 'none' }); + const workspaceJson = readJson(tree, 'workspace.json'); + expect(workspaceJson.cli.defaultCollection).toEqual('@nrwl/react'); + }); + }); +}); diff --git a/packages/react-native/src/generators/init/init.ts b/packages/react-native/src/generators/init/init.ts new file mode 100644 index 0000000000000..d7a331f18d04b --- /dev/null +++ b/packages/react-native/src/generators/init/init.ts @@ -0,0 +1,89 @@ +import { setDefaultCollection } from '@nrwl/workspace/src/utilities/set-default-collection'; +import { + addDependenciesToPackageJson, + convertNxGenerator, + formatFiles, + removeDependenciesFromPackageJson, + Tree, +} from '@nrwl/devkit'; +import { Schema } from './schema'; +import { + jestReactNativeVersion, + metroReactNativeBabelPresetVersion, + metroVersion, + nxVersion, + reactNativeCodegenVersion, + reactNativeCommunityCli, + reactNativeCommunityCliAndroid, + reactNativeCommunityCliIos, + reactNativeVersion, + reactTestRendererVersion, + reactVersion, + testingLibraryJestNativeVersion, + testingLibraryReactNativeVersion, + typesReactNativeVersion, + typesReactVersion, +} from '../../utils/versions'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { addGitIgnoreEntry } from './lib/add-git-ignore-entry'; +import { jestInitGenerator } from '@nrwl/jest'; +import { detoxInitGenerator } from '@nrwl/detox'; + +export async function reactNativeInitGenerator(host: Tree, schema: Schema) { + setDefaultCollection(host, '@nrwl/react-native'); + addGitIgnoreEntry(host); + + const tasks = [moveDependency(host), updateDependencies(host)]; + + if (!schema.unitTestRunner || schema.unitTestRunner === 'jest') { + const jestTask = jestInitGenerator(host, {}); + tasks.push(jestTask); + } + + if (!schema.e2eTestRunner || schema.e2eTestRunner === 'detox') { + const detoxTask = await detoxInitGenerator(host, {}); + tasks.push(detoxTask); + } + + if (!schema.skipFormat) { + await formatFiles(host); + } + + return runTasksInSerial(...tasks); +} + +export function updateDependencies(host: Tree) { + return addDependenciesToPackageJson( + host, + { + react: reactVersion, + 'react-native': reactNativeVersion, + }, + { + '@nrwl/react-native': nxVersion, + '@types/react': typesReactVersion, + '@types/react-native': typesReactNativeVersion, + '@react-native-community/cli': reactNativeCommunityCli, + '@react-native-community/cli-platform-android': + reactNativeCommunityCliAndroid, + '@react-native-community/cli-platform-ios': reactNativeCommunityCliIos, + 'metro-react-native-babel-preset': metroReactNativeBabelPresetVersion, + '@testing-library/react-native': testingLibraryReactNativeVersion, + '@testing-library/jest-native': testingLibraryJestNativeVersion, + 'jest-react-native': jestReactNativeVersion, + metro: metroVersion, + 'metro-resolver': metroVersion, + 'react-native-codegen': reactNativeCodegenVersion, + 'react-test-renderer': reactTestRendererVersion, + } + ); +} + +function moveDependency(host: Tree) { + return removeDependenciesFromPackageJson(host, ['@nrwl/react-native'], []); +} + +export default reactNativeInitGenerator; +export const reactNativeInitSchematic = convertNxGenerator( + reactNativeInitGenerator +); diff --git a/packages/react-native/src/generators/init/lib/add-git-ignore-entry.ts b/packages/react-native/src/generators/init/lib/add-git-ignore-entry.ts new file mode 100644 index 0000000000000..d1fcae7717055 --- /dev/null +++ b/packages/react-native/src/generators/init/lib/add-git-ignore-entry.ts @@ -0,0 +1,26 @@ +import { logger, Tree } from '@nrwl/devkit'; +import ignore from 'ignore'; +import { gitIgnoreEntriesForReactNative } from './gitignore-entries'; + +export function addGitIgnoreEntry(host: Tree) { + if (!host.exists('.gitignore')) { + logger.warn(`Couldn't find .gitignore file to update`); + return; + } + + let content = host.read('.gitignore')?.toString('utf-8').trimRight(); + + const ig = ignore(); + ig.add(host.read('.gitignore').toString()); + + if (!ig.ignores('apps/example/ios/Pods/Folly')) { + content = `${content}\n${gitIgnoreEntriesForReactNative}/\n`; + } + + // also ignore nested node_modules folders due to symlink for React Native + if (!ig.ignores('apps/example/node_modules')) { + content = `${content}\n## Nested node_modules\n\nnode_modules/\n`; + } + + host.write('.gitignore', content); +} diff --git a/packages/react-native/src/generators/init/lib/gitignore-entries.ts b/packages/react-native/src/generators/init/lib/gitignore-entries.ts new file mode 100644 index 0000000000000..70dc2a827ae6a --- /dev/null +++ b/packages/react-native/src/generators/init/lib/gitignore-entries.ts @@ -0,0 +1,53 @@ +export const gitIgnoreEntriesForReactNative = ` +# React Native + +## Xcode + +**/ios/**/build/ +**/ios/**/*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate + +## Android + +**/android/**/build/ +**/android/**/.gradle +**/android/**/local.properties +**/android/**/*.iml + +## BUCK + +buck-out/ +\\.buckd/ +*.keystore +!debug.keystore + +## fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ +# +*/fastlane/report.xml +*/fastlane/Preview.html +*/fastlane/screenshots + +## Bundle artifact +*.jsbundle + +## CocoaPods +**/ios/Pods/ +`; diff --git a/packages/react-native/src/generators/init/schema.d.ts b/packages/react-native/src/generators/init/schema.d.ts new file mode 100644 index 0000000000000..11ea540ee8778 --- /dev/null +++ b/packages/react-native/src/generators/init/schema.d.ts @@ -0,0 +1,5 @@ +export interface Schema { + unitTestRunner?: 'jest' | 'none'; + skipFormat?: boolean; + e2eTestRunner?: 'detox' | 'none'; +} diff --git a/packages/react-native/src/generators/init/schema.json b/packages/react-native/src/generators/init/schema.json new file mode 100644 index 0000000000000..69ce55450463f --- /dev/null +++ b/packages/react-native/src/generators/init/schema.json @@ -0,0 +1,27 @@ +{ + "cli": "nx", + "$id": "NxReactNativeInit", + "$schema": "http://json-schema.org/schema", + "title": "Add Nx React Schematics", + "type": "object", + "properties": { + "unitTestRunner": { + "description": "Adds the specified unit test runner", + "type": "string", + "enum": ["jest", "none"], + "default": "jest" + }, + "skipFormat": { + "description": "Skip formatting files", + "type": "boolean", + "default": false + }, + "e2eTestRunner": { + "description": "Adds the specified e2e test runner", + "type": "string", + "enum": ["detox", "none"], + "default": "detox" + } + }, + "required": [] +} diff --git a/packages/react-native/src/generators/library/files/lib/.babelrc__tmpl__ b/packages/react-native/src/generators/library/files/lib/.babelrc__tmpl__ new file mode 100644 index 0000000000000..d4b74b5be7b43 --- /dev/null +++ b/packages/react-native/src/generators/library/files/lib/.babelrc__tmpl__ @@ -0,0 +1,3 @@ +{ + "presets": ["module:metro-react-native-babel-preset"] +} diff --git a/packages/react-native/src/generators/library/files/lib/README.md b/packages/react-native/src/generators/library/files/lib/README.md new file mode 100644 index 0000000000000..b74453ce2e839 --- /dev/null +++ b/packages/react-native/src/generators/library/files/lib/README.md @@ -0,0 +1,7 @@ +# <%= name %> + +This library was generated with [Nx](https://nx.dev). + +## Running unit tests + +Run `nx test <%= name %>` to execute the unit tests via [Jest](https://jestjs.io). diff --git a/packages/react-native/src/generators/library/files/lib/package.json__tmpl__ b/packages/react-native/src/generators/library/files/lib/package.json__tmpl__ new file mode 100644 index 0000000000000..fa518765a372f --- /dev/null +++ b/packages/react-native/src/generators/library/files/lib/package.json__tmpl__ @@ -0,0 +1,4 @@ +{ + "name": "<%= name %>", + "version": "0.0.1" +} diff --git a/packages/react-native/src/generators/library/files/lib/src/index.ts__tmpl__ b/packages/react-native/src/generators/library/files/lib/src/index.ts__tmpl__ new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/packages/react-native/src/generators/library/files/lib/test-setup.ts.template b/packages/react-native/src/generators/library/files/lib/test-setup.ts.template new file mode 100644 index 0000000000000..9f28ad211b736 --- /dev/null +++ b/packages/react-native/src/generators/library/files/lib/test-setup.ts.template @@ -0,0 +1 @@ +import '@testing-library/jest-native/extend-expect'; diff --git a/packages/react-native/src/generators/library/files/lib/tsconfig.json__tmpl__ b/packages/react-native/src/generators/library/files/lib/tsconfig.json__tmpl__ new file mode 100644 index 0000000000000..514efa55d7104 --- /dev/null +++ b/packages/react-native/src/generators/library/files/lib/tsconfig.json__tmpl__ @@ -0,0 +1,16 @@ +{ + "extends": "<%= offsetFromRoot %>tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/react-native/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ b/packages/react-native/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ new file mode 100644 index 0000000000000..566fcd105dd27 --- /dev/null +++ b/packages/react-native/src/generators/library/files/lib/tsconfig.lib.json__tmpl__ @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "<%= offsetFromRoot %>dist/out-tsc", + "types": ["node"] + }, + "exclude": ["**/*.spec.ts", "**/*.spec.tsx"], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/packages/react-native/src/generators/library/lib/normalize-options.ts b/packages/react-native/src/generators/library/lib/normalize-options.ts new file mode 100644 index 0000000000000..3f121a3600653 --- /dev/null +++ b/packages/react-native/src/generators/library/lib/normalize-options.ts @@ -0,0 +1,52 @@ +import { + getWorkspaceLayout, + joinPathFragments, + names, + Tree, +} from '@nrwl/devkit'; +import { Schema } from '../schema'; + +export interface NormalizedSchema extends Schema { + name: string; + fileName: string; + projectRoot: string; + routePath: string; + projectDirectory: string; + parsedTags: string[]; + appMain?: string; + appSourceRoot?: string; +} + +export function normalizeOptions( + host: Tree, + options: Schema +): NormalizedSchema { + const name = names(options.name).fileName; + const projectDirectory = options.directory + ? `${names(options.directory).fileName}/${name}` + : name; + + const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-'); + const fileName = projectName; + const { libsDir, npmScope } = getWorkspaceLayout(host); + const projectRoot = joinPathFragments(`${libsDir}/${projectDirectory}`); + + const parsedTags = options.tags + ? options.tags.split(',').map((s) => s.trim()) + : []; + + const importPath = options.importPath || `@${npmScope}/${projectDirectory}`; + + const normalized: NormalizedSchema = { + ...options, + fileName, + routePath: `/${name}`, + name: projectName, + projectRoot, + projectDirectory, + parsedTags, + importPath, + }; + + return normalized; +} diff --git a/packages/react-native/src/generators/library/library.spec.ts b/packages/react-native/src/generators/library/library.spec.ts new file mode 100644 index 0000000000000..7545fe1a7086c --- /dev/null +++ b/packages/react-native/src/generators/library/library.spec.ts @@ -0,0 +1,341 @@ +import { getProjects, readJson, Tree, updateJson } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import libraryGenerator from './library'; +import { Linter } from '@nrwl/linter'; +import { Schema } from './schema'; + +describe('lib', () => { + let appTree: Tree; + + const defaultSchema: Schema = { + name: 'myLib', + linter: Linter.EsLint, + skipFormat: false, + skipTsConfig: false, + unitTestRunner: 'jest', + strict: true, + }; + + beforeEach(() => { + appTree = createTreeWithEmptyWorkspace(); + appTree.write('.gitignore', ''); + }); + + describe('not nested', () => { + it('should update workspace.json', async () => { + await libraryGenerator(appTree, defaultSchema); + const workspaceJson = readJson(appTree, '/workspace.json'); + expect(workspaceJson.projects['my-lib'].root).toEqual('libs/my-lib'); + expect(workspaceJson.projects['my-lib'].architect.build).toBeUndefined(); + expect(workspaceJson.projects['my-lib'].architect.lint).toEqual({ + builder: '@nrwl/linter:eslint', + options: { + lintFilePatterns: ['libs/my-lib/**/*.{ts,tsx,js,jsx}'], + }, + }); + }); + + it('should update nx.json', async () => { + await libraryGenerator(appTree, { ...defaultSchema, tags: 'one,two' }); + const nxJson = readJson(appTree, '/nx.json'); + expect(nxJson.projects).toEqual({ + 'my-lib': { + tags: ['one', 'two'], + }, + }); + }); + + it('should update tsconfig.base.json', async () => { + await libraryGenerator(appTree, defaultSchema); + const tsconfigJson = readJson(appTree, '/tsconfig.base.json'); + expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([ + 'libs/my-lib/src/index.ts', + ]); + }); + + it('should update root tsconfig.base.json (no existing path mappings)', async () => { + updateJson(appTree, 'tsconfig.base.json', (json) => { + json.compilerOptions.paths = undefined; + return json; + }); + + await libraryGenerator(appTree, defaultSchema); + const tsconfigJson = readJson(appTree, '/tsconfig.base.json'); + expect(tsconfigJson.compilerOptions.paths['@proj/my-lib']).toEqual([ + 'libs/my-lib/src/index.ts', + ]); + }); + + it('should create a local tsconfig.json', async () => { + await libraryGenerator(appTree, defaultSchema); + const tsconfigJson = readJson(appTree, 'libs/my-lib/tsconfig.json'); + expect(tsconfigJson.references).toEqual([ + { + path: './tsconfig.lib.json', + }, + { + path: './tsconfig.spec.json', + }, + ]); + expect( + tsconfigJson.compilerOptions.forceConsistentCasingInFileNames + ).toEqual(true); + expect(tsconfigJson.compilerOptions.strict).toEqual(true); + expect(tsconfigJson.compilerOptions.noImplicitReturns).toEqual(true); + expect(tsconfigJson.compilerOptions.noFallthroughCasesInSwitch).toEqual( + true + ); + }); + + it('should extend the local tsconfig.json with tsconfig.spec.json', async () => { + await libraryGenerator(appTree, defaultSchema); + const tsconfigJson = readJson(appTree, 'libs/my-lib/tsconfig.spec.json'); + expect(tsconfigJson.extends).toEqual('./tsconfig.json'); + }); + + it('should extend the local tsconfig.json with tsconfig.lib.json', async () => { + await libraryGenerator(appTree, defaultSchema); + const tsconfigJson = readJson(appTree, 'libs/my-lib/tsconfig.lib.json'); + expect(tsconfigJson.extends).toEqual('./tsconfig.json'); + }); + }); + + describe('nested', () => { + it('should update nx.json', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + directory: 'myDir', + tags: 'one', + }); + const nxJson = readJson(appTree, '/nx.json'); + expect(nxJson.projects).toEqual({ + 'my-dir-my-lib': { + tags: ['one'], + }, + }); + + await libraryGenerator(appTree, { + ...defaultSchema, + name: 'myLib2', + directory: 'myDir', + tags: 'one,two', + }); + + const nxJson2 = readJson(appTree, '/nx.json'); + expect(nxJson2.projects).toEqual({ + 'my-dir-my-lib': { + tags: ['one'], + }, + 'my-dir-my-lib2': { + tags: ['one', 'two'], + }, + }); + }); + + it('should update workspace.json', async () => { + await libraryGenerator(appTree, { ...defaultSchema, directory: 'myDir' }); + const workspaceJson = readJson(appTree, '/workspace.json'); + + expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual( + 'libs/my-dir/my-lib' + ); + expect(workspaceJson.projects['my-dir-my-lib'].architect.lint).toEqual({ + builder: '@nrwl/linter:eslint', + options: { + lintFilePatterns: ['libs/my-dir/my-lib/**/*.{ts,tsx,js,jsx}'], + }, + }); + }); + + it('should update tsconfig.base.json', async () => { + await libraryGenerator(appTree, { ...defaultSchema, directory: 'myDir' }); + const tsconfigJson = readJson(appTree, '/tsconfig.base.json'); + expect(tsconfigJson.compilerOptions.paths['@proj/my-dir/my-lib']).toEqual( + ['libs/my-dir/my-lib/src/index.ts'] + ); + expect( + tsconfigJson.compilerOptions.paths['my-dir-my-lib/*'] + ).toBeUndefined(); + }); + + it('should create a local tsconfig.json', async () => { + await libraryGenerator(appTree, { ...defaultSchema, directory: 'myDir' }); + + const tsconfigJson = readJson( + appTree, + 'libs/my-dir/my-lib/tsconfig.json' + ); + expect(tsconfigJson.references).toEqual([ + { + path: './tsconfig.lib.json', + }, + { + path: './tsconfig.spec.json', + }, + ]); + }); + }); + + describe('--unit-test-runner none', () => { + it('should not generate test configuration', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + unitTestRunner: 'none', + }); + + expect(appTree.exists('libs/my-lib/tsconfig.spec.json')).toBeFalsy(); + expect(appTree.exists('libs/my-lib/jest.config.js')).toBeFalsy(); + const workspaceJson = readJson(appTree, 'workspace.json'); + expect(workspaceJson.projects['my-lib'].architect.test).toBeUndefined(); + expect(workspaceJson.projects['my-lib'].architect.lint) + .toMatchInlineSnapshot(` + Object { + "builder": "@nrwl/linter:eslint", + "options": Object { + "lintFilePatterns": Array [ + "libs/my-lib/**/*.{ts,tsx,js,jsx}", + ], + }, + } + `); + }); + }); + + describe('--buildable', () => { + it('should have a builder defined', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + buildable: true, + }); + + const workspaceJson = getProjects(appTree); + + expect(workspaceJson.get('my-lib').targets.build).toBeDefined(); + }); + }); + + describe('--publishable', () => { + it('should add build architect', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + publishable: true, + importPath: '@proj/my-lib', + }); + + const workspaceJson = getProjects(appTree); + + expect(workspaceJson.get('my-lib').targets.build).toMatchObject({ + executor: '@nrwl/web:package', + outputs: ['{options.outputPath}'], + options: { + external: ['react/jsx-runtime'], + entryFile: 'libs/my-lib/src/index.ts', + outputPath: 'dist/libs/my-lib', + project: 'libs/my-lib/package.json', + tsConfig: 'libs/my-lib/tsconfig.lib.json', + rollupConfig: '@nrwl/react/plugins/bundle-rollup', + }, + }); + }); + + it('should fail if no importPath is provided with publishable', async () => { + expect.assertions(1); + + try { + await libraryGenerator(appTree, { + ...defaultSchema, + directory: 'myDir', + publishable: true, + }); + } catch (e) { + expect(e.message).toContain( + 'For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)' + ); + } + }); + + it('should add package.json and .babelrc', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + publishable: true, + importPath: '@proj/my-lib', + }); + + const packageJson = readJson(appTree, '/libs/my-lib/package.json'); + expect(packageJson.name).toEqual('@proj/my-lib'); + expect(appTree.exists('/libs/my-lib/.babelrc')); + }); + }); + + describe('--js', () => { + it('should generate JS files', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + js: true, + }); + + expect(appTree.exists('/libs/my-lib/src/index.js')).toBe(true); + }); + }); + + describe('--importPath', () => { + it('should update the package.json & tsconfig with the given import path', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + publishable: true, + directory: 'myDir', + importPath: '@myorg/lib', + }); + const packageJson = readJson(appTree, 'libs/my-dir/my-lib/package.json'); + const tsconfigJson = readJson(appTree, '/tsconfig.base.json'); + + expect(packageJson.name).toBe('@myorg/lib'); + expect( + tsconfigJson.compilerOptions.paths[packageJson.name] + ).toBeDefined(); + }); + + it('should fail if the same importPath has already been used', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + name: 'myLib1', + publishable: true, + importPath: '@myorg/lib', + }); + + try { + await libraryGenerator(appTree, { + ...defaultSchema, + name: 'myLib2', + publishable: true, + importPath: '@myorg/lib', + }); + } catch (e) { + expect(e.message).toContain( + 'You already have a library using the import path' + ); + } + + expect.assertions(1); + }); + }); + + describe('--no-strict', () => { + it('should not add options for strict mode', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + strict: false, + }); + const tsconfigJson = readJson(appTree, '/libs/my-lib/tsconfig.json'); + + expect( + tsconfigJson.compilerOptions.forceConsistentCasingInFileNames + ).not.toBeDefined(); + expect(tsconfigJson.compilerOptions.strict).not.toBeDefined(); + expect(tsconfigJson.compilerOptions.noImplicitReturns).not.toBeDefined(); + expect( + tsconfigJson.compilerOptions.noFallthroughCasesInSwitch + ).not.toBeDefined(); + }); + }); +}); diff --git a/packages/react-native/src/generators/library/library.ts b/packages/react-native/src/generators/library/library.ts new file mode 100644 index 0000000000000..c92ffc4f16cc0 --- /dev/null +++ b/packages/react-native/src/generators/library/library.ts @@ -0,0 +1,193 @@ +import { + addProjectConfiguration, + convertNxGenerator, + formatFiles, + generateFiles, + GeneratorCallback, + getWorkspaceLayout, + joinPathFragments, + names, + offsetFromRoot, + TargetConfiguration, + toJS, + Tree, + updateJson, +} from '@nrwl/devkit'; +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import init from '../init/init'; +import { addLinting } from '../../utils/add-linting'; +import { addJest } from '../../utils/add-jest'; +import { NormalizedSchema, normalizeOptions } from './lib/normalize-options'; +import { Schema } from './schema'; + +export async function reactNativeLibraryGenerator( + host: Tree, + schema: Schema +): Promise { + const options = normalizeOptions(host, schema); + if (options.publishable === true && !schema.importPath) { + throw new Error( + `For publishable libs you have to provide a proper "--importPath" which needs to be a valid npm package name (e.g. my-awesome-lib or @myorg/my-lib)` + ); + } + + addProject(host, options); + createFiles(host, options); + + const initTask = await init(host, { + ...options, + skipFormat: true, + e2eTestRunner: 'none', + }); + + const lintTask = await addLinting( + host, + options.name, + options.projectRoot, + [joinPathFragments(options.projectRoot, 'tsconfig.lib.json')], + options.linter, + options.setParserOptionsProject + ); + + if (!options.skipTsConfig) { + updateBaseTsConfig(host, options); + } + + const jestTask = await addJest( + host, + options.unitTestRunner, + options.name, + options.projectRoot + ); + + if (options.publishable || options.buildable) { + updateLibPackageNpmScope(host, options); + } + + if (!options.skipFormat) { + await formatFiles(host); + } + + return runTasksInSerial(initTask, lintTask, jestTask); +} + +function addProject(host: Tree, options: NormalizedSchema) { + const targets: { [key: string]: TargetConfiguration } = {}; + + if (options.publishable || options.buildable) { + const { libsDir } = getWorkspaceLayout(host); + const external = ['react/jsx-runtime']; + + targets.build = { + executor: '@nrwl/web:package', + outputs: ['{options.outputPath}'], + options: { + outputPath: `dist/${libsDir}/${options.projectDirectory}`, + tsConfig: `${options.projectRoot}/tsconfig.lib.json`, + project: `${options.projectRoot}/package.json`, + entryFile: maybeJs(options, `${options.projectRoot}/src/index.ts`), + external, + rollupConfig: `@nrwl/react/plugins/bundle-rollup`, + assets: [ + { + glob: `${options.projectRoot}/README.md`, + input: '.', + output: '.', + }, + ], + }, + }; + } + + addProjectConfiguration(host, options.name, { + root: options.projectRoot, + sourceRoot: joinPathFragments(options.projectRoot, 'src'), + projectType: 'library', + tags: options.parsedTags, + targets, + }); +} + +function updateTsConfig(tree: Tree, options: NormalizedSchema) { + updateJson( + tree, + joinPathFragments(options.projectRoot, 'tsconfig.json'), + (json) => { + if (options.strict) { + json.compilerOptions = { + ...json.compilerOptions, + forceConsistentCasingInFileNames: true, + strict: true, + noImplicitReturns: true, + noFallthroughCasesInSwitch: true, + }; + } + + return json; + } + ); +} + +function updateBaseTsConfig(host: Tree, options: NormalizedSchema) { + updateJson(host, 'tsconfig.base.json', (json) => { + const c = json.compilerOptions; + c.paths = c.paths || {}; + delete c.paths[options.name]; + + if (c.paths[options.importPath]) { + throw new Error( + `You already have a library using the import path "${options.importPath}". Make sure to specify a unique one.` + ); + } + + const { libsDir } = getWorkspaceLayout(host); + + c.paths[options.importPath] = [ + maybeJs(options, `${libsDir}/${options.projectDirectory}/src/index.ts`), + ]; + + return json; + }); +} + +function createFiles(host: Tree, options: NormalizedSchema) { + generateFiles( + host, + joinPathFragments(__dirname, './files/lib'), + options.projectRoot, + { + ...options, + ...names(options.name), + tmpl: '', + offsetFromRoot: offsetFromRoot(options.projectRoot), + } + ); + + if (!options.publishable && !options.buildable) { + host.delete(`${options.projectRoot}/package.json`); + } + + if (options.js) { + toJS(host); + } + + updateTsConfig(host, options); +} + +function updateLibPackageNpmScope(host: Tree, options: NormalizedSchema) { + return updateJson(host, `${options.projectRoot}/package.json`, (json) => { + json.name = options.importPath; + return json; + }); +} + +function maybeJs(options: NormalizedSchema, path: string): string { + return options.js && (path.endsWith('.ts') || path.endsWith('.tsx')) + ? path.replace(/\.tsx?$/, '.js') + : path; +} + +export default reactNativeLibraryGenerator; +export const reactNativeLibrarySchematic = convertNxGenerator( + reactNativeLibraryGenerator +); diff --git a/packages/react-native/src/generators/library/schema.d.ts b/packages/react-native/src/generators/library/schema.d.ts new file mode 100644 index 0000000000000..0e3f85008eee9 --- /dev/null +++ b/packages/react-native/src/generators/library/schema.d.ts @@ -0,0 +1,22 @@ +import { Linter } from '@nrwl/linter'; + +/** + * Same as the @nrwl/react library schema, except it removes keys: style, component, routing, appProject + */ +export interface Schema { + name: string; + directory?: string; + skipTsConfig: boolean; + skipFormat: boolean; + tags?: string; + pascalCaseFiles?: boolean; + unitTestRunner: 'jest' | 'none'; + linter: Linter; + publishable?: boolean; + buildable?: boolean; + importPath?: string; + js?: boolean; + globalCss?: boolean; + strict?: boolean; + setParserOptionsProject?: boolean; +} diff --git a/packages/react-native/src/generators/library/schema.json b/packages/react-native/src/generators/library/schema.json new file mode 100644 index 0000000000000..5bed94ff0cf1d --- /dev/null +++ b/packages/react-native/src/generators/library/schema.json @@ -0,0 +1,97 @@ +{ + "cli": "nx", + "$id": "NxReactNativeLibrary", + "$schema": "http://json-schema.org/schema", + "title": "Create a React Native Library for Nx", + "type": "object", + "examples": [ + { + "command": "g lib mylib --directory=myapp", + "description": "Generate libs/myapp/mylib" + } + ], + "properties": { + "name": { + "type": "string", + "description": "Library name", + "$default": { + "$source": "argv", + "index": 0 + }, + "x-prompt": "What name would you like to use for the library?", + "pattern": "^[a-zA-Z].*$" + }, + "directory": { + "type": "string", + "description": "A directory where the lib is placed.", + "alias": "d" + }, + "linter": { + "description": "The tool to use for running lint checks.", + "type": "string", + "enum": ["eslint", "tslint"], + "default": "eslint" + }, + "unitTestRunner": { + "type": "string", + "enum": ["jest", "none"], + "description": "Test runner to use for unit tests.", + "default": "jest" + }, + "tags": { + "type": "string", + "description": "Add tags to the library (used for linting).", + "alias": "t" + }, + "skipFormat": { + "description": "Skip formatting files.", + "type": "boolean", + "default": false + }, + "skipTsConfig": { + "type": "boolean", + "default": false, + "description": "Do not update tsconfig.json for development experience." + }, + "pascalCaseFiles": { + "type": "boolean", + "description": "Use pascal case component file name (e.g. App.tsx).", + "alias": "P", + "default": false + }, + "publishable": { + "type": "boolean", + "description": "Create a publishable library." + }, + "buildable": { + "type": "boolean", + "default": false, + "description": "Generate a buildable library." + }, + "importPath": { + "type": "string", + "description": "The library name used to import it, like @myorg/my-awesome-lib" + }, + "js": { + "type": "boolean", + "description": "Generate JavaScript files rather than TypeScript files.", + "default": false + }, + "globalCss": { + "type": "boolean", + "description": "When true, the stylesheet is generated using global CSS instead of CSS modules (e.g. file is '*.css' rather than '*.module.css').", + "default": false + }, + "strict": { + "type": "boolean", + "description": "Whether to enable tsconfig strict mode or not.", + "default": true + }, + "setParserOptionsProject": { + "type": "boolean", + "description": "Whether or not to configure the ESLint \"parserOptions.project\" option. We do not do this by default for lint performance reasons.", + "default": false + } + }, + "required": ["name"] +} diff --git a/packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.spec.ts b/packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.spec.ts new file mode 100644 index 0000000000000..cd5c9753948f6 --- /dev/null +++ b/packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.spec.ts @@ -0,0 +1,100 @@ +import { addProjectConfiguration, formatFiles, Tree } from '@nrwl/devkit'; +import update from './update-jest-for-react-native'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { format } from 'prettier'; + +jest.mock('@nrwl/jest/src/migrations/update-10-0-0/require-jest-config', () => { + return { + getJestObject: () => { + return { + preset: 'react-native', + }; + }, + }; +}); + +describe('update jest for react native 12.5.0', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + + tree.write( + '/apps/products/jest.config.js', + `const workspacePreset = require('../../jest.preset'); +module.exports = { + ...workspacePreset, + preset: 'react-native' +};` + ); + tree.write('/apps/products-2/jest.config.js', `{preset: 'other'}`); + tree.write( + 'apps/products/test-setup.ts', + `import { jest } from '@jest/globals'; +import '@testing-library/jest-native/extend-expect'; +jest.useFakeTimers();` + ); + + addProjectConfiguration(tree, 'products', { + root: 'apps/products', + sourceRoot: 'apps/products/src', + targets: { + test: { + executor: '@nrwl/jest:jest', + options: { + jestConfig: 'apps/products/jest.config.js', + passWithNoTests: true, + }, + }, + }, + }); + + addProjectConfiguration(tree, 'products-2', { + root: 'apps/products-2', + sourceRoot: 'apps/products-2/src', + targets: { + test: { + executor: '@nrwl/jest:jest', + options: { + jestConfig: 'apps/products-2/jest.config.js', + passWithNoTests: true, + }, + }, + }, + }); + }); + + it('should update the jest.config files by removing ...workspacePreset for react-native apps', async () => { + await update(tree); + await formatFiles(tree); + + const jestConfig = tree.read('apps/products/jest.config.js', 'utf-8'); + expect( + format(jestConfig, { + singleQuote: true, + parser: 'typescript', + }) + ).toEqual( + `module.exports = { + resolver: '@nrwl/jest/plugins/resolver', + preset: 'react-native', +}; +` + ); + const testSetup = tree.read('apps/products/test-setup.ts', 'utf-8'); + expect( + format(testSetup, { + singleQuote: true, + parser: 'typescript', + }) + ).toEqual(`import '@testing-library/jest-native/extend-expect'; +`); + }); + + it('should not update the jest.config files ', async () => { + await update(tree); + + const jestConfig = tree.read('apps/products-2/jest.config.js', 'utf-8'); + expect(jestConfig).toEqual(`{preset: 'other'}`); + }); +}); diff --git a/packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.ts b/packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.ts new file mode 100644 index 0000000000000..3a2fb9db505a3 --- /dev/null +++ b/packages/react-native/src/migrations/update-12-5-0/update-jest-for-react-native.ts @@ -0,0 +1,81 @@ +import { + formatFiles, + logger, + readProjectConfiguration, + stripIndents, + Tree, +} from '@nrwl/devkit'; +import { join } from 'path'; +import { forEachExecutorOptions } from '@nrwl/workspace/src/utilities/executor-options-utils'; +import { JestExecutorOptions } from '@nrwl/jest/src/executors/jest/schema'; +import { getJestObject } from '@nrwl/jest/src/migrations/update-10-0-0/require-jest-config'; + +/** + * This function update jest.config.js and test.setup.ts for react native project for Jest 27. + * Inside jest.config.js, remove ...workspacePreset because it has testEnvironment set as jsdom. + * Just set preset as react-native is sufficient. + * Also remove the jest.useFakeTimers() in test.setup.ts. + */ +function updateJestConfig(tree: Tree) { + forEachExecutorOptions( + tree, + '@nrwl/jest:jest', + (options, project) => { + if (!options.jestConfig) { + return; + } + + const jestConfigPath = options.jestConfig; + const jestConfig = getJestObject(join(tree.root, jestConfigPath)); + const testEnvironment = jestConfig.testEnvironment; + const preset = jestConfig.preset; + + if (testEnvironment === 'node' || preset !== 'react-native') { + return; + } + + try { + const contents = tree.read(jestConfigPath, 'utf-8'); + tree.write( + jestConfigPath, + contents + .replace( + `...workspacePreset,`, + "resolver: '@nrwl/jest/plugins/resolver'," + ) + .replace( + `const workspacePreset = require('../../jest.preset');`, + '' + ) + ); + } catch { + logger.error( + stripIndents`Unable to update jest.config.js for project ${project}.` + ); + } + + try { + const { root } = readProjectConfiguration(tree, project); + const setupTestPath = join(root, 'test-setup.ts'); + if (tree.exists(setupTestPath)) { + const contents = tree.read(setupTestPath, 'utf-8'); + tree.write( + setupTestPath, + contents + .replace(`jest.useFakeTimers();`, '') + .replace(`import { jest } from '@jest/globals';`, '') + ); + } + } catch { + logger.error( + stripIndents`Unable to update test-setup.ts for project ${project}.` + ); + } + } + ); +} + +export default async function update(tree: Tree) { + updateJestConfig(tree); + await formatFiles(tree); +} diff --git a/packages/react-native/src/utils/add-jest.ts b/packages/react-native/src/utils/add-jest.ts new file mode 100644 index 0000000000000..57aad1399dc6d --- /dev/null +++ b/packages/react-native/src/utils/add-jest.ts @@ -0,0 +1,40 @@ +import { Tree } from '@nrwl/devkit'; +import { jestProjectGenerator } from '@nrwl/jest'; + +export async function addJest( + host: Tree, + unitTestRunner: 'jest' | 'none', + projectName: string, + appProjectRoot: string +) { + if (unitTestRunner !== 'jest') { + return () => {}; + } + + const jestTask = await jestProjectGenerator(host, { + project: projectName, + supportTsx: true, + skipSerializers: true, + setupFile: 'none', + babelJest: true, + }); + + // overwrite the jest.config.js file because react native needs to have special transform property + const configPath = `${appProjectRoot}/jest.config.js`; + const content = `module.exports = { + displayName: '${projectName}', + preset: 'react-native', + resolver: '@nrwl/jest/plugins/resolver', + moduleFileExtensions: ['ts', 'js', 'html', 'tsx', 'jsx'], + setupFilesAfterEnv: ['/test-setup.ts'], + transform: { + '\\\\.(js|ts|tsx)$': require.resolve('react-native/jest/preprocessor.js'), + '^.+\\\\.(bmp|gif|jpg|jpeg|mp4|png|psd|svg|webp)$': require.resolve( + 'react-native/jest/assetFileTransformer.js', + ), + } +};`; + host.write(configPath, content); + + return jestTask; +} diff --git a/packages/react-native/src/utils/add-linting.spec.ts b/packages/react-native/src/utils/add-linting.spec.ts new file mode 100644 index 0000000000000..c06e679b3260a --- /dev/null +++ b/packages/react-native/src/utils/add-linting.spec.ts @@ -0,0 +1,60 @@ +import { readProjectConfiguration, Tree } from '@nrwl/devkit'; +import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing'; +import { Linter } from '@nrwl/linter'; +import { libraryGenerator } from '@nrwl/workspace/src/generators/library/library'; +import { addLinting } from './add-linting'; + +describe('Add Linting', () => { + let tree: Tree; + + beforeEach(async () => { + tree = createTreeWithEmptyWorkspace(); + await libraryGenerator(tree, { + name: 'my-lib', + linter: Linter.None, + }); + }); + + it('should add update `workspace.json` file properly when eslint is passed', () => { + addLinting( + tree, + 'my-lib', + 'libs/my-lib', + ['libs/my-lib/tsconfig.lib.json'], + Linter.EsLint + ); + const project = readProjectConfiguration(tree, 'my-lib'); + + expect(project.targets.lint).toBeDefined(); + expect(project.targets.lint.executor).toEqual('@nrwl/linter:eslint'); + }); + + it('should add update `workspace.json` file properly when tslint is passed', () => { + addLinting( + tree, + 'my-lib', + 'libs/my-lib', + ['libs/my-lib/tsconfig.lib.json'], + Linter.TsLint + ); + const project = readProjectConfiguration(tree, 'my-lib'); + + expect(project.targets.lint).toBeDefined(); + expect(project.targets.lint.executor).toEqual( + '@angular-devkit/build-angular:tslint' + ); + }); + + it('should not add lint target when "none" is passed', async () => { + addLinting( + tree, + 'my-lib', + 'libs/my-lib', + ['libs/my-lib/tsconfig.lib.json'], + Linter.None + ); + const project = readProjectConfiguration(tree, 'my-lib'); + + expect(project.targets.lint).toBeUndefined(); + }); +}); diff --git a/packages/react-native/src/utils/add-linting.ts b/packages/react-native/src/utils/add-linting.ts new file mode 100644 index 0000000000000..41ba24827f1d4 --- /dev/null +++ b/packages/react-native/src/utils/add-linting.ts @@ -0,0 +1,85 @@ +import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial'; +import { Linter, lintProjectGenerator } from '@nrwl/linter'; +import { + addDependenciesToPackageJson, + joinPathFragments, + updateJson, + Tree, +} from '@nrwl/devkit'; +import { extraEslintDependencies, createReactEslintJson } from '@nrwl/react'; +import type { Linter as ESLintLinter } from 'eslint'; + +export async function addLinting( + host: Tree, + projectName: string, + appProjectRoot: string, + tsConfigPaths: string[], + linter: Linter, + setParserOptionsProject?: boolean +) { + if (linter === Linter.None) { + return () => {}; + } + + const lintTask = await lintProjectGenerator(host, { + linter, + project: projectName, + tsConfigPaths, + eslintFilePatterns: [`${appProjectRoot}/**/*.{ts,tsx,js,jsx}`], + skipFormat: true, + }); + + if (linter === Linter.TsLint) { + return () => {}; + } + + const reactEslintJson = createReactEslintJson( + appProjectRoot, + setParserOptionsProject + ); + + updateJson( + host, + joinPathFragments(appProjectRoot, '.eslintrc.json'), + (json: ESLintLinter.Config) => { + json = reactEslintJson; + json.ignorePatterns = ['!**/*', 'public', '.cache', 'node_modules']; + + for (const override of json.overrides) { + if (!override.files || override.files.length !== 2) { + continue; + } + + // for files ['*.tsx', '*.ts'], add rule '@typescript-eslint/ban-ts-comment': 'off' + if ( + override.files.includes('*.ts') && + override.files.includes('*.tsx') + ) { + override.rules = override.rules || {}; + override.rules['@typescript-eslint/ban-ts-comment'] = 'off'; + continue; + } + + // for files ['*.js', '*.jsx'], add rule '@typescript-eslint/no-var-requires': 'off' + if ( + override.files.includes('*.js') && + override.files.includes('*.jsx') + ) { + override.rules = override.rules || {}; + override.rules['@typescript-eslint/no-var-requires'] = 'off'; + continue; + } + } + + return json; + } + ); + + const installTask = await addDependenciesToPackageJson( + host, + extraEslintDependencies.dependencies, + extraEslintDependencies.devDependencies + ); + + return runTasksInSerial(lintTask, installTask); +} diff --git a/packages/react-native/src/utils/chmod-task.ts b/packages/react-native/src/utils/chmod-task.ts new file mode 100644 index 0000000000000..a71d8b89f59c4 --- /dev/null +++ b/packages/react-native/src/utils/chmod-task.ts @@ -0,0 +1,16 @@ +import { chmodSync } from 'fs'; +import { GeneratorCallback, logger } from '@nrwl/devkit'; + +export function runChmod( + file: string, + mode: number | string +): GeneratorCallback { + return () => { + logger.info(`chmod ${mode} ${file}`); + try { + chmodSync(file, mode); + } catch { + throw new Error(`chmod failed for ${file}`); + } + }; +} diff --git a/packages/react-native/src/utils/ensure-node-modules-symlink.spec.ts b/packages/react-native/src/utils/ensure-node-modules-symlink.spec.ts new file mode 100644 index 0000000000000..d4ef710cff63f --- /dev/null +++ b/packages/react-native/src/utils/ensure-node-modules-symlink.spec.ts @@ -0,0 +1,188 @@ +import { tmpdir } from 'os'; +import { join } from 'path'; +import * as fs from 'fs'; +import { ensureNodeModulesSymlink } from './ensure-node-modules-symlink'; + +const workspaceDir = join(tmpdir(), 'nx-react-native-test'); + +describe('ensureNodeModulesSymlink', () => { + let appDir: string; + + beforeEach(() => { + appDir = join(workspaceDir, 'apps/myapp'); + if (fs.existsSync(workspaceDir)) + fs.rmdirSync(workspaceDir, { recursive: true }); + fs.mkdirSync(workspaceDir); + fs.mkdirSync(appDir, { recursive: true }); + fs.mkdirSync(appDir, { recursive: true }); + fs.writeFileSync( + join(appDir, 'package.json'), + JSON.stringify({ + name: 'myapp', + dependencies: { 'react-native': '*' }, + }) + ); + fs.writeFileSync( + join(workspaceDir, 'package.json'), + JSON.stringify({ + name: 'workspace', + dependencies: { + '@nrwl/react-native': '9999.9.9', + '@react-native-community/cli-platform-ios': '7777.7.7', + '@react-native-community/cli-platform-android': '7777.7.7', + 'react-native': '0.9999.0', + }, + }) + ); + }); + + afterEach(() => { + if (fs.existsSync(workspaceDir)) + fs.rmdirSync(workspaceDir, { recursive: true }); + }); + + it('should create symlinks', () => { + createNpmDirectory('@nrwl/react-native', '9999.9.9'); + createNpmDirectory( + '@react-native-community/cli-platform-android', + '7777.7.7' + ); + createNpmDirectory('@react-native-community/cli-platform-ios', '7777.7.7'); + createNpmDirectory('hermes-engine', '3333.3.3'); + createNpmDirectory('react-native', '0.9999.0'); + createNpmDirectory('jsc-android', '888888.0.0'); + createNpmDirectory('@babel/runtime', '5555.0.0'); + + ensureNodeModulesSymlink(workspaceDir, appDir); + + expectSymlinkToExist('@nrwl/react-native'); + expectSymlinkToExist('react-native'); + expectSymlinkToExist('jsc-android'); + expectSymlinkToExist('hermes-engine'); + expectSymlinkToExist('@react-native-community/cli-platform-ios'); + expectSymlinkToExist('@react-native-community/cli-platform-android'); + expectSymlinkToExist('@babel/runtime'); + }); + + it('should add packages listed in workspace package.json', () => { + fs.writeFileSync( + join(workspaceDir, 'package.json'), + JSON.stringify({ + name: 'workspace', + dependencies: { + random: '9999.9.9', + }, + }) + ); + createNpmDirectory('@nrwl/react-native', '9999.9.9'); + createNpmDirectory( + '@react-native-community/cli-platform-android', + '7777.7.7' + ); + createNpmDirectory('@react-native-community/cli-platform-ios', '7777.7.7'); + createNpmDirectory('hermes-engine', '3333.3.3'); + createNpmDirectory('react-native', '0.9999.0'); + createNpmDirectory('jsc-android', '888888.0.0'); + createNpmDirectory('@babel/runtime', '5555.0.0'); + createNpmDirectory('random', '9999.9.9'); + + ensureNodeModulesSymlink(workspaceDir, appDir); + + expectSymlinkToExist('random'); + }); + + it('should support pnpm', () => { + createPnpmDirectory('@nrwl/react-native', '9999.9.9'); + createPnpmDirectory( + '@react-native-community/cli-platform-android', + '7777.7.7' + ); + createPnpmDirectory('@react-native-community/cli-platform-ios', '7777.7.7'); + createPnpmDirectory('hermes-engine', '3333.3.3'); + createPnpmDirectory('react-native', '0.9999.0'); + createPnpmDirectory('jsc-android', '888888.0.0'); + createPnpmDirectory('@babel/runtime', '5555.0.0'); + + ensureNodeModulesSymlink(workspaceDir, appDir); + + expectSymlinkToExist('react-native'); + expectSymlinkToExist('jsc-android'); + expectSymlinkToExist('hermes-engine'); + expectSymlinkToExist('@react-native-community/cli-platform-ios'); + expectSymlinkToExist('@react-native-community/cli-platform-android'); + expectSymlinkToExist('@babel/runtime'); + }); + + it('should support pnpm with multiple react-native versions', () => { + createPnpmDirectory('@nrwl/react-native', '9999.9.9'); + createPnpmDirectory( + '@react-native-community/cli-platform-android', + '7777.7.7' + ); + createPnpmDirectory('@react-native-community/cli-platform-ios', '7777.7.7'); + createPnpmDirectory('hermes-engine', '3333.3.3'); + createPnpmDirectory('react-native', '0.9999.0'); + createPnpmDirectory('react-native', '0.59.1'); + createPnpmDirectory('jsc-android', '888888.0.0'); + createPnpmDirectory('@babel/runtime', '5555.0.0'); + + ensureNodeModulesSymlink(workspaceDir, appDir); + + expectSymlinkToExist('react-native'); + expectSymlinkToExist('jsc-android'); + expectSymlinkToExist('hermes-engine'); + expectSymlinkToExist('@react-native-community/cli-platform-ios'); + expectSymlinkToExist('@react-native-community/cli-platform-android'); + expectSymlinkToExist('@babel/runtime'); + }); + + it('should throw error if pnpm package cannot be matched', () => { + createPnpmDirectory('@nrwl/react-native', '9999.9.9'); + createPnpmDirectory( + '@react-native-community/cli-platform-android', + '7777.7.7' + ); + createPnpmDirectory('@react-native-community/cli-platform-ios', '7777.7.7'); + createPnpmDirectory('hermes-engine', '3333.3.3'); + createPnpmDirectory('jsc-android', '888888.0.0'); + createPnpmDirectory('react-native', '0.60.1'); + createPnpmDirectory('react-native', '0.59.1'); + createPnpmDirectory('@babel/runtime', '5555.0.0'); + + expect(() => { + ensureNodeModulesSymlink(workspaceDir, appDir); + }).toThrow(/Cannot find/); + }); + + function createPnpmDirectory(packageName, version) { + const dir = join( + workspaceDir, + `node_modules/.pnpm/${packageName.replace( + '/', + '+' + )}@${version}/node_modules/${packageName}` + ); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync( + join(dir, 'package.json'), + JSON.stringify({ name: packageName, version: version }) + ); + return dir; + } + + function createNpmDirectory(packageName, version) { + const dir = join(workspaceDir, `node_modules/${packageName}`); + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync( + join(dir, 'package.json'), + JSON.stringify({ name: packageName, version: version }) + ); + return dir; + } + + function expectSymlinkToExist(packageName) { + expect( + fs.existsSync(join(appDir, `node_modules/${packageName}/package.json`)) + ).toBe(true); + } +}); diff --git a/packages/react-native/src/utils/ensure-node-modules-symlink.ts b/packages/react-native/src/utils/ensure-node-modules-symlink.ts new file mode 100644 index 0000000000000..a4da761ad6ed6 --- /dev/null +++ b/packages/react-native/src/utils/ensure-node-modules-symlink.ts @@ -0,0 +1,144 @@ +import { join } from 'path'; +import { platform } from 'os'; +import * as fs from 'fs'; +import { + createDirectory, + readJsonFile, +} from '@nrwl/workspace/src/utilities/fileutils'; +import chalk = require('chalk'); + +const requiredPackages = [ + 'react-native', + 'jsc-android', + '@react-native-community/cli-platform-ios', + '@react-native-community/cli-platform-android', + 'hermes-engine', + '@nrwl/react-native', + '@babel/runtime', +]; + +/** + * This function symlink workspace node_modules folder with app project's node_mdules folder. + * For yarn and npm, it will symlink the entire node_modules folder. + * If app project's node_modules already exist, it will remove it first then symlink it. + * For pnpm, it will go through the package.json' dependencies and devDependencies, and also the required packages listed above. + * @param workspaceRoot path of the workspace root + * @param projectRoot path of app project root + */ +export function ensureNodeModulesSymlink( + workspaceRoot: string, + projectRoot: string +): void { + const worksapceNodeModulesPath = join(workspaceRoot, 'node_modules'); + if (!fs.existsSync(worksapceNodeModulesPath)) { + throw new Error(`Cannot find ${worksapceNodeModulesPath}`); + } + + const appNodeModulesPath = join(projectRoot, 'node_modules'); + // `mklink /D` requires admin privilege in Windows so we need to use junction + const symlinkType = platform() === 'win32' ? 'junction' : 'dir'; + + if (fs.existsSync(appNodeModulesPath)) { + fs.rmdirSync(appNodeModulesPath, { recursive: true }); + } + fs.symlinkSync(worksapceNodeModulesPath, appNodeModulesPath, symlinkType); + + if (isPnpm(workspaceRoot)) { + symlinkPnpm(workspaceRoot, appNodeModulesPath, symlinkType); + } +} + +function isPnpm(workspaceRoot: string): boolean { + const pnpmDir = join(workspaceRoot, 'node_modules/.pnpm'); + return fs.existsSync(pnpmDir); +} + +function symlinkPnpm( + workspaceRoot: string, + appNodeModulesPath: string, + symlinkType: 'junction' | 'dir' +) { + const worksapcePackageJsonPath = join(workspaceRoot, 'package.json'); + const workspacePackageJson = readJsonFile(worksapcePackageJsonPath); + const workspacePackages = Object.keys({ + ...workspacePackageJson.dependencies, + ...workspacePackageJson.devDependencies, + }); + + const packagesToSymlink = new Set([ + ...workspacePackages, + ...requiredPackages, + ]); + + createDirectory(appNodeModulesPath); + + packagesToSymlink.forEach((p) => { + const dir = join(appNodeModulesPath, p); + if (!fs.existsSync(dir)) { + if (isScopedPackage(p)) + createDirectory(join(appNodeModulesPath, getScopedData(p).scope)); + fs.symlinkSync(locateNpmPackage(workspaceRoot, p), dir, symlinkType); + } + if (!fs.existsSync(join(dir, 'package.json'))) { + throw new Error( + `Invalid symlink ${chalk.bold(dir)}. Remove ${chalk.bold( + appNodeModulesPath + )} and try again.` + ); + } + }); +} + +function locateNpmPackage(workspaceRoot: string, packageName: string): string { + const pnpmDir = join(workspaceRoot, 'node_modules/.pnpm'); + + let candidates: string[]; + if (isScopedPackage(packageName)) { + const { scope, name } = getScopedData(packageName); + candidates = fs + .readdirSync(pnpmDir) + .filter( + (f) => + f.startsWith(`${scope}+${name}`) && + fs.lstatSync(join(pnpmDir, f)).isDirectory() + ); + } else { + candidates = fs + .readdirSync(pnpmDir) + .filter( + (f) => + f.startsWith(packageName) && + fs.lstatSync(join(pnpmDir, f)).isDirectory() + ); + } + + if (candidates.length === 0) { + throw new Error(`Could not locate pnpm directory for ${packageName}`); + } else if (candidates.length === 1) { + return join(pnpmDir, candidates[0], 'node_modules', packageName); + } else { + const packageJson = readJsonFile(join(workspaceRoot, 'package.json')); + const deps = { + ...packageJson.dependencies, + ...packageJson.devDependencies, + }; + const version = deps[packageName]; + const found = candidates.find((c) => c.includes(version)); + if (found) { + return join(pnpmDir, found, 'node_modules', packageName); + } else { + throw new Error( + `Cannot find ${packageName}@${version}. Install it with 'pnpm install --save ${packageName}@${version}'.` + ); + } + } +} + +function isScopedPackage(p) { + return p.startsWith('@'); +} + +function getScopedData(p) { + const [scope, name] = p.split('/'); + return { scope, name }; +} diff --git a/packages/react-native/src/utils/find-all-npm-dependencies.spec.ts b/packages/react-native/src/utils/find-all-npm-dependencies.spec.ts new file mode 100644 index 0000000000000..f5c422dd24bfd --- /dev/null +++ b/packages/react-native/src/utils/find-all-npm-dependencies.spec.ts @@ -0,0 +1,93 @@ +import { findAllNpmDependencies } from './find-all-npm-dependencies'; +import { ProjectGraph } from '@nrwl/workspace/src/core/project-graph'; + +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: [] }, + }, + 'npm:react-native-image-picker': { + type: 'npm', + name: 'npm:react-native-image-picker', + data: { + files: [], + packageName: 'react-native-image-picker', + }, + }, + 'npm:react-native-dialog': { + type: 'npm', + name: 'npm:react-native-dialog', + data: { + files: [], + packageName: 'react-native-dialog', + }, + }, + 'npm:react-native-snackbar': { + type: 'npm', + name: 'npm:react-native-snackbar', + data: { + files: [], + packageName: 'react-native-snackbar', + }, + }, + 'npm:@nrwl/react-native': { + type: 'npm', + name: 'npm:@nrwl/react-native', + data: { + files: [], + packageName: '@nrwl/react-native', + }, + }, + }, + dependencies: { + myapp: [ + { type: 'static', source: 'myapp', target: 'lib1' }, + { type: 'static', source: 'myapp', target: 'lib2' }, + { + type: 'static', + source: 'myapp', + target: 'npm:react-native-image-picker', + }, + { + type: 'static', + source: 'myapp', + target: 'npm:@nrwl/react-native', + }, + ], + lib1: [ + { type: 'static', source: 'lib1', target: 'lib2' }, + { type: 'static', source: 'lib3', target: 'npm:react-native-snackbar' }, + ], + lib2: [{ type: 'static', source: 'lib2', target: 'lib3' }], + lib3: [ + { type: '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', + ]); +}); diff --git a/packages/react-native/src/utils/find-all-npm-dependencies.ts b/packages/react-native/src/utils/find-all-npm-dependencies.ts new file mode 100644 index 0000000000000..76bc47102f6c1 --- /dev/null +++ b/packages/react-native/src/utils/find-all-npm-dependencies.ts @@ -0,0 +1,25 @@ +import { ProjectGraph } from '@nrwl/workspace/src/core/project-graph'; + +export function findAllNpmDependencies( + graph: ProjectGraph, + projectName: string, + list: string[] = [], + seen = new Set() +) { + // In case of bad circular dependencies + if (seen.has(projectName)) return list; + seen.add(projectName); + + const node = graph.nodes[projectName]; + + // Don't want to include '@nrwl/react-native' because React Native + // autolink will warn that the package has no podspec file for iOS. + if (node.type === 'npm' && node.name !== 'npm:@nrwl/react-native') + list.push(node.data.packageName); + + graph.dependencies[projectName]?.forEach((dep) => + findAllNpmDependencies(graph, dep.target, list, seen) + ); + + return list; +} diff --git a/packages/react-native/src/utils/pod-install-task.ts b/packages/react-native/src/utils/pod-install-task.ts new file mode 100644 index 0000000000000..291b44341ca63 --- /dev/null +++ b/packages/react-native/src/utils/pod-install-task.ts @@ -0,0 +1,54 @@ +import { spawn } from 'child_process'; +import { platform } from 'os'; +import * as chalk from 'chalk'; +import { GeneratorCallback, logger } from '@nrwl/devkit'; + +const podInstallErrorMessage = ` +Running ${chalk.bold('pod install')} failed, see above. +Do you have CocoaPods (https://cocoapods.org/) installed? + +Check that your XCode path is correct: +${chalk.bold('sudo xcode-select --print-path')} + +If the path is wrong, switch the path: (your path may be different) +${chalk.bold('sudo xcode-select --switch /Applications/Xcode.app')} +`; + +/** + * Run pod install on current working directory + * @param cwd current working directory + * @returns resolve with 0 if not error, reject with error otherwise + */ +export function runPodInstall(cwd: string): GeneratorCallback { + return () => { + if (platform() !== 'darwin') { + logger.info('Skipping `pod install` on non-darwin platform'); + return; + } + + logger.info(`Running \`pod install\` from "${cwd}"`); + + return podInstall(cwd); + }; +} + +export function podInstall(cwd: string): Promise { + return new Promise((resolve, reject) => { + const process = spawn('pod', ['install'], { + cwd, + stdio: [0, 1, 2], + }); + + process.on('close', (code: number) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(podInstallErrorMessage)); + } + }); + + process.on('error', () => { + reject(new Error(podInstallErrorMessage)); + }); + }); +} diff --git a/packages/react-native/src/utils/symlink-task.ts b/packages/react-native/src/utils/symlink-task.ts new file mode 100644 index 0000000000000..d3e85f10d1eb5 --- /dev/null +++ b/packages/react-native/src/utils/symlink-task.ts @@ -0,0 +1,17 @@ +import { ensureNodeModulesSymlink } from './ensure-node-modules-symlink'; +import * as chalk from 'chalk'; +import { appRootPath } from '@nrwl/workspace/src/utils/app-root'; +import { GeneratorCallback, logger } from '@nrwl/devkit'; + +export function runSymlink(projectRoot: string): GeneratorCallback { + return () => { + logger.info(`creating symlinks for ${chalk.bold(projectRoot)}`); + try { + ensureNodeModulesSymlink(appRootPath, projectRoot); + } catch { + throw new Error( + `Failed to create symlinks for ${chalk.bold(projectRoot)}` + ); + } + }; +} diff --git a/packages/react-native/src/utils/testing-generators.ts b/packages/react-native/src/utils/testing-generators.ts new file mode 100644 index 0000000000000..89fe1f652a1cb --- /dev/null +++ b/packages/react-native/src/utils/testing-generators.ts @@ -0,0 +1,28 @@ +import { addProjectConfiguration, names, Tree } from '@nrwl/devkit'; +import applicationGenerator from '../generators/application/application'; +import { Linter } from '@nrwl/linter'; + +export async function createApp(tree: Tree, appName: string): Promise { + await applicationGenerator(tree, { + linter: Linter.EsLint, + skipFormat: true, + style: 'css', + unitTestRunner: 'none', + name: appName, + e2eTestRunner: 'none', + }); +} + +export async function createLib(tree: Tree, libName: string): Promise { + const { fileName } = names(libName); + + tree.write(`/libs/${fileName}/src/index.ts`, `import React from 'react';\n`); + + addProjectConfiguration(tree, fileName, { + tags: [], + root: `libs/${fileName}`, + projectType: 'library', + sourceRoot: `libs/${fileName}/src`, + targets: {}, + }); +} diff --git a/packages/react-native/src/utils/versions.ts b/packages/react-native/src/utils/versions.ts new file mode 100644 index 0000000000000..e17f9132a4cf6 --- /dev/null +++ b/packages/react-native/src/utils/versions.ts @@ -0,0 +1,23 @@ +export const nxVersion = '*'; + +export const reactVersion = '17.0.2'; +export const typesReactVersion = '17.0.19'; + +export const reactNativeVersion = '0.65.1'; +export const typesReactNativeVersion = '0.64.13'; + +export const metroVersion = '0.66.2'; + +export const reactNativeCommunityCli = '6.0.0'; +export const reactNativeCommunityCliIos = '6.0.0'; +export const reactNativeCommunityCliAndroid = '6.0.0'; + +export const metroReactNativeBabelPresetVersion = '0.66.2'; + +export const testingLibraryReactNativeVersion = '8.0.0-rc.0'; +export const testingLibraryJestNativeVersion = '4.0.2'; + +export const jestReactNativeVersion = '18.0.0'; + +export const reactTestRendererVersion = '17.0.2'; +export const reactNativeCodegenVersion = '0.0.7'; diff --git a/packages/react-native/tsconfig.json b/packages/react-native/tsconfig.json new file mode 100644 index 0000000000000..62ebbd946474c --- /dev/null +++ b/packages/react-native/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/react-native/tsconfig.lib.json b/packages/react-native/tsconfig.lib.json new file mode 100644 index 0000000000000..037d796f28623 --- /dev/null +++ b/packages/react-native/tsconfig.lib.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "commonjs", + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "exclude": ["**/*.spec.ts"], + "include": ["**/*.ts"] +} diff --git a/packages/react-native/tsconfig.spec.json b/packages/react-native/tsconfig.spec.json new file mode 100644 index 0000000000000..559410b96af67 --- /dev/null +++ b/packages/react-native/tsconfig.spec.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "**/*.spec.ts", + "**/*.spec.tsx", + "**/*.spec.js", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/packages/tao/src/commands/migrate.ts b/packages/tao/src/commands/migrate.ts index c81dbf4d0e659..e817ee1b13b57 100644 --- a/packages/tao/src/commands/migrate.ts +++ b/packages/tao/src/commands/migrate.ts @@ -239,6 +239,8 @@ export class Migrator { '@nrwl/storybook', '@nrwl/tao', '@nrwl/web', + '@nrwl/react-native', + '@nrwl/detox', ] .filter((pkg) => { const { dependencies, devDependencies } = this.packageJson; diff --git a/packages/workspace/src/generators/new/new.ts b/packages/workspace/src/generators/new/new.ts index 4b16250a70145..30f207a5bb56b 100644 --- a/packages/workspace/src/generators/new/new.ts +++ b/packages/workspace/src/generators/new/new.ts @@ -252,6 +252,12 @@ const presetDependencies: Omit< '@nrwl/gatsby': nxVersion, }, }, + [Preset.ReactNative]: { + dependencies: {}, + dev: { + '@nrwl/react-native': nxVersion, + }, + }, }; function addPresetDependencies(host: Tree, options: Schema) { diff --git a/packages/workspace/src/generators/preset/preset.spec.ts b/packages/workspace/src/generators/preset/preset.spec.ts index ba35db5dc59cc..b67d754b2fe73 100644 --- a/packages/workspace/src/generators/preset/preset.spec.ts +++ b/packages/workspace/src/generators/preset/preset.spec.ts @@ -166,4 +166,19 @@ describe('preset', () => { '@nrwl/gatsby' ); }); + + it('should create files (preset = react-native)', async () => { + await presetGenerator(tree, { + name: 'proj', + preset: Preset.ReactNative, + linter: 'eslint', + cli: 'nx', + standaloneConfig: false, + }); + + expect(tree.exists('/apps/proj/src/app/App.tsx')).toBe(true); + expect(readJson(tree, '/workspace.json').cli.defaultCollection).toBe( + '@nrwl/react-native' + ); + }); }); diff --git a/packages/workspace/src/generators/preset/preset.ts b/packages/workspace/src/generators/preset/preset.ts index 6e6cdc5f14f29..966a7005f7747 100644 --- a/packages/workspace/src/generators/preset/preset.ts +++ b/packages/workspace/src/generators/preset/preset.ts @@ -177,6 +177,16 @@ async function createPreset(tree: Tree, options: Schema) { standaloneConfig: options.standaloneConfig, }); setDefaultCollection(tree, '@nrwl/gatsby'); + } else if (options.preset === 'react-native') { + const { reactNativeApplicationGenerator } = require('@nrwl' + + '/react-native'); + await reactNativeApplicationGenerator(tree, { + name: options.name, + linter: options.linter, + standaloneConfig: options.standaloneConfig, + e2eTestRunner: 'detox', + }); + setDefaultCollection(tree, '@nrwl/react-native'); } else { throw new Error(`Invalid preset ${options.preset}`); } diff --git a/packages/workspace/src/generators/utils/presets.ts b/packages/workspace/src/generators/utils/presets.ts index aec4e9060e074..41f24cf961db5 100644 --- a/packages/workspace/src/generators/utils/presets.ts +++ b/packages/workspace/src/generators/utils/presets.ts @@ -10,4 +10,5 @@ export enum Preset { Gatsby = 'gatsby', Nest = 'nest', Express = 'express', + ReactNative = 'react-native', } diff --git a/scripts/check-versions.ts b/scripts/check-versions.ts index 9622091b5102a..839002d74719f 100644 --- a/scripts/check-versions.ts +++ b/scripts/check-versions.ts @@ -26,7 +26,6 @@ const scoped = [ 'reduxjs', 'testing-library', 'types', - 'zeit', ]; diff --git a/scripts/e2e-build-package-publish.ts b/scripts/e2e-build-package-publish.ts index e8480c7c67d45..f52189f0ba0dd 100644 --- a/scripts/e2e-build-package-publish.ts +++ b/scripts/e2e-build-package-publish.ts @@ -109,6 +109,8 @@ function build(nxVersion: string) { 'storybook', 'angular', 'workspace', + 'react-native', + 'detox', ].map((f) => `${f}/src/utils/versions.js`), ...[ 'react', @@ -131,6 +133,8 @@ function build(nxVersion: string) { 'create-nx-workspace', 'create-nx-plugin', 'nx-plugin', + 'react-native', + 'detox', ].map((f) => `${f}/package.json`), 'create-nx-workspace/bin/create-nx-workspace.js', 'create-nx-plugin/bin/create-nx-plugin.js', diff --git a/scripts/nx-release.js b/scripts/nx-release.js index c4403852186b8..7096087aa2ba7 100755 --- a/scripts/nx-release.js +++ b/scripts/nx-release.js @@ -71,6 +71,8 @@ function updatePackageJsonFiles(parsedVersion, isLocal) { 'build/npm/linter/package.json', 'build/npm/nx-plugin/package.json', 'build/npm/nx/package.json', + 'build/npm/react-native/package.json', + 'build/npm/detox/package.json', ]; if (isLocal) { pkgFiles = pkgFiles.filter((f) => f !== 'package.json'); diff --git a/scripts/package.sh b/scripts/package.sh index 4e63b7390e3d0..3c24c69e6ee39 100755 --- a/scripts/package.sh +++ b/scripts/package.sh @@ -18,8 +18,8 @@ npx nx run-many --target=build --all --parallel || { echo 'Build failed' ; exit cd build/packages if [[ "$OSTYPE" == "darwin"* ]]; then - sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin}/src/utils/versions.js - sed -i "" "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin}/package.json + sed -i "" "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox}/src/utils/versions.js + sed -i "" "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json sed -i "" "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js @@ -29,8 +29,8 @@ if [[ "$OSTYPE" == "darwin"* ]]; then sed -i "" "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js sed -i "" "s|PRETTIER_VERSION|$PRETTIER_VERSION|g" create-nx-plugin/bin/create-nx-plugin.js else - sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin}/src/utils/versions.js - sed -i "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin}/package.json + sed -i "s|exports.nxVersion = '\*';|exports.nxVersion = '$NX_VERSION';|g" {react,next,gatsby,web,jest,node,linter,express,nest,cypress,storybook,angular,workspace,nx-plugin,react-native,detox}/src/utils/versions.js + sed -i "s|\*|$NX_VERSION|g" {react,next,gatsby,web,jest,node,express,nest,cypress,storybook,angular,workspace,cli,linter,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json sed -i "s|NX_VERSION|$NX_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|ANGULAR_CLI_VERSION|$ANGULAR_CLI_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js sed -i "s|TYPESCRIPT_VERSION|$TYPESCRIPT_VERSION|g" create-nx-workspace/bin/create-nx-workspace.js @@ -43,9 +43,9 @@ fi if [[ $NX_VERSION == "*" ]]; then if [[ "$OSTYPE" == "darwin"* ]]; then - sed -E -i "" "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {jest,web,react,next,gatsby,node,express,nest,cypress,storybook,angular,workspace,linter,cli,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin}/package.json + sed -E -i "" "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {jest,web,react,next,gatsby,node,express,nest,cypress,storybook,angular,workspace,linter,cli,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json else echo $PWD - sed -E -i "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {jest,web,react,next,gatsby,node,express,nest,cypress,storybook,angular,workspace,linter,cli,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin}/package.json + sed -E -i "s|\"@nrwl\/([^\"]+)\": \"\\*\"|\"@nrwl\/\1\": \"file:$PWD\/\1\"|" {jest,web,react,next,gatsby,node,express,nest,cypress,storybook,angular,workspace,linter,cli,tao,devkit,eslint-plugin-nx,create-nx-workspace,create-nx-plugin,nx-plugin,react-native,detox}/package.json fi fi diff --git a/tsconfig.base.json b/tsconfig.base.json index 1709c9f6e89e4..06daed4a737ea 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -22,6 +22,7 @@ "@nrwl/cli/*": ["./packages/cli/*"], "@nrwl/cypress": ["./packages/cypress"], "@nrwl/cypress/*": ["./packages/cypress/*"], + "@nrwl/detox": ["./packages/detox"], "@nrwl/devkit": ["./packages/devkit"], "@nrwl/devkit/ngcli-adapter": ["./packages/devkit/ngcli-adapter"], "@nrwl/devkit/testing": ["./packages/devkit/testing"], @@ -50,6 +51,7 @@ "@nrwl/nx-dev/ui/common": ["./nx-dev/ui/common/src/index.ts"], "@nrwl/nx-dev/ui/member-card": ["./nx-dev/ui/member-card/src/index.ts"], "@nrwl/react": ["./packages/react"], + "@nrwl/react-native": ["./packages/react-native"], "@nrwl/react/*": ["./packages/react/*"], "@nrwl/storybook": ["./packages/storybook"], "@nrwl/tao": ["./packages/tao"], diff --git a/workspace.json b/workspace.json index e79aa74076b9c..8ad41ade576ac 100644 --- a/workspace.json +++ b/workspace.json @@ -18,12 +18,14 @@ "cypress": "packages/cypress", "dep-graph-dep-graph": "dep-graph/dep-graph", "dep-graph-dep-graph-e2e": "dep-graph/dep-graph-e2e", + "detox": "packages/detox", "devkit": "packages/devkit", "docs": "docs", "e2e-angular-core": "e2e/angular-core", "e2e-angular-extensions": "e2e/angular-extensions", "e2e-cli": "e2e/cli", "e2e-cypress": "e2e/cypress", + "e2e-detox": "e2e/detox", "e2e-gatsby": "e2e/gatsby", "e2e-jest": "e2e/jest", "e2e-linter": "e2e/linter", @@ -31,6 +33,7 @@ "e2e-node": "e2e/node", "e2e-nx-plugin": "e2e/nx-plugin", "e2e-react": "e2e/react", + "e2e-react-native": "e2e/react-native", "e2e-storybook": "e2e/storybook", "e2e-utils": "e2e/utils", "e2e-web": "e2e/web", @@ -55,6 +58,7 @@ "nx-dev-ui-member-card": "nx-dev/ui/member-card", "nx-plugin": "packages/nx-plugin", "react": "packages/react", + "react-native": "packages/react-native", "storybook": "packages/storybook", "tao": "packages/tao", "typedoc-theme": "typedoc-theme", diff --git a/yarn.lock b/yarn.lock index f73f9be153857..b72ec48c23b28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6996,6 +6996,11 @@ abbrev@1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== +absolute-path@^0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/absolute-path/-/absolute-path-0.0.0.tgz#a78762fbdadfb5297be99b15d35a785b2f095bf7" + integrity sha1-p4di+9rftSl76ZsV01p4Wy8JW/c= + accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: version "1.3.7" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" @@ -17681,6 +17686,13 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= +metro-resolver@^0.66.2: + version "0.66.2" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.66.2.tgz#f743ddbe7a12dd137d1f7a555732cafcaea421f8" + integrity sha512-pXQAJR/xauRf4kWFj2/hN5a77B4jLl0Fom5I3PHp6Arw/KxSBp0cnguXpGLwNQ6zQC0nxKCoYGL9gQpzMnN7Hw== + dependencies: + absolute-path "^0.0.0" + microevent.ts@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0"