expo-build-properties 0.3.0 → 0.4.1

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/CHANGELOG.md CHANGED
@@ -10,6 +10,23 @@
10
10
 
11
11
  ### 💡 Others
12
12
 
13
+ ## 0.4.1 — 2022-11-24
14
+
15
+ ### 🐛 Bug fixes
16
+
17
+ - Fixed `extraProguardRules` be overwritten from multiple `withBuildProperties` execution. ([#20106](https://github.com/expo/expo/pull/20106) by [@kudo](https://github.com/kudo))
18
+
19
+ ## 0.4.0 — 2022-10-25
20
+
21
+ ### 🛠 Breaking changes
22
+
23
+ - [plugin] Upgrade minimum runtime requirement to Node 14 (LTS). ([#18204](https://github.com/expo/expo/pull/18204) by [@EvanBacon](https://github.com/EvanBacon))
24
+ - Bumped iOS deployment target to 13.0 and deprecated support for iOS 12. ([#18873](https://github.com/expo/expo/pull/18873) by [@tsapeta](https://github.com/tsapeta))
25
+
26
+ ### 💡 Others
27
+
28
+ - Drop `@expo/config-plugins` dependency in favor of peer dependency on `expo`. ([#18595](https://github.com/expo/expo/pull/18595) by [@EvanBacon](https://github.com/EvanBacon))
29
+
13
30
  ## 0.3.0 — 2022-07-07
14
31
 
15
32
  ### 🎉 New features
@@ -1,15 +1,20 @@
1
- import { ConfigPlugin } from '@expo/config-plugins';
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
2
  import type { PluginConfigType } from './pluginConfig';
3
3
  export declare const withAndroidBuildProperties: ConfigPlugin<PluginConfigType>;
4
4
  /**
5
5
  * Appends `props.android.extraProguardRules` content into `android/app/proguard-rules.pro`
6
6
  */
7
7
  export declare const withAndroidProguardRules: ConfigPlugin<PluginConfigType>;
8
+ /**
9
+ * Purge generated proguard contents from previous prebuild.
10
+ * This plugin only runs once in the prebuilding phase and should execute before any `withAndroidProguardRules` calls.
11
+ */
12
+ export declare const withAndroidPurgeProguardRulesOnce: ConfigPlugin;
8
13
  /**
9
14
  * Update `newProguardRules` to original `proguard-rules.pro` contents if needed
10
15
  *
11
16
  * @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.
17
+ * @param newProguardRules new proguard rules to add. If the value is null, the returned value will be original `contents`.
18
+ * @returns return updated contents
14
19
  */
15
- export declare function updateAndroidProguardRules(contents: string, newProguardRules: string | null): string | null;
20
+ export declare function updateAndroidProguardRules(contents: string, newProguardRules: string | null, updateMode: 'append' | 'overwrite'): string;
package/build/android.js CHANGED
@@ -3,52 +3,52 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
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");
6
+ exports.updateAndroidProguardRules = exports.withAndroidPurgeProguardRulesOnce = exports.withAndroidProguardRules = exports.withAndroidBuildProperties = void 0;
7
+ const config_plugins_1 = require("expo/config-plugins");
9
8
  const fs_1 = __importDefault(require("fs"));
10
9
  const path_1 = __importDefault(require("path"));
10
+ const fileContentsUtils_1 = require("./fileContentsUtils");
11
11
  const { createBuildGradlePropsConfigPlugin } = config_plugins_1.AndroidConfig.BuildProperties;
12
12
  exports.withAndroidBuildProperties = createBuildGradlePropsConfigPlugin([
13
13
  {
14
14
  propName: 'android.minSdkVersion',
15
- propValueGetter: (config) => { var _a, _b; return (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.minSdkVersion) === null || _b === void 0 ? void 0 : _b.toString(); },
15
+ propValueGetter: (config) => config.android?.minSdkVersion?.toString(),
16
16
  },
17
17
  {
18
18
  propName: 'android.compileSdkVersion',
19
- 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(); },
19
+ propValueGetter: (config) => config.android?.compileSdkVersion?.toString(),
20
20
  },
21
21
  {
22
22
  propName: 'android.targetSdkVersion',
23
- 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(); },
23
+ propValueGetter: (config) => config.android?.targetSdkVersion?.toString(),
24
24
  },
25
25
  {
26
26
  propName: 'android.buildToolsVersion',
27
- propValueGetter: (config) => { var _a; return (_a = config.android) === null || _a === void 0 ? void 0 : _a.buildToolsVersion; },
27
+ propValueGetter: (config) => config.android?.buildToolsVersion,
28
28
  },
29
29
  {
30
30
  propName: 'android.kotlinVersion',
31
- propValueGetter: (config) => { var _a; return (_a = config.android) === null || _a === void 0 ? void 0 : _a.kotlinVersion; },
31
+ propValueGetter: (config) => config.android?.kotlinVersion,
32
32
  },
33
33
  {
34
34
  propName: 'android.packagingOptions.pickFirsts',
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.pickFirst) === null || _c === void 0 ? void 0 : _c.join(','); },
35
+ propValueGetter: (config) => config.android?.packagingOptions?.pickFirst?.join(','),
36
36
  },
37
37
  {
38
38
  propName: 'android.packagingOptions.excludes',
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.exclude) === null || _c === void 0 ? void 0 : _c.join(','); },
39
+ propValueGetter: (config) => config.android?.packagingOptions?.exclude?.join(','),
40
40
  },
41
41
  {
42
42
  propName: 'android.packagingOptions.merges',
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.merge) === null || _c === void 0 ? void 0 : _c.join(','); },
43
+ propValueGetter: (config) => config.android?.packagingOptions?.merge?.join(','),
44
44
  },
45
45
  {
46
46
  propName: 'android.packagingOptions.doNotStrip',
47
- 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(','); },
47
+ propValueGetter: (config) => config.android?.packagingOptions?.doNotStrip?.join(','),
48
48
  },
49
49
  {
50
50
  propName: 'android.enableProguardInReleaseBuilds',
51
- propValueGetter: (config) => { var _a, _b; return (_b = (_a = config.android) === null || _a === void 0 ? void 0 : _a.enableProguardInReleaseBuilds) === null || _b === void 0 ? void 0 : _b.toString(); },
51
+ propValueGetter: (config) => config.android?.enableProguardInReleaseBuilds?.toString(),
52
52
  },
53
53
  ], 'withAndroidBuildProperties');
54
54
  /**
@@ -58,12 +58,11 @@ const withAndroidProguardRules = (config, props) => {
58
58
  return (0, config_plugins_1.withDangerousMod)(config, [
59
59
  'android',
60
60
  async (config) => {
61
- var _a, _b;
62
- const extraProguardRules = (_b = (_a = props.android) === null || _a === void 0 ? void 0 : _a.extraProguardRules) !== null && _b !== void 0 ? _b : null;
61
+ const extraProguardRules = props.android?.extraProguardRules ?? null;
63
62
  const proguardRulesFile = path_1.default.join(config.modRequest.platformProjectRoot, 'app', 'proguard-rules.pro');
64
63
  const contents = await fs_1.default.promises.readFile(proguardRulesFile, 'utf8');
65
- const newContents = updateAndroidProguardRules(contents, extraProguardRules);
66
- if (newContents) {
64
+ const newContents = updateAndroidProguardRules(contents, extraProguardRules, 'append');
65
+ if (contents !== newContents) {
67
66
  await fs_1.default.promises.writeFile(proguardRulesFile, newContents);
68
67
  }
69
68
  return config;
@@ -71,35 +70,70 @@ const withAndroidProguardRules = (config, props) => {
71
70
  ]);
72
71
  };
73
72
  exports.withAndroidProguardRules = withAndroidProguardRules;
73
+ /**
74
+ * Purge generated proguard contents from previous prebuild.
75
+ * This plugin only runs once in the prebuilding phase and should execute before any `withAndroidProguardRules` calls.
76
+ */
77
+ const withAndroidPurgeProguardRulesOnce = (config) => {
78
+ return (0, config_plugins_1.withDangerousMod)(config, [
79
+ 'android',
80
+ async (config) => {
81
+ const RUN_ONCE_NAME = 'expo-build-properties-android-purge-proguard-rules-once';
82
+ /**
83
+ * The `withRunOnce` plugin will delay this plugin's execution.
84
+ * To make sure this plugin executes before any `withAndroidProguardRules`.
85
+ * We use the `withRunOnce` internal History functions to do the check.
86
+ * Example calls to demonstrate the case:
87
+ * ```ts
88
+ * config = withBuildProperties(config as ExpoConfig, {
89
+ * android: {
90
+ * kotlinVersion: "1.6.10",
91
+ * },
92
+ * });
93
+ * config = withBuildProperties(config as ExpoConfig, {
94
+ * android: {
95
+ * enableProguardInReleaseBuilds: true,
96
+ * extraProguardRules: "-keep class com.mycompany.** { *; }",
97
+ * },
98
+ * });
99
+ * ```
100
+ */
101
+ if (config_plugins_1.History.getHistoryItem(config, RUN_ONCE_NAME)) {
102
+ return config;
103
+ }
104
+ else {
105
+ config_plugins_1.History.addHistoryItem(config, { name: RUN_ONCE_NAME });
106
+ }
107
+ const proguardRulesFile = path_1.default.join(config.modRequest.platformProjectRoot, 'app', 'proguard-rules.pro');
108
+ const contents = await fs_1.default.promises.readFile(proguardRulesFile, 'utf8');
109
+ const newContents = updateAndroidProguardRules(contents, '', 'overwrite');
110
+ if (contents !== newContents) {
111
+ await fs_1.default.promises.writeFile(proguardRulesFile, newContents);
112
+ }
113
+ return config;
114
+ },
115
+ ]);
116
+ };
117
+ exports.withAndroidPurgeProguardRulesOnce = withAndroidPurgeProguardRulesOnce;
74
118
  /**
75
119
  * Update `newProguardRules` to original `proguard-rules.pro` contents if needed
76
120
  *
77
121
  * @param contents the original `proguard-rules.pro` contents
78
- * @param newProguardRules new proguard rules to add. If the value is null, the generated proguard rules will be cleanup
79
- * @returns return string when results is updated or return null when nothing changed.
122
+ * @param newProguardRules new proguard rules to add. If the value is null, the returned value will be original `contents`.
123
+ * @returns return updated contents
80
124
  */
81
- function updateAndroidProguardRules(contents, newProguardRules) {
82
- const mergeTag = 'expo-build-properties';
83
- let mergeResults;
84
- if (newProguardRules) {
85
- mergeResults = (0, generateCode_1.mergeContents)({
86
- tag: mergeTag,
87
- src: contents,
88
- newSrc: newProguardRules,
89
- anchor: /^/,
90
- offset: contents.length,
91
- comment: '#',
92
- });
125
+ function updateAndroidProguardRules(contents, newProguardRules, updateMode) {
126
+ if (newProguardRules == null) {
127
+ return contents;
93
128
  }
94
- else {
95
- mergeResults = (0, generateCode_1.removeContents)({
96
- tag: mergeTag,
97
- src: contents,
98
- });
129
+ const options = { tag: 'expo-build-properties', commentPrefix: '#' };
130
+ let newContents = contents;
131
+ if (updateMode === 'overwrite') {
132
+ newContents = (0, fileContentsUtils_1.purgeContents)(contents, options);
99
133
  }
100
- if (mergeResults.didMerge || mergeResults.didClear) {
101
- return mergeResults.contents;
134
+ if (newProguardRules !== '') {
135
+ newContents = (0, fileContentsUtils_1.appendContents)(newContents, newProguardRules, options);
102
136
  }
103
- return null;
137
+ return newContents;
104
138
  }
105
139
  exports.updateAndroidProguardRules = updateAndroidProguardRules;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * A set of helper functions to update file contents. This is a simplified version to the config-plugins internal generateCode functions.
3
+ */
4
+ /**
5
+ * Options for a generated section
6
+ */
7
+ export interface SectionOptions {
8
+ /**
9
+ * A meaningful tag to differentiate the generated section
10
+ */
11
+ tag: string;
12
+ /**
13
+ * Defines comment for the generated code.
14
+ * E.g. '//' for js code or '#' for shell script
15
+ */
16
+ commentPrefix: string;
17
+ }
18
+ /**
19
+ * Append new contents to src with generated section comments
20
+ *
21
+ * If there is already a generated section, this function will append the new contents at the end of the section.
22
+ * Otherwise, this function will generate a new section at the end of file.
23
+ */
24
+ export declare function appendContents(src: string, contents: string, sectionOptions: SectionOptions): string;
25
+ /**
26
+ * Purge a generated section
27
+ */
28
+ export declare function purgeContents(src: string, sectionOptions: SectionOptions): string;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * A set of helper functions to update file contents. This is a simplified version to the config-plugins internal generateCode functions.
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.purgeContents = exports.appendContents = void 0;
10
+ const assert_1 = __importDefault(require("assert"));
11
+ /**
12
+ * Append new contents to src with generated section comments
13
+ *
14
+ * If there is already a generated section, this function will append the new contents at the end of the section.
15
+ * Otherwise, this function will generate a new section at the end of file.
16
+ */
17
+ function appendContents(src, contents, sectionOptions) {
18
+ const start = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, true);
19
+ const end = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, false);
20
+ const regex = new RegExp(`(\\n${escapeRegExp(end)})`, 'gm');
21
+ const match = regex.exec(src);
22
+ if (match) {
23
+ (0, assert_1.default)(src.indexOf(start) >= 0);
24
+ return src.replace(regex, `\n${contents}$1`);
25
+ }
26
+ else {
27
+ const sectionedContents = `${start}\n${contents}\n${end}`;
28
+ return `${src}\n${sectionedContents}`;
29
+ }
30
+ }
31
+ exports.appendContents = appendContents;
32
+ /**
33
+ * Purge a generated section
34
+ */
35
+ function purgeContents(src, sectionOptions) {
36
+ const start = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, true);
37
+ const end = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, false);
38
+ const regex = new RegExp(`\\n${escapeRegExp(start)}\\n[\\s\\S]*\\n${escapeRegExp(end)}`, 'gm');
39
+ return src.replace(regex, '');
40
+ }
41
+ exports.purgeContents = purgeContents;
42
+ /**
43
+ * Create comments for generated section
44
+ */
45
+ function createSectionComment(commentPrefix, tag, isBeginComment) {
46
+ if (isBeginComment) {
47
+ return `${commentPrefix} @generated begin ${tag} - expo prebuild (DO NOT MODIFY)`;
48
+ }
49
+ else {
50
+ return `${commentPrefix} @generated end ${tag}`;
51
+ }
52
+ }
53
+ /**
54
+ * Escape a string for regular expression
55
+ *
56
+ * Reference from {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping}
57
+ */
58
+ function escapeRegExp(input) {
59
+ return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
60
+ }
package/build/ios.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ConfigPlugin } from '@expo/config-plugins';
1
+ import { ConfigPlugin } from 'expo/config-plugins';
2
2
  import type { PluginConfigType } from './pluginConfig';
3
3
  export declare const withIosBuildProperties: ConfigPlugin<PluginConfigType>;
4
4
  export declare const withIosDeploymentTarget: ConfigPlugin<PluginConfigType>;
package/build/ios.js CHANGED
@@ -1,17 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.withIosDeploymentTarget = exports.withIosBuildProperties = void 0;
4
- const config_plugins_1 = require("@expo/config-plugins");
4
+ const config_plugins_1 = require("expo/config-plugins");
5
5
  const { createBuildPodfilePropsConfigPlugin } = config_plugins_1.IOSConfig.BuildProperties;
6
6
  exports.withIosBuildProperties = createBuildPodfilePropsConfigPlugin([
7
7
  {
8
8
  propName: 'ios.useFrameworks',
9
- propValueGetter: (config) => { var _a; return (_a = config.ios) === null || _a === void 0 ? void 0 : _a.useFrameworks; },
9
+ propValueGetter: (config) => config.ios?.useFrameworks,
10
10
  },
11
11
  ], 'withIosBuildProperties');
12
12
  const withIosDeploymentTarget = (config, props) => {
13
- var _a;
14
- const deploymentTarget = (_a = props.ios) === null || _a === void 0 ? void 0 : _a.deploymentTarget;
13
+ const deploymentTarget = props.ios?.deploymentTarget;
15
14
  if (!deploymentTarget) {
16
15
  return config;
17
16
  }
@@ -37,7 +36,7 @@ function updateDeploymentTargetXcodeProject(project, deploymentTarget) {
37
36
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
38
37
  for (const [_, configurations] of config_plugins_1.IOSConfig.XcodeUtils.getBuildConfigurationsForListId(project, buildConfigListId)) {
39
38
  const { buildSettings } = configurations;
40
- if (buildSettings === null || buildSettings === void 0 ? void 0 : buildSettings.IPHONEOS_DEPLOYMENT_TARGET) {
39
+ if (buildSettings?.IPHONEOS_DEPLOYMENT_TARGET) {
41
40
  buildSettings.IPHONEOS_DEPLOYMENT_TARGET = deploymentTarget;
42
41
  }
43
42
  }
@@ -47,6 +46,6 @@ function updateDeploymentTargetXcodeProject(project, deploymentTarget) {
47
46
  const withIosDeploymentTargetPodfile = createBuildPodfilePropsConfigPlugin([
48
47
  {
49
48
  propName: 'ios.deploymentTarget',
50
- propValueGetter: (config) => { var _a; return (_a = config.ios) === null || _a === void 0 ? void 0 : _a.deploymentTarget; },
49
+ propValueGetter: (config) => config.ios?.deploymentTarget,
51
50
  },
52
51
  ], 'withIosDeploymentTargetPodfile');
@@ -18,7 +18,7 @@ const EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS = {
18
18
  kotlinVersion: '1.6.10',
19
19
  },
20
20
  ios: {
21
- deploymentTarget: '12.0',
21
+ deploymentTarget: '13.0',
22
22
  },
23
23
  };
24
24
  const schema = {
@@ -66,31 +66,30 @@ const schema = {
66
66
  * @ignore
67
67
  */
68
68
  function maybeThrowInvalidVersions(config) {
69
- var _a, _b, _c, _d, _e, _f, _g;
70
69
  const checkItems = [
71
70
  {
72
71
  name: 'android.minSdkVersion',
73
- configVersion: (_a = config.android) === null || _a === void 0 ? void 0 : _a.minSdkVersion,
72
+ configVersion: config.android?.minSdkVersion,
74
73
  minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.minSdkVersion,
75
74
  },
76
75
  {
77
76
  name: 'android.compileSdkVersion',
78
- configVersion: (_b = config.android) === null || _b === void 0 ? void 0 : _b.compileSdkVersion,
77
+ configVersion: config.android?.compileSdkVersion,
79
78
  minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.compileSdkVersion,
80
79
  },
81
80
  {
82
81
  name: 'android.targetSdkVersion',
83
- configVersion: (_c = config.android) === null || _c === void 0 ? void 0 : _c.targetSdkVersion,
82
+ configVersion: config.android?.targetSdkVersion,
84
83
  minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.targetSdkVersion,
85
84
  },
86
85
  {
87
86
  name: 'android.kotlinVersion',
88
- configVersion: (_d = config.android) === null || _d === void 0 ? void 0 : _d.kotlinVersion,
87
+ configVersion: config.android?.kotlinVersion,
89
88
  minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.android.kotlinVersion,
90
89
  },
91
90
  {
92
91
  name: 'ios.deploymentTarget',
93
- configVersion: (_e = config.ios) === null || _e === void 0 ? void 0 : _e.deploymentTarget,
92
+ configVersion: config.ios?.deploymentTarget,
94
93
  minimalVersion: EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS.ios.deploymentTarget,
95
94
  },
96
95
  ];
@@ -102,7 +101,7 @@ function maybeThrowInvalidVersions(config) {
102
101
  }
103
102
  if (typeof configVersion === 'string' &&
104
103
  typeof minimalVersion === 'string' &&
105
- semver_1.default.lt((_f = semver_1.default.coerce(configVersion)) !== null && _f !== void 0 ? _f : '0.0.0', (_g = semver_1.default.coerce(minimalVersion)) !== null && _g !== void 0 ? _g : '0.0.0')) {
104
+ semver_1.default.lt(semver_1.default.coerce(configVersion) ?? '0.0.0', semver_1.default.coerce(minimalVersion) ?? '0.0.0')) {
106
105
  throw new Error(`\`${name}\` needs to be at least version ${minimalVersion}.`);
107
106
  }
108
107
  }
@@ -1,4 +1,4 @@
1
- import type { ConfigPlugin } from '@expo/config-plugins';
1
+ import type { ConfigPlugin } from 'expo/config-plugins';
2
2
  import { PluginConfigType } from './pluginConfig';
3
3
  /**
4
4
  * Config plugin to customize native Android or iOS build properties for managed apps
@@ -14,6 +14,13 @@ const withBuildProperties = (config, props) => {
14
14
  const pluginConfig = (0, pluginConfig_1.validateConfig)(props || {});
15
15
  config = (0, android_1.withAndroidBuildProperties)(config, pluginConfig);
16
16
  config = (0, android_1.withAndroidProguardRules)(config, pluginConfig);
17
+ // Assuming `withBuildProperties` could be called multiple times from different config-plugins,
18
+ // the `withAndroidProguardRules` always appends new rules by default.
19
+ // That is not ideal if we leave generated contents from previous prebuild there.
20
+ // The `withAndroidPurgeProguardRulesOnce` is for this purpose and it would only run once in prebuilding phase.
21
+ //
22
+ // plugins order matter: the later one would run first
23
+ config = (0, android_1.withAndroidPurgeProguardRulesOnce)(config);
17
24
  config = (0, ios_1.withIosBuildProperties)(config, pluginConfig);
18
25
  config = (0, ios_1.withIosDeploymentTarget)(config, pluginConfig);
19
26
  return config;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-build-properties",
3
- "version": "0.3.0",
3
+ "version": "0.4.1",
4
4
  "description": "Config plugin to customize native build properties on prebuild",
5
5
  "main": "build/withBuildProperties.js",
6
6
  "types": "build/withBuildProperties.d.ts",
@@ -31,12 +31,11 @@
31
31
  "license": "MIT",
32
32
  "homepage": "https://docs.expo.dev/versions/latest/sdk/build-properties",
33
33
  "dependencies": {
34
- "@expo/config-plugins": "~5.0.0",
35
34
  "ajv": "^8.11.0",
36
35
  "semver": "^7.3.5"
37
36
  },
38
37
  "devDependencies": {
39
- "expo-module-scripts": "^2.0.0"
38
+ "expo-module-scripts": "^3.0.0"
40
39
  },
41
40
  "jest": {
42
41
  "preset": "expo-module-scripts"
@@ -44,5 +43,5 @@
44
43
  "peerDependencies": {
45
44
  "expo": "*"
46
45
  },
47
- "gitHead": "e893ff2b01e108cf246cec02318c0df9d6bc603c"
46
+ "gitHead": "1c5b1b798ecf9801a8004614140441c4d37f3935"
48
47
  }
package/src/android.ts CHANGED
@@ -1,12 +1,8 @@
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';
1
+ import { AndroidConfig, ConfigPlugin, History, withDangerousMod } from 'expo/config-plugins';
7
2
  import fs from 'fs';
8
3
  import path from 'path';
9
4
 
5
+ import { appendContents, purgeContents } from './fileContentsUtils';
10
6
  import type { PluginConfigType } from './pluginConfig';
11
7
 
12
8
  const { createBuildGradlePropsConfigPlugin } = AndroidConfig.BuildProperties;
@@ -72,8 +68,59 @@ export const withAndroidProguardRules: ConfigPlugin<PluginConfigType> = (config,
72
68
  );
73
69
 
74
70
  const contents = await fs.promises.readFile(proguardRulesFile, 'utf8');
75
- const newContents = updateAndroidProguardRules(contents, extraProguardRules);
76
- if (newContents) {
71
+ const newContents = updateAndroidProguardRules(contents, extraProguardRules, 'append');
72
+ if (contents !== newContents) {
73
+ await fs.promises.writeFile(proguardRulesFile, newContents);
74
+ }
75
+ return config;
76
+ },
77
+ ]);
78
+ };
79
+
80
+ /**
81
+ * Purge generated proguard contents from previous prebuild.
82
+ * This plugin only runs once in the prebuilding phase and should execute before any `withAndroidProguardRules` calls.
83
+ */
84
+ export const withAndroidPurgeProguardRulesOnce: ConfigPlugin = (config) => {
85
+ return withDangerousMod(config, [
86
+ 'android',
87
+ async (config) => {
88
+ const RUN_ONCE_NAME = 'expo-build-properties-android-purge-proguard-rules-once';
89
+
90
+ /**
91
+ * The `withRunOnce` plugin will delay this plugin's execution.
92
+ * To make sure this plugin executes before any `withAndroidProguardRules`.
93
+ * We use the `withRunOnce` internal History functions to do the check.
94
+ * Example calls to demonstrate the case:
95
+ * ```ts
96
+ * config = withBuildProperties(config as ExpoConfig, {
97
+ * android: {
98
+ * kotlinVersion: "1.6.10",
99
+ * },
100
+ * });
101
+ * config = withBuildProperties(config as ExpoConfig, {
102
+ * android: {
103
+ * enableProguardInReleaseBuilds: true,
104
+ * extraProguardRules: "-keep class com.mycompany.** { *; }",
105
+ * },
106
+ * });
107
+ * ```
108
+ */
109
+ if (History.getHistoryItem(config, RUN_ONCE_NAME)) {
110
+ return config;
111
+ } else {
112
+ History.addHistoryItem(config, { name: RUN_ONCE_NAME });
113
+ }
114
+
115
+ const proguardRulesFile = path.join(
116
+ config.modRequest.platformProjectRoot,
117
+ 'app',
118
+ 'proguard-rules.pro'
119
+ );
120
+
121
+ const contents = await fs.promises.readFile(proguardRulesFile, 'utf8');
122
+ const newContents = updateAndroidProguardRules(contents, '', 'overwrite');
123
+ if (contents !== newContents) {
77
124
  await fs.promises.writeFile(proguardRulesFile, newContents);
78
125
  }
79
126
  return config;
@@ -85,34 +132,25 @@ export const withAndroidProguardRules: ConfigPlugin<PluginConfigType> = (config,
85
132
  * Update `newProguardRules` to original `proguard-rules.pro` contents if needed
86
133
  *
87
134
  * @param contents the original `proguard-rules.pro` contents
88
- * @param newProguardRules new proguard rules to add. If the value is null, the generated proguard rules will be cleanup
89
- * @returns return string when results is updated or return null when nothing changed.
135
+ * @param newProguardRules new proguard rules to add. If the value is null, the returned value will be original `contents`.
136
+ * @returns return updated contents
90
137
  */
91
138
  export function updateAndroidProguardRules(
92
139
  contents: string,
93
- newProguardRules: string | null
94
- ): string | null {
95
- const mergeTag = 'expo-build-properties';
96
-
97
- let mergeResults: MergeResults;
98
- if (newProguardRules) {
99
- mergeResults = mergeContents({
100
- tag: mergeTag,
101
- src: contents,
102
- newSrc: newProguardRules,
103
- anchor: /^/,
104
- offset: contents.length,
105
- comment: '#',
106
- });
107
- } else {
108
- mergeResults = removeContents({
109
- tag: mergeTag,
110
- src: contents,
111
- });
140
+ newProguardRules: string | null,
141
+ updateMode: 'append' | 'overwrite'
142
+ ): string {
143
+ if (newProguardRules == null) {
144
+ return contents;
112
145
  }
113
146
 
114
- if (mergeResults.didMerge || mergeResults.didClear) {
115
- return mergeResults.contents;
147
+ const options = { tag: 'expo-build-properties', commentPrefix: '#' };
148
+ let newContents = contents;
149
+ if (updateMode === 'overwrite') {
150
+ newContents = purgeContents(contents, options);
151
+ }
152
+ if (newProguardRules !== '') {
153
+ newContents = appendContents(newContents, newProguardRules, options);
116
154
  }
117
- return null;
155
+ return newContents;
118
156
  }
@@ -0,0 +1,73 @@
1
+ /**
2
+ * A set of helper functions to update file contents. This is a simplified version to the config-plugins internal generateCode functions.
3
+ */
4
+
5
+ import assert from 'assert';
6
+
7
+ /**
8
+ * Options for a generated section
9
+ */
10
+ export interface SectionOptions {
11
+ /**
12
+ * A meaningful tag to differentiate the generated section
13
+ */
14
+ tag: string;
15
+
16
+ /**
17
+ * Defines comment for the generated code.
18
+ * E.g. '//' for js code or '#' for shell script
19
+ */
20
+ commentPrefix: string;
21
+ }
22
+
23
+ /**
24
+ * Append new contents to src with generated section comments
25
+ *
26
+ * If there is already a generated section, this function will append the new contents at the end of the section.
27
+ * Otherwise, this function will generate a new section at the end of file.
28
+ */
29
+ export function appendContents(src: string, contents: string, sectionOptions: SectionOptions) {
30
+ const start = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, true);
31
+ const end = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, false);
32
+
33
+ const regex = new RegExp(`(\\n${escapeRegExp(end)})`, 'gm');
34
+ const match = regex.exec(src);
35
+ if (match) {
36
+ assert(src.indexOf(start) >= 0);
37
+ return src.replace(regex, `\n${contents}$1`);
38
+ } else {
39
+ const sectionedContents = `${start}\n${contents}\n${end}`;
40
+ return `${src}\n${sectionedContents}`;
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Purge a generated section
46
+ */
47
+ export function purgeContents(src: string, sectionOptions: SectionOptions) {
48
+ const start = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, true);
49
+ const end = createSectionComment(sectionOptions.commentPrefix, sectionOptions.tag, false);
50
+
51
+ const regex = new RegExp(`\\n${escapeRegExp(start)}\\n[\\s\\S]*\\n${escapeRegExp(end)}`, 'gm');
52
+ return src.replace(regex, '');
53
+ }
54
+
55
+ /**
56
+ * Create comments for generated section
57
+ */
58
+ function createSectionComment(commentPrefix: string, tag: string, isBeginComment: boolean) {
59
+ if (isBeginComment) {
60
+ return `${commentPrefix} @generated begin ${tag} - expo prebuild (DO NOT MODIFY)`;
61
+ } else {
62
+ return `${commentPrefix} @generated end ${tag}`;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Escape a string for regular expression
68
+ *
69
+ * Reference from {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping}
70
+ */
71
+ function escapeRegExp(input: string) {
72
+ return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
73
+ }
package/src/ios.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { IOSConfig, ConfigPlugin, withXcodeProject, XcodeProject } from '@expo/config-plugins';
1
+ import { IOSConfig, ConfigPlugin, withXcodeProject, XcodeProject } from 'expo/config-plugins';
2
2
 
3
3
  import type { PluginConfigType } from './pluginConfig';
4
4
 
@@ -13,7 +13,7 @@ const EXPO_SDK_MINIMAL_SUPPORTED_VERSIONS = {
13
13
  kotlinVersion: '1.6.10',
14
14
  },
15
15
  ios: {
16
- deploymentTarget: '12.0',
16
+ deploymentTarget: '13.0',
17
17
  },
18
18
  };
19
19
 
@@ -1,6 +1,10 @@
1
- import type { ConfigPlugin } from '@expo/config-plugins';
1
+ import type { ConfigPlugin } from 'expo/config-plugins';
2
2
 
3
- import { withAndroidBuildProperties, withAndroidProguardRules } from './android';
3
+ import {
4
+ withAndroidBuildProperties,
5
+ withAndroidProguardRules,
6
+ withAndroidPurgeProguardRulesOnce,
7
+ } from './android';
4
8
  import { withIosBuildProperties, withIosDeploymentTarget } from './ios';
5
9
  import { PluginConfigType, validateConfig } from './pluginConfig';
6
10
 
@@ -14,7 +18,16 @@ export const withBuildProperties: ConfigPlugin<PluginConfigType> = (config, prop
14
18
  const pluginConfig = validateConfig(props || {});
15
19
 
16
20
  config = withAndroidBuildProperties(config, pluginConfig);
21
+
17
22
  config = withAndroidProguardRules(config, pluginConfig);
23
+ // Assuming `withBuildProperties` could be called multiple times from different config-plugins,
24
+ // the `withAndroidProguardRules` always appends new rules by default.
25
+ // That is not ideal if we leave generated contents from previous prebuild there.
26
+ // The `withAndroidPurgeProguardRulesOnce` is for this purpose and it would only run once in prebuilding phase.
27
+ //
28
+ // plugins order matter: the later one would run first
29
+ config = withAndroidPurgeProguardRulesOnce(config);
30
+
18
31
  config = withIosBuildProperties(config, pluginConfig);
19
32
  config = withIosDeploymentTarget(config, pluginConfig);
20
33