diff --git a/android/app/build.gradle b/android/app/build.gradle index c272e406f..62c9bac8e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -90,8 +90,8 @@ android { applicationId project.ext.react.applicationId minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion - versionCode reactTestApp.versionCode - versionName reactTestApp.versionName + versionCode getVersionCode() + versionName getVersionName() buildConfigField "boolean", "ReactTestApp_useFabric", project.ext.react.enableFabric.toString() diff --git a/android/app/src/main/java/com/microsoft/reacttestapp/manifest/Manifest.kt b/android/app/src/main/java/com/microsoft/reacttestapp/manifest/Manifest.kt index 5abbcde46..ace17ed2c 100644 --- a/android/app/src/main/java/com/microsoft/reacttestapp/manifest/Manifest.kt +++ b/android/app/src/main/java/com/microsoft/reacttestapp/manifest/Manifest.kt @@ -21,6 +21,7 @@ data class Component( data class Manifest( val name: String, val displayName: String, + val version: String?, val bundleRoot: String?, val singleApp: String?, val components: List?, diff --git a/android/dependencies.gradle b/android/dependencies.gradle index 12e206426..d09a5ce04 100644 --- a/android/dependencies.gradle +++ b/android/dependencies.gradle @@ -3,11 +3,6 @@ static String versionOf(String spec) { } ext { - reactTestApp = [ - versionCode: 1, - versionName: "1.0" - ] - compileSdkVersion = 31 minSdkVersion = 23 targetSdkVersion = 29 diff --git a/android/support/build.gradle b/android/support/build.gradle index dee3ba4e8..266fb7d38 100644 --- a/android/support/build.gradle +++ b/android/support/build.gradle @@ -9,15 +9,16 @@ repositories { android { def androidDir = "${buildscript.sourceFile.getParent()}/../" - apply(from: "$androidDir/dependencies.gradle") + apply(from: "${androidDir}/dependencies.gradle") + apply(from: "${androidDir}/test-app-util.gradle") compileSdkVersion project.ext.compileSdkVersion defaultConfig { minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.targetSdkVersion - versionCode reactTestApp.versionCode - versionName reactTestApp.versionName + versionCode getVersionCode() + versionName getVersionName() } compileOptions { diff --git a/android/test-app-util.gradle b/android/test-app-util.gradle index 9c2133036..9fa5ffb5e 100644 --- a/android/test-app-util.gradle +++ b/android/test-app-util.gradle @@ -191,6 +191,30 @@ ext.getSingleAppMode = { return false } +ext.getVersionCode = { + def manifest = getManifest() + if (manifest != null) { + def config = manifest["android"] + if (config instanceof Object && config.containsKey("versionCode")) { + return config["versionCode"] + } + } + + return 1 +} + +ext.getVersionName = { + def manifest = getManifest() + if (manifest != null) { + def version = manifest["version"] + if (version instanceof String) { + return version + } + } + + return "1.0" +} + ext.isFabricEnabled = { baseDir -> return project.hasProperty("USE_FABRIC") && project.getProperty("USE_FABRIC") == "1" && diff --git a/example/ios/ExampleTests/ManifestTests.swift b/example/ios/ExampleTests/ManifestTests.swift index 8d988b8a9..3c20e83a7 100644 --- a/example/ios/ExampleTests/ManifestTests.swift +++ b/example/ios/ExampleTests/ManifestTests.swift @@ -55,6 +55,7 @@ class ManifestTests: XCTestCase { let expected = Manifest( name: "Name", displayName: "Display Name", + version: "1.0", bundleRoot: nil, singleApp: nil, components: expectedComponents diff --git a/ios/ReactTestApp/Info.plist b/ios/ReactTestApp/Info.plist index a616e8db8..1058305f9 100644 --- a/ios/ReactTestApp/Info.plist +++ b/ios/ReactTestApp/Info.plist @@ -17,7 +17,7 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(PRODUCT_VERSION) CFBundleURLTypes @@ -29,7 +29,7 @@ CFBundleVersion - 1 + $(PRODUCT_BUILD_NUMBER) LSApplicationQueriesSchemes linkedin diff --git a/ios/ReactTestApp/Manifest.swift b/ios/ReactTestApp/Manifest.swift index 08f1ffba4..e568da548 100644 --- a/ios/ReactTestApp/Manifest.swift +++ b/ios/ReactTestApp/Manifest.swift @@ -12,6 +12,7 @@ struct Component: Decodable { struct Manifest: Decodable { let name: String let displayName: String + let version: String? let bundleRoot: String? let singleApp: String? let components: [Component]? diff --git a/ios/test_app.rb b/ios/test_app.rb index b86b35e8c..fbaf65cf4 100644 --- a/ios/test_app.rb +++ b/ios/test_app.rb @@ -22,7 +22,7 @@ def app_config(project_root) manifest = app_manifest(project_root) return [nil, nil, nil] if manifest.nil? - [manifest['name'], manifest['displayName'], manifest['singleApp']] + [manifest['name'], manifest['displayName'], manifest['version'], manifest['singleApp']] end def autolink_script_path @@ -163,7 +163,7 @@ def make_project!(xcodeproj, project_root, target_platform, options) # Copy Xcode project files FileUtils.mkdir_p(destination) FileUtils.cp_r(xcodeproj_src, destination) - name, display_name, single_app = app_config(project_root) + name, display_name, version, single_app = app_config(project_root) unless name.nil? xcschemes_path = File.join(xcodeproj_dst, 'xcshareddata', 'xcschemes') FileUtils.cp(File.join(xcschemes_path, 'ReactTestApp.xcscheme'), @@ -209,9 +209,9 @@ def make_project!(xcodeproj, project_root, target_platform, options) end react_native = react_native_path(project_root, target_platform) - version = package_version(react_native.to_s).segments - version = (version[0] * 10_000) + (version[1] * 100) + version[2] - version_macro = "REACT_NATIVE_VERSION=#{version}" + rn_version = package_version(react_native.to_s).segments + rn_version = (rn_version[0] * 10_000) + (rn_version[1] * 100) + rn_version[2] + version_macro = "REACT_NATIVE_VERSION=#{rn_version}" build_settings = {} tests_build_settings = {} @@ -242,9 +242,13 @@ def make_project!(xcodeproj, project_root, target_platform, options) end build_settings['PRODUCT_DISPLAY_NAME'] = display_name + build_settings['PRODUCT_VERSION'] = version || '1.0' + + build_number = platform_config('buildNumber', project_root, target_platform) + build_settings['PRODUCT_BUILD_NUMBER'] = build_number || '1' supports_flipper = target_platform == :ios && flipper_enabled? - use_fabric = options[:fabric_enabled] && version >= 6800 + use_fabric = options[:fabric_enabled] && rn_version >= 6800 app_project = Xcodeproj::Project.open(xcodeproj_dst) app_project.native_targets.each do |target| diff --git a/macos/ReactTestApp/Info.plist b/macos/ReactTestApp/Info.plist index 17d8733b0..14025d2e0 100644 --- a/macos/ReactTestApp/Info.plist +++ b/macos/ReactTestApp/Info.plist @@ -19,9 +19,9 @@ CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString - 1.0 + $(PRODUCT_VERSION) CFBundleVersion - 1 + $(PRODUCT_BUILD_NUMBER) LSApplicationCategoryType public.app-category.developer-tools LSMinimumSystemVersion diff --git a/schema.json b/schema.json index 5412a7fa9..8ea3813de 100644 --- a/schema.json +++ b/schema.json @@ -41,19 +41,24 @@ "displayName": { "type": "string" }, + "version": { + "type": "string", + "description": "The app version shown to users. The required format is three period-separated\nintegers, such as 1.3.11.", + "markdownDescription": "The app version shown to users. The required format is three period-separated\nintegers, such as 1.3.11.\n\n- **Android**: Equivalent to setting\n [`versionName`](https://developer.android.com/studio/publish/versioning#appversioning).\n- **iOS**: This is the same as setting\n [`CFBundleShortVersionString`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring)\n in `Info.plist`.\n- **macOS**: This is the same as setting\n [`CFBundleShortVersionString`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring)\n in `Info.plist`.\n- **Windows**: Please see [`windows.appxmanifest`](#windows.appxmanifest) for\n how to use a custom\n [app package manifest](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/appx-package-manifest)\n instead." + }, "bundleRoot": { "description": "Specifies the root of the bundle file name. E.g., if the bundle file is\n`index.[platform].bundle`, `index` is the bundle root.", - "markdownDescription": "Specifies the root of the bundle file name. E.g., if the bundle file is\n`index.[platform].bundle`, `index` is the bundle root.\n\nDefaults to `index` and `main`.\n\nWhen set, the test app will look for the following files on startup:\n\n- `myRoot.[platform].jsbundle`\n- `myRoot.[platform].bundle`\n- `myRoot.native.jsbundle`\n- `myRoot.native.bundle`\n- `myRoot.jsbundle`\n- `myRoot.bundle`\n\nIntroduced in\n0.9.0.\n", + "markdownDescription": "Specifies the root of the bundle file name. E.g., if the bundle file is\n`index.[platform].bundle`, `index` is the bundle root.\n\nDefaults to `index` and `main`.\n\nWhen set, the test app will look for the following files on startup:\n\n- `myRoot.[platform].jsbundle`\n- `myRoot.[platform].bundle`\n- `myRoot.native.jsbundle`\n- `myRoot.native.bundle`\n- `myRoot.jsbundle`\n- `myRoot.bundle`\n\nIntroduced in\n0.9.0.", "type": "string" }, "singleApp": { "description": "In single-app mode, the component with the specified slug gets launched\nautomatically, essentially behaving as a normal app.", - "markdownDescription": "In single-app mode, the component with the specified slug gets launched\nautomatically, essentially behaving as a normal app.\n\nDefaults to multi-app mode.\n\nFor more details, see [its dedicated page](Single-app-Mode).\n\nIntroduced in\n1.3.0.\n", + "markdownDescription": "In single-app mode, the component with the specified slug gets launched\nautomatically, essentially behaving as a normal app.\n\nDefaults to multi-app mode.\n\nFor more details, see [its dedicated page](Single-app-Mode).\n\nIntroduced in\n1.3.0.", "type": "string" }, "components": { "description": "All components that should be accessible from the home screen should be declared\nunder this property. Each component must have `appKey` set, i.e. the name that\nyou passed to `AppRegistry.registerComponent`.", - "markdownDescription": "All components that should be accessible from the home screen should be declared\nunder this property. Each component must have `appKey` set, i.e. the name that\nyou passed to `AppRegistry.registerComponent`.\n\n```javascript\nAppRegistry.registerComponent(\"Example\", () => Example);\n```\n\nFor each entry, you can declare additional (optional) properties:\n\n```javascript\n{\n \"components\": [\n {\n // The app key passed to `AppRegistry.registerComponent()`\n \"appKey\": \"Example\",\n\n // [Optional] Name to be displayed on home screen\n \"displayName\": \"App\",\n\n // [Optional] Properties that should be passed to your component\n \"initialProperties\": {\n \"concurrentRoot\": false\n },\n\n // [Optional] The style in which to present your component.\n // Valid values are: \"modal\"\n \"presentationStyle\": \"\",\n\n // [Optional] URL slug that uniquely identifies this component.\n // Used for deep linking.\n \"slug\": \"\"\n }\n ]\n}\n```\n\nIf you're on React Native 0.69 and want to enable\n[Concurrent React](https://reactjs.org/blog/2022/03/29/react-v18.html#what-is-concurrent-react),\nset `\"concurrentRoot\": true` in `initialProperties`.\n\n\n\n#### [Android] Adding Fragments\n\nOn Android, you can add fragments to the home screen by using their fully\nqualified class names, e.g. `com.example.app.MyFragment`, as app key:\n\n```javascript\n\"components\": [\n {\n \"appKey\": \"com.example.app.MyFragment\",\n \"displayName\": \"App\"\n }\n]\n```\n\nIf you need to get the `ReactNativeHost` instance within `MyFragment`, you can\nrequest it as a service from the context:\n\n```java\n@Override\n@SuppressLint(\"WrongConstant\")\npublic void onAttach(@NonNull Context context) {\n super.onAttach(context);\n\n ReactNativeHost reactNativeHost = (ReactNativeHost)\n context.getSystemService(\"service:reactNativeHostService\");\n ReactInstanceManager reactInstanceManager =\n reactNativeHost.getReactInstanceManager();\n}\n```\n\n\n\n#### [iOS, macOS] Adding View Controllers\n\nOn iOS/macOS, you can have native view controllers on the home screen by using\ntheir Objective-C names as app key (Swift classes can declare Objective-C names\nwith the\n`@objc`\nattribute):\n\n```javascript\n\"components\": [\n {\n \"appKey\": \"RTAMyViewController\",\n \"displayName\": \"App\"\n }\n]\n```\n\nThe view controller must implement an initializer that accepts a `RCTBridge`\ninstance:\n\n```objc\n- (nonnull instancetype)initWithBridge:(nonnull RCTBridge *)bridge;\n```\n", + "markdownDescription": "All components that should be accessible from the home screen should be declared\nunder this property. Each component must have `appKey` set, i.e. the name that\nyou passed to `AppRegistry.registerComponent`.\n\n```javascript\nAppRegistry.registerComponent(\"Example\", () => Example);\n```\n\nFor each entry, you can declare additional (optional) properties:\n\n```javascript\n{\n \"components\": [\n {\n // The app key passed to `AppRegistry.registerComponent()`\n \"appKey\": \"Example\",\n\n // [Optional] Name to be displayed on home screen\n \"displayName\": \"App\",\n\n // [Optional] Properties that should be passed to your component\n \"initialProperties\": {\n \"concurrentRoot\": false\n },\n\n // [Optional] The style in which to present your component.\n // Valid values are: \"modal\"\n \"presentationStyle\": \"\",\n\n // [Optional] URL slug that uniquely identifies this component.\n // Used for deep linking.\n \"slug\": \"\"\n }\n ]\n}\n```\n\nIf you're on React Native 0.69 and want to enable\n[Concurrent React](https://reactjs.org/blog/2022/03/29/react-v18.html#what-is-concurrent-react),\nset `\"concurrentRoot\": true` in `initialProperties`.\n\n\n\n#### [Android] Adding Fragments\n\nOn Android, you can add fragments to the home screen by using their fully\nqualified class names, e.g. `com.example.app.MyFragment`, as app key:\n\n```javascript\n\"components\": [\n {\n \"appKey\": \"com.example.app.MyFragment\",\n \"displayName\": \"App\"\n }\n]\n```\n\nIf you need to get the `ReactNativeHost` instance within `MyFragment`, you can\nrequest it as a service from the context:\n\n```java\n@Override\n@SuppressLint(\"WrongConstant\")\npublic void onAttach(@NonNull Context context) {\n super.onAttach(context);\n\n ReactNativeHost reactNativeHost = (ReactNativeHost)\n context.getSystemService(\"service:reactNativeHostService\");\n ReactInstanceManager reactInstanceManager =\n reactNativeHost.getReactInstanceManager();\n}\n```\n\n\n\n#### [iOS, macOS] Adding View Controllers\n\nOn iOS/macOS, you can have native view controllers on the home screen by using\ntheir Objective-C names as app key (Swift classes can declare Objective-C names\nwith the\n`@objc`\nattribute):\n\n```javascript\n\"components\": [\n {\n \"appKey\": \"RTAMyViewController\",\n \"displayName\": \"App\"\n }\n]\n```\n\nThe view controller must implement an initializer that accepts a `RCTBridge`\ninstance:\n\n```objc\n- (nonnull instancetype)initWithBridge:(nonnull RCTBridge *)bridge;\n```", "type": "array", "items": { "$ref": "#/$defs/component" @@ -100,7 +105,7 @@ "properties": { "resources": { "description": "Here you should declare all resources that should be bundled with the app. The\nproperty can be a list of paths to resources:", - "markdownDescription": "Here you should declare all resources that should be bundled with the app. The\nproperty can be a list of paths to resources:\n\n```javascript\n\"resources\": [\n \"dist/assets\",\n \"dist/main.jsbundle\"\n]\n```\n\nOr you can declare platform specific resources using platform names as key:\n\n```javascript\n\"resources\": {\n \"android\": [\n \"dist/res\",\n \"dist/main.android.jsbundle\"\n ],\n \"ios\": [\n \"dist/assets\",\n \"dist/main.ios.jsbundle\"\n ],\n \"macos\": [\n \"dist/assets\",\n \"dist/main.macos.jsbundle\"\n ],\n \"windows\": [\n \"dist/assets\",\n \"dist/main.windows.bundle\"\n ]\n}\n```\n\nA path must be relative to the path of `app.json`, and can point to both a file\nor a directory.\n", + "markdownDescription": "Here you should declare all resources that should be bundled with the app. The\nproperty can be a list of paths to resources:\n\n```javascript\n\"resources\": [\n \"dist/assets\",\n \"dist/main.jsbundle\"\n]\n```\n\nOr you can declare platform specific resources using platform names as key:\n\n```javascript\n\"resources\": {\n \"android\": [\n \"dist/res\",\n \"dist/main.android.jsbundle\"\n ],\n \"ios\": [\n \"dist/assets\",\n \"dist/main.ios.jsbundle\"\n ],\n \"macos\": [\n \"dist/assets\",\n \"dist/main.macos.jsbundle\"\n ],\n \"windows\": [\n \"dist/assets\",\n \"dist/main.windows.bundle\"\n ]\n}\n```\n\nA path must be relative to the path of `app.json`, and can point to both a file\nor a directory.", "oneOf": [ { "type": "array", @@ -152,9 +157,14 @@ "description": "Use this property to set the application ID of the APK. The value is set to `applicationId` in `build.gradle`.", "type": "string" }, + "versionCode": { + "description": "A positive integer used as an internal version number. Google uses this number\nto determine whether one version is more recent than another. See\n[Version your app](https://developer.android.com/studio/publish/versioning#appversioning)\nfor more on how it is used and how it differs from [`version`](#version).", + "markdownDescription": "A positive integer used as an internal version number. Google uses this number\nto determine whether one version is more recent than another. See\n[Version your app](https://developer.android.com/studio/publish/versioning#appversioning)\nfor more on how it is used and how it differs from [`version`](#version).", + "type": "string" + }, "signingConfigs": { "description": "Use this to set the\nsigning\nconfigurations for the app.", - "markdownDescription": "Use this to set the\nsigning\nconfigurations for the app.\n\nThe JSON schema follows the Gradle DSL very closely. Below is what one would add\nfor the debug and release flavors:\n\n```javascript\n{\n \"android\": {\n \"signingConfigs\": {\n \"debug\": { // optional\n \"keyAlias\": \"androiddebugkey\", // defaults to \"androiddebugkey\"\n \"keyPassword\": \"android\", // defaults to \"android\n \"storeFile\": \"debug.keystore\", // required\n \"storePassword\": \"android\" // defaults to \"android\n },\n \"release\": { // optional\n \"keyAlias\": \"androiddebugkey\", // defaults to \"androiddebugkey\"\n \"keyPassword\": \"android\", // defaults to \"android\n \"storeFile\": \"release.keystore\", // required\n \"storePassword\": \"android\" // defaults to \"android\n }\n }\n }\n}\n```\n\nIntroduced in\n0.11.0.\n", + "markdownDescription": "Use this to set the\nsigning\nconfigurations for the app.\n\nThe JSON schema follows the Gradle DSL very closely. Below is what one would add\nfor the debug and release flavors:\n\n```javascript\n{\n \"android\": {\n \"signingConfigs\": {\n \"debug\": { // optional\n \"keyAlias\": \"androiddebugkey\", // defaults to \"androiddebugkey\"\n \"keyPassword\": \"android\", // defaults to \"android\n \"storeFile\": \"debug.keystore\", // required\n \"storePassword\": \"android\" // defaults to \"android\n },\n \"release\": { // optional\n \"keyAlias\": \"androiddebugkey\", // defaults to \"androiddebugkey\"\n \"keyPassword\": \"android\", // defaults to \"android\n \"storeFile\": \"release.keystore\", // required\n \"storePassword\": \"android\" // defaults to \"android\n }\n }\n }\n}\n```\n\nIntroduced in\n0.11.0.", "type": "object", "properties": { "debug": { @@ -187,19 +197,24 @@ "description": "Use this property to set the bundle identifier of the final app bundle. This is the same as setting `PRODUCT_BUNDLE_IDENTIFIER` in Xcode.", "type": "string" }, + "buildNumber": { + "description": "Similar to [`version`](#version), but is not shown to users. This is a required\nkey for App Store. The equivalent key in `Info.plist` is\n[`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion).", + "markdownDescription": "Similar to [`version`](#version), but is not shown to users. This is a required\nkey for App Store. The equivalent key in `Info.plist` is\n[`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion).", + "type": "string" + }, "codeSignEntitlements": { "description": "Specifies the path to a custom\nEntitlements\nfile. The path should be relative to `app.json`. This is the same as setting\n`CODE_SIGN_ENTITLEMENTS` in Xcode.", - "markdownDescription": "Specifies the path to a custom\nEntitlements\nfile. The path should be relative to `app.json`. This is the same as setting\n`CODE_SIGN_ENTITLEMENTS` in Xcode.\n\nIntroduced in\n0.9.7.\n", + "markdownDescription": "Specifies the path to a custom\nEntitlements\nfile. The path should be relative to `app.json`. This is the same as setting\n`CODE_SIGN_ENTITLEMENTS` in Xcode.\n\nIntroduced in\n0.9.7.", "type": "string" }, "codeSignIdentity": { "description": "Sets the\ncode\nsigning identity to use when signing code. This is the same as setting\n`CODE_SIGN_IDENTITY` in Xcode.", - "markdownDescription": "Sets the\ncode\nsigning identity to use when signing code. This is the same as setting\n`CODE_SIGN_IDENTITY` in Xcode.\n\nIntroduced in\n0.9.7.\n", + "markdownDescription": "Sets the\ncode\nsigning identity to use when signing code. This is the same as setting\n`CODE_SIGN_IDENTITY` in Xcode.\n\nIntroduced in\n0.9.7.", "type": "string" }, "developmentTeam": { "description": "Sets the\ndevelopment\nteam that the app should be assigned to. This is the same as setting\n`DEVELOPMENT_TEAM` in Xcode.", - "markdownDescription": "Sets the\ndevelopment\nteam that the app should be assigned to. This is the same as setting\n`DEVELOPMENT_TEAM` in Xcode.\n\nIntroduced in\n0.9.7.\n", + "markdownDescription": "Sets the\ndevelopment\nteam that the app should be assigned to. This is the same as setting\n`DEVELOPMENT_TEAM` in Xcode.\n\nIntroduced in\n0.9.7.", "type": "string" }, "reactNativePath": { @@ -216,19 +231,24 @@ "description": "Use this property to set the bundle identifier of the final app bundle. This is the same as setting `PRODUCT_BUNDLE_IDENTIFIER` in Xcode.", "type": "string" }, + "buildNumber": { + "description": "Similar to [`version`](#version), but is not shown to users. This is a required\nkey for App Store. The equivalent key in `Info.plist` is\n[`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion).", + "markdownDescription": "Similar to [`version`](#version), but is not shown to users. This is a required\nkey for App Store. The equivalent key in `Info.plist` is\n[`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion).", + "type": "string" + }, "codeSignEntitlements": { "description": "Specifies the path to a custom\nEntitlements\nfile. The path should be relative to `app.json`. This is the same as setting\n`CODE_SIGN_ENTITLEMENTS` in Xcode.", - "markdownDescription": "Specifies the path to a custom\nEntitlements\nfile. The path should be relative to `app.json`. This is the same as setting\n`CODE_SIGN_ENTITLEMENTS` in Xcode.\n\nIntroduced in\n0.9.7.\n", + "markdownDescription": "Specifies the path to a custom\nEntitlements\nfile. The path should be relative to `app.json`. This is the same as setting\n`CODE_SIGN_ENTITLEMENTS` in Xcode.\n\nIntroduced in\n0.9.7.", "type": "string" }, "codeSignIdentity": { "description": "Sets the\ncode\nsigning identity to use when signing code. This is the same as setting\n`CODE_SIGN_IDENTITY` in Xcode.", - "markdownDescription": "Sets the\ncode\nsigning identity to use when signing code. This is the same as setting\n`CODE_SIGN_IDENTITY` in Xcode.\n\nIntroduced in\n0.9.7.\n", + "markdownDescription": "Sets the\ncode\nsigning identity to use when signing code. This is the same as setting\n`CODE_SIGN_IDENTITY` in Xcode.\n\nIntroduced in\n0.9.7.", "type": "string" }, "developmentTeam": { "description": "Sets the\ndevelopment\nteam that the app should be assigned to. This is the same as setting\n`DEVELOPMENT_TEAM` in Xcode.", - "markdownDescription": "Sets the\ndevelopment\nteam that the app should be assigned to. This is the same as setting\n`DEVELOPMENT_TEAM` in Xcode.\n\nIntroduced in\n0.9.7.\n", + "markdownDescription": "Sets the\ndevelopment\nteam that the app should be assigned to. This is the same as setting\n`DEVELOPMENT_TEAM` in Xcode.\n\nIntroduced in\n0.9.7.", "type": "string" }, "reactNativePath": { @@ -243,22 +263,22 @@ "properties": { "appxManifest": { "description": "Sets the path to your\napp\npackage manifest. If none is set, a default manifest will be provided.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.", - "markdownDescription": "Sets the path to your\napp\npackage manifest. If none is set, a default manifest will be provided.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n0.5.5.\n", + "markdownDescription": "Sets the path to your\napp\npackage manifest. If none is set, a default manifest will be provided.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n0.5.5.", "type": "string" }, "certificateKeyFile": { "description": "The path to the certificate to use. If specified, it will also enable package\nsigning. Changes to this property will not be automatically be picked up; you\nneed to re-run `npx install-windows-test-app` to update the solution.", - "markdownDescription": "The path to the certificate to use. If specified, it will also enable package\nsigning. Changes to this property will not be automatically be picked up; you\nneed to re-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n1.1.0.\n", + "markdownDescription": "The path to the certificate to use. If specified, it will also enable package\nsigning. Changes to this property will not be automatically be picked up; you\nneed to re-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n1.1.0.", "type": "string" }, "certificatePassword": { "description": "The password for the private key in the certificate. Leave unset if no password.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.", - "markdownDescription": "The password for the private key in the certificate. Leave unset if no password.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n1.1.0.\n", + "markdownDescription": "The password for the private key in the certificate. Leave unset if no password.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n1.1.0.", "type": "string" }, "certificateThumbprint": { "description": "This value must match the thumbprint in the signing certificate, or be unset.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.", - "markdownDescription": "This value must match the thumbprint in the signing certificate, or be unset.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n1.1.0.\n", + "markdownDescription": "This value must match the thumbprint in the signing certificate, or be unset.\nChanges to this property will not be automatically be picked up; you need to\nre-run `npx install-windows-test-app` to update the solution.\n\nIntroduced in\n1.1.0.", "type": "string" } } diff --git a/scripts/docs/android.versionCode.md b/scripts/docs/android.versionCode.md new file mode 100644 index 000000000..16a644368 --- /dev/null +++ b/scripts/docs/android.versionCode.md @@ -0,0 +1,4 @@ +A positive integer used as an internal version number. Google uses this number +to determine whether one version is more recent than another. See +[Version your app](https://developer.android.com/studio/publish/versioning#appversioning) +for more on how it is used and how it differs from [`version`](#version). diff --git a/scripts/docs/ios.buildNumber.md b/scripts/docs/ios.buildNumber.md new file mode 100644 index 000000000..fa5233bb9 --- /dev/null +++ b/scripts/docs/ios.buildNumber.md @@ -0,0 +1,3 @@ +Similar to [`version`](#version), but is not shown to users. This is a required +key for App Store. The equivalent key in `Info.plist` is +[`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion). diff --git a/scripts/docs/version.md b/scripts/docs/version.md new file mode 100644 index 000000000..9cc767158 --- /dev/null +++ b/scripts/docs/version.md @@ -0,0 +1,15 @@ +The app version shown to users. The required format is three period-separated +integers, such as 1.3.11. + +- **Android**: Equivalent to setting + [`versionName`](https://developer.android.com/studio/publish/versioning#appversioning). +- **iOS**: This is the same as setting + [`CFBundleShortVersionString`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring) + in `Info.plist`. +- **macOS**: This is the same as setting + [`CFBundleShortVersionString`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring) + in `Info.plist`. +- **Windows**: Please see [`windows.appxmanifest`](#windows.appxmanifest) for + how to use a custom + [app package manifest](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/appx-package-manifest) + instead. diff --git a/scripts/generate-manifest-docs.mjs b/scripts/generate-manifest-docs.mjs index ee7163cc9..af19fafcf 100755 --- a/scripts/generate-manifest-docs.mjs +++ b/scripts/generate-manifest-docs.mjs @@ -50,7 +50,7 @@ async function generateManifestDocs() { : ""; lines.push(""); lines.push(`${anchor}${breadcrumb}${key}`); - lines.push(`\n\n${text}\n`); + lines.push(`\n\n${text}\n\n`); lines.push(""); if (type === "object") { render(def, lines, scope + `/${key}`); diff --git a/scripts/generate-schema.mjs b/scripts/generate-schema.mjs index e820dfd4c..098e5c68a 100755 --- a/scripts/generate-schema.mjs +++ b/scripts/generate-schema.mjs @@ -20,7 +20,7 @@ const docsDir = path.join(path.dirname(thisScript), "docs"); */ function extractBrief(content) { const endBrief = content.indexOf("\n\n"); - return content.substring(0, endBrief); + return endBrief > 0 ? content.substring(0, endBrief) : content; } /** @@ -30,7 +30,7 @@ function extractBrief(content) { async function readMarkdown(name) { const filename = path.join(docsDir, name + ".md"); const md = await fs.readFile(filename, { encoding: "utf-8" }); - return trimCarriageReturn(md); + return trimCarriageReturn(md).trim(); } /** @@ -46,7 +46,10 @@ export async function generateSchema() { components: null, resources: null, singleApp: null, + version: null, "android.signingConfigs": null, + "android.versionCode": null, + "ios.buildNumber": null, "ios.codeSignEntitlements": null, "ios.codeSignIdentity": null, "ios.developmentTeam": null, @@ -99,6 +102,11 @@ export async function generateSchema() { displayName: { type: "string", }, + version: { + type: "string", + description: extractBrief(await docs.version), + markdownDescription: await docs.version, + }, bundleRoot: { description: extractBrief(await docs.bundleRoot), markdownDescription: await docs.bundleRoot, @@ -194,6 +202,11 @@ export async function generateSchema() { "Use this property to set the application ID of the APK. The value is set to `applicationId` in `build.gradle`.", type: "string", }, + versionCode: { + description: extractBrief(await docs["android.versionCode"]), + markdownDescription: await docs["android.versionCode"], + type: "string", + }, signingConfigs: { description: extractBrief(await docs["android.signingConfigs"]), markdownDescription: await docs["android.signingConfigs"], @@ -224,6 +237,11 @@ export async function generateSchema() { "Use this property to set the bundle identifier of the final app bundle. This is the same as setting `PRODUCT_BUNDLE_IDENTIFIER` in Xcode.", type: "string", }, + buildNumber: { + description: extractBrief(await docs["ios.buildNumber"]), + markdownDescription: await docs["ios.buildNumber"], + type: "string", + }, codeSignEntitlements: { description: extractBrief(await docs["ios.codeSignEntitlements"]), markdownDescription: await docs["ios.codeSignEntitlements"], @@ -255,6 +273,11 @@ export async function generateSchema() { "Use this property to set the bundle identifier of the final app bundle. This is the same as setting `PRODUCT_BUNDLE_IDENTIFIER` in Xcode.", type: "string", }, + buildNumber: { + description: extractBrief(await docs["ios.buildNumber"]), + markdownDescription: await docs["ios.buildNumber"], + type: "string", + }, codeSignEntitlements: { description: extractBrief(await docs["ios.codeSignEntitlements"]), markdownDescription: await docs["ios.codeSignEntitlements"], diff --git a/test/__fixtures__/single_app_mode/app.json b/test/__fixtures__/single_app_mode/app.json index 8dd630e51..f1a89656f 100644 --- a/test/__fixtures__/single_app_mode/app.json +++ b/test/__fixtures__/single_app_mode/app.json @@ -1,5 +1,6 @@ { "name": "TestFixture", "displayName": "Test Fixture", + "version": "1.0.0", "singleApp": "test-fixture" } diff --git a/test/test_test_app.rb b/test/test_test_app.rb index 5b4926c52..94d8324ea 100644 --- a/test/test_test_app.rb +++ b/test/test_test_app.rb @@ -25,9 +25,10 @@ def test_app_config end def test_app_config_single_app - name, display_name, single_app = app_config(fixture_path('single_app_mode')) + name, display_name, version, single_app = app_config(fixture_path('single_app_mode')) assert_equal(name, 'TestFixture') assert_equal(display_name, 'Test Fixture') + assert_equal(version, '1.0.0') assert_equal(single_app, 'test-fixture') end diff --git a/windows/ReactTestApp/Manifest.h b/windows/ReactTestApp/Manifest.h index 40b94176a..eb1b707c4 100644 --- a/windows/ReactTestApp/Manifest.h +++ b/windows/ReactTestApp/Manifest.h @@ -24,6 +24,7 @@ namespace ReactTestApp struct Manifest { std::string name; std::string displayName; + std::optional version; std::optional bundleRoot; std::optional singleApp; std::optional> components;