expo-build-properties 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.eslintrc.js ADDED
@@ -0,0 +1,2 @@
1
+ // @generated by expo-module-scripts
2
+ module.exports = require('expo-module-scripts/eslintrc.base.js');
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## Unpublished
4
+
5
+ ### 🛠 Breaking changes
6
+
7
+ ### 🎉 New features
8
+
9
+ ### 🐛 Bug fixes
10
+
11
+ ### 💡 Others
12
+
13
+ ## 0.0.0 — 2022-05-02
14
+
15
+ _This version does not introduce any user-facing changes._
package/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # expo-build-properties
2
+
3
+ **`expo-build-properties`** is a config plugin for managed apps to override the default native build properties.
4
+
5
+ ## API documentation
6
+
7
+ - [Documentation for the main branch][docs-main]
8
+ - [Documentation for the latest stable release][docs-stable]
9
+
10
+ ### Installation
11
+
12
+ **Note:** To use this config plugin, your apps must be a managed app and build by either [EAS Build](/build/introduction.md) or `expo run:[android|ios]`.
13
+
14
+ ```
15
+ expo install expo-build-properties
16
+ ```
17
+
18
+ Add plugin to `app.json`. For example:
19
+
20
+ ```json
21
+ {
22
+ "expo": {
23
+ "plugins": [
24
+ [
25
+ "expo-build-properties",
26
+ {
27
+ "android": {
28
+ "compileSdkVersion": 31,
29
+ "targetSdkVersion": 31,
30
+ "buildToolsVersion": "31.0.0"
31
+ },
32
+ "ios": {
33
+ "deploymentTarget": "13.0"
34
+ }
35
+ }
36
+ ]
37
+ ]
38
+ }
39
+ }
40
+ ```
41
+
42
+ ## Contributing
43
+
44
+ Contributions are very welcome! Please refer to guidelines described in the [contributing guide][contributing].
45
+
46
+ [docs-main]: https://github.com/expo/expo/blob/main/docs/pages/versions/unversioned/sdk/build-properties.md
47
+ [docs-stable]: https://docs.expo.dev/versions/latest/sdk/build-properties/
48
+ [contributing]: https://github.com/expo/expo#contributing
49
+ [config-plugins]: https://docs.expo.dev/guides/config-plugins/
package/app.plugin.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./build/withBuildProperties');
@@ -0,0 +1,15 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ import type { PluginConfigType } from './pluginConfig';
3
+ export declare const withAndroidBuildProperties: ConfigPlugin<PluginConfigType>;
4
+ /**
5
+ * Appends `props.android.extraProguardRules` content into `android/app/proguard-rules.pro`
6
+ */
7
+ export declare const withAndroidProguardRules: ConfigPlugin<PluginConfigType>;
8
+ /**
9
+ * Update `newProguardRules` to original `proguard-rules.pro` contents if needed
10
+ *
11
+ * @param contents the original `proguard-rules.pro` contents
12
+ * @param newProguardRules new proguard rules to add. If the value is null, the generated proguard rules will be cleanup
13
+ * @returns return string when results is updated or return null when nothing changed.
14
+ */
15
+ export declare function updateAndroidProguardRules(contents: string, newProguardRules: string | null): string | null;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.updateAndroidProguardRules = exports.withAndroidProguardRules = exports.withAndroidBuildProperties = void 0;
7
+ const config_plugins_1 = require("@expo/config-plugins");
8
+ const generateCode_1 = require("@expo/config-plugins/build/utils/generateCode");
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const { createBuildGradlePropsConfigPlugin } = config_plugins_1.AndroidConfig.BuildProperties;
12
+ exports.withAndroidBuildProperties = createBuildGradlePropsConfigPlugin([
13
+ {
14
+ propName: 'android.compileSdkVersion',
15
+ propValueGetter: (config) => { var _a, _b; return (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.compileSdkVersion) === null || _b === void 0 ? void 0 : _b.toString(); },
16
+ },
17
+ {
18
+ propName: 'android.targetSdkVersion',
19
+ propValueGetter: (config) => { var _a, _b; return (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.targetSdkVersion) === null || _b === void 0 ? void 0 : _b.toString(); },
20
+ },
21
+ {
22
+ propName: 'android.buildToolsVersion',
23
+ propValueGetter: (config) => { var _a; return (_a = config.android) === null || _a === void 0 ? void 0 : _a.buildToolsVersion; },
24
+ },
25
+ {
26
+ propName: 'android.kotlinVersion',
27
+ propValueGetter: (config) => { var _a; return (_a = config.android) === null || _a === void 0 ? void 0 : _a.kotlinVersion; },
28
+ },
29
+ {
30
+ propName: 'android.packagingOptions.pickFirsts',
31
+ propValueGetter: (config) => { var _a, _b, _c; return (_c = (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.packagingOptions) === null || _b === void 0 ? void 0 : _b.pickFirst) === null || _c === void 0 ? void 0 : _c.join(','); },
32
+ },
33
+ {
34
+ propName: 'android.packagingOptions.excludes',
35
+ propValueGetter: (config) => { var _a, _b, _c; return (_c = (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.packagingOptions) === null || _b === void 0 ? void 0 : _b.exclude) === null || _c === void 0 ? void 0 : _c.join(','); },
36
+ },
37
+ {
38
+ propName: 'android.packagingOptions.merges',
39
+ propValueGetter: (config) => { var _a, _b, _c; return (_c = (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.packagingOptions) === null || _b === void 0 ? void 0 : _b.merge) === null || _c === void 0 ? void 0 : _c.join(','); },
40
+ },
41
+ {
42
+ propName: 'android.packagingOptions.doNotStrip',
43
+ propValueGetter: (config) => { var _a, _b, _c; return (_c = (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.packagingOptions) === null || _b === void 0 ? void 0 : _b.doNotStrip) === null || _c === void 0 ? void 0 : _c.join(','); },
44
+ },
45
+ ], 'withAndroidBuildProperties');
46
+ /**
47
+ * Appends `props.android.extraProguardRules` content into `android/app/proguard-rules.pro`
48
+ */
49
+ const withAndroidProguardRules = (config, props) => {
50
+ return (0, config_plugins_1.withDangerousMod)(config, [
51
+ 'android',
52
+ async (config) => {
53
+ var _a, _b;
54
+ const extraProguardRules = (_b = (_a = props.android) === null || _a === void 0 ? void 0 : _a.extraProguardRules) !== null && _b !== void 0 ? _b : null;
55
+ const proguardRulesFile = path_1.default.join(config.modRequest.platformProjectRoot, 'app', 'proguard-rules.pro');
56
+ const contents = await fs_1.default.promises.readFile(proguardRulesFile, 'utf8');
57
+ const newContents = updateAndroidProguardRules(contents, extraProguardRules);
58
+ if (newContents) {
59
+ await fs_1.default.promises.writeFile(proguardRulesFile, newContents);
60
+ }
61
+ return config;
62
+ },
63
+ ]);
64
+ };
65
+ exports.withAndroidProguardRules = withAndroidProguardRules;
66
+ /**
67
+ * Update `newProguardRules` to original `proguard-rules.pro` contents if needed
68
+ *
69
+ * @param contents the original `proguard-rules.pro` contents
70
+ * @param newProguardRules new proguard rules to add. If the value is null, the generated proguard rules will be cleanup
71
+ * @returns return string when results is updated or return null when nothing changed.
72
+ */
73
+ function updateAndroidProguardRules(contents, newProguardRules) {
74
+ const mergeTag = 'expo-build-properties';
75
+ let mergeResults;
76
+ if (newProguardRules) {
77
+ mergeResults = (0, generateCode_1.mergeContents)({
78
+ tag: mergeTag,
79
+ src: contents,
80
+ newSrc: newProguardRules,
81
+ anchor: /^/,
82
+ offset: contents.length,
83
+ comment: '#',
84
+ });
85
+ }
86
+ else {
87
+ mergeResults = (0, generateCode_1.removeContents)({
88
+ tag: mergeTag,
89
+ src: contents,
90
+ });
91
+ }
92
+ if (mergeResults.didMerge || mergeResults.didClear) {
93
+ return mergeResults.contents;
94
+ }
95
+ return null;
96
+ }
97
+ exports.updateAndroidProguardRules = updateAndroidProguardRules;
package/build/ios.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import { ConfigPlugin } from '@expo/config-plugins';
2
+ import type { PluginConfigType } from './pluginConfig';
3
+ export declare const withIosBuildProperties: ConfigPlugin<PluginConfigType>;
4
+ export declare const withIosDeploymentTarget: ConfigPlugin<PluginConfigType>;
package/build/ios.js ADDED
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withIosDeploymentTarget = exports.withIosBuildProperties = void 0;
4
+ const config_plugins_1 = require("@expo/config-plugins");
5
+ const { createBuildPodfilePropsConfigPlugin } = config_plugins_1.IOSConfig.BuildProperties;
6
+ exports.withIosBuildProperties = createBuildPodfilePropsConfigPlugin([
7
+ {
8
+ propName: 'ios.useFrameworks',
9
+ propValueGetter: (config) => { var _a; return (_a = config.ios) === null || _a === void 0 ? void 0 : _a.useFrameworks; },
10
+ },
11
+ ], 'withIosBuildProperties');
12
+ const withIosDeploymentTarget = (config, props) => {
13
+ var _a;
14
+ const deploymentTarget = (_a = props.ios) === null || _a === void 0 ? void 0 : _a.deploymentTarget;
15
+ if (!deploymentTarget) {
16
+ return config;
17
+ }
18
+ // Updates deployment target in app xcodeproj
19
+ config = withIosDeploymentTargetXcodeProject(config, { deploymentTarget });
20
+ // Updates deployement target in Podfile (Pods project)
21
+ config = withIosDeploymentTargetPodfile(config, props);
22
+ return config;
23
+ };
24
+ exports.withIosDeploymentTarget = withIosDeploymentTarget;
25
+ const withIosDeploymentTargetXcodeProject = (config, props) => {
26
+ return (0, config_plugins_1.withXcodeProject)(config, (config) => {
27
+ config.modResults = updateDeploymentTargetXcodeProject(config.modResults, props.deploymentTarget);
28
+ return config;
29
+ });
30
+ };
31
+ function updateDeploymentTargetXcodeProject(project, deploymentTarget) {
32
+ const { Target } = config_plugins_1.IOSConfig;
33
+ const targetBuildConfigListIds = Target.getNativeTargets(project)
34
+ .filter(([_, target]) => Target.isTargetOfType(target, Target.TargetType.APPLICATION))
35
+ .map(([_, target]) => target.buildConfigurationList);
36
+ for (const buildConfigListId of targetBuildConfigListIds) {
37
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
+ for (const [_, configurations] of config_plugins_1.IOSConfig.XcodeUtils.getBuildConfigurationsForListId(project, buildConfigListId)) {
39
+ const { buildSettings } = configurations;
40
+ if (buildSettings === null || buildSettings === void 0 ? void 0 : buildSettings.IPHONEOS_DEPLOYMENT_TARGET) {
41
+ buildSettings.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
42
+ }
43
+ }
44
+ }
45
+ return project;
46
+ }
47
+ const withIosDeploymentTargetPodfile = createBuildPodfilePropsConfigPlugin([
48
+ {
49
+ propName: 'ios.deploymentTarget',
50
+ propValueGetter: (config) => { var _a; return (_a = config.ios) === null || _a === void 0 ? void 0 : _a.deploymentTarget; },
51
+ },
52
+ ], 'withIosDeploymentTargetPodfile');
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Configuration for `expo-build-properties`
3
+ */
4
+ export interface PluginConfigType {
5
+ android?: PluginConfigTypeAndroid;
6
+ ios?: PluginConfigTypeIos;
7
+ }
8
+ /**
9
+ * Config for Android native build properties
10
+ */
11
+ export interface PluginConfigTypeAndroid {
12
+ /** Override the default `compileSdkVersion` version number in `build.gradle` */
13
+ compileSdkVersion?: number;
14
+ /** Override the default `targetSdkVersion` version number in `build.gradle` */
15
+ targetSdkVersion?: number;
16
+ /** Override the default `buildToolsVersion` version number in `build.gradle` */
17
+ buildToolsVersion?: string;
18
+ /** Override the default Kotlin version when building the app */
19
+ kotlinVersion?: string;
20
+ /** Enable Proguard (R8) in release builds to obfuscate Java code and reduce app size */
21
+ enableProguardInReleaseBuilds?: boolean;
22
+ /** Append custom [Proguard rules](https://www.guardsquare.com/manual/configuration/usage) to `android/app/proguard-rules.pro` */
23
+ extraProguardRules?: string;
24
+ /** AGP [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions) */
25
+ packagingOptions?: PluginConfigTypeAndroidPackagingOptions;
26
+ }
27
+ /**
28
+ * Config for iOS native build properties
29
+ */
30
+ export interface PluginConfigTypeIos {
31
+ /**
32
+ * Override the default iOS *Deployment Target* version in the following projects:
33
+ * - in CocoaPods projects
34
+ * - `PBXNativeTarget` with `com.apple.product-type.application` productType in the app project
35
+ */
36
+ deploymentTarget?: string;
37
+ /** Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang) in `Podfile` */
38
+ useFrameworks?: 'static' | 'dynamic';
39
+ }
40
+ /**
41
+ * AGP [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions)
42
+ */
43
+ export interface PluginConfigTypeAndroidPackagingOptions {
44
+ /** Adds a first-pick pattern */
45
+ pickFirst?: string[];
46
+ /** Adds an excluded pattern */
47
+ exclude?: string[];
48
+ /** Adds a merge pattern */
49
+ merge?: string[];
50
+ /** Adds a doNotStrip pattern */
51
+ doNotStrip?: string[];
52
+ }
53
+ /**
54
+ * @ignore
55
+ */
56
+ export declare function validateConfig(config: any): PluginConfigType;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.validateConfig = void 0;
7
+ const ajv_1 = __importDefault(require("ajv"));
8
+ const semver_1 = __importDefault(require("semver"));
9
+ /**
10
+ * The minimal supported versions. These values should align to SDK
11
+ * @ignore
12
+ */
13
+ const EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS = {
14
+ android: {
15
+ compileSdkVersion: 31,
16
+ targetSdkVersion: 31,
17
+ kotlinVersion: '1.6.10',
18
+ },
19
+ ios: {
20
+ deploymentTarget: '12.0',
21
+ },
22
+ };
23
+ const schema = {
24
+ type: 'object',
25
+ properties: {
26
+ android: {
27
+ type: 'object',
28
+ properties: {
29
+ compileSdkVersion: { type: 'integer', nullable: true },
30
+ targetSdkVersion: { type: 'integer', nullable: true },
31
+ buildToolsVersion: { type: 'string', nullable: true },
32
+ kotlinVersion: { type: 'string', nullable: true },
33
+ enableProguardInReleaseBuilds: { type: 'boolean', nullable: true },
34
+ extraProguardRules: { type: 'string', nullable: true },
35
+ packagingOptions: {
36
+ type: 'object',
37
+ properties: {
38
+ pickFirst: { type: 'array', items: { type: 'string' }, nullable: true },
39
+ exclude: { type: 'array', items: { type: 'string' }, nullable: true },
40
+ merge: { type: 'array', items: { type: 'string' }, nullable: true },
41
+ doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true },
42
+ },
43
+ nullable: true,
44
+ },
45
+ },
46
+ nullable: true,
47
+ },
48
+ ios: {
49
+ type: 'object',
50
+ properties: {
51
+ deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true },
52
+ useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true },
53
+ },
54
+ nullable: true,
55
+ },
56
+ },
57
+ };
58
+ /**
59
+ * Check versions to meet expo minimal supported versions.
60
+ * Will throw error message whenever there are invalid versions.
61
+ * For the implementation, we check items one by one because ajv does not well support custom error message.
62
+ *
63
+ * @param config the validated config passed from ajv
64
+ * @ignore
65
+ */
66
+ function maybeThrowInvalidVersions(config) {
67
+ var _a, _b, _c, _d, _e, _f;
68
+ const checkItems = [
69
+ {
70
+ name: 'android.compileSdkVersion',
71
+ configVersion: (_a = config.android) === null || _a === void 0 ? void 0 : _a.compileSdkVersion,
72
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion,
73
+ },
74
+ {
75
+ name: 'android.targetSdkVersion',
76
+ configVersion: (_b = config.android) === null || _b === void 0 ? void 0 : _b.targetSdkVersion,
77
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion,
78
+ },
79
+ {
80
+ name: 'android.kotlinVersion',
81
+ configVersion: (_c = config.android) === null || _c === void 0 ? void 0 : _c.kotlinVersion,
82
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion,
83
+ },
84
+ {
85
+ name: 'ios.deploymentTarget',
86
+ configVersion: (_d = config.ios) === null || _d === void 0 ? void 0 : _d.deploymentTarget,
87
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget,
88
+ },
89
+ ];
90
+ for (const { name, configVersion, minimalVersion } of checkItems) {
91
+ if (typeof configVersion === 'number' &&
92
+ typeof minimalVersion === 'number' &&
93
+ configVersion < minimalVersion) {
94
+ throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`);
95
+ }
96
+ if (typeof configVersion === 'string' &&
97
+ typeof minimalVersion === 'string' &&
98
+ semver_1.default.lt((_e = semver_1.default.coerce(configVersion)) !== null && _e !== void 0 ? _e : '0.0.0', (_f = semver_1.default.coerce(minimalVersion)) !== null && _f !== void 0 ? _f : '0.0.0')) {
99
+ throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`);
100
+ }
101
+ }
102
+ }
103
+ /**
104
+ * @ignore
105
+ */
106
+ function validateConfig(config) {
107
+ const validate = new ajv_1.default().compile(schema);
108
+ if (!validate(config)) {
109
+ throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors));
110
+ }
111
+ maybeThrowInvalidVersions(config);
112
+ return config;
113
+ }
114
+ exports.validateConfig = validateConfig;
@@ -0,0 +1,10 @@
1
+ import type { ConfigPlugin } from '@expo/config-plugins';
2
+ import { PluginConfigType } from './pluginConfig';
3
+ /**
4
+ * Config plugin to customize native Android or iOS build properties for managed apps
5
+ *
6
+ * @param config ExpoConfig
7
+ * @param props Configuration for the config plugin
8
+ */
9
+ export declare const withBuildProperties: ConfigPlugin<PluginConfigType>;
10
+ export default withBuildProperties;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withBuildProperties = void 0;
4
+ const android_1 = require("./android");
5
+ const ios_1 = require("./ios");
6
+ const pluginConfig_1 = require("./pluginConfig");
7
+ /**
8
+ * Config plugin to customize native Android or iOS build properties for managed apps
9
+ *
10
+ * @param config ExpoConfig
11
+ * @param props Configuration for the config plugin
12
+ */
13
+ const withBuildProperties = (config, props) => {
14
+ const pluginConfig = (0, pluginConfig_1.validateConfig)(props || {});
15
+ config = (0, android_1.withAndroidBuildProperties)(config, pluginConfig);
16
+ config = (0, android_1.withAndroidProguardRules)(config, pluginConfig);
17
+ config = (0, ios_1.withIosBuildProperties)(config, pluginConfig);
18
+ config = (0, ios_1.withIosDeploymentTarget)(config, pluginConfig);
19
+ return config;
20
+ };
21
+ exports.withBuildProperties = withBuildProperties;
22
+ exports.default = exports.withBuildProperties;
package/jest.config.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('expo-module-scripts/jest-preset-plugin');
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "expo-build-properties",
3
+ "version": "0.0.0",
4
+ "description": "Config plugin to customize native build properties on prebuild",
5
+ "main": "build/withBuildProperties.js",
6
+ "types": "build/withBuildProperties.d.ts",
7
+ "scripts": {
8
+ "build": "expo-module build",
9
+ "clean": "expo-module clean",
10
+ "lint": "expo-module lint",
11
+ "test": "expo-module test",
12
+ "prepare": "expo-module prepare",
13
+ "prepublishOnly": "expo-module prepublishOnly",
14
+ "expo-module": "expo-module"
15
+ },
16
+ "keywords": [
17
+ "react-native",
18
+ "expo",
19
+ "build",
20
+ "build-properties"
21
+ ],
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "https://github.com/expo/expo.git",
25
+ "directory": "packages/expo-build-properties"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/expo/expo/issues"
29
+ },
30
+ "author": "650 Industries, Inc.",
31
+ "license": "MIT",
32
+ "homepage": "https://docs.expo.dev/versions/latest/sdk/build-properties",
33
+ "dependencies": {
34
+ "@expo/config-plugins": "~4.1.4",
35
+ "ajv": "^8.11.0",
36
+ "semver": "^7.3.5"
37
+ },
38
+ "devDependencies": {
39
+ "expo-module-scripts": "^2.0.0"
40
+ },
41
+ "jest": {
42
+ "preset": "expo-module-scripts"
43
+ },
44
+ "peerDependencies": {
45
+ "expo": "*"
46
+ },
47
+ "gitHead": "6720e2f264c7439062be002ea343f33090aa89f8"
48
+ }
package/src/android.ts ADDED
@@ -0,0 +1,110 @@
1
+ import { AndroidConfig, ConfigPlugin, withDangerousMod } from '@expo/config-plugins';
2
+ import {
3
+ mergeContents,
4
+ removeContents,
5
+ MergeResults,
6
+ } from '@expo/config-plugins/build/utils/generateCode';
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+
10
+ import type { PluginConfigType } from './pluginConfig';
11
+
12
+ const { createBuildGradlePropsConfigPlugin } = AndroidConfig.BuildProperties;
13
+
14
+ export const withAndroidBuildProperties = createBuildGradlePropsConfigPlugin<PluginConfigType>(
15
+ [
16
+ {
17
+ propName: 'android.compileSdkVersion',
18
+ propValueGetter: (config) => config.android?.compileSdkVersion?.toString(),
19
+ },
20
+ {
21
+ propName: 'android.targetSdkVersion',
22
+ propValueGetter: (config) => config.android?.targetSdkVersion?.toString(),
23
+ },
24
+ {
25
+ propName: 'android.buildToolsVersion',
26
+ propValueGetter: (config) => config.android?.buildToolsVersion,
27
+ },
28
+ {
29
+ propName: 'android.kotlinVersion',
30
+ propValueGetter: (config) => config.android?.kotlinVersion,
31
+ },
32
+ {
33
+ propName: 'android.packagingOptions.pickFirsts',
34
+ propValueGetter: (config) => config.android?.packagingOptions?.pickFirst?.join(','),
35
+ },
36
+ {
37
+ propName: 'android.packagingOptions.excludes',
38
+ propValueGetter: (config) => config.android?.packagingOptions?.exclude?.join(','),
39
+ },
40
+ {
41
+ propName: 'android.packagingOptions.merges',
42
+ propValueGetter: (config) => config.android?.packagingOptions?.merge?.join(','),
43
+ },
44
+ {
45
+ propName: 'android.packagingOptions.doNotStrip',
46
+ propValueGetter: (config) => config.android?.packagingOptions?.doNotStrip?.join(','),
47
+ },
48
+ ],
49
+ 'withAndroidBuildProperties'
50
+ );
51
+
52
+ /**
53
+ * Appends `props.android.extraProguardRules` content into `android/app/proguard-rules.pro`
54
+ */
55
+ export const withAndroidProguardRules: ConfigPlugin<PluginConfigType> = (config, props) => {
56
+ return withDangerousMod(config, [
57
+ 'android',
58
+ async (config) => {
59
+ const extraProguardRules = props.android?.extraProguardRules ?? null;
60
+ const proguardRulesFile = path.join(
61
+ config.modRequest.platformProjectRoot,
62
+ 'app',
63
+ 'proguard-rules.pro'
64
+ );
65
+
66
+ const contents = await fs.promises.readFile(proguardRulesFile, 'utf8');
67
+ const newContents = updateAndroidProguardRules(contents, extraProguardRules);
68
+ if (newContents) {
69
+ await fs.promises.writeFile(proguardRulesFile, newContents);
70
+ }
71
+ return config;
72
+ },
73
+ ]);
74
+ };
75
+
76
+ /**
77
+ * Update `newProguardRules` to original `proguard-rules.pro` contents if needed
78
+ *
79
+ * @param contents the original `proguard-rules.pro` contents
80
+ * @param newProguardRules new proguard rules to add. If the value is null, the generated proguard rules will be cleanup
81
+ * @returns return string when results is updated or return null when nothing changed.
82
+ */
83
+ export function updateAndroidProguardRules(
84
+ contents: string,
85
+ newProguardRules: string | null
86
+ ): string | null {
87
+ const mergeTag = 'expo-build-properties';
88
+
89
+ let mergeResults: MergeResults;
90
+ if (newProguardRules) {
91
+ mergeResults = mergeContents({
92
+ tag: mergeTag,
93
+ src: contents,
94
+ newSrc: newProguardRules,
95
+ anchor: /^/,
96
+ offset: contents.length,
97
+ comment: '#',
98
+ });
99
+ } else {
100
+ mergeResults = removeContents({
101
+ tag: mergeTag,
102
+ src: contents,
103
+ });
104
+ }
105
+
106
+ if (mergeResults.didMerge || mergeResults.didClear) {
107
+ return mergeResults.contents;
108
+ }
109
+ return null;
110
+ }
package/src/ios.ts ADDED
@@ -0,0 +1,77 @@
1
+ import { IOSConfig, ConfigPlugin, withXcodeProject, XcodeProject } from '@expo/config-plugins';
2
+
3
+ import type { PluginConfigType } from './pluginConfig';
4
+
5
+ const { createBuildPodfilePropsConfigPlugin } = IOSConfig.BuildProperties;
6
+
7
+ export const withIosBuildProperties = createBuildPodfilePropsConfigPlugin<PluginConfigType>(
8
+ [
9
+ {
10
+ propName: 'ios.useFrameworks',
11
+ propValueGetter: (config) => config.ios?.useFrameworks,
12
+ },
13
+ ],
14
+ 'withIosBuildProperties'
15
+ );
16
+
17
+ export const withIosDeploymentTarget: ConfigPlugin<PluginConfigType> = (config, props) => {
18
+ const deploymentTarget = props.ios?.deploymentTarget;
19
+ if (!deploymentTarget) {
20
+ return config;
21
+ }
22
+
23
+ // Updates deployment target in app xcodeproj
24
+ config = withIosDeploymentTargetXcodeProject(config, { deploymentTarget });
25
+
26
+ // Updates deployement target in Podfile (Pods project)
27
+ config = withIosDeploymentTargetPodfile(config, props);
28
+
29
+ return config;
30
+ };
31
+
32
+ const withIosDeploymentTargetXcodeProject: ConfigPlugin<{ deploymentTarget: string }> = (
33
+ config,
34
+ props
35
+ ) => {
36
+ return withXcodeProject(config, (config) => {
37
+ config.modResults = updateDeploymentTargetXcodeProject(
38
+ config.modResults,
39
+ props.deploymentTarget
40
+ );
41
+ return config;
42
+ });
43
+ };
44
+
45
+ function updateDeploymentTargetXcodeProject(
46
+ project: XcodeProject,
47
+ deploymentTarget: string
48
+ ): XcodeProject {
49
+ const { Target } = IOSConfig;
50
+ const targetBuildConfigListIds = Target.getNativeTargets(project)
51
+ .filter(([_, target]) => Target.isTargetOfType(target, Target.TargetType.APPLICATION))
52
+ .map(([_, target]) => target.buildConfigurationList);
53
+
54
+ for (const buildConfigListId of targetBuildConfigListIds) {
55
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
56
+ for (const [_, configurations] of IOSConfig.XcodeUtils.getBuildConfigurationsForListId(
57
+ project,
58
+ buildConfigListId
59
+ )) {
60
+ const { buildSettings } = configurations;
61
+ if (buildSettings?.IPHONEOS_DEPLOYMENT_TARGET) {
62
+ buildSettings.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
63
+ }
64
+ }
65
+ }
66
+ return project;
67
+ }
68
+
69
+ const withIosDeploymentTargetPodfile = createBuildPodfilePropsConfigPlugin<PluginConfigType>(
70
+ [
71
+ {
72
+ propName: 'ios.deploymentTarget',
73
+ propValueGetter: (config) => config.ios?.deploymentTarget,
74
+ },
75
+ ],
76
+ 'withIosDeploymentTargetPodfile'
77
+ );
@@ -0,0 +1,184 @@
1
+ import Ajv, { JSONSchemaType } from 'ajv';
2
+ import semver from 'semver';
3
+
4
+ /**
5
+ * The minimal supported versions. These values should align to SDK
6
+ * @ignore
7
+ */
8
+ const EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS = {
9
+ android: {
10
+ compileSdkVersion: 31,
11
+ targetSdkVersion: 31,
12
+ kotlinVersion: '1.6.10',
13
+ },
14
+ ios: {
15
+ deploymentTarget: '12.0',
16
+ },
17
+ };
18
+
19
+ /**
20
+ * Configuration for `expo-build-properties`
21
+ */
22
+ export interface PluginConfigType {
23
+ android?: PluginConfigTypeAndroid;
24
+ ios?: PluginConfigTypeIos;
25
+ }
26
+
27
+ /**
28
+ * Config for Android native build properties
29
+ */
30
+ export interface PluginConfigTypeAndroid {
31
+ /** Override the default `compileSdkVersion` version number in `build.gradle` */
32
+ compileSdkVersion?: number;
33
+
34
+ /** Override the default `targetSdkVersion` version number in `build.gradle` */
35
+ targetSdkVersion?: number;
36
+
37
+ /** Override the default `buildToolsVersion` version number in `build.gradle` */
38
+ buildToolsVersion?: string;
39
+
40
+ /** Override the default Kotlin version when building the app */
41
+ kotlinVersion?: string;
42
+
43
+ /** Enable Proguard (R8) in release builds to obfuscate Java code and reduce app size */
44
+ enableProguardInReleaseBuilds?: boolean;
45
+
46
+ /** Append custom [Proguard rules](https://www.guardsquare.com/manual/configuration/usage) to `android/app/proguard-rules.pro` */
47
+ extraProguardRules?: string;
48
+
49
+ /** AGP [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions) */
50
+ packagingOptions?: PluginConfigTypeAndroidPackagingOptions;
51
+ }
52
+
53
+ /**
54
+ * Config for iOS native build properties
55
+ */
56
+ export interface PluginConfigTypeIos {
57
+ /**
58
+ * Override the default iOS *Deployment Target* version in the following projects:
59
+ * - in CocoaPods projects
60
+ * - `PBXNativeTarget` with `com.apple.product-type.application` productType in the app project
61
+ */
62
+ deploymentTarget?: string;
63
+
64
+ /** Enable [`use_frameworks!`](https://guides.cocoapods.org/syntax/podfile.html#use_frameworks_bang) in `Podfile` */
65
+ useFrameworks?: 'static' | 'dynamic';
66
+ }
67
+
68
+ /**
69
+ * AGP [PackagingOptions](https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/dsl/PackagingOptions)
70
+ */
71
+ export interface PluginConfigTypeAndroidPackagingOptions {
72
+ /** Adds a first-pick pattern */
73
+ pickFirst?: string[];
74
+
75
+ /** Adds an excluded pattern */
76
+ exclude?: string[];
77
+
78
+ /** Adds a merge pattern */
79
+ merge?: string[];
80
+
81
+ /** Adds a doNotStrip pattern */
82
+ doNotStrip?: string[];
83
+ }
84
+
85
+ const schema: JSONSchemaType<PluginConfigType> = {
86
+ type: 'object',
87
+ properties: {
88
+ android: {
89
+ type: 'object',
90
+ properties: {
91
+ compileSdkVersion: { type: 'integer', nullable: true },
92
+ targetSdkVersion: { type: 'integer', nullable: true },
93
+ buildToolsVersion: { type: 'string', nullable: true },
94
+ kotlinVersion: { type: 'string', nullable: true },
95
+
96
+ enableProguardInReleaseBuilds: { type: 'boolean', nullable: true },
97
+ extraProguardRules: { type: 'string', nullable: true },
98
+
99
+ packagingOptions: {
100
+ type: 'object',
101
+ properties: {
102
+ pickFirst: { type: 'array', items: { type: 'string' }, nullable: true },
103
+ exclude: { type: 'array', items: { type: 'string' }, nullable: true },
104
+ merge: { type: 'array', items: { type: 'string' }, nullable: true },
105
+ doNotStrip: { type: 'array', items: { type: 'string' }, nullable: true },
106
+ },
107
+ nullable: true,
108
+ },
109
+ },
110
+ nullable: true,
111
+ },
112
+ ios: {
113
+ type: 'object',
114
+ properties: {
115
+ deploymentTarget: { type: 'string', pattern: '\\d+\\.\\d+', nullable: true },
116
+ useFrameworks: { type: 'string', enum: ['static', 'dynamic'], nullable: true },
117
+ },
118
+ nullable: true,
119
+ },
120
+ },
121
+ };
122
+
123
+ /**
124
+ * Check versions to meet expo minimal supported versions.
125
+ * Will throw error message whenever there are invalid versions.
126
+ * For the implementation, we check items one by one because ajv does not well support custom error message.
127
+ *
128
+ * @param config the validated config passed from ajv
129
+ * @ignore
130
+ */
131
+ function maybeThrowInvalidVersions(config: PluginConfigType) {
132
+ const checkItems = [
133
+ {
134
+ name: 'android.compileSdkVersion',
135
+ configVersion: config.android?.compileSdkVersion,
136
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion,
137
+ },
138
+ {
139
+ name: 'android.targetSdkVersion',
140
+ configVersion: config.android?.targetSdkVersion,
141
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion,
142
+ },
143
+ {
144
+ name: 'android.kotlinVersion',
145
+ configVersion: config.android?.kotlinVersion,
146
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion,
147
+ },
148
+ {
149
+ name: 'ios.deploymentTarget',
150
+ configVersion: config.ios?.deploymentTarget,
151
+ minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget,
152
+ },
153
+ ];
154
+
155
+ for (const { name, configVersion, minimalVersion } of checkItems) {
156
+ if (
157
+ typeof configVersion === 'number' &&
158
+ typeof minimalVersion === 'number' &&
159
+ configVersion < minimalVersion
160
+ ) {
161
+ throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`);
162
+ }
163
+ if (
164
+ typeof configVersion === 'string' &&
165
+ typeof minimalVersion === 'string' &&
166
+ semver.lt(semver.coerce(configVersion) ?? '0.0.0', semver.coerce(minimalVersion) ?? '0.0.0')
167
+ ) {
168
+ throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`);
169
+ }
170
+ }
171
+ }
172
+
173
+ /**
174
+ * @ignore
175
+ */
176
+ export function validateConfig(config: any): PluginConfigType {
177
+ const validate = new Ajv().compile(schema);
178
+ if (!validate(config)) {
179
+ throw new Error('Invalid expo-build-properties config: ' + JSON.stringify(validate.errors));
180
+ }
181
+
182
+ maybeThrowInvalidVersions(config);
183
+ return config;
184
+ }
@@ -0,0 +1,24 @@
1
+ import type { ConfigPlugin } from '@expo/config-plugins';
2
+
3
+ import { withAndroidBuildProperties, withAndroidProguardRules } from './android';
4
+ import { withIosBuildProperties, withIosDeploymentTarget } from './ios';
5
+ import { PluginConfigType, validateConfig } from './pluginConfig';
6
+
7
+ /**
8
+ * Config plugin to customize native Android or iOS build properties for managed apps
9
+ *
10
+ * @param config ExpoConfig
11
+ * @param props Configuration for the config plugin
12
+ */
13
+ export const withBuildProperties: ConfigPlugin<PluginConfigType> = (config, props) => {
14
+ const pluginConfig = validateConfig(props || {});
15
+
16
+ config = withAndroidBuildProperties(config, pluginConfig);
17
+ config = withAndroidProguardRules(config, pluginConfig);
18
+ config = withIosBuildProperties(config, pluginConfig);
19
+ config = withIosDeploymentTarget(config, pluginConfig);
20
+
21
+ return config;
22
+ };
23
+
24
+ export default withBuildProperties;
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "expo-module-scripts/tsconfig.plugin",
3
+ "compilerOptions": {
4
+ "outDir": "./build"
5
+ },
6
+ "include": ["./src"],
7
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"]
8
+ }