diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f43a15fa..47a7d71a 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -43,6 +43,7 @@ jobs:
react-native-siri-shortcut,
react-native-google-cast,
react-native-pdf,
+ managed-config,
]
name: Test ${{ matrix.package }} on Node ${{ matrix.node }}
steps:
diff --git a/apps/app/app.json b/apps/app/app.json
index 4146c461..63883c0a 100644
--- a/apps/app/app.json
+++ b/apps/app/app.json
@@ -33,7 +33,21 @@
"@config-plugins/react-native-adjust",
"@config-plugins/react-native-callkeep",
"@config-plugins/android-jsc-intl",
- "expo-localization"
+ "expo-localization",
+ [
+ "@config-plugins/managed-config",
+ {
+ "restrictions": [
+ {
+ "key": "test_key",
+ "title": "Test Title",
+ "restrictionType": "string",
+ "description": "Testing managed config plugin",
+ "defaultValue": "Hello world"
+ }
+ ]
+ }
+ ]
]
}
}
diff --git a/packages/managed-config/.eslintrc.js b/packages/managed-config/.eslintrc.js
new file mode 100644
index 00000000..463a4e1f
--- /dev/null
+++ b/packages/managed-config/.eslintrc.js
@@ -0,0 +1,2 @@
+// @generated by expo-module-scripts
+module.exports = require("expo-module-scripts/eslintrc.base.js");
diff --git a/packages/managed-config/README.md b/packages/managed-config/README.md
new file mode 100644
index 00000000..2a22839b
--- /dev/null
+++ b/packages/managed-config/README.md
@@ -0,0 +1,94 @@
+# @config-plugins/managed-config
+
+Expo Config Plugin to auto-configure [Managed Configurations](https://developer.android.com/work/managed-configurations) on Android, facilitating the integration with Mobile Device Management (MDM) solutions like [Microsoft Intune](https://www.microsoft.com/en-us/mem/intune) and [VMware Workspace ONE](https://www.vmware.com/products/workspace-one.html). This allows enterprises to remotely manage and configure apps. It can be used with libraries such as [react-native-emm](https://github.com/mattermost/react-native-emm) or [react-native-mdm](https://github.com/robinpowered/react-native-mdm) to allow your app to be managed by an MDM solution.
+
+## Expo Installation
+
+> Note: This package cannot be utilized in the "Expo Go" app due to its reliance on custom native code, as detailed in Expo's [customizing the build process guide](https://docs.expo.io/workflow/customizing/).
+
+First, install the package using yarn, npm, or [`npx expo install`](https://docs.expo.io/workflow/expo-cli/#expo-install) for better version compatibility:
+
+```sh
+npm install @config-plugins/managed-config
+```
+
+Or
+
+```sh
+yarn add @config-plugins/managed-config
+```
+
+After installation, add the [config plugin](https://docs.expo.io/guides/config-plugins/) to the [`plugins`](https://docs.expo.io/versions/latest/config/app/#plugins) array in your project's `app.json` or `app.config.js`:
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "@config-plugins/managed-config",
+ {
+ "restrictions": [
+ {
+ "key": "test_key",
+ "title": "Test Title",
+ "restrictionType": "string",
+ "description": "A test description",
+ "defaultValue": "Default value"
+ }
+ // Add more restrictions as needed
+ ]
+ }
+ ]
+ ]
+ }
+}
+```
+
+Lastly, you'll need to rebuild your app to apply these changes. Refer to Expo's guide on ["Adding custom native code"](https://docs.expo.io/workflow/customizing/) for instructions on rebuilding your app.
+
+## API
+
+The plugin is configured through the `plugins` section of your `app.json` or `app.config.js`. The configuration object accepts a `restrictions` array, where each object represents a managed configuration that your app supports. The structure for each restriction object is as follows:
+
+### Properties
+
+- **`key`** (string): A unique identifier for the restriction.
+- **`title`** (string): A human-readable title for the restriction, used by the MDM solution to display to admins.
+- **`restrictionType`** (string): The type of the restriction. Possible values include `bool`, `string`, `integer`, `choice`, `multi-select`, `hidden`, `bundle`, and `bundle_array`.
+- **`description`** (optional, string): A detailed description of the restriction.
+- **`defaultValue`** (optional, string | boolean | number | string[] | null): The default value for the restriction. The type depends on the `restrictionType`.
+- **`entries`** (optional, string[]): Applicable to `choice` and `multi-select` types. An array of human-readable options.
+- **`entryValues`** (optional, string[]): Applicable to `choice` and `multi-select` types. An array of values corresponding to each option in `entries`.
+
+### Example:
+
+Adding a configuration for a "dark mode" setting that allows users to choose between 'enabled' and 'disabled'.
+
+```json
+{
+ "expo": {
+ "plugins": [
+ [
+ "@config-plugins/managed-config",
+ {
+ "restrictions": [
+ {
+ "key": "dark_mode",
+ "title": "Dark Mode",
+ "restrictionType": "choice",
+ "entries": ["Enabled", "Disabled"],
+ "entryValues": ["enabled", "disabled"],
+ "defaultValue": "disabled",
+ "description": "Allow users to select dark mode preference"
+ }
+ ]
+ }
+ ]
+ ]
+ }
+}
+```
+
+This plugin simplifies the process of making your app manageable through MDM solutions by automating the setup of managed configurations.
+
+For further details or support, check the [official documentation](https://developer.android.com/work/managed-configurations) on managed configurations.
diff --git a/packages/managed-config/app.plugin.js b/packages/managed-config/app.plugin.js
new file mode 100644
index 00000000..944d9787
--- /dev/null
+++ b/packages/managed-config/app.plugin.js
@@ -0,0 +1 @@
+module.exports = require("./build/withManagedConfig");
diff --git a/packages/managed-config/jest.config.js b/packages/managed-config/jest.config.js
new file mode 100644
index 00000000..d52d0349
--- /dev/null
+++ b/packages/managed-config/jest.config.js
@@ -0,0 +1 @@
+module.exports = require("expo-module-scripts/jest-preset-plugin");
diff --git a/packages/managed-config/package.json b/packages/managed-config/package.json
new file mode 100644
index 00000000..36362773
--- /dev/null
+++ b/packages/managed-config/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@config-plugins/managed-config",
+ "version": "0.0.0",
+ "description": "Config plugin for managed-config package",
+ "main": "build/withManagedConfig.js",
+ "types": "build/withManagedConfig.d.ts",
+ "sideEffects": false,
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/expo/config-plugins.git",
+ "directory": "packages/managed-config"
+ },
+ "scripts": {
+ "build": "expo-module build",
+ "clean": "expo-module clean",
+ "lint": "expo-module lint",
+ "test": "expo-module test",
+ "prepare": "expo-module prepare",
+ "prepublishOnly": "expo-module prepublishOnly",
+ "expo-module": "expo-module"
+ },
+ "keywords": [
+ "react",
+ "expo",
+ "config-plugins",
+ "prebuild",
+ "managed-config",
+ "expo-50"
+ ],
+ "peerDependencies": {
+ "expo": "^50"
+ },
+ "devDependencies": {
+ "expo-module-scripts": "^3.4.1"
+ }
+}
diff --git a/packages/managed-config/src/__tests__/generateAppRestrictionsContent.test.ts b/packages/managed-config/src/__tests__/generateAppRestrictionsContent.test.ts
new file mode 100644
index 00000000..3c0c6bff
--- /dev/null
+++ b/packages/managed-config/src/__tests__/generateAppRestrictionsContent.test.ts
@@ -0,0 +1,438 @@
+import { AppRestriction } from "../appRestrictionTypes";
+import { generateAppRestrictionsContent } from "../generateAppRestrictionsContent";
+
+describe("generateAppRestrictionsContent", () => {
+ describe("string", () => {
+ it('correctly generates XML content for "string" type restriction with all possible fields', () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_nickname",
+ title: "User Nickname",
+ restrictionType: "string",
+ description: "The nickname of the user",
+ defaultValue: "ExpoFan",
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+
+ it("correctly generates XML content for 'string' type restriction with only required fields", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_nickname",
+ title: "User Nickname",
+ restrictionType: "string",
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+ });
+
+ describe("integer", () => {
+ it("correctly generates XML content for 'integer' type restriction with all possible fields", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_age",
+ title: "User Age",
+ restrictionType: "integer",
+ description: "The age of the user",
+ defaultValue: 25,
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+
+ it("correctly generates XML content for 'integer' type restriction with only required fields", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_age",
+ title: "User Age",
+ restrictionType: "integer",
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+ });
+
+ describe("bool", () => {
+ it("correctly generates XML content for 'bool' type restriction with all possible fields", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_is_active",
+ title: "User Active",
+ restrictionType: "bool",
+ description: "Whether the user is active or not",
+ defaultValue: true,
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+
+ it("correctly generates XML content for 'bool' type restriction with only required fields", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_is_active",
+ title: "User Active",
+ restrictionType: "bool",
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+ });
+
+ describe("hidden", () => {
+ it("correctly generates XML content for 'hidden' type restriction with string type", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_secret",
+ title: "User Secret",
+ restrictionType: "hidden",
+ defaultValue: "4a5678",
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+
+ it("correctly generates XML content for 'hidden' type restriction with integer type", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_secret",
+ title: "User Secret",
+ restrictionType: "hidden",
+ defaultValue: false,
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+
+ it("correctly generates XML content for 'hidden' type restriction with bool type", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_secret",
+ title: "User Secret",
+ restrictionType: "hidden",
+ defaultValue: 123456,
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+ });
+
+ describe("bundle and bundle_array", () => {
+ it("correctly generates XML content for 'bundle' type restriction with nested restrictions", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_settings",
+ title: "User Settings",
+ restrictionType: "bundle",
+ restrictions: [
+ {
+ key: "user_theme",
+ title: "User Theme",
+ restrictionType: "string",
+ defaultValue: "dark",
+ },
+ {
+ key: "notifications_enabled",
+ title: "Notifications Enabled",
+ restrictionType: "bool",
+ defaultValue: true,
+ },
+ ],
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+
+ it("correctly generates XML content for 'bundle_array' type restriction with nested restrictions", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_accounts",
+ title: "User Accounts",
+ restrictionType: "bundle_array",
+ restrictions: [
+ {
+ key: "account_1",
+ title: "Account 1",
+ restrictionType: "bundle",
+ restrictions: [
+ {
+ key: "username",
+ title: "Username",
+ restrictionType: "string",
+ defaultValue: "user1",
+ },
+ {
+ key: "active",
+ title: "Active",
+ restrictionType: "bool",
+ defaultValue: true,
+ },
+ ],
+ },
+ {
+ key: "account_2",
+ title: "Account 2",
+ restrictionType: "bundle",
+ restrictions: [
+ {
+ key: "username",
+ title: "Username",
+ restrictionType: "string",
+ defaultValue: "user2",
+ },
+ {
+ key: "active",
+ title: "Active",
+ restrictionType: "bool",
+ defaultValue: false,
+ },
+ ],
+ },
+ ],
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+
+
+
+
+
+
+
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+ });
+
+ describe("choice", () => {
+ it("correctly generates XML content for 'choice' type restriction with all fields", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_color_preference",
+ title: "User Color Preference",
+ restrictionType: "choice",
+ description: "The preferred color theme for the user interface",
+ entries: ["Light", "Dark", "System default"],
+ entryValues: ["light", "dark", "default"],
+ defaultValue: "default",
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+ });
+
+ describe("multi-select", () => {
+ it("correctly generates XML content for 'multi-select' type restriction with all fields", () => {
+ const restrictions: AppRestriction[] = [
+ {
+ key: "user_interests",
+ title: "User Interests",
+ restrictionType: "multi-select",
+ description: "The interests selected by the user",
+ entries: ["Technology", "Sports", "Arts", "Science"],
+ entryValues: ["tech", "sports", "arts", "science"],
+ defaultValue: ["tech", "science"],
+ },
+ ];
+
+ const expectedXmlContent = `
+
+
+`.trim();
+
+ const generatedXmlContent =
+ generateAppRestrictionsContent(restrictions).trim();
+ expect(generatedXmlContent).toEqual(expectedXmlContent);
+ });
+ });
+});
diff --git a/packages/managed-config/src/appRestrictionTypes.ts b/packages/managed-config/src/appRestrictionTypes.ts
new file mode 100644
index 00000000..6d91e782
--- /dev/null
+++ b/packages/managed-config/src/appRestrictionTypes.ts
@@ -0,0 +1,54 @@
+type BaseRestriction = {
+ key: string;
+ title: string;
+ description?: string;
+ defaultValue?: boolean | number | string | string[]; // Adjust based on restrictionType requirements
+};
+
+type BooleanRestriction = BaseRestriction & {
+ restrictionType: "bool";
+ defaultValue?: boolean;
+};
+
+type StringRestriction = BaseRestriction & {
+ restrictionType: "string";
+ defaultValue?: string;
+};
+
+type IntegerRestriction = BaseRestriction & {
+ restrictionType: "integer";
+ defaultValue?: number;
+};
+
+type BundleRestriction = BaseRestriction & {
+ restrictionType: "bundle" | "bundle_array";
+ restrictions: AppRestriction[];
+};
+
+type ChoiceRestriction = BaseRestriction & {
+ restrictionType: "choice";
+ entries: string[];
+ entryValues: string[];
+ defaultValue?: string;
+};
+
+type MultiSelectRestriction = BaseRestriction & {
+ restrictionType: "multi-select";
+ entries: string[];
+ entryValues: string[];
+ defaultValue?: string[];
+};
+
+type HiddenRestriction = BaseRestriction & {
+ restrictionType: "hidden";
+ defaultValue: boolean | number | string; // Hidden must have a defaultValue
+};
+
+export type AppRestriction =
+ | BooleanRestriction
+ | StringRestriction
+ | IntegerRestriction
+ | ChoiceRestriction
+ | MultiSelectRestriction
+ | HiddenRestriction
+ | BundleRestriction;
diff --git a/packages/managed-config/src/constants.ts b/packages/managed-config/src/constants.ts
new file mode 100644
index 00000000..86509274
--- /dev/null
+++ b/packages/managed-config/src/constants.ts
@@ -0,0 +1,3 @@
+export const AppRestrictionsFileNameRoot = "app_restrictions";
+export const AppRestrictionsFileExtension = "xml";
+export const AppRestrictionsFileName = `${AppRestrictionsFileNameRoot}.${AppRestrictionsFileExtension}`;
diff --git a/packages/managed-config/src/generateAppRestrictionsContent.ts b/packages/managed-config/src/generateAppRestrictionsContent.ts
new file mode 100644
index 00000000..f6fb69b2
--- /dev/null
+++ b/packages/managed-config/src/generateAppRestrictionsContent.ts
@@ -0,0 +1,62 @@
+import { AppRestriction } from "./appRestrictionTypes";
+
+export const generateAppRestrictionsContent = (
+ restrictions: AppRestriction[]
+): string => {
+ const content = getConfigContent(restrictions);
+ return `
+
+${content}
+`;
+};
+
+const getConfigContent = (restrictions: AppRestriction[]) => {
+ return restrictions.map(createAppRestrictionXML).join("\n");
+};
+
+const createAppRestrictionXML = (restriction: AppRestriction): string => {
+ let xml = `";
+ xml += "\n" + getConfigContent(restriction.restrictions) + "\n";
+ xml += "";
+ } else {
+ xml += "/>";
+ }
+
+ return xml;
+};
diff --git a/packages/managed-config/src/index.ts b/packages/managed-config/src/index.ts
new file mode 100644
index 00000000..53f4c229
--- /dev/null
+++ b/packages/managed-config/src/index.ts
@@ -0,0 +1,16 @@
+import { ConfigPlugin } from "@expo/config-plugins";
+
+import { AppRestriction } from "./appRestrictionTypes";
+import { withAppRestrictions } from "./withAppRestrictionsFile";
+
+export type WithEMMConfig = {
+ restrictions: AppRestriction[];
+};
+
+const withEmm: ConfigPlugin = (config, { restrictions }) => {
+ config = withAppRestrictions(config, { restrictions });
+
+ return config;
+};
+
+export default withEmm;
diff --git a/packages/managed-config/src/withAppRestrictionsFile.ts b/packages/managed-config/src/withAppRestrictionsFile.ts
new file mode 100644
index 00000000..a31558ab
--- /dev/null
+++ b/packages/managed-config/src/withAppRestrictionsFile.ts
@@ -0,0 +1,65 @@
+import {
+ ConfigPlugin,
+ withAndroidManifest,
+ withDangerousMod,
+ AndroidConfig,
+} from "@expo/config-plugins";
+import fs from "fs";
+import path from "path";
+
+import { AppRestriction } from "./appRestrictionTypes";
+import { generateAppRestrictionsContent } from "./generateAppRestrictionsContent";
+
+const appRestrictionsFileNameRoot = "app_restrictions";
+const appRestrictionsFileExtension = "xml";
+const appRestrictionsFileName = `${appRestrictionsFileNameRoot}.${appRestrictionsFileExtension}`;
+
+const withAppRestrictionsConfigFile: ConfigPlugin<{
+ restrictions: AppRestriction[];
+}> = (config, { restrictions }) => {
+ return withDangerousMod(config, [
+ "android",
+ (config) => {
+ const folder = path.join(
+ config.modRequest.platformProjectRoot,
+ `app/src/main/res/xml`
+ );
+ fs.mkdirSync(folder, { recursive: true });
+ fs.writeFileSync(
+ path.join(folder, appRestrictionsFileName),
+ generateAppRestrictionsContent(restrictions),
+ {
+ encoding: "utf8",
+ }
+ );
+ return config;
+ },
+ ]);
+};
+
+export const withAppRestrictions: ConfigPlugin<{
+ restrictions: AppRestriction[];
+}> = (config, props) => {
+ if (
+ typeof props.restrictions === "object" &&
+ props.restrictions.length === 0
+ ) {
+ // if restrictions is an empty array, skip...
+ return config;
+ }
+
+ config = withAppRestrictionsConfigFile(config, props);
+ return withAndroidManifest(config, (config) => {
+ const application = AndroidConfig.Manifest.getMainApplicationOrThrow(
+ config.modResults
+ );
+ application["meta-data"] = application["meta-data"] || [];
+ application["meta-data"].push({
+ $: {
+ "android:name": "android.content.APP_RESTRICTIONS",
+ "android:resource": `@xml/${appRestrictionsFileNameRoot}`,
+ },
+ });
+ return config;
+ });
+};
diff --git a/packages/managed-config/src/withManagedConfig.ts b/packages/managed-config/src/withManagedConfig.ts
new file mode 100644
index 00000000..f5598f08
--- /dev/null
+++ b/packages/managed-config/src/withManagedConfig.ts
@@ -0,0 +1,78 @@
+import {
+ ConfigPlugin,
+ createRunOncePlugin,
+ withDangerousMod,
+ withAndroidManifest,
+ AndroidConfig,
+} from "@expo/config-plugins";
+import fs from "fs";
+import path from "path";
+
+import { AppRestriction } from "./appRestrictionTypes";
+import {
+ AppRestrictionsFileName,
+ AppRestrictionsFileNameRoot,
+} from "./constants";
+import { generateAppRestrictionsContent } from "./generateAppRestrictionsContent";
+
+const pkg = {
+ // Prevent this plugin from being run more than once.
+ // This pattern enables users to safely migrate off of this
+ // out-of-tree `@config-plugins/managed-config` to a future
+ // upstream plugin in `managed-config`
+ name: "managed-config",
+ // Indicates that this plugin is dangerously linked to a module,
+ // and might not work with the latest version of that module.
+ version: "UNVERSIONED",
+};
+
+const withAppRestrictionsConfigFile: ConfigPlugin<{
+ restrictions: AppRestriction[];
+}> = (config, { restrictions }) => {
+ return withDangerousMod(config, [
+ "android",
+ (config) => {
+ const folder = path.join(
+ config.modRequest.platformProjectRoot,
+ `app/src/main/res/xml`
+ );
+ fs.mkdirSync(folder, { recursive: true });
+ fs.writeFileSync(
+ path.join(folder, AppRestrictionsFileName),
+ generateAppRestrictionsContent(restrictions),
+ {
+ encoding: "utf8",
+ }
+ );
+ return config;
+ },
+ ]);
+};
+
+const withManagedConfig: ConfigPlugin<{
+ restrictions: AppRestriction[];
+}> = (config, props) => {
+ if (
+ typeof props.restrictions === "object" &&
+ props.restrictions.length === 0
+ ) {
+ return config;
+ }
+
+ config = withAppRestrictionsConfigFile(config, props);
+ return withAndroidManifest(config, (config) => {
+ const application = AndroidConfig.Manifest.getMainApplicationOrThrow(
+ config.modResults
+ );
+ application["meta-data"] = application["meta-data"] || [];
+ application["meta-data"].push({
+ $: {
+ "android:name": "android.content.APP_RESTRICTIONS",
+ "android:resource": `@xml/${AppRestrictionsFileNameRoot}`,
+ },
+ });
+ return config;
+ });
+};
+
+export default createRunOncePlugin(withManagedConfig, pkg.name, pkg.version);
diff --git a/packages/managed-config/tsconfig.json b/packages/managed-config/tsconfig.json
new file mode 100644
index 00000000..901571ed
--- /dev/null
+++ b/packages/managed-config/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "expo-module-scripts/tsconfig.plugin",
+ "compilerOptions": {
+ "outDir": "./build"
+ },
+ "include": ["./src"],
+ "exclude": ["**/__mocks__/*", "**/__tests__/*"]
+}