diff --git a/package.json b/package.json index 6b7b51786..65d1dbfd0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "nx": "nx", "start": "nx serve", - "build": "nx build nx", + "build": "nx prepare nx", "test": "nx run-many --target=test --all --parallel", "lint": "nx workspace-lint && nx lint", "e2e": "nx e2e", @@ -46,13 +46,14 @@ "@types/jest": "29.5.13", "@types/node": "^20.0.0", "@types/plist": "^3.0.2", - "@types/xml2js": "^0.4.9", "conventional-changelog-cli": "^5.0.0", "cz-conventional-changelog": "^3.3.0", "doctoc": "^2.0.0", "dotenv": "~16.4.0", + "enquirer": "^2.4.1", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", + "fast-xml-parser": "^4.5.0", "fs-extra": "^11.2.0", "jest": "29.7.0", "nx": "20.0.3", @@ -62,8 +63,8 @@ "ts-jest": "29.2.5", "ts-node": "10.9.2", "tslib": "^2.8.0", - "typescript": "^5.6.0", - "xml2js": "^0.6.2" + "typescript": "^5.6.0" }, "dependencies": {} } + diff --git a/packages/nx/README.md b/packages/nx/README.md index de30df756..412f97354 100644 --- a/packages/nx/README.md +++ b/packages/nx/README.md @@ -246,13 +246,27 @@ Here's an example app config: "executor": "@nativescript/nx:build", "options": { "noHmr": true, - "production": true, "uglify": true, + "forDevice": true, "release": true, - "forDevice": true + "android": { + "copyTo": "./dist/app.apk", + "keyStorePath": "/path/to/android.keystore", + "keyStoreAlias": "app", + "keyStorePassword": "pass", + "keyStoreAliasPassword": "pass" + }, + "ios": { + "copyTo": "./dist/app.ipa" + } }, "configurations": { - "prod": { + "production": { + "production": true, + "release": true, + "android": { + "keyStorePassword": "productionpw" + }, "fileReplacements": [ { "replace": "./src/environments/environment.ts", @@ -262,56 +276,69 @@ Here's an example app config: } } }, - "ios": { - "executor": "@nativescript/nx:build", + "debug": { + "executor": "@nativescript/nx:debug", "options": { - "platform": "ios" + "noHmr": true, + "uglify": false, + "release": false, + "forDevice": false, + "prepare": false }, "configurations": { - "build": { - "provision": "AppStore Profile", - "copyTo": "./dist/build.ipa" - }, "prod": { - "combineWithConfig": "build:prod" + "fileReplacements": [ + { + "replace": "./src/environments/environment.ts", + "with": "./src/environments/environment.prod.ts" + } + ] } } }, - "android": { - "executor": "@nativescript/nx:build", + "prepare": { + "executor": "@nativescript/nx:prepare", "options": { - "platform": "android" + "noHmr": true, + "production": true, + "uglify": true, + "release": true, + "forDevice": true, + "prepare": true }, "configurations": { - "build": { - "aab": true, - "keyStorePath": "./tools/keystore.jks", - "keyStorePassword": "your-password", - "keyStoreAlias": "keystore-alias", - "keyStoreAliasPassword": "keystore-alias-password", - "copyTo": "./dist/build.aab" - }, "prod": { - "combineWithConfig": "build:prod" + "fileReplacements": [ + { + "replace": "./src/environments/environment.ts", + "with": "./src/environments/environment.prod.ts" + } + ] } } }, - "test": { - "executor": "@nativescript/nx:test", - "outputs": ["coverage/apps/nativescript-mobile"], + "clean": { + "executor": "@nativescript/nx:clean", + "options": {} + }, + "lint": { + "executor": "@nx/linter:eslint", "options": { - "coverage": false - }, - "configurations": { - "android": {}, - "ios": {} + "lintFilePatterns": [ + "apps/nativescript-app/**/*.ts", + "apps/nativescript-app/src/**/*.html" + ] } }, - "clean": { - "executor": "@nativescript/nx:build", + "test": { + "executor": "@nativescript/nx:test", + "outputs": [ + "coverage/apps/nativescript-app" + ], "options": { - "clean": true - } + "coverage": true + }, + "configurations": {} } } } @@ -322,13 +349,13 @@ Here's an example app config: **Android:** ```sh -npx nx run :android:prod +npx nx debug android -c=prod ``` **iOS:** (Mac only) ```sh -npx nx run :ios:prod +npx nx debug ios -c=prod ``` #### Run tests @@ -336,19 +363,19 @@ npx nx run :ios:prod **Android:** ```sh -npx nx run :test:android +npx nx test android ``` **iOS:** (Mac only) ```sh -npx nx run :test:ios +npx nx test ios ``` You can generate coverage reports by using the flag with iOS or Android, for example: ```sh -npx nx run :test:ios --coverage +npx nx test ios --coverage ``` You can also set this option in the config, for example: @@ -380,7 +407,7 @@ Build with an environment configuration enabled (for example, with `prod`): **Android:** ```sh -npx nx run :build:prod --platform=android +npx nx build android --c=prod ``` You can pass additional NativeScript CLI options as flags on the end of you build command. @@ -388,7 +415,7 @@ You can pass additional NativeScript CLI options as flags on the end of you buil * example of building AAB bundle for upload to Google Play: ``` -npx nx run :build:prod --platform=android \ +npx nx build android --c=prod \ --aab \ --key-store-path= \ --key-store-password= \ @@ -400,7 +427,7 @@ npx nx run :build:prod --platform=android \ **iOS:** (Mac only) ```sh -npx nx run :build:prod --platform=ios +npx nx build iod --c=prod ``` As mentioned, you can pass any additional NativeScript CLI options as flags on the end of your nx build command: @@ -408,7 +435,7 @@ As mentioned, you can pass any additional NativeScript CLI options as flags on t * example of building IPA for upload to iOS TestFlight: ``` -npx nx run :build:prod --platform=ios \ +npx nx build ios --c=prod \ --provision \ --copy-to ./dist/build.ipa ``` @@ -418,7 +445,7 @@ npx nx run :build:prod --platform=ios \ It can be helpful to clean the app at times. This will clear out old dependencies plus iOS/Android platform files to give your app a nice reset. ```sh -npx nx run :clean +npx nx clean ``` ## Create NativeScript library diff --git a/packages/nx/executors.json b/packages/nx/executors.json index 860f125af..ed395d866 100644 --- a/packages/nx/executors.json +++ b/packages/nx/executors.json @@ -3,25 +3,65 @@ "executors": { "build": { "implementation": "./src/executors/build/executor", - "schema": "./src/executors/build/schema.json", - "description": "NativeScript builder" + "schema": "./src/executors/build/build.schema.json", + "description": "NativeScript executor" + }, + "debug": { + "implementation": "./src/executors/build/executor", + "schema": "./src/executors/build/debug.schema.json", + "description": "NativeScript debug executor" + }, + "run": { + "implementation": "./src/executors/build/executor", + "schema": "./src/executors/build/run.schema.json", + "description": "NativeScript run executor" + }, + "prepare": { + "implementation": "./src/executors/build/executor", + "schema": "./src/executors/build/prepare.schema.json", + "description": "NativeScript prepare executor" }, "test": { - "implementation": "./src/executors/test/executor", - "schema": "./src/executors/test/schema.json", + "implementation": "./src/executors/build/executor", + "schema": "./src/executors/build/test.schema.json", "description": "Start the NativeScript unit test runner" + }, + "clean": { + "implementation": "./src/executors/build/executor", + "schema": "./src/executors/build/clean.schema.json", + "description": "NativeScript clean executor" } }, "builders": { "build": { "implementation": "./src/executors/build/builder", - "schema": "./src/executors/build/schema.json", - "description": "build executor" + "schema": "./src/executors/build/build.schema.json", + "description": "NativeScript build builder" + }, + "debug": { + "implementation": "./src/executors/build/builder", + "schema": "./src/executors/build/debug.schema.json", + "description": "NativeScript debug builder" + }, + "run": { + "implementation": "./src/executors/build/builder", + "schema": "./src/executors/build/run.schema.json", + "description": "NativeScript run builder" + }, + "prepare": { + "implementation": "./src/executors/build/builder", + "schema": "./src/executors/build/prepare.schema.json", + "description": "NativeScript prepare builder" }, "test": { - "implementation": "./src/executors/test/builder", - "schema": "./src/executors/test/schema.json", - "description": "test executor" + "implementation": "./src/executors/build/builder", + "schema": "./src/executors/build/test.schema.json", + "description": "NativeScript test builder" + }, + "clean": { + "implementation": "./src/executors/build/builder", + "schema": "./src/executors/build/clean.schema.json", + "description": "NativeScript clean builder" } } } diff --git a/packages/nx/project.json b/packages/nx/project.json index 2972b9306..41d712558 100644 --- a/packages/nx/project.json +++ b/packages/nx/project.json @@ -20,6 +20,19 @@ "jestConfig": "packages/nx/jest.config.ts" } }, + "prepare": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "ts-node -P ./packages/nx/tsconfig.lib.json ./packages/nx/src/utils/generate-schemas.ts" + }, + { + "command": "nx build nx" + } + ] + } + }, "build": { "executor": "@nx/js:tsc", "outputs": ["{options.outputPath}"], diff --git a/packages/nx/src/executors/build/build.schema.json b/packages/nx/src/executors/build/build.schema.json new file mode 100644 index 000000000..4c82eabf4 --- /dev/null +++ b/packages/nx/src/executors/build/build.schema.json @@ -0,0 +1,145 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "NativeScript builder", + "description": "", + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "NativeScript CLI command to invoke", + "default": "build" + }, + "platform": { + "type": "string", + "description": "Platform to run on" + }, + "force": { + "type": "boolean", + "default": true, + "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + }, + "silent": { + "type": "boolean", + "default": false, + "description": "If true, skips prompts.", + "alias": "s" + }, + "verbose": { + "type": "boolean", + "default": false, + "description": "Enable verbose logging" + }, + "id": { + "type": "string", + "description": "App bundle id. Use with configurations that desire a specific bundle id to be set." + }, + "combineWithConfig": { + "type": "string", + "description": "Used with targets to share build configurations and avoid duplicating configurations across multiple targets." + }, + "android": { + "type": "object", + "aab": { + "type": "boolean", + "default": false, + "description": "(Android Only) When building, create an Android App Bundle (.aab file)." + }, + "keyStorePath": { + "type": "string", + "description": "(Android Only) When building, use the keystore file at this location." + }, + "keyStorePassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore password." + }, + "keyStoreAlias": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias." + }, + "keyStoreAliasPassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias password." + }, + "xmlUpdates": { + "type": "object", + "description": "Update any .xml value. Specify name of any filename with key/value pairs, e.g. { 'src/main/res/values/strings.xml': { app_name: 'MyApp', title_activity_kimera: 'MyApp' } }. Defaults to look in App_Resources/Android/{filepath} however you can specify relative path if located elsewhere." + } + }, + "ios": { + "type": "object", + "required": [], + "provision": { + "type": "string", + "description": "(iOS Only) When building, use this provision profile name." + }, + "plistUpdates": { + "type": "object", + "description": "Update any .plist value. Specify name of any filename with key/value pairs, e.g. { 'Info.plist': { CFBundleDisplayName: 'MyApp' } }. Defaults to look in App_Resources/iOS/{filepath} however you can specify relative path if located elsewhere." + } + }, + "device": { + "type": "string", + "description": "Device identifier to run app on.", + "alias": "d" + }, + "emulator": { + "type": "boolean", + "default": false, + "description": "Explicitly run with an emulator or simulator" + }, + "noHmr": { + "type": "boolean", + "default": false, + "description": "Disable HMR" + }, + "uglify": { + "type": "boolean", + "default": false, + "description": "Enable uglify during the webpack build" + }, + "release": { + "type": "boolean", + "default": false, + "description": "Enable release mode during build using the --release flag" + }, + "forDevice": { + "type": "boolean", + "default": false, + "description": "Build in device mode using the --for-device flag" + }, + "production": { + "type": "boolean", + "default": false, + "description": "Build in production mode using the --env.production flag", + "alias": "prod" + }, + "fileReplacements": { + "description": "Replace files with other files in the build.", + "type": "array", + "items": { + "type": "object", + "properties": { + "replace": { + "type": "string", + "description": "The file to be replaced." + }, + "with": { + "type": "string", + "description": "The file to replace with." + } + }, + "additionalProperties": false, + "required": [ + "replace", + "with" + ] + }, + "default": [] + }, + "clean": { + "type": "boolean", + "default": false, + "description": "Do a full project clean" + } + } +} \ No newline at end of file diff --git a/packages/nx/src/executors/build/clean.schema.json b/packages/nx/src/executors/build/clean.schema.json new file mode 100644 index 000000000..e158d5988 --- /dev/null +++ b/packages/nx/src/executors/build/clean.schema.json @@ -0,0 +1,30 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "NativeScript clean", + "description": "", + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "NativeScript CLI command to invoke", + "default": "clean" + }, + "force": { + "type": "boolean", + "default": true, + "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + }, + "silent": { + "type": "boolean", + "default": false, + "description": "If true, skips prompts.", + "alias": "s" + }, + "verbose": { + "type": "boolean", + "default": false, + "description": "Enable verbose logging" + } + }, + "required": [] +} \ No newline at end of file diff --git a/packages/nx/src/executors/build/debug.schema.json b/packages/nx/src/executors/build/debug.schema.json new file mode 100644 index 000000000..2dcf590c2 --- /dev/null +++ b/packages/nx/src/executors/build/debug.schema.json @@ -0,0 +1,168 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "NativeScript builder", + "description": "", + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "NativeScript CLI command to invoke", + "default": "debug" + }, + "platform": { + "type": "string", + "description": "Platform to run on" + }, + "force": { + "type": "boolean", + "default": true, + "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + }, + "silent": { + "type": "boolean", + "default": false, + "description": "If true, skips prompts.", + "alias": "s" + }, + "verbose": { + "type": "boolean", + "default": false, + "description": "Enable verbose logging" + }, + "id": { + "type": "string", + "description": "App bundle id. Use with configurations that desire a specific bundle id to be set." + }, + "combineWithConfig": { + "type": "string", + "description": "Used with targets to share build configurations and avoid duplicating configurations across multiple targets." + }, + "android": { + "type": "object", + "aab": { + "type": "boolean", + "default": false, + "description": "(Android Only) When building, create an Android App Bundle (.aab file)." + }, + "keyStorePath": { + "type": "string", + "description": "(Android Only) When building, use the keystore file at this location." + }, + "keyStorePassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore password." + }, + "keyStoreAlias": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias." + }, + "keyStoreAliasPassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias password." + }, + "xmlUpdates": { + "type": "object", + "description": "Update any .xml value. Specify name of any filename with key/value pairs, e.g. { 'src/main/res/values/strings.xml': { app_name: 'MyApp', title_activity_kimera: 'MyApp' } }. Defaults to look in App_Resources/Android/{filepath} however you can specify relative path if located elsewhere." + } + }, + "ios": { + "type": "object", + "required": [], + "provision": { + "type": "string", + "description": "(iOS Only) When building, use this provision profile name." + }, + "plistUpdates": { + "type": "object", + "description": "Update any .plist value. Specify name of any filename with key/value pairs, e.g. { 'Info.plist': { CFBundleDisplayName: 'MyApp' } }. Defaults to look in App_Resources/iOS/{filepath} however you can specify relative path if located elsewhere." + } + }, + "device": { + "type": "string", + "description": "Device identifier to run app on.", + "alias": "d" + }, + "emulator": { + "type": "boolean", + "default": false, + "description": "Explicitly run with an emulator or simulator" + }, + "noHmr": { + "type": "boolean", + "default": false, + "description": "Disable HMR" + }, + "uglify": { + "type": "boolean", + "default": false, + "description": "Enable uglify during the webpack build" + }, + "release": { + "type": "boolean", + "default": false, + "description": "Enable release mode during build using the --release flag" + }, + "forDevice": { + "type": "boolean", + "default": false, + "description": "Build in device mode using the --for-device flag" + }, + "production": { + "type": "boolean", + "default": false, + "description": "Build in production mode using the --env.production flag", + "alias": "prod" + }, + "fileReplacements": { + "description": "Replace files with other files in the build.", + "type": "array", + "items": { + "type": "object", + "properties": { + "replace": { + "type": "string", + "description": "The file to be replaced." + }, + "with": { + "type": "string", + "description": "The file to replace with." + } + }, + "additionalProperties": false, + "required": [ + "replace", + "with" + ] + }, + "default": [] + }, + "clean": { + "type": "boolean", + "default": false, + "description": "Do a full project clean" + }, + "debug": { + "type": "boolean", + "default": true, + "description": "Use 'ns debug' instead of 'ns run'. Defaults to true" + }, + "timeout": { + "type": "number", + "default": -1, + "description": "Increase the default 90s timeout to connect to a device/simulator" + }, + "copyTo": { + "type": "string", + "description": "When building, copy the package to this location." + }, + "prepare": { + "type": "boolean", + "description": "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + "default": false + }, + "flags": { + "type": "string", + "description": "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts" + } + } +} \ No newline at end of file diff --git a/packages/nx/src/executors/build/executor.spec.ts b/packages/nx/src/executors/build/executor.spec.ts index e77a16e29..f58713732 100644 --- a/packages/nx/src/executors/build/executor.spec.ts +++ b/packages/nx/src/executors/build/executor.spec.ts @@ -1,8 +1,6 @@ -import { ExecutorContext } from '@nx/devkit'; -import { BuildExecutorSchema } from '../../utils'; -import runExecutor from './executor'; +import { ExecutorSchema } from '../../utils/types'; -const options: BuildExecutorSchema = { +const options: Partial = { noHmr: true, prepare: true, platform: 'ios', diff --git a/packages/nx/src/executors/build/executor.ts b/packages/nx/src/executors/build/executor.ts index 4b9563763..980cc1a4e 100644 --- a/packages/nx/src/executors/build/executor.ts +++ b/packages/nx/src/executors/build/executor.ts @@ -1,6 +1,7 @@ import { ExecutorContext } from '@nx/devkit'; -import { BuildExecutorSchema, commonExecutor } from '../../utils'; +import { commonExecutor } from '../../utils'; +import { ExecutorSchema } from '../../utils/types'; -export default async function runExecutor(options: BuildExecutorSchema, context: ExecutorContext) { +export default async function runExecutor(options: ExecutorSchema, context: ExecutorContext) { return commonExecutor(options, context); } diff --git a/packages/nx/src/executors/build/prepare.schema.json b/packages/nx/src/executors/build/prepare.schema.json new file mode 100644 index 000000000..da5f79c97 --- /dev/null +++ b/packages/nx/src/executors/build/prepare.schema.json @@ -0,0 +1,167 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "NativeScript builder", + "description": "", + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "NativeScript CLI command to invoke", + "default": "prepare" + }, + "platform": { + "type": "string", + "description": "Platform to run on" + }, + "force": { + "type": "boolean", + "default": true, + "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + }, + "silent": { + "type": "boolean", + "default": false, + "description": "If true, skips prompts.", + "alias": "s" + }, + "verbose": { + "type": "boolean", + "default": false, + "description": "Enable verbose logging" + }, + "id": { + "type": "string", + "description": "App bundle id. Use with configurations that desire a specific bundle id to be set." + }, + "combineWithConfig": { + "type": "string", + "description": "Used with targets to share build configurations and avoid duplicating configurations across multiple targets." + }, + "android": { + "type": "object", + "aab": { + "type": "boolean", + "default": false, + "description": "(Android Only) When building, create an Android App Bundle (.aab file)." + }, + "keyStorePath": { + "type": "string", + "description": "(Android Only) When building, use the keystore file at this location." + }, + "keyStorePassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore password." + }, + "keyStoreAlias": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias." + }, + "keyStoreAliasPassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias password." + }, + "xmlUpdates": { + "type": "object", + "description": "Update any .xml value. Specify name of any filename with key/value pairs, e.g. { 'src/main/res/values/strings.xml': { app_name: 'MyApp', title_activity_kimera: 'MyApp' } }. Defaults to look in App_Resources/Android/{filepath} however you can specify relative path if located elsewhere." + } + }, + "ios": { + "type": "object", + "required": [], + "provision": { + "type": "string", + "description": "(iOS Only) When building, use this provision profile name." + }, + "plistUpdates": { + "type": "object", + "description": "Update any .plist value. Specify name of any filename with key/value pairs, e.g. { 'Info.plist': { CFBundleDisplayName: 'MyApp' } }. Defaults to look in App_Resources/iOS/{filepath} however you can specify relative path if located elsewhere." + } + }, + "device": { + "type": "string", + "description": "Device identifier to run app on.", + "alias": "d" + }, + "emulator": { + "type": "boolean", + "default": false, + "description": "Explicitly run with an emulator or simulator" + }, + "noHmr": { + "type": "boolean", + "default": false, + "description": "Disable HMR" + }, + "uglify": { + "type": "boolean", + "default": false, + "description": "Enable uglify during the webpack build" + }, + "release": { + "type": "boolean", + "default": false, + "description": "Enable release mode during build using the --release flag" + }, + "forDevice": { + "type": "boolean", + "default": false, + "description": "Build in device mode using the --for-device flag" + }, + "production": { + "type": "boolean", + "default": false, + "description": "Build in production mode using the --env.production flag" + }, + "fileReplacements": { + "description": "Replace files with other files in the build.", + "type": "array", + "items": { + "type": "object", + "properties": { + "replace": { + "type": "string", + "description": "The file to be replaced." + }, + "with": { + "type": "string", + "description": "The file to replace with." + } + }, + "additionalProperties": false, + "required": [ + "replace", + "with" + ] + }, + "default": [] + }, + "clean": { + "type": "boolean", + "default": false, + "description": "Do a full project clean" + }, + "debug": { + "type": "boolean", + "default": true, + "description": "Use 'ns debug' instead of 'ns run'. Defaults to true" + }, + "timeout": { + "type": "number", + "default": -1, + "description": "Increase the default 90s timeout to connect to a device/simulator" + }, + "copyTo": { + "type": "string", + "description": "When building, copy the package to this location." + }, + "prepare": { + "type": "boolean", + "description": "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + "default": false + }, + "flags": { + "type": "string", + "description": "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts" + } + } +} \ No newline at end of file diff --git a/packages/nx/src/executors/build/schema.json b/packages/nx/src/executors/build/run.schema.json similarity index 64% rename from packages/nx/src/executors/build/schema.json rename to packages/nx/src/executors/build/run.schema.json index 79a123940..793edecec 100644 --- a/packages/nx/src/executors/build/schema.json +++ b/packages/nx/src/executors/build/run.schema.json @@ -1,19 +1,81 @@ { - "version": 2, - "outputCapture": "direct-nodejs", "$schema": "http://json-schema.org/schema", "title": "NativeScript builder", "description": "", "type": "object", "properties": { + "command": { + "type": "string", + "description": "NativeScript CLI command to invoke", + "default": "run" + }, "platform": { "type": "string", "description": "Platform to run on" }, - "debug": { + "force": { "type": "boolean", "default": true, - "description": "Use 'ns debug' instead of 'ns run'. Defaults to true" + "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + }, + "silent": { + "type": "boolean", + "default": false, + "description": "If true, skips prompts.", + "alias": "s" + }, + "verbose": { + "type": "boolean", + "default": false, + "description": "Enable verbose logging" + }, + "id": { + "type": "string", + "description": "App bundle id. Use with configurations that desire a specific bundle id to be set." + }, + "combineWithConfig": { + "type": "string", + "description": "Used with targets to share build configurations and avoid duplicating configurations across multiple targets." + }, + "android": { + "type": "object", + "aab": { + "type": "boolean", + "default": false, + "description": "(Android Only) When building, create an Android App Bundle (.aab file)." + }, + "keyStorePath": { + "type": "string", + "description": "(Android Only) When building, use the keystore file at this location." + }, + "keyStorePassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore password." + }, + "keyStoreAlias": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias." + }, + "keyStoreAliasPassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias password." + }, + "xmlUpdates": { + "type": "object", + "description": "Update any .xml value. Specify name of any filename with key/value pairs, e.g. { 'src/main/res/values/strings.xml': { app_name: 'MyApp', title_activity_kimera: 'MyApp' } }. Defaults to look in App_Resources/Android/{filepath} however you can specify relative path if located elsewhere." + } + }, + "ios": { + "type": "object", + "required": [], + "provision": { + "type": "string", + "description": "(iOS Only) When building, use this provision profile name." + }, + "plistUpdates": { + "type": "object", + "description": "Update any .plist value. Specify name of any filename with key/value pairs, e.g. { 'Info.plist': { CFBundleDisplayName: 'MyApp' } }. Defaults to look in App_Resources/iOS/{filepath} however you can specify relative path if located elsewhere." + } }, "device": { "type": "string", @@ -35,16 +97,6 @@ "default": false, "description": "Enable uglify during the webpack build" }, - "verbose": { - "type": "boolean", - "default": false, - "description": "Enable verbose logging" - }, - "timeout": { - "type": "number", - "default": -1, - "description": "Increase the default 90s timeout to connect to a device/simulator" - }, "release": { "type": "boolean", "default": false, @@ -60,44 +112,6 @@ "default": false, "description": "Build in production mode using the --env.production flag" }, - "copyTo": { - "type": "string", - "description": "When building, copy the package to this location." - }, - "prepare": { - "type": "boolean", - "description": "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", - "default": false - }, - "provision": { - "type": "string", - "description": "(iOS Only) When building, use this provision profile name." - }, - "aab": { - "type": "boolean", - "default": false, - "description": "(Android Only) When building, create an Android App Bundle (.aab file)." - }, - "keyStorePath": { - "type": "string", - "description": "(Android Only) When building, use the keystore file at this location." - }, - "keyStorePassword": { - "type": "string", - "description": "(Android Only) When building, use this keystore password." - }, - "keyStoreAlias": { - "type": "string", - "description": "(Android Only) When building, use this keystore alias." - }, - "keyStoreAliasPassword": { - "type": "string", - "description": "(Android Only) When building, use this keystore alias password." - }, - "combineWithConfig": { - "type": "string", - "description": "Used with platform targets (ios|android) to share build configurations." - }, "fileReplacements": { "description": "Replace files with other files in the build.", "type": "array", @@ -114,36 +128,40 @@ } }, "additionalProperties": false, - "required": ["replace", "with"] + "required": [ + "replace", + "with" + ] }, "default": [] }, - "flags": { - "type": "string", - "description": "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts" - }, - "id": { - "type": "string", - "description": "App bundle id. Use with configurations that desire a specific bundle id to be set." - }, - "plistUpdates": { - "type": "object", - "description": "Update any .plist value. Specify name of any filename with key/value pairs, e.g. { 'Info.plist': { CFBundleDisplayName: 'MyApp' } }. Defaults to look in App_Resources/iOS/{filepath} however you can specify relative path if located elsewhere." - }, - "xmlUpdates": { - "type": "object", - "description": "Update any .xml value. Specify name of any filename with key/value pairs, e.g. { 'src/main/res/values/strings.xml': { app_name: 'MyApp', title_activity_kimera: 'MyApp' } }. Defaults to look in App_Resources/Android/{filepath} however you can specify relative path if located elsewhere." - }, "clean": { "type": "boolean", "default": false, "description": "Do a full project clean" }, - "force": { + "debug": { "type": "boolean", "default": true, - "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + "description": "Use 'ns debug' instead of 'ns run'. Defaults to true" + }, + "timeout": { + "type": "number", + "default": -1, + "description": "Increase the default 90s timeout to connect to a device/simulator" + }, + "copyTo": { + "type": "string", + "description": "When building, copy the package to this location." + }, + "prepare": { + "type": "boolean", + "description": "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + "default": false + }, + "flags": { + "type": "string", + "description": "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts" } - }, - "required": [] -} + } +} \ No newline at end of file diff --git a/packages/nx/src/executors/build/test.schema.json b/packages/nx/src/executors/build/test.schema.json new file mode 100644 index 000000000..3e9638889 --- /dev/null +++ b/packages/nx/src/executors/build/test.schema.json @@ -0,0 +1,172 @@ +{ + "$schema": "http://json-schema.org/schema", + "title": "NativeScript builder", + "description": "", + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "NativeScript CLI command to invoke", + "default": "test" + }, + "platform": { + "type": "string", + "description": "Platform to run on" + }, + "force": { + "type": "boolean", + "default": true, + "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." + }, + "silent": { + "type": "boolean", + "default": false, + "description": "If true, skips prompts.", + "alias": "s" + }, + "verbose": { + "type": "boolean", + "default": false, + "description": "Enable verbose logging" + }, + "id": { + "type": "string", + "description": "App bundle id. Use with configurations that desire a specific bundle id to be set." + }, + "combineWithConfig": { + "type": "string", + "description": "Used with targets to share build configurations and avoid duplicating configurations across multiple targets." + }, + "android": { + "type": "object", + "aab": { + "type": "boolean", + "default": false, + "description": "(Android Only) When building, create an Android App Bundle (.aab file)." + }, + "keyStorePath": { + "type": "string", + "description": "(Android Only) When building, use the keystore file at this location." + }, + "keyStorePassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore password." + }, + "keyStoreAlias": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias." + }, + "keyStoreAliasPassword": { + "type": "string", + "description": "(Android Only) When building, use this keystore alias password." + }, + "xmlUpdates": { + "type": "object", + "description": "Update any .xml value. Specify name of any filename with key/value pairs, e.g. { 'src/main/res/values/strings.xml': { app_name: 'MyApp', title_activity_kimera: 'MyApp' } }. Defaults to look in App_Resources/Android/{filepath} however you can specify relative path if located elsewhere." + } + }, + "ios": { + "type": "object", + "required": [], + "provision": { + "type": "string", + "description": "(iOS Only) When building, use this provision profile name." + }, + "plistUpdates": { + "type": "object", + "description": "Update any .plist value. Specify name of any filename with key/value pairs, e.g. { 'Info.plist': { CFBundleDisplayName: 'MyApp' } }. Defaults to look in App_Resources/iOS/{filepath} however you can specify relative path if located elsewhere." + } + }, + "device": { + "type": "string", + "description": "Device identifier to run app on.", + "alias": "d" + }, + "emulator": { + "type": "boolean", + "default": false, + "description": "Explicitly run with an emulator or simulator" + }, + "noHmr": { + "type": "boolean", + "default": false, + "description": "Disable HMR" + }, + "uglify": { + "type": "boolean", + "default": false, + "description": "Enable uglify during the webpack build" + }, + "release": { + "type": "boolean", + "default": false, + "description": "Enable release mode during build using the --release flag" + }, + "forDevice": { + "type": "boolean", + "default": false, + "description": "Build in device mode using the --for-device flag" + }, + "production": { + "type": "boolean", + "default": false, + "description": "Build in production mode using the --env.production flag" + }, + "fileReplacements": { + "description": "Replace files with other files in the build.", + "type": "array", + "items": { + "type": "object", + "properties": { + "replace": { + "type": "string", + "description": "The file to be replaced." + }, + "with": { + "type": "string", + "description": "The file to replace with." + } + }, + "additionalProperties": false, + "required": [ + "replace", + "with" + ] + }, + "default": [] + }, + "clean": { + "type": "boolean", + "default": false, + "description": "Do a full project clean" + }, + "debug": { + "type": "boolean", + "default": true, + "description": "Use 'ns debug' instead of 'ns run'. Defaults to true" + }, + "timeout": { + "type": "number", + "default": -1, + "description": "Increase the default 90s timeout to connect to a device/simulator" + }, + "copyTo": { + "type": "string", + "description": "When building, copy the package to this location." + }, + "prepare": { + "type": "boolean", + "description": "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + "default": false + }, + "flags": { + "type": "string", + "description": "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts" + }, + "coverage": { + "type": "boolean", + "description": "", + "default": false + } + } +} \ No newline at end of file diff --git a/packages/nx/src/executors/test/builder.ts b/packages/nx/src/executors/test/builder.ts deleted file mode 100644 index 7a778f186..000000000 --- a/packages/nx/src/executors/test/builder.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { convertNxExecutor } from '@nx/devkit'; - -import testExecutor from './executor'; - -export default convertNxExecutor(testExecutor); diff --git a/packages/nx/src/executors/test/executor.spec.ts b/packages/nx/src/executors/test/executor.spec.ts deleted file mode 100644 index ff3ee402d..000000000 --- a/packages/nx/src/executors/test/executor.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { TestExecutorSchema } from '../../utils'; -import testExecutor from './executor'; - -const options: TestExecutorSchema = { - coverage: false, - platform: 'ios', -}; - -describe('Executor: test', () => { - it('sample', () => { - expect(true).toBe(true); - }); -}); - -// xdescribe('NativeScript Test Builder', () => { -// const context = { -// logger: { -// info: (args) => { -// console.log(args); -// }, -// }, -// } as any; -// let architect: Architect; -// let architectHost: TestingArchitectHost; - -// beforeEach(async () => { -// const registry = new schema.CoreSchemaRegistry(); -// registry.addPostTransform(schema.transforms.addUndefinedDefaults); - -// architectHost = new TestingArchitectHost('/root', '/root'); -// architect = new Architect(architectHost, registry); - -// // This will either take a Node package name, or a path to the directory -// // for the package.json file. -// await architectHost.addBuilderFromPackage(join(__dirname, '../../..')); -// }); -// }); diff --git a/packages/nx/src/executors/test/executor.ts b/packages/nx/src/executors/test/executor.ts deleted file mode 100644 index 3ee62fb1d..000000000 --- a/packages/nx/src/executors/test/executor.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { ExecutorContext } from '@nx/devkit'; -import { TestExecutorSchema, commonExecutor } from '../../utils'; - -export default async function testExecutor(options: TestExecutorSchema, context: ExecutorContext) { - return commonExecutor(options, context, true); -} diff --git a/packages/nx/src/executors/test/schema.json b/packages/nx/src/executors/test/schema.json deleted file mode 100644 index b073e2dc5..000000000 --- a/packages/nx/src/executors/test/schema.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "version": 2, - "outputCapture": "direct-nodejs", - "$schema": "http://json-schema.org/schema", - "title": "Start the NativeScript unit test runner", - "description": "", - "type": "object", - "properties": { - "platform": { - "type": "string", - "description": "Platform to test." - }, - "coverage": { - "type": "boolean", - "default": false, - "description": "Enable code coverage reports." - }, - "device": { - "type": "string", - "description": "Device identifier to run tests on.", - "alias": "d" - }, - "flags": { - "type": "string", - "description": "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts" - }, - "id": { - "type": "string", - "description": "App bundle id. Use with configurations that desire a specific bundle id to be set." - }, - "force": { - "type": "boolean", - "default": true, - "description": "If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate." - } - }, - "required": [] -} diff --git a/packages/nx/src/generators/app-resources/files/__name__/Android/app.gradle b/packages/nx/src/generators/app-resources/files/__name__/Android/app.gradle index ad47319d8..e40cb93f4 100644 --- a/packages/nx/src/generators/app-resources/files/__name__/Android/app.gradle +++ b/packages/nx/src/generators/app-resources/files/__name__/Android/app.gradle @@ -4,18 +4,11 @@ dependencies { } android { - // compileSdkVersion 32 - // buildToolsVersion "32.0.0" - // ndkVersion "" - + compileSdkVersion 34 + buildToolsVersion "34" defaultConfig { - minSdkVersion 21 - // targetSdkVersion 32 - - // Version Information - versionCode 1 - versionName "1.0.0" - + minSdkVersion 23 + targetSdkVersion 34 generatedDensities = [] } diff --git a/packages/nx/src/generators/app-resources/files/__name__/Android/src/main/AndroidManifest.xml b/packages/nx/src/generators/app-resources/files/__name__/Android/src/main/AndroidManifest.xml index e26fa65cd..2756e5359 100644 --- a/packages/nx/src/generators/app-resources/files/__name__/Android/src/main/AndroidManifest.xml +++ b/packages/nx/src/generators/app-resources/files/__name__/Android/src/main/AndroidManifest.xml @@ -2,7 +2,8 @@ + android:versionName="1.0" + xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + + + + \ No newline at end of file diff --git a/packages/nx/src/schemas/android-properties.schema.ts b/packages/nx/src/schemas/android-properties.schema.ts new file mode 100644 index 000000000..c2c822698 --- /dev/null +++ b/packages/nx/src/schemas/android-properties.schema.ts @@ -0,0 +1,48 @@ +import { KeysOfProperty } from '../utils/types'; + +export interface AndroidSchema { + aab: boolean; + keyStoreAlias: string; + keyStoreAliasPassword: string; + keyStorePassword: string; + keyStorePath: string; + xmlUpdates: Record; +} + +export const androidSchema = { + $schema: 'http://json-schema.org/schema', + title: 'Android Properties', + description: '', + type: 'object', + properties: { + android: >{ + type: 'object', + aab: { + type: 'boolean', + default: false, + description: '(Android Only) When building, create an Android App Bundle (.aab file).', + }, + keyStorePath: { + type: 'string', + description: '(Android Only) When building, use the keystore file at this location.', + }, + keyStorePassword: { + type: 'string', + description: '(Android Only) When building, use this keystore password.', + }, + keyStoreAlias: { + type: 'string', + description: '(Android Only) When building, use this keystore alias.', + }, + keyStoreAliasPassword: { + type: 'string', + description: '(Android Only) When building, use this keystore alias password.', + }, + xmlUpdates: { + type: 'object', + description: + "Update any .xml value. Specify name of any filename with key/value pairs, e.g. { 'src/main/res/values/strings.xml': { app_name: 'MyApp', title_activity_kimera: 'MyApp' } }. Defaults to look in App_Resources/Android/{filepath} however you can specify relative path if located elsewhere.", + }, + }, + }, +}; diff --git a/packages/nx/src/schemas/base.schema.ts b/packages/nx/src/schemas/base.schema.ts new file mode 100644 index 000000000..159ed6c7e --- /dev/null +++ b/packages/nx/src/schemas/base.schema.ts @@ -0,0 +1,55 @@ +import { COMMANDS } from '../utils/commands'; +import { KeysOfProperty } from '../utils/types'; + +export type Platform = 'ios' | 'android'; + +export interface BaseSchema { + command: COMMANDS; + force: boolean; + id: string; + platform: Platform; + silent: boolean; + verbose: boolean; +} + +export const baseSchema = { + $schema: 'http://json-schema.org/schema', + title: 'Base Schema', + description: 'Base schema for all base properties.', + type: 'object', + properties: >{ + command: { + type: 'string', + description: 'NativeScript CLI command to invoke', + default: 'debug', + }, + platform: { + type: 'string', + description: 'Platform to run on', + }, + force: { + type: 'boolean', + default: true, + description: 'If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate.', + }, + silent: { + type: 'boolean', + default: false, + description: 'If true, skips prompts.', + alias: 's', + }, + verbose: { + type: 'boolean', + default: false, + description: 'Enable verbose logging', + }, + id: { + type: 'string', + description: 'App bundle id. Use with configurations that desire a specific bundle id to be set.', + }, + combineWithConfig: { + type: 'string', + description: 'Used with targets to share build configurations and avoid duplicating configurations across multiple targets.', + }, + }, +}; \ No newline at end of file diff --git a/packages/nx/src/schemas/build.schema.ts b/packages/nx/src/schemas/build.schema.ts new file mode 100644 index 000000000..fdd91b145 --- /dev/null +++ b/packages/nx/src/schemas/build.schema.ts @@ -0,0 +1,91 @@ +import { COMMANDS } from '../utils/commands'; +import { KeysOfProperty } from '../utils/types'; + +export interface BuildSchema { + clean: boolean; + command: COMMANDS; + device: string; + emulator: boolean; + fileReplacements: any; + forDevice: boolean; + noHmr: boolean; + production: boolean; + release: boolean; + uglify: boolean; +} + +export const buildSchema = { + $schema: 'http://json-schema.org/schema', + title: 'NativeScript builder', + description: '', + type: 'object', + properties: >{ + command: { + type: 'string', + description: 'NativeScript CLI command to invoke', + default: 'build', + }, + device: { + type: 'string', + description: 'Device identifier to run app on.', + alias: 'd', + }, + emulator: { + type: 'boolean', + default: false, + description: 'Explicitly run with an emulator or simulator', + }, + noHmr: { + type: 'boolean', + default: false, + description: 'Disable HMR', + }, + uglify: { + type: 'boolean', + default: false, + description: 'Enable uglify during the webpack build', + }, + release: { + type: 'boolean', + default: false, + description: 'Enable release mode during build using the --release flag', + }, + forDevice: { + type: 'boolean', + default: false, + description: 'Build in device mode using the --for-device flag', + }, + production: { + type: 'boolean', + default: false, + description: 'Build in production mode using the --env.production flag', + alias: 'prod', + }, + fileReplacements: { + description: 'Replace files with other files in the build.', + type: 'array', + items: { + type: 'object', + properties: { + replace: { + type: 'string', + description: 'The file to be replaced.', + }, + with: { + type: 'string', + description: 'The file to replace with.', + }, + }, + additionalProperties: false, + required: ['replace', 'with'], + }, + default: [], + }, + clean: { + type: 'boolean', + default: false, + description: 'Do a full project clean', + }, + }, + required: [], +}; diff --git a/packages/nx/src/schemas/clean.schema.ts b/packages/nx/src/schemas/clean.schema.ts new file mode 100644 index 000000000..d96bed6db --- /dev/null +++ b/packages/nx/src/schemas/clean.schema.ts @@ -0,0 +1,37 @@ +import { COMMANDS } from '../utils/commands'; +import { KeysOfProperty } from '../utils/types'; + +export interface CleanSchema { + command: COMMANDS; +} + +export const cleanSchema = { + $schema: 'http://json-schema.org/schema', + title: 'NativeScript clean', + description: '', + type: 'object', + properties: >{ + command: { + type: 'string', + description: 'NativeScript CLI command to invoke', + default: 'clean', + }, + force: { + type: 'boolean', + default: true, + description: 'If true, skips the application compatibility checks and forces npm i to ensure all dependencies are installed. Otherwise, the command will check the application compatibility with the current CLI version and could fail requiring ns migrate.', + }, + silent: { + type: 'boolean', + default: false, + description: 'If true, skips prompts.', + alias: 's', + }, + verbose: { + type: 'boolean', + default: false, + description: 'Enable verbose logging', + }, + }, + required: [], +}; diff --git a/packages/nx/src/schemas/debug.schema.ts b/packages/nx/src/schemas/debug.schema.ts new file mode 100644 index 000000000..026fa1c45 --- /dev/null +++ b/packages/nx/src/schemas/debug.schema.ts @@ -0,0 +1,119 @@ +import { COMMANDS } from '../utils/commands'; +import { KeysOfProperty } from '../utils/types'; + +export interface DebugSchema { + clean: boolean; + command: COMMANDS; + copyTo: string; + debug: boolean; + device: string; + emulator: boolean; + fileReplacements: any; + flags: string; + forDevice: boolean; + noHmr: boolean; + prepare: boolean; + production: boolean; + release: boolean; + uglify: boolean; + timeout: number; +} + +export const debugSchema = { + $schema: 'http://json-schema.org/schema', + title: 'NativeScript builder', + description: '', + type: 'object', + properties: >{ + command: { + type: 'string', + description: 'NativeScript CLI command to invoke', + default: 'debug', + }, + debug: { + type: 'boolean', + default: true, + description: "Use 'ns debug' instead of 'ns run'. Defaults to true", + }, + device: { + type: 'string', + description: 'Device identifier to run app on.', + alias: 'd', + }, + emulator: { + type: 'boolean', + default: false, + description: 'Explicitly run with an emulator or simulator', + }, + noHmr: { + type: 'boolean', + default: false, + description: 'Disable HMR', + }, + uglify: { + type: 'boolean', + default: false, + description: 'Enable uglify during the webpack build', + }, + timeout: { + type: 'number', + default: -1, + description: 'Increase the default 90s timeout to connect to a device/simulator', + }, + release: { + type: 'boolean', + default: false, + description: 'Enable release mode during build using the --release flag', + }, + forDevice: { + type: 'boolean', + default: false, + description: 'Build in device mode using the --for-device flag', + }, + production: { + type: 'boolean', + default: false, + description: 'Build in production mode using the --env.production flag', + alias: 'prod', + }, + copyTo: { + type: 'string', + description: 'When building, copy the package to this location.', + }, + prepare: { + type: 'boolean', + description: "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + default: false, + }, + fileReplacements: { + description: 'Replace files with other files in the build.', + type: 'array', + items: { + type: 'object', + properties: { + replace: { + type: 'string', + description: 'The file to be replaced.', + }, + with: { + type: 'string', + description: 'The file to replace with.', + }, + }, + additionalProperties: false, + required: ['replace', 'with'], + }, + default: [], + }, + flags: { + type: 'string', + description: "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts", + }, + clean: { + type: 'boolean', + default: false, + description: 'Do a full project clean', + }, + }, + required: [], +}; diff --git a/packages/nx/src/schemas/deep-merge.ts b/packages/nx/src/schemas/deep-merge.ts new file mode 100644 index 000000000..5124ceea5 --- /dev/null +++ b/packages/nx/src/schemas/deep-merge.ts @@ -0,0 +1,21 @@ +export function isObject(item) { + return item && typeof item === 'object' && !Array.isArray(item); +} + +export function mergeDeep(target, ...sources) { + if (!sources.length) return target; + const source = sources.shift(); + + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) Object.assign(target, { [key]: {} }); + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return mergeDeep(target, ...sources); +} diff --git a/packages/nx/src/schemas/ios-properties.schema.ts b/packages/nx/src/schemas/ios-properties.schema.ts new file mode 100644 index 000000000..6eff2f652 --- /dev/null +++ b/packages/nx/src/schemas/ios-properties.schema.ts @@ -0,0 +1,27 @@ +import { KeysOfProperty } from '../utils/types'; + +export interface IosSchema { + plistUpdates: Record; + provision: string; +} + +export const iosSchema = { + $schema: 'http://json-schema.org/schema', + title: 'iOS Properties', + description: '', + type: 'object', + properties: { + ios: >{ + type: 'object', + required: [], + provision: { + type: 'string', + description: '(iOS Only) When building, use this provision profile name.', + }, + plistUpdates: { + type: 'object', + description: "Update any .plist value. Specify name of any filename with key/value pairs, e.g. { 'Info.plist': { CFBundleDisplayName: 'MyApp' } }. Defaults to look in App_Resources/iOS/{filepath} however you can specify relative path if located elsewhere.", + }, + }, + }, +}; diff --git a/packages/nx/src/schemas/prepare.schema.ts b/packages/nx/src/schemas/prepare.schema.ts new file mode 100644 index 000000000..1501fce36 --- /dev/null +++ b/packages/nx/src/schemas/prepare.schema.ts @@ -0,0 +1,88 @@ +import { COMMANDS } from '../utils/commands'; +import { KeysOfProperty } from '../utils/types'; + +export interface PrepareSchema { + clean: boolean; + command: COMMANDS; + copyTo: string; + fileReplacements: any; + flags: string; + forDevice: boolean; + prepare: boolean; + production: boolean; + release: boolean; + uglify: boolean; +} + +export const prepareSchema = { + $schema: 'http://json-schema.org/schema', + title: 'NativeScript builder', + description: '', + type: 'object', + properties: >{ + command: { + type: 'string', + description: 'NativeScript CLI command to invoke', + default: 'prepare', + }, + uglify: { + type: 'boolean', + default: false, + description: 'Enable uglify during the webpack build', + }, + release: { + type: 'boolean', + default: false, + description: 'Enable release mode during build using the --release flag', + }, + forDevice: { + type: 'boolean', + default: false, + description: 'Build in device mode using the --for-device flag', + }, + production: { + type: 'boolean', + default: false, + description: 'Build in production mode using the --env.production flag', + }, + copyTo: { + type: 'string', + description: 'When building, copy the package to this location.', + }, + prepare: { + type: 'boolean', + description: "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + default: false, + }, + fileReplacements: { + description: 'Replace files with other files in the build.', + type: 'array', + items: { + type: 'object', + properties: { + replace: { + type: 'string', + description: 'The file to be replaced.', + }, + with: { + type: 'string', + description: 'The file to replace with.', + }, + }, + additionalProperties: false, + required: ['replace', 'with'], + }, + default: [], + }, + flags: { + type: 'string', + description: "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts", + }, + clean: { + type: 'boolean', + default: false, + description: 'Do a full project clean', + }, + }, + required: [], +}; diff --git a/packages/nx/src/schemas/run.schema.ts b/packages/nx/src/schemas/run.schema.ts new file mode 100644 index 000000000..bb0152030 --- /dev/null +++ b/packages/nx/src/schemas/run.schema.ts @@ -0,0 +1,119 @@ +import { COMMANDS } from '../utils/commands'; +import { KeysOfProperty } from '../utils/types'; + +export interface RunSchema { + clean: boolean; + command: COMMANDS; + copyTo: string; + debug: boolean; + device: string; + emulator: boolean; + fileReplacements: any; + flags: string; + forDevice: boolean; + noHmr: boolean; + prepare: boolean; + production: boolean; + release: boolean; + uglify: boolean; + timeout: number; +} + +export const runSchema = { + $schema: 'http://json-schema.org/schema', + title: 'NativeScript builder', + description: '', + type: 'object', + properties: >{ + command: { + type: 'string', + description: 'NativeScript CLI command to invoke', + default: 'run', + }, + + debug: { + type: 'boolean', + default: true, + description: "Use 'ns debug' instead of 'ns run'. Defaults to true", + }, + device: { + type: 'string', + description: 'Device identifier to run app on.', + alias: 'd', + }, + emulator: { + type: 'boolean', + default: false, + description: 'Explicitly run with an emulator or simulator', + }, + noHmr: { + type: 'boolean', + default: false, + description: 'Disable HMR', + }, + uglify: { + type: 'boolean', + default: false, + description: 'Enable uglify during the webpack build', + }, + timeout: { + type: 'number', + default: -1, + description: 'Increase the default 90s timeout to connect to a device/simulator', + }, + release: { + type: 'boolean', + default: false, + description: 'Enable release mode during build using the --release flag', + }, + forDevice: { + type: 'boolean', + default: false, + description: 'Build in device mode using the --for-device flag', + }, + production: { + type: 'boolean', + default: false, + description: 'Build in production mode using the --env.production flag', + }, + copyTo: { + type: 'string', + description: 'When building, copy the package to this location.', + }, + prepare: { + type: 'boolean', + description: "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + default: false, + }, + fileReplacements: { + description: 'Replace files with other files in the build.', + type: 'array', + items: { + type: 'object', + properties: { + replace: { + type: 'string', + description: 'The file to be replaced.', + }, + with: { + type: 'string', + description: 'The file to replace with.', + }, + }, + additionalProperties: false, + required: ['replace', 'with'], + }, + default: [], + }, + flags: { + type: 'string', + description: "Extra flags to pass to the NativeScript CLI (e.g. '--env.config=myapp'). You can separate multiple flags by spaces and use '=' to join option/values (e.g. '--env.config=myapp --env.appComponents=myCustomActivity.ts", + }, + clean: { + type: 'boolean', + default: false, + description: 'Do a full project clean', + }, + }, + required: [], +}; diff --git a/packages/nx/src/schemas/test.schema.ts b/packages/nx/src/schemas/test.schema.ts new file mode 100644 index 000000000..46cf64e98 --- /dev/null +++ b/packages/nx/src/schemas/test.schema.ts @@ -0,0 +1,113 @@ +import { COMMANDS } from '../utils/commands'; +import { KeysOfProperty } from '../utils/types'; + +export interface TestSchema { + command: COMMANDS; + copyTo: string; + coverage: boolean; + debug: boolean; + device: string; + emulator: boolean; + fileReplacements: any; + forDevice: boolean; + noHmr: boolean; + prepare: boolean; + production: boolean; + release: boolean; + uglify: boolean; + timeout: number; +} + +export const testSchema = { + $schema: 'http://json-schema.org/schema', + title: 'NativeScript builder', + description: '', + type: 'object', + properties: >{ + command: { + type: 'string', + description: 'NativeScript CLI command to invoke', + default: 'test', + }, + debug: { + type: 'boolean', + default: true, + description: "Use 'ns debug' instead of 'ns run'. Defaults to true", + }, + device: { + type: 'string', + description: 'Device identifier to run app on.', + alias: 'd', + }, + emulator: { + type: 'boolean', + default: false, + description: 'Explicitly run with an emulator or simulator', + }, + noHmr: { + type: 'boolean', + default: false, + description: 'Disable HMR', + }, + uglify: { + type: 'boolean', + default: false, + description: 'Enable uglify during the webpack build', + }, + timeout: { + type: 'number', + default: -1, + description: 'Increase the default 90s timeout to connect to a device/simulator', + }, + release: { + type: 'boolean', + default: false, + description: 'Enable release mode during build using the --release flag', + }, + forDevice: { + type: 'boolean', + default: false, + description: 'Build in device mode using the --for-device flag', + }, + production: { + type: 'boolean', + default: false, + description: 'Build in production mode using the --env.production flag', + }, + copyTo: { + type: 'string', + description: 'When building, copy the package to this location.', + }, + prepare: { + type: 'boolean', + description: "Starts a Webpack compilation and prepares the app's App_Resources and the plugins platforms directories. The output is generated in a subdirectory for the selected target platform in the platforms directory. This lets you build the project for the selected platform.", + default: false, + }, + coverage: { + type: 'boolean', + description: '', + default: false, + }, + fileReplacements: { + description: 'Replace files with other files in the build.', + type: 'array', + items: { + type: 'object', + properties: { + replace: { + type: 'string', + description: 'The file to be replaced.', + }, + with: { + type: 'string', + description: 'The file to replace with.', + }, + }, + additionalProperties: false, + required: ['replace', 'with'], + }, + default: [], + }, + }, + required: [], +}; diff --git a/packages/nx/src/utils/commands.ts b/packages/nx/src/utils/commands.ts new file mode 100644 index 000000000..6ace0a342 --- /dev/null +++ b/packages/nx/src/utils/commands.ts @@ -0,0 +1,8 @@ +export enum COMMANDS { + BUILD = 'build', + CLEAN = 'clean', + DEBUG = 'debug', + PREPARE = 'prepare', + RUN = 'run', + TEST = 'test', +} diff --git a/packages/nx/src/utils/executors.ts b/packages/nx/src/utils/executors.ts index 1f4ef9601..6efcfc4f7 100644 --- a/packages/nx/src/utils/executors.ts +++ b/packages/nx/src/utils/executors.ts @@ -1,452 +1,321 @@ -import { ExecutorContext, readProjectConfiguration } from '@nx/devkit'; -import * as childProcess from 'child_process'; -import { resolve as nodeResolve } from 'path'; -import { parse, build } from 'plist'; -import { parseString, Builder } from 'xml2js'; +import { ExecutorContext } from '@nx/devkit'; +import childProcess from 'child_process'; +import { prompt } from 'enquirer'; +import { XMLBuilder, XMLParser } from 'fast-xml-parser'; import { readFileSync, writeFileSync } from 'fs-extra'; +import { resolve as nodeResolve } from 'path'; +import { build, parse } from 'plist'; +import { Platform } from '../schemas/base.schema'; +import { mergeDeep } from '../schemas/deep-merge'; +import { COMMANDS } from './commands'; +import { ExecutorSchema } from './types'; import { quoteString } from './helpers'; -const isWindows = process.platform === 'win32'; - -export interface BuildExecutorSchema { - debug?: boolean; - device?: string; - emulator?: boolean; - clean?: boolean; - noHmr?: boolean; - timeout?: number; - uglify?: boolean; - verbose?: boolean; - release?: boolean; - forDevice?: boolean; - production?: boolean; - platform?: 'ios' | 'android'; - copyTo?: string; - force?: boolean; - flags?: string; - combineWithConfig?: string; - fileReplacements?: Array<{ replace: string; with: string }>; - id?: string; - plistUpdates?: any; - xmlUpdates?: any; - /** For running `ns prepare ` */ - prepare?: boolean; - - // ios only - provision?: string; - - // android only - aab?: boolean; - keyStorePath?: string; - keyStorePassword?: string; - keyStoreAlias?: string; - keyStoreAliasPassword?: string; -} - -export interface TestExecutorSchema extends BuildExecutorSchema { - coverage?: boolean; -} +export function commonExecutor(options: ExecutorSchema, context: ExecutorContext): Promise<{ success: boolean }> { + // global vars + const isWindows = process.platform === 'win32'; + let projectCwd: string; -export function commonExecutor( - options: BuildExecutorSchema | TestExecutorSchema, - context: ExecutorContext, - isTesting?: boolean -): Promise<{ success: boolean }> { - return new Promise((resolve, reject) => { + // eslint-disable-next-line no-async-promise-executor + return new Promise(async (resolve, reject) => { try { - const projectConfig = context.projectsConfigurations.projects[context.projectName]; - const activeTarget = projectConfig.targets[context.targetName]; - const buildTarget = projectConfig.targets['build']; - // determine if running or building only - const isBuild = context.targetName === 'build' || process.argv.find((a) => a === 'build' || a.endsWith(':build')); - if (isBuild) { - // allow build options to override run target options - if (buildTarget && buildTarget.options) { - options = { - ...options, - ...buildTarget.options, - }; - } - } - // console.log('context.projectName:', context.projectName); - const projectCwd = projectConfig.root; - // console.log('projectCwd:', projectCwd); - // console.log('context.targetName:', context.targetName); - // console.log('context.configurationName:', context.configurationName); - // console.log('context.target.options:', context.target.options); - - let targetConfigName = ''; - if (context.configurationName && context.configurationName !== 'build') { - targetConfigName = context.configurationName; - } + const isBuild = options.command === COMMANDS.BUILD; + const isClean = options.command === COMMANDS.CLEAN; + const isDebug = options.command === COMMANDS.DEBUG; + const isPrepare = options.command === COMMANDS.PREPARE; + const isRun = options.command === COMMANDS.RUN; + const isTest = options.command === COMMANDS.TEST; + const isSilent = options.silent === true; - // determine if any trailing args that need to be added to run/build command - const configTarget = targetConfigName ? `:${targetConfigName}` : ''; - const projectTargetCmd = `${context.projectName}:${context.targetName}${configTarget}`; - const projectTargetCmdIndex = process.argv.findIndex((c) => c === projectTargetCmd); - - const nsCliFileReplacements: Array = []; - - let configOptions; - if (activeTarget && activeTarget.configurations) { - configOptions = activeTarget.configurations[targetConfigName]; - // console.log('configOptions:', configOptions) - - if (isBuild) { - // merge any custom build options for the target - const targetBuildConfig = activeTarget.configurations['build']; - if (targetBuildConfig) { - options = { - ...options, - ...targetBuildConfig, - }; - } - } + const platformCheck = [context.configurationName, options.platform].concat(options?.['_']); + let isIos = platformCheck.some((overrides) => overrides === 'ios'); + let isAndroid = platformCheck.some((overrides) => overrides === 'android'); - if (configOptions) { - if (configOptions.fileReplacements) { - for (const r of configOptions.fileReplacements) { - nsCliFileReplacements.push(`${r.replace.replace(projectCwd, './')}:${r.with.replace(projectCwd, './')}`); - } - } - if (configOptions.combineWithConfig) { - const configParts = configOptions.combineWithConfig.split(':'); - const combineWithTargetName = configParts[0]; - const combineWithTarget = projectConfig.targets[combineWithTargetName]; - if (combineWithTarget && combineWithTarget.configurations) { - if (configParts.length > 1) { - const configName = configParts[1]; - const combineWithTargetConfig = combineWithTarget.configurations[configName]; - // TODO: combine configOptions with combineWithConfigOptions - if (combineWithTargetConfig) { - if (combineWithTargetConfig.fileReplacements) { - for (const r of combineWithTargetConfig.fileReplacements) { - nsCliFileReplacements.push(`${r.replace.replace(projectCwd, './')}:${r.with.replace(projectCwd, './')}`); - } - } - } - } - } else { - console.warn(`Warning: No configurations will be combined. "${combineWithTargetName}" was not found for project name: "${context.projectName}"`); - } - } - } + if (!isClean && !isSilent && !isIos && !isAndroid) { + const platform = await selectPlatform(options); + isIos = platform === 'ios'; + isAndroid = platform === 'android'; } - let nsOptions = []; - if (isTesting) { - nsOptions.push('test'); + if (!isClean) { + options.platform = isAndroid ? 'android' : 'ios'; } - if (options.clean) { - nsOptions.push('clean'); - } else { - if (isBuild) { - nsOptions.push('build'); - } else if (options.prepare) { - nsOptions.push('prepare'); - } else if (!isTesting) { - if (options.debug === false) { - nsOptions.push('run'); - } else { - // default to debug mode - nsOptions.push('debug'); - } - } + const projectConfig = context.projectsConfigurations.projects[context.projectName]; + projectCwd = projectConfig.root; - if (!options.platform) { - // a platform must be set - // absent of it being explicitly set, we default to 'ios' - // this helps cases where nx run-many is used for general targets used in, for example, pull request auto prepare/build checks (just need to know if there are any .ts errors in the build where platform often doesn't matter - much) - options.platform = 'ios'; - } + const target = projectConfig.targets[options.command]; + const targetOptions = target.options; + const targetPlatformOptions = targetOptions[options.platform]; + // const targetDescription = JSON.parse(process.argv.find((arg) => arg.indexOf('targetDescription') !== -1)); - if (options.platform) { - nsOptions.push(options.platform); - } - if ((options).coverage) { - nsOptions.push('--env.codeCoverage'); - } - if (options.device && !options.emulator) { - nsOptions.push(`--device=${options.device}`); - } - if (options.emulator) { - nsOptions.push('--emulator'); - } - if (options.noHmr) { - nsOptions.push('--no-hmr'); - } - if (options.timeout && options.timeout > -1) { - nsOptions.push(`--timeout=${options.timeout}`); - } - if (options.uglify) { - nsOptions.push('--env.uglify'); - } - if (options.verbose) { - nsOptions.push('--log=trace'); - } - if (options.production) { - nsOptions.push('--env.production'); - } - if (options.forDevice) { - nsOptions.push('--for-device'); - } - if (options.release) { - nsOptions.push('--release'); - } - if (options.aab) { - nsOptions.push('--aab'); - } - if (options.keyStorePath) { - nsOptions.push(`--key-store-path=${options.keyStorePath}`); - } - if (options.keyStorePassword) { - nsOptions.push(`--key-store-password=${options.keyStorePassword}`); - } - if (options.keyStoreAlias) { - nsOptions.push(`--key-store-alias=${options.keyStoreAlias}`); - } - if (options.keyStoreAliasPassword) { - nsOptions.push(`--key-store-alias-password=${options.keyStoreAliasPassword}`); - } - if (options.provision) { - nsOptions.push(`--provision=${options.provision}`); - } - if (options.copyTo) { - nsOptions.push(`--copy-to=${options.copyTo}`); - } + // fix for nx overwriting android and ios sub properties + mergeDeep(options, targetOptions); - if (nsCliFileReplacements.length) { - // console.log('nsCliFileReplacements:', nsCliFileReplacements); - nsOptions.push(`--env.replace="${nsCliFileReplacements.join(',')}"`); - } - // always add --force (unless explicity set to false) for now since within Nx we use @nativescript/webpack at root only and the {N} cli shows a blocking error if not within the app - if (options?.force !== false) { - nsOptions.push('--force'); - } - } + const configurationName = await selectConfiguration(target.configurations, context.configurationName); + // fix for nx overwriting android and ios sub properties + if (configurationName) mergeDeep(options, target.configurations[configurationName]); - // some options should never be duplicated - const enforceSingularOptions = ['provision', 'device', 'copy-to']; - const parseOptionName = (flag: string) => { - // strip just the option name from extra arguments - // --provision='match AppStore my.bundle.com' > provision - return flag.split('=')[0].replace('--', ''); - }; - // additional cli flags - // console.log('projectTargetCmdIndex:', projectTargetCmdIndex) - let additionalArgs = []; + const nsOptions = prepareNsOptions(options, projectCwd); + const additionalArgs: string[] = []; // Assuming any extra flags are handled here if (options.flags) { // persisted flags in configurations additionalArgs.push(...options.flags.split(' ')); } - if (!options.clean && process.argv.length > projectTargetCmdIndex + 1) { - // manually added flags to the execution command - const extraFlags = process.argv.slice(projectTargetCmdIndex + 1, process.argv.length); - for (const flag of extraFlags) { - const optionName = parseOptionName(flag); - if (optionName?.indexOf('/') === -1 && optionName?.indexOf('{') === -1 && optionName?.indexOf('\\') === -1) { - // no valid options should start with '/' or '{' - those are often extra process.argv context args that should be ignored - if (!nsOptions.includes(flag) && !additionalArgs.includes(flag) && !enforceSingularOptions.includes(optionName)) { - additionalArgs.push(flag); + + if (options.android?.xmlUpdates) updateXml(options.android.xmlUpdates, 'android'); + if (options.ios?.plistUpdates) updateXml(options.ios.plistUpdates, 'android'); + + await checkOptions(); + + const result = await runCommand(nsOptions, additionalArgs); + resolve(result); + } catch (err) { + console.error(err); + reject(err); + } + }); + + async function selectPlatform(options: ExecutorSchema): Promise { + if (options.platform) return options.platform; + + if (options.silent) { + console.warn('No platform was specified. Defaulting to iOS.'); + return 'ios'; + } + + const platformChoices: Platform[] = ['ios', 'android']; + const { platform } = await prompt<{ platform: Platform }>({ + type: 'select', + name: 'platform', + message: 'Which platform do you want to target?', + choices: platformChoices, + }); + return platform; + } + + async function selectConfiguration(targetConfigurations: any, configurationName: string) { + if (!configurationName && targetConfigurations && Object.keys(targetConfigurations).length) { + const { configurationName: selectedConfig } = await prompt<{ configurationName: string }>({ + type: 'select', + name: 'configurationName', + message: 'No configuration was provided. Did you mean to select one of these configurations?', + choices: ['No', ...Object.keys(targetConfigurations)], + }); + if (selectedConfig == 'No') { + console.warn(`Continuing with no configuration. Specify with --configuration=prod, -c=prod, or :prod`); + } + return selectedConfig !== 'No' ? selectedConfig : undefined; + } + return configurationName; + } + + function prepareNsOptions(options: ExecutorSchema, projectCwd: string) { + const nsOptions: string[] = []; + nsOptions.push(options.command); + + // early exit for `ns clean` + if (options.command === COMMANDS.CLEAN) return nsOptions; + + if (options.platform === 'android' && options.android) { + options.android.aab && nsOptions.push('--aab'); + options.android.keyStorePath && nsOptions.push(`--key-store-path=${options.android.keyStorePath}`); + options.android.keyStorePassword && nsOptions.push(`--key-store-password=${options.android.keyStorePassword}`); + options.android.keyStoreAlias && nsOptions.push(`--key-store-alias=${options.android.keyStoreAlias}`); + options.android.keyStoreAliasPassword && nsOptions.push(`--key-store-alias-password=${options.android.keyStoreAliasPassword}`); + } + + if (options.platform === 'ios' && options.ios) { + options.ios.provision && nsOptions.push(`--provision=${options.ios.provision}`); + } + + // Append common options + options.platform && nsOptions.push(options.platform); + options.clean && nsOptions.push('--clean'); + options.coverage && nsOptions.push('--env.codeCoverage'); + options.device && !options.emulator && nsOptions.push(`--device=${options.device}`); + options.emulator && nsOptions.push('--emulator'); + options.noHmr && nsOptions.push('--no-hmr'); + options.timeout && options.timeout > -1 && nsOptions.push(`--timeout=${options.timeout}`); + options.uglify && nsOptions.push('--env.uglify'); + options.verbose && nsOptions.push('--env.verbose'); + options.production && nsOptions.push('--env.production'); + options.forDevice && nsOptions.push('--for-device'); + options.release && nsOptions.push('--release'); + options.copyTo && nsOptions.push(`--copy-to=${options.copyTo}`); + options.force !== false && nsOptions.push('--force'); + + const nsFileReplacements: Array = []; + for (const fileReplacement of options.fileReplacements) { + nsFileReplacements.push(`${fileReplacement.replace.replace(projectCwd, './')}:${fileReplacement.with.replace(projectCwd, './')}`); + } + nsFileReplacements.length && nsOptions.push(`--env.replace="${nsFileReplacements.join(',')}"`); + + return nsOptions; + } + + function updateXml(xmlUpdatesConfig: Record, type: Platform) { + const xmlUpdatesKeys = Object.keys(xmlUpdatesConfig || {}); + for (const filePathKeys of xmlUpdatesKeys) { + let xmlFilePath: string; + if (filePathKeys.indexOf('.') === 0) { + // resolve relative to project directory + xmlFilePath = nodeResolve(projectCwd, filePathKeys); + } else { + // default to locating in App_Resources + let defaultDir: string[]; + if (type === 'ios') { + defaultDir = ['App_Resources', 'iOS']; + } else if (type === 'android') { + defaultDir = ['App_Resources', 'Android']; + } + xmlFilePath = nodeResolve(projectCwd, ...defaultDir, filePathKeys); + } + + let xmlFileContent: any; + const fileContent = readFileSync(xmlFilePath, 'utf8'); + const xmlUpdates = xmlUpdatesConfig[filePathKeys]; + + if (type === 'ios') { + xmlFileContent = parse(fileContent); + } else if (type === 'android') { + const parser = new XMLParser({ + ignoreAttributes: false, + ignoreDeclaration: false, + ignorePiTags: false, + attributeNamePrefix: '', + allowBooleanAttributes: true, + }); + xmlFileContent = parser.parse(fileContent); + } + + let needsUpdate = false; + + const recursiveUpdate = function (target: any, updates: any): void { + for (const key in updates) { + if (typeof updates[key] === 'object' && !Array.isArray(updates[key])) { + if (!target[key]) { + target[key] = {}; + } + recursiveUpdate(target[key], updates[key]); + } else { + if (Array.isArray(target[key])) { + recursiveUpdate(target[key], updates[key]); + } else { + target[key] = updates[key]; + needsUpdate = true; } } } - // console.log('additionalArgs:', additionalArgs); - } + }; + recursiveUpdate(xmlFileContent, xmlUpdates); - const runCommand = function () { - let icon = ''; - if (!options.clean) { - if (options.platform === 'ios') { - icon = ''; - } else if (options.platform === 'android') { - icon = '🤖'; - } else if (['vision', 'visionos'].includes(options.platform)) { - icon = '🥽'; - } + if (needsUpdate) { + let newXmlFileContent; + if (type === 'ios') { + newXmlFileContent = build(xmlFileContent, { pretty: true, indent: '\t' }); + } else if (type === 'android') { + const builder = new XMLBuilder({ + ignoreAttributes: false, + format: true, + suppressEmptyNode: true, + attributeNamePrefix: '', + suppressBooleanAttributes: false, + }); + newXmlFileContent = builder.build(xmlFileContent); } + writeFileSync(xmlFilePath, newXmlFileContent); + console.log(`Updated: ${xmlFilePath}`); + } + } + } + + async function checkOptions() { + if (!options.id) return; + const id = await checkAppId(); + if (options.id !== id) { + return new Promise((resolve) => { + let args = ['config', 'set', `${options.platform}.id`, options.id]; if (isWindows) { - // https://github.com/NativeScript/nativescript-cli/pull/5808 - nsOptions = nsOptions.map((arg) => quoteString(arg)); - additionalArgs = additionalArgs.map((arg) => quoteString(arg)); + args = args.map((arg) => quoteString(arg)); } - console.log(`―――――――――――――――――――――――― ${icon}`); - console.log(`Running NativeScript ${isTesting ? 'unit tests' : 'CLI'} within ${projectCwd}`); - console.log(' '); - console.log([`ns`, ...nsOptions, ...additionalArgs].join(' ')); - console.log(' '); - if (additionalArgs.length) { - console.log('Note: When using extra cli flags, ensure all key/value pairs are separated with =, for example: --provision="Name"'); - console.log(' '); - } - console.log(`---`); - const child = childProcess.spawn(/^win/.test(process.platform) ? 'ns.cmd' : 'ns', [...nsOptions, ...additionalArgs], { + const child = childProcess.spawn(isWindows ? 'ns.cmd' : 'ns', args, { cwd: projectCwd, stdio: 'inherit', shell: isWindows ? true : undefined, }); child.on('close', (code) => { - console.log(`Done.`); child.kill('SIGKILL'); - resolve({ success: code === 0 }); + resolve(); }); - }; - - const checkAppId = function () { - return new Promise((resolve) => { - let args = ['config', 'get', `id`]; - if (isWindows) { - args = args.map((arg) => quoteString(arg)); - } - const child = childProcess.spawn(/^win/.test(process.platform) ? 'ns.cmd' : 'ns', args, { - cwd: projectCwd, - shell: isWindows ? true : undefined, - }); - child.stdout.setEncoding('utf8'); - child.stdout.on('data', function (data) { - // ensure no newline chars at the end - const appId = (data || '').toString().replace('\n', '').replace('\r', ''); - // console.log('existing app id:', appId); - resolve(appId); - }); - child.on('close', (code) => { - child.kill('SIGKILL'); - }); - }); - }; + }); + } + } - const checkOptions = function () { - if (options.id) { - // only modify app id if doesn't match (modifying nativescript.config will cause full native build) - checkAppId().then((id) => { - if (options.id !== id) { - // set custom app bundle id before running the app - let args = ['config', 'set', `${options.platform}.id`, options.id]; - if (isWindows) { - args = args.map((arg) => quoteString(arg)); - } - const child = childProcess.spawn(/^win/.test(process.platform) ? 'ns.cmd' : 'ns', args, { - cwd: projectCwd, - stdio: 'inherit', - shell: isWindows ? true : undefined, - }); - child.on('close', (code) => { - child.kill('SIGKILL'); - runCommand(); - }); - } else { - runCommand(); - } - }); - } else { - runCommand(); - } - }; + async function checkAppId(): Promise { + return new Promise((resolve) => { + let args = ['config', 'get', `id`]; + if (isWindows) { + args = args.map((arg) => quoteString(arg)); + } - if (options.clean) { - runCommand(); - } else { - const plistKeys = Object.keys(options.plistUpdates || {}); - if (plistKeys.length) { - for (const filepath of plistKeys) { - let plistPath: string; - if (filepath.indexOf('.') === 0) { - // resolve relative to project directory - plistPath = nodeResolve(projectCwd, filepath); - } else { - // default to locating in App_Resources - plistPath = nodeResolve(projectCwd, 'App_Resources', 'iOS', filepath); - } - const plistFile = parse(readFileSync(plistPath, 'utf8')); - const plistUpdates = options.plistUpdates[filepath]; - // check if updates are needed to avoid native build if not needed - let needsUpdate = false; - for (const key in plistUpdates) { - if (Array.isArray(plistUpdates[key])) { - try { - // compare stringified - const plistString = JSON.stringify(plistFile[key] || {}); - const plistUpdateString = JSON.stringify(plistUpdates[key]); - if (plistString !== plistUpdateString) { - plistFile[key] = plistUpdates[key]; - console.log(`Updating ${filepath}: ${key}=`, plistFile[key]); - needsUpdate = true; - } - } catch (err) { - console.log(`plist file parse error:`, err); - } - } else if (plistFile[key] !== plistUpdates[key]) { - plistFile[key] = plistUpdates[key]; - console.log(`Updating ${filepath}: ${key}=${plistFile[key]}`); - needsUpdate = true; - } - } - if (needsUpdate) { - writeFileSync(plistPath, build(plistFile)); - console.log(`Updated: ${plistPath}`); - } - } - } + const child = childProcess.spawn(isWindows ? 'ns.cmd' : 'ns', args, { + cwd: projectCwd, + shell: isWindows ? true : undefined, + }); + child.stdout.setEncoding('utf8'); + child.stdout.on('data', function (data) { + // ensure no newline chars at the end + const appId: string = (data || '').toString().replace('\n', '').replace('\r', ''); + // console.log('existing app id:', appId); + resolve(appId); + }); + child.on('close', (code) => { + child.kill('SIGKILL'); + }); + }); + } - const xmlKeys = Object.keys(options.xmlUpdates || {}); - if (xmlKeys.length) { - for (const filepath of xmlKeys) { - let xmlPath: string; - if (filepath.indexOf('.') === 0) { - // resolve relative to project directory - xmlPath = nodeResolve(projectCwd, filepath); - } else { - // default to locating in App_Resources - xmlPath = nodeResolve(projectCwd, 'App_Resources', 'Android', filepath); - } - parseString(readFileSync(xmlPath, 'utf8'), (err, result) => { - if (err) { - throw err; - } - if (!result) { - result = {}; - } - // console.log('BEFORE---'); - // console.log(JSON.stringify(result, null, 2)); - - const xmlUpdates = options.xmlUpdates[filepath]; - for (const key in xmlUpdates) { - result[key] = {}; - for (const subKey in xmlUpdates[key]) { - result[key][subKey] = []; - for (let i = 0; i < xmlUpdates[key][subKey].length; i++) { - const node = xmlUpdates[key][subKey][i]; - const attrName = Object.keys(node)[0]; - - result[key][subKey].push({ - _: node[attrName], - $: { - name: attrName, - }, - }); - } - } - } - - // console.log('AFTER---'); - // console.log(JSON.stringify(result, null, 2)); - - const builder = new Builder(); - const xml = builder.buildObject(result); - writeFileSync(xmlPath, xml); - console.log(`Updated: ${xmlPath}`); - - checkOptions(); - }); - } - } else { - checkOptions(); - } + async function runCommand(nsOptions: any, additionalArgs: string[]): Promise<{ success: boolean }> { + let icon = ''; + if (!nsOptions.clean) { + if (nsOptions.platform === 'ios') { + icon = ''; + } else if (nsOptions.platform === 'android') { + icon = '🤖'; + } else if (['vision', 'visionos'].includes(nsOptions.platform)) { + icon = '🥽'; } - } catch (err) { - console.error(err); - reject(err); } - }); + if (isWindows) { + // https://github.com/NativeScript/nativescript-cli/pull/5808 + nsOptions = nsOptions.map((arg) => quoteString(arg)); + additionalArgs = additionalArgs.map((arg) => quoteString(arg)); + } + + console.log(`―――――――――――――――――――――――― ${icon}`); + console.log(`Running NativeScript ${options.command === COMMANDS.TEST ? 'unit tests' : 'CLI'} in ${projectCwd}`); + console.log(' '); + console.log([`ns`, ...nsOptions, ...additionalArgs].join(' ')); + console.log(' '); + + if (additionalArgs.length) { + console.log('Note: When using extra cli flags, ensure all key/value pairs are separated with =, for example: --provision="Name"'); + console.log(' '); + } + console.log(`---`); + + return new Promise((resolve) => { + const child = childProcess.spawn(isWindows ? 'ns.cmd' : 'ns', [...nsOptions, ...additionalArgs], { + cwd: projectCwd, + stdio: 'inherit', + shell: isWindows ? true : undefined, + }); + + child.on('close', (code) => { + child.kill('SIGKILL'); + resolve({ success: code === 0 }); + }); + }); + } } diff --git a/packages/nx/src/utils/generate-schemas.ts b/packages/nx/src/utils/generate-schemas.ts new file mode 100644 index 000000000..6dc360148 --- /dev/null +++ b/packages/nx/src/utils/generate-schemas.ts @@ -0,0 +1,35 @@ +import { writeFileSync } from 'fs-extra'; +import { join } from 'path'; +import { androidSchema } from '../schemas/android-properties.schema'; +import { baseSchema } from '../schemas/base.schema'; +import { buildSchema } from '../schemas/build.schema'; +import { debugSchema } from '../schemas/debug.schema'; +import { iosSchema } from '../schemas/ios-properties.schema'; +import { prepareSchema } from '../schemas/prepare.schema'; +import { runSchema } from '../schemas/run.schema'; +import { testSchema } from '../schemas/test.schema'; +import { cleanSchema } from '../schemas/clean.schema'; + +(async () => { + const outputDirectory = join(__dirname, '..', 'executors', 'build'); + + const outputs = [ + { name: 'build', schemas: [baseSchema, androidSchema, iosSchema, buildSchema] }, + { name: 'debug', schemas: [baseSchema, androidSchema, iosSchema, debugSchema] }, + { name: 'prepare', schemas: [baseSchema, androidSchema, iosSchema, prepareSchema] }, + { name: 'run', schemas: [baseSchema, androidSchema, iosSchema, runSchema] }, + { name: 'test', schemas: [baseSchema, androidSchema, iosSchema, testSchema] }, + { name: 'clean', schemas: [cleanSchema] }, + ]; + + for (const output of outputs) { + let combinedSchema; + for (const schema of output.schemas) { + combinedSchema = combinedSchema || schema; + Object.assign(combinedSchema.properties, schema.properties); + combinedSchema.title = schema?.title; + combinedSchema.description = schema?.description; + } + writeFileSync(join(outputDirectory, output.name + '.schema.json'), JSON.stringify(combinedSchema, null, 2)); + } +})(); diff --git a/packages/nx/src/utils/normalize-extra-flags.ts b/packages/nx/src/utils/normalize-extra-flags.ts new file mode 100644 index 000000000..6eef0dd1b --- /dev/null +++ b/packages/nx/src/utils/normalize-extra-flags.ts @@ -0,0 +1,18 @@ +export function normalizeExtraFlags(extraFlags: Record, prefix?) { + let additionalFlags = []; + for (const flag of Object.keys(extraFlags)) { + switch (typeof extraFlags[flag]) { + case 'boolean': + case 'number': + case 'string': + additionalFlags.push(`--${prefix ? prefix + '.' : ''}${flag}=${extraFlags[flag]}`); + break; + case 'object': + additionalFlags.push(...normalizeExtraFlags(extraFlags[flag], flag)); + break; + default: + break; + } + } + return additionalFlags; +} diff --git a/packages/nx/src/utils/parse-option-name.ts b/packages/nx/src/utils/parse-option-name.ts new file mode 100644 index 000000000..92a8dce94 --- /dev/null +++ b/packages/nx/src/utils/parse-option-name.ts @@ -0,0 +1,5 @@ +export function parseOptionName(flag: string) { + // strip just the option name from extra arguments + // --provision='match AppStore my.bundle.com' > provision + return flag.split('=')[0].replace('--', ''); +} diff --git a/packages/nx/src/utils/types.ts b/packages/nx/src/utils/types.ts new file mode 100644 index 000000000..67188793f --- /dev/null +++ b/packages/nx/src/utils/types.ts @@ -0,0 +1,24 @@ +import { AndroidSchema } from '../schemas/android-properties.schema'; +import { BaseSchema } from '../schemas/base.schema'; +import { BuildSchema } from '../schemas/build.schema'; +import { DebugSchema } from '../schemas/debug.schema'; +import { IosSchema } from '../schemas/ios-properties.schema'; +import { PrepareSchema } from '../schemas/prepare.schema'; +import { RunSchema } from '../schemas/run.schema'; +import { TestSchema } from '../schemas/test.schema'; + +export type KeysOfProperty = { + [P in keyof T]: Property; +}; + +export interface Property { + type: string; + description: string; + default?: any; + alias?: string; +} + +export interface ExecutorSchema extends BaseSchema, BuildSchema, DebugSchema, PrepareSchema, RunSchema, TestSchema { + android: AndroidSchema; + ios: IosSchema; +} diff --git a/tsconfig.base.json b/tsconfig.base.json index f26f72ff9..f5481ab92 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -4,6 +4,7 @@ "rootDir": ".", "sourceMap": true, "declaration": false, + "esModuleInterop": true, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, diff --git a/yarn.lock b/yarn.lock index 06d230971..cc609e5ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2537,13 +2537,6 @@ dependencies: "@types/node" "*" -"@types/xml2js@^0.4.9": - version "0.4.14" - resolved "https://registry.yarnpkg.com/@types/xml2js/-/xml2js-0.4.14.tgz#5d462a2a7330345e2309c6b549a183a376de8f9a" - integrity sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ== - dependencies: - "@types/node" "*" - "@types/yargs-parser@*": version "21.0.3" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.3.tgz#815e30b786d2e8f0dcd85fd5bcf5e1a04d008f15" @@ -4421,6 +4414,14 @@ enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1, enhanced-resolve@^5.7.0: graceful-fs "^4.2.4" tapable "^2.2.0" +enquirer@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.4.1.tgz#93334b3fbd74fc7097b224ab4a8fb7e40bf4ae56" + integrity sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ== + dependencies: + ansi-colors "^4.1.1" + strip-ansi "^6.0.1" + enquirer@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -4840,6 +4841,13 @@ fast-uri@^3.0.1: resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== +fast-xml-parser@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.5.0.tgz#2882b7d01a6825dfdf909638f2de0256351def37" + integrity sha512-/PlTQCI96+fZMAOLMZK4CWG1ItCbfZ/0jx7UIJFChPNrx7tcEgerUgWbeieCM9MfHInUDyK8DWYZ+YrywDJuTg== + dependencies: + strnum "^1.0.5" + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -8438,7 +8446,7 @@ sass@^1.42.1: immutable "^4.0.0" source-map-js ">=0.6.2 <2.0.0" -sax@>=0.6.0, sax@^1.2.4: +sax@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== @@ -8862,6 +8870,11 @@ strip-json-comments@3.1.1, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strnum@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" + integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== + style-loader@^3.3.0: version "3.3.4" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.4.tgz#f30f786c36db03a45cbd55b6a70d930c479090e7" @@ -9710,24 +9723,11 @@ ws@8.18.0, ws@^8.18.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== -xml2js@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" - integrity sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA== - dependencies: - sax ">=0.6.0" - xmlbuilder "~11.0.0" - xmlbuilder@>=11.0.1, xmlbuilder@^15.1.1: version "15.1.1" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== -xmlbuilder@~11.0.0: - version "11.0.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" - integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== - y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"