diff --git a/packages/app-harness/renative.json b/packages/app-harness/renative.json index 837337d4d..f07797618 100644 --- a/packages/app-harness/renative.json +++ b/packages/app-harness/renative.json @@ -13,8 +13,96 @@ "portOffset": 10 }, "platforms": { + "ios": { + "templateXcode": { + "AppDelegate_h": { + "appDelegateMethods": ["@property (nonatomic, strong) UIView *appSwitcherView;"] + }, + "AppDelegate_mm": { + "appDelegateMethods": { + "custom": [ + "- (UIImage *)createScreenshotOfCurrentContext {", + " CGSize screenSize = self.window.screen.bounds.size;", + " UIGraphicsBeginImageContext(screenSize);", + " CGContextRef currentContext = UIGraphicsGetCurrentContext();", + " if (!currentContext) {", + " return nil;", + " }", + " [self.window.layer renderInContext:currentContext];", + " UIImage *image = UIGraphicsGetImageFromCurrentImageContext();", + " UIGraphicsEndImageContext();", + " return image;", + "}" + ] + } + } + }, + "privacyManifests": { + "NSPrivacyAccessedAPITypes": [ + { + "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryActiveKeyboards", + "NSPrivacyAccessedAPITypeReasons": ["CA92.1"] + }, + { + "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategorySystemBootTime", + "NSPrivacyAccessedAPITypeReasons": ["CA92.1", "E174.1"] + } + ] + } + }, + "tvos": { + "templateXcode": { + "AppDelegate_h": { + "appDelegateMethods": ["@property (nonatomic, strong) UIView *appSwitcherView;"] + }, + "AppDelegate_mm": { + "appDelegateMethods": { + "custom": [ + "- (UIImage *)createScreenshotOfCurrentContext {", + " CGSize screenSize = self.window.screen.bounds.size;", + " UIGraphicsBeginImageContext(screenSize);", + " CGContextRef currentContext = UIGraphicsGetCurrentContext();", + " if (!currentContext) {", + " return nil;", + " }", + " [self.window.layer renderInContext:currentContext];", + " UIImage *image = UIGraphicsGetImageFromCurrentImageContext();", + " UIGraphicsEndImageContext();", + " return image;", + "}" + ] + } + } + }, + "privacyManifests": { + "NSPrivacyAccessedAPITypes": [ + { + "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryActiveKeyboards", + "NSPrivacyAccessedAPITypeReasons": ["CA92.1"] + }, + { + "NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategorySystemBootTime", + "NSPrivacyAccessedAPITypeReasons": ["CA92.1", "E174.1"] + } + ] + } + }, "android": { "templateAndroid": { + "build_gradle": { + "buildscript": { + "custom": [], + "dependencies": ["def customVar2 = '2'"], + "repositories": ["def customVar1 = '1'"], + "ext": ["playServicesLocationVersion = \"21.0.1\""] + }, + "injectAfterAll": [ + "allprojects {", + " repositories {", + " }", + "}" + ] + }, "styles_xml": { "tag": "resources", "children": [ diff --git a/packages/core/jsonSchema/renative-1.0.schema.json b/packages/core/jsonSchema/renative-1.0.schema.json index d206b5200..304ba7e0a 100644 --- a/packages/core/jsonSchema/renative-1.0.schema.json +++ b/packages/core/jsonSchema/renative-1.0.schema.json @@ -480,6 +480,13 @@ }, "getJsBundleFile": { "type": "string" + }, + "webpackExcludedDirs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allows to specify files or directories in the src folder that webpack should ignore when bundling code. By default, the \"pages\" folder is excluded for web platforms that do not use next.js." } }, "additionalProperties": false @@ -680,22 +687,6 @@ "build_gradle": { "type": "object", "properties": { - "allprojects": { - "type": "object", - "properties": { - "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" - }, - "description": "Customize repositories section of build.gradle" - } - }, - "required": [ - "repositories" - ], - "additionalProperties": false - }, "plugins": { "type": "array", "items": { @@ -706,30 +697,38 @@ "type": "object", "properties": { "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" } }, "dependencies": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" + } + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "custom": { + "type": "array", + "items": { + "type": "string" } } }, "required": [ "repositories", - "dependencies" + "dependencies", + "ext", + "custom" ], "additionalProperties": false }, - "dexOptions": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - }, "injectAfterAll": { "type": "array", "items": { @@ -1165,6 +1164,12 @@ } }, "additionalProperties": false + }, + "custom": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -1192,6 +1197,12 @@ "items": { "type": "string" } + }, + "appDelegateMethods": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -1592,6 +1603,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "enableAndroidX": { "type": [ "boolean", @@ -1839,6 +1853,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "enableAndroidX": { "$ref": "#/definitions/zodPlatformsSchema/properties/android/properties/buildSchemes/additionalProperties/properties/enableAndroidX", "description": "Enables new android X architecture" @@ -2051,6 +2068,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "type": "boolean", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -2175,6 +2195,63 @@ "firebaseId": { "type": "string" }, + "privacyManifests": { + "type": "object", + "properties": { + "NSPrivacyAccessedAPITypes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "NSPrivacyAccessedAPIType": { + "type": "string", + "enum": [ + "NSPrivacyAccessedAPICategorySystemBootTime", + "NSPrivacyAccessedAPICategoryDiskSpace", + "NSPrivacyAccessedAPICategoryActiveKeyboards", + "NSPrivacyAccessedAPICategoryUserDefaults" + ] + }, + "NSPrivacyAccessedAPITypeReasons": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "DDA9.1", + "C617.1", + "3B52.1", + "0A2A.1", + "35F9.1", + "8FFB.1", + "3D61.1", + "85F4.1", + "E174.1", + "7D9E.1", + "B728.1", + "3EC4.1", + "54BD.1", + "CA92.1", + "1C8F.1", + "C56D.1", + "AC6B.1" + ] + } + } + }, + "required": [ + "NSPrivacyAccessedAPIType", + "NSPrivacyAccessedAPITypeReasons" + ], + "additionalProperties": false, + "description": "Official apple documentation https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api" + } + } + }, + "required": [ + "NSPrivacyAccessedAPITypes" + ], + "additionalProperties": false + }, "exportOptions": { "type": "object", "properties": { @@ -2332,6 +2409,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -2420,6 +2500,9 @@ "firebaseId": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -2534,6 +2617,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "package": { "type": "string" }, @@ -2689,6 +2775,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "package": { "$ref": "#/definitions/zodPlatformsSchema/properties/tizen/properties/buildSchemes/additionalProperties/properties/package" }, @@ -2817,6 +2906,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "iconColor": { "type": "string" }, @@ -2950,6 +3042,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "iconColor": { "$ref": "#/definitions/zodPlatformsSchema/properties/webos/properties/buildSchemes/additionalProperties/properties/iconColor" }, @@ -3066,6 +3161,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "webpackConfig": { "$ref": "#/definitions/zodPlatformsSchema/properties/tizen/properties/buildSchemes/additionalProperties/properties/webpackConfig" }, @@ -3214,6 +3312,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "webpackConfig": { "$ref": "#/definitions/zodPlatformsSchema/properties/tizen/properties/buildSchemes/additionalProperties/properties/webpackConfig" }, @@ -3351,6 +3452,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings" }, @@ -3429,6 +3533,9 @@ "firebaseId": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -3585,6 +3692,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -3673,6 +3783,9 @@ "firebaseId": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -3795,6 +3908,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "electronConfig": { "$ref": "#/definitions/zodPlatformsSchema/properties/macos/properties/buildSchemes/additionalProperties/properties/electronConfig" }, @@ -4042,6 +4158,9 @@ "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "electronConfig": { "$ref": "#/definitions/zodPlatformsSchema/properties/macos/properties/buildSchemes/additionalProperties/properties/electronConfig", "description": "Allows you to configure electron app as per https://www.electron.build/" @@ -4226,6 +4345,9 @@ "firebaseId": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/zodPlatformsSchema/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" } @@ -4258,6 +4380,9 @@ }, "getJsBundleFile": { "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" + }, + "webpackExcludedDirs": { + "$ref": "#/definitions/zodCommonSchema/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" } }, "additionalProperties": false @@ -5185,6 +5310,13 @@ "xbox" ] } + }, + "webpackExcludedDirs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allows to specify files or directories in the src folder that webpack should ignore when bundling code." } }, "additionalProperties": false diff --git a/packages/core/jsonSchema/rnv.app.json b/packages/core/jsonSchema/rnv.app.json index 35d6f38b2..377cea82b 100644 --- a/packages/core/jsonSchema/rnv.app.json +++ b/packages/core/jsonSchema/rnv.app.json @@ -260,6 +260,13 @@ }, "getJsBundleFile": { "type": "string" + }, + "webpackExcludedDirs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allows to specify files or directories in the src folder that webpack should ignore when bundling code. By default, the \"pages\" folder is excluded for web platforms that do not use next.js." } }, "additionalProperties": false @@ -370,6 +377,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "enableAndroidX": { "type": [ "boolean", @@ -513,22 +523,6 @@ "build_gradle": { "type": "object", "properties": { - "allprojects": { - "type": "object", - "properties": { - "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" - }, - "description": "Customize repositories section of build.gradle" - } - }, - "required": [ - "repositories" - ], - "additionalProperties": false - }, "plugins": { "type": "array", "items": { @@ -539,30 +533,38 @@ "type": "object", "properties": { "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" } }, "dependencies": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" + } + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "custom": { + "type": "array", + "items": { + "type": "string" } } }, "required": [ "repositories", - "dependencies" + "dependencies", + "ext", + "custom" ], "additionalProperties": false }, - "dexOptions": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - }, "injectAfterAll": { "type": "array", "items": { @@ -1022,6 +1024,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "enableAndroidX": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/android/properties/buildSchemes/additionalProperties/properties/enableAndroidX", "description": "Enables new android X architecture" @@ -1234,6 +1239,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "type": "boolean", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -1358,6 +1366,63 @@ "firebaseId": { "type": "string" }, + "privacyManifests": { + "type": "object", + "properties": { + "NSPrivacyAccessedAPITypes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "NSPrivacyAccessedAPIType": { + "type": "string", + "enum": [ + "NSPrivacyAccessedAPICategorySystemBootTime", + "NSPrivacyAccessedAPICategoryDiskSpace", + "NSPrivacyAccessedAPICategoryActiveKeyboards", + "NSPrivacyAccessedAPICategoryUserDefaults" + ] + }, + "NSPrivacyAccessedAPITypeReasons": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "DDA9.1", + "C617.1", + "3B52.1", + "0A2A.1", + "35F9.1", + "8FFB.1", + "3D61.1", + "85F4.1", + "E174.1", + "7D9E.1", + "B728.1", + "3EC4.1", + "54BD.1", + "CA92.1", + "1C8F.1", + "C56D.1", + "AC6B.1" + ] + } + } + }, + "required": [ + "NSPrivacyAccessedAPIType", + "NSPrivacyAccessedAPITypeReasons" + ], + "additionalProperties": false, + "description": "Official apple documentation https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api" + } + } + }, + "required": [ + "NSPrivacyAccessedAPITypes" + ], + "additionalProperties": false + }, "exportOptions": { "type": "object", "properties": { @@ -1590,6 +1655,12 @@ } }, "additionalProperties": false + }, + "custom": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -1617,6 +1688,12 @@ "items": { "type": "string" } + }, + "appDelegateMethods": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -1748,6 +1825,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -1836,6 +1916,9 @@ "firebaseId": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -1950,6 +2033,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "package": { "type": "string" }, @@ -2105,6 +2191,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "package": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/tizen/properties/buildSchemes/additionalProperties/properties/package" }, @@ -2233,6 +2322,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "iconColor": { "type": "string" }, @@ -2366,6 +2458,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "iconColor": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/webos/properties/buildSchemes/additionalProperties/properties/iconColor" }, @@ -2482,6 +2577,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "webpackConfig": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/tizen/properties/buildSchemes/additionalProperties/properties/webpackConfig" }, @@ -2630,6 +2728,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "webpackConfig": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/tizen/properties/buildSchemes/additionalProperties/properties/webpackConfig" }, @@ -2767,6 +2868,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings" }, @@ -2845,6 +2949,9 @@ "firebaseId": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -3001,6 +3108,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -3089,6 +3199,9 @@ "firebaseId": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -3211,6 +3324,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "electronConfig": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/macos/properties/buildSchemes/additionalProperties/properties/electronConfig" }, @@ -3458,6 +3574,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.app/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "electronConfig": { "$ref": "#/definitions/rnv.app/properties/platforms/properties/macos/properties/buildSchemes/additionalProperties/properties/electronConfig", "description": "Allows you to configure electron app as per https://www.electron.build/" diff --git a/packages/core/jsonSchema/rnv.engine.json b/packages/core/jsonSchema/rnv.engine.json index c9982208c..127cc6418 100644 --- a/packages/core/jsonSchema/rnv.engine.json +++ b/packages/core/jsonSchema/rnv.engine.json @@ -85,6 +85,13 @@ ] } }, + "webpackExcludedDirs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allows to specify files or directories in the src folder that webpack should ignore when bundling code." + }, "$schema": { "type": "string", "description": "schema definition" diff --git a/packages/core/jsonSchema/rnv.plugin.json b/packages/core/jsonSchema/rnv.plugin.json index c6edc0754..038967ff6 100644 --- a/packages/core/jsonSchema/rnv.plugin.json +++ b/packages/core/jsonSchema/rnv.plugin.json @@ -212,22 +212,6 @@ "build_gradle": { "type": "object", "properties": { - "allprojects": { - "type": "object", - "properties": { - "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" - }, - "description": "Customize repositories section of build.gradle" - } - }, - "required": [ - "repositories" - ], - "additionalProperties": false - }, "plugins": { "type": "array", "items": { @@ -238,30 +222,38 @@ "type": "object", "properties": { "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" } }, "dependencies": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" + } + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "custom": { + "type": "array", + "items": { + "type": "string" } } }, "required": [ "repositories", - "dependencies" + "dependencies", + "ext", + "custom" ], "additionalProperties": false }, - "dexOptions": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - }, "injectAfterAll": { "type": "array", "items": { @@ -848,6 +840,12 @@ } }, "additionalProperties": false + }, + "custom": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -875,6 +873,12 @@ "items": { "type": "string" } + }, + "appDelegateMethods": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false diff --git a/packages/core/jsonSchema/rnv.project.json b/packages/core/jsonSchema/rnv.project.json index aa133c437..98ded95d3 100644 --- a/packages/core/jsonSchema/rnv.project.json +++ b/packages/core/jsonSchema/rnv.project.json @@ -605,6 +605,13 @@ }, "getJsBundleFile": { "type": "string" + }, + "webpackExcludedDirs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Allows to specify files or directories in the src folder that webpack should ignore when bundling code. By default, the \"pages\" folder is excluded for web platforms that do not use next.js." } }, "additionalProperties": false @@ -715,6 +722,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "enableAndroidX": { "type": [ "boolean", @@ -858,22 +868,6 @@ "build_gradle": { "type": "object", "properties": { - "allprojects": { - "type": "object", - "properties": { - "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" - }, - "description": "Customize repositories section of build.gradle" - } - }, - "required": [ - "repositories" - ], - "additionalProperties": false - }, "plugins": { "type": "array", "items": { @@ -884,30 +878,38 @@ "type": "object", "properties": { "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" } }, "dependencies": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" + } + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "custom": { + "type": "array", + "items": { + "type": "string" } } }, "required": [ "repositories", - "dependencies" + "dependencies", + "ext", + "custom" ], "additionalProperties": false }, - "dexOptions": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - }, "injectAfterAll": { "type": "array", "items": { @@ -1367,6 +1369,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "enableAndroidX": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/android/properties/buildSchemes/additionalProperties/properties/enableAndroidX", "description": "Enables new android X architecture" @@ -1579,6 +1584,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "type": "boolean", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -1703,6 +1711,63 @@ "firebaseId": { "type": "string" }, + "privacyManifests": { + "type": "object", + "properties": { + "NSPrivacyAccessedAPITypes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "NSPrivacyAccessedAPIType": { + "type": "string", + "enum": [ + "NSPrivacyAccessedAPICategorySystemBootTime", + "NSPrivacyAccessedAPICategoryDiskSpace", + "NSPrivacyAccessedAPICategoryActiveKeyboards", + "NSPrivacyAccessedAPICategoryUserDefaults" + ] + }, + "NSPrivacyAccessedAPITypeReasons": { + "type": "array", + "items": { + "type": "string", + "enum": [ + "DDA9.1", + "C617.1", + "3B52.1", + "0A2A.1", + "35F9.1", + "8FFB.1", + "3D61.1", + "85F4.1", + "E174.1", + "7D9E.1", + "B728.1", + "3EC4.1", + "54BD.1", + "CA92.1", + "1C8F.1", + "C56D.1", + "AC6B.1" + ] + } + } + }, + "required": [ + "NSPrivacyAccessedAPIType", + "NSPrivacyAccessedAPITypeReasons" + ], + "additionalProperties": false, + "description": "Official apple documentation https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api" + } + } + }, + "required": [ + "NSPrivacyAccessedAPITypes" + ], + "additionalProperties": false + }, "exportOptions": { "type": "object", "properties": { @@ -1935,6 +2000,12 @@ } }, "additionalProperties": false + }, + "custom": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -1962,6 +2033,12 @@ "items": { "type": "string" } + }, + "appDelegateMethods": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -2093,6 +2170,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -2181,6 +2261,9 @@ "firebaseId": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -2295,6 +2378,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "package": { "type": "string" }, @@ -2450,6 +2536,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "package": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/tizen/properties/buildSchemes/additionalProperties/properties/package" }, @@ -2578,6 +2667,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "iconColor": { "type": "string" }, @@ -2711,6 +2803,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "iconColor": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/webos/properties/buildSchemes/additionalProperties/properties/iconColor" }, @@ -2827,6 +2922,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "webpackConfig": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/tizen/properties/buildSchemes/additionalProperties/properties/webpackConfig" }, @@ -2975,6 +3073,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "webpackConfig": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/tizen/properties/buildSchemes/additionalProperties/properties/webpackConfig" }, @@ -3112,6 +3213,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings" }, @@ -3190,6 +3294,9 @@ "firebaseId": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -3346,6 +3453,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "ignoreWarnings": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/ignoreWarnings", "description": "Injects `inhibit_all_warnings` into Podfile" @@ -3434,6 +3544,9 @@ "firebaseId": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/firebaseId" }, + "privacyManifests": { + "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/privacyManifests" + }, "exportOptions": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/ios/properties/buildSchemes/additionalProperties/properties/exportOptions" }, @@ -3556,6 +3669,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "electronConfig": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/macos/properties/buildSchemes/additionalProperties/properties/electronConfig" }, @@ -3803,6 +3919,9 @@ "getJsBundleFile": { "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/getJsBundleFile" }, + "webpackExcludedDirs": { + "$ref": "#/definitions/rnv.project/properties/common/properties/buildSchemes/additionalProperties/properties/webpackExcludedDirs" + }, "electronConfig": { "$ref": "#/definitions/rnv.project/properties/platforms/properties/macos/properties/buildSchemes/additionalProperties/properties/electronConfig", "description": "Allows you to configure electron app as per https://www.electron.build/" diff --git a/packages/core/jsonSchema/rnv.templates.json b/packages/core/jsonSchema/rnv.templates.json index 823596f26..f920f51ab 100644 --- a/packages/core/jsonSchema/rnv.templates.json +++ b/packages/core/jsonSchema/rnv.templates.json @@ -314,22 +314,6 @@ "build_gradle": { "type": "object", "properties": { - "allprojects": { - "type": "object", - "properties": { - "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" - }, - "description": "Customize repositories section of build.gradle" - } - }, - "required": [ - "repositories" - ], - "additionalProperties": false - }, "plugins": { "type": "array", "items": { @@ -340,30 +324,38 @@ "type": "object", "properties": { "repositories": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" } }, "dependencies": { - "type": "object", - "additionalProperties": { - "type": "boolean" + "type": "array", + "items": { + "type": "string" + } + }, + "ext": { + "type": "array", + "items": { + "type": "string" + } + }, + "custom": { + "type": "array", + "items": { + "type": "string" } } }, "required": [ "repositories", - "dependencies" + "dependencies", + "ext", + "custom" ], "additionalProperties": false }, - "dexOptions": { - "type": "object", - "additionalProperties": { - "type": "boolean" - } - }, "injectAfterAll": { "type": "array", "items": { @@ -950,6 +942,12 @@ } }, "additionalProperties": false + }, + "custom": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false @@ -977,6 +975,12 @@ "items": { "type": "string" } + }, + "appDelegateMethods": { + "type": "array", + "items": { + "type": "string" + } } }, "additionalProperties": false diff --git a/packages/core/src/plugins/index.ts b/packages/core/src/plugins/index.ts index 3be6a5cfc..40ec39174 100644 --- a/packages/core/src/plugins/index.ts +++ b/packages/core/src/plugins/index.ts @@ -483,7 +483,6 @@ export const parsePlugins = ( if (plugins) { Object.keys(plugins).forEach((key) => { const plugin = getMergedPlugin(c, key); - if (!plugin) return; if ( diff --git a/packages/core/src/projects/__tests__/appConfig.test.ts b/packages/core/src/projects/__tests__/appConfig.test.ts new file mode 100644 index 000000000..51005f7e5 --- /dev/null +++ b/packages/core/src/projects/__tests__/appConfig.test.ts @@ -0,0 +1,170 @@ +import { copyBuildsFolder } from '../appConfig'; +import { getAppConfigBuildsFolder, getAppFolder, getTimestampPathsConfig } from '../../context/contextProps'; +import { isPlatformActive } from '../../platforms'; +import { copyTemplatePluginsSync } from '../../plugins'; +import { copyFolderContentsRecursiveSync, fsExistsSync } from '../../system/fs'; +import { logDefault } from '../../logger'; +import { getContext } from '../../context/provider'; +import path from 'path'; +jest.mock('../../system/injectors'); +jest.mock('../../context/provider'); +jest.mock('../../context/contextProps'); +jest.mock('../../platforms'); +jest.mock('../../plugins'); +jest.mock('../../system/fs'); +jest.mock('../../logger'); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +const mockContext = { + configPropsInjects: [], + systemPropsInjects: [], + runtimePropsInjects: [], + paths: { + project: { + appConfigBase: { + dir: '/path/to/project', + }, + }, + workspace: { + project: { + appConfigBase: { + dir: '/path/to/workspace', + }, + }, + appConfig: { + dir: '/path/to/appConfig', + }, + }, + appConfig: { + dirs: ['/path/to/appConfig1', '/path/to/appConfig2'], + }, + }, + runtime: { + currentPlatform: { + isWebHosted: true, + }, + }, +}; + +describe('copyBuildsFolder', () => { + it('should copy builds folder', async () => { + // GIVEN + const mockContext = { + configPropsInjects: [], + systemPropsInjects: [], + runtimePropsInjects: [], + paths: { + project: { + appConfigBase: { + dir: '/path/to/project', + }, + }, + workspace: { + project: { + appConfigBase: { + dir: '/path/to/workspace', + }, + }, + appConfig: { + dir: '/path/to/appConfig', + }, + }, + appConfig: { + dirs: ['/path/to/appConfig1', '/path/to/appConfig2'], + }, + }, + runtime: { + currentPlatform: { + isWebHosted: true, + }, + }, + }; + (getContext as jest.Mock).mockReturnValue(mockContext); + (isPlatformActive as jest.Mock).mockReturnValue(true); + (fsExistsSync as jest.Mock).mockReturnValue(true); + (getAppFolder as jest.Mock).mockReturnValue('/path/to/appFolder'); + (getAppConfigBuildsFolder as jest.Mock).mockImplementation((dir) => dir); + (getTimestampPathsConfig as jest.Mock).mockReturnValue({}); + + // WHEN + await copyBuildsFolder(); + + // THEN + + const sourcePath1 = getAppConfigBuildsFolder(mockContext.paths.project.appConfigBase.dir); + const sourcePath1sec = getAppConfigBuildsFolder(mockContext.paths.workspace.project.appConfigBase.dir); + const sourcePathShared = path.join(mockContext.paths.project.appConfigBase.dir, 'builds/_shared'); + const sourcePath0sec = getAppConfigBuildsFolder(mockContext.paths.workspace.appConfig.dir); + const sourceV = getAppConfigBuildsFolder(mockContext.paths.appConfig.dirs[0]); + const destPath = '/path/to/appFolder'; + const allInjects = []; + const tsPathsConfig = {}; + + expect(copyFolderContentsRecursiveSync).toHaveBeenCalledWith( + sourcePath1, + destPath, + true, + undefined, + false, + allInjects, + tsPathsConfig + ); + expect(copyFolderContentsRecursiveSync).toHaveBeenCalledWith( + sourcePath1sec, + destPath, + true, + undefined, + false, + allInjects, + tsPathsConfig + ); + expect(copyFolderContentsRecursiveSync).toHaveBeenCalledWith( + sourcePathShared, + getAppFolder(), + true, + undefined, + false, + allInjects + ); + expect(copyFolderContentsRecursiveSync).toHaveBeenCalledWith( + sourcePath0sec, + destPath, + true, + undefined, + false, + allInjects, + tsPathsConfig + ); + expect(copyFolderContentsRecursiveSync).toHaveBeenCalledWith( + sourceV, + destPath, + true, + undefined, + false, + allInjects, + tsPathsConfig + ); + + expect(logDefault).toHaveBeenCalledWith('copyBuildsFolder'); + expect(isPlatformActive).toHaveBeenCalled(); + expect(copyFolderContentsRecursiveSync).toHaveBeenCalledTimes(6); + expect(copyTemplatePluginsSync).toHaveBeenCalledWith(mockContext); + }); + it('paths.appConfig.dirs is not defined and runtime.currentPlatform.isWebHosted is false', async () => { + // GIVEN + mockContext.paths.appConfig.dirs = undefined as any; + mockContext.runtime.currentPlatform.isWebHosted = false; + + (getContext as jest.Mock).mockReturnValue(mockContext); + (fsExistsSync as jest.Mock).mockReturnValue(true); + + // WHEN + await copyBuildsFolder(); + + // THEN + expect(copyFolderContentsRecursiveSync).toHaveBeenCalledTimes(4); + }); +}); diff --git a/packages/core/src/projects/__tests__/mutations.test.ts b/packages/core/src/projects/__tests__/mutations.test.ts new file mode 100644 index 000000000..e26e15c01 --- /dev/null +++ b/packages/core/src/projects/__tests__/mutations.test.ts @@ -0,0 +1,55 @@ +import { handleMutations } from '../mutations'; +import { getContext } from '../../context/provider'; +import { inquirerPrompt } from '../../api'; +import { updatePackage } from '../package'; +import { logRaw, logWarning } from '../../logger'; + +jest.mock('../../context/provider'); +jest.mock('../../api'); +jest.mock('../package'); +jest.mock('../../logger', () => ({ + ...jest.requireActual('../../logger'), + logWarning: jest.fn(), + logRaw: jest.fn(), + chalk: () => ({ + bold: jest.fn((text) => text), + red: jest.fn((text) => text), + green: jest.fn((text) => text), + gray: jest.fn((text) => text), + }), +})); + +describe('handleMutations', () => { + it('should handle mutations correctly', async () => { + // GIVEN + const mockContext = { + mutations: { + pendingMutations: [ + { + name: 'test', + original: { version: '1.0.0' }, + updated: { version: '1.1.0' }, + msg: 'test message', + source: 'test source', + type: 'test type', + }, + ], + }, + buildConfig: { isTemplate: false }, + runtime: { + isAppConfigured: true, + } + }; + (getContext as jest.Mock).mockReturnValue(mockContext); + (inquirerPrompt as jest.Mock).mockResolvedValue({ confirm: 'Update package and install (recommended)' }); + + // WHEN + const result = await handleMutations(); + + // THEN + expect(result).toBe(true); + expect(logWarning).toHaveBeenCalledWith('Updates to package.json are required:'); + expect(logRaw).toHaveBeenCalledWith('- test (1.0.0) => (1.1.0) test message | test source\n'); + expect(updatePackage).toHaveBeenCalledWith({ 'test type': { test: '1.1.0' } }); + }); +}); diff --git a/packages/core/src/projects/__tests__/npm.test.ts b/packages/core/src/projects/__tests__/npm.test.ts new file mode 100644 index 000000000..2016eca1c --- /dev/null +++ b/packages/core/src/projects/__tests__/npm.test.ts @@ -0,0 +1,65 @@ +import { checkNpxIsInstalled, listAndSelectNpmVersion } from '../npm'; +import { logWarning } from '../../logger'; +import { inquirerPrompt } from '../../api'; +import { executeAsync, commandExistsSync } from '../../system/exec'; + +jest.mock('../../logger'); +jest.mock('command-exists'); +jest.mock('../../api'); +jest.mock('../../system/exec'); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('checkNpxIsInstalled', () => { + it('installs npx if not installed and user confirms', async () => { + // GIVEN + jest.mocked(commandExistsSync).mockReturnValue(false); + jest.mocked(inquirerPrompt).mockResolvedValue({ confirm: true }); + const executeAsyncMock = jest.mocked(executeAsync).mockResolvedValue('MOCK_RESULT'); + + // WHEN + await checkNpxIsInstalled(); + + // THEN + expect(logWarning).toHaveBeenCalledWith('npx is not installed, please install it before running this command'); + expect(executeAsyncMock).toHaveBeenCalledWith('npm install -g npx'); + }); + + it('fetches and processes versions and tags, and prompts user to select a version', async () => { + // GIVEN + const npmPackage = 'test-package'; + const versionsStr = "'1.0.0', '1.0.1', '1.1.0'"; + const tagsStr = 'latest: 1.1.0\nbeta: 1.0.1'; + jest.mocked(executeAsync).mockImplementation((command) => { + if (command.includes('versions')) { + return Promise.resolve(versionsStr); + } else if (command.includes('dist-tag ls')) { + return Promise.resolve(tagsStr); + } + return Promise.resolve(''); + }); + jest.mocked(inquirerPrompt).mockResolvedValue({ inputTemplateVersion: '1.1.0' }); + + // WHEN + const selectedVersion = await listAndSelectNpmVersion(npmPackage); + + // THEN + expect(selectedVersion).toBe('1.1.0'); + expect(jest.mocked(executeAsync)).toHaveBeenCalledWith(`npm view ${npmPackage} versions`); + expect(jest.mocked(executeAsync)).toHaveBeenCalledWith(`npm dist-tag ls ${npmPackage}`); + expect(jest.mocked(inquirerPrompt)).toHaveBeenCalledWith({ + name: 'inputTemplateVersion', + type: 'list', + message: `What ${npmPackage} version to use?`, + default: '1.1.0', + loop: false, + choices: [ + { name: '1.1.0 (@latest)', value: '1.1.0' }, + { name: '1.0.1 (@beta)', value: '1.0.1' }, + { name: '1.0.0', value: '1.0.0' }, + ], + }); + }); +}); diff --git a/packages/core/src/projects/__tests__/package.test.ts b/packages/core/src/projects/__tests__/package.test.ts new file mode 100644 index 000000000..c45c282b8 --- /dev/null +++ b/packages/core/src/projects/__tests__/package.test.ts @@ -0,0 +1,72 @@ +import { checkAndCreateProjectPackage } from '../package'; +import { getContext } from '../../context/provider'; +import { fsExistsSync, fsWriteFileSync, readObjectSync, loadFile } from '../../system/fs'; +import { logInfo } from '../../logger'; + +jest.mock('../../context/provider'); +jest.mock('../../system/fs'); +jest.mock('../../logger'); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +const mockContext = { + files: { + project: { + config: { + projectName: 'test-project', + projectVersion: '1.0.0', + templateConfig: { + name: 'test-template', + version: '1.0.0', + }, + }, + }, + rnvCore: { + package: { + version: '1.0.0', + }, + }, + }, + paths: { + project: { + dir: '/path/to/project', + package: '/path/to/package', + }, + template: { + configTemplate: '/path/to/template', + }, + }, +}; + +describe('package', () => { + it('should check and create project package correctly', async () => { + // GIVEN + + (getContext as jest.Mock).mockReturnValue(mockContext); + (fsExistsSync as jest.Mock).mockReturnValue(false); + (readObjectSync as jest.Mock).mockReturnValue({ templateConfig: { package_json: {} } }); + // WHEN + const result = await checkAndCreateProjectPackage(); + // THEN + expect(result).toBe(true); + expect(logInfo).toHaveBeenCalledWith('Your /path/to/package is missing. CREATING...DONE'); + expect(fsWriteFileSync).toHaveBeenCalledWith('/path/to/package', expect.any(String)); + expect(loadFile).toHaveBeenCalledWith(mockContext.files.project, mockContext.paths.project, 'package'); + }); + it('should not create a new package when package already exists', async () => { + // GIVEN + (getContext as jest.Mock).mockReturnValue(mockContext); + (fsExistsSync as jest.Mock).mockReturnValue(true); // Package already exists + + // WHEN + const result = await checkAndCreateProjectPackage(); + + // THEN + expect(result).toBe(true); + expect(logInfo).not.toHaveBeenCalledWith('Your /path/to/package is missing. CREATING...DONE'); + expect(fsWriteFileSync).not.toHaveBeenCalled(); + expect(loadFile).toHaveBeenCalledWith(mockContext.files.project, mockContext.paths.project, 'package'); + }); +}); diff --git a/packages/core/src/projects/__tests__/version.test.ts b/packages/core/src/projects/__tests__/version.test.ts new file mode 100644 index 000000000..5f3691891 --- /dev/null +++ b/packages/core/src/projects/__tests__/version.test.ts @@ -0,0 +1,71 @@ +import { versionCheck } from '../version'; +import { logDefault } from '../../logger'; +import { inquirerPrompt } from '../../api'; +import { generateContextDefaults } from '../../context/defaults'; +import { upgradeProjectDependencies } from '../../configs/configProject'; + +jest.mock('../../logger'); +jest.mock('../../api'); +jest.mock('../../configs/configProject'); +jest.mock('../../api', () => ({ + inquirerPrompt: jest.fn().mockResolvedValue({ chosenAction: 'Upgrade project to 2.0.0' }), +})); +jest.mock('../../logger', () => ({ + logDefault: jest.fn(), + getCurrentCommand: jest.fn(), + chalk: () => { + return { red: jest.fn(), bold: jest.fn() }; + }, +})); + +describe('versionCheck', () => { + it('not upgrading project', async () => { + const context = generateContextDefaults(); + context.runtime.versionCheckCompleted = true; + context.files.project.config = { + skipAutoUpdate: true, + }; + context.program.opts = () => ({ skipDependencyCheck: false }); + + const result = await versionCheck(context); + + expect(result).toBe(true); + expect(logDefault).toHaveBeenCalledWith('versionCheck'); + expect(upgradeProjectDependencies).not.toHaveBeenCalled(); + }); + it('upgrading project', async () => { + const context = generateContextDefaults(); + context.runtime.versionCheckCompleted = false; + context.files.project.config = { + skipAutoUpdate: false, + }; + context.files.project.package = { + devDependencies: { + '@rnv/core': '1.0.0', + }, + }; + context.files.rnvCore = { + package: { + version: '2.0.0', + }, + }; + context.program.opts = () => ({ skipDependencyCheck: false }); + + const result = await versionCheck(context); + + expect(result).toBe(true); + expect(logDefault).toHaveBeenCalledWith('versionCheck'); + expect(inquirerPrompt).toHaveBeenCalledWith({ + message: 'What to do next?', + type: 'list', + name: 'chosenAction', + choices: [ + 'Continue and skip updating package.json', + 'Continue and update package.json', + 'Upgrade project to 2.0.0', + ], + warningMessage: expect.any(String), + }); + expect(upgradeProjectDependencies).toHaveBeenCalledWith('2.0.0'); + }); +}); diff --git a/packages/core/src/projects/appConfig.ts b/packages/core/src/projects/appConfig.ts index 2ba7475ab..c1d525993 100644 --- a/packages/core/src/projects/appConfig.ts +++ b/packages/core/src/projects/appConfig.ts @@ -16,7 +16,6 @@ export const copyBuildsFolder = () => const destPath = path.join(getAppFolder()); const tsPathsConfig = getTimestampPathsConfig(); - generateConfigPropInjects(); const allInjects = [...c.configPropsInjects, ...c.systemPropsInjects, ...c.runtimePropsInjects]; diff --git a/packages/core/src/schema/platforms/fragments/ios.ts b/packages/core/src/schema/platforms/fragments/ios.ts index e2e57c473..e0d2327ca 100644 --- a/packages/core/src/schema/platforms/fragments/ios.ts +++ b/packages/core/src/schema/platforms/fragments/ios.ts @@ -47,6 +47,39 @@ export const zodPlatformiOSFragment = z sdk: z.string(), testFlightId: z.string(), firebaseId: z.string(), + privacyManifests: z.object({ + NSPrivacyAccessedAPITypes: z.array( + z.object({ + NSPrivacyAccessedAPIType: z.union([ + z.literal('NSPrivacyAccessedAPICategorySystemBootTime'), + z.literal('NSPrivacyAccessedAPICategoryDiskSpace'), + z.literal('NSPrivacyAccessedAPICategoryActiveKeyboards'), + z.literal('NSPrivacyAccessedAPICategoryUserDefaults'), + ]), + NSPrivacyAccessedAPITypeReasons: z.array( + z.union([ + z.literal('DDA9.1'), + z.literal('C617.1'), + z.literal('3B52.1'), + z.literal('0A2A.1'), + z.literal('35F9.1'), + z.literal('8FFB.1'), + z.literal('3D61.1'), + z.literal('85F4.1'), + z.literal('E174.1'), + z.literal('7D9E.1'), + z.literal('B728.1'), + z.literal('3EC4.1'), + z.literal('54BD.1'), + z.literal('CA92.1'), + z.literal('1C8F.1'), + z.literal('C56D.1'), + z.literal('AC6B.1'), + ]) + ), + }).describe("Official apple documentation https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api") + ), + }), exportOptions: z .object({ method: z.string(), diff --git a/packages/core/src/schema/platforms/fragments/templateAndroid.ts b/packages/core/src/schema/platforms/fragments/templateAndroid.ts index 04a4d752e..e97d2df8b 100644 --- a/packages/core/src/schema/platforms/fragments/templateAndroid.ts +++ b/packages/core/src/schema/platforms/fragments/templateAndroid.ts @@ -25,7 +25,8 @@ Injects / Overrides values in res/values files of generated android based projec > IMPORTANT: always ensure that your object contains \`tag\` and \`name\` to target correct tag to merge into `); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) -export type ConfigTemplateAndroidResources = z.infer; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConfigTemplateAndroidResources extends z.infer {} // AndroidManifest.xml // ============================================================== @@ -74,7 +75,8 @@ Injects / Overrides values in AndroidManifest.xml file of generated android base > IMPORTANT: always ensure that your object contains \`tag\` and \`android:name\` to target correct tag to merge into `); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) -export type ConfigTemplateAndroidAndroidManifest = z.infer; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConfigTemplateAndroidAndroidManifest extends z.infer {} // MainActivity.kt // ============================================================== @@ -92,7 +94,8 @@ const zodMainActivity_kt = z }) .partial(); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) -export type ConfigTemplateAndroidMainActivityKT = z.infer; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConfigTemplateAndroidMainActivityKT extends z.infer {} // MainApplication.kt // ============================================================== @@ -111,7 +114,8 @@ const zodMainApplication_kt = z .partial() .describe('Allows you to configure behaviour of MainActivity'); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) -export type ConfigTemplateAndroidMainApplicationKT = z.infer; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConfigTemplateAndroidMainApplicationKT extends z.infer {} // templateAndroid // ============================================================== @@ -122,17 +126,13 @@ const templateAndroid = z .describe('Overrides values in `gradle.properties` file of generated android based project'), build_gradle: z .object({ - allprojects: z.object({ - repositories: z - .record(z.string(), z.boolean()) - .describe('Customize repositories section of build.gradle'), - }), plugins: z.array(z.string()), buildscript: z.object({ - repositories: z.record(z.string(), z.boolean()), - dependencies: z.record(z.string(), z.boolean()), + repositories: z.array(z.string()), + dependencies: z.array(z.string()), + ext: z.array(z.string()), + custom: z.array(z.string()), }), - dexOptions: z.record(z.string(), z.boolean()), injectAfterAll: z.array(z.string()), }) .partial() @@ -168,7 +168,8 @@ const templateAndroid = z }) .partial(); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) -export type ConfigTemplateAndroidBase = z.infer; +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface ConfigTemplateAndroidBase extends z.infer {} export const zodTemplateAndroidFragment = z .object({ diff --git a/packages/core/src/schema/platforms/fragments/templateXcode.ts b/packages/core/src/schema/platforms/fragments/templateXcode.ts index f6d3becc2..b12b63d1d 100644 --- a/packages/core/src/schema/platforms/fragments/templateXcode.ts +++ b/packages/core/src/schema/platforms/fragments/templateXcode.ts @@ -14,6 +14,7 @@ const zodAppDelegateMethod = z.array( ]) ); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) +// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ConfigTemplateXcodeAppDelegateMethod extends z.infer {} const zodXcodeApplication_mm = z @@ -33,6 +34,7 @@ const zodXcodeApplication_mm = z }) .partial(); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) +// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ConfigTemplateXcodeApplication extends z.infer {} const project_pbxproj = z @@ -52,6 +54,7 @@ const project_pbxproj = z }) .partial(); // We using interfaces to reduce the size of d.ts files (zod + types in d.ts files are huge) +// eslint-disable-next-line @typescript-eslint/no-empty-interface export interface ConfigTemplateXcodeProjectPbxproj extends z.infer {} export const zodTemplateXcodeFragment = z @@ -85,6 +88,7 @@ export const zodTemplateXcodeFragment = z didReceiveNotificationResponse: zodAppDelegateMethod, }) .partial(), + custom: z.array(z.string()), }) .partial(), @@ -95,6 +99,7 @@ export const zodTemplateXcodeFragment = z .object({ appDelegateImports: z.array(z.string()), appDelegateExtensions: z.array(z.string()), + appDelegateMethods: z.array(z.string()), }) .partial(), Info_plist: z.record(z.string(), z.string()), diff --git a/packages/core/src/tasks/__tests__/taskHelpers.test.ts b/packages/core/src/tasks/__tests__/taskHelpers.test.ts new file mode 100644 index 000000000..aba36f1cc --- /dev/null +++ b/packages/core/src/tasks/__tests__/taskHelpers.test.ts @@ -0,0 +1,60 @@ +import * as taskHelpers from '../taskHelpers'; +import { getContext } from '../../context/provider'; +import { registerPlatformEngine } from '../../engines'; +import * as engines from '../../engines'; +import { inquirerPrompt } from '../../api'; +import { logInfo } from '../../logger'; +import { generateContextDefaults } from '../../context/defaults'; + +jest.mock('../../context/provider'); +jest.mock('../../api'); +jest.mock('../../logger', () => ({ + ...jest.requireActual('../../logger'), + logInfo: jest.fn(), + + chalk: () => ({ + white: { + bold: jest.fn().mockReturnValue('mocked value'), + }, + }), +})); + +describe('selectPlatformIfRequired', () => { + it('selects the platform and registers the engine', async () => { + // GIVEN + const knownTaskInstance = { + task: 'test-task', + platforms: ['ios'], + description: 'test-description', + key: 'key', + }; + + const context = generateContextDefaults(); + context.buildConfig.defaults = { supportedPlatforms: ['ios'] }; + context.runtime.availablePlatforms = []; + context.runtime.engine = undefined; + context.program.opts = () => ({ platform: undefined }); + + jest.mocked(getContext).mockReturnValue(context); + const getTaskNameFromCommandMock = jest.spyOn(taskHelpers, 'getTaskNameFromCommand'); + getTaskNameFromCommandMock.mockReturnValue('test-command'); + jest.mocked(inquirerPrompt).mockResolvedValue({ platform: 'ios' }); + const registerPlatformEngineMock = jest.spyOn(engines, 'registerPlatformEngine'); + registerPlatformEngineMock.mockResolvedValue(undefined); + const engineRunner = { runtimeExtraProps: { prop: 'value' } }; + const getEngineRunnerByPlatformMock = jest.spyOn(engines, 'getEngineRunnerByPlatform'); + getEngineRunnerByPlatformMock.mockReturnValue(engineRunner as any); + + // WHEN + await taskHelpers.selectPlatformIfRequired(knownTaskInstance, true); + + // THEN + expect(logInfo).toHaveBeenCalledWith( + 'Task "test-task" has only one supported platform: "ios". Automatically selecting it.' + ); + expect(context.platform).toBe('ios'); + expect(registerPlatformEngine).toHaveBeenCalledWith('ios'); + expect(context.runtime.engine).toBe(engineRunner); + expect(context.runtime.runtimeExtraProps).toEqual({ prop: 'value' }); + }); +}); diff --git a/packages/core/src/tasks/taskHelpers.ts b/packages/core/src/tasks/taskHelpers.ts index ac0e39e4e..c3cf7e64f 100644 --- a/packages/core/src/tasks/taskHelpers.ts +++ b/packages/core/src/tasks/taskHelpers.ts @@ -28,7 +28,8 @@ export const selectPlatformIfRequired = async ( `Task "${knownTaskInstance?.task}" has only one supported platform: "${platforms[0]}". Automatically selecting it.` ); c.platform = platforms[0]; - c.program.opts().platform = c.platform; + // c.program.opts().platform = c.platform; + // after making UTs, this doesn't work - no changes happen, so commenting it out } else { const { platform } = await inquirerPrompt({ type: 'list', diff --git a/packages/engine-core/src/tasks/bootstrap/__tests__/questionHelpers.test.ts b/packages/engine-core/src/tasks/bootstrap/__tests__/questionHelpers.test.ts new file mode 100644 index 000000000..883485252 --- /dev/null +++ b/packages/engine-core/src/tasks/bootstrap/__tests__/questionHelpers.test.ts @@ -0,0 +1,84 @@ +import { validateAndAssign, configureConfigOverrides } from '../questionHelpers'; +import { inquirerPrompt } from '@rnv/core'; + +jest.mock('@rnv/core', () => ({ + inquirerPrompt: jest.fn(), + isYarnInstalled: jest.fn(), + chalk: () => { + return { + bold: jest.fn(), + green: jest.fn(), + }; + }, +})); + +describe('validateAndAssign', () => { + it('returns the value if it is valid', async () => { + const validFn = jest.fn((): string | true => { + return true; + }); + const value = 'valid value'; + + const result = await validateAndAssign( + { value, validFn, name: 'test', defaultVal: 'default', message: 'message', warning: 'warning' }, + false + ); + + expect(result).toBe(value); + expect(validFn).toHaveBeenCalledWith(value); + }); + + it('prompts the user if the value is not valid', async () => { + const validFn = jest.fn(() => 'error message'); + const value = 'invalid value'; + const inquirerResponse = { test: 'user input' }; + (inquirerPrompt as jest.Mock).mockResolvedValue(inquirerResponse); + + const result = await validateAndAssign( + { value, validFn, name: 'test', defaultVal: 'default', message: 'message', warning: 'warning' }, + false + ); + + expect(result).toBe(inquirerResponse.test); + expect(validFn).toHaveBeenCalledWith(value); + expect(inquirerPrompt).toHaveBeenCalledWith({ + name: 'test', + type: 'input', + default: 'default', + validate: validFn, + message: 'message', + warningMessage: undefined, + }); + }); +}); + +describe('configureConfigOverrides', () => { + it('should modify renativeConfig based on supportedPlatforms', async () => { + const data = { + inputs: { + supportedPlatforms: ['platform1', 'platform2'], + }, + files: { + project: { + renativeConfig: { + platforms: { + platform1: {}, + platform2: {}, + platform3: {}, + }, + }, + }, + }, + }; + + await configureConfigOverrides(data as any); + + expect(data.files.project.renativeConfig.platforms).toEqual({ + platform1: {}, + platform2: {}, + }); + expect((data.files.project.renativeConfig as any).defaults).toEqual({ + supportedPlatforms: ['platform1', 'platform2'], + }); + }); +}); diff --git a/packages/engine-core/src/tasks/global/__tests__/takDoctor.test.ts b/packages/engine-core/src/tasks/global/__tests__/takDoctor.test.ts new file mode 100644 index 000000000..722ed873e --- /dev/null +++ b/packages/engine-core/src/tasks/global/__tests__/takDoctor.test.ts @@ -0,0 +1,68 @@ +jest.mock('@rnv/core'); + +import { logToSummary, readObjectSync, fsExistsSync, validateRenativeProjectSchema } from '@rnv/core'; + +beforeEach(() => { + jest.resetAllMocks(); +}); + +describe('createTask', () => { + it('checks validity and config health of the project', async () => { + // GIVEN + const task = require('../taskDoctor').default; + + const ctx = { + paths: { + 'workspace.config': '/path/to/workspace.config', + 'workspace.project.config': '/path/to/workspace.project.config', + 'workspace.appConfig.configs': [ + '/path/to/workspace.appConfig.config1', + '/path/to/workspace.appConfig.config2', + ], + 'project.config': '/path/to/project.config', + 'appConfig.configs': ['/path/to/appConfig.config1', '/path/to/appConfig.config2'], + }, + }; + + (fsExistsSync as jest.Mock).mockReturnValue(true); + (readObjectSync as jest.Mock).mockReturnValue({}); + (validateRenativeProjectSchema as jest.Mock).mockReturnValue({ success: true }); + + // WHEN + + await task.fn({ ctx }); + + // THEN + + expect(logToSummary).toHaveBeenCalledWith( + expect.stringMatching(/^RENATIVE JSON SCHEMA VALIDITY CHECK:\s*PASSED 7 files$/) + ); + }); + it('handles invalid schema', async () => { + // GIVEN + const task = require('../taskDoctor').default; + + const ctx = { + paths: { + 'workspace.config': '/path/to/workspace.config', + }, + }; + + (fsExistsSync as jest.Mock).mockReturnValue(true); + (readObjectSync as jest.Mock).mockReturnValue({}); + (validateRenativeProjectSchema as jest.Mock).mockReturnValue({ + success: false, + error: { errors: [{ path: 'path', message: 'message' }] }, + }); + + // WHEN + await task.fn({ ctx }); + + // THEN + expect(logToSummary).toHaveBeenCalledWith( + expect.stringMatching( + /^RENATIVE JSON SCHEMA VALIDITY CHECK:\s*\n\s*Invalid schema in \/path\/to\/workspace\.config\. ISSUES:\s*path: message$/ + ) + ); + }); +}); diff --git a/packages/engine-core/src/tasks/linking/__tests__/taskLink.test.ts b/packages/engine-core/src/tasks/linking/__tests__/taskLink.test.ts new file mode 100644 index 000000000..35e47306c --- /dev/null +++ b/packages/engine-core/src/tasks/linking/__tests__/taskLink.test.ts @@ -0,0 +1,47 @@ +import { logInfo, fsExistsSync, fsRenameSync, fsSymlinkSync, mkdirSync, inquirerPrompt } from '@rnv/core'; +import { traverseTargetProject, getSourceDir } from '../linker'; +import task from '../taskLink'; + +jest.mock('@rnv/core'); +jest.mock('../linker'); + +describe('taskLink', () => { + it('should link packages correctly', async () => { + // GIVEN + const mockPackage = { + name: 'test-package', + cacheDir: '/path/to/cache', + nmPath: '/path/to/nm', + isBrokenLink: false, + isLinked: false, + skipLinking: false, + nmPathExists: true, + unlinkedPathExists: false, + sourcePath: '/path/to/source', + unlinkedPath: '/path/to/unlinked', + }; + + (traverseTargetProject as jest.Mock).mockReturnValue([mockPackage]); + (getSourceDir as jest.Mock).mockReturnValue('/path/to/source'); + (inquirerPrompt as jest.Mock).mockResolvedValue({ selectedLinkableProjects: [mockPackage] }); + + // WHEN + await task.fn?.({} as any); + + // THEN + expect(traverseTargetProject).toHaveBeenCalledWith('/path/to/source'); + expect(inquirerPrompt).toHaveBeenCalledWith({ + name: 'selectedLinkableProjects', + type: 'checkbox', + message: `Found following packages to link?`, + default: [mockPackage], + loop: false, + choices: [{ name: 'test-package', value: mockPackage }], + }); + expect(fsExistsSync).toHaveBeenCalledWith(mockPackage.cacheDir); + expect(mkdirSync).toHaveBeenCalledWith(mockPackage.unlinkedPath); + expect(fsRenameSync).toHaveBeenCalledWith(mockPackage.nmPath, mockPackage.unlinkedPath); + expect(fsSymlinkSync).toHaveBeenCalledWith(mockPackage.sourcePath, mockPackage.nmPath); + expect(logInfo).toHaveBeenCalledWith('✔ test-package (/path/to/nm)'); + }); +}); diff --git a/packages/engine-core/src/tasks/platform/__tests__/taskPlatformConnect.test.ts b/packages/engine-core/src/tasks/platform/__tests__/taskPlatformConnect.test.ts new file mode 100644 index 000000000..9abb2248a --- /dev/null +++ b/packages/engine-core/src/tasks/platform/__tests__/taskPlatformConnect.test.ts @@ -0,0 +1,120 @@ +import task from '../taskPlatformConnect'; +import { writeFileSync, logSuccess, inquirerPrompt, generatePlatformChoices, removeDirs } from '@rnv/core'; +import path from 'path'; + +jest.mock('@rnv/core', () => ({ + writeFileSync: jest.fn(), + logSuccess: jest.fn(), + logToSummary: jest.fn(), + removeDirs: jest.fn(), + generatePlatformChoices: jest.fn(), + inquirerPrompt: jest.fn(), + RnvPlatformKey: { android: 'android', ios: 'ios' }, + createTask: jest.fn((args) => args), + RnvTaskName: { platformConnect: 'platformConnect' }, + chalk: () => { + return { + bold: jest.fn(), + }; + }, +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('taskPlatformEject', () => { + it('should handle ctx.platform being defined', async () => { + // GIVEN + const ctx = { + platform: 'android', + files: { + project: { + config_original: { + paths: { + platformTemplatesDirs: { + android: './path/to/android', + }, + }, + }, + config: { + paths: { + platformTemplatesDirs: { + android: './path/to/android', + }, + }, + }, + }, + }, + paths: { + project: { + config: './path/to/config', + }, + }, + }; + + (inquirerPrompt as jest.Mock).mockResolvedValueOnce({ connectedPlatforms: ['android', 'ios'] }); + + // WHEN + await task.fn?.({ ctx } as any); + + // THEN + expect(writeFileSync).toHaveBeenCalledWith(ctx.paths.project.config, ctx.files.project.config_original); + expect(logSuccess).toHaveBeenCalled(); + }); + + it('should handle ctx.platform being undefined', async () => { + // GIVEN + const ctx = { + files: { + project: { + config_original: { + paths: { + platformTemplatesDirs: { + android: './path/to/android', + ios: './path/to/ios', + }, + }, + }, + config: { + paths: { + platformTemplatesDirs: { + android: './path/to/android', + ios: './path/to/ios', + }, + }, + }, + }, + }, + paths: { + project: { + platformTemplatesDirs: { + android: './path/to/android', + ios: './path/to/ios', + }, + config: './path/to/config', + }, + }, + }; + + (generatePlatformChoices as jest.Mock).mockReturnValue([ + { name: 'android', isConnected: false }, + { name: 'ios', isConnected: false }, + ]); + + (inquirerPrompt as jest.Mock) + .mockResolvedValueOnce({ connectedPlatforms: ['android', 'ios'] }) + .mockResolvedValueOnce({ deletePlatformFolder: true }); + + // WHEN + await task.fn?.({ ctx } as any); + + // THEN + expect(writeFileSync).toHaveBeenCalledWith(ctx.paths.project.config, ctx.files.project.config_original); + expect(removeDirs).toHaveBeenCalledWith([ + path.join(ctx.paths.project.platformTemplatesDirs['android'], 'android'), + path.join(ctx.paths.project.platformTemplatesDirs['ios'], 'ios'), + ]); + expect(logSuccess).toHaveBeenCalled(); + }); +}); diff --git a/packages/engine-core/src/tasks/platform/__tests__/taskPlatformEject.test.ts b/packages/engine-core/src/tasks/platform/__tests__/taskPlatformEject.test.ts new file mode 100644 index 000000000..2cdd8e0fb --- /dev/null +++ b/packages/engine-core/src/tasks/platform/__tests__/taskPlatformEject.test.ts @@ -0,0 +1,114 @@ +import { logSuccess, writeFileSync, ejectPlatform, logError, inquirerPrompt } from '@rnv/core'; +import task from '../taskPlatformEject'; + +jest.mock('@rnv/core', () => ({ + chalk: jest.fn().mockReturnValue({ + bold: jest.fn((str) => str), + }), + logSuccess: jest.fn(), + logError: jest.fn(), + logInfo: jest.fn(), + writeFileSync: jest.fn(), + generatePlatformChoices: jest.fn().mockReturnValue([ + { name: 'android', isConnected: true }, + { name: 'ios', isConnected: true }, + ]), + ejectPlatform: jest.fn(), + inquirerPrompt: jest.fn().mockResolvedValue({ + ejectedPlatforms: ['android', 'ios'], + }), + RnvPlatformKey: { android: 'android', ios: 'ios' }, + createTask: jest.fn((args) => args), + RnvTaskName: { platformEject: 'platformEject' }, +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); + +describe('taskPlatformEject', () => { + it('ejects selected platform and updates project config', async () => { + // GIVEN + const ctx = { + platform: 'android', + files: { + project: { + config: { + paths: { + platformTemplatesDirs: { + android: './platformTemplates', + }, + }, + }, + config_original: { + paths: { + platformTemplatesDirs: { + android: './platformTemplates', + }, + }, + }, + }, + }, + paths: { + project: { + config: '/path/to/project/config', + }, + }, + }; + + // WHEN + await task.fn?.({ ctx } as any); + + // THEN + expect(ejectPlatform).toHaveBeenCalledWith('android'); + expect(writeFileSync).toHaveBeenCalledWith('/path/to/project/config', { + paths: { + platformTemplatesDirs: { + android: './platformTemplates', + }, + }, + }); + expect(logSuccess).toHaveBeenCalledWith( + 'android platform templates are located in ./platformTemplates now. You can edit them directly!' + ); + }); + + it('logs an error if no platforms are selected', async () => { + // GIVEN + const ctx = { + files: { + project: { + config: { + paths: { + platformTemplatesDirs: { + android: './platformTemplates', + }, + }, + }, + config_original: { + paths: { + platformTemplatesDirs: { + android: './platformTemplates', + }, + }, + }, + }, + }, + paths: { + project: { + config: '/path/to/project/config', + }, + }, + }; + + (inquirerPrompt as jest.Mock).mockResolvedValue({ ejectedPlatforms: [] }); + + // WHEN + + await task.fn?.({ ctx } as any); + // THEN + expect(logError).toHaveBeenCalledWith( + `You haven't selected any platform to eject.\nTIP: You can select options with SPACE key before pressing ENTER!` + ); + }); +}); diff --git a/packages/engine-core/src/tasks/project/__tests__/taskProjectConfigure.test.ts b/packages/engine-core/src/tasks/project/__tests__/taskProjectConfigure.test.ts new file mode 100644 index 000000000..9c94f89c7 --- /dev/null +++ b/packages/engine-core/src/tasks/project/__tests__/taskProjectConfigure.test.ts @@ -0,0 +1,152 @@ +// will get back to this test later - seems difficult to understand yet. + +import task from '../taskProjectConfigure'; +import { + getContext, + fsExistsSync, + fsMkdirSync, + updateRenativeConfigs, + executeTask, + configureRuntimeDefaults, + applyTemplate, + findSuitableTask, + initializeTask, + versionCheck, + configureEngines, + resolvePluginDependants, + configurePlugins, + cleanPlaformAssets, + copyRuntimeAssets, + generatePlatformAssetsRuntimeConfig, + overrideTemplatePlugins, + checkForPluginDependencies, +} from '@rnv/core'; +import { configureFonts } from '@rnv/sdk-utils'; +import { checkCrypto } from '../../crypto/common'; +import { checkAndInstallIfRequired } from '../../../taskHelpers'; + +jest.mock('../../crypto/common'); +jest.mock('../../../taskHelpers', () => ({ + checkAndInstallIfRequired: jest.fn().mockReturnValue(true), + installPackageDependenciesAndPlugins: jest.fn().mockReturnValue(true), +})); + +beforeEach(() => { + jest.resetAllMocks(); +}); + +jest.mock('@rnv/core', () => ({ + ...jest.requireActual('@rnv/core'), + chalk: () => { + return { + bold: jest.fn().mockReturnValue('bold'), + }; + }, + getContext: jest.fn(), + checkForPluginDependencies: jest.fn().mockResolvedValue(true), + configurePlugins: jest.fn().mockResolvedValue(true), + overrideTemplatePlugins: jest.fn().mockResolvedValue(true), + resolvePluginDependants: jest.fn().mockResolvedValue(true), + updateRenativeConfigs: jest.fn().mockResolvedValue(true), + configureRuntimeDefaults: jest.fn().mockResolvedValue(true), + applyTemplate: jest.fn().mockResolvedValue(true), + isTemplateInstalled: jest.fn().mockReturnValue(true), + fsExistsSync: jest.fn(), + fsMkdirSync: jest.fn(), + checkAndMigrateProject: jest.fn().mockResolvedValue(true), + copyRuntimeAssets: jest.fn().mockResolvedValue(true), + cleanPlaformAssets: jest.fn().mockResolvedValue(true), + versionCheck: jest.fn().mockResolvedValue(true), + configureEngines: jest.fn().mockResolvedValue(true), + executeTask: jest.fn().mockResolvedValue(true), + initializeTask: jest.fn().mockResolvedValue(true), + findSuitableTask: jest.fn(), + generatePlatformAssetsRuntimeConfig: jest.fn().mockResolvedValue(true), + generateLocalJsonSchemas: jest.fn().mockResolvedValue(true), + RnvTaskName: { + projectConfigure: 'project configure', + workspaceConfigure: 'workspace configure', + appConfigure: 'app configure', + cryptoDecrypt: 'crypto decrypt', + templateApply: 'template apply', + }, +})); +jest.mock('@rnv/sdk-utils'); + +describe('taskProjectConfigure', () => { + it('should configure the project correctly', async () => { + // GIVEN + const ctx = { + paths: { + project: { + builds: { + dir: '/path/to/builds', + }, + configExists: true, + config: '/path/to/config', + }, + workspace: { + dir: '/path/to/workspace', + }, + }, + files: { + project: { + package: { + name: 'test-package', + }, + }, + }, + runtime: { + requiresBootstrap: true, + disableReset: false, + }, + buildConfig: { + isTemplate: false, + platforms: {}, + }, + program: { + opts: () => ({ + only: false, + resetHard: false, + resetAssets: false, + }), + }, + }; + (getContext as jest.Mock).mockReturnValue(ctx); + const taskName = 'taskName'; + const originTaskName = 'originTaskName'; + const parentTaskName = 'parentTaskName'; + // WHEN + await task.fn?.({ ctx, taskName, originTaskName, parentTaskName } as any); + // THEN + expect(fsExistsSync).toHaveBeenCalledWith(ctx.paths.project.builds.dir); + expect(fsMkdirSync).toHaveBeenCalledWith(ctx.paths.project.builds.dir); + expect(updateRenativeConfigs).toHaveBeenCalled(); + expect(executeTask).toHaveBeenCalledWith( + expect.objectContaining({ + taskName: 'workspace configure', + }) + ); + expect(executeTask).toHaveBeenCalledWith( + expect.objectContaining({ + taskName: 'app configure', + }) + ); + expect(checkAndInstallIfRequired).toHaveBeenCalled(); + expect(checkCrypto).toHaveBeenCalled(); + expect(configureRuntimeDefaults).toHaveBeenCalled(); + expect(applyTemplate).toHaveBeenCalled(); + expect(findSuitableTask).toHaveBeenCalled(); + expect(initializeTask).not.toHaveBeenCalled(); + expect(versionCheck).toHaveBeenCalledWith(ctx); + expect(configureEngines).toHaveBeenCalledWith(ctx); + expect(resolvePluginDependants).toHaveBeenCalled(); + expect(configurePlugins).toHaveBeenCalled(); + expect(cleanPlaformAssets).not.toHaveBeenCalled(); + expect(copyRuntimeAssets).toHaveBeenCalled(); + expect(generatePlatformAssetsRuntimeConfig).toHaveBeenCalled(); + expect(overrideTemplatePlugins).toHaveBeenCalled(); + expect(checkForPluginDependencies).toHaveBeenCalled(); + expect(configureFonts).toHaveBeenCalled(); + }); +}); diff --git a/packages/engine-rn-tvos/templates/platforms/androidtv/build.gradle b/packages/engine-rn-tvos/templates/platforms/androidtv/build.gradle index 34d32ff15..b900c6a3a 100644 --- a/packages/engine-rn-tvos/templates/platforms/androidtv/build.gradle +++ b/packages/engine-rn-tvos/templates/platforms/androidtv/build.gradle @@ -10,27 +10,40 @@ buildscript { // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. ndkVersion = '{{NDK_VERSION}}' kotlinVersion = '{{INJECT_KOTLIN_VERSION}}' + + {{INJECT_BUILDSCRIPT_EXT}} } + subprojects { subproject -> - afterEvaluate{ - if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { - android { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion + afterEvaluate { + if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { + android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + } } } } -} repositories { google() mavenCentral() + maven { url = uri("https://www.jitpack.io" ) } + + {{INJECT_BUILDSCRIPT_REPOSITORIES}} } + + {{INJECT_BUILDSCRIPT_CUSTOM}} + dependencies { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + + {{INJECT_BUILDSCRIPT_DEPENDENCIES}} } } -apply plugin: "com.facebook.react.rootproject" +{{INJECT_GRADLE_AFTER_ALL}} + +apply plugin: "com.facebook.react.rootproject" \ No newline at end of file diff --git a/packages/engine-rn-tvos/templates/platforms/firetv/build.gradle b/packages/engine-rn-tvos/templates/platforms/firetv/build.gradle index 8af47d3fd..b900c6a3a 100644 --- a/packages/engine-rn-tvos/templates/platforms/firetv/build.gradle +++ b/packages/engine-rn-tvos/templates/platforms/firetv/build.gradle @@ -10,24 +10,40 @@ buildscript { // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. ndkVersion = '{{NDK_VERSION}}' kotlinVersion = '{{INJECT_KOTLIN_VERSION}}' + + {{INJECT_BUILDSCRIPT_EXT}} } + subprojects { subproject -> - afterEvaluate{ - if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { - android { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion + afterEvaluate { + if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { + android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + } } } } -} repositories { google() mavenCentral() + maven { url = uri("https://www.jitpack.io" ) } + + {{INJECT_BUILDSCRIPT_REPOSITORIES}} } + + {{INJECT_BUILDSCRIPT_CUSTOM}} + dependencies { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + + {{INJECT_BUILDSCRIPT_DEPENDENCIES}} } } + +{{INJECT_GRADLE_AFTER_ALL}} + +apply plugin: "com.facebook.react.rootproject" \ No newline at end of file diff --git a/packages/engine-rn-tvos/templates/platforms/tvos/RNVApp/AppDelegate.h b/packages/engine-rn-tvos/templates/platforms/tvos/RNVApp/AppDelegate.h index 17e115ed4..ed020741e 100644 --- a/packages/engine-rn-tvos/templates/platforms/tvos/RNVApp/AppDelegate.h +++ b/packages/engine-rn-tvos/templates/platforms/tvos/RNVApp/AppDelegate.h @@ -5,4 +5,6 @@ @interface AppDelegate : RCTAppDelegate {{APPDELEGATE_H_EXTENSIONS}} +{{APPDELEGATE_H_METHODS}} + @end diff --git a/packages/engine-rn/templates/platforms/android/build.gradle b/packages/engine-rn/templates/platforms/android/build.gradle index 9ebe9747f..b900c6a3a 100644 --- a/packages/engine-rn/templates/platforms/android/build.gradle +++ b/packages/engine-rn/templates/platforms/android/build.gradle @@ -10,27 +10,40 @@ buildscript { // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. ndkVersion = '{{NDK_VERSION}}' kotlinVersion = '{{INJECT_KOTLIN_VERSION}}' + + {{INJECT_BUILDSCRIPT_EXT}} } + subprojects { subproject -> - afterEvaluate{ - if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { - android { - compileSdkVersion rootProject.ext.compileSdkVersion - buildToolsVersion rootProject.ext.buildToolsVersion + afterEvaluate { + if ((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) { + android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + } } } } -} + repositories { google() mavenCentral() maven { url = uri("https://www.jitpack.io" ) } + + {{INJECT_BUILDSCRIPT_REPOSITORIES}} } + + {{INJECT_BUILDSCRIPT_CUSTOM}} + dependencies { classpath("com.android.tools.build:gradle") classpath("com.facebook.react:react-native-gradle-plugin") classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + + {{INJECT_BUILDSCRIPT_DEPENDENCIES}} } } +{{INJECT_GRADLE_AFTER_ALL}} + apply plugin: "com.facebook.react.rootproject" \ No newline at end of file diff --git a/packages/engine-rn/templates/platforms/ios/RNVApp/AppDelegate.h b/packages/engine-rn/templates/platforms/ios/RNVApp/AppDelegate.h index 17e115ed4..ed020741e 100644 --- a/packages/engine-rn/templates/platforms/ios/RNVApp/AppDelegate.h +++ b/packages/engine-rn/templates/platforms/ios/RNVApp/AppDelegate.h @@ -5,4 +5,6 @@ @interface AppDelegate : RCTAppDelegate {{APPDELEGATE_H_EXTENSIONS}} +{{APPDELEGATE_H_METHODS}} + @end diff --git a/packages/sdk-android/src/gradleParser.ts b/packages/sdk-android/src/gradleParser.ts index b538b4cc6..a64da3883 100644 --- a/packages/sdk-android/src/gradleParser.ts +++ b/packages/sdk-android/src/gradleParser.ts @@ -28,13 +28,9 @@ export const parseBuildGradleSync = () => { const c = getContext(); const appFolder = getAppFolder(); - let dexOptions = ''; - if (c.payload.pluginConfigAndroid.buildGradleBuildScriptDexOptions) { - dexOptions = `dexOptions() { - ${c.payload.pluginConfigAndroid.buildGradleBuildScriptDexOptions} - }`; - } + const templateAndroid = getConfigProp('templateAndroid'); + const buildscript = templateAndroid?.build_gradle?.buildscript; const injects: OverridesOptions = [ { @@ -57,14 +53,6 @@ export const parseBuildGradleSync = () => { pattern: '{{BUILD_TOOLS_VERSION}}', override: c.payload.pluginConfigAndroid.buildToolsVersion, }, - { - pattern: '{{PLUGIN_INJECT_ALLPROJECTS_REPOSITORIES}}', - override: c.payload.pluginConfigAndroid.buildGradleAllProjectsRepositories, - }, - { - pattern: '{{PLUGIN_INJECT_BUILDSCRIPT_REPOSITORIES}}', - override: c.payload.pluginConfigAndroid.buildGradleBuildScriptRepositories, - }, { pattern: '{{INJECT_KOTLIN_VERSION}}', override: c.payload.pluginConfigAndroid.kotlinVersion, @@ -89,14 +77,6 @@ export const parseBuildGradleSync = () => { pattern: '{{INJECT_AFTER_ALL}}', override: c.payload.pluginConfigAndroid.buildGradleAfterAll, }, - { - pattern: '{{PLUGIN_INJECT_BUILDSCRIPT_DEPENDENCIES}}', - override: c.payload.pluginConfigAndroid.buildGradleBuildScriptDependencies, - }, - { - pattern: '{{PLUGIN_INJECT_DEXOPTIONS}}', - override: dexOptions, - }, { pattern: '{{INJECT_REACT_NATIVE_ENGINE}}', override: c.payload.pluginConfigAndroid.injectReactNativeEngine, @@ -125,7 +105,28 @@ export const parseBuildGradleSync = () => { doResolve('react-native', true, { forceForwardPaths: true }) || 'react-native' }/sdks/hermesc/${currentOs}-bin/hermesc`, }, + { + pattern: '{{INJECT_BUILDSCRIPT_EXT}}', + override: buildscript?.ext?.join('\n') ?? '', + }, + { + pattern: '{{INJECT_BUILDSCRIPT_REPOSITORIES}}', + override: buildscript?.repositories?.join('\n') ?? '', + }, + { + pattern: '{{INJECT_BUILDSCRIPT_CUSTOM}}', + override: buildscript?.custom?.join('\n') ?? '', + }, + { + pattern: '{{INJECT_BUILDSCRIPT_DEPENDENCIES}}', + override: buildscript?.dependencies?.join('\n') ?? '', + }, + { + pattern: '{{INJECT_GRADLE_AFTER_ALL}}', + override: templateAndroid?.build_gradle?.injectAfterAll?.join('\n') ?? '', + }, ]; + addSystemInjects(injects); writeCleanFile(getBuildFilePath('build.gradle'), path.join(appFolder, 'build.gradle'), injects, undefined, c); @@ -699,47 +700,12 @@ export const parseAndroidConfigObject = (plugin?: ConfigPluginPlatformSchema, ke // BUILD.GRADLE const buildGradle = templateAndroid?.build_gradle; - const allProjRepos = buildGradle?.allprojects?.repositories; - if (allProjRepos) { - Object.keys(allProjRepos).forEach((k) => { - if (allProjRepos[k] === true) { - c.payload.pluginConfigAndroid.buildGradleAllProjectsRepositories += `${sanitizePluginPath(k, key)}\n`; - } - }); - } - const plugins = buildGradle?.plugins; if (plugins?.forEach) { plugins.forEach((k) => { c.payload.pluginConfigAndroid.buildGradlePlugins += `${k}\n`; }); } - const buildscriptRepos = buildGradle?.buildscript?.repositories; - if (buildscriptRepos) { - Object.keys(buildscriptRepos).forEach((k) => { - if (buildscriptRepos[k] === true) { - c.payload.pluginConfigAndroid.buildGradleBuildScriptRepositories += `${k}\n`; - } - }); - } - - const buildscriptDeps = buildGradle?.buildscript?.dependencies; - if (buildscriptDeps) { - Object.keys(buildscriptDeps).forEach((k) => { - if (buildscriptDeps[k] === true) { - c.payload.pluginConfigAndroid.buildGradleBuildScriptDependencies += `${k}\n`; - } - }); - } - - const buildscriptDexOptions = buildGradle?.dexOptions; - if (buildscriptDexOptions) { - Object.keys(buildscriptDexOptions).forEach((k) => { - if (buildscriptDexOptions[k] === true) { - c.payload.pluginConfigAndroid.buildGradleBuildScriptDexOptions += `${k}\n`; - } - }); - } const injectAfterAll = buildGradle?.injectAfterAll; if (injectAfterAll?.forEach) { diff --git a/packages/sdk-android/src/index.ts b/packages/sdk-android/src/index.ts index 3c085a7df..d4ccad3ca 100644 --- a/packages/sdk-android/src/index.ts +++ b/packages/sdk-android/src/index.ts @@ -71,6 +71,8 @@ const RnvModule = createRnvModule({ splits: '', supportLibVersion: '', targetSdkVersion: DEFAULTS.targetSdkVersion, + settingsGradleInclude: '', + settingsGradleProject: '', }, } as Payload, }); diff --git a/packages/sdk-android/src/runner.ts b/packages/sdk-android/src/runner.ts index cc521eea4..06b3dc9ab 100644 --- a/packages/sdk-android/src/runner.ts +++ b/packages/sdk-android/src/runner.ts @@ -374,6 +374,9 @@ export const configureProject = async () => { mkdirSync(path.join(appFolder, 'app/src/main/assets')); fsWriteFileSync(path.join(appFolder, `app/src/main/assets/${outputFile}.bundle`), '{}'); + // console.log({ templateAndroid: c }); + + // PLUGINS parsePlugins((plugin, pluginPlat, key) => { injectPluginGradleSync(plugin, pluginPlat, key); diff --git a/packages/sdk-android/src/types.ts b/packages/sdk-android/src/types.ts index d2001d1c1..3d962cd40 100644 --- a/packages/sdk-android/src/types.ts +++ b/packages/sdk-android/src/types.ts @@ -25,12 +25,9 @@ export type Payload = { kotlinVersion: string; pluginIncludes: string; pluginPaths: string; - buildGradleBuildScriptDexOptions: string; gradleBuildToolsVersion: string; supportLibVersion: string; buildToolsVersion: string; - buildGradleAllProjectsRepositories: string; - buildGradleBuildScriptRepositories: string; googleServicesVersion: string; pluginActivityImports: string; buildGradlePlugins: string; diff --git a/packages/sdk-apple/src/index.ts b/packages/sdk-apple/src/index.ts index 27775f64b..dedd6a11e 100644 --- a/packages/sdk-apple/src/index.ts +++ b/packages/sdk-apple/src/index.ts @@ -46,6 +46,7 @@ const RnvModule = createRnvModule({ embeddedFontSources: [], ignoreProjectFonts: [], pluginAppDelegateHImports: '', + pluginAppDelegateHMethods: '', pluginAppDelegateHExtensions: '', pluginAppDelegateMmImports: '', pluginAppDelegateMmMethods: '', @@ -68,6 +69,7 @@ const RnvModule = createRnvModule({ willPresent: [], didReceiveNotificationResponse: [], }, + custom: [], }, podfileSources: '', deploymentTarget: '', diff --git a/packages/sdk-apple/src/objectiveCParser.ts b/packages/sdk-apple/src/objectiveCParser.ts index b30a62945..2fb7afedb 100644 --- a/packages/sdk-apple/src/objectiveCParser.ts +++ b/packages/sdk-apple/src/objectiveCParser.ts @@ -33,6 +33,8 @@ export const parseAppDelegate = ( logDefault('parseAppDelegateSync'); const appDelegateMm = 'AppDelegate.mm'; const appDelegateH = 'AppDelegate.h'; + const templateXcode = getConfigProp('templateXcode'); + // const entryFile = getEntryFile(c, platform); // const forceBundle = getGetJsBundleFile(c, platform); @@ -177,6 +179,7 @@ export const parseAppDelegate = ( end: null, }, }, + custom: [] }; const constructMethod = (lines: Array, method: ObjectiveCMethod) => { @@ -203,10 +206,10 @@ export const parseAppDelegate = ( mk.forEach((key) => { const method = methods[key]; const mk2 = Object.keys(method) as Array; + mk2.forEach((key2) => { const f = method[key2]; - const lines: Array = - c.payload.pluginConfigiOS.appDelegateMmMethods[key][key2] || []; + const lines: Array = c.payload.pluginConfigiOS.appDelegateMmMethods[key][key2] || []; const cleanedLines: Record = {}; @@ -233,6 +236,18 @@ export const parseAppDelegate = ( c.payload.pluginConfigiOS.pluginAppDelegateMmMethods += constructMethod(v.lines, v.f); }); + if (c.payload.pluginConfigiOS.appDelegateMmMethods.custom) { + c.payload.pluginConfigiOS.pluginAppDelegateMmMethods += c.payload.pluginConfigiOS.appDelegateMmMethods.custom.join('\n '); + } + + // Root renative.json injections + injectPluginObjectiveCSync(c, null, '', true); + + if (templateXcode?.AppDelegate_mm?.appDelegateMethods?.custom) { + c.payload.pluginConfigiOS.pluginAppDelegateMmMethods += templateXcode.AppDelegate_mm.appDelegateMethods.custom.join('\n '); + } + // end + const injectsMm = [ // { pattern: '{{BUNDLE}}', override: bundle }, // { pattern: '{{ENTRY_FILE}}', override: entryFile }, @@ -253,6 +268,10 @@ export const parseAppDelegate = ( pattern: '{{APPDELEGATE_H_IMPORTS}}', override: c.payload.pluginConfigiOS.pluginAppDelegateHImports, }, + { + pattern: '{{APPDELEGATE_H_METHODS}}', + override: c.payload.pluginConfigiOS.pluginAppDelegateHMethods ?? '', + }, { pattern: '{{APPDELEGATE_H_EXTENSIONS}}', override: c.payload.pluginConfigiOS.pluginAppDelegateHExtensions @@ -281,9 +300,9 @@ export const parseAppDelegate = ( resolve(); }); -export const injectPluginObjectiveCSync = (c: Context, plugin: ConfigPluginPlatformSchema, key: string) => { +export const injectPluginObjectiveCSync = (c: Context, plugin: ConfigPluginPlatformSchema | null, key: string, configProp = false) => { logDebug(`injectPluginObjectiveCSync:${c.platform}:${key}`); - const templateXcode = getFlavouredProp(plugin, 'templateXcode'); + const templateXcode = configProp ? getConfigProp('templateXcode') : getFlavouredProp(plugin!, 'templateXcode'); const appDelegateMmImports = templateXcode?.AppDelegate_mm?.appDelegateImports; if (appDelegateMmImports) { @@ -293,9 +312,14 @@ export const injectPluginObjectiveCSync = (c: Context, plugin: ConfigPluginPlatf // c.payload.pluginConfigiOS.pluginAppDelegateMethods += `${plugin.appDelegateMethods.join('\n ')}`; // } const appDelegateHhImports = templateXcode?.AppDelegate_h?.appDelegateImports; + if (appDelegateHhImports) { addAppDelegateImports(c, appDelegateHhImports, 'pluginAppDelegateHImports'); } + const appDelegateHMethods = templateXcode?.AppDelegate_h?.appDelegateMethods; + if (appDelegateHMethods) { + c.payload.pluginConfigiOS['pluginAppDelegateHMethods'] += appDelegateHMethods.join('\n '); + } const appDelegateExtensions = templateXcode?.AppDelegate_h?.appDelegateExtensions; if (appDelegateExtensions instanceof Array) { appDelegateExtensions.forEach((appDelegateExtension) => { @@ -316,26 +340,30 @@ export const injectPluginObjectiveCSync = (c: Context, plugin: ConfigPluginPlatf admk.forEach((delKey) => { const apDelMet = appDelegateMethods[delKey]; if (apDelMet) { - const amdk2 = Object.keys(apDelMet) as Array; - amdk2.forEach((key2) => { - const plugArr: Array = - c.payload.pluginConfigiOS.appDelegateMmMethods[delKey][key2]; - if (!plugArr) { - logWarning(`appDelegateMethods.${delKey}.${chalk().red(key2)} not supported. SKIPPING.`); - } else { - const plugVal: Array = apDelMet[key2]; - if (plugVal) { - plugVal.forEach((v) => { - const isString = typeof v === 'string'; - plugArr.push({ - order: isString ? 0 : v?.order || 0, - value: isString ? v : v?.value, - weight: isString ? 0 : v?.weight || 0, + if (delKey === 'custom' && Array.isArray(apDelMet)) { + c.payload.pluginConfigiOS.appDelegateMmMethods[delKey] = apDelMet; + } else { + const amdk2 = Object.keys(apDelMet) as Array; + amdk2.forEach((key2) => { + const plugArr: Array = + c.payload.pluginConfigiOS.appDelegateMmMethods[delKey][key2]; + if (!plugArr) { + logWarning(`appDelegateMethods.${delKey}.${chalk().red(key2)} not supported. SKIPPING.`); + } else { + const plugVal: Array = apDelMet[key2]; + if (plugVal) { + plugVal.forEach((v) => { + const isString = typeof v === 'string'; + plugArr.push({ + order: isString ? 0 : v?.order || 0, + value: isString ? v : v?.value, + weight: isString ? 0 : v?.weight || 0, + }); }); - }); + } } - } - }); + }); + } } }); } @@ -356,4 +384,4 @@ export const addAppDelegateImports = ( : `#import "${appDelegateImport}"\n`; } }); -}; +}; \ No newline at end of file diff --git a/packages/sdk-apple/src/privacyManifestParser.ts b/packages/sdk-apple/src/privacyManifestParser.ts new file mode 100644 index 000000000..69bb856f9 --- /dev/null +++ b/packages/sdk-apple/src/privacyManifestParser.ts @@ -0,0 +1,59 @@ +import path from 'path'; +import { getConfigProp, fsWriteFileSync, getAppFolder, doResolve, logError } from '@rnv/core'; +import { getAppFolderName } from './common'; + +export const parsePrivacyManifest = async () => { + const privacyManifest = getConfigProp('privacyManifests'); + + if (privacyManifest) { + const apiTypes = privacyManifest.NSPrivacyAccessedAPITypes; + + const output = ` + + + + + NSPrivacyAccessedAPITypes + + ${apiTypes + .map((api) => { + return ` + + NSPrivacyAccessedAPIType + ${api.NSPrivacyAccessedAPIType} + + ${api.NSPrivacyAccessedAPITypeReasons?.map((reason) => { + return `${reason}`; + }).join('\n')} + + + `; + }) + .join('')} + + + + `; + + const appFolder = getAppFolder(); + const appFolderName = getAppFolderName(); + + const filePath = path.join(appFolder, `${appFolderName}/PrivacyInfo.xcprivacy`); + + fsWriteFileSync(filePath, output); + + const xcodePath = doResolve('xcode'); + if (!xcodePath) { + logError(`Cannot resolve xcode path`); + return; + } + const xcode = require(xcodePath); + const projectPath = path.join(appFolder, `${appFolderName}.xcodeproj/project.pbxproj`); + const xcodeProj = xcode.project(projectPath); + + xcodeProj.parse(() => { + xcodeProj.addResourceFile(filePath); + fsWriteFileSync(projectPath, xcodeProj.writeSync()); + }); + } +}; diff --git a/packages/sdk-apple/src/runner.ts b/packages/sdk-apple/src/runner.ts index 0ce238cf8..a549bf512 100644 --- a/packages/sdk-apple/src/runner.ts +++ b/packages/sdk-apple/src/runner.ts @@ -36,6 +36,7 @@ import { ObjectEncodingOptions } from 'fs'; import { packageReactNativeIOS, runCocoaPods, runReactNativeIOS, EnvVars } from '@rnv/sdk-react-native'; import { registerDevice } from './fastlane'; import { Context, getContext } from './getContext'; +import { parsePrivacyManifest } from './privacyManifestParser'; export const packageBundleForXcode = () => { return packageReactNativeIOS(); @@ -797,6 +798,7 @@ export const configureXcodeProject = async () => { await copyBuildsFolder(); await runCocoaPods(c.program.opts().updatePods); await parseXcodeProject(); + await parsePrivacyManifest(); return true; }; diff --git a/packages/sdk-apple/src/types.ts b/packages/sdk-apple/src/types.ts index 82df3dd18..cf65de11c 100644 --- a/packages/sdk-apple/src/types.ts +++ b/packages/sdk-apple/src/types.ts @@ -37,6 +37,7 @@ export type Payload = { pluginAppDelegateMmImports: string; pluginAppDelegateMmMethods: string; pluginAppDelegateHExtensions: string; + pluginAppDelegateHMethods: string; pluginAppDelegateHImports: string; appDelegateMmMethods: { application: { @@ -57,6 +58,7 @@ export type Payload = { willPresent: Array; didReceiveNotificationResponse: Array; }; + custom: Array; }; podfileSources: string; deploymentTarget: string; @@ -146,6 +148,7 @@ export type ObjectiveCAppDelegate = { willPresent: ObjectiveCMethod; didReceiveNotificationResponse: ObjectiveCMethod; }; + custom: Array; }; export type ObjectiveCAppDelegateSubKey = keyof ObjectiveCAppDelegate['application'] &