feat(react-native): add convert-to-inferred generator for Expo and React Native (#27326)
This PR adds `convert-to-inferred` generators to convert React Native an Expo apps using executors to use the corresponding inference plugins. Also: 1. Fixes casing for `@nx/react-native/plugin` so it is correctly set as `upgradeTargetName` not `upgradeTargetname` 2. Migration for the above fix for existing projects
This commit is contained in:
parent
2ce679755f
commit
d3747e020f
@ -7251,6 +7251,14 @@
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "convert-to-inferred",
|
||||
"path": "/nx-api/detox/generators/convert-to-inferred",
|
||||
"name": "convert-to-inferred",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@ -7659,6 +7667,14 @@
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "convert-to-inferred",
|
||||
"path": "/nx-api/expo/generators/convert-to-inferred",
|
||||
"name": "convert-to-inferred",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
@ -9233,6 +9249,14 @@
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
},
|
||||
{
|
||||
"id": "convert-to-inferred",
|
||||
"path": "/nx-api/react-native/generators/convert-to-inferred",
|
||||
"name": "convert-to-inferred",
|
||||
"children": [],
|
||||
"isExternal": false,
|
||||
"disableCollapsible": false
|
||||
}
|
||||
],
|
||||
"isExternal": false,
|
||||
|
||||
@ -607,6 +607,15 @@
|
||||
"originalFilePath": "/packages/detox/src/generators/application/schema.json",
|
||||
"path": "/nx-api/detox/generators/application",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/detox/generators/convert-to-inferred": {
|
||||
"description": "Convert existing Detox project(s) using `@nx/detox:*` executors to use `@nx/detox/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/detox/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/detox/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "/nx-api/detox/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/detox"
|
||||
@ -991,6 +1000,15 @@
|
||||
"originalFilePath": "/packages/expo/src/generators/component/schema.json",
|
||||
"path": "/nx-api/expo/generators/component",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/expo/generators/convert-to-inferred": {
|
||||
"description": "Convert existing Expo project(s) using `@nx/expo:*` executors to use `@nx/expo/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/expo/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/expo/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "/nx-api/expo/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/expo"
|
||||
@ -2553,6 +2571,15 @@
|
||||
"originalFilePath": "/packages/react-native/src/generators/web-configuration/schema.json",
|
||||
"path": "/nx-api/react-native/generators/web-configuration",
|
||||
"type": "generator"
|
||||
},
|
||||
"/nx-api/react-native/generators/convert-to-inferred": {
|
||||
"description": "Convert existing React Native project(s) using `@nx/react-native:*` executors to use `@nx/react-native/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/react-native/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/react-native/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "/nx-api/react-native/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
},
|
||||
"path": "/nx-api/react-native"
|
||||
|
||||
@ -598,6 +598,15 @@
|
||||
"originalFilePath": "/packages/detox/src/generators/application/schema.json",
|
||||
"path": "detox/generators/application",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Convert existing Detox project(s) using `@nx/detox:*` executors to use `@nx/detox/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/detox/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/detox/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "detox/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
@ -977,6 +986,15 @@
|
||||
"originalFilePath": "/packages/expo/src/generators/component/schema.json",
|
||||
"path": "expo/generators/component",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Convert existing Expo project(s) using `@nx/expo:*` executors to use `@nx/expo/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/expo/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/expo/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "expo/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
@ -2526,6 +2544,15 @@
|
||||
"originalFilePath": "/packages/react-native/src/generators/web-configuration/schema.json",
|
||||
"path": "react-native/generators/web-configuration",
|
||||
"type": "generator"
|
||||
},
|
||||
{
|
||||
"description": "Convert existing React Native project(s) using `@nx/react-native:*` executors to use `@nx/react-native/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"file": "generated/packages/react-native/generators/convert-to-inferred.json",
|
||||
"hidden": false,
|
||||
"name": "convert-to-inferred",
|
||||
"originalFilePath": "/packages/react-native/src/generators/convert-to-inferred/schema.json",
|
||||
"path": "react-native/generators/convert-to-inferred",
|
||||
"type": "generator"
|
||||
}
|
||||
],
|
||||
"githubRoot": "https://github.com/nrwl/nx/blob/master",
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "convert-to-inferred",
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxDetoxConvertToInferred",
|
||||
"description": "Convert existing Detox project(s) using `@nx/detox:*` executors to use `@nx/detox/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert Detox project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/detox:*` executors to use `@nx/detox/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Convert existing Detox project(s) using `@nx/detox:*` executors to use `@nx/detox/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"implementation": "/packages/detox/src/generators/convert-to-inferred/convert-to-inferred.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/detox/src/generators/convert-to-inferred/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "convert-to-inferred",
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxExpoConvertToInferred",
|
||||
"description": "Convert existing Expo project(s) using `@nx/expo:*` executors to use `@nx/expo/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert Expo project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/expo:*` executors to use `@nx/expo/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Convert existing Expo project(s) using `@nx/expo:*` executors to use `@nx/expo/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"implementation": "/packages/expo/src/generators/convert-to-inferred/convert-to-inferred.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/expo/src/generators/convert-to-inferred/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "convert-to-inferred",
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": {
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxReactNativeConvertToInferred",
|
||||
"description": "Convert existing React Native project(s) using `@nx/react-native:*` executors to use `@nx/react-native/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert React Native project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/react-native:*` executors to use `@nx/react-native/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
},
|
||||
"presets": []
|
||||
},
|
||||
"description": "Convert existing React Native project(s) using `@nx/react-native:*` executors to use `@nx/react-native/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"implementation": "/packages/react-native/src/generators/convert-to-inferred/convert-to-inferred.ts",
|
||||
"aliases": [],
|
||||
"hidden": false,
|
||||
"path": "/packages/react-native/src/generators/convert-to-inferred/schema.json",
|
||||
"type": "generator"
|
||||
}
|
||||
@ -388,6 +388,7 @@
|
||||
- [generators](/nx-api/detox/generators)
|
||||
- [init](/nx-api/detox/generators/init)
|
||||
- [application](/nx-api/detox/generators/application)
|
||||
- [convert-to-inferred](/nx-api/detox/generators/convert-to-inferred)
|
||||
- [devkit](/nx-api/devkit)
|
||||
- [documents](/nx-api/devkit/documents)
|
||||
- [Overview](/nx-api/devkit/documents/nx_devkit)
|
||||
@ -437,6 +438,7 @@
|
||||
- [application](/nx-api/expo/generators/application)
|
||||
- [library](/nx-api/expo/generators/library)
|
||||
- [component](/nx-api/expo/generators/component)
|
||||
- [convert-to-inferred](/nx-api/expo/generators/convert-to-inferred)
|
||||
- [express](/nx-api/express)
|
||||
- [documents](/nx-api/express/documents)
|
||||
- [Overview](/nx-api/express/documents/overview)
|
||||
@ -628,6 +630,7 @@
|
||||
- [stories](/nx-api/react-native/generators/stories)
|
||||
- [upgrade-native](/nx-api/react-native/generators/upgrade-native)
|
||||
- [web-configuration](/nx-api/react-native/generators/web-configuration)
|
||||
- [convert-to-inferred](/nx-api/react-native/generators/convert-to-inferred)
|
||||
- [remix](/nx-api/remix)
|
||||
- [documents](/nx-api/remix/documents)
|
||||
- [Overview](/nx-api/remix/documents/overview)
|
||||
|
||||
@ -15,6 +15,11 @@
|
||||
"aliases": ["app"],
|
||||
"x-type": "application",
|
||||
"description": "Create a Detox application."
|
||||
},
|
||||
"convert-to-inferred": {
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": "./src/generators/convert-to-inferred/schema.json",
|
||||
"description": "Convert existing Detox project(s) using `@nx/detox:*` executors to use `@nx/detox/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,14 +64,14 @@ describe('detox application generator', () => {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.release': {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
@ -136,14 +136,14 @@ describe('detox application generator', () => {
|
||||
binaryPath:
|
||||
'../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.release': {
|
||||
binaryPath:
|
||||
'../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
@ -208,14 +208,14 @@ describe('detox application generator', () => {
|
||||
binaryPath:
|
||||
'../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.release': {
|
||||
binaryPath:
|
||||
'../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
@ -279,14 +279,14 @@ describe('detox application generator', () => {
|
||||
binaryPath:
|
||||
'../../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.release': {
|
||||
binaryPath:
|
||||
'../../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
@ -355,7 +355,7 @@ describe('detox application generator', () => {
|
||||
binaryPath:
|
||||
'../../my-dir/my-app/ios/build/Build/Products/Debug-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.local': {
|
||||
@ -368,7 +368,7 @@ describe('detox application generator', () => {
|
||||
binaryPath:
|
||||
'../../my-dir/my-app/ios/build/Build/Products/Release-iphonesimulator/MyDirMyApp.app',
|
||||
build:
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../../my-dir/my-app/ios && xcodebuild -workspace MyDirMyApp.xcworkspace -scheme MyDirMyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
|
||||
@ -11,12 +11,12 @@
|
||||
"apps": {
|
||||
"ios.debug": {
|
||||
"type": "ios.app",
|
||||
"build": "cd <%= offsetFromRoot %><%= appRoot %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"build": "cd <%= offsetFromRoot %><%= appRoot %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/ios/build/Build/Products/Debug-iphonesimulator/<%= appClassName %>.app"
|
||||
},
|
||||
"ios.release": {
|
||||
"type": "ios.app",
|
||||
"build": "cd <%= offsetFromRoot %><%= appRoot %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"build": "cd <%= offsetFromRoot %><%= appRoot %>/ios && xcodebuild -workspace <%= appClassName %>.xcworkspace -scheme <%= appClassName %> -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
"binaryPath": "<%= offsetFromRoot %><%= appRoot %>/ios/build/Build/Products/Release-iphonesimulator/<%= appClassName %>.app"
|
||||
},
|
||||
<% if (framework === 'expo') { %>
|
||||
@ -48,7 +48,7 @@
|
||||
"simulator": {
|
||||
"type": "ios.simulator",
|
||||
"device": {
|
||||
"type": "iPhone 14"
|
||||
"type": "iPhone 15 Plus"
|
||||
}
|
||||
},
|
||||
"emulator": {
|
||||
|
||||
@ -0,0 +1,432 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
joinPathFragments,
|
||||
type ProjectConfiguration,
|
||||
type ProjectGraph,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
type Tree,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { TempFs } from '@nx/devkit/internal-testing-utils';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { join } from 'node:path';
|
||||
import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration';
|
||||
import { convertToInferred } from './convert-to-inferred';
|
||||
|
||||
let fs: TempFs;
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual('@nx/devkit'),
|
||||
createProjectGraphAsync: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(projectGraph)),
|
||||
updateProjectConfiguration: jest
|
||||
.fn()
|
||||
.mockImplementation((tree, projectName, projectConfiguration) => {
|
||||
function handleEmptyTargets(
|
||||
projectName: string,
|
||||
projectConfiguration: ProjectConfiguration
|
||||
): void {
|
||||
if (
|
||||
projectConfiguration.targets &&
|
||||
!Object.keys(projectConfiguration.targets).length
|
||||
) {
|
||||
// Re-order `targets` to appear after the `// target` comment.
|
||||
delete projectConfiguration.targets;
|
||||
projectConfiguration[
|
||||
'// targets'
|
||||
] = `to see all targets run: nx show project ${projectName} --web`;
|
||||
projectConfiguration.targets = {};
|
||||
} else {
|
||||
delete projectConfiguration['// targets'];
|
||||
}
|
||||
}
|
||||
|
||||
const projectConfigFile = joinPathFragments(
|
||||
projectConfiguration.root,
|
||||
'project.json'
|
||||
);
|
||||
|
||||
if (!tree.exists(projectConfigFile)) {
|
||||
throw new Error(
|
||||
`Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.`
|
||||
);
|
||||
}
|
||||
handleEmptyTargets(projectName, projectConfiguration);
|
||||
writeJson(tree, projectConfigFile, {
|
||||
name: projectConfiguration.name ?? projectName,
|
||||
$schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration),
|
||||
...projectConfiguration,
|
||||
root: undefined,
|
||||
});
|
||||
projectGraph.nodes[projectName].data = projectConfiguration;
|
||||
}),
|
||||
}));
|
||||
jest.mock('nx/src/devkit-internals', () => ({
|
||||
...jest.requireActual('nx/src/devkit-internals'),
|
||||
getExecutorInformation: jest
|
||||
.fn()
|
||||
.mockImplementation((pkg, ...args) =>
|
||||
jest
|
||||
.requireActual('nx/src/devkit-internals')
|
||||
.getExecutorInformation('@nx/webpack', ...args)
|
||||
),
|
||||
}));
|
||||
|
||||
function addProject(tree: Tree, name: string, project: ProjectConfiguration) {
|
||||
addProjectConfiguration(tree, name, project);
|
||||
projectGraph.nodes[name] = {
|
||||
name: name,
|
||||
type: project.projectType === 'application' ? 'app' : 'lib',
|
||||
data: {
|
||||
projectType: project.projectType,
|
||||
root: project.root,
|
||||
targets: project.targets,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectOptions {
|
||||
appName: string;
|
||||
appRoot: string;
|
||||
buildAndroidTargetName: string;
|
||||
buildIosTargetName: string;
|
||||
testAndroidTargetName: string;
|
||||
testIosTargetName: string;
|
||||
}
|
||||
|
||||
const defaultProjectOptions: ProjectOptions = {
|
||||
appName: 'demo-e2e',
|
||||
appRoot: 'apps/demo-e2e',
|
||||
buildAndroidTargetName: 'build-android',
|
||||
buildIosTargetName: 'build-ios',
|
||||
testAndroidTargetName: 'test-android',
|
||||
testIosTargetName: 'test-ios',
|
||||
};
|
||||
|
||||
const detoxConfig = {
|
||||
testRunner: {
|
||||
args: {
|
||||
$0: 'jest',
|
||||
config: './jest.config.json',
|
||||
},
|
||||
jest: {
|
||||
setupTimeout: 120000,
|
||||
},
|
||||
},
|
||||
apps: {
|
||||
'ios.debug': {
|
||||
type: 'ios.app',
|
||||
build:
|
||||
"cd ../demo/ios && xcodebuild -workspace Demo.xcworkspace -scheme Demo -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
binaryPath:
|
||||
'../demo/ios/build/Build/Products/Debug-iphonesimulator/Demo.app',
|
||||
},
|
||||
'ios.release': {
|
||||
type: 'ios.app',
|
||||
build:
|
||||
"cd ../demo/ios && xcodebuild -workspace Demo.xcworkspace -scheme Demo -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
binaryPath:
|
||||
'../demo/ios/build/Build/Products/Release-iphonesimulator/Demo.app',
|
||||
},
|
||||
|
||||
'android.debug': {
|
||||
type: 'android.apk',
|
||||
build:
|
||||
'cd ../demo/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug',
|
||||
binaryPath: '../demo/android/app/build/outputs/apk/debug/app-debug.apk',
|
||||
},
|
||||
'android.release': {
|
||||
type: 'android.apk',
|
||||
build:
|
||||
'cd ../demo/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release',
|
||||
binaryPath:
|
||||
'../demo/android/app/build/outputs/apk/release/app-release.apk',
|
||||
},
|
||||
},
|
||||
devices: {
|
||||
simulator: {
|
||||
type: 'ios.simulator',
|
||||
device: {
|
||||
type: 'iPhone 15 Plus',
|
||||
},
|
||||
},
|
||||
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',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
function writeDetoxConfig(tree: Tree, projectRoot: string) {
|
||||
tree.write(`${projectRoot}/.detoxrc.json`, JSON.stringify(detoxConfig));
|
||||
fs.createFileSync(
|
||||
`${projectRoot}/.detoxrc.json`,
|
||||
JSON.stringify(detoxConfig)
|
||||
);
|
||||
jest.doMock(
|
||||
join(fs.tempDir, projectRoot, '.detoxrc.json'),
|
||||
() => detoxConfig,
|
||||
{
|
||||
virtual: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createProject(
|
||||
tree: Tree,
|
||||
options: Partial<ProjectOptions> = {},
|
||||
extraTargetOptions?: Record<string, Record<string, unknown>>,
|
||||
extraTargetConfigurations?: Record<
|
||||
string,
|
||||
Record<string, Record<string, unknown>>
|
||||
>
|
||||
) {
|
||||
let projectOptions = { ...defaultProjectOptions, ...options };
|
||||
const project: ProjectConfiguration = {
|
||||
name: projectOptions.appName,
|
||||
root: projectOptions.appRoot,
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
[projectOptions.buildAndroidTargetName]: {
|
||||
executor: '@nx/detox:build',
|
||||
options: {
|
||||
detoxConfiguration: 'android.emu.debug',
|
||||
...extraTargetOptions?.[projectOptions.buildAndroidTargetName],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
...extraTargetConfigurations?.[
|
||||
projectOptions.buildAndroidTargetName
|
||||
].production,
|
||||
detoxConfiguration: 'android.emu.release',
|
||||
},
|
||||
},
|
||||
},
|
||||
[projectOptions.buildIosTargetName]: {
|
||||
executor: '@nx/detox:build',
|
||||
options: {
|
||||
detoxConfiguration: 'ios.sim.debug',
|
||||
...extraTargetOptions?.[projectOptions.buildIosTargetName],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
...extraTargetConfigurations?.[projectOptions.buildIosTargetName]
|
||||
.production,
|
||||
detoxConfiguration: 'ios.sim.release',
|
||||
},
|
||||
},
|
||||
},
|
||||
[projectOptions.testAndroidTargetName]: {
|
||||
executor: '@nx/detox:test',
|
||||
options: {
|
||||
detoxConfiguration: 'android.emu.debug',
|
||||
buildTarget: 'demo-e2e:build-android',
|
||||
...extraTargetOptions?.[projectOptions.testAndroidTargetName],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
detoxConfiguration: 'android.emu.release',
|
||||
buildTarget: 'demo-e2e:build-android:production',
|
||||
...extraTargetConfigurations?.[projectOptions.testAndroidTargetName]
|
||||
.production,
|
||||
},
|
||||
},
|
||||
},
|
||||
[projectOptions.testIosTargetName]: {
|
||||
executor: '@nx/detox:test',
|
||||
options: {
|
||||
detoxConfiguration: 'ios.sim.debug',
|
||||
buildTarget: 'demo-e2e:build-ios',
|
||||
...extraTargetOptions?.[projectOptions.testIosTargetName],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
detoxConfiguration: 'ios.sim.release',
|
||||
buildTarget: 'demo-e2e:build-ios:production',
|
||||
...extraTargetConfigurations?.[projectOptions.testIosTargetName]
|
||||
.production,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
addProject(tree, project.name, project);
|
||||
fs.createFileSync(
|
||||
`${projectOptions.appRoot}/project.json`,
|
||||
JSON.stringify(project)
|
||||
);
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
describe('convert-to-inferred', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
fs = new TempFs('detox');
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.root = fs.tempDir;
|
||||
|
||||
projectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.cleanup();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should convert project to use inference plugin', async () => {
|
||||
const project = createProject(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
'build-android': {
|
||||
configPath: '.detoxrc.dev.json',
|
||||
},
|
||||
'build-ios': {
|
||||
configPath: '.detoxrc.dev.json',
|
||||
},
|
||||
'test-android': {
|
||||
configPath: '.detoxrc.dev.json',
|
||||
},
|
||||
'test-ios': {
|
||||
configPath: '.detoxrc.dev.json',
|
||||
},
|
||||
},
|
||||
{
|
||||
'build-android': {
|
||||
production: { configPath: '.detoxrc.prod.json' },
|
||||
},
|
||||
'build-ios': {
|
||||
production: { configPath: '.detoxrc.prod.json' },
|
||||
},
|
||||
'test-android': {
|
||||
production: { configPath: '.detoxrc.prod.json' },
|
||||
},
|
||||
'test-ios': {
|
||||
production: { configPath: '.detoxrc.prod.json' },
|
||||
},
|
||||
}
|
||||
);
|
||||
writeDetoxConfig(tree, project.root);
|
||||
|
||||
await convertToInferred(tree, { project: project.name });
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, project.name);
|
||||
const nxJson = readNxJson(tree);
|
||||
expect(nxJson.plugins).toEqual([
|
||||
{
|
||||
options: {
|
||||
buildTargetName: 'build',
|
||||
startTargetName: 'start',
|
||||
testTargetName: 'test',
|
||||
},
|
||||
plugin: '@nx/detox/plugin',
|
||||
},
|
||||
]);
|
||||
expect(projectConfig.targets['build-android']).toEqual({
|
||||
command: 'nx run demo-e2e:build',
|
||||
options: {
|
||||
args: [
|
||||
'--args="-c android.emu.debug"',
|
||||
'--config-path',
|
||||
'.detoxrc.dev.json',
|
||||
],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
args: [
|
||||
'--args="-c android.emu.release"',
|
||||
'--config-path',
|
||||
'.detoxrc.prod.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(projectConfig.targets['build-ios']).toEqual({
|
||||
command: 'nx run demo-e2e:build',
|
||||
options: {
|
||||
args: [
|
||||
'--args="-c ios.sim.debug"',
|
||||
'--config-path',
|
||||
'.detoxrc.dev.json',
|
||||
],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
args: [
|
||||
'--args="-c ios.sim.release"',
|
||||
'--config-path',
|
||||
'.detoxrc.prod.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(projectConfig.targets['test-android']).toEqual({
|
||||
command: 'nx run demo-e2e:test',
|
||||
options: {
|
||||
args: [
|
||||
'--args="-c android.emu.debug"',
|
||||
'--config-path',
|
||||
'.detoxrc.dev.json',
|
||||
],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
args: [
|
||||
'--args="-c android.emu.release"',
|
||||
'--config-path',
|
||||
'.detoxrc.prod.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(projectConfig.targets['test-ios']).toEqual({
|
||||
command: 'nx run demo-e2e:test',
|
||||
options: {
|
||||
args: [
|
||||
'--args="-c ios.sim.debug"',
|
||||
'--config-path',
|
||||
'.detoxrc.dev.json',
|
||||
],
|
||||
},
|
||||
configurations: {
|
||||
production: {
|
||||
args: [
|
||||
'--args="-c ios.sim.release"',
|
||||
'--config-path',
|
||||
'.detoxrc.prod.json',
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,112 @@
|
||||
import {
|
||||
createProjectGraphAsync,
|
||||
formatFiles,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
type Tree,
|
||||
updateNxJson,
|
||||
} from '@nx/devkit';
|
||||
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { migrateProjectExecutorsToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
|
||||
import { createNodes } from '../../plugins/plugin';
|
||||
import { processBuildOptions } from './lib/process-build-options';
|
||||
import { postTargetTransformer } from './lib/post-target-transformer';
|
||||
import { processTestOptions } from './lib/process-test-options';
|
||||
|
||||
interface Schema {
|
||||
project?: string;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
export async function convertToInferred(tree: Tree, options: Schema) {
|
||||
const projectGraph = await createProjectGraphAsync();
|
||||
const migrationLogs = new AggregatedLog();
|
||||
const migratedProjects = await migrateProjectExecutorsToPluginV1(
|
||||
tree,
|
||||
projectGraph,
|
||||
'@nx/detox/plugin',
|
||||
createNodes,
|
||||
{
|
||||
buildTargetName: 'build',
|
||||
startTargetName: 'start',
|
||||
testTargetName: 'test',
|
||||
},
|
||||
[
|
||||
{
|
||||
executors: ['@nx/detox:build'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processBuildOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
buildTargetName: targetName, // We should use "build" instead of "build-ios" or "build-android". We'll handle this later.
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/detox:test'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processTestOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
testTargetName: targetName, // We should use "test" instead of "test-ios" or "test-android". We'll handle this later.
|
||||
}),
|
||||
},
|
||||
],
|
||||
options.project
|
||||
);
|
||||
|
||||
const nxJson = readNxJson(tree);
|
||||
const detoxPlugins = nxJson.plugins?.filter(
|
||||
(p) => typeof p !== 'string' && p.plugin === '@nx/detox/plugin'
|
||||
);
|
||||
|
||||
// These were either `build-ios`, `test-ios`, etc., and we need to set them back to their generic names.
|
||||
// The per-project targets will call these with additional `--args` passed to maintain the same
|
||||
// behavior as previous executor-based targets.
|
||||
for (const p of detoxPlugins) {
|
||||
if (typeof p === 'string') continue;
|
||||
p.options['buildTargetName'] = 'build';
|
||||
p.options['testTargetName'] = 'test';
|
||||
}
|
||||
|
||||
// Inform the users that the inferred targets are platform-agnostic, and they can remove the old targets if unnecessary.
|
||||
for (const [project] of migratedProjects) {
|
||||
migrationLogs.addLog({
|
||||
project,
|
||||
executorName: '@nx/detox:build',
|
||||
log: `The "build-android" target was migrated to use "nx run ${project}:build", which is platform-agnostic. If you no longer need this target, you can remove it.`,
|
||||
});
|
||||
migrationLogs.addLog({
|
||||
project,
|
||||
executorName: '@nx/detox:test',
|
||||
log: `The "test-android" target was migrated to use "nx run ${project}:test", which is platform-agnostic. If you no longer need this target, you can remove it.`,
|
||||
});
|
||||
migrationLogs.addLog({
|
||||
project,
|
||||
executorName: '@nx/detox:build',
|
||||
log: `The "build-ios" target was migrated to use "nx run ${project}:build", which is platform-agnostic. If you no longer need this target, you can remove it.`,
|
||||
});
|
||||
migrationLogs.addLog({
|
||||
project,
|
||||
executorName: '@nx/detox:test',
|
||||
log: `The "test-ios" target was migrated to use "nx run ${project}:test", which is platform-agnostic. If you no longer need this target, you can remove it.`,
|
||||
});
|
||||
}
|
||||
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
if (migratedProjects.size === 0) {
|
||||
throw new Error('Could not find any targets to migrate.');
|
||||
}
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return () => {
|
||||
migrationLogs.flushLogs();
|
||||
};
|
||||
}
|
||||
|
||||
export default convertToInferred;
|
||||
@ -0,0 +1,70 @@
|
||||
import type { TargetConfiguration, Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processTargetOutputs } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
|
||||
|
||||
export function postTargetTransformer(
|
||||
migrationLogs: AggregatedLog,
|
||||
processOptions: (
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
target: TargetConfiguration | undefined,
|
||||
migrationLogs: AggregatedLog
|
||||
) => void
|
||||
) {
|
||||
return (
|
||||
target: TargetConfiguration,
|
||||
tree: Tree,
|
||||
projectDetails: { projectName: string; root: string },
|
||||
inferredTargetConfiguration: TargetConfiguration
|
||||
) => {
|
||||
if (target.options) {
|
||||
processOptions(
|
||||
tree,
|
||||
target.options,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root,
|
||||
target,
|
||||
migrationLogs
|
||||
);
|
||||
}
|
||||
|
||||
if (target.configurations) {
|
||||
for (const configurationName in target.configurations) {
|
||||
const configuration = target.configurations[configurationName];
|
||||
processOptions(
|
||||
tree,
|
||||
configuration,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root,
|
||||
undefined,
|
||||
migrationLogs
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(target.configurations).length === 0) {
|
||||
if ('defaultConfiguration' in target) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
delete target.configurations;
|
||||
}
|
||||
|
||||
if (
|
||||
'defaultConfiguration' in target &&
|
||||
!target.configurations[target.defaultConfiguration]
|
||||
) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.outputs) {
|
||||
processTargetOutputs(target, [], inferredTargetConfiguration, {
|
||||
projectName: projectDetails.projectName,
|
||||
projectRoot: projectDetails.root,
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,35 @@
|
||||
import { names, type TargetConfiguration, type Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
|
||||
export function processBuildOptions(
|
||||
_tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
_projectRoot: string,
|
||||
target: TargetConfiguration | undefined,
|
||||
_migrationLogs: AggregatedLog
|
||||
): void {
|
||||
const args: string[] = [];
|
||||
|
||||
if ('detoxConfiguration' in options) {
|
||||
// Need to wrap in --args since --configuration/-c is swallowed by Nx CLI.
|
||||
args.push(`--args="-c ${options.detoxConfiguration}"`);
|
||||
delete options.detoxConfiguration;
|
||||
}
|
||||
|
||||
for (const key of Object.keys(options)) {
|
||||
let value = options[key];
|
||||
if (typeof value === 'boolean') {
|
||||
if (value) args.push(`--${names(key).fileName}`);
|
||||
} else {
|
||||
args.push(`--${names(key).fileName}`, value);
|
||||
}
|
||||
delete options[key];
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target.command = `nx run ${projectName}:build`;
|
||||
}
|
||||
|
||||
options.args = args;
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
import { names, type TargetConfiguration, type Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
|
||||
export function processTestOptions(
|
||||
_tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
_projectRoot: string,
|
||||
target: TargetConfiguration | undefined,
|
||||
migrationLogs: AggregatedLog
|
||||
): void {
|
||||
const args: string[] = [];
|
||||
|
||||
if ('detoxConfiguration' in options) {
|
||||
// Need to wrap in --args since --configuration/-c is swallowed by Nx CLI.
|
||||
args.push(`--args="-c ${options.detoxConfiguration}"`);
|
||||
delete options.detoxConfiguration;
|
||||
}
|
||||
|
||||
if ('deviceBootArgs' in options) {
|
||||
args.push(`--device-boot-args="${options.deviceBootArgs}"`); // the value must be specified after an equal sign (=) and inside quotes: https://wix.github.io/Detox/docs/cli/test
|
||||
delete options.deviceBootArgs;
|
||||
}
|
||||
|
||||
if ('appLaunchArgs' in options) {
|
||||
args.push(`--app-launch-args="${options.appLaunchArgs}"`); // the value must be specified after an equal sign (=) and inside quotes: https://wix.github.io/Detox/docs/cli/test
|
||||
delete options.appLaunchArgs;
|
||||
}
|
||||
|
||||
if ('color' in options) {
|
||||
// detox only accepts --no-color, not --color
|
||||
if (!options.color) args.push('--no-color');
|
||||
delete options.color;
|
||||
}
|
||||
|
||||
if ('buildTarget' in options) {
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName: '@nx/expo:test',
|
||||
log: 'Unable to migrate `buildTarget` for Detox test. Use "nx run <project>:run-ios" or "nx run <project>:run-android", and pass "--reuse" option when running tests.',
|
||||
});
|
||||
delete options.buildTarget;
|
||||
}
|
||||
|
||||
const deprecatedOptions = [
|
||||
'runnerConfig',
|
||||
'recordTimeline',
|
||||
'workers',
|
||||
'deviceLaunchArgs',
|
||||
];
|
||||
for (const key of deprecatedOptions) {
|
||||
if (!(key in options)) continue;
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName: '@nx/expo:test',
|
||||
log: `Option "${key}" is not migrated since it was removed in Detox 20.`,
|
||||
});
|
||||
delete options[key];
|
||||
}
|
||||
|
||||
for (const key of Object.keys(options)) {
|
||||
let value = options[key];
|
||||
if (typeof value === 'boolean') {
|
||||
if (value) args.push(`--${names(key).fileName}`);
|
||||
} else {
|
||||
args.push(`--${names(key).fileName}`, value);
|
||||
}
|
||||
delete options[key];
|
||||
}
|
||||
|
||||
if (target) {
|
||||
target.command = `nx run ${projectName}:test`;
|
||||
}
|
||||
|
||||
options.args = args;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxDetoxConvertToInferred",
|
||||
"description": "Convert existing Detox project(s) using `@nx/detox:*` executors to use `@nx/detox/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert Detox project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/detox:*` executors to use `@nx/detox/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,11 @@
|
||||
"schema": "./src/generators/component/schema.json",
|
||||
"description": "Create a component",
|
||||
"aliases": ["c"]
|
||||
},
|
||||
"convert-to-inferred": {
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": "./src/generators/convert-to-inferred/schema.json",
|
||||
"description": "Convert existing Expo project(s) using `@nx/expo:*` executors to use `@nx/expo/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -139,7 +139,7 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-dir/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.local': {
|
||||
@ -152,7 +152,7 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-dir/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
@ -200,7 +200,7 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.local': {
|
||||
@ -213,7 +213,7 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
@ -264,7 +264,7 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.local': {
|
||||
@ -277,7 +277,7 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
|
||||
@ -0,0 +1,464 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
type ExpandedPluginConfiguration,
|
||||
joinPathFragments,
|
||||
type ProjectConfiguration,
|
||||
type ProjectGraph,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
type Tree,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { TempFs } from '@nx/devkit/internal-testing-utils';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { join } from 'node:path';
|
||||
import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration';
|
||||
import { convertToInferred } from './convert-to-inferred';
|
||||
|
||||
let fs: TempFs;
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual('@nx/devkit'),
|
||||
createProjectGraphAsync: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(projectGraph)),
|
||||
updateProjectConfiguration: jest
|
||||
.fn()
|
||||
.mockImplementation((tree, projectName, projectConfiguration) => {
|
||||
function handleEmptyTargets(
|
||||
projectName: string,
|
||||
projectConfiguration: ProjectConfiguration
|
||||
): void {
|
||||
if (
|
||||
projectConfiguration.targets &&
|
||||
!Object.keys(projectConfiguration.targets).length
|
||||
) {
|
||||
// Re-order `targets` to appear after the `// target` comment.
|
||||
delete projectConfiguration.targets;
|
||||
projectConfiguration[
|
||||
'// targets'
|
||||
] = `to see all targets run: nx show project ${projectName} --web`;
|
||||
projectConfiguration.targets = {};
|
||||
} else {
|
||||
delete projectConfiguration['// targets'];
|
||||
}
|
||||
}
|
||||
|
||||
const projectConfigFile = joinPathFragments(
|
||||
projectConfiguration.root,
|
||||
'project.json'
|
||||
);
|
||||
|
||||
if (!tree.exists(projectConfigFile)) {
|
||||
throw new Error(
|
||||
`Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.`
|
||||
);
|
||||
}
|
||||
handleEmptyTargets(projectName, projectConfiguration);
|
||||
writeJson(tree, projectConfigFile, {
|
||||
name: projectConfiguration.name ?? projectName,
|
||||
$schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration),
|
||||
...projectConfiguration,
|
||||
root: undefined,
|
||||
});
|
||||
projectGraph.nodes[projectName].data = projectConfiguration;
|
||||
}),
|
||||
}));
|
||||
jest.mock('nx/src/devkit-internals', () => ({
|
||||
...jest.requireActual('nx/src/devkit-internals'),
|
||||
getExecutorInformation: jest
|
||||
.fn()
|
||||
.mockImplementation((pkg, ...args) =>
|
||||
jest
|
||||
.requireActual('nx/src/devkit-internals')
|
||||
.getExecutorInformation('@nx/webpack', ...args)
|
||||
),
|
||||
}));
|
||||
|
||||
function addProject(tree: Tree, name: string, project: ProjectConfiguration) {
|
||||
addProjectConfiguration(tree, name, project);
|
||||
projectGraph.nodes[name] = {
|
||||
name: name,
|
||||
type: project.projectType === 'application' ? 'app' : 'lib',
|
||||
data: {
|
||||
projectType: project.projectType,
|
||||
root: project.root,
|
||||
targets: project.targets,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectOptions {
|
||||
appName: string;
|
||||
appRoot: string;
|
||||
buildTargetName: string;
|
||||
exportTargetName: string;
|
||||
installTargetName: string;
|
||||
prebuildTargetName: string;
|
||||
runIosTargetName: string;
|
||||
runAndroidTargetName: string;
|
||||
serveTargetName: string;
|
||||
startTargetName: string;
|
||||
submitTargetName: string;
|
||||
}
|
||||
|
||||
const defaultProjectOptions: ProjectOptions = {
|
||||
appName: 'demo',
|
||||
appRoot: 'apps/demo',
|
||||
buildTargetName: 'build',
|
||||
exportTargetName: 'export',
|
||||
installTargetName: 'install',
|
||||
prebuildTargetName: 'prebuild',
|
||||
runAndroidTargetName: 'run-android',
|
||||
runIosTargetName: 'run-ios',
|
||||
serveTargetName: 'serve',
|
||||
startTargetName: 'start',
|
||||
submitTargetName: 'submit',
|
||||
};
|
||||
|
||||
const defaultExpoConfig = {
|
||||
expo: {
|
||||
name: 'demo',
|
||||
slug: 'demo',
|
||||
version: '1.0.0',
|
||||
orientation: 'portrait',
|
||||
icon: './assets/icon.png',
|
||||
splash: {
|
||||
image: './assets/splash.png',
|
||||
resizeMode: 'contain',
|
||||
backgroundColor: '#ffffff',
|
||||
},
|
||||
updates: {
|
||||
fallbackToCacheTimeout: 0,
|
||||
},
|
||||
assetBundlePatterns: ['**/*'],
|
||||
ios: {
|
||||
supportsTablet: true,
|
||||
bundleIdentifier: 'com.anonymous.demo',
|
||||
},
|
||||
android: {
|
||||
adaptiveIcon: {
|
||||
foregroundImage: './assets/adaptive-icon.png',
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
},
|
||||
web: {
|
||||
favicon: './assets/favicon.png',
|
||||
bundler: 'metro',
|
||||
},
|
||||
plugins: [],
|
||||
},
|
||||
};
|
||||
|
||||
function writeExpoConfig(
|
||||
tree: Tree,
|
||||
projectRoot: string,
|
||||
expoConfig = defaultExpoConfig
|
||||
) {
|
||||
tree.write(`${projectRoot}/app.json`, JSON.stringify(expoConfig));
|
||||
fs.createFileSync(`${projectRoot}/app.json`, JSON.stringify(expoConfig));
|
||||
jest.doMock(join(fs.tempDir, projectRoot, 'app.json'), () => expoConfig, {
|
||||
virtual: true,
|
||||
});
|
||||
}
|
||||
|
||||
function createProject(
|
||||
tree: Tree,
|
||||
options: Partial<ProjectOptions> = {},
|
||||
additionalTargetOptions?: Record<string, Record<string, unknown>>
|
||||
) {
|
||||
let projectOptions = { ...defaultProjectOptions, ...options };
|
||||
const project: ProjectConfiguration = {
|
||||
name: projectOptions.appName,
|
||||
root: projectOptions.appRoot,
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
[projectOptions.buildTargetName]: {
|
||||
executor: '@nx/expo:build',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.buildTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.exportTargetName]: {
|
||||
executor: '@nx/expo:export',
|
||||
options: {
|
||||
platform: 'all',
|
||||
outputDir: `dist/${projectOptions.appName}`,
|
||||
...additionalTargetOptions?.[projectOptions.exportTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.installTargetName]: {
|
||||
executor: '@nx/expo:install',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.installTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.prebuildTargetName]: {
|
||||
executor: '@nx/expo:prebuild',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.prebuildTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.runAndroidTargetName]: {
|
||||
executor: '@nx/expo:run',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.runAndroidTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.runIosTargetName]: {
|
||||
executor: '@nx/expo:run',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.runIosTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.serveTargetName]: {
|
||||
executor: '@nx/expo:serve',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.startTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.startTargetName]: {
|
||||
executor: '@nx/expo:start',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.serveTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.submitTargetName]: {
|
||||
executor: '@nx/expo:submit',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.submitTargetName],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
addProject(tree, project.name, project);
|
||||
fs.createFileSync(
|
||||
`${projectOptions.appRoot}/project.json`,
|
||||
JSON.stringify(project)
|
||||
);
|
||||
|
||||
// These file need to exist for inference, but they can be empty for the convert generator
|
||||
fs.createFileSync(`${projectOptions.appRoot}/package.json`, '{}');
|
||||
fs.createFileSync(`${projectOptions.appRoot}/metro.config.js`, '// empty');
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
describe('convert-to-inferred', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
fs = new TempFs('expo');
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.root = fs.tempDir;
|
||||
|
||||
projectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.cleanup();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should convert project to use inference plugin', async () => {
|
||||
const project = createProject(tree);
|
||||
writeExpoConfig(tree, project.root);
|
||||
|
||||
const project2 = createProject(tree, {
|
||||
appName: 'app2',
|
||||
appRoot: 'apps/app2',
|
||||
});
|
||||
|
||||
const project2Build = project2.targets.build;
|
||||
|
||||
await convertToInferred(tree, { project: project.name });
|
||||
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const expoPlugin = nxJsonPlugins.find(
|
||||
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||
typeof plugin !== 'string' && plugin.plugin === '@nx/expo/plugin'
|
||||
);
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, project.name);
|
||||
|
||||
expect(expoPlugin).toBeDefined();
|
||||
expect(projectConfig.targets).toEqual({
|
||||
export: {
|
||||
options: {
|
||||
args: ['--output-dir=../../dist/demo', '--platform=all'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||
expect(updatedProject2.targets.build).toEqual(project2Build);
|
||||
});
|
||||
|
||||
it('should migrate options to CLI options and args', async () => {
|
||||
const project = createProject(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
build: {
|
||||
wait: true,
|
||||
clearCache: true,
|
||||
},
|
||||
export: { bytecode: false, minify: false, platform: 'android' },
|
||||
'run-android': {
|
||||
platform: 'android',
|
||||
variant: 'release',
|
||||
clean: true,
|
||||
bundler: false,
|
||||
},
|
||||
'run-ios': {
|
||||
platform: 'ios',
|
||||
xcodeConfiguration: 'Release',
|
||||
buildCache: false,
|
||||
},
|
||||
install: { check: true },
|
||||
prebuild: { clean: true },
|
||||
serve: { dev: false },
|
||||
start: { dev: false },
|
||||
submit: { wait: true, interactive: false },
|
||||
}
|
||||
);
|
||||
writeExpoConfig(tree, project.root);
|
||||
|
||||
await convertToInferred(tree, { project: project.name });
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, project.name);
|
||||
expect(projectConfig.targets.build.options).toEqual({
|
||||
args: ['--wait', '--clear-cache'],
|
||||
});
|
||||
expect(projectConfig.targets.export.options).toEqual({
|
||||
args: [
|
||||
'--no-minify',
|
||||
'--no-bytecode',
|
||||
'--output-dir=../../dist/demo',
|
||||
'--platform=android',
|
||||
],
|
||||
});
|
||||
expect(projectConfig.targets.install.options).toEqual({
|
||||
args: ['--check'],
|
||||
});
|
||||
expect(projectConfig.targets.prebuild.options).toEqual({
|
||||
args: ['--clean'],
|
||||
});
|
||||
expect(projectConfig.targets['run-android'].options).toEqual({
|
||||
args: ['--variant', 'release', '--no-bundler'],
|
||||
});
|
||||
expect(projectConfig.targets['run-ios'].options).toEqual({
|
||||
args: ['--configuration', 'Release', '--no-build-cache'],
|
||||
});
|
||||
expect(projectConfig.targets.serve.options).toEqual({
|
||||
args: ['--no-dev'],
|
||||
});
|
||||
expect(projectConfig.targets.start.options).toEqual({
|
||||
args: ['--no-dev'],
|
||||
});
|
||||
expect(projectConfig.targets.submit.options).toEqual({
|
||||
args: ['--non-interactive', '--wait'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should migrate custom run:ios and run:android target names', async () => {
|
||||
const project1 = createProject(
|
||||
tree,
|
||||
{
|
||||
appName: 'app1',
|
||||
appRoot: 'apps/app1',
|
||||
runAndroidTargetName: 'run-android-custom-1',
|
||||
runIosTargetName: 'run-ios-custom-1',
|
||||
},
|
||||
{
|
||||
'run-android-custom-1': {
|
||||
platform: 'android',
|
||||
buildCache: false,
|
||||
},
|
||||
'run-ios-custom-1': {
|
||||
platform: 'ios',
|
||||
buildCache: false,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
const project2 = createProject(
|
||||
tree,
|
||||
{
|
||||
appName: 'app2',
|
||||
appRoot: 'apps/app2',
|
||||
runAndroidTargetName: 'run-android-custom-2',
|
||||
runIosTargetName: 'run-ios-custom-2',
|
||||
},
|
||||
{
|
||||
'run-android-custom-2': {
|
||||
platform: 'android',
|
||||
variant: 'release',
|
||||
},
|
||||
'run-ios-custom-2': {
|
||||
platform: 'ios',
|
||||
xcodeConfiguration: 'Release',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
writeExpoConfig(tree, project2.root);
|
||||
writeExpoConfig(tree, project1.root);
|
||||
|
||||
await convertToInferred(tree, {});
|
||||
|
||||
const config1 = readProjectConfiguration(tree, project1.name);
|
||||
const config2 = readProjectConfiguration(tree, project2.name);
|
||||
const nxJson = readNxJson(tree);
|
||||
|
||||
expect(config1.targets['run-android-custom-1'].options).toEqual({
|
||||
args: ['--no-build-cache'],
|
||||
});
|
||||
expect(config1.targets['run-ios-custom-1'].options).toEqual({
|
||||
args: ['--no-build-cache'],
|
||||
});
|
||||
expect(config2.targets['run-android-custom-2'].options).toEqual({
|
||||
args: ['--variant', 'release'],
|
||||
});
|
||||
expect(config2.targets['run-ios-custom-2'].options).toEqual({
|
||||
args: ['--configuration', 'Release'],
|
||||
});
|
||||
expect(nxJson.plugins).toEqual([
|
||||
{
|
||||
plugin: '@nx/expo/plugin',
|
||||
options: {
|
||||
buildTargetName: 'build',
|
||||
exportTargetName: 'export',
|
||||
installTargetName: 'install',
|
||||
prebuildTargetName: 'prebuild',
|
||||
runAndroidTargetName: 'run-android-custom-1',
|
||||
runIosTargetName: 'run-ios-custom-1',
|
||||
serveTargetName: 'serve',
|
||||
startTargetName: 'start',
|
||||
submitTargetName: 'submit',
|
||||
},
|
||||
include: ['apps/app1/**/*'],
|
||||
},
|
||||
{
|
||||
plugin: '@nx/expo/plugin',
|
||||
options: {
|
||||
buildTargetName: 'build',
|
||||
exportTargetName: 'export',
|
||||
installTargetName: 'install',
|
||||
prebuildTargetName: 'prebuild',
|
||||
runAndroidTargetName: 'run-android-custom-2',
|
||||
runIosTargetName: 'run-ios-custom-2',
|
||||
serveTargetName: 'serve',
|
||||
startTargetName: 'start',
|
||||
submitTargetName: 'submit',
|
||||
},
|
||||
include: ['apps/app2/**/*'],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,170 @@
|
||||
import {
|
||||
createProjectGraphAsync,
|
||||
formatFiles,
|
||||
getProjects,
|
||||
type Tree,
|
||||
} from '@nx/devkit';
|
||||
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { migrateProjectExecutorsToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
|
||||
import { createNodes } from '../../../plugins/plugin';
|
||||
import { processBuildOptions } from './lib/process-build-options';
|
||||
import { postTargetTransformer } from './lib/post-target-transformer';
|
||||
import { processExportOptions } from './lib/process-export-options';
|
||||
import { processRunOptions } from './lib/process-run-options';
|
||||
import { processServeOptions } from './lib/process-serve-options';
|
||||
import { processStartOptions } from './lib/process-start-options';
|
||||
import { processSubmitOptions } from './lib/process-submit-options';
|
||||
import { processPrebuildOptions } from './lib/process-prebuild-options';
|
||||
import { processInstallOptions } from './lib/process-install-options';
|
||||
|
||||
interface Schema {
|
||||
project?: string;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
export async function convertToInferred(tree: Tree, options: Schema) {
|
||||
const projectGraph = await createProjectGraphAsync();
|
||||
const migrationLogs = new AggregatedLog();
|
||||
const projects = getProjects(tree);
|
||||
const migratedProjects = await migrateProjectExecutorsToPluginV1(
|
||||
tree,
|
||||
projectGraph,
|
||||
'@nx/expo/plugin',
|
||||
createNodes,
|
||||
{
|
||||
buildTargetName: 'build',
|
||||
exportTargetName: 'export',
|
||||
installTargetName: 'install',
|
||||
prebuildTargetName: 'prebuild',
|
||||
runAndroidTargetName: 'run-android',
|
||||
runIosTargetName: 'run-ios',
|
||||
serveTargetName: 'serve',
|
||||
startTargetName: 'start',
|
||||
submitTargetName: 'submit',
|
||||
},
|
||||
[
|
||||
{
|
||||
executors: ['@nx/expo:build'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processBuildOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
buildTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/expo:export'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processExportOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
exportTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/expo:install'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processInstallOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
installTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/expo:prebuild'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processPrebuildOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
prebuildTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/expo:run'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processRunOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => {
|
||||
// Assumption: There are no targets with the same name but different platforms.
|
||||
// Most users will likely keep the `run-ios` and `run-android` target names that are generated.
|
||||
// Otherwise, we look for the first target with a matching name, and use that platform.
|
||||
const platform = getPlatformForFirstMatchedTarget(
|
||||
targetName,
|
||||
'@nx/expo:run',
|
||||
projects
|
||||
);
|
||||
return {
|
||||
[platform === 'android'
|
||||
? 'runAndroidTargetName'
|
||||
: 'runIosTargetName']: targetName,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
executors: ['@nx/expo:serve'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processServeOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
serveTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/expo:start'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processStartOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
startTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/expo:submit'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processSubmitOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
submitTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
],
|
||||
options.project
|
||||
);
|
||||
|
||||
if (migratedProjects.size === 0) {
|
||||
throw new Error('Could not find any targets to migrate.');
|
||||
}
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return () => {
|
||||
migrationLogs.flushLogs();
|
||||
};
|
||||
}
|
||||
|
||||
function getPlatformForFirstMatchedTarget(
|
||||
targetName: string,
|
||||
executorName: string,
|
||||
projects: Map<string, any>
|
||||
): string {
|
||||
for (const [, project] of projects) {
|
||||
const target = project.targets[targetName];
|
||||
if (target && target.executor === executorName && target.options.platform) {
|
||||
return target.options.platform;
|
||||
}
|
||||
}
|
||||
// Default is ios in executor, although we do always generate it in project.json.
|
||||
return 'ios';
|
||||
}
|
||||
|
||||
export default convertToInferred;
|
||||
@ -0,0 +1,67 @@
|
||||
import type { TargetConfiguration, Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processTargetOutputs } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
|
||||
|
||||
export function postTargetTransformer(
|
||||
migrationLogs: AggregatedLog,
|
||||
processOptions: (
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
) => void
|
||||
) {
|
||||
return (
|
||||
target: TargetConfiguration,
|
||||
tree: Tree,
|
||||
projectDetails: { projectName: string; root: string },
|
||||
inferredTargetConfiguration: TargetConfiguration
|
||||
) => {
|
||||
if (target.options) {
|
||||
processOptions(
|
||||
tree,
|
||||
target.options,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root,
|
||||
migrationLogs
|
||||
);
|
||||
}
|
||||
|
||||
if (target.configurations) {
|
||||
for (const configurationName in target.configurations) {
|
||||
const configuration = target.configurations[configurationName];
|
||||
processOptions(
|
||||
tree,
|
||||
configuration,
|
||||
projectDetails.projectName,
|
||||
projectDetails.root,
|
||||
migrationLogs
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(target.configurations).length === 0) {
|
||||
if ('defaultConfiguration' in target) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
delete target.configurations;
|
||||
}
|
||||
|
||||
if (
|
||||
'defaultConfiguration' in target &&
|
||||
!target.configurations[target.defaultConfiguration]
|
||||
) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.outputs) {
|
||||
processTargetOutputs(target, [], inferredTargetConfiguration, {
|
||||
projectName: projectDetails.projectName,
|
||||
projectRoot: projectDetails.root,
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processGenericOptions } from './process-generic-options';
|
||||
|
||||
export function processBuildOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
): void {
|
||||
const args: string[] = [];
|
||||
if ('interactive' in options && options.interactive === false) {
|
||||
args.push('--non-interactive');
|
||||
delete options.interactive;
|
||||
}
|
||||
if ('wait' in options && options.wait === false) {
|
||||
if (options.wait) args.push('--wait');
|
||||
else args.push('--no-wait');
|
||||
delete options.wait;
|
||||
}
|
||||
options.args = args;
|
||||
processGenericOptions(tree, options, projectName, projectRoot, migrationLogs);
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { joinPathFragments, offsetFromRoot } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processGenericOptions } from './process-generic-options';
|
||||
|
||||
export function processExportOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
) {
|
||||
const args: string[] = [];
|
||||
if ('minify' in options) {
|
||||
if (options.minify === false) args.push('--no-minify');
|
||||
delete options.minify;
|
||||
}
|
||||
if ('bytecode' in options) {
|
||||
if (options.bytecode === false) args.push('--no-bytecode');
|
||||
delete options.bytecode;
|
||||
}
|
||||
if ('outputDir' in options) {
|
||||
const value = joinPathFragments(
|
||||
offsetFromRoot(projectRoot),
|
||||
options.outputDir
|
||||
);
|
||||
args.push(`--output-dir=${value}`);
|
||||
delete options.outputDir;
|
||||
}
|
||||
options.args = args;
|
||||
processGenericOptions(tree, options, projectName, projectRoot, migrationLogs);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import { names } from '@nx/devkit';
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
|
||||
export function processGenericOptions(
|
||||
_tree: Tree,
|
||||
options: any,
|
||||
_projectName: string,
|
||||
_projectRoot: string,
|
||||
_migrationLogs: AggregatedLog
|
||||
) {
|
||||
const args = options.args ?? [];
|
||||
for (const key of Object.keys(options)) {
|
||||
if (key === 'args') continue;
|
||||
let value = options[key];
|
||||
if (typeof value === 'boolean') {
|
||||
if (value) args.push(`--${names(key).fileName}`);
|
||||
} else {
|
||||
args.push(`--${names(key).fileName}=${value}`);
|
||||
}
|
||||
delete options[key];
|
||||
}
|
||||
options.args = args;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processGenericOptions } from './process-generic-options';
|
||||
|
||||
export function processInstallOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
): void {
|
||||
const args: string[] = [];
|
||||
// Technically this will not be set in project.json since the value is passed through CLI e.g. nx run <project>:install foo,bar.
|
||||
// Handling it here for correctness.
|
||||
if ('packages' in options) {
|
||||
const v = options.packages;
|
||||
const packages = typeof v === 'string' ? v.split(',') : v;
|
||||
args.push(...packages);
|
||||
delete options.packages;
|
||||
}
|
||||
options.args = args;
|
||||
processGenericOptions(tree, options, projectName, projectRoot, migrationLogs);
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processGenericOptions } from './process-generic-options';
|
||||
|
||||
export function processPrebuildOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
): void {
|
||||
const args: string[] = [];
|
||||
if ('install' in options && options.install === false) {
|
||||
args.push('--no-install');
|
||||
delete options.install;
|
||||
}
|
||||
options.args = args;
|
||||
processGenericOptions(tree, options, projectName, projectRoot, migrationLogs);
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import { names } from '@nx/devkit';
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
|
||||
export function processRunOptions(
|
||||
_tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
_projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
) {
|
||||
const args: string[] = [];
|
||||
|
||||
for (const key of Object.keys(options)) {
|
||||
const v = options[key];
|
||||
if (key === 'xcodeConfiguration') {
|
||||
args.push('--configuration', v);
|
||||
} else if (typeof v === 'boolean') {
|
||||
// no need to pass in the flag when it is true, pass the --no-<flag> when it is false. e.g. --no-build-cache, --no-bundler
|
||||
if (v === false) {
|
||||
args.push(`--no-${names(key).fileName}`);
|
||||
}
|
||||
} else {
|
||||
if (key === 'platform') {
|
||||
// Platform isn't necessary to pass to the CLI since it is already part of the inferred command. e.g. run:ios, run:android
|
||||
} else if (key === 'clean') {
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName: '@nx/export:run',
|
||||
log: 'Unable to migrate "clean" option. Use `nx run <project>:prebuild --clean` to regenerate native files.',
|
||||
});
|
||||
} else {
|
||||
args.push(`--${names(key).fileName}`, v);
|
||||
}
|
||||
}
|
||||
delete options[key];
|
||||
}
|
||||
|
||||
options.args = args;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processGenericOptions } from './process-generic-options';
|
||||
|
||||
export function processServeOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
) {
|
||||
const args: string[] = [];
|
||||
if ('dev' in options) {
|
||||
if (options.dev === false) args.push('--no-dev');
|
||||
delete options.dev;
|
||||
}
|
||||
options.args = args;
|
||||
processGenericOptions(tree, options, projectName, projectRoot, migrationLogs);
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processGenericOptions } from './process-generic-options';
|
||||
|
||||
export function processStartOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
) {
|
||||
const args: string[] = [];
|
||||
if ('dev' in options) {
|
||||
if (options.dev === false) args.push('--no-dev');
|
||||
delete options.dev;
|
||||
}
|
||||
options.args = args;
|
||||
processGenericOptions(tree, options, projectName, projectRoot, migrationLogs);
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processGenericOptions } from './process-generic-options';
|
||||
|
||||
export function processSubmitOptions(
|
||||
tree: Tree,
|
||||
options: any,
|
||||
projectName: string,
|
||||
projectRoot: string,
|
||||
migrationLogs: AggregatedLog
|
||||
) {
|
||||
const args: string[] = [];
|
||||
if ('interactive' in options && options.interactive === false) {
|
||||
args.push('--non-interactive');
|
||||
delete options.interactive;
|
||||
}
|
||||
if ('wait' in options) {
|
||||
if (options.wait) args.push('--wait');
|
||||
else args.push('--no-wait');
|
||||
delete options.wait;
|
||||
}
|
||||
options.args = args;
|
||||
processGenericOptions(tree, options, projectName, projectRoot, migrationLogs);
|
||||
}
|
||||
19
packages/expo/src/generators/convert-to-inferred/schema.json
Normal file
19
packages/expo/src/generators/convert-to-inferred/schema.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxExpoConvertToInferred",
|
||||
"description": "Convert existing Expo project(s) using `@nx/expo:*` executors to use `@nx/expo/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert Expo project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/expo:*` executors to use `@nx/expo/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,6 +53,11 @@
|
||||
"factory": "./src/generators/web-configuration/web-configuration#webConfigurationGenerator",
|
||||
"schema": "./src/generators/web-configuration/schema.json",
|
||||
"description": "Set up web configuration for a React Native app"
|
||||
},
|
||||
"convert-to-inferred": {
|
||||
"factory": "./src/generators/convert-to-inferred/convert-to-inferred",
|
||||
"schema": "./src/generators/convert-to-inferred/schema.json",
|
||||
"description": "Convert existing React Native project(s) using `@nx/react-native:*` executors to use `@nx/react-native/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -65,6 +65,12 @@
|
||||
"version": "18.0.0-beta.0",
|
||||
"description": "Add upgrade target to react native projects",
|
||||
"implementation": "./src/migrations/update-18-0-0/add-upgrade-target"
|
||||
},
|
||||
"update-19-6-0-rename-upgrade-target-name": {
|
||||
"cli": "nx",
|
||||
"version": "19.6.0-beta.1",
|
||||
"description": "Rename upgrade target name to fix casing.",
|
||||
"implementation": "./src/migrations/update-19-6-0/rename-upgrade-target-name"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -26,7 +26,7 @@ export interface ReactNativePluginOptions {
|
||||
buildAndroidTargetName?: string;
|
||||
bundleTargetName?: string;
|
||||
syncDepsTargetName?: string;
|
||||
upgradeTargetname?: string;
|
||||
upgradeTargetName?: string;
|
||||
}
|
||||
|
||||
const cachePath = join(workspaceDataDirectory, 'react-native.hash');
|
||||
@ -143,7 +143,7 @@ function buildReactNativeTargets(
|
||||
[options.syncDepsTargetName]: {
|
||||
executor: '@nx/react-native:sync-deps',
|
||||
},
|
||||
[options.upgradeTargetname]: {
|
||||
[options.upgradeTargetName]: {
|
||||
command: `react-native upgrade`,
|
||||
options: { cwd: projectRoot },
|
||||
},
|
||||
@ -194,6 +194,6 @@ function normalizeOptions(
|
||||
options.buildAndroidTargetName ??= 'build-android';
|
||||
options.bundleTargetName ??= 'bundle';
|
||||
options.syncDepsTargetName ??= 'sync-deps';
|
||||
options.upgradeTargetname ??= 'upgrade';
|
||||
options.upgradeTargetName ??= 'upgrade';
|
||||
return options;
|
||||
}
|
||||
|
||||
@ -173,14 +173,14 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-dir/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.release': {
|
||||
binaryPath:
|
||||
'../my-dir/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-dir/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
@ -224,14 +224,14 @@ describe('app', () => {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Debug-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Debug -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
'ios.release': {
|
||||
binaryPath:
|
||||
'../my-app/ios/build/Build/Products/Release-iphonesimulator/MyApp.app',
|
||||
build:
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 14' -derivedDataPath ./build -quiet",
|
||||
"cd ../my-app/ios && xcodebuild -workspace MyApp.xcworkspace -scheme MyApp -configuration Release -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 15 Plus' -derivedDataPath ./build -quiet",
|
||||
type: 'ios.app',
|
||||
},
|
||||
});
|
||||
|
||||
@ -0,0 +1,327 @@
|
||||
import {
|
||||
addProjectConfiguration,
|
||||
type ExpandedPluginConfiguration,
|
||||
joinPathFragments,
|
||||
type ProjectConfiguration,
|
||||
type ProjectGraph,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
type Tree,
|
||||
writeJson,
|
||||
} from '@nx/devkit';
|
||||
import { TempFs } from '@nx/devkit/internal-testing-utils';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { join } from 'node:path';
|
||||
import { getRelativeProjectJsonSchemaPath } from 'nx/src/generators/utils/project-configuration';
|
||||
import { convertToInferred } from './convert-to-inferred';
|
||||
|
||||
let fs: TempFs;
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual('@nx/devkit'),
|
||||
createProjectGraphAsync: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(projectGraph)),
|
||||
updateProjectConfiguration: jest
|
||||
.fn()
|
||||
.mockImplementation((tree, projectName, projectConfiguration) => {
|
||||
function handleEmptyTargets(
|
||||
projectName: string,
|
||||
projectConfiguration: ProjectConfiguration
|
||||
): void {
|
||||
if (
|
||||
projectConfiguration.targets &&
|
||||
!Object.keys(projectConfiguration.targets).length
|
||||
) {
|
||||
// Re-order `targets` to appear after the `// target` comment.
|
||||
delete projectConfiguration.targets;
|
||||
projectConfiguration[
|
||||
'// targets'
|
||||
] = `to see all targets run: nx show project ${projectName} --web`;
|
||||
projectConfiguration.targets = {};
|
||||
} else {
|
||||
delete projectConfiguration['// targets'];
|
||||
}
|
||||
}
|
||||
|
||||
const projectConfigFile = joinPathFragments(
|
||||
projectConfiguration.root,
|
||||
'project.json'
|
||||
);
|
||||
|
||||
if (!tree.exists(projectConfigFile)) {
|
||||
throw new Error(
|
||||
`Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.`
|
||||
);
|
||||
}
|
||||
handleEmptyTargets(projectName, projectConfiguration);
|
||||
writeJson(tree, projectConfigFile, {
|
||||
name: projectConfiguration.name ?? projectName,
|
||||
$schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration),
|
||||
...projectConfiguration,
|
||||
root: undefined,
|
||||
});
|
||||
projectGraph.nodes[projectName].data = projectConfiguration;
|
||||
}),
|
||||
}));
|
||||
jest.mock('nx/src/devkit-internals', () => ({
|
||||
...jest.requireActual('nx/src/devkit-internals'),
|
||||
getExecutorInformation: jest
|
||||
.fn()
|
||||
.mockImplementation((pkg, ...args) =>
|
||||
jest
|
||||
.requireActual('nx/src/devkit-internals')
|
||||
.getExecutorInformation('@nx/webpack', ...args)
|
||||
),
|
||||
}));
|
||||
|
||||
function addProject(tree: Tree, name: string, project: ProjectConfiguration) {
|
||||
addProjectConfiguration(tree, name, project);
|
||||
projectGraph.nodes[name] = {
|
||||
name: name,
|
||||
type: project.projectType === 'application' ? 'app' : 'lib',
|
||||
data: {
|
||||
projectType: project.projectType,
|
||||
root: project.root,
|
||||
targets: project.targets,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectOptions {
|
||||
appName: string;
|
||||
appRoot: string;
|
||||
buildAndroidTargetName: string;
|
||||
buildIosTargetName: string;
|
||||
bundleAndroidTargetName: string;
|
||||
bundleIosTargetName: string;
|
||||
podInstallTargetName: string;
|
||||
runAndroidTargetName: string;
|
||||
runIosTargetName: string;
|
||||
startTargetName: string;
|
||||
syncDepsTargetName: string;
|
||||
upgradeTargetName: string;
|
||||
}
|
||||
|
||||
const defaultProjectOptions: ProjectOptions = {
|
||||
appName: 'demo',
|
||||
appRoot: 'apps/demo',
|
||||
buildAndroidTargetName: 'build-android',
|
||||
buildIosTargetName: 'build-ios',
|
||||
bundleAndroidTargetName: 'bundle-android',
|
||||
bundleIosTargetName: 'bundle-ios',
|
||||
podInstallTargetName: 'pod-install',
|
||||
runAndroidTargetName: 'run-android',
|
||||
runIosTargetName: 'run-ios',
|
||||
syncDepsTargetName: 'sync-deps',
|
||||
startTargetName: 'start',
|
||||
upgradeTargetName: 'upgrade',
|
||||
};
|
||||
|
||||
const appConfig = { name: 'demo', displayName: 'Demo' };
|
||||
|
||||
function writeAppConfig(tree: Tree, projectRoot: string) {
|
||||
tree.write(`${projectRoot}/app.json`, JSON.stringify(appConfig));
|
||||
fs.createFileSync(`${projectRoot}/app.json`, JSON.stringify(appConfig));
|
||||
jest.doMock(join(fs.tempDir, projectRoot, 'app.json'), () => appConfig, {
|
||||
virtual: true,
|
||||
});
|
||||
}
|
||||
|
||||
function createProject(
|
||||
tree: Tree,
|
||||
options: Partial<ProjectOptions> = {},
|
||||
additionalTargetOptions?: Record<string, Record<string, unknown>>
|
||||
) {
|
||||
let projectOptions = { ...defaultProjectOptions, ...options };
|
||||
const project: ProjectConfiguration = {
|
||||
name: projectOptions.appName,
|
||||
root: projectOptions.appRoot,
|
||||
projectType: 'application',
|
||||
targets: {
|
||||
[projectOptions.buildAndroidTargetName]: {
|
||||
executor: '@nx/react-native:build-android',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.buildAndroidTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.buildIosTargetName]: {
|
||||
executor: '@nx/react-native:build-ios',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.buildIosTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.bundleAndroidTargetName]: {
|
||||
executor: '@nx/react-native:bundle',
|
||||
options: {
|
||||
platform: 'android',
|
||||
...additionalTargetOptions?.[projectOptions.bundleAndroidTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.bundleIosTargetName]: {
|
||||
executor: '@nx/react-native:bundle',
|
||||
options: {
|
||||
platform: 'ios',
|
||||
...additionalTargetOptions?.[projectOptions.bundleIosTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.podInstallTargetName]: {
|
||||
executor: '@nx/react-native:pod-install',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.podInstallTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.runAndroidTargetName]: {
|
||||
executor: '@nx/react-native:run-android',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.runAndroidTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.runIosTargetName]: {
|
||||
executor: '@nx/react-native:run-ios',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.runIosTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.startTargetName]: {
|
||||
executor: '@nx/react-native:start',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.startTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.syncDepsTargetName]: {
|
||||
executor: '@nx/react-native:sync-deps',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.syncDepsTargetName],
|
||||
},
|
||||
},
|
||||
[projectOptions.upgradeTargetName]: {
|
||||
executor: '@nx/react-native:upgrade',
|
||||
options: {
|
||||
...additionalTargetOptions?.[projectOptions.upgradeTargetName],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
addProject(tree, project.name, project);
|
||||
fs.createFileSync(
|
||||
`${projectOptions.appRoot}/project.json`,
|
||||
JSON.stringify(project)
|
||||
);
|
||||
|
||||
// These file need to exist for inference, but they can be empty for the convert generator
|
||||
fs.createFileSync(`${projectOptions.appRoot}/package.json`, '{}');
|
||||
fs.createFileSync(`${projectOptions.appRoot}/metro.config.js`, '// empty');
|
||||
|
||||
return project;
|
||||
}
|
||||
|
||||
describe('convert-to-inferred', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(() => {
|
||||
fs = new TempFs('expo');
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.root = fs.tempDir;
|
||||
|
||||
projectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fs.cleanup();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should convert project to use inference plugin', async () => {
|
||||
const project = createProject(tree);
|
||||
writeAppConfig(tree, project.root);
|
||||
|
||||
const project2 = createProject(tree, {
|
||||
appName: 'app2',
|
||||
appRoot: 'apps/app2',
|
||||
});
|
||||
|
||||
const project2Build = project2.targets['build-ios'];
|
||||
|
||||
await convertToInferred(tree, { project: project.name });
|
||||
|
||||
const nxJsonPlugins = readNxJson(tree).plugins;
|
||||
const rnPlugin = nxJsonPlugins.find(
|
||||
(plugin): plugin is ExpandedPluginConfiguration =>
|
||||
typeof plugin !== 'string' &&
|
||||
plugin.plugin === '@nx/react-native/plugin'
|
||||
);
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, project.name);
|
||||
|
||||
expect(rnPlugin).toBeDefined();
|
||||
expect(projectConfig.targets).toEqual({
|
||||
'bundle-android': {
|
||||
executor: '@nx/react-native:bundle',
|
||||
options: {
|
||||
platform: 'android',
|
||||
},
|
||||
},
|
||||
'bundle-ios': {
|
||||
executor: '@nx/react-native:bundle',
|
||||
options: {
|
||||
platform: 'ios',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const updatedProject2 = readProjectConfiguration(tree, project2.name);
|
||||
expect(updatedProject2.targets['build-ios']).toEqual(project2Build);
|
||||
});
|
||||
|
||||
it('should migrate options to CLI options and args', async () => {
|
||||
const project = createProject(
|
||||
tree,
|
||||
{},
|
||||
{
|
||||
'build-android': {
|
||||
mode: 'Release',
|
||||
},
|
||||
'build-ios': {
|
||||
mode: 'Release',
|
||||
},
|
||||
'run-android': {
|
||||
resetCache: true,
|
||||
activeArchOnly: true,
|
||||
},
|
||||
'run-ios': {
|
||||
resetCache: true,
|
||||
buildFolder: './custom',
|
||||
},
|
||||
start: {
|
||||
resetCache: true,
|
||||
},
|
||||
}
|
||||
);
|
||||
writeAppConfig(tree, project.root);
|
||||
|
||||
await convertToInferred(tree, { project: project.name });
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, project.name);
|
||||
expect(projectConfig.targets['build-android'].options).toEqual({
|
||||
args: ['--mode', 'Release'],
|
||||
});
|
||||
expect(projectConfig.targets['build-ios'].options).toEqual({
|
||||
args: ['--mode', 'Release'],
|
||||
});
|
||||
expect(projectConfig.targets['run-android'].options).toEqual({
|
||||
args: ['--active-arch-only'],
|
||||
});
|
||||
expect(projectConfig.targets['run-ios'].options).toEqual({
|
||||
args: ['--buildFolder', './custom'],
|
||||
});
|
||||
expect(projectConfig.targets['start'].options).toEqual({
|
||||
args: ['--reset-cache'],
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,138 @@
|
||||
import { createProjectGraphAsync, formatFiles, type Tree } from '@nx/devkit';
|
||||
import { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { migrateProjectExecutorsToPluginV1 } from '@nx/devkit/src/generators/plugin-migrations/executor-to-plugin-migrator';
|
||||
import { createNodes } from '../../../plugins/plugin';
|
||||
import { postTargetTransformer } from './lib/post-target-transformer';
|
||||
import { processStartOptions } from './lib/process-start-options';
|
||||
import { createProcessOptions } from './lib/create-process-options';
|
||||
|
||||
interface Schema {
|
||||
project?: string;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
export async function convertToInferred(tree: Tree, options: Schema) {
|
||||
const projectGraph = await createProjectGraphAsync();
|
||||
const migrationLogs = new AggregatedLog();
|
||||
const migratedProjects = await migrateProjectExecutorsToPluginV1(
|
||||
tree,
|
||||
projectGraph,
|
||||
'@nx/react-native/plugin',
|
||||
createNodes,
|
||||
{
|
||||
buildAndroidTargetName: 'build-android',
|
||||
buildIosTargetName: 'build-ios',
|
||||
bundleTargetName: 'bundle',
|
||||
podInstallTargetName: 'pod-install',
|
||||
runAndroidTargetName: 'run-android',
|
||||
runIosTargetName: 'run-ios',
|
||||
startTargetName: 'start',
|
||||
syncDepsTargetName: 'sync-deps',
|
||||
upgradeTargetName: 'upgrade',
|
||||
},
|
||||
[
|
||||
{
|
||||
executors: ['@nx/react-native:build-android'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
createProcessOptions(
|
||||
'@nx/react-native:build-android',
|
||||
['port', 'resetCache'],
|
||||
[]
|
||||
)
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
buildAndroidTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/react-native:build-ios'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
createProcessOptions(
|
||||
'@nx/react-native:build-ios',
|
||||
['port', 'resetCache'],
|
||||
['buildFolder']
|
||||
)
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
buildIosTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/react-native:run-android'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
createProcessOptions(
|
||||
'@nx/react-native:run-android',
|
||||
['port', 'resetCache'],
|
||||
['appId', 'appIdSuffix', 'deviceId']
|
||||
)
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
runAndroidTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/react-native:pod-install'],
|
||||
postTargetTransformer: postTargetTransformer(migrationLogs),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
podInstallTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/react-native:run-ios'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
createProcessOptions(
|
||||
'@nx/react-native:run-ios',
|
||||
['port', 'resetCache'],
|
||||
['buildFolder']
|
||||
)
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
runIosTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/react-native:start'],
|
||||
postTargetTransformer: postTargetTransformer(
|
||||
migrationLogs,
|
||||
processStartOptions
|
||||
),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
startTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/react-native:sync-deps'],
|
||||
postTargetTransformer: postTargetTransformer(migrationLogs),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
startTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
{
|
||||
executors: ['@nx/react-native:upgrade'],
|
||||
postTargetTransformer: postTargetTransformer(migrationLogs),
|
||||
targetPluginOptionMapper: (targetName) => ({
|
||||
upgradeTargetName: targetName,
|
||||
}),
|
||||
},
|
||||
],
|
||||
options.project
|
||||
);
|
||||
|
||||
if (migratedProjects.size === 0) {
|
||||
throw new Error('Could not find any targets to migrate.');
|
||||
}
|
||||
|
||||
if (!options.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
return () => {
|
||||
migrationLogs.flushLogs();
|
||||
};
|
||||
}
|
||||
|
||||
export default convertToInferred;
|
||||
@ -0,0 +1,43 @@
|
||||
import { names } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
|
||||
/**
|
||||
* Logic copied from `packages/react-native/src/utils/get-cli-options.ts`,
|
||||
* which was used by most executors to map their options to CLI options.
|
||||
*/
|
||||
export function createProcessOptions(
|
||||
executorName: string,
|
||||
optionKeysToIgnore: string[],
|
||||
optionKeysInCamelName: string[]
|
||||
) {
|
||||
return (projectName: string, options: any, migrationLogs: AggregatedLog) => {
|
||||
const args = [];
|
||||
for (const optionKey of Object.keys(options)) {
|
||||
const optionValue = options[optionKey];
|
||||
delete options[optionKey];
|
||||
|
||||
if (optionKeysToIgnore.includes(optionKey)) {
|
||||
migrationLogs.addLog({
|
||||
project: projectName,
|
||||
executorName,
|
||||
log: `Unable to migrate '${optionKey}' to inferred target configuration.`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const cliKey = optionKeysInCamelName.includes(optionKey)
|
||||
? names(optionKey).propertyName
|
||||
: names(optionKey).fileName; // cli uses kebab case as default
|
||||
|
||||
if (Array.isArray(optionValue)) {
|
||||
args.push(`--${cliKey}`, optionValue.join(','));
|
||||
} else if (typeof optionValue === 'boolean' && optionValue) {
|
||||
// no need to pass in the value when it is true, just the flag name
|
||||
args.push(`--${cliKey}`);
|
||||
} else {
|
||||
args.push(`--${cliKey}`, optionValue);
|
||||
}
|
||||
}
|
||||
options.args = args;
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
import type { TargetConfiguration, Tree } from '@nx/devkit';
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
import { processTargetOutputs } from '@nx/devkit/src/generators/plugin-migrations/plugin-migration-utils';
|
||||
|
||||
export function postTargetTransformer(
|
||||
migrationLogs: AggregatedLog,
|
||||
processOptions?: (
|
||||
projectName: string,
|
||||
options: any,
|
||||
migrationLogs: AggregatedLog
|
||||
) => void
|
||||
) {
|
||||
return (
|
||||
target: TargetConfiguration,
|
||||
_tree: Tree,
|
||||
projectDetails: { projectName: string; root: string },
|
||||
inferredTargetConfiguration: TargetConfiguration
|
||||
) => {
|
||||
if (target.options && processOptions) {
|
||||
processOptions(projectDetails.projectName, target.options, migrationLogs);
|
||||
}
|
||||
|
||||
if (target.configurations && processOptions) {
|
||||
for (const configurationName in target.configurations) {
|
||||
const configuration = target.configurations[configurationName];
|
||||
processOptions(
|
||||
projectDetails.projectName,
|
||||
configuration,
|
||||
migrationLogs
|
||||
);
|
||||
}
|
||||
|
||||
if (Object.keys(target.configurations).length === 0) {
|
||||
if ('defaultConfiguration' in target) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
delete target.configurations;
|
||||
}
|
||||
|
||||
if (
|
||||
'defaultConfiguration' in target &&
|
||||
!target.configurations[target.defaultConfiguration]
|
||||
) {
|
||||
delete target.defaultConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
if (target.outputs) {
|
||||
processTargetOutputs(target, [], inferredTargetConfiguration, {
|
||||
projectName: projectDetails.projectName,
|
||||
projectRoot: projectDetails.root,
|
||||
});
|
||||
}
|
||||
|
||||
return target;
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import type { AggregatedLog } from '@nx/devkit/src/generators/plugin-migrations/aggregate-log-util';
|
||||
|
||||
export function processStartOptions(
|
||||
_projectName: string,
|
||||
options: any,
|
||||
_migrationLogs: AggregatedLog
|
||||
) {
|
||||
const args: string[] = [];
|
||||
for (const k of Object.keys(options)) {
|
||||
if (k === 'resetCache') {
|
||||
if (options[k] === true) {
|
||||
args.push(`--reset-cache`);
|
||||
}
|
||||
} else if (k === 'interactive') {
|
||||
if (options[k] === false) {
|
||||
args.push(`--no-interactive`);
|
||||
}
|
||||
} else {
|
||||
args.push(`--${k}`, options[k]);
|
||||
}
|
||||
delete options[k];
|
||||
}
|
||||
options.args = args;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/schema",
|
||||
"$id": "NxReactNativeConvertToInferred",
|
||||
"description": "Convert existing React Native project(s) using `@nx/react-native:*` executors to use `@nx/react-native/plugin`. Defaults to migrating all projects. Pass '--project' to migrate only one target.",
|
||||
"title": "Convert React Native project from executor to plugin",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"project": {
|
||||
"type": "string",
|
||||
"description": "The project to convert from using the `@nx/react-native:*` executors to use `@nx/react-native/plugin`.",
|
||||
"x-priority": "important"
|
||||
},
|
||||
"skipFormat": {
|
||||
"type": "boolean",
|
||||
"description": "Whether to format files at the end of the migration.",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -46,7 +46,7 @@ export async function reactNativeInitGeneratorInternal(
|
||||
createNodes,
|
||||
{
|
||||
startTargetName: ['start', 'react-native:start', 'react-native-start'],
|
||||
upgradeTargetname: [
|
||||
upgradeTargetName: [
|
||||
'update',
|
||||
'react-native:update',
|
||||
'react-native-update',
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { readNxJson, Tree, updateNxJson } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import update from './rename-upgrade-target-name';
|
||||
|
||||
describe('rename-upgrade-target-name', () => {
|
||||
let tree: Tree;
|
||||
|
||||
beforeEach(async () => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
});
|
||||
|
||||
it('should fix upgrade target name option', async () => {
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/react-native/plugin',
|
||||
options: {
|
||||
upgradeTargetname: 'upgrade',
|
||||
buildIosTargetName: 'build-ios',
|
||||
},
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
await update(tree);
|
||||
|
||||
const updatedNxJson = readNxJson(tree);
|
||||
|
||||
expect(updatedNxJson.plugins).toEqual([
|
||||
{
|
||||
plugin: '@nx/react-native/plugin',
|
||||
options: {
|
||||
upgradeTargetName: 'upgrade',
|
||||
buildIosTargetName: 'build-ios',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import { formatFiles, readNxJson, Tree, updateNxJson } from '@nx/devkit';
|
||||
|
||||
/**
|
||||
* There was a typo in @nx/react-native/plugin, where "upgradeTargetName" was "upgradeTargetname"
|
||||
*/
|
||||
export default async function update(tree: Tree) {
|
||||
const nxJson = readNxJson(tree);
|
||||
let updated = false;
|
||||
for (const plugin of nxJson.plugins) {
|
||||
if (typeof plugin === 'string') continue;
|
||||
if (plugin.plugin !== '@nx/react-native/plugin') continue;
|
||||
if (plugin.options['upgradeTargetname']) {
|
||||
plugin.options['upgradeTargetName'] = plugin.options['upgradeTargetname'];
|
||||
delete plugin.options['upgradeTargetname'];
|
||||
updated = true;
|
||||
}
|
||||
}
|
||||
if (updated) updateNxJson(tree, nxJson);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user