diff --git a/packages/schemer/LICENSE b/packages/schemer/LICENSE deleted file mode 100644 index 339cf3e66d..0000000000 --- a/packages/schemer/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2017-present, 650 Industries - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/schemer/README.md b/packages/schemer/README.md index 84663dda6e..b539b4dd34 100644 --- a/packages/schemer/README.md +++ b/packages/schemer/README.md @@ -1,110 +1,3 @@ - -

-đź‘‹ Welcome to
@expo/schemer -

+# `@expo/schemer` -

A Schema validation library for Expo.

- -

- - - - - -

- - - -Details can be found here: -* https://paper.dropbox.com/doc/Expo-Schema-Validation-Library-mQU07rRejSnEe4Vf5dkcS - -## Usage - -### Usage with XDL - -```javascript -import { ExpSchema } from '@expo/xdl'; -import { getConfig } from '@expo/config'; -import Schemer from '@expo/schemer'; -const { exp } = getConfig(projectRoot); -const schema = await getSchemaAsync(exp.sdkVersion); -const validator = new Schemer(schema); -validator.validateName('Wilson Zhao'); -validator.validateAssets(exp); -``` - -### Schema-only validation - -```javascript -const validator = new Schemer(require('schema.json')); -try { - await validator.validateSchemaAsync(require('data.json')); -} catch (e) { - console.error(e); -} -``` - -### Validating a property - -```javascript -const validator = new Schemer(require('schema.json')); -await validator.validateName('Wilson Zhao'); -``` - -## Description - -Schemer takes in a custom JSON Schema and uses it to validate various data. - -Under the hood, it uses Ajv (https://github.com/epoberezkin/ajv) as the Javascript engine for basic schema validation. -However, each subschema also contains a custom meta tag, which can be parsed for further "manual" validation. As of now, Schemer supports manual validation for assets: - -```javascript -{ - meta: - { - asset, - contentType, //mime type - dimensions: {width, height}, - square, - - // For custom error messages and docs - regexHuman, - autogenerated, - notHuman - } -} -``` - -All errors can be accessed in `this.errors`, which has a getter function that combines Ajv JSON Schema errors with custom meta/asset validation errors into a unified array of `ValidationErrors`. -If they exist, the errors are thrown at the end of each public-facing function. - -All public-facing functions are async functions because asset validation has to be async (accessing the file-system or making a web request). - -## API - -#### new Schemer(Object JSON Schema, Object options) -> Object - -#### .validateSchemaAsync(Object data) -> Promise - -Returns a promise that resolve to `true` if the data is conforms to the schema. Otherwise, it rejects and throws an array of `ValidationError`s. - -#### .validateAssetsAsync(Object data) -> Promise - -Returns a promise that resolve to `true` if the data is conforms to the additional validation steps found in each meta tag. For example, it will download an asset and read the header of the file to see if it is a certain content type. -Otherwise, it rejects and throws an array of `ValidationError`s. - -#### .validateAll(Object data) -> Promise - -Runs both `.validateSchemaAsync` and `.validateAssetsAsync`. -Returns a promise that resolve to `true` if the data passes both functions. Otherwise, it rejects and throws an array of `ValidationError`s. - -#### .validateProperty(String fieldPath, Object data) -> Promise - -Extracts the subSchema for the given field path and validates the data against it. Also checks for the meta tag. -Returns a promise that resolve to `true` if the data conforms to the subschema. Otherwise, it rejects and throws an array of `ValidationError`s. - -#### .errors - -Contains an array of ValidationErrors - -#### new ValidationError({errorCode, fieldPath, message, data, meta}) -> Object +This package has [moved to the `expo/expo` repo](https://github.com/expo/expo/tree/main/packages/%40expo/schemer). diff --git a/packages/schemer/__tests__/__snapshots__/network-test.ts.snap b/packages/schemer/__tests__/__snapshots__/network-test.ts.snap deleted file mode 100644 index 633dc11d0f..0000000000 --- a/packages/schemer/__tests__/__snapshots__/network-test.ts.snap +++ /dev/null @@ -1,21 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Remote Remote icon dimensions wrong 1`] = ` -Array [ - Object { - "data": "https://httpbin.org/image/png", - "errorCode": "INVALID_DIMENSIONS", - "fieldPath": "icon", - "message": "'icon' should have dimensions 101x100, but the file at 'https://httpbin.org/image/png' has dimensions 100x100", - "meta": Object { - "asset": true, - "contentTypePattern": "^image/png$", - "dimensions": Object { - "height": 100, - "width": 101, - }, - }, - "name": "ValidationError", - }, -] -`; diff --git a/packages/schemer/__tests__/__snapshots__/test.ts.snap b/packages/schemer/__tests__/__snapshots__/test.ts.snap deleted file mode 100644 index 601b840775..0000000000 --- a/packages/schemer/__tests__/__snapshots__/test.ts.snap +++ /dev/null @@ -1,196 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Holistic Unit Test bad example app.json - invalid path for app icon 1`] = ` -Array [ - Object { - "data": "./unknown/path.png", - "errorCode": "INVALID_ASSET_URI", - "fieldPath": "icon", - "message": "cannot access file at './unknown/path.png'", - "meta": Object { - "asset": true, - "bareWorkflow": "To change your app's icon, edit or replace the files in \`ios//Assets.xcassets/AppIcon.appiconset\` (we recommend using Xcode), and \`android/app/src/main/res/mipmap-\`. Be sure to follow the guidelines for each platform ([iOS](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/), [Android 7.1 and below](https://material.io/design/iconography/#icon-treatments), and [Android 8+](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)) and to provide your new icon in each existing size.", - "contentTypeHuman": ".png image", - "contentTypePattern": "^image/png$", - "square": true, - }, - "name": "ValidationError", - }, -] -`; - -exports[`Holistic Unit Test bad example app.json schema 1`] = ` -Array [ - Object { - "data": Object { - "asdfasdfandroid": Object { - "package": "com.yourcompany.yourappname", - }, - "icon": "DoesNotExist.png", - "orientaasdfasdftion": "portrait", - "sdkVersion": "17.0.0abad", - "slug": "1*@)#($*@)(#$*)", - }, - "errorCode": "SCHEMA_MISSING_REQUIRED_PROPERTY", - "fieldPath": "", - "message": "is missing required property 'name'", - "meta": undefined, - "name": "ValidationError", - }, - Object { - "data": Object { - "asdfasdfandroid": Object { - "package": "com.yourcompany.yourappname", - }, - "icon": "DoesNotExist.png", - "orientaasdfasdftion": "portrait", - "sdkVersion": "17.0.0abad", - "slug": "1*@)#($*@)(#$*)", - }, - "errorCode": "SCHEMA_ADDITIONAL_PROPERTY", - "fieldPath": "", - "message": "should NOT have additional property 'orientaasdfasdftion'", - "meta": undefined, - "name": "ValidationError", - }, - Object { - "data": Object { - "asdfasdfandroid": Object { - "package": "com.yourcompany.yourappname", - }, - "icon": "DoesNotExist.png", - "orientaasdfasdftion": "portrait", - "sdkVersion": "17.0.0abad", - "slug": "1*@)#($*@)(#$*)", - }, - "errorCode": "SCHEMA_ADDITIONAL_PROPERTY", - "fieldPath": "", - "message": "should NOT have additional property 'asdfasdfandroid'", - "meta": undefined, - "name": "ValidationError", - }, - Object { - "data": "1*@)#($*@)(#$*)", - "errorCode": "SCHEMA_INVALID_PATTERN", - "fieldPath": "slug", - "message": "'slug' must match pattern \\"^[a-zA-Z0-9_\\\\-]+$\\"", - "meta": undefined, - "name": "ValidationError", - }, -] -`; - -exports[`Holistic Unit Test bad example app.json schema with field with not 1`] = ` -Array [ - Object { - "data": "1.0", - "errorCode": "SCHEMA_INVALID_NOT", - "fieldPath": "runtimeVersion", - "message": "'runtimeVersion' should be not a decimal ending in a 0.", - "meta": Object { - "notHuman": "Not a decimal ending in a 0.", - }, - "name": "ValidationError", - }, -] -`; - -exports[`Image Validation errors for webp images 1`] = ` -Array [ - Object { - "data": "./files/webp.webp", - "errorCode": "INVALID_CONTENT_TYPE", - "fieldPath": "Android.adaptiveIcon.foregroundImage", - "message": "field 'Android.adaptiveIcon.foregroundImage' should point to .png image but the file at './files/webp.webp' has type webp", - "meta": Object { - "asset": true, - "contentTypeHuman": ".png image", - "contentTypePattern": "^image/png$", - "square": true, - }, - "name": "ValidationError", - }, - Object { - "data": "./files/webp.webp", - "errorCode": "NOT_SQUARE", - "fieldPath": "Android.adaptiveIcon.foregroundImage", - "message": "image should be square, but the file at './files/webp.webp' has dimensions 320x214", - "meta": Object { - "asset": true, - "contentTypeHuman": ".png image", - "contentTypePattern": "^image/png$", - "square": true, - }, - "name": "ValidationError", - }, -] -`; - -exports[`Image Validation errors when file extension and content do not match up 1`] = ` -Array [ - Object { - "data": "./files/secretlyPng.jpg", - "errorCode": "FILE_EXTENSION_MISMATCH", - "fieldPath": "icon", - "message": "the file extension should match the content, but the file extension is .jpg while the file content at './files/secretlyPng.jpg' is of type png", - "meta": Object { - "asset": true, - "bareWorkflow": "To change your app's icon, edit or replace the files in \`ios//Assets.xcassets/AppIcon.appiconset\` (we recommend using Xcode), and \`android/app/src/main/res/mipmap-\`. Be sure to follow the guidelines for each platform ([iOS](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/), [Android 7.1 and below](https://material.io/design/iconography/#icon-treatments), and [Android 8+](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)) and to provide your new icon in each existing size.", - "contentTypeHuman": ".png image", - "contentTypePattern": "^image/png$", - "square": true, - }, - "name": "ValidationError", - }, -] -`; - -exports[`Individual Unit Tests Error when data has an additional property 1`] = ` -Array [ - Object { - "data": Object { - "extraProperty": "extra", - }, - "errorCode": "SCHEMA_ADDITIONAL_PROPERTY", - "fieldPath": "", - "message": "should NOT have additional property 'extraProperty'", - "meta": undefined, - "name": "ValidationError", - }, -] -`; - -exports[`Individual Unit Tests Error when missing Required Property 1`] = ` -Array [ - Object { - "data": Object { - "noName": "", - }, - "errorCode": "SCHEMA_MISSING_REQUIRED_PROPERTY", - "fieldPath": "", - "message": "is missing required property 'name'", - "meta": undefined, - "name": "ValidationError", - }, -] -`; - -exports[`Manual Validation Individual Unit Tests Local icon dimensions wrong 1`] = ` -Array [ - Object { - "data": "./files/check.png", - "errorCode": "INVALID_DIMENSIONS", - "fieldPath": "icon", - "message": "'icon' should have dimensions 400x401, but the file at './files/check.png' has dimensions 512x512", - "meta": Object { - "asset": true, - "contentTypePattern": "^image/png$", - "dimensions": Object { - "height": 401, - "width": 400, - }, - }, - "name": "ValidationError", - }, -] -`; diff --git a/packages/schemer/__tests__/files/app.json b/packages/schemer/__tests__/files/app.json deleted file mode 100644 index d8c5702635..0000000000 --- a/packages/schemer/__tests__/files/app.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "test app", - "slug": "asdfasdf", - "sdkVersion": "17.0.0", - "orientation": "portrait", - "android": { - "package": "com.yourcompany.yourappname" - }, - "icon": "https://upload.wikimedia.org/wikipedia/commons/0/0f/Icon_Pinguin_2_512x512.png" -} diff --git a/packages/schemer/__tests__/files/bad.json b/packages/schemer/__tests__/files/bad.json deleted file mode 100644 index c4c733e844..0000000000 --- a/packages/schemer/__tests__/files/bad.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "sdkVersion": "17.0.0abad", - "orientaasdfasdftion": "portrait", - "asdfasdfandroid": { - "package": "com.yourcompany.yourappname" - }, - "slug": "1*@)#($*@)(#$*)", - "icon": "DoesNotExist.png" -} diff --git a/packages/schemer/__tests__/files/badwithnot.json b/packages/schemer/__tests__/files/badwithnot.json deleted file mode 100644 index d8f546c20d..0000000000 --- a/packages/schemer/__tests__/files/badwithnot.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "test app", - "slug": "asdfasdf", - "sdkVersion": "17.0.0", - "orientation": "portrait", - "android": { - "package": "com.yourcompany.yourappname" - }, - "runtimeVersion": "1.0", - "icon": "https://upload.wikimedia.org/wikipedia/commons/0/0f/Icon_Pinguin_2_512x512.png" -} diff --git a/packages/schemer/__tests__/files/check.png b/packages/schemer/__tests__/files/check.png deleted file mode 100644 index b54421b580..0000000000 Binary files a/packages/schemer/__tests__/files/check.png and /dev/null differ diff --git a/packages/schemer/__tests__/files/invalidAppIcon.json b/packages/schemer/__tests__/files/invalidAppIcon.json deleted file mode 100644 index 8067378062..0000000000 --- a/packages/schemer/__tests__/files/invalidAppIcon.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "test app", - "slug": "asdfasdf", - "sdkVersion": "17.0.0", - "orientation": "portrait", - "android": { - "package": "com.yourcompany.yourappname" - }, - "icon": "./unknown/path.png" -} diff --git a/packages/schemer/__tests__/files/schema.json b/packages/schemer/__tests__/files/schema.json deleted file mode 100644 index 6a300303f8..0000000000 --- a/packages/schemer/__tests__/files/schema.json +++ /dev/null @@ -1,1371 +0,0 @@ -{ - "schema": { - "definitions": { - "Android": { - "description": "Configuration that is specific to the Android platform.", - "type": "object", - "meta": { - "standaloneOnly": true - }, - "properties": { - "publishManifestPath": { - "description": "The manifest for the Android version of your app will be written to this path during publish.", - "type": "string", - "meta": { - "autogenerated": true - } - }, - "publishBundlePath": { - "description": "The bundle for the Android version of your app will be written to this path during publish.", - "type": "string", - "meta": { - "autogenerated": true - } - }, - "package": { - "description": "The package name for your Android standalone app. You make it up, but it needs to be unique on the Play Store. See [this StackOverflow question](http://stackoverflow.com/questions/6273892/android-package-name-convention).", - "type": "string", - "pattern": "^[a-zA-Z][a-zA-Z0-9\\_]*(\\.[a-zA-Z][a-zA-Z0-9\\_]*)+$", - "meta": { - "regexHuman": "Reverse DNS notation unique name for your app. Valid Android Application ID. For example, `com.example.app`, where `com.example` is our domain and `app` is our app. The name may only contain lowercase and uppercase letters (a-z, A-Z), numbers (0-9) and underscores (_), separated by periods (.). Each component of the name should start with a lowercase letter.", - "bareWorkflow": "This is set in `android/app/build.gradle` as `applicationId` as well as in your `AndroidManifest.xml` file (multiple places)." - } - }, - "versionCode": { - "description": "Version number required by Google Play. Increment by one for each release. Must be a positive integer. [Learn more](https://developer.android.com/studio/publish/versioning.html)", - "type": "integer", - "minimum": 0, - "maximum": 2100000000, - "meta": { - "bareWorkflow": "This is set in `android/app/build.gradle` as `versionCode`" - } - }, - "backgroundColor": { - "description": "The background color for your Android app, behind any of your React views. Overrides the top-level `backgroundColor` key if it is present.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`", - "bareWorkflow": "This is set in `android/app/src/main/AndroidManifest.xml` under `android:windowBackground`" - } - }, - "userInterfaceStyle": { - "description": "Configuration to force the app to always use the light or dark user-interface appearance, such as \"dark mode\", or make it automatically adapt to the system preferences. If not provided, defaults to `light`. Requires `expo-system-ui` be installed in your project to work on Android.", - "type": "string", - "fallback": "light", - "enum": ["light", "dark", "automatic"] - }, - "useNextNotificationsApi": { - "deprecated": true, - "description": "@deprecated A Boolean value that indicates whether the app should use the new notifications API.", - "type": "boolean", - "fallback": false - }, - "icon": { - "description": "Local path or remote URL to an image to use for your app's icon on Android. If specified, this overrides the top-level `icon` key. We recommend that you use a 1024x1024 png file (transparency is recommended for the Google Play Store). This icon will appear on the home screen and within the Expo app.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image", - "square": true - } - }, - "adaptiveIcon": { - "description": "Settings for an Adaptive Launcher Icon on Android. [Learn more](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)", - "type": "object", - "properties": { - "foregroundImage": { - "description": "Local path or remote URL to an image to use for your app's icon on Android. If specified, this overrides the top-level `icon` and the `android.icon` keys. Should follow the [specified guidelines](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive). This icon will appear on the home screen.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image", - "square": true - } - }, - "backgroundImage": { - "description": "Local path or remote URL to a background image for your app's Adaptive Icon on Android. If specified, this overrides the `backgroundColor` key. Must have the same dimensions as foregroundImage`, and has no effect if `foregroundImage` is not specified. Should follow the [specified guidelines](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive).", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image", - "square": true - } - }, - "backgroundColor": { - "description": "Color to use as the background for your app's Adaptive Icon on Android. Defaults to white, `#FFFFFF`. Has no effect if `foregroundImage` is not specified.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - } - }, - "additionalProperties": false - }, - "playStoreUrl": { - "description": "URL to your app on the Google Play Store, if you have deployed it there. This is used to link to your store page from your Expo project page if your app is public.", - "pattern": "^https://play\\.google\\.com/", - "example": "https://play.google.com/store/apps/details?id=host.exp.exponent", - "type": ["string"] - }, - "permissions": { - "description": "List of permissions used by the standalone app. \n\n To use ONLY the following minimum necessary permissions and none of the extras supported by Expo in a default managed app, set `permissions` to `[]`. The minimum necessary permissions do not require a Privacy Policy when uploading to Google Play Store and are: \n• receive data from Internet \n• view network connections \n• full network access \n• change your audio settings \n• prevent device from sleeping \n\n To use ALL permissions supported by Expo by default, do not specify the `permissions` key. \n\n To use the minimum necessary permissions ALONG with certain additional permissions, specify those extras in `permissions`, e.g.\n\n `[ \"CAMERA\", \"ACCESS_FINE_LOCATION\" ]`.\n\n You can specify the following permissions depending on what you need:\n\n- `ACCESS_COARSE_LOCATION`\n- `ACCESS_FINE_LOCATION`\n- `ACCESS_BACKGROUND_LOCATION`\n- `CAMERA`\n- `RECORD_AUDIO`\n- `READ_CONTACTS`\n- `WRITE_CONTACTS`\n- `READ_CALENDAR`\n- `WRITE_CALENDAR`\n- `READ_EXTERNAL_STORAGE`\n- `WRITE_EXTERNAL_STORAGE`\n- `USE_FINGERPRINT`\n- `USE_BIOMETRIC`\n- `WRITE_SETTINGS`\n- `VIBRATE`\n- `READ_PHONE_STATE`\n- `FOREGROUND_SERVICE`\n- `WAKE_LOCK`\n- `com.anddoes.launcher.permission.UPDATE_COUNT`\n- `com.android.launcher.permission.INSTALL_SHORTCUT`\n- `com.google.android.c2dm.permission.RECEIVE`\n- `com.google.android.gms.permission.ACTIVITY_RECOGNITION`\n- `com.google.android.providers.gsf.permission.READ_GSERVICES`\n- `com.htc.launcher.permission.READ_SETTINGS`\n- `com.htc.launcher.permission.UPDATE_SHORTCUT`\n- `com.majeur.launcher.permission.UPDATE_BADGE`\n- `com.sec.android.provider.badge.permission.READ`\n- `com.sec.android.provider.badge.permission.WRITE`\n- `com.sonyericsson.home.permission.BROADCAST_BADGE`\n", - "type": "array", - "meta": { - "bareWorkflow": "To change the permissions your app requests, you'll need to edit `AndroidManifest.xml` manually. To prevent your app from requesting one of the permissions listed below, you'll need to explicitly add it to `AndroidManifest.xml` along with a `tools:node=\"remove\"` tag." - }, - "items": { - "type": "string" - } - }, - "googleServicesFile": { - "description": "[Firebase Configuration File](https://support.google.com/firebase/answer/7015592) Location of the `GoogleService-Info.plist` file for configuring Firebase. Including this key automatically enables FCM in your standalone app.", - "type": "string", - "meta": { - "bareWorkflow": "Add or edit the file directly at `android/app/google-services.json`" - } - }, - "config": { - "type": "object", - "description": "Note: This property key is not included in the production manifest and will evaluate to `undefined`. It is used internally only in the build process, because it contains API keys that some may want to keep private.", - "properties": { - "branch": { - "description": "[Branch](https://branch.io/) key to hook up Branch linking services.", - "type": "object", - "properties": { - "apiKey": { - "description": "Your Branch API key", - "type": "string" - } - }, - "additionalProperties": false - }, - "googleMaps": { - "description": "[Google Maps Android SDK](https://developers.google.com/maps/documentation/android-api/signup) configuration for your standalone app.", - "type": "object", - "properties": { - "apiKey": { - "description": "Your Google Maps Android SDK API key", - "type": "string" - } - }, - "additionalProperties": false - }, - "googleMobileAdsAppId": { - "description": "[Google Mobile Ads App ID](https://support.google.com/admob/answer/6232340) Google AdMob App ID. ", - "type": "string" - }, - "googleMobileAdsAutoInit": { - "description": "A boolean indicating whether to initialize Google App Measurement and begin sending user-level event data to Google immediately when the app starts. The default in Expo (Client and in standalone apps) is `false`. [Sets the opposite of the given value to the following key in `Info.plist`](https://developers.google.com/admob/ios/eu-consent#delay_app_measurement_optional)", - "type": "boolean", - "fallback": false - }, - "googleSignIn": { - "deprecated": true, - "meta": { - "deprecated": true - }, - "description": "@deprecated Use `googleServicesFile` instead. [Google Sign-In Android SDK](https://developers.google.com/identity/sign-in/android/start-integrating) keys for your standalone app.", - "type": "object", - "properties": { - "apiKey": { - "description": "The Android API key. Can be found in the credentials section of the developer console or in `google-services.json`.", - "type": "string" - }, - "certificateHash": { - "description": "The SHA-1 hash of the signing certificate used to build the APK without any separator (`:`). Can be found in `google-services.json`. https://developers.google.com/android/guides/client-auth", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "splash": { - "description": "Configuration for loading and splash screen for managed and standalone Android apps.", - "type": "object", - "properties": { - "backgroundColor": { - "description": "Color to fill the loading screen background", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "resizeMode": { - "description": "Determines how the `image` will be displayed in the splash loading screen. Must be one of `cover`, `contain` or `native`, defaults to `contain`.", - "enum": ["cover", "contain", "native"], - "type": "string" - }, - "image": { - "description": "Local path or remote URL to an image to fill the background of the loading screen. Image size and aspect ratio are up to you. Must be a .png.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - }, - "mdpi": { - "description": "Local path or remote URL to an image to fill the background of the loading screen in \"native\" mode. Image size and aspect ratio are up to you. [Learn more]( https://developer.android.com/training/multiscreen/screendensities) \n\n `Natural sized image (baseline)`", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - }, - "hdpi": { - "description": "Local path or remote URL to an image to fill the background of the loading screen in \"native\" mode. Image size and aspect ratio are up to you. [Learn more]( https://developer.android.com/training/multiscreen/screendensities) \n\n `Scale 1.5x`", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - }, - "xhdpi": { - "description": "Local path or remote URL to an image to fill the background of the loading screen in \"native\" mode. Image size and aspect ratio are up to you. [Learn more]( https://developer.android.com/training/multiscreen/screendensities) \n\n `Scale 2x`", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - }, - "xxhdpi": { - "description": "Local path or remote URL to an image to fill the background of the loading screen in \"native\" mode. Image size and aspect ratio are up to you. [Learn more]( https://developer.android.com/training/multiscreen/screendensities) \n\n `Scale 3x`", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - }, - "xxxhdpi": { - "description": "Local path or remote URL to an image to fill the background of the loading screen in \"native\" mode. Image size and aspect ratio are up to you. [Learn more]( https://developer.android.com/training/multiscreen/screendensities) \n\n `Scale 4x`", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - } - } - }, - "intentFilters": { - "description": "Configuration for setting an array of custom intent filters in Android manifest. [Learn more](https://developer.android.com/guide/components/intents-filters)", - "example": [ - { - "autoVerify": true, - "action": "VIEW", - "data": { - "scheme": "https", - "host": "*.example.com" - }, - "category": ["BROWSABLE", "DEFAULT"] - } - ], - "exampleString": "\n [{ \n \"autoVerify\": true, \n \"action\": \"VIEW\", \n \"data\": { \n \"scheme\": \"https\", \n \"host\": \"*.example.com\" \n }, \n \"category\": [\"BROWSABLE\", \"DEFAULT\"] \n }]", - "type": "array", - "uniqueItems": true, - "items": { - "type": "object", - "properties": { - "autoVerify": { - "description": "You may also use an intent filter to set your app as the default handler for links (without showing the user a dialog with options). To do so use `true` and then configure your server to serve a JSON file verifying that you own the domain. [Learn more](https://developer.android.com/training/app-links)", - "type": "boolean" - }, - "action": { "type": "string" }, - "data": { - "anyOf": [ - { "$ref": "#/definitions/AndroidIntentFiltersData" }, - { - "type": ["array"], - "items": { - "$ref": "#/definitions/AndroidIntentFiltersData" - } - } - ] - }, - "category": { - "anyOf": [ - { - "type": ["string"] - }, - { - "type": "array", - "items": { "type": "string" } - } - ] - } - }, - "additionalProperties": false, - "required": ["action"] - }, - "meta": { - "bareWorkflow": "This is set in `AndroidManifest.xml` directly. [Learn more.](https://developer.android.com/guide/components/intents-filters)" - } - }, - "allowBackup": { - "description": "Allows your user's app data to be automatically backed up to their Google Drive. If this is set to false, no backup or restore of the application will ever be performed (this is useful if your app deals with sensitive information). Defaults to the Android default, which is `true`.", - "fallback": true, - "type": "boolean" - }, - "softwareKeyboardLayoutMode": { - "description": "Determines how the software keyboard will impact the layout of your application. This maps to the `android:windowSoftInputMode` property. Defaults to `resize`. Valid values: `resize`, `pan`.", - "enum": ["resize", "pan"], - "type": "string", - "fallback": "resize" - }, - "jsEngine": { - "description": "Specifies the JavaScript engine for Android apps. Supported only on EAS Build and in Expo Go. Defaults to `jsc`. Valid values: `hermes`, `jsc`.", - "type": "string", - "fallback": "jsc", - "enum": ["hermes", "jsc"], - "meta": { - "bareWorkflow": "To change the JavaScript engine, update the `expo.jsEngine` value in `android/gradle.properties`" - } - }, - "runtimeVersion": { - "description": "**Note: Don't use this property unless you are sure what you're doing** \n\nThe runtime version associated with this manifest for the Android platform. If provided, this will override the top level runtimeVersion key.\nSet this to `{\"policy\": \"nativeVersion\"}` to generate it automatically.", - "$ref": "#/definitions/RuntimeVersion" - } - }, - "additionalProperties": false - }, - "AndroidIntentFiltersData": { - "type": "object", - "properties": { - "scheme": { - "description": "Scheme of the URL, e.g. `https`", - "type": "string" - }, - "host": { "description": "Hostname, e.g. `myapp.io`", "type": "string" }, - "port": { "description": "Port, e.g. `3000`", "type": "string" }, - "path": { - "description": "Exact path for URLs that should be matched by the filter, e.g. `/records`", - "type": "string" - }, - "pathPattern": { - "description": "Pattern for paths that should be matched by the filter, e.g. `.*`. Must begin with `/`", - "type": "string" - }, - "pathPrefix": { - "description": "Prefix for paths that should be matched by the filter, e.g. `/records/` will match `/records/123`", - "type": "string" - }, - "mimeType": { - "description": "MIME type for URLs that should be matched by the filter", - "type": "string" - } - }, - "additionalProperties": false - }, - "IOS": { - "description": "Configuration that is specific to the iOS platform.", - "type": "object", - "meta": { - "standaloneOnly": true - }, - "properties": { - "publishManifestPath": { - "description": "The manifest for the iOS version of your app will be written to this path during publish.", - "type": "string", - "meta": { - "autogenerated": true - } - }, - "publishBundlePath": { - "description": "The bundle for the iOS version of your app will be written to this path during publish.", - "type": "string", - "meta": { - "autogenerated": true - } - }, - "bundleIdentifier": { - "description": "The bundle identifier for your iOS standalone app. You make it up, but it needs to be unique on the App Store. See [this StackOverflow question](http://stackoverflow.com/questions/11347470/what-does-bundle-identifier-mean-in-the-ios-project).", - "type": "string", - "pattern": "^[a-zA-Z0-9.-]+$", - "meta": { - "bareWorkflow": "Set this value in `info.plist` under `CFBundleIdentifier`", - "regexHuman": "iOS bundle identifier notation unique name for your app. For example, `host.exp.expo`, where `exp.host` is our domain and `expo` is our app name." - } - }, - "buildNumber": { - "description": "Build number for your iOS standalone app. Corresponds to `CFBundleVersion` and must match Apple's [specified format](https://developer.apple.com/library/content/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/20001431-102364). (Note: Transporter will pull the value for `Version Number` from `expo.version` and NOT from `expo.ios.buildNumber`.)", - "type": "string", - "pattern": "^[A-Za-z0-9\\.]+$", - "meta": { - "bareWorkflow": "Set this value in `info.plist` under `CFBundleVersion`" - } - }, - "backgroundColor": { - "description": "The background color for your iOS app, behind any of your React views. Overrides the top-level `backgroundColor` key if it is present. Requires `expo-system-ui` be installed in your project to work on iOS.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "icon": { - "description": "Local path or remote URL to an image to use for your app's icon on iOS. If specified, this overrides the top-level `icon` key. Use a 1024x1024 icon which follows Apple's interface guidelines for icons, including color profile and transparency. \n\n Expo will generate the other required sizes. This icon will appear on the home screen and within the Expo app.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image", - "square": true - } - }, - "merchantId": { - "description": "@deprecated Merchant ID for use with Apple Pay in your standalone app.", - "type": "string", - "meta": { - "deprecated": true - } - }, - "appStoreUrl": { - "description": "URL to your app on the Apple App Store, if you have deployed it there. This is used to link to your store page from your Expo project page if your app is public.", - "pattern": "^https://(itunes|apps)\\.apple\\.com/.*?\\d+", - "example": "https://apps.apple.com/us/app/expo-client/id982107779", - "type": ["string"] - }, - "bitcode": { - "description": "Enable iOS Bitcode optimizations in the native build. Accepts the name of an iOS build configuration to enable for a single configuration and disable for all others, e.g. Debug, Release. Not available in the classic 'expo build:ios' or Expo Go. Defaults to `undefined` which uses the template's predefined settings.", - "anyOf": [ - { - "type": ["boolean"] - }, - { - "type": ["string"] - } - ] - }, - "config": { - "type": "object", - "description": "Note: This property key is not included in the production manifest and will evaluate to `undefined`. It is used internally only in the build process, because it contains API keys that some may want to keep private.", - "properties": { - "branch": { - "description": "[Branch](https://branch.io/) key to hook up Branch linking services.", - "type": "object", - "properties": { - "apiKey": { - "description": "Your Branch API key", - "type": "string" - } - }, - "additionalProperties": false - }, - "usesNonExemptEncryption": { - "description": "Sets `ITSAppUsesNonExemptEncryption` in the standalone ipa's Info.plist to the given boolean value.", - "type": "boolean" - }, - "googleMapsApiKey": { - "description": "[Google Maps iOS SDK](https://developers.google.com/maps/documentation/ios-sdk/start) key for your standalone app.", - "type": "string" - }, - "googleMobileAdsAppId": { - "description": "[Google Mobile Ads App ID](https://support.google.com/admob/answer/6232340) Google AdMob App ID. ", - "type": "string" - }, - "googleMobileAdsAutoInit": { - "description": "A boolean indicating whether to initialize Google App Measurement and begin sending user-level event data to Google immediately when the app starts. The default in Expo (Go and in standalone apps) is `false`. [Sets the opposite of the given value to the following key in `Info.plist`.](https://developers.google.com/admob/ios/eu-consent#delay_app_measurement_optional)", - "type": "boolean", - "fallback": false - }, - "googleSignIn": { - "description": "@deprecated Use `ios.googleServicesFile` instead.", - "type": "object", - "meta": { - "deprecated": true - }, - "properties": { - "reservedClientId": { - "description": "@deprecated Use `ios.googleServicesFile` instead.", - "type": "string", - "meta": { - "deprecated": true - } - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "googleServicesFile": { - "description": "[Firebase Configuration File](https://support.google.com/firebase/answer/7015592) Location of the `GoogleService-Info.plist` file for configuring Firebase.", - "type": "string" - }, - "supportsTablet": { - "description": "Whether your standalone iOS app supports tablet screen sizes. Defaults to `false`.", - "type": "boolean", - "meta": { - "bareWorkflow": "Set this value in `info.plist` under `UISupportedInterfaceOrientations~ipad`" - } - }, - "isTabletOnly": { - "description": "If true, indicates that your standalone iOS app does not support handsets, and only supports tablets.", - "type": "boolean", - "meta": { - "bareWorkflow": "Set this value in `info.plist` under `UISupportedInterfaceOrientations`" - } - }, - "requireFullScreen": { - "description": "If true, indicates that your standalone iOS app does not support Slide Over and Split View on iPad. Defaults to `false`", - "type": "boolean", - "meta": { - "bareWorkflow": "Use Xcode to set `UIRequiresFullScreen`" - } - }, - "userInterfaceStyle": { - "description": "Configuration to force the app to always use the light or dark user-interface appearance, such as \"dark mode\", or make it automatically adapt to the system preferences. If not provided, defaults to `light`.", - "type": "string", - "fallback": "light", - "enum": ["light", "dark", "automatic"] - }, - "infoPlist": { - "description": "Dictionary of arbitrary configuration to add to your standalone app's native Info.plist. Applied prior to all other Expo-specific configuration. No other validation is performed, so use this at your own risk of rejection from the App Store.", - "type": "object", - "properties": {}, - "additionalProperties": true - }, - "entitlements": { - "description": "Dictionary of arbitrary configuration to add to your standalone app's native *.entitlements (plist). Applied prior to all other Expo-specific configuration. No other validation is performed, so use this at your own risk of rejection from the App Store.", - "type": "object", - "properties": {}, - "additionalProperties": true - }, - "associatedDomains": { - "description": "An array that contains Associated Domains for the standalone app. [Learn more](https://developer.apple.com/documentation/safariservices/supporting_associated_domains).", - "type": "array", - "uniqueItems": true, - "items": { "type": "string" }, - "meta": { - "regexHuman": "Entries must follow the format `applinks:[:port number]`. [Learn more](https://developer.apple.com/documentation/safariservices/supporting_associated_domains).", - "bareWorkflow": "Build with EAS, or use Xcode to enable this capability manually. [Learn more](https://developer.apple.com/documentation/safariservices/supporting_associated_domains)." - } - }, - "usesIcloudStorage": { - "description": "A boolean indicating if the app uses iCloud Storage for `DocumentPicker`. See `DocumentPicker` docs for details.", - "type": "boolean", - "meta": { - "bareWorkflow": "Use Xcode, or ios.entitlements to configure this." - } - }, - "usesAppleSignIn": { - "description": "A boolean indicating if the app uses Apple Sign-In. See `AppleAuthentication` docs for details.", - "type": "boolean", - "fallback": false - }, - "accessesContactNotes": { - "description": "A Boolean value that indicates whether the app may access the notes stored in contacts. You must [receive permission from Apple](https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_developer_contacts_notes) before you can submit your app for review with this capability.", - "type": "boolean", - "fallback": false - }, - "splash": { - "description": "Configuration for loading and splash screen for standalone iOS apps.", - "type": "object", - "properties": { - "xib": { - "description": "@deprecated Apple has deprecated `.xib` splash screens in favor of `.storyboard` files. Local path to a XIB file as the loading screen. It overrides other loading screen options. Note: This will only be used in the standalone app (i.e., after you build the app). It will not be used in the Expo Go.", - "type": "string", - "meta": { - "deprecated": true, - "asset": true, - "contentTypePattern": "^text/xml$", - "contentTypeHuman": ".xib interface builder document" - } - }, - "backgroundColor": { - "description": "Color to fill the loading screen background", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "resizeMode": { - "description": "Determines how the `image` will be displayed in the splash loading screen. Must be one of `cover` or `contain`, defaults to `contain`.", - "enum": ["cover", "contain"], - "type": "string" - }, - "image": { - "description": "Local path or remote URL to an image to fill the background of the loading screen. Image size and aspect ratio are up to you. Must be a .png.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - }, - "tabletImage": { - "description": "Local path or remote URL to an image to fill the background of the loading screen. Image size and aspect ratio are up to you. Must be a .png.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - } - } - }, - "jsEngine": { - "description": "Specifies the JavaScript engine for iOS apps. Supported only on EAS Build. Defaults to `jsc`. Valid values: `hermes`, `jsc`.", - "type": "string", - "fallback": "jsc", - "enum": ["hermes", "jsc"], - "meta": { - "bareWorkflow": "To change the JavaScript engine, update the `expo.jsEngine` value in `ios/Podfile.properties.json`" - } - }, - "runtimeVersion": { - "description": "**Note: Don't use this property unless you are sure what you're doing** \n\nThe runtime version associated with this manifest for the iOS platform. If provided, this will override the top level runtimeVersion key.\nSet this to `{\"policy\": \"nativeVersion\"}` to generate it automatically.", - "$ref": "#/definitions/RuntimeVersion" - } - }, - "additionalProperties": false - }, - "Splash": { - "description": "Configuration for loading and splash screen for standalone apps.", - "type": "object", - "properties": { - "backgroundColor": { - "description": "Color to fill the loading screen background", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`", - "bareWorkflow": "For Android, edit the `colorPrimary` item in `android/app/src/main/res/values/colors.xml`" - } - }, - "resizeMode": { - "description": "Determines how the `image` will be displayed in the splash loading screen. Must be one of `cover` or `contain`, defaults to `contain`.", - "enum": ["cover", "contain"], - "type": "string" - }, - "image": { - "description": "Local path or remote URL to an image to fill the background of the loading screen. Image size and aspect ratio are up to you. Must be a .png.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - } - }, - "meta": { - "bareWorkflow": "To change your app's icon, edit or replace the files in `ios//Assets.xcassets/AppIcon.appiconset` (we recommend using Xcode), and `android/app/src/main/res/mipmap-` (Android Studio can [generate the appropriate image files for you](https://developer.android.com/studio/write/image-asset-studio)). Be sure to follow the guidelines for each platform ([iOS](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/), [Android 7.1 and below](https://material.io/design/iconography/#icon-treatments), and [Android 8+](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)) and to provide your new icon in each required size." - } - }, - "PublishHook": { - "type": "object", - "additionalProperties": true, - "properties": { - "file": { - "type": "string" - }, - "config": { - "type": "object", - "additionalProperties": true, - "properties": {} - } - } - }, - "Web": { - "description": "Configuration that is specific to the web platform.", - "type": "object", - "additionalProperties": true, - "properties": { - "favicon": { - "description": "Relative path of an image to use for your app's favicon.", - "type": "string" - }, - "name": { - "description": "Defines the title of the document, defaults to the outer level name", - "type": "string", - "meta": { - "pwa": "name" - } - }, - "shortName": { - "description": "A short version of the app's name, 12 characters or fewer. Used in app launcher and new tab pages. Maps to `short_name` in the PWA manifest.json. Defaults to the `name` property.", - "type": "string", - "meta": { - "pwa": "short_name", - "regexHuman": "Maximum 12 characters long" - } - }, - "lang": { - "description": "Specifies the primary language for the values in the name and short_name members. This value is a string containing a single language tag.", - "type": "string", - "fallback": "en", - "meta": { - "pwa": "lang" - } - }, - "scope": { - "description": "Defines the navigation scope of this website's context. This restricts what web pages can be viewed while the manifest is applied. If the user navigates outside the scope, it returns to a normal web page inside a browser tab/window. If the scope is a relative URL, the base URL will be the URL of the manifest.", - "type": "string", - "meta": { - "pwa": "scope" - } - }, - "themeColor": { - "description": "Defines the color of the Android tool bar, and may be reflected in the app's preview in task switchers.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "pwa": "theme_color", - "html": "theme-color", - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "description": { - "description": "Provides a general description of what the pinned website does.", - "type": "string", - "meta": { - "html": "description", - "pwa": "description" - } - }, - "dir": { - "description": "Specifies the primary text direction for the name, short_name, and description members. Together with the lang member, it helps the correct display of right-to-left languages.", - "enum": ["auto", "ltr", "rtl"], - "type": "string", - "meta": { - "pwa": "dir" - } - }, - "display": { - "description": "Defines the developers’ preferred display mode for the website.", - "enum": ["fullscreen", "standalone", "minimal-ui", "browser"], - "type": "string", - "meta": { - "pwa": "display" - } - }, - "startUrl": { - "description": "The URL that loads when a user launches the application (e.g., when added to home screen), typically the index. Note: This has to be a relative URL, relative to the manifest URL.", - "type": "string", - "meta": { - "pwa": "start_url" - } - }, - "orientation": { - "description": "Defines the default orientation for all the website's top level browsing contexts.", - "enum": [ - "any", - "natural", - "landscape", - "landscape-primary", - "landscape-secondary", - "portrait", - "portrait-primary", - "portrait-secondary" - ], - "type": "string", - "meta": { - "pwa": "orientation" - } - }, - "backgroundColor": { - "description": "Defines the expected “background color” for the website. This value repeats what is already available in the site’s CSS, but can be used by browsers to draw the background color of a shortcut when the manifest is available before the stylesheet has loaded. This creates a smooth transition between launching the web application and loading the site's content.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "pwa": "background_color", - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "barStyle": { - "description": "If content is set to default, the status bar appears normal. If set to black, the status bar has a black background. If set to black-translucent, the status bar is black and translucent. If set to default or black, the web content is displayed below the status bar. If set to black-translucent, the web content is displayed on the entire screen, partially obscured by the status bar.", - "enum": ["default", "black", "black-translucent"], - "type": "string", - "fallback": "black-translucent", - "meta": { - "html": "apple-mobile-web-app-status-bar-style", - "pwa": "name" - } - }, - "preferRelatedApplications": { - "description": "Hints for the user agent to indicate to the user that the specified native applications (defined in expo.ios and expo.android) are recommended over the website.", - "type": "boolean", - "fallback": true, - "meta": { - "pwa": "prefer_related_applications" - } - }, - "dangerous": { - "description": "Experimental features. These will break without deprecation notice.", - "type": "object", - "properties": {}, - "additionalProperties": true - }, - "splash": { - "description": "Configuration for PWA splash screens.", - "type": "object", - "properties": { - "backgroundColor": { - "description": "Color to fill the loading screen background", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "resizeMode": { - "description": "Determines how the `image` will be displayed in the splash loading screen. Must be one of `cover` or `contain`, defaults to `contain`.", - "enum": ["cover", "contain"], - "type": "string" - }, - "image": { - "description": "Local path or remote URL to an image to fill the background of the loading screen. Image size and aspect ratio are up to you. Must be a .png.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image" - } - } - }, - "meta": { - "bareWorkflow": "Use [expo-splash-screen](https://github.com/expo/expo/tree/main/packages/expo-splash-screen#expo-splash-screen)" - } - }, - "config": { - "description": "Firebase web configuration. Used by the expo-firebase packages on both web and native. [Learn more](https://firebase.google.com/docs/reference/js/firebase.html#initializeapp)", - "type": "object", - "properties": { - "firebase": { - "type": "object", - "properties": { - "apiKey": { "type": "string" }, - "authDomain": { "type": "string" }, - "databaseURL": { "type": "string" }, - "projectId": { "type": "string" }, - "storageBucket": { "type": "string" }, - "messagingSenderId": { "type": "string" }, - "appId": { "type": "string" }, - "measurementId": { "type": "string" } - } - } - } - } - } - }, - "RuntimeVersion": { - "allOf": [ - { - "anyOf": [ - { - "type": "string", - "pattern": "^[a-zA-Z\\d][a-zA-Z\\d._+()-]{0,254}$", - "meta": { - "regexHuman": "String beginning with an alphanumeric character followed by any combination of alphanumeric character, \"_\", \"+\", \".\",\"(\", \")\", or \"-\". Examples: \"1.0.3a+\", \"1.0.0\", \"._+()-0a1\", \"0\"." - } - }, - { - "type": "string", - "pattern": "^exposdk:((\\d+\\.\\d+\\.\\d+)|(UNVERSIONED))$", - "meta": { - "regexHuman": "An 'exposdk:' prefix followed by the SDK version of your project. Example: \"exposdk:44.0.0\"." - } - }, - { - "type": "object", - "properties": { - "policy": { - "type": "string", - "enum": ["nativeVersion", "sdkVersion"] - } - }, - "required": ["policy"], - "additionalProperties": false - } - ] - }, - { - "not": { - "pattern": "^\\d+\\.\\d*0$" - }, - "meta": { - "notHuman": "Not a decimal ending in a 0." - } - } - ] - } - }, - "type": "object", - "properties": { - "name": { - "description": "The name of your app as it appears both within Expo Go and on your home screen as a standalone app.", - "type": "string", - "meta": { - "bareWorkflow": "To change the name of your app, edit the 'Display Name' field in Xcode and the `app_name` string in `android/app/src/main/res/values/strings.xml`" - } - }, - "description": { - "description": "A short description of what your app is and why it is great.", - "type": "string" - }, - "slug": { - "description": "The friendly URL name for publishing. For example, `myAppName` will refer to the `expo.dev/@project-owner/myAppName` project.", - "type": "string", - "pattern": "^[a-zA-Z0-9_\\-]+$" - }, - "owner": { - "description": "The name of the Expo account that owns the project. This is useful for teams collaborating on a project. If not provided, the owner defaults to the username of the current user.", - "type": "string", - "minLength": 1 - }, - "currentFullName": { - "description": "The auto generated Expo account name and slug used for display purposes. Formatted like `@username/slug`. When unauthenticated, the username is `@anonymous`. For published projects, this value may change when a project is transferred between accounts or renamed.", - "type": "string", - "meta": { - "autogenerated": true - } - }, - "originalFullName": { - "description": "The auto generated Expo account name and slug used for services like Notifications and AuthSession proxy. Formatted like `@username/slug`. When unauthenticated, the username is `@anonymous`. For published projects, this value will not change when a project is transferred between accounts or renamed.", - "type": "string", - "meta": { - "autogenerated": true - } - }, - "privacy": { - "description": "Defaults to `unlisted`. `unlisted` hides the project from search results. `hidden` restricts access to the project page to only the owner and other users that have been granted access. Valid values: `public`, `unlisted`, `hidden`.", - "enum": ["public", "unlisted", "hidden"], - "type": "string", - "fallback": "unlisted" - }, - "sdkVersion": { - "description": "The Expo sdkVersion to run the project on. This should line up with the version specified in your package.json.", - "type": "string", - "pattern": "^(\\d+\\.\\d+\\.\\d+)|(UNVERSIONED)$" - }, - "runtimeVersion": { - "description": "**Note: Don't use this property unless you are sure what you're doing** \n\nThe runtime version associated with this manifest.\nSet this to `{\"policy\": \"nativeVersion\"}` to generate it automatically.", - "$ref": "#/definitions/RuntimeVersion" - }, - "version": { - "description": "Your app version. In addition to this field, you'll also use `ios.buildNumber` and `android.versionCode` — read more about how to version your app [here](https://docs.expo.dev/distribution/app-stores/#versioning-your-app). On iOS this corresponds to `CFBundleShortVersionString`, and on Android, this corresponds to `versionName`. The required format can be found [here](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleshortversionstring).", - "type": "string", - "meta": { - "bareWorkflow": "To change your app version, edit the 'Version' field in Xcode and the `versionName` string in `android/app/build.gradle`" - } - }, - "platforms": { - "description": "Platforms that your project explicitly supports. If not specified, it defaults to `[\"ios\", \"android\"]`.", - "example": ["ios", "android", "web"], - "type": "array", - "uniqueItems": true, - "items": { "type": "string", "enum": ["android", "ios", "web"] } - }, - "githubUrl": { - "description": "If you would like to share the source code of your app on Github, enter the URL for the repository here and it will be linked to from your Expo project page.", - "pattern": "^https://github\\.com/", - "example": "https://github.com/expo/expo", - "type": ["string"] - }, - "orientation": { - "description": "Locks your app to a specific orientation with portrait or landscape. Defaults to no lock. Valid values: `default`, `portrait`, `landscape`", - "enum": ["default", "portrait", "landscape"], - "type": "string" - }, - "userInterfaceStyle": { - "description": "Configuration to force the app to always use the light or dark user-interface appearance, such as \"dark mode\", or make it automatically adapt to the system preferences. If not provided, defaults to `light`. Requires `expo-system-ui` be installed in your project to work on Android.", - "type": "string", - "fallback": "light", - "enum": ["light", "dark", "automatic"] - }, - "backgroundColor": { - "description": "The background color for your app, behind any of your React views. This is also known as the root view background color. Requires `expo-system-ui` be installed in your project to work on iOS.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`. Default is white: `'#ffffff'`" - } - }, - "primaryColor": { - "description": "On Android, this will determine the color of your app in the multitasker. Currently this is not used on iOS, but it may be used for other purposes in the future.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "icon": { - "description": "Local path or remote URL to an image to use for your app's icon. We recommend that you use a 1024x1024 png file. This icon will appear on the home screen and within the Expo app.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image", - "square": true, - "bareWorkflow": "To change your app's icon, edit or replace the files in `ios//Assets.xcassets/AppIcon.appiconset` (we recommend using Xcode), and `android/app/src/main/res/mipmap-`. Be sure to follow the guidelines for each platform ([iOS](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/app-icon/), [Android 7.1 and below](https://material.io/design/iconography/#icon-treatments), and [Android 8+](https://developer.android.com/guide/practices/ui_guidelines/icon_design_adaptive)) and to provide your new icon in each existing size." - } - }, - "notification": { - "description": "Configuration for remote (push) notifications.", - "type": "object", - "properties": { - "icon": { - "description": "(Android only) Local path or remote URL to an image to use as the icon for push notifications. 96x96 png grayscale with transparency. We recommend following [Google's design guidelines](https://material.io/design/iconography/product-icons.html#design-principles). If not provided, defaults to your app icon.", - "type": "string", - "meta": { - "asset": true, - "contentTypePattern": "^image/png$", - "contentTypeHuman": ".png image", - "square": true - } - }, - "color": { - "description": "(Android only) Tint color for the push notification image when it appears in the notification tray. Defaults to `#ffffff`", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - }, - "iosDisplayInForeground": { - "description": "Whether or not to display notifications when the app is in the foreground on iOS. `_displayInForeground` option in the individual push notification message overrides this option. [Learn more.](https://docs.expo.dev/push-notifications/receiving-notifications/#foreground-notification-behavior) Defaults to `false`.", - "type": "boolean" - }, - "androidMode": { - "description": "Show each push notification individually (`default`) or collapse into one (`collapse`).", - "enum": ["default", "collapse"], - "type": "string" - }, - "androidCollapsedTitle": { - "description": "If `androidMode` is set to `collapse`, this title is used for the collapsed notification message. For example, `'#{unread_notifications} new interactions'`.", - "type": "string" - } - }, - "additionalProperties": false - }, - "appKey": { - "description": "@deprecated By default, Expo looks for the application registered with the AppRegistry as `main`. If you would like to change this, you can specify the name in this property.", - "meta": { - "deprecated": true - }, - "type": "string" - }, - "androidStatusBar": { - "description": "Configuration for the status bar on Android. For more details please navigate to [Configuring StatusBar](https://docs.expo.dev/guides/configuring-statusbar/).", - "type": "object", - "properties": { - "barStyle": { - "description": "Configures the status bar icons to have a light or dark color. Valid values: `light-content`, `dark-content`. Defaults to `dark-content`", - "type": "string", - "enum": ["light-content", "dark-content"] - }, - "backgroundColor": { - "description": "Specifies the background color of the status bar. Defaults to `#00000000` (transparent) for `dark-content` bar style and `#00000088` (semi-transparent black) for `light-content` bar style", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string `'#RRGGBB'`, for example, `'#000000'` for black. Or 8 character long hex color string `'#RRGGBBAA'`, for example, `'#00000088'` for semi-transparent black." - } - }, - "hidden": { - "description": "Instructs the system whether the status bar should be visible or not. Defaults to `false`", - "type": "boolean" - }, - "translucent": { - "description": "Sets `android:windowTranslucentStatus` in `styles.xml`. When false, the system status bar pushes the content of your app down (similar to `position: relative`). When true, the status bar floats above the content in your app (similar to `position: absolute`). Defaults to `true` to match the iOS status bar behavior (which can only float above content).", - "type": "boolean" - } - }, - "additionalProperties": false - }, - "androidNavigationBar": { - "description": "Configuration for the bottom navigation bar on Android. Can be used to configure the `expo-navigation-bar` module in EAS Build.", - "type": "object", - "properties": { - "visible": { - "description": "Determines how and when the navigation bar is shown. [Learn more](https://developer.android.com/training/system-ui/immersive). Requires `expo-navigation-bar` be installed in your project. Valid values: `leanback`, `immersive`, `sticky-immersive` \n\n `leanback` results in the navigation bar being hidden until the first touch gesture is registered. \n\n `immersive` results in the navigation bar being hidden until the user swipes up from the edge where the navigation bar is hidden. \n\n `sticky-immersive` is identical to `'immersive'` except that the navigation bar will be semi-transparent and will be hidden again after a short period of time.", - "type": "string", - "enum": ["leanback", "immersive", "sticky-immersive"] - }, - "barStyle": { - "description": "Configure the navigation bar icons to have a light or dark color. Supported on Android Oreo and newer. Valid values: `'light-content'`, `'dark-content'`", - "type": "string", - "enum": ["light-content", "dark-content"] - }, - "backgroundColor": { - "description": "Specifies the background color of the navigation bar.", - "type": "string", - "pattern": "^#|(#)\\d{6}$", - "meta": { - "regexHuman": "6 character long hex color string, for example, `'#000000'`" - } - } - }, - "additionalProperties": false - }, - "developmentClient": { - "description": "Settings that apply specifically to running this app in a development client", - "type": "object", - "properties": { - "silentLaunch": { - "description": "If true, the app will launch in a development client with no additional dialogs or progress indicators, just like in a standalone app.", - "type": "boolean", - "fallback": false - } - }, - "additionalProperties": false - }, - "scheme": { - "description": "**Standalone Apps Only**. URL scheme to link into your app. For example, if we set this to `'demo'`, then demo:// URLs would open your app when tapped.", - "type": "string", - "pattern": "^[a-z][a-z0-9+.-]*$", - "meta": { - "regexHuman": "String beginning with a **lowercase** letter followed by any combination of **lowercase** letters, digits, \"+\", \".\" or \"-\"", - "standaloneOnly": true, - "bareWorkflow": "To change your app's scheme, replace all occurrences of the old scheme in `Info.plist` and `AndroidManifest.xml`" - } - }, - "entryPoint": { - "description": "The relative path to your main JavaScript file.", - "type": "string" - }, - "extra": { - "description": "Any extra fields you want to pass to your experience. Values are accessible via `Expo.Constants.manifest.extra` ([Learn more](https://docs.expo.dev/versions/latest/sdk/constants/#constantsmanifest))", - "type": "object", - "properties": {}, - "additionalProperties": true - }, - "packagerOpts": { - "description": "@deprecated Use a `metro.config.js` file instead. [Learn more](https://docs.expo.dev/guides/customizing-metro/)", - "meta": { - "deprecated": true, - "autogenerated": true - }, - "type": "object", - "properties": {}, - "additionalProperties": true - }, - "updates": { - "description": "Configuration for how and when the app should request OTA JavaScript updates", - "type": "object", - "properties": { - "enabled": { - "description": "If set to false, your standalone app will never download any code, and will only use code bundled locally on the device. In that case, all updates to your app must be submitted through app store review. Defaults to true. (Note: This will not work out of the box with ExpoKit projects)", - "type": "boolean" - }, - "checkAutomatically": { - "description": "By default, Expo will check for updates every time the app is loaded. Set this to `ON_ERROR_RECOVERY` to disable automatic checking unless recovering from an error. Must be one of `ON_LOAD` or `ON_ERROR_RECOVERY`", - "enum": ["ON_ERROR_RECOVERY", "ON_LOAD"], - "type": "string" - }, - "fallbackToCacheTimeout": { - "description": "How long (in ms) to allow for fetching OTA updates before falling back to a cached version of the app. Defaults to 0. Must be between 0 and 300000 (5 minutes).", - "type": "number", - "minimum": 0, - "maximum": 300000 - }, - "url": { - "description": "URL from which expo-updates will fetch update manifests", - "type": "string" - }, - "codeSigningCertificate": { - "description": "Local path of a PEM-formatted X.509 certificate used for requiring and verifying signed Expo updates", - "type": "string" - }, - "codeSigningMetadata": { - "description": "Metadata for `codeSigningCertificate`", - "type": "object", - "properties": { - "alg": { - "description": "Algorithm used to generate manifest code signing signature.", - "enum": ["rsa-v1_5-sha256"], - "type": "string" - }, - "keyid": { - "description": "Identifier for the key in the certificate. Used to instruct signing mechanisms when signing or verifying signatures.", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "locales": { - "description": "Provide overrides by locale for System Dialog prompts like Permissions Boxes", - "type": "object", - "properties": {}, - "meta": { - "bareWorkflow": "To add or change language and localization information in your iOS app, you need to use Xcode." - }, - "additionalProperties": { - "type": ["string", "object"] - } - }, - "facebookAppId": { - "description": "Used for all Facebook libraries. Set up your Facebook App ID at https://developers.facebook.com.", - "type": "string", - "pattern": "^[0-9]+$", - "meta": { - "bareWorkflow": "For details, check the [Facebook iOS SDK documentation](https://developers.facebook.com/docs/facebook-login/ios/#4--configure-your-project) and [Android SDK documentation](https://developers.facebook.com/docs/facebook-login/android#manifest)" - } - }, - "facebookAutoInitEnabled": { - "description": "Whether the Facebook SDK should be initialized automatically. The default in Expo (Client and in standalone apps) is `false`.", - "type": "boolean" - }, - "facebookAutoLogAppEventsEnabled": { - "description": "Whether the Facebook SDK log app events automatically. If you don't set this property, Facebook's default will be used. (Applicable only to standalone apps.) Note: The Facebook SDK must be initialized for app events to work. You may autoinitialize Facebook SDK by setting `facebookAutoInitEnabled` to `true`", - "type": "boolean", - "meta": { - "bareWorkflow": "For details, check the [Facebook iOS SDK documentation](https://developers.facebook.com/docs/facebook-login/ios/#4--configure-your-project) and [Android SDK documentation](https://developers.facebook.com/docs/facebook-login/android#manifest)" - } - }, - "facebookAdvertiserIDCollectionEnabled": { - "description": "Whether the Facebook SDK should collect advertiser ID properties, like the Apple IDFA and Android Advertising ID, automatically. If you don't set this property, Facebook's default policy will be used. (Applicable only to standalone apps.)", - "type": "boolean", - "meta": { - "bareWorkflow": "For details, check the [Facebook iOS SDK documentation](https://developers.facebook.com/docs/facebook-login/ios/#4--configure-your-project) and [Android SDK documentation](https://developers.facebook.com/docs/facebook-login/android#manifest)" - } - }, - "facebookDisplayName": { - "description": "Used for native Facebook login.", - "type": "string", - "meta": { - "bareWorkflow": "For details, check the [Facebook iOS SDK documentation](https://developers.facebook.com/docs/facebook-login/ios/#4--configure-your-project) and [Android SDK documentation](https://developers.facebook.com/docs/facebook-login/android#manifest)" - } - }, - "facebookScheme": { - "description": "Used for Facebook native login. Starts with 'fb' and followed by a string of digits, like 'fb1234567890'. You can find your scheme [here](https://developers.facebook.com/docs/facebook-login/ios)in the 'Configuring Your info.plist' section (only applicable to standalone apps and custom Expo Go apps).", - "type": "string", - "pattern": "^fb[0-9]+[A-Za-z]*$", - "meta": { - "bareWorkflow": "For details, check the [Facebook iOS SDK documentation](https://developers.facebook.com/docs/facebook-login/ios/#4--configure-your-project) and [Android SDK documentation](https://developers.facebook.com/docs/facebook-login/android#manifest)" - } - }, - "isDetached": { - "description": "Is app detached", - "type": "boolean", - "meta": { - "autogenerated": true - } - }, - "detach": { - "description": "Extra fields needed by detached apps", - "type": "object", - "properties": {}, - "meta": { - "autogenerated": true - }, - "additionalProperties": true - }, - "assetBundlePatterns": { - "description": "An array of file glob strings which point to assets that will be bundled within your standalone app binary. Read more in the [Offline Support guide](https://docs.expo.dev/guides/offline-support/)", - "type": "array", - "items": { "type": "string" } - }, - "plugins": { - "description": "Config plugins for adding extra functionality to your project. [Learn more](https://docs.expo.dev/guides/config-plugins/).", - "meta": { - "bareWorkflow": "Plugins that add modifications can only be used with [prebuilding](https://expo.fyi/prebuilding) and managed EAS Build" - }, - "type": "array", - "items": { - "anyOf": [ - { - "type": ["string"] - }, - { - "type": "array", - "items": [ - { - "type": ["string"] - }, - {} - ], - "additionalItems": false - } - ] - } - }, - "splash": { - "$ref": "#/definitions/Splash" - }, - "jsEngine": { - "description": "Specifies the JavaScript engine for apps. Supported only on EAS Build. Defaults to `jsc`. Valid values: `hermes`, `jsc`.", - "type": "string", - "fallback": "jsc", - "enum": ["hermes", "jsc"], - "meta": { - "bareWorkflow": "To change the JavaScript engine, update the `expo.jsEngine` value in `ios/Podfile.properties.json` or `android/gradle.properties`" - } - }, - "ios": { - "$ref": "#/definitions/IOS" - }, - "android": { - "$ref": "#/definitions/Android" - }, - "web": { - "$ref": "#/definitions/Web" - }, - "hooks": { - "description": "Configuration for scripts to run to hook into the publish process", - "type": "object", - "additionalProperties": false, - "properties": { - "postPublish": { - "type": "array", - "items": { - "$ref": "#/definitions/PublishHook" - } - }, - "postExport": { - "type": "array", - "items": { - "$ref": "#/definitions/PublishHook" - } - } - } - }, - "experiments": { - "description": "Enable experimental features that may be unstable, unsupported, or removed without deprecation notices.", - "type": "object", - "additionalProperties": false, - "properties": { - "turboModules": { - "description": "Enables Turbo Modules, which are a type of native modules that use a different way of communicating between JS and platform code. When installing a Turbo Module you will need to enable this experimental option (the library still needs to be a part of Expo SDK already, like react-native-reanimated v2). Turbo Modules do not support remote debugging and enabling this option will disable remote debugging.", - "type": "boolean", - "fallback": false - } - } - }, - "_internal": { - "description": "Internal properties for developer tools", - "type": "object", - "properties": { - "pluginHistory": { - "description": "List of plugins already run on the config", - "type": "object", - "properties": {}, - "additionalProperties": true - } - }, - "additionalProperties": true, - "meta": { - "autogenerated": true - } - } - }, - "additionalProperties": false, - "required": ["name", "slug"] - } -} diff --git a/packages/schemer/__tests__/files/secretlyPng.jpg b/packages/schemer/__tests__/files/secretlyPng.jpg deleted file mode 100644 index 9c34ad30b1..0000000000 Binary files a/packages/schemer/__tests__/files/secretlyPng.jpg and /dev/null differ diff --git a/packages/schemer/__tests__/files/webp.webp b/packages/schemer/__tests__/files/webp.webp deleted file mode 100644 index 0da983e2ce..0000000000 Binary files a/packages/schemer/__tests__/files/webp.webp and /dev/null differ diff --git a/packages/schemer/__tests__/helper-test.ts b/packages/schemer/__tests__/helper-test.ts deleted file mode 100644 index c715d1c7a5..0000000000 --- a/packages/schemer/__tests__/helper-test.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { fieldPathToSchemaPath, schemaPointerToFieldPath } from '../src/Util'; - -describe('Helper function unit tests', () => { - it('fieldPathToSchemaPath short path', () => { - expect(fieldPathToSchemaPath('name')).toBe('properties.name'); - }); - it('fieldPathToSchemaPath long property string', () => { - expect(fieldPathToSchemaPath('app.android.icon')).toBe( - 'properties.app.properties.android.properties.icon' - ); - }); - it('fieldPathToSchemaPath mixed characters', () => { - expect(fieldPathToSchemaPath('a23-df34.fef4383')).toBe( - 'properties.a23-df34.properties.fef4383' - ); - }); - - it('schemaPointerToFieldPath', () => { - expect(schemaPointerToFieldPath('/properties/loading/properties/backgroundImage')).toBe( - 'loading.backgroundImage' - ); - }); -}); diff --git a/packages/schemer/__tests__/network-test.ts b/packages/schemer/__tests__/network-test.ts deleted file mode 100644 index 477c5ccdbe..0000000000 --- a/packages/schemer/__tests__/network-test.ts +++ /dev/null @@ -1,58 +0,0 @@ -import Schemer from '../src/index'; - -const schema = require('./files/schema.json').schema; -const S = new Schemer(schema, { rootDir: './__tests__' }); - -describe('Remote', () => { - it('Icon', async () => { - expect( - await S.validateIcon( - 'https://upload.wikimedia.org/wikipedia/commons/0/0f/Icon_Pinguin_2_512x512.png' - ) - ).toEqual(undefined); - }); - - it('Remote icon dimensions correct', async () => { - const S = new Schemer({ - properties: { - icon: { - meta: { asset: true, dimensions: { width: 100, height: 100 } }, - }, - }, - }); - expect(await S.validateIcon('https://httpbin.org/image/png')).toEqual(undefined); - }); - - it('Remote icon dimensions wrong', async () => { - let didError = false; - const S = new Schemer( - { - properties: { - icon: { - meta: { - asset: true, - dimensions: { width: 101, height: 100 }, - contentTypePattern: '^image/png$', - }, - }, - }, - }, - { rootDir: './__tests__' } - ); - try { - await S.validateIcon('https://httpbin.org/image/png'); - } catch (e) { - didError = true; - expect(e).toBeTruthy(); - expect(e.errors.length).toBe(1); - expect( - e.errors.map(validationError => { - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - - expect(didError).toBe(true); - }); -}); diff --git a/packages/schemer/__tests__/test.ts b/packages/schemer/__tests__/test.ts deleted file mode 100644 index da0912b191..0000000000 --- a/packages/schemer/__tests__/test.ts +++ /dev/null @@ -1,291 +0,0 @@ -import { ErrorCodes, SchemerError } from '../src/Error'; -import Schemer from '../src/index'; -import good from './files/app.json'; -import bad from './files/bad.json'; -import badWithNot from './files/badwithnot.json'; -import invalidAppIcon from './files/invalidAppIcon.json'; -import schema from './files/schema.json'; - -const S = new Schemer(schema.schema, { rootDir: './__tests__' }); - -describe('Sanity Tests', () => { - it('is a class', () => { - const schema = require('./files/schema.json'); - const S = new Schemer(schema, { rootDir: './__tests__' }); - expect(S instanceof Schemer).toBe(true); - }); - - it('has public functions', () => { - const schema = require('./files/schema.json'); - const S = new Schemer(schema, { rootDir: './__tests__' }); - expect(S.validateAll).toBeDefined(); - expect(S.validateProperty).toBeDefined(); - }); -}); - -describe('Image Validation', () => { - it('errors for webp images', async () => { - let didError = false; - try { - await S.validateAssetsAsync({ - android: { - adaptiveIcon: { foregroundImage: './files/webp.webp' }, - }, - }); - } catch (e) { - didError = true; - expect(e.errors[0].errorCode).toBe('INVALID_CONTENT_TYPE'); - expect(e.errors[1].errorCode).toBe('NOT_SQUARE'); - expect( - e.errors.map(validationError => { - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - - expect(didError).toBe(true); - }); - - it('errors when file extension and content do not match up', async () => { - let didError = false; - try { - await S.validateAssetsAsync({ - icon: './files/secretlyPng.jpg', - }); - } catch (e) { - didError = true; - expect(e.errors[0].errorCode).toBe('FILE_EXTENSION_MISMATCH'); - expect( - e.errors.map(validationError => { - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - - expect(didError).toBe(true); - }); -}); - -describe('Holistic Unit Test', () => { - it('good example app.json all', async () => { - expect(await S.validateAll(good)).toEqual(undefined); - }); - - it('good example app.json schema', async () => { - expect(await S.validateSchemaAsync(good)).toEqual(undefined); - }); - - it('bad example app.json schema', async () => { - let didError = false; - try { - await S.validateSchemaAsync(bad); - } catch (e) { - didError = true; - expect(e).toBeInstanceOf(SchemerError); - const errors = e.errors; - expect(errors.length).toBe(4); - expect( - errors.map(validationError => { - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - expect(didError).toBe(true); - }); - - it('bad example app.json schema with field with not', async () => { - let didError = false; - try { - await S.validateSchemaAsync(badWithNot); - } catch (e) { - didError = true; - expect(e).toBeInstanceOf(SchemerError); - const errors = e.errors; - expect(errors.length).toBe(1); - expect( - errors.map(validationError => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - expect(didError).toBe(true); - }); - - it('bad example app.json - invalid path for app icon', async () => { - let didError = false; - try { - await S.validateAll(invalidAppIcon); - } catch (e) { - didError = true; - expect(e).toBeInstanceOf(SchemerError); - const errors = e.errors; - expect(errors.length).toBe(1); - expect( - errors.map(validationError => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - expect(didError).toBe(true); - }); -}); - -describe('Manual Validation Individual Unit Tests', () => { - it('Local Icon', async () => { - expect(await S.validateIcon('./files/check.png')).toEqual(undefined); - }); - - it('Local Square Icon correct', async () => { - const S = new Schemer( - { properties: { icon: { meta: { asset: true, square: true } } } }, - { rootDir: './__tests__' } - ); - expect(await S.validateIcon('./files/check.png')).toEqual(undefined); - }); - - it('Local icon dimensions wrong', async () => { - let didError = false; - const S = new Schemer( - { - properties: { - icon: { - meta: { - asset: true, - dimensions: { width: 400, height: 401 }, - contentTypePattern: '^image/png$', - }, - }, - }, - }, - { rootDir: './__tests__' } - ); - try { - await S.validateIcon('./files/check.png'); - } catch (e) { - didError = true; - expect(e).toBeTruthy(); - expect(e.errors.length).toBe(1); - expect( - e.errors.map(validationError => { - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - expect(didError).toBe(true); - }); -}); - -describe('Individual Unit Tests', () => { - it('Error when missing Required Property', async () => { - let didError = false; - const S = new Schemer( - { - properties: { - name: {}, - }, - required: ['name'], - }, - { rootDir: './__tests__' } - ); - try { - await S.validateAll({ noName: '' }); - } catch (e) { - didError = true; - expect(e.errors.length).toBe(1); - expect(e.errors[0].errorCode).toBe(ErrorCodes.SCHEMA_MISSING_REQUIRED_PROPERTY); - expect( - e.errors.map(validationError => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - expect(didError).toBe(true); - }); - - it('Error when data has an additional property', async () => { - let didError = false; - const S = new Schemer({ additionalProperties: false }, { rootDir: './__tests__' }); - try { - await S.validateAll({ extraProperty: 'extra' }); - } catch (e) { - didError = true; - expect(e.errors.length).toBe(1); - expect(e.errors[0].errorCode).toBe(ErrorCodes.SCHEMA_ADDITIONAL_PROPERTY); - expect( - e.errors.map(validationError => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { stack, ...rest } = validationError; - return rest; - }) - ).toMatchSnapshot(); - } - expect(didError).toBe(true); - }); - - it.each` - name | expectedError - ${'wilson'} | ${undefined} - ${[1, 2, 3, 4]} | ${'must be string'} - ${23.232332} | ${'must be string'} - ${/regex.*/} | ${'must be string'} - `('validates name: $name', async ({ name, expectedError }) => { - let didError = false; - try { - expect(await S.validateName(name)).toBe(undefined); - } catch (e) { - didError = true; - expect(e.message).toBe(expectedError); - } - expect(didError).toBe(Boolean(expectedError)); - }); - - it.each` - slug | expectedError - ${'wilson'} | ${undefined} - ${12312123123} | ${'must be string'} - ${[1, 23]} | ${'must be string'} - ${'wilson123'} | ${undefined} - ${'wilson-123'} | ${undefined} - ${'wilson/test'} | ${'\'\' must match pattern "^[a-zA-Z0-9_\\-]+$"'} - ${'wilson-test%'} | ${'\'\' must match pattern "^[a-zA-Z0-9_\\-]+$"'} - ${'wilson-test-zhao--javascript-is-super-funky'} | ${undefined} - `('validates slug: $slug', async ({ slug, expectedError }) => { - let didError = false; - try { - expect(await S.validateSlug(slug)).toBe(undefined); - } catch (e) { - didError = true; - expect(e.message).toBe(expectedError); - } - expect(didError).toBe(Boolean(expectedError)); - }); - - it.each` - sdkVersion | expectedError - ${'1.0.0'} | ${undefined} - ${'2.0.0.0.1'} | ${undefined} - ${'UNVERSIONED'} | ${undefined} - ${'12.2a.3'} | ${'\'\' must match pattern "^(\\d+\\.\\d+\\.\\d+)|(UNVERSIONED)$"'} - ${'9,9,9'} | ${'\'\' must match pattern "^(\\d+\\.\\d+\\.\\d+)|(UNVERSIONED)$"'} - ${'1.2'} | ${'\'\' must match pattern "^(\\d+\\.\\d+\\.\\d+)|(UNVERSIONED)$"'} - `('validates SDK version: $sdkVersion', async ({ sdkVersion, expectedError }) => { - let didError = false; - try { - expect(await S.validateSdkVersion(sdkVersion)).toBe(undefined); - } catch (e) { - didError = true; - expect(e.message).toBe(expectedError); - } - expect(didError).toBe(Boolean(expectedError)); - }); -}); diff --git a/packages/schemer/jest.config.js b/packages/schemer/jest.config.js deleted file mode 100644 index 1d7ccd2df9..0000000000 --- a/packages/schemer/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -const path = require('path'); - -module.exports = { - preset: '../../jest/unit-test-config', - rootDir: path.resolve(__dirname), - displayName: require('./package').name, -}; diff --git a/packages/schemer/package.json b/packages/schemer/package.json deleted file mode 100644 index c620a900a9..0000000000 --- a/packages/schemer/package.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "name": "@expo/schemer", - "version": "1.4.5", - "description": "Centralized scheme validation library for Expo", - "main": "./build/index.js", - "scripts": { - "test": "jest --testPathIgnorePatterns __tests__/network-test.ts", - "test-integration": "jest", - "watch": "tsc --watch --preserveWatchOutput", - "build": "tsc", - "prepare": "yarn build" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "https://github.com/expo/expo-cli.git", - "directory": "packages/schemer" - }, - "bugs": { - "url": "https://github.com/expo/expo-cli/issues" - }, - "homepage": "https://github.com/expo/expo-cli/tree/main/packages/schemer#readme", - "files": [ - "build" - ], - "devDependencies": { - "@types/lodash": "^4.14.176" - }, - "dependencies": { - "ajv": "^8.1.0", - "ajv-formats": "^2.0.2", - "json-schema-traverse": "^1.0.0", - "lodash": "^4.17.21", - "probe-image-size": "^7.1.0" - }, - "publishConfig": { - "access": "public" - }, - "volta": { - "extends": "../../package.json" - } -} diff --git a/packages/schemer/src/Error.ts b/packages/schemer/src/Error.ts deleted file mode 100644 index da488276d5..0000000000 --- a/packages/schemer/src/Error.ts +++ /dev/null @@ -1,55 +0,0 @@ -export class SchemerError extends Error { - readonly name = 'SchemerError'; - errors: ValidationError[]; - - constructor(errors: ValidationError[]) { - super(''); - this.message = errors.map(e => e.message).join('\n'); - this.errors = errors; - } -} - -export class ValidationError extends Error { - readonly name = 'ValidationError'; - errorCode: string; - fieldPath: string; - message: string; - data: any; - meta: any; - constructor({ - errorCode, - fieldPath, - message, - data, - meta, - }: { - errorCode: ErrorCode; - fieldPath: string; - message: string; - data: any; - meta: any; - }) { - super(message); - this.errorCode = errorCode; - this.fieldPath = fieldPath; - this.message = message; - this.data = data; - this.meta = meta; - } -} - -export type ErrorCode = keyof typeof ErrorCodes; - -export const ErrorCodes = { - SCHEMA_VALIDATION_ERROR: 'SCHEMA_VALIDATION_ERROR', - SCHEMA_ADDITIONAL_PROPERTY: 'SCHEMA_ADDITIONAL_PROPERTY', - SCHEMA_MISSING_REQUIRED_PROPERTY: 'SCHEMA_MISSING_REQUIRED_PROPERTY', - SCHEMA_INVALID_PATTERN: 'SCHEMA_INVALID_PATTERN', - SCHEMA_INVALID_NOT: 'SCHEMA_INVALID_NOT', - - INVALID_ASSET_URI: 'INVALID_ASSET_URI', - INVALID_DIMENSIONS: 'INVALID_DIMENSIONS', - INVALID_CONTENT_TYPE: 'INVALID_CONTENT_TYPE', - NOT_SQUARE: 'NOT_SQUARE', - FILE_EXTENSION_MISMATCH: 'FILE_EXTENSION_MISMATCH', -}; diff --git a/packages/schemer/src/Util.ts b/packages/schemer/src/Util.ts deleted file mode 100644 index 354318e0e2..0000000000 --- a/packages/schemer/src/Util.ts +++ /dev/null @@ -1,18 +0,0 @@ -import get from 'lodash/get'; -import zip from 'lodash/zip'; - -export const fieldPathToSchemaPath = (fieldPath: string) => { - return zip(fieldPath.split('.').fill('properties'), fieldPath.split('.')).flat().join('.'); -}; -// Assumption: used only for jsonPointer returned from traverse -export const schemaPointerToFieldPath = (jsonPointer: string) => { - return jsonPointer - .split('/') - .slice(2) - .filter(e => e !== 'properties') - .join('.'); -}; - -export const fieldPathToSchema = (schema: object, fieldPath: string) => { - return get(schema, fieldPathToSchemaPath(fieldPath)); -}; diff --git a/packages/schemer/src/index.ts b/packages/schemer/src/index.ts deleted file mode 100644 index c66d9bde17..0000000000 --- a/packages/schemer/src/index.ts +++ /dev/null @@ -1,293 +0,0 @@ -import Ajv, { ErrorObject, Options } from 'ajv'; -import addFormats from 'ajv-formats'; -import fs from 'fs'; -import traverse from 'json-schema-traverse'; -import get from 'lodash/get'; -import path from 'path'; -import imageProbe from 'probe-image-size'; - -import { SchemerError, ValidationError } from './Error'; -import { fieldPathToSchema, schemaPointerToFieldPath } from './Util'; - -function lowerFirst(str: string): string { - return str.charAt(0).toLowerCase() + str.slice(1); -} - -type Meta = { - asset?: boolean; - dimensions?: { - width: number; - height: number; - }; - square?: boolean; - contentTypePattern?: string; - contentTypeHuman?: string; -}; - -type SchemerOptions = Options & { - rootDir?: string; -}; - -type AssetField = { fieldPath: string; data: string; meta: Meta }; - -export { SchemerError, ValidationError, ErrorCodes, ErrorCode } from './Error'; -export default class Schemer { - options: SchemerOptions; - ajv: Ajv; - schema: object; - rootDir: string; - manualValidationErrors: ValidationError[]; - // Schema is a JSON Schema object - constructor(schema: object, options: SchemerOptions = {}) { - this.options = { - allErrors: true, - verbose: true, - meta: true, - strict: false, - unicodeRegExp: false, - ...options, - }; - - this.ajv = new Ajv(this.options); - addFormats(this.ajv, { mode: 'full' }); - this.schema = schema; - this.rootDir = this.options.rootDir || __dirname; - this.manualValidationErrors = []; - } - - _formatAjvErrorMessage({ - keyword, - instancePath, - params, - parentSchema, - data, - message, - }: ErrorObject) { - const meta = parentSchema && (parentSchema as any).meta; - // This removes the "." in front of a fieldPath - instancePath = instancePath.slice(1); - switch (keyword) { - case 'additionalProperties': { - return new ValidationError({ - errorCode: 'SCHEMA_ADDITIONAL_PROPERTY', - fieldPath: instancePath, - message: `should NOT have additional property '${(params as any).additionalProperty}'`, - data, - meta, - }); - } - case 'required': - return new ValidationError({ - errorCode: 'SCHEMA_MISSING_REQUIRED_PROPERTY', - fieldPath: instancePath, - message: `is missing required property '${(params as any).missingProperty}'`, - data, - meta, - }); - case 'pattern': { - //@TODO Parse the message in a less hacky way. Perhaps for regex validation errors, embed the error message under the meta tag? - const regexHuman = meta?.regexHuman; - const regexErrorMessage = regexHuman - ? `'${instancePath}' should be a ${regexHuman[0].toLowerCase() + regexHuman.slice(1)}` - : `'${instancePath}' ${message}`; - return new ValidationError({ - errorCode: 'SCHEMA_INVALID_PATTERN', - fieldPath: instancePath, - message: regexErrorMessage, - data, - meta, - }); - } - case 'not': { - const notHuman = meta?.notHuman; - const notHumanErrorMessage = notHuman - ? `'${instancePath}' should be ${notHuman[0].toLowerCase() + notHuman.slice(1)}` - : `'${instancePath}' ${message}`; - return new ValidationError({ - errorCode: 'SCHEMA_INVALID_NOT', - fieldPath: instancePath, - message: notHumanErrorMessage, - data, - meta, - }); - } - default: - return new ValidationError({ - errorCode: 'SCHEMA_VALIDATION_ERROR', - fieldPath: instancePath, - message: message || 'Validation error', - data, - meta, - }); - } - } - - getErrors(): ValidationError[] { - // Convert AJV JSONSchema errors to our ValidationErrors - let valErrors: ValidationError[] = []; - if (this.ajv.errors) { - valErrors = this.ajv.errors.map(e => this._formatAjvErrorMessage(e)); - } - return [...valErrors, ...this.manualValidationErrors]; - } - - _throwOnErrors() { - // Clean error state after each validation - const errors = this.getErrors(); - if (errors.length > 0) { - this.manualValidationErrors = []; - this.ajv.errors = []; - throw new SchemerError(errors); - } - } - - async validateAll(data: any) { - await this._validateSchemaAsync(data); - await this._validateAssetsAsync(data); - this._throwOnErrors(); - } - - async validateAssetsAsync(data: any) { - await this._validateAssetsAsync(data); - this._throwOnErrors(); - } - - async validateSchemaAsync(data: any) { - await this._validateSchemaAsync(data); - this._throwOnErrors(); - } - - _validateSchemaAsync(data: any) { - this.ajv.validate(this.schema, data); - } - - async _validateAssetsAsync(data: any) { - const assets: AssetField[] = []; - traverse(this.schema, { allKeys: true }, (subSchema, jsonPointer, a, b, c, d, property) => { - if (property && subSchema.meta && subSchema.meta.asset) { - const fieldPath = schemaPointerToFieldPath(jsonPointer); - assets.push({ - fieldPath, - data: get(data, lowerFirst(fieldPath)) || get(data, fieldPath), - meta: subSchema.meta, - }); - } - }); - await Promise.all(assets.map(this._validateAssetAsync.bind(this))); - } - - async _validateImageAsync({ fieldPath, data, meta }: AssetField) { - if (meta && meta.asset && data) { - const { dimensions, square, contentTypePattern }: Meta = meta; - // filePath could be an URL - const filePath = path.resolve(this.rootDir, data); - try { - // This cases on whether filePath is a remote URL or located on the machine - const isLocalFile = fs.existsSync(filePath); - const probeResult = isLocalFile - ? await imageProbe(require('fs').createReadStream(filePath)) - : await imageProbe(data, { useElectronNet: false }); - - if (!probeResult) { - return; - } - - const { width, height, type, mime } = probeResult; - - const fileExtension = filePath.split('.').pop(); - - if (isLocalFile && mime !== `image/${fileExtension}`) { - this.manualValidationErrors.push( - new ValidationError({ - errorCode: 'FILE_EXTENSION_MISMATCH', - fieldPath, - message: `the file extension should match the content, but the file extension is .${fileExtension} while the file content at '${data}' is of type ${type}`, - data, - meta, - }) - ); - } - - if (contentTypePattern && !mime.match(new RegExp(contentTypePattern))) { - this.manualValidationErrors.push( - new ValidationError({ - errorCode: 'INVALID_CONTENT_TYPE', - fieldPath, - message: `field '${fieldPath}' should point to ${meta.contentTypeHuman} but the file at '${data}' has type ${type}`, - data, - meta, - }) - ); - } - - if (dimensions && (dimensions.height !== height || dimensions.width !== width)) { - this.manualValidationErrors.push( - new ValidationError({ - errorCode: 'INVALID_DIMENSIONS', - fieldPath, - message: `'${fieldPath}' should have dimensions ${dimensions.width}x${dimensions.height}, but the file at '${data}' has dimensions ${width}x${height}`, - data, - meta, - }) - ); - } - - if (square && width !== height) { - this.manualValidationErrors.push( - new ValidationError({ - errorCode: 'NOT_SQUARE', - fieldPath, - message: `image should be square, but the file at '${data}' has dimensions ${width}x${height}`, - data, - meta, - }) - ); - } - } catch { - this.manualValidationErrors.push( - new ValidationError({ - errorCode: 'INVALID_ASSET_URI', - fieldPath, - message: `cannot access file at '${data}'`, - data, - meta, - }) - ); - } - } - } - - async _validateAssetAsync({ fieldPath, data, meta }: AssetField) { - if (meta && meta.asset && data) { - if (meta.contentTypePattern && meta.contentTypePattern.startsWith('^image')) { - await this._validateImageAsync({ fieldPath, data, meta }); - } - } - } - - async validateProperty(fieldPath: string, data: any) { - const subSchema = fieldPathToSchema(this.schema, fieldPath); - this.ajv.validate(subSchema, data); - - if (subSchema.meta && subSchema.meta.asset) { - await this._validateAssetAsync({ fieldPath, data, meta: subSchema.meta }); - } - this._throwOnErrors(); - } - - validateName(name: string) { - return this.validateProperty('name', name); - } - - validateSlug(slug: string) { - return this.validateProperty('slug', slug); - } - - validateSdkVersion(version: string) { - return this.validateProperty('sdkVersion', version); - } - - validateIcon(iconPath: string) { - return this.validateProperty('icon', iconPath); - } -} diff --git a/packages/schemer/tsconfig.json b/packages/schemer/tsconfig.json deleted file mode 100644 index c2ad6364cc..0000000000 --- a/packages/schemer/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.base", - "include": ["src/**/*.ts"], - "compilerOptions": { - "outDir": "build", - "rootDir": "src" - }, - "exclude": ["**/__mocks__/*", "**/__tests__/*"] -}