# Expo Application Services (EAS) Documentation Expo Application Services (EAS) are deeply integrated cloud services for Expo and React Native apps, from the team behind Expo. ## 介绍 ## 使用 eas.json 进行配置 了解 EAS 构建和 EAS 提交的可用属性,以在项目中配置和覆盖它们的默认行为。 eas.json 是 EAS CLI 和服务的配置文件。你可以在此页面上找到 [EAS 构建](/build/introduction) 和 [EAS 提交](/submit/introduction) 的所有可用架构属性的完整参考。 ¥**eas.json** is the configuration file for EAS CLI and services. You can find the complete reference of all available schema properties for [EAS Build](/build/introduction) and [EAS Submit](/submit/introduction) on this page. > **info** 要了解有关如何使用 eas.json 配置使用 EAS 服务的项目的更多信息,请参阅 [使用 eas.json 配置 EAS 构建](/build/eas-json/) 和 [使用 eas.json 配置 EAS 提交](/submit/eas-json/)。 > > ¥To learn more about how a project using EAS services is configured with **eas.json**, see [Configure EAS Build with eas.json](/build/eas-json/) and [Configure EAS Submit with eas.json](/submit/eas-json/). ## EAS 构建 ¥EAS Build eas.json 中 `build` 键的架构中提供以下属性。 ¥The following properties are available in the schema for the `build` key in **eas.json**. Note: Example schema of multiple build profiles --- !!!IG0!!! --- ### Common properties for native platforms ### Android-specific options ### iOS-specific options ## EAS Submit The following properties are available in the schema for the `submit` key in **eas.json**. Note: Example schema of with production profile --- !!!IG1!!! --- ### Android-specific options ### iOS-specific options ## EAS 中的环境变量 通过示例了解如何在 EAS 中使用和管理环境变量。 [Expo 中的环境变量](/guides/environment-variables) 描述如何使用环境变量与 Expo 框架和 .env 文件来设置可以在 JavaScript 代码中内联的环境变量。Expo CLI 将使用开发机器上 .env 文件中的相应环境变量值替换代码中的前缀变量(例如 `process.env.EXPO_PUBLIC_VARNAME`)。 ¥[Environment variables in Expo](/guides/environment-variables) describe how to use environment variables with the Expo framework and **.env** files to set environment variables that can be inlined in your JavaScript code. Expo CLI will substitute prefixed variables in your code (for example, `process.env.EXPO_PUBLIC_VARNAME`) with the corresponding environment variable values in **.env** files on your development machine. 由于 EAS Build 和 Workflows 作业在远程服务器上运行,因此 .env 文件可能不可用。例如,如果 .env 文件被排除在你的项目之外,那是因为它们已在 .gitignore 文件中列出,或者尚未提交到你的本地版本控制系统。 ¥Since EAS Build and Workflows jobs run on a remote server, **.env** files may not be available. For instance, if **.env** files are excluded from your project, it is because they are listed in **.gitignore** or not committed to your local version control system. 此外,你可能希望在构建时使用项目代码之外的环境变量来自定义应用二进制文件,例如为错误报告服务设置包标识符或私钥。为了满足这些需求,我们有一个单独的(但兼容的)机制来管理 EAS 中的环境变量,该机制专注于在 EAS 服务器上存储和管理环境变量并同步它们以进行本地开发。 ¥Additionally, you may want to use environment variables outside of your project code to customize your app binary at build time, like setting a bundle identifier or a private key for an error reporting service. To accommodate for those needs we have a separate (but compatible) mechanism for managing environment variables in EAS, which is focused on storing and managing environment variables on EAS servers and synchronizing them for local development. 本指南通过关键实例介绍了如何在 EAS 中使用和管理环境变量。 ¥This guide describes how to use and manage environment variables in EAS with key practical examples. ## 关键概念 ¥Key concepts Note: Environments in EAS --- Currently, EAS supports three environments for environment variables: `development`, `preview` and `production`. Environments are independent sets of environment variables that can be used to customize your app in different contexts. For example, you might want to use different API keys for development and production, or different bundle identifiers for different App Store releases. Environments allows you to do so. Every EAS Build and Workflows job runs using environment variables from one of the available environments. You can also use environments for updates, allowing you to use the same set of environment variables for your build jobs. You can do this when publishing an update by providing the `--environment` flag. Environment variables can be assigned to multiple environments and have the same value across all of them, or be created for a single environment, so that you can set a specific value for a single environment. --- Note: Project-wide environment variables --- Project-wide environment variables are specific to a single EAS project. You can view and manage them by navigating to the [Environment Variables](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) page on your project. These environment variables are available in any jobs that run on EAS servers and updates for this project. They can also be pulled locally for development if their visibility setting allows it. --- Note: Account-wide environment variables --- Account-wide environment variables are available across all of your projects in your EAS account. You can view and manage them by navigating to [Environment Variables](https://expo.dev/accounts/[account]/settings/environment-variables) page on your account. They are available in jobs that run on EAS servers and updates, together with project-wide variables for a project. You can pull them locally or read outside of EAS servers if their visibility setting allows it. --- Note: Visibility settings for environment variables --- There are three different visibility settings: | Visibility | Description | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------- | | Plain text | Visible on the website, in EAS CLI, and in logs. | | Sensitive | Obfuscated in build and workflow jobs logs. You can use a toggle to make them visible on the website. They're also readable in EAS CLI. | | Secret | Not readable outside of the EAS servers, including on the website and in EAS CLI. They are obfuscated in build and workflow jobs logs. | > **warning** Always remember that **anything that is included in your client side code should be considered public and readable to any individual that can run the application**. Secret type environment variables are intended to be used to provide values to an EAS Build or Workflows job so that they may be used to alter how a job runs. For example, a good use case is setting an `NPM_TOKEN` to install private packages from npm, or a setting a Sentry API key to create a release and upload your source maps. Secrets do not provide any additional security for values that you end up embedding in your application itself. --- ## Creating and using environment variables The sections below use the following common environment variables as examples: - `EXPO_PUBLIC_API_URL`: a plain text [`EXPO_PUBLIC_`](/guides/environment-variables/) variable that holds the URL of the API server - `APP_VARIANT`: a plain text variable to select an [app variant](/tutorial/eas/multiple-app-variants/) - `GOOGLE_SERVICES_JSON`: a secret file variable to supply your git ignored **google-services.json** file to the build job - `SENTRY_AUTH_TOKEN`: a sensitive variable that holds the authentication token for Sentry used to upload source maps after builds and updates ### Use environment variables in your code The environment variables with the [`EXPO_PUBLIC_`](/guides/environment-variables) prefix are available as `process.env` variables in your app's code. This means you can use them to dynamically configure your app behavior based on the values from environment variables. !!!IG0!!! In above example, `EXPO_PUBLIC_API_URL` is used to dynamically set the API URL for the fetch request. > **warning** Do not store sensitive information in `EXPO_PUBLIC_` variables, such as private keys. These variables will be visible in plain-text in your compiled app. Other variables, without the `EXPO_PUBLIC_` prefix, can be used during app config resolution. An example of this is the `APP_VARIANT` variable used to determine the app name and package name or bundle identifier based on the selected app variant. !!!IG1!!! The `GOOGLE_SERVICES_JSON` is a secret file variable that is not readable outside of EAS servers and is used to provide the git ignored **google-services.json** file to the build job. To use it in the app config, you can use the `process.env` variable and provide a fallback value in case the variable is not set (for local development when you usually have it inside your project's repository). !!!IG2!!! ### Create environment variables To create environment variables on EAS servers, you can use the [environment variables creation form](https://expo.dev/accounts/[account]/projects/[project]/environment-variables/new) page or `eas env:create` command. In the form, you can specify the name, value, environment(s) and visibility for the variable. Created environment variables will be available in the list on the [Environment Variables](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) page on the Expo website. Based on the environment variables in this example, the list will look like this: In above example, the `SENTRY_AUTH_TOKEN` can be treated as a sensitive environment variable. It is used to authenticate Sentry to upload source maps after builds and updates, so it has to be accessible outside of EAS servers. ### Pull environment variables for your local development The easiest way to use the EAS environment variables for local development is to pull them into a **.env** file using the `eas env:pull --environment environment` command. You can also use the Export option on the [EAS dashboard](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) to download the file and store it inside your project. Run the following command to create a **.env** file in the root of your project: ```sh $ eas env:pull --environment development ``` The created **.env** file will look like this: !!!IG3!!! The downloaded `EXPO_PUBLIC_` variables are available for local development when using the `npx expo start` command. The `GOOGLE_SERVICES_JSON` variable is not available to be pulled to the local environment since it is a secret variable. > **warning** It is best to add all of the **.env** files to **.gitignore** to avoid committing them to your repository and exposing sensitive information. Committing a **.env** file may additionally lead to environment variables resolution conflicts. ### Use environment variables with EAS Build To have a full control over the environments used for your builds, you can specify the [`environment`](/eas/json#environment) field in the build profiles settings in the **eas.json** file. !!!IG4!!! All of the environment variables from the selected environment will be used during the build process. Plain text and sensitive variables will be available when resolving build configuration based on the dynamic app config in EAS CLI as well. > **info** The environment variables of secret type are not available during build configuration resolution in EAS CLI as they are not readable outside of the EAS servers. In this example app, all of the environment variables except `GOOGLE_SERVICES_JSON` (which is secret) will be available during build configuration resolution in EAS CLI. It's especially important in this context to have the correct visibility set for the `APP_VARIANT` variable so that the variable available in EAS CLI and is able to generate credentials for the correct package name and bundle identifier. ### Use environment variables with EAS Update The most convenient way to use EAS environment variables with EAS Update is to run the `eas update` command with the `--environment` flag specified: ```sh $ eas update --environment production ``` When the `--environment` flag is used, **only the environment variables from the specified EAS environment will be used during the update process** and won't use the **.env** files present in your project. This flag allows you to use the same environment variables while creating updates as with creating builds. Expo CLI will substitute prefixed variables in your code (for example,`process.env.EXPO_PUBLIC_VARNAME`) with the corresponding plain text and sensitive environment variable values set on EAS servers for the environment specified with the `--environment` flag. Any `EXPO_PUBLIC_` variables in your application code will be replaced inline with the corresponding values from your EAS environment whether that is your local machine or your CI/CD server. We recommend using the `--environment` flag to ensure the same environment variables are used both for your update and build jobs. > **info** The secret variables will not be available during the update process as they are not readable outside of EAS servers. Note: Using .env files with EAS Update --- When the `--environment` flag is **not provided**, `eas update` will use the **.env** files present in your project directory for the update job and won't use the environment variables set on the EAS servers. [Environment variables in Expo](/guides/environment-variables) describes how to use **.env** files to set and use environment variables within your JavaScript code. Expo CLI will substitute prefixed variables in your code (for example,`process.env.EXPO_PUBLIC_VARNAME`) with the corresponding environment variable values in **.env** files present on your development machine. When you run `eas update`, all **.env** files will be evaluated when your JavaScript is bundled. Any `EXPO_PUBLIC_` variables in your application code will be replaced inline with the corresponding values from your **.env** files that are present on the machine from which the update is published, whether that is your local machine or your CI/CD server. > **info** When using **.env** files with EAS Update, `EXPO_PUBLIC_` variables in these files will only be used when bundling your app's JavaScript. They will not be available when evaluating **app.config.js**. --- ### Use environment variables for other commands One way to supply non-secret EAS environment variables to other EAS commands is to use the `eas env:exec` command. ```sh $ eas env:exec --environment production 'echo $APP_VARIANT' ``` For example, it can be useful when uploading source maps to Sentry using a [`SENTRY_AUTH_TOKEN`](/guides/using-sentry) variable after an update bundle is created. ```sh $ eas env:exec --environment production 'npx sentry-expo-upload-sourcemaps dist' ``` ### EAS Build and Workflows job runs #### Setting the environment for your builds To set the environment for your EAS Build jobs, you can use the `environment` option in **eas.json** and set it to one of the available environments: `development`, `preview` or `production`. !!!IG5!!! If you don't set the `environment` option, we will set the environment automatically based on your build's configuration: - `development`: for development client build - `preview`: for other configurations - `production`: for app store build > **info** This resolution logic is only available for EAS CLI in version 13.3.0 and higher. For older CLI versions, we always assume the `production` environment for all builds. Note: Built-in environment variables --- The following environment variables are additional system environment variables exposed to each job and can be used within any build step. They are not a part of any project environment and are not available when evaluating **app.config.js** locally: - `CI=1`: indicates this is a CI environment - `EAS_BUILD=true`: indicates this is an EAS Build environment - `EAS_BUILD_PLATFORM`: either `android` or `ios` - `EAS_BUILD_RUNNER`: either `eas-build` for EAS Build cloud builds or `local-build-plugin` for [local builds](/build-reference/local-builds/) - `EAS_BUILD_ID`: the build ID, for example, `f51831f0-ea30-406a-8c5f-f8e1cc57d39c` - `EAS_BUILD_PROFILE`: the name of the build profile from **eas.json**, for example, `production` - `EAS_BUILD_GIT_COMMIT_HASH`: the hash of the Git commit, for example, `88f28ab5ea39108ade978de2d0d1adeedf0ece76` - `EAS_BUILD_NPM_CACHE_URL`: the URL of npm cache ([learn more](/build-reference/private-npm-packages)) - `EAS_BUILD_MAVEN_CACHE_URL`: the URL of Maven cache ([learn more](/build-reference/caching/#android-dependencies)) - `EAS_BUILD_COCOAPODS_CACHE_URL`: the URL of CocoaPods cache ([learn more](/build-reference/caching/#ios-dependencies)) - `EAS_BUILD_USERNAME`: the username of the user initiating the build (it's undefined for bot users) - `EAS_BUILD_WORKINGDIR`: the remote directory path with your project --- #### Dynamically setting environment variables during the job execution You can also set environment variables dynamically during the job execution using the `set-env` command. The `set-env` executable is available in the `PATH` on EAS Build workers, and can be used to set environment variables that will be visible in the next build phases. For example, you can add the following in one of the [EAS Build hooks](/build-reference/npm-hooks/) and the environment variable `EXAMPLE_ENV` will be available until the end of the build job. ```sh $ set-env EXAMPLE_ENV "example value" ``` #### Accessing environment variables After creating an environment variable, you can read it on subsequent EAS Build jobs with `process.env.VARIABLE_NAME` from Node.js or in shell scripts as `$VARIABLE_NAME`. ## Managing environment variables Note: Managing environment variables using EAS dashboard --- The easiest way to create, read, update, export and delete environment variables is to use the Expo website. Navigate to the [**Environment variables** page on your project](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) or [**Environment variables** page on your account](https://expo.dev/accounts/[account]/settings/environment-variables) to manage your environment variables. --- Note: Managing environment variables using EAS CLI --- To manage environment variables using EAS CLI, you can use the `eas env:create`, `eas env:update`, `eas env:list`, and `eas env:delete` commands. You can additionally use `eas env:pull` command to pull environment variables from EAS servers to your local **.env** file for development. See the [EAS CLI command reference](https://github.com/expo/eas-cli/blob/main/packages/eas-cli/README.md) for more information about these commands. --- ## Common questions Note: What is the recommended workflow for using environment variables in my EAS project? --- One possible way to efficiently work with environment variables in your EAS projects is to: #### Use correct visibility settings Make sure to set the visibility of your environment variables to the appropriate level. Avoid setting excessive secret visibility to `EXPO_PUBLIC_` variables that are used in your app's JavaScript code or are used to resolve your app's configuration. Be aware that environment variables with secret visibility are not readable outside of EAS servers, and can't be pulled locally for development or to bundle your app's JavaScript code for updates. #### Add .env files to .gitignore To avoid confusing overrides during cloud jobs and leaking sensitive information, add **.env** files to your **.gitignore** file. #### Use the `--environment` flag with `eas update` When publishing updates, use the `--environment` flag with the `eas update` command to ensure that the same environment variables are used for your updates as your build jobs. When the `--environment` flag is provided, `eas update` will use the environment variables on EAS servers for the update job and won't use the **.env** files present in your project often used for local development. #### Sync the environment variables for local development using `eas env:pull` You can use the `eas env:pull` command to pull environment variables from EAS servers to your local **.env** file for development. The ideal environment that can be used for this purpose is the `development` environment, as it's the default environment used for development builds. #### Explicitly specify the environment for your builds Explicitly set the [`environment`](/eas/json#environment) value in **eas.json** for your build profiles to ensure that the correct environment variables are always used for your build jobs and you have full control over this process. --- !!!IG7!!! Note: Can I set my environment variables on a CI provider when triggering the build using command? --- Environment variables must be defined on EAS servers to be made available to EAS Build builders. If you are triggering builds from CI the same rule applies, and you should be careful to not confuse setting environment variables on GitHub Actions (or the provider of your choice) with setting environment variables and secrets on EAS servers. --- !!!IG8!!! Note: How do environment variables work for my development builds? --- Environment variables set in your build profile that impact **app.config.js** will be used for configuring the development build. When you run `npx expo start` to load your app inside of your development build, only environment variables that are available on your development machine will be used. --- Note: Can I use file environment variables in my EAS project? --- In addition to setting strings as values, you can also upload files as the value of an environment variable. One common use case of using file environment variable is passing a git ignored **google-services.json** configuration file to a build job. During the job run, the file will be created in a location outside of the project directory and the path to the file will be assigned to the environment variable (`GOOGLE_SERVICES_JSON=/path/to/google-services.json`). For example, you can then set `android.googleServicesFile` in your app config to the value of the `GOOGLE_SERVICES_JSON` environment variable to use this file when executing the build or workflow job. !!!IG6!!! --- Note: Differences between handling environment variables in EAS CLI and Expo CLI --- One of the differences between using environment variables with the Expo framework and EAS is that EAS CLI itself does not support loading **.env** files to set environment variables when resolving the app config. Instead, it's recommended to use the EAS environment variables management system with EAS CLI commands to set environment variables for your build jobs and updates to avoid confusion, and ensure that exactly the same environment variables are used both for: - Local app config resolution, done by EAS CLI when preparing the app config - Remote jobs happening on EAS servers, which often don't have access to your local **.env** files that are git ignored Using `eas update` is the one exception to this rule. By default, for backward compatibility reasons, it uses **.env** files present in your project directory to set environment variables for the update job, the same way [Expo CLI](/guides/environment-variables) does (it executes the `npx expo export` command under the hood). To ensure that you can use the same environment variables in your updates as your build jobs, you can use the `--environment` flag with the `eas update` command to force it to use **only** the environment variables set on the EAS servers instead of the **.env** files present in your project directory. It will ignore environment variables from **.env**. --- Note: Are there any limitations to using environment variables in EAS? --- - Environment variable value size is limited to 32 KiB for environment variables with secret visibility and 4 KiB for other visibility types. - You can create up to 100 account-wide environment variables for each Expo account and 100 project-specific environment variables for each app. --- # EAS 工作流程 ## 开始使用 EAS 工作流 了解如何使用 EAS Workflows 自动化你的 React Native CI/CD 开发和发布流程。 EAS Workflows 允许你自动化开发和发布流程。它是一项 React Native CI/CD 服务,可以构建和提交每两周一次的应用商店版本,为每次提交创建预览更新等等。 ¥EAS Workflows allow you to automate your development and release processes. It's a React Native CI/CD service that can build and submit your biweekly app store releases, create preview updates for every commit, and more. Video Tutorial: [Watch: Get Started with EAS Workflows](https://www.youtube.com/watch?v=OJ2u9tQCpr4) Note: How do workflows compare to other CI services? --- EAS Workflows are designed to help you and your team release your app. It comes preconfigured with pre-packaged job types that can build, submit, update, run Maestro tests, and more. All job types run on EAS, so you'll only have to manage one set of YAML files, and all the artifacts from your job runs will appear on [expo.dev](https://expo.dev/). Other CI services, like CircleCI and GitHub Actions, are more generalized and have the ability to do more than workflows. However, those services also require you to understand more about the implementation of each job. While that is necessary in some cases, workflows help you get common tasks done quickly by pre-packaging the most essential types of jobs for app developers. In addition, workflows are designed to provide you with the fastest possible cloud machine for the task at hand, and we're constantly updating those for you. EAS Workflows are great for operations related to your Expo apps, while other CI/CD services will provide a better experience for other types of workflows. --- Note: Considering Workflows? Share the following slide in your next team meeting --- Share the following slide in your next team meeting to discuss what EAS Workflows are and how they can help your team: --- ## Get started You'll need to [sign up](https://expo.dev/signup) for an Expo account. You'll need to create a project with the following command: ```sh $ npx create-expo-app@latest ``` You'll need to sync the project with EAS with the following command. This will create an EAS project and link it to your local project: ```sh $ npx eas-cli@latest init ``` You'll need to add an `eas.json` file to the root of your project if it doesn't already exist: ```sh $ touch eas.json && echo "{}" > eas.json ``` Step 1: Create a directory named **.eas/workflows** at the root of your project with a YAML file inside of it. For example: **.eas/workflows/create-production-builds.yml**. Step 2: Add the following YAML to the `create-production-builds.yml` file: ```yaml .eas/workflows/create-production-builds.yml name: Create Production Builds jobs: build_android: type: build # This job type creates a production build for Android params: platform: android build_ios: type: build # This job type creates a production build for iOS params: platform: ios ``` The workflow above will create a production build for Android and iOS in parallel. To run this workflow successfully, you'll need to [set up and build your project using EAS CLI](/build/setup/) first. Step 3: Finally, run the workflow with the following command: ```sh $ npx eas-cli@latest workflow:run create-production-builds.yml ``` Once you do, you can see your workflow running on your project's [workflows page](https://expo.dev/accounts/[account]/projects/[projectName]/workflows). ## More ### Automate workflows with GitHub events You can trigger a workflow by pushing a commit to your GitHub repository. You can link a GitHub repo to your EAS project with the following steps: - Navigate to your project's [GitHub settings](https://expo.dev/accounts/%5Baccount%5D/projects/%5BprojectName%5D/github). - Follow the UI to install the GitHub app. - Select the GitHub repository that matches the Expo project and connect it. Then, add the [`on` trigger](/eas/workflows/syntax/#on) to your workflow file. For example, if you want to trigger the workflow when a commit is pushed to the `main` branch, you can add the following: ```yaml .eas/workflows/create-production-builds.yml name: Create Production Builds # @info # on: push: branches: ['main'] # @end # jobs: build_android: type: build params: platform: android build_ios: type: build ``` ### VS Code extension Download the [Expo Tools VS Code extension](https://marketplace.visualstudio.com/items?itemName=expo.vscode-expo-tools) to get descriptions and autocompletions for your workflow files. > 有反馈或功能请求吗?请发送电子邮件至 [workflows@expo.dev](mailto:workflows@expo.dev)。 ¥Got feedback or feature requests? Send us an email at [workflows@expo.dev](mailto:workflows@expo.dev). ## 预打包作业 了解如何设置和使用预打包的作业。 预打包作业是随时可用的工作流作业,可帮助你自动执行常见任务,例如构建、提交和测试应用。它们提供了一种标准化的方法来处理这些操作,而无需从头编写自定义作业配置。本指南介绍了可用的预打包作业以及如何在你的工作流中使用它们。 ¥Pre-packaged jobs are ready-to-use workflow jobs that help you automate common tasks like building, submitting, and testing your app. They provide a standardized way to handle these operations without having to write custom job configurations from scratch. This guide covers the available pre-packaged jobs and how to use them in your workflows. ## 构建 ¥Build 将你的项目构建成 Android 或 iOS 应用。 ¥Build your project into an Android or iOS app. 可以自定义构建作业,以便你可以在构建过程中执行自定义命令。请参阅 [定制构建](/custom-builds/get-started/) 了解更多信息。 ¥Build jobs can be customized so that you can execute custom commands during the build process. See [Custom builds](/custom-builds/get-started/) for more information. ### 先决条件 ¥Prerequisites 要成功使用构建作业,你需要使用与预打包作业相同的平台和配置文件,通过 EAS CLI 完成构建。了解如何使用 [创建你的第一个构建](/build/setup/) 开始使用。 ¥To successfully use the build job, you'll need to complete a build with EAS CLI using the same platform and profile as the pre-packaged job. Learn how to [create your first build](/build/setup/) to get started. ### 语法 ¥Syntax ```yaml jobs: build_app: type: build params: platform: android | ios # required profile: string # optional, default: production ``` #### 参数 ¥Parameters 你可以将以下参数传递到 `params` 列表中: ¥You can pass the following parameters into the `params` list: | 参数 | 类型 | 描述 | | -------- | ------ | ---------------------------------- | | platform | string | 必填。构建所针对的平台。可以是 `android` 或 `ios`。 | | profile | string | 可选。要使用的构建配置文件。默认为 `production`。 | #### 输出 ¥Outputs 你可以在后续作业中引用以下输出: ¥You can reference the following outputs in subsequent jobs: | 输出 | 类型 | 描述 | | ------------------- | ------ | --------------------------------- | | build\_id | string | 已创建的构建版本的 ID。 | | app\_build\_version | string | 应用的版本号/内部版本号。 | | app\_identifier | string | 应用的软件包标识符/包名称。 | | app\_version | string | 应用的版本。 | | channel | string | 用于构建的更新渠道。 | | distribution | string | 使用的分发方法。可以是 `internal` 或 `store`。 | | fingerprint\_hash | string | 构建的指纹哈希值。 | | git\_commit\_hash | string | 用于构建的 git 提交哈希值。 | | platform | string | 构建所针对的平台。`android` 或 `ios`。 | | profile | string | 使用的构建配置文件。 | | runtime\_version | string | 使用的运行时版本。 | | sdk\_version | string | 使用的 SDK 版本。 | | simulator | string | 构建是否用于模拟器。 | ### 示例 ¥Examples 以下是一些使用构建作业的实际示例: ¥Here are some practical examples of using the build job: Note: Basic build for a specific platform --- This workflow builds your iOS app whenever you push to the main branch. !!!IG1!!! --- Note: Build for both platforms in parallel --- This workflow builds both Android and iOS apps in parallel when you push to the main branch. !!!IG2!!! --- Note: Build with environment variables --- This workflow builds your Android app with custom environment variables that can be used during the build process. !!!IG3!!! --- Note: Build with different profiles --- This workflow creates two different Android builds using different profiles - one for internal distribution and one for store submission using the development and production profiles. !!!IG4!!! --- ## Deploy Deploy your application using [EAS Hosting](/eas/hosting/introduction). ### Prerequisites To deploy your application using EAS Hosting, you'll need to set up your project. See [Get Started with EAS Hosting](/eas/hosting/get-started/#prerequisites) for more information. ### Syntax !!!IG5!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | --------- | ------- | ---------------------------------------------------------------------------------- | | alias | string | Optional. The [alias](/eas/hosting/deployments-and-aliases/#aliases) to deploy to. | | prod | boolean | Optional. Whether to deploy to production. | ### Examples Here are some practical examples of using the deploy job: Note: Basic deployment to production --- This workflow deploys your application to production using EAS Hosting. !!!IG6!!! --- Note: Deploy to production only on merges to the `main` branch --- This workflow deploys your application to production when you merge to the main branch, and makes a non-production deployment on all other branches. !!!IG7!!! --- Note: Deployment with custom alias --- This workflow deploys your application to a custom alias in production. !!!IG8!!! --- ## Fingerprint Calculates a fingerprint of your project. > **Note:** This job type only supports [CNG (managed)](/workflow/continuous-native-generation/) workflows. If you commit your **android** or **ios** directories, the fingerprint job won't work. ### Syntax !!!IG9!!! #### Outputs You can reference the following outputs in subsequent jobs: | Output | Type | Description | | ------------------------ | ------ | --------------------------------- | | android_fingerprint_hash | string | The fingerprint hash for Android. | | ios_fingerprint_hash | string | The fingerprint hash for iOS. | ### Examples Here are some practical examples of using the fingerprint job: Note: Basic fingerprint calculation --- This workflow calculates fingerprints for both Android and iOS builds in the production environment. !!!IG10!!! --- Note: Fingerprint with environment variables --- This workflow calculates fingerprints with custom environment variables that can affect the build configuration. !!!IG11!!! --- ## Get Build Retrieve an existing build from EAS that matches the provided parameters. ### Syntax !!!IG12!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | ----------------- | ------- | ------------------------------------------------------------------------------ | | platform | string | Optional. The platform to get the build for. Can be `ios` or `android`. | | profile | string | Optional. The build profile to use. | | distribution | string | Optional. The distribution method. Can be `store`, `internal`, or `simulator`. | | channel | string | Optional. The update channel. | | app_identifier | string | Optional. The bundle identifier/package name. | | app_build_version | string | Optional. The build version. | | app_version | string | Optional. The app version. | | git_commit_hash | string | Optional. The git commit hash. | | fingerprint_hash | string | Optional. The fingerprint hash. | | sdk_version | string | Optional. The SDK version. | | runtime_version | string | Optional. The runtime version. | | simulator | boolean | Optional. Whether to get a simulator build. | #### Outputs You can reference the following outputs in subsequent jobs: | Output | Type | Description | | ----------------- | ------ | ---------------------------------------------- | | build_id | string | The ID of the retrieved build. | | app_build_version | string | The build version of the app. | | app_identifier | string | The bundle identifier/package name of the app. | | app_version | string | The version of the app. | | channel | string | The update channel used for the build. | | distribution | string | The distribution method used. | | fingerprint_hash | string | The fingerprint hash of the build. | | git_commit_hash | string | The git commit hash used for the build. | | platform | string | The platform the build was created for. | | profile | string | The build profile used. | | runtime_version | string | The runtime version used. | | sdk_version | string | The SDK version used. | | simulator | string | Whether the build is for simulator. | ### Examples Here are some practical examples of using the get-build job: Note: Get latest production build --- This workflow retrieves the latest production build for iOS from the store distribution channel. !!!IG13!!! --- Note: Get build by version --- This workflow retrieves a specific version of an Android build by its app version and build version. !!!IG14!!! --- Note: Get simulator build --- This workflow retrieves a simulator build for iOS development. !!!IG15!!! --- ## Submit Submit an Android or iOS build to the app store using EAS Submit. ### Prerequisites Submission jobs require additional configuration to run within a CI/CD process. See our [Apple App Store CI/CD submission guide](/submit/ios/#submitting-your-app-using-cicd-services) and [Google Play Store CI/CD submission guide](/submit/android/#submitting-your-app-using-cicd-services) for more information. ### Syntax !!!IG16!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | build_id | string | Required. The ID of the build to submit. | | profile | string | Optional. The submit profile to use. Defaults to `production`. | | groups | string[] | Optional. An array of TestFlight internal group names to add the build to (only affects iOS submissions; for Android, this parameter is ignored). Overrides `groups` in the [submit profile](/eas/json/#ios-specific-options-1). Note: On top of the groups you provide here, the build will be automatically added to the groups that have been created with the "Enable automatic distribution" App Store Connect setting. | #### Outputs You can reference the following outputs in subsequent jobs: | Output | Type | Description | | --------------------- | ------ | ------------------------------------------------- | | apple_app_id | string | The Apple App ID of the submitted build. | | ios_bundle_identifier | string | The iOS bundle identifier of the submitted build. | | android_package_id | string | The Android package ID of the submitted build. | ### Examples Here are some practical examples of using the submit job: Note: Submit iOS build --- This workflow submits an iOS build to the App Store using the production submit profile. !!!IG17!!! --- Note: Submit Android build --- This workflow submits an Android build to the Play Store using the production submit profile. !!!IG18!!! --- ## Update Publish an update using [EAS Update](/eas-update/introduction/). ### Prerequisites To publish update previews and to send over-the-air updates, you'll need to run `npx eas-cli@latest update:configure`, then create new builds. Learn more about [configuring EAS Update](/eas-update/getting-started/#prerequisites). ### Syntax !!!IG19!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | message | string | Optional. Message to use for the update. If not provided, the commit message will be used. | | platform | string | Optional. Platform to use for the update. Can be `android`, `ios`, or `all`. Defaults to `all`. | | branch | string | Optional. Branch to use for the update. If not provided, the branch from the workflow run will be used. For manually run workflows you need to provide a value. Example: `${{ github.ref_name \|\| 'testing' }}`. Provide _either_ a branch or a channel, not both. | | channel | string | Optional. Channel to use for the update. Provide _either_ a branch or a channel, not both. | #### Outputs You can reference the following outputs in subsequent jobs: | Output | Type | Description | | --------------------- | ------ | ------------------------------------------------------------- | | first_update_group_id | string | The ID of the first update group. | | updates_json | string | A JSON string containing information about all update groups. | ### Examples Here are some practical examples of using the update job: Note: Basic update to production channel --- This workflow publishes an update to the production channel whenever you push to the main branch, using the commit message as the update message. !!!IG20!!! --- Note: Platform-specific updates --- This workflow publishes separate updates for Android and iOS platforms, allowing for platform-specific changes. !!!IG21!!! --- Note: Update with branch-based deployment --- This workflow publishes updates based on the branch name, allowing for different environments (staging/production) based on the branch. !!!IG22!!! --- ## Maestro Run Maestro tests on a Android emulator or iOS Simulator build. > **important** Maestro tests are experimental and may experience flakiness. ### Syntax !!!IG23!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | ---------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | build_id | string | Required. The ID of the build to test. | | flow_path | string | Required. The path to the Maestro flow file(s) to run. | | shards | number | Optional and experimental. The number of shards to split the tests into. Defaults to 1. | | retries | number | Optional. The number of times to retry failed tests. Defaults to 1. | | record_screen | boolean | Optional. Whether to record the screen. Defaults to false. Note: recording screen may impact emulator performance. You may want to use large runners when recording screen. | | include_tags | string | Optional. Flow tags to include in the tests. Will be passed to Maestro as `--include-tags`. | | exclude_tags | string | Optional. Flow tags to exclude from the tests. Will be passed to Maestro as `--exclude-tags`. | | maestro_version | string | Optional. Version of Maestro to use for the tests. If not provided, the latest version will be used. | | android_system_image_package | string | Optional. Android Emulator system image package to use. Run `sdkmanager --list` on your machine to list available packages. Choose an `x86_64` variant. Examples: `system-images;android-36;google_apis;x86_64`, `system-images;android-35-ext15;google_apis_playstore;x86_64`. Note that newer images require more computing resources, for which you may want to use large runners. | | device_identifier | string or `{ android?: string, ios?: string }` object | Optional. Device identifier to use for the tests. You can also use a single-value expression like `pixel_6`, `iPhone 16 Plus` or `${{ needs.build.outputs.platform == "android" ? "pixel_6" : "iPhone 16 Plus" }}` and an object like `device_identifier: { android: "pixel_6", ios: "iPhone 16 Plus" }`. Note that iOS devices availability differs across runner images. A list of available devices can be found in the jobs logs. | ### Examples Here are some practical examples of using the Maestro job: Note: Basic Maestro test --- This workflow runs Maestro tests on an iOS Simulator build using the default settings. !!!IG24!!! --- Note: Maestro test with sharding --- This workflow runs Maestro tests on an Android emulator build with 3 shards and 2 retries for failed tests. !!!IG25!!! --- Note: Using Maestro prefixed environment variables --- Maestro can automatically read environment variables in a workflow when the variable is prefixed by `MAESTRO_`. For more information, see the [Maestro documentation on shell variables](https://docs.maestro.dev/advanced/parameters-and-constants#accessing-variables-from-the-shell). !!!IG26!!! --- Note: Recording screen and using a specific device --- This workflow runs Maestro tests on an Android emulator build with a specific device and records the screen. !!!IG27!!! --- ## Maestro Cloud Run Maestro tests on Maestro Cloud. > **important** This requires a Maestro Cloud account and Cloud Plan subscription. Go to [Maestro docs](https://docs.maestro.dev/cloud/run-maestro-tests-in-the-cloud) to learn more. ### Syntax !!!IG28!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | ------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | build_id | string | Required. The ID of the build to test. Example: `${{ needs.build_android.outputs.build_id }}`. | | maestro_project_id | string | Required. The ID of the Maestro Cloud project to use. Corresponds to `--project-id` param to `maestro cloud`. Example: `proj_01jw6hxgmdffrbye9fqn0pyzm0`. Go to [Maestro Cloud](https://app.maestro.dev/) to find yours. | | flows | string | Required. The path to the Maestro flow file or directory containing the flows to run. Corresponds to `--flows` param to `maestro cloud`. | | maestro_api_key | string | Optional. The API key to use for the Maestro project. By default, `MAESTRO_CLOUD_API_KEY` environment variable will be used. Corresponds to `--api-key` param to `maestro cloud`. | | include_tags | string | Optional. The tags to include in the tests. Corresponds to `--include-tags` param to `maestro cloud`. Example: `"pull,push"`. | | exclude_tags | string | Optional. The tags to exclude from the tests. Corresponds to `--exclude-tags` param to `maestro cloud`. Example: `"disabled"`. | | maestro_version | string | Optional. The version of Maestro to use. Example: `1.30.0`. | | android_api_level | string | Optional. The Android API level to use. Corresponds to `--android-api-level` param to `maestro cloud`. Example: `29`. | | maestro_config | string | Optional. The path to the Maestro `config.yaml` file to use. Corresponds to `--config` param to `maestro cloud`. Example: `.maestro/config.yaml`. | | device_locale | string | Optional. The locale that will be set on devices used for the tests. Corresponds to `--device-locale` param to `maestro cloud`. Example: `pl_PL`. | | device_model | string | Optional. The model of the device to use for the tests. Corresponds to `--device-model` param to `maestro cloud`. Example: `iPhone-11`. Run `maestro cloud --help` for a list of supported values. | | device_os | string | Optional. The OS of the device to use for the tests. Corresponds to `--device-os` param to `maestro cloud`. Example: `iOS-18-2`. Run `maestro cloud --help` for a list of supported values. | | name | string | Optional. Name for the Maestro Cloud upload. Corresponds to `--name` param to `maestro cloud`. | | branch | string | Optional. Override for the branch the Maestro Cloud upload originated from. By default, if the workflow run has been triggered from GitHub, the branch of the workflow run will be used. Corresponds to `--branch` param to `maestro cloud`. | | async | boolean | Optional. Run the Maestro Cloud tests asynchronously. If true, the status of the job will only denote whether the upload was successful, _not_ whether the tests succeeded. Corresponds to `--async` param to `maestro cloud`. | > **important** You need to either set `maestro_api_key` parameter or `MAESTRO_CLOUD_API_KEY` environment variable in the job environment. Go to "Settings" on [Maestro Cloud](https://app.maestro.dev/) to generate an API key and then to [Environment variables](https://expo.dev/accounts/[account]/projects/[project]/environment-variables) to add it to your project. ### Examples Here are some practical examples of using the Maestro job: Note: Basic Maestro Cloud test --- This workflow runs Maestro tests on an iOS Simulator build using the default settings. !!!IG29!!! --- Note: Using Maestro prefixed environment variables --- Maestro can automatically read environment variables in a workflow when the variable is prefixed by `MAESTRO_`. For more information, see the [Maestro documentation on shell variables](https://docs.maestro.dev/advanced/parameters-and-constants#accessing-variables-from-the-shell). !!!IG30!!! --- ## Slack Send a message to a Slack channel using a [Slack webhook URL](https://api.slack.com/messaging/webhooks). ### Syntax !!!IG31!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | ----------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | webhook_url | string | Required. The Slack webhook URL to send the message to. Currently only hardcoded strings are supported. Using webhooks stored in `env` are upcoming but not yet supported. | | message | string | Required if payload is not provided. The message to send. | | payload | object | Required if message is not provided. The [Slack Block Kit](https://api.slack.com/block-kit) payload to send. | ### Examples Here are some practical examples of using the Slack job: Note: Basic build notification --- This workflow builds an iOS app and then sends a notification with the app identifier and version from the build job outputs. !!!IG32!!! --- Note: Rich build notification with Block Kit --- This workflow builds an Android app and sends a rich notification using the build job outputs. !!!IG33!!! --- ## Require Approval Require approval from a user before continuing with the workflow. A user can approve or reject which translates to success or failure of the job. ### Syntax !!!IG34!!! #### Parameters This job doesn't take any parameters. ### Examples Here are some practical examples of using the Require Approval job: Note: Ask for approval before deploying to production --- This workflow deploys a web app to preview and then requires approval from a user before deploying to production. !!!IG35!!! --- Note: Control flow of the workflow --- This workflow lets a user decide how the story ends by requiring approval before revealing the conclusion. !!!IG36!!! --- ## Doc Displays a Markdown section in the workflow logs. ### Syntax !!!IG37!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | --------- | ------ | ------------------------------------------------------------------------------------------- | | md | string | Required. The Markdown content to display. You can use `${{ ... }}` workflow interpolation. | ### Examples Here are some practical examples of using the Doc job: Note: Display instructions --- This workflow builds an iOS app and then displays a Markdown section in the workflow logs. !!!IG38!!! --- ## Repack Repackages an app from an existing build. This job repackages the app's metadata and JavaScript bundle without performing a full native rebuild, which is useful for creating a faster build compatible with a specific fingerprint. ### Syntax !!!IG39!!! #### Parameters You can pass the following parameters into the `params` list: | Parameter | Type | Description | | ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | build_id | string | Required. The source build ID of the build to repack. | | profile | string | Optional. The build profile to use. Defaults to the profile of the source build retrieved from `build_id`. | | embed_bundle_assets | boolean | Optional. Whether to embed the bundle assets in the repacked build. By default, this is automatically determined based on the source build. | ### Examples Here are some practical examples of using the Fingerprint with Repack jobs: Note: Continuous Deployment with Fingerprint and Repack --- This workflow first generates a fingerprint and then builds or repacks the app depending on whether a compatible build for that fingerprint already exists. Finally, it runs Maestro tests. !!!IG40!!! --- ## EAS 工作流程的语法 EAS Workflows 配置文件语法的参考指南。 工作流是由一个或多个作业组成的可配置自动化过程。你必须创建一个 YAML 文件来定义你的工作流配置。 ¥A workflow is a configurable automated process made up of one or more jobs. You must create a YAML file to define your workflow configuration. 要开始使用工作流,请参阅 [开始使用 EAS 工作流](/eas/workflows/get-started) 或参阅 [示例](/eas/workflows/examples) 了解完整的工作流配置。 ¥To get started with workflows, see [Get Started with EAS Workflows](/eas/workflows/get-started) or see [Examples](/eas/workflows/examples) for complete workflow configurations. ## 工作流文件 ¥Workflow files 工作流文件使用 YAML 语法,必须具有 `.yml` 或 `.yaml` 文件扩展名。如果你是 YAML 新手并想了解更多信息,请参阅 [在 Y 分钟内学习 YAML](https://learnxinyminutes.com/docs/yaml/)。 ¥Workflow files use YAML syntax and must have either a `.yml` or `.yaml` file extension. If you're new to YAML and want to learn more, see [Learn YAML in Y minutes](https://learnxinyminutes.com/docs/yaml/). 工作流文件位于项目中的 .eas/workflows 目录中。.eas 目录应与你的 [**eas.json**](/build/eas-json/) 文件处于同一级别。 ¥Workflow files are located in the **.eas/workflows** directory in your project. The **.eas** directory should be at the same level as your [**eas.json**](/build/eas-json/) file. 例如: ¥For example: ## 配置参考 ¥Configuration reference 以下是工作流配置文件语法的参考。 ¥Below is a reference for the syntax of the workflow configuration file. ## `name` 工作流的人性化名称。此 SDK 显示在工作流列表页面的 EAS 仪表板上,是工作流详情页面的标题。 ¥The human-friendly name of the workflow. This is displayed on the EAS dashboard on the workflows list page and is the title of the workflow's detail page. ```yaml # @info # name: My workflow # @end # ``` ## `on` `on` 键定义哪些 GitHub 事件触发工作流。任何工作流程都可以使用 `eas workflow:run` 命令触发,无论 `on` 密钥如何。 ¥The `on` key defines which GitHub events trigger the workflow. Any workflow can be triggered with the `eas workflow:run` command, regardless of the `on` key. ```yaml on: # Trigger on pushes to main branch push: branches: - main # And on pull requests starting with 'version-' pull_request: branches: - version-* ``` ### `on.push` 当拉取请求带有匹配标签时运行你的工作流程。 ¥Runs your workflow when you push a commit to matching branches and/or tags. 使用 `branches` 列表,你只能在推送到那些指定的分支时触发工作流。例如,如果你使用 `branches: ['main']`,则只有推送到 `main` 分支才会触发工作流程。支持 glob。使用 `!` 前缀,你可以指定要忽略的分支(即使没有该分支,你仍然需要提供至少一个分支模式)。 ¥With the `branches` list, you can trigger the workflow only when those specified branches are pushed to. For example, if you use `branches: ['main']`, only pushes to the `main` branch will trigger the workflow. Supports globs. By using the `!` prefix you can specify branches to ignore (you still need to provide at least one branch pattern without it). 使用 `tags` 列表,你只能在推送指定的标签时触发工作流程。例如,如果你使用 `tags: ['v1']`,则只有推送的 `v1` 标签才会触发工作流程。支持 glob。使用 `!` 前缀,你可以指定要忽略的标签(即使没有该标签,你仍然需要提供至少一个标签模式)。 ¥With the `tags` list, you can trigger the workflow only when those specified tags are pushed. For example, if you use `tags: ['v1']`, only the `v1` tag being pushed will trigger the workflow. Supports globs. By using the `!` prefix you can specify tags to ignore (you still need to provide at least one tag pattern without it). 使用 `paths` 列表,你仅可在对与指定路径匹配的文件进行更改时触发工作流。例如,如果你使用 `paths: ['apps/mobile/**']`,则只有更改 `apps/mobile` 目录中的文件才会触发工作流。支持 glob。默认情况下,更改任何路径都会触发此工作流。 ¥With the `paths` list, you can trigger the workflow only when changes are made to files matching the specified paths. For example, if you use `paths: ['apps/mobile/**']`, only changes to files in the `apps/mobile` directory will trigger the workflow. Supports globs. By default, changes to any path will trigger the workflow. 当未提供 `branches` 和 `tags` 时,`branches` 默认为 `['*']`,`tags` 默认为 `[]`,这意味着工作流将在推送事件到所有分支时触发,而不会在标签推送时触发。如果仅提供两个列表中的一个,则另一个默认为 `[]`。 ¥When neither `branches` nor `tags` are provided, `branches` defaults to `['*']` and `tags` defaults to `[]`, which means the workflow will trigger on push events to all branches and will not trigger on tag pushes. If only one of the two lists is provided the other defaults to `[]`. ```yaml on: # @info # push: # @end # branches: - main - feature/** - !feature/test-** # other branch names and globs tags: - v1 - v2* - !v2-preview** # other tag names and globs paths: - apps/mobile/** - packages/shared/** - !**/*.md # ignore markdown files ``` ### `on.pull_request` 当你创建或更新针对匹配分支之一的拉取请求时,运行你的工作流。 ¥Runs your workflow when you create or update a pull request that targets one of the matching branches. 使用 `branches` 列表,你只能在指定的分支是拉取请求的目标时触发工作流程。例如,如果你使用 `branches: ['main']`,则只有合并到主分支的拉取请求才会触发工作流程。支持 glob。未提供时默认为 `['*']`,这意味着工作流将在所有分支的拉取请求事件上触发。使用 `!` 前缀,你可以指定要忽略的分支(即使没有该分支,你仍然需要提供至少一个分支模式)。 ¥With the `branches` list, you can trigger the workflow only when those specified branches are the target of the pull request. For example, if you use `branches: ['main']`, only pull requests to merge into the main branch will trigger the workflow. Supports globs. Defaults to `['*']` when not provided, which means the workflow will trigger on pull request events to all branches. By using the `!` prefix you can specify branches to ignore (you still need to provide at least one branch pattern without it). 使用 `types` 列表,你只能在指定的拉取请求事件类型上触发工作流。例如,如果你使用 `types: ['opened']`,则只有 `pull_request.opened` 事件(首次打开拉取请求时发送)才会触发工作流。未提供时默认为 `['opened', 'reopened', 'synchronize']`。支持的事件类型: ¥With the `types` list, you can trigger the workflow only on the specified pull request event types. For example, if you use `types: ['opened']`, only the `pull_request.opened` event (sent when a pull request is first opened) will trigger the workflow. Defaults to `['opened', 'reopened', 'synchronize']` when not provided. Supported event types: * `opened` * `reopened` * `synchronize` * `labeled` 使用 `paths` 列表,你仅可在对与指定路径匹配的文件进行更改时触发工作流。例如,如果你使用 `paths: ['apps/mobile/**']`,则只有更改 `apps/mobile` 目录中的文件才会触发工作流。支持 glob。默认情况下,更改任何路径都会触发此工作流。 ¥With the `paths` list, you can trigger the workflow only when changes are made to files matching the specified paths. For example, if you use `paths: ['apps/mobile/**']`, only changes to files in the `apps/mobile` directory will trigger the workflow. Supports globs. By default, changes to any path will trigger the workflow. ```yaml on: # @info # pull_request: # @end # branches: - main - feature/** - !feature/test-** # other branch names and globs types: - opened # other event types paths: - apps/mobile/** - packages/shared/** - !**/*.md # ignore markdown files ``` ### `on.pull_request_labeled` 当匹配分支上发生拉取请求事件时运行你的工作流程。 ¥Runs your workflow when a pull request is labeled with a matching label. 使用 `labels` 列表,你可以指定哪些标签在分配给你的拉取请求时会触发工作流。例如,如果你使用 `labels: ['Test']`,则只有使用 `Test` 标签标记拉取请求才会触发工作流。未提供时默认为 `[]`,这意味着没有标签会触发工作流。 ¥With the `labels` list, you can specify which labels, when assigned to your pull request, will trigger the workflow. For example, if you use `labels: ['Test']`, only labeling a pull request with the `Test` label will trigger the workflow. Defaults to `[]` when not provided, which means no labels will trigger the workflow. 你还可以直接向 `on.pull_request_labeled` 提供匹配标签列表,以获得更简单的语法。 ¥You can also provide a list of matching labels directly to `on.pull_request_labeled` for simpler syntax. ```yaml on: # @info # pull_request_labeled: # @end # labels: - Test - Preview # other labels ``` 或者: ¥Alternatively: ```yaml on: # @info # pull_request_labeled: # @end # - Test - Preview # other labels ``` ### `on.schedule.cron` 使用 [unix-cron](https://www.ibm.com/docs/en/db2/11.5?topic=task-unix-cron-format) 语法按计划运行你的工作流。你可以使用 [crontab 专家](https://crontab.guru/) 及其 [examples](https://crontab.guru/examples.html) 生成 cron 字符串。 ¥Runs your workflow on a schedule using [unix-cron](https://www.ibm.com/docs/en/db2/11.5?topic=task-unix-cron-format) syntax. You can use [crontab guru](https://crontab.guru/) and their [examples](https://crontab.guru/examples.html) to generate cron strings. * 计划的工作流仅在代码库的默认分支上运行。在许多情况下,这意味着 `main` 分支上工作流文件中的 cron 将被调度,而功能分支中工作流文件中的 cron 将不会被调度。 ¥Scheduled workflows will only run on the default branch of the repository. In many cases, this means crons inside workflow files on the `main` branch will be scheduled, while crons inside workflow files in feature branches will not be scheduled. * 在高负载期间,计划的工作流可能会延迟。高加载时间包括每小时的开始时间。在极少数情况下,作业可能会被跳过或多次运行。确保你的工作流程是幂等的,并且没有有害的副作用。 ¥Scheduled workflows may be delayed during periods of high load. High load times include the start of every hour. In rare circumstances, jobs may be skipped or run multiple times. Make sure that your workflows are idempotent and do not have harmful side effects. * 一个工作流可以有多个 `cron` 计划。 ¥A workflow can have multiple `cron` schedules. * 计划的工作流在 GMT 时区运行。 ¥Scheduled workflows run in the GMT time zone. ```yaml on: schedule: - cron: '0 0 * * *' # Runs at midnight GMT every day ``` ## `jobs` 工作流程运行由一个或多个作业组成。 ¥A workflow run is made up of one or more jobs. ```yaml jobs: # @info # job_1: # ... job_2: # ... # @end # ``` ### `jobs.` 每个作业都必须有一个 ID。ID 在工作流中应该是唯一的,并且可以包含字母数字字符和下划线。例如,以下 YAML 中的 `my_job`: ¥Each job must have an ID. The ID should be unique within the workflow and can contain alphanumeric characters and underscores. For example, `my_job` in the following YAML: ```yaml jobs: # @info # my_job: # @end # # ... ``` ### `jobs..name` 工作流详细信息页面上显示的作业的人性化名称。 ¥The human-friendly name of the job displayed on the workflow's detail page. ```yaml jobs: my_job: # @info # name: Build app # @end # ``` ### `jobs..environment` 为作业设置 [EAS 环境变量](/eas/environment-variables/) 环境。有三个可能的值: ¥Sets the [EAS environment variable](/eas/environment-variables/) environment for the job. There are three possible values: * `production`(默认) ¥`production` (default) * `preview` * `development` `environment` 密钥适用于所有作业。 ¥The `environment` key is available on all jobs. ```yaml jobs: my_job: # @info # environment: production | preview | development # @end # ``` ### `jobs..env` 为作业设置环境变量。该属性适用于所有运行虚拟机的作业(除预打包的 `require-approval`、`doc`、`get-build` 和 `slack` 作业外的所有作业)。 ¥Sets environment variables for the job. The property is available on all jobs running a VM (all jobs except for the pre-packaged `require-approval`, `doc`, `get-build` and `slack` jobs). ```yaml jobs: my_job: # @info # env: APP_VARIANT: staging RETRY_COUNT: 3 PREV_JOB_OUTPUT: ${{ needs.previous_job.outputs.some_output }} # @end # ``` ### `jobs..defaults.run.working_directory` 为作业中的所有步骤设置运行命令的目录。 ¥Sets the directory to run commands in for all steps in the job. ```yaml jobs: my_job: # @info # defaults: run: working_directory: ./my-app # @end # steps: - name: My first step run: pwd # prints: /home/expo/workingdir/build/my-app ``` ## `defaults` 用作工作流配置中定义的所有作业的默认值的参数。 ¥Parameters to use as defaults for all jobs defined in the workflow configuration. ### `defaults.run.working_directory` 运行脚本的默认工作目录。相对路径(如 "./assets" 或 "assets")从应用的基本目录中解析。 ¥Default working directory to run the scripts in. Relative paths like "./assets" or "assets" are resolved from the app's base directory. ### `defaults.tools` 应用于此工作流配置中定义的作业的特定版本的工具。请遵循每个工具的文档以了解可用值。 ¥Specific versions of tools that should be used for jobs defined in this workflow configuration. Follow each tool's documentation for available values. | 工具 | 描述 | | ----------- | ------------------------------------------------------------------------------------ | | `node` | 通过 `nvm` 安装的 Node.js 版本。 | | `yarn` | 通过 `npm -g` 安装的 Yarn 版本。 | | `corepack` | 如果设置为 `true`,[corepack](https://nodejs.cn/api/corepack.html) 将在构建过程开始时启用。默认为 false。 | | `pnpm` | 通过 `npm -g` 安装的 pnpm 版本。 | | `bun` | 通过将 `bun-v$VERSION` 传递给 Bun 安装脚本安装的 Bun 版本。 | | `ndk` | 通过 `sdkmanager` 安装的 Android NDK 版本。 | | `bundler` | 将传递给 `gem install -v` 的 Bundler 版本。 | | `fastlane` | 将传递给 `gem install -v` 的 fastlane 版本。 | | `cocoapods` | 将传递给 `gem install -v` 的 CocoaPods 版本。 | 使用 `defaults.tools` 的工作流程示例: ¥Example of workflow using `defaults.tools`: ```yaml .eas/workflows/publish-update.yml name: Set up custom versions defaults: tools: node: latest yarn: '2' corepack: true pnpm: '8' bun: '1.0.0' fastlane: 2.224.0 cocoapods: 1.12.0 on: push: branches: ['*'] jobs: setup: steps: - name: Check Node version run: node --version # should print a concrete version, like 23.9.0 - name: Check Yarn version run: yarn --version # should print a concrete version, like 2.4.3 ``` ## `concurrency` 并发控制配置。目前仅允许为同一分支的工作流设置 `cancel_in_progress`。 ¥Configuration for concurrency control. Currently only allows setting `cancel_in_progress` for same-branch workflows. ```yaml # @info # concurrency: cancel_in_progress: true group: ${{ workflow.filename }}-${{ github.ref }} # @end # ``` | 属性 | 类型 | 描述 | | -------------------- | --------- | ----------------------------------------------- | | `cancel_in_progress` | `boolean` | 如果设置为 true,从 GitHub 启动的新工作流程运行将取消同一分支当前正在进行的运行。 | | `group` | `string` | 我们目前不支持自定义并发组。设置此占位符值,以便在我们支持自定义组时,你的工作流程仍然兼容。 | ## 控制流 ¥Control flow 你可以使用 `needs` 和 `after` 关键字控制作业的运行时间。此外,你可以使用 `if` 关键字来控制是否应根据条件运行作业。 ¥You can control when a job runs with the `needs` and `after` keywords. In addition, you can use the `if` keyword to control whether a job should run based on a condition. ### `jobs..needs` 作业 ID 列表,这些作业必须先成功完成,然后此作业才会运行。 ¥A list of job IDs whose jobs must complete successfully before this job will run. ```yaml jobs: test: steps: - uses: eas/checkout - uses: eas/use_npm_token - uses: eas/install_node_modules - name: tsc run: yarn tsc build: # @info # needs: [test] # This job will only run if the 'test' job succeeds # @end # type: build params: platform: ios ``` ### `jobs..after` 在运行此作业之前必须完成(成功或失败)的作业 ID 列表。 ¥A list of job IDs that must complete (successfully or not) before this job will run. ```yaml jobs: build: type: build params: platform: ios notify: # @info # after: [build] # This job will run after build completes (whether build succeeds or fails) # @end # ``` ### `jobs..if` `if` 条件确定是否应运行作业。当满足 `if` 条件时,作业将运行。当不满足条件时,将跳过作业。跳过的作业不会成功完成,并且任何在其 `needs` 列表中包含此作业的下游作业都不会运行。 ¥The `if` conditional determines if a job should run. When an `if` condition is met, the job will run. When the condition is not met, the job will be skipped. A skipped job won't have completed successfully and any downstream jobs will not run that have this job in their `needs` list. ```yaml jobs: my_job: # @info # if: ${{ github.ref_name == 'main' }} # @end # ``` ## 插值 ¥Interpolation 你可以根据工作流运行的上下文自定义工作流的行为,包括要执行的命令、控制流、环境变量、构建配置文件、应用版本等等。 ¥You can customize the behavior of a workflow — commands to execute, control flow, environment variables, build profile, app version, and more — based on the workflow run's context. ### `after` 和 `needs` ¥`after` and `needs` 当你在 `after` 或 `needs` 中指定上游作业时,你可以在下游作业中使用其输出。所有作业都公开 `status` 属性。大多数预打包的作业也会公开一些输出。你可以 [使用 `set-output` 函数在自定义任务中设置输出](#jobsjob_idoutputs)。 ¥When you specify an upstream job in `after` or `needs`, you can use its outputs in a downstream job. All jobs expose a `status` property. Most pre-packaged jobs also expose some outputs. You can [set outputs in custom jobs using the `set-output` function](#jobsjob_idoutputs). ```json { "status": "success" | "failure" | "skipped", "outputs": {} } ``` 用法示例: ¥Example usage: ```yaml jobs: setup: outputs: date: ${{ steps.current_date.outputs.date }} steps: - id: current_date run: | DATE=$(date +"%Y.%-m.%-d") set-output date "$DATE" build_ios: needs: [setup] type: build env: # You might use process.env.VERSION_SUFFIX to customize # app version in your dynamic app config. VERSION_SUFFIX: ${{ needs.setup.outputs.date }} params: platform: ios profile: development ``` ### `github` 为了简化从 GitHub Actions 到 EAS Workflows 的迁移,我们公开了一些你可能会觉得有用的上下文的字段。 ¥To ease the migration from GitHub Actions to EAS Workflows we expose some context fields you may find useful. ```json { "event_name": "pull_request" | "push" | "schedule" | "workflow_dispatch", "sha": string, "ref": string, // e.g. refs/heads/main "ref_name": string, // e.g. main "ref_type": "branch" | "tag" | "other" } ``` 如果工作流程运行从 `eas workflow:run` 开始,则其 `event_name` 将为 `workflow_dispatch`,其余所有属性将为空。 ¥If a workflow run is started from `eas workflow:run`, its `event_name` will be `workflow_dispatch` and all the rest of the properties will be empty. 用法示例: ¥Example usage: ```yaml jobs: build_ios: type: build # @info # if: ${{ github.ref_name == 'main' }} # @end # params: platform: ios profile: production ``` ### `success()`, `failure()` 仅当先前的任务均未失败时,`success()` 才返回 `true`。如果先前的任何任务失败,`failure()` 将返回 `true`。 ¥`success()` returns `true` only if no previous job has failed. `failure()` returns `true` if any previous job has failed. ### `fromJSON()`, `toJSON()` `fromJSON()` 是 `JSON.parse()`,`toJSON()` 是 `JSON.stringify()`。你可以使用它们来消费或生成 JSON 输出。 ¥`fromJSON()` is `JSON.parse()`, and `toJSON()` is `JSON.stringify()`. You can use them to consume or produce JSON outputs. 用法示例: ¥Example usage: ```yaml jobs: publish_update: type: update print_debug_info: needs: [publish_update] steps: - run: | echo "First update group: ${{ needs.publish_update.outputs.first_update_group_id }}" echo "Second update group: ${{ fromJSON(needs.publish_update.outputs.updates_json || '[]')[1].group }}" ``` ## 预打包作业 ¥Pre-packaged jobs ### `jobs..type` 指定要运行的预打包作业的​​类型。预打包的作业根据工作流详细信息页面上的作业类型生成专门的 UI。 ¥Specifies the type of pre-packaged job to run. Pre-packaged jobs produce specialized UI according to the type of job on the workflow's detail page. ```yaml jobs: my_job: # @info # type: build # @end # ``` 了解以下不同的预打包作业。 ¥Learn about the different pre-packaged jobs below. #### `build` 使用 [EAS 构建](/build/introduction/) 创建项目的 Android 或 iOS 版本。有关详细信息和示例,请参阅 [构建任务文档](/eas/workflows/pre-packaged-jobs#build)。 ¥Creates an Android or iOS build of your project using [EAS Build](/build/introduction/). See [Build job documentation](/eas/workflows/pre-packaged-jobs#build) for detailed information and examples. ```yaml jobs: my_job: # @info # type: build # @end # params: platform: ios | android # required profile: string # optional, default: production ``` 此作业输出以下属性: ¥This job outputs the following properties: ```json { "build_id": string, "app_build_version": string | null, "app_identifier": string | null, "app_version": string | null, "channel": string | null, "distribution": "internal" | "store" | null, "fingerprint_hash": string | null, "git_commit_hash": string | null, "platform": "ios" | "android" | null, "profile": string | null, "runtime_version": string | null, "sdk_version": string | null, "simulator": "true" | "false" | null } ``` #### `deploy` 使用 [EAS 托管](/eas/hosting/introduction/) 部署你的应用。有关详细信息和示例,请参阅 [部署任务文档](/eas/workflows/pre-packaged-jobs#deploy)。 ¥Deploys your application using [EAS Hosting](/eas/hosting/introduction/). See [Deploy job documentation](/eas/workflows/pre-packaged-jobs#deploy) for detailed information and examples. ```yaml jobs: my_job: # @info # type: deploy # @end # params: alias: string # optional prod: boolean # optional ``` #### `fingerprint` 计算项目的指纹。有关详细信息和示例,请参阅 [指纹任务文档](/eas/workflows/pre-packaged-jobs#fingerprint)。 ¥Calculates a fingerprint of your project. See [Fingerprint job documentation](/eas/workflows/pre-packaged-jobs#fingerprint) for detailed information and examples. ```yaml jobs: my_job: # @info # type: fingerprint # @end # ``` 此作业输出以下属性: ¥This job outputs the following properties: ```json { "android_fingerprint_hash": string, "ios_fingerprint_hash": string, } ``` #### `get-build` 从 EAS 中检索与提供的参数匹配的现有构建。有关详细信息和示例,请参阅 [获取构建任务文档](/eas/workflows/pre-packaged-jobs#get-build)。 ¥Retrieves an existing build from EAS that matches the provided parameters. See [Get Build job documentation](/eas/workflows/pre-packaged-jobs#get-build) for detailed information and examples. ```yaml jobs: my_job: # @info # type: get-build # @end # params: platform: ios | android # optional profile: string # optional distribution: store | internal | simulator # optional channel: string # optional app_identifier: string # optional app_build_version: string # optional app_version: string # optional git_commit_hash: string # optional fingerprint_hash: string # optional sdk_version: string # optional runtime_version: string # optional simulator: boolean # optional ``` 此作业输出以下属性: ¥This job outputs the following properties: ```json { "build_id": string, "app_build_version": string | null, "app_identifier": string | null, "app_version": string | null, "channel": string | null, "distribution": "internal" | "store" | null, "fingerprint_hash": string | null, "git_commit_hash": string | null, "platform": "ios" | "android" | null, "profile": string | null, "runtime_version": string | null, "sdk_version": string | null, "simulator": "true" | "false" | null } ``` #### `submit` 使用 [EAS 提交](/submit/introduction/) 向应用商店提交 Android 或 iOS 版本。有关详细信息和示例,请参阅 [提交任务文档](/eas/workflows/pre-packaged-jobs#submit)。 ¥Submits an Android or iOS build to the app store using [EAS Submit](/submit/introduction/). See [Submit job documentation](/eas/workflows/pre-packaged-jobs#submit) for detailed information and examples. ```yaml jobs: my_job: # @info # type: submit # @end # params: build_id: string # required profile: string # optional, default: production groups: string[] # optional ``` 此作业输出以下属性: ¥This job outputs the following properties: ```json { "apple_app_id": string | null, // Apple App ID. https://expo.fyi/asc-app-id "ios_bundle_identifier": string | null, // iOS bundle identifier of the submitted build. https://expo.fyi/bundle-identifier "android_package_id": string | null // Submitted Android package ID. https://expo.fyi/android-package } ``` #### `update` 使用 [EAS 更新](/eas-update/introduction/) 发布更新。有关详细信息和示例,请参阅 [更新任务文档](/eas/workflows/pre-packaged-jobs#update)。 ¥Publishes an update using [EAS Update](/eas-update/introduction/). See [Update job documentation](/eas/workflows/pre-packaged-jobs#update) for detailed information and examples. ```yaml jobs: my_job: # @info # type: update # @end # params: message: string # optional platform: string # optional - android | ios | all, defaults to all branch: string # optional channel: string # optional - cannot be used with branch ``` 此作业输出以下属性: ¥This job outputs the following properties: ```json { "first_update_group_id": string, // ID of the first update group. You can use it to e.g. construct the update URL for a development client deep link. "updates_json": string // Stringified JSON array of update groups. Output of `eas update --json`. } ``` #### `maestro` 运行 [大师](https://maestro.dev/) 并在编译之前删除生成的原生目录。有关详细信息和示例,请参阅 [Maestro 任务文档](/eas/workflows/pre-packaged-jobs#maestro)。 ¥Runs [Maestro](https://maestro.dev/) tests on a build. See [Maestro job documentation](/eas/workflows/pre-packaged-jobs#maestro) for detailed information and examples. > **important** Maestro 测试是实验性的,可能会出现不稳定的情况。 > > ¥Maestro tests are experimental and may experience flakiness. ```yaml jobs: my_job: # @info # type: maestro # @end # environment: production | preview | development # optional, defaults to preview image: string # optional. See https://expo.nodejs.cn/build-reference/infrastructure/ for a list of available images. params: build_id: string # required flow_path: string | string[] # required shards: number # optional, defaults to 1 retries: number # optional, defaults to 1 record_screen: boolean # optional, defaults to false. If true, uploads a screen recording of the tests. include_tags: string | string[] # optional. Tags to include in the tests. Will be passed to Maestro as `--include-tags`. exclude_tags: string | string[] # optional. Tags to exclude from the tests. Will be passed to Maestro as `--exclude-tags`. maestro_version: string # optional. Version of Maestro to use for the tests. If not provided, the latest version will be used. android_system_image_package: string # optional. Android emulator system image package to use. device_identifier: string | { android?: string, ios?: string } # optional. Device identifier to use for the tests. ``` #### `maestro-cloud` 在 [Maestro 云](https://docs.maestro.dev/cloud/run-maestro-tests-in-the-cloud) 中的版本上运行 [大师](https://maestro.dev/) 测试。有关详细信息和示例,请参阅 [Maestro 云任务文档](/eas/workflows/pre-packaged-jobs#maestro-cloud)。 ¥Runs [Maestro](https://maestro.dev/) tests on a build in [Maestro Cloud](https://docs.maestro.dev/cloud/run-maestro-tests-in-the-cloud). See [Maestro Cloud job documentation](/eas/workflows/pre-packaged-jobs#maestro-cloud) for detailed information and examples. > **important** 在 Maestro Cloud 中运行测试需要 Maestro Cloud 账户和云计划订阅。转到 [Maestro 文档](https://docs.maestro.dev/cloud/run-maestro-tests-in-the-cloud) 了解更多信息。 > > ¥Running tests in Maestro Cloud requires a Maestro Cloud account and Cloud Plan subscription. Go to [Maestro docs](https://docs.maestro.dev/cloud/run-maestro-tests-in-the-cloud) to learn more. ```yaml jobs: my_job: # @info # type: maestro-cloud # @end # environment: production | preview | development # optional, defaults to preview image: string # optional. See https://expo.nodejs.cn/build-reference/infrastructure/ for a list of available images. params: build_id: string # required. ID of the build to test. maestro_project_id: string # required. Maestro Cloud project ID. Example: `proj_01jw6hxgmdffrbye9fqn0pyzm0`. flows: string # required. Path to the Maestro flow file or directory containing the flows to run. Corresponds to `--flows` param to `maestro cloud`. maestro_api_key: string # optional. The API key to use for the Maestro project. By default, `MAESTRO_CLOUD_API_KEY` environment variable will be used. Corresponds to `--api-key` param to `maestro cloud`. include_tags: string | string[] # optional. Tags to include in the tests. Will be passed to Maestro as `--include-tags`. exclude_tags: string | string[] # optional. Tags to exclude from the tests. Will be passed to Maestro as `--exclude-tags`. maestro_version: string # optional. Version of Maestro to use for the tests. If not provided, the latest version will be used. android_api_level: string # optional. Android API level to use for the tests. Will be passed to Maestro as `--android-api-level`. maestro_config: string # optional. Path to the Maestro `config.yaml` file to use for the tests. Will be passed to Maestro as `--config`. device_locale: string # optional. Device locale to use for the tests. Will be passed to Maestro as `--device-locale`. Run `maestro cloud --help` for a list of supported values. device_model: string # optional. Model of the device to use for the tests. Will be passed to Maestro as `--device-model`. Run `maestro cloud --help` for a list of supported values. device_os: string # optional. OS of the device to use for the tests. Will be passed to Maestro as `--device-os`. Run `maestro cloud --help` for a list of supported values. name: string # optional. Name for the Maestro Cloud upload. Corresponds to `--name` param to `maestro cloud`. branch: string # optional. Override for the branch the Maestro Cloud upload originated from. By default, if the workflow run has been triggered from GitHub, the branch of the workflow run will be used. Corresponds to `--branch` param to `maestro cloud`. async: boolean # optional. Run the Maestro Cloud tests asynchronously. If true, the status of the job will only denote whether the upload was successful, *not* whether the tests succeeded. Corresponds to `--async` param to `maestro cloud`. ``` #### `slack` 使用 webhook URL 向 Slack 通道发送消息。有关详细信息和示例,请参阅 [Slack 任务文档](/eas/workflows/pre-packaged-jobs#slack)。 ¥Sends a message to a Slack channel using a webhook URL. See [Slack job documentation](/eas/workflows/pre-packaged-jobs#slack) for detailed information and examples. ```yaml jobs: my_job: # @info # type: slack # @end # params: webhook_url: string # required message: string # required if payload is not provided payload: object # required if message is not provided ``` #### `require-approval` 继续工作流程之前需要获得用户的批准。用户可以批准或拒绝,这意味着作业成功或失败。有关详细信息和示例,请参阅 [需要审批任务文档](/eas/workflows/pre-packaged-jobs#require-approval)。 ¥Requires approval from a user before continuing with the workflow. A user can approve or reject which translates to success or failure of the job. See [Require Approval job documentation](/eas/workflows/pre-packaged-jobs#require-approval) for detailed information and examples. ```yaml jobs: confirm: # @info # type: require-approval # @end # ``` #### `doc` 在工作流日志中显示 Markdown 部分。有关详细信息和示例,请参阅 [文档任务文档](/eas/workflows/pre-packaged-jobs#doc)。 ¥Displays a Markdown section in the workflow logs. See [Doc job documentation](/eas/workflows/pre-packaged-jobs#doc) for detailed information and examples. ```yaml jobs: next_steps: # @info # type: doc params: md: string # @end # ``` #### `repack` 从现有版本重新打包应用。此作业会重新打包应用的元数据和 JavaScript 包,而无需执行完整的原生重建,这对于创建与特定指纹兼容的更快构建非常有用。有关详细信息和示例,请参阅 [重新打包任务文档](/eas/workflows/pre-packaged-jobs#repack)。 ¥Repackages an app from an existing build. This job repackages the app's metadata and JavaScript bundle without performing a full native rebuild, which is useful for creating a faster build compatible with a specific fingerprint. See [Repack job documentation](/eas/workflows/pre-packaged-jobs#repack) for detailed information and examples. ```yaml jobs: next_steps: # @info # type: repack params: build_id: string # @end # ``` ## 自定义作业 ¥Custom jobs 在构建上运行 测试。不需要 `type` 字段。 ¥Runs custom code and can use built-in EAS functions. Does not require a `type` field. ```yaml jobs: my_job: steps: # ... ``` ### `jobs..steps` 作业包含名为 `steps` 的一系列任务。步骤可以运行命令。`steps` 只能在自定义作业和 `build` 作业中提供。 ¥A job contains a sequence of tasks called `steps`. Steps can run commands. `steps` may only be provided in custom jobs and `build` jobs. ```yaml jobs: my_job: # @info # steps: - name: My first step run: echo "Hello World" # @end # ``` ### `jobs..outputs` 由作业定义的输出列表。这些输出可供所有依赖于此作业的下游作业访问。要设置输出,请在作业步骤中使用 `set-output` 函数。 ¥A list of outputs defined by the job. These outputs are accessible to all downstream jobs that depend on this job. To set outputs, use the `set-output` function within a job step. 下游作业可以在 [插值上下文](#interpolation) 中使用以下表达式访问这些输出: ¥Downstream jobs can access these outputs using the following expressions within [interpolation contexts](#interpolation): * `needs..outputs.` * `after..outputs.` 其中,`` 指的是上游作业的标识符,`` 指的是你要访问的特定输出变量。 ¥Here, `` refers to the identifier of the upstream job, and `` refers to the specific output variable you want to access. 在下面的示例中,`set-output` 函数在 `job_1` 的 `step_1` 步骤中将名为 `test` 的输出设置为值 `hello world`。稍后在 `job_2` 中,使用 `needs.job_1.outputs.output_1` 在 `step_2` 中访问它。 ¥In the example below, the `set-output` function sets the output named `test` to the value `hello world` in `job_1`'s `step_1` step. Later in `job_2`, it's accessed in `step_2` using `needs.job_1.outputs.output_1`. ```yaml jobs: job_1: # @info # outputs: output_1: ${{ steps.step_1.outputs.test }} # @end # steps: - id: step_1 # @info # run: set-output test "hello world" # @end # job_2: needs: [job_1] steps: # @info # - id: step_2 run: echo ${{ needs.job_1.outputs.output_1 }} # @end # ``` ### `jobs..image` 指定用于该任务的虚拟机映像。查看 [基础设施](/build-reference/infrastructure/) 获取可用图片。 ¥Specifies the VM image to use for the job. See [Infrastructure](/build-reference/infrastructure/) for available images. ```yaml jobs: my_job: # @info # image: auto | string # optional, defaults to 'auto' # @end # ``` ### `jobs..runs_on` 指定将执行作业的工作线程。仅适用于自定义作业。 ¥Specifies the worker that will execute the job. Available only on custom jobs. ```yaml jobs: my_job: # @info # runs_on: linux-medium | linux-large | linux-medium-nested-virtualization | linux-large-nested-virtualization | macos-medium | macos-large # optional, defaults to linux-medium # @end # ``` | Worker | vCPU | 内存(GiB RAM) | SSD (GiB) | 注释 | | ---------------------------------- | ---- | ----------- | --------- | ----------------- | | linux-medium | 4 | 16 | 14 | 默认工作器。 | | linux-large | 8 | 32 | 28 | | | linux-medium-nested-virtualization | 4 | 16 | 14 | 允许运行 Android 模拟器。 | | linux-large-nested-virtualization | 4 | 32 | 28 | 允许运行 Android 模拟器。 | | Worker | 效率核心 | 统一内存 (GiB RAM) | SSD (GiB) | 注释 | | ------------ | ---- | -------------- | --------- | ---------------- | | macos-medium | 5 | 20 | 125 | 运行 iOS 作业,包括模拟器。 | | macos-large | 10 | 40 | 125 | 运行 iOS 作业,包括模拟器。 | > 注意:对于 Android 模拟器作业,你必须使用 `linux-*-nested-virtualization` 工作器。对于 iOS 构建和 iOS 模拟器作业,你必须使用 `macos-*` 工作者。 > > ¥**Note:** For Android Emulator jobs, you must use a `linux-*-nested-virtualization` worker. For iOS builds and iOS Simulator jobs, you must use a `macos-*` worker. ### `jobs..steps..id` `id` 属性用于引用作业中的步骤。用于在下游作业中使用步骤的输出。 ¥The `id` property is used to reference the step in the job. Useful for using the step's output in a downstream job. ```yaml jobs: my_job: outputs: test: ${{ steps.step_1.outputs.test }} # References the output from step_1 steps: # @info # - id: step_1 # @end # run: set-output test "hello world" ``` ### `jobs..steps..name` 步骤的人性化名称,显示在作业的日志中。如果未提供步骤的名称,则使用 `run` 命令作为步骤名称。 ¥The human-friendly name of the step, which is displayed in the job's logs. When a step's name is not provided, the `run` command is used as the step name. ```yaml jobs: my_job: steps: # @info # - name: My first step # @end # run: echo "Hello World" ``` ### `jobs..steps..run` 在步骤中运行的 shell 命令。 ¥The shell command to run in the step. ```yaml jobs: my_job: steps: # @info # - run: echo "Hello World" # @end # ``` ### `jobs..steps..shell` 用于运行命令的 shell。默认为 `bash`。 ¥The shell to use for running the command. Defaults to `bash`. ```yaml jobs: my_job: steps: # @info # - run: echo "Hello World" shell: bash # @end # ``` ### `jobs..steps..working_directory` 运行命令的目录。在步骤级别定义时,如果作业上也定义了 `jobs..defaults.run.working_directory` 设置,它将覆盖该设置。 ¥The directory to run the command in. When defined at the step level, it overrides the `jobs..defaults.run.working_directory` setting on the job if it is also defined. ```yaml jobs: my_job: steps: - uses: eas/checkout - run: pwd # prints: /home/expo/workingdir/build/my-app # @info # working_directory: ./my-app # @end # ``` ### `jobs..steps..uses` EAS 提供了一组内置可重用函数,你可以在工作流步骤中使用它们。`uses` 关键字用于指定要使用的函数。所有内置函数都以 `eas/` 前缀开头。 ¥EAS provides a set of built-in reusable functions that you can use in workflow steps. The `uses` keyword is used to specify the function to use. All built-in functions start with the `eas/` prefix. ```yaml jobs: my_job: steps: # @info # - uses: eas/checkout - uses: eas/install_node_modules - uses: eas/prebuild - name: List files run: ls -la # @end # ``` 以下是你可以在工作流步骤中使用的内置函数列表。 ¥Below is a list of built-in functions you can use in your workflow steps. #### `eas/checkout` 签出你的项目源文件。 ¥Checks out your project source files. ```yaml jobs: my_job: steps: # @info # - uses: eas/checkout # @end # ``` #### `eas/install_node_modules` 使用根据你的项目检测到的包管理器(bun、npm、pnpm 或 Yarn)安装 node_modules。与 monorepos 一起使用。 ¥Installs node_modules using the package manager (bun, npm, pnpm, or Yarn) detected based on your project. Works with monorepos. ```yaml example.yml jobs: my_job: steps: - uses: eas/checkout # @info # - uses: eas/install_node_modules # @end # ``` #### `eas/download_build` 下载指定版本的应用存档。默认情况下,下载的工件可以是 .apk、.aab、.ipa 或 .app 文件,也可以是包含一个或多个这些文件的 .tar.gz 压缩包。如果工件是 .tar.gz 存档,它将被解压并返回第一个与指定扩展名匹配的文件。如果构建没有生成应用存档,则该步骤将失败。 ¥Downloads application archive of a given build. By default, the downloaded artifact can be an **.apk**, **.aab**, **.ipa**, or **.app** file, or a **.tar.gz** archive containing one or more of these files. If the artifact is a **.tar.gz** archive, it will be extracted and the first file matching the specified extensions will be returned. If the build produced no application archive, the step will fail. ```yaml jobs: my_job: steps: - uses: eas/download_build with: build_id: string # Required. ID of the build to download. extensions: [apk, aab, ipa, app] # Optional. List of file extensions to look for. Defaults to ["apk", "aab", "ipa", "app"]. ``` | 属性 | 类型 | 必需的 | 默认 | 描述 | | ------------ | --------- | --- | ------------------------------ | ------------------------- | | `build_id` | string | 是 | – | 要下载的构建版本的 ID。必须是有效的 UUID。 | | `extensions` | string\[] | 否 | `["apk", "aab", "ipa", "app"]` | 在下载的工件或存档中查找的文件扩展名列表。 | **输出:** ¥**Outputs:** * `artifact_path`:匹配应用归档的绝对路径。此输出可用作工作流程中其他步骤的输入。例如,上传或进一步处理工件。 ¥`artifact_path`: The absolute path to the matching application archive. This output can be used as input into other steps in your workflow. For example, to upload or process the artifact further. 用法示例: ¥Example usage: ```yaml jobs: build_ios: type: build params: platform: ios profile: production my_job: needs: [build_ios] steps: - uses: eas/download_build id: download_build with: build_id: ${{ needs.build_ios.outputs.build_id }} - name: Print artifact path run: | echo "Artifact path: ${{ steps.download_build.outputs.artifact_path }}" ``` #### `eas/prebuild` 运行 iOS 作业,包括模拟器。 ¥Runs the `expo prebuild` command using the package manager (bun, npm, pnpm, or Yarn) detected based on your project with the command best suited for your build type and build environment. ```yaml jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules # @info # - uses: eas/prebuild # @end # ``` ```yaml jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules - uses: eas/resolve_apple_team_id_from_credentials id: resolve_apple_team_id_from_credentials # @info # - uses: eas/prebuild with: clean: false apple_team_id: ${{ steps.resolve_apple_team_id_from_credentials.outputs.apple_team_id }} # @end # ``` | 属性 | 类型 | 描述 | | --------------- | --------- | ----------------------------------------------- | | `clean` | `boolean` | 可选属性定义函数在运行命令时是否应使用 `--clean` 标志。默认为 false。 | | `apple_team_id` | `string` | 可选属性定义在进行预构建时应使用的 Apple 团队 ID。应使用凭据为 iOS 构建指定它。 | #### `eas/send_slack_message` 将指定的消息发送到配置的 [Slack Webhook URL](https://api.slack.com/messaging/webhooks),然后将其发布到相关的 Slack 通道中。消息可以指定为纯文本或 [Slack 块套件](https://api.slack.com/block-kit) 消息。 ¥Sends a specified message to a configured [Slack webhook URL](https://api.slack.com/messaging/webhooks), which then posts it in the related Slack channel. The message can be specified as plaintext or as a [Slack Block Kit](https://api.slack.com/block-kit) message. 对于这两种情况,你都可以在消息中引用构建作业属性和 [使用其他步骤输出](#jobsjob_idoutputs) 以进行动态评估。例如,`Build URL: https://expo.dev/builds/${{ needs.build_ios.outputs.build_id }}`、`Build finished with status: ${{ after.build_android.outputs.status }}`。 ¥With both cases, you can reference build job properties and [use other steps outputs](#jobsjob_idoutputs) in the message for dynamic evaluation. For example, `Build URL: https://expo.dev/builds/${{ needs.build_ios.outputs.build_id }}`, `Build finished with status: ${{ after.build_android.outputs.status }}`. 必须指定 `message` 或 `payload`,但不能同时指定两者。 ¥Either `message` or `payload` has to be specified, but not both. ```yaml jobs: my_job: steps: # @info # - uses: eas/send_slack_message # @end # with: message: 'This is a message to plain input URL' slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]' ``` | 属性 | 类型 | 描述 | | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `message` | `string` | 你要发送的消息的文本。例如 `'This is the content of the message'`. 注:需要提供 `message` 或 `payload` 之一,但不能同时提供两者。 | | `payload` | `json` | 要发送的消息内容,使用 [Slack 块套件](https://api.slack.com/block-kit) 布局定义。 注意:需要提供 `message` 或 `payload` 之一,但不能同时提供两者。 | | `slack_hook_url` | `string` | 之前配置的 Slack webhook URL,它将把你的消息发布到指定的通道。你可以提供纯文本 URL(例如 `slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]'`),使用 [EAS 环境变量](/eas/environment-variables/#managing-environment-variables)(例如 `slack_hook_url: ${{ env.ANOTHER_SLACK_HOOK_URL }}`),或者设置 `SLACK_HOOK_URL` 环境变量,该变量将用作默认的 webhook URL(在最后一种情况下,无需提供 `slack_hook_url` 属性)。 | #### `eas/use_npm_token` 配置 Node 包管理器(bun、npm、pnpm 或 Yarn)以用于私有包,发布到 npm 或私有注册表。 ¥Configures Node package managers (bun, npm, pnpm, or Yarn) for use with private packages, published either to npm or a private registry. 在项目的密钥中设置 `NPM_TOKEN`,此函数将通过使用令牌创建 .npmrc 来配置构建环境。 ¥Set `NPM_TOKEN` in your project's secrets, and this function will configure the build environment by creating **.npmrc** with the token. ```yaml example.yml jobs: my_job: name: Install private npm modules steps: - uses: eas/checkout # @info # - uses: eas/use_npm_token # @end # - name: Install dependencies run: npm install # <---- Can now install private packages ``` ## 自动执行 EAS CLI 命令 了解如何使用 EAS 工作流自动执行一系列 EAS CLI 命令。 如果你使用 EAS CLI 构建、提交和更新应用,则可以自动执行以下序列使用 EAS 工作流的命令。EAS 工作流可以构建、提交和更新你的应用,同时还可以运行其他作业,如 Maestro 测试、单元测试、自定义脚本等。 ¥If you're using EAS CLI to build, submit, and update your app, you can automate sequences of commands with EAS Workflows. EAS Workflows can build, submit, and update your app, while also running other jobs like Maestro tests, unit tests, custom scripts, and more. 下面你将了解如何设置项目以使用 EAS 工作流,然后是 EAS CLI 命令的常用示例以及如何使用 EAS 工作流运行它们。 ¥Below you'll find how to set up your project to use EAS Workflows, followed by common examples of EAS CLI commands and how you can run them using EAS Workflows. ## 配置你的项目 ¥Configure your project EAS Workflows 可选地支持链接到你的 EAS 项目的 GitHub 代码库以运行。本指南假设已链接 GitHub 存储库,并展示如何在推送到 GitHub 上的特定分支时触发工作流。你可以使用以下步骤将 GitHub 存储库链接到你的 EAS 项目: ¥EAS Workflows optionally supports a GitHub repository that's linked to your EAS project to run. This guide assumes a GitHub repository is linked, and shows how to trigger workflows when pushing to specific branches on GitHub. You can link a GitHub repo to your EAS project with the following steps: * 导航到项目的 [GitHub 设置](https://expo.dev/accounts/%5Baccount%5D/projects/%5BprojectName%5D/github)。 ¥Navigate to your project's [GitHub settings](https://expo.dev/accounts/%5Baccount%5D/projects/%5BprojectName%5D/github). * 按照 UI 安装 GitHub 应用。 ¥Follow the UI to install the GitHub app. * 选择与 Expo 项目匹配的 GitHub 存储库并连接它。 ¥Select the GitHub repository that matches the Expo project and connect it. ## 创建构建 ¥Creating builds 你可以使用 EAS CLI 和 `eas build` 命令构建项目。要使用 `production` 构建配置文件进行 iOS 构建,你可以运行以下 EAS CLI 命令: ¥You can make a build of your project using EAS CLI with the `eas build` command. To make an iOS build with the `production` build profile, you could run the following EAS CLI command: ```sh $ eas build --platform ios --profile production ``` 要将此命令编写为工作流,请在项目根目录下创建一个名为 .eas/workflows/build-ios-production.yml 的工作流文件。 ¥To write this command as a workflow, create a workflow file named **.eas/workflows/build-ios-production.yml** at the root of your project. 在 build-ios-production.yml 中,你可以使用以下工作流程启动使用 `production` 构建配置文件创建 iOS 构建的作业。 ¥Inside **build-ios-production.yml**, you can use the following workflow to kick off a job that creates an iOS build with the `production` build profile. ```yaml .eas/workflows/build-ios-production.yml name: iOS production build on: push: branches: ['main'] jobs: build_ios: name: Build iOS type: build params: platform: ios profile: production ``` 获得此工作流文件后,你可以通过向 `main` 分支推送提交或运行以下 EAS CLI 命令来启动它: ¥Once you have this workflow file, you can kick it off by pushing a commit to the `main` branch, or by running the following EAS CLI command: ```sh $ eas workflow:run build-ios-production.yml ``` 你可以提供参数来构建 Android 或使用其他构建配置文件。了解有关使用 [构建作业文档](/eas/workflows/syntax/#build) 构建作业参数的更多信息。 ¥You can provide parameters to make Android builds or use other build profiles. Learn more about build job parameters with the [build job documentation](/eas/workflows/syntax/#build). ## 提交构建 ¥Submitting builds 你可以使用 EAS CLI 和 `eas submit` 命令将你的应用提交到应用商店。要提交 iOS 应用,你可以运行以下 EAS CLI 命令: ¥You can submit your app to the app stores using EAS CLI with the `eas submit` command. To submit an iOS app, you could run the following EAS CLI command: ```sh $ eas submit --platform ios ``` 要将此命令编写为工作流,请在项目根目录下创建一个名为 .eas/workflows/submit-ios.yml 的工作流文件。 ¥To write this command as a workflow, create a workflow file named **.eas/workflows/submit-ios.yml** at the root of your project. 在 submit-ios.yml 中,你可以使用以下工作流程启动提交 iOS 应用的作业。 ¥Inside **submit-ios.yml**, you can use the following workflow to kick off a job that submits an iOS app. ```yaml .eas/workflows/submit-ios.yml name: Submit iOS app on: push: branches: ['main'] jobs: submit_ios: name: Submit iOS type: submit params: platform: ios ``` 获得此工作流文件后,你可以通过向 `main` 分支推送提交或运行以下 EAS CLI 命令来启动它: ¥Once you have this workflow file, you can kick it off by pushing a commit to the `main` branch, or by running the following EAS CLI command: ```sh $ eas workflow:run submit-ios.yml ``` 你可以提供参数来提交其他平台或使用其他提交配置文件。了解有关使用 [提交作业文档](/eas/workflows/syntax/#submit) 提交作业参数的更多信息。 ¥You can provide parameters to submit other platforms or use other submit profiles. Learn more about submit job parameters with the [submit job documentation](/eas/workflows/syntax/#submit). ## 发布更新 ¥Publishing updates 你可以使用 EAS CLI 和 `eas update` 命令更新你的应用。要更新应用,你可以运行以下 EAS CLI 命令: ¥You can update your app using EAS CLI with the `eas update` command. To update your app, you could run the following EAS CLI command: ```sh $ eas update --auto ``` 要将此命令编写为工作流,请在项目根目录下创建一个名为 .eas/workflows/publish-update.yml 的工作流文件。 ¥To write this command as a workflow, create a workflow file named **.eas/workflows/publish-update.yml** at the root of your project. 在 publish-update.yml 中,你可以使用以下工作流程启动发送无线更新的作业。 ¥Inside **publish-update.yml**, you can use the following workflow to kick off a job that sends and over-the-air update. ```yaml .eas/workflows/publish-update.yml name: Publish update on: push: branches: ['*'] jobs: update: name: Update type: update params: branch: ${{ github.ref_name || 'test'}} ``` 获得此工作流文件后,你可以通过向任何分支推送提交或运行以下 EAS CLI 命令来启动它: ¥Once you have this workflow file, you can kick it off by pushing a commit to any branch, or by running the following EAS CLI command: ```sh $ eas workflow:run publish-update.yml ``` 你可以提供参数来更新特定分支或渠道,并配置更新的消息。了解有关使用 [更新作业文档](/eas/workflows/syntax/#update) 更新作业参数的更多信息。 ¥You can provide parameters to update specific branches or channels, and configure the update's message. Learn more about update job parameters with the [update job documentation](/eas/workflows/syntax/#update). ## 下一步 ¥Next step 工作流是自动化开发和发布流程的强大方法。通过工作流示例指南了解如何创建开发版本、发布预览更新和创建生产版本: ¥Workflows are a powerful way to automate your development and release processes. Learn how to create development builds, publish preview updates, and create production builds with the workflows examples guide: ## EAS 工作流限制 了解 EAS 工作流的当前局限性。 EAS Workflows 旨在帮助你自动化开发和发布流程。但是,请注意我们计划解决的某些限制,因为它们可能会影响你的工作流自动化策略。 ¥EAS Workflows is designed to help you automate your development and release processes. However, it is good to be aware of certain limitations that we plan to address since they could affect your workflow automation strategy. ## Maestro 测试处于实验阶段 ¥Maestro tests are experimental Maestro 测试目前处于实验阶段。虽然你可以在工作流程中使用它们,但测试结果可能会出现一些不稳定的情况。我们正在积极努力提高 Maestro 测试集成的稳定性和可靠性。 ¥Maestro tests are currently in an experimental phase. While you can use them in your workflows, you may experience some flakiness in test results. We're actively working to improve the stability and reliability of Maestro test integration. ## 无法通过编程方式访问作业状态 ¥No programmatic job status access 目前,无法以编程方式获取作业状态以用于其他 CI 工作流。此功能已列入我们的路线图,并将在未来的更新中实现。 ¥Currently, it's not possible to programmatically get the status of jobs for use in other CI workflows. This feature is on our roadmap and will be implemented in future updates. ## 无法共享工作流配置 ¥No shared workflow configurations 目前,无法定义共享工作流配置。每个工作流都需要独立定义,这可能会导致一些配置重复。 ¥At this time, it's not possible to define shared workflow configurations. Each workflow needs to be defined independently, which may lead to some configuration duplication. ## 不支持矩阵 ¥No matrix support EAS 工作流目前不支持矩阵构建。这意味着你无法使用不同的配置并行运行同一工作流程的多个变体。 ¥Matrix builds are not currently supported in EAS Workflows. This means you cannot run multiple variations of the same workflow in parallel with different configurations. ## 收到有关更改的通知 ¥Get notified about changes 要在这些项目取得进展时收到通知,你可以订阅 [expo.dev/eas](https://expo.dev/eas) 的 EAS 新闻通讯。 ¥To be notified as progress is made on these items, you can subscribe to the EAS newsletter on [expo.dev/eas](https://expo.dev/eas). # 示例 ## EAS Workflows 示例 用于开发、审查和发布应用的常用 React Native CI/CD 工作流。 以下工作流程是如何使用 EAS 工作流自动化开发、审核和发布流程的示例。它们可以帮助你和你的团队进行开发、互相审查彼此的 PR,并持续向用户发布更改。 ¥The following workflows are examples of how you can use EAS Workflows to automate your development, review, and release processes. They can help you and your team develop, review each other's PRs, and release changes to your users continuously. ### 示例 ¥Examples ## 使用 EAS Workflows 创建开发版本 了解如何使用 EAS Workflows 创建开发版本。 [开发构建](/develop/development-builds/introduction/) 是你项目的专门版本,其中包括 Expo 的开发者工具。这些类型的构建包括项目内的所有原生依赖,​​使你可以在模拟器、模拟器或物理设备上运行类似于生产的项目构建。此工作流程允许你为每个平台以及物理设备、Android 模拟器和 iOS 模拟器创建开发版本,你的团队可以使用 `eas build:dev` 访问这些版本。 ¥[Development builds](/develop/development-builds/introduction/) are specialized builds of your project that include Expo's developer tools. These types of builds include all native dependencies inside your project, enabling you to run a production-like build of your project on a simulator, emulator, or a physical device. This workflow allows you to create development builds for each platform and for both physical devices, Android emulators, and iOS simulators, which your team can access with `eas build:dev`. ## 开始使用 ¥Get started 首先,你需要配置你的项目和设备以构建和运行开发版本。通过以下指南了解如何为开发版本设置环境: ¥To get started, you'll need to configure your project and devices to build and run development builds. Learn how to set up your environment for development builds with the following guides: 配置项目和设备后,将以下构建配置文件添加到 eas.json 文件中。 ¥After you've configured your project and devices, add the following build profiles to your **eas.json** file. ```json eas.json { "build": { "development": { "developmentClient": true, "distribution": "internal" }, "development-simulator": { "developmentClient": true, "distribution": "internal", "ios": { "simulator": true } } } } ``` 以下工作流程为每个平台以及物理设备、Android 模拟器和 iOS 模拟器创建一个构建。它们都将并行运行。 ¥The following workflow creates a build for each platform and for both physical devices, Android emulators, and iOS simulators. They all will run in parallel. ```yaml .eas/workflows/create-development-builds.yml name: Create development builds jobs: android_development_build: name: Build Android type: build params: platform: android profile: development ios_device_development_build: name: Build iOS device type: build params: platform: ios profile: development ios_simulator_development_build: name: Build iOS simulator type: build params: platform: ios profile: development-simulator ``` 使用以下命令运行上述工作流程: ¥Run the above workflow with: ```sh $ eas workflow:run .eas/workflows/create-development-builds.yml ``` ## 使用 EAS 工作流发布预览更新 了解如何使用 EAS 工作流发布预览更新。 对项目进行更改后,你可以通过发布 [预览更新](/review/share-previews-with-your-team/) 与你的团队分享更改预览。当你想与团队一起审查更改,而无需拉取最新更改并在本地运行它们时,这非常有用。 ¥Once you've made changes to your project, you can share a preview of your changes with your team by publishing a [preview update](/review/share-previews-with-your-team/). This is useful when you want to review changes with your team without pulling the latest changes and running them locally. 你可以在开发构建界面中以及通过 EAS 仪表板上的可扫描二维码访问预览更新。在每次提交时发布预览时,你的团队可以查看更改,而无需提取最新更改并在本地运行它们。 ¥You can access preview updates in the development build UI and through scannable QR codes on the EAS dashboard. When publishing a preview on every commit, your team can review changes without pulling the latest changes and running them locally. ## 开始使用 ¥Get started 你的项目需要设置 [EAS 更新](/eas-update/introduction/) 才能发布预览更新。你可以使用以下方式设置你的项目: ¥Your project needs to have [EAS Update](/eas-update/introduction/) setup to publish preview updates. You can set up your project with: ```sh $ eas update:configure ``` 配置项目后,为每个平台创建新的 [开发构建](/develop/development-builds/create-a-build/)。 ¥After you've configured your project, create new [development builds](/develop/development-builds/create-a-build/) for each platform. 以下工作流程会为每个分支上的每次提交发布预览更新。 ¥The following workflow publishes a preview update for every commit on every branch. ```yaml .eas/workflows/publish-preview-update.yml name: Publish preview update on: push: branches: ['*'] jobs: publish_preview_update: name: Publish preview update type: update params: branch: ${{ github.ref_name || 'test' }} ``` ## 使用 EAS Workflows 部署到生产环境 了解如何使用 EAS Workflows 部署到生产环境。 当你准备好向用户提供更改时,你可以构建并提交到应用商店,也可以发送无线更新。以下工作流程检测你是否需要新构建,如果需要,它会将它们发送到应用商店。如果不需要新版本,它将发送无线更新。 ¥When you're ready to deliver changes to your users, you can build and submit to the app stores or you can send an over-the-air update. The following workflow detects if you need new builds, and if so, it sends them to the app stores. If new builds are not required, it will send an over-the-air update. ## 开始使用 ¥Get started 要设置 EAS Build,请遵循以下指南: ¥To set up EAS Build, follow this guide: 要设置 EAS Submit,请遵循 Google Play Store 和 Apple App Store 提交指南: ¥To set up EAS Submit, follow the Google Play Store and Apple App Store submissions guides: 最后,你需要设置 EAS 更新,你可以使用以下方法执行此操作: ¥And finally, you'll need to set up EAS Update, which you can do with: ```sh $ eas update:configure ``` 以下工作流程在每次推送到 `main` 分支时运行并执行以下操作: ¥The following workflow runs on each push to the `main` branch and performs the following: * 使用 [Expo 指纹](/versions/latest/sdk/fingerprint/) 对项目的原生特性进行哈希处理。 ¥Takes a hash of the native characteristics of the project using [Expo Fingerprint](/versions/latest/sdk/fingerprint/). * 检查指纹是否已存在构建。 ¥Checks if a build already exists for the fingerprint. * 如果构建不存在,它将构建项目并将其提交到应用商店。 ¥If a build does not exist, it will build the project and submit it to the app stores. * 如果构建存在,它将发送无线更新。 ¥If a build exists, it will send an over-the-air update. ```yaml .eas/workflows/deploy-to-production.yml name: Deploy to production on: push: branches: ['main'] jobs: fingerprint: name: Fingerprint type: fingerprint get_android_build: name: Check for existing android build needs: [fingerprint] type: get-build params: fingerprint_hash: ${{ needs.fingerprint.outputs.android_fingerprint_hash }} profile: production get_ios_build: name: Check for existing ios build needs: [fingerprint] type: get-build params: fingerprint_hash: ${{ needs.fingerprint.outputs.ios_fingerprint_hash }} profile: production build_android: name: Build Android needs: [get_android_build] if: ${{ !needs.get_android_build.outputs.build_id }} type: build params: platform: android profile: production build_ios: name: Build iOS needs: [get_ios_build] if: ${{ !needs.get_ios_build.outputs.build_id }} type: build params: platform: ios profile: production submit_android_build: name: Submit Android Build needs: [build_android] type: submit params: build_id: ${{ needs.build_android.outputs.build_id }} submit_ios_build: name: Submit iOS Build needs: [build_ios] type: submit params: build_id: ${{ needs.build_ios.outputs.build_id }} publish_android_update: name: Publish Android update needs: [get_android_build] if: ${{ needs.get_android_build.outputs.build_id }} type: update params: branch: production platform: android publish_ios_update: name: Publish iOS update needs: [get_ios_build] if: ${{ needs.get_ios_build.outputs.build_id }} type: update params: branch: production platform: ios ``` ## 在 EAS 工作流和 Maestro 上运行端到端测试 了解如何使用 Maestro 在 EAS 工作流上设置和运行端到端测试。 在本指南中,你将学习如何使用 [大师](https://maestro.dev/) 在 EAS 工作流上运行端到端 (E2E) 测试。此示例演示了如何使用 [默认 Expo 模板](/more/create-expo/#--template) 配置端到端测试工作流。对于你自己的应用,你需要调整流程以匹配应用的 UI。 ¥In this guide, you'll learn how to run end-to-end (E2E) tests on EAS Workflows using [Maestro](https://maestro.dev/). The example demonstrates how to configure your E2E tests workflow using the [default Expo template](/more/create-expo/#--template). For your own app, you'll need to adjust the flows to match your app's UI. Step 1: ## Set up your project If you haven't already, create a new project and sync it with EAS. Follow the [Get started with EAS Workflows guide](/eas/workflows/get-started/#set-up-your-project) to create a new project and sync it with EAS. Then, [configure your project](/eas/workflows/get-started/#configure-your-project) and link your GitHub repository. Step 2: ## Add example Maestro test cases This is what the UI of the app created from the default Expo template looks like:
Let's create two simple Maestro flows for the example app. Start by creating a directory called **.maestro** in the root of your project directory. This directory will contain the flows you'll configure and should be at the same level as **eas.json**. Inside, create a new file called **home.yml**. This flow will launch the app and assert that the text "Welcome!" is visible on the home screen. ```yaml .maestro/home.yml appId: dev.expo.eastestsexample # This is an example app id. Replace it with your app id. --- - launchApp - assertVisible: 'Welcome!' ``` Next, create a new flow called **expand_test.yml**. This flow will open the "Explore" screen in the example app, click on the "File-based routing" collapsible, and assert that the text "This app has two screens." is visible on the screen. ```yaml .maestro/expand_test.yml appId: dev.expo.eastestsexample # This is an example app id. Replace it with your app id. --- - launchApp - tapOn: 'Explore.*' - tapOn: '.*File-based routing' - assertVisible: 'This app has two screens.*' ``` Step 3: ## Run Maestro tests locally (optional) To run Maestro tests locally, install the Maestro CLI by following the instructions in [Installing Maestro](https://docs.maestro.dev/getting-started/installing-maestro). [Install your app on a local Android Emulator or iOS Simulator](/more/expo-cli/#compiling). Open a terminal, navigate to the Maestro directory, and run the following commands to start the tests with the Maestro CLI: ```sh $ maestro test .maestro/expand_test.yml $ maestro test .maestro/home.yml ``` The video below shows a successful run of the **.maestro/expand_test.yml** flow: Step 4: ## Build profile for E2E tests E2E tests require a built app file: **.apk** for Android or **.app** for iOS — that EAS can install and test on an emulator/simulator. In your **eas.json** file, create a build profile for E2E tests. If the file doesn't exist, run `eas build:configure` to generate it. ```json eas.json { "build": { "e2e-test": { "withoutCredentials": true, "ios": { "simulator": true }, "android": { "buildType": "apk" } } } } ``` The above build profile creates an **.apk** for Android and an **.app** for iOS. The workflow uses this profile to build the app on EAS servers. Step 5: ## Create an E2E test workflow At the root of your project, create an **.eas/workflows** directory. Then, add a YAML file for your E2E test workflow, such as **.eas/workflows/e2e-test-android.yml**. ```yaml .eas/workflows/e2e-test-android.yml name: e2e-test-android on: pull_request: branches: ['*'] # Run the E2E test workflow on every pull request. jobs: build_android_for_e2e: type: build params: platform: android profile: e2e-test # your eas build profile for E2E test maestro_test: needs: [build_android_for_e2e] type: maestro params: build_id: ${{ needs.build_android_for_e2e.outputs.build_id }} flow_path: ['.maestro/home.yml'] ``` This workflow builds an **.apk** for Android using the `e2e-test` build profile from the previous step. Then it runs the **.maestro/home.yml** flow on the built APK. Here's an example of the same test workflow for iOS: ```yaml .eas/workflows/e2e-test.yml name: e2e-test-ios on: pull_request: branches: ['*'] jobs: build_ios_for_e2e: type: build params: platform: ios profile: e2e-test # your eas build profile for E2E test maestro_test: needs: [build_ios_for_e2e] type: maestro params: build_id: ${{ needs.build_ios_for_e2e.outputs.build_id }} flow_path: ['.maestro/home.yml'] ``` Learn more about [Syntax for EAS Workflows](/eas/workflows/syntax/). Step 6: ## Run the E2E test workflow You can run the E2E test workflow in two ways: 1. **Manually using the EAS CLI** ```sh $ npx eas-cli@latest workflow:run .eas/workflows/e2e-test-android.yml ``` 2. **Automatically when you open a pull request** The workflow uses a `pull_request` trigger to run automatically when someone opens a pull request to your repository. Learn more about [EAS Workflow triggers](/eas/workflows/syntax/#on). After the workflow starts, you can track its progress and view the results in the EAS dashboard. Here's a screenshot of a completed workflow run: ## 更多 ¥More # EAS 构建 ## EAS 构建 EAS Build 是一项托管服务,用于为你的 Expo 和 React Native 项目构建应用二进制文件。 EAS Build 是一项托管服务,用于为你的 Expo 和 React Native 项目构建应用二进制文件。 ¥**EAS Build** is a hosted service for building app binaries for your Expo and React Native projects. 它通过提供开箱即用的适用于 Expo 和 React Native 项目的默认值,并为你处理应用签名凭据(如果你愿意),使构建用于分发的应用变得简单且易于自动化。它还使与 [内部分配](/build/internal-distribution/) 的团队共享构建比以往任何时候都更加容易(使用临时和/或企业 "universal" 配置),与 EAS Submit 深度集成以进行应用商店提交,并对 [`expo-updates`](/build/updates/) 库提供一流的支持。 ¥It makes building your apps for distribution simple and easy to automate by providing defaults that work well for Expo and React Native projects out of the box, and by handling your app signing credentials for you (if you wish). It also makes sharing builds with your team easier than ever with [internal distribution](/build/internal-distribution/) (using ad hoc and/or enterprise "universal" provisioning), deeply integrates with EAS Submit for app store submissions, and has first-class support for the [`expo-updates`](/build/updates/) library. 无论你是否使用 Expo 和 React Native,它都适用于任何原生项目。这是从 `npx create-expo-app` 或 `npx @react-native-community/cli@latest init` 到达应用商店的最快方式。 ¥It's designed to work for any native project, whether or not you use Expo and React Native. It's the fastest way to get from `npx create-expo-app` or `npx @react-native-community/cli@latest init` to app stores. ### 开始使用 ¥Get started ## 创建你的第一个版本 了解如何使用 EAS Build 为你的应用创建构建。 EAS Build 允许你为 Google Play Store 或 Apple App Store 构建可随时提交的应用二进制文件。在本指南中,让我们学习如何做到这一点。 ¥EAS Build allows you to build a ready-to-submit binary of your app for the Google Play Store or Apple App Store. In this guide, let's learn how to do that. 或者,如果你希望将应用直接安装到 Android 设备/模拟器或将其安装在 iOS 模拟器中,我们将向你提供解释如何执行此操作的资源。 ¥Alternatively, if you prefer to install the app directly to your Android device/emulator or install it in the iOS Simulator, we will point you toward resources that explain how to do that. 对于小型应用,针对 Android 和 iOS 平台的构建会在几分钟内触发。如果你在此过程中遇到任何问题,可以通过 Discord and Forums 联系。 ¥For a small app, builds for Android and iOS platforms trigger within a few minutes. If you encounter any issues along the way, you can reach out on Discord and Forums. ## 先决条件 ¥Prerequisites EAS Build 是一项快速发展的服务。在你开始为你的项目创建构建之前,我们建议你查阅 [limitations](/build-reference/limitations) 页面和下面的其他先决条件。 ¥EAS Build is a rapidly evolving service. Before you set out to create a build for your project we recommend consulting the [limitations](/build-reference/limitations) page and the other prerequisites below. Note: A React Native Android and/or iOS project that you want to build --- Don't have a project yet? No problem. It's quick and easy to create a "Hello world" app that you can use with this guide. Run the following command to create a new project: ```sh $ npx create-expo-app my-app ``` EAS Build also works well with projects created by `npx create-react-native-app`, `npx react-native`, `ignite-cli`, and other project bootstrapping tools. --- Note: An Expo user account --- EAS Build is available to anyone with an Expo account, regardless of whether you pay for EAS or use our Free plan. You can sign up at [https://expo.dev/signup](https://expo.dev/signup). Paid subscribers get quality improvements such as additional build concurrencies, priority access to minimize the time your builds spend queueing, and increased limits on build timeouts. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing). --- Step 1: ## Install the latest EAS CLI EAS CLI is the command-line app that you will use to interact with EAS services from your terminal. To install it, run the command: ```sh $ npm install -g eas-cli ``` You can also use the above command to check if a new version of EAS CLI is available. We encourage you to always stay up to date with the latest version. > We recommend using `npm` instead of `yarn` for global package installations. You may alternatively use `npx eas-cli@latest`. Remember to use that instead of `eas` whenever it's called for in the documentation. Step 2: ## Log in to your Expo account If you are already signed in to an Expo account using Expo CLI, you can skip the steps described in this section. If you are not, run the following command to log in: ```sh $ eas login ``` You can check whether you are logged in by running `eas whoami`. Step 3: ## Configure the project To configure an Android or an iOS project for EAS Build, run the following command: ```sh $ eas build:configure ``` To learn more about what happens behind the scenes, see [build configuration process reference](/build-reference/build-configuration). For development, we recommend creating a [development build](/develop/development-builds/introduction/), which is a debug build of your app and contains the [`expo-dev-client`](/versions/latest/sdk/dev-client/) library. It helps you iterate as quickly as possible and provides a more flexible, reliable, and complete development environment. To install the library, run the following command: ```sh $ npx expo install expo-dev-client ``` Additional configuration may be required for some scenarios: - Does your app code depend on environment variables? [Add them to your build configuration](/build-reference/variables). - Is your project inside of a monorepo? [Follow these instructions](/build-reference/build-with-monorepos). - Do you use private npm packages? [Add your npm token](/build-reference/private-npm-packages). - Does your app depend on specific versions of tools like Node, Yarn, npm, CocoaPods, or Xcode? [Specify these versions in your build configuration](/build/eas-json). Step 4: ## Run a build ### Build for Android Emulator/device or iOS Simulator The easiest way to try out EAS Build is to create a build that you can run on your Android device/emulator or iOS Simulator. It's quicker than uploading it to a store, and you don't need store developer membership accounts. If you'd like to try this, read about [creating an installable APK for Android](/tutorial/eas/android-development-build/) and [creating a simulator build for iOS](/tutorial/eas/ios-development-build-for-simulators/). ### Build for app stores Before the build process can start for app stores, you will need to have a store developer account and generate or provide app signing credentials. Whether you have experience with generating app signing credentials or not, EAS CLI does the heavy lifting. You can opt-in for EAS CLI to handle the app signing credentials process. Check out the steps for [Android app signing credentials](#android-app-signing-credentials) or [iOS app signing credentials](#ios-app-signing-credentials) process below for more information. Note: Google Play Developer membership is required to distribute to the Google Play Store. --- You can build and sign your app using EAS Build, but you can't upload it to the Google Play Store unless you have a membership, a one-time $25 USD fee. --- Note: Apple Developer Program membership is required to build for the Apple App Store. --- If you are going to use EAS Build to create release builds for the Apple App Store, you need access to an account with a $99 USD [Apple Developer Program](https://developer.apple.com/programs) membership. --- After you have confirmed that you have a Google Play Store or Apple App Store account and decided whether or not EAS CLI should handle app signing credentials, you can proceed with the following set of commands to build for the platform's store: ```sh $ eas build --platform android ``` ```sh $ eas build --platform ios ``` > You can attach a message to the build by passing `--message` to the build command, for example, `eas build --platform ios --message "Some message"`. The message will appear on the website. It comes in handy when you want to leave a note with the purpose of the build for your team. Alternatively, you can use `--platform all` option to build for Android and iOS at the same time: ```sh $ eas build --platform all ``` > If you have released your app to stores previously and have existing [app signing credentials](/app-signing/app-credentials/) that you want to use, [follow these instructions to configure them](/app-signing/existing-credentials). #### Android app signing credentials - If you have not yet generated a keystore for your app, you can let EAS CLI take care of that for you by selecting `Generate new keystore`, and then you are done. The keystore is stored securely on EAS servers. - If you have previously built your app with `expo build:android`, you can use the same credentials here. - If you want to manually generate your keystore, see the [manual Android credentials guide](/app-signing/local-credentials#android-credentials) for more information. #### iOS app signing credentials - If you have not generated a provisioning profile and/or distribution certificate yet, you can let EAS CLI take care of that for you by signing into your Apple Developer Program account and following the prompts. - If you have already built your app with `expo build:ios`, you can use the same credentials here. - If you want to rather manually generate your credentials, refer to the [manual iOS credentials guide](/app-signing/local-credentials#ios-credentials) for more information. Step 5: ## Wait for the build to complete By default, the `eas build` command will wait for your build to complete, but you can interrupt it if you prefer not to wait. Monitor the progress and read the logs by following the link to the build details page that EAS CLI prompts once the build process gets started. You can also find this page by visiting [your build dashboard](https://expo.dev/builds) or running the following command: ```sh $ eas build:list ``` If you are a member of an organization and your build is on its behalf, you will find the build details on [the build dashboard for that account](https://expo.dev/accounts/[account]/builds). > **Did your build fail?** Double check that you followed any applicable instructions in the [configuration step](#3-configure-the-project) and refer to the [troubleshooting guide](/build-reference/troubleshooting) if needed. Step 6: ## Deploy the build If you have made it to this step, congratulations! Depending on which path you chose, you now either have a build that is ready to upload to an app store, or you have a build that you can install directly on an Android device/iOS Simulator. ### Distribute your app to an app store You will only be able to submit to an app store if you built specifically for that purpose. If you created a build for a store, [learn how to submit your app to app stores with EAS Submit](/submit/introduction). ### Install and run the app You will only be able to install the app directly to your Android device/iOS Simulator if you explicitly built it for that purpose. If you built for app store distribution, you will need to upload to an app store and then install it from there (for example, from Apple's TestFlight app). To learn how to install the app directly to your Android device/iOS Simulator, navigate to your build details page from [your build dashboard](https://expo.dev/accounts/[account]/builds) and click the "Install" button. ## 下一步 ¥Next steps 我们引导你完成使用 EAS Build 创建第一个构建的步骤,而没有深入介绍该过程的任何特定部分。 ¥We walked you through the steps to create your first build with EAS Build without going into too much depth on any particular part of the process. 当你准备了解更多信息时,我们建议你继续阅读以下主题以了解更多信息: ¥When you are ready to learn more, we recommend proceeding through the following topics to learn more: * [使用 eas.json 进行配置](/build/eas-json) ¥[Configuration with eas.json](/build/eas-json) * [内部分发](/build/internal-distribution) ¥[Internal distribution](/build/internal-distribution) * [更新](/build/updates) ¥[Updates](/build/updates) * [自动提交](/build/automate-submissions) ¥[Automating submissions](/build/automate-submissions) * [从 CI 触发构建](/build/building-on-ci) ¥[Triggering builds from CI](/build/building-on-ci) 你可能还想深入研究参考部分,以了解有关你最感兴趣的主题的更多信息,例如: ¥You may also want to dig through the reference section to learn more about the topics that interest you most, such as: * [构建网络钩子](/eas/webhooks) ¥[Build webhooks](/eas/webhooks) * [构建服务器基础设施](/build-reference/infrastructure) ¥[Build server infrastructure](/build-reference/infrastructure) * [安卓](/build-reference/android-builds) 和 [iOS 系统](/build-reference/ios-builds) 构建流程如何工作 ¥How the [Android](/build-reference/android-builds) and [iOS](/build-reference/ios-builds) build processes work ## 使用 eas.json 配置 EAS 构建 了解如何使用 eas.json 配置使用 EAS 服务的项目。 eas.json 是 EAS CLI 和服务的配置文件。它是在你的项目中第一次运行 [`eas build:configure` 命令](/build/setup/#configure-the-project) 时生成的,位于项目根目录的 package.json 旁边。EAS Build 的配置全部属于 `build` 密钥。 ¥**eas.json** is the configuration file for EAS CLI and services. It is generated when the [`eas build:configure` command](/build/setup/#configure-the-project) runs for the first time in your project and is located next to **package.json** at the root of your project. Configuration for EAS Build all belongs under the `build` key. 新项目中生成的 eas.json 的默认配置如下所示: ¥The default configuration for **eas.json** generated in a new project is shown below: ```json eas.json { "build": { "development": { "developmentClient": true, "distribution": "internal" }, "preview": { "distribution": "internal" }, "production": {} } } ``` ## 建立档案 ¥Build profiles 构建配置文件是一组命名的配置,描述了执行某种类型的构建所需的参数。 ¥A build profile is a named group of configurations that describes the necessary parameters to perform a certain type of build. `build` 键下的 JSON 对象可以包含多个构建配置文件,并且你可以拥有自定义构建配置文件名称。在默认配置中,存在三个构建配置文件:`development`、`preview` 和 `production`。然而,这些可能被命名为 `foo`、`bar` 和 `baz`。 ¥The JSON object under the `build` key can contain multiple build profiles, and you can have custom build profile names. In the default configuration, there are three build profiles: `development`, `preview`, and `production`. However, these could have been named `foo`, `bar`, and `baz`. 要使用特定配置文件运行构建,请使用如下所示的带有 `` 的命令: ¥To run a build with a specific profile, use the command as shown below with a ``: ```sh $ eas build --profile ``` 如果省略 `--profile` 标志,EAS CLI 将默认使用名为 `production` 的配置文件(如果存在)。 ¥If you omit the `--profile` flag, EAS CLI will default to using the profile with the name `production` (if it exists). ### 特定于平台的选项和通用选项 ¥Platform-specific and common options 在每个构建配置文件中,你可以指定包含特定于平台的构建配置的 [`android`](/eas/json/#android-specific-options) 和 [`ios`](/eas/json/#ios-specific-options) 字段。[两个平台都可用的选项](/eas/json/#common-properties-for-native-platforms) 可以在特定于平台的配置对象或配置文件的根上提供。 ¥Inside each build profile, you can specify [`android`](/eas/json/#android-specific-options) and [`ios`](/eas/json/#ios-specific-options) fields that contain platform-specific configuration for the build. [Options that are available to both platforms](/eas/json/#common-properties-for-native-platforms) can be provided on the platform-specific configuration object or the root of a profile. ### 在配置文件之间共享配置 ¥Sharing configuration between profiles 可以使用 `extends` 选项将构建配置文件扩展到其他构建配置文件属性。 ¥Build profiles can be extended to other build profile properties using the `extends` option. 例如,在 `preview` 配置文件中,你可能有 `"extends": "production"`。这将使 `preview` 配置文件继承 `production` 配置文件的配置。 ¥For example, in the `preview` profile you might have `"extends": "production"`. This will make the `preview` profile inherit the configuration of the `production` profile. 只要避免产生循环依赖,你就可以将配置文件扩展链接至 5 个深度。 ¥You can keep chaining profile extensions up to the depth of 5 as long as you avoid making circular dependencies. ## 常见用例 ¥Common use cases 使用 Expo 工具的开发者通常最终会得到三种不同类型的构建:开发、预览和生产。 ¥Developers using Expo tools usually end up having three different types of builds: **development**, **preview**, and **production**. ### 开发构建 ¥Development builds 默认情况下,`eas build:configure` 将使用 `"developmentClient": true` 创建 `development` 配置文件。这表明该构建依赖于 [`expo-dev-client`](/develop/development-builds/introduction/)。这些构建包括开发者工具,并且它们永远不会提交到应用商店。 ¥By default, `eas build:configure` will create a `development` profile with `"developmentClient": true`. This indicates that this build depends on [`expo-dev-client`](/develop/development-builds/introduction/). These builds include developer tools, and they are never submitted to an app store. `development` 配置文件也默认为 [`"distribution": "internal"`](/build/internal-distribution)。这将使你可以轻松地将应用直接分发到物理 Android 和 iOS 设备。 ¥The `development` profile also defaults to [`"distribution": "internal"`](/build/internal-distribution). This will make it easy to distribute your app directly to physical Android and iOS devices. 你还可以配置你的开发版本以在 [iOS 模拟器](/build-reference/simulators) 上运行。为此,请对 `development` 配置文件使用以下配置: ¥You can also configure your development builds to run on the [iOS Simulator](/build-reference/simulators). To do this, use the following configuration for the `development` profile: ```json eas.json { "build": { "development": { "developmentClient": true, "distribution": "internal", "ios": { "simulator": true } } } } ``` > 注意:对于 iOS,要为内部分发创建一个构建,并为 iOS 模拟器创建另一个构建,你可以为该构建创建单独的开发配置文件。你可以为配置文件指定一个自定义名称。例如,`development-simulator`,并在该配置文件上使用 [iOS 模拟器具体配置](/build-reference/simulators/#configuring-a-profile-to-build-for-simulators),而不是在 `development` 上。运行 [设备和 Android 模拟器上的 Android .apk](/build-reference/apk) 不需要此类配置,因为相同的 .apk 将在两种环境中运行。 > > ¥**Note:** For iOS, to create a build for internal distribution and another for the iOS Simulator, you can create a separate development profile for that build. You can give the profile a custom name. For example, `development-simulator`, and use the [iOS Simulator specific configuration](/build-reference/simulators/#configuring-a-profile-to-build-for-simulators) on that profile instead of on `development`. No such configuration is required to run an [Android **.apk** on a device and an Android Emulator](/build-reference/apk) as the same **.apk** will run with both environments. ### 预览版本 ¥Preview builds 这些版本不包括开发者工具。它们旨在由你的团队和其他利益相关者安装,以在类似生产的环境中测试应用。从这一点来说,它们和 [生产构建](#production-builds) 很相似。但是,它们与生产版本不同,因为它们要么没有在应用商店上进行分发(iOS 上的临时或企业配置),要么以不适合商店部署的方式打包(建议使用 Android .apk 进行预览) ,.aab 建议用于 Google Play 商店)。 ¥These builds don't include developer tools. They are intended to be installed by your team and other stakeholders, to test out the app in production-like circumstances. In this way, they are similar to [production builds](#production-builds). However, they are different from production builds because they are either not signed for distribution on app stores (ad hoc or enterprise provisioning on iOS), or are packaged in a way that is not optimal for store deployment (Android **.apk** is recommended for preview, **.aab** is recommended for Google Play Store). 最小 `preview` 配置文件示例: ¥A minimal `preview` profile example: ```json eas.json { "build": { "preview": { "distribution": "internal" } } } ``` 与 [开发构建](#development-builds) 类似,你可以配置预览版本以在 [iOS 模拟器](/build-reference/simulators) 上运行,或为此目的创建预览配置文件的变体。运行 [设备和 Android 模拟器上的 Android .apk](/build-reference/apk) 不需要此类配置,因为相同的 .apk 将在两种环境中运行。 ¥Similar to [development builds](#development-builds), you can configure a preview build to run on the [iOS Simulator](/build-reference/simulators) or create a variant of your preview profile for that purpose. No such configuration is required to run an [Android **.apk** on a device and an Android Emulator](/build-reference/apk) as the same **.apk** will run with both environments. ### 生产构建 ¥Production builds 这些构建版本被提交到应用商店,以便向公众发布或作为商店促进的测试过程(例如 TestFlight)的一部分。 ¥These builds are submitted to an app store, for release to the general public or as part of a store-facilitated testing process such as TestFlight. 生产版本必须通过各自的应用商店安装。它们无法直接安装在你的 Android 模拟器或设备、iOS 模拟器或设备上。唯一的例外是,如果你在构建配置文件中明确为 Android 设置了 `"buildType": "apk"`。但是,建议在提交到商店时使用 .aab,因为这是默认配置。 ¥Production builds must be installed through their respective app stores. They cannot be installed directly on your Android Emulator or device, or iOS Simulator or device. The only exception to this is if you explicitly set `"buildType": "apk"` for Android on your build profile. However, it is recommended to use **.aab** when submitting to stores, as this is the default configuration. 最小 `production` 配置文件示例: ¥A minimal `production` profile example: ```json eas.json { "build": { "production": {} } } ``` ### 在单个设备上安装同一应用的多个版本 ¥Installing multiple builds of the same app on a single device 在同一设备上同时安装开发和生产版本是很常见的。参见 [在同一设备上安装应用变体](/build-reference/variants/)。 ¥It's common to have development and production builds installed simultaneously on the same device. See [Install app variants on the same device](/build-reference/variants/). ## 配置构建工具 ¥Configuring build tools 每个构建都隐式或显式地依赖于执行构建过程所需的一组特定版本的相关工具。这些包括但不限于:Node.js、npm、Yarn、Ruby、Bundler、CocoaPods、Fastlane、Xcode 和 Android NDK。 ¥Every build depends either implicitly or explicitly on a specific set of versions of related tools that are needed to carry out the build process. These include but are not limited to: Node.js, npm, Yarn, Ruby, Bundler, CocoaPods, Fastlane, Xcode, and Android NDK. ### 选择构建工具版本 ¥Selecting build tool versions 可以在构建配置文件上设置最常见构建工具的版本,其中的字段与工具名称相对应。例如 [`node`](/eas/json/#node): ¥Versions for the most common build tools can be set on build profiles with fields corresponding to the names of the tools. For example [`node`](/eas/json/#node): ```json eas.json { "build": { "production": { "node": "18.18.0" } } } ``` 在配置文件之间共享构建工具配置是很常见的。使用 `extends` 来实现: ¥It's common to share build tool configurations between profiles. Use `extends` for that: ```json eas.json { "build": { "production": { "node": "18.18.0" }, "preview": { "extends": "production", "distribution": "internal" }, "development": { "extends": "production", "developmentClient": true, "distribution": "internal" } } } ``` ### 选择资源类别 ¥Selecting resource class 资源类是 EAS Build 为你的作业提供的虚拟机资源配置(CPU 核心、RAM 大小)。默认情况下,资源类别设置为 `medium`,这通常足以满足小型和大型项目的需要。但是,如果你的项目需要更强大的 CPU 或更大的内存,或者你希望构建更快地完成,则可以切换到 `large` 工作线程。 ¥A resource class is the virtual machine resources configuration (CPU cores, RAM size) EAS Build provides to your jobs. By default, the resource class is set to `medium`, which is usually sufficient for both small and bigger projects. However, if your project requires a more powerful CPU or bigger memory, or if you want your builds to finish faster, you can switch to `large` workers. 有关为每个类提供的资源的更多详细信息,请参阅 [`android.resourceClass`](/eas/json/#resourceclass-1) 和 [`ios.resourceClass`](/eas/json/#resourceclass-2) 属性。要在特定资源类的工作线程上运行构建,请在构建配置文件中配置此属性: ¥For more details on resources provided to each class, see [`android.resourceClass`](/eas/json/#resourceclass-1) and [`ios.resourceClass`](/eas/json/#resourceclass-2) properties. To run your build on a worker of a specific resource class, configure this property in your build profile: ```json eas.json { "build": { "production": { "android": { "resourceClass": "medium" }, "ios": { "resourceClass": "large" }, } } } ``` > 注意:在 `large` 工作线程上运行作业需要 [付费 EAS 计划](https://expo.dev/accounts/\[account]/settings/billing)。 > > ¥**Note**: Running jobs on a `large` worker requires a [paid EAS plan](https://expo.dev/accounts/\[account]/settings/billing). ### 选择基础图片 ¥Selecting a base image 构建作业的基础镜像控制各种依赖的默认版本,例如 Node.js、Yarn 和 CocoaPods。你可以使用特定的命名字段来覆盖它们,如上一节中所述,使用 `resourceClass`。但是,该映像包含无法以任何其他方式显式设置的特定工具版本,例如操作系统版本和 Xcode 版本。 ¥The base image for the build job controls the default versions for a variety of dependencies, such as Node.js, Yarn, and CocoaPods. You can override them using the specific named fields as described in the previous section using `resourceClass`. However, the image includes specific versions of tools that can't be explicitly set any other way, such as the operating system version and Xcode version. 如果你正在使用 Expo 构建应用,EAS Build 将选择适当的图片,并使用一组合理的依赖来适应你正在构建的 SDK 版本。否则,建议查看 [构建服务器基础设施](/build-reference/infrastructure) 上的可用图片列表。 ¥If you are building an app with Expo, EAS Build will pick the appropriate image to use with a reasonable set of dependencies for the SDK version that you are building for. Otherwise, it is recommended to see the list of available images on [Build server infrastructure](/build-reference/infrastructure). ### 示例 ¥Examples #### 模式 ¥Schema ```json eas.json { "cli": { }, "build": { "android": { }, "ios": { } }, } } ``` > 你可以在特定于平台的配置对象或配置文件的根中指定 [共同属性](/eas/json/##common-properties-for-native-platforms)。特定于平台的选项优先于全局定义的选项。 > > ¥You can specify [common properties](/eas/json/##common-properties-for-native-platforms) both in the platform-specific configuration object or at the profile's root. The platform-specific options take precedence over globally-defined ones. Note: A managed project with several profiles --- ```json eas.json { "build": { "base": { "node": "12.13.0", "yarn": "1.22.5", "env": { "EXAMPLE_ENV": "example value" }, "android": { "image": "default", "env": { "PLATFORM": "android" } }, "ios": { "image": "latest", "env": { "PLATFORM": "ios" } } }, "development": { "extends": "base", "developmentClient": true, "env": { "ENVIRONMENT": "development" }, "android": { "distribution": "internal", "withoutCredentials": true }, "ios": { "simulator": true } }, "staging": { "extends": "base", "env": { "ENVIRONMENT": "staging" }, "distribution": "internal", "android": { "buildType": "apk" } }, "production": { "extends": "base", "env": { "ENVIRONMENT": "production" } } } } ``` --- Note: A bare project with several profiles --- ```json eas.json { "build": { "base": { "env": { "EXAMPLE_ENV": "example value" }, "android": { "image": "ubuntu-18.04-android-30-ndk-r19c", "ndk": "21.4.7075529" }, "ios": { "image": "latest", "node": "12.13.0", "yarn": "1.22.5" } }, "development": { "extends": "base", "env": { "ENVIRONMENT": "staging" }, "android": { "distribution": "internal", "withoutCredentials": true, "gradleCommand": ":app:assembleDebug" }, "ios": { "simulator": true, "buildConfiguration": "Debug" } }, "staging": { "extends": "base", "env": { "ENVIRONMENT": "staging" }, "distribution": "internal", "android": { "gradleCommand": ":app:assembleRelease" } }, "production": { "extends": "base", "env": { "ENVIRONMENT": "production" } } } } ``` --- ## 环境变量 ¥Environment variables 你可以使用 `"env"` 字段在构建配置文件上配置环境变量。当你运行 `eas build` 时,这些环境变量将用于在本地评估 app.config.js,并且它们也将在 EAS Build 构建器上设置。 ¥You can configure environment variables on your build profiles using the `"env"` field. These environment variables will be used to evaluate **app.config.js** locally when you run `eas build`, and they will also be set on the EAS Build builder. ```json eas.json { "build": { "production": { "node": "16.13.0", "env": { "API_URL": "https://company.com/api" } }, "preview": { "extends": "production", "distribution": "internal", "env": { "API_URL": "https://staging.company.com/api" } } } } ``` [环境变量和秘密](/build-reference/variables) 参考更详细地解释了此主题,[使用 EAS 更新](/build/updates) 指南提供了与 `expo-updates` 一起使用此功能时的注意事项。 ¥The [Environment variables and secrets](/build-reference/variables) reference explains this topic in greater detail, and the [Use EAS Update](/build/updates) guide provides considerations when using this feature alongside `expo-updates`. ## 更多 ¥More ## 内部分发 了解 EAS Build 如何为你的构建提供可与你的团队共享的 URL,以便进行内部分发。 使用 EAS Build 只需几分钟即可设置内部分发版本,并提供一种简化的方式,以便与你的团队和其他测试人员共享你的应用以获取反馈。它通过提供一个 URL 来实现此目的,该 URL 允许用户将应用直接安装到他们的设备上。如果你还不确定是否要使用此方法,并且想要了解所有可用于内部分发应用的选项,请参阅 [待审核分发应用概览](/review/overview/) 指南。 ¥Setting up an internal distribution build only takes a few minutes with EAS Build and provides a streamlined way to share your app with your team and other testers for feedback. It does this by providing a URL that allows them to install the app directly to their device. If you are not sure yet if you want to use this approach and want to learn about all of the options available for distributing your app internally, refer to the [overview of distribution apps for review](/review/overview/) guide. ## 使用内部分发 ¥Using internal distribution 要配置用于内部分发的构建配置文件,请在其上设置 `"distribution": "internal"`。设置此配置后,它将对构建配置文件产生以下影响: ¥To configure a build profile for internal distribution, set `"distribution": "internal"` on it. When you set this configuration, it has the following effects on the build profile: * 安卓:`gradleCommand` 的默认行为将更改为生成 APK 而不是 AAB。如果你指定了自定义 `gradleCommand`,请确保它也指定了 [生成 APK](/build-reference/apk/#configuring-a-profile-to-build-apks),否则它将无法直接安装在 Android 设备上。此外,EAS Build 将生成一个新的 Android 密钥库用于对 APK 进行签名,如果软件包名称与你的 [开发构建](/develop/development-builds/introduction/) 相同,它将使用现有的密钥库。 ¥**Android**: The default behavior for the `gradleCommand` will change to generate an APK instead of an AAB. If you have specified a custom `gradleCommand`, then make sure that it [produces an APK](/build-reference/apk/#configuring-a-profile-to-build-apks), or it won't be directly installable on an Android device. Additionally, EAS Build will generate a new Android keystore for signing the APK, or it will use an existing one if the package name is the same as your [development build](/develop/development-builds/introduction/). * iOS:使用此配置文件的构建将使用 [临时或企业级配置](#overview-of-distribution-mechanisms)。使用临时配置时,EAS Build 将生成一个包含设备 UDID 允许列表的配置文件,并且只有在构建时列表中的设备才能安装它。你可以通过运行 `eas device:create` 并创建新版本来添加设备。 ¥**iOS**: Builds using this profile will use either [ad hoc or enterprise provisioning](#overview-of-distribution-mechanisms). When using ad hoc provisioning, EAS Build will generate a provisioning profile containing an allow-list of device UDIDs, and only those devices in the list at build time will be able to install it. You can add a device by running `eas device:create` and creating a new build. * 默认情况下,任何拥有 URL 的人都可以访问内部分发构建 URL,每个 URL 都由一个 32 个字符的 UUID 标识。如果你希望要求登录授权的 Expo 账户才能访问这些构建,你可以在 [项目设置](https://expo.dev/accounts/\[account]/projects/\[project]/settings) 中禁用“未经身份验证访问内部构建”选项。 ¥By default, internal distribution build URLs are available to anybody with the URL, and each is identified by a 32 character UUID. If you would like to require sign-in to an authorized Expo account to access these builds, you can disable the **Unauthenticated access to internal builds** option in your [project settings](https://expo.dev/accounts/\[account]/projects/\[project]/settings). 有关如何配置、创建和安装构建的更多信息,请参阅下面有关使用 EAS Build 进行内部分发的教程: ¥See the tutorial on Internal distribution with EAS Build below for more information on how to configure, create, and install a build: ### CI 自动化(可选) ¥Automation on CI (optional) 可以使用 `--non-interactive` 标志在 CI 中以非交互方式运行内部分发版本。但是,如果你在 iOS 上使用临时配置,则使用此标志时将无法将新设备添加到你的配置文件中。通过 `eas device:create` 注册设备后,你需要以交互方式运行 `eas build` 并通过 Apple 进行身份验证,以便 EAS 将设备添加到你的配置文件中。[了解有关从 CI 触发构建的更多信息](/build/building-on-ci)。 ¥It's possible to run internal distribution builds non-interactively in CI using the `--non-interactive` flag. However, if you are using ad hoc provisioning on iOS you will not be able to add new devices to your provisioning profile when using this flag. After registering a device through `eas device:create`, you need to run `eas build` interactively and authenticate with Apple in order for EAS to add the device to your provisioning profile. [Learn more about triggering builds from CI](/build/building-on-ci). ### 管理设备 ¥Managing devices 你可以通过运行以下命令查看通过 `eas device:create` 注册的任何设备: ¥You can see any devices registered via `eas device:create` by running: ```sh $ eas device:list ``` 在 Expo 中注册用于临时配置的设备在用于通过 EAS Build 生成新的内部构建或通过 `eas build:resign` 生成 [放弃现有的构建](/app-signing/app-credentials/#re-signing-new-credentials) 的配置文件后,将出现在你的 Apple 开发者门户上。 ¥Devices registered with Expo for ad hoc provisioning will appear on your Apple Developer Portal after they are used to generate a provisioning profile for a new internal build with EAS Build or to [resign an existing build](/app-signing/app-credentials/#re-signing-new-credentials) with `eas build:resign`. #### 删除设备 ¥Remove devices 如果不再使用某个设备,可以通过运行以下命令将其从该列表中删除: ¥If a device is no longer in use, it can be removed from this list by running: ```sh optionally disable them on the Apple Developer Portal $ eas device:delete ``` 此命令还会提示你在 Apple 开发者门户上禁用该设备。对于每个应用的临时分发,已禁用的设备仍计入 [Apple 的设备数量限制为 100 台](https://developer.apple.com/support/account/#:~:text=Resetting%20your%20device%20list%20annually)。 ¥This command will also prompt you to disable the device on the Apple Developer Portal. Disabled devices still count against [Apple's limit of 100 devices](https://developer.apple.com/support/account/#:~:text=Resetting%20your%20device%20list%20annually) for ad hoc distribution per app. #### 重命名设备 ¥Rename devices 通过网站 URL/QR 代码添加的设备在选择 EAS 构建时将默认显示其 UDID。你可以使用以下命令为设备分配友好名称: ¥Devices added via the website URL/QR code will default to displaying their UDID when selecting them for an EAS Build. You can assign friendly names to your devices with the following command: ```sh $ eas device:rename ``` ## 分发机制概述 ¥Overview of distribution mechanisms 以下是将你的应用分发到内部分发支持的设备的不同机制。 ¥The following are the different mechanisms for distributing your app to devices supported by internal distribution. Note: Android: Build and distribute an APK --- To share your app to Android devices, you must build an APK (Android application package file) of your project. APKs can be installed directly to an Android device over USB, by downloading the file over the web or through an email or chat app, once the user accepts the security warning for installing an app that has not gone through Play Store review. AAB (Android app bundle) binaries of your app must be distributed through the Play Store. --- Note: iOS: Ad Hoc distribution --- Apple offers [ad hoc provisioning profiles](https://help.apple.com/xcode/mac/current/#/dev7ccaf4d3c) to distribute your app to test devices once they have been registered to your Apple Developer account. This method requires a paid Apple Developer account and that account will only be able to use this method to distribute to at most 100 iPhones per year. You will need to know the UDID (Unique Device Identifier) of each device that will install your app, which may be challenging if you try to share with someone who is not a developer. Adding a new device will require a rebuild of your app or [re-signing the build with new credentials](/app-signing/app-credentials/#re-signing-new-credentials). Setting up Ad Hoc certificates correctly can be intimidating if you haven't done it before and tedious even if you have. If you're using [EAS Build](#internal-distribution-with-eas-build), which is optimized for Expo and React Native projects, we'll handle the time-consuming parts of setting up Ad Hoc credentials for you. --- Note: iOS: Enterprise distribution --- If your app is only intended for internal use by employees of a large organization and cannot be distributed through the App Store, you should use Enterprise distribution. Unlike with Ad Hoc Distribution, the number of devices that can install your app is unlimited, and you do not need to manage each device's UDID. Often these apps will be distributed to end users through a mobile device management (MDM) solution. Enterprise Distribution requires membership in the [Apple Developer Enterprise Program](https://developer.apple.com/programs/enterprise/). Organizations joining the Enterprise Program must meet additional requirements beyond what is required for App Store distribution. --- ## 自动提交 了解如何使用 EAS Build 启用自动提交。 许多移动部署流程最终发展到这样的程度:一旦完成适当的构建,应用就会自动提交到相应的商店。这使开发者不必等待构建完成,避免了一些手动工作,并且无需协调向团队提供应用商店凭据。 ¥Many mobile deployment processes eventually evolve to the point where the app is automatically submitted to the respective store once an appropriate build is completed. This saves developers from having to wait around for the build to complete, avoids a bit of manual work, and eliminates the need to coordinate providing app store credentials to the team. EAS Build 为你提供带有 `--auto-submit` 标志的开箱即用的自动提交。该标志告诉 EAS Build 在完成后将构建传递给 EAS Submit,并提供适当的提交配置文件。有关如何设置和配置提交的更多信息,请参阅 [EAS 提交文件](/submit/introduction)。 ¥EAS Build gives you automatic submissions out of the box with the `--auto-submit` flag. This flag tells EAS Build to pass the build along to EAS Submit with the appropriate submission profile upon completion. Refer to the [EAS Submit documentation](/submit/introduction) for more information on how to set up and configure submissions. 当你运行 `eas build --auto-submit` 时,你将获得一个提交详细信息页面的链接,你可以在其中跟踪提交的进度。你还可以随时在 [你项目的提交仪表板](https://expo.dev/accounts/\[account]/projects/\[project]/submissions) 上找到此页面,它是从你的构建详细信息页面链接的。 ¥When you run `eas build --auto-submit` you will be provided with a link to a submission details page, where you can track the progress of the submission. You can also find this page at any time on the [submissions dashboard for your project](https://expo.dev/accounts/\[account]/projects/\[project]/submissions), and it is linked from your build details page. ## 选择提交配置文件 ¥Selecting a submission profile 默认情况下,`--auto-submit` 将尝试使用与所选构建配置文件同名的提交配置文件。如果该配置文件不存在,或者你希望使用不同的配置文件,则可以使用 `--auto-submit-with-profile=`。 ¥By default, `--auto-submit` will try to use a submission profile with the same name as the selected build profile. If this does not exist, or if you prefer to use a different profile, you can use `--auto-submit-with-profile=` instead. ## 构建配置文件环境变量并提交 ¥Build profile environment variables and submissions 运行 `eas build --profile --auto-submit` 时,将使用与构建配置文件 `` 关联的任何环境变量来评估项目的 app.config.js。例如,假设我们使用以下配置运行 `eas build -p ios --profile production --auto-submit`: ¥When running `eas build --profile --auto-submit`, the project's **app.config.js** will be evaluated using any environment variables associated with the build profile ``. For example, suppose we ran `eas build -p ios --profile production --auto-submit` with the following configuration: ```json eas.json { "build": { "production": { "env": { "APP_ENV": "production" } }, "development": { "env": { "APP_ENV": "development" } } } } ``` ```js app.config.js export default () => { return { name: process.env.APP_ENV === 'production' ? 'My App' : 'My App (DEV)', ios: { bundleIdentifier: process.env.APP_ENV === 'production' ? 'com.my.app' : 'com.my.app-dev', }, // ... other config here }; }; ``` 在评估提交的 app.config.js 时,将使用 `production` 配置文件中的 `APP_ENV` 变量,因此名称将为 `My App`,包标识符将为 `com.my.app`。 ¥The `APP_ENV` variable from the `production` profile will be used when evaluating **app.config.js** for the submission, and therefore the name will be `My App` and the bundle identifier will be `com.my.app`. ## 应用商店的默认提交行为 ¥Default submission behavior for app stores 默认情况下,`--auto-submit` 标志将使你的构建可用于内部测试,但不会自动提交你的应用进行公开发布审核。以下部分描述了 Android 和 iOS 的默认提交行为。 ¥By default, the `--auto-submit` flag will make your build available for internal testing, but will not automatically submit your app to review for public distribution. Sections below describe the default submission behavior for Android and iOS. ### Android 提交 ¥Android submissions 对于 Android,如果未提供足够的元数据,则默认行为是为新应用创建内部版本。要控制版本的提交位置和方式,你可以在 eas.json 提交配置文件中指定 `releaseStatus` 和 `track` 字段: ¥For Android, if sufficient metadata is not provided, the default behavior is to create an internal release for new apps. To control where and how your build is submitted, you can specify the `releaseStatus` and `track` fields in your **eas.json** submission profile: **发布状态选项:** ¥**Release status options:** * `draft`:创建需要在 Google Play 管理中心手动推广的草稿版本 ¥`draft`: Creates a draft release that requires manual promotion in the Google Play Console * `completed`:立即向指定轨道上的用户发布 ¥`completed`: Immediately releases to users on the specified track * `inProgress`:分阶段发布(与 `rollout` 百分比配合使用) ¥`inProgress`: Staged rollout release (use with `rollout` percentage) * `halted`:暂停发布 ¥`halted`: Halted release 当你在 eas.json 中为提交配置文件明确设置轨道时,`--auto-submit` 标志会将构建提交到所选轨道。这还需要将 `releaseStatus` 设置为 `completed`: ¥When you explicitly set a track to your submission profile in **eas.json**, the `--auto-submit` flag will submit the build to the chosen track. This also requires the `releaseStatus` to be set to `completed`: **轨道选项:** ¥**Track options:** * `internal`:内部测试轨道(最多 100 名测试人员)(默认) ¥`internal`: Internal testing track (up to 100 testers) (default) * `alpha`:封闭测试轨道 ¥`alpha`: Closed testing track * `beta`:开放测试轨道 ¥`beta`: Open testing track * `production`:生产轨道(公开发布) ¥`production`: Production track (public release) ### iOS 提交 ¥iOS submissions 对于 iOS,默认提交行为是将构建提交到 TestFlight,但不提交到 App Store 审核。这意味着: ¥For iOS, the default submission behavior is to submit the build to TestFlight, but not for App Store review. This means: * 构建版本已提交至 TestFlight 并可供内部测试。 ¥The build is submitted to TestFlight and becomes available for internal testing. * 如果你已在 App Store Connect 中启用 "启用自动分发",TestFlight 将自动创建一个组并邀请所有内部 TestFlight 用户测试版本。 ¥If you have "Enable automatic distribution" turned on in App Store Connect, TestFlight will automatically create a group and invite all your internal TestFlight users to test the build. * 你还可以使用 eas.json 提交配置文件中的 [`groups`](/eas/json/#groups) 字段指定其他 TestFlight 组。 ¥You can also specify additional TestFlight groups using the [`groups`](/eas/json/#groups) field in your **eas.json** submission profile. * 使用 TestFlight,你可以发布一个可供内部和外部测试的应用版本。TestFlight 允许最多 100 名内部测试人员共享,并提供一个公共链接,最多可与 10,000 名外部测试人员共享。 ¥Using TestFlight, you can release a version of your app available for internal and external testing. TestFlight allows sharing with up to 100 testers internally and provides a public link to share with up to 10,000 external testers. * 发布到 Apple App Store 的审核是一个手动过程。提交到 TestFlight 后,你必须手动将版本推送到 App Store。 ¥The release to Apple App Store review is a manual process. Once you have made a submission to TestFlight, you'll have to manually promote the build to the App Store. 此行为可确保所有 iOS 版本在使用 `--auto-submit` 时都经过 TestFlight 测试,从而允许你在决定向公众发布版本之前进行测试。 ¥This behavior ensures that all iOS releases go through TestFlight when using `--auto-submit`, allowing you to test the release before deciding to make it available to the public. ### 修改 App Store 列表(仅限 iOS) ¥Modifying App Store listing (iOS only) EAS Submit 本身不会更新商店元数据(应用描述、Apple 建议信息、语言等)。但是,一旦你使用 EAS Submit 将新版本号的构建版本上传到 Testflight,你就可以使用 EAS Metadata 更新此信息。 ¥On its own, EAS Submit does not update store metadata (app description, Apple advisory information, languages, and so on). However, once you upload a build to Testflight with EAS Submit with a new version number, you can update this information with EAS Metadata. ## 使用 EAS 更新 了解如何将 EAS 更新与 EAS 构建结合使用。 EAS Build 为 [`expo-updates`](/versions/latest/sdk/updates/) 库提供了一些特殊好处。特别是,你可以在 eas.json 中配置 [`channel`](/eas-update/how-it-works/#distributing-builds) 属性,EAS Build 将在构建时在你的原生项目中更新它。 ¥EAS Build includes some special benefits for [`expo-updates`](/versions/latest/sdk/updates/) library. In particular, you can configure the [`channel`](/eas-update/how-it-works/#distributing-builds) property in **eas.json** and EAS Build will take care of updating it in your native project at build time. 本文档涵盖了将 `expo-updates` 库与 EAS Build 结合使用的特定问题。有关使用 EAS 更新配置磁带库的更多一般信息,请参阅 [开始使用 EAS 更新](/eas-update/getting-started/)。 ¥This document covers concerns specific to using `expo-updates` library with EAS Build. For more general information about configuring the library with EAS Update, see [Getting started with EAS Update ](/eas-update/getting-started/). ## 设置构建配置文件的通道 ¥Setting the channel for a build profile 每个 [建立档案](/build/eas-json/#build-profiles) 都可以分配给一个通道,因此为给定配置文件生成的版本的更新将仅提取发布到其通道的那些版本。 ¥Each [build profile](/build/eas-json/#build-profiles) can be assigned to a channel, so updates for builds produced for a given profile will pull only those releases that are published to its channel. 以下示例演示了如何使用 `"production"` 通道进行生产构建,以及如何使用 `"staging"` 通道进行随 [内部分配](/build/internal-distribution/) 分发的测试构建。 ¥The following example demonstrates how you might use the `"production"` channel for production builds, and the `"staging"` channel for test builds distributed with [internal distribution](/build/internal-distribution/). ```json eas.json { "build": { "production": { "channel": "production" }, "preview": { "channel": "staging", "distribution": "internal" } } } ``` ## 二进制兼容性和运行时版本 ¥Binary compatibility and runtime versions 你的原生运行时可能会在每次构建时发生变化,具体取决于你是否以更改 JavaScript 的 API 协定的方式修改代码。如果你将 JavaScript 打包包发布到具有不兼容原生运行时的二进制文件(例如,JavaScript 打包包期望存在的函数不存在),那么你的应用可能无法按预期工作,或者可能崩溃。 ¥Your native runtime may change on each build, depending on whether you modify the code in a way that changes the API contract with JavaScript. If you publish a JavaScript bundle to a binary with an incompatible native runtime (for example, a function that the JavaScript bundle expects to exist does not exist) then your app may not work as expected, or it may crash. 我们建议为应用的每个二进制版本使用不同的 [运行时版本](/distribution/runtime-versions/)。任何时候更改原生运行时(在托管应用中,当你添加或删除原生库或修改 app.json 时都会发生这种情况),你应该增加运行时版本。 ¥We recommend using a different [runtime version](/distribution/runtime-versions/) for each binary version of your app. Any time you change the native runtime (in managed apps, this happens when you add or remove a native library, or modify **app.json**), you should increment the runtime version. ## 预览开发版本中的更新 ¥Previewing updates in development builds 使用 `runtimeVersion` 字段发布的更新无法在 Expo Go 中加载。相反,你应该使用 [`expo-dev-client`](/versions/latest/sdk/dev-client/) 来创建开发版本。 ¥Updates published with the `runtimeVersion` field can't be loaded in Expo Go. Instead, you should use [`expo-dev-client`](/versions/latest/sdk/dev-client/) to create a development build. ## 环境变量和 `eas update` ¥Environment variables and `eas update` 当你运行 `eas update` 时,构建配置文件中 `env` 字段上设置的环境变量不可用。了解有关使用 [EAS 更新的环境变量](/eas-update/environment-variables) 的更多信息。 ¥Environment variables set on the `env` field in build profiles are not available when you run `eas update`. Learn more about using [environment variables with EAS Update](/eas-update/environment-variables). ## 从 CI 触发构建 了解如何从 CI 环境(例如 GitHub Actions 等)触发应用在 EAS 上的构建。 本文档概述了如何从 CI 环境(例如 GitHub Actions、Travis CI 等)触发应用的 EAS 构建。 ¥This document outlines how to trigger builds on EAS for your app from a CI environment such as GitHub Actions, Travis CI, and more. ## 先决条件 ¥Prerequisites ### 从本地计算机运行成功的构建 ¥Run a successful build from your local machine 要从 CI 环境触发 EAS 构建,你的应用需要设置为在非交互模式下使用 EAS Build。为此,请完成 EAS 构建初始化步骤,并从本地终端为你想要在 CI 上支持的每个平台运行成功的构建。这样,`eas build` 命令可以提示它需要的任何其他配置,然后该配置将可用于未来在 CI 上进行非交互式运行。 ¥To trigger EAS builds from a CI environment, your app needs to be set up to use EAS Build in non-interactive mode. To do this, go through the EAS Build initialization steps and run a successful build from your local terminal for each platform you would like to support on CI. This way, the `eas build` command can prompt for any additional configuration it needs, and then that configuration will be available for future non-interactive runs on CI. 在本地运行构建将完成以下关键配置步骤: ¥Running a build locally will accomplish the following critical configuration steps: * 通过生成 `projectId` 在 EAS 上初始化项目。 ¥Initialize the project on EAS by generating a `projectId`. * 添加定义构建配置文件的 eas.json 文件。 ¥Add an **eas.json** file defining your build profiles. * 填充原生版本的关键应用配置属性,例如 `android.packageName` 和 `ios.bundleIdentifier`。 ¥Populates critical app config properties for native builds, such as `android.packageName` and `ios.bundleIdentifier`. * 确保创建构建凭据,包括 Android 密钥库和 iOS 分发证书和配置文件。 ¥Ensure build credentials are created, including Android keystores and iOS distribution certs and provisioning profiles. 运行 `eas build -p [all|android|ios]` 并验证每个平台的构建是否成功完成。然后,继续执行以下步骤在 CI 上实现 EAS 构建。 ¥Run `eas build -p [all|android|ios]` and verify that your builds for each platform complete successfully. Then, continue with the below steps for implementing EAS Build on CI. 如果你尚未完成此操作,请参阅 [创建你的第一个版本](/build/setup/) 指南并在准备好后返回此处。 ¥If you haven't done this yet, see the [Create your first build](/build/setup/) guide and return here when you're ready. ## 使用 EAS 工作流 ¥Using EAS Workflows [EAS 工作流程](/eas/workflows/get-started) 是 Expo 提供的一项 CI/CD 服务,允许你在 EAS 上运行构建以及许多其他类型的作业。你可以使用 EAS 工作流来自动化你的开发和发布流程,例如创建开发版本或自动构建并提交到应用商店。 ¥[EAS Workflows](/eas/workflows/get-started) is a CI/CD service from Expo that allows you to run builds, and many other types of jobs, on EAS. You can use EAS Workflows to automate your development and release processes, like creating development builds or automatically building and submitting to the app stores. 要使用 EAS 工作流创建构建,请先在 .eas/workflows/build.yml 中添加以下代码: ¥To create a build with EAS Workflows, start by adding the following code in **.eas/workflows/build.yml**: ```yaml name: Build on: push: branches: - main jobs: build_android: name: Build Android App type: build params: platform: android build_ios: name: Build iOS App type: build params: platform: ios ``` 当将提交推送到主分支时,此工作流将创建 Android 和 iOS 构建。你可以了解如何修改此工作流程并对 [EAS 工作流文档](/eas/workflows/get-started) 中的其他类型的作业进行排序。 ¥When a commit is pushed to the main branch, this workflow will create Android and iOS builds. You can learn how to modify this workflow and sequence other types of jobs in the [EAS Workflows documentation](/eas/workflows/get-started). ## 为其他 CI 配置应用服务 ¥Configuring your app for other CI services {/* ### Make EAS CLI available in your CI environment */} {/* To interact with the EAS API, we need to install EAS CLI. You can use an environment with this library preinstalled, or you can add it to the project as a development dependency. */} {/* The latter is the easiest way, but may increase the installation time. */} {/* For vendors that charge you per minute, it might be worth creating a prebuilt environment. */} {/* To install EAS CLI in your project, run: */} {/* ```sh */} {/* npm install --save-dev eas-cli */} {/* ``` */} {/* > **info** Make sure to update this dependency frequently to stay up to date with the EAS API interface. */} ### 提供个人访问令牌以通过 CI 上的 Expo 账户进行身份验证 ¥Provide a personal access token to authenticate with your Expo account on CI 接下来,我们需要确保我们可以在 CI 上验证自己作为应用所有者的身份。这可以通过在 CI 设置的 `EXPO_TOKEN` 环境变量中存储个人访问令牌来实现。 ¥Next, we need to ensure that we can authenticate ourselves on CI as the owner of the app. This is possible by storing a personal access token in the `EXPO_TOKEN` environment variable in the CI settings. 请参阅 [个人访问令牌](/accounts/programmatic-access/#personal-access-tokens) 了解如何创建访问令牌。 ¥See [personal access tokens](/accounts/programmatic-access/#personal-access-tokens) to learn how to create access tokens. ### (可选)为你的 Apple 团队提供 ASC API 令牌 ¥(Optional) Provide an ASC API Token for your Apple Team 如果你的 iOS 凭据需要修复,我们将需要一个 ASC API 密钥来在 CI 中向 Apple 进行身份验证。一种常见的情况是你的配置文件需要重新签名。 ¥In the event your iOS credentials need to be repaired, we will need an ASC API key to authenticate ourselves to Apple in CI. A common case is when your provisioning profile needs to be re-signed. 你将需要创建一个 [API 密钥](https://expo.fyi/creating-asc-api-key)。接下来,你需要收集有关你的 [苹果团队](https://expo.fyi/apple-team) 的信息。 ¥You will need to create an [API Key](https://expo.fyi/creating-asc-api-key). Next, you will need to gather information about your [Apple Team](https://expo.fyi/apple-team). 使用你收集到的信息,通过环境变量将其传递到构建命令中。你将需要通过以下内容: ¥Using the information you've gathered, pass it into the build command through environment variables. You will need to pass the following: * `EXPO_ASC_API_KEY_PATH`:ASC API 密钥 .p8 文件的路径。例如,/path/to/key/AuthKey_SFB993FB5F.p8。 ¥`EXPO_ASC_API_KEY_PATH`: The path to your ASC API Key **.p8** file. For example, **/path/to/key/AuthKey_SFB993FB5F.p8**. * `EXPO_ASC_KEY_ID`:你的 ASC API 密钥的密钥 ID。例如,`SFB993FB5F`。 ¥`EXPO_ASC_KEY_ID`: The key ID of your ASC API Key. For example, `SFB993FB5F`. * `EXPO_ASC_ISSUER_ID`:你的 ASC API 密钥的颁发者 ID。例如,`f9675cff-f45d-4116-bd2c-2372142cee09`。 ¥`EXPO_ASC_ISSUER_ID`: The issuer ID of your ASC API Key. For example, `f9675cff-f45d-4116-bd2c-2372142cee09`. * `EXPO_APPLE_TEAM_ID`:你的 Apple 团队 ID。例如,`77KQ969CHE`。 ¥`EXPO_APPLE_TEAM_ID`: Your Apple Team ID. For example, `77KQ969CHE`. * `EXPO_APPLE_TEAM_TYPE`:你的 Apple 团队类型。有效类型为 `IN_HOUSE`、`COMPANY_OR_ORGANIZATION` 或 `INDIVIDUAL`。 ¥`EXPO_APPLE_TEAM_TYPE`: Your Apple Team Type. Valid types are `IN_HOUSE`, `COMPANY_OR_ORGANIZATION`, or `INDIVIDUAL`. ### 触发新构建 ¥Trigger new builds 现在我们已经通过 Expo CLI 进行了身份验证,我们可以创建构建步骤。 ¥Now that we're authenticated with Expo CLI, we can create the build step. 为了触发新的构建,我们将此脚本添加到我们的配置中: ¥To trigger new builds, we will add this script to our configuration: ```sh $ npx eas-cli build --platform all --non-interactive --no-wait ``` 这将触发 EAS 的新构建。将打印一个 URL,链接到 EAS 仪表板中的构建进度。 ¥This will trigger a new build on EAS. A URL will be printed, linking to the build's progress in the EAS dashboard. > **info** 一旦触发构建,`--no-wait` 标志就会退出该步骤。EAS 执行构建时,你无需支付 CI 执行时间的费用。但是,仅当触发 EAS 构建成功时,你的 CI 才会报告构建作业正在通过。 > > ¥The `--no-wait` flag exits the step once the build has been triggered. You are not billed for CI execution time while EAS performs the build. However, your CI will report that the build job is passing only if triggering EAS Build is successful. > > 如果你需要在构建完成后添加另一个 CI 步骤来运行,请删除此标志。 > > ¥If you need to add another CI step to run once the build is complete, remove this flag. Note: Travis CI --- Add the following code snippet in **.travis.yml** at the root of your project repository. !!!IG2!!! --- Note: GitLab CI --- Add the following code snippet in **.gitlab-ci.yml** at the root of your project repository. !!!IG3!!! --- Note: Bitbucket Pipelines --- Add the following code snippet in **bitbucket-pipelines.yml** at the root of your project repository. !!!IG4!!! --- Note: CircleCI --- Add the following code snippet in **circleci/config.yml** at the root of your project repository. !!!IG5!!! --- Note: GitHub Actions --- Add the following code snippet in **.github/workflows/eas-build.yml** at the root of your project repository. !!!IG6!!! --- ## 从 Expo GitHub 应用触发构建 了解如何使用 Expo GitHub 应用在 EAS 上触发应用的构建。 本指南介绍了如何使用 Expo GitHub 应用直接从 GitHub 存储库触发构建。 ¥This guide explains how to trigger builds directly from your GitHub repository using the Expo GitHub App. ## 先决条件 ¥Prerequisites ### 在 eas.json 中设置 `image` 字段 ¥Set the `image` field in your eas.json 对于要与 GitHub 一起使用的构建配置文件,请在 eas.json 中指定用于原生平台的 [`image`](/eas/json/#image)。 ¥For the build profiles you want to use with GitHub, specify an [`image`](/eas/json/#image) to use for the native platform in **eas.json**. 如果你的项目配置不依赖于特定的 [建立形象](/build-reference/infrastructure/),请使用 `latest` 映像。例如: ¥Use the `latest` image if your project's configuration does not rely on a specific [build image](/build-reference/infrastructure/). For example: ```json eas.json { "build": { "production": { "android": { "image": "latest" }, "ios": { "image": "latest" } } } } ``` ### 从本地计算机运行成功的构建 ¥Run a successful build from your local machine 要从 GitHub 存储库触发 EAS 构建,你需要为 EAS 构建配置项目,并从你的计算机为你希望在 GitHub 上支持的每个平台成功运行构建。 ¥To trigger EAS builds from a GitHub repo, you'll need to configure your project for EAS Build and successfully run a build from your computer for each platform that you'd like to support on GitHub. 如果你尚未成功运行 `eas build -p [all|ios|android]`,请参阅 [创建你的第一个版本](/build/setup/) 了解更多信息。一旦完成,请继续执行本指南中的步骤。 ¥If you haven't successfully run `eas build -p [all|ios|android]` yet, see [Create your first build](/build/setup/) for more information. Once you have, continue with the steps in this guide. 以下也必须为真: ¥The following must also be true: * 组织中的 Expo 用户必须拥有有权访问目标存储库的链接的 GitHub 用户。检查“账户设置”>“概览”>“用户设置”>“[**连接**](https://expo.dev/settings#connections)”,并验证你的 GitHub 用户账户是否已关联。 ¥An Expo user in the organization must have a linked GitHub user with access to the target repository. Check **Account settings** > **Overview** > **User settings** > [**Connections**](https://expo.dev/settings#connections) and verify that your GitHub user account is linked. * 你必须接受 [Expo GitHub 应用](https://github.com/settings/installations) 请求的权限。 ¥You must accept the permissions requested by the [Expo GitHub app](https://github.com/settings/installations). ## 为 GitHub 配置你的应用 ¥Configure your app for GitHub ### 将你的 GitHub 存储库链接到你的 Expo 项目 ¥Link your GitHub repository to your Expo project 访问你项目的 [GitHub 设置](https://expo.dev/accounts/\[account]/projects/\[projectName]/github)。 ¥Visit your project's [GitHub settings](https://expo.dev/accounts/\[account]/projects/\[projectName]/github). 在你的 GitHub 账户上安装 Expo GitHub 应用。 ¥Install the Expo GitHub App on your GitHub account. > 注意:你必须拥有 Expo 账户的 [所有者或管理员访问权限](/accounts/account-types/#manage-access) 才能安装应用。 > > ¥**Note:** You must have [Owner or Admin access](/accounts/account-types/#manage-access) of the Expo account to install the app. 然后,将 GitHub 存储库链接到你的 Expo 项目。 ¥Then, link the GitHub repository to your Expo project. > 注意:你只能将 [GitHub 组织存储库](https://docs.github.com/en/organizations) 链接到 Expo 组织。 > > ¥**Note:** You can only link [GitHub organization repositories](https://docs.github.com/en/organizations) to Expo organizations. 要从其他 GitHub 账户添加存储库,请单击账户选择器下拉菜单中的添加新账户选项。 ¥To add a repository from a different GitHub account, click the **Add new account** option in the account selector dropdown. ### 配置你的存储库设置 ¥Configure your repository settings 在运行构建之前,Expo GitHub 应用需要知道在哪里可以找到项目的源代码。如果你的 Expo 项目源代码位于存储库的根目录中,那么你不需要执行任何操作。如果你的 Expo 项目源代码位于子目录中,那么你需要在项目的 [GitHub 设置页面](https://expo.dev/accounts/\[account]/projects/\[projectName]/github) 上为存储库配置 "基目录" 设置。 ¥Before you run a build, the Expo GitHub App needs to know where to find the source code for your project. If your Expo project source code is in the root of your repository, then you don't need to do anything. If your Expo project source code is in a subdirectory, then you'll need to configure "Base directory" settings for your repository on your project's [GitHub settings page](https://expo.dev/accounts/\[account]/projects/\[projectName]/github). ## 从 GitHub 触发构建 ¥Trigger a build from GitHub 为 GitHub 配置应用后,你可以使用项目构建列表页面上的 UI 或 GitHub PR 上的标签从 GitHub 触发构建。 ¥Once you have configured your app for GitHub, you can trigger a build from GitHub by using the UI on your project's build list page or by labels on your GitHub PRs. ### 使用 Expo 网站进行构建 ¥Build using the Expo website 访问你项目的 [构建列表页面](https://expo.dev/accounts/\[account]/projects/\[projectName]/builds) 并单击 "从 GitHub 构建" 按钮。系统将提示你选择 Git 引用(分支/提交/标签)、要构建的平台以及要应用到的构建配置文件。 ¥Visit your project's [build list page](https://expo.dev/accounts/\[account]/projects/\[projectName]/builds) and click the "Build from GitHub" button. You'll be prompted to select a Git ref (branch/commit/tag), a platform to build for, and the build profile to apply to it. 你还可以为此特定构建指定基目录。这不会更改该项目的全局设置。 ¥You can also specify a base directory for this specific build. That will not change the global settings for this project. ### 使用 GitHub PR 标签进行构建 ¥Build using GitHub PR labels 你可以通过向 PR 添加标签来从 GitHub PR 触发构建。标签必须采用 `eas-build-[platform]:[profile]` 的形式,其中 `[platform]` 是 `android`、`ios` 或 `all`,`[profile]` 是 eas.json 文件中指定的构建配置文件的名称。如果不指定构建平台,则默认为 `all`。如果你不指定构建配置文件,它将默认为 `production`。 ¥You can trigger a build from a GitHub PR by adding a label to the PR. The label must be in the form of `eas-build-[platform]:[profile]` where `[platform]` is either `android`, `ios`, or `all` and `[profile]` is the name of a build profile specified in your **eas.json** file. If you don't specify a build platform, it will default to `all`. If you don't specify a build profile, it will default to `production`. 例如,如果你想触发 Android 的生产版本,请将标签 `eas-build-android` 添加到 PR。 ¥For example, if you want to trigger a production build for Android, add the label `eas-build-android` to the PR. PR 基础分支上的最新提交将触发构建。你可以在 PR 的检查中查看构建的状态。检查详细信息中将提供指向构建的链接。 ¥The build will be triggered for the latest commit on the PR's base branch. You can view the status of the build in the PR's checks. A link to the build will be available in the check's details. ### 当代码推送到存储库时自动构建 ¥Build automatically when code is pushed to repository 当你将代码推送到 GitHub 时,你可以通过自动构建 Expo 项目来进一步实现构建自动化。 ¥You can take your build automation further by automatically building your Expo project when you push code to GitHub. #### 设置构建触发器 ¥Set up build triggers 你可以设置构建触发器来配置 EAS 从 GitHub 构建应用的时间。我们允许你在推送到分支、拉取请求和 Git 标签时进行构建。 ¥You can set up build triggers to configure when EAS builds your app from GitHub. We allow you to build when pushing to a branch, pull request, and Git tag. 在仪表板中打开你的 Expo 项目。要创建构建触发器,请向下滚动到项目 GitHub 设置页面的构建触发器部分,然后单击新建构建触发器。 ¥Open your Expo project in the dashboard. To create a build trigger, scroll down to the **Build triggers** section of the project GitHub settings page and click **New Build Trigger**. 当你单击“新建构建触发器”时,你将看到一个表单来配置此构建应如何运行。 ¥When you click **New Build Trigger**, you will be presented with a form to configure how this build should run. 这些模式可以包括由星号 (`*`) 表示的通配符,它可以匹配模式内的任何字符和字符数。例如,`releases/*` 可以匹配 `releases/`、`release/1234`、`release/genesis` 等。如果将模式指定为唯一的星号 (`*`),则将匹配所有分支/标签。 ¥These patterns can include wildcards represented by asterisks (`*`), which can match any character and number of characters inside the pattern. For example, `releases/*` can match `releases/`, `release/1234`, `release/genesis`, and so on. If you specify the pattern as a sole asterisk (`*`), all branches/tags will be matched. 你还可以为特定平台配置触发器并构建配置文件。如果你选择多个平台,将为每个平台创建一个单独的触发器。 ¥You can also configure triggers for specific platforms and build profiles. If you select multiple platforms, a separate trigger will be made for each. 当你推送到分支或标签时,你可以通过查看提交的检查部分来找到构建。 ¥When you push to a branch or tag, you can find the builds by looking at a commit's **Checks** section. 对于拉取请求,你可以配置目标分支模式。这是你要构建的拉取请求的目标分支。相同的规则也适用于此处的通配符。 ¥For pull requests, you can configure a **target branch pattern**. This is the destination branch of the pull request you want to build. The same rules apply for wildcards here as well. 当你推送具有与此触发器匹配的源和目标分支的拉取请求时,你将在拉取请求的检查部分中找到这些构建: ¥When you push to a pull request with a source and target branch matching this trigger, you'll find these builds in the checks section of the pull request: > 注意:要从拉取请求触发构建,拉取请求的作者必须是 GitHub 存储库上的协作者。如果你想构建来自外部贡献者的拉取请求,[应用 PR 标签](#build-using-github-pr-labels)。 > > ¥**Note:** To trigger builds from a pull request, the pull request's author must be a collaborator > on the GitHub repository. If you want to build pull requests from external contributors, [apply a PR Label](#build-using-github-pr-labels). #### 管理构建触发器 ¥Manage build triggers 在 EAS 仪表板中项目的 GitHub 设置页面上,你可以点击构建触发器行右侧的选项按钮来禁用、编辑或删除触发器。 ¥On your project's GitHub settings page in the EAS dashboard, you can click the options button to the right of a build trigger row to disable, edit, or delete the trigger. 你还可以使用触发器中的参数手动运行 GitHub 构建。这不会计入你的自动构建触发记录。 ¥You can also run a GitHub build with the parameters from the trigger manually. This will not count towards your automatic build trigger record. #### 使用 EAS Submit 自动提交应用商店 ¥Automatic app stores submission with EAS Submit 构建完成后,你可以使用 EAS Submit 自动将你的应用提交到应用商店。此功能简化了流程,减少了发布应用所需的手动步骤。 ¥Once your build completes, you can automatically submit your app to the app stores using EAS Submit. This feature streamlines the process, reducing the manual steps required to publish your app. 要启用自动提交,你需要配置构建触发器以将提交作为构建过程的一部分。以下是设置方法: ¥To enable automatic submission, you need to configure your build triggers to include submission as part of the build process. Here's how you can set it up: * 在 EAS 仪表板上导航到项目的 GitHub 设置页面。 ¥Navigate to your project's GitHub settings page on the EAS dashboard. * 找到你要修改的构建触发器,然后单击选项按钮。 ¥Find the build trigger you want to modify, and click the options button. * 选择编辑触发器,然后在出现的对话框中,选中构建后提交到存储选项。 ¥Select **Edit trigger** and in the dialog that appears, check the option **Submit to store after build**. * 保存你的更改。 ¥Save your changes. 启用后,每次从此配置触发构建时,它将自动提交到你在 eas.json 中 `submit` 字段下配置的应用商店。 ¥Once enabled, every time a build is triggered from this configuration, it will automatically be submitted to the app stores you have configured in your **eas.json** under the `submit` field. > 注意:确保你的 eas.json 已正确配置以进行提交,包括指定正确的应用商店凭据和提交配置文件。有关详细信息,请参阅 [EAS 提交](/submit/eas-json/)。 > > ¥**Note:** Ensure that your **eas.json** is properly configured for submission, including specifying the correct app store's credentials and submission profile. For more information, see the [EAS Submit](/submit/eas-json/). ### 故障排除 ¥Troubleshooting * 当出现问题时,我们将使用一些错误信息对尝试构建的提交进行评论。我们还在构建触发器 UI 中显示最新结果,其中包括当你将鼠标悬停在错误标签上时的错误信息。 ¥When things go wrong, we will comment on the commit we attempted to build with some error information. We also show the latest result in the build triggers UI, which includes error information when you hover the **Error** tag. * 尝试构建时,请仔细检查 [先决条件](#prerequisites) 部分中的所有内容是否正确。 ¥Double check everything in the [Prerequisites](#prerequisites) section is true when trying to build. * 如果你使用的是 monorepo 设置,请确认你的基本目录是准确的。 ¥Confirm that your base directory is accurate if you're using a monorepo setup. * 你的构建配置文件正确吗?如果在 eas.json 中找不到匹配的配置文件,则不会调度构建。 ¥Is your build profile correct? If a matching profile can't be found in **eas.json**, the build will not dispatch. ## Expo 轨道 通过一键构建和更新启动以及模拟器管理加速你的开发工作流程。 macOS 和 Windows 的 [Expo 轨道](https://expo.dev/orbit) 可以更快地在模拟器和物理设备上从 EAS、本地文件安装和启动构建或更新,或运行 Snack 项目。 ¥[Expo Orbit](https://expo.dev/orbit) for macOS and Windows enables faster to install and launch builds or updates from EAS, local files, or run Snack projects, on simulators and physical devices. ## 为什么选择 Orbit ¥Why Orbit 在 Orbit 之前,安装来自 EAS 的版本或更新(在 Android 和 iOS 物理设备或模拟器/模拟器上)或在模拟器上运行 Snack 项目是手动的。你必须运行 `eas build:run` 命令并为你选择的设备选择一个版本,或者下载存档,然后将其拖放到模拟器中(对于 iOS)。此外,对于 Snack 项目,其他步骤包括在虚拟设备上安装 Expo Go、登录,然后从列表中选择 Snack。Orbit 使所有这些步骤尽可能无缝。 ¥Before Orbit, installing builds or updates from EAS (on Android and iOS physical devices or emulator/simulator) or running Snack projects on simulators was manual. You had to run `eas build:run` command and select a build for your chosen device or download the archive and then drag and drop it to the simulator (in the case of iOS). Also, for Snack projects, additional steps included installing Expo Go on the virtual device, logging in, and then selecting the Snack from the list. Orbit makes all of these steps as seamless as possible. ## 高亮 ¥Highlights * 列出并启动模拟器,包括运行不带音频的 Android 模拟器。 ¥List and launch simulators, including running Android emulators without audio. * 一键安装并从 EAS 在模拟器和真实设备上启动构建。 ¥Install and launch builds from EAS on simulators and real devices in one click. * Android 模拟器或 iOS 模拟器上的 [安装并打开来自 EAS 的更新](/review/with-orbit/)。 ¥[Install and open updates from EAS](/review/with-orbit/) on Android Emulators or iOS Simulators. * 一键在模拟器中启动 Snack 项目。 ¥Launch Snack projects in your simulators in one click. * 使用 Finder 从本地文件安装和启动应用,或将文件拖放到菜单栏应用中。Orbit 支持任何 Android .apk、iOS Simulator 兼容的 .app 或临时签名的应用。 ¥Install and launch apps from local files using Finder or drag and drop a file into the menu bar app. Orbit supports any Android **.apk**, iOS Simulator compatible **.app**, or ad hoc signed apps. * 查看 [EAS 仪表板](https://expo.dev) 中的固定项目并快速启动最新版本。 ¥See pinned projects from your [EAS dashboard](https://expo.dev) and quickly launch your latest builds. ## 安装 ¥Installation > **info** Orbit 依赖于 macOS 和 Windows 上的 Android SDK,以及 macOS 上仅用于设备管理的 `xcrun`,这需要同时设置 [安卓工作室](/workflow/android-studio-emulator/) 和 [Xcode](/workflow/ios-simulator/)。 > > ¥Orbit relies on the Android SDK on both macOS and Windows and `xcrun` for device management only on macOS, which requires setting up both [Android Studio](/workflow/android-studio-emulator/) and [Xcode](/workflow/ios-simulator/). For macOS: 你可以使用 Homebrew for macOS 下载 Orbit,也可以直接从 [GitHub 发布](https://github.com/expo/orbit/releases) 下载。 ¥You can download Orbit with Homebrew for macOS, or directly from the [GitHub releases](https://github.com/expo/orbit/releases). ```sh $ brew install expo-orbit ``` 如果你希望 Orbit 在你登录时自动启动,请单击菜单栏中的 Orbit 图标,然后单击“设置”并选择“登录时启动”选项。 ¥If you want Orbit to start when you log in automatically, click on the Orbit icon in the menu bar, then **Settings** and select the **Launch on Login** option. For Windows: > 注意:Windows 版 Orbit 处于预览阶段,仅与 x64 和 x86 机器兼容。将来会添加对其他架构的兼容性。 > > ¥**Note**: Orbit for Windows is in preview and is only compatible with x64 and x86 machines. Compatibility for other architectures will be added in the future. 你可以直接从 [GitHub 发布](https://github.com/expo/orbit/releases) 下载适用于 Windows 的 Orbit。 ¥You can download Orbit for Windows directly from the [GitHub releases](https://github.com/expo/orbit/releases). # 应用签名 ## 应用凭据 了解 Android 和 iOS 需要什么应用凭据。 Expo 会自动执行 Android 和 iOS 应用的签名过程,但在这两种情况下,你都可以选择提供覆盖。[EAS 构建](/build/introduction) 可以生成签名或未签名的应用,但要通过商店分发你的应用,它必须是签名的应用。 ¥Expo automates the process of signing your app for Android and iOS, but in both cases, you can choose to provide your overrides. [EAS Build](/build/introduction) can generate signed or unsigned applications, but to distribute your application through the stores, it **must** be a signed application. 在此页面上,你将了解每个平台所需的凭据。如果你对我们如何存储你的凭据感到好奇,请查看我们的 [安全文档](/app-signing/security)。 ¥On this page, you'll learn about the credentials that each platform requires. If you're curious about how we store your credentials on our end, take a look at our [security documentation](/app-signing/security). ## 安卓 ¥Android Google 要求所有 Android 应用在安装到设备上或更新之前都必须使用证书进行数字签名。通常,私钥及其公共证书存储在密钥库中。过去,上传到商店的 APK 需要使用应用签名证书(将附加到 Play 商店中的应用的证书)进行签名,如果密钥库丢失,则无法恢复或重置它 。现在,你可以选择加入 Google Play 的应用签名,只需上传使用上传证书签名的 APK,Google Play 就会自动将其替换为应用签名证书。旧方法(应用签名证书)和新方法(上传证书)本质上是相同的机制,但使用新方法时,如果你的上传密钥库丢失或被泄露,你可以联系 Google Play 支持团队重置密钥。 ¥Google requires all Android apps to be digitally signed with a certificate before they are installed on a device or updated. Usually, a private key and its public certificate are stored in a keystore. In the past, APKs uploaded to the store were required to be signed with the **app signing certificate** (a certificate that will be attached to the app in the Play Store), and if the keystore was lost there was no way to recover or reset it. Now, you can opt-in to App Signing by Google Play and simply upload an APK signed with an **upload certificate**, and Google Play will automatically replace it with the **app signing certificate**. Both the old method (app signing certificate) and new method (upload certificate) are essentially the same mechanisms, but using the new method, if your upload keystore is lost or compromised, you can contact the Google Play support team to reset the key. 从 Expo 构建过程的角度来看,使用上传证书或应用签名密钥对应用进行签名没有区别。无论哪种方式,`eas build` 都会生成一个使用当前与你的应用关联的密钥库签名的 .apk 或 .aab。如果你想手动生成上传密钥库,可以按照创建原始密钥库的方式进行操作。 ¥From the Expo build process's perspective, there is no difference between whether an app is signed with an **upload certificate** or an **app signing key**. Either way, `eas build` will generate an **.apk** or **.aab** signed with the keystore currently associated with your application. If you want to generate an upload keystore manually, you can do that the same way you created your original keystore. 请参阅 [Android 的文档](https://developer.android.com/studio/publish/app-signing) 以查找有关此过程的更多信息。 ¥See [Android's documentation](https://developer.android.com/studio/publish/app-signing) to find more information about this process. ### Google Play 的应用签名 ¥App signing by Google Play 当你 [将你的第一个版本上传到 Google Play](https://expo.fyi/first-android-submission) 时,你将看到有关 "Google Play 的应用签名" 和 "Google 正在保护你的应用签名密钥" 的通知。这是默认行为,除了按 "继续" 之外,你不需要执行任何操作。 ¥When you [upload your first release to Google Play](https://expo.fyi/first-android-submission) you will see a notice about "App signing by Google Play" and "Google is protecting your app signing key". This is the default behavior and requires no action on your behalf except to press "Continue". 如果你当前管理你的应用签名密钥并希望 Google 为你管理,请参阅 [使用 Google Play 的应用签名](https://support.google.com/googleplay/android-developer/answer/9842756)。 ¥If you currently manage your app signing key and want Google to manage it for you, see [Use app signing by Google Play](https://support.google.com/googleplay/android-developer/answer/9842756). Note: Lost your keystore? Learn how to reset your upload key on Google Play --- To sync your Expo keystore with Google, follow these steps: #### Download credentials In a terminal window: 1. Run `eas credentials` command. 2. Select `Android` for the platform and the profile whose credentials you wish to download. 3. Select the option `credentials.json: Upload/Download credentials between EAS servers and your local json`. 4. Select `Download credentials from EAS to credentials.json`. Your application's keystore should be kept private. **Under no circumstances should you check it into your repository.** Debug keystores are the only exception because we don't use them for uploading apps to the Google Play Store. #### Export keystore to `pem` format Once you have downloaded your credentials and the keystore, export it to the `pem` format so that you can submit it to Google: 1. Find the key alias in your **credentials.json** file under the `keyAlias` key. 2. Use `keytool` to export the certificate: ```sh $keytool -export -rfc -alias alias_from_step_1 -file certificate_for_google.pem -keystore ./path/to/keystore.jks ``` #### Contact Google support Contact Google Support and request them to change your key using [this support form](https://support.google.com/googleplay/android-developer/contact/key). While filling out the form, attach the `pem` file exported from the keystore. Once Google updates this on your account, builds created through `eas build` will be correctly signed as expected by the Google Play Store. Note that Google will set the validity start date of the new upload certificate to 72 hours in the future so you'll have to wait before your first submission after performing this process. --- ## iOS The 3 primary iOS credentials, all of which are associated with your Apple Developer account, are: - Distribution Certificate - Provisioning Profiles - Push Notification Keys Whether you let EAS handle all your credentials, or you handle them yourself, it can be valuable to understand what each of these credentials means, when and where they're used, and what happens when they expire or are revoked. You can inspect and manage all your credentials with EAS CLI by running `eas credentials`. ### Distribution certificate The distribution certificate is all about you, the developer, and not about any particular app. You may only have one distribution certificate associated with your Apple Developer account. This certificate will be used for all of your apps. If this certificate expires, your apps in production will not be affected. However, you will need to generate a new certificate if you want to upload new apps to the App Store or update any of your existing apps. Deleting a distribution certificate has no effect on any apps already on the App Store. You can clear the distribution certificate Expo currently has stored for your app the next time you build by running `eas credentials` and following the prompts. ### Push Notification keys Apple Push Notification Keys (often abbreviated as APN keys) allow the associated apps to send and receive push notifications. You can have a maximum of 2 APN keys associated with your Apple Developer account, and a single key can be used with any number of apps. If you revoke an APN key, all apps that rely on that key will no longer be able to send or receive push notifications until you upload a new key to replace it. Uploading a new APN key **will not** change your users' [Expo Push Tokens](/versions/latest/sdk/notifications#notificationsgetexpopushtokenasync). Push notification keys do not expire. You can clear the APN key Expo currently has stored for your app by running `eas credentials` and following the prompts. > APN keys created by Expo can be downloaded on the [Expo website](https://expo.dev/accounts/[account]/settings/credentials). ### Provisioning profiles Each profile is app-specific, meaning you will have a provisioning profile for every app you submit to the App Store. These provisioning profiles are associated with your distribution certificate, so if that is revoked or expired, you'll need to regenerate the app's provisioning profile, as well. Similar to the distribution certificate, revoking your app's provisioning profile will not have any effect on apps already on the App Store. Provisioning profiles expire after 12 months, but this won't affect apps in production. You will just need to create a new one the next time you build your app by running `eas build -p ios`, or manually with `eas credentials`. ### Summary | Credential | Limit Per Account | App-specific? | Can be revoked with no production side effects? | Used at | | ------------------------ | ----------------- | ------------- | ----------------------------------------------- | ---------- | | Distribution Certificate | 2 | | | Build time | | Push Notification Key | 2 | | | Run time | | Provisioning Profile | Unlimited | | | Build time | ### Clearing credentials When you use the `eas credentials` command to delete your credentials, this only removes those credentials from Expo's servers. **It does not delete the credentials from Apple's perspective**. This means that to fully delete your credentials (for example, if you want a new push notification key, however, you already have two), you'll need to do so from the [Apple Developer Console](https://developer.apple.com/account/resources/certificates/list). ### Re-signing new credentials You can use `eas build:resign` to codesign an existing **.ipa** for iOS to a new ad hoc provisioning profile. This helps reduce time when distributing internally — for example, if you want to add a new test device to an existing build, you can use this command to update the provisioning profile to include the device without rebuilding the entire app from scratch. Running the command will ask you to select a build that you want to re-sign. For example, running the command in an example project shows an available build: 选择构建后,按照步骤登录你的 Apple 开发者账户。当出现提示“显示设备并再次询问我”时,你可以选择新的配置文件。 ¥After selecting the build, follow the steps to log in to your Apple Developer account. When prompted **Show devices and ask me again**, you can select a new provisioning profile. 选择一个新设备,该命令将再次运行 EAS Build。请注意,这次触发的构建重用了所选构建中的应用工件,并使用新的配置文件对其进行了共同设计。此过程完成后,你可以使用此新的构建链接在添加到配置文件的 iOS 设备上安装 .ipa。 ¥Select a new device, and the command will run the EAS Build again. Note that the build triggered this time reuses the application artifact from the selected build and codesigns it with the new provisioning profile. Once this process is complete, you can use this new build link to install the **.ipa** on the iOS device added to the provisioning profile. ## 使用自动管理的凭据 了解如何使用 EAS 自动管理你的应用凭据。 为了让你的应用在应用商店中分发,需要使用密钥库或分发证书等凭据进行数字签名。这可以证明应用的来源并确保它不会被篡改。发送推送通知需要其他凭据,例如 FCM API 密钥和 Apple 推送密钥,但它们不参与应用签名。 ¥For your app to be distributed in an app store, it needs to be digitally signed with credentials such as a keystore or a distribution certificate. This certifies the source of the app and ensures that it can't be tampered with. Other credentials, such as your FCM API Key and Apple Push Key are needed to send push notifications, but they are not involved in app signing. 这就是你使用 EAS Build 构建应用所需要了解的全部内容,但如果你想了解更多信息,可以参考 [应用签名](/app-signing/app-credentials) 指南。 ¥That's all that you need to know about any of this to build an app with EAS Build, but if you would like to learn more you can refer to the [App Signing](/app-signing/app-credentials) guide. 请继续阅读,了解 EAS 如何自动为你和你的团队管理凭证。 ¥Read on to learn how EAS can automatically manage credentials for you and your team. ## 生成应用签名凭据 ¥Generating app signing credentials 当你运行 `eas build` 时,系统将提示你生成凭据(如果你尚未这样做)。按照简单的说明生成你的凭据。如果需要,它们将存储在 EAS 服务器上。在你的应用的后续版本中,除非你另有指定,否则将重新使用这些凭据。 ¥When you run `eas build`, you will be prompted to generate credentials if you have not done so already. Follow the simple instructions to generate your credentials. Where needed, they will be stored on EAS servers. On subsequent builds of your app, these credentials will be re-used unless you specify otherwise. 生成 iOS 凭据(分发证书、配置文件和推送密钥)需要你使用 [苹果开发者计划](https://developer.apple.com/programs) 成员身份登录。 ¥Generating your iOS credentials (distribution certificate, provisioning profile, and push key) requires you to sign in with an [Apple Developer Program](https://developer.apple.com/programs) membership. > 如果你对 EAS 管理你的凭据或通过 EAS CLI 登录 Apple Developer 账户有任何安全问题,请参阅 [安全](/app-signing/security) 指南。如果这不能满足你的担忧,你可以联系 [secure@expo.dev](mailto:secure@expo.dev) 了解更多信息,或使用 [本地凭证](/app-signing/local-credentials/)。 > > ¥If you have any security concerns about EAS managing your credentials or about logging in to your Apple Developer account through EAS CLI, see [Security](/app-signing/security) guide. If that does not satisfy your concerns, you can reach out to [secure@expo.dev](mailto:secure@expo.dev) for more information, or use [local credentials](/app-signing/local-credentials/) instead. ### 推送通知凭证 ¥Push notification credentials #### 安卓 ¥Android EAS Build 的 Android 推送通知凭据设置需要使用 FCM 配置你的应用。运行 `eas credentials`,选择 `Android`,然后选择 `Push Notifications: Manage your FCM Api Key`,然后选择适当的选项来设置密钥。 ¥The Android push notification credentials setup for EAS Build requires configuring your app with FCM. Run `eas credentials`, select `Android`, then `Push Notifications: Manage your FCM Api Key`, and then choose the appropriate option to set up the key. #### iOS 系统 ¥iOS 如果你尚未设置推送通知密钥,EAS CLI 将要求你在下一次 `eas build` 运行期间进行设置。 ¥If you haven't set up your Push Notifications key yet, EAS CLI will ask you to set it up during the next `eas build` run. 你还可以使用 `eas credentials` 命令设置推送通知键。运行它,选择 `iOS`,然后选择 `Push Notifications: Manage your Apple Push Notifications Key`,然后选择适当的选项来设置密钥。 ¥You can also set up the Push Notifications key with the `eas credentials` command. Run it, select `iOS`, then `Push Notifications: Manage your Apple Push Notifications Key`, and then choose the appropriate option to set up the key. ## 与你的团队共享凭据 ¥Sharing credentials with your team 如果你与其他开发者合作开发你的项目,那么授予他们自行执行构建的权限通常很有用。[确保你的项目配置为协作](/accounts/account-types/#organizations) 和你通过 [EAS 仪表板](https://expo.dev/) 添加的任何队友将能够无缝运行 `eas build`,前提是他们有足够的权限。 ¥If you collaborate on your project with other developers, it is often useful to give them access to perform builds on their own. [Ensure that your project is configured for collaboration](/accounts/account-types/#organizations) and any teammates that you have added through your [EAS dashboard](https://expo.dev/) will be able to run `eas build` seamlessly, provided that they have sufficient permissions. 生成 iOS 凭据后,不再需要访问 Apple 开发者团队即可开始构建。这意味着你的协作者只能使用他们的 Expo 账户启动新的 iOS 版本。 ¥After you have generated your iOS credentials, it's no longer necessary to have access to the Apple Developer team to start a build. This means that your collaborators can start new iOS builds with only their Expo accounts. ## 检查凭证配置 ¥Inspecting credentials configuration 你可以通过运行 `eas credentials` 查看当前配置的应用签名凭据。如果你需要进行任何更改,此命令还允许你删除和修改凭据。通常这不是必需的,但如果你想要 [将你的凭据同步到本地计算机以在本地运行构建](/app-signing/syncing-credentials/) 或 [迁移现有凭证以进行自动管理](/app-signing/existing-credentials/),你可能需要使用它。 ¥You can view your currently configured app signing credentials by running `eas credentials`. This command also lets you remove and modify credentials, should you need to make any changes. Typically this is not necessary, but you may want to use it if you want to [sync your credentials to your local machine to run a build locally](/app-signing/syncing-credentials/) or [migrate existing credentials to be automatically managed](/app-signing/existing-credentials/). ## 使用本地凭据 了解如何在使用 EAS 时配置和使用本地凭据。 通常,到 [让 EAS 为你处理](/app-signing/managed-credentials/) 为止,你就可以不再成为代码签名专家。但是,在某些情况下,某些用户可能希望自己管理其项目密钥库、证书和配置文件。 ¥You can usually get away with not being a code signing expert by [letting EAS handle it for you](/app-signing/managed-credentials/). However, there are cases where some users might want to manage their project keystore, certificates and profiles on their own. 如果你想管理自己的应用签名凭据,可以使用 credentials.json 为 EAS Build 提供本地文件系统上凭据的相对路径及其关联的密码,以使用它们对你的构建进行签名。 ¥If you would like to manage your own app signing credentials, you can use **credentials.json** to give EAS Build relative paths to the credentials on your local file system and their associated passwords to use them to sign your builds. ## credentials.json 如果你选择加入本地凭据配置,则需要在项目的根目录中创建一个 credentials.json 文件,它应该看起来像这样: ¥If you opt-in to local credentials configuration, you'll need to create a **credentials.json** file at the root of your project, and it should look something like this: ```json credentials.json { "android": { "keystore": { "keystorePath": "android/keystores/release.keystore", "keystorePassword": "paofohlooZ9e", "keyAlias": "keyalias", "keyPassword": "aew1Geuthoev" } }, "ios": { "provisioningProfilePath": "ios/certs/profile.mobileprovision", "distributionCertificate": { "path": "ios/certs/dist-cert.p12", "password": "iex3shi9Lohl" } } } ``` > 请记住将 credentials.json 和所有凭据添加到.gitignore,这样你就不会意外地将它们提交到存储库并可能泄露你的秘密。 > > ¥Remember to add **credentials.json** and all of your credentials to **.gitignore** so you don't accidentally commit them to the repository and potentially leak your secrets. ### 安卓凭证 ¥Android credentials 如果你想构建 Android 应用二进制文件,你需要有一个密钥库。如果你还没有发布密钥库,你可以使用以下命令自行生成它(将 `KEYSTORE_PASSWORD`、`KEY_PASSWORD`、`KEY_ALIAS` 和 `com.expo.your.android.package` 替换为你选择的值): ¥If you want to build an Android app binary you'll need to have a keystore. If you don't have a release keystore yet, you can generate it on your own using the following command (replace `KEYSTORE_PASSWORD`, `KEY_PASSWORD`, `KEY_ALIAS` and `com.expo.your.android.package` with the values of your choice): 一旦你的计算机上有了密钥库文件,你应该将其移动到适当的目录。我们建议你将密钥库保存在 android/keystores 目录中。请记住 git-ignore 所有发布密钥库!如果你已运行上述 keytool 命令并将密钥库放在 android/keystores/release.keystore,则可以通过在 .gitignore 中添加以下行来忽略该文件: ¥Once you have the keystore file on your computer, you should move it to the appropriate directory. We recommend you keep your keystores in the **android/keystores** directory. **Remember to git-ignore all your release keystores!** If you have run the above keytool command and placed the keystore at **android/keystores/release.keystore**, you can ignore that file by adding the following line to **.gitignore**: ```sh .gitignore android/keystores/release.keystore ``` 创建凭据.json 并使用凭据配置它: ¥Create **credentials.json** and configure it with the credentials: ```json credentials.json { "android": { "keystore": { "keystorePath": "android/keystores/release.keystore", "keystorePassword": "KEYSTORE_PASSWORD", "keyAlias": "KEY_ALIAS", "keyPassword": "KEY_PASSWORD" } }, "ios": { } } ``` * `keystorePath` 指向密钥库在你计算机上的位置。支持相对路径(相对于项目根目录)和绝对路径。 ¥`keystorePath` points to where the keystore is located on your computer. Both relative (to the project root) and absolute paths are supported. * `keystorePassword` 是密钥库密码。如果你已执行前面的步骤,则其值为 `KEYSTORE_PASSWORD`。 ¥`keystorePassword` is the keystore password. If you have followed the previous steps it's the value of `KEYSTORE_PASSWORD`. * `keyAlias` 是关键别名。如果你已执行前面的步骤,则其值为 `KEY_ALIAS`。 ¥`keyAlias` is the key alias. If you have followed the previous steps it's the value of `KEY_ALIAS`. * `keyPassword` 是密钥密码。如果你已执行前面的步骤,则其值为 `KEY_PASSWORD`。 ¥`keyPassword` is the key password. If you have followed the previous steps it's the value of `KEY_PASSWORD`. ### iOS 凭证 ¥iOS credentials 构建 iOS 应用二进制文件还有一些先决条件。你需要一个付费的 Apple 开发者账户,然后需要为你的应用生成分发证书和配置文件,这可以通过 [苹果开发者门户](https://developer.apple.com/account/resources/certificates/list)。 ¥There are a few more prerequisites for building the iOS app binary. You need a paid Apple Developer Account, and then you'll need to generate the Distribution Certificate and Provisioning Profile for your application, which can be done via the [Apple Developer Portal](https://developer.apple.com/account/resources/certificates/list). 一旦你的计算机上有了分发证书和配置文件,你应该将它们移动到适当的目录。我们建议你将它们保存在 `ios/certs` 目录中。在本文档的其余部分中,我们假设它们分别命名为 dist.p12 和 profile.mobileprovision。 ¥Once you have the Distribution Certificate and Provisioning Profile on your computer, you should move them to the appropriate directory. We recommend you keep them in the `ios/certs` directory. In the rest of this document we assume that they are named **dist.p12** and **profile.mobileprovision** respectively. > 请记住将包含你的凭据的目录添加到 .gitignore,这样你就不会意外地将它们提交到存储库并可能泄露你的秘密。 > > ¥Remember to add directory with your credentials to **.gitignore**, so you don't accidentally commit them to the repository and potentially leak your secrets. 如果你已将凭据放置在建议的目录中,则可以通过将以下行添加到 .gitignore 来忽略这些文件: ¥If you have placed the credentials in the suggested directory, you can ignore those files by adding the following line to **.gitignore**: ```sh .gitignore ios/certs/* ``` 创建(或编辑)credentials.json 并使用凭据配置它: ¥Create (or edit) **credentials.json** and configure it with the credentials: ```json credentials.json { "android": { }, "ios": { "provisioningProfilePath": "ios/certs/profile.mobileprovision", "distributionCertificate": { "path": "ios/certs/dist.p12", "password": "DISTRIBUTION_CERTIFICATE_PASSWORD" } } } ``` * `provisioningProfilePath` 指向配置文件在你计算机上的位置。支持相对路径(相对于项目根目录)和绝对路径。 ¥`provisioningProfilePath` points to where the Provisioning Profile is located on your computer. Both relative (to the project root) and absolute paths are supported. * `distributionCertificate.path` 指向分发证书在你计算机上的位置。支持相对路径(相对于项目根目录)和绝对路径。 ¥`distributionCertificate.path` points to where the Distribution Certificate is located on your computer. Both relative (to the project root) and absolute paths are supported. * `distributionCertificate.password` 是位于 `distributionCertificate.path` 的分发证书的密码。 ¥`distributionCertificate.password` is the password for the Distribution Certificate located at `distributionCertificate.path`. #### 多目标项目 ¥Multi-target project 如果你的 iOS 应用使用 [应用扩展](https://developer.apple.com/app-extensions/)(例如共享扩展、小组件扩展等),你需要为 Xcode 项目的每个目标提供凭据。这是必要的,因为每个扩展都由单独的包标识符来标识。 ¥If your iOS app is using [App Extensions](https://developer.apple.com/app-extensions/) like Share Extension, Widget Extension, and so on, you need to provide credentials for every target of the Xcode project. This is necessary because each extension is identified by an individual bundle identifier. 假设你的项目由一个主应用目标(名为 `multitarget`)和一个共享扩展目标(名为 `shareextension`)组成。 ¥Let's say that your project consists of a main application target (named `multitarget`) and a Share Extension target (named `shareextension`). 在这种情况下,你的 credentials.json 应如下所示: ¥In this case, your **credentials.json** should look like below: ```json credentials.json|collapseHeight=440 { "ios": { "multitarget": { "provisioningProfilePath": "ios/certs/multitarget-profile.mobileprovision", "distributionCertificate": { "path": "ios/certs/dist.p12", "password": "DISTRIBUTION_CERTIFICATE_PASSWORD" } }, "shareextension": { "provisioningProfilePath": "ios/certs/shareextension-profile.mobileprovision", "distributionCertificate": { "path": "ios/certs/another-dist.p12", "password": "ANOTHER_DISTRIBUTION_CERTIFICATE_PASSWORD" } } } } ``` ## 设置凭证源 ¥Setting a credentials source 你可以通过在构建配置文件上指定 `"credentialsSource": "local"` 或 `"credentialsSource:" "remote"` 来告诉 EAS Build 应如何解析凭据。 ¥You can tell EAS Build how it should resolve credentials by specifying `"credentialsSource": "local"` or `"credentialsSource:" "remote"` on a build profile. * 如果提供了 `"local"`,则将使用 credentials.json。 ¥If `"local"` is provided, then **credentials.json** will be used. * 如果提供了 `"remote"`,则将从 EAS 服务器解析凭据。 ¥If `"remote"` is provided, then credentials will be resolved from EAS servers. 例如,你可能希望在部署到 Amazon Appstore 时使用本地凭据,在部署到 Google Play 商店时使用远程凭据: ¥For example, maybe you want to use local credentials when deploying to the Amazon Appstore and remote credentials when deploying to the Google Play Store: ```json eas.json { "build": { "amazon-production": { "android": { "credentialsSource": "local" } }, "google-production": { "android": { "credentialsSource": "remote" } } } } ``` 如果不设置任何选项,`"credentialsSource"` 将默认为 `"remote"`。 ¥If you do not set any option, `"credentialsSource"` will default to `"remote"`. ## 在 CI 触发的构建上使用本地凭据 ¥Using local credentials on builds triggered from CI 在开始设置 CI 作业之前,请确保已配置 [如上所述](#credentialsjson) 的 credential.json 和 eas.json 文件。 ¥Before you start setting up your CI job, make sure you have your **credentials.json** and **eas.json** files configured [as described above](#credentialsjson). 开发者倾向于使用环境变量为 CI 作业提供密钥。这种方法的挑战之一是,credentials.json 文件包含一个 JSON 对象,并且可能很难正确转义它,因此你可以将其分配给环境变量。此问题的一种可能解决方案是将文件转换为 Base64 编码的字符串,将环境变量设置为该值,然后对其进行解码并在 CI 上恢复文件。 ¥Developers tend to provide CI jobs with secrets by using environment variables. One of the challenges with this approach is that the **credentials.json** file contains a JSON object and it might be difficult to escape it properly, so you could assign it to an environment variable. One possible solution to this problem is to convert the file to a base64-encoded string, set an environment variable to that value, and later decode it and restore the file on the CI. 考虑以下步骤: ¥Consider the following steps: * 在控制台中运行以下命令,根据你的凭据文件生成 Base64 字符串: ¥Run the following command in the console to generate Base64 string based on your credentials file: ```sh $ base64 credentials.json ``` * 在 CI 上,使用上述命令的输出设置 `CREDENTIALS_JSON_BASE64` 环境变量。 ¥On your CI, set the `CREDENTIALS_JSON_BASE64` environment variable with the output of the above command. * 在 CI 作业中,使用简单的 shell 命令恢复文件: ¥In the CI job, restore the file using a simple shell command: ```sh $ echo $CREDENTIALS_JSON_BASE64 | base64 -d > credentials.json ``` 同样,你可以对密钥库、配置文件和分发证书进行编码,以便稍后可以在 CI 上恢复它们。要使用 CI 中的本地凭据成功触发构建,你必须确保所有凭据都存在于 CI 实例的文件系统中(位于与 credentials.json 中定义的位置相同的位置)。 ¥Similarly, you can encode your keystore, provisioning profile and distribution certificate so you can restore them later on the CI. To successfully trigger your build using local credentials from CI, you'll have to make sure all the credentials exist in the CI instance's file system (at the same locations as defined in **credentials.json**). 恢复步骤到位后,你可以使用 [从 CI 触发构建](/build/building-on-ci) 指南中描述的相同流程来触发构建。 ¥Once the restoring steps are in place, you can use the same process described in the [Triggering builds from CI](/build/building-on-ci) guide to trigger the builds. ## 使用现有凭据 了解向 EAS Build 提供应用签名凭据的不同选项。 EAS Build 为你提供了两种如何为构建作业提供应用签名凭据的选项: ¥EAS Build gives you two options for how you can supply your build jobs with app signing credentials: 1. [自动管理凭证](/app-signing/managed-credentials/):EAS 可以托管你的应用签名凭据,并负责与拥有必要权限的团队成员共享这些凭据。 ¥[Automatically managed credentials](/app-signing/managed-credentials/): EAS can host your app signing credentials and take care of sharing them with teammates that have the necessary permissions. 2. [本地凭证](/app-signing/local-credentials/):你在项目中创建一个凭据.json 文件,该文件指向你的密钥库 (Android) 和/或配置文件和分发证书 (iOS) 以及关联的密码。这是在任何给定的构建作业运行时从本地计算机上传的,并在构建作业完成后处理。 ¥[Local credentials](/app-signing/local-credentials/): You create a **credentials.json** file in your project that points to your keystore (Android) and/or provisioning profile and distribution certificate (iOS), along with associated passwords. This is uploaded from your local machine at the time any given build job is run, and disposed of once that build job has completed. 无论你选择哪个选项,使用现有凭据集的第一步都是在 credentials.json 中将它们设置为本地凭据。有关如何执行此操作的更多信息,请参阅 [本地凭据指南的凭据.json 部分](/app-signing/local-credentials/#credentialsjson)。 ¥Regardless of which option you choose, your first step for using your existing set of credentials is to set them up as local credentials in **credentials.json**. Refer to the [credentials.json section of the local credentials guide](/app-signing/local-credentials/#credentialsjson) for more information on how to do this. 配置 credential.json 文件后,你可以运行 `eas credentials`,选择一个平台,然后选择 `"Update credentials on Expo servers with values from credentials.json"` 将其上传以由 EAS 托管和管理(如果你愿意)。[了解有关同步凭据的更多信息](/app-signing/syncing-credentials/)。 ¥Once your **credentials.json** file is configured, you can run `eas credentials`, choose a platform, and then select `"Update credentials on Expo servers with values from credentials.json"` to upload them to be hosted and managed by EAS, if you would like. [Read more about syncing credentials](/app-signing/syncing-credentials/). ## 在远程和本地源之间同步凭据 了解如何在远程和本地源之间同步凭据。 如果你使用自动管理的凭据,你的凭据将远程托管在 EAS 服务器上,但你可能会遇到需要拉取凭据以在本地运行构建的情况。如果你使用本地凭证,你可能会发现自己想要将在 credentials.json 中指定的凭证上传到 EAS 来为你进行管理。这两种情况都可以使用 `eas credentials` 命令实现。 ¥If you use automatically managed credentials, your credentials will be hosted remotely on EAS servers, but you may encounter a situation where you want to pull your credentials down to run a build locally. And if you use local credentials, you may find yourself in a position where you want to upload credentials specified in **credentials.json** up to EAS to be managed for you. Both of these are possible using the `eas credentials` command. ## 下载凭证 ¥Downloading credentials 要下载自动管理的凭据,请在项目的根目录中运行 `eas credentials`,选择一个平台,选择 `"Credentials.json: Upload/Download credentials between EAS servers and your local json"`,然后选择 `"Download credentials from EAS to credentials.json"`。如果需要,再次运行该命令以下载另一个平台的凭据。 ¥To download your automatically managed credentials, run `eas credentials` in the root of your project, pick a platform, choose `"Credentials.json: Upload/Download credentials between EAS servers and your local json"`, and then `"Download credentials from EAS to credentials.json"`. Run the command again to download the credentials for another platform, if needed. Android 凭证将立即可供使用,因为你的项目将从 credentials.json 中读取凭证。 ¥Android credentials will be ready to use immediately because your project will read the credentials from **credentials.json**. iOS 凭据需要两个步骤才能在本地设置。你首先需要将分发证书安装到你的密钥串中。接下来,打开你的项目 Xcode 并导航到 "签名和能力" 部分,然后导入你的配置文件并选择它。 ¥iOS credentials require two steps to set up locally. You will first need to install the distribution certificate into your keychain. Next, open your project Xcode and navigate to the "Signing & Capabilities" section, then import your provisioning profile and select it. ## 上传凭证 ¥Uploading credentials 要从 credential.json 上传凭证以由 EAS 管理,请在项目的根目录中运行 `eas credentials`,选择一个平台,选择 `"Credentials.json: Upload/Download credentials between EAS servers and your local json"`,然后选择 `"Upload credentials from credentials.json to EAS"`。如果需要,再次运行该命令以上传另一个平台的凭据。 ¥To upload your credentials from **credentials.json** to be managed by EAS, run `eas credentials` in the root of your project, pick a platform, choose `"Credentials.json: Upload/Download credentials between EAS servers and your local json"`, and then `"Upload credentials from credentials.json to EAS"`. Run the command again to upload the credentials for another platform, if needed. ## 安全 了解使用 EAS 时如何处理凭证和其他敏感数据。 在输入外部凭据或向第三方软件提供其他敏感数据之前,你应该问自己是否相信该软件会负责任地使用它并保护它。由于构建应用二进制文件以在应用商店上分发的性质,Expo 独立应用构建服务需要具有不同敏感度的各种信息。本文档解释了它们是什么、我们如何存储它们以及如果它们被泄露可能会出现什么问题。 ¥Before you enter outside credentials or provide other sensitive data to third-party software you should ask yourself whether you trust the software to use it responsibly and protect it. Due to the nature of what goes into building an app binary for distribution on app stores, the Expo standalone app build service requires various pieces of information with varying degrees of sensitivity. This document explains what those are, how we store them, and what could go wrong if they were to be compromised. Expo 服务器存储的大多数数据(凭证或其他)都由我们的云提供商 Google Cloud 静态加密。凭证还使用 [KMS](https://cloud.google.com/security/products/security-key-management) 进行加密。仅当我们在独立应用构建器或推送通知服务的内存中需要凭证时,凭证才会被加密。在我们的数据库、消息队列和系统的其他不太短暂的部分中,凭证始终是加密的。 ¥Most data stored by Expo servers (credentials or otherwise) is encrypted at rest by our cloud provider, Google Cloud. Credentials are additionally encrypted using [KMS](https://cloud.google.com/security/products/security-key-management). Credentials are only unencrypted for as long as we need them in memory in the standalone app builders or push notification services. Credentials are always encrypted in our databases, message queues, and other less transient parts of the system. 与下面解释的信息相关的所有数据都可以从 Expo 服务器下载和删除(如果它首先存储在那里),并且其中一些数据可以通过其他位置(例如 Apple 开发者门户)获得。 ¥All of the data related to the information explained below can be downloaded and removed from Expo servers (if it is stored there at all in the first place), and some of it may be available through other locations such as the Apple Developer Portal. ## Android 推送通知凭据 ¥Android Push Notification credentials Android 使用 Firebase 云消息传递 (FCM) 来推送通知。如果你使用 Expo 构建独立应用,我们会为你存储你的 FCM 服务器密钥。 ¥Android uses Firebase Cloud Messaging (FCM) for push notifications. If you build a standalone app with Expo we store your FCM server key for you. ### 妥协的后果 ¥Consequences if compromised 每个 FCM 服务器密钥都可以向与该密钥所属的 Firebase 项目关联的任何 Android 应用发送推送通知。恶意行为者需要访问 FCM 服务器密钥和设备令牌才能发送通知。 ¥Each FCM server key can send push notifications to any of the Android apps associated with the Firebase project to which the key belongs. A malicious actor would need to have access to the FCM server key and device tokens to send a notification. 你可以通过 Firebase 控制台创建和删除服务器密钥。当你删除某个键时,使用该键的通知将停止工作。当你创建新通知并将其上传到 Expo 时,通知将恢复工作。 ¥You can create and delete server keys through the Firebase console. When you delete a key, notifications using that key will stop working. When you create a new one and upload it to Expo, notifications will resume working. ### 丢失后果 ¥Consequences if lost 没有任何。你可以通过 Firebase 控制台访问它。 ¥None. You can access it through the Firebase console. ## Android 构建凭据 ¥Android build credentials 需要密钥库和密钥库密码才能对版本进行签名以发布到 Play 商店。这些均使用 KMS 加密,并且处于静态状态。应用首次提交到 Google Play 商店后,必须使用相同的密钥库再次对应用进行签名以进行更新。它证明 APK 来自拥有密钥库的开发者。仅凭密钥库并不能让你提交到 Google Play - 你的 Google 账户还需要访问 Google Play Console。 ¥A keystore and keystore password are required to sign a build for release to the Play Store. These are encrypted with KMS and additionally at rest. After an app is first submitted to the Google Play Store, the same keystore must be used to sign the app again to update it. It proves that the APK came from the developer who owns the keystore. The keystore alone doesn't let you submit to Google Play — your Google account needs access to the Google Play Console as well. ### 妥协的后果 ¥Consequences if compromised 如果你的 Google Play 开发者账户是安全的,恶意行为者将无法使用你的密钥库和密钥库密码更新你的应用。你无法更改你的密钥库。 ¥Provided that your Google Play Developer account is secure, a malicious actor will not be able to update your app with your keystore and keystore password. You cannot change your keystore. ### 丢失后果 ¥Consequences if lost 你将无法在 Google Play 上更新你的应用。你可能需要在你选择的安全位置或使用应用签名功能在 Google Play 中下载并备份密钥库和密钥库密码。 ¥You will not be able to update your app on Google Play. You may want to download and backup the keystore and keystore password in a secure location of your choosing or in Google Play with the App Signing feature. ## 谷歌开发者凭据 ¥Google Developer credentials Expo 工具绝不会要求你提供 Google 账户凭据。 ¥Expo tools never ask you to provide your Google account credentials. ## Android 提交凭据 ¥Android submit credentials ### Google 服务账户密钥 ¥Google Service Account Key Google 服务账户密钥是使用 EAS Submit 将 Android 应用提交到 Google Play Store 的身份验证方法。此密钥存储在 Expo 服务器上,并在静止时使用 [KMS](https://cloud.google.com/security/products/security-key-management) 加密。密钥将保留在 Expo 服务器上,以便在后续提交中重复使用,并且具有必要权限的用户可以随时将其删除。 ¥Google Service Account Key is the authentication method used to submit an Android app to the Google Play Store with EAS Submit. This key is stored on Expo servers and encrypted using [KMS](https://cloud.google.com/security/products/security-key-management) when at rest. The key will remain on Expo servers to be re-used for subsequent submissions, and it can be removed at any time by a user with the requisite permissions. #### 妥协的后果 ¥Consequences if compromised 如果恶意行为者以某种方式获得你的 Google 服务账户密钥的访问权限,他们将能够代表你在 Google Play 控制台中执行操作。他们可以执行的操作将仅限于授予服务账户密钥的权限。 ¥If a malicious actor somehow gains access to your Google Service Account Key, they would be able to perform actions in the Google Play Console on your behalf. The actions they could perform would be limited to the permissions granted to the service account key. 如果攻击者还获得了对你的上传密钥库的访问权限,他们将能够提交现有应用的新版本。参与者将无法以你的名义向 Google Play 商店提交新应用,因为第一次 Google Play 提交需要通过 Web 控制台完成。 ¥If the attacker has additionally gained access to your upload keystore, they would be able to submit a new version of an existing app. The actor would not be able to submit a new app to the Google Play Store in your name, as the first Google Play submission needs to be done through the web console. #### 丢失后果 ¥Consequences if lost 没有任何。如果你丢失了 Google 服务账户密钥,可以使用 Google Cloud Console 将其撤销并创建一个新的。 ¥None. If you lose the Google Service Account Key, you can revoke it using the Google Cloud Console and create a new one. ## iOS 推送通知凭据 ¥iOS Push Notification credentials iOS 推送通知凭据有两种类型:Apple 推荐的一种现代方法和传统方法。默认行为是使用现代方法,但开发者可以通过提供 p12 证书来选择使用旧方法。 ¥There are two types of iOS push notification credentials: one modern approach recommended by Apple and the legacy approach. The default behavior is to use the modern approach, but developers may opt-in to the legacy approach by providing a p12 certificate. ### APNs 身份验证密钥 (p8) + 密钥 ID(字符串) ¥APNs auth key (p8) + key ID (string) 每个开发者账户最多有两个身份验证密钥,每个身份验证密钥都可以向账户上的任何应用发送通知。 ¥Each developer account has up to two auth keys, each of which can send notifications to any app on the account. 授权密钥可从 Apple 开发者中心撤销。如果你撤销它们,通知将停止工作。如果你提供新的身份验证密钥并将其上传到 Expo,则通知将恢复工作。当授权密钥被撤销时,设备令牌不会失效。 ¥Auth keys are revocable from the Apple Developer Center. If you revoke them, notifications will stop working. If you provision a new auth key and upload it to Expo then notifications will resume working. Device tokens are not invalidated when auth keys are revoked. ### 妥协的后果 ¥Consequences if compromised 如果恶意行为者以某种方式获得凭据的访问权限,他们将能够向你的应用发送推送通知。但是,他们需要知道将其发送到哪个设备令牌。 ¥If a malicious actor were to somehow gain access to the credentials, they would be able to send push notifications to your app. However, they would need to know which device tokens to send them to. ### 丢失后果 ¥Consequences if lost Apple 开发者控制台仅允许你在创建 APNs 身份验证密钥时下载该密钥。如果 Auth Key 丢失,可以通过 Apple Developer 控制台撤销它并用新密钥替换。 ¥The Apple Developer console lets you download an APNs Auth Key only when it is created. If an Auth Key is lost, it can be revoked through the Apple Developer console and replaced with a new key. ## iOS 构建凭据 ¥iOS build credentials 这是指生产分发证书和密码(如果你让 Expo 为你管理它们,则会自动生成)和配置文件(不是秘密的)。与 Expo 存储的大多数凭证数据一样,这些数据均使用 KMS 加密。你的构建凭据可让你构建一个应用并上传到 App Store Connect。不过,要实际上传并提交审核,你需要拥有 Apple 开发者账户凭据。 ¥This refers to the production distribution certificate and password (which are automatically generated if you let Expo manage them for you) and provisioning profiles (which are not secret). Like most credential data stored by Expo these are all encrypted with KMS. Your build credentials let you build an app to upload to App Store Connect. To actually upload it and submit it for review, though, you need to have your Apple Developer account credentials. ### 妥协的后果 ¥Consequences if compromised 恶意行为者仅凭此无法做太多事情 - 如果没有你的 Apple 开发者账户凭据,他们将无法提交任何应用。你可以从 Apple Developer 网站撤销分发证书和配置文件。 ¥There isn't much that a malicious actor could do with this alone — they would be unable to submit any apps without having your Apple Developer account credentials. You can revoke the distribution certificate and provisioning profile from the Apple Developer website. ### 丢失后果 ¥Consequences if lost 没有任何。它们可以通过 Apple 开发者控制台获得。 ¥None. They are available through the Apple Developer console. ## Apple 开发者账户凭据 ¥Apple Developer account credentials 创建独立应用版本或上传到 App Store 时,系统将提示你输入 Apple 开发者账户凭据。我们不会将这些存储在我们的服务器上 - EAS CLI 仅在本地使用它们。你的计算机单独提供发送到 Expo 服务器的分发证书和身份验证密钥;你的开发者凭据不会发送到 Expo 服务器。Apple 强制实现了额外的安全层,因为它们要求所有 Apple 开发者账户进行双重身份验证。 ¥When creating a standalone app build, or uploading to the App Store you will be prompted for your Apple Developer account credentials. We do not store these on our servers — EAS CLI only uses them locally. Your computer alone provisions distribution certificates and auth keys that are sent to Expo servers; your developer credentials are not sent to Expo servers. An additional layer of security is enforced by Apple, as they require two-factor authentication for all Apple Developer accounts. 创建临时构建时,我们会临时存储一个 Apple Developer 会话令牌,用于使用你的开发设备的 UDID 创建临时配置文件。一旦我们使用完这个会话令牌,我们就会销毁它。 ¥When creating ad-hoc builds, we temporarily store an Apple Developer session token used to create an ad-hoc provisioning profile with your development device's UDID. Once we are done using this session token we destroy it. ### 密钥链 ¥Keychain 默认情况下,你的 Apple ID 凭据存储在 macOS 密钥串中。你的密码仅存储在你的本地计算机上。此功能不适用于 Windows 或 Linux 用户。 ¥By default, your Apple ID credentials are stored in the macOS Keychain. Your password is only ever stored locally on your computer. This feature is not available for Windows or Linux users. 使用环境变量 `EXPO_NO_KEYCHAIN=1` 禁用密钥串支持。你还可以使用它来更改保存的密码。 ¥Disable Keychain support with the environment variable `EXPO_NO_KEYCHAIN=1`. You can also use this to change the saved password. ### 更改密钥串中的 Apple ID 密码 ¥Changing Apple ID password in Keychain 要删除本地存储的密码,请打开 "密钥串访问" 应用,切换到 "所有项目",然后搜索“deliver.txt”。[你的 Apple ID]”(例如:`deliver.bacon@expo.dev`)。选择你要修改的项目并将其删除。下次运行 Expo 命令时,系统将提示你输入新密码。 ¥To delete the locally stored password, open the "Keychain Access" app, switch to "All Items", and search for "deliver. [Your Apple ID]" (example: `deliver.bacon@expo.dev`). Select the item you wish to modify and delete it. Next time running an Expo command you'll be prompted for a new password. ### 妥协的后果 ¥Consequences if compromised 对于独立构建,如上所述,你的计算机需要受到损害,恶意行为者才能访问你的用户名和密码。他们还需要访问你的双重身份验证代码生成器,对于 Apple 开发者账户来说,该生成器是预先授权的 Apple 设备。此时,你可能会遇到更严重的问题,但正如你所期望的那样,攻击者将能够使用你的 Apple 开发者账户做任何他们喜欢做的事情。 ¥For standalone builds, as explained above, your machine would need to be compromised for a malicious actor to have access to your username and password. They would also need to have access to your two-factor authentication code generator, which for Apple Developer accounts is a pre-authorized Apple device. At this point, you may have worse problems, but as you may expect, the actor would be able to do whatever they like with your Apple Developer account. 对于临时构建,如果用户要访问你的会话令牌,则相当于登录你的账户。 ¥For ad-hoc builds, if a user were to gain access to your session token it would be comparable to being signed in to your account. ### 丢失后果 ¥Consequences if lost 没有任何。它们可以通过 Apple 开发者控制台获得。 ¥None. They are available through the Apple Developer console. ## iOS 提交凭据 ¥iOS submit credentials ### Apple App Store Connect (ASC) API 密钥 ¥Apple App Store Connect (ASC) API key Apple App Store Connect (ASC) API 密钥是可用于使用 EAS Submit 服务将 iOS 应用提交到 Apple 的 App Store 的身份验证方法之一。此密钥存储在 Expo 服务器上,并在静止时使用 [KMS](https://cloud.google.com/security/products/security-key-management) 加密。密钥将保留在 Expo 服务器上,以便在后续提交中重复使用,并且具有必要权限的用户可以随时将其删除。 ¥Apple App Store Connect (ASC) API key is one of the authentication methods that can be used to submit an iOS app to Apple's App Store using the EAS Submit service. This key is stored on the Expo servers and encrypted using [KMS](https://cloud.google.com/security/products/security-key-management) when at rest. The key will remain on Expo servers to be re-used for subsequent submissions, and it can be removed at any time by a user with the requisite permissions. ASC API 密钥是使用 EAS Submit 将你的应用提交到 App Store 的默认和推荐身份验证方法。 ¥The ASC API key is the default and **recommended** authentication method for submitting your apps to the App Store using EAS Submit. #### 妥协的后果 ¥Consequences if compromised 如果恶意行为者以某种方式获得 ASC API 密钥的访问权限,他们将能够代表你在 App Store Connect 中执行操作。他们可以执行的操作将仅限于授予 API 密钥的权限。 ¥If a malicious actor somehow gains access to the ASC API key, they would be able to perform actions in the App Store Connect on your behalf. The actions they could perform would be limited to the permissions granted to the API key. 如果攻击者还获得了对你的构建凭据的访问权限,他们将能够提交现有应用的新版本。他们只能提交使用这些构建凭据签名的应用,不能以你的名义向 App Store 提交任何任意应用。 ¥If the attacker has additionally gained access to your build credentials, they would be able to submit a new version of an existing app. They would only be able to submit the app signed with those build credentials, and they can't submit any arbitrary app to the App Store in your name. #### 丢失后果 ¥Consequences if lost 没有任何。如果你丢失了 ASC API 密钥,可以使用 App Store Connect 门户将其撤销并创建一个新的。 ¥None. If you lose the ASC API key, you can revoke it using the App Store Connect portal and create a new one. ### Apple 应用专用密码 ¥Apple app-specific password Apple 应用专用密码是另一种可用于使用 EAS Submit 将 iOS 应用提交到 Apple 的 App Store 的身份验证方法。与其他凭据不同,应用专用密码不会在提交之间存储在 Expo 服务器中,每次使用时都必须提供。 ¥Apple app-specific password is another authentication method that can be used to submit an iOS app to Apple's App Store using the EAS Submit. Unlike other credentials, the app-specific password is not stored in the Expo servers between submissions, it must be provided each time it is to be used. 密码使用 [KMS](https://cloud.google.com/security/products/security-key-management) 加密,仅存储将应用提交到 App Store 所需的时间加上 24 小时,以允许在此期间重试。此期限结束后,密码将从 Expo 服务器中删除。 ¥The password is encrypted using [KMS](https://cloud.google.com/security/products/security-key-management) and stored only for the period required to submit the app to the App Store plus 24 hours, to allow for retries during that time. Once this period is over, the password is deleted from the Expo servers. 不建议使用此身份验证方法。我们建议使用 Apple Store Connect (ASC) API 密钥将你的应用提交到 App Store。除了将你的应用提交到 App Store 之外,Expo 不会以任何方式使用 Apple 应用专用密码。 ¥This authentication method is **not recommended**. We recommend using the Apple Store Connect (ASC) API key for submitting your apps to the App Store instead. Expo won't use the Apple app-specific password in any way other than to submit your app to the App Store. #### 妥协的后果 ¥Consequences if compromised 如果恶意行为者以某种方式获得应用特定密码的访问权限,他们将能够访问你存储在 iCloud 中的邮件、联系人和日历等信息(查看 [苹果的文档](https://support.apple.com/en-us/102654) 了解更多详情)。 ¥If a malicious actor somehow gains access to the app-specific password, they would be able to access information like mail, contacts, and calendars that you store in iCloud (check [Apple's documentation](https://support.apple.com/en-us/102654) for more details). 如果攻击者还获得了对你的构建凭据的访问权限,他们将能够提交现有应用的新版本。他们只能提交使用这些构建凭据签名的应用,不能以你的名义向 App Store 提交任何任意应用。 ¥If the attacker has additionally gained access to your build credentials, they would be able to submit a new version of an existing app. They would only be able to submit the app signed with those build credentials, and they can't submit any arbitrary app to the App Store in your name. #### 丢失后果 ¥Consequences if lost 没有任何。如果你丢失了应用专用密码,则可以在 Apple 账户设置中将其撤销并创建一个新的。 ¥None. If you lose the app-specific password, you can revoke it and create a new one in the Apple account settings. ## Android 和 iOS 推送通知的设备令牌 ¥Device tokens for Android and iOS push notifications 除了特定于平台的凭据之外,还需要设备令牌才能发送推送通知。Expo 为你管理此操作,并通过 Expo Push Token 在其之上提供抽象。设备令牌标识接收者,即通知发送到的设备。设备令牌静态加密,并由 Android 和 iOS 定期自动循环。 ¥On top of the platform-specific credentials, a device token is necessary to send a push notification. Expo manages this for you and provides an abstraction on top of it with the Expo Push Token. The device token identifies the recipient, that is, the device to whom the notification is being sent. The device tokens are encrypted at rest and periodically cycled automatically by Android and iOS. ### 妥协的后果 ¥Consequences if compromised 如果恶意行为者有权访问设备令牌,他们将无法使用它们执行任何操作,除非他们还拥有适当平台的推送通知凭据。 ¥If a malicious actor has access to the device tokens, they will be unable to do anything with them unless they also have the push notification credentials for the appropriate platform. ### 丢失后果 ¥Consequences if lost 在用户再次打开你的应用之前,你将无法向他们发送通知。 ¥You won't be able to send notifications to users until they open your app again. ## 需要更多控制? ¥Need more control? 如果上述信息不能满足你的安全要求,你可能希望运行独立应用版本 [在你的基础设施上](/build-reference/local-builds/)。请注意,你仍然需要提供推送通知凭据才能使用推送通知服务。如果这也是不可能的,我们建议你自行处理推送通知。 ¥If the above information doesn't satisfy your security requirements, you may wish to run your standalone app builds [on your infrastructure](/build-reference/local-builds/). Note that you will still need to provide your push notification credentials to use the push notification service. If that is also impossible, we recommend handling push notifications on your own. ## EAS Build 的 Apple 开发者计划角色和权限 了解创建 EAS 构建的 Apple 开发者账户成员资格要求。 使用 EAS Build 创建 iOS 设备版本时,需要具有创建 [应用签名凭据](/app-signing/managed-credentials/#generating-app-signing-credentials)(例如证书、标识符和配置文件)权限的 Apple 开发者账户。这些凭据可以在通过从 EAS CLI 登录你的 Apple 账户提交构建时生成,也可以由授权用户将其上传到你的 Expo 账户,因此没有 Apple 开发者账户访问权限的用户可以使用上传的凭据创建构建。 ¥An Apple Developer account with permissions to create [app signing credentials](/app-signing/managed-credentials/#generating-app-signing-credentials), such as certificates, identifiers, and provisioning profiles, is required when using EAS Build to create iOS device builds. These credentials can be generated when submitting the build by logging into your Apple Account from the EAS CLI, or they can be uploaded to your Expo account by an authorized user, so users without Apple Developer account access can create builds using the uploaded credentials. 在个人 Apple 开发者账户中,只有账户持有人角色可以生成应用签名凭据。在组织 Apple 开发者账户中,账户持有人和管理员角色始终可以生成应用签名凭据,而当具有此角色的用户在其 App Store Connect 用户权限中启用了对证书、标识符和配置文件的访问权限时,应用管理员角色可以生成凭据。 ¥On individual Apple Developer accounts, only the Account Holder role can generate app signing credentials. On an organization Apple Developer account, the Account Holder and Admin roles can always generate app signing credentials, and the App Manager role can generate credentials when a user with this role has **Access to Certificates, Identifiers, and Profiles** enabled in their App Store Connect user permissions. 本指南提供了授权用户可以遵循的步骤,以确保生成应用签名凭据并供使用 EAS 的团队成员使用。它还为团队开发者提供了使用预生成的凭据创建 EAS Build 的步骤。 ¥This guide provides steps that an authorized user can follow to ensure app signing credentials are generated and available to their team members who use EAS. It also provides steps for the team developer to create an EAS Build by using pre-generated credentials. > 有关基于开发者账户类型和每个角色所需权限的不同角色及其权限的详细信息,请参阅 [Apple 关于程序角色的文档](https://developer.apple.com/support/roles/)。 > > ¥See [Apple's documentation on Program Roles](https://developer.apple.com/support/roles/) for details on the different roles and their permissions based on the type of Developer account and the permissions that are required for each role. ## Apple Developer 账户授权用户的步骤 ¥Steps for Apple Developer account's authorized user Apple 开发者账户的授权用户需要生成以下凭据: ¥The authorized user of the Apple Developer account needs to generate the following credentials: * 分发签名证书:需要签署安装在 iOS 设备上的开发和发布版本。 ¥**Distribution signing certificate**: Required to sign development and release builds that are installed on an iOS device. * 临时配置文件:需要签署安装在 Apple App Store 之外的设备上的内部版本。 ¥**Ad hoc provisioning profile**: Required to sign builds that are installed on a device outside of the Apple App Store. * 分发配置文件:需要签署提交给 Apple App Store 的版本。 ¥**Distribution provisioning profile**: Required to sign the build that is submitted to the Apple App Store. * 按键:使用推送通知服务时需要。 ¥**Push key**: Required when using a push notification service. 有关分发证书、配置文件和推送密钥的详细信息,请参阅 [路由参数](/app-signing/app-credentials/#ios)。 ¥For details on Distribution certificate, Provision profiles, and Push keys, see [required iOS app credentials](/app-signing/app-credentials/#ios). 使用 EAS CLI,可以创建上述所有凭据并将其自动与 Apple 开发者账户同步。授权用户登录到他们的 [Expo 账户](/accounts/account-types/) 后,他们可以通过使用 EAS CLI 运行 `eas credentials` 来创建或更新配置文件。 ¥With EAS CLI, all of the above credentials can be created and synced automatically with the Apple Developer account. Once the authorized user logs in to their [Expo account](/accounts/account-types/), they can create or update the provisioning profile by running `eas credentials` using the EAS CLI. ```sh $ eas login $ eas credentials ``` CLI 将提示选择用于 EAS 构建的 [建立档案](/build/eas-json/#build-profiles)。如果 Apple Developer 账户的授权用户正在创建生产版本,请按照以下步骤操作 [创建分发配置文件](/tutorial/eas/ios-production-build/#create-a-distribution-provisioning-profile)。要创建开发者版本,请按照以下步骤操作 [创建临时配置文件](/tutorial/eas/ios-development-build-for-devices/#provisioning-profile)。 ¥The CLI will prompt for selecting a [build profile](/build/eas-json/#build-profiles) to use for the EAS Build. If the Apple Developer account's authorized user is creating a production build, follow these steps to [create a distribution provisioning profile](/tutorial/eas/ios-production-build/#create-a-distribution-provisioning-profile). To create a developer build, follow these steps to [create an ad hoc provisioning profile](/tutorial/eas/ios-development-build-for-devices/#provisioning-profile). 这可确保与 Expo 账户关联的配置文件具有必要的权限。 ¥This ensures that the provisioning profile associated with the Expo account has necessary permissions. > 对于具有现有凭据的项目,请参阅 [使用现有凭据](/app-signing/existing-credentials/) 了解如何将这些凭据同步到 EAS 或手动管理它们的详细信息。 > > ¥For projects with existing credentials, see [Using existing credentials](/app-signing/existing-credentials/) for details on how to sync these to EAS or manage them manually. ## 团队开发者的步骤 ¥Steps for the team developer 作为团队中的开发者,在终端窗口中运行 `eas build -p ios` 时,EAS CLI 会要求你登录 Apple 开发者账户。 ¥As a developer on the team, when running `eas build -p ios` in the terminal window, the EAS CLI asks you to login to an Apple Developer account. ```sh ? Do you want to log in to your Apple account? > (Y/n) No problem! 👌 If any of the next steps will require Apple account access we will ask you again about it. ``` 如果你无权访问,请按 n 跳过登录 Apple 开发者账户(并避免登录你的个人 Apple 开发者账户,如果有)。CLI 显示有关跳过配置文件验证和其他应用签名凭据验证的消息,并将继续使用现有凭据创建 EAS 构建 ¥Press n to skip logging into Apple Developer account if you don't have access (and avoid logging into your personal Apple Developer account, if any). The CLI displays message about skipping provisioning profile validation and other app signing credential validation and will continue creating the EAS Build with existing credentials EAS CLI 需要使用与 Expo 账户关联的配置文件来为 iOS 创建构建。当你跳过登录时,EAS Build 将使用你组织的 Expo 账户中 Apple Developer 账户的授权用户更新的最后一个配置文件和其他凭据。 ¥The EAS CLI needs to use the provisioning profile associated with the Expo account to create a build for iOS. When you skip login, the EAS Build will use the last provisioning profile and other credentials that were updated by the Apple Developer account's authorized user in your organization's Expo account. ## 附加信息 ¥Additional information ### 上传预生成的 Apple 凭据 ¥Uploading pre-generated Apple credentials 一些开发团队可能会选择在 EAS 之外生成分发证书和配置文件。任何具有“开发者”或更高权限的 EAS 用户都可以使用 `eas credentials` 添加这些凭据,或者在“选择你的项目”>“项目设置”>“配置”>“凭据”下使用 EAS 信息中心添加这些凭据。 ¥Some development teams may choose to generate distribution certificates and provisioning profiles outside of EAS. These credentials can be added by any EAS user with Developer or higher permissions using `eas credentials` or under **Select your project** > **Project settings** > **Configuration** > **Credentials** using the EAS dashboard. 上传凭据时,你将需要 .p12 和 .mobileprovision 文件,以及生成分发证书时设置的任何密码。 ¥When uploading the credentials, you will need the **.p12** and **.mobileprovision** files, and any passwords set when generating the distribution certificate. ### 配置文件到期和更新 ¥Provisioning profile expiry and updates 如果添加或删除某些 [iOS 功能](/build-reference/ios-capabilities/)(例如,权利),或者在配置文件每年到期时,需要更新关联的配置文件。此步骤由 Apple 开发者账户的授权用户处理。 ¥The associated provisioning profile needs to be updated if certain [iOS capabilities](/build-reference/ios-capabilities/) (such as, entitlements) are added or removed, or at the annual expiry of the profile. This step is handled by the Apple Developer account's authorized user. ### 联合 Apple 开发者账户 ¥Federated Apple Developer accounts #### EAS 构建 ¥EAS Build EAS CLI 只能接受 Apple 账户的电子邮件和密码来登录你的 Apple 开发者账户。你无法登录 [联合 Apple 开发者账户](https://support.apple.com/en-in/guide/apple-business-manager/axmb19317543/web) 并更新分发证书或配置文件。如果你的构建凭据不需要任何更改,则可以跳过登录。然后,你可以继续构建,EAS CLI 将继续使用你当前上传的凭据。 ¥EAS CLI can only accept an Apple account's email and password to login into your Apple Developer account. You cannot login into [Federated Apple Developer account](https://support.apple.com/en-in/guide/apple-business-manager/axmb19317543/web) and make updates to the distribution certificate or provisioning profile. If your build credentials do not require any changes, you can skip logging in. Then, you can proceed with the build and EAS CLI will continue using your current uploaded credentials. 但是,你可以提供具有管理员访问权限的 Apple Store Connect (ASC) API 令牌,以便在运行 `eas build` 命令时检查和更新 Apple 凭据。按照 [为你的 Apple 团队提供 ASC API 令牌](/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team) 中的步骤通过将所需的令牌值传递给 `eas build` 命令来创建构建。 ¥However, you can provide an Apple Store Connect (ASC) API token with Admin access to check and update Apple credentials when running `eas build` command. Follow the steps in [Provide an ASC API Token for your Apple Team](/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team) to create a build by passing the required token value to the `eas build` command. #### EAS 提交 ¥EAS Submit EAS Submit 使用 ASC API 令牌提交给 TestFlight。如果你拥有联合 Apple 开发者账户,则可以按照标准 EAS Submit 设置进行操作。它允许你使用 `eas build --auto-submit` 自动提交构建。 ¥EAS Submit uses the ASC API token for submitting to TestFlight. If you have a Federated Apple Developer account, you can follow the standard EAS Submit setup. It lets you automatically submit your builds using `eas build --auto-submit`. # 定制构建 ## 开始定制构建 了解如何使用自定义构建扩展 EAS Build。 自定义构建允许通过在构建过程之前、期间或之后运行命令来自定义项目的构建过程。自定义构建可以从 EAS CLI 运行,也可以在 React Native CI/CD 管道中运行构建,例如使用 [EAS 工作流程](/eas/workflows/get-started/)。 ¥Custom builds allow customizing the build process for your project by running commands before, during, or after the build process. Customized builds can run from EAS CLI or when running builds in a React Native CI/CD pipeline, like with [EAS Workflows](/eas/workflows/get-started/). Step 1: ## Create a custom build config To get started, create directories and a file named **.eas/build/hello-world.yml** at the same level as **eas.json**. The location and name of both directories are important for EAS Build to identify that a project contains a custom build config. Inside the **hello-world.yml**, you'll write your custom build config. The filename is unimportant; you can name it whatever you want. The only requirement is that the file extension uses **.yml**. Add the following custom build config steps in the file: ```yaml .eas/build/hello-world.yml build: name: Hello World! steps: - run: echo "Hello, world!" # A built-in function (optional) ``` In a real world scenario, you will call a [built-in function](/custom-builds/schema/#built-in-eas-functions) to trigger the build. Step 2: ## Add `config` property in eas.json To use the custom build config, add the `config` property in **eas.json** under a build profile. Let's create a new [build profile](/build/eas-json/#build-profiles) called `test` under `build` to run the custom config from the **test.yml** file: ```json eas.json { "build": { "test": { "config": "test.yml", }, } ``` If you wish to use separate configs for each platform, you can create separate YAML config files for Android and iOS. For example: ```json eas.json { "build": { "test": { "ios": { "config": "hello-ios.yml", }, "android": { "config": "hello-android.yml", } }, } ``` Step 3: ## Run a build to test the custom build config To test the custom build config, run the following command: ```sh $ eas build -p android -e test ``` After the build is complete, you can verify that the `echo "Hello World!"` script was executed by checking the logs on the build's detail page. ## 了解更多 ¥Learn more 查看示例存储库以获取更详细的示例: ¥Check out the example repository for more detailed examples: ## 自定义构建配置架构 使用 EAS Build 进行自定义构建的配置选项参考。 为 EAS Build 创建自定义版本有助于自定义项目的构建过程。 ¥Creating custom builds for EAS Build helps customize the build process for your project. ## 自定义构建的 YAML 语法 ¥YAML syntax for custom builds 自定义构建配置文件存储在 .eas/build 目录路径内。它们使用 YAML 语法,并且必须具有 `.yml` 或 `.yaml` 文件扩展名。如果你不熟悉 YAML 或者想了解有关语法的更多信息,请参阅 [在 Y 分钟内学习 YAML](https://learnxinyminutes.com/docs/yaml/)。 ¥Custom build config files are stored inside the **.eas/build** directory path. They use YAML syntax and must have a `.yml` or `.yaml` file extension. If you are new to YAML or want to learn more about the syntax, see [Learn YAML in Y minutes](https://learnxinyminutes.com/docs/yaml/). ## `build` 定义以描述自定义构建配置。创建自定义构建的所有配置选项都在其下指定。 ¥Defined to describe a custom build configuration. All config options to create a custom build are specified under it. ### `name` 用于在构建日志中识别自定义构建的名称。EAS Build 使用此属性在仪表板中显示构建的名称。 ¥The name of your custom build that is used to identify it in the build logs. EAS Build uses this property to display the name of your build in the dashboard. 例如,构建名称为 `Run tests`: ¥For example, the build name is `Run tests`: ```yaml build: # @info # name: Run tests # @end # steps: - eas/checkout - run: name: Install dependencies command: npm install ``` ### `steps` 步骤用于描述操作列表,可以是命令或函数调用的形式。当自定义构建在 EAS Build 上运行时,将执行这些操作。你可以在构建配置中定义单个或多个步骤。但是,每个构建至少需要定义一个步骤。 ¥Steps are used to describe a list of actions, either in the form of commands or function calls. These actions are executed when a custom build runs on EAS Build. You can define single or multiple steps in a build config. However, it is **required** to define at least one step per build. 每个步骤都配置有以下属性: ¥Each step is configured with the following properties: #### `steps[].run` `run` 键用于触发一组指令。例如,`run` 密钥用于使用 `npm install` 命令安装依赖: ¥The `run` key is used to trigger a set of instructions. For example, a `run` key is used to install dependencies using the `npm install` command: ```yaml build: name: Install npm dependencies steps: - eas/checkout # @info # - run: name: Install dependencies command: npm install # @end # ``` 你还可以使用 `steps[].run` 执行单行或多行 shell 命令: ¥You can also use `steps[].run` to execute single or multiline shell commands: ```yaml build: name: Run inline shell commands steps: # @info # - run: echo "Hello world" - run: | echo "Multiline" echo "bash commands" # @end # ``` #### 使用单步 ¥Use a single step 例如,具有以下 `steps` 的构建配置将打印 "你好世界": ¥For example, a build config with the following `steps` will print "Hello world": ```yaml build: name: Greeting steps: # @info # - run: echo "Hello world" # @end # ``` > 注意:`run` 之前的 `-` 算作缩进。 > > ¥**Note:** `-` before `run` counts as indentation. #### 使用多个步骤 ¥Use multiple steps 当定义多个 `steps` 时,它们是顺序执行的。例如,具有以下 `steps` 的构建配置将首先检出项目,安装 npm 依赖,然后运行命令来运行测试: ¥When multiple `steps` are defined, they are executed sequentially. For example, a build config with the following `steps` will first check out the project, install npm dependencies, and then run a command to run tests: ```yaml build: name: Run tests steps: # @info # - eas/checkout - run: name: Install dependencies command: npm install - run: name: Run tests command: | echo "Running tests..." npm test # @end # ``` #### 与其他步骤共享环境变量 ¥Sharing environment variables with other steps 在一个步骤的 `command` 中导出(使用 `export`)的环境变量不会自动暴露给其他步骤。要与其他步骤共享环境变量,请使用 `set-env` 可执行文件。 ¥Environment variables exported (using `export`) in one step's `command` are not automatically exposed to other steps. To share an environment variable with other steps, use the `set-env` executable. `set-env` 期望使用两个参数进行调用:环境变量的名称和值。例如,`set-env NPM_TOKEN "abcdef"` 将向其他步骤公开值为 `abcdef` 的 `$NPM_TOKEN` 变量。 ¥`set-env` expects to be called with two arguments: environment variable's name and value. For example, `set-env NPM_TOKEN "abcdef"` will expose `$NPM_TOKEN` variable with value `abcdef` to other steps. > 注意:与 `set-env` 共享的变量不会自动导出到本地。你需要自己调用 `export`。 > > ¥**Note:** Variables shared with `set-env` are not automatically exported locally. You need to call `export` yourself. ```yaml build: name: Shared environment variable example steps: - run: name: Set environment variables command: | set -x # Set variable ENV_TEST_LOCAL="present-only-in-current-shell-context" # Set and export variable export ENV_TEST_LOCAL_EXPORT="present-in-current-step" # Set shared variable set-env ENV_TEST_SET_ENV "present-in-following-steps" # Will print "ENV_TEST_LOCAL: present-only-in-current-shell-context" # because current shell has access to this local variable. echo "ENV_TEST_LOCAL: $ENV_TEST_LOCAL" # Will print "ENV_TEST_LOCAL_EXPORT: present-in-current-step" # because export also sets the local variable value. echo "ENV_TEST_LOCAL_EXPORT: $ENV_TEST_LOCAL_EXPORT" # Will "ENV_TEST_SET_ENV: " # because set-env does not set or export variables. echo "ENV_TEST_SET_ENV: $ENV_TEST_SET_ENV" # Will only print LOCALLY_EXPORTED_ENV, # because it is the only export-ed variable. env | grep ENV_TEST_ - run: name: Check variables values in next step command: | set -x # Will print "ENV_TEST_LOCAL: ", because ENV_TEST_LOCAL # is only a local variable in previous step. echo "ENV_TEST_LOCAL: $ENV_TEST_LOCAL" # Will print "ENV_TEST_LOCAL_EXPORT: " # because export does not share a variable to other steps. echo "ENV_TEST_LOCAL_EXPORT: $ENV_TEST_LOCAL_EXPORT" # Will print "ENV_TEST_SET_ENV: present-in-following-steps" # because set-env "exported" variable to other steps. echo "ENV_TEST_SET_ENV: $ENV_TEST_SET_ENV" # Will only print ENV_TEST_SET_ENV, # because set-env "exported" it to other steps. env | grep ENV_TEST_ ``` #### `steps[].run.name` 构建日志中用于显示步骤名称的名称。 ¥The name used in build logs to display the name of the step. #### `steps[].run.command` `command` 定义了在执行步骤时运行的自定义 shell 命令。需要为每个步骤定义一个命令。它可以是多行 shell 命令: ¥The `command` defines a custom shell command to run when a step is executed. It is **required** to define a command for each step. It can be a multiline shell command: ```yaml build: name: Run tests steps: - eas/checkout - run: name: Run tests # @info # command: | echo "Running tests..." npm test # @end # ``` #### `steps[].run.working_directory` `working_directory` 用于定义项目根目录中的现有目录。在步骤中定义现有路径后,使用它会更改该步骤的当前目录。例如,创建一个步骤来列出资源目录(Expo 项目中的目录)内的所有资源。`working_directory` 设置为 `assets`: ¥The `working_directory` is used to define an existing directory from the project's root directory. After an existing path is defined in a step, using it changes the current directory for that step. For example, a step is created to list all the assets inside the **assets** directory, which is a directory in your Expo project. The `working_directory` is set to `assets`: ```yaml build: name: Demo steps: - eas/checkout - run: name: List assets # @info # working_directory: assets # @end # command: ls -la ``` #### `steps[].run.shell` 用于定义步骤的默认可执行 shell。例如,该步骤的 shell 设置为 `/bin/sh`: ¥Used to define the default executable shell for a step. For example, the step's shell is set to `/bin/sh`: ```yaml build: name: Demo steps: - run: # @info # shell: /bin/sh # @end # command: | echo "Steps can use another shell" ps -p $$ ``` #### `steps[].run.inputs` 输入值被提供给步骤。例如,你可以使用 `input` 来提供值: ¥Input values are provided to a step. For example, you can use `input` to provide a value: ```yaml build: name: Demo steps: - run: name: Say Hi # @info # inputs: name: Expo # @end # command: echo "Hi, ${ inputs.name }!" ``` #### `steps[].run.outputs` 在一个步骤中预计会有一个输出值。例如,某个步骤的输出值为 `Hello world`: ¥An output value is expected during a step. For example, a step has an output value of `Hello world`: ```yaml build: name: Demo steps: - run: name: Produce output # @info # outputs: [value] # @end # command: | echo "Producing output for another step" set-output value "Output from another step..." ``` #### `steps[].run.outputs.required` 输出值可以使用布尔值来指示是否需要输出值。例如,函数没有所需的输出值: ¥An output value can use a boolean to indicate if the output value is required or not. For example, a function does not have a required output value: ```yaml build: name: Demo steps: - run: name: Produce another output id: id456 # @info # outputs: - required_param - name: optional_param required: false # @end # command: | echo "Producing more output" set-output required_param "abc 123 456" ``` #### `steps[].run.id` 为步骤定义 `id` 允许: ¥Defining an `id` for a step allows: * 多次调用产生一个或多个输出的同一函数 ¥Calling the same function that produces one or more outputs multiple times * 使用从一个步骤到另一步骤的输出 ¥Using the output from one step to another #### 调用同一个函数一次或多次 ¥Call the same function one or more times 例如,以下函数生成一个随机数: ¥For example, the following function generates a random number: ```yaml functions: random: name: Generate random number outputs: [value] command: set-output value `random_number` ``` 在构建配置中,让我们使用 `random` 函数生成两个随机数并打印它们: ¥In a build config, let's use the `random` function to generate two random numbers and print them: ```yaml build: name: Functions Demo steps: - random: id: random_1 - random: id: random_2 - run: name: Print random numbers inputs: random_1: ${ steps.random_1.value } random_2: ${ steps.random_2.value } command: | echo "${ inputs.random_1 }" echo "${ inputs.random_2 }" ``` #### 使用从一个步骤到另一步骤的输出 ¥Use output from one step to another 例如,以下构建配置演示了如何使用从一个步骤到另一个步骤的输出: ¥For example, the following build config demonstrates how to use output from one step to another: ```yaml build: name: Outputs demo steps: - run: name: Produce output id: id123 # <---- !!! outputs: [foo] command: | echo "Producing output for another step" set-output foo bar - run: name: Use output from another step inputs: foo: ${ steps.id123.foo } command: | echo "foo = \"${ inputs.foo }\"" ``` ## `functions` 定义以描述可在构建配置中使用的可重用函数。创建函数的所有配置选项均使用以下属性指定: ¥Defined to describe a reusable function that can be used in a build config. All config options to create a function are specified with the following properties: ### `functions.[function_name]` `[function_name]` 是你定义的函数名称,用于在 `build.steps` 中标识它。例如,你可以定义一个名为 `greetings` 的函数: ¥The `[function_name]` is the name of a function that you define to identify it in the `build.steps`. For example, you can define a function with the name `greetings`: ```yaml functions: # @info # greetings: # @end # name: Say Hi! ``` ### `functions.[function_name].name` 在构建日志中用于显示函数名称的名称。例如,显示名称为 `Say Hi!` 的函数: ¥The name that is used in build logs to display the name of the function. For example, a function with the display name `Say Hi!`: ```yaml functions: greetings: # @info # name: Say Hi! # @end # ``` ### `functions.[function_name].inputs` 输入值被提供给函数。 ¥Input values are provided to a function. #### `inputs[].name` 输入值的名称。它用作访问输入值的标识符,例如在 bash 命令插值中。 ¥The name of the input value. It is used as an identifier to access the input value such as in bash command interpolation. ```yaml functions: greetings: name: Say Hi! inputs: # @info # - name: name # @end # default_value: Hello world command: echo "${ inputs.name }!" ``` #### `inputs[].required` 布尔值指示是否需要输入值。例如,一个函数没有所需的值: ¥Boolean to indicate if the input value is required or not. For example, a function does not have a required value: ```yaml functions: greetings: name: Say Hi! inputs: - name: name # @info # required: false # @end # ``` #### `inputs[].type` 输入值的类型。它可以是 `string`、`num` 或 `json`。 ¥The type of the input value. It can be either `string`, `num` or `json`. 函数调用中设置的输入值以及函数的 `default_value` 和 `allowed_values` 将根据类型进行验证。 ¥Input values set in the function call as well as `default_value` and `allowed_values` for the function are validated against the type. 默认输入 `type` 是 `string`。 ¥The default input `type` is `string`. 例如,函数具有 `string` 类型的输入值: ¥For example, a function has an input value of type `string`: ```yaml functions: greetings: name: Say Hi! inputs: - name: name # @info # type: string # @end # - name: age # @info # type: num # @end # - name: other_data # @info # type: json # @end # ``` #### `inputs[].default_value` 你可以使用 `default_value` 提供一个默认输入。例如,函数的默认值为 `Hello world`: ¥You can use `default_value` to provide one default input. For example, a function has a default value of `Hello world`: ```yaml functions: greetings: name: Say Hi! inputs: - name: name # @info # default_value: Hello world # @end # ``` #### `inputs[].allowed_values` 你可以使用 `allowed_values` 在数组中提供多个值。例如,一个函数有多个允许值: ¥You can use `allowed_values` to provide multiple values in an array. For example, a function has multiple allowed values: ```yaml functions: greetings: name: Say Hi! inputs: - name: name default_value: Hello world # @info # allowed_values: [Hi, Hello, Hey] # @end # type: string ``` #### 多个输入值 ¥Multiple input values 可以向函数提供多个输入值。 ¥Multiple input values can be provided to a function. ```yaml functions: greetings: name: Say Hi! # @info # inputs: - name: name default_value: Expo - name: greeting default_value: Hi allowed_values: [Hi, Hello] # @end # command: echo "${ inputs.greeting }, ${ inputs.name }!" ``` ### `functions.[function_name].outputs` 函数期望输出值。例如,函数的输出值为 `Hello world`: ¥An output value is expected from a function. For example, a function has an output value of `Hello world`: ```yaml functions: greetings: name: Say Hi! # @info # outputs: [value] # @end # command: set-output value "Hello world" ``` #### `outputs[].name` 输出值的名称。它用作访问另一个步骤中的输出值的标识符: ¥The name of the output value. It is used as an identifier to access the output value in another step: ```yaml functions: greetings: name: Say Hi! outputs: # @info # - name: name # @end # ``` #### `outputs[].required` 布尔值,指示是否需要输出值。例如,函数没有所需的输出值: ¥Boolean to indicate if the output value is required or not. For example, a function does not have a required output value: ```yaml functions: greetings: name: Say Hi! outputs: - name: value # @info # required: false # @end # ``` ### `functions.[function_name].command` 如果你希望函数是一个简单的 shell 脚本,则用于定义执行函数时要运行的命令。每个函数都需要为实现该函数的 JS/TS 模块定义 `command` 或 `path`。例如,命令 `echo "Hello world"` 用于打印一条消息: ¥Used to define the command to run when a function is executed, if you wish the function to be a simple shell script. Each function is **required** to define either a `command` or a `path` to JS/TS module implementing the function. For example, the command `echo "Hello world"` is used to print a message: ```yaml functions: greetings: name: Say Hi! # @info # command: echo "Hi!" # @end # ``` ### `functions.[function_name].path` 用于定义实现该功能的 JavaScript/TypeScript 模块的路径。每个函数都需要定义 `command` 或 `path` 属性。例如,路径 `./greetings` 用于执行 `greetings` 模块中声明的 `greetings` 函数: ¥Used to define the path to a JavaScript/TypeScript module implementing the function. Each function is **required** to define either a `command` or a `path` property. For example, the path `./greetings` is used to execute a `greetings` function declared in the `greetings` module: ```yaml functions: greetings: name: Say Hi! # @info # path: ./greetings # @end # ``` > [了解有关构建和使用自定义 TypeScript/JavaScript 函数的更多信息](/custom-builds/functions/)。 > > ¥[Learn more about building and using custom TypeScript/JavaScript functions](/custom-builds/functions/). ### `functions.[function_name].shell` 用于定义执行函数的步骤的默认可执行 shell。例如,该步骤的 shell 设置为 `/bin/sh`: ¥Used to define the default executable shell for a step where a function is executed. For example, the step's shell is set to `/bin/sh`: ```yaml functions: greetings: name: Say Hi! # @info # shell: /bin/sh # @end # command: echo "Hi!" ``` ### `functions.[function_name].supported_platforms` 用于定义函数支持的平台。默认为所有平台。允许的平台:`darwin`、`linux`。 ¥Used to define the supported platforms for a function. Defaults to all platforms. Allowed platforms: `darwin`, `linux`. 例如,该函数支持的平台是 `darwin`(macOS): ¥For example, the function's supported platform is `darwin` (macOS): ```yaml functions: greetings: name: Say Hi! # @info # supported_platforms: [darwin] # @end # command: echo "Hi!" ``` ## `import` 用于从其他配置文件导入函数的配置文件路径列表。导入的文件不能有 `build` 部分。 ¥A config file path list used to import functions from other config files. Imported files cannot have the `build` section. 例如,以下构建配置导入两个文件并调用两个导入的函数 - `say_hi` 和 `say_bye`。 ¥For example, the following build config imports two files and calls two imported functions - `say_hi` and `say_bye`. ```yaml build-and-test.yml import: - common-functions.yml - another-file.yml build: steps: - say_hi - say_bye ``` ```yaml common-functions.yml functions: say_hi: name: Say Hi! command: echo "Hi!" ``` ```yaml another-file.yml functions: say_bye: name: Say bye :( command: echo "Bye!" ``` ## 函数 ¥Functions ### 内置 EAS 功能 ¥Built-in EAS functions EAS 提供了一组内置可重用函数,你可以在构建配置中使用这些函数,而无需定义函数定义。 ¥EAS provides a set of built-in reusable functions that you can use in a build config without defining the function definition. > **info** 提示:EAS 内置并提供的任何功能都必须以 `eas/` 前缀开头。 > > ¥**Tip:** Any function that is built-in and provided by EAS must start with the `eas/` prefix. #### `eas/build` 封装了整个 EAS Build 构建过程的一体化功能。它根据你的 [**eas.json**](/eas/json/) 中的构建配置文件设置解析最佳构建配置。 ¥The all-in-one function that encapsulates the entire EAS Build build process. It resolves the best build configuration based on your build profile's settings from [**eas.json**](/eas/json/). 它非常适合那些希望完成构建而不必担心手动更改和配置构建过程的人。如果你有兴趣在构建过程之前或之后使用其他自定义步骤并且不想更改构建过程本身,那么它可能是自定义构建配置的一个很好的起点。 ¥It's ideal for people who want to have the build done without worrying about altering and configuring the build process manually. It can be a great starting point for your custom build configuration if you are interested in using other custom steps before or after the build process and you don't want to change the build process itself. ```yaml example.yml build: name: Run a build using a single command steps: - eas/build ``` 要更好地控制构建过程并根据你的要求对其进行自定义,请参阅 `eas/build` 在后台运行的以下自定义函数和步骤。它们根据你的构建配置文件的配置作为构建过程执行。 ¥To have more control over the build process and customize it as per your requirements, see the following custom functions and steps that run in the background by `eas/build`. They are executed as a build process based on your build profile's configuration. ##### 安卓 ¥Android 当构建配置使用 [`withoutCredentials`](/eas/json/#withoutcredentials) 时: ¥When a build configuration is using [`withoutCredentials`](/eas/json/#withoutcredentials): * [`eas/checkout`](#eascheckout) * [`eas/use_npm_token`](#easuse_npm_token) * [`eas/install_node_modules`](#easinstall_node_modules) * [`eas/resolve_build_config`](#easresolve_build_config) * [`eas/prebuild`](#easprebuild) * [`eas/configure_eas_update`](#easconfigure_eas_update) * [`eas/run_gradle`](#easrun_gradle) * [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) 当构建配置使用凭据时(对于 `internal` 和 `store` [distribution](/eas/json/#distribution) 构建): ¥When a build configuration uses credentials (for both `internal` and `store` [distribution](/eas/json/#distribution) builds): * [`eas/checkout`](#eascheckout) * [`eas/use_npm_token`](#easuse_npm_token) * [`eas/install_node_modules`](#easinstall_node_modules) * [`eas/resolve_build_config`](#easresolve_build_config) * [`eas/prebuild`](#easprebuild) * [`eas/configure_eas_update`](#easconfigure_eas_update) * [`eas/inject_android_credentials`](#easinject_android_credentials) * [`eas/configure_android_version`](#easconfigure_android_version) * [`eas/run_gradle`](#easrun_gradle) * [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) ##### iOS 系统 ¥iOS 当构建配置使用 [`withoutCredentials`](/eas/json/#withoutcredentials) 或 [`simulator`](/eas/json/#simulator) 时: ¥When a build configuration is using [`withoutCredentials`](/eas/json/#withoutcredentials) or [`simulator`](/eas/json/#simulator): * [`eas/checkout`](#eascheckout) * [`eas/use_npm_token`](#easuse_npm_token) * [`eas/install_node_modules`](#easinstall_node_modules) * [`eas/resolve_build_config`](#easresolve_build_config) * [`eas/prebuild`](#easprebuild) * 使用 `pod install` 命令安装 Pod ¥Install pods using the `pod install` command * [`eas/configure_eas_update`](#easconfigure_eas_update) * [`eas/generate_gymfile_from_template`](#easgenerate_gymfile_from_template) * [`eas/run_fastlane`](#easrun_fastlane) * [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) 当构建配置使用凭据时(对于 `internal` 和 `store` [distribution](/eas/json/#distribution) 构建): ¥When a build configuration uses credentials (for both `internal` and `store` [distribution](/eas/json/#distribution) builds): * [`eas/checkout`](#eascheckout) * [`eas/use_npm_token`](#easuse_npm_token) * [`eas/install_node_modules`](#easinstall_node_modules) * [`eas/resolve_build_config`](#easresolve_build_config) * [`eas/resolve_apple_team_id_from_credentials`](#easresolve_apple_team_id_from_credentials) * [`eas/prebuild`](#easprebuild) * 使用 `pod install` 命令安装 Pod ¥Install pods using the `pod install` command * [`eas/configure_eas_update`](#easconfigure_eas_update) * [`eas/configure_ios_credentials`](#easconfigure_ios_credentials) * [`eas/configure_ios_version`](#easconfigure_ios_version) * [`eas/generate_gymfile_from_template`](#easgenerate_gymfile_from_template) * [`eas/run_fastlane`](#easrun_fastlane) * [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) 你可以通过在 YAML 配置文件中使用以下步骤来替换 `eas/build` 命令调用: ¥You can replace the `eas/build` command call by using these steps in your YAML configuration file: ##### 已知的限制 ¥Known limitations * 它不接受任何输入,并且已解析的构建过程将根据 [**eas.json**](/eas/json/) 中的构建配置文件进行配置。 ¥It doesn't accept any inputs, and the resolved build process will be configured based on your build profile from [**eas.json**](/eas/json/). * `eas/build` 生成的构建过程不可配置,你也无法自定义它。如果你需要自定义构建过程,请使用此函数在后台执行的函数和步骤的子集,并在 YAML 配置文件中手动配置它们,如上面的示例所示。 ¥The build process produced by `eas/build` is not configurable and you can't customize it. If you need to customize the build process, use the subset of functions and steps that are executed behind the scenes by this function and configure them manually in the YAML configuration file, as shown in the examples above. #### `eas/maestro_test` 安装 Maestro、准备测试环境(Android 模拟器或 iOS 模拟器)并测试应用的一体化功能。 ¥All-in-one function that installs Maestro, prepares a testing environment (Android Emulator or iOS Simulator), and tests the app. > **warning** 你的项目必须配置为使用旧的 Build Infrastructure 来启动 Android 模拟器。转到 [项目设置](https://expo.dev/accounts/\[account]/projects/\[project]/settings) 进行配置。请参阅 [此更改日志帖子](https://expo.dev/changelog/2024/08-29-c3d-default) 了解更多信息。 > > ¥Your project must be configured to use the old Build Infrastructure to start Android Emulator. Go to [Project settings](https://expo.dev/accounts/\[account]/projects/\[project]/settings) to configure. See [this changelog post](https://expo.dev/changelog/2024/08-29-c3d-default) for more information. | 输入 | 类型 | 描述 | | ----------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `flow_path` | `string` | 运行 [大师流](https://docs.maestro.dev/getting-started/writing-your-first-flow) 的路径(或多个路径,每个路径占一行)。 | | `app_path` | `string` | 应测试的模拟器/模拟器应用的路径(或正则表达式模式)。如果未提供,则默认为 Android 的 android/app/build/outputs/\*\*/\*.apk 和 iOS 的 ios/build/Build/Products/*simulator/*.app。 | ```yaml build-and-test.yml build: name: Build and test steps: - eas/build # @info # - eas/maestro_test: inputs: flow_path: | maestro/sign_in.yml maestro/create_post.yml maestro/sign_out.yml # @end # ``` ```yaml test-ios-simulator-app.yml build: name: Build and test iOS simulator app steps: - eas/checkout # @info # - eas/maestro_test: app_path: ./fixtures/my_app.app inputs: flow_path: | maestro/sign_in.yml maestro/create_post.yml maestro/sign_out.yml # @end # ``` ```yaml test-android-emulator-app.yml build: name: Build and test Android emulator app steps: - eas/checkout # @info # - eas/maestro_test: app_path: ./fixtures/my_app.apk inputs: flow_path: | maestro/sign_in.yml maestro/create_post.yml maestro/sign_out.yml # @end # ``` 在幕后,它使用: ¥Behind the scenes, it uses: * [`eas/install_maestro`](#easinstall_maestro) 安装 Maestro ¥[`eas/install_maestro`](#easinstall_maestro) to install Maestro * [`eas/start_android_emulator`](#easstart_android_emulator) 启动 Android 模拟器(如果需要) ¥[`eas/start_android_emulator`](#easstart_android_emulator) to start an Android Emulator if needed * 如果需要,[`eas/start_ios_simulator`](#easstart_ios_simulator) 启动 iOS 模拟器 ¥[`eas/start_ios_simulator`](#easstart_ios_simulator) to start an iOS Simulator if needed * 自定义 `run` 将 .apk 安装到正在运行的 Android 模拟器,并将 .app 安装到 iOS 模拟器 ¥Custom `run` to install **.apk** to the running Android Emulator and **.app** to iOS Simulator * 为每个提供的流程执行 `maestro test` 的系列 `run` ¥Series of `run` to execute `maestro test` for each of the provided flows * [`eas/upload_artifact`](#easupload_artifact) 将 Maestro 测试工件上传为构建工件 ¥[`eas/upload_artifact`](#easupload_artifact) to upload Maestro test artifacts as build artifact > **警告** 我们观察到,如果在使用 Xcode 15.0 或 15.2 的图片上运行,Maestro 测试经常会超时。使用 `latest` 图片避免任何问题。 > > ¥We have observed that Maestro tests often time out if run on images with Xcode 15.0 or 15.2. Use the `latest` image to avoid any issues. 如果你需要自定义 Maestro 版本、运行特定的 Android 模拟器或 iOS 模拟器,或上传多个构建工件,你将需要自己编写这一系列步骤。 ¥If you need to customize the Maestro version, run a specific Android Emulator or iOS Simulator, or upload multiple build artifacts you will need to write this series of steps yourself. An example Android build config with eas/maestro_test expanded}> ```yaml build-and-test-android-expanded.yml build: name: Build and test (Android, expanded) steps: - eas/build # @info # - eas/install_maestro - eas/start_android_emulator: inputs: system_package_name: system-images;android-34;default;x86_64 - run: command: | # shopt -s globstar is necessary to add /**/ support shopt -s globstar # shopt -s nullglob is necessary not to try to install # SEARCH_PATH literally if there are no matching files. shopt -s nullglob SEARCH_PATH="android/app/build/outputs/**/*.apk" FILES_FOUND=false for APP_PATH in $SEARCH_PATH; do FILES_FOUND=true echo "Installing \\"$APP_PATH\\"" adb install "$APP_PATH" done if ! $FILES_FOUND; then echo "No files found matching \\"$SEARCH_PATH\\". Are you sure you've built an Emulator app?" exit 1 fi - run: command: | maestro test maestro/flow.yml - eas/upload_artifact: name: Upload test artifact if: ${ always() } inputs: type: build-artifact path: ${ eas.env.HOME }/.maestro/tests # @end # ``` --- An example iOS build config with eas/maestro_test expanded}> ```yaml build-and-test-ios-expanded.yml build: name: Build and test (iOS, expanded) steps: - eas/build # @info # - eas/install_maestro - eas/start_ios_simulator - run: command: | # shopt -s nullglob is necessary not to try to install # SEARCH_PATH literally if there are no matching files. shopt -s nullglob SEARCH_PATH="ios/build/Build/Products/*simulator/*.app" FILES_FOUND=false for APP_PATH in $SEARCH_PATH; do FILES_FOUND=true echo "Installing \\"$APP_PATH\\"" xcrun simctl install booted "$APP_PATH" done if ! $FILES_FOUND; then echo "No files found matching \\"$SEARCH_PATH\\". Are you sure you've built a Simulator app?" exit 1 fi - run: command: | maestro test maestro/flow.yml - eas/upload_artifact: name: Upload test artifact if: ${ always() } inputs: type: build-artifact path: ${ eas.env.HOME }/.maestro/tests # @end # ``` --- #### `eas/checkout` Checks out your project source files. For example, a build config with the following `steps` will check out the project and list the files in the **assets** directory: ```yaml upload.yml build: name: List files steps: - eas/checkout - run: name: List assets run: ls assets ``` #### `eas/use_npm_token` Configures node package managers (npm, pnpm, or Yarn) for use with private packages, published either to npm or a private registry. Set `NPM_TOKEN` in your project's secrets, and this function will configure the build environment by creating **.npmrc** with the token. ```yaml example.yml build: name: Install private npm modules steps: - eas/checkout # @info # - eas/use_npm_token # @end # - run: name: Install dependencies run: npm install # <---- Can now install private packages ``` #### `eas/install_node_modules` Installs node modules using the package manager (npm, pnpm, or Yarn) detected based on your project. Works with monorepos. ```yaml example.yml build: name: Install node modules steps: - eas/checkout # @info # - eas/install_node_modules # @end # ``` #### `eas/resolve_build_config` Resolves and prints the build configuration. If the build has been triggered by the GitHub integration, it will update the current `job` and `metadata` context values. It should be called after installing the dependencies because the config may be influenced by config plugins. This function is automatically executed by the [`eas/build`](#easbuild) function group. #### `eas/get_credentials_for_build_triggered_by_github_integration` > **warning** **Deprecated:** Replace this step with [`eas/resolve_build_config`](#easresolve_build_config). #### `eas/resolve_apple_team_id_from_credentials` > **Warning** This function is only available for iOS builds. Resolves the Apple team ID value based on build credentials provided in the `inputs.credentials`. The resolved Apple team ID is stored in the `outputs.apple_team_id` output value. ```yaml example.yml build: name: Run prebuild script steps: - eas/checkout - eas/install_node_modules # @info # - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials # @end # - eas/prebuild: inputs: apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } ``` | Property | Type | Description | | -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | `string` | The name of the step in the reusable function that shows in the build logs. Defaults to `Resolve Apple team ID from credentials`. | | `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for iOS. | #### `eas/prebuild` Runs the `expo prebuild` command using the package manager (npm, pnpm, or Yarn) detected based on your project with the command best suited for your build type and build environment. ```yaml example.yml build: name: Run prebuild script steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials # @info # - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } # @end # ``` ```yaml example.yml build: name: Run prebuild script steps: - eas/checkout - eas/install_node_modules # @info # - eas/prebuild # @end # ``` | Property | Type | Description | | ---------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Prebuild`. | | `inputs.clean` | `boolean` | Optional input defining whether the function should use `--clean` flag when running the command. Defaults to false | | `inputs.apple_team_id` | `boolean` | Optional input defining Apple team ID which should be used when doing prebuild. It should be specified for iOS builds using credentials. | #### `eas/configure_eas_update` > **Warning** To use this function you need to have EAS Update configured for your project. Configures runtime version and release channel for your build. ```yaml example.yml build: name: Configure EAS Update steps: - eas/checkout - eas/install_node_modules - eas/prebuild # @info # - eas/configure_eas_update # @end # ``` ```yaml example.yml build: name: Configure EAS Update steps: - eas/checkout - eas/install_node_modules - eas/prebuild # @info # - eas/configure_eas_update: inputs: runtime_version: 1.0.0 channel: mychannel # @end # ``` | Property | Type | Description | | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure EAS Update`. | | `inputs.runtime_version` | `string` | Optional input defining runtime version which should be configured for the build. Defaults to `${ eas.job.version.runtimeVersion }` or natively defined runtime version. | | `inputs.channel` | `string` | Optional input defining channel which should be configured for the build. Defaults to `${ eas.job.updates.channel }`. | #### `eas/inject_android_credentials` > **Warning** This function is only available for Android builds. Configures Android keystore with credentials on the builder and injects app signing config using these credentials into gradle config. ```yaml example.yml build: name: Android credentials steps: - eas/checkout - eas/install_node_modules - eas/prebuild # @info # - eas/inject_android_credentials # @end # ``` | Property | Type | Description | | -------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Inject Android credentials`. | | `inputs.credentials` | `json` | Optional input defining the app credentials for your Android build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for Android. | #### `eas/configure_ios_credentials` > **Warning** This function is only available for iOS builds. Configures iOS credentials on the builder. Modifies the configuration of the Xcode project by assigning provisioning profiles to the targets. ```yaml example.yml build: name: iOS credentials steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } # @info # - eas/configure_ios_credentials # @end # ``` | Property | Type | Description | | ---------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure iOS credentials`. | | `inputs.build_configuration` | `string` | Optional input defining the Xcode project's Build Configuration. Defaults to `${ eas.job.buildConfiguration }` or if not specified is resolved to `Debug` for development client or `Release` for other builds. | | `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for iOS. | #### `eas/configure_android_version` > **Warning** This function is only available for Android builds. Configures the version of your Android app. It's used to set a version when using [remote app version management](/build-reference/app-versions/). It's not mandatory to use this function, if it's not used the version from native code generated during the prebuild phase will be used. ```yaml example.yml build: name: Configure Android version steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/configure_eas_update - eas/inject_android_credentials # @info # - eas/configure_android_version # @end # ``` ```yaml example.yml build: name: Configure Android version steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/configure_eas_update - eas/inject_android_credentials # @info # - eas/configure_android_version: inputs: version_code: '123' version_name: '1.0.0' # @end # ``` | Property | Type | Description | | --------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure Android version`. | | `inputs.version_code` | `string` | Optional input defining `versionCode` of your Android build. Defaults to `${ eas.job.version.versionCode }` | | `inputs.version_name` | `string` | Optional input defining `versionName` of your Android build. Defaults to `${ eas.job.version.versionName }`. | #### `eas/configure_ios_version` > **Warning** This function is only available for iOS builds. Configures the version of your iOS app. It's used to set a version when using [remote app version management](/build-reference/app-versions/). It's not mandatory to use this function, if it's not used the version from native code generated during the prebuild phase will be used. ```yaml example.yml build: name: Configure iOS version steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } - eas/configure_eas_update - eas/configure_ios_credentials # @info # - eas/configure_ios_version # @end # ``` ```yaml example.yml build: name: Configure iOS version steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } - eas/configure_eas_update - eas/configure_ios_credentials # @info # - eas/configure_ios_version: inputs: build_number: '123' app_version: '1.0.0' # @end # ``` | Property | Type | Description | | ---------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Configure iOS version`. | | `inputs.build_number` | `string` | Optional input defining the build number (`CFBundleVersion`) of your iOS build. Defaults to `${ eas.job.version.buildNumber }` | | `inputs.app_version` | `string` | Optional input defining the app version (`CFBundleShortVersionString`) of your iOS build. Defaults to `${ eas.job.version.appVersion }`. | | `inputs.build_configuration` | `string` | Optional input defining the Xcode project's Build Configuration. Defaults to `${ eas.job.buildConfiguration }` or if not specified is resolved to `Debug` for development client or `Release` for other builds. | | `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. Defaults to `${ eas.job.secrets.buildCredentials }`. Needs to comply to `${ eas.job.secrets.buildCredentials }` schema for iOS. | #### `eas/run_gradle` > **Warning** This function is only available for Android builds. Runs a Gradle command to build an Android app. ```yaml example.yml build: name: Build Android app steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/configure_eas_update - eas/inject_android_credentials # @info # - eas/run_gradle # @end # ``` ```yaml example.yml build: name: Build Android app steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/configure_eas_update - eas/inject_android_credentials # @info # - eas/run_gradle: inputs: command: :app:bundleRelease # @end # ``` | Property | Type | Description | | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Run gradle`. | | `inputs.command` | `string` | Optional input defining the Gradle command to run to build the Android app. If not specified it is resolved based on the build configuration and contents of the `${ eas.job }` object. | #### `eas/generate_gymfile_from_template` > **Warning** This function is only available for iOS builds. Generates a [`Gymfile`](https://docs.fastlane.tools/actions/gym/#gymfile) used to build the iOS app using Fastlane from a template. Default template used when credentials are passed: ```ruby Gymfile suppress_xcode_output(true) clean(<%- CLEAN %>) scheme("<%- SCHEME %>") <% if (BUILD_CONFIGURATION) { %> configuration("<%- BUILD_CONFIGURATION %>") <% } %> export_options({ method: "<%- EXPORT_METHOD %>", provisioningProfiles: {<% _.forEach(PROFILES, function(profile) { %> "<%- profile.BUNDLE_ID %>" => "<%- profile.UUID %>",<% }); %> }<% if (ICLOUD_CONTAINER_ENVIRONMENT) { %>, iCloudContainerEnvironment: "<%- ICLOUD_CONTAINER_ENVIRONMENT %>" <% } %> }) export_xcargs "OTHER_CODE_SIGN_FLAGS=\\"--keychain <%- KEYCHAIN_PATH %>\\"" disable_xcpretty(true) buildlog_path("<%- LOGS_DIRECTORY %>") output_directory("<%- OUTPUT_DIRECTORY %>") ``` Default template used when credentials are not passed (simulator build): ```ruby Gymfile suppress_xcode_output(true) clean(<%- CLEAN %>) scheme("<%- SCHEME %>") <% if (BUILD_CONFIGURATION) { %> configuration("<%- BUILD_CONFIGURATION %>") <% } %> derived_data_path("<%- DERIVED_DATA_PATH %>") skip_package_ipa(true) skip_archive(true) destination("<%- SCHEME_SIMULATOR_DESTINATION %>") disable_xcpretty(true) buildlog_path("<%- LOGS_DIRECTORY %>") ``` `CLEAN`, `SCHEME`, `BUILD_CONFIGURATION`, `EXPORT_METHOD`, `PROFILES`, `ICLOUD_CONTAINER_ENVIRONMENT`, `KEYCHAIN_PATH`, `LOGS_DIRECTORY`, `OUTPUT_DIRECTORY`, `DERIVED_DATA_PATH`and `SCHEME_SIMULATOR_DESTINATION` values are provided to the template based on the inputs and default internal configuration of EAS Build. ```yaml example.yml build: name: Generate Gymfile template steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } - eas/configure_eas_update - eas/configure_ios_credentials # @info # - eas/generate_gymfile_from_template: inputs: credentials: ${ eas.job.secrets.buildCredentials } # @end # ``` ```yaml example.yml build: name: Generate Gymfile template steps: - eas/checkout - eas/install_node_modules - eas/prebuild # @info # - eas/generate_gymfile_from_template # @end # ``` However, you can also use other custom properties in the template, by specifying your custom template in `inputs.template` and providing the values for the custom properties in the `inputs.extra` object. ```yaml example.yml build: name: Generate Gymfile template steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } - eas/configure_eas_update - eas/configure_ios_credentials # @info # - eas/generate_gymfile_from_template: inputs: credentials: ${ eas.job.secrets.buildCredentials } extra: MY_VALUE: my value template: | suppress_xcode_output(true) clean(<%- CLEAN %>) scheme("<%- SCHEME %>") <% if (BUILD_CONFIGURATION) { %> configuration("<%- BUILD_CONFIGURATION %>") <% } %> export_options({ method: "<%- EXPORT_METHOD %>", provisioningProfiles: {<% _.forEach(PROFILES, function(profile) { %> "<%- profile.BUNDLE_ID %>" => "<%- profile.UUID %>",<% }); %> }<% if (ICLOUD_CONTAINER_ENVIRONMENT) { %>, iCloudContainerEnvironment: "<%- ICLOUD_CONTAINER_ENVIRONMENT %>" <% } %> }) export_xcargs "OTHER_CODE_SIGN_FLAGS=\"--keychain <%- KEYCHAIN_PATH %>\"" disable_xcpretty(true) buildlog_path("<%- LOGS_DIRECTORY %>") output_directory("<%- OUTPUT_DIRECTORY %>") sth_else("<%- MY_VALUE %>") # @end # ``` | Property | Type | Description | | ---------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `name` | - | The name of the step in the reusable function that shows in the build logs. Defaults to `Generate Gymfile from template`. | | `inputs.template` | `string` | Optional input defining the Gymfile template which should be used. If not specified one out of two default templates will be used depending on whether the `inputs.credentials` value is specified. | | `inputs.credentials` | `json` | Optional input defining the app credentials for your iOS build. If specified `KEYCHAIN_PATH`, `EXPORT_METHOD`, and `PROFILES` values will be provided to the template. | | `inputs.build_configuration` | `string` | Optional input defining the Xcode project's Build Configuration. Defaults to `${ eas.job.buildConfiguration }` or if not specified is resolved to `Debug` for development client or `Release` for other builds. Corresponds to the `BUILD_CONFIGURATION` template value. | | `inputs.scheme` | `string` | Optional input defining the Xcode project's scheme which should be used for the build. Defaults to `${ eas.job.scheme }` or if not specified is resolved to the first scheme found in the Xcode project. Corresponds to the `SCHEME` template value. | | `inputs.clean` | `boolean` | Optional input defining whether the Xcode project should be cleaned before the build. Defaults to `true`. Corresponds to `CLEAN` template variable. | | `inputs.extra` | `json` | Optional input defining extra values which should be provided to the template. | #### `eas/run_fastlane` > **Warning** This function is only available for iOS builds. Runs [`fastlane gym`](https://docs.fastlane.tools/actions/gym/#gym) command against the [`Gymfile`](https://docs.fastlane.tools/actions/gym/#gymfile) located in the `ios` project directory to build the iOS app. ```yaml example.yml build: name: Build iOS app steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } - eas/configure_eas_update - eas/configure_ios_credentials - eas/generate_gymfile_from_template: inputs: credentials: ${ eas.job.secrets.buildCredentials } # @info # - eas/run_fastlane # @end # ``` ```yaml example.yml build: name: Build iOS app steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/configure_eas_update - eas/generate_gymfile_from_template # @info # - eas/run_fastlane # @end # ``` #### `eas/find_and_upload_build_artifacts` > **Warning** **You can currently upload each artifact type only once per build job.**If you use [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) while having [`buildArtifactPaths`](/eas/json/#buildartifactpaths) configured in your build profile and the step finds and uploads some build artifacts, any following `eas/upload_artifact` step will fail.To solve this, for now, we recommend removing `buildArtifactPaths` from custom build's profiles and uploading artifacts manually with `eas/upload_artifact` in the YAML if you need to call it there. Automatically finds and uploads application archive, additional build artifacts, and Xcode logs from the default locations and using the [`buildArtifactPaths`](/eas/json#buildartifactpaths) configuration. Uploads found artifacts to the EAS servers. ```yaml example.yml|collapseHeight=440 build: name: Build iOS app steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: clean: false apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } - eas/configure_eas_update - eas/configure_ios_credentials - eas/generate_gymfile_from_template: inputs: credentials: ${ eas.job.secrets.buildCredentials } - eas/run_fastlane # @info # - eas/find_and_upload_build_artifacts # @end # ``` ```yaml example.yml build: name: Build iOS app steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/configure_eas_update - eas/generate_gymfile_from_template - eas/run_fastlane # @info # - eas/find_and_upload_build_artifacts # @end # ``` ```yaml example.yml build: name: Build Android app steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/configure_eas_update - eas/inject_android_credentials - eas/run_gradle - eas/find_and_upload_build_artifacts ``` #### `eas/upload_artifact` Uploads build artifacts from provided paths. > **Warning** **You can currently upload each artifact type only once per build job.**If you use [`eas/find_and_upload_build_artifacts`](#easfind_and_upload_build_artifacts) while having [`buildArtifactPaths`](/eas/json/#buildartifactpaths) configured in your build profile and the step finds and uploads some build artifacts, any following `eas/upload_artifact` step will fail.To solve this, for now, we recommend removing `buildArtifactPaths` from custom build's profiles and uploading artifacts manually with `eas/upload_artifact` in the YAML if you need to call it there. For example, a build config with the following `steps` will upload an artifact to the EAS servers: ```yaml upload.yml build: name: Upload artifacts steps: - eas/checkout # - ... - eas/upload_artifact: name: Upload application archive inputs: path: fixtures/app-debug.apk - eas/upload_artifact: name: Upload artifacts inputs: type: build-artifact path: | assets/*.jpg assets/*.png ``` | Input | Type | Description | | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `path` | `string` | Required. Path or newline-delimited list of paths to the artifacts to upload to EAS servers. You can use `*` wildcard and other [glob patterns that `fast-glob` supports](https://github.com/mrmlnc/fast-glob#pattern-syntax). | | `type` | `string` | The type of artifact that is uploaded to the EAS servers. Allowed values are `application-archive` and `build-artifact`. Defaults to `application-archive`. | #### `eas/install_maestro` Makes sure [Maestro](https://maestro.dev/), the mobile UI testing framework, is installed along with all its dependencies. ```yaml build-and-test.yml build: name: Build and test steps: - eas/build # ... simulator/emulator setup # @info # - eas/install_maestro: # @end # inputs: # @info If you need to, you can provide the Maestro version to install. # maestro_version: 1.35.0 # @end # - run: command: maestro test flows/signin.yml - eas/upload_artifact: name: Upload Maestro artifacts inputs: type: build-artifact path: ${ eas.env.HOME }/.maestro/tests ``` | Input | Type | Description | | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | `maestro_version` | `string` | Maestro version to install (for example, 1.35.0). If not provided, `install_maestro` will install the latest version. | #### `eas/start_android_emulator` Starts an Android Emulator you can use to test your apps on. Only available when running a build for Android. > **warning** Your project must be configured to use the old Build Infrastructure to start Android Emulator. Go to [Project settings](https://expo.dev/accounts/[account]/projects/[project]/settings) to configure. See [this changelog post](https://expo.dev/changelog/2024/08-29-c3d-default) for more information. ```yaml build-and-test.yml build: name: Build and test steps: - eas/build # @info # - eas/start_android_emulator: inputs: system_image_package: system-images;android-30;default;x86_64 # @end # # ... Maestro setup and tests ``` | Input | Type | Description | | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `device_name` | `string` | Name for the created device. You can customize it if starting multiple emulators. | | `system_image_package` | `string` | Android package path to use for the emulator. For example, `system-images;android-30;default;x86_64`.To get a list of available system images, run [`sdkmanager --list`](https://developer.android.com/tools/sdkmanager#list) on a local computer. VMs run on x86_64 architecture, so always choose `x86_64` package variants. The [`sdkmanager` tool](https://developer.android.com/tools/sdkmanager) comes from Android SDK command-line tools. | #### `eas/start_ios_simulator` Starts an iOS Simulator you can use to test your apps on. Only available when running a build for iOS. ```yaml build-and-test.yml build: name: Build and test steps: - eas/build # @info # - eas/start_ios_simulator # @end # # ... Maestro setup and tests ``` | Input | Type | Description | | ------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `device_identifier` | `string` | Name or UDID of the Simulator you want to start. Examples include `iPhone [XY] Pro`, `AEF997BB-222C-4379-89BA-D21070B1D787`.**Note:** Available Simulators are different for every image. If you change the image, the Simulator for a given name may become unavailable. For instance, an Xcode 14 image will have iPhone 14 Simulators, while an Xcode 15 image will have iPhone 15 simulators. In general, we encourage not providing this input. See [runner images](/build/eas-json/#selecting-a-base-image) for more information. | #### `eas/send_slack_message` Sends a specified message to a configured [Slack webhook URL](https://api.slack.com/messaging/webhooks), which then posts it in the related Slack channel. The message can be specified as plain text or as a [Slack Block Kit](https://api.slack.com/block-kit) message. With both cases, you can reference build job properties and [use other steps outputs](#use-output-from-one-step-to-another) in the message for dynamic evaluation. For example, `'Build URL: ${ eas.job.expoBuildUrl }'`, `Build finished with status: ${ steps.run_fastlane.status_text }`, `Build failed with error: ${ steps.run_gradle.error_text }`. Either "message" or "payload" has to be specified, but not both. ```yaml send-slack-message.yml build: name: Slack your team from custom build steps: # @info # - eas/send_slack_message: name: Send Slack message to a given webhook URL inputs: message: 'This is a message to plain input URL' slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]' # @end # - eas/send_slack_message: name: Send Slack message to a default webhook URL from SLACK_HOOK_URL secret inputs: message: 'This is a test message to default URL from SLACK_HOOK_URL secret' - eas/send_slack_message: name: Send Slack message to a webhook URL from specified secret inputs: message: 'This is a test message to a URL from specified secret' slack_hook_url: ${ eas.env.ANOTHER_SLACK_HOOK_URL } - eas/build - eas/send_slack_message: if: ${ always() } name: Send Slack message when the build finishes (Android) inputs: message: | This is a test message when Android build finishes Status: `${ steps.run_gradle.status_text }` Link: `${ eas.job.expoBuildUrl }` - eas/send_slack_message: if: ${ always() } name: Send Slack message when the build finishes (iOS) inputs: message: | This is a test message when iOS build finishes Status: `${ steps.run_fastlane.status_text }` Link: `${ eas.job.expoBuildUrl }` - eas/send_slack_message: if: ${ failure() } name: Send Slack message when the build fails (Android) inputs: message: | This is a test message when Android build fails Error: `${ steps.run_gradle.error_text }` - eas/send_slack_message: if: ${ failure() } name: Send Slack message when the build fails (iOS) inputs: message: | This is a test message when iOS build fails Error: `${ steps.run_fastlane.error_text }` - eas/send_slack_message: if: ${ success() } name: Send Slack message when the build succeeds inputs: message: | This is a test message when build succeeds - eas/send_slack_message: if: ${ always() } name: Send Slack message with Slack Block Kit layout inputs: payload: blocks: - type: section text: type: mrkdwn text: |- Hello, Sir Developer *Your build has finished!* - type: divider - type: section text: type: mrkdwn text: |- *${ eas.env.EAS_BUILD_ID }* *Status:* `${ steps.run_gradle.status_text }` *Link:* `${ eas.job.expoBuildUrl }` accessory: type: image image_url: [your_image_url] alt_text: alt text for image - type: divider - type: actions elements: - type: button text: type: plain_text text: 'Do a thing :rocket:' emoji: true value: a_thing - type: button text: type: plain_text text: 'Do another thing :x:' emoji: true value: another_thing ``` | Input | Type | Description | | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `message` | `string` | The text of the message you want to send. For example, `'This is the content of the message'`. **Note:** Either `message` or `payload` needs to be provided, but not both. | | `payload` | `string` | The contents of the message you want to send defined using [Slack Block Kit](https://api.slack.com/block-kit) layout. **Note:** Either `message` or `payload` needs to be provided, but not both. | | `slack_hook_url` | `string` | The URL of the previously configured Slack webhook URL, which will post your message to the specified channel. You can provide the plain URL like `slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]'`, use EAS secrets like `slack_hook_url: ${ eas.env.ANOTHER_SLACK_HOOK_URL }`, or set the `SLACK_HOOK_URL` secret, which will serve as a default webhook URL (in this last case, there is no need to provide `slack_hook_url` input). | ### Using built-in EAS functions to build an app Using the built-in EAS functions you can recreate the default EAS Build process for different build types. For example, to trigger a build that creates internal distribution build for Android and a simulator build for iOS you can use the following configuration: ```json eas.json|collapseHeight=420 { "build": { "developmentBuild": { "distribution": "internal", "android": { "config": "development-build-android.yml" }, "ios": { "simulator": true, "config": "development-build-ios.yml" } } } } ``` ```yaml .eas/build/development-build-android.yml build: name: Simple internal distribution Android build steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/inject_android_credentials - eas/run_gradle - eas/find_and_upload_build_artifacts ``` ```yaml .eas/build/development-build-ios.yml|collapseHeight=440 build: name: Simple simulator iOS build steps: - eas/checkout - eas/install_node_modules - eas/prebuild - run: name: Install pods working_directory: ./ios command: pod install - eas/generate_gymfile_from_template - eas/run_fastlane - eas/find_and_upload_build_artifacts ``` To create a Google Play Store build for Android and an Apple App Store build for iOS you can use the following configuration: ```json eas.json { "build": { "productionBuild": { "android": { "config": "production-build-android.yml" }, "ios": { "config": "production-build-ios.yml" } } } } ``` ```yaml .eas/build/production-build-android.yml build: name: Customized Android Play Store build example steps: - eas/checkout - eas/install_node_modules - eas/prebuild - eas/inject_android_credentials - eas/run_gradle - eas/find_and_upload_build_artifacts ``` ```yaml .eas/build/production-build-ios.yml build: name: Customized iOS App Store build example steps: - eas/checkout - eas/install_node_modules - eas/resolve_apple_team_id_from_credentials: id: resolve_apple_team_id_from_credentials - eas/prebuild: inputs: apple_team_id: ${ steps.resolve_apple_team_id_from_credentials.apple_team_id } - run: name: Install pods working_directory: ./ios command: pod install - eas/configure_ios_credentials - eas/generate_gymfile_from_template: inputs: credentials: ${ eas.job.secrets.buildCredentials } - eas/run_fastlane - eas/find_and_upload_build_artifacts ``` Check out the **example repository** for more detailed examples: ### Use a reusable function in a `build` For example, a custom build config with the following reusable function contains a single command to print a message that is echoed. ```yaml functions: greetings: - name: name default_value: Hello world inputs: [value] command: echo "${ inputs.name }, { inputs.value }" ``` The above function can be used in a `build` as follows: ```yaml build: name: Functions Demo steps: # @info # - greetings: inputs: value: Expo # @end # ``` > info 提示:`build.steps` 可以顺序执行多个可复用的 `functions`。 ¥**info** **Tip:** `build.steps` can execute multiple reusable `functions` sequentially. ## 覆盖 `build` 中的值 ¥Override values in a `build` 你可以覆盖以下属性的值: ¥You can override values for the following properties: * `working_directory` * `name` * `shell` 例如,一个名为 `list_files` 的可重用函数: ¥For example, a reusable function called `list_files`: ```yaml functions: # @info # list_files: name: List files command: ls -la # @end # ``` 当在构建配置中调用 `list_files` 时,它会列出项目根目录中的所有文件: ¥When `list_files` is called in a build config, it lists all files in the root directory of a project: ```yaml build: name: List files # @info # steps: - eas/checkout - list_files # @end # ``` 你可以使用 `working_directory` 属性覆盖函数调用中的行为,通过指定该目录的路径来列出不同目录中的文件: ¥You can use the `working_directory` property to override the behavior in the function call to list the files in a different directory by specifying the path to that directory: ```yaml build: name: List files steps: # @info # - eas/checkout - list_files: working_directory: /a/b/c # @end # ``` ## TypeScript 函数 了解如何在自定义构建配置中创建和使用 EAS 构建功能。 EAS 构建功能是扩展自定义构建功能的好方法。你可以使用它们创建可重用的步骤,并用 JavaScript、TypeScript 或 Bash 编写逻辑(更多信息请参见 [配置模式中的 `command`](./schema.mdx#functionsfunction_namecommand))。本指南提供了在 TypeScript 中创建函数的演练。 ¥EAS Build functions are a great way to extend the functionality of custom builds. You can use them to create reusable steps, and to write your logic in JavaScript, TypeScript, or Bash (more information in [`command` in the config schema](./schema.mdx#functionsfunction_namecommand)). This guide provides a walkthrough of creating a function in TypeScript. Step 1: ## Initialize an EAS Build function module The easiest way to create an EAS Build function is to use the `create-eas-build-function` CLI tool. By running the following command from the same directory as your **eas.json** file, you can create a new custom TypeScript function: ```sh $ npx create-eas-build-function@latest ./.eas/build/myFunction ``` This creates a new module called `myFunction` in the **.eas/build** directory. The module will contain a pre-generated module configuration and the **src** directory with the **index.ts** file containing the default TypeScript function template. ```ts .eas/build/myFunction/src/index.ts // This file was autogenerated by `create-eas-build-function` command. // Go to README.md to learn more about how to write your own custom build functions. import { BuildStepContext } from '@expo/steps'; // interface FunctionInputs { // // specify the type of the inputs value and whether they are required here // // example: name: BuildStepInput; // } // interface FunctionOutputs { // // specify the function outputs and whether they are required here // // example: name: BuildStepOutput; // } async function myFunction( ctx: BuildStepContext // { // inputs, // outputs, // env, // }: { // inputs: FunctionInputs; // outputs: FunctionOutputs; // env: BuildStepEnv; // } ): Promise { ctx.logger.info('Hello from my TypeScript function!'); } export default myFunction; ``` Step 2: ## Compile the function Functions must be compiled to a single JavaScript file that can be run without installing any dependencies. The default `build` script for generated functions uses [ncc](https://github.com/vercel/ncc) to compile your function into a single file with all its dependencies. If you don't have the `ncc` installed globally on your machine, run `npm install -g @vercel/ncc` to install it. Next, run the build script in the **.eas/build/myFunction** directory: ```sh $ npm run build ``` This command triggers the `build` script placed in the **package.json** file of your custom function module. ```json package.json { ... "scripts": { ... ... }, ... } ``` The `build` script generates **build/index.js**. This file must be uploaded to EAS Build as a part of your project archive, so that the builder can run your function. Ensure that the file is not excluded by a **.gitignore** file or **.easignore** file. Step 3: ## Expose the function to the custom build config > **Note**: The following example assumes that you have already set up a custom build workflow and configured it in your **eas.json**. If not, see [Get started with custom builds](/custom-builds/get-started/#create-a-workflow) before proceeding. Let's assume that you have a **config.yml** file in the **.eas/build** directory. It contains the following content: ```yaml .eas/build/config.yml build: name: My example config steps: - eas/checkout - eas/install_node_modules - run: name: Finished command: echo Finished ``` To add your function to the config, you need to add the following lines to the **config.yml** file: ```yaml .eas/build/config.yml build: name: My example config steps: - eas/checkout - eas/install_node_modules - run: name: Finished command: echo Finished # @info # functions: my_function: name: My function path: ./myFunction # @end # ``` The `path` property should be a relative path from the config file to your function directory. In this case, it's just `./myFunction`. Now, add a call to the `my_function` function in the **config.yml** file: ```yaml .eas/build/config.yml build: name: My example config steps: - eas/checkout - eas/install_node_modules # @info # - my_function # @end # - run: name: Finished command: echo Finished functions: my_function: name: My function path: ./myFunction ``` Step 4: ## Working on the function For a more advanced example, let's say you want to make a function calculate the sum of two numbers and print the result to the console, and then output that value from the function. To do this, modify the **config.yml** and **index.ts** files to make the function accept two inputs called `num1` and `num2` and return their sum as an output called `sum`. ```yaml .eas/build/config.yml build: name: My example config steps: - eas/checkout - eas/install_node_modules # @info # - my_function: inputs: num1: 1 num2: 2 id: sum_function # @end # - run: name: Print the sum inputs: sum: ${ steps.sum_function.sum } command: echo ${ inputs.sum } - run: name: Finished command: echo Finished functions: my_function: name: My function # @info # inputs: - name: num1 type: number - name: num2 type: number outputs: - name: sum # @end # path: ./myFunction ``` ```ts .eas/build/myFunction/src/index.ts // This file was autogenerated by `create-eas-build-function` command. // Go to README.md to learn more about how to write your own custom build functions. import { BuildStepContext, BuildStepInput, BuildStepInputValueTypeName, BuildStepOutput, } from '@expo/steps'; interface FunctionInputs { // first template argument is the type of the input value, second template argument is a boolean indicating if the input is required num1: BuildStepInput; num2: BuildStepInput; } interface FunctionOutputs { // template argument is a boolean indicating if the output is required sum: BuildStepOutput; } async function myFunction( ctx: BuildStepContext, { inputs, outputs, }: // env, { inputs: FunctionInputs; outputs: FunctionOutputs; // env: BuildStepEnv; } ): Promise { ctx.logger.info(`num1: ${inputs.num1.value}`); ctx.logger.info(`num2: ${inputs.num2.value}`); const sum = inputs.num1.value + inputs.num2.value; ctx.logger.info(`sum: ${sum}`); outputs.sum.set(sum.toString()); // Currently, outputs must be strings. This will improve in the future. } export default myFunction; ``` > **Info** Remember to compile your function each time you make changes to it: `npm run build`. ## 概括 ¥Summary * 编写函数是使用你自己的逻辑扩展自定义构建功能的好方法。 ¥Writing functions is a great way to extend the functionality of custom builds with your own logic. * EAS 构建功能是可重用的 - 你可以在多个自定义构建配置中使用它们。 ¥EAS Build functions are reusable — you can use them in multiple custom build configurations. * 对于通过编写 shell 脚本不容易完成的更高级用例来说,使用 EAS 构建函数是一个不错的选择。 ¥Using EAS Build functions is a great option for more advanced use cases that are not easy to do by writing shell scripts. * [内置函数](/custom-builds/schema/#built-in-eas-functions) 的大部分都是开源的,可以分叉或用作编写自己的函数的参考。 ¥Most of the [built-in functions](/custom-builds/schema/#built-in-eas-functions) are open-source and can be forked or used as a reference for writing your own functions. 查看示例存储库以获取更详细的示例: ¥Check out the **example repository** for more detailed examples: # 参考 ## 构建生命周期钩子 了解如何将 EAS Build 生命周期钩子与 npm 结合使用来自定义构建过程。 EAS Build 生命周期 npm hooks 允许你通过在构建过程之前或之后运行脚本来自定义构建过程。 ¥EAS Build lifecycle npm hooks allows you to customize your build process by running scripts before or after the build process. > 为了更好地理解,请参阅 [安卓构建流程](/build-reference/android-builds/) 和 [iOS 构建过程](/build-reference/ios-builds/)。 > > ¥For better understanding, see the [Android build process](/build-reference/android-builds/) and the [iOS build process](/build-reference/ios-builds/). > **warning** [自定义构建](/custom-builds/get-started/) 中的构建过程不执行生命周期钩子。它们需要在过程中由构建步骤手动提取和调用。 > > ¥The lifecycle hooks are not executed by the build process in [custom builds](/custom-builds/get-started/). They need to be manually extracted and called by the build steps during the process. ## EAS Build 生命周期钩子 ¥EAS Build lifecycle hooks 有六个 EAS Build 生命周期 npm 钩子可用。要使用它们,你可以在 package.json 中设置它们。 ¥There are six EAS Build lifecycle npm hooks available. To use, them, you can set them in your **package.json**. | 构建生命周期 npm 钩子 | 描述 | | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `eas-build-pre-install` | 在 EAS Build 运行 `npm install` 之前执行。 | | `eas-build-post-install` | 行为取决于平台和项目类型。 对于 Android,在以下命令全部完成后运行一次:`npm install` 和 `npx expo prebuild`(如果需要)。 对于 iOS,在以下命令全部完成后运行一次:`npm install`、`npx expo prebuild`(如果需要)和 `pod install`。 | | `eas-build-on-success` | 如果构建成功,则在构建过程结束时触发此钩子。 | | `eas-build-on-error` | 如果构建失败,则在构建过程结束时触发此钩子。 | | `eas-build-on-complete` | 此钩子在构建过程结束时触发。你可以使用 `EAS_BUILD_STATUS` 环境变量检查构建的状态。要么是 `finished`,要么是 `errored`。 | | `eas-build-on-cancel` | 如果取消构建,则会触发此钩子。 | 使用一个或多个生命周期钩子时 package.json 的外观示例: ¥An example of how a **package.json** can look when using one or more lifecycle hooks: ```json package.json { "name": "my-app", "scripts": { "eas-build-pre-install": "echo 123", "eas-build-post-install": "echo 456", "eas-build-on-success": "echo 789", "eas-build-on-error": "echo 012", "eas-build-on-cancel": "echo 345", "start": "expo start", "test": "jest" }, "dependencies": { "expo": "51.0.0" } } ``` ## 特定于平台的钩子行为 ¥Platform-specific hook behavior 要仅为 Android 或 iOS 版本运行脚本(或脚本的某些部分),你可以根据脚本中的平台分叉行为。请参阅以下常见示例,通过 shell 脚本或 Node 脚本执行此操作。 ¥To run a script (or some part of a script) only for Android or iOS builds, you can fork the behavior depending on the platform within the script. See the following common examples to do this through a shell script or a Node script. ### 示例 ¥Examples #### package.json 和 shell 脚本 ¥package.json and shell script ```json package.json { "name": "my-app", "scripts": { "eas-build-pre-install": "./pre-install", "start": "expo start" }, "dependencies": { } } ``` ```bash pre-install #!/bin/bash # This is a file called "pre-install" in the root of the project if [[ "$EAS_BUILD_PLATFORM" == "android" ]]; then echo "Run commands for Android builds here" elif [[ "$EAS_BUILD_PLATFORM" == "ios" ]]; then echo "Run commands for iOS builds here" fi ``` Note: Example: Pre-install script that installs on macOS workers --- The following script installs [`git-lfs`](https://git-lfs.com/) if it is not yet installed. This is useful in some cases where `git-lfs` is required to install certain CocoaPods. ```bash pre-install if [[ "$EAS_BUILD_PLATFORM" == "ios" ]]; then if brew list git-lfs > /dev/null 2>&1; then echo "=====> git-lfs is already installed." else echo "=====> Installing git-lfs" HOMEBREW_NO_AUTO_UPDATE=1 brew install git-lfs git lfs install fi fi ``` --- #### package.json 和 Node 脚本 ¥package.json and Node script ```json package.json { "name": "my-app", "scripts": { "eas-build-pre-install": "node pre-install.js", "start": "expo start" // ... }, "dependencies": { // ... } } ``` ```js pre-install.js // Create a file called "pre-install.js" at the root of the project if (process.env.EAS_BUILD_PLATFORM === 'android') { console.log('Run commands for Android builds here'); } else if (process.env.EAS_BUILD_PLATFORM === 'ios') { console.log('Run commands for iOS builds here'); } ``` ## 使用私有 npm 包 了解如何配置 EAS Build 以使用私有 npm 包。 EAS Build 完全支持在项目中使用私有 npm 包。这些可以发布到 npm(如果你有 [专业/团队计划](https://www.npmjs.com/products))或私有注册表(例如,使用自托管 [Verdaccio](https://verdaccio.org/))。 ¥EAS Build has full support for using private npm packages in your project. These can either be published to npm (if you have [the Pro/Teams plan](https://www.npmjs.com/products)) or to a private registry (for example, using self-hosted [Verdaccio](https://verdaccio.org/)). 在开始构建之前,你需要配置项目以向 EAS Build 提供你的 npm 令牌。 ¥Before starting the build, you will need to configure your project to provide EAS Build with your npm token. ## 默认 npm 配置 ¥Default npm configuration 默认情况下,EAS Build 使用自托管的 npm 缓存,可以加快所有构建的依赖安装速度。每个 EAS Build 构建器都为每个平台配置了一个 .npmrc 文件: ¥By default, EAS Build uses a self-hosted npm cache that speeds up installing dependencies for all builds. Every EAS Build builder is configured with a **.npmrc** file for each platform: ### 安卓 ¥Android ```ini registry=http://npm-cache-service.worker-infra-production.svc.cluster.local:4873 ``` ### iOS 系统 ¥iOS ```ini registry=http://10.254.24.8:4873 ``` ## 私有包发布到 npm ¥Private packages published to npm 如果你的项目使用发布到 npm 的私有包,你需要提供带有 [只读 npm 令牌](https://npm.nodejs.cn/about-access-tokens) 的 EAS Build,以便它可以成功安装你的依赖。 ¥If your project is using private packages published to npm, you need to provide EAS Build with [a read-only npm token](https://npm.nodejs.cn/about-access-tokens) so that it can install your dependencies successfully. 推荐的方法是将 `NPM_TOKEN` 密钥添加到你的账户或项目的密钥中: ¥The recommended way is to add the `NPM_TOKEN` secret to your account or project's secrets: 有关如何执行此操作的更多信息,请参阅 [秘密环境变量](/build-reference/variables/#secrets-on-the-expo-website)。 ¥For more information on how to do that, see [secret environment variables](/build-reference/variables/#secrets-on-the-expo-website). 当 EAS 在构建过程中检测到 `NPM_TOKEN` 环境变量可用时,它会自动创建以下 .npmrc: ¥When EAS detects that the `NPM_TOKEN` environment variable is available during a build, it automatically creates the following **.npmrc**: ```ini .npmrc //registry.npmjs.org/:_authToken=${NPM_TOKEN} registry=https://registry.npmjs.org/ ``` 但是,只有当 .npmrc 不在项目的根目录中时才会发生这种情况。如果你已有此文件,则需要手动更新。 ¥However, this only happens when **.npmrc** is not in your project's root directory. If you already have this file, you need to update it manually. 你可以通过查看构建日志并查找准备项目构建阶段来验证它是否有效: ¥You can verify if it worked by viewing build logs and looking for the **Prepare project** build phase: ## 发布到私有注册表的包 ¥Packages published to a private registry 如果你使用私有 npm 注册表(例如自托管 [Verdaccio](https://verdaccio.org/)),则需要手动配置 .npmrc。 ¥If you're using a private npm registry such as self-hosted [Verdaccio](https://verdaccio.org/), you will need to configure the **.npmrc** manually. 在项目的根目录中创建一个包含以下内容的 .npmrc 文件: ¥Create a **.npmrc** file in your project's root directory with the following contents: ```ini .npmrc registry=__REPLACE_WITH_REGISTRY_URL__ ``` 如果你的注册表需要身份验证,你将需要提供令牌。例如,如果你的注册表 URL 是 `https://registry.johndoe.com/`,则使用以下命令更新文件: ¥If your registry requires authentication, you will need to provide the token. For example, if your registry URL is `https://registry.johndoe.com/`, then update the file with: ```ini .npmrc //registry.johndoe.com/:_authToken=${NPM_TOKEN} registry=https://registry.johndoe.com/ ``` ## 私有 npm 包和私有注册表 ¥Both private npm packages and private registry > 这是一个高级示例。 > > ¥This is an advanced example. 私有 npm 包始终是 [scoped](https://npm.nodejs.cn/about-scopes#scopes-and-package-visibility)。例如,如果你的 npm 用户名是 `johndoe`,则私有自托管注册表 URL 是 `https://registry.johndoe.com/`。如果要从两个源安装依赖,请使用以下内容在项目的根目录中创建 .npmrc: ¥Private npm packages are always [scoped](https://npm.nodejs.cn/about-scopes#scopes-and-package-visibility). For example, if your npm username is `johndoe`, the private self-hosted registry URL is `https://registry.johndoe.com/`. If you want to install dependencies from both sources, create a **.npmrc** in your project's root directory with the following: ```ini .npmrc //registry.npmjs.org/:_authToken=${NPM_TOKEN} @johndoe:registry=https://registry.npmjs.org/ registry=https://registry.johndoe.com/ ``` ## 私有存储库中的子模块 ¥Submodules in private repositories 如果私有存储库中有子模块,则需要通过设置 SSH 密钥来初始化它。欲了解更多信息,请参阅 [子模块初始化](/build-reference/git-submodules/#submodules-initialization)。 ¥If you have a submodule in a private repository, you will need to initialize it by setting up an SSH key. For more information, see [submodules initialization](/build-reference/git-submodules/#submodules-initialization). ## 使用 Git 子模块 了解如何配置 EAS Build 以使用 git 子模块。 使用默认版本控制系统 (VCS) 工作流程时,工作目录的内容将按原样上传到 EAS Build,包括 Git 子模块的内容。但是,如果你正在 CI 上构建或在 eas.json 中将 `cli.requireCommit` 设置为 `true` 或在私有存储库中有子模块,则需要对其进行初始化以避免上传空目录。 ¥When using the default Version Control Systems (VCS) workflow, the content of your working directory is uploaded to EAS Build as it is, including the content of Git submodules. However, if you are building on CI or have `cli.requireCommit` set to `true` in **eas.json** or have a submodule in a private repository, you will need to initialize it to avoid uploading empty directories. ## 子模块初始化 ¥Submodules initialization 要在 EAS Build 构建器上初始化子模块: ¥To initialize a submodule on EAS Build builder: Step 1: Create a [secret](/build-reference/variables/#using-secrets-in-environment-variables) with a base64 encoded private SSH key that has permission to access submodule repositories. Step 2: Add an [`eas-build-pre-install` npm hook](/build-reference/npm-hooks/) to check out those submodules, for example: !!!IG0!!! ## 使用 Yarn 1 (Classic) 的 npm 缓存 了解如何通过覆盖 Yarn 1 (Classic) 中的注册表来使用 npm 缓存。 默认情况下,EAS npm 缓存不适用于 Yarn 1(Classic),因为 yarn.lock 文件包含每个库的注册表 URL。Yarn 1 没有提供任何方法来覆盖它,Yarn 团队不打算在 Yarn 1 中支持它。但是,此问题已在 Yarn 2+ 中修复。 ¥By default, the EAS npm cache won't work with Yarn 1 (Classic) because **yarn.lock** files contain URLs to registries for every library. Yarn 1 does not provide any way to override it and Yarn team does not plan to support it in Yarn 1. However, this issue is fixed in Yarn 2+. 如果你想利用 Yarn 1 的 npm 缓存,请在 package.json 中添加 [`eas-build-pre-install` npm 钩子](/build-reference/npm-hooks/) 以覆盖 yarn.lock 中的注册表: ¥If you want to take advantage of the npm cache with Yarn 1, add the [`eas-build-pre-install` npm hook](/build-reference/npm-hooks/) in **package.json** to override the registry in the **yarn.lock**: ```json package.json { "scripts": { "eas-build-pre-install": "bash -c \"[ ! -z \\\"$EAS_BUILD_NPM_CACHE_URL\\\" ] && sed -i -e \\\"s#https://registry.yarnpkg.com#$EAS_BUILD_NPM_CACHE_URL#g\\\" yarn.lock\" || true" } } ``` ## 使用 monorepo 设置 EAS Build 了解如何使用 monorepo 设置 EAS Build。 要使用 monorepo 设置 EAS Build,你需要按照如下所述的标准流程进行操作: ¥To set up EAS Build with a monorepo, you need to follow the standard process as described below: * 从应用目录的根运行所有 EAS CLI 命令。例如,如果你的项目存在于 apps/my-app 的 git 存储库中,则从那里运行 `eas build`。 ¥Run all EAS CLI commands from the root of the app directory. For example, if your project exists inside your git repository at **apps/my-app**, then run `eas build` from there. * 与 EAS Build 相关的所有文件(例如 eas.json 和 credentials.json)应位于应用目录的根目录中。如果你的 monorepo 中有多个使用 EAS Build 的应用,则每个应用目录都将拥有自己的这些文件副本。 ¥All files related to EAS Build, such as **eas.json** and **credentials.json**, should be in the root of the app directory. If you have multiple apps that use EAS Build in your monorepo, each app directory will have its own copy of these files. * 如果你正在 monorepo 中构建托管项目,请参阅 [与 Monorepos 合作](/guides/monorepos) 指南。 ¥**If you are building a managed project in a monorepo**, see [Working with Monorepos](/guides/monorepos) guide. * 如果你的项目需要超出所提供的设置之外的其他设置,请向项目中的 package.json 添加 `postinstall` 步骤,以在其他工作区中构建所有必要的依赖。例如: ¥If your project needs additional setup beyond what is provided, add a `postinstall` step to **package.json** in your project that builds all necessary dependencies in other workspaces. For example: ```json package.json { "scripts": { "postinstall": "cd ../.. && yarn build" } } ``` ## 为 Android 模拟器和设备构建 APK 了解如何在使用 EAS Build 时为 Android 模拟器和设备配置和安装 .apk。 使用 EAS Build 构建 Android 应用时使用的默认文件格式是 [安卓应用包](https://developer.android.com/platform/technology/app-bundle) (AAB/.aab)。此格式针对分发到 Google Play 商店进行了优化。但是,AAB 无法直接安装在你的设备上。要将构建直接安装到 Android 设备或模拟器,你需要构建 [安卓包](https://en.wikipedia.org/wiki/Android_application_package) (APK/.apk)。 ¥The default file format used when building Android apps with EAS Build is an [Android App Bundle](https://developer.android.com/platform/technology/app-bundle) (AAB/**.aab**). This format is optimized for distribution to the Google Play Store. However, AABs can't be installed directly on your device. To install a build directly to your Android device or emulator, you need to build an [Android Package](https://en.wikipedia.org/wiki/Android_application_package) (APK/**.apk**) instead. ## 配置配置文件来构建 APK ¥Configuring a profile to build APKs 要生成 .apk,请通过在构建配置文件中添加以下属性之一来修改 [**eas.json**](/build/eas-json): ¥To generate an **.apk**, modify the [**eas.json**](/build/eas-json) by adding one of the following properties in a build profile: * `developmentClient` 至 `true`(默认) ¥`developmentClient` to `true` (**default**) * `distribution` 至 `internal` ¥`distribution` to `internal` * `android.buildType` 至 `apk` ¥`android.buildType` to `apk` * `android.gradleCommand` 到 `:app:assembleRelease`、`:app:assembleDebug` 或任何其他生成 .apk 的 gradle 命令 ¥`android.gradleCommand` to `:app:assembleRelease`, `:app:assembleDebug` or any other gradle command that produces **.apk** ```json eas.json { "build": { "preview": { "android": { "buildType": "apk" } }, "preview2": { "android": { "gradleCommand": ":app:assembleRelease" } }, "preview3": { "developmentClient": true }, "preview4": { "distribution": "internal" }, "production": {} } } ``` 现在你可以使用以下命令运行构建: ¥Now you can run your build with the following command: ```sh $ eas build -p android --profile preview ``` 请记住,你可以根据自己的喜好命名配置文件。我们将该配置文件命名为 `preview`。但是,你可以将其称为 `local`、`emulator` 或任何对你来说最有意义的名称。 ¥Remember that you can name the profile whatever you like. We named the profile `preview`. However, you can call it `local`, `emulator`, or whatever makes the most sense for you. ## 安装你的版本 ¥Installing your build ### 模拟器(虚拟设备) ¥Emulator (virtual device) > 如果你之前尚未安装或运行 Android 模拟器,请先按照 [Android Studio 模拟器指南](/workflow/android-studio-emulator) 操作,然后再继续。 > > ¥If you haven't installed or run an Android Emulator before, follow the [Android Studio emulator guide](/workflow/android-studio-emulator) before proceeding. 构建完成后,CLI 将提示你自动下载并将其安装到 Android 模拟器上。出现提示时,按 Y 直接安装到模拟器上。 ¥Once your build is completed, the CLI will prompt you to automatically download and install it on the Android Emulator. When prompted, press Y to directly install it on the emulator. 如果你有多个版本,你还可以随时运行 `eas build:run` 命令来下载特定版本并将其自动安装在 Android 模拟器上: ¥In case you have multiple builds, you can also run the `eas build:run` command at any time to download a specific build and automatically install it on the Android Emulator: ```sh $ eas build:run -p android ``` 该命令还显示项目的可用构建列表。你可以从此列表中选择要安装在模拟器上的版本。列表中的每个构建都有一个构建 ID、自构建创建以来经过的时间、构建号、版本号和 git 提交信息。如果项目有无效构建,该列表还会显示无效构建。 ¥The command also shows a list of available builds of your project. You can select the build to install on the emulator from this list. Each build in the list has a build ID, the time elapsed since the build creation, the build number, the version number, and the git commit information. The list also displays invalid builds if a project has any. 例如,下图列出了项目的构建: ¥For example, the image below lists the build of a project: 构建安装完成后,它将出现在主屏幕上。如果是开发版本,请打开终端窗口并通过运行命令 `npx expo start` 启动开发服务器。 ¥When the build's installation is complete, it will appear on the home screen. If it's a development build, open a terminal window and start the development server by running the command `npx expo start`. #### 运行最新版本 ¥Running the latest build 将 `--latest` 标志传递给 `eas build:run` 命令以在 Android 模拟器上下载并安装最新版本: ¥Pass the `--latest` flag to the `eas build:run` command to download and install the latest build on the Android Emulator: ```sh $ eas build:run -p android --latest ``` ### 物理设备 ¥Physical device #### 直接下载到设备 ¥Download directly to the device * 构建完成后,从构建详细信息页面或 `eas build` 完成时提供的链接将 URL 复制到 APK。 ¥Once your build is completed, copy the URL to the APK from the build details page or the link provided when `eas build` is done. * 将该 URL 发送到你的设备。也许通过电子邮件?由你决定。 ¥Send that URL to your device. Maybe by email? Up to you. * 在你的设备上打开 URL,安装 APK 并运行它。 ¥Open the URL on your device, install the APK and run it. #### 使用 `adb` 安装 ¥Install with `adb` * [安装亚行](https://developer.android.com/studio/command-line/adb)(如果你尚未安装)。 ¥[Install adb](https://developer.android.com/studio/command-line/adb) if you don't have it installed already. * 将你的设备连接到计算机和 [在你的设备上启用 adb 调试](https://developer.android.com/studio/command-line/adb#Enabling)(如果尚未连接)。 ¥Connect your device to your computer and [enable adb debugging on your device](https://developer.android.com/studio/command-line/adb#Enabling) if you haven't already. * 构建完成后,从构建详细信息页面或 `eas build` 完成时提供的链接下载 APK。 ¥Once your build is completed, download the APK from the build details page or the link provided when `eas build` is done. * 运行 `adb install path/to/the/file.apk`。 ¥Run `adb install path/to/the/file.apk`. * 在你的设备上运行该应用。 ¥Run the app on your device. ## 为 iOS 模拟器构建 了解如何在使用 EAS Build 时配置和安装 iOS 模拟器的构建。 在 iOS 模拟器上运行应用的构建非常有用。你可以配置构建配置文件并在模拟器上自动安装构建。这提供了一个独立(独立于 Expo Go)版本的应用运行,无需部署到 TestFlight,甚至无需拥有 Apple 开发者账户。 ¥Running a build of your app on an iOS Simulator is useful. You can configure the build profile and install the build automatically on the simulator. This provides a standalone (independent of Expo Go) version of the app running without needing to deploy to TestFlight or even having an Apple Developer account. ## 配置为模拟器构建的配置文件 ¥Configuring a profile to build for simulators 要在 iOS 模拟器上安装应用的构建,请修改 [**eas.json**](/build/eas-json/) 中的构建配置文件并将 `ios.simulator` 值设置为 `true`: ¥To install a build of your app on an iOS Simulator, modify the build profile in [**eas.json**](/build/eas-json/) and set the `ios.simulator` value to `true`: ```json eas.json { "build": { "preview": { "ios": { "simulator": true } }, "production": {} } } ``` 现在,执行如下所示的命令来运行构建: ¥Now, execute the command as shown below to run the build: ```sh $ eas build -p ios --profile preview ``` 请记住,配置文件可以任意命名。在上面的例子中,它被称为 `preview`。但是,你可以将其称为 `local`、`simulator` 或任何最有意义的名称。 ¥Remember that a profile can be named whatever you like. In the above example, it is called `preview`. However, you can call it `local`, `simulator`, or whatever makes the most sense. ## 在模拟器上安装构建 ¥Installing build on the simulator > 如果你之前尚未安装或运行 iOS 模拟器,请先按照 [iOS 模拟器指南](/workflow/ios-simulator) 操作,然后再继续。 > > ¥If you haven't installed or run the iOS Simulator before, follow the [iOS Simulator guide](/workflow/ios-simulator) before proceeding. 构建完成后,CLI 将提示你自动下载并将其安装到 iOS 模拟器上。出现提示时,按 Y 直接安装到模拟器上。 ¥Once your build is completed, the CLI will prompt you to automatically download and install it on the iOS Simulator. When prompted, press Y to directly install it on the simulator. 如果你有多个版本,你还可以随时运行 `eas build:run` 命令来下载特定版本并将其自动安装在 iOS 模拟器上: ¥In case you have multiple builds, you can also run the `eas build:run` command at any time to download a specific build and automatically install it on the iOS Simulator: ```sh $ eas build:run -p ios ``` 该命令还显示项目的可用构建列表。你可以从此列表中选择要安装在模拟器上的版本。列表中的每个构建都有一个构建 ID、自构建创建以来经过的时间、构建号、版本号和 git 提交信息。如果项目有无效构建,该列表还会显示无效构建。 ¥The command also shows a list of available builds of your project. You can select the build to install on the simulator from this list. Each build in the list has a build ID, the time elapsed since the build creation, the build number, the version number, and the git commit information. The list also displays invalid builds if a project has any. 例如,下图列出了项目的两个先前版本: ¥For example, the image below lists two previous builds of a project: 构建安装完成后,它将出现在主屏幕上。如果是开发版本,请打开终端窗口并通过运行命令 `npx expo start` 启动开发服务器。 ¥When the build's installation is complete, it will appear on the home screen. If it's a development build, open a terminal window and start the development server by running the command `npx expo start`. ### 运行最新版本 ¥Running the latest build 将 `--latest` 标志传递给 `eas build:run` 命令以在 iOS 模拟器上下载并安装最新版本: ¥Pass the `--latest` flag to the `eas build:run` command to download and install the latest build on the iOS Simulator: ```sh $ eas build:run -p ios --latest ``` ## 应用版本管理 了解不同的版本类型以及如何远程或本地管理它们。 Android 和 iOS 各自公开两个值来识别应用的版本:在商店中可见的版本(面向用户的版本)和仅对开发者可见的版本(面向开发者的构建版本)。本指南介绍如何远程或本地管理这些版本。 ¥Android and iOS each expose two values to identify the version of an app: the version visible in stores (user-facing version) and the version visible only to developers (developer-facing build version). This guide explains how you can manage those versions remotely or locally. Video Tutorial: [Automatic App Version Management](https://www.youtube.com/watch?v=Gk7RHDWsLsQ) ## 应用版本 ¥App versions 在 Expo 项目中,以下属性可用于在 [应用配置](/workflow/configuration/) 文件中定义应用版本。 ¥In Expo projects, the following properties can be used to define app versions in the [app config](/workflow/configuration/) file. | 属性 | 描述 | | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | [`version`](/versions/latest/config/app/#version) | 商店中可见的面向用户的版本。在 Android 上,它代表 android/app/build.gradle 中的 `versionName` 名称。在 iOS 上,它代表 Info.plist 中的 `CFBundleShortVersionString`。 | | [`android.versionCode`](/versions/latest/config/app/#versioncode) | 面向开发者的 Android 构建版本。它在 android/app/build.gradle 中代表 `versionCode`。 | | [`ios.buildNumber`](/versions/latest/config/app/#buildnumber) | 面向开发者的 iOS 构建版本。它在 Info.plist 中代表 `CFBundleVersion`。 | ### 在你的应用中使用应用版本 ¥Using app versions in your app 要在你的应用内显示面向用户的版本,你可以使用 `expo-application` 库中的 [`Application.nativeApplicationVersion`](/versions/latest/sdk/application/#applicationnativeapplicationversion)。 ¥To show the user-facing version inside your app, you can use [`Application.nativeApplicationVersion`](/versions/latest/sdk/application/#applicationnativeapplicationversion) from the `expo-application` library. 要在你的应用内显示面向开发者的构建版本,你可以使用 `expo-application` 库中的 [`Application.nativeBuildVersion`](/versions/latest/sdk/application/#applicationnativebuildversion)。 ¥To show the developer-facing build version inside your app, you can use [`Application.nativeBuildVersion`](/versions/latest/sdk/application/#applicationnativebuildversion) from the `expo-application` library. ## 推荐的工作流程 ¥Recommended workflow ### 面向用户的版本 ¥User-facing version 进行生产发布时,面向用户的版本应由你明确设置和更新。当生产版本提交到应用商店时,你可以在应用配置中更新 `version` 属性。如果你的项目使用具有自动运行时版本策略的 `expo-updates`,这也适用。这标志着新版本应用新开发周期的开始。了解有关 [部署模式](/eas-update/deployment-patterns) 的更多信息。 ¥When you are doing a production release, the user-facing version should be explicitly set and updated by you. You can update the `version` property in app config when production build is submitted to the app stores. This also applies if your project uses `expo-updates` with an automatic runtime version policy. This marks the beginning of a new development cycle for a new version of your app. Learn more about [deployment patterns](/eas-update/deployment-patterns). ### 面向开发者的构建版本 ¥Developer-facing build version 对于面向开发者的构建版本,你可以将它们设置为在每次构建时自动递增。这将帮助你避免每次在 Play Store 测试渠道或 TestFlight 上上传新存档时手动更改项目。应用商店拒绝的一个常见原因是提交具有重复版本号的版本。当开发者在创建新版本之前忘记增加面向开发者的版本号时,就会发生这种情况。 ¥For developer-facing build version, you can set them to autoincrement on every build. This will help you avoid making manual changes to the project every time you upload a new archive on Play Store testing channels or TestFlight. One common cause for app store rejections is submitting a build with a duplicate version number. It happens when a developer forgets to increment the developer-facing build version number before creating a new build. 如果你选择使用 [`remote` 版本源](#remote-version-source)(这是推荐的行为),EAS Build 可以通过为你自动增加这些版本来帮助管理面向开发者的构建版本。或者,你可以选择使用 `local` 应用版本源,这意味着你可以在各自的配置文件中手动控制版本。 ¥EAS Build can help manage developer-facing build versions automatically by incrementing these versions for you if you opt into using the [`remote` version source](#remote-version-source), which is the recommended behaviour. Optionally, you can choose to use a `local` app version source, which means you control versions manually in their respective config files. ## 远程版本源码 ¥Remote version source > **warning** `remote` 版本源是来自 EAS CLI 版本 `12.0.0` 的推荐行为。如果你使用的是旧版本的 EAS CLI,则 `local` 是默认设置。 > > ¥The `remote` version source is the recommended behavior from EAS CLI version `12.0.0`. If you are using an older version of the EAS CLI, `local` is the default. EAS 服务器可以远程存储和管理应用面向开发者的构建版本 (`android.versionCode` 和 `ios.buildNumber`)。要启用它,你需要在 eas.json 中将 `cli.appVersionSource` 设置为 `remote`。然后,在 `production` 构建配置文件下,你可以将 `autoIncrement` 属性设置为 `true`。 ¥EAS servers can store and manage your app's developer-facing build version (`android.versionCode` and `ios.buildNumber`) remotely. To enable it, you need to set `cli.appVersionSource` to `remote` in **eas.json**. Then, under the `production` build profile, you can set the `autoIncrement` property to `true`. ```json eas.json { "cli": { "appVersionSource": "remote" }, "build": { "development": { }, "preview": { }, "production": { "autoIncrement": true } } } ``` 远程版本使用本地项目的值初始化。例如,如果你在应用配置中将 `android.versionCode` 设置为 `1`,则当你使用远程版本源创建新版本时,它将自动递增到 `2`。但是,如果你没有在应用配置中设置构建版本,则在创建第一个构建时,远程版本将使用 `1` 进行初始化。 ¥The remote version is initialized with the value from the local project. For example, if you have `android.versionCode` set to `1` in app config, when you create a new build using the remote version source, it will auto increment to `2`. However, if you do not have build versions set in your app config, the remote version will initialize with `1` when the first build is created. 当在 eas.json 中启用 `remote` 版本属性时,存储在应用配置中的构建版本值将被忽略,并且在远程增加版本时不会更新。运行构建时,远程版本源值是在原生项目上设置的,这被视为这些值的真实来源。你可以安全地从应用配置中删除这些值。 ¥When the `remote` version property is enabled inside **eas.json**, the build version values stored in app config are ignored and not updated when the version is incremented remotely. The remote version source values are set on the native project when running a build, which is considered the source of truth for these values. You can safely remove these values from your app config. ### 将已定义的版本同步到远程 ¥Syncing already defined versions to remote 在不同的场景中,你已经为项目设置了版本,并希望在创建新的 EAS 构建时从这些版本开始递增。但是,这些现有版本可能无法与 EAS 远程同步。其中一些场景是: ¥There are different scenarios where you already have versions set up for your project and want to increment from those versions when you create a new EAS Build. However, these existing versions might not be synced remotely with EAS. Some of these scenarios are: * 你已经在应用商店中发布了你的应用,并希望继续使用相同的版本号。 ¥You have already published your app in the app stores and want to continue using the same version numbers. * EAS CLI 无法检测应用的版本。 ¥EAS CLI is not able to detect what version the app is on. * 出于任何其他原因,你已明确设置版本,例如在你的应用配置中。 ¥For any other reason, you have versions explicitly set, such as inside your app config. 在这些情况下,你可以使用 EAS CLI 将当前版本同步到 EAS Build,步骤如下: ¥In these scenarios, you can sync the current version to EAS Build using the EAS CLI using the following steps: * 在终端窗口中,运行以下命令: ¥In the terminal window, run the following command: ```sh $ eas build:version:set ``` * 在提示时选择平台(Android 或 iOS)。 ¥Select the platform (Android or iOS) when prompted. * 当提示“你是否要立即将应用版本源设置为远程?”时,选择“是”。这会将 eas.json 中的 `cli.appVersionSource` 设置为 `remote`。 ¥When prompted **Do you want to set app version source to remote now?**, select **yes**. This will set the `cli.appVersionSource` to `remote` in **eas.json**. * 当提示“你想用哪个版本初始化它?”时,输入你在应用商店中设置的最后一个版本号。 ¥When prompted **What version would you like to initialize it with?**, enter the last version number that you have set in the app stores. 完成这些步骤后,应用版本将远程同步到 EAS Build。你现在可以在 eas.json 中将 `build.production.autoIncrement` 设置为 `true`。当你创建新的生产版本时,`versionCode` 和 `buildNumber` 将自动递增。 ¥After these steps, the app versions will be synced to EAS Build remotely. You can now set `build.production.autoIncrement` to `true` in **eas.json**. When you create a new production build, the `versionCode` and `buildNumber` will be automatically incremented. ### 将版本从远程同步到本地 ¥Syncing versions from remote to local 要使用远程存储在 EAS 上的相同版本在 Android Studio 或 Xcode 中本地构建你的项目,请使用以下命令使用远程版本更新你的本地项目: ¥To build your project locally in Android Studio or Xcode using the same version stored remotely on EAS, update your local project with the remote versions using the following command: ```sh $ eas build:version:sync ``` ### 局限性 ¥Limitations * Android 上的 `eas build:version:sync` 命令不支持具有多种风格的裸项目。但是,其余的远程版本控制功能应该适用于所有项目。 ¥`eas build:version:sync` command on Android does not support bare projects with multiple flavors. However, the rest of the remote versioning functionality should work with all projects. * `autoIncrement` 不支持 `version` 选项。 ¥`autoIncrement` does not support the `version` option. * 如果你使用 EAS 更新且运行时策略设置为 `"runtimeVersion": { "policy": "nativeVersion" }`,则不支持。对于类似的行为,请改用 `"appVersion"` 策略。 ¥It's not supported if you are using EAS Update and runtime policy set to `"runtimeVersion": { "policy": "nativeVersion" }`. For similar behavior, use the `"appVersion"` policy instead. ## 本地版本源码 ¥Local version source > **warning** `remote` 版本源作为推荐行为已在 `eas-cli` 版本 `12.0.0` 中引入。如果你使用的是旧版本的 CLI,则无需向 `local` 明确指定版本源。 > > ¥The `remote` version source as a recommended behavior has been introduced in `eas-cli` version `12.0.0`. If you are using an older version of the CLI, you **don't need to specify the version source explicitly to `local`**. 你可以配置你的项目,以便项目版本的真实来源是本地项目源代码本身。为此,请在 eas.json 中将 `cli.appVersionSource` 设置为 `local`。 ¥You can configure your project so that the source of truth for project versions is the local project source code itself. To do this, set `cli.appVersionSource` to `local` in your **eas.json**. 使用此设置,EAS 会读取应用版本值并按原样构建项目。它不会写入项目。你还可以通过在构建配置文件上设置 `autoIncrement` 选项来本地启用自动递增版本。 ¥With this setup, EAS reads app version values and builds projects as they are. It doesn't write to the project. You can also enable auto incrementing versions locally by setting the `autoIncrement` option on a build profile. ```json eas.json { "cli": { "appVersionSource": "local" }, "build": { "development": { }, "preview": { }, "production": { "autoIncrement": true } } } ``` 对于 [现有的 React Native 项目](/bare/overview/),原生代码中的值优先。库 `expo-constants` 和 `expo-updates` 从应用配置文件中读取值。如果你依赖清单中的版本值,则应使它们与原生代码保持同步。如果你在运行时策略设置为 `"runtimeVersion": { "policy": "nativeVersion" }` 的情况下使用 EAS 更新,则保持这些值同步尤其重要,因为不匹配的版本可能会导致将更新传送到错误版本的应用。我们建议使用 [`expo-application`](/versions/latest/sdk/application/#constants) 读取版本,而不是依赖应用配置中的值。 ¥In the case of [existing React Native projects](/bare/overview/), the values in native code take precedence. The libraries `expo-constants` and `expo-updates` read values from the app config file. If you rely on version values from a manifest, you should keep them in sync with native code. Keeping these values in sync is especially important if you are using EAS Update with the runtime policy set to `"runtimeVersion": { "policy": "nativeVersion" }`, because mismatched versions may result in the delivery of updates to the wrong version of an application. We recommend using [`expo-application`](/versions/latest/sdk/application/#constants) to read the version instead of depending on values from app config. ### 局限性 ¥Limitations * 对于 `autoIncrement`,如果你希望版本更改持续存在,则需要在每次构建时提交更改。在 CI 上构建时,这可能很难协调。 ¥With `autoIncrement`, you need to commit your changes on every build if you want the version change to persist. This can be difficult to coordinate when building on CI. * 对于具有支持多种风格的 Gradle 配置的现有 React Native 项目,EAS CLI 无法读取或修改版本,因此不支持 `autoIncrement` 选项,并且版本不会在 [expo.dev](https://expo.dev) 上的构建详细信息页面中列出。 ¥For existing React Native projects with Gradle configuration that supports multiple flavors, EAS CLI is not able to read or modify the version, so `autoIncrement` option is not supported, and versions will not be listed in the build details page on [expo.dev](https://expo.dev). ## 解决构建错误和崩溃问题 使用 EAS Build 时排除构建错误和崩溃的参考。 当出现问题时,可能会以以下两种方式之一出现问题: ¥When something goes wrong, it probably will go wrong in one of two following ways: 1. 你的构建将会失败。 ¥Your build will fail. 2. 构建将成功,但会遇到运行时错误,例如,运行时崩溃或挂起。 ¥The build will succeed but encounter a runtime error, for example, it crashes or hangs when you run it. 关于 [缩小错误来源范围](https://expo.fyi/manual-debugging) 的所有标准建议均适用于此处;本文档提供的信息可能对你的典型故障排除过程和技术有用。故障排除是一门艺术,你可能需要创造性地思考。 ¥All standard advice around [narrowing down the source of an error](https://expo.fyi/manual-debugging) applies here; this document provides information that may be useful on top of your typical troubleshooting processes and techniques. Troubleshooting is an art, and you might need to think creatively. ## 查找相关错误日志 ¥Find the related error logs 在进一步操作之前,你需要确保已找到错误消息并阅读它。根据你是调查构建失败还是运行时错误,执行此操作的方式会有所不同。 ¥Before you go further, you need to be sure that you have located the error message and read it. How you do this will be different depending on whether you're investigating a build failure or runtime error. ### 运行时错误 ¥Runtime errors 属于此类别的常见问题有:"我的应用在本地运行良好,但当我运行构建时立即崩溃" 或 "我的应用可以在 Expo Go 中运行,但在我的构建中挂在启动屏幕上"。当你的应用构建成功但在运行时崩溃或挂起时,这被视为运行时错误。 ¥Common questions that fall under this category are: "my app runs well locally but crashes immediately when I run a build" or "my app works in Expo Go but hangs on the splash screen in my build". When your app builds successfully but crashes or hangs when you run it, this is considered a runtime error. 请参阅 [调试指南 "生产错误" 部分](/debugging/runtime-issues/#production-errors) 了解如何在发布版本在运行时崩溃时查找日志。 ¥Refer to the ["Production errors" section of the debugging guide](/debugging/runtime-issues/#production-errors) to learn how to locate logs when your release builds are crashing at runtime. 如果你无法通过这种方法找到任何有用的信息,请尝试 [逐步缩小崩溃根源](https://expo.fyi/manual-debugging)。 ¥If you can't find any useful information through this approach, try [narrowing down the source of the crash step by step](https://expo.fyi/manual-debugging). ### 构建错误 ¥Build errors 转到构建详细信息页面(如果尚未打开,请在 [构建仪表板](https://expo.dev/accounts/\[account]/projects/\[project]/builds) 上找到它)并通过单击任何失败的构建阶段来展开它们。通常,最早出现错误的阶段将包含最有用的信息,并且任何后续失败的阶段都将从第一个阶段开始级联。 ¥Go to your build details page (find it on the [build dashboard](https://expo.dev/accounts/\[account]/projects/\[project]/builds) if you don't have it open already) and expand any failed build phases by clicking on them. Often, the earliest phase with errors will contain the most useful information and any subsequent failed phase will have cascaded from the first. 无论处于哪个阶段,都经常会看到以 `[stderr]` 为前缀的日志条目,但请记住,这并不一定意味着这些日志指向错误;CLI 工具通常使用 [stderr](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_\(stderr\)) 来输出警告和其他诊断信息。 ¥Regardless of the phase, **it's common to see log entries prefixed with `[stderr]`, but keep in mind that this doesn't necessarily mean those logs point to errors**; it's common for CLI tools to use [stderr](https://en.wikipedia.org/wiki/Standard_streams#Standard_error_\(stderr\)) to output warnings and other diagnostics. 例如,你可能会在 Android 版本上看到类似的内容: ¥For example, you might see something like this on your Android builds: ```sh `[stderr] Note: /build/workingdir/build/app/node_modules/@react-native-async-storage/async-storage/android/src/main/java/com/reactnativecommunity/asyncstorage/AsyncStorageModule.java uses or overrides a deprecated API.` `[stderr] Note: Recompile with -Xlint:deprecation for details.` ``` 尽管你可能有兴趣跟进该警告,也可能不感兴趣,但这并不是构建失败的原因。那么你如何知道哪些日志真正负责呢?如果你正在构建一个裸项目,那么你已经擅长于此。如果你正在构建 [管理项目](/archive/managed-vs-bare/),这可能会很棘手,因为你不直接与原生代码交互,只编写 JavaScript。 ¥While you may or may not be interested in following up on that warning, it is not the cause of your failed build. So how do you know which logs are truly responsible? If you are building a bare project, you will already be good at this. If you are building a [managed project](/archive/managed-vs-bare/), it may be tricky because you don't directly interact with the native code, only write JavaScript. 一个好的方法是确定构建是否由于原生错误或 JavaScript 错误而失败。当你的构建由于 JavaScript 构建错误而失败时,你通常会看到如下内容: ¥A good path forward is to **determine if the build failed due to a native or JavaScript error**. When your build fails due to a JavaScript build error, you will usually see something like this: ```sh `❌ Metro encountered an error:` `Unable to resolve module ./src/Routes from /Users/expo/workingdir/build/App.js` ``` 此特定错误意味着应用正在导入 ./src/Routes 但未找到。原因可能是 Git 中的文件名大小写与开发者的文件系统不同(例如,Git 中的 paths.js 而不是 Routes.js),或者项目可能有一个构建步骤,但未设置为在其上运行 EAS 构建。在这种情况下,事实证明,在这种情况下 ./src/Routes 旨在导入 ./src/Routes/index.js,但该路径被意外地排除在开发者的 .gitignore 中。 ¥This particular error means that the app is importing **./src/Routes** and it is not found. The cause could be that the filename case is different in Git than the developer's filesystem (for example, **routes.js** in Git instead of **Routes.js**), or maybe the project has a build step and it wasn't set up to run on EAS Build. In this case, it turns out that in this case **./src/Routes** was intended to import **./src/Routes/index.js**, but that path was accidentally excluded in the developer's **.gitignore**. 需要注意的是,对于 iOS 构建,构建详细信息页面仅显示日志的缩略版本,因为 `xcodebuild` 的完整输出可能约为 10MB。有时需要打开完整的 Xcode 日志来查找所需的信息;例如,如果 JavaScript 构建失败,但你在构建详细信息页面上看不到任何有用的信息。要打开完整的 Xcode 日志,请在构建完成后滚动到构建详细信息页面的底部,然后单击查看或下载它们。 ¥It's important to note that with iOS builds the build details page only displays an abridged version of the logs because the full output from `xcodebuild` can be in the order of 10MB. Sometimes it's necessary to open the full Xcode logs to find the information that you need; for example, if the JavaScript build failed but you don't see any useful information on the build details page. To open the full Xcode logs, scroll to the bottom of the build details page when the build has been completed and either click to view or download them. 如果你正在开发托管应用,并且构建错误是原生错误而不是 JavaScript 错误,则这可能是由于项目中的 [配置插件](/config-plugins/introduction/) 或依赖造成的。请留意日志中自上次成功构建以来添加的任何新软件包。运行 `npx expo-doctor` 以确定你的项目中的 Expo SDK 依赖的版本与你的 Expo SDK 版本兼容。 ¥If you are working on a managed app and the build error is a native error rather than a JavaScript error, this is likely due to a [config plugin](/config-plugins/introduction/) or a dependency in your project. Keep an eye out in the logs for any new packages that you have added since your previous successful build. Run `npx expo-doctor` to determine that the versions of Expo SDK dependencies in your project are compatible with your Expo SDK version. 有了错误日志,你通常可以开始修复构建或搜索 [forums](https://chat.expo.dev/) 和 GitHub 问题以查找相关包以进行更深入的挖掘。下面列出了一些常见的问题来源。 ¥Armed with your error logs, you can often start to fix your build or search the [forums](https://chat.expo.dev/) and GitHub issues for related packages to dig deeper. Some common sources of problems are listed below. Note: Are you using a monorepo? --- Monorepos are incredibly useful but they do introduce their own set of problems. It's necessary to upload the entire monorepo to the EAS Build builders, set it up, and run the build. EAS Build is more like a typical CI service in that we need the source code, rather than a compiled JavaScript bundle and manifest. EAS Build has first-class support for Yarn workspaces, and [your success may vary when using other monorepo tools](/build-reference/limitations). For more information, see [Working with monorepos](/guides/monorepos). --- Note: Out-of-memory (OOM) errors --- If your build fails with "Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)" in your Gradle logs, this is because the Node process responsible for bundling your app JavaScript was killed. This can often be a sign that your app bundle is extremely large, which will make your overall app binary larger and lead to slow boot up times, especially on low-end Android devices. Sometimes the error can occur when large text files are treated as source code, for example, if you have a JavaScript file that contains a string of 1MB+ of HTML to load into a webview, or a similarly sized JSON file. To determine how large your bundle is and to see a breakdown of where the size comes from, use [Expo Atlas](/guides/analyzing-bundles/). To increase memory limits on your EAS Build builders, use [`large` resource class](/eas/json/#resourceclass) in your **eas.json**. See [Android-specific resource class](/build-reference/infrastructure/#android-build-server-configurations) and [iOS-specific resource class](/build-reference/infrastructure/#ios-build-server-configurations) for more information. --- Note: None of the files exist error --- When you run `eas build`, your project's files are uploaded to Expo's build servers. However, any file or directory mentioned in the **.gitignore** is **not uploaded**. This is intentional to prevent sensitive information, such as API keys, from being exposed in your app's code. If your project imports a file listed in **.gitignore**, the build will fail with a `None of these files exist` error. There are different ways you can resolve this error: - Remove the import statement for the ignored file and test your project. If your project functions as expected, that import statement may have been outdated or unused. - Remove any files or directories Metro was unable to resolve from your **.gitignore**. However, this poses a security risk since any sensitive information included in these files will now be available in your project's source code and Git commit history. - Encode the file with `base64`, save that string as secrets, and create the file in an EAS Build hook. See [How can I upload files to EAS Build if they are gitignored?](https://expo.fyi/eas-build-archive.md#how-can-i-upload-files-to-eas-build-if-they-are-gitignored) for more information. - Refactor your source code to avoid importing sensitive files on the client side. If a file is an auto-generated code from a third-party provider and that provider has automatically listed files in your **.gitignore**, then that file probably contains sensitive information. You should not include it on the client side. During app development, ensure you follow secure practices, such as using environment variables or serving them through your backend. See [Using secrets in environment variables](/build-reference/variables/#using-secrets-in-environment-variables) for more information. --- ## Verify that your JavaScript bundles locally When a build fails with `Task :app:bundleReleaseJsAndAssets FAILED` (Android) or `Metro encountered an error` (iOS), it means Metro bundler was unable to bundle the app's JavaScript code while trying to embed it in your app's binary. This error message is usually followed by a syntax error or other details about why bundling failed. Unfortunately, a standard React Native project is configured to perform this step late in the Gradle/Xcode build step, meaning it can take a while to see this error. You can build the production bundle locally by running `npx expo export` to bypass all of the other build steps so you can see this error much more quickly. Run this command repeatedly, resolving any syntax errors or other issues uncovered until the bundle builds successfully. Then try your EAS Build again. ## Verify that your project builds and runs locally If the logs weren't enough to immediately help you understand and fix the root cause, it's time to try to reproduce the issue locally. If your project builds and runs locally in release mode then it will also build on EAS Build, provided that the following are all true: - Relevant [Build tool versions](/build/eas-json#configuring-your-build-tools) (for example, Xcode, Node.js, npm, Yarn) are the same in both environments. - Relevant [environment variables](/build-reference/variables) are the same in both environments. - The [archive](https://expo.fyi/eas-build-archive) that is uploaded to EAS Build includes the same relevant source files. You can verify that your project builds on your local machine with the `npx expo run:android` and `npx expo run:ios` commands, with variant/configuration flags set to release to most faithfully reproduce what executes on EAS Build. For more information, see [Android build process](/build-reference/android-builds) and [iOS build process](/build-reference/ios-builds). ```sh $ npx expo run:android --variant release $ npx expo run:ios --configuration Release ``` > If use [CNG](/workflow/continuous-native-generation/), these commands will run `npx expo prebuild` to generate native projects to compile them.You likely want to [clean up the changes](https://expo.fyi/prebuild-cleanup) once you are done troubleshooting, unless you want to start managing these projects directly instead of generating them on demand. > > > You can alternatively run a local build with `eas build --local` — this command will run a > series of steps that is as close as it can be to what runs remotely on the hosted EAS Build > service. It will copy your project to a temporary directory and make any necessary changes there. > [Learn how to set this up and use it for > debugging](/build-reference/local-builds.mdx#using-local-builds-for-debugging). If your native toolchains are installed correctly and you are unable to build and run your project in release mode on your local machine, it will not build on EAS Build. Fix the issues locally, then try again on EAS Build. The other advice in this doc may be useful to help you resolve the issue locally, but often this requires some knowledge of native tooling or judicious application of Google, Stack Overflow, and GitHub Issues. Note: Don't have Xcode and Android Studio set up on your machine? --- **If you do not have native toolchains installed locally**, for example, because you do not have an Apple computer and therefore cannot build an iOS app on your machine, it can be trickier to get to the bottom of build errors. The feedback loop of making small changes locally and then seeing the result on EAS Build is slower than doing the same steps locally because the EAS Build builder must set up its environment, download your project, and install dependencies before starting the build. If you are willing and able to set up the appropriate native tools, then refer to the [React Native environment setup guide](https://rn.nodejs.cn/docs/environment-setup). --- Note: My app builds locally, but not on EAS Build --- By default, EAS Build follows a relatively straightforward process for building your app for ([Android](/build-reference/android-builds) or [iOS](/build-reference/ios-builds)). If `npx expo run:android --variant release` and `npx expo run:ios --configuration Release` work locally, but your builds fail, then it's time to narrow down what configuration exists on your machine that hasn't been set up for your project on EAS Build yet. - Do a fresh `git clone` of your project to a new directory and get it running, ideally on a different machine. Pay attention to each of the steps that are needed and verify that they are also configured for EAS Build. - Check that your [environment variables](/guides/environment-variables) are properly configured. - Verify that versions of Node.js, npm, Yarn, Xcode, Java, and other tools are the same in both environments. - Ensure that the [archive you are uploading to EAS Build](https://expo.fyi/eas-build-archive) includes the same relevant source files. --- Note: Why does my production app not match my development app? --- You can test how the JS part of your app will run in production by starting it with [`npx expo start --no-dev`](/workflow/development-mode/#production-mode). This tells the bundler to minify JavaScript before serving it, most notably stripping code protected by the `__DEV__` boolean. This will remove most of the logging, HMR, Fast Refresh functionality, and make debugging a bit harder, but you can iterate on the production bundle faster this way. --- ## Still having trouble? This guide is far from being comprehensive, and depending on your level of experience you might still be struggling to get your app working. If you have followed the advice here, you're now in a good position to describe your issue to other developers and get some help. ### How to ask a good question Join us on Discord and Forums 用于向社区和 Expo 团队寻求帮助。Expo 团队会尽最大努力回应高质量且清晰明确的问题,但除非你注册了 [支持计划](https://expo.dev/support-terms#target-response-time-guidelines-for-subscriptions),否则不能保证得到回应。为确保 Expo 团队成员看到你的问题,你可以在 [expo.dev/contact](https://expo.dev/contact) 提交票证。 ¥to ask for help from the community and the Expo team. The Expo team does our best to respond to high quality and well-articulated questions and issues, but responses are not guaranteed unless you are signed up for a [support plan](https://expo.dev/support-terms#target-response-time-guidelines-for-subscriptions). To ensure that an Expo team member sees your question, you can file a ticket at [expo.dev/contact](https://expo.dev/contact). 当你寻求故障排除帮助时,请务必分享以下信息: ¥When you ask for troubleshooting help, be sure to share the following information: * 指向你的构建页面的链接。只有你的团队或 Expo 员工才能访问此内容。如果你想更公开地分享,请截取屏幕截图。如果你想更私密地分享,请发送电子邮件至 [secure@expo.dev](mailto:secure@expo.dev),并在聊天或论坛上的帮助请求中提及这一点。如果你使用 `eas build --local` 在本地执行此构建,则可以省略这一点,但请提及这一事实。 ¥**A link to your build page**. This can only be accessed by your team or Expo employees. If you'd like to share it more publicly, take a screenshot. If you'd like to share it more privately, send an email to [secure@expo.dev](mailto:secure@expo.dev) and mention that in your help request on chat or forums. If you are performing this build locally with `eas build --local`, you can omit this, but do mention this fact. * 错误日志。你怀疑的任何事情都可能与你的构建或运行时错误有关。如果你无法提供此信息,请解释原因。 ¥**Error logs**. Anything that you suspect may be related to your build or runtime error. If you can't provide this, explain why not. * 最小的可重现示例或存储库的链接。解决问题的最快方法是确保其他开发者可以重现该问题。如果你曾经在团队中工作过,你就会从经验中知道这一点。在许多情况下,如果你无法提供可重现的示例,那么它可能无法为你提供帮助,并且来回询问和回答问题的过程充其量只是一种低效的时间利用。在 [手动调试指南](https://expo.fyi/manual-debugging) 和 Stack Overflow 的 [最小可行可重复示例](https://stackoverflow.com/help/minimal-reproducible-example) 指南中了解有关如何创建可重现示例的更多信息。 ¥**Minimal reproducible example or a link to your repository**. The quickest way to get a solution to your problem is to ensure that other developers can reproduce it. If you have ever worked on a team, you know this from experience. In many cases, if you can't provide a reproducible example then it may not be possible to help you, and at best the back-and-forth process of asking and answering questions will be an inefficient use of time. Learn more about how to create a reproducible example in the [manual debugging guide](https://expo.fyi/manual-debugging) and Stack Overflow's [Minimal Viable Reproducible Example](https://stackoverflow.com/help/minimal-reproducible-example) guide. 尽量做到清晰、准确且有帮助。Stack Overflow 的 [如何提出一个好问题](https://stackoverflow.com/help/how-to-ask) 指南提供的一般指导适用。 ¥Try to be clear, precise, and helpful. General guidance provided by Stack Overflow's [How to ask a good question](https://stackoverflow.com/help/how-to-ask) guide applies. ## 在同一设备上安装应用变体 了解如何在同一设备上安装应用的多个变体。 创建 [开发、预览和生产版本](/build/eas-json/#common-use-cases) 时,在同一设备上同时安装这些构建变体是很常见的。这允许进行开发、预览应用的下一个版本以及在设备上运行生产版本,而无需卸载并重新安装应用。 ¥When creating [development, preview, and production builds](/build/eas-json/#common-use-cases), installing these build variants simultaneously on the same device is common. This allows working in development, previewing the next version of the app, and running the production version on a device without needing to uninstall and reinstall the app. 本指南提供了配置多个(开发和生产)变体以在同一设备上安装和使用它们所需的步骤。 ¥This guide provides the steps required to configure multiple (development and production) variants to install and use them on the same device. ## 先决条件 ¥Prerequisites 要在你的设备上安装应用的多个变体,每个变体必须具有唯一的 [应用 ID (Android)](/versions/latest/config/app/#package) 或 [打包包标识符 (iOS)](/versions/latest/config/app/#bundleidentifier)。 ¥To have multiple variants of an app installed on your device, each variant must have a unique [Application ID (Android)](/versions/latest/config/app/#package) or [Bundle Identifier (iOS)](/versions/latest/config/app/#bundleidentifier). ## 配置开发和生产变体 ¥Configure development and production variants 你已经使用 Expo 工具创建了一个项目,现在你想要创建一个开发和生产版本。你的项目的 app.json 可能具有以下配置: ¥You have created a project using Expo tooling, and now you want to create a development and a production build. Your project's **app.json** may have the following configuration: ```json app.json { "expo": { "name": "MyApp", "slug": "my-app", "ios": { "bundleIdentifier": "com.myapp" }, "android": { "package": "com.myapp" } } } ``` 如果你的项目配置了 EAS Build,则 eas.json 也具有类似的配置,如下所示: ¥If your project has EAS Build configured, the **eas.json** also has a similar configuration as shown below: ```json eas.json { "build": { "development": { "developmentClient": true }, "production": {} } } ``` ### 将 app.json 转换为 app.config.js ¥Convert app.json to app.config.js 要在同一设备上安装应用的多个变体,请将 app.json 重命名为 app.config.js 并导出配置,如下所示: ¥To have multiple variants of the app installed on the same device, rename the **app.json** to **app.config.js** and export the configuration as shown below: ```js app.config.js export default { name: 'MyApp', slug: 'my-app', ios: { bundleIdentifier: 'com.myapp', }, android: { package: 'com.myapp', }, }; ``` 在 app.config.js 中,添加一个名为 `IS_DEV` 的环境变量,以根据该变量切换每个变体的 `android.package` 和 `ios.bundleIdentifier`: ¥In **app.config.js**, add an environment variable called `IS_DEV` to switch the `android.package` and `ios.bundleIdentifier` for each variant based on the variable: ```js app.config.js const IS_DEV = process.env.APP_VARIANT === 'development'; export default { name: IS_DEV ? 'MyApp (Dev)' : 'MyApp', slug: 'my-app', ios: { bundleIdentifier: IS_DEV ? 'com.myapp.dev' : 'com.myapp', }, android: { package: IS_DEV ? 'com.myapp.dev' : 'com.myapp', } }; ``` 在上面的例子中,环境变量 `IS_DEV` 用于区分开发环境和生产环境。根据其值,为每个变体设置不同的应用 ID 或打包包标识符。 ¥In the above example, the environment variable `IS_DEV` is used to differentiate between the development and production environment. Based on its value, the different Application IDs or Bundle Identifiers are set for each variant. Note: Additional app variant customizations --- You can customize other aspects of your app on a per-variant basis. You can swap any configuration that you used previously in **app.json** using the same approach as above. **Examples:** - If you are using a library that requires you to register your application identifier with an external service to use its SDK, such as Google Maps or Firebase Cloud Messaging (FCM), you'll need to have a separate configuration for that API for the `android.package` and `ios.bundleIdentifier`. - If you're using [development builds](/develop/development-builds/introduction/), you can configure the `expo-dev-client` plugin to disable the app scheme used by Expo CLI and EAS Update QR codes in non-development builds. This ensures that those URLs will always launch the development build, regardless of your device's defaults: ```js app.config.js plugins: [ [ 'expo-dev-client', { addGeneratedScheme: !!IS_DEV, }, ], ], ``` --- ### Configuration for EAS Build In **eas.json**, set the `APP_VARIANT` environment variable to run builds with the **development** profile by using the `env` property: ```json eas.json { "build": { "development": { "developmentClient": true, "env": { "APP_VARIANT": "development" } }, "production": {} } } ``` Now, when you run `eas build --profile development`, the environment variable `APP_VARIANT` is set to `development` when evaluating **app.config.js** both locally and on the EAS Build builder. ### Using the development server When you start your development server, you'll need to run `APP_VARIANT=development npx expo start` (or the platform equivalent if you use Windows). A shortcut for this is to add the following script to your **package.json**: ```json package.json { "scripts": { "dev": "APP_VARIANT=development npx expo start" } } ``` ### Using production variant When you run `eas build --profile production` the `APP_VARIANT` variable environment is not set, and the build runs as the production variant. > **Note**: If you use EAS Update to publish JavaScript updates of your app, you should be cautious to set the correct environment variables for the app variant that you are publishing for when you run the `eas update` command. See the EAS Build [Environment variables and secrets](/build/updates) for more information. ### In bare project #### Android In **android/app/build.gradle**, create a separate flavor for every build profile from **eas.json** that you want to build. ```groovy android/app/build.gradle android { flavorDimensions "env" productFlavors { production { dimension "env" applicationId 'com.myapp' } development { dimension "env" applicationId 'com.myapp.dev' } } } ``` > **Note**: Currently, EAS CLI supports only the `applicationId` field. If you use `applicationIdSuffix` inside `productFlavors` or `buildTypes` sections then this value will not be detected correctly. Assign Android flavors to EAS Build profiles by specifying a `gradleCommand` in the **eas.json**: ```json eas.json { "build": { "development": { "android": { "gradleCommand": ":app:assembleDevelopmentDebug" } }, "production": { "android": { "gradleCommand": ":app:bundleProductionRelease" } } } } ``` By default, every flavor can be built in either debug or release mode. If you want to restrict some flavor to a specific mode, see the snippet below, and modify **build.gradle**. ```groovy android/app/build.gradle android { variantFilter { variant -> def validVariants = [ ["production", "release"], ["development", "debug"], ] def buildTypeName = variant.buildType*.name def flavorName = variant.flavors*.name def isValid = validVariants.any { flavorName.contains(it[0]) && buildTypeName.contains(it[1]) } if (!isValid) { setIgnore(true) } } } ``` The rest of the configuration at this point is not specific to EAS, it's the same as it would be for any Android project with flavors. There are a few common configurations that you might want to apply to your project: - To change the name of the app built with the development profile, create a **android/app/src/development/res/value/strings.xml** file: ```xml android/app/src/development/res/value/strings.xml MyApp - Dev ``` - To change the icon of the app built with the development profile, create **android/app/src/development/res/mipmap-\*** directories with appropriate assets (you can copy them from **android/app/src/main/res** and replace the icon files). - To specify **google-services.json** for a specific flavor, put it in the **android/app/src/{flavor}/google-services.json** file. - To configure sentry, add `project.ext.sentryCli = [ flavorAware: true ]` to **android/app/build.gradle** and name your properties file **android/sentry-\{flavor\}-\{buildType\}.properties** (for example, **android/sentry-production-release.properties**) #### iOS Assign a different `scheme` to every build profile in **eas.json**: ```json eas.json { "build": { "development": { "ios": { "buildConfiguration": "Debug", "scheme": "myapp-dev" } }, "production": { "ios": { "buildConfiguration": "Release", "scheme": "myapp" } } } } ``` **Podfile** should have a target defined like this: ```ruby Podfile target 'myapp' do # @hide ... # # @end # end ``` Replace it with an abstract target, where the common configuration can be copied from the old target: ```ruby Podfile abstract_target 'common' do # put common target configuration here target 'myapp' do end target 'myapp-dev' do end end ``` Open project in Xcode, click on the project name in the navigation panel, right click on the existing target, and click "Duplicate": Rename the target to something more meaningful, for example, `myapp copy` -> `myapp-dev`. Configure a scheme for the new target: - Go to `Product` -> `Scheme` -> `Manage schemes`. - Find scheme `myapp copy` on the list. - Change scheme name `myapp copy` -> `myapp-dev`. - By default, the new scheme should be marked as shared, but Xcode does not create `.xcscheme` files. To fix that, uncheck the "Shared" checkbox and check it again, after that new `.xcscheme` file should show up in the **ios/myapp.xcodeproj/xcshareddata/xcschemes** directory. By default, the newly created target has separate **Info.plist** file (in the above example, it's **ios/myapp copy-Info.plist**). To simplify your project we recommend using the same file for all targets: - Delete **./ios/myapp copy-Info.plist**. - Click on the new target. - Go to `Build Settings` tab. - Find `Packaging` section. - Change **Info.plist** value - **myapp copy-Info.plist** -> **myapp/Info.plist**. - Change `Product Bundle Identifier`. 要更改显示名称: ¥To change the display name: * 打开 Info.plist 并添加键 `Bundle display name` 和值 `$(DISPLAY_NAME)`。 ¥Open **Info.plist** and add key `Bundle display name` with value `$(DISPLAY_NAME)`. * 打开两个目标的 `Build Settings` 并找到 `User-Defined` 部分。 ¥Open `Build Settings` for both targets and find `User-Defined` section. * 添加密钥 `DISPLAY_NAME` 以及你要用于该目标的名称。 ¥Add key `DISPLAY_NAME` with the name you want to use for that target. 要更改应用图标: ¥To change the app icon: * 创建一个新的图片集(你可以根据当前图标的现有图片集创建它,通常命名为 `AppIcon`) ¥Create a new image set (you can create it from the existing image set for the current icon, it's usually named `AppIcon`) * 打开你要更改图标的目标的 `Build Settings`。 ¥Open `Build Settings` for the target that you want to change icon. * 找到 `Asset Catalog Compiler - Options` 部分。 ¥Find `Asset Catalog Compiler - Options` section. * 将 `Primary App Icon Set Name` 更改为新图片集的名称。 ¥Change `Primary App Icon Set Name` to the name of the new image set. ## iOS 功能 了解 EAS Build 中支持的内置 iOS 功能以及如何启用或禁用它们。 当你对 iOS 权利进行更改时,需要在进行生产构建之前在 Apple 服务器上远程更新此更改。当你运行 `eas build` 时,EAS Build 会自动将 Apple 开发者控制台上的功能与你的本地权利配置同步。功能是 Apple 提供的 Web 服务,可以将其视为 AWS 或 Firebase 服务。 ¥When you make a change to your iOS entitlements, this change needs to be updated remotely on Apple's servers before making a production build. EAS Build automatically synchronizes capabilities on the Apple Developer Console with your local entitlements configuration when you run `eas build`. Capabilities are web services provided by Apple, think of them like AWS or Firebase services. > 可以使用 `EXPO_NO_CAPABILITY_SYNC=1 eas build` 禁用此功能 > > ¥This feature can be disabled with `EXPO_NO_CAPABILITY_SYNC=1 eas build` ## 权利 ¥Entitlements 在 Expo 应用中,权利是从内省的应用配置中读取的。要编辑它们,请参阅应用配置文件中的 [`ios.entitlements`](/versions/latest/config/app/#entitlements) 字段。你可以通过在项目中运行 `npx expo config --type introspect` 来查看内省配置,然后查找 `ios.entitlements` 对象以获取结果。 ¥In an Expo app, the entitlements are read from the introspected app config. To edit them, see the [`ios.entitlements`](/versions/latest/config/app/#entitlements) field in your app config file. You can see your introspected config by running `npx expo config --type introspect` in your project and then look for the `ios.entitlements` object for the results. 在裸 React Native 应用中,权利是从 ios/**/*.entitlements 文件中读取的。 ¥In a bare React Native app, the entitlements are read from your **ios/**/*.entitlements** file. ## 启用 ¥Enabling 如果权利文件中存在受支持的权利,则运行 `eas build` 将在 Apple 开发者控制台上启用它。如果该功能已启用,则 EAS Build 将跳过它。 ¥If a supported entitlement is present in the entitlements file, then running `eas build` will enable it on Apple Developer Console. If the capability is already enabled, then EAS Build will skip it. ## 禁用 ¥Disabling 如果你的应用远程启用了某项功能,但原生权利文件中不存在该功能,则运行 `eas build` 将自动禁用它。 ¥If a capability is enabled for your app remotely, but not present in the native entitlements file, then running `eas build` will automatically disable it. ## 支持的功能 ¥Supported capabilities EAS Build 将仅启用其内置支持的功能,任何不支持的权利必须通过 [苹果开发者控制台][apple-dev-console] 手动启用。 ¥EAS Build will only enable capabilities that it has built-in support for, any unsupported entitlements must be manually enabled via [Apple Developer Console][apple-dev-console]. | 支持 | 能力 | 权利字符串 | | -- | --------------------------------- | --------------------------------------------------------------------------------------------------------------- | | | 访问 Wi-Fi 信息 | `com.apple.developer.networking.wifi-info` | | | 应用认证 | `com.apple.developer.devicecheck.appattest-environment` | | | 应用组 | `com.apple.security.application-groups` | | | Apple Pay 稍后推销 | `com.apple.developer.pay-later-merchandising` | | | Apple Pay 支付处理 | `com.apple.developer.in-app-payments` | | | 相关字段 | `com.apple.developer.associated-domains` | | | 自动填充凭证提供者 | `com.apple.developer.authentication-services.autofill-credential-provider` | | | ClassKit | `com.apple.developer.ClassKit-environment` | | | 与司机沟通 | `com.apple.developer.driverkit.communicates-with-drivers` | | | 通讯通知 | `com.apple.developer.usernotifications.communication` | | | 自定义网络协议 | `com.apple.developer.networking.custom-protocol` | | | 数据保护 | `com.apple.developer.default-data-protection` | | | DriverKit 允许第三方用户客户端 | `com.apple.developer.driverkit.allow-third-party-userclients` | | | DriverKit 系列音频(开发) | `com.apple.developer.driverkit.family.audio` | | | DriverKit 系列 HID 设备(开发) | `com.apple.developer.driverkit.family.hid.device` | | | DriverKit 系列 HID EventService(开发) | `com.apple.developer.driverkit.family.hid.eventservice` | | | DriverKit 系列网络(开发) | `com.apple.developer.driverkit.family.networking` | | | DriverKit 系列 SCSI 控制器(开发) | `com.apple.developer.driverkit.family.scsicontroller` | | | DriverKit 系列系列(开发) | `com.apple.developer.driverkit.family.serial` | | | DriverKit 传输 HID(开发) | `com.apple.developer.driverkit.transport.hid` | | | DriverKit USB 传输(开发) | `com.apple.developer.driverkit.transport.usb` | | | 用于开发的驱动程序套件 | `com.apple.developer.driverkit` | | | 扩展虚拟地址空间 | `com.apple.developer.kernel.extended-virtual-addressing` | | | 家庭控制 | `com.apple.developer.family-controls` | | | 文件提供者测试模式 | `com.apple.developer.fileprovider.testing-mode` | | | 字体 | `com.apple.developer.user-fonts` | | | 群组活动 | `com.apple.developer.group-session` | | | HealthKit | `com.apple.developer.healthkit` | | | HomeKit | `com.apple.developer.homekit` | | | 热点 | `com.apple.developer.networking.HotspotConfiguration` | | | 增加内存限制 | `com.apple.developer.kernel.increased-memory-limit` | | | 应用间音频 | `inter-app-audio` | | | 日记建议 | `com.apple.developer.journal.allow` | | | 低延迟 HLS | `com.apple.developer.coremedia.hls.low-latency` | | | MDM 管理的关联域 | `com.apple.developer.associated-domains.mdm-managed` | | | 托管应用安装 UI | `com.apple.developer.managed-app-distribution.install-ui` | | | 地图 | `com.apple.developer.maps` | | | 物质允许设置有效负载 | `com.apple.developer.matter.allow-setup-payload` | | | 媒体设备发现 | `com.apple.developer.media-device-discovery-extension` | | | 消息协作 | `com.apple.developer.shared-with-you.collaboration` | | | 多路径 | `com.apple.developer.networking.multipath` | | | NFC 标签读取 | `com.apple.developer.nfc.readersession.formats` | | | 网络扩展 | `com.apple.developer.networking.networkextension` | | | 5G 网络切片 | `com.apple.developer.networking.slicing.appcategory` 或 `com.apple.developer.networking.slicing.trafficcategory` | | | 能够按需安装 App Clip 扩展 | `com.apple.developer.on-demand-install-capable` | | | 个人 VPN | `com.apple.developer.networking.vpn.api` | | | 推送通知有效负载规范 | `aps-environment` | | | 一键通 | `com.apple.developer.push-to-talk` | | | 重新校准估计 | `com.apple.developer.healthkit.recalibrate-estimates` | | | 敏感内容分析 | `com.apple.developer.sensitivecontentanalysis.client` | | | 浅深度和压力 | `com.apple.developer.submerged-shallow-depth-and-pressure` | | | 与你共享 | `com.apple.developer.shared-with-you` | | | 使用 Apple 登录 | `com.apple.developer.applesignin` | | | SiriKit | `com.apple.developer.siri` | | | 系统扩展 | `com.apple.developer.system-extension.install` | | | 在 iPhone 上点击支付 | `com.apple.developer.proximity-reader.payment.acceptance` | | | 点击以在 iPhone 上出示 ID(仅显示) | `com.apple.developer.proximity-reader.identity.display` | | | 电视服务 | `com.apple.developer.user-management` | | | 时间敏感的通知 | `com.apple.developer.usernotifications.time-sensitive` | | | 钱包 | `com.apple.developer.pass-type-identifiers` | | | WeatherKit | `com.apple.developer.weatherkit` | | | 无线访问器配置 | `com.apple.external-accessory.wireless-configuration` | | | iCloud | `com.apple.developer.icloud-container-identifiers` | | | HLS 插页式预览 | 未知 | 不受支持的功能要么不支持 iOS,要么没有相应的权利值。这是所有 [Apple 官方功能](https://developer.apple.com/help/account/reference/supported-capabilities-ios) 的列表。 ¥The unsupported capabilities either don't support iOS, or they don't have a corresponding entitlement value. Here is a list of all of the [official Apple capabilities](https://developer.apple.com/help/account/reference/supported-capabilities-ios). ## 能力标识符 ¥Capability identifiers 商户 ID、应用组和 CloudKit 容器都可以自动注册并分配给你的应用。这些分配需要 Apple cookie 身份验证(本地运行),因为官方 App Store Connect API 不支持这些操作。 ¥Merchant IDs, App Groups, and CloudKit Containers can all be automatically registered and assigned to your app. These assignments require Apple cookies authentication (running locally) as the official App Store Connect API does not support these operations. ## 调试 iOS 功能 ¥Debugging iOS capabilities 你可以运行 `EXPO_DEBUG=1 eas build` 以获取有关功能同步的更详细日志。 ¥You can run `EXPO_DEBUG=1 eas build` to get more detailed logs regarding the capability syncing. 如果你在使用此功能时遇到问题,可以使用环境变量 `EXPO_NO_CAPABILITY_SYNC=1` 禁用它。 ¥If you have trouble using this feature, you can disable it with the environment variable `EXPO_NO_CAPABILITY_SYNC=1`. 要查看当前启用的所有功能,请访问 [苹果开发者控制台][apple-dev-console],并找到与你的应用匹配的包标识符,如果单击它,你应该会看到所有当前启用的功能的列表。 ¥To see all of the currently enabled capabilities, visit [Apple Developer Console][apple-dev-console], and find the bundle identifier matching your app, if you click on it you should see a list of all the currently enabled capabilities. ## 手动设置 ¥Manual setup 有两种方法可以手动启用 Apple 功能,这两种系统都需要重新生成任何现有的 Apple 配置文件。 ¥There are two ways to manually enable Apple capabilities, both systems will require any existing Apple provisioning profiles to be regenerated. ### Xcode > 对于不使用 [Expo 预建](/workflow/prebuild) 的项目,这是首选方法,用于持续生成原生 android 和 ios 目录。 > > ¥Preferred method for projects that do **not** use [Expo Prebuild](/workflow/prebuild) to continuously generate the native **android** and **ios** directories. 1. 使用 `xed ios` 打开 Xcode 中的 ios 目录。如果你没有 ios 目录,请运行 `npx expo prebuild -p ios` 来生成一个。 ¥Open the **ios** directory in Xcode with `xed ios`. If you don't have an **ios** directory, run `npx expo prebuild -p ios` to generate one. 2. 然后按照 [添加一个能力][apple-enable-capability] 中提到的步骤进行操作。 ¥Then follow the steps mentioned in [Add a capability][apple-enable-capability]. ### 苹果开发者控制台 ¥Apple Developer Console 第一步是将相应的键/值对添加到你的 ios/[app]/[app].entitlements (或多目标应用的更具体的权利文件)。你可以参考 [支持的功能](#supported-capabilities) 来确定应添加哪些权利密钥。 ¥First step is to add the respective key/value pairs to your **ios/[app]/[app].entitlements** (or more specific entitlements file for multi-target apps). You can refer to [Supported Capabilities](#supported-capabilities) to determine which entitlements keys should be added. 1. 登录 [苹果开发者控制台][apple-dev-console]。单击 "证书、ID 和档案",然后导航至 "身份标识" 页面。 ¥Log into [Apple Developer Console][apple-dev-console]. Click on "Certificates, IDs & Profiles", then navigate to "Identifiers" page. 2. 选择与你的应用的打包包标识符匹配的打包包标识符。 ¥Choose the bundle identifier that matches your app's bundle identifier. 3. 向下滚动并启用一项功能,某些功能可能需要额外的设置。 ¥Scroll down and enable a capability, some capabilities may require extra setup. 4. 滚动到顶部并按 "保存"。你将看到一个对话框,显示 "修改应用功能",按 "确认" 继续。你需要重新生成任何使用此打包包标识符的配置文件,然后它们才能有效构建代码签名的生产 .ipa。 ¥Scroll to the top and press "Save". You will see a dialog that says "Modify App Capabilities", press "Confirm" to continue. You will need to regenerate any provisioning profiles that use this bundle identifier before they'll be valid for building a code signed production **.ipa**. 如果添加功能过程未正确完成,那么你的 iOS 原生构建将失败,并显示类似以下内容的错误: ¥If adding capabilities process has not been done correctly then your iOS native build will fail with an error similar to: ```text ❌ error: Provisioning profile "*[expo] app.bacon.hello AppStore ..." doesn't support the Associated Domains capability. ❌ error: Provisioning profile "*[expo] app.bacon.hello AppStore ..." doesn't include the com.apple.developer.associated-domains entitlement. ``` [apple-enable-capability]: https://help.apple.com/xcode/mac/current/#/dev88ff319e7 [apple-dev-console]: https://developer.apple.com/account/resources/identifiers/list ## 运行 reset-project 脚本 了解如何使用 --local 标志在你的计算机或自定义基础架构上本地使用 EAS Build。 你可以使用 `eas build --local` 标志直接在你的机器上运行通常在 EAS Build 服务器上运行的相同构建过程。这是一种调试云构建中发生的构建失败的有用方法,如果不运行相同的步骤,你可能无法重现这些失败。 ¥You can run the same build process that is typically run on the EAS Build servers directly on your machine by using the `eas build --local` flag. This is a useful way to debug build failures that are happening on your cloud builds, which you may not be able to reproduce without running the same set of steps. ```sh $ eas build --platform android --local $ eas build --platform ios --local ``` ## 先决条件 ¥Prerequisites 你需要通过 Expo 的身份验证: ¥You need to be authenticated with Expo: * 运行 `eas login` ¥Run `eas login` * 或者,设置 `EXPO_TOKEN` [使用基于令牌的身份验证](/accounts/programmatic-access) ¥Alternatively, set `EXPO_TOKEN` [using token-based authentication](/accounts/programmatic-access) ## 本地构建的用例 ¥Use cases for local builds * EAS 服务器上的 [调试](#use-local-builds-for-debugging) 构建失败。 ¥[Debugging](#use-local-builds-for-debugging) build failures on EAS servers. * 限制使用第三方 CI/CD 服务的公司政策。通过本地构建,整个过程在你的基础设施上运行,与 EAS 服务器的唯一通信是: ¥Company policies that restrict the use of third-party CI/CD services. With local builds, the entire process runs on your infrastructure and the only communication with EAS servers is: * 确保项目 `@account/slug` 存在 ¥to make sure project `@account/slug` exists * 如果你使用托管凭据来下载它们 ¥if you are using managed credentials to download them ## 使用本地构建进行调试 ¥Use local builds for debugging 如果你在 EAS 服务器上遇到构建失败,并且无法通过检查日志确定原因,你可能会发现在本地调试问题很有帮助。为了简化该过程,我们支持多个环境变量来配置本地构建过程。 ¥If you encounter build failures on EAS servers and you're unable to determine the cause from inspecting the logs, you may find it helpful to debug the issue locally. To simplify that process we support several environment variables to configure the local build process. * `EAS_LOCAL_BUILD_SKIP_CLEANUP=1` - 设置此选项可在构建过程完成后禁用清理工作目录。 ¥`EAS_LOCAL_BUILD_SKIP_CLEANUP=1` - Set this to disable cleaning up the working directory after the build process is finished. * `EAS_LOCAL_BUILD_WORKINGDIR` - 指定构建过程的工作目录,默认情况下它位于 /tmp 目录中的某个位置(取决于平台)。 ¥`EAS_LOCAL_BUILD_WORKINGDIR` - Specify the working directory for the build process, by default it's somewhere (it's platform dependent) in **/tmp** directory. * `EAS_LOCAL_BUILD_ARTIFACTS_DIR` - 成功构建后复制工件的目录。默认情况下,这些文件会被复制到当前目录,如果你正在运行许多连续的构建,这可能是不理想的。 ¥`EAS_LOCAL_BUILD_ARTIFACTS_DIR` - The directory where artifacts are copied after a successful build. By default, these files are copied to the current directory, which may be undesirable if you are running many consecutive builds. 如果你使用 `EAS_LOCAL_BUILD_SKIP_CLEANUP` 和 `EAS_LOCAL_BUILD_WORKINGDIR` 进行 iOS 构建,你应该能够检查工作目录的 `logs` 子目录的内容以读取你的 Xcode 日志。 ¥If you use `EAS_LOCAL_BUILD_SKIP_CLEANUP` and `EAS_LOCAL_BUILD_WORKINGDIR` for iOS builds you should be able to inspect the contents of the `logs` subdirectory of the working directory to read your Xcode logs. ## 局限性 ¥Limitations 一些可用于云构建的选项在本地不可用。你应该注意的限制: ¥Some of the options available for cloud builds are not available locally. Limitations you should be aware of: * 你只能针对特定平台进行构建(选项 `all` 已禁用)。 ¥You can only build for a specific platform (option `all` is disabled). * 不支持自定义软件版本,eas.json 中的 `node`、`yarn`、`fastlane`、`cocoapods`、`ndk`、`image` 字段将被忽略。 ¥Customizing versions of software is not supported, fields `node`, `yarn`, `fastlane`, `cocoapods`, `ndk`, `image` in **eas.json** are ignored. * 不支持缓存。 ¥Caching is not supported. * 不支持带有 ["密钥" 可见性](/eas/environment-variables/#visibility-settings-for-environment-variables) 的 EAS 环境变量(请在你的本地环境中设置它们)。 ¥EAS environment variables with ["Secret" visibility](/eas/environment-variables/#visibility-settings-for-environment-variables) are not supported (set them in your local environment instead). * 你有责任确保环境安装了所有必要的工具: ¥You are responsible for making sure that the environment has all the necessary tools installed: * Node.js/Yarn/npm * 快速通道(仅限 iOS) ¥fastlane (iOS only) * CocoaPods(仅限 iOS) ¥CocoaPods (iOS only) * Android SDK 和 NDK ¥Android SDK and NDK * 在 Windows 上,你可以使用 [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) 进行本地 EAS 构建。但是,我们并未针对此平台进行正式测试,并且不支持 Windows 本地构建(支持 macOS 和 Linux)。 ¥On Windows, you can use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install) for local EAS Builds. However, we do not officially test against this platform and do not support Windows for local builds (macOS and Linux are supported). ## 本地开发和生产构建的应用编译 ¥App compilation for development and production builds locally 要使用 Expo CLI 在本地编译你的应用以进行开发,请改用 `npx expo run:android` 或 `npx expo run:ios` 命令。如果你使用 [持续的原生生成](/workflow/continuous-native-generation),你还可以运行 [prebuild](/workflow/prebuild) 来生成你的 android 和 ios 目录,然后继续在相应的 IDE 中打开项目并像任何原生项目一样构建它们。有关更多详细信息,请参阅: ¥To compile your app locally for development with Expo CLI, use `npx expo run:android` or `npx expo run:ios` commands instead. If you use [Continuous Native Generation](/workflow/continuous-native-generation), you can also run [prebuild](/workflow/prebuild) to generate your **android** and **ios** directories and then proceed to open the projects in the respective IDEs and build them like any native project. For more details, see: 要在本地创建生产版本,你需要在计算机上安装 Android Studio 和 Xcode。查看以下指南以了解更多信息: ¥To create a production build locally, you need Android Studio and Xcode installed on your computer. See the following guide for more information: 使用上述任何一种方法,你都将遵循与使用 EAS Build 在云上创建构建不同的程序 — 这就是 `eas build --local` 标志的用途。 ¥With any of the above approaches, you'll be following procedures which are different from creating a build on the cloud with EAS Build — that is what the `eas build --local` flag is for. ## 缓存依赖 了解如何通过缓存依赖来加快构建速度。 在构建作业开始编译项目之前,所有项目依赖都需要在磁盘上可用。获取依赖所需的时间越长,你需要等待构建完成的时间就越长,因此缓存依赖是加快构建速度的重要部分。 ¥Before a build job can begin compiling your project, all project dependencies need to be available on disk. The longer it takes to acquire the dependencies, the more you need to wait for your build to complete — so caching dependencies is an important part of speeding up your builds. > 我们正在积极致力于改进缓存和构建过程的其他方面,以使构建可靠、快速。 > > ¥We're actively working on improving caching and other aspects of the build process to make builds reliably fast. ## 自定义缓存 ¥Custom caching [eas.json](/build/eas-json) 中构建配置文件上的 `cache` 字段可用于配置特定文件和目录的缓存。成功构建后,指定的文件将保存到持久存储中,并在安装 JavaScript 依赖后在后续构建中恢复。恢复不会覆盖现有文件。更改 `cache.key` 值将使缓存失效。更改 `cache` 对象的任何其他属性也会使缓存失效。 ¥The `cache` field on build profiles in [eas.json](/build/eas-json) can be used to configure caching for specific files and directories. Specified files will be saved to persistent storage after a successful build and restored on subsequent builds after the JavaScript dependencies are installed. Restoring does not overwrite existing files. Changing the `cache.key` value will invalidate the cache. Changing any other property of the `cache` object will also invalidate the cache. ## JavaScript 依赖 ¥JavaScript dependencies EAS Build 运行一个 npm 缓存服务器,可以加快下载构建作业的 JavaScript 依赖的速度。默认情况下,使用 npm 或 Yarn 2+ 的项目将使用缓存。但是,Yarn 1(经典版)要求你应用此 [workaround](/build-reference/npm-cache-with-yarn) 以使用项目 package.json 中的缓存。 ¥EAS Build runs an npm cache server that can speed up downloading JavaScript dependencies for your build jobs. By default, projects using npm or Yarn 2+ will use the cache. However, Yarn 1 (Classic) requires that you apply this [workaround](/build-reference/npm-cache-with-yarn) to use the cache in your project's **package.json**. 要在构建中禁用我们的 npm 缓存服务器,请在 eas.json 中将 `EAS_BUILD_DISABLE_NPM_CACHE` 环境变量值设置为 `"1"`。 ¥To disable using our npm cache server for your builds set the `EAS_BUILD_DISABLE_NPM_CACHE` env variable value to `"1"` in **eas.json**. ```json eas.json { "build": { "production": { "env": { "EAS_BUILD_DISABLE_NPM_CACHE": "1" } } } } ``` ### 不可变的锁文件 ¥Immutable lockfiles 默认情况下,Node 软件包将使用你首选的软件包管理器的不可变 lockfile 标志/命令(例如 `yarn --frozen-lockfile` 或 `npm ci`)进行安装。如果你想禁用此功能,可以在 eas.json 中将 `EAS_NO_FROZEN_LOCKFILE` 环境变量设置为 `"1"`。 ¥By default, Node packages will be installed with your preferred package manager's immutable lockfile flag/command (for example, `yarn --frozen-lockfile` or `npm ci`). If you would like to disable this, you can set the `EAS_NO_FROZEN_LOCKFILE` environment variable to `"1"` in **eas.json**. ## Android 依赖 ¥Android dependencies EAS Build 运行 Maven 缓存服务器,可以加快构建作业下载 Android 依赖的速度。 ¥EAS Build runs a Maven cache server that can speed up downloading Android dependencies for your build jobs. 目前,我们正在缓存: ¥Currently, we are caching: * `maven-central` - [https://repo1.maven.org/maven2/](https://repo1.maven.org/maven2/) * `google` - [https://maven.google.com/](https://maven.google.com/) * `jcenter` - [https://jcenter.bintray.com/](https://jcenter.bintray.com/) * `plugins` - [https://plugins.gradle.org/m2/](https://plugins.gradle.org/m2/) 要在构建中禁用我们的 Maven 缓存服务器,请在 eas.json 中将 `EAS_BUILD_DISABLE_MAVEN_CACHE` 环境变量值设置为 `"1"`。 ¥To disable using our Maven cache server for your builds set the `EAS_BUILD_DISABLE_MAVEN_CACHE` env variable value to `"1"` in **eas.json**. ```json eas.json { "build": { "production": { "env": { "EAS_BUILD_DISABLE_MAVEN_CACHE": "1" } } } } ``` ## iOS 依赖 ¥iOS dependencies EAS Build 从缓存服务器提供大多数 CocoaPods 工件。这提高了 `pod install` 倍的一致性,总体上提高了速度。如果你提供自己的 .netrc 或 .curlrc 文件,缓存将被自动绕过。 ¥EAS Build serves most CocoaPods artifacts from a cache server. This improves the consistency of `pod install` times and generally improves speed. The cache will be bypassed automatically if you provide your own **.netrc** or **.curlrc** files. 要禁用我们的 CocoaPods 缓存服务器进行构建,请在 eas.json 中将 `EAS_BUILD_DISABLE_COCOAPODS_CACHE` 环境变量值设置为 `"1"`。 ¥To disable using our CocoaPods cache server for your builds set the `EAS_BUILD_DISABLE_COCOAPODS_CACHE` env variable value to `"1"` in **eas.json**. ```json eas.json { "build": { "production": { "env": { "EAS_BUILD_DISABLE_COCOAPODS_CACHE": "1" } } } } ``` 使用 [prebuild](/workflow/prebuild) 生成 ios 目录 [在构建时远程](/build-reference/ios-builds) 时,通常不会将项目 Podfile.lock 提交给源代码管理。缓存 Podfile.lock 以获得确定性构建可能很有用,但这种情况下的权衡是,因为你在本地开发期间不使用锁文件,所以你无法确定何时需要更改并更新特定依赖 是有限的。如果缓存此文件,有时可能会出现需要清除缓存的构建错误。要缓存 Podfile.lock,请将 ./ios/Podfile.lock 添加到 eas.json 中构建配置文件的 `cache.paths` 列表中。 ¥It is typical to not have your project **Podfile.lock** committed to source control when using [prebuild](/workflow/prebuild) to generate your **ios** directory [remotely at build time](/build-reference/ios-builds). It can be useful to cache your **Podfile.lock** to have deterministic builds, but the tradeoff in this case is that, because you don't use the lockfile during local development, your ability to determine when a change is needed and to update specific dependencies is limited. If you cache this file, you may occasionally end up with build errors that require clearing the cache. To cache **Podfile.lock**, add **./ios/Podfile.lock** to the `cache.paths` list in your build profile in **eas.json**. ```json eas.json { "build": { "production": { "cache": { "paths": ["./ios/Podfile.lock"] } } } } ``` ## 安卓构建流程 了解如何在 EAS Build 上构建 Android 项目。 本页描述了使用 EAS Build 构建 Android 项目的过程。如果你对构建服务的实现细节感兴趣,你可能需要阅读本文。 ¥This page describes the process of building Android projects with EAS Build. You may want to read this if you are interested in the implementation details of the build service. ## 构建过程 ¥Build process 让我们仔细看看使用 EAS Build 构建 Android 项目的步骤。我们将首先在本地计算机上运行一些步骤来准备项目,然后我们将在远程服务上构建项目。 ¥Let's take a closer look at the steps for building Android projects with EAS Build. We'll first run some steps on your local machine to prepare the project, and then we'll build the project on a remote service. ### 本地步骤 ¥Local steps 第一阶段发生在你的计算机上。EAS CLI 负责完成以下步骤: ¥The first phase happens on your computer. EAS CLI is in charge of completing the following steps: 1. 如果 eas.json 中 `cli.requireCommit` 设置为 `true`,检查 git 索引是否干净 - 这意味着不存在任何未提交的更改。如果不干净,EAS CLI 将提供一个选项来为你提交本地更改或中止构建过程。 ¥If `cli.requireCommit` is set to `true` in **eas.json**, check if the git index is clean - this means that there aren't any uncommitted changes. If it's not clean, EAS CLI will provide an option to commit local changes for you or abort the build process. 2. 准备构建所需的凭据,除非 `builds.android.PROFILE_NAME.withoutCredentials` 设置为 `true`。 ¥Prepare the credentials needed for the build unless `builds.android.PROFILE_NAME.withoutCredentials` is set to `true`. * 根据 `builds.android.PROFILE_NAME.credentialsSource` 的值,凭证是从本地凭据.json 文件或 EAS 服务器获取的。如果选择 `remote` 模式但尚不存在凭据,系统会提示你生成新的密钥库。 ¥Depending on the value of `builds.android.PROFILE_NAME.credentialsSource`, the credentials are obtained from either the local **credentials.json** file or from the EAS servers. If the `remote` mode is selected but no credentials exist yet, you're prompted to generate a new keystore. 3. 创建包含存储库副本的 tarball。实际行为取决于你使用的 [视频控制系统工作流程](https://expo.fyi/eas-vcs-workflow)。 ¥Create a tarball containing a copy of the repository. Actual behavior depends on the [VCS workflow](https://expo.fyi/eas-vcs-workflow) you are using. 4. 将项目 tarball 上传到私有 AWS S3 存储桶并将构建请求发送到 EAS Build。 ¥Upload the project tarball to a private AWS S3 bucket and send the build request to EAS Build. ### 远程步骤 ¥Remote steps 接下来,当 EAS Build 收到你的请求时会发生以下情况: ¥Next, this is what happens when EAS Build picks up your request: 1. 为构建创建一个新的 Docker 容器。 ¥Create a new Docker container for the build. * 每个构建都有自己的新容器,其中安装了所有构建工具(Java JDK、Android SDK、NDK 等)。 ¥Every build gets its own fresh container with all build tools installed there (Java JDK, Android SDK, NDK, and so on). 2. 从私有 AWS S3 存储桶下载项目 tarball 并解压。 ¥Download the project tarball from a private AWS S3 bucket and unpack it. 3. 如果设置了 `NPM_TOKEN`,则为 [创建.npmrc](/build-reference/private-npm-packages)。 ¥[Create **.npmrc**](/build-reference/private-npm-packages) if `NPM_TOKEN` is set. 4. 从 package.json 运行 `eas-build-pre-install` 脚本(如果已定义)。 ¥Run the `eas-build-pre-install` script from **package.json** if defined. 5. 在项目根目录中运行 `npm install`(如果 `yarn.lock` 存在,则运行 `yarn install`)。 ¥Run `npm install` in the project root (or `yarn install` if `yarn.lock` exists). 6. 运行 `npx expo-doctor` 来诊断项目配置的潜在问题。 ¥Run `npx expo-doctor` to diagnose potential issues with your project configuration. 7. 托管项目的附加步骤:运行 `npx expo prebuild` 将项目转换为裸项目。此步骤将使用版本化的 Expo CLI。 ¥Additional step for **managed** projects: Run `npx expo prebuild` to convert the project to a bare one. This step will use the versioned Expo CLI. 8. 恢复以前保存的由 [建立档案](/build/eas-json) 中的 `cache.key` 值标识的缓存。 ¥Restore a previously saved cache identified by the `cache.key` value in the [build profile](/build/eas-json). 9. 从 package.json 运行 `eas-build-post-install` 脚本(如果已定义)。 ¥Run the `eas-build-post-install` script from **package.json** if defined. 10. 恢复密钥库(如果它包含在构建请求中)。 ¥Restore the keystore (if it was included in the build request). 11. 注入签名 [配置到 build.gradle](#configuring-gradle)。 ¥Inject the signing [configuration into **build.gradle**](#configuring-gradle). 12. 在项目内的 android 目录中运行 `./gradlew COMMAND`。 ¥Run `./gradlew COMMAND` in the **android** directory inside your project. * `COMMAND` 是在 `builds.android.PROFILE_NAME.gradleCommand` 处的 eas.json 中定义的命令。它默认为 `:app:bundleRelease`,它生成 AAB(Android 应用包)。 ¥`COMMAND` is the command defined in your **eas.json** at `builds.android.PROFILE_NAME.gradleCommand`. It defaults to `:app:bundleRelease` which produces the AAB (Android App Bundle). 13. 已弃用:从 package.json 运行 `eas-build-pre-upload-artifacts` 脚本(如果已定义)。 ¥**Deprecated:** Run the `eas-build-pre-upload-artifacts` script from **package.json** if defined. 14. 存储 [建立档案](/build/eas-json/).conf 中定义的文件和目录的缓存。后续构建将恢复此缓存。 ¥Store a cache of files and directories defined in the [build profile](/build/eas-json/). Subsequent builds will restore this cache. 15. 将应用存档上传到 AWS S3。 ¥Upload the application archive to AWS S3. * 工件路径可以在 `builds.android.PROFILE_NAME.applicationArchivePath` 处的 eas.json 中配置。默认为 `android/app/build/outputs/**/*.{apk,aab}`。我们使用 [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) 包进行模式匹配。 ¥The artifact path can be configured in **eas.json** at `builds.android.PROFILE_NAME.applicationArchivePath`. It defaults to `android/app/build/outputs/**/*.{apk,aab}`. We're using the [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) package for pattern matching. 16. 如果构建成功:如果已定义,则运行 package.json 中的 `eas-build-on-success` 脚本。 ¥If the build was successful: run the `eas-build-on-success` script from **package.json** if defined. 17. 如果构建失败:如果已定义,则运行 package.json 中的 `eas-build-on-error` 脚本。 ¥If the build failed: run the `eas-build-on-error` script from **package.json** if defined. 18. 从 package.json 运行 `eas-build-on-complete` 脚本(如果已定义)。`EAS_BUILD_STATUS` 环境变量设置为 `finished` 或 `errored`。 ¥Run the `eas-build-on-complete` script from **package.json** if defined. The `EAS_BUILD_STATUS` env variable is set to either `finished` or `errored`. 19. 如果在构建配置文件中指定了 `buildArtifactPaths`,则将构建工件存档上传到私有 AWS S3 存储桶。 ¥Upload the build artifacts archive to a private AWS S3 bucket if `buildArtifactPaths` is specified in the build profile. ## 项目自动配置 ¥Project auto-configuration 每次你想要构建新的 Android 应用二进制文件时,我们都会验证项目是否正确设置,以便我们可以在服务器上无缝运行构建过程。这主要适用于裸项目,但在构建托管项目时也会运行类似的步骤。 ¥Every time you want to build a new Android app binary, we validate that the project is set up correctly so we can seamlessly run the build process on our servers. This mainly applies to bare projects, but similar steps are run when building managed projects. ### 安卓密钥库 ¥Android keystore Android 要求你使用证书签署你的应用。该证书存储在你的密钥库中。Google Play 商店根据证书识别应用。这意味着如果你丢失了密钥库,你可能无法在商店中更新你的应用。但是,使用 [播放应用签名](https://developer.android.com/studio/publish/app-signing#app-signing-google-play),你可以降低丢失密钥库的风险。 ¥Android requires you to sign your application with a certificate. That certificate is stored in your keystore. The Google Play Store identifies applications based on the certificate. This means that if you lose your keystore, you may not be able to update your application in the store. However, with [Play App Signing](https://developer.android.com/studio/publish/app-signing#app-signing-google-play), you can mitigate the risk of losing your keystore. 你的应用的密钥库应保密。在任何情况下都不应将其签入存储库。调试密钥库是唯一的例外,因为我们不使用它们将应用上传到 Google Play 商店。 ¥Your application's keystore should be kept private. **Under no circumstances should you check it in to your repository.** Debug keystores are the only exception because we don't use them for uploading apps to the Google Play Store. ### 配置 Gradle ¥Configuring Gradle 你的应用二进制文件需要使用密钥库进行签名。由于我们是在远程服务器上构建项目,因此我们必须想出一种方法来向 Gradle 提供凭据,出于安全原因,这些凭据不会签入你的存储库。在远程步骤之一中,我们将签名配置注入到你的 build.gradle 中。EAS Build 创建 android/app/eas-build.gradle 文件,其中包含以下内容: ¥Your app binary needs to be signed with a keystore. Since we're building the project on a remote server, we had to come up with a way to provide Gradle with credentials which aren't, for security reasons, checked in to your repository. In one of the remote steps, we inject the signing configuration into your **build.gradle**. EAS Build creates the **android/app/eas-build.gradle** file with the following contents: ```groovy android/app/eas-build.gradle // Build integration with EAS import java.nio.file.Paths android { signingConfigs { release { // This is necessary to avoid needing the user to define a release signing config manually // If no release config is defined, and this is not present, build for assembleRelease will crash } } buildTypes { release { // This is necessary to avoid needing the user to define a release build type manually } debug { // This is necessary to avoid needing the user to define a debug build type manually } } } tasks.whenTaskAdded { android.signingConfigs.release { def credentialsJson = rootProject.file("../credentials.json"); def credentials = new groovy.json.JsonSlurper().parse(credentialsJson) def keystorePath = Paths.get(credentials.android.keystore.keystorePath); def storeFilePath = keystorePath.isAbsolute() ? keystorePath : rootProject.file("..").toPath().resolve(keystorePath); storeFile storeFilePath.toFile() storePassword credentials.android.keystore.keystorePassword keyAlias credentials.android.keystore.keyAlias if (credentials.android.keystore.containsKey("keyPassword")) { keyPassword credentials.android.keystore.keyPassword } else { // key password is required by Gradle, but PKCS keystores don't have one // using the keystore password seems to satisfy the requirement keyPassword credentials.android.keystore.keystorePassword } } android.buildTypes.release { signingConfig android.signingConfigs.release } android.buildTypes.debug { signingConfig android.signingConfigs.release } } ``` 最重要的部分是 `release` 签名配置。它配置为从项目根目录下的 credentials.json 文件中读取密钥库和密码。尽管你不需要自己创建此文件,但 EAS Build 在运行构建之前会创建该文件并使用你的凭据填充。 ¥The most important part is the `release` signing config. It's configured to read the keystore and passwords from the **credentials.json** file at the project root. Even though you're not required to create this file on your own, it's created and populated with your credentials by EAS Build before running the build. 该文件像这样导入到 android/app/build.gradle 中: ¥This file is imported in **android/app/build.gradle** like this: ```groovy android/app/build.gradle // ... apply from: "./eas-build.gradle" ``` ## iOS 构建过程 了解如何在 EAS Build 上构建 iOS 项目。 本页描述了使用 EAS Build 构建 iOS 项目的过程。如果你对构建服务的实现细节感兴趣,你可能需要阅读本文。 ¥This page describes the process of building iOS projects with EAS Build. You may want to read this if you are interested in the implementation details of the build service. ## 构建过程 ¥Build process 让我们仔细看看使用 EAS Build 构建 iOS 项目的步骤。我们将首先在本地计算机上运行一些步骤来准备项目,然后我们将在远程服务上构建项目。 ¥Let's take a closer look at the steps for building iOS projects with EAS Build. We'll first run some steps on your local machine to prepare the project, and then we'll build the project on a remote service. ### 本地步骤 ¥Local steps 第一阶段发生在你的计算机上。EAS CLI 负责完成以下步骤: ¥The first phase happens on your computer. EAS CLI is in charge of completing the following steps: 1. 如果 eas.json 中 `cli.requireCommit` 设置为 `true`,检查 git 索引是否干净 - 这意味着不存在任何未提交的更改。如果不干净,EAS CLI 将提供一个选项来为你提交本地更改或中止构建过程。 ¥If `cli.requireCommit` is set to `true` in **eas.json**, check if the git index is clean - this means that there aren't any uncommitted changes. If it's not clean, EAS CLI will provide an option to commit local changes for you or abort the build process. 2. 准备构建所需的凭据。 ¥Prepare the credentials needed for the build. * 根据 `builds.ios.PROFILE_NAME.credentialsSource` 的值,凭证是从本地凭据.json 文件或 EAS 服务器获取的。如果选择了 `remote` 模式但尚不存在凭据,系统会要求你生成它们。 ¥Depending on the value of `builds.ios.PROFILE_NAME.credentialsSource`, the credentials are obtained from either the local **credentials.json** file or from the EAS servers. If the `remote` mode is selected but no credentials exist yet, you're offered to generate them. 3. 裸项目需要额外的步骤:检查 Xcode 项目是否配置为可在 EAS 服务器上构建(以确保设置正确的打包包标识符和 Apple 团队 ID)。 ¥**Bare** projects require an additional step: check whether the Xcode project is configured to be buildable on the EAS servers (to ensure the correct bundle identifier and Apple Team ID are set). 4. 创建包含存储库副本的 tarball。实际行为取决于你使用的 [视频控制系统工作流程](https://expo.fyi/eas-vcs-workflow)。 ¥Create the tarball containing a copy of the repository. Actual behavior depends on the [VCS workflow](https://expo.fyi/eas-vcs-workflow) you are using. 5. 将项目 tarball 上传到私有 AWS S3 存储桶并将构建请求发送到 EAS Build。 ¥Upload the project tarball to a private AWS S3 bucket and send the build request to EAS Build. ### 远程步骤 ¥Remote steps 在下一阶段,当 EAS Build 收到你的请求时,会发生以下情况: ¥In this next phase, this is what happens when EAS Build picks up your request: 1. 为构建创建一个新的 macOS VM。 ¥Create a new macOS VM for the build. * 每个构建都有自己的全新 macOS VM,其中安装了所有构建工具(Xcode、Fastlane 等)。 ¥Every build gets its own fresh macOS VM with all build tools installed there (Xcode, Fastlane, and so on). 2. 从私有 AWS S3 存储桶下载项目 tarball 并解压。 ¥Download the project tarball from a private AWS S3 bucket and unpack it. 3. 如果设置了 `NPM_TOKEN`,则为 [创建.npmrc](/build-reference/private-npm-packages)。 ¥[Create **.npmrc**](/build-reference/private-npm-packages) if `NPM_TOKEN` is set. 4. 从 package.json 运行 `eas-build-pre-install` 脚本(如果已定义)。 ¥Run the `eas-build-pre-install` script from **package.json** if defined. 5. 在项目根目录中运行 `npm install`(如果 yarn.lock 存在,则运行 `yarn install`)。 ¥Run `npm install` in the project root (or `yarn install` if **yarn.lock** exists). 6. 运行 `npx expo-doctor` 来诊断项目配置的潜在问题。 ¥Run `npx expo-doctor` to diagnose potential issues with your project configuration. 7. 恢复凭据 ¥Restore the credentials * 创建一个新的密钥串。 ¥Create a new keychain. * 将分发证书导入密钥串。 ¥Import the Distribution Certificate into the keychain. * 将配置文件写入 ~/Library/MobileDevice/Provisioning Profiles 目录。 ¥Write the Provisioning Profile to the **~/Library/MobileDevice/Provisioning Profiles** directory. * 验证分发证书和配置文件是否匹配(每个配置文件都分配给特定的分发证书,并且不能用于使用任何其他证书构建 iOS)。 ¥Verify that the Distribution Certificate and Provisioning Profile match (every Provisioning Profile is assigned to a particular Distribution Certificate and cannot be used for building the iOS with any other certificate). 8. 托管项目的附加步骤:运行 `npx expo prebuild` 将项目转换为裸项目。此步骤将使用版本化的 Expo CLI。 ¥Additional step for **managed** projects: Run `npx expo prebuild` to convert the project to a bare one. This step will use the versioned Expo CLI. 9. 恢复以前保存的由 [建立档案](/build/eas-json) 中的 `cache.key` 值标识的缓存。 ¥Restore a previously saved cache identified by the `cache.key` value in the [build profile](/build/eas-json). 10. 在项目内的 ios 目录中运行 `pod install`。 ¥Run `pod install` in the **ios** directory inside your project. 11. 从 package.json 运行 `eas-build-post-install` 脚本(如果已定义)。 ¥Run the `eas-build-post-install` script from **package.json** if defined. 12. 使用配置文件的 ID 更新 Xcode 项目。 ¥Update the Xcode project with the ID of the Provisioning Profile. 13. 如果 Gymfile 不存在,请在 ios 目录中创建它(查看 [默认健身房文件](#default-gymfile) 部分)。 ¥Create **Gymfile** in the **ios** directory if it does **not** already exist (check out the [Default Gymfile](#default-gymfile) section). 14. 在 ios 目录下运行 `fastlane gym`。 ¥Run `fastlane gym` in the **ios** directory. 15. 已弃用:从 package.json 运行 `eas-build-pre-upload-artifacts` 脚本(如果已定义)。 ¥**Deprecated:** Run the `eas-build-pre-upload-artifacts` script from **package.json** if defined. 16. 存储 [建立档案](/build/eas-json).conf 中定义的文件和目录的缓存。Podfile.lock 默认被缓存。后续构建将恢复此缓存。 ¥Store a cache of files and directories defined in the [build profile](/build/eas-json). **Podfile.lock** is cached by default. Subsequent builds will restore this cache. 17. 将应用存档上传到私有 AWS S3 存储桶。 ¥Upload the application archive to a private AWS S3 bucket. * 工件路径可以在 `builds.ios.PROFILE_NAME.applicationArchivePath` 处的 eas.json 中配置。它默认为 ios/build/App.ipa。你可以为 `applicationArchivePath` 指定类似 glob 的模式。我们在底层使用 [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) 包。 ¥The artifact path can be configured in **eas.json** at `builds.ios.PROFILE_NAME.applicationArchivePath`. It defaults to **ios/build/App.ipa**. You can specify a glob-like pattern for `applicationArchivePath`. We're using the [fast-glob](https://github.com/mrmlnc/fast-glob#pattern-syntax) package under the hood. 18. 如果构建成功:如果已定义,则运行 package.json 中的 `eas-build-on-success` 脚本。 ¥If the build was successful: run the `eas-build-on-success` script from **package.json** if defined. 19. 如果构建失败:如果已定义,则运行 package.json 中的 `eas-build-on-error` 脚本。 ¥If the build failed: run the `eas-build-on-error` script from **package.json** if defined. 20. 从 package.json 运行 `eas-build-on-complete` 脚本(如果已定义)。`EAS_BUILD_STATUS` 环境变量设置为 `finished` 或 `errored`。 ¥Run the `eas-build-on-complete` script from **package.json** if defined. The `EAS_BUILD_STATUS` env variable is set to either `finished` or `errored`. 21. 如果在构建配置文件中指定了 `buildArtifactPaths`,则将构建工件存档上传到私有 AWS S3 存储桶。 ¥Upload the build artifacts archive to a private AWS S3 bucket if `buildArtifactPaths` is specified in the build profile. ## 使用 Fastlane 构建 iOS 项目 ¥Building iOS projects with Fastlane 我们使用 [快车道](https://fastlane.tools/) 来构建 iOS 项目。更准确地说,我们使用的是 `fastlane gym` 命令 ([请参阅 Fastlane 文档以了解更多信息](https://docs.fastlane.tools/actions/gym/))。此命令允许你在 Gymfile 中声明构建配置。 ¥We're using [Fastlane](https://fastlane.tools/) for building iOS projects. To be more precise, we're using the `fastlane gym` command ([see the Fastlane docs to learn more](https://docs.fastlane.tools/actions/gym/)). This command allows you to declare the build configuration in **Gymfile**. EAS Build 可以使用你自己的 Gymfile。你需要做的就是将此文件放在 ios 目录中。 ¥EAS Build can use your own **Gymfile**. All you need to do is to place this file in the **ios** directory. ### 默认健身房文件 ¥Default Gymfile 如果 ios/Gymfile 文件不存在,iOS 构建器会创建一个默认文件,如下所示: ¥If the **ios/Gymfile** file doesn't exist, the iOS builder creates a default one which looks similar to the following: ```rb ios/Gymfile suppress_xcode_output(true) clean(true) scheme("app") export_options({ method: "app-store", provisioningProfiles: { "com.expo.eas.builds.test.application" => "dd83ed9c-4f89-462e-b901-60ae7fe6d737" } }) export_xcargs "OTHER_CODE_SIGN_FLAGS=\"--keychain /tmp/path/to/keychain\"" disable_xcpretty(true) output_directory("./build") output_name("App") ``` ## 构建配置流程 了解 EAS CLI 如何为 EAS Build 配置项目。 在本指南中,你将了解当 EAS CLI 使用 `eas build:configure`(或 `eas build`,如果项目尚未配置,则运行相同的过程)配置你的项目时会发生什么。 ¥In this guide, you will learn what happens when EAS CLI configures your project with `eas build:configure` (or `eas build`, which runs this same process if the project is not yet configured). 配置项目时,EAS CLI 执行以下步骤: ¥EAS CLI performs the following steps when configuring your project: Step 1: ## Ask you about the platform(s) to configure When you run the command for the first time, it will initialize your EAS project and ask you to select the platform(s) you want to configure. If you only want to use EAS Build for a single platform, that's fine. If you change your mind, you can come back and configure the other later. Step 2: ## Create eas.json The command will create an **eas.json** file in the root directory with the default configuration. It looks something like this: !!!IG0!!! If you have a bare project, it will look a bit different. This is your EAS Build configuration. It defines three build profiles named `"development"`, `"preview"`, and `"production"` (you can have multiple build profiles like `"production"`, `"debug"`, `"testing"`, and so on) for each platform. If you want to learn more about **eas.json** see the [Configuration with **eas.json**](/build/eas-json) page. Step 3: ## Configure the project This step varies depending on the project type you have. Step 3.1: ### Initialization complete This completes the initialization of your project to be compatible with EAS Build. Step 3.2: ### Expo project If you haven't configured your **app.json** with `android.package` and/or `ios.bundleIdentifier` yet, EAS CLI will prompt you to specify them when you create your first build. - `android.package` will be used as the Android application ID which is used to identify your app on the Google Play Store - `ios.bundleIdentifier` will be used to identify you app on the Apple App Store In the example above, the `eas build --platform android` command prompts to set the Android application ID. If you run the command with `--platform ios`, it will prompt you to set the iOS bundle identifier. Step 3.3: ### Bare React Native project There are no additional steps for bare projects. Step 4: ## Next steps That's all there is to configuring a project to be compatible with EAS Build. There is one more step, if you set `cli.requireCommit` to `true` in your **eas.json** — you'll be prompted to commit all the changes we made for you. You can choose to review them before committing, and you can either specify the git commit message or use a default message. ## 构建服务器基础设施 了解使用 EAS 时当前的构建服务器基础设施。 ## 生成器 IP 地址 ¥Builder IP addresses 构建服务器的 IP 地址列表可用 [了解有关第三方库中已知问题的更多信息](https://expo.dev/eas-build-worker-ips.txt)。我们不希望经常更改列表。列表包括 "上次修改" 和 "过期" ISO 8601 时间戳,分别指定列表的上次更新时间和我们保证不更改列表的时间。 ¥A list of the IP addresses of the build servers is available [in this file](https://expo.dev/eas-build-worker-ips.txt). We do not expect to change the list often. The list includes "Last-Modified" and "Expires" ISO 8601 timestamps that respectively specify the last time the list was updated and the time until which we commit to not change the list. Linux 运行器托管在 Google Cloud Platform 中。macOS 运行器托管在我们自己的 macOS 云中。 ¥Linux runners are hosted in Google Cloud Platform. macOS runners are hosted in our own macOS cloud. ## 配置构建环境 ¥Configuring build environment 每个平台的图片都有一个特定版本的 Node.js、Yarn、CocoaPods、Xcode、Ruby、Fastlane 等。你可以覆盖 [eas.json](/build/eas-json) 中的某些版本。如果没有你正在寻找的专用配置选项,你可以使用 [npm 钩子](/build-reference/npm-hooks) 通过 `apt-get` 或 `brew` 安装或更新任何系统依赖。考虑到这些自定义是在构建期间应用的,并且会增加你的构建时间。 ¥Images for each platform have one specific version of Node.js, Yarn, CocoaPods, Xcode, Ruby, Fastlane, and so on. You can override some of the versions in [eas.json](/build/eas-json). If there is no dedicated configuration option you are looking for, you can use [npm hooks](/build-reference/npm-hooks) to install or update any system dependencies with `apt-get` or `brew`. Consider that those customizations are applied during the build and will increase your build times. 为构建选择图片时,你可以使用下面提供的全名或别名之一:`auto`、`latest` 或特定 SDK(例如 `sdk-53`)。 ¥When selecting an image for the build you can use the full name provided below or one of the aliases: `auto`, `latest`, or for a particular SDK such as `sdk-53`. * 使用特定名称可保证环境一致,只需进行少量更新。 ¥The use of a specific name guarantees a consistent environment with only minor updates. * 当使用 `auto` 别名时,将根据项目配置、Expo SDK 版本和 React Native 版本选择构建映像。你可以在 Spin up 构建环境构建日志部分中检查用于构建的映像。 ¥When using the `auto` alias, the build image will be selected based on the project configuration, Expo SDK version, and React Native version. You can check what image is used for a build in the **Spin up build environment** build logs section. * `latest` 别名将分配给具有最新版本软件的映像。 ¥The `latest` alias will be assigned to the image with the most up-to-date versions of the software. * `sdk-53` 别名将分配给最适合 SDK 53 版本的镜像。 ¥The `sdk-53` alias will be assigned to the image best suited for SDK 53 builds. * `sdk-52` 别名将分配给最适合 SDK 52 版本的图片。 ¥The `sdk-52` alias will be assigned to the image best suited for SDK 52 builds. * `sdk-51` 别名将分配给最适合 SDK 51 版本的图片。 ¥The `sdk-51` alias will be assigned to the image best suited for SDK 51 builds. * SDK 别名将随着每个新的 SDK 版本更新。 ¥SDK aliases will be updated with every new SDK release. * `latest` 别名将随着每个新映像的发布而更新。 ¥The `latest` alias will be updated with every new image release. > **info** 注意:如果你未在 eas.json 中提供 `image`,则默认情况下你的构建将使用 `auto` 别名。 > > ¥**Note:** If you do not provide `image` in **eas.json**, your build by default will use the `auto` alias. ## Android 构建服务器配置 ¥Android build server configurations Android 构建器在隔离环境中的虚拟机上运行。每个构建都有自己专用的 VM 实例。 ¥Android builders run on virtual machines in an isolated environment. Every build gets its own dedicated VM instance. * 构建资源: ¥Build resources: * [使用 Kubernetes 部署的 npm 缓存](/build-reference/caching/#javascript-dependencies) ¥[npm cache deployed with Kubernetes](/build-reference/caching/#javascript-dependencies) * [使用 Kubernetes 部署 Maven 缓存](/build-reference/caching/#android-dependencies) ¥[Maven cache deployed with Kubernetes](/build-reference/caching/#android-dependencies) * ~/.gradle/gradle.properties 中的全局 Gradle 配置: ¥Global Gradle configuration in **~/.gradle/gradle.properties**: ```ini ~/.gradle/gradle.properties org.gradle.jvmargs=-Xmx14g -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 org.gradle.parallel=true org.gradle.configureondemand=true org.gradle.daemon=false ``` * ~/.npmrc 中的全局 npm 配置: ¥Global npm configuration in **~/.npmrc**: ```ini ~/.npmrc registry=http://10.4.0.19:4873 ``` * ~/.yarnrc.yml 中的全局 Yarn 配置: ¥Global Yarn configuration in **~/.yarnrc.yml**: ```yaml ~/.yarnrc.yml unsafeHttpWhitelist: - '*' npmRegistryServer: 'http://10.4.0.19:4873' enableImmutableInstalls: false ``` ### Android 服务器镜像 ¥Android server images #### `ubuntu-22.04-jdk-17-ndk-r26b`(`latest`, `sdk-53`) Note: Details --- - Docker image: `ubuntu:jammy-v20250112` - NDK 26.1.10909125 - Node.js 20.19.2 - Bun 1.2.4 - Yarn 1.22.22 - pnpm 9.15.5 - npm 10.8.2 - Java 17 - node-gyp 11.1.0 --- #### Legacy `ubuntu-22.04-jdk-17-ndk-r26b`-like (`sdk-51`, `sdk-52`) Note: Details --- - Docker image: `ubuntu:jammy-v20250112` - NDK 26.1.10909125 - Node.js 20.18.3 - Bun 1.2.4 - Yarn 1.22.22 - pnpm 9.15.5 - npm 10.8.2 - Java 17 - node-gyp 11.1.0 --- #### `ubuntu-22.04-jdk-17-ndk-r25b` (`sdk-50`) Note: Details --- - Docker image: `ubuntu:jammy-20220810` - NDK 25.1.8937393 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 8.9.2 - npm 9.8.1 - Java 17 - node-gyp 10.0.1 --- #### `ubuntu-22.04-jdk-11-ndk-r23b` (`sdk-49`) Note: Details --- - Docker image: `ubuntu:jammy-20220810` - NDK 23.1.7779620 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 8.7.5 - npm 9.8.1 - Java 11 - node-gyp 10.0.1 --- #### `ubuntu-22.04-jdk-17-ndk-r21e` Note: Details --- - Docker image: `ubuntu:jammy-20220810` - NDK 21.4.7075529 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 8.9.2 - npm 9.8.1 - Java 17 - node-gyp 10.0.1 --- #### `ubuntu-22.04-jdk-11-ndk-r21e` Note: Details --- - Docker image: `ubuntu:jammy-20220810` - NDK 21.4.7075529 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 8.7.5 - npm 9.8.1 - Java 11 - node-gyp 10.0.1 --- #### `ubuntu-22.04-jdk-8-ndk-r21e` (deprecated) Note: Details --- - Docker image: `ubuntu:jammy-20220810` - NDK 21.4.7075529 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 7.0.0 - npm 9.8.1 - Java 8 - node-gyp 10.0.1 --- #### `ubuntu-20.04-jdk-11-ndk-r23b` (deprecated) Note: Details --- - Docker image: `ubuntu:focal-20220823` - NDK 23.1.7779620 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 7.0.0 - npm 9.8.1 - Java 11 - node-gyp 10.0.1 --- #### `ubuntu-20.04-jdk-11-ndk-r21e` (deprecated) Note: Details --- - Docker image: `ubuntu:focal-20220823` - NDK 21.4.7075529 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 7.0.0 - npm 9.8.1 - Java 11 - node-gyp 10.0.1 --- #### `ubuntu-20.04-jdk-8-ndk-r21e` (deprecated) Note: Details --- - Docker image: `ubuntu:focal-20220823` - NDK 21.4.7075529 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 7.0.0 - npm 9.8.1 - Java 8 - node-gyp 10.0.1 --- #### `ubuntu-20.04-jdk-11-ndk-r19c` (deprecated) Note: Details --- - Docker image: `ubuntu:focal-20220823` - NDK 19.2.5345600 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 7.0.0 - npm 9.8.1 - Java 11 - node-gyp 10.0.1 --- #### `ubuntu-20.04-jdk-8-ndk-r19c` (deprecated) Note: Details --- - Docker image: `ubuntu:focal-20220823` - NDK 19.2.5345600 - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 7.0.0 - npm 9.8.1 - Java 8 - node-gyp 10.0.1 --- ## iOS build server configurations iOS builder VMs run on Mac mini hosts in an isolated environment. Every build gets its own fresh macOS VM. For more information, see [iOS-specific resource classes](/eas/json/#resourceclass-2). - Build resources: - [npm cache](/build-reference/caching/#javascript-dependencies) - [CocoaPods cache](/build-reference/caching/#ios-dependencies) - [`cocoapods-nexus-plugin`](https://github.com/expo/eas-build/tree/main/packages/cocoapods-nexus-plugin) - Global npm configuration in **~/.npmrc**: ```ini ~/.npmrc registry=http://10.94.183.70:4873 ``` - Global Yarn configuration in **~/.yarnrc.yml**: ```yaml ~/.yarnrc.yml unsafeHttpWhitelist: - '*' npmRegistryServer: 'http://10.94.183.70:4873' enableImmutableInstalls: false ``` ### iOS server images #### `macos-sequoia-15.5-xcode-16.4` (`latest`, `sdk-53`) Note: Details --- - macOS Sequoia 15.5 - Xcode 16.4 (16E140) - Node.js 20.19.2 - Bun 1.2.15 - Yarn 1.22.22 - pnpm 9.15.9 - npm 10.8.2 - fastlane 2.227.1 - CocoaPods 1.16.2 - Ruby 3.2 - node-gyp 11.2.0 - jq 1.8.0 - Azul Zulu JDK 17.58.21 (OpenJDK 17.0.15) - Git 2.49.0 - Git LFS 3.6.1 - applesimutils 0.9.10 - idb-companion 1.1.8 --- #### `macos-sequoia-15.4-xcode-16.3` Note: Details --- - macOS Sequoia 15.4.1 - Xcode 16.3 (16E140) - Node.js 20.19.1 - Bun 1.2.11 - Yarn 1.22.22 - pnpm 9.15.9 - npm 9.8.1 - fastlane 2.227.1 - CocoaPods 1.16.2 - Ruby 3.2 - node-gyp 11.2.0 - jq 1.7.1 - Azul Zulu JDK 17.58.21 (OpenJDK 17.0.15) - Git 2.49.0 - Git LFS 3.6.1 - applesimutils 0.9.10 - idb-companion 1.1.8 --- #### `macos-sequoia-15.3-xcode-16.2` (`sdk-52`) Note: Details --- - macOS Sequoia 15.3 - Xcode 16.2 (16C5032a) - Node.js 20.18.3 - Bun 1.2.4 - Yarn 1.22.22 - pnpm 9.15.5 - npm 9.8.1 - fastlane 2.226.0 - CocoaPods 1.16.2 - Ruby 3.2 - node-gyp 11.1.0 --- #### `macos-sonoma-14.6-xcode-16.1` Note: Details --- - macOS Sonoma 14.6 - Xcode 16.1 (16B40) - Node.js 18.18.0 - Bun 1.1.33 - Yarn 1.22.21 - pnpm 9.12.3 - npm 9.8.1 - fastlane 2.225.0 - CocoaPods 1.16.2 - Ruby 3.2 - node-gyp 10.2.0 --- #### `macos-sonoma-14.6-xcode-16.0` Note: Details --- - macOS Sonoma 14.6 - Xcode 16.0 (16A242d) - Node.js 18.18.0 - Bun 1.1.27 - Yarn 1.22.21 - pnpm 9.10.0 - npm 9.8.1 - fastlane 2.222.0 - CocoaPods 1.15.2 - Ruby 3.2 - node-gyp 10.2.0 --- #### `macos-sonoma-14.5-xcode-15.4` (`sdk-51`, `sdk-50`, `sdk-49`) Note: Details --- - macOS Sonoma 14.5 - Xcode 15.4 (15F31d) - Node.js 18.18.0 - Bun 1.1.13 - Yarn 1.22.21 - pnpm 9.3.0 - npm 9.8.1 - fastlane 2.220.0 - CocoaPods 1.14.3 - Ruby 2.7 - node-gyp 10.1.0 --- #### `macos-sonoma-14.4-xcode-15.3` Note: Details --- - macOS Sonoma 14.4.1 - Xcode 15.3 (15E204a) - Node.js 18.18.0 - Bun 1.0.35 - Yarn 1.22.21 - pnpm 8.14.1 - npm 9.8.1 - fastlane 2.219.0 - CocoaPods 1.14.3 - Ruby 2.7 - node-gyp 10.0.1 --- #### `macos-ventura-13.6-xcode-15.2` Note: Details --- - macOS Ventura 13.6 - Xcode 15.2 (15C500b) - Node.js 18.18.0 - Bun 1.0.23 - Yarn 1.22.21 - pnpm 8.14.1 - npm 9.8.1 - fastlane 2.219.0 - CocoaPods 1.14.3 - Ruby 2.7 - node-gyp 10.0.1 --- #### `macos-ventura-13.6-xcode-15.1` Note: Details --- - macOS Ventura 13.6 - Xcode 15.1 (15C65) - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 8.12.1 - npm 9.8.1 - fastlane 2.217.0 - CocoaPods 1.14.3 - Ruby 2.7 - node-gyp 10.0.1 --- #### `macos-ventura-13.6-xcode-15.0` Note: Details --- - macOS Ventura 13.6 - Xcode 15.0 (15A240d) - Node.js 18.18.0 - Bun 1.0.14 - Yarn 1.22.19 - pnpm 8.7.6 - npm 9.8.1 - fastlane 2.216.0 - CocoaPods 1.13.0 - Ruby 2.7 - node-gyp 10.0.1 --- ### 支持的 Xcode 版本 ¥Supported Xcode versions 我们旨在支持所有稳定的 Xcode 版本,这些版本允许你在构建过程中使用时将你的应用提交到 App Store Connect。 ¥We aim to support all stable Xcode releases that allow you to submit your app to the App Store Connect when used during the build process. 这通常意味着我们支持最新的稳定 Xcode 版本和之前的版本(直到 Apple 推出新的 [新开发版本](https://developer.apple.com/news/upcoming-requirements/?id=04292024a))。 ¥This usually means that we support the latest stable Xcode version and the previous one (until the new [minimal Xcode version requirement](https://developer.apple.com/news/upcoming-requirements/?id=04292024a) is introduced by Apple). ## iOS 应用扩展 了解如何将应用扩展与 EAS Build 结合使用来添加自定义功能。 应用扩展可让你将自定义功能和内容扩展到应用之外,并在用户与其他应用或 iOS 系统功能交互时可供用户使用。EAS Build 提供了在裸项目和托管项目中包含应用扩展的功能。 ¥App extensions let you extend custom functionality and content beyond your app and make it available to users while they're interacting with other apps or iOS system functionality. EAS Build provides affordances for including app extensions in both bare and managed projects. ## 主持项目(实验支持) ¥Managed projects (experimental support) 一个典型的、简单的托管项目,我们有一个应用目标,没有应用扩展。你可以通过编写 [配置插件](/config-plugins/introduction/) (或使用使用自己的配置插件创建扩展的库)来将应用扩展添加到你的项目中。配置插件允许你将目标添加到在构建作业的 "预建" 阶段生成的 Xcode 项目。 ¥A typical, simple managed project we have a single application target and no app extensions. You can add an app extension to your project by writing a [config plugin](/config-plugins/introduction/) (or using a library that creates an extension with its own config plugin). Config plugins let you add targets to the Xcode project that is generated during the "Prebuild" phase of a build job. 在应用配置中使用 `extra.eas.build.experimental.ios.appExtensions` 声明应用扩展,使 EAS CLI 可以在构建开始之前(生成 Xcode 项目之前)了解存在哪些应用扩展,以确保生成并验证所需的凭据。配置插件还可以修改应用配置,在大多数情况下,如果你使用添加扩展的库,那么配置插件还将添加所需的配置以在应用配置中声明扩展。如果你正在编写一个库,我们建议你考虑这一点。以下是直接在 app.json 中声明的示例: ¥Declaring app extensions with `extra.eas.build.experimental.ios.appExtensions` in your app config makes it possible for EAS CLI to know what app extensions exist *before the build starts* (before the Xcode project has been generated) to ensure that the required credentials are generated and validated. Config plugins are also able to modify the app config, and in most cases, if you are using a library that adds an extension then the config plugin will also add the required configuration to declare the extension in your app config. If you are writing a library, we recommend that you consider this. The following is an example of what this would look like if it were declared directly in **app.json**: ```json app.json { "expo": { ... "extra": { "eas": { "build": { "experimental": { "ios": { "appExtensions": [ { "targetName": "myappextension", "bundleIdentifier": "com.myapp.extension", "entitlements": { "com.apple.example": "entitlement value" } } } ] } } } } } ``` ## 裸项目 ¥Bare projects 当你构建裸项目时,EAS CLI 将自动检测 Xcode 项目中配置的应用扩展,并为每个目标生成所有必需的凭据,或者你可以在 credentials.json 中提供它们有关更多信息,请参阅 [多目标项目](/app-signing/local-credentials/#multi-target-project)。 ¥When you build a bare project, EAS CLI will automatically detect app extensions configured in your Xcode project and generate all necessary credentials for each target, or you can provide them in **credentials.json** For more information, see [Multi target project](/app-signing/local-credentials/#multi-target-project). ## 通过 .easignore 忽略文件 了解如何配置 EAS 以在构建过程中忽略不必要的文件。 .easignore 文件定义在将项目上传到 [EAS 构建](/build/introduction) 服务器时 [EAS](https://expo.dev/eas) 应该忽略哪些文件。 ¥A **.easignore** file defines which files [EAS](https://expo.dev/eas) should ignore when uploading your project to the [EAS Build](/build/introduction) servers. > **info** 忽略不必要的文件可以帮助减少应用的存档大小和上传时间。 > > ¥Ignoring unnecessary files can help reduce your app's archive size and upload time. 默认情况下,[EAS 命令行接口](/build/setup/#install-the-latest-eas-cli) 引用 [**.gitignore**](https://git-scm.com/docs/gitignore) 文件(如果存在)来确定要忽略哪些文件。如果你创建 .easignore 文件,EAS CLI 会将其优先于 .gitignore 文件。创建 .easignore 文件时,请包含 .gitignore 文件中的所有文件和目录,并添加要忽略的其他文件。 ¥By default, the [EAS CLI](/build/setup/#install-the-latest-eas-cli) refers to the [**.gitignore**](https://git-scm.com/docs/gitignore) file (if it exists) to determine which files to ignore. If you create a **.easignore** file, the EAS CLI prioritizes it over the **.gitignore** file. When creating a **.easignore** file, include all files and directories from your **.gitignore** file and add additional files you want to ignore. Step 1: Create a **.easignore** file in the root of your project. Step 2: Copy the content of the **.gitignore** file into the **.easignore** file. Then, add any files that are unnecessary for the build process. ```bash .easignore # Copy everything from your .gitignore file here # Ignore files and directories that EAS Build doesn't need to build your app /docs # Ignore native directories (if you are using EAS Build) /android /ios # Ignore test coverage reports /coverage ``` If your project does not contain **android** and **ios** directories, [EAS Build will run Prebuild](/workflow/prebuild/#usage-with-eas-build) to generate these native directories before compilation. Step 3: Save the file and trigger a new build. ```sh $ eas build --platform ios --profile development ``` 你已成功配置 .easignore 文件。 ¥You've successfully configured your **.easignore** file. ## 使用 .easignore 将文件添加到你的项目上传 ¥Adding files to your project upload with .easignore 除了忽略 gitignore 文件中的文件之外,你还可以使用 .easignore 文件将未提交到源代码管理的文件包含在 EAS Build 上传文件中。如果你有自定义脚本,用于在构建之前生成构建过程所需的临时文件,这将非常有用。要将不在源代码管理中的文件上传到 EAS Build,请将其添加到 .easignore 文件中,并以 `!` 为前缀,并与其他 .gitignore 内容一起添加。以 `!` 为前缀的文件应放在最后,因此它优先于任何忽略它的规则。 ¥In addition to ignoring additional files beyond what is in your gitignore file, you can also use the **.easignore** file to include files with your EAS Build upload that are not committed to source control. This is useful if you have custom scripts that generate temporary files needed for your build process just before the build. To upload a file not in source control to EAS Build, add it to the **.easignore** file with a `!` prefix, along with the rest of your **.gitignore** contents. The `!` prefixed file should be last, so it takes precedence over any prior rules that would ignore it. ```bash .easignore # Copy everything from your .gitignore file here /android /ios # Include a file not in source control !temp_file.json ``` ## EAS 构建限制 了解 EAS Build 当前的限制。 EAS Build 旨在适用于任何 React Native 项目。但是,最好了解我们计划解决的某些限制,因为它们可能会阻止你在应用中使用该服务或可能造成不便。 ¥EAS Build is designed to work for any React Native project. However, it is good to be aware of certain limitations that we plan to address since they could prevent you from being able to use the service for your applications or might cause an inconvenience. ## 修复了构建工作服务器上的内存和 CPU 限制 ¥Fixed memory and CPU limits on build worker servers 如果你的构建过程需要大量内存,则可用资源可能不足以构建你的应用。在这种情况下,请考虑在 eas.json 中使用 [`large` 资源类](/eas/json/#resourceclass)。参见 [Android 特定的资源类](/build-reference/infrastructure/#android-build-server-configurations) 和 [iOS 特定的资源类](/build-reference/infrastructure/#ios-build-server-configurations)。 ¥The resources available might be insufficient to build your app if your build process requires a significant amount of memory. In this case, consider using a [`large` resource class](/eas/json/#resourceclass) in the **eas.json**. See [Android-specific resource class](/build-reference/infrastructure/#android-build-server-configurations) and [iOS-specific resource class](/build-reference/infrastructure/#ios-build-server-configurations). 请参阅 [服务器基础架构参考](/build-reference/infrastructure) 了解更多信息。它包含有关 Android (Ubuntu) 和 iOS (macOS) 构建服务器当前规范的最新信息。 ¥See [Server infrastructure reference](/build-reference/infrastructure) for more information. It contains the most up-to-date information about the current specifications of the Android (Ubuntu) and iOS (macOS) build servers. ## 有限的依赖缓存 ¥Limited dependency caching Android 的构建作业从本地缓存安装 npm 和 Maven 依赖。iOS 的构建作业从本地缓存安装 npm 依赖,从缓存服务器安装 CocoaPods 工件。 ¥Build jobs for Android install npm and Maven dependencies from a local cache. Build jobs for iOS install npm dependencies from a local cache, and CocoaPods artifacts from a cache server. 中间工件(如 node_modules 目录)不会被缓存和恢复(例如,基于 package-lock.json 或 yarn.lock),但如果你将它们提交到 Git 存储库,则它们将被上传到构建服务器。 ¥Intermediate artifacts like **node_modules** directories are not cached and restored (for example, based on **package-lock.json** or **yarn.lock**), but if you commit them to your Git repository then they will be uploaded to build servers. 请参阅 [依赖缓存](/build-reference/caching) 了解更多信息。 ¥See [dependency caching](/build-reference/caching) for more information. ## 最长构建持续时间为 2 小时 ¥Maximum build duration of 2 hours 如果你的构建运行时间超过 2 小时,它将被取消。免费计划的此限制较低,并且该限制将来可能会发生变化。 ¥If your build takes longer than 2 hours to run, it will be canceled. This limit is lower on the free plan, and the limit is subject to change in the future. ## 每个平台每个账户的最大待构建数量为 50 个 ¥Maximum number of pending builds is 50 per platform per account 如果你有超过 50 个平台待处理的构建,新的构建将被拒绝,直到待处理的构建数量低于限制。 ¥If you have more than 50 builds pending for a platform, new builds will be rejected until the number of pending builds drops below the limit. ## 建议使用 Yarn 工作区进行 monorepos ¥Yarn workspaces is recommended for monorepos > 注意:除了 Yarn 之外,其他包管理器的官方指导是有限的。 > > ¥**Note**: Official guidance for package managers other than Yarn is limited. 虽然如果你愿意深入研究并了解工具并亲自动手,那么你可能可以使用 Nx 等其他 monorepo 工具取得成功,但 Expo 团队将无法提供有关这些工具的支持和指导。我们建议使用 [Yarn 工作区](https://yarn.nodejs.cn/en/docs/workspaces),因为它是我们目前唯一提供一流集成的 monorepo 工具。 ¥While you likely can have success using other monorepo tools like Nx if you are willing to dig in and understand the tooling and get your hands dirty, the Expo team will be unable to provide support and guidance on those tools. We recommend using [Yarn Workspaces](https://yarn.nodejs.cn/en/docs/workspaces) because it is the only monorepo tool that we provide first-class integration with at the moment. ## 收到有关更改的通知 ¥Get notified about changes 要在这些项目取得进展时收到通知,你可以订阅 [expo.dev/eas](https://expo.dev/eas) 的 EAS 新闻通讯。 ¥To be notified as progress is made on these items, you can subscribe to the EAS newsletter on [expo.dev/eas](https://expo.dev/eas). # EAS 提交 ## EAS 提交 EAS Submit 是一项托管服务,用于将应用二进制文件上传并提交到应用商店。 EAS Submit 是一项托管服务,用于将你的应用二进制文件提交到 Google Play 商店和 Apple App Store。 ¥**EAS Submit** is a hosted service for submitting your app binaries to the Google Play Store and the Apple App Store. 通过创建和处理提交凭据并运行提交流程,你可以轻松提交应用。你可以在构建完成后,通过 CLI 命令或 CI/CD 服务触发提交。这是将 Android 和 iOS 版本提交到应用商店的最快方法。 ¥It makes submitting your apps simple and easy by creating and handling submission credentials and running the submission process. You can trigger a submission from a CLI command, after a build is finished, or from a CI/CD service. It's the fastest way to take Android and iOS builds and submit them to the app stores. ### 开始使用 ¥Get started ## 提交到 Google Play 商店 了解如何通过计算机和 CI/CD 服务将应用提交到 Google Play Store。 本指南概述了如何从计算机或 CI/CD 服务将你的应用提交到 Google Play Store。 ¥This guide outlines how to submit your app to the Google Play Store from your computer or from a CI/CD service. ## 从你的电脑 ¥Submitting your app from your computer 需要 Google Play 开发者账户才能将你的应用提交到 Google Play 商店。你可以在 [Google Play 管理中心注册页面](https://play.google.com/apps/publish/signup/) 上注册 Google Play 开发者账户。 ¥A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up for a Google Play Developer account on the [Google Play Console sign-up page](https://play.google.com/apps/publish/signup/). 点击 [谷歌游戏控制台](https://play.google.com/apps/publish/) 中的“创建应用”创建一个应用。 ¥Create an app by clicking **Create app** in the [Google Play Console](https://play.google.com/apps/publish/). EAS 要求你上传并配置 Google 服务账户密钥,才能将你的 Android 应用提交到 Google Play 商店。你可以按照 [使用 EAS 上传 Google 服务帐号密钥,用于 Play 商店提交](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) 指南创建一个。 ¥EAS requires you to upload and configure a Google Service Account Key to submit your Android app to the Google Play Store. You can create one with the [uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) guide. 安装 EAS CLI 并使用你的 Expo 账户登录: ¥Install EAS CLI and login with your Expo account: ```sh $ npm install -g eas-cli && eas login ``` 在 app.json 中包含你应用的包名: ¥Include your app's package name in **app.json**: ```json app.json { "android": { "package": "com.yourcompany.yourapp" } } ``` 你需要一个准备好提交到商店的生产版本。你可以使用 [EAS 构建](/build/introduction/) 创建一个: ¥You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/): ```sh $ eas build --platform android --profile production ``` 或者,你可以使用 `eas build --platform android --profile production --local` 或 Android Studio 在自己的计算机上构建应用。 ¥Alternatively, you can build the app on your own computer with `eas build --platform android --profile production --local` or with Android Studio. 你必须至少手动上传一次你的应用。这是 Google Play 商店 API 的限制。 ¥You have to upload your app manually at least once. This is a limitation of the Google Play Store API. 请参阅 [首次提交 Android 应用](https://expo.fyi/first-android-submission) 指南了解如何操作。 ¥Learn how with the [first submission of an Android app](https://expo.fyi/first-android-submission) guide. 完成所有先决条件后,即可启动提交流程。 ¥Once you have completed all the prerequisites, you can start the submission process. 运行以下命令将构建提交到 Google Play Store: ¥Run the following command to submit a build to the Google Play Store: ```sh $ eas submit --platform android ``` 该命令将引导你逐步完成提交应用的过程。你可以通过在 eas.json 中添加提交配置文件来配置提交流程。了解你可以在 [eas.json 参考](/eas/json/#android-specific-options-1) 中提供的所有选项。 ¥The command will lead you step by step through the process of submitting the app. You can configure the submission process by adding a submission profile in **eas.json**. Learn about all the options you can provide in the [eas.json reference](/eas/json/#android-specific-options-1). 要加快提交过程,你可以使用 `--auto-submit` 标志在构建完成后自动提交构建: ¥To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it's built: ```sh $ eas build --platform android --auto-submit ``` 请参阅 [自动提交](/build/automate-submissions/) 指南,了解更多关于 `--auto-submit` 标志的信息。 ¥Learn more about the `--auto-submit` flag in the [automate submissions](/build/automate-submissions/) guide. ## 使用 CI/CD 服务提交你的应用 ¥Submitting your app using CI/CD services 需要 Google Play 开发者账户才能将你的应用提交到 Google Play 商店。你可以在 [Google Play 管理中心注册页面](https://play.google.com/apps/publish/signup/) 上注册 Google Play 开发者账户。 ¥A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up for a Google Play Developer account on the [Google Play Console sign-up page](https://play.google.com/apps/publish/signup/). 点击 [谷歌游戏控制台](https://play.google.com/apps/publish/) 中的“创建应用”创建一个应用。 ¥Create an app by clicking **Create app** in the [Google Play Console](https://play.google.com/apps/publish/). EAS 要求你上传并配置 Google 服务账户密钥,才能将你的 Android 应用提交到 Google Play 商店。你可以按照 [使用 EAS 上传 Google 服务帐号密钥,用于 Play 商店提交](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) 指南创建一个。 ¥EAS requires you to upload and configure a Google Service Account Key to submit your Android app to the Google Play Store. You can create one with the [uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) guide. 安装 EAS CLI 并使用你的 Expo 账户登录: ¥Install EAS CLI and login with your Expo account: ```sh $ npm install -g eas-cli && eas login ``` 在 app.json 中包含你应用的包名: ¥Include your app's package name in **app.json**: ```json app.json { "android": { "package": "com.yourcompany.yourapp" } } ``` 然后,你需要在 eas.json 中提供包含应用 `serviceAccountKeyPath` 的提交配置文件: ¥Then, you'll need to provide a submission profile in **eas.json** that includes your app's `serviceAccountKeyPath`: ```json eas.json { "submit": { "production": { "android": { "serviceAccountKeyPath": "../path/to/api-xxx-yyy-zzz.json" } } } } ``` Android 提交还有其他选项。了解更多信息,请访问 [eas.json 参考](/eas/json/#android-specific-options-1)。 ¥There are additional options available for Android submissions. Learn more from the [eas.json reference](/eas/json/#android-specific-options-1). 你需要一个准备好提交到商店的生产版本。你可以使用 [EAS 构建](/build/introduction/) 创建一个: ¥You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/): ```sh $ eas build --platform android --profile production ``` 或者,你可以使用 `eas build --platform android --profile production --local` 或 Android Studio 在自己的计算机上构建应用。 ¥Alternatively, you can build the app on your own computer with `eas build --platform android --profile production --local` or with Android Studio. 你必须至少手动上传一次你的应用。这是 Google Play 商店 API 的限制。 ¥You have to upload your app manually at least once. This is a limitation of the Google Play Store API. 请参阅 [首次提交 Android 应用](https://expo.fyi/first-android-submission) 指南了解如何操作。 ¥Learn how with the [first submission of an Android app](https://expo.fyi/first-android-submission) guide. 完成所有先决条件后,你可以设置 CI/CD 流水线,将你的应用提交到 Google Play 商店。 ¥Once you have completed all the prerequisites, you can set up a CI/CD pipeline to submit your app to the Google Play Store. ### 使用 EAS Workflows CI/CD ¥Use EAS Workflows CI/CD 你可以使用 [EAS 工作流程](/eas-workflows/get-started/) 自动构建和提交你的应用。 ¥You can use [EAS Workflows](/eas-workflows/get-started/) to build and submit your app automatically. 1. 在项目根目录下创建一个名为 .eas/workflows/submit-android.yml 的工作流文件。 ¥Create a workflow file named **.eas/workflows/submit-android.yml** at the root of your project. 2. 在 submit-android.yml 文件中,你可以使用以下工作流程启动提交 Android 应用的作业: ¥Inside **submit-android.yml**, you can use the following workflow to kick off a job that submits an Android app: ```yaml .eas/workflows/submit-android.yml on: push: branches: ['main'] jobs: build_android: name: Build Android app type: build params: platform: android profile: production # @info # submit_android: name: Submit to Google Play Store needs: [build_android] type: submit params: platform: android build_id: ${{ needs.build_android.outputs.build_id }} # @end # ``` 上述工作流程将构建 Android 应用,然后将其提交到 Google Play 商店。 ¥The workflow above will build the Android app and then submit it to the Google Play Store. ### 使用其他 CI/CD 服务 ¥Use other CI/CD services 你可以使用其他 CI/CD 服务通过 EAS Submit 提交你的应用,例如 GitHub Actions、GitLab CI 等,只需运行以下命令即可: ¥You can use other CI/CD services to submit your app with EAS Submit, like GitHub Actions, GitLab CI, and more by running the following command: ```sh $ eas submit --platform android --profile production ``` 此命令需要 [个人访问令牌](/accounts/programmatic-access/#personal-access-tokens) 来验证你的 Expo 账户。设置完成后,请在 CI/CD 服务中提供 `EXPO_TOKEN` 环境变量,这将允许 `eas submit` 命令运行。 ¥This command requires a [personal access token](/accounts/programmatic-access/#personal-access-tokens) to authenticate with your Expo account. Once you have one, provide the `EXPO_TOKEN` environment variable in the CI/CD service, which will allow the `eas submit` command to run. ## 提交至苹果应用商店 了解如何通过计算机和 CI/CD 服务将应用提交到 Apple App Store。 本指南概述了如何从计算机或 CI/CD 服务将你的应用提交到 Apple App Store。 ¥This guide outlines how to submit your app to the Apple App Store from your computer or from a CI/CD service. ## 从你的电脑 ¥Submitting your app from your computer 需要 Apple 开发者账户才能将你的应用提交到 Apple App Store。你可以在 [苹果开发者门户](https://developer.apple.com/account/) 上注册 Apple 开发者账户。 ¥An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/). 在 app.json 中包含你应用的 bundle identifier: ¥Include your app's bundle identifier in **app.json**: ```json app.json { "ios": { "bundleIdentifier": "com.yourcompany.yourapp" } } ``` 安装 EAS CLI 并使用你的 Expo 账户登录: ¥Install EAS CLI and login with your Expo account: ```sh $ npm install -g eas-cli && eas login ``` 你需要一个准备好提交到商店的生产版本。你可以使用 [EAS 构建](/build/introduction/) 创建一个: ¥You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/): ```sh $ eas build --platform ios --profile production ``` 或者,你也可以在自己的电脑上使用 `eas build --platform ios --profile production --local` 或 Xcode 构建应用。 ¥Alternatively, you can build the app on your own computer with `eas build --platform ios --profile production --local` or with Xcode. 完成所有先决条件后,即可启动提交流程。 ¥Once you have completed all the prerequisites, you can start the submission process. 运行以下命令将构建提交到 Apple App Store: ¥Run the following command to submit a build to the Apple App Store: ```sh $ eas submit --platform ios ``` 该命令将引导你逐步完成提交应用的过程。你可以通过在 eas.json 中添加提交配置文件来配置提交流程: ¥The command will lead you step by step through the process of submitting the app. You can configure the submission process by adding a submission profile in **eas.json**: ```json eas.json { "submit": { "production": { "ios": { "ascAppId": "your-app-store-connect-app-id" } } } } ``` Note: How to find --- 1. Sign in to [App Store Connect](https://appstoreconnect.apple.com/) and select your team. 2. Navigate to the [Apps](https://appstoreconnect.apple.com/apps). 3. Click on your app. 3. Ensure that **App Store** tab is active. 4. On the left pane, under the **General** section, select **App Information**. 5. Your app's `ascAppId` can be found under **General Information** section under **Apple ID**. --- Learn about all the options you can provide in the [eas.json reference](/eas/json/#ios-specific-options-1). To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it's built: ```sh $ eas build --platform ios --auto-submit ``` Learn more about the `--auto-submit` flag in the [automate submissions](/build/automate-submissions/) guide. ## Submitting your app using CI/CD services An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/). Include your app's bundle identifier in **app.json**: !!!IG2!!! Run the following command to configure your App Store Connect API Key: ```sh $ eas credentials --platform ios ``` The command will prompt you to select the type of credentials you want to configure. 1. Select the `production` build profile 2. Log in with your Apple Developer account and follow the prompts 3. Select **App Store Connect: Manage your API Key** 4. Select **Set up your project to use an API Key for EAS Submit** Note: Do you want to use your own credentials? --- **App Store Connect API Key:** Create your own [API Key](https://expo.fyi/creating-asc-api-key) then set it with the `ascApiKeyPath`, `ascApiKeyIssuerId`, and `ascApiKeyId` fields in **eas.json**. **App Specific Password:** Provide your [password](https://expo.fyi/apple-app-specific-password) and Apple ID Username by passing them in with the `EXPO_APPLE_APP_SPECIFIC_PASSWORD` environment variable and `appleId` field in **eas.json**, respectively. --- Then, you'll need to provide a submission profile in **eas.json** that includes the following fields: !!!IG3!!! Note: How to find --- 1. Sign in to [App Store Connect](https://appstoreconnect.apple.com/) and select your team. 2. Navigate to the [Apps](https://appstoreconnect.apple.com/apps). 3. Click on your app. 3. Ensure that **App Store** tab is active. 4. On the left pane, under the **General** section, select **App Information**. 5. Your app's `ascAppId` can be found under **General Information** section under **Apple ID**. --- Learn about all the options you can provide in the [eas.json reference](/eas/json/#ios-specific-options-1). You'll need a production build ready for store submission. You can create one using [EAS Build](/build/introduction/): ```sh $ eas build --platform ios --profile production ``` Alternatively, you can build the app on your own computer with `eas build --platform ios --profile production --local` or with Xcode. Once you have completed all the prerequisites, you can set up a CI/CD pipeline to submit your app to the Apple App Store. ### Use EAS Workflows CI/CD You can use [EAS Workflows](/eas-workflows/get-started/) to build and submit your app automatically. 1. Create a workflow file named **.eas/workflows/submit-ios.yml** at the root of your project. 2. Inside **submit-ios.yml**, you can use the following workflow to kick off a job that submits an iOS app: !!!IG4!!! The workflow above will build the iOS app and then submit it to the Apple App Store. ### Use other CI/CD services You can use other CI/CD services to submit your app with EAS Submit, like GitHub Actions, GitLab CI, and more by running the following command: ```sh $ eas submit --platform ios --profile production ``` This command requires a [personal access token](/accounts/programmatic-access/#personal-access-tokens) to authenticate with your Expo account. Once you have one, provide the `EXPO_TOKEN` environment variable in the CI/CD service, which will allow the `eas submit` command to run. ## Manual submissions If you ever need to submit your build without going through EAS Submit, for example, if the service is temporarily unavailable for maintenance, you can upload to the Apple App Store manually from a macOS device. Note: How to upload to the Apple App Store manually from a macOS device --- #### Creating an entry on App Store Connect Start by creating an app profile in App Store Connect, if you haven't already: 1. Go to [App Store Connect](https://appstoreconnect.apple.com) and sign in. Make sure you have accepted any legal notices or terms at the top of the page. 2. Click the blue plus button by the Apps header, then click **New App**. 3. Add your app's name, language, bundle identifier, and SKU (this isn't seen by end users, it can be any unique string. A common choice is your app's bundle identifier, for example, "com.company.my-app"). 4. Click **Create**. If this succeeds, then you have created your application record. #### Uploading with Transporter Finally, you need to upload the IPA to the Apple App Store. 1. Download [**Transporter** from the App Store](https://apps.apple.com/app/transporter/id1450874784). 2. Sign in with your Apple ID. 3. Add the build either by dragging the IPA file directly into the Transporter window or by selecting it from the file dialog opened with **+** or **Add App** button. 4. Submit it by clicking the **Deliver** button. This process can take a few minutes, then another 10-15 minutes of processing on Apple's servers. Afterward, you can check the status of your binary in App Store Connect: 1. Visit [App Store Connect](https://appstoreconnect.apple.com), select **My Apps**, and click on the app entry you created earlier. 2. Scroll down to the **Build** section and select your newly uploaded binary. --- ## 使用 eas.json 配置 EAS 提交 了解如何使用 eas.json 配置 EAS 提交项目。 eas.json 是 EAS CLI 和服务的配置文件。它是在你的项目中第一次运行 [`eas build:configure` 命令](/build/setup/#configure-the-project) 时生成的,位于项目根目录的 package.json 旁边。尽管 eas.json 对于使用 EAS Submit 不是必需的,但如果你需要在不同配置之间切换,它会让你的生活更轻松。 ¥**eas.json** is the configuration file for EAS CLI and services. It is generated when the [`eas build:configure` command](/build/setup/#configure-the-project) runs for the first time in your project and is located next to **package.json** at the root of your project. Even though **eas.json** is not mandatory for using EAS Submit, it makes your life easier if you need to switch between different configurations. ## 生产概况 ¥Production profile 如果已在 eas.json 中定义了 `production` 配置文件来配置提交,则在不指定配置文件名称的情况下运行 `eas submit` 将使用 `production` 配置文件。如果 `production` 配置文件中不存在任何值,EAS CLI 将提示你以交互方式提供这些值。 ¥Running `eas submit` without specifying a profile name will use the `production` profile if it is already defined in **eas.json** to configure the submission. If no values exist in the `production` profile, EAS CLI will prompt you to provide the values interactively. 如下所示的 `production` 配置文件是在 CI/CD 流程中运行 Android 和 iOS 提交所必需的,就像 [EAS 工作流程](/eas/workflows/get-started/) 一样: ¥The `production` profile shown below is required to run Android and iOS submissions in a CI/CD process, like with [EAS Workflows](/eas/workflows/get-started/): ```json eas.json { "cli": { "version": ">= 0.34.0" }, "submit": { "production": { "android": { "serviceAccountKeyPath": "../path/to/api-xxx-yyy-zzz.json", "track": "internal" }, "ios": { "ascAppId": "your-app-store-connect-app-id" } } } } ``` 了解更多关于你可以使用 [Android 特定选项](/eas/json/#android-specific-options) 和 [iOS 特定选项](/eas/json/#ios-specific-options) 设置的值的信息。你还可以了解如何提交到 [苹果应用商店](/submit/ios) 和 [谷歌应用商店](/submit/android)。 ¥Learn more about the values you can set with the [Android specific options](/eas/json/#android-specific-options) and the [iOS specific options](/eas/json/#ios-specific-options). You can also learn how to submit to the [Apple App Store](/submit/ios) and the [Google Play Store](/submit/android). ## 多个配置文件 ¥Multiple profiles `submit` 下的 JSON 对象可以包含多个提交配置文件。`submit` 下的每个配置文件可以有任意名称,如下例所示: ¥The JSON object under `submit` can contain multiple submit profiles. Each profile under `submit` can have an arbitrary name as shown in the example below: ```json eas.json { "cli": { }, "build": { // EAS Build configuration }, "submit": { "android": { }, "ios": { } }, "extends": "SUBMIT_PROFILE_NAME_1", "android": { } }, } } ``` 当你选择要提交的构建时,它会选择用于所选构建的配置文件。如果该配置文件不存在,则选择默认的 `production` 配置文件。 ¥When you select a build for submission, it chooses the profile that is used for the selected build. If the profile does not exist, it selects the default `production` profile. 你还可以使用 EAS CLI 通过指定参数来获取另一个 `submit` 配置文件,例如: ¥You can also use EAS CLI to pick up another `submit` profile by specifying it with a parameter, for example: ```sh $ eas submit --platform ios --profile ``` ## 在 `submit` 配置文件之间共享配置 ¥Share configuration between `submit` profiles `submit` 配置文件可以使用 `extends` 密钥扩展另一个配置文件。 ¥A `submit` profile can extend another profile using the `extends` key. 例如,在 `preview` 配置文件中,你可能有 `"extends": "production"`。这使得 `preview` 配置文件继承 `production` 配置文件的配置。 ¥For example, in the `preview` profile you may have `"extends": "production"`. This makes the `preview` profile inherit the configuration of the `production` profile. 只要避免产生循环依赖,你就可以将配置文件扩展链接至 5 个深度。 ¥You can keep chaining profile extensions up to the depth of 5 as long as you avoid making circular dependencies. ## 下一步 ¥Next step # EAS 托管 ## EAS Hosting 简介 EAS Hosting 是一种快速部署使用 Expo Router 库和 React Native Web 构建的 Web 项目的服务。 > **important** EAS Hosting 目前处于预览阶段。如果你有任何问题或反馈,请发布到我们的 [Discord](https://chat.expo.dev/) 通道或发送电子邮件至 [hosting@expo.dev](mailto:hosting@expo.dev)。 > > ¥**EAS Hosting** is currently in preview. If you have any issues or feedback, post to our [Discord](https://chat.expo.dev/) channel or send us an email at [hosting@expo.dev](mailto:hosting@expo.dev). EAS Hosting 是一种快速部署使用 [Expo 路由](/router/introduction/) 和 React Native Web 构建的 Web 项目的服务。它与 Expo CLI 无缝集成,允许你自动部署 API 路由、服务器功能和服务器端资源。 ¥**EAS Hosting** is a service for quickly deploying your web projects built using [Expo Router](/router/introduction/) and React Native web. It seamlessly integrates with the Expo CLI, allowing you to automate the deployment of API routes, server functions, and server-side assets. 托管服务提供了从 `npx create-expo-app` 到完整部署的 Web 应用的最快路径,该应用具有 API 路由和服务器功能。 ¥Hosting offers the fastest path from `npx create-expo-app` to a fully deployed web app with API routes and server functions. ### 开始使用 ¥Get started ## 部署你的第一个 Expo Router 和 React 应用 了解如何将你的 Expo Router 和 React 应用部署到 EAS Hosting。 EAS Hosting 是一种反应托管服务,可让你将导出的 Expo Web 构建部署到预览或生产 URL。 ¥EAS Hosting is a react hosting service that allows you to deploy an exported Expo web build to a preview or production URL. 本指南将引导你完成创建第一个 Web 部署的过程。 ¥This guide will walk you through the process of creating your first web deployment. Video Tutorial: [Watch: Deploy your Expo Router web project](https://www.youtube.com/watch?v=NaKsfWciJLo) ## 为什么选择 EAS 托管 ¥Why EAS Hosting 从历史上看,建议使用传统的网站托管服务来部署 Expo Router 和 React 应用。但是,这种方法并不能解决处理原生应用的独特挑战。以下是一些关键限制: ¥Historically, traditional website hosting services were recommended for deploying Expo Router and React apps. However, this approach doesn't address the unique challenges of dealing with native apps. Here are some key limitations: * 版本同步:在应用商店发布过程中,你可能需要部署服务器的新版本。 ¥Version synchronization: During the app store publishing process, you may need to deploy new versions of your servers. * 请求路由复杂度:原生应用的不同版本可能需要路由到特定的服务器版本。这会在处理请求时产生额外的复杂性。 ¥Request routing complexity: Different versions of your native app may require routing to specific server versions. This can create additional complexity when handling requests. * 特定于平台的分析:运行原生应用时,你需要增强平台特定指标的可观察性。 ¥Platform-specific analysis: When running native apps, you need enhanced observability for platform-specific metrics. EAS Hosting 的引入旨在解决这些限制。 ¥The introduction of EAS Hosting aims to address these limitations. ## 先决条件 ¥Prerequisites Note: An Expo user account --- EAS Hosting is available to anyone with an Expo account, regardless of whether you pay for EAS or use the Free plan. You can sign up at [expo.dev/signup](https://expo.dev/signup). Paid subscribers can create more deployments, have more bandwidth, storage, requests, and may set up a custom domain. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing#host). --- Note: An Expo Router web project --- Don't have a project yet? No problem. It's quick and easy to create a "Hello world" app that you can use with this guide. Run the following command to create a new project: ```sh $ npx create-expo-app@latest my-app ``` --- Step 1: ## Install the latest EAS CLI EAS CLI is the command line app you will use to interact with EAS services from your terminal. To install it, run the command: ```sh $ npm install --global eas-cli ``` You can also use the above command to check if a new version of EAS CLI is available. We encourage you to always stay up to date with the latest version. > We recommend using `npm` instead of `yarn` for global package installations. You may alternatively use `npx eas-cli@latest`. Remember to use that instead of `eas` whenever it's called for in the documentation. Step 2: ## Log in to your Expo account If you are already signed in to an Expo account using Expo CLI, you can skip the steps described in this section. If you are not, run the following command to log in: ```sh $ eas login ``` You can check whether you are logged in by running `eas whoami`. Step 3: ## Prepare your project For your app config file's [`expo.web.output`](/versions/latest/config/app/#output), decide whether to set it to either `single`, `static`, or `server`. - `single`: Exports your Expo app to a single-page app with only one `index.html` output - `static`: Exports your Expo app to a [statically generated web app](/router/reference/static-rendering/) - `server`: Supports [server functions](/guides/server-components/#react-server-functions) and [API routes](/router/reference/api-routes/) as well as static pages for your app > Don't worry if you're not sure which output mode you need, you can always change this value later and re-deploy. Step 4: ### Export your app You need to export your web project into a **dist** directory. To do this, run: ```sh $ npx expo export --platform web ``` > Remember to re-run this command every time before deploying. Step 5: ### Deploy your app Now publish your website to EAS Hosting: ```sh $ eas deploy ``` The first time you run this command, it will: 1. Prompt you to connect an EAS project if you haven't done so yet 2. Ask you to choose a preview subdomain name > **info** A **preview subdomain name** is a prefix used for the preview URL of your app. > 例如,如果你选择 `my-app` 作为预览子域名,你的预览 URL 将如下所示:`https://my-app--or1170q9ix.expo.app/`,你的生产 URL 将是:`https://my-app.expo.app/`。 ¥For example, if you choose `my-app` as your preview subdomain name, your preview URL would look something like this: `https://my-app--or1170q9ix.expo.app/`, and your production URL would be: `https://my-app.expo.app/`. 部署完成后,CLI 将输出可访问部署应用的预览 URL,以及 EAS 仪表板上的部署详细信息链接。 ¥Once your deployment is complete, the CLI will output a preview URL for where your deployed app is accessible, as well as a link to the deployment details on the EAS Dashboard. ## 分配别名并提升到生产 了解部署 URL 以及如何设置别名。 ## 部署 ¥Deployments 部署到 EAS Hosting 是不可变的。每个部署都可以通过由预览子域名和部署 ID 组成的唯一部署 URL 进行访问。 ¥Deployments to EAS Hosting are immutable. Each deployment is accessible via a unique deployment URL consisting of the preview subdomain name and the deployment ID. ### 预览子域名 ¥Preview subdomain name 要为项目激活 EAS 托管,你需要选择一个预览子域名。你可以通过 [expo.dev](http://expo.dev) 网站上项目的托管部分执行此操作。或者,当你使用 EAS CLI 创建第一个部署时,系统会提示你选择预览子域。 ¥To activate EAS Hosting for a project, you'll need to choose a **preview subdomain name**. You can do this via the **Hosting** section of your project on the [expo.dev](http://expo.dev) website. Alternatively, you'll be prompted to choose a preview subdomain when you create your first deployment using the EAS CLI. ### 预览和生产 URL ¥Preview and production URLs 预览子域名是用于应用预览 URL 的前缀。例如,如果你选择 `my-app` 作为预览子域名,你的预览 URL 将是:`https://my-app--or1170q9ix.expo.app/`,你的生产 URL 将是:`https://my-app.expo.app/`。 ¥A **preview subdomain name** is a prefix used for the preview URL of your app. For example, if you choose `my-app` as the preview subdomain name, your preview URL would be: `https://my-app--or1170q9ix.expo.app/`, and your production URL would be: `https://my-app.expo.app/`. ### 部署 ID ¥Deployment ID 每个部署都可使用唯一部署 ID 进行识别。此 ID 可以自定义,但默认情况下将是一串随机的字母和数字。 ¥Each deployment is identifiable using a unique deployment ID. This ID can be customized but will be a random string of letters and numbers by default. 部署不可变。一旦部署,它们就无法更改,并且始终可以使用其部署 ID 进行访问和识别。 ¥Deployments are immutable. Once they are deployed, they cannot be changed and will always remain accessible and identifiable using their deployment ID. ## 别名 ¥Aliases 别名是用于创建部署自定义 URL 的用户定义值。 ¥Aliases are user-defined values used for creating custom URLs for deployments. 要进行部署并将其分配给别名,请使用 `--alias` 选项: ¥To make a deployment and assign it to an alias, use the `--alias` option: ```sh $ eas deploy --alias hello ``` 上述命令将创建一个部署,其中 `https://my-app--or1170q9ix.expo.app/` 具有标准 URL,`https://my-app--hello.expo.app/` 具有别名。 ¥The above command will create a deployment with both a standard URL at `https://my-app--or1170q9ix.expo.app/` and an alias at `https://my-app--hello.expo.app/`. > 每个项目的别名都是唯一的。如果你选择的别名已在使用中,它将被重新分配给新的部署。 > > ¥Aliases are unique per project. If you choose an alias that was already in use, it will get re-assigned to the new deployment. 单个部署可以有多个别名。还可以使用 `--id` 选项将别名分配给现有部署: ¥A single deployment can have multiple aliases. Aliases can also be assigned to an existing deployment by using the `--id` option: ```sh $ eas deploy:alias --id=my-id ``` 在上面的命令中,`my-id` 是预览 URL 中的 ID。 ¥In the above command, the `my-id` is the ID in the preview URL. 别名可以有任意名称。例如,如果你想创建一个暂存环境,你可以创建一个名为 `staging` 的别名并为其分配一个部署。 ¥Aliases can have arbitrary names. For example, if you want to create a staging environment, you may create an alias called `staging` and assign a deployment to it. ### 生产别名 ¥Production alias 如果你的预览子域名是 `my-app`,则你的生产 URL 将为 `https://my-app.expo.app/`。 ¥If your preview subdomain name is `my-app`, your production URL will be `https://my-app.expo.app/`. 与其他别名类似,可以使用 `--prod` 选项将部署提升到生产: ¥Similar to other aliases, a deployment can be promoted to production using `--prod` option: ```sh $ eas deploy --prod ``` 也可以使用其部署 ID 和 `--id` 选项将现有部署提升到生产环境: ¥Existing deployment can also be promoted to production using its deployment ID with the `--id` option: ```sh $ eas deploy:alias --prod --id=deploymentId ``` ## 术语 ¥Terminology 在以下示例中,`my-app` 被选为预览子域名: ¥In the following example, `my-app` is selected as the preview subdomain name: * `https://my-app--or1170q9ix.expo.app/`:预览 URL,它是唯一的,并且你的部署可用。 ¥`https://my-app--or1170q9ix.expo.app/` : Preview URL, which is unique and where your deployment is available. * `my-app`:预览子域名。与你的项目绑定的全局唯一前缀。 ¥`my-app`: Preview subdomain name. Globally unique prefix tied to your project. * `or1170q9ix`:部署 ID,此部署独有。 ¥`or1170q9ix`: Deployment ID, which is unique to this deployment. * `https://my-app--hello.expo.app/`:带有别名的部署 URL。 ¥`https://my-app--hello.expo.app/`: A deployment URL with an alias. * `hello`:用户定义的别名。 ¥`hello`: User-defined alias. * `https://my-app.expo.app/`:生产部署 URL。 ¥`https://my-app.expo.app/`: Production deployment URL. ## 常见问题 ¥Common questions ### EAS Hosting 是否提供专用 IP 地址? ¥Does EAS Hosting provide dedicated IP addresses? 不,EAS Hosting 使用 SNI(服务器名称指示),这意味着 IP 地址是共享的,而不是专用于单个项目。 ¥No, EAS Hosting uses **SNI (Server Name Indication)**, which means that IP addresses are shared and are not dedicated to a single project. ## 环境变量在 EAS Hosting 中 了解如何在使用 EAS 托管时在项目中使用环境变量。 Expo Router Web 项目可能包括客户端和服务器端环境变量。当你运行 `npx expo export` 时,客户端环境变量嵌入在应用中并内联在 JavaScript 包中。服务器端环境变量安全地保存在服务器上,并在你运行 `eas deploy` 时与 API 路由代码一起部署。 ¥An Expo Router web project may include client-side and server-side environment variables. The client-side environment variables are embedded in the app and inlined in the JavaScript bundle when you run `npx expo export`. The server-side environment variables are kept securely on the server and are deployed with the API routes code when you run `eas deploy`. > **warning** 对于 EAS 环境变量,只能使用纯文本和敏感的 [环境变量](/eas/environment-variables/#visibility-settings-for-environment-variables)。当你将提交推送到匹配的分支和/或标签时运行你的工作流程。 > > ¥With EAS environment variables, only **plain text** and **sensitive** [environment variables](/eas/environment-variables/#visibility-settings-for-environment-variables) can be used. Secrets cannot be deployed with EAS Hosting. ## 客户端环境变量 ¥Client-side environment variables 在浏览器中运行的所有代码都是客户端的。在 Expo Router 项目中,这包括所有非 API Route 或服务器函数的代码。 ¥All the code that runs in the browser is client-side. In an Expo Router project, this includes all code that is *not* an API Route or server function. 客户端代码中的环境变量在构建时内联。你永远不应在客户端代码中放置任何敏感信息,这就是为什么所有客户端环境变量都必须以 [`EXPO_PUBLIC_`](/guides/environment-variables/) 为前缀的原因。 ¥The environment variables in your client-side code are inlined at build time. You should never put any sensitive information in your client-side code, which is why all client-side environment variables must be prefixed with [`EXPO_PUBLIC_`](/guides/environment-variables/). 然后,当你运行 `npx expo export` 时,`process.env.EXPO_PUBLIC_*` 环境变量的所有实例都将被环境中的值替换。 ¥Then, when you run `npx expo export`, all instances of `process.env.EXPO_PUBLIC_*` environment variables will be replaced with values from the environment. ## 服务器端环境变量 ¥Server-side environment variables API 路由中的所有代码(即以 +api.ts 结尾的文件)都在服务器上运行。 ¥All the code in your API routes (that is, files that end with **+api.ts**) runs on the server. 由于服务器上运行的代码对应用用户永远不可见,因此服务器端代码可以安全地使用敏感的环境变量,例如 API 密钥和令牌。 ¥Since the code running on the server is never visible to the app user, the server-side code can safely use sensitive environment variables such as API keys and tokens. 与客户端环境变量不同,服务器端环境变量未内联在代码中,而是在你运行 `eas deploy` 命令时随部署一起上传。 ¥Unlike client-side environment variables, server-side environment variables are not inlined in the code, they are uploaded with the deployment when you run the `eas deploy` command. ## 本地开发的环境变量 ¥Environment variables for local development 对于本地开发,服务器端和客户端环境变量都是从 [本地 .env 文件](/guides/environment-variables/) 加载的,应该将其忽略。如果你使用 EAS 环境变量,请使用 [`eas env:pull`](/eas/environment-variables/#sync-the-environment-variables-for-local-development-using-eas-envpull) 检索 `development`、`preview` 或 `production` 的环境变量。 ¥For local development, both server- and client-side environment variables are loaded from a [local **.env** file](/guides/environment-variables/), which should be gitignored. If you're using EAS environment variables, use [`eas env:pull`](/eas/environment-variables/#sync-the-environment-variables-for-local-development-using-eas-envpull) to retrieve the environment variables for `development`, `preview`, or `production`. ## 存储环境变量 ¥Storing environment variables 处理环境变量的两种主要方法是通过 EAS 环境变量或 .env 文件。 ¥The two primary ways of handling environment variables are via EAS environment variables or **.env** files. ### 使用 EAS 环境变量 ¥Using EAS environment variables 使用 [EAS 环境变量](/eas/environment-variables/),你可以使用 EAS CLI 或 Web UI 将所有变量存储在 EAS 中,并在必要时在本地将它们拉下。 ¥With [EAS environment variables](/eas/environment-variables/), you store all variables in EAS using the EAS CLI or the web UI and pull them down locally as and when necessary. 使用 EAS 环境变量时,使用 [`eas env:pull`](/eas/environment-variables/#sync-the-environment-variables-for-local-development-using-eas-envpull) 命令下拉要针对其进行开发的环境。 ¥When using the EAS environment variables, use the [`eas env:pull`](/eas/environment-variables/#sync-the-environment-variables-for-local-development-using-eas-envpull) command to pull down the environment you want to develop against. 对于使用环境变量部署项目,请注意客户端和服务器端代码的环境变量包含在不同的步骤中: ¥For deploying a project with environment variables, note that the environment variables for the client- and server-side code are included at different steps: * 运行向导命令,选择 `npx expo export --platform web` 作为应用的根目录,其他所有内容均使用默认值:因此,在运行导出命令之前,请确保你的 .env 或 .env.local 文件包含正确的环境变量。 ¥Running `npx expo export --platform web` will inline the `EXPO_PUBLIC_` variables in the frontend code. So ensure that your **.env** or **.env.local** file includes the correct environment variables before running the export command. * `eas deploy --environment production` 将在 API 路由中包含给定环境(在本例中为 `production`)的所有变量。使用 `--environment` 标志加载的 EAS 环境变量将优先于 .env 和 .env.local 文件中定义的变量。 ¥`eas deploy --environment production` will include all variables for the given environment (in this case, `production`) in the API routes. EAS Environment variables loaded with the `--environment` flag will take precedence over ones defined in **.env** and **.env.local** files. > **warning** 环境变量是每个部署的,并且部署是不可变的。这意味着在更改环境变量后,你需要重新导出你的项目并重新部署以便更新它们。 > > ¥**Environment variables are per deployment, and deployments are immutable**. This means that after changing an environment variable, you will need to re-export your project, and re-deploy in order for them to be updated. ## 自定义域 为你的生产部署设置自定义域。 默认情况下,你在 EAS Hosting 上的生产部署将如下所示:`my-app.expo.app`,其中 `my-app` 是你选择的预览子域名。如果你拥有一个域,你可以将其作为自定义域分配给生产部署。 ¥By default, your production deployment on EAS Hosting will look like this: `my-app.expo.app` , where `my-app` is your chosen preview subdomain name. If you own a domain, you may assign it as a custom domain to the production deployment. 每个项目只能有一个自定义域,该域分配给生产部署。 ¥Each project can have exactly one custom domain, which is assigned to the production deployment. > **info** 注意:设置自定义域是一项高级功能,在免费计划中不可用。在 [电子防盗系统定价](https://expo.dev/pricing) 了解有关不同计划和福利的更多信息。 > > ¥**Note**: Setting up a custom domain is a premium feature and isn't available on the free plan. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing). ## 先决条件 ¥Prerequisites Note: An EAS Hosting project with a production deployment --- The custom domain will always load the production deployment. Therefore, to add a custom domain to your project, you will need a deployment that's been promoted to production first. --- Note: A domain name --- You will need to own a domain name you want to use. --- ## Assigning a custom domain 1. In your project's dashboard, navigate to [Hosting settings](https://expo.dev/accounts/[accountName]/projects/[projectName]/hosting/settings). 2. If you do not have a production deployment, you'll be prompted to assign one first. 3. Under **Custom domain**, enter the custom domain you'd like to set up. Both apex domains and subdomains are supported. If you own `example.com`, you can select: - `example.com`: apex domain - `anything.example.com`: a subdomain 4. Next, you'll be prompted to fill out some DNS records with your DNS provider: - **Verification**: to prove you own the domain - **SSL**: to set up SSL certificates - **CNAME** (subdomains) or **A record** (apex domains): to point the domain at your production deployment 5. Press the refresh button until all checks pass. Depending on your DNS provider, this step usually only takes a couple of minutes. > If you require for the domain name switchover to be **zero downtime**, it's important to fill out these records one by one in the order they are presented in the table. > That is, add the **Verification TXT** record first, and press "Refresh" until the UI confirms the verification record. Then add the **SSL CNAME** record next until > it is confirmed, and set up the third record last. > If downtime isn't important or relevant, you may add all three DNS records at once. After assigning a custom domain to your app, the custom domain will route to your **production** deployment. ### Custom domain DNS records Two of the three records the dashboard presents you are to validate ownership of your domain. The **Verification TXT** record proves ownership of your domain, since it adds a custom token that can be read back to verify you're setting the domain up on a domain you control. The **SSL CNAME** record proves ownership of your domain to a certificate authority, also known as Domain Control Validation (DCV). This is a CNAME record because both renewal and validation is delegated to an automated process, which prevents certificates from expiring. Both records are created on a subdomain of the custom domain you're setting up. - If you're setting up `example.com`, the records must be created on `_cf-custom-hostname.example.com` and `_acme-challenge.example.com` respectively - If you're setting up `anything.example.com`, the records must be created on `_cf-custom-hostname.anything.example.com` and `_acme-challenge.anything.example.com` respectively Lastly, the third DNS entry that the dashboard presents will always be the actual DNS record that points your domain at EAS Hosting. - For apex domains, the dashboard typically recommends an **A record** to `172.66.0.241` - For subdomains, the dashboard typically recommends a **CNAME record** to `origin.expo.app` Both of these records are equivalent, however some DNS providers do not allow CNAME records to be set up on apex domains. ### Alias and wildcard subdomains > 如果你在 2025 年 3 月 19 日之前已设置自定义域名,则必须先按下项目 [托管设置](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/settings) 中的 "刷新" 按钮,然后才能按照设置通配符域名的说明进行操作。 ¥**info** If you had a custom domain set up already before **March 19, 2025**, you must press the "Refresh" button in your project's [Hosting settings](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/settings) before following instructions for setting up wildcard domains. 虽然每个项目只能设置一个自定义域,但你可以设置更多子域 DNS 记录来处理除生产别名之外的其他别名的请求。请求将被路由到别名与子域名匹配的部署。 ¥While only a single custom domain can be set up per project, you can set up further subdomain DNS records to handle requests for other aliases than just the production alias. Requests will be routed to the deployment whose alias matches the subdomain. 例如,在 [创建 `staging` 别名](/eas/hosting/deployments-and-aliases/#aliases) 之后,你可以为别名设置 CNAME 记录: ¥For example, after [creating a `staging` alias](/eas/hosting/deployments-and-aliases/#aliases), you may set up CNAME record for your alias: * 如果你设置了顶层域名,例如 `example.com`,请在 `staging.example.com` 上创建一条 CNAME 记录,并将其设置为 `origin.expo.app` ¥If you've set up an apex domain, for example `example.com`, create a CNAME record on `staging.example.com` set to `origin.expo.app` * 如果你设置了子域名,例如 `anything.example.com`,请在 `staging.anything.example.com` 上创建一条 CNAME 记录,并将其设置为 `origin.expo.app` ¥If you've set up subdomain domain, for example `anything.example.com`, create a CNAME record on `staging.anything.example.com` set to `origin.expo.app` 如果你想将任何子域名请求定向到你创建的任何别名,你可以改为设置通配符 CNAME 记录: ¥If you'd like to direct any subdomain request to any alias you've created, you may instead set up a wildcard CNAME record: * 如果你设置了顶层域名,例如 `example.com`,请在 `*.example.com` 上创建一条 CNAME 记录,并将其设置为 `origin.expo.app` ¥If you've set up an apex domain, for example `example.com`, create a CNAME record on `*.example.com` set to `origin.expo.app` * 如果你设置了子域名,例如 `anything.example.com`,请在 `*.anything.example.com` 上创建一条 CNAME 记录,并将其设置为 `origin.expo.app` ¥If you've set up a subdomain domain, for example `anything.example.com`, create a CNAME record on `*.anything.example.com` set to `origin.expo.app` 通配符 CNAME 记录始终以 `*` 开头,代表任何子域名。只要你的自定义域名上的子域名设置为 `origin.expo.app`,EAS Hosting 就会尝试将请求发送到分配了匹配名称的别名的部署。 ¥A wildcard CNAME record always starts with `*` and stands for any subdomain. As long as subdomains on your custom domain are set to `origin.expo.app`, EAS Hosting will attempt to send the request to the deployment assigned to an alias with a matching name. `www` 子域名除外。如果你设置了 `www` 子域名,但不存在名为 `www` 的别名,则请求将被重定向到自定义域名并返回 308 响应,并被视为对生产部署的请求。如果你只想为自定义域名上的 `www` 子域名设置自动重定向,请在 `www.` 上创建一条 CNAME 记录,并将其设置为 `origin.expo.app`。 ¥The exceptions are `www` subdomains. If you've set up a `www` subdomain, and no alias named `www` exists, the request will be redirected to the custom domain with a 308 response and be treated as a request to the production deployment. If you wish to only set up an automatic redirection for the `www` subdomain on your custom domain, create a CNAME record on `www.` set to `origin.expo.app`. ## API 路由 了解如何在 EAS Hosting 仪表板上检查来自 API 路由的请求。 > **info** 此页面用于有关 API 路由的 EAS Hosting 特定详细信息。有关该主题的一般文档,请参阅 Expo Router 下的 [API 路由](/router/reference/api-routes/) 文档。 > > ¥This page is for EAS Hosting specific details about API routes. For general documentation about the topic, see the [API routes](/router/reference/api-routes/) documentation under Expo Router. 可以在 EAS Hosting 仪表板上检查 API 路由中发生的崩溃、日志和请求。 ¥Crashes, logs, and requests that occur in API routes can be inspected on the EAS Hosting dashboard. ### 崩溃 ¥Crashes 崩溃是处理请求时抛出的任何未捕获的错误,这会阻止返回响应,例如 `throw new Error("An error!")`。可以在 [托管崩溃](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/crashes) 页面上查看崩溃。 ¥A crash is any uncaught error that is thrown while a request was handled, which prevented a response from being returned, for example, `throw new Error("An error!")`. Crashes may be viewed on the [Hosting crashes](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/crashes) page. 崩溃已分组。如果检测到类似的崩溃,你将只看到一行项目。崩溃详细信息将显示第一次和最后一次已知崩溃发生的堆栈跟踪和元数据。 ¥Crashes are grouped. If similar crashes are detected, you will see just one line item for them. The crash details will show the stack trace and metadata for the first and last known occurrence of the crash. ### 日志 ¥Logs 来自 API 路由和服务器功能(`console.log`、`console.info`、`console.error` 等)的所有日志都记录在部署级别日志页面。转到 [托管部署](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/deployments) > 选择部署 > 日志。 ¥All logs from API routes and server functions (`console.log`, `console.info`, `console.error`, and so on) are recorded on the deployment level logs page. Go to [Hosting deployments](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/deployments) > *select a deployment* > **Logs**. ### 要求 ¥Requests 可以在项目级别 [托管请求](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/requests) 和部署级别 [托管部署](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/deployments) > 选择部署 > 请求中查看请求。 ¥Requests can be viewed on the project level at [Hosting requests](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/requests) and deployment level [Hosting Deployments](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/deployments) > *select a deployment* > **Requests**. 这将显示针对你的服务的请求列表,每个请求包含元数据(状态、浏览器、区域、持续时间等)。这些包括对服务的所有请求,包括对 API 路由的请求。 ¥This will show a list of requests against your service, with metadata (status, browser, region, duration, and more) per request. These include all requests to the service, including requests to API routes. ### 通过 ID 查找请求 ¥Looking up a request by ID 所有响应标头都包含一个类似于 `8ffb63895cf6779b-LHR` 的 `Cf-Ray` 标头。第一部分是请求 ID,你可以使用 [主机 > 请求](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/requests) 中的过滤器通过此 ID 在 EAS 仪表板上查找请求。 ¥All response headers include a `Cf-Ray` header that looks like `8ffb63895cf6779b-LHR`. The first part of this is the request ID and you may look up the request on the EAS dashboard via this ID using the filters in [**Hosting** > **Requests**](https://expo.dev/accounts/\[accountName]/projects/\[projectName]/hosting/requests). 此请求 ID 也会显示在任何服务级错误页面上。 ¥This request ID is also displayed on any service-level error pages. ### 采样 ¥Sampling 如果部署收到大量流量,EAS Hosting 记录的数据将为 [downsampled](https://developers.cloudflare.com/analytics/graphql-api/sampling/)。这意味着随着你的部署收到更多请求,记录的数据点将减少,并且你可能看不到逐一列出的单个请求、日志和崩溃。但是,统计计数(例如请求数或崩溃次数)将估计为仍按比例反映所有请求。 ¥If a deployment receives a high amount of traffic, data that EAS Hosting records will be [downsampled](https://developers.cloudflare.com/analytics/graphql-api/sampling/). This means as your deployments receive more requests, fewer data points will be recorded, and you may not see individual requests, logs, and crashes be listed one by one. However, statistical counts, such as number of requests or crashes, will be estimated to still reflect all requests proportionally. ## 使用 EAS 工作流程进行 Web 部署 了解如何使用 EAS Hosting 和 Workflows 自动化网站和服务器部署。 EAS Workflows 是一种自动化 React Native CI/CD 流水线的好方法,可用于将项目的网站和 API 路由部署到 EAS Hosting,并支持拉取请求 (PR) 预览和生产部署。 ¥EAS Workflows is a great way to automate the React Native CI/CD pipeline for deploying your project's website and API routes to EAS Hosting with pull request (PR) previews and production deployments. ## 设置工作流程 ¥Set up workflows 要使用 [EAS 工作流程](/eas/workflows/get-started/) 自动部署你的项目,请按照 [开始使用 EAS 工作流](/eas/workflows/get-started/) 中的说明操作。你还可以添加 [GitHub 集成](/eas/workflows/get-started/#configure-your-project),将 GitHub 代码库连接到你的工作流程。 ¥To use [EAS Workflows](/eas/workflows/get-started/) to automatically deploy your project, follow the instructions in [Get started with EAS Workflows](/eas/workflows/get-started/). You can also add the [GitHub integration](/eas/workflows/get-started/#configure-your-project) to connect a GitHub repository to your workflows. ## 创建部署工作流 ¥Create a deployment workflow 将以下文件添加到 .eas/workflows/deploy.yml。这将使用生产环境变量,导出 Web 包,部署你的项目,并在你推送到 `main` 分支时将其提升到生产状态。 ¥Add the following file to **.eas/workflows/deploy.yml**. This will use the production environment variables, export the web bundle, deploy your project and promote it to production whenever you push to the `main` branch. ```yaml .eas/workflows/deploy.yml name: Deploy on: push: branches: ['main'] jobs: deploy: type: deploy name: Deploy environment: production params: prod: true ``` 现在,每当将提交推送到 `main` 或合并 PR 时,工作流都会运行以部署你的网站。 ¥Now, whenever a commit is pushed to `main` or a PR is merged, the workflow will run to deploy your website. 你还可以通过手动触发来测试此工作流程: ¥You can also test this workflow by triggering it manually: ```sh $ eas workflow:run .eas/workflows/deploy.yml ``` # 参考 ## 使用 EAS 托管部署缓存 了解缓存在 EAS Hosting 上的工作原理。 ## 使用 API 路由缓存 ¥Caching with API Routes API 路由可以返回 `Cache-Control` 指令,EAS Hosting 将使用这些指令根据缓存指令的值适当地缓存响应。 ¥API routes can return `Cache-Control` directives that will be used by EAS Hosting to cache the response appropriately according to the value of cache directives. ```js export async function GET(request) { return Response.json({ ... }, { headers: { 'Cache-Control': 'public, max-age=3600' }, }); } ``` 响应中存在的 `Cache-Control` 指令将被 EAS Hosting 用于按指定方式缓存响应。例如,如果 `Response` 指定缓存指令,并将 `max-age` 设置为 1800 秒,则在再次调用 API 路由之前,响应将被缓存指定的时间。 ¥`Cache-Control` directives present in the response will be used by EAS Hosting to cache the response as specified. For example, if the `Response` specifies a cache directive with `max-age` set to 1800 seconds, the response will be cached for the specified amount of time before the API route is invoked again. 有关 `Cache-Control` 指令的更多详细信息,请参阅 [MDN 文档](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cache-Control)。 ¥For more details about `Cache-Control` directives, refer to the [MDN documentation](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Cache-Control). ## Cache-Control 指令 ¥Cache-Control directives `Cache-Control` 标头可以作为请求和响应标头的一部分发送,并且是一串以逗号分隔的设置。 ¥`Cache-Control` headers may be sent as part of a request's and a response's headers and are a string of comma-separated settings. 当请求发送 `Cache-Control` 标头时,它通常会发送限制如何传递缓存响应的指令。如果它作为响应标头发送,它会向 EAS Hosting 指定如何缓存响应以及通过重新调用 API 路由重新验证响应的频率。 ¥When a request sends a `Cache-Control` header, it typically sends directives that constrain how a cached response may be delivered. If it's sent as a response header, it specifies to EAS Hosting how a response will be cached and how often it will be revalidated by re-invoking an API route. 如果缓存指令接受参数,则该指令后跟等号和参数的值,例如 `max-age=3600`。如果指令不接受参数,则列出时没有值,例如 `public`。 ¥If a cache directive accepts a parameter, the directive is followed by an equal sign and the parameter's value, for example, `max-age=3600`. If the directive does not accept a parameter, it's listed without a value, for example, `public`. 如果传递了多个缓存指令,则每个指令与最后一个指令之间用逗号分隔,例如 `public, max-age=3600`。 ¥If multiple cache directives are passed, each is separated from the last by a comma, for example, `public, max-age=3600`. ## 可缓存性 ¥Cacheability 几个响应指令确定缓存的响应是否可以缓存或返回给客户端: ¥Several response directives determine whether a cached response may be cached or returned to a client: * `public` — 表示任何缓存(包括 EAS 托管)都可以存储响应。如果没有它,则意味着响应不会在多个请求之间共享。 ¥`public` — Indicates any cache (including EAS Hosting) may store the response. Without it, it's implied that the response is not shared between multiple requests. * `private` — 表示响应仅供单个用户使用,并且只能由浏览器缓存。 ¥`private` — Indicates the response is intended for a single user and may only be cached by a browser. * `no-store` 或 `no-cache` — 表示此响应可能永远不会被缓存或存储。 ¥`no-store` or `no-cache` — Indicates that this response may never be cached or stored. 例如,指定 `public, max-age=3600` 指定允许 EAS Hosting(除了用户的浏览器之外)存储响应 3600 秒。但是,`private, max-age=3600` 意味着只有用户的浏览器可以存储响应 3600 秒,而 EAS Hosting 不会缓存它。 ¥For example, specifying `public, max-age=3600` specifies that EAS Hosting is (additionally to a user's browser) allowed to store the response for 3600 seconds. However, `private, max-age=3600` means only the user's browser may store the response for 3600 seconds, while EAS Hosting will not cache it. 对未设置 `Authorization` 标头且针对 `HEAD` 或 `GET` 请求方法的请求的响应将自动被视为可公开缓存。 ¥Responses to requests with no `Authorization` header set and are either for the `HEAD` or `GET` request methods are automatically considered publicly cacheable. 要区分浏览器和 EAS Hosting 可能缓存的内容,可以使用 `s-maxage` 指令。例如,使用 `s-maxage=3600` 指令进行响应将允许 EAS Hosting 将响应缓存 3600 秒,而用户的浏览器根本不会缓存它。 ¥To differentiate between what a browser and EAS Hosting may cache, the `s-maxage` directive may be used. For example, responding with the `s-maxage=3600` directive will allow EAS Hosting to cache the response for 3600 seconds, while the user's browser won't cache it at all. ## 标头名称 ¥Header names 如上所示,浏览器和 EAS Hosting 都接受并理解 Cache-Control 标头。要更精细地自定义 EAS Hosting 的缓存,并与用户的浏览器分开,你可以使用 CDN-Cache-Control 标头进行响应。使用此标头时,它会隐式将 public 添加到你的指令中,并强制 EAS Hosting 根据你的指令缓存响应。 ¥As seen above, the Cache-Control header is accepted and understood both by browsers and EAS Hosting. To customize caching for EAS Hosting more granularly, and separately from the user's browser, you can respond with a CDN-Cache-Control header. When this header is used, it implicitly adds public to your directives and forces EAS Hosting to cache the response according to your directives. ```js export async function GET(request) { return Response.json({ ... }, { headers: { 'Cache-Control': 'no-store', // browsers should never store the response 'CDN-Cache-Control': 'max-age=3600', // EAS Hosting should cache for 3600s }, }); } ``` ## 到期指令 ¥Expiration directives * `max-age` 用于指定响应缓存多长时间才会被视为过时 ¥`max-age` is used to specify how long a response is cached until it's considered stale * `s-maxage` 仅用于向 EAS Hosting 指示应缓存响应多长时间 ¥`s-maxage` is used to indicate only to EAS Hosting how long it should cache a response * `no-cache` 相当于指定最大年龄为零 ¥`no-cache` is equivalent to specifying a max-age of zero * `immutable` 用于指示响应可无限期缓存,应尽可能长时间缓存,并且永远不会被视为过时。 ¥`immutable` is used to indicate that the response is indefinitely cacheable and should be cached for as long as possible and is never considered stale. 此外,可以使用两个较新的缓存控制指令来确定陈旧响应的使用时间可能超过为其指定的 `max-age`。 ¥Additionally, two newer cache control directives can be used to determine how stale responses may be used for longer than the `max-age` specified for them. * `stale-while-revalidate` 指定响应的过时时间段。缓存响应被视为过时后,它允许在指定的时间范围内仍将响应返回给客户端,同时在后台重新验证请求。 ¥`stale-while-revalidate` specifies a stale time period for a response. After a cached response is considered stale, it allows the response to still be returned to clients for the specified timeframe, while re-validating the request in the background. * 例如,`max-age=1800, stale-while-revalidate=3600` 指定响应缓存 1800 秒。1800 秒后,如果对此响应发出新请求,则如果请求是在 3600 秒内发出的,则会返回该响应,但请求也会在后台发送到你的 API 路由。 ¥For example, `max-age=1800, stale-while-revalidate=3600` specifies that the response is cached for 1800 seconds. After 1800 seconds, if a new request is made for this response, it is returned if the request is made within 3600 seconds, but the request will also be sent onwards to your API route in the background. * `stale-if-error` 指定在底层 API 路由意外失败时返回响应的过时时间段。这对于使 API 路由具有容错能力非常有用,并且适用于你的 API 路由因运行时错误而崩溃或返回 `500`、`502`、`503` 或 `504` 响应状态的情况。 ¥`stale-if-error` specifies a stale time period for a response to be returned if the underlying API route fails unexpectedly. This is useful to make an API route fault-tolerant and applies when your API route crashes with a runtime error or returns a `500`, `502`, `503` or `504` response status. * 例如,`max-age=1800, stale-if-error=3600` 指定响应缓存 1800 秒。1800 秒后,如果你的 API 路由响应错误,则将过时的缓存响应而不是错误发送给客户端。 ¥For example, `max-age=1800, stale-if-error=3600` specifies that the response is cached for 1800 seconds. After 1800 seconds, if your API route responds with an error, the stale cached response is sent to clients instead of the error. ## 请求指令 ¥Request directives `Cache-Control` 标头可以作为请求标头的一部分发送,并将影响 EAS Hosting 选择返回缓存响应的方式。 ¥`Cache-Control` headers can be sent as part of a request's headers and will affect how EAS Hosting chooses to return cached responses. * `only-if-cached` 仅在缓存时才返回响应,否则将使用 `504` 响应(使用 `must-revalidate` 指令)中止请求 ¥`only-if-cached` will only return a response if it's cached, and otherwise abort the request with a `504` response (with a `must-revalidate` directive) * `no-store`、`no-cache` 或 `max-age=0` 将跳过缓存响应并始终强制 EAS Hosting 忽略其请求缓存 ¥`no-store`, `no-cache`, or `max-age=0` will skip cached responses and always force EAS Hosting to ignore its request cache * 如果缓存的响应早于指定值,`min-fresh` 将跳过缓存的响应。例如,如果缓存响应的时间超过 360 秒,`min-fresh=360` 将阻止返回缓存响应。 ¥`min-fresh` will skip a cached response if it's older than the specified value. For example, `min-fresh=360` will prevent a cached response from being returned if it's been cached for longer than 360 seconds. 此外,`max-stale` 和 `stale-if-error` 可以作为请求的缓存指令的一部分发送,并限制缓存响应的陈旧时间。但是,请记住,这不会覆盖请求的缓存时间,因此这只能用于减少缓存响应的可接受陈旧量。 ¥Additionally, `max-stale` and `stale-if-error` may be sent as part of the request's cache directive and limit the stale time of cached responses. However, remember that this doesn't override how long a request is cached, so this may only be used to **reduce** the amount of acceptable staleness of a cached response. * `max-stale` 指定客户端接受缓存响应的最大可接受时间。例如,如果使用 `stale-while-revalidate=3600` 指令缓存了响应,则请求可以指定 `max-stale=1800` 以仅接受最大时间为 1800 秒的陈旧响应(在其陈旧期限内,而不是 `max-age`) ¥`max-stale` specifies a maximum time that is acceptable for a client to accept a cached response. For example, if a response was cached using the `stale-while-revalidate=3600` directive, a request may specify `max-stale=1800` to instead only accept a stale response with a maximum age of 1800 seconds (in its stale period, not `max-age`) * 如果 API 路由否则会响应错误,则可以使用 `stale-if-error` 自定义接受过时响应的时间段。 ¥`stale-if-error` may be used to customize the period for which stale responses are accepted if the API route would otherwise respond with an error. 对于这两个指令,如果服务器端响应的缓存时间短于响应的 `max-age` 之上指定的 `max-stale` 或 `stale-if-error` 周期,则这些指令将不执行任何操作。 ¥For both directives, if the server-side response was cached for a shorter amount of time than the specified `max-stale` or `stale-if-error` periods on top of the response's `max-age`, then these directives will do nothing. ## 请求方法 ¥Request methods 除了缓存 `GET` 和 `HEAD` 请求外,EAS Hosting 还支持缓存 `POST` 请求。 ¥In addition to caching `GET` and `HEAD` requests, EAS Hosting also supports caching `POST` requests. 如果发送的 `POST` 请求的请求主体小于 1MB,你的响应可能会使用 `public` 指令指定 `Cache-Control` 标头以将请求标记为可缓存。 ¥Provided a `POST` request with a request body smaller than 1MB is sent, your response may specify a `Cache-Control` header with the `public` directive to mark the request as cacheable. ## `Expires` 标头 ¥The `Expires` header EAS Hosting 还支持使用较旧的 [`Expires` 标头](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Expires) 进行缓存。 ¥EAS Hosting also supports caching using the older [`Expires` header](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Expires). 由于这不会将响应标记为可公开缓存,因此通常仅用于未经身份验证的 `GET` 响应。它可以指定 HTTP 日期值,直到缓存响应。在指定的时间戳之后,响应被视为过时。 ¥As this won't mark the response as publicly cacheable, it's typically only used for unauthenticated `GET` responses. It may specify an HTTP Date value until a response is cached. After the specified timestamp, the response is considered stale. ## `Vary` 标头 ¥The `Vary` header 默认情况下: ¥By default: * `GET` 或 `HEAD` 请求仅通过其 URL 缓存 ¥a `GET` or `HEAD` request is only cached by its URL * `POST` 请求仅通过其 URL 和请求正文缓存 ¥a `POST` request is only cached by its URL and request body 但是,你可以使用 `Vary` 标头来指定请求应使用请求标头作为缓存键。例如,如果 API 路由使用 `Vary: custom-header` 进行响应,则仅当请求的 `custom-header` 标头值与缓存请求的 `custom-header` 值匹配时,才会使用缓存的响应。 ¥However, you can use the `Vary` header to specify that the request should be using request headers as a cache key. For example, if an API route responds with `Vary: custom-header` , then the cached response will only be used if the request's `custom-header` header value matches the cached request's `custom-header` value. ## CORS 缓存 ¥CORS caching 对于许多 Web 请求,浏览器将使用 `OPTIONS` 方法发出 CORS 请求,以确定路由的访问控制设置。 ¥For many web requests, the browser will make CORS requests with the `OPTIONS` method to determine access control settings for a route. 这些请求可使用特殊的 `Access-Control-Max-Age` 标头进行缓存。例如,`Access-Control-Max-Age: 3600` 将缓存 `OPTIONS` 响应 3600 秒,这适用于浏览器和 EAS Hosting 缓存。这可防止浏览器发出过多请求,并防止你的 API 路由因 CORS 请求而被过度频繁调用。 ¥These requests are cacheable using the special `Access-Control-Max-Age` header. For example, `Access-Control-Max-Age: 3600` will cache the `OPTIONS` response for 3600 seconds, which applies to both browsers and the EAS Hosting cache. This prevents excessive requests made by browsers and prevents your API route from being called excessively often for CORS requests. ## 资源缓存 ¥Asset Caching 对于你的部署响应的任何资源,浏览器缓存的默认缓存时间为 3600 秒。为了提高性能,每个部署资源都会在内部无限期缓存。由于部署是不可变的,因此这不会影响你。 ¥For any assets your deployment responds with, a default cache time of 3600 seconds will be applied for browser caches. To improve performance, per-deployment assets are cached indefinitely internally. Since deployments are immutable, this doesn't affect you. 当你将新部署分配给别名时,EAS Hosting 将忽略其缓存的资源。例如,当你将新部署推广到生产环境时,缓存将被忽略,你的资源响应应立即切换到新部署。 ¥EAS Hosting will ignore its cached assets when you assign a new deployment to an alias. For example, when you promote a new deployment to production, the cache will be ignored and your asset responses should switch over to your new deployment instantly. ## 计费和指标 ¥Billing and metrics EAS Hosting 按请求计费(以 1M 个请求为单位)。但是,即使缓存的请求由 EAS Hosting 缓存,你仍需为请求付费。 ¥EAS Hosting bills per request (in units of 1M requests). However, cached requests **still count** against your quota and you will be charged for requests, even if they are cached by EAS Hosting. 缓存不会影响指标。缓存请求将像任何其他请求一样被记录,EAS 仪表板中的指标也将反映和表示缓存请求。 ¥Metrics are not affected by caching. Cached requests will be logged like any other request, and the metrics in the EAS dashboard will also reflect and represent cached requests. ## 默认响应和标头 使用 EAS Hosting 时自动添加到请求中的默认值。 EAS Hosting 为你的部署应用了几个默认值,这些默认值应该可以帮助你并减少你必须为简单的 API 路由自行添加的代码量。 ¥**EAS Hosting** applies several defaults to your deployment that are supposed to help you and reduce the amount of code you have to add yourself for simple API routes. ## 资源响应 ¥Asset responses 资源响应包含用于浏览器的附加元数据标头,主要用于缓存。 ¥An asset response contains additional metadata headers for browsers, mostly for caching. 所有资源响应中都会添加默认的 `ETag` 标头,以允许浏览器使用 `if-none-match` 请求标头重新验证其缓存。 ¥A default `ETag` header is added to all asset responses to allow browsers to re-validate their caches using `if-none-match` request headers. ## CORS 响应 ¥CORS responses 默认情况下,如果 API 路由不处理 `OPTIONS` 请求,EAS Hosting 将自动使用默认 CORS 响应进行响应。 ¥By default, if an API route does not handle `OPTIONS` requests, EAS Hosting will automatically respond with a default CORS response. 此默认值非常允许,通常允许所有浏览器向 API 路由发出请求。如果你不想这样,请自行处理 API 路由中的 `OPTIONS` 请求。 ¥This default is very permissible and generally allows all browsers to make requests to the API route. **If you don't want this**, handle `OPTIONS` requests in API routes yourself. 默认情况下将发送以下标头: ¥The following headers will be sent by default: ```sh Access-Control-Allow-Origin: Access-Control-Allow-Headers: Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE Access-Control-Allow-Credentials: true Access-Control-Expose-Headers: * Access-Control-Max-Age: 3600 Vary: Origin, Access-Control-Request-Headers ``` 这些标头将允许任何客户端从任何来源、使用任何标头、使用凭据发出请求,并缓存 `OPTIONS` 响应 3600 秒。 ¥These headers will allow any client to make a request from any origin, with any headers, with credentials, and cache the `OPTIONS` response for 3600 seconds. 有关 [可以在 MDN 文档中找到预检 `OPTIONS` 请求](https://web.nodejs.cn/en-US/docs/Glossary/Preflight_request) 的更多信息。 ¥More information on [preflight `OPTIONS` requests can be found in the MDN documentation](https://web.nodejs.cn/en-US/docs/Glossary/Preflight_request). ## Strict-Transport-Security 标头 ¥Strict-Transport-Security header 此标头告诉浏览器将来仅使用 HTTPS 协议访问 URL。如果缺少此标头,EAS Hosting 会自动添加此标头。 ¥This header tells browsers to only access a URL with the HTTPS protocol in the future. EAS Hosting automatically adds this header if it's missing. 其默认值设置为 `max-age=31536000; includeSubDomains; preload`。 ¥Its default value is set to `max-age=31536000; includeSubDomains; preload`. 有关此标头为何是良好的默认值、提高安全性和性能的更多信息,请参阅 [阅读有关 `web.dev` 的这篇文章](https://web.dev/blog/bbc-hsts) 并在 MDN 文档中阅读有关 [Strict-Transport-Security](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) 标头的更多信息。 ¥For more information on why this header is a good default, improves security, and performance, [read this article on `web.dev`](https://web.dev/blog/bbc-hsts) and read more about [Strict-Transport-Security](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security) header in the MDN documentation. ## 通用版本标头 ¥Common version headers 默认情况下,EAS Hosting 将删除并且不转发任何 `X-Powered-By` 和 `X-Aspnet-Version` 标头。对于 API 路由,此标头没有多大用处,我们不建议你将 `X-Powered-By` 等替代标头添加到 API 路由,因为它不必要地暴露了你正在运行的代码的内部信息。 ¥By default, EAS Hosting will remove and not forward any `X-Powered-By` and `X-Aspnet-Version` headers. With API routes, this header does not serve much of a purpose and we don't recommend you add alternative headers like `X-Powered-By` to your API routes as it unnecessarily exposes internal information on the code you're running. ## 框架内容安全策略 (CSP) ¥Frame Content-Security Policy (CSP) 默认情况下,浏览器允许任何网页在 iframe 中渲染。不幸的是,这允许网络钓鱼攻击,例如恶意行为者使用点击劫持在页面上覆盖他们自己的控件和输入,以窃取凭据或强迫用户执行某些操作。 ¥By default, browsers allow any webpage to render in an iframe. Unfortunately, this allows phishing attacks such as clickjacking which is used by malicious actors to overlay a page with their own controls and inputs, to steal credentials or coerce users to perform certain actions. 为了防止这种情况,EAS Hosting 默认添加了 `frame-ancestors 'self'` 指令,告诉浏览器不要将 EAS Hosting 嵌入到你无法控制的其他域的 iframe 中。 ¥To prevent this, EAS Hosting adds a `frame-ancestors 'self'` directive by default, telling browsers to not embed EAS Hosting in iframes on other domains that you don't control. 这相当于将(较旧的)`X-Frame-Options` 标头设置为 `SAMEORIGIN`。此默认指令仅适用于具有 `text/html` 内容类型的响应,因此它仅适用于 HTML 页面。 ¥This is equivalent to setting the (older) `X-Frame-Options` header to `SAMEORIGIN`.This default directive is only applied to responses with `text/html` content types, so it will only be applied to HTML pages. 如果你的 API 路由使用自定义 `X-Frame-Options` 标头进行响应,则这些标头将在你的响应中自动转换为 `Content-Security-Policy` 指令。 ¥If your API routes respond with custom `X-Frame-Options` headers, these headers will automatically be converted to `Content-Security-Policy` directives in your response. ## 崩溃页面 ¥Crash pages 如果你的 API 路由抛出未处理的 JavaScript 错误,则由于你的 API 路由无法传递错误,因此将被视为 "crash"。 ¥If your API route throws an unhandled JavaScript error, this is treated as a "crash" since your API route was unable to deliver an error. 在这些情况下,EAS Hosting 将响应错误页面。如果发送了 `Accept: text/html` 请求标头,则错误页面将渲染为 HTML 响应。否则,它只会以纯文本响应进行响应。 ¥EAS Hosting will respond with an error page in these cases. The error page will be rendered as an HTML response, if the `Accept: text/html` request header was sent. Otherwise, it will only respond with a plaintext response. ## 请求查询参数 ¥Request headers EAS Hosting 会在每个请求转发到你的 API 路由之前添加以下标头。这些标头通常会添加有关谁发出请求的更多信息。 ¥**EAS Hosting** will add the following headers to every request, before they're forwarded to your API routes. These headers generally add more information about who made the request. | 请求标头 | 描述 | | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Forwarded` | 以分号分隔的 `for`、`host` 和 `proto` 参数列表。 请参阅 MDN 文档中关于 [HTTP `Forwarded` 标头](https://web.nodejs.cn/en-US/docs/Web/HTTP/Headers/Forwarded) 的更多信息。 | | `X-Forwarded-For` | 以逗号分隔的指定请求的转发器 IP 列表 | | `X-Forwarded-Proto` | 用于发出请求的协议。通常为 `https` | | `X-Forwarded-Host` | 传入请求的主机名 | | `X-Real-IP` | 传入请求的 IP 地址 | | `Origin` | 传入请求的 URL 来源 | | `Host` | 转发请求的主机名(与 `request.url` 的主机名匹配) | | `eas-colo` | 处理请求的 Cloudflare 数据中心的代码。例如,`lhr` | | `eas-ip-continent` | 客户的两字母大洲代码。 以下之一:`AF`、`AN`、`AS`、`EU`、`NA`、`OC` 或 `SA` | | `eas-ip-country` | 客户端的三个字母的国家/地区代码,采用 ISO-3166 Alpha 2 格式。,例如 `US` 或 `JP` | | `eas-ip-region` | 客户端的区域代码,采用 ISO-3166-2 格式,最大长度为三个字符。 | | `eas-ip-city` | 客户端的可读城市名称(可选)。例如,`London` 或 `Chicago` | | `eas-ip-latitude` | 客户端纬度的最佳猜测(可选) | | `eas-ip-longitude` | 客户端经度的最佳猜测(可选) | | `eas-ip-timezone` | 客户端时区。例如,`Europe/London` | | `eas-ip-eu` | 当请求可能源自欧盟管辖区域时,设置为 `1`。 | ### 请求 URL 和来源 ¥Request URL and origin EAS Hosting 将来自多个主机名的请求路由到你的部署。[别名](/eas/hosting/deployments-and-aliases) 和 [自定义域](/eas/hosting/custom-domain) 意味着客户端用于发出请求的传入 URL 与你的 API 路由接收的目标 URL 之间可能存在差异。 ¥EAS Hosting routes requests from several hostnames to your deployments. [Aliases](/eas/hosting/deployments-and-aliases) and [Custom domains](/eas/hosting/custom-domain) mean that there may be a difference between the **incoming** URL that clients have used to make a request, and the **target** URL that your API routes receive. 例如,虽然客户端可能向别名 URL(例如 `https://my-app--staging.expo.app/`)发出请求,但接收该请求的工作线程部署将具有包含其部署 ID 的 URL(例如 `https://my-app--or1170q9ix.expo.app/`)。 ¥For example, while a client may make a request to an alias URL such as `https://my-app--staging.expo.app/`, the worker deployment that will receive the request will have a URL containing its deployment ID, such as `https://my-app--or1170q9ix.expo.app/`. 此差异也体现在你 API 路由中收到的 `Request` 的 URL 和标头中。虽然 `request.url` 将是你的工作器部署的 URL,但 `Origin` 和 `X-Forwarded-Host` 标头将设置为客户端用于发出请求的传入 URL。 ¥This difference is also present in the `Request`'s URL and headers that you receive in your API routes. While the `request.url` will be your worker deployment's URL, the `Origin` and `X-Forwarded-Host` header will be set to the incoming URL that the client used to make the request. ```js export async function GET(request) { request.url; // 'https://my-app--or1170q9ix.expo.app/' request.headers.get('Origin'); // 'https://my-app--staging.expo.app/' request.headers.get('X-Forwarded-Host'); // 'my-app--staging.expo.app' origin; // 'https://my-app--staging.expo.app/' } ``` ### IP 标头 ¥IP headers 请求包含几个标头,用于识别发出请求的用户设备的 IP 地址: ¥The request contains several headers to identify the IP address of the user's device that made the request: * `Forwarded` 包含以逗号分隔的分号分隔参数列表。列表中的每个条目都代表一个转发了请求的代理。因此,第一个条目的 `for` 参数很可能是原始客户端的 IP 地址。 ¥`Forwarded` contains a comma-separated list of semicolon-separated parameters. Each entry in the list represents a proxy that forwarded the request. Therefore, the first entry's `for` parameter is likely the original client's IP address. * `X-Forwarded-For` 仅包含以逗号分隔的 IP 地址列表。列表中的每个条目都代表一个转发了请求的代理。 ¥`X-Forwarded-For` only contains a comma-separated list of IP addresses. Each entry in the list represents a proxy that forwarded the request as well. * `X-Real-IP` 仅包含原始请求的 IP 地址 ¥`X-Real-IP` contains only the original request's IP address 例如,要检索调用 API 路由的用户浏览器的 IP 地址,请从请求中读取 `X-Real-IP` 标头: ¥For example, to retrieve the IP address of the user's browser that is calling your API route, read the `X-Real-IP` header from the request: ```js export async function GET(request) { const ip = request.headers.get('X-Real-IP'); } ``` ### 地理位置标头 ¥Geo headers 请求还包含几个标头,其中包含有关请求来源的地理信息: ¥The request also contains several headers containing geographical information about where the request came from: * `eas-colo` 包含处理你请求的数据中心的 Cloudflare 代码。例如,`lhr`。 ¥`eas-colo` contains the Cloudflare code for the data center that handled your request. For example, `lhr`. * `eas-ip-continent` 包含当前请求的大陆代码: ¥`eas-ip-continent` contains the continent code of the current request: * `AF` 代表非洲 ¥`AF` for Africa * `AN` 代表南极洲 ¥`AN` for Antarctica * `AS` 代表亚洲 ¥`AS` for Asia * `EU` 代表欧洲 ¥`EU` for Europe * `NA` 代表北美洲 ¥`NA` for North America * `OC` 代表大洋洲 ¥`OC` for Oceania * `SA` 代表南美洲。 ¥`SA` for South America. * `eas-ip-country` 包含 ISO-3166 Alpha 2 国家代码。这最多有两个字母长。例如,`US` 或 `JP`。 ¥`eas-ip-country` contains the ISO-3166 Alpha 2 country code. This is at most two letters long. For example, `US` or `JP`. * `eas-ip-region` 包含请求的 ISO-3166-2 区域代码。此值的最大长度为三个字符。但是,它可能会根据特定国家/地区的区域代码对请求来源的工作方式而有所不同。它可以包含一到三个数字、一到三个字母或任何其他组合。 ¥`eas-ip-region` contains the ISO-3166-2 region code for the request. This value is a maximum of three characters long. However, it can vary based on how region codes in a specific country work for the requested origin.. It could comprise one to three digits, one to three letters, or any other combination. * `eas-ip-city` 可能包含一个人类可读的城市名称。例如 `London` 或 `Chicago`。 ¥`eas-ip-city` may contain a human-readable name of a city. For example `London` or `Chicago`. * `eas-ip-latitude` 和 `eas-ip-longitude` 包含请求的大致经纬度。 ¥`eas-ip-latitude` and `eas-ip-longitude` contain an approximate latitude and longitude for the request. * `eas-ip-timezone` 包含对请求来源时区的最佳猜测。例如,`Europe/London` ¥`eas-ip-timezone` contains a best guess of the timezone the request originated in. For example, `Europe/London` * 当请求可能源自欧盟管辖区域时,`eas-ip-eu` 设置为 `1`。 ¥`eas-ip-eu` is set to `1` when the request likely originated in the jurisdictional area of the European Union. ## EAS 托管工作器运行时 了解 EAS Hosting 工作器运行时和 Node.js 兼容性。 EAS Hosting 基于 [Cloudflare Workers](https://developers.cloudflare.com/workers/) 构建,这是一个现代而强大的无服务器 API 平台,旨在实现全球无缝可扩展性、高可靠性和卓越性能。 ¥EAS Hosting is built on [Cloudflare Workers](https://developers.cloudflare.com/workers/), a modern and powerful platform for serverless APIs that's been built for seamless scalability, high reliability, and exceptional performance globally. Cloudflare Workers 运行时在 V8 JavaScript 引擎上运行,该引擎为 Node.js 和 Chromium 中的 JavaScript 提供支持。但是,它的运行时与你在传统无服务器 Node.js 部署中可能习惯的运行时有几个关键区别。 ¥The Cloudflare Workers runtime runs on the V8 JavaScript engine, the same powering JavaScript in Node.js and Chromium. However, its runtime has a few key differences from what you might be used to in traditional serverless Node.js deployments. 不是每个请求都在完整的 JavaScript 进程中运行,而是 Workers 被设计为在小型 V8 隔离中运行它们,这是 V8 运行时的一个功能。将它们视为单个 JavaScript 进程中的微容器。 ¥Instead of each request running in a full JavaScript process, Workers are designed to run them in small V8 isolates, a feature of the V8 runtime. Think of them as micro-containers in a single JavaScript process. 有关 Workers 如何工作的更多信息,请参阅 [Cloudflare Workers](https://developers.cloudflare.com/workers/reference/how-workers-works/) 文档。 ¥For more information on how Workers work, see [Cloudflare Workers](https://developers.cloudflare.com/workers/reference/how-workers-works/) documentation. ## Node.js 兼容性 ¥Node.js compatibility Cloudflare 是 [冬季 TC](https://wintertc.org/) 的一部分,与 Node.js 中的 JavaScript 环境相比,它更类似于浏览器和服务工作者中的 JavaScript 环境。与仍然熟悉的 Node.js 相比,这些限制提供了更精简的运行时。此通用运行时是当今许多 JavaScript 运行时支持的最小标准。 ¥Cloudflare is part of [Winter TC](https://wintertc.org/), is more similar to the JavaScript environments in browsers and service workers rather than in Node.js. Restrictions like these provide a leaner runtime than Node.js, which is still familiar. This common runtime is a minimal standard supported by many JavaScript runtime these days. 这意味着,你可能习惯的许多 Node.js API 或你使用的某些依赖在 EAS Hosting 运行时中无法直接使用。为了简化这种转变,由于并非所有依赖都具有对 Web API 的一流支持,因此存在 Node.js 兼容性模块,可以在你的 API 路由中使用。 ¥This means, many Node.js APIs that you might be used to or some dependencies you utilize, aren't directly available in the EAS Hosting runtime. To ease this transition, as not all dependencies will have first-class support for Web APIs yet, Node.js compatibility modules exist and can be used in your API routes. | Node.js 内置模块 | 支持的 | 实现说明 | | -------------------------- | --- | ----------------------------------------- | | `node:assert` | | | | `node:async_hooks` | | | | `node:buffer` | | | | `node:crypto` | | 部分弃用算法不可用 | | `node:console` | | 以部分功能性 JS shim 的形式提供 | | `node:constants` | | | | `node:diagnostics_channel` | | 部分弃用算法未实现 | | `node:dns` | | `Resolver` 尚未实现,所有 DNS 请求都将发送到 Cloudflare | | `node:dns/promises` | | 所有 DNS 请求都将发送到 Cloudflare | | `node:events` | | | | `node:fs` | | 以 JS 存根的形式提供,因为 Worker 没有文件系统 | | `node:fs/promises` | | 以 JS 存根的形式提供,因为 Worker 没有文件系统 | | `node:http` | | 以基于 `fetch` 的部分功能性 JS shim 的形式提供 | | `node:https` | | 以基于 `fetch` 的部分功能性 JS shim 的形式提供 | | `node:module` | | `SourceMap` 尚未实现,除此以外部分支持。 | | `node:net` | | `Server` 和 `BlockList` 尚未实现,部分支持客户端套接字 | | `node:os` | | 以 JS 存根的形式提供,提供与 Linux 上的 Node.js 匹配的模拟值 | | `node:path` | | | | `node:path/posix` | | | | `node:path/win32` | | | | `node:process` | | 以 JS 存根形式提供 | | `node:punycode` | | | | `node:querystring` | | | | `node:readline` | | 以非功能性 JS 存根的形式提供,因为 Worker 没有 `stdin` | | `node:readline/promises` | | 以非功能性 JS 存根的形式提供,因为 Worker 没有 `stdin` | | `node:stream` | | | | `node:stream/consumers` | | | | `node:stream/promises` | | | | `node:stream/web` | | | | `node:string_decoder` | | | | `node:timers` | | | | `node:timers/promises` | | | | `node:tls` | | `Server` 尚未实现,部分支持客户端套接字。 | | `node:trace_events` | | 以非功能性 JS 存根的形式提供 | | `node:tty` | | 以 JS shim 的形式提供,将输出重定向到控制台 API | | `node:url` | | | | `node:util` | | | | `node:util/types` | | | | `node:worker_threads` | | 以非功能性 JS 存根的形式提供,因为 Worker 不支持线程 | | `node:zlib` | | | 这些模块通常提供精度较低的 polyfill 或与 Node.js 对应模块的近似版本。例如,`http` 和 `https` 模块仅提供围绕 `fetch` API 的薄 Node.js 兼容性封装器,不能用于发出任意 HTTP 请求。 ¥These modules generally provide a lower-accuracy polyfill or approximation of their Node.js counterparts. For example, the `http` and `https` modules only provide thin Node.js compatibility wrappers around the `fetch` API and cannot be used to make arbitrary HTTP requests. 以上列出的任何 Node.js 模块均可照​​常用于 API 路由或 API 路由的依赖,并将使用相应的兼容性模块。但是,其中一些模块可能不提供任何实际功能,仅用于填充 API 以防止运行时崩溃。 ¥Any of the above listed Node.js modules can be used in API routes or dependencies of your API routes as usual and will use appropriate compatibility modules. However, some of these modules may not provide any practical functionality and only exist to shim APIs to prevent runtime crashes. 此处未提及的任何模块均不可用或不受支持,你的代码和任何依赖都不应依赖于这些模块。 ¥Any modules that aren't mentioned here are unavailable or unsupported, and your code and none of your dependencies should rely on them being provided. > 未来可能会添加更多 Node.js 兼容性插件,但所有未在此非详尽列表中记录的 Node.js API 预计不会起作用。 > > ¥More Node.js compatibility shims may be added in the future, but all Node.js APIs that are not documented in this non-exhaustive list are not expected to work. ## 全局变量 ¥Globals | JavaScript 运行时全局变量 | 支持的 | 实现说明 | | ---------------------- | --- | ----------------------------------------- | | `origin` | | 始终与传入请求的 `Origin` 标头相同 | | `process` | | | | `process.env` | | 使用 EAS Hosting 环境变量填充 | | `process.stdout` | | 将输出重定向到控制台 API (`console.log`) 进行日志记录 | | `process.stderr` | | 将输出重定向到控制台 API (`console.error`) 进行日志记录 | | `setImmediate` | | | | `clearImmediate` | | | | `Buffer` | | 从 `node:buffer` 设置为 `Buffer` | | `EventEmitter` | | 从 `node:events` 设置为 `EventEmitter` | | `global` | | 设置为 `globalThis` | | `WeakRef` | | | | `FinalizationRegistry` | | | | `require` | | 支持外部请求,但仅限于已部署的 JS 文件和内置模块。不支持 Node 模块解析。 | | `require.cache` | | | # EAS 更新 ## EAS 更新 EAS Update 简介,它是使用 expo-updates 库的项目的托管服务。 EAS 更新是一项托管服务,为使用 [`expo-updates`](/versions/latest/sdk/updates) 库的项目提供更新。 ¥**EAS Update** is a hosted service that serves updates for projects using the [`expo-updates`](/versions/latest/sdk/updates) library. EAS 更新可以在应用商店提交之间快速修复小错误并推送快速修复。它通过允许应用无线更新其自己的非原生部分(例如 JS、样式和图片)来实现这一点。 ¥EAS Update makes fixing small bugs and pushing quick fixes a snap in between app store submissions. It accomplishes this by enabling an app to update its own non-native pieces (such as JS, styling, and images) over-the-air. 所有包含 `expo-updates` 库的应用都能够接收更新。 ¥All apps that include the `expo-updates` library have the ability to receive updates. * 要开始使用 EAS 更新,请继续阅读 [入门](/eas-update/getting-started) 指南。 ¥To start using EAS Update, continue to the [Getting Started](/eas-update/getting-started) guide. * 有关将 EAS Update 与其他 EAS 服务一起使用的完整教程,请参阅 [EAS 教程](/tutorial/eas/introduction/)。 ¥For a complete tutorial of using EAS Update with other EAS services, refer to the [EAS Tutorial](/tutorial/eas/introduction/). ## 用于更新管理的 JS API ¥JS API for update management 更新 [JavaScript API](/versions/latest/sdk/updates/) 包含一个名为 `useUpdates()` 的 React hook。此钩子提供有关当前正在运行的更新以及任何可用或已下载的新更新的详细信息。此外,你可以查看更新过程中遇到的任何错误,以帮助你在应用尝试更新时调试任何问题。 ¥The updates [JavaScript API](/versions/latest/sdk/updates/) includes a React hook called `useUpdates()`. This hook provides detailed information about the currently running update and any new updates that are available or have been downloaded. In addition, you can view any errors that were encountered during the update process to help you debug any issues while the app is attempting to update. API 还提供了 `checkForUpdateAsync()` 和 `fetchUpdateAsync()` 等方法,允许你控制应用何时检查和下载更新。 ¥The API also provides methods such as `checkForUpdateAsync()` and `fetchUpdateAsync()` which allows you to control when your app checks for and downloads updates. ## 洞察跟踪 ¥Insight tracking 你将获得一个 [部署仪表板](https://expo.dev/accounts/\[account]/projects/\[project]/deployments),它可以帮助你直观地看到哪些更新正在发送到构建。更新与 [insights](/eas-insights/introduction/) 协同工作,提供有关用户更新采用率的数据。 ¥You'll get a [deployments dashboard](https://expo.dev/accounts/\[account]/projects/\[project]/deployments) that helps visualize which updates are being sent to builds. Updates work in concert with [insights](/eas-insights/introduction/) to provide data on the adoption rates of your updates with your users. ## 请求标头 ¥Republish for reverting mistakes 如果更新未按预期执行,你可以在有问题的版本之上 [republish](/eas-update/eas-cli/#republish-a-previous-update-within-a-branch) 上一个稳定的版本,就像版本控制系统中的新 "commit" 一样。 ¥If an update isn't performing as expected, you can [republish](/eas-update/eas-cli/#republish-a-previous-update-within-a-branch) a previous, stable version on top of the problematic one, much like a new "commit" in version control systems. ## 开始使用 ¥Get started ## 开始使用 EAS 更新 了解如何开始在项目中配置和使用 EAS Update 所需的设置。 通过设置 EAS 更新,你可以立即推送用户需要的关键错误修复和改进。本指南将引导你完成在新项目或现有项目中设置 EAS 更新的过程。 ¥Setting up EAS Update allows you to push critical bug fixes and improvements that your users need right away. This guide will walk you through the process of setting up EAS Update in a new or existing project. > **info** 如果你计划将 EAS Update 与 EAS Build 结合使用,我们建议你在继续此处之前先遵循 [EAS Build 设置指南](/build/setup/)。也就是说,[你可以在没有任何其他 EAS 服务的情况下使用 EAS 更新](/eas-update/standalone-service/)。 > > ¥If you plan to use EAS Update with EAS Build, we recommend following the [EAS Build setup guide](/build/setup/) before proceeding here. That said, [you can use EAS Update without any other EAS services](/eas-update/standalone-service/). ## 先决条件 ¥Prerequisites Note: An Expo user account --- EAS Update is available to anyone with an Expo account, regardless of whether you pay for EAS or use the Free plan. You can sign up at [expo.dev/signup](https://expo.dev/signup). Paid subscribers can publish updates to more users and use more bandwidth and storage. Learn more about different plans and benefits at [EAS pricing](https://expo.dev/pricing). --- Note: A React Native project --- Don't have a project yet? No problem. It's quick and easy to create a "Hello world" app that you can use with this guide. Run the following command to create a new project: ```sh $ npx create-expo-app my-app ``` EAS Update also works well with projects created by `npx create-react-native-app`, `npx react-native`, `ignite-cli`, and other project bootstrapping tools. --- Note: Your project must use Expo CLI and extend the Expo Metro Config --- If you already run your project with `npx expo [command]` (for example, if you created it with `npx create-expo-app`) then you're all set. If you don't have the `expo` package in your project yet, then install it by running the command below and [opt in to using Expo CLI and Metro Config](/bare/installing-expo-modules/#configure-expo-cli-for-bundling-on-android-and-ios): ```sh $ npx install-expo-modules@latest ``` If the command fails, refer to the [Installing Expo modules](/bare/installing-expo-modules/#manual-installation) guide. --- Note: Your project must use the registerRootComponent function instead of registerComponent --- If you created your project with `npx create-expo-app`, or you don't call `registerRootComponent` in your app at all (for example, it's handled by Expo Router), then you are all set. The following applies to projects created with other tools, such as React Native Community CLI. We recommend that apps using EAS Update use Expo's [`registerRootComponent`](/versions/latest/sdk/expo/#registerrootcomponentcomponent) instead of React Native's `registerApplication`. This will ensure that Expo is able to configure React Native to load assets, such as images, that are included in updates. If you do not use `registerRootComponent`, you may find that your assets will not be available in release builds. In a simple app created with React Native Community CLI, the diff would look like this: ```diff diff --git a/index.js b/index.js index a850d03..8fb69fd 100644 --- a/index.js +++ b/index.js @@ -2,8 +2,7 @@ * @forma */ -import {AppRegistry} from 'react-native'; +import {registerRootComponent} from 'expo'; import App from './App'; -import {name as appName} from './app.json'; -AppRegistry.registerComponent(appName, () => App); +export default registerRootComponent(App); ``` After making that change, update your [`MainActivity`](/versions/latest/sdk/expo/#rootregistercomponent-setup-for-existing-react-native-projects) and [`AppDelegate`](/versions/latest//sdk/expo/#rootregistercomponent-setup-for-existing-react-native-projects) to use the module name `"main"` instead of your app name. --- Step 1: ## Install the latest EAS CLI EAS CLI is the command line app you will use to interact with EAS services from your terminal. To install it, run the command: ```sh $ npm install --global eas-cli ``` You can also use the above command to check if a new version of EAS CLI is available. We encourage you to always stay up to date with the latest version. > We recommend using `npm` instead of `yarn` for global package installations. You may alternatively use `npx eas-cli@latest`. Remember to use that instead of `eas` whenever it's called for in the documentation. Step 2: ## Log in to your Expo account If you are already signed in to an Expo account using Expo CLI, you can skip the steps described in this section. If you are not, run the following command to log in: ```sh $ eas login ``` You can check whether you are logged in by running `eas whoami`. Step 3: ## Configure your project Navigate to your project directory in your terminal and run the following command: ```sh $ eas update:configure ``` Note: What does this command do? --- The `eas update:configure` command will update your **app.json** file with the `runtimeVersion` and `updates.url` properties, and add the `extra.eas.projectId` field if your project wasn't using any EAS services previously. When you run `eas update:configure` in a project that doesn't use [CNG](/workflow/continuous-native-generation/), you'll see the following changes to your native projects: #### Android Inside the **android/app/src/main/AndroidManifest.xml** file, you'll see the following additions: ```xml android/app/src/main/AndroidManifest.xml ``` The `EXPO_UPDATE_URL` value should contain your project's ID. Inside **android/app/src/main/res/values/strings.xml**, you'll see the `expo_runtime_version` string entry in the `resources` object: #### iOS Inside **ios/project-name/Supporting/Expo.plist**, you'll see the following additions: ```xml ios/project-name/Supporting/Expo.plist EXUpdatesRuntimeVersion 1.0.0 EXUpdatesURL https://u.expo.dev/your-project-id ``` The `EXUpdatesURL` value should contain your project's ID. > **info** If you use Xcode to create project builds, make sure that the `Expo.plist` file [is added to your Xcode project](https://developer.apple.com/documentation/xcode/managing-files-and-folders-in-your-xcode-project#Add-existing-files-and-folders-to-a-project). --- Step 4: ## Configure the update channel The channel property on a build allows you to point updates at specific types of builds. For example, it allows you to publish updates to a preview build without impacting your production deployment. **If you are using EAS Build**, `eas update:configure` will set the update `channel` property on the `preview` and `production` profiles in **eas.json**. Set them manually if you use different profile names. Note: Example channel configuration in eas.json --- ```json eas.json { "build": { "preview": { "channel": "preview" // ... }, "production": { "channel": "production" // ... } } } ``` --- **If you are not using EAS Build**, then you will need to configure the channel in **app.json** or in your native projects, depending on whether you use [CNG](/workflow/continuous-native-generation/). When you create a build for a different environment, you will need to modify the channel to ensure your build pulls updates from the correct channel. Learn more using [EAS Update as a standalone service](/eas-update/standalone-service/). Note: Configure update channels in app.json --- If you use Continuous Native Generation (CNG), then you can configure the channel with the `updates.requestHeaders` property in your **app.json**: ```json app.json { "expo": { "updates": { "requestHeaders": { "expo-channel-name": "your-channel-name" } } } } ``` The configuration will be applied the next time you run `npx expo prebuild`. --- Note: Configure update channels in an Android native project --- In **AndroidManifest.xml**, you will need to add replace `your-channel-name` with the channel that matches your project: ```xml android/app/src/main/AndroidManifest.xml ``` --- Note: Configure update channels in an iOS native project --- In **Expo.plist**, you'll need to add the following, replacing `your-channel-name` with the channel that matches your project: ```xml ios/project-name/Supporting/Expo.plist EXUpdatesRequestHeaders expo-channel-name your-channel-name ``` > **info** If you use Xcode to create project builds, make sure that the `Expo.plist` file [is added to your Xcode project](https://developer.apple.com/documentation/xcode/managing-files-and-folders-in-your-xcode-project#Add-existing-files-and-folders-to-a-project). --- Step 5: ## Create a build for the project You need to create a build for Android or iOS. We recommend creating a build with the `preview` build profile first. See [Create your first build](/build/setup/) on how to get started and set up [Internal distribution](/build/internal-distribution/#setting-up-internal-distribution) for your device or simulator. Once you have a build running on your device or a simulator, you are ready to send an update. Step 6: ## Make changes locally After creating the build, you are ready to iterate on the project. Start a local development server with the following command: ```sh $ npx expo start ``` Then, make any desired changes to your project's JavaScript, styling, or image assets. Step 7: ## Publish an update Publishing an update allows: - Fixing bugs and quickly updating non-native parts of a project instead of creating a new build - [Sharing a preview version of an app](/review/overview/) using internal distribution To publish an update with changes from your project, use the `eas update` command, and specify a name for the channel and a `message` to describe the update: ```sh $ eas update --channel [channel-name] --message "[message]" ``` Note: How does publishing an update work? --- When you publish an update with the `eas update` command, it generates a new update bundle and uploads it to the EAS servers. The channel name is used to locate the correct branch to publish a new update from other update branches. It is similar to how Git commit works, where every commit is on a Git branch. For example, when an app is set to pull updates from the `preview` channel, you can publish an update for that build with `eas update --channel preview`. This will create a branch (called `preview` by default) on the `preview` channel. Behind the scenes, this command runs `npx expo export` to generate a **dist** directory and create a local update bundle. This update bundle is uploaded to EAS Update servers. --- Step 8: ## Test the update After the update is uploaded to EAS Update, you can use one of the following methods to test the update: - Use the Extensions tab in a [development build](/eas-update/expo-dev-client/) to load the update. - Use [Expo Orbit](/review/with-orbit/) to install and launch the update in a development build. - Implement a custom strategy with [Updates API](/versions/latest/sdk/updates/) and [app config](/versions/latest/config/app/#updates) to load updates in the app programmatically. - Manually test the update by force closing and reopening a release build of your app up to two times to download and apply the update. Updates for non-development builds (preview or production) are automatically downloaded to the device in the background when the app starts up and makes a request for any new updates. The update will be applied after it is downloaded and the app is restarted. Note: Something not working? --- If your app is not updating as expected, see the [debugging guide](/eas-update/debug/) for techniques to validate your configuration. --- ## 下一步 ¥Next steps # 预览 ## 预览更新 了解如何在开发、预览和生产版本中预览更新。 在将更新部署到生产环境之前,你通常需要在类似生产的环境中进行测试。本指南将概述预览更新的不同方法,并链接到每种方法的更详细指南。 ¥Before deploying an update to production, you will often want to test it in a production-like environment. This guide will outline different approaches for previewing updates, and link out to more detailed guides for each approach. ## 预览开发版本中的更新 ¥Previewing updates in development builds 开发构建版本是预览拉取请求更新的绝佳方式,可以直接从 EAS 仪表板或 `expo-dev-client` 库提供的内置 UI 中预览。 ¥Development builds are a great way to preview updates from pull requests, directly from the EAS dashboard, or from the built-in UI provided by the `expo-dev-client` library. ## 预览版本中的预览更新 ¥Previewing updates in preview builds 非技术用户通常不想与开发版本交互,他们希望在 [App Store 测试轨道](/review/overview/#app-store-testing-tracks) 或 [内部分配](/review/overview/#internal-distribution-with-eas-build) 上测试预览版本的更改。 ¥Non-technical users will typically not want to interact with a development build, and they will want to test changes from a preview build on an [App store testing track](/review/overview/#app-store-testing-tracks) or [internal distribution](/review/overview/#internal-distribution-with-eas-build). 如果你的团队规模较小,一次将一个预览版本部署到应用商店测试轨道或内部分发版可能就足够了。然后,你可以将更新发布到该预览版本使用的通道。[了解更多关于预览版本的信息](/review/overview/)。 ¥If your team is smaller, it may be sufficient to deploy a single preview build at a time to an app store testing track or internal distribution. You can then publish updates to the channel that is used by that preview build. [Learn more about preview builds](/review/overview/). 或者,你可以在预览版本中构建一种机制,允许用户选择要加载的其他更新或渠道。这在 [应用运行时](/eas-update/runtime-versions/) 不经常更改且可以在同一个应用中加载许多不同更新的情况下非常有用。[了解更多](/eas-update/override/)。 ¥Alternatively, you can build a mechanism into your preview build that allows users to select a different update or channel to load. This can be useful in cases where the [app runtime](/eas-update/runtime-versions/) doesn't change often, and many different updates can be loaded in the same app. [Learn more](/eas-update/override/). ## 生产版本中的预览更新 ¥Previewing updates in production builds 在将更新部署到所有终端用户之前,某些团队可能希望先在生产环境中将其推广到一小部分内部用户。实现此目的的一种方法是在运行时针对已知的用户子集使用 [覆盖更新渠道](/eas-update/override/)。在继续此操作之前,请务必注意 [安全注意事项](/eas-update/override/#security-considerations)。此外,不建议非内部用户使用此方法,因为这可能会导致应用处于必须卸载并重新安装的状态。 ¥Before deploying an update to all end-users, some teams will want to first roll it out in production to a small set of internal users. One way this can be accomplished is by [overriding the update channel](/eas-update/override/) at runtime for a known subset of users. **Be sure to note the [security considerations](/eas-update/override/#security-considerations) before proceeding down this path.** Additionally, it is not recommended to use this approach for non-internal users because it makes it possible to get the app into a state where it must be uninstalled and reinstalled. 另一种方法是使用类似 [持久暂存流程](/eas-update/deployment-patterns/#persistent-staging-flow) 的部署模式,该模式要求始终有一个指向暂存渠道的生产应用版本。 ¥Another approach is to use a deployment pattern like the [Persistent Staging Flow](/eas-update/deployment-patterns/#persistent-staging-flow), which involves always having a version of your production app that points to a staging channel. ## 在运行时覆盖更新配置 了解如何在运行时覆盖更新 URL 和请求标头,以控制在客户端加载哪些更新。 > **warning** 本指南中描述的功能在 Expo SDK 52 和 `expo-updates` 版本 0.27.0 及更高版本中可用。不建议在生产应用中使用 `disableAntiBrickingMeasures` 选项,它目前主要用于预览环境。 > > ¥The features described in this guide are available in Expo SDK 52 with the `expo-updates` version 0.27.0 and later. Using the `disableAntiBrickingMeasures` option is not recommended for production apps, it is currently primarily intended for preview environments. 使用 EAS 更新的典型方法是将单个更新 URL 和一组请求标头(例如更新渠道名称)嵌入到应用的构建中。要控制加载哪个更新,你可以通过 `eas update` 命令或 EAS 仪表板在服务器上进行更改。例如,你将新更新发布到构建指向的通道,然后构建将在下次启动时获取该更新。使用此方法不会下载发布到与你的构建指向不同的通道的更新。 ¥The typical way to use EAS Update is to have a single update URL and a set of request headers (such as update channel name) embedded in a build of your app. To control which update is loaded you make changes on the server through the `eas update` command or the EAS dashboard. For example, you publish a new update to a channel that your build is pointing to, then the build fetches that update on the next launch. Updates published to a channel different from the one your build is pointing to will not be downloaded with this approach. 本指南解释了如何在运行时更改更新 URL 和请求标头,从而可以通过 ID 加载特定更新或更改从中提取更新的渠道,而无需创建和安装新版本。 ¥This guide explains how you can change the update URL and request headers at runtime, making it possible to load a specific update by ID or change the channel that updates are pulled from without creating and installing a new build. ## 在运行时更改更新 URL 和请求标头的用例 ¥Use cases for changing the update URL and request headers at runtime 这主要用于在预览版本中启用更新之间的切换,类似于在开发版本中可能实现的切换。这有助于非技术利益相关者测试和验证正在进行的工作的更新(例如来自拉取请求或不同的功能分支),而无需使用开发版本或为每个更改创建单独的预览版本。 ¥The primary use case that this is intended for is to **enable switching between updates in a preview build**, similar to what is possible in a development build. This is useful to enable non-technical stakeholders to test and validate updates of work-in-progress (such as from a pull request or different feature branches) without having to use a development build or create a separate preview build for each change. 另一个潜在用例是向不同的用户提供不同的更新,例如,让一组内部用户(如员工)在终端用户之前收到更新。在决定在生产中使用此功能之前,熟悉 [安全注意事项](#security-considerations) 非常重要。将来,我们可能会添加对更适合此用例的更受限制的功能版本的支持。 ¥Another potential use case is to provide different updates to different users, for example so that a group of internal users (such as employees) receive updates before end-users. It is important to be familiar with the [security considerations](#security-considerations) before deciding to use this feature in production. In the future, we may add support for a more restricted version of the feature that would be more suitable for this use case. ## 工作原理 ¥How it works 有两个相关的 API: ¥There are two relevant APIs: 1. `Updates.setUpdateURLAndRequestHeadersOverride({ url: string, requestHeaders: Object })` - 此方法将覆盖更新 URL 和 app.json / Expo.plist / AndroidManifest.xml 中指定的请求标头,例如 `expo-channel-name` 标头。 ¥`Updates.setUpdateURLAndRequestHeadersOverride({ url: string, requestHeaders: Object })` - this method overrides the update URL and the request headers that are specified in **app.json** / **Expo.plist** / **AndroidManifest.xml**, such as the `expo-channel-name` header. 2. `disableAntiBrickingMeasures` - 应用配置中的此字段禁用 `expo-updates` 内置的防砖措施,以确保始终可以发布后续更新以修复先前安装的更新中的问题。更改此值时,你需要创建一个新的构建才能使其生效。请勿在生产版本中启用此功能。使用这个名字的原因是为了清楚地表明,当你覆盖更新 URL/标头时,我们将无法再安全地回滚到之前加载的更新。因此,如果你加载的新更新导致应用崩溃,则 `expo-updates` 无法自动恢复,因为此字段与 `setUpdateURLAndRequestHeadersOverride` 结合将禁用嵌入式更新,因此不会有任何更新可回滚。用户需要卸载并重新安装应用。你只应在预览版本中使用此功能。 ¥`disableAntiBrickingMeasures` - this field in the app config disables anti-bricking measures built-in to `expo-updates` which ensure subsequent updates can always be published to fix issues in previously-installed update. When you change this value, you will need to create a new build for it to take effect. **Do not enable this in your production builds.** The reason for this name is to clearly indicate that when you override the update URL/headers, we're no longer able to safely rollback to the previous update that was loaded. So, if the new update you have loaded causes the app to crash then `expo-updates`cannot automatically recover, because this field in conjunction with `setUpdateURLAndRequestHeadersOverride` will disable embedded updates and therefore there will not be any update to rollback to. The user would need to uninstall and reinstall the app. You should only use this feature in preview builds. 如何使用这些 API: ¥How to use these APIs: 1. 覆盖更新 URL/标头,指示用户关闭应用:在你的应用中的某个地方,你将为用户提供一种触发对 URL 和/或请求标头的更改的方法。这可能位于只有受信任用户才能访问的隐藏菜单中,或者根据你的用例使用其他机制。当参数发生变化时,通知用户他们需要关闭并重新打开应用,例如通过警报。`expo-updates` 库具有类似 `checkForUpdateAsync()` 的方法,在应用关闭并重新打开之前,不会使用新的覆盖 URL 和请求标头。 ¥**Override the update URL/headers, instruct user to close the app**: Somewhere in your app, you would provide a way for users to trigger the change to the URL and/or request headers. This may be in a hidden menu that only trusted users have access to, or some other mechanism depending on your use case. When the parameters are changed, notify the user that they need to close and re-open the app, such as via an alert. The `expo-updates` library, with methods like `checkForUpdateAsync()`, will not use the new overridden URL and request headers until the app is closed and reopened. 2. 新的更新将在下次打开应用时下载并启动:应用完全关闭("killed" 而不仅仅是后台)并重新打开后,更新及其相关资源将全部下载。一旦它们准备就绪,应用就会启动。在下载时,用户必须在启动画面上等待。我们理解在启动画面上等待并不理想,如果此功能被广泛使用,我们打算在未来改善这种体验。对于当前推荐的用例(预览),这可能是一个可以接受的折衷方案。 ¥**The new update will be downloaded and launched on the next app open**: After the app is completely closed ("killed" and not just backgrounded) and re-opened, the update and its related assets will all be downloaded. Once they are ready, the app will launch. While it's downloading, the user will have to wait on the splash screen. We understand that waiting on the splash screen is not ideal, and we intend to improve this experience in the future if this feature is widely used. For the currently recommended use case (previews), this is likely an acceptable compromise. ## 安全考虑 ¥Security considerations 可以使用 `disableAntiBrickingMeasures` 禁用的防砖措施可确保无论发布什么更新,你都可以随时发布将要应用的另一个更新。禁用防砖措施后,某些类型的攻击和漏洞利用成为可能,尤其是在内部(受感染员工)发布恶意更新时。例如,具有发布更新能力的员工可以发布恶意更新,更改更新 URL 和请求标头以指向他们自己的服务器,并接管应用的安装。通过使用 [代码签名](/eas-update/code-signing/) 进行生产更新并限制对密钥的访问,可以减轻但不能消除这种风险。 ¥The anti-bricking measures that can be disabled with `disableAntiBrickingMeasures` ensure that, no matter what update is published, you can always publish another update afterwards that will be applied. By disabling the anti-bricking measures, certain categories of attacks and exploits become possible, especially around in-house (compromised employee) publishing of malicious updates. For example, an employee with the ability to publish updates could publish a malicious update that changes the update URL and request headers to point to their own server, and take over installations of the app. This risk can be mitigated, but not eliminated, by using [code signing](/eas-update/code-signing/) for production updates and limiting access to the key. Note: Did similar usage of CodePush carry the same risk? --- Yes. CodePush allowed developers to swap deployment keys with `sync({ deploymentKey: string })` which could be used maliciously take over an app installation in this same way. --- ## 示例代码 ¥Example code 以下是如何使用这些 API 的示例: ¥Here's an example of how you might use these APIs: ```js import * as Updates from 'expo-updates'; // Where you call this method depends on your use case - it may make sense to // have a menu in your preview builds that allows testers to pick from available // pull requests, for example. function overrideUpdateURLAndHeaders() { Updates.setUpdateURLAndRequestHeadersOverride({ url: '...', requestHeaders: { 'expo-channel-name': 'pr-123' }, }); alert('Close and re-open the app to load the latest version.'); } ``` ```json { "expo": { "updates": { // We recommend only enabling this in preview builds. // You can use app.config.js to configure it dynamically. "disableAntiBrickingMeasures": true // etc.. } } } ``` ## 开发版本中的预览更新 了解如何使用 expo-dev-client 库在开发版本中预览已发布的 EAS 更新。 [`expo-dev-client`](/develop/development-builds/introduction/) 库允许通过创建开发构建来启动项目的不同版本。任何兼容的 EAS 更新都可以在开发版本中预览。 ¥[`expo-dev-client`](/develop/development-builds/introduction/) library allows launching different versions of a project by creating a development build. Any compatible EAS Update can be previewed in a development build. 本指南介绍了使用扩展选项卡或构建特定更新 URL 在开发版本中加载和预览已发布更新所需的步骤。 ¥This guide walks through the steps required to load and preview a published update inside a development build using the **Extensions** tab or constructing a specific Update URL. ## 先决条件 ¥Prerequisites * 你的设备或 Android 模拟器或 iOS 模拟器上的 [创建开发版本并安装它](/develop/development-builds/create-a-build/)。 ¥[Create a development build and install it](/develop/development-builds/create-a-build/) on your device or Android Emulator or iOS Simulator. * 确保你的开发版本具有 [已安装 `expo-updates` 库](/eas-update/getting-started/#configure-your-project)。 ¥Make sure your development build has the [`expo-updates` library installed](/eas-update/getting-started/#configure-your-project). ## 什么是扩展选项卡 ¥What is an Extensions tab 当在开发版本中使用 `expo-updates` 库时,“扩展”选项卡提供自动加载和预览已发布更新的功能。 ¥When using the `expo-updates` library inside a development build, the **Extensions** tab provides the ability to load and preview a published update automatically. ### 使用扩展选项卡预览更新 ¥Preview an update using the Extensions tab Step 1: Make non-native changes locally in your project and then [publish them using `eas update`](/eas-update/getting-started/#publish-an-update). The update will be published on a branch. Step 2: After publishing the update, open your development build, go to **Extensions**, and tap **Login** to log in to your Expo account within the development build. This step is required for the **Extensions** tab to load any published updates associated with the project under your Expo account. Step 3: After logging in, an EAS Update section will appear inside the **Extensions** tab with one or more of the latest published updates. Tap **Open** next to the update you want to preview. In the **Extensions** tab, you can view the list of all published updates for a branch. Tap the branch name in the **Extensions** tab. ## Preview an update using the EAS dashboard You can also preview an update using the EAS dashboard by following the steps below: - Click the published updated link in the CLI after running the command to publish an update. This will open the update's details on the **Updates** page in the EAS dashboard. - Click **Preview**. This will open the **Preview** dialog. - To preview the update, you can either scan the QR code with your device's camera or select a platform to [launch the update under **Open with Orbit**](/review/with-orbit/). ## Construct an update URL As an alternative to the methods described in the previous sections, you can construct a specific URL to open your EAS Update in the development build. The URL will look like the following: ```sh [slug]://expo-development-client/?url=[https://u.expo.dev/project-id]/group/[group-id] my-app://expo-development-client/?url=https://u.expo.dev/675cb1f0-fa3c-11e8-ac99-6374d9643cb2/group/47839bf2-9e01-467b-9378-4a978604ab11 ``` 让我们分解此 URL 以了解每个部分的作用: ¥Let's break this URL to understand what each part does: | URL 的一部分 | 描述 | | --------------------------------------------------------- | ------------------------------------------------------------------------ | | `slug` | 在应用配置中找到项目的 [slug](/versions/latest/config/app/#slug)。 | | `://expo-development-client/` | 深层链接与 [`expo-dev-client`](/versions/latest/sdk/dev-client/) 库一起使用所必需的。 | | `?url=` | 定义 `url` 查询参数。 | | `https://u.expo.dev/675cb1f0-fa3c-11e8-ac99-6374d9643cb2` | 这是更新 URL,位于 [`updates.url`](/versions/latest/config/app/#url) 下的项目应用配置内。 | | `/group/47839bf2-9e01-467b-9378-4a978604ab11` | 更新的组 ID。 | 构建 URL 后,将其直接复制并粘贴到手动输入 URL 下的开发构建启动器屏幕中。 ¥Once you've constructed the URL, copy and paste it directly into the development build's launcher screen under **Enter URL Manually**. 或者,你可以使用设备的相机 [为 URL 创建二维码](/more/qr-codes/) 并进行扫描。扫描后,URL 将打开指定通道的开发版本。 ¥Alternatively, you can [create a QR code for the URL](/more/qr-codes/) and scan it using your device's camera. When scanned, the URL will open up the development build to the specified channel. ## 示例 ¥Example } Icon={GithubIcon} href="https://github.com/jonsamp/test-expo-dev-client-eas-update" /> ## 用于 PR 预览的 Github Action 了解如何使用 GitHub Actions 通过 EAS 更新自动发布更新。 GitHub Action 是一种云函数,每次 GitHub 上发生事件时都会运行。你可以配置 GitHub Actions,以便在你或你的团队成员合并到分支(例如 "production")时自动构建和发布更新。这使得部署过程一致且快速,让你有更多时间来开发应用。 ¥A GitHub Action is a cloud function that runs every time an event on GitHub occurs. You can configure GitHub Actions to automate building and publishing updates when you or members of your team merge to a branch, like "production". This makes the process of deploying consistent and fast, leaving you more time to develop your app. 本指南将引导你完成如何设置 GitHub Actions 以在拉取请求上发布预览。 ¥This guide will walk you through how to set up GitHub Actions to publish previews on pull requests. ## 发布拉取请求预览 ¥Publish previews on pull requests 另一个常见的用例是为每个拉取请求创建新的更新。这允许你在合并代码之前在设备上测试拉取请求中的更改,而无需在本地启动项目。以下是每次打开拉取请求时发布更新的步骤: ¥Another common use case is to create a new update for every pull request. This allows you to test the changes in the pull request on a device before merging the code, and without having to start the project locally. Below are the steps to publish an update every time a pull request is opened: Step 1: Create a file path named **.github/workflows/preview.yml** at the root of your project. Step 2: Inside **preview.yml**, copy and paste the following snippet: !!!IG0!!! In the above script: - You are using the workflow event `on` to run every time a pull request is opened or updated. - In the `update` job, the Node.js version, Expo's GitHub Action and the dependencies are set up using GitHub Action's built-in cache. - The `eas update --auto` is run by the [preview subaction](https://github.com/expo/expo-github-action/tree/main/preview#readme). It adds a comment to the pull request with basic information about the update and a QR code to scan the update. > Don't forget to add the `permissions` section to the job. This enables the job to add comments to the pull request. Step 3: You can skip this step if you have already set up `EXPO_TOKEN` in the previous section. Only one valid `EXPO_TOKEN` is required to authenticate GitHub Actions with your Expo account. If you haven't, you need to give the script above permission to run by providing an `EXPO_TOKEN` environment variable. - Navigate to [https://expo.dev/settings/access-tokens](https://expo.dev/settings/access-tokens). - Click **Create token** to create a new personal access token. - Copy the token generated. - Navigate to https://github.com/your-username/your-repo-name/settings/secrets/actions by replacing "your-username" and "your-repo-name" with your project's info. - Under **Repository secrets**, click **New repository secret**. - Create a secret with the name **EXPO_TOKEN**, and paste the copied access token in as the value. Your GitHub Action should be set up now. Whenever a developer creates a pull request, this action will build an update and publish it, making it available to all reviewers with builds that have access to the EAS branch. > Some repositories or organizations might need to explicitly enable GitHub Workflows and allow third-party Actions. ## Using Bun instead of Yarn To use [Bun](/guides/using-bun/) as the package manager instead of Yarn, follow the steps below for both publishing updates on push and previews on pull requests: Step 1: Replace the `Setup Node` step in **update.yml** or **preview.yml** with the following snippet: !!!IG1!!! Step 2: To install dependencies using Bun, replace the **Install dependencies** step with the following snippet: !!!IG2!!! # 部署 ## 部署更新 了解一个简单但功能强大的过程,可以安全地将更新部署给你的用户。 当你在生产环境中拥有具有多个二进制版本的应用时(这很常见 - 用户并不总是及时了解你最新的商店版本),了解哪些代码在哪些版本上运行以及能够使用修补程序专门针对特定版本非常重要。 ¥When you have an app with multiple binary versions in production (this is common — users do not always stay up to date with your latest store release), it's important to understand what code is running on which versions and to be able to specifically target a particular version with a hotfix. EAS Update 提供 "channels"、"branches" 和 "运行时版本",帮助你确定要定位的应用版本,帮助你进行簿记以了解部署状态,并支持各种 [部署模式](/eas-update/deployment-patterns/)。 ¥EAS Update provides "channels", "branches", and "runtime versions" to help you determine which app version to target, to help you with bookkeeping to understand the state of your deployments, and to support a variety of [deployment patterns](/eas-update/deployment-patterns/). Note: What if my preferred release process isn't supported by EAS Update? --- Release management is a large topic in software engineering, and everyone has a slightly different take on how they would like to do it. EAS Update is designed to support [a variety of different workflows](/eas-update/deployment-patterns/), but this guide will focus on the simplest workflow that works for most apps. That said, there are some other workflows that may not work within the constraints of the EAS Update service. For example, each binary version must always point to a single channel, and you cannot dynamically update the channel. As an escape hatch, you can host your own update service that is compatible with the [Expo Updates Protocol](/technical-specs/expo-updates-1/) and point your `expo-updates` configuration to that service instead. The only concepts relevant to update selection that exist on the protocol level are "Runtime Version" and "Platform", and you are free to create your own concepts on top of those in the same way we built channels and branches. [Learn more about creating a custom expo-updates server](https://github.com/expo/custom-expo-updates-server). --- ## A simple release process In this guide, we'll describe a simple but powerful release process that uses **channels** and **runtime versions**, and mostly ignores _branches_. This gives most of the benefits of EAS Update with a minimal amount of conceptual overhead. You can evolve this process to suit your needs as they arise, or move to [other deployment patterns](/eas-update/deployment-patterns/). Note: Why ignore branches in this release process? --- The most simple way to use EAS Update is to ignore the concept of "branches" and focus on "channels". Branches will still exist, but you will not have to interact with them directly to manage deployments. You can keep your channels pointed at a branch with the same name as the channel and think of them as a singular concept. EAS Update branches were meant to map to Git branches and enable teams to publish changes from a Git branch directly to an EAS Update branch of the same name. This can be helpful for [previewing updates](/eas-update/develop-faster/), but for many apps, this level of integration with Git is not required. Often, developers are only interested in being able to release hotfixes to a staging or production version of their app manually, and can run `eas update --channel staging` or `eas update --channel production`, when needed rather than managing branches to accomplish the same result. --- ## Configuring your project Channels will indicate which environment the update targets (such as "production" or "staging"), and runtime versions will indicate the app version that the update will target (such as "1.0.0" or "1.0.1"). ### Channel configuration Run `eas update:configure` in your project if you haven't already. **If you use EAS Build**, the default configuration that will be applied by the configure command, which is almost what we want to use here: each profile will point to a channel of the same name, so our production release of our app will point to the "production" channel. We only need to add a "staging" profile that points to the "staging" channel. Note: Example eas.json configuration --- The following configuration is approximately what `eas update:configure` will generate for you if you haven't already configured your project. ```json { "build": { "production": { "channel": "production" }, "staging": { "channel": "staging" }, "preview": { "channel": "preview", "distribution": "internal" } } } ``` --- **If you do not use EAS Build**, you will need to modify the channel used in your [native project configuration](/eas-update/getting-started/#configure-the-update-channel). When you release to production, ensure you update the channel name in native config to "production", and when you release to staging, ensure you update the channel name in native config to "preview". It's worth noting that using EAS Build with EAS Update helps you get the best out of the product, but it is not required. ### Runtime version configuration By default, `eas update:configure` will set `"runtimeVersion": { "policy": "appVersion" }` in your app config. This is the recommended configuration, it will ensure that the runtime version of your app is always the same as the app version, and you have a unique runtime version to target for every release of your app. In this case, the app version refers to the native version of your app that users will see on the app store, and it does not include the build number or version code. For example: `"1.0.0"` will be used as the runtime version, and not `"1.0.0(1)"` (where `1` is the build number or version code). Note: Example app.json configuration --- ```json { "expo": { "runtimeVersion": { "policy": "appVersion" } } } ``` --- Note: What about the fingerprint runtime version policy? --- We hope that this will be the future of runtime version policies, but for now, we recommend using the `"appVersion"` policy. The `"fingerprint"` policy is experimental and not yet widely recommended. --- ## Deploying previews You can preview updates in your internal distribution release builds or in development builds. Using internal distribution instead of deploying to a store beta track reduces the friction of distributing the app to internal testers, and is suitable for cases where you want to, for example, share a build on every pull request or an early concept that you're working on. ### Internal distribution release builds As explained above, preview builds will point to the "preview" channel. If you want multiple versions of the preview app distributed internally at any given time, you can change the channel name based on the feature name. For example, you might set your channel on your build to "preview-feature-a" when working on feature A, and then set it to "preview-feature-b" when working on feature B. ### Preview in development builds Development builds can load updates from any channel, provided the runtime version is compatible. Learn more about this in [Previewing updates](/eas-update/develop-faster/). ## Deploying to staging Run `eas update --channel staging` to publish an update to staging. This will make your hotfix immediately available to users of staging builds with the targeted runtime version. Your staging environment will be Google Play Beta or TestFlight — the "beta track" on respective app stores. You may alternatively use internal distribution, but deploying to a store beta track is generally recommended when you are staging code for a production release since users are able to access it without any knowledge of internal processes for distributing the app (while using internal distribution would require users to download the app from an expo.dev URL). A common practice for creating staging builds is to always create one whenever you upload a production build to a store. This allows you to have a staging build with an identical runtime to the production build, which you can use to test your updates before rolling them out to production. With EAS Build, this means running `eas build --profile staging --auto-submit` every time you run `eas build --profile production --auto-submit`. ## Deploying to production Run `eas update --channel production` to bundle and push a new update to production. This will make your hotfix immediately available to production build users with the same runtime version. **If you have already published the fix to staging and verified it there**, ensure that you are republishing from the same commit. For this release process we recommend using identical environment variables and code signing configuration on staging as on production, to ensure that updates verified in staging work exactly the same in production. If do this, then you can `eas update:republish --destination-channel production` to promote the update rather than generating a new one. This will ensure the exact same bundle that you tested in staging is used in production. Run `eas update --channel production` to publish an update to production. This will make your hotfix immediately available to users of production builds with the same runtime version. ### Runtime versions When creating a new production build, we recommend incrementing your [app version](/build-reference/app-versions/#app-versions) to ensure it has a unique runtime version for each release of your app. ### Gradually rolling out updates You can use [per-update rollouts](/eas-update/rollouts/#per-update-rollouts) to deploy updates gradually to an increasing percentage of users. For example: `eas update --rollout-percentage 10` will roll out the update to 10% of users, and you can use `eas update:edit` to edit the rollout percentage later. Learn more in [Rollouts](/eas-update/rollouts/). Note: What other types of rollouts are available? --- Another type of rollout is called "branch-based rollouts". These require a deployment strategy focused around update branches, which we are not using in this guide and are not required for most use cases. The distinction between per-update rollouts and branch-based rollouts is that per-update rollouts operate on a single update (update with ID `123` will be rolled out to 10% of users on the `production` channel/branch), whereas branch-based rollouts will roll out switching over to a different branch (which is a stream of updates) (branch `hotfix-123` will be rolled out to 10% of users on the `production` channel, and `hotfix-123` can point to update ID `123` or `124`). --- ### 路由参数 ¥Rolling back to a previous update version 如果你错误地向任何环境发布了更新,则可以运行 `eas update:rollback` 启动回滚到以前的更新。 ¥If you've mistakenly published an update to any of your environments, you can run `eas update:rollback` initiate a rollback to a previous update. 在 [回滚](/eas-update/rollbacks/) 中了解更多信息。 ¥Learn more in [Rollbacks](/eas-update/rollbacks/). ## 下一步 ¥Next steps * [了解有关持久暂存发布过程的更多信息](/eas-update/deployment-patterns/#persistent-staging-flow),与此处描述的非常相似。 ¥[Learn more about the Persistent staging release process](/eas-update/deployment-patterns/#persistent-staging-flow), which is very similar to what is described here. * [探索在开发版本中使用预览更新](/eas-update/develop-faster/)。 ¥[Explore using preview updates in development builds](/eas-update/develop-faster/). ## 下载更新 了解下载和启动更新的策略。 > **info** 本页面中的以下所有信息仅适用于 [启用 `EX_UPDATES_NATIVE_DEBUG` 后](/eas-update/debug/#runtime-issues) 的发布版本和调试版本。 > > ¥All of the following information on this page applies only to release builds and debug builds [with `EX_UPDATES_NATIVE_DEBUG` enabled](/eas-update/debug/#runtime-issues). 在本节中,我们将介绍下载和启动更新的不同策略。目标是确保终端用户在应用发布后尽快采用最新版本,而不会因引入缓慢的加载屏幕或其他问题而牺牲用户体验。这些策略并不互相排斥,你可以根据应用的需求进行混合搭配。 ¥In this section, we'll cover the different strategies for downloading and launching updates. The goal is to ensure that the end user adopts the latest version of the app as soon as possible after it is published, without sacrificing the user experience by introducing slow loading screens or other issues. The strategies are not mutually exclusive, and you can mix and match them as needed for the requirements of your app. ## 默认情况下,更新在启动时异步加载 ¥Updates are loaded asynchronously on startup by default 默认行为是在应用冷启动(从终止状态启动)时检查更新,并在更新可用时下载。此过程不会阻止应用的加载,因此使用此策略时,终端用户仅在更新发布后冷启动应用时加载更新,然后在某个时间点终止并重新启动应用(例如,从操作系统的最近应用列表中关闭应用,或者关闭设备然后再打开)。 ¥The default behavior is to check for an update when the app is cold booted (launched from a killed state) and download the update if it's available. This process does not block loading the app, so when using this strategy the end user will only load the update when they cold boot the app after an update has been published, and then at some point kill and restart the app (for example, if they close it from the recent apps list on the OS or if they turn the device off and on). 这种行为是安全的,因为它不会干扰应用启动以等待网络请求完成(在常见的实际情况下,当用户发现自己的网络连接速度很慢并且卡在加载屏幕上几秒钟时,这将是糟糕的用户体验)。缺点是用户需要更长的时间来适应最新版本的应用。如果理想的情况是更新发布后立即被所有用户采用,那么此策略远远达不到此要求。 ¥This behavior is safe because it doesn't interfere with app startup to wait for a network request to complete (which would be a bad user experience in common real-world cases where a user finds themself with a slow connection and they are stuck on a loading screen for several seconds). The downside is that it takes much longer for users to adopt the latest version of the app. If the ideal is for an update to be adopted immediately by all users as soon as it is published, then this strategy falls very short of that. Note: What if I want to always block app startup until the latest update is downloaded? --- We recommend against this strategy because the resulting user experience is extremely poor. Typically when a user is stuck waiting on a splash screen when booting an app, they will close the app and try again (and so downloading the update won't complete), or give up and use another app. When the users' device is connected to a slow network, even when there is no update, they may have to wait several seconds or more to load the app. If ensuring that your users always have the latest version of your app is critical, you may want to explore one of the other strategies explained here. --- Note: How can I disable the default behavior? --- You can disable the default behavior by setting the [`checkAutomatically`](/versions/latest/sdk/updates/#updatescheckautomaticallyvalue) option to `NEVER` in the `updates` configuration. This will prevent the app from checking for updates and downloading them automatically. --- ## Checking for updates while the app is running You can use `Updates.checkForUpdateAsync()` to check for updates while the app is running. This will return a promise that resolves to a [`UpdateCheckResult` object](/versions/latest/sdk/updates/#updatecheckresult), with `isAvailable` set to `true` if an update is available, and information about the update in the [`manifest`](/versions/latest/sdk/manifests/#expoupdatesmanifest) property. If an update is available, you can use the `Updates.downloadAsync()` method to download the update. This will return a promise that resolves when the download is complete. Finally, you can use the `Updates.reloadAsync()` method to reload the app with the new version. The `useUpdates()` hook can also be used to monitor the state of the `expo-updates` library from a React component. Note: What are common patterns for checking for updates while the app is running? --- - You can check for updates at various points in your app's lifecycle, such as [when it is foregrounded](https://rn.nodejs.cn/docs/appstate) or at some interval. When an update is found, you may want to show a dialog to the user to prompt the user to update. - You can check for updates at launch and display your own custom loading screen, if it is very important for your use case to ensure that users always get the latest version at launch. --- ## Checking for updates while the app is backgrounded You can use [`expo-background-task`](/versions/latest/sdk/background-task/) to check for updates while the app is backgrounded. To do this, use the same `Updates.checkForUpdateAsync()` and `Updates.downloadAsync()` methods as you would in the foreground, but execute them inside of a background task. This is a great way to ensure that the user always has the latest version of the app, even if they have not opened the app in a while. It's worth considering whether you want to reload the app after an update is downloaded in the background, or wait for the user to close and reopen it. If you choose to only download it in the background and not apply it, this should still be useful to ensure that the next boot will immediately have the latest version, and it will lead to a faster adoption rate for updates compared to the default behavior. Note: An example of how to check for updates in the background --- To ensure the background task is registered when the application starts, import and invoke the `setupBackgroundUpdates` function within the top-level component. ```ts import * as TaskManager from 'expo-task-manager'; import * as BackgroundTask from 'expo-background-task'; import * as Updates from 'expo-updates'; const BACKGROUND_TASK_NAME = 'task-run-expo-update'; export const setupBackgroundUpdates = async () => { TaskManager.defineTask(BACKGROUND_TASK_NAME, async () => { const update = await Updates.checkForUpdateAsync(); if (update.isAvailable) { await Updates.fetchUpdateAsync(); await Updates.reloadAsync(); } return Promise.resolve(); }); await BackgroundTask.registerTaskAsync(BACKGROUND_TASK_NAME, { minimumInterval: 60 * 24, }); }; setupBackgroundUpdates(); ``` --- Note: Should I also apply the update with Updates.reloadAsync() when the app is backgrounded? --- **Support for calling `Updates.reloadAsync()` while the app is backgrounded is experimental**. This is a new feature and it is not widely used, be sure to monitor for crashes when you first enable it. Downloading an update in the background is safe. Reloading an update while the app is backgrounded can be a great way to ensure that the user has the latest version of the app when they open it again. However, it is important to note that, unless you persist the state that the app was in at the time it was backgrounded and restore that state, the user will experience a cold boot when they open the app again. One way to mitigate this is to only do a reload in the background if the app has been inactive for a certain period of time, after which a user is unlikely to expect the app to restore its previous state. --- ## 关键/强制更新 ¥Critical/mandatory updates `expo-updates` 库不提供对关键/强制更新的一级支持。但是,你可以实现自己的逻辑来检查关键更新并手动应用它们。[`expo/UpdatesAPIDemo` 代码库](https://github.com/expo/UpdatesAPIDemo) 包含一个解决此问题的示例。你可以将该方法与上述策略结合使用来检查更新。 ¥There is no first-class support for critical/mandatory updates in the `expo-updates` library. However, you can implement your own logic to check for critical updates and apply them manually. The [`expo/UpdatesAPIDemo` repository](https://github.com/expo/UpdatesAPIDemo) contains an example of one way to approach this. You can combine that approach with the strategies above to check for updates. ## 控制从客户端加载哪些更新 ¥Controlling which update to load from the client side 使用 EAS 更新的典型方法是将单个更新 URL 和一组请求标头(例如更新渠道名称)嵌入到应用的构建中。要控制加载哪些更新,你可以通过 `eas update` 命令或 EAS 仪表板在服务器上进行更改。例如,你将新更新发布到构建指向的通道,然后构建将在下次启动时获取该更新。使用此方法不会下载发布到与你的构建指向不同的通道的更新。 ¥The typical way to use EAS Update is to have a single update URL and a set of request headers (such as update channel name) embedded in a build of your app. To control which update is loaded, you make changes on the server through the `eas update` command or the EAS dashboard. For example, you publish a new update to a channel that your build is pointing to, then the build fetches that update on the next launch. Updates published to a channel different from the one your build is pointing to will not be downloaded with this approach. 你可以使用 `Updates.setUpdateURLAndRequestHeadersOverride()` 方法在运行时覆盖更新 URL 和请求标头。如果你想在应用运行时加载特定更新或更改更新渠道,这将非常有用。[了解更多](/eas-update/override/)。 ¥You can override the update URL and request headers at runtime using the `Updates.setUpdateURLAndRequestHeadersOverride()` method. This can be useful if you want to load a specific update or change the update channel while the app is running. [Learn more](/eas-update/override/). ## 监控更新的采用情况 ¥Monitoring adoption of updates 更新详情页面(例如:`https://expo.dev/accounts/[account]/projects/[project]/updates/[id]`)显示已运行更新的用户数量指标,以及安装失败数量(下载并尝试运行更新但崩溃的用户)。 ¥The details page for an update (for example: `https://expo.dev/accounts/[account]/projects/[project]/updates/[id]`) shows metrics for the number of users who have run the update, in addition to the number of failed installs (users who download and attempted to run the update, but it crashed). 部署页面(例如:`https://expo.dev/accounts/[account]/projects/[project]/deployments/production/[runtime-version]`)包含一个表格和图表,显示在给定时间段内运行过与特定更新渠道和运行时版本组合相关的每个更新的用户数量。 ¥The deployments page (for example: `https://expo.dev/accounts/[account]/projects/[project]/deployments/production/[runtime-version]`) includes a table and chart that shows the number of users who have run each update related to a particular update channel and runtime version combination, over a given time period. ## 推出 了解如何使用推出机制逐步向用户部署更新。 推出允许你向部分用户推出更改,以在向所有用户发布更改之前捕获错误或其他问题。 ¥A rollout allows you to roll out a change to a portion of your users to catch bugs or other issues before releasing that change to all your users. EAS 根据你的用例提供每次更新和基于分支的推出机制。 ¥EAS provides **per-update and branch-based rollout mechanisms** depending on your use case. ## 每次更新推出 ¥Per-update rollouts 此推出机制允许你指定发布新更新时应接收新更新的用户百分比,然后逐渐增加该百分比。 ¥This rollout mechanism allows you to specify a percentage of users that should receive a new update when you publish it, and then increase that percentage gradually afterwards. ### 启动 ¥Starting 要启动基于更新的部署,请将 `--rollout-percentage` 标志添加到你的常规 `eas update` 命令中: ¥To start an update-based rollout, add the `--rollout-percentage` flag to your normal `eas update` command: ```sh $ eas update --rollout-percentage=10 ``` 在此示例中,发布时,更新将仅对 10% 的终端用户可用。 ¥In this example, when published, the update will only be available to 10% of your end users. ### 进行中 ¥Progressing 要编辑基于更新的推出的百分比: ¥To edit the percentage of an update-based rollout: ```sh $ eas update:edit ``` 系统将引导你完成选择要编辑的更新的过程,并要求你输入新的百分比。 ¥You will be guided through the process of selecting the update to edit and asked for the new percentage. ### 结束 ¥Ending 在结束基于更新的发布时,你有两个选择: ¥When ending an update-based rollout, you have two options: * 全面推出:要实现此最终状态,请按上述详细说明推进部署并将百分比设置为 100。 ¥**Roll out fully**: To accomplish this end state, progress the rollout as detailed above and set the percentage to 100. * 恢复到先前状态:要实现此目的,请运行 `eas update:revert-update-rollout`,它将引导你恢复到之前的状态。 ¥**Revert back to previous state**: To accomplish this, run `eas update:revert-update-rollout` which will guide you through reverting back to the previous state. ### 附加说明 ¥Additional notes * 一次只能在分支上推出一个更新。 ¥Only one update can be rolled out on a branch at one time. * 当发布正在进行时,必须使用上述选项之一结束发布,然后才能发布新的更新(使用相同的运行时版本)。这可以防止意外破坏部署。 ¥When a rollout is in progress, it must be ended using one of the options above before a new update (with the same runtime version) can be published. This prevents accidentally clobbering the rollout. * 要查看推出状态,请使用 `eas update:list` 或 `eas update:view` 命令。 ¥To see the state of the rollout, use the `eas update:list` or `eas update:view` commands. * 恢复在具有现有更新的分支上创建的部署将重新发布控制更新。这可确保所有客户端都恢复到之前的状态。 ¥Reverting a rollout that is created on a branch with an existing update will republish the control update. This ensures that all clients are reverted back to the previous state. * 可以在没有当前更新的分支上启动部署,在这种情况下,第一个更新将部署给指定比例的用户。还原时,将创建一个回滚到嵌入式更新,这会将客户端还原到之前的状态(嵌入式更新)。 ¥A rollout can be started on a branch with no current update, in which case the first update will be rolled out to the specified percentage of users. When reverted, a rollback-to-embedded update will be created, which will revert the clients to their previous state (embedded update). ## 基于分支的推出 ¥Branch-based rollouts 此推出机制允许你逐步将新分支上的一组更新推出给一定比例的终端用户,并将剩余比例的用户留在当前分支上。 ¥This rollout mechanism allows you to incrementally roll out a set of updates on a new branch to a percentage of end users and leave the remaining percentage of users on the current branch. ### 启动 ¥Starting 要启动基于分支的部署,请运行以下 EAS CLI 命令: ¥To start a branch-based rollout, run the following EAS CLI command: ```sh $ eas channel:rollout ``` 在终端中,交互式指南将帮助你选择通道、选择要推出的分支以及设置要推出的用户百分比。要增加或减少转出量,请再次运行该命令并选择 `Edit` 选项来调整转出百分比。 ¥In the terminal, an interactive guide will assist you in selecting a channel, choosing a branch for the rollout, and setting the percentage of users for the rollout. To increase or decrease the rollout amount, run the command again and choose the `Edit` option to adjust the rollout percentage. ### 结束 ¥Ending 当你在交互式指南中选择 `End` 选项时,有两种方法可以结束推出: ¥Two methods are available to end a rollout when you choose the `End` option in the interactive guide: * 重新发布并恢复:当你对新分支的状态有信心时,请使用此选项。这会将最新更新从新分支重新发布到旧分支,并且所有用户都将被指向旧分支。 ¥**Republish and revert:** Use this option when you are confident with the state of the new branch. This will republish the latest update from the new branch to the old branch, and all users will be pointed to the old branch. * 恢复:选择忽略新分支上的更新并将用户返回到旧分支。 ¥**Revert:** Choose to disregard the updates on the new branch and return users to the old branch. ### 附加说明 ¥Additional notes * 一个通道上一次只能推出一个分支。 ¥Only one branch can be rolled out on a channel at a single time. * 要查看推出的状态,请使用 `eas channel:rollout` 命令。 ¥To see the state of the rollout, use the `eas channel:rollout` command. * 例如,当部署正在进行时,你可以通过运行 `eas update --branch [branch]` 将更新发布到已部署分支和当前分支。 ¥When a rollout is in progress, you can publish updates to both rolled out and current branches by running `eas update --branch [branch]`, for example. * `eas update --channel [channel]` 无法在部署过程中使用,因为它无法知道将更新与部署中的哪个分支关联。 ¥`eas update --channel [channel]` cannot be used when a rollout is in progress since it cannot know which branch in the rollout to associate the update with. ## 回滚 将分支回滚到以前的更新或嵌入式更新。 EAS 更新支持两种类型的回滚: ¥There are two types of rollbacks supported by EAS Update: * 回滚到之前发布的更新。 ¥Roll back to a previously-published update. * 回滚到构建中嵌入的更新。 ¥Roll back to the update embedded in the build. ## 开始回滚 ¥Start a rollback 要开始回滚,请运行以下命令: ¥To start a rollback, run the following command: ```sh $ eas update:rollback ``` 在终端中,交互式指南将帮助你选择回滚类型并执行回滚。 ¥In the terminal, an interactive guide will assist you in selecting the type of rollback and doing the rollback. ## 回滚到之前发布的更新 ¥Rolling back to a previously-published update 上述命令重新发布之前发布的更新,以便在功能上将客户端回滚到该更新。 ¥The above command re-publishes a previously-published update to functionally roll back clients to that update. ## 回滚到构建中嵌入的更新 ¥Rolling back to the update embedded in the build 上述命令指示客户端运行构建中嵌入的更新。 ¥The above command instructs the client to run the update embedded in the build. ## 回滚后发布 ¥Publishing after the rollback 回滚后再次发布时,所有客户端都将收到新的更新。 ¥Upon publishing again after a rollback, all clients will receive the new update. ## 优化 EAS 更新资源 了解 EAS Update 如何下载资源以及如何优化它们的下载大小。 > **info** 新的 [资源选择功能](/eas-update/asset-selection) 可以大大减少下载资源的总数和大小。 > > ¥The new [asset selection feature](/eas-update/asset-selection) can greatly reduce the total number and size of downloaded assets. 当应用发现新的更新时,它会下载清单,然后下载任何新的或更新的资源,以便可以运行更新。流程如下: ¥When an app finds a new update, it downloads a manifest and then downloads any new or updated assets so that it can run the update. The process is as follows: 许多运行 Android 和 iOS 应用的用户所使用的移动连接不如使用 Wi-Fi 时那样一致或快速,因此作为更新的一部分提供的资源尽可能小非常重要。 ¥Many users running Android and iOS apps are using mobile connections that are not as consistent or fast as when they are using Wi-Fi, so it's important that the assets shipped as a part of an update are as small as possible. ## 代码资源 ¥Code assets 发布更新时,EAS CLI 运行 Expo CLI 将项目打包到更新中。更新将显示在我们项目的 dist 目录中。 ¥When publishing an update, EAS CLI runs Expo CLI to bundle the project into an update. The update will appear in our project's **dist** directory. 在 dist/bundles 中,我们可以看到 index.android.js 和 index.ios.js 文件的大小,它们分别将作为 Android 和 iOS 更新的一部分。请注意,这些是未压缩的文件大小;EAS Update 使用 Brotli 和 gzip 压缩,可以显着减少下载大小。不过,如果设备之前未下载过这些文件,那么在获取新更新时,这些文件将被下载到用户的设备上。使这些文件大小尽可能小有助于终端用户快速下载更新。 ¥In **dist/bundles**, we can see the size of the **index.android.js** and **index.ios.js** files that will be part of the Android and iOS updates, respectively. Note that these are uncompressed file sizes; EAS Update uses Brotli and gzip compression, which can significantly reduce download sizes. Nevertheless, these files will be downloaded to a user's device when getting the new update if the device has not downloaded them before. Making these file sizes as small as possible helps end-users download updates quickly. ## 图片资源 ¥Image assets 如果这些资源尚未成为其版本的一部分,则应用用户在检测到新更新时必须下载任何新图片或其他资源。你可以在 dist/assets 中查看已上传到 EAS 服务器的所有资源。那里的资源经过哈希处理,并删除了扩展名,因此很难知道那里有哪些资源。要查看打印精美的资源列表,我们可以运行: ¥App users will have to download any new images or other assets when they detect a new update if those assets are not already a part of their build. You can view all the assets uploaded to EAS servers in **dist/assets**. The assets there are hashed with their extensions removed, so it is difficult to know what assets are there. To see a pretty-printed list of assets, we can run: ```sh $ npx expo export ``` ### 优化图片资源 ¥Optimizing image assets 要手动优化项目中的图片资源,你可以使用 `npx expo-optimize` 命令。它使用 [sharp](https://sharp.nodejs.cn/) 库来压缩图片。 ¥To manually optimize image assets in your project, you can use the `npx expo-optimize` command. It uses [sharp](https://sharp.nodejs.cn/) library to compress images. ```sh $ npx expo-optimize ``` 运行命令后,除已优化的图片资源外,所有图片资源都将被压缩。你可以通过在命令中包含 `--quality [number]` 选项来调整压缩质量。例如,要压缩到 90%,请运行: ¥After running the command, all image assets are compressed except the ones that are already optimized. You can adjust the compression quality by including the `--quality [number]` option with the command. For example, to compress to 90%, run: ```sh $ npx expo-optimize --quality 90 ``` ### 其他手动优化方法 ¥Other manual optimization methods 要手动优化图片和视频,请参阅 [资源](/develop/user-interface/assets/#manual-optimization-methods) 了解更多信息。 ¥To optimize images and videos manually, see [Assets](/develop/user-interface/assets/#manual-optimization-methods) for more information. ## 确保资源包含在更新中 ¥Ensuring assets are included in updates 当你发布更新时,EAS 会将你的资源上传到 CDN,以便用户在运行你的应用时可以获取它们。但是,对于要上传到 CDN 的资源,必须在应用代码的某个地方明确要求它们。有条件地要求资源将导致打包器无法检测到它们,并且在你发布项目时不会上传它们。 ¥When you publish an update, EAS will upload your assets to the CDN so that they may be fetched when users run your app. However, for assets to be uploaded to the CDN, they must be explicitly required somewhere in your application's code. Conditionally requiring assets will result in the bundler being unable to detect them, and they will not be uploaded when you publish your project. ## 进一步的考虑 ¥Further considerations 需要注意的是,用户的应用只会下载新的或更新的资源。它不会重新下载应用中已存在的未更改的资源。 ¥It's important to note that a user's app will only download new or updated assets. It will not re-download unchanged assets that already exist inside the app. 确保更新尽可能少的一种方法是经常构建应用并将其提交到应用商店,以便用户可以下载包含更多最新资源的新应用二进制文件。通常,在添加大型或多个资源时构建和提交应用是一种很好的做法,并且最好使用更新来修复小错误并在应用商店版本之间进行微小更改。 ¥One way to make sure that updates stay as slim as possible is to build and submit the app frequently to the app stores so that users can download a new app binary that includes more up-to-date assets. Generally, it's a good practice to build and submit an app when adding large or multiple assets, and it's good to use updates to fix small bugs and make minor changes between app store releases. ## 替代部署模式 了解使用 EAS 更新时项目的不同部署模式。 一旦我们在应用中创建了功能并修复了错误,我们希望尽可能快速、安全地向用户提供这些功能和错误修复。在向用户交付代码时,"safe" 和 "fast" 通常是对立的力量。我们可以将代码直接推送到生产环境,这会更快但安全性较差,因为我们从未测试过我们的代码。另一方面,我们可以进行测试构建,与 QA 团队共享它们,并定期发布它们,这会更安全,但向用户交付更改的速度会更慢。 ¥Once we've created features and fixed bugs in our app, we want to deliver those features and bug fixes to our users as quickly and safely as we can. Often "safe" and "fast" are opposing forces when delivering code to our users. We could push our code directly to production, which would be fast yet less safe since we never tested our code. On the other hand, we could make test builds, share them with a QA team, and release them periodically, which would be safer but slower to deliver changes to our users. 根据你的项目,在向用户提供更新时,你需要对 "fast" 和 "safe" 的方式有一定的容忍度。 ¥Depending on your project, you'll have some tolerance for how "fast" and how "safe" you'll need to be when delivering updates to your users. 设计 EAS 更新部署流程时需要考虑三个部分: ¥There are three parts to consider when designing the EAS Update deployment process: 1. **创建构建** ¥**Creating builds** * (a)我们只能创建仅供生产使用的版本。 ¥(a) We can create builds for production use only. * (b)我们可以创建用于生产用途的构建,并为生产前变更测试创建单独的构建。 ¥(b) We can create builds for production use and separate builds for pre-production change testing. 2. **测试变更** ¥**Testing changes** * (a)我们可以使用 TestFlight 和 Play Store Internal Track 来测试更改。 ¥(a) We can test changes with TestFlight and Play Store Internal Track. * (b)我们可以通过内部发行版构建来测试更改。 ¥(b) We can test changes with an internal distribution build. * (c)我们可以使用 Expo Go 或 [开发构建](/develop/development-builds/introduction/) 来测试更改。 ¥(c) We can test changes with Expo Go or a [development build](/develop/development-builds/introduction/). 3. **发布更新** ¥**Publishing updates** * (a)我们可以将更新发布到单个分支。 ¥(a) We can publish updates to a single branch. * (b)我们可以创建基于环境的更新分支,例如 "production" 和 "staging"。 ¥(b) We can create update branches that are environment-based, like "production" and "staging". * (c)我们可以创建基于版本的更新分支,例如 "版本-1.0",这使我们能够将更新从一个渠道推广到另一个渠道。 ¥(c) We can create update branches that are version-based, like "version-1.0", which enables us to promote updates from one channel to another. 我们可以混合、匹配和调整上述部分,为我们的团队和用户创建一个在发布节奏和安全性之间取得适当平衡的流程。 ¥We can mix, match, and tweak the parts above to create a process that is the right balance of release cadence and safety for our team and users. 另一个需要考虑的权衡是我们在整个过程中必须做的版本/名称/环境的簿记量。我们要做的簿记工作越少,就越容易遵循一致的流程。它还将使我们与同事的沟通变得更加容易。如果我们需要细粒度的控制,就需要簿记来获得我们想要的精确流程。 ¥Another trade-off to consider is the amount of bookkeeping of versions/names/environments we'll have to do throughout the process. The less bookkeeping we have to do will make it easier to follow a consistent process. It'll also make it easier to communicate with our colleagues. If we need fine-grained control, bookkeeping will be required to get the exact process we want. 下面,我们概述了有关如何使用 EAS 更新部署项目的四种常见模式。 ¥Below, we've outlined four common patterns on how to deploy a project using EAS Update. ## 两条命令流程 ¥Two-command flow 此流程是最简单、最快的流程,安全检查量也最少。它非常适合尝试 Expo 和小型项目。以下是构成此流程的上述部署过程的部分: ¥This flow is the simplest and fastest flow, with the fewest amount of safety checks. It's great for trying out Expo and for smaller projects. Here are the parts of the deployment process above that make up this flow: 创建构建:(a)创建仅供生产使用的版本。 ¥**Creating builds:** (a) Create builds for production use only. 测试变化:(c)使用 Expo Go 或 [开发构建](/develop/development-builds/introduction/) 测试更改。 ¥**Testing changes:** (c) Test changes with Expo Go or a [development build](/develop/development-builds/introduction/). 发布更新:(a)发布到单个分支。 ¥**Publishing updates:** (a) Publish to a single branch. ### 流程图 ¥Diagram of flow ### 流程说明 ¥Explanation of flow 1. 在本地开发项目并在开发版本或 Expo Go 中测试更改。 ¥Develop a project locally and test changes in a development build or in Expo Go. 2. 运行 `eas build` 创建构建,然后将它们提交到应用商店。这些版本供公众使用,应提交/审核并在应用商店上发布。 ¥Run `eas build` to create builds, then submit them to app stores. These builds are for public use and should be submitted/reviewed, and released on the app stores. 3. 当我们有想要提供的更新时,请运行 `eas update --branch production` 以立即向我们的用户提供更新。 ¥When we have updates we'd like to deliver, run `eas update --branch production` to deliver updates to our users immediately. #### 该流程的优点 ¥Advantages of this flow * 此流程不需要记录额外的版本或环境名称,这使得与其他人交流变得容易。 ¥This flow does not require bookkeeping extra version or environment names, which makes it easy to communicate to others. * 向构建提供更新的速度非常快。 ¥Delivering updates to builds is very fast. #### 此流程的缺点 ¥Disadvantages of this flow * 没有预生产检查来确保代码按预期运行。我们可以使用 Expo Go 或 [开发构建](/develop/development-builds/introduction/) 进行测试,但这不如专用测试环境安全。 ¥There are no pre-production checks to make sure the code will function as intended. We can test with Expo Go or a [development build](/develop/development-builds/introduction/), but this is less safe than having a dedicated test environment. ## 持续的分期流程 ¥Persistent staging flow 此流程就像 "分店促销流程" 的未版本化变体。我们不跟踪分支的发行版本。相反,我们将拥有可以永久合并的持久 "staging" 和 "production" 分支。以下是构成此流程的上述部署过程的部分: ¥This flow is like an un-versioned variant of the "branch promotion flow". We do not track release versions with branches. Instead, we'll have persistent "staging" and "production" branches that we can merge into forever. Here are the parts of the deployment process above that make up this flow: 创建构建:(b)创建用于生产的构建和单独的测试构建。 ¥**Creating builds:** (b) Create builds for production and separate builds for testing. 测试变化:(a)在 TestFlight 和 Play Store 内部轨道上测试更改和/或 (b) 使用内部发行版测试更改。 ¥**Testing changes:** (a) Test changes on TestFlight and the Play Store Internal Track and/or (b) Test changes with internal distribution builds. 发布更新:(b)创建基于环境的更新分支,例如 "staging" 和 "production"。 ¥**Publishing updates:** (b) Create update branches that are environment-based, like "staging" and "production". ### 流程图 ¥Diagram of flow ### 流程说明 ¥Explanation of flow 1. 在本地开发一个项目并在 Expo Go 中测试更改。 ¥Develop a project locally and test changes in Expo Go. 2. 使用名为 "production" 的渠道创建版本,最终将接受审核并在应用商店上提供。使用名为 "staging" 的通道创建另一组构建,该版本将用于在 TestFlight 和 Play Store Internal Track 上进行测试。 ¥Create builds with channels named "production", which will eventually get reviewed and become available on app stores. Create another set of builds with channels named "staging", which will be used for testing on TestFlight and the Play Store Internal Track. 3. 设置 `expo-github-action` 在合并提交到分支时发布更新。 ¥Set up `expo-github-action` to publish updates when merging commits to branches. 4. 将更改合并到名为 "staging" 的分支中。GitHub Action 将发布更新并使其在我们的测试版本中可用。 ¥Merge changes into a branch named "staging". The GitHub Action will publish an update and make it available on our test builds. 5. 准备好后,将更改合并到 "production" 分支以发布对我们的生产版本的更新。 ¥When ready, merge changes into the "production" branch to publish an update to our production builds. #### 该流程的优点 ¥Advantages of this flow * 此流程允许你独立于开发速度来控制部署到生产的速度。这增加了测试你的应用的额外机会,并避免你的用户每次提交 PR 时都必须下载新的更新。 ¥This flow allows you to control the pace of deploying to production independent of the pace of development. This adds an extra chance to test your app and avoids your user having to download a new update every time a PR is landed. * 与你的团队沟通很容易,因为在合并到名为 "staging" 和 "production" 的 GitHub 分支时会发生部署更新。 ¥It's easy to communicate to your team, since deploying updates occurs when merging into GitHub branches named "staging" and "production". #### 此流程的缺点 ¥Disadvantages of this flow * 检查应用的早期版本稍微复杂一些,因为我们需要检查旧的提交而不是旧的分支。 ¥Checking out previous versions of your app is slightly more complex, since we'd need to check out an old commit instead of an old branch. * 合并到 "production" 时,更新将重新构建并重新发布,而不是从通道 "staging" 的构建移至通道 "production" 的构建。 ¥When merging to "production", the update would be re-built and re-published instead of moved from the builds with channel "staging" to the builds with channel "production". ## 特定于平台的流程 ¥Platform-specific flow 此流程适用于需要始终单独构建和更新 Android 和 iOS 应用的项目。它将产生单独的命令来向 Android 和 iOS 应用提供更新。以下是构成此流程的上述部署过程的部分: ¥This flow is for projects that need to build and update their Android and iOS apps separately all the time. It will result in separate commands for delivering updates to the Android and iOS apps. Here are the parts of the deployment process above that make up this flow: 创建构建:(a)仅创建用于生产的构建,或者 (b) 创建用于生产的构建和单独的测试构建。 ¥**Creating builds:** (a) Create builds for production only, or (b) create builds for production and separate builds for testing. 测试变化:(a)在 TestFlight 和 Play Store 内部轨道上测试更改和/或 (b) 使用内部发行版测试更改。 ¥**Testing changes:** (a) Test changes on TestFlight and the Play Store Internal Track and/or (b) Test changes with internal distribution builds. 发布更新:(b)创建基于环境和平台的更新分支,例如 "ios-staging"、"ios-production"、"android-staging" 和 "android-production"。 ¥**Publishing updates:** (b) Create update branches that are environment- and platform-based, like "ios-staging", "ios-production", "android-staging", and "android-production". ### 流程图 ¥Diagram of flow ### 流程说明 ¥Explanation of flow 1. 在本地开发一个项目并在 Expo Go 中测试更改。 ¥Develop a project locally and test changes in Expo Go. 2. 使用名为 "ios-staging"、"ios-production"、"android-staging" 和 "android-production" 的通道创建构建。然后将 "ios-staging" 构建放在 TestFlight 上,并将 "ios-production" 构建提交到公共 App Store。同样,将 "android-staging" 版本放在 Play 商店内部轨道上,并将 "android-production" 版本提交到公共 Play 商店。 ¥Create builds with channels named like "ios-staging", "ios-production", "android-staging", and "android-production". Then put the "ios-staging" build on TestFlight and submit the "ios-production" build to the public App Store. Likewise, put the "android-staging" build on the Play Store Internal Track, and submit the "android-production" build to the public Play Store. 3. 设置 `expo-github-action` 在将提交合并到分支时将更新发布到所需的平台。 ¥Set up `expo-github-action` to publish updates to the required platforms when merging commits to branches. 4. 然后,将 iOS 应用的更改合并到分支 "ios-staging" 中,然后在准备就绪时将更改合并到 "ios-production" 分支中。同样,将 Android 应用的更改合并到分支 "android-staging" 中,准备就绪后,合并到名为 "android-production" 的分支中。 ¥Then, merge changes for the iOS app into the branch "ios-staging", then when ready merge changes into the "ios-production" branch. Likewise, merge changes for the Android app into the branch "android-staging" and when ready, into the branch named "android-production". #### 该流程的优点 ¥Advantages of this flow * 此流程使你可以完全控制哪些更新进入你的 Android 和 iOS 版本。更新永远不会适用于两个平台。 ¥This flow gives you full control of which updates go to your Android and iOS builds. Updates will never apply to both platforms. #### 此流程的缺点 ¥Disadvantages of this flow * 你必须运行两个命令而不是一个命令才能修复两个平台上的更改。 ¥You'll have to run two commands instead of one to fix changes on both platforms. ## 分店促销流程 ¥Branch promotion flow 此流程是用于管理版本化版本的流程示例。 ¥This flow is an example of a flow for managing versioned releases. > **warning** 此流程需要更多的簿记,并且不支持自动 [运行时版本策略](/eas-update/runtime-versions/#setting-runtimeversion)(`"sdkVersion"`、`"appVersion"`、`"nativeVersion"` 和 `"fingerprint"`)。你将需要使用此流程 [手动指定](/eas-update/runtime-versions/#custom-runtimeversion) 运行时的版本。 > > ¥This flow requires a bit more bookkeeping and does not support automatic [runtime version policies](/eas-update/runtime-versions/#setting-runtimeversion) (`"sdkVersion"`, `"appVersion"`, `"nativeVersion"` and `"fingerprint"`). You will need to [manually specify](/eas-update/runtime-versions/#custom-runtimeversion) your runtimes' versions with this flow. 以下是构成此流程的上述部署过程的部分: ¥Here are the parts of the deployment process above that make up this flow: 创建构建:(b)创建生产版本(每个主要版本一个)和单独的测试版本。 ¥**Creating builds:** (b) Create builds for production (one per major version) and separate builds for testing. 测试变化:(a)在 TestFlight 和 Play Store 内部轨道上测试更改和/或 (b) 使用内部发行版测试更改。 ¥**Testing changes:** (a) Test changes on TestFlight and the Play Store Internal Track and/or (b) Test changes with internal distribution builds. 发布更新:(c)创建基于版本的更新分支,例如 "版本-1.0"。分支动态映射到渠道,以促进从测试到生产的经过充分测试的变更。 ¥**Publishing updates:** (c) Create update branches that are version based, like "version-1.0". Branches are dynamically mapped to channels to promote well-tested changes from testing to production. ### 流程图 ¥Diagram of flow ### 流程说明 ¥Explanation of flow 1. 在本地开发一个项目并在 Expo Go 或 [开发构建](/develop/development-builds/introduction/) 中测试更改。 ¥Develop a project locally and test changes in Expo Go or a [development build](/develop/development-builds/introduction/). 2. 使用名为 "production-rtv-1" 的通道(表示运行时版本为 "1" 的通道)创建构建,该版本最终将接受审核并在应用商店中提供。使用名为 "staging" 的通道创建另一组构建,该版本将用于在 TestFlight 和 Play Store Internal Track 上进行测试。 ¥Create builds with channels named "production-rtv-1" (indicating a channel with a runtime version "1"), which will eventually get reviewed and become available on app stores. Create another set of builds with channels named "staging", which will be used for testing on TestFlight and the Play Store Internal Track. 3. 设置 `expo-github-action` 在合并提交到分支时发布更新。 ¥Set up `expo-github-action` to publish updates when merging commits to branches. 4. 将更改合并到名为 "version-1" 的分支中。 ¥Merge changes into a branch named "version-1". 5. 使用网站或 EAS CLI 将 "staging" 通道指向 EAS 更新分支 "version-1"。通过打开 TestFlight 和 Play Store Internal Track 上的应用来测试更新。 ¥Use the website or EAS CLI to point the "staging" channel at the EAS Update branch "version-1". Test the update by opening the apps on TestFlight and the Play Store Internal Track. 6. 准备就绪后,使用网站或 EAS CLI 将 "production-rtv-1" 通道指向 EAS 更新分支 "version-1"。 ¥When ready, use the website or EAS CLI to point the "production-rtv-1" channel at the EAS Update branch "version-1". 7. 那么,你可能会遇到两种更新场景: ¥Then, there are two update scenarios you may encounter: * 新版本不需要新的运行时版本: ¥A new release does not require a new runtime version: 1. 创建另一个名为 "version-2" 的 GitHub 分支。 ¥Create another GitHub branch named "version-2". 2. 使用网站或 EAS CLI 将 "staging" 通道指向 EAS 更新分支 "version-2"。 ¥Use the website or EAS CLI to point the "staging" channel at the EAS Update branch "version-2". 3. 合并提交到 "version-2" 分支,直到新功能和修复准备就绪且稳定。 ¥Merge commits into the "version-2" branch until the new features and fixes are ready and stable. 4. 使用网站或 EAS CLI 将 "production-rtv-1" 通道指向 EAS 更新分支 "version-2"。这意味着拥有生产版本的每个人(从应用商店下载应用的用户)现在都将获得 "version-2" 分支上的最新更新。 ¥Use the website or EAS CLI to point the "production-rtv-1" channel at the EAS Update branch "version-2". This will mean that everyone with a production build (users who downloaded the app from the app stores) will now get the latest update on the "version-2" branch. * 新版本需要新的运行时版本(例如,添加新的原生库或升级 SDK 版本时): ¥A new release requires a new runtime version (for example, when new native libraries are added or SDK version is upgraded): 1. 将运行时版本从 "1" 升级到 "2"。 ¥Bump your runtime version from "1" to "2". 2. 使用新的运行时版本创建新的 "staging" 版本。 ¥Create a new "staging" build with the new runtime version. 3. 创建另一个名为 "version-2" 的 GitHub 分支。 ¥Create another GitHub branch named "version-2". 4. 使用网站或 EAS CLI 将 "staging" 通道指向 EAS 更新分支 "version-2"。 ¥Use the website or EAS CLI to point the "staging" channel at the EAS Update branch "version-2". 5. 合并提交到 "version-2" 分支,直到新功能和修复准备就绪且稳定。 ¥Merge commits into the "version-2" branch until the new features and fixes are ready and stable. 6. 创建一个名为 "production-rtv-2" 的新版本,该版本最终将接受审核并在应用商店上架。 ¥Create a new build with channel named "production-rtv-2", which will eventually get reviewed and become available on app stores. 7. 使用网站或 EAS CLI 将 "production-rtv-2" 通道指向 EAS 更新分支 "version-2"。这意味着之前拥有生产版本的每个人(从应用商店下载应用的用户)将继续在 EAS 更新分支 "version-1" 上获取最新更新,直到他们从应用商店下载应用的新版本,此时 他们将在 EAS 更新分支 "version-2" 上获得最新更新。 ¥Use the website or EAS CLI to point the "production-rtv-2" channel at the EAS Update branch "version-2". This will mean that everyone who previously had a production build (users who downloaded the app from the app stores) will continue to get the latest update on EAS Update branch "version-1" until they download the new version of the app from the app store, at which time they will get the latest update on EAS Update branch "version-2". #### 该流程的优点 ¥Advantages of this flow * 此流程比其他流程更安全。所有更新都在分发给内部测试人员的测试版本上进行测试,并且分支在通道之间移动,因此测试的确切工件就是部署到生产版本的工件。 ¥This flow is safer than the other flows. All updates are tested on test builds which are distributed to internal testers, and branches are moved between channels, so the exact artifact tested is the one deployed to production builds. * 此流程在 GitHub 分支和 EAS 更新分支之间创建直接映射。它还创建 GitHub 提交和 EAS 更新更新之间的映射。如果你要跟踪 GitHub 分支,则可以为每个 GitHub 分支创建 EAS 更新分支,并将这些分支链接到构建的通道。实际上,这使得你可以推送到 GitHub,然后在 Expo 上选择相同的分支名称来链接到构建。 ¥This flow creates a direct mapping between GitHub branches and EAS Update branches. It also creates a mapping between GitHub commits and EAS Update updates. If you're keeping track of GitHub branches, you can create EAS Update branches for each GitHub branch and link those branches to a build's channel. In practice, this makes it so you can push to GitHub, then select the same branch name on Expo to link to builds. * 以前的部署版本始终保留在 GitHub 上。一旦部署了 "版本-1.0" 分支,然后再部署另一个版本(如 "版本 1.1"),"版本-1.0" 分支将永远保留,从而可以轻松签出项目的先前版本。 ¥Previous versions of your deployments are always preserved on GitHub. Once the "version-1.0" branch is deployed, then another version is deployed after it (like "version-1.1"), the "version-1.0" branch is forever preserved, making it easy to checkout a previous version of your project. #### 此流程的缺点 ¥Disadvantages of this flow * 每个生产运行时版本需要一个通道来维护先前生产版本的历史更新。这使得使用运行时版本策略变得更加困难。 ¥One channel per production runtime version is needed to maintain historical updates for previous production releases. This makes using a runtime version policy more difficult. * 此流程需要记录分支名称,这意味着与你的团队沟通哪些分支当前指向你的测试构建和生产构建。 ¥Bookkeeping of branch names is required for this flow, which will mean communicating with your team which branches are currently pointed at your test builds and your production builds. # 概念 ## EAS 更新的工作原理 EAS 更新工作原理的概念性概述。 EAS 更新是一项服务,可让你在开发下一个应用商店版本时立即向用户提供小错误修复和更新。使更新可用于构建涉及在构建和更新之间创建链接。 ¥EAS Update is a service that allows you to deliver small bug fixes and updates to your users immediately as you work on your next app store release. Making an update available to builds involves creating a link between a build and an update. 要在构建和更新之间创建链接,我们必须确保更新可以在构建上运行。我们还希望确保可以创建一个部署流程,以便在准备就绪时可以向某些构建公开某些更新。 ¥To create a link between a build and an update, we have to make sure the update can run on the build. We also want to make sure we can create a deployment process so that we can expose certain updates to certain builds when we're ready. 为了说明构建和更新如何交互,请看下图: ¥To illustrate how builds and updates interact, take a look at the following diagram: 构建可以被认为是两层:内置于应用二进制文件中的原生层和可与其他兼容更新交换的更新层。这种分离允许我们将错误修复发送到构建,只要错误修复的更新可以在构建内的原生层上运行。 ¥Builds can be thought of as two layers: a native layer that's built into the app's binary, and an update layer, that is swappable with other compatible updates. This separation allows us to ship bug fixes to builds as long as the update with the bug fix can run on the native layer inside the build. 为了确保更新可以在构建上运行,我们必须设置各种属性,以便我们可以确保我们的构建可以运行更新。当我们创建项目的构建时,这一切就开始了。 ¥To make sure the update can run on the build, we have to set a variety of properties so that we can be sure our builds can run our updates. This starts when we create a build of our project. ## 概念概述 ¥Conceptual overview ### 分发构建 ¥Distributing builds 当我们准备好创建 Expo 项目的构建时,我们可以运行 `eas build` 来创建构建。在构建过程中,该过程将在构建中包含一些对于更新很重要的属性。他们是: ¥When we're ready to create a build of our Expo project, we can run `eas build` to create a build. During the build, the process will include some properties inside the build that are important for updates. They are: * 渠道:通道是我们可以为多个构建指定的名称,以便轻松识别它们。它在 `eas.json` 中定义。例如,我们可能有一个 Android 和一个 iOS 版本,其通道名为 "production",而我们还有另一对版本,其通道名为 "staging"。然后,我们可以将 "production" 渠道的构建分发到公共应用商店,同时将 "staging" 构建保留在 Play Store Internal Track 和 TestFlight 上。稍后当我们发布更新时,我们可以首先将其提供给具有 "staging" 通道的构建;然后,一旦我们测试了我们的更改,我们就可以通过 "production" 通道将更新提供给构建。 ¥**Channel:** The channel is a name we can give to multiple builds to identify them easily. It is defined in `eas.json`. For instance, we may have an Android and an iOS build with a channel named "production", while we have another pair of builds with a channel named "staging". Then, we can distribute the builds with the "production" channel to the public app stores, while keeping the "staging" builds on the Play Store Internal Track and TestFlight. Later when we publish an update, we can make it available to the builds with the "staging" channel first; then once we test our changes, we can make the update available to the builds with the "production" channel. * 运行时版本:运行时版本描述了由运行应用更新层的原生代码层定义的 JS 原生接口。它在项目的 [应用配置](/workflow/configuration/) 中定义。每当我们对原生代码进行更改以更改应用的 JS 原生界面时,我们都需要更新运行时版本。[了解更多。](/eas-update/runtime-versions) ¥**Runtime version:** The runtime version describes the JS-native interface defined by the native code layer that runs our app's update layer. It is defined in a project's [app config](/workflow/configuration/). Whenever we make changes to our native code that change our app's JS-native interface, we'll need to update the runtime version. [Learn more.](/eas-update/runtime-versions) * 平台:每个构建都有一个平台,例如 "安卓" 或 "iOS 系统"。 ¥**Platform:** Every build has a platform, such as "Android" or "iOS". 如果我们使用名为 "staging" 和 "production" 的通道进行两组构建,我们可以将构建分发到四个不同的位置: ¥If we made two sets of builds with the channels named "staging" and "production", we could distribute builds to four different places: 此图只是一个示例,说明如何创建构建并为其通道命名,以及可以将这些构建放置在何处。最终,这取决于你设置的通道名称以及将这些构建放在哪里。 ¥This diagram is just an example of how you could create builds and name their channels, and where you could put those builds. Ultimately it's up to you which channel names you set and where you put those builds. ### 发布更新 ¥Publishing an update 创建构建后,我们可以通过发布更新来更改项目的更新层。例如,我们可以更改 App.js 中的一些文本,然后我们可以将该更改作为更新发布。 ¥Once we've created builds, we can change the update layer of our project by publishing an update. For example, we could change some text inside **App.js**, then we could publish that change as an update. 要发布更新,我们可以运行 `eas update --auto`。此命令将在我们项目的 dist 目录中创建本地更新包。一旦创建了更新包,它就会将该包上传到 EAS 服务器上名为分支的数据库对象中。分支具有名称并包含更新列表,其中最新更新是分支上的活动更新。我们可以将 EAS 分支视为 Git 分支。正如 Git 分支包含提交列表一样,EAS 分支包含更新列表。 ¥To publish an update, we can run `eas update --auto`. This command will create a local update bundle inside the **dist** directory in our project. Once it's created an update bundle, it will upload that bundle to EAS servers, in a database object named a *branch*. A branch has a name and contains a list of updates, where the most recent update is the active update on the branch. We can think of EAS branches just like Git branches. Just as Git branches contain a list of commits, EAS branches contain a list of updates. ### 匹配更新和构建 ¥Matching updates and builds 与构建一样,分支上的每个更新都包含目标运行时版本和目标平台。通过这些字段,我们可以确保更新将在具有更新策略的构建上运行。EAS 的更新政策如下: ¥Like builds, every update on a branch includes a target runtime version and target platform. With these fields, we can make sure that an update will run on a build with something called an *update policy*. EAS' update policy is as follows: * 构建的平台和更新的目标平台必须完全匹配。 ¥The platform of the build and the target platform of an update must match exactly. * 构建的运行时版本和更新的目标运行时版本必须完全匹配。 ¥The runtime version of the build and the target runtime version of an update must match exactly. * 通道可以链接到任何分支。默认情况下,通道链接到同名的分支。 ¥A channel can be linked to any branch. By default, a channel is linked to a branch of the same name. 让我们关注最后一点。每个构建都有一个通道,作为开发者,我们可以将该通道链接到任何分支,这将使分支上的最新兼容更新可用到链接的通道。为了简化此链接,默认情况下我们自动将通道链接到同名的分支。例如,如果我们使用名为 "production" 的通道创建构建,我们可以将更新发布到名为 "production" 的分支,并且我们的构建将从名为 "production" 的分支获取更新,即使我们没有手动链接任何内容。 ¥Let's focus on that last point. Every build has a channel, and we, as developers, can link that channel to any branch, which will make its most recent compatible update available on the branch to the linked channel. To simplify this linking, by default we auto-link channels to branches of the same name. For instance, if we created builds with the channel named "production", we could publish updates to a branch named "production" and our builds would get the updates from the branch named "production", even though we did not manually link anything. 如果你的部署过程中有多个一致的 Git 和 EAS 分支,则此默认链接非常有效。例如,我们可以在 Git 和 EAS 上有一个 "production" 分支和一个 "staging" 分支。与 [GitHub 行动](/eas-update/github-actions) 配对,我们可以做到每次提交推送到 "staging" Git 分支时,我们都会发布到 "staging" EAS 更新分支,这将使该更新适用于我们使用 "staging" 通道的所有构建。一旦我们测试了暂存版本的更改,我们就可以将 "staging" Git 分支合并到 "production" Git 分支,这将在 "production" EAS 更新分支上发布更新。最后,"production" EAS 更新分支上的最新更新将适用于使用 "production" 通道的构建。 ¥This default linking works great if you have a deployment process where you have multiple consistent Git and EAS branches. For instance, we could have a "production" branch and a "staging" branch, both on Git and on EAS. Paired with a [GitHub Action](/eas-update/github-actions), we could make it so that every time a commit is pushed to the "staging" Git branch, we publish to the "staging" EAS Update branch, which would make that update apply to all our builds with the "staging" channel. Once we tested changes on the staging builds, then we could merge the "staging" Git branch into the "production" Git branch, which would publish an update on the "production" EAS Update branch. Finally, the latest update on the "production" EAS Update branch would apply to builds with the "production" channel. 这个流程使得我们可以推送到 GitHub,然后查看我们的构建更新,而无需任何其他干预。 ¥This flow makes it so that we can push to GitHub, then see our builds update without any other interventions. 虽然此流程适用于许多开发者,但我们可以完成另一个流程,因为我们能够更改通道和分支之间的链接。想象一下,我们将分支命名为 "版本-1.0"、"版本-2.0" 和 "版本 3.0"。我们可以将 "版本-1.0" EAS 更新分支链接到 "production" 通道,以使其可用于我们的 "production" 版本。我们还可以将 "版本-2.0" EAS 更新分支链接到 "staging" 通道,以供测试人员使用。最后,我们可以创建一个尚未链接到任何构建的 "版本 3.0" EAS 更新分支,只有开发者才能使用开发构建进行测试。 ¥While this flow works for many developers, there's another flow we can accomplish since we have the ability to change the link between channels and branches. Imagine we name our branches like "version-1.0", "version-2.0", and "version-3.0". We could link the "version-1.0" EAS Update branch to the "production" channel, to make it available to our "production" builds. We could also link the "version-2.0" EAS Update branch to the "staging" channel to make it available to testers. Finally, we could make a "version-3.0" EAS Update branch that is not linked to any builds yet, that only developers are testing with a development build. 一旦测试人员验证 "版本-2.0" EAS 更新分支上的更新已准备好用于生产,我们就可以更新 "production" 通道,以便将其链接到 "版本-2.0" 分支。为了实现这一点,我们可以运行: ¥Once testers verify that the update on the "version-2.0" EAS Update branch is ready for production, we can update the "production" channel so that it's linked to the "version-2.0" branch. To accomplish this, we could run: ```sh $ eas channel:edit production --branch version-2.0 ``` 在此状态之后,我们就准备开始测试 "版本 3.0" EAS 更新分支。与上一步类似,我们可以使用以下命令将 "staging" 通道链接到 "版本 3.0" EAS 更新分支: ¥After this state, we'd be ready to start testing the "version-3.0" EAS Update branch. Similarly to the last step, we could link the "staging" channel to the "version-3.0" EAS Update branch with this command: ```sh $ eas channel:edit staging --branch version-3.0 ``` ## 实用概述 ¥Practical overview 现在我们已经熟悉了 EAS 更新的核心概念,我们来谈谈这个过程是如何发生的。 ¥Now that we're familiar with the core concepts of EAS Update, let's talk about how this process occurs. 当构建包含 `expo-updates` 的 Expo 项目时,包含的原生 Android 和 iOS 代码负责管理、获取、解析和验证更新。 ¥When an Expo project that includes `expo-updates` is built the included native Android and iOS code is responsible for managing, fetching, parsing, and validating updates. 当库检查更新并下载更新时,它们是 [configurable](/versions/latest/config/app/#updates)。默认情况下,该库将在打开时检查更新。如果发现比当前正在运行的更新更新的更新,它将下载并运行较新的更新。如果库没有找到较新的更新,它将运行最新下载的更新,如果尚未下载,则回退到构建时嵌入应用内的更新。 ¥When the library checks for updates and when it downloads them, they are [configurable](/versions/latest/config/app/#updates). By default, the library will check for an update when it is opened. If an update newer than the currently running update is found, it will download and run the newer update. If the library does not find a newer update, it will instead run the newest downloaded update, falling back to the update that was embedded inside the app at build time if none have been downloaded. `expo-updates` 分两个阶段下载更新。首先,它下载最新的更新清单,其中包含有关更新的信息,包括运行更新所需的资源列表(图片、JavaScript 打包包、字体文件等)。其次,库会下载清单中指定的尚未从之前的更新中下载的资源。例如,如果更新包含新图片,则库将在运行更新之前下载新图片资源。为了帮助终端用户快速可靠地获得更新,更新应尽可能小。 ¥`expo-updates` downloads updates in two phases. First, it downloads the most recent update *manifest*, which contains information about the update including a list of assets (images, JavaScript bundles, font files, and so on) that are required to run the update. Second, the library downloads the assets specified in the manifest that it has not yet downloaded from prior updates. For instance, if an update contains a new image, the library will download the new image asset before running the update. To help end-users get updates quickly and reliably, updates should be kept as small as possible. 如果库能够在 `fallbackToCacheTimeout` 设置之前下载清单(第 1 阶段)和所有必需的资源(第 2 阶段),则新更新将在启动后立即运行。如果库无法在 `fallbackToCacheTimeout` 内获取清单和资源,它将继续在后台下载新更新,并在下次启动时运行它。 ¥If the library is able to download the manifest (phase 1) and all the required assets (phase 2) before the `fallbackToCacheTimeout` setting, then the new update will run immediately upon launch. If the library is not able to fetch the manifest and assets within `fallbackToCacheTimeout`, it will continue to download the new update in the background and will run it upon the next launch. ## 包起来 ¥Wrap up 通过 EAS 更新,我们可以快速向用户提供小型、关键的错误修复,并为用户提供最佳体验。这是通过构建的运行时版本、平台和通道来设置的。有了这三个约束,我们就可以为特定的构建组提供更新。这使我们能够在部署过程中投入生产之前测试我们的更改。根据我们设置部署流程的方式,我们可以优化速度。我们还可以优化我们的部署,使其尽可能安全且无错误。部署的可能性是巨大的,几乎可以匹配你喜欢的任何发布过程。 ¥With EAS Update, we can quickly deliver small, critical bug fixes to our users and give users the best experience possible. This is set up with a build's runtime version, platform, and channel. With these three constraints, we can make an update available to a specific group of builds. This allows us to test our changes before going to production within a deployment process. Depending on how we set up our deployment process, we can optimize for speed. We can also optimize our deployments to be as safe and bug-free as possible. The deployment possibilities are vast and can match nearly any release process you prefer. ## 使用 EAS CLI 管理分支和渠道 了解如何将分支链接到通道并使用 EAS CLI 发布更新。 EAS 更新通过将分支机构链接到渠道来工作。通道是在构建时指定的,并且存在于构建的原生代码中。分支是更新的有序列表,类似于 Git 分支,它是提交的有序列表。通过 EAS 更新,我们可以将任何通道链接到任何分支,从而使我们能够为不同的版本提供不同的更新。 ¥EAS Update works by linking *branches* to *channels*. Channels are specified at build time and exist inside a build's native code. Branches are an ordered list of updates, similar to a Git branch, which is an ordered list of commits. With EAS Update, we can link any channel to any branch, allowing us to make different updates available to different builds. 上图直观地显示了此链接。在这里,我们的构建将 "production" 通道链接到名为 "版本-1.0" 的分支。当我们准备好后,我们可以调整通道分支指针。想象一下,我们在名为 "版本-2.0" 的分支上测试并准备了更多修复程序。我们可以更新此链接,使 "版本-2.0" 分支可用于具有 "production" 通道的所有构建。 ¥The diagram above visualizes this link. Here, we have the builds with the "production" channel linked to the branch named "version-1.0". When we're ready, we can adjust the channel–branch pointer. Imagine we have more fixes tested and ready on a branch named "version-2.0". We could update this link to make the "version-2.0" branch available to all builds with the "production" channel. ## 检查项目更新的状态 ¥Inspecting the state of your project's updates ### 检查渠道 ¥Inspect channels 查看所有通道: ¥View all channels: ```sh $ eas channel:list ``` 查看特定通道: ¥View a specific channel: ```sh $ eas channel:view [channel-name] $ eas channel:view production ``` 创建通道: ¥Create a channel: ```sh $ eas channel:create [channel-name] $ eas channel:create production ``` ### 视察分行 ¥Inspect branches 查看所有分支: ¥See all branches: ```sh $ eas branch:list ``` 查看特定分支及其更新列表: ¥See a specific branch and a list of its updates: ```sh $ eas branch:view [branch-name] $ eas branch:view version-1.0 ``` ### 检查更新 ¥Inspect updates 查看具体更新: ¥View a specific update: ```sh $ eas update:view [update-group-id] $ eas update:view dbfd479f-d981-44ce-8774-f2fbcc386aa ``` ## 更改项目更新的状态 ¥Changing the state of your project's updates ### 创建新更新并发布 ¥Create a new update and publish it ```sh $ eas update --branch [branch-name] --message "..." $ eas update --branch version-1.0 --message "Fixes typo" ``` 如果你使用 Git,我们可以使用 `--auto` 标志来自动填充分支名称和消息。该标志将使用当前的 Git 分支作为分支名称,并使用最新的 Git 提交消息作为消息。 ¥If you're using Git, we can use the `--auto` flag to auto-fill the branch name and the message. This flag will use the current Git branch as the branch name and the latest Git commit message as the message. ```sh $ eas update --auto ``` ### 删除分支 ¥Delete a branch ```sh $ eas branch:delete [branch-name] $ eas branch:delete version-1.0 ``` ### 重命名分支 ¥Rename a branch 重命名分支不会断开任何通道-分支链接。如果你将名为 "production" 的通道链接到名为 "版本-1.0" 的分支,然后将名为 "版本-1.0" 的分支重命名为 "版本-1.0-新",则 "production" 通道将链接到现在重命名的分支 "版本-1.0-新"。 ¥Renaming branches do not disconnect any channel–branch links. If you had a channel named "production" linked to a branch named "version-1.0", and then you renamed the branch named "version-1.0" to "version-1.0-new", the "production" channel would be linked to the now-renamed branch "version-1.0-new". ```sh $ eas branch:rename --from [branch-name] --to [branch-name] $ eas branch:rename --from version-1.0 --to version-1.0-new ``` ### 在分支内重新发布以前的更新 ¥Republish a previous update within a branch 我们可以立即向所有用户提供先前的更新。此命令获取先前的更新并再次发布它,以便它成为分支上的最新更新。当你的用户重新打开他们的应用时,应用将看到新重新发布的更新并下载它。 ¥We can make a previous update immediately available to all users. This command takes the previous update and publishes it again so that it becomes the most current update on the branch. As your users re-open their apps, the apps will see the newly re-published update and will download it. > 重新发布类似于 Git 还原,其中正确的提交位于 Git 历史记录的顶部。 > > ¥Republish is similar to a Git reversion, where the correct commit is placed on top of the Git history. ```sh $ eas update:republish --group [update-group-id] $ eas update:republish --branch [branch-name] $ eas update:republish --group dbfd479f-d981-44ce-8774-f2fbcc386aa $ eas update:republish --branch version-1.0 ``` > 如果你不知道确切的更新组 ID,可以使用 `--branch` 标志。这会显示分支上最近更新的列表,并允许你选择要重新发布的更新组。 > > ¥If you don't know the exact update group ID, you can use the `--branch` flag. This shows a list of the recent updates on the branch and allows you to select the update group to republish. ## 运行时版本和更新 了解不同的运行时版本策略以及它们如何适合你的项目。 运行时版本是保证构建的原生代码和更新之间的兼容性的属性。当项目被制作成构建时,构建将包含一些无法通过更新更改的原生代码。因此,更新必须与构建的原生代码兼容才能在构建上运行。 ¥Runtime versions are a property that guarantees compatibility between a build's native code and an update. When a project is made into a build, the build will contain some native code that cannot be changed with an update. Therefore, an update must be compatible with a build's native code to run on the build. 为了说明构建和更新如何交互,请看下图: ¥To illustrate how builds and updates interact, take a look at the following diagram: 构建可以被认为是两层:内置于应用二进制文件中的原生层和可与其他兼容更新交换的更新层。这种分离允许我们将错误修复发送到构建,只要错误修复的更新可以在构建内的原生层上运行。`"runtimeVersion"` 属性使我们能够保证更新与特定构建的原生代码兼容。 ¥Builds can be thought of as two layers: a native layer that's built into the app's binary, and an update layer, that is swappable with other compatible updates. This separation allows us to ship bug fixes to builds as long as the update with the bug fix can run on the native layer inside the build. The `"runtimeVersion"` property allows us to guarantee that an update is compatible with a specific build's native code. 由于更新必须与构建的原生代码兼容,因此每次更新原生代码时,我们都需要在发布更新之前进行新的构建。一些开发者仅在升级到新的 Expo SDK 时更新原生代码,而其他开发者可能会在构建之间或以其他时间间隔升级原生代码。以下是对可能适合你的项目的不同情况和配置的说明。 ¥Since updates must be compatible with a build's native code, any time native code is updated, we're required to make a new build before publishing an update. Some developers only update native code when upgrading to a new Expo SDK, while others may upgrade native code between builds or at other intervals. Below is an explanation of different situations and configurations that may suite your project. ## 设置 `"runtimeVersion"` ¥Setting `"runtimeVersion"` 为了在构建和更新之间更轻松地管理 `"runtimeVersion"` 属性,我们创建了从项目中已存在的另一条信息中派生运行时版本的策略。如果这些策略与项目的开发流程不匹配,还可以选择手动设置 `"runtimeVersion"`。 ¥To make managing the `"runtimeVersion"` property easier between builds and updates, we've created policies that derive the runtime version from another piece of information already present in your project. If these policies do not match the development flow of a project, there's also an option to set the `"runtimeVersion"` manually. ### 服务器组件 ¥Runtime version policies 可用的策略记录在 [`expo-updates` 库文档](/versions/latest/sdk/updates/#automatic-configuration-using-runtime-version-policies) 中。 ¥The available policies are documented in the [`expo-updates` library documentation](/versions/latest/sdk/updates/#automatic-configuration-using-runtime-version-policies). ### 自定义运行时版本 ¥Custom runtime version 你还可以设置满足 [运行时版本格式要求](/versions/latest/config/app/#runtimeversion) 的自定义运行时版本: ¥You can also set a custom runtime version that meets the [runtime version formatting requirements](/versions/latest/config/app/#runtimeversion): ```json { "expo": { "runtimeVersion": "1.0.0" } } ``` 对于想要手动管理运行时版本(与项目应用配置中存在的任何其他版本号分开)的开发者来说,此选项非常有用。它使开发者可以完全控制哪些更新与哪些版本兼容。 ¥This option is good for developers who want to manage the runtime version manually, separately from any other version numbers present in a project's app config. It gives the developer complete control over which updates are compatible with which builds. ### 平台特定运行时版本 ¥Platform-specific runtime version 你还可以为每个平台设置运行时版本,例如 ¥You can also set runtime version per-platform, for example ```json { "expo": { "android": { "runtimeVersion": "1.0.0" } } } ``` 或者: ¥Or: ```json { "expo": { "android": { "runtimeVersion": { "policy": "appVersion" } } } } ``` 当同时设置顶层运行时和特定于平台的运行时时,特定于平台的运行时优先。 ¥When both a top level runtime and a platform specific runtime are set, the platform specific one takes precedence. ## 避免不兼容的更新 ¥Avoiding incompatible updates 发布更新时可能出现的主要问题是更新可能依赖于其运行的版本不支持的原生代码。例如,假设我们使用 `"1.0.0"` 的运行时版本进行了构建。然后,我们将该版本提交给应用商店并向公众发布。 ¥The main issue that can arise when publishing updates is that the update could rely on native code that the build it's running on does not support. For instance, imagine we made a build with a runtime version of `"1.0.0"`. Then, we submitted that build to the app stores and released it to the public. 后来,假设我们开发了一个依赖于新安装的原生库的更新,例如 `expo-camera` 库,并且我们没有更新 `"runtimeVersion"` 属性,因此它仍然是 `"1.0.0"`。如果我们发布更新,使用 `"runtimeVersion"` 或 `"1.0.0"` 的构建会认为具有相同运行时版本的传入更新是兼容的,并且会尝试加载更新。由于更新会调用构建中不存在的代码,`expo-updates` 可能会检测到错误并尝试回滚到之前正常工作的更新 ([了解更多关于错误恢复行为的信息](/eas-update/error-recovery/))。 ¥Later on, imagine that we developed an update that relied on a newly installed native library, like the `expo-camera` library, and we did not update the `"runtimeVersion"` property, so that it is still `"1.0.0"`. If we published an update, the builds with the `"runtimeVersion"` of `"1.0.0"` would think the incoming update with the same runtime version was compatible and it would attempt to load the update. Since the update would make calls to code that does not exist inside the build, `expo-updates` may detect an error and attempt to roll back to the previously working update ([learn more about error recovery behavior](/eas-update/error-recovery/)). 以下是一些避免部署与构建原生代码不兼容的更新的策略。 ¥The following are some strategies to avoid deploying updates that are incompatible with a build's native code. ### 使用运行时版本策略,在原生代码更新时自动更新运行时版本 ¥Use a runtime version policy that automatically updates the runtime version when native code is updated `"appVersion"` 策略会在应用版本升级时同步更新运行时版本,但如果你在更改原生运行时时忘记更新应用版本,则会导致运行时版本不匹配。如果你想以更频繁地创建版本为代价,尽可能减少不兼容的更新,那么你可以使用 `"fingerprint"` 策略。每当发生任何可能影响原生运行时 ([了解更多关于指纹识别的信息](/versions/latest/sdk/fingerprint/)) 的更改时,这将递增运行时版本号。 ¥The `"appVersion"` policy will increment the runtime version whenever the app version is incremented, but if you forget to bump the app version when changing the native runtime, then you'll have a runtime version mismatch. If you want to make incompatible updates extremely unlikely, at the cost of making it necessary to create builds more often, then you can use the `"fingerprint"` policy. This will increment the runtime version whenever anything that may impact the native runtime changes ([learn more about fingerprinting](/versions/latest/sdk/fingerprint/)). ### 手动增加运行时版本 ¥Manually increment the runtime version 每当安装或更新原生代码时,[手动增加 `"runtimeVersion"` 属性](/eas-update/runtime-versions/#custom-runtime-version) 都会在项目的 [应用配置](/workflow/configuration/) 中生效。 ¥Whenever installing or updating native code, [manually increment the `"runtimeVersion"` property](/eas-update/runtime-versions/#custom-runtime-version) in the project's [app config](/workflow/configuration/). ### 逐步推出更新 ¥Roll out the update gradually 如果你不确定新更新的影响,可以先将其推广到一小部分用户。使用 [rollouts](/eas-update/rollouts/) 将更新发布给一小部分用户,并在 EAS 控制面板上监控更新的错误率。如果你发现错误率很高,请取消部署。如果你已经完全部署了 [回滚](/eas-update/rollbacks/)。 ¥If you're not sure about the impact of a new update, you can roll it out to a small group of users first. Use [rollouts](/eas-update/rollouts/) to publish the update to a small percentage of users and monitor the error rate for the update on the EAS dashboard. If you are noticing high error rates, then cancel the rollout. If you already rolled it out fully, then [roll it back](/eas-update/rollbacks/). ### 手动与较小用户组验证更新 ¥Manually verify updates with a smaller group of users 部署到生产环境时,请创建一个使用相同运行时但指向不同渠道的预览构建。在将这些更新发布到生产环境之前,请先在这些版本上测试你的更新(更多信息请参阅 [部署指南](/eas-update/deployment/#deploying-previews))。或者,你可以通过在运行时覆盖更新参数 ([了解更多](/eas-update/override/)),选择让应用的某些用户接收更新。 ¥When you deploy to production, create a preview build that uses the same runtime but points to a different channel. Test your updates on those builds before promoting them to production (learn more about this in the [deployment guide](/eas-update/deployment/#deploying-previews)). Alternatively, you can opt-in certain users of your app to receive the update by overriding update parameters at runtime ([learn more](/eas-update/override/)). # 故障排除 ## EAS 更新调试 了解如何使用基本调试技术修复更新问题。 本指南展示了如何验证我们的配置,以便我们可以找到问题的根源,例如应用未显示已发布的更新。在任何给定时间告知应用的当前状态非常重要,因此 EAS 更新是在构建时考虑到这一点的。一旦我们知道哪些更新正在哪些版本上运行,我们就可以进行更改,以便我们的应用处于我们期望的状态。 ¥This guide shows how to verify our configuration so that we can find the source of problems like an app not showing a published update. It's important to tell the current state of our app at any given time, so EAS Update was built with this in mind. Once we know which updates are running on which builds, we can make changes so that our apps are in the state we expect. Video Tutorial: [How to debug EAS Update](https://www.youtube.com/watch?v=m9PLTr3t3S4) > 如果我们不使用 EAS Build,我们的部署页面将为空。请按照 [无需 EAS Build 的调试配置](/eas-update/debug-advanced/#configuration-without-eas-build) 上的指南进行操作。 > > ¥If we are not using EAS Build, our Deployments page will be empty. Follow the guide on [debugging configuration without EAS Build](/eas-update/debug-advanced/#configuration-without-eas-build) instead. ## 进入部署页面 ¥Go to the Deployments page EAS 网站有一个 [部署页面](https://expo.dev/accounts/\[account]/projects/\[project]/deployments),显示我们应用的当前状态。术语“部署”是指一组构建及其相应的更新。如果我们使用 EAS 进行了构建和更新,我们可以在网站上的“部署”选项卡中查看项目的状态。 ¥The EAS website has a [Deployments page](https://expo.dev/accounts/\[account]/projects/\[project]/deployments) that shows the current state of our app. The term *deployment* refers to a group of builds and their corresponding updates. If we've made builds and updates with EAS, we can see our project's status on the website in the Deployments tab. ## 常见问题 ¥Common problems 以下部分介绍常见问题以及如何解决这些问题。下图显示了 EAS 更新的工作原理以及在查找问题根本原因时可用于检查的点。在接下来的部分中,我们将检查和验证这些点以及更多内容。 ¥The following section describes common problems and how to fix them. Below is a diagram of how EAS Update works and the spots that are useful to inspect when finding the root cause of an issue. In the following sections, we'll inspect and verify these spots and more. ### 意想不到的通道 ¥Unexpected channel 如果部署通道是意外的,则意味着我们的构建不是使用正确的通道构建的。要解决此问题,请 [配置我们的通道](/#configure-channel) 并重建我们的应用。 ¥If the deployment channel is unexpected, it means our build was not built with the correct channel. To fix this, [configure our channel](/#configure-channel) and rebuild our app. ### 意外的运行时版本 ¥Unexpected runtime version 如果部署运行时版本是意外的,则意味着我们的构建不是使用正确的运行时版本构建的。要解决此问题,请 [配置我们的运行时版本](/#configure-runtime-version) 并重建我们的应用。 ¥If the deployment runtime version is unexpected, it means our build was not built with the correct runtime version. To fix this, [configure our runtime version](/#configure-runtime-version) and rebuild our app. ### 意想不到的分支 ¥Unexpected branch 如果部署有意外分支,我们需要 [将我们的通道映射到正确的分支](/#map-channel-to-branch)。 ¥If the deployment has an unexpected branch, we need to [map our channel to the correct branch](/#map-channel-to-branch). ### 缺少更新 ¥Missing updates 显示的部署没有任何更新。为了解决这个问题,[向分支发布更新](/#publish-update).如果更新已发布,请检查 [更新页面](https://expo.dev/accounts/\[account]/projects/\[project]/updates) 以确保它与我们构建的运行时版本匹配。 ¥The displayed deployment does not have any updates. To fix this, [publish an update to the branch](/#publish-update). If an update was already published, check the [Updates page](https://expo.dev/accounts/\[account]/projects/\[project]/updates) to make sure it matches the runtime version of our build. ### 缺少分支 ¥Missing branch 显示的部署具有正确的通道,但未链接到分支。为了解决这个问题,[将通道映射到正确的分支](/#map-channel-to-branch). ¥The displayed deployment has the correct channel, but it is not linked to a branch. To fix this, [map the channel to the correct branch](/#map-channel-to-branch). ### 缺少部署 ¥Missing deployment 如果未显示我们的部署,则意味着我们的构建未正确配置 EAS 更新。要解决此问题,请执行 [配置我们的通道](/#configure-channel)、[配置我们的运行时版本](/#configure-runtime-version) 并验证我们的 [通用配置](/eas-update/debug-advanced/#verifying-app-configuration)。进行这些更改后,我们需要重建我们的应用。 ¥If our deployment is not displayed, it means our build is not configured properly for EAS Update. To fix this, [configure our channel](/#configure-channel), [configure our runtime version](/#configure-runtime-version) and verify our [general configuration](/eas-update/debug-advanced/#verifying-app-configuration). We'll need to rebuild our app after making these changes. ### 更新崩溃时自动回滚 ¥Automatic roll back when an update crashes 如果“部署”页面上的所有内容看起来都正确,但你的应用仍显示上一个更新或嵌入构建的代码,则你的新更新的代码可能正在崩溃。当应用下载并应用新更新后,此新更新在根组件渲染之前崩溃时,可能会发生这种情况。 ¥If everything looks correct on the Deployments page, but your app still shows the previous update or the code embedded with the build, your new update's code may be crashing. This can happen when this new update crashes before the root component renders after the app downloads and applies the new update. 如果 EAS Update 检测到新更新在启动后不久崩溃,则会自动回滚到上一个更新。请参阅 [EAS Update 如何检测崩溃并回滚到以前的工作版本](/eas-update/error-recovery/#explaining-the-error-recovery-flow) 了解更多信息。 ¥EAS Update is designed to automatically roll back to the previous update if it detects that a new update crashed shortly after launch. See [how EAS Update detects crashes and rolls back to a previous working version](/eas-update/error-recovery/#explaining-the-error-recovery-flow) for more information. 要诊断导致更新崩溃的错误: ¥To diagnose the error causing the update crash: * 请参阅 [运行时问题故障排除指南](/debugging/runtime-issues/) 以应用策略来识别错误。 ¥See the [Troubleshooting guide on runtime issues](/debugging/runtime-issues/) to apply a strategy to identify the error. * 识别错误后,发布修复崩溃的新更新以解决问题。 ¥After identifying the error, publish a new update that fixes the crash to resolve the issue. 新更新不起作用但嵌入代码起作用的常见原因是缺少环境变量。请参阅 [环境变量如何与 EAS Update 配合使用](/eas-update/environment-variables/) 了解更多信息。 ¥A common reason a new update does not work but embedded code does is due to a missing environment variable. See [how environment variables work with EAS Update](/eas-update/environment-variables/) for more information. ### 无法加载所有资源 ¥Failed to load all assets 如果你的用户看到 "无法加载所有资源" 错误,则表示应用能够下载清单,但无法下载运行更新所需的所有资源。如果原始构建中不存在资源,则需要下载该资源。常见的错误原因包括: ¥If your users are seeing a "Failed to load all assets" error, it means that the app was able to download the manifest but could not download all of the assets required for the update to run. An asset will need to be downloaded if it is not present in the original build. Common reasons for error are: * 更新中添加了许多大型资源,由于网络问题,应用无法全部下载。 ¥Many large assets were added to the update, and the app is unable to download them all due to network issues. * 用户的网络连接状况不佳。 ¥The user has poor internet connection. * 用户所在的国家/地区屏蔽或限制了 Cloudflare IP 地址,而 EAS Update 会使用这些 IP 地址来提供资源。 ¥The user is in a country that blocks or throttles Cloudflare IP addresses, which are used by EAS Update to serve assets. 要诊断资源加载问题: ¥To diagnose the asset loading issue: * 通过检查 [资源列表](/eas-update/debug/#viewing-all-assets-included-in-an-update) 来验证用户下载了哪些资源及其大小。 ¥Verify which assets are downloaded by users and their sizes by inspecting the [asset list](/eas-update/debug/#viewing-all-assets-included-in-an-update). * 在你自己的设备上重现问题,并检查原生层出现的 `expo-updates` [日志条目](/versions/latest/sdk/updates/#updatesreadlogentriesasyncmaxage)。 ¥Reproduce the issue on your own device and examine the `expo-updates` [logs entries](/versions/latest/sdk/updates/#updatesreadlogentriesasyncmaxage) surfaced from the native layer. * 如果你已在 Sentry 等服务中记录了此错误,请检查遇到此错误的用户的 IP 地址,并确认该用户不在已知会阻止或限制 Cloudflare IP 地址的国家/地区。 ¥If you've logged this error in a service like Sentry, inspect the IP address of the user that encountered the error and verify it is not in a country known to block or throttle Cloudflare IP addresses. ## 解决方案 ¥Solutions ### 配置通道 ¥Configure channel 要验证构建是否具有特定通道,请确保 eas.json 中的构建配置文件具有通道属性: ¥To verify that a build has a specific channel, make sure our build profile in **eas.json** has a channel property: ```json eas.json { "build": { "preview": { "distribution": "internal", "channel": "preview" }, "production": { "channel": "production" } } } ``` 然后,我们可以运行 `eas build --profile preview` 这样的命令来创建具有名为 "preview" 的通道的构建。 ¥Then, we can run a command like `eas build --profile preview` to create a build with a channel named "preview". ### 配置运行时版本 ¥Configure runtime version 为了验证我们的运行时版本,我们确保我们的应用配置(app.json/app.config.js)具有 `runtimeVersion` 属性: ¥To verify our runtime version, we make sure our app config (**app.json**/**app.config.js**) has a `runtimeVersion` property: ```json app.json { "expo": { "runtimeVersion": { "policy": "sdkVersion" } } } ``` 默认情况下,它是 `{ "policy": "sdkVersion" }`,但我们可以将运行时间更改为 [使用不同的策略或特定版本](/eas-update/runtime-versions)。然后,我们可以运行 `eas build --profile preview` 这样的命令来创建具有我们期望的运行时版本的构建。 ¥By default, it is `{ "policy": "sdkVersion" }`, but we can change our runtime to [use a different policy or a specific version](/eas-update/runtime-versions). Then, we can run a command like `eas build --profile preview` to create a build with the runtime version we expect. ### 将通道映射到分支 ¥Map channel to branch 如果通道未映射到我们期望的分支,我们可以使用以下命令更改链接: ¥If the channel is not mapped to the branch we expect, we can change the link with: ```sh $ eas channel:edit production --branch release-1.0 ``` 如果我们的分支没有列出,我们可以使用 `eas branch:create` 创建一个新分支。 ¥If our branch is not listed, we can create a new branch with `eas branch:create`. ### 发布更新 ¥Publish update 要创建并发布更新,我们可以运行以下命令: ¥To create and publish an update, we can run the following command: ```sh $ eas update ``` 发布后,输出将显示分支和运行时版本。此信息可以帮助我们验证是否正在使用预期的配置创建更新。 ¥After publishing, the output will display the branch and the runtime version. This information can help us verify that we're creating an update with the configuration we expect. ## 一般策略 ¥General strategies 在使用本指南中提到的更具体的策略之前,请先尝试这些策略。 ¥Try these strategies before using the more specific ones mentioned in this guide. ### 使用 `expo-dev-client` ¥Use `expo-dev-client` 创建 [我们构建的开发版本](/eas-update/expo-dev-client)。它将帮助我们预览有问题的版本中已发布的更新。 ¥Create a [development version of our build](/eas-update/expo-dev-client). It will help us preview published updates inside a problematic build. ### 应用内调试 ¥In-app debugging 一旦应用已经运行,`expo-updates` 库就会导出各种函数来与更新交互。在某些情况下,调用获取更新并查看错误消息可以帮助我们缩小根本原因的范围。我们可以对项目进行模拟器构建,并手动检查更新是否可用或者获取更新时是否出现错误。 ¥The `expo-updates` library exports a variety of functions to interact with updates once the app is already running. In certain cases, making a call to fetch an update and seeing an error message can help us narrow down the root cause. We can make a simulator build of the project and manually check to see if updates are available or if there are errors when fetching updates. * 打印 [Update.Constants](/versions/latest/sdk/updates/#constants) 以验证我们的配置。 ¥Print the [Update.Constants](/versions/latest/sdk/updates/#constants) to verify our configuration. * [检查日志条目](/versions/latest/sdk/updates/#updatesreadlogentriesasyncmaxage) 从原生层浮出水面。 ¥[Examine log entries](/versions/latest/sdk/updates/#updatesreadlogentriesasyncmaxage) surfaced from the native layer. * 获取并 [手动加载更新](/versions/latest/sdk/updates/#check-for-updates-manually)。 ¥Fetch and [load updates manually](/versions/latest/sdk/updates/#check-for-updates-manually). ## 配置问题 ¥Configuration issues 尽管遵循了 [基本指南](/eas-update/debug),我们的应用仍然没有收到预期的更新。 ¥Our app is still not receiving the expected update despite following the [basic guide](/eas-update/debug). ### `expo-updates` 配置 ¥`expo-updates` configuration `expo-updates` 库在终端用户的应用内运行,并向更新服务器发出请求以获取最新更新。 ¥The `expo-updates` library runs inside an end-user's app and makes requests to an update server to get the latest update. #### 验证应用配置 ¥Verifying app configuration 当我们设置 EAS 更新时,我们可能运行 `eas update:configure` 来配置 expo-updates 以与 EAS 更新配合使用。此命令更改我们的应用配置 (app.json/app.config.js)。以下是我们期望看到的字段: ¥When we set up EAS Update, we likely ran `eas update:configure` to configure expo-updates to work with EAS Update. This command makes changes to our app config (**app.json**/**app.config.js**). Here are the fields we'd expect to see: * 应设置 `runtimeVersion`。默认为 `{ "policy": "sdkVersion" }`。如果我们的项目有 android 和 ios 目录,我们必须手动设置 `runtimeVersion`。 ¥`runtimeVersion` should be set. By default, it is `{ "policy": "sdkVersion" }`. If our project has **android** and **ios** directories, we'll have to set the `runtimeVersion` manually. * `updates.url` 应该是类似于 `https://u.expo.dev/your-project-id` 的值,其中 `your-project-id` 与我们项目的 ID 匹配。我们可以在 [我们的网站](https://expo.dev/accounts/\[account]/projects/\[project]) 上看到这个 ID。 ¥`updates.url` should be a value like `https://u.expo.dev/your-project-id`, where `your-project-id` matches the ID of our project. We can see this ID on [our website](https://expo.dev/accounts/\[account]/projects/\[project]). * `updates.enabled` 不应该是 `false`。如果不指定则默认为 `true`。 ¥`updates.enabled` should not be `false`. It's `true` by default if it is not specified. 最后,确保 `expo-updates` 包含在 package.json 中。如果不是,请运行: ¥Finally, make sure that `expo-updates` is included in **package.json**. If it's not, run: ```sh $ npx expo install expo-updates ``` #### 预构建后检查 expo-updates 配置 ¥Inspecting expo-updates configuration after prebuild 每当我们运行 `eas build` 时,都会在 EAS 服务器上的项目上运行 `npx expo prebuild` 命令,以解压包含原生文件的 android 和 ios 目录。这使得 EAS Build 可以构建任何项目,无论它是否包含原生文件。 ¥Whenever we run `eas build`, the `npx expo prebuild` command is run on our project on EAS servers to unpack the **android** and **ios** directories that contain native files. This makes it so EAS Build can build any project, whether it includes the native files or not. 如果我们的项目没有 android 或 ios 目录,我们可以提交任何现有更改,然后运行 `npx expo prebuild` 来检查 EAS Build 将执行的项目状态。运行后,查找以下文件:android/app/src/main/AndroidManifest.xml 和 ios/your-project-name/Supporting/Expo.plist。 ¥If our project does not have **android** or **ios** directories, we can make commit any existing changes, then run `npx expo prebuild` to inspect the project state that EAS Build will act on. After running this, look for the following files: **android/app/src/main/AndroidManifest.xml** and **ios/your-project-name/Supporting/Expo.plist**. 在每个中,我们期望看到 EAS 更新 URL 和运行时版本的配置。以下是我们希望在每个文件中看到的属性: ¥In each, we expect to see configuration for the EAS Update URL and the runtime version. Here are the properties we'd expect to see in each file: ```xml AndroidManifest.xml ``` ```xml Expo.plist EXUpdatesRuntimeVersion your-runtime-version-here EXUpdatesURL https://u.expo.dev/your-project-id-here ``` ### 无需 EAS 构建的配置 ¥Configuration without EAS Build 如果我们不使用 EAS Build,本节将逐步调试项目中 EAS Update 的状态。我们需要查看系统中的多个点。下图显示了 EAS 更新的工作原理以及在查找问题根本原因时可用于检查的点。在接下来的部分中,我们将检查并验证这些点以及更多内容。 ¥If we aren't using EAS Build, this section will walk through debugging the state of EAS Update in our project. We'll need to look at multiple spots in the system. Below is a diagram of how EAS Update works and the spots that are useful to inspect when finding the root cause of an issue. In the sections following, we'll inspect and verify these spots and more. #### 验证构建配置 ¥Verify build configuration 按照 [本地构建指南](/eas-update/standalone-service/) 配置我们应用的通道和运行时版本。我们还需要确保 [通用配置](/eas-update/debug-advanced/#expo-updates-configuration) 正确。 ¥Follow the [Building Locally guide](/eas-update/standalone-service/) to configure our app's channel and runtime version. We'll also need to make sure our [general configuration](/eas-update/debug-advanced/#expo-updates-configuration) is correct. #### 验证通道 ¥Verify the channel 构建有一个名为 `channel` 的属性,EAS 更新使用该属性链接到分支。通常会为多个特定于平台的构建提供一个通道。例如,我们可能有一个 Android 版本和一个 iOS 版本,两者都具有名为 `"production"` 的通道。 ¥Builds have a property named `channel`, which EAS Update uses to link to a branch. A channel is often given to multiple platform-specific builds. For instance, we might have an Android build and an iOS build, both with a channel named `"production"`. 一旦构建有了通道名称,我们就可以通过检查 [通道页面](https://expo.dev/accounts/\[account]/projects/\[project]/channels).conf 文件来确保 EAS 服务器知道它。 ¥Once a build has a channel name, we can make sure that EAS servers know about it by checking the [Channels page](https://expo.dev/accounts/\[account]/projects/\[project]/channels). 我们希望页面显示与我们的构建相同的通道名称。如果不存在,我们可以使用以下命令在 EAS 服务器上创建通道: ¥We'd expect the page to display the same channel name that our build has. If it's not there, we can create the channel on EAS servers with: ```sh $ eas channel:create production ``` #### 验证通道/分支映射 ¥Verify the channel/branch mapping 开发者在通道和分支之间定义了一个链接。当通道和分支链接时,具有通道的应用将获得链接分支上的最新兼容更新。 ¥There is a link that is defined by the developer between a channel and a branch. When a channel and branch are linked, an app with a channel will get the most recent compatible update on the linked branch. [通道页面](https://expo.dev/accounts/\[account]/projects/\[project]/channels) 将显示通道到分支映射(如果存在)。 ¥The [Channels page](https://expo.dev/accounts/\[account]/projects/\[project]/channels) will display the channel to branch mapping if it exists. 如果通道未链接到我们期望的分支,我们可以使用以下命令更改链接: ¥If the channel is not linked to the branch we expect, we can change the link with: ```sh $ eas channel:edit production --branch release-1.0 ``` #### 验证更新 ¥Verify the update 每个分支都包含一个更新列表。当构建调用更新时,我们会找到构建的通道,然后找到链接到该通道的分支。一旦找到分支,EAS 将返回该分支上的最新兼容更新。当构建和更新共享相同的运行时版本和平台时,它们是兼容的。 ¥Every branch contains a list of updates. When a build makes a call for an update, we find the channel of the build, then the branch linked to that channel. Once the branch is found, EAS will return the most recent compatible update on that branch. A build and an update are compatible when they share the same runtime version and platform. 要检查分支上有哪些更新,我们可以转到 [分行页面](https://expo.dev/accounts/\[account]/projects/\[project]/branches) 并选择我们感兴趣的分支。 ¥To inspect which updates are on a branch, we can go to the [Branches page](https://expo.dev/accounts/\[account]/projects/\[project]/branches) and choose our branch of interest. 分支详细信息页面将向我们显示更新列表及其运行时版本和平台。从这个列表中,我们应该能够通过将构建的运行时版本和平台与更新的运行时版本和平台相匹配来找出应该应用于给定构建的更新。兼容的最新更新将可供构建下载和执行。 ¥The Branch Detail page will show us a list of updates and their runtime versions and platforms. From this list, we should be able to figure out which update should apply to a given build, by matching the build's runtime version and platform to update's runtime version and platform. The most recent update that is compatible will be available for a build to download and execute. ## 调试 EAS 更新 ¥Debugging EAS Update 验证 `expo-updates` 和 EAS 更新配置后,我们可以继续调试我们的项目如何与更新交互。 ¥After verifying `expo-updates` and EAS Update configurations, we can move on to debugging how our project is interacting with updates. ### 应用内调试 ¥In-app debugging 一旦应用已经运行,`expo-updates` 库就会导出各种函数来与更新交互。在某些情况下,调用获取更新并查看错误消息可以帮助我们缩小根本原因的范围。我们可以对项目进行模拟器构建,并手动检查更新是否可用或者获取更新时是否出现错误。请参阅 [手动检查更新](/versions/latest/sdk/updates/#use-expo-updates-with-a-custom-server) 的代码示例。 ¥The `expo-updates` library exports a variety of functions to interact with updates once the app is already running. In certain cases, making a call to fetch an update and seeing an error message can help us narrow down the root cause. We can make a simulator build of the project and manually check to see if updates are available or if there are errors when fetching updates. See the code example to [check for updates manually](/versions/latest/sdk/updates/#use-expo-updates-with-a-custom-server). ### 手动检查构建 ¥Inspecting a build manually 将项目构建到应用中时,可能有多个步骤会改变 `npx expo prebuild` 的输出。进行构建后,可以打开构建的内容并检查原生文件以查看其最终配置。 ¥When building a project into an app, there can be multiple steps that alter the output of `npx expo prebuild`. After making a build, it is possible to open the build's contents and inspect native files to see its final configuration. 以下是检查 macOS 上的 iOS 模拟器版本的步骤: ¥Here are the steps for inspecting an iOS Simulator build on macOS: 1. 使用 EAS Build 创建应用的 iOS 模拟器版本。这是通过将 `"ios": { "simulator": true }` 添加到构建配置文件来完成的。 ¥Create an iOS Simulator build of the app using EAS Build. This is done by adding `"ios": { "simulator": true }` to a build profile. 2. 构建完成后,下载结果并解压缩。 ¥Once the build is finished, download the result and unzip it. 3. 然后,右键单击该应用并选择 "显示封装内容"。 ¥Then, right click on the app and select "Show Package Contents". 4. 从那里,我们可以检查 Expo.plist 文件。 ¥From there, we can inspect the **Expo.plist** file. 在 Expo.plist 文件中,我们期望看到以下配置: ¥Inside the **Expo.plist** file, we expect to see the following configurations: ```xml Expo.plist EXUpdatesRequestHeaders expo-channel-name your-channel-name EXUpdatesRuntimeVersion your-runtime-version EXUpdatesURL https://u.expo.dev/your-project-id ``` ### 手动检查清单 ¥Inspecting manifests manually 当使用 EAS 更新发布更新时,我们会根据终端用户应用的请求创建一个清单。清单包含加载更新所需的资源和版本等信息。我们可以通过访问浏览器中的特定 URL 或使用 `curl`.txt 来检查清单。 ¥When an update is published with EAS Update, we create a manifest that end-user app's request. The manifest has information like which assets and versions are needed for an update to load. We can inspect the manifest by going to a specific URL in a browser or by using `curl`. 在我们项目的应用配置(app.json/app.config.json)中,我们可以获取的 URL 位于 `updates.url` 下。 ¥Inside our project's app config (**app.json**/**app.config.json**), the URL we can GET is under `updates.url`. 这个 `url` 是 EAS 的“[https://u.expo.dev](https://u.expo.dev)”域,后面是 EAS 服务器上的项目 ID。如果我们直接访问 URL,我们会看到有关缺少标头的错误。我们可以通过向 URL 添加三个查询参数来查看清单:`runtime-version`、`channel-name` 和 `platform`。如果我们发布运行时版本为 `1.0.0`、通道为 `production`、平台为 `android` 的更新,我们可以访问的完整 URL 将类似于以下内容: ¥This `url` is EAS' "[https://u.expo.dev](https://u.expo.dev)" domain, followed by the project's ID on EAS servers. If we go to the URL directly, we'll see an error about missing a header. We can view a manifest by adding three query parameters to the URL: `runtime-version`, `channel-name`, and `platform`. If we published an update with a runtime version of `1.0.0`, a channel of `production` and a platform of `android`, the full URL we could visit would be similar to this: ```text https://u.expo.dev/your-project-id?runtime-version=1.0.0&channel-name=production&platform=android ``` ### 查看网络请求 ¥Viewing network requests 确定问题根本原因的另一种方法是查看应用向 EAS 服务器发出的网络请求,然后查看响应。我们建议使用 [代理人](https://proxyman.io/) 或 [Charles 代理](https://www.charlesproxy.com/) 等程序来监视来自我们应用的网络请求。 ¥Another way to identify the root cause of an issue is to look at the network requests that the app is making to EAS servers, then viewing the responses. We recommend using a program like [Proxyman](https://proxyman.io/) or [Charles Proxy](https://www.charlesproxy.com/) to watch network requests from our app. 对于任一程序,我们都需要按照其说明安装 SSL 证书,以便该程序可以解码 HTTPS 请求。一旦在模拟器或实际设备上设置完毕,我们就可以打开我们的应用并监视请求。 ¥With either program, we'll need to follow their instructions for installing an SSL certificate, so that the program can decode HTTPS requests. Once that's set up in a simulator or on an actual device, we can open our app and watch requests. 我们感兴趣的请求来自 [https://u.expo.dev](https://u.expo.dev) 和 [https://assets.eascdn.net](https://assets.eascdn.net)。来自 [https://u.expo.dev](https://u.expo.dev) 的响应将包含更新清单,其中指定应用需要获取哪些资源来运行更新。来自 [https://assets.eascdn.net](https://assets.eascdn.net) 的响应将包含运行更新所需的资源,例如图片、字体文件等。 ¥The requests we're interested in are from [https://u.expo.dev](https://u.expo.dev) and [https://assets.eascdn.net](https://assets.eascdn.net). Responses from [https://u.expo.dev](https://u.expo.dev) will contain an update manifest, which specifies which assets the app will need to fetch to run the update. Responses from [https://assets.eascdn.net](https://assets.eascdn.net) will contain assets, like images, font files, and so on, that are required for the update to run. 在检查对 [https://u.expo.dev](https://u.expo.dev) 的请求时,我们可以查找以下请求标头: ¥When inspecting the request to [https://u.expo.dev](https://u.expo.dev), we can look for the following request headers: * `Expo-Runtime-Version`:这应该成为我们构建和更新的运行时版本。 ¥`Expo-Runtime-Version`: this should make the runtime version we made our build and update with. * `expo-channel-name`:这应该是 eas.json 构建配置文件中指定的通道名称。 ¥`expo-channel-name`: this should be the channel name specified in the **eas.json** build profile. * `Expo-Platform`:这应该是 "android" 或 "ios"。 ¥`Expo-Platform`: this should be either "android" or "ios". 对于所有请求,我们期望看到 `200` 响应代码,或者 `304`(如果没有任何更改)。 ¥As for all requests, we expect to see either `200` response codes, or `304` if nothing has changed. 下面是显示成功更新清单请求的屏幕截图: ¥Below is a screenshot showing the request of a successful update manifest request: ## 运行时问题 ¥Runtime issues 我们能够加载预期的更新,但我们的项目显示出意外的行为。 ¥We are able to load the expected update but our project is displaying unexpected behavior. ### 通过 expo-updates 加载应用时调试原生代码 ¥Debugging of native code while loading the app through expo-updates 默认情况下,我们需要为 `expo-updates` 进行发布构建以启用并加载更新,而不是从开发服务器读取。这是因为调试构建的行为类似于普通的 React Native 项目调试构建。 ¥By default, we need to make a release build for `expo-updates` to be enabled and to load updates rather than reading from a development server. This is because debug builds behave like normal React Native project debug builds. 为了更轻松地在更接近生产的环境中测试和调试原生代码,请按照以下步骤创建启用了 `expo-updates` 的应用的调试版本。 ¥To make it easier to test and debug native code in an environment that is closer to production, follow the steps below to create a debug build of the app with `expo-updates` enabled. 我们还在使用 Android Studio 或 Xcode 的本地开发环境中提供 [快速尝试 EAS 更新的分步指南](/eas-update/standalone-service/),以及应用的发布或调试版本。 ¥We also provide a [step-by-step guide to try out EAS Update quickly](/eas-update/standalone-service/) in a local development environment using Android Studio or Xcode, with either release or debug builds of the app. #### Android 本地构建 ¥Android local builds * 设置调试环境变量:`export EX_UPDATES_NATIVE_DEBUG=1` ¥Set the debug environment variable: `export EX_UPDATES_NATIVE_DEBUG=1` * [确保在 AndroidManifest.xml 中设置所需的通道](/eas-update/getting-started/#configure-the-update-channel) ¥[Ensure the desired channel is set in your **AndroidManifest.xml**](/eas-update/getting-started/#configure-the-update-channel) * 使用 Android Studio 或从命令行执行应用的 [调试构建](/debugging/runtime-issues/#native-debugging)。 ¥Execute a [debug build](/debugging/runtime-issues/#native-debugging) of the app with Android Studio or from the command line. #### iOS 本地构建 ¥iOS local builds * 设置调试环境变量:`export EX_UPDATES_NATIVE_DEBUG=1` ¥Set the debug environment variable: `export EX_UPDATES_NATIVE_DEBUG=1` * 使用 `npx pod-install` 重新安装 Pod。`expo-updates` podspec 现在检测此环境变量,并进行更改,以便绕过通常从 Metro 打包程序加载的调试代码,并且应用是使用 EXUpdates 打包包和从 EAS 加载更新所需的其他依赖构建的。 ¥Reinstall pods with `npx pod-install`. The `expo-updates` podspec now detects this environment variable, and makes changes so that the debug code that would normally load from the Metro packager is bypassed, and the app is built with the EXUpdates bundle and other dependencies needed to load updates from EAS. * [确保在我们的 Expo.plist 中设置所需的通道](/eas-update/getting-started/#configure-the-update-channel) ¥[Ensure the desired channel is set in our **Expo.plist**](/eas-update/getting-started/#configure-the-update-channel) * 修改应用 Xcode 项目文件以强制打包应用 JavaScript 以用于发布和调试版本: ¥Modify the application Xcode project file to force bundling of the application JavaScript for both release and debug builds: ```sh $ sed -i '' 's/SKIP_BUNDLING/FORCE_BUNDLING/g;' ios/.xcodeproj/project.pbxproj ``` * 使用 Xcode 或从命令行执行应用的 [调试构建](/debugging/runtime-issues/#native-debugging)。 ¥Execute a [debug build](/debugging/runtime-issues/#native-debugging) of the app with Xcode or from the command line. #### EAS 构建 ¥EAS Build 或者,我们可以使用 EAS 创建启用 `expo-updates` 的调试版本。环境变量在 eas.json 中设置,如下例所示: ¥Alternatively, we can use EAS to create a debug build where `expo-updates` is enabled. The environment variable is set in **eas.json**, as shown in the example below: ```json eas.json { "build": { "preview_debug": { "env": { "EX_UPDATES_NATIVE_DEBUG": "1" }, "android": { "distribution": "internal", "withoutCredentials": true, "gradleCommand": ":app:assembleDebug" }, "ios": { "simulator": true, "buildConfiguration": "Debug" }, "channel": "preview_debug" } } } ``` ## 发布问题 ¥Publishing issues 我们无法发布更新,或者部分更新未按预期发布。 ¥We are not able to publish an update, or parts of our update are not being published as expected. ### 检查本地最新更新 ¥Inspecting the latest update locally 当我们使用 EAS Update 发布更新时,它会在本地项目的根目录中创建一个 /dist 目录,其中包括作为更新的一部分上传的资源。 ¥When we publish an update with EAS Update, it creates a **/dist** directory in the root of our project locally, which includes the assets that were uploaded as a part of the update. ### 查看更新中包含的所有资源 ¥Viewing all assets included in an update 查看我们的更新包中包含哪些资源可能会有所帮助。我们可以从 [更新详情](https://expo.dev/accounts/\[account]/projects/\[project]/updates) 页面看到命名资源的列表: ¥It may be helpful to see which assets are included in our update bundle. We can see a list of named assets from the [Updates Detail](https://expo.dev/accounts/\[account]/projects/\[project]/updates) page: 或在本地运行: ¥Or run locally: ```sh $ npx expo export ``` ## 缓解措施 ¥Mitigation steps 一旦我们找到了问题的根本原因,我们可能需要采取各种缓解措施。最常见的问题之一是推送内部存在错误的更新。发生这种情况时,我们可以重新发布以前的更新来解决问题。 ¥Once we've found the root cause of the issue, there are various mitigation steps we might want to take. One of the most common problems is pushing an update that has a bug inside it. When this happens, we can re-publish a previous update to resolve the issue. ### 重新发布之前的更新 ¥Re-publishing a previous update 修复错误发布的最快方法是重新发布已知良好的更新。想象一下我们有一个有两个更新的分支: ¥The fastest way to "undo" a bad publish is to re-publish a known good update. Imagine we have a branch with two updates: ```sh branch: "production" updates: [ update 2 (id: xyz2) "fixes typo" // bad update update 1 (id: abc1) "updates color" // good update ] ``` 如果 "更新 2" 被证明是一个错误的更新,我们可以使用如下命令重新发布 "更新 1": ¥If "update 2" turned out to be a bad update, we can re-publish "update 1" with a command like this: ```sh $ eas update:republish --group abc1 $ eas update:republish --branch production ``` 上面的示例命令将产生一个现在如下所示的分支: ¥The example command above would result in a branch that now appears like this: ```sh branch: "production" updates: [ update 3 (id: def3) "updates color" // re-publish of update 1 (id: abc1) update 2 (id: xyz2) "fixes typo" // bad update update 1 (id: abc1) "updates color" // good update ] ``` 由于 "更新 3" 现在是 "production" 分支上的最新更新,因此将来查询更新的所有用户都将收到 "更新 3",而不是错误的更新 "更新 2"。 ¥Since "update 3" is now the most recent update on the "production" branch, all users who query for an update in the future will receive "update 3" instead of the bad update, "update 2". 虽然这将阻止所有新用户看到错误更新,但已经收到错误更新的用户将运行它,直到他们可以下载最新更新。由于移动网络并不总是能够下载最新的更新,有时用户可能会长时间运行错误的更新。查看应用的错误日志时,当用户的应用获得最新更新或构建时,看到挥之不去的长尾错误是正常的。当我们看到错误率急剧下降时,我们就知道我们解决了这个错误;然而,如果我们在许多地点和移动网络上拥有多样化的用户群,它可能不会完全消失。 ¥While this will prevent all new users from seeing the bad update, users who've already received the bad update will run it until they can download the latest update. Since mobile networks are not always able to download the most recent update, sometimes users may run a bad update for a long time. When viewing error logs for our app, it's normal to see a lingering long tail of errors as our users' apps get the most recent update or build. We'll know we solved the bug when we see the error rate decline dramatically; however, it likely will not disappear completely if we have a diverse user base across many locations and mobile networks. ## 错误恢复 了解如何在使用 expo-updates 库时利用内置错误恢复。 使用 `expo-updates` 的应用可以利用内置的错误恢复行为作为防止意外发布损坏更新的额外保护措施。 ¥Apps using `expo-updates` can take advantage of built-in error recovery behavior as an extra safeguard against accidentally publishing broken updates. 虽然我们无法充分强调在发布到生产环境之前在暂存环境中测试更新的重要性,但人类(甚至计算机)偶尔会犯错误,并且此处描述的错误恢复行为可以作为这种情况下的最后手段。 ¥While we cannot stress enough the importance of testing updates in a staging environment before publishing to production, humans (and even computers) occasionally make mistakes, and the error recovery behavior described here can serve as a last resort in such cases. > **warning** 免责声明:下面记录的行为可能会发生变化,不应依赖。在发布更新之前,请务必在类似生产的环境中仔细、彻底地测试你的代码。 > > ¥**Disclaimer:** The behavior documented below is subject to change and should not be relied upon. Always test your code carefully and thoroughly in production-like environments before publishing updates. ## 帮助!我发布了一个损坏的生产更新。我应该怎么办? ¥Help! I published a broken update to production. What should I do? 首先,不要惊慌。错误会发生;最有可能的是,一切都会好起来的。 ¥First of all, don't panic. Mistakes happen; most likely, everything will be fine. 重要的是尽快发布包含修复程序的新更新(尽管在你对修复程序 100% 有信心之前)。`expo-updates` 中的错误恢复机制将确保在大多数情况下,即使是已经下载了损坏更新的用户也应该能够获得修复。 ¥The important thing is to **publish a new update with a fix as soon as possible (though not before you are 100% confident in your fix).** The error recovery mechanism in `expo-updates` will ensure that in most cases, even users who have already downloaded the broken update should be able to get the fix. 首先要尝试的是回滚到你知道正在运行的旧更新。然而,这并不总是安全的。例如,你损坏的更新可能以非向后兼容的方式修改了持久状态(例如存储在 AsyncStorage 或设备文件系统中的数据)。在尽可能模拟终端用户设备状态的临时环境中进行测试以加载损坏的更新然后回滚非常重要。 ¥The first thing to try is rolling back to an older update that you know was working. **However, this may not always be safe;** your broken update may, for example, have modified persistent state (such as data stored in AsyncStorage or on the device's file system) in a non-backwards-compatible way. It's important to test in a staging environment that emulates an end user's device state as closely as possible to load the broken update and then roll back. 如果你可以识别可以安全回滚的旧更新,则可以使用 [EAS 仪表板](https://expo.dev/accounts/\[account]/projects/\[project]/updates) 或 [EAS 命令行接口](/eas-update/eas-cli/#republish-a-previous-update-within-a-branch) 中的 EAS 更新的 `republish` 选项来执行此操作。 ¥If you can identify an older update that is safe to roll back to, you can do so using EAS Update's `republish` option from [EAS dashboard](https://expo.dev/accounts/\[account]/projects/\[project]/updates) or [EAS CLI](/eas-update/eas-cli/#republish-a-previous-update-within-a-branch). 如果你无法识别可以安全回滚的旧更新,则需要向前修复它。虽然最好尽快推出修复程序,但你应该花时间确保修复程序可靠,并且知道即使同时下载损坏的更新的用户也应该能够下载你的修复程序。 ¥If you cannot identify an older update that is safe to roll back to, you'll need to fix it forward. While it's best to roll out a fix as quickly as possible, you should take the time to ensure your fix is solid, and know that even users who download your broken update in the meantime should be able to download your fix. 如果你想了解有关其工作原理的更多详细信息,请继续阅读。 ¥If you'd like more details about how this works, read on. ## 解释错误恢复流程 ¥Explaining the error recovery flow 错误恢复流程旨在尽可能轻量。它不是一个完整的安全网,可以保护你的终端用户免受错误结果的影响;在许多情况下,用户仍然会看到崩溃。 ¥The error recovery flow is intended to be as lightweight as possible. It is not a full safety net that protects your end users from the results of errors; in many cases, users will still see a crash. 相反,目的是通过确保在尽可能多的情况下,应用有机会防止对应用进行 "bricking" 更新(在应用检查更新之前导致启动时崩溃,使应用在卸载并重新安装之前无法使用) 下载新的更新并自行修复。 ¥Rather, the purpose is to prevent updates from "bricking" your app (causing a crash on launch before the app can check for updates, making the app unusable until uninstalled and reinstalled) by ensuring that in as many cases as possible, the app has the opportunity to download a new update and fix itself. ### 捕获错误 ¥Catching an error 如果你的应用在执行 JS 时抛出致命错误,而该错误在应用生命周期的早期阶段可能会阻止应用下载进一步的更新,则 `expo-updates` 将捕获此错误。 ¥If your app throws a fatal error when executing JS which is early enough in the app's lifecycle that it may prevent the app from being able to download further updates, `expo-updates` will catch this error. > 如果应用第一次渲染和抛出致命错误之间的时间间隔超过 10 秒,`expo-updates` 根本不会捕获此错误,并且不会触发任何错误恢复代码。因此,我们强烈建议你的应用在启动后立即检查更新,无论是自动还是手动,以确保你可以在将来出现错误时推出修复程序。 > > ¥If more than 10 seconds have elapsed between your app's first render and the time a fatal error is thrown, `expo-updates` will not catch this error at all and none of the error recovery code will be triggered. Therefore, we highly recommend that your app check for updates very shortly after launching, whether automatically or manually to ensure you can push out fixes in the event of a future error. 如果 `expo-updates` 捕获到 JS 错误,接下来会发生什么取决于 React Native 是否触发了原生 "内容出现" 事件(Android 上的 `ReactMarkerConstants.CONTENT_APPEARED` 或 iOS 上的 `RCTContentDidAppearNotification`) - 大约在你的应用的第一个视图在屏幕上渲染时,对于此特定更新,无论是在本次启动还是上一次启动。 ¥If `expo-updates` catches a JS error, what will happen next depends on whether React Native has fired the native "content appeared" event (`ReactMarkerConstants.CONTENT_APPEARED` on Android or `RCTContentDidAppearNotification` on iOS) — approximately when your app's first view has been rendered on the screen, for this particular update, either on this launch or a previous one. > 为什么会有这样的区别?在某些情况下,`expo-updates` 可能会尝试自动回滚到较旧的(工作)更新,但如果你的新更新以非向后兼容的方式修改了持久状态,这可能会很危险。我们假设,如果错误发生在第一个视图渲染之前,则无法执行此类代码,因此回滚是安全的。此后 `expo-updates` 只会向前修复,不会回滚。 > > ¥**Why this distinction?** In some cases `expo-updates` may try to automatically roll back to an older (working) update, but this can be dangerous if your new update has modified persistent state in a non-backwards compatible way. We assume that if the error occurs before the first view has rendered, no such code has been able to execute, and so rolling back is safe. After this point `expo-updates` will only fix forward and will not roll back. ### 如果内容已经出现 ¥If content has appeared 如果捕获到错误并且 "内容出现" 事件已经触发,或者如果在过去启动相同更新时曾在此设备上触发过该事件,则会发生以下情况: ¥If an error is caught and the "content appeared" event has already fired, OR if it has ever been fired on this device on a past launch of the same update, the following will happen: * 将启动 5 秒计时器,并且(除非 `EXUpdatesCheckOnLaunch`/`expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH` 设置为 `NEVER`)应用将检查是否有新更新,如果有则下载。 ¥A 5 second timer will be started, and (unless `EXUpdatesCheckOnLaunch`/`expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH` is set to `NEVER`) the app will check for a new update and download it if there is one. * 如果没有新的更新、更新下载完成或计时器超时(以先发生者为准),应用将抛出原始错误并崩溃。 ¥If there is no new update, the update finishes downloading, or the timer runs out (whichever happens first), the app will throw the original error and crash. 请注意,如果下载了新更新,它将在用户下次尝试打开应用时启动。 ¥Note that if a new update is downloaded, it will launch when the user next tries to open the app. ### 如果内容还没有出现 ¥If content has not appeared 如果在 "内容出现" 事件触发之前捕获到错误,并且这是当前更新首次在此设备上启动,则会发生以下情况: ¥If an error is caught before the "content appeared" event has fired, and this is the first time the current update is being launched on this device, the following will happen: * 该更新将在本地标记为 "failed",并且不会在此设备上再次启动。 ¥The update will be marked as "failed" locally and will not be launched again on this device. * 将启动 5 秒计时器,并且(除非 `EXUpdatesCheckOnLaunch`/`expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH` 设置为 `NEVER`)应用将检查是否有新更新,如果有则下载。 ¥A 5 second timer will be started, and (unless `EXUpdatesCheckOnLaunch`/`expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH` is set to `NEVER`) the app will check for a new update and download it if there is one. * 如果新的更新在计时器耗尽之前完成下载,应用将立即尝试重新加载自身并启动新下载的更新。 ¥If a new update finishes downloading before the timer runs out, the app will immediately try to reload itself and launch the newly downloaded update. * 如果这个新下载的更新也引发致命错误,或者没有新的更新,或者计时器用完,应用将立即尝试通过回滚到较旧的更新(无论哪个最近成功启动)来重新加载。 ¥If this newly downloaded update also throws a fatal error, or there is no new update, or the timer runs out, the app will immediately try to reload by rolling back to an older update, whichever one was most recently launched successfully. * 如果这也失败,或者设备上没有可用的旧更新,则应用将抛出原始错误并崩溃。 ¥If this also fails, or there is no older update available on the device, the app will throw the original error and crash. ## 错误堆栈跟踪 ¥Error stacktraces 如果你的应用遇到致命的 JS 错误,并且错误恢复系统无法恢复,则会重新抛出原始异常导致崩溃。堆栈跟踪将类似于以下内容: ¥If your app encounters a fatal JS error, and the error recovery system cannot recover, it will re-throw the original exception to cause a crash. The stacktrace will look similar to this: ```text --------- beginning of crash AndroidRuntime: FATAL EXCEPTION: expo-updates-error-recovery AndroidRuntime: Process: com.myapp.MyApp, PID: 12498 AndroidRuntime: com.facebook.react.common.JavascriptException AndroidRuntime: AndroidRuntime: at com.facebook.react.modules.core.ExceptionsManagerModule.reportException(ExceptionsManagerModule.java:72) AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) AndroidRuntime: at com.facebook.react.bridge.JavaMethodWrapper.invoke(JavaMethodWrapper.java:372) AndroidRuntime: at com.facebook.react.bridge.JavaModuleWrapper.invoke(JavaModuleWrapper.java:188) AndroidRuntime: at com.facebook.react.bridge.queue.NativeRunnable.run(Native Method) AndroidRuntime: at android.os.Handler.handleCallback(Handler.java:938) AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:99) AndroidRuntime: at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:27) AndroidRuntime: at android.os.Looper.loop(Looper.java:223) AndroidRuntime: at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:228) AndroidRuntime: at java.lang.Thread.run(Thread.java:923) ``` 在 Android 上,原始异常的堆栈跟踪被保留。根据你的崩溃报告服务,你可能需要也可能不需要在本地重现崩溃以查看有关潜在错误的更多信息。 ¥On Android, the stacktrace of the original exception is preserved. Depending on your crash reporting service, you may or may not need to reproduce the crash locally to see more information about the underlying error. ```text Last Exception Backtrace: 0 CoreFoundation 0xf203feba4 __exceptionPreprocess + 220 (NSException.m:200) 1 libobjc.A.dylib 0xf201a1be7 objc_exception_throw + 60 (objc-exception.mm:565) 2 MyApp 0x10926b7ee -[EXUpdatesAppController throwException:] + 24 (EXUpdatesAppController.m:422) 3 MyApp 0x109280352 -[EXUpdatesErrorRecovery _crash] + 984 (EXUpdatesErrorRecovery.m:222) 4 MyApp 0x10927fa3d -[EXUpdatesErrorRecovery _runNextTask] + 148 (EXUpdatesErrorRecovery.m:0) 5 libdispatch.dylib 0x109bc1848 _dispatch_call_block_and_release + 32 (init.c:1517) 6 libdispatch.dylib 0x109bc2a2c _dispatch_client_callout + 20 (object.m:560) 7 libdispatch.dylib 0x109bc93a6 _dispatch_lane_serial_drain + 668 (inline_internal.h:2622) 8 libdispatch.dylib 0x109bca0bc _dispatch_lane_invoke + 392 (queue.c:3944) 9 libdispatch.dylib 0x109bd6472 _dispatch_workloop_worker_thread + 648 (queue.c:6732) 10 libsystem_pthread.dylib 0xf6da2845d _pthread_wqthread + 288 (pthread.c:2599) 11 libsystem_pthread.dylib 0xf6da2742f start_wqthread + 8 ``` 尽管看起来异常是从 expo-updates 引发的,但此堆栈跟踪通常表明源自 JavaScript 的错误。 ¥Even though it appears the exception was thrown from expo-updates, this stacktrace generally indicates **an error that originated in JavaScript**. 不幸的是,Apple 的崩溃报告不包括异常消息,该消息详细说明了潜在错误及其在 JavaScript 中的位置。要查看该消息并帮助你缩小问题范围,你可能需要使用附加的 Xcode 调试器或 macOS 控制台应用在本地重现崩溃。 ¥Unfortunately, Apple's crash reporting does not include the exception message, which details the underlying error and its location in JavaScript. To see the message and help you narrow down the issue, you may need to reproduce the crash locally with the Xcode debugger or macOS Console app attached. # 参考 ## 使用 EAS 更新进行端到端代码签名 了解 EAS 更新中代码签名和密钥轮换的工作原理。 > **info** EAS 更新代码签名仅适用于订阅了 EAS 生产或企业计划的账户。[了解更多](https://expo.dev/pricing)。 > > ¥EAS Update Code Signing is only available to accounts subscribed to the EAS Production or Enterprise plans. [Learn more](https://expo.dev/pricing). `expo-updates` 库支持使用 [公钥密码学](https://en.wikipedia.org/wiki/Public-key_cryptography) 进行端到端代码签名。代码签名允许开发者使用自己的密钥对更新进行加密签名。然后在应用更新之前在客户端上验证签名,这确保 ISP、CDN、云提供商甚至 EAS 本身无法篡改应用运行的更新。 ¥The `expo-updates` library supports end-to-end code signing using [public-key cryptography](https://en.wikipedia.org/wiki/Public-key_cryptography). Code signing allows developers to cryptographically sign their updates with their own keys. The signatures are then verified on the client before the update is applied, which ensures ISPs, CDNs, cloud providers, and even EAS itself cannot tamper with updates run by apps. 以下步骤将指导你完成生成私钥和相应证书、配置项目以使用代码签名以及发布应用的签名更新的过程。 ¥The following steps will guide you through the process of generating a private key and corresponding certificate, configuring your project to use code signing, and publishing a signed update for your app. Step 1: ## Generate a private key and corresponding certificate In this step, we will generate a key pair and corresponding code signing certificate for your app. Specify a directory outside of your source control for the `--key-output-directory` flag to ensure the generated private key isn't accidentally added to source control. ```sh $ npx expo-updates codesigning:generate \\ --key-output-directory ../keys \\ --certificate-output-directory certs \\ --certificate-validity-duration-years 10 \\ --certificate-common-name "Your Organization Name" ]} cmdCopy='npx expo-updates codesigning:generate --key-output-directory ../keys --certificate-output-directory certs --certificate-validity-duration-years 10 --certificate-common-name "Your Organization Name"' /> This command generated a key pair along with a code signing certificate to be included in the app: - `../keys/private-key.pem`: the private key of the key pair. - `../keys/public-key.pem`: the public key of the key pair. - `certs/certificate.pem`: the code signing certificate configured to be valid for 10 years. This file should be added to source control (if applicable). - The generated private key must be kept private and secure. The command above suggests generating and storing these keys in a directory outside of your source control to ensure they are not accidentally checked into source control. We recommend storing the private key in the same way you would store other sensitive information (KMS password manager and so on) and how you store it will vary the steps needed to publish an update in step (3). - The public key may be stored alongside the private key but isn't sensitive. - The certificate should be included in the project (checked into source control). It contains the public key and a method for verifying code signatures. When a signed update is downloaded the update's signature is verified using this certificate. - The certificate validity duration is a setting that may vary based on the security needs of your app. - A shorter validity duration will require [key rotation](#key-rotation) more frequently but is considered better practice since a compromised private key will have a sooner expiration which limits exposure. - Shorter validity durations add overhead to your app's release process as the key must be rotated more frequently. Binaries with expired certificates won't apply new updates. - For example Expo sets this value to 20 years for the public Expo Go app but only 1 year for internal apps with binaries that are distributed more frequently. We plan to rotate our keys every 10 years. Step 2: ## Configure your project to use code signing ` element in **android/app/src/main/AndroidManifest.xml** Before doing that, we need to generate an XML-escaped version of your certificate. You can either copy the contents of **certs/certificate.pem** and replace all of the `\r` characters with ` ` and `\n` with ` ` manually, or run the following script to do that for you: Now add the following two fields, and replace the `android:value` for the `expo.modules.updates.CODE_SIGNING_CERTIFICATE` field with the XML-escaped certificate. You do not need to modify the value for `expo.modules.updates.CODE_SIGNING_METADATA` entry. ```xml android/app/src/main/AndroidManifest.xml ``` --- Note: Configure code signing in an iOS native project --- You will need to add two fields to the `` element in **ios/project-name/Supporting/Expo.plist**. Before doing that, we need to generate an XML-escaped version of your certificate. You can either copy the contents of **certs/certificate.pem** and replace all of the `\r` characters with ` `, or run the following script to do that for you: Now add the following two fields, and replace the certificate value with the XML-escaped certificate. You do not need to update the `EXUpdatesCodeSigningMetadata` field. ```xml ios/project-name/Supporting/Expo.plist EXUpdatesCodeSigningCertificate -----BEGIN CERTIFICATE----- (insert XML-escaped certificate, it should look something like this) (spanning multiple lines with \r escaped but \n not escaped) +-----END CERTIFICATE----- EXUpdatesCodeSigningMetadata keyid main alg rsa-v1_5-sha256 ``` --- With code signing configured, create a new build with a new runtime version. The code signing certificate will be embedded in this new build. Step 3: ## Publish a signed update for your app ```sh $ eas update --private-key-path ../keys/private-key.pem ``` During an EAS Update publish using `eas update`, the EAS CLI automatically detects that code signing is configured for your app. It then verifies the integrity of the update and creates a digital signature using your private key. This process is performed locally so that your private key never leaves your machine. The generated signature is automatically sent to EAS to store alongside the update. Step 4: ## Verify that the update is loaded Download the update on the client (this step is done automatically by the library). The build from step (2) that is configured for code signing checks if there is a new update available. The server responds with the update published in step (3) and its generated signature. After being downloaded but before being applied, the update is verified against the embedded certificate and included signature. The update is applied if the certificate and signature are valid, and rejected otherwise. ## 附加信息 ¥Additional information ### 密钥轮换 ¥Key rotation 密钥轮换是更改用于签名更新的密钥对的过程。这在以下几种情况下最常见: ¥Key rotation is the process by which the key pair used for signing updates is changed. This is most commonly done in a few cases: * 密钥过期。在上一节的步骤 (1) 中,我们将 `certificate-validity-duration-years` 设置为 10 年(尽管它可以配置为任何值)。这意味着 10 年后,使用证书对应的私钥签名的更新在被应用下载后将不再应用。在签名证书到期之前下载的更新将继续正常运行。在证书过期之前轮换密钥有助于预防任何潜在的密钥过期问题,并有助于保证所有用户在旧证书过期之前使用新证书。 ¥Key expiration. In step (1) from the section above, we set `certificate-validity-duration-years` to 10 years (though it can be configured to any value). This means that after 10 years, updates signed with the private key corresponding to the certificate will no longer be applied after being downloaded by the app. Updates downloaded before the expiration of their signing certificate will continue to function normally. Rotating keys well before the certificate expires helps to preempt any potential key expiration issues and helps to guarantee all users are using the new certificate before the old certificate expires. * 私钥泄露。如果用于签署更新的私钥意外暴露给公众,则不再被视为安全,因此无法再保证用它签名的更新的完整性。例如,恶意行为者可以制作恶意更新并使用泄露的私钥对其进行签名。 ¥Private key compromise. If the private key used to sign updates is accidentally exposed to the public, it can no longer be considered secure and therefore the integrity of updates signed with it can no longer be guaranteed. For example, a malicious actor could craft a malicious update and sign it with the leaked private key. * 安全最佳实践的密钥轮换。最佳实践是定期轮换密钥,以确保系统能够适应上述其他原因之一的手动密钥轮换。 ¥Key rotation for security best practices. It is best practice to rotate keys periodically to ensure that a system is resilient to manual key rotation in response to one of the other reasons above. 在任何这些情况下,程序都是相似的: ¥In any of these cases, the procedure is similar: 1. 备份上述步骤 (1) 中生成的旧密钥和证书。 ¥Back up the old keys and certificate that were generated in step (1) above. 2. 按照上述步骤从步骤 (1) 开始生成新密钥。为了帮助调试,你可能希望通过修改应用配置 (app.json) 中的 `updates.codeSigningMetadata.keyid` 字段来更改新密钥的 `keyid`。 ¥Generate a new key by following the steps above starting from step (1). To assist in debugging, you may wish to change the `keyid` of the new key by modifying the `updates.codeSigningMetadata.keyid` field in your app config (**app.json**). 3. 代码签名证书是应用运行时的一部分,因此应为使用此证书的构建设置新的运行时版本,以确保只有使用新密钥签名的更新才会在新构建中运行。 ¥The code signing certificate is part of the app's runtime, so a new runtime version should be set for builds using this certificate to ensure that only updates signed with the new key run in the new build. 4. 按照上述步骤 (3) 使用新密钥发布签名更新。 ¥Publish signed updates using the new key by following step (3) above. ### 删除代码签名 ¥Removing code signing 从应用中删除代码签名的过程与 [密钥轮换](#key-rotation) 类似,可以视为 `null` 密钥的密钥轮换。 ¥The process of removing code signing from an app is similar to [key rotation](#key-rotation) and can be thought of as a key rotation to a `null` key. 1. 备份在上述步骤 (1) 中生成的旧密钥和证书。 ¥Backup the old key and certificate that were generated in step (1) above. 2. 从应用配置 (app.json) 中删除 `updates.codeSigningMetadata` 字段。 ¥Remove the `updates.codeSigningMetadata` field from your app config (**app.json**). 3. 新的无证书应用是一个新的独特运行时,因此应为构建设置新的运行时版本,以确保在新构建中仅运行未签名的更新。 ¥The new certificate-less app is a new distinct runtime, so a new runtime version should be set for builds to ensure that only unsigned updates run in the new build. ## 资源选择和排除 了解如何使用资源选择功能并验证更新是否包含所有必需的应用资源。 实验性的资源选择功能允许开发者指定仅应在更新中包含某些资源。这可以大大减少需要上传到更新服务器和从更新服务器下载的资源数量。此功能将与 EAS 更新服务器或任何符合 [`expo-updates` 协议](/technical-specs/expo-updates-1) 的自定义服务器配合使用。 ¥Experimental **asset selection feature** allows the developer to specify that only certain assets should be included in updates. This can greatly reduce the number of assets that need to be uploaded to and downloaded from the updates server. This feature will work with the EAS Update server or any custom server that complies with the [`expo-updates` protocol](/technical-specs/expo-updates-1). SDK 52 向公众推出了此功能。 ¥SDK 52 launched this feature to general availability. ## 使用资源选择 ¥Using asset selection 要在 SDK 51 及以下版本中使用资源选择,请在应用配置中包含属性 `extra.updates.assetPatternsToBeBundled`。它应该定义一个或多个文件匹配模式(正则表达式)。例如,app.json 文件具有以下定义的模式: ¥To use asset selection in SDK 51 and below, include the property `extra.updates.assetPatternsToBeBundled` in your app config. It should define one or more file-matching patterns (regular expressions). For example, an **app.json** file has the patterns defined in the following way: ```json app.json "expo": { "extra": { "updates": { "assetPatternsToBeBundled": [ "app/images/**/*.png" ] } } } ``` 要在 SDK 52 及以上版本中使用资源选择,请在应用配置中包含属性 `updates.assetPatternsToBeBundled`。它应该定义一个或多个文件匹配模式(正则表达式)。例如,app.json 文件具有以下定义的模式: ¥To use asset selection in SDK 52 and above, include the property `updates.assetPatternsToBeBundled` in your app config. It should define one or more file-matching patterns (regular expressions). For example, an **app.json** file has the patterns defined in the following way: ```json app.json "expo": { "updates": { "assetPatternsToBeBundled": [ "app/images/**/*.png" ] } } ``` 添加此配置后,app/images 所有子目录中的所有 .png 文件都将包含在更新中。你还必须确保这些资源在你的 JavaScript 代码中是必需的。 ¥After adding this configuration all **.png** files in all subdirectories of **app/images** will be included in updates. You have to also ensure that these assets need to be required in your JavaScript code. 如果应用配置中不包含 `assetPatternsToBeBundled`,则打包器解析的所有资源都将包含在更新中(根据 SDK 49 及更早的行为)。 ¥If `assetPatternsToBeBundled` isn't included in the app config, all assets resolved by the bundler will be included in updates (as per SDK 49 and earlier behavior). ## 验证更新是否包含所有必需的应用资源 ¥Verifying that an update includes all required app assets 使用资源选择时,与任何文件模式不匹配的资源将在 Metro 打包器中解析。但是,这些资源不会上传到更新服务器。你必须确保更新中未包含的资源已内置到应用的原生构建中。 ¥When using the asset selection, assets that do not match any file patterns will resolve in the Metro bundler. However, these assets will not be uploaded to the updates server. You have to be certain that assets not included in updates are built into the native build of the app. 如果你在本地构建应用或可以访问正确的版本以发布更新(使用相同的 [运行时版本](/eas-update/runtime-versions/)),请使用 `npx expo-updates assets:verify` 命令。它允许你检查发布更新时是否包含所有必需的资源: ¥If you are building your app locally or have access to the correct build for publishing updates (with the same [runtime version](/eas-update/runtime-versions/)), then use the `npx expo-updates assets:verify` command. It allows you to check whether all required assets will be included when you publish an update: ```sh $ npx expo-updates assets:verify ``` > **info** 这个新命令是 `expo-updates` CLI 的一部分,它也支持 [EAS 更新代码签名](/eas-update/code-signing/)。它不是 [Expo CLI](/more/expo-cli/) 或 [EAS 命令行接口](https://github.com/expo/eas-cli) 的一部分。仅适用于([`expo-updates`](/versions/latest/sdk/updates/) >= 0.24.10)。 > > ¥This new command is part of the `expo-updates` CLI, which also supports [EAS Update code signing](/eas-update/code-signing/). It is not part of the [Expo CLI](/more/expo-cli/) or the [EAS CLI](https://github.com/expo/eas-cli). Only available for ([`expo-updates`](/versions/latest/sdk/updates/) >= 0.24.10). 你还可以使用命令中的 `--help` 选项来查看可用选项: ¥You can also use the `--help` option with the command to see the available options: | 选项 | 描述 | | ------------------------------------- | ----------------------------------------------------------------- | | `` | Expo 项目的目录。默认:当前工作目录。 | | `-a, --asset-map-path ` | `npx expo export --dump-assetmap` 命令生成的导出中的 assetmap.json 路径。 | | `-e, --exported-manifest-path ` | `npx expo export --dump-assetmap` 命令生成的导出中的 metadata.json 路径。 | | `-b, --build-manifest-path ` | `expo-updates` 在 Expo 应用构建(android 或 ios)中创建的 app.manifest 文件的路径。 | | `-p, --platform ` | 选项:\["android", "ios"] | | `-h, --help` | 使用信息。 | ## 示例 ¥Example } Icon={GithubIcon} href="https://github.com/expo/UpdatesAPIDemo" /> ## 使用 Expo Router 的 组件 了解如何独立于其他 EAS 服务(例如 Build)使用 EAS Update。 EAS Update 作为独立服务运行良好,因此你可以在有或没有 EAS Build 和其他 EAS 服务的情况下使用它。其所有主要功能都设计为与构建管道无关,并且由不使用其他 EAS 服务的大型组织在生产中使用。 ¥EAS Update works great as a standalone service, so you can use it with or without EAS Build and other EAS services. All of its main features are designed to be agnostic of the build pipeline, and its used in production by large organizations that do not use other EAS services. Note: What are the downsides of using EAS Update without other EAS services? --- EAS Update and Build work closely together to provide an experience that is greater than the sum of its parts. For example, when you create a build with EAS Build we will help with the bookkeeping for various aspects related to updates, such as the runtime version and channel. Builds that use the same channel and runtime version are grouped into a **Deployments** section on [expo.dev](https://expo.dev/accounts/[account-name/projects/[project-name]/deployments). These sorts of bookkeeping and insights features that depend on knowledge of builds or other aspects of your app won't be available if you use EAS Update independently of other EAS services. That said, many organizations are already heavily invested in their CI/CD infrastructure or may have other reasons for wanting to use another build pipeline, and the benefits offered by deeper integration across EAS services may not be worth the switching costs of migrating to a different CI/CD provider. --- ## Using EAS Update without EAS Build Most of the [installation and configuration steps](/eas-update/getting-started) are identical whether or not you use EAS Build. The primary difference is how the update [channel](/eas-update/eas-cli/) is configured. When using EAS Build, the channel from **eas.json** will automatically be added to your build's **AndroidManifest.xml** and **Expo.plist** at build time. When not using EAS Build, this must be configured manually by [setting the request header in the app config](/eas-update/getting-started/#configure-update-channels-in-appjson), followed by manually creating the channel on the server. ```sh which points to the production EAS Update branch by default) $ eas channel:create production ``` ## 请求代理 通过你自己的服务器代理请求 EAS 更新服务器。 EAS Update 支持请求代理,允许你通过自己的服务器将请求代理到 EAS Update 服务器。这有很多用途,例如添加自定义标头、记录请求或实现额外的安全措施或请求 IP 匿名化措施。 ¥EAS Update supports request proxying, which allows you to proxy requests to the EAS Update server through your own server. This can be useful for various reasons, such as adding custom headers, logging requests, or implementing additional security or request IP anonymization measures. ## 启用请求代理 ¥Enabling request proxying 1. 创建两个用于处理请求的代理服务器: ¥Create two proxy servers that will handle the requests: * 一个用于更新资源请求(JavaScript 包、图片等)。 ¥One for the update asset requests (JavaScript bundles, images, and so on). * 这必须将请求转发到 `assets.eascdn.net`,即 EAS 更新资源服务器。 ¥This must forward requests to `assets.eascdn.net`, the EAS Update asset server. * 这必须传递所有 URL 内容(路径、查询参数等)。 ¥This must pass-through all URL contents (path, query parameters, and so on). * 这必须传递所有以 `expo-` 或 `eas-` 为前缀的标头。 ¥This must pass-through all headers prefixed with `expo-` or `eas-`. * 一个用于更新清单请求。 ¥One for the update manifest requests. * 这必须将请求转发到 `u.expo.dev`,即 EAS 更新服务器。 ¥This must forward requests to `u.expo.dev`, the EAS Update server. * 这必须传递所有 URL 内容(路径、查询参数等)。 ¥This must pass-through all URL contents (path, query parameters, and so on). * 这必须传递所有以 `expo-` 或 `eas-` 为前缀的标头。 ¥This must pass-through all headers prefixed with `expo-` or `eas-`. 2. 将以下字段添加到你的 eas.json 配置文件中,并将占位符替换为你的实际代理服务器 URL: ¥Add the following fields to your **eas.json** configuration file, replacing the placeholders with your actual proxy server URLs: ```json eas.json { "cli": { "updateAssetHostOverride": "updates-asset-proxy.example.com", "updateManifestHostOverride": "updates-manifest-proxy.example.com" } } ``` 1. 运行以下命令应用更改: ¥Run the following command to apply the changes: ```sh $ eas update:configure ``` 1. 发布更新以测试代理: ¥Publish an update to test the proxying: ```sh $ eas update ``` 1. 通过导航到 [EAS 更新仪表板](https://expo.dev/accounts/\[account]/projects/\[project]/updates) 上的更新组并点击其中一个平台的 "查看元数据" 进行验证。 ¥Verify by navigating to the update group on the [EAS Update dashboard](https://expo.dev/accounts/\[account]/projects/\[project]/updates) and clicking "View Metadata" for one of the platforms. * manifest.json 应该会显示已覆盖的 `manifestHostOverride`。 ¥**manifest.json** should show the overridden `manifestHostOverride`. * 其他资源应显示已覆盖的 `assetHostOverride`。 ¥Other assets should show the overridden `assetHostOverride`. ## 从 CodePush 迁移 帮助从 CodePush 迁移到 EAS 更新的指南。 本指南介绍了如何将使用 CodePush 的 React Native 项目转换为使用提供 [许多优点](/eas-update/introduction/#pitch) 的 EAS 更新。它假设你使用默认的 React Native 项目结构。如需将棕地原生应用迁移到 EAS 更新的帮助,请参阅 [在现有原生应用中使用 EAS 更新](/eas-update/integration-in-existing-native-apps/)。 ¥This guide explains how to transition a React Native project that uses CodePush to use EAS Update which offers [many advantages](/eas-update/introduction/#pitch). It assumes that you're using the default React Native project structure. For assistance with migrating brownfield native apps to EAS Update, see [Using EAS Update in an existing native app](/eas-update/integration-in-existing-native-apps/). 要了解有关 CodePush 和 EAS Update 之间的区别的更多信息,请参阅 [CodePush 和 EAS Update 之间的概念差异](#conceptual-differences-between-codepush-and-eas-update) 和 [Expo 博客上没有 CodePush 帖子怎么办](https://expo.dev/blog/what-to-do-without-codepush)。 ¥To learn more about the differences between CodePush and EAS Update, see [Conceptual differences between CodePush and EAS Update](#conceptual-differences-between-codepush-and-eas-update) and the [What to do without CodePush post on the Expo Blog](https://expo.dev/blog/what-to-do-without-codepush). Step 1: ## Ensure your app is using the latest Expo SDK version To migrate from CodePush to EAS Update, we recommend that you use the latest Expo SDK version. Instructions are not available for older Expo SDK and React Native version. While you may be able to migrate successfully by adapting the instructions as needed for the older Expo SDK and React Native version that your app uses, additional hands-on support for integrating with older versions can only be provided for enterprise customers ([contact us](https://expo.dev/contact)). Step 2: ## Uninstall CodePush To avoid conflicts and unexpected behavior, it's recommended to uninstall CodePush if you're using EAS Update. This is because your app could periodically fetch updates from both services, leading to issues, especially if you're using different configurations for each service. Remove the CodePush SDK from your project by uninstalling the `react-native-code-push` package: ```sh $ npm uninstall react-native-code-push ``` You'll also need to remove CodePush references from JS and native code. See this [GitHub comment](https://github.com/Microsoft/react-native-code-push/issues/1101#issuecomment-350204507) for more detailed instructions. Step 3: ## Add an `expo` key to your `app.json` Ensure that your project has an **app.json** file with an `expo` object. If you don't have anything specific to configure in your **app.json** file yet, you can create a minimal file with an empty `expo` object like this: !!!IG0!!! Step 4: ## Follow the "Getting Started" guide The instructions in the [EAS Update Getting Started guide](/eas-update/getting-started/) will guide you through setting up EAS Update in your project. Step 5: ## Resubmit your app Since you have changed the update provider from CodePush to EAS Update, you will need to rebuild your app and submit the new build to the respective app stores (Google Play Store and Apple App Store) to ensure the update mechanism works as expected for your end-users. Follow the respective store guidelines for submitting a new build of your application: - [Submitting to Google Play Store](/submit/android) - [Submitting to Apple App Store](/submit/ios) After successfully submitting your app, users will be able to download and use the latest build with EAS Update integration. If your app is not updating as expected, [validate your configuration](/eas-update/debug). ## Common questions Note: How do I release mandatory/critical updates with EAS Update? --- CodePush CLI has a `--mandatory` flag that allows you to release mandatory updates. You can build this functionality with EAS Update but there is no specific flag for it. [Learn more about mandatory/critical updates](/eas-update/download-updates/#criticalmandatory-updates). --- Note: How do I include a message in an update? --- CodePush CLI has a `--description` flag that allows you to include a message in an update. You can build this functionality with EAS Update using the `extra` field in your app config. Refer the `--message` flag in this example: [`expo/UpdatesAPIDemo`](https://github.com/expo/UpdatesAPIDemo). --- Note: How do I switch the 'deployment' that is being used at runtime, similar to the sync() function in CodePush? --- This is possible using `Updates.setUpdateURLAndRequestHeadersOverride()`. Learn more in the [Override update configuration at runtime](/eas-update/override/) guide. --- Note: How do I handle different environments (such as staging and production) with EAS Update? --- With EAS Update, you can use channels and branches to manage different environments and rollouts. [Learn more](/eas-update/eas-cli/). --- Note: How do I roll back updates with EAS Update? --- You can roll back updates using `eas update:rollback`. Learn more in the [Rollback to a previous update](/eas-update/rollbacks/) guide. --- Note: How do I gradually roll out updates with EAS Update? --- EAS Update supports various strategies for gradually rolling out updates, so you can pick which approach best fits your needs. [Learn more](/eas-update/rollouts/). --- Note: How can I have direct control over when an update is downloaded and applied? --- Learn more about different strategies for downloading and applying updates in the [Downloading updates](/eas-update/download-updates/) guide, such as checking for updates while the app is running or even when backgrounded with `Updates.checkForUpdateAsync()`. --- Note: Does EAS Update support end-to-end code signing? --- Yes, EAS Update supports end-to-end code signing. It is available for EAS Production and Enterprise plan subscribers. Learn more in the [Code signing](/eas-update/code-signing/) guide. --- Note: What else should I know about? --- - Expo Orbit: The macOS, Windows, and Linux desktop launcher app. You can [launch updates](/review/with-orbit/) directly from the website with it in one click, among other features. - You can monitor the adoption of updates from the EAS website. [Learn more](/eas-update/download-updates/#monitoring-adoption-of-updates). You can also roll out and roll back updates from the website. - You can use EAS Update to achieve a web-like preview workflow. [Learn more](/eas-update/preview). - Each update and build created with EAS is associated with a [fingerprint](/versions/latest/sdk/fingerprint/). You can diff these fingerprints through the website UI or with `eas fingerprint:compare` to see what changed in the native runtime of your app between your builds and updates, to understand build/update compatibility, and guide your decision about when to bump the [`runtimeVersion`](/eas-update/runtime-versions/). --- ## Conceptual differences between CodePush and EAS Update CodePush and EAS Update are both services that allow you to send hotfixes to the JavaScript code of your app, but they take slightly different approaches, and so you may need to adapt your release process when moving to EAS Update. Note: Differences in how updates are organized within streams --- **CodePush has single streams of updates for deployments**. What this means is that you can point a build to a deployment, and it will pull updates from that. If you want to change the deployment that is targeted by a build, you can do this at runtime through a JavaScript API. **EAS Update has multiple streams of updates** — one that correspond to your source control branches (called branches), and another called channels, which point to branches. The mapping between channels and branches is handled on the server side, and a channel can point to different branches for each runtime version (additionally, more advanced logic may be expressed, such as to support incremental rollouts). Builds are not directly associated with branches, but rather with channels. Each build points to a single channel, which is set at build time and cannot be modified at runtime. The reason for this is that it ensures that certain branches (for example: development, staging) don't automatically go out to production — your preview updates don't go to your production users. This helps you separate the two main uses of EAS Update: previews and production hotfixes. --- Note: Differences in how updates are selected at runtime --- A key distinction between CodePush and EAS Update that can impact your release process is that **with CodePush, the client controls the target update deployment at runtime**, and **with EAS Update, it is controlled on the server side, by mapping channels to branches**. This means that you can't include code in your app using EAS Update to instruct it to load a different stream of updates depending on runtime criteria, such as the current user role (for example: distribute beta releases to employees) - it will only load the branch that is mapped on EAS Update servers to the corresponding channel (such as production or staging). The ability to control the target deployment at runtime is commonly used with CodePush in staging environments to allow non-technical stakeholders to test features from a single build on Google Play Beta / TestFlight. The current alternative for this with EAS Update is to use [development builds](/eas-update/expo-dev-client/). We're currently working on providing a way to do this with release builds. --- ## 从经典更新迁移 帮助从经典更新迁移到 EAS 更新的指南。 > **warning** SDK 49 是支持经典更新的最后一个版本。要继续使用已弃用的 `expo publish` 命令,请在应用配置中设置 [`updates.useClassicUpdates`](/versions/latest/config/app/#useclassicupdates)。 > > ¥SDK 49 was the last version to support Classic Updates. To continue using the deprecated `expo publish` command, set [`updates.useClassicUpdates`](/versions/latest/config/app/#useclassicupdates) in your app config. EAS Update 是 Expo 的下一代更新服务。如果你使用经典更新,本指南将帮助你升级到 EAS 更新。 ¥EAS Update is the next generation of Expo's updates service. If you're using Classic Updates, this guide will help you upgrade to EAS Update. ## 先决条件 ¥Prerequisites EAS 更新需要以下版本或更高版本: ¥EAS Update requires the following versions or greater: * Expo SDK >= 45.0.0 * Expo CLI >= 5.3.0 * EAS CLI >= 0.50.0 * expo-updates >= 0.13.0 ## 安装 EAS CLI ¥Install EAS CLI Step 1: Install EAS CLI: ```sh $ npm install --global eas-cli ``` Step 2: Then, log in with your expo account: ```sh $ eas login ``` ## Configure your project You'll need to make the following changes to your project: Step 1: Initialize your project with EAS Update: ```sh $ eas update:configure ``` After this command, you should have two new fields in your app config at `expo.updates.url` and `expo.runtimeVersion`. Step 2: To ensure that updates are compatible with the underlying native code inside a build, EAS Update uses a new field named `runtimeVersion` that replaces the `sdkVersion` field in your project's app config. Remove the `expo.sdkVersion` property from your app config. Step 3: To allow updates to apply to builds built with EAS, update your EAS Build profiles in **eas.json** to include `channel` properties. These channels replace `releaseChannel` properties. We find it convenient to name the `channel` after the profile's name. For instance, the `preview` profile has a `channel` named `"preview"` and the `production` profile has a `channel` named `"production"`. ```json eas.json { "build": { "development": { "developmentClient": true, "distribution": "internal" }, "preview": { "distribution": "internal", "channel": "preview" }, "production": { "channel": "production" } } } ``` Step 4: **Optional**: If your project is a bare React Native project, see [Use EAS Update in an existing project](/eas-update/getting-started/) for the extra configuration you may need. ## Create new builds The changes above affect the native code layer inside builds, which means you'll need to make new builds to start sending updates. Once your builds are complete, you'll be ready to publish an update. ## Publish an update After making a change to your project locally, you're ready to publish an update, run: ```sh $ eas update --channel [channel-name] --message [message] $ eas update --channel production --message "Fixes typo" ``` Once published, you can see the update in the [EAS dashboard](https://expo.dev/accounts/[account]/projects/[project]/updates). ## Additional migration steps - Replace instances of `expo publish` with `eas update` in scripts. You can view all the options for publishing with `eas update --help`. - If you have any code that references `Updates.releaseChannel` from the `expo-updates` library, replace them with `Updates.channel`. - Remove any code that references `Constants.manifest`. That will now always return `null`. You can access most properties you'll need with `Constants.expoConfig` from the `expo-constants` library. ## Learn more The steps described above allow you to use a similar flow to Classic Updates. However, EAS Update is more flexible and has more features. It can be used to create more stable release flows. Learn [how EAS Update works](/eas-update/how-it-works) and how you can craft a more stable [deployment process](/eas-update/deployment-patterns) for your project and your team. If you experience issues with migrating, check out our [debugging guide](/eas-update/debug). If you have feedback, join us on Discord 在 #update 通道中。 ¥in the #update channel. ## 如何将更新 ID 追溯到 EAS 仪表板 了解如何在使用 EAS 更新和 expo-updates 库时将更新 ID 追溯到 EAS 仪表板。 使用 [EAS 更新](/eas-update/introduction/) 时,你可能会遇到需要将 `updateId` 追溯到 [EAS 仪表板](https://expo.dev/accounts/\[account]) 的情况。这可能具有挑战性,因为 `Updates.updateId` 始终返回一个 ID,无论 [`Updates.isEmbeddedLaunch`](/versions/latest/sdk/updates/#updatesisembeddedlaunch) 是 `true` 还是 `false`。但是,如果应用正在运行嵌入式更新,则尝试在 [EAS 仪表板](https://expo.dev/accounts/\[account]) 中查找 `updateId` 将导致错误。发生这种情况是因为仪表板中未跟踪嵌入式更新。 ¥When working with [EAS Updates](/eas-update/introduction/), you might encounter a scenario where you need to trace an `updateId` back to the [EAS dashboard](https://expo.dev/accounts/\[account]). This can be challenging because `Updates.updateId` always returns an ID, regardless of whether [`Updates.isEmbeddedLaunch`](/versions/latest/sdk/updates/#updatesisembeddedlaunch) is `true` or `false`. However, if the app is running an embedded update, attempting to look up the `updateId` in the [EAS dashboard](https://expo.dev/accounts/\[account]) will result in an error. This happens because embedded updates are not tracked in the dashboard. ## 确定更新是嵌入还是下载 ¥Determine if the update is embedded or downloaded 为了避免此问题,你可以使用 `Updates.isEmbeddedLaunch` 属性来确定应用是在运行嵌入式更新还是从服务器下载的更新。如果 `Updates.isEmbeddedLaunch` 等于 `true`,则表示当前正在运行的更新嵌入在版本中,这意味着该更新在 EAS 仪表板中不可用。 ¥To avoid this issue, you can use the `Updates.isEmbeddedLaunch` property to determine whether the app is running an embedded update or one downloaded from the server. If `Updates.isEmbeddedLaunch` is `true`, the currently running update is embedded in the build, which means it won't be available in the EAS dashboard. 以下是如何显示更新是嵌入还是下载的示例: ¥Here's an example of how you can display whether the update is embedded or downloaded: ```tsx UpdateStatus.tsx import * as Updates from 'expo-updates'; import { Text } from 'react-native'; export default function UpdateStatus() { return ( {Updates.isEmbeddedLaunch ? '(Embedded) ❌ You cannot trace this update in the EAS dashboard.' : '(Downloaded) ✅ You can trace this update in the EAS dashboard.'} ); } ``` 当你在 EAS 仪表板中导航到更新组时(打开你的项目,选择“无线更新”,然后点击特定更新),URL 显示如下: ¥When you navigate to an update group in the EAS dashboard (open your project, select **Over-the-air updates**, and click a specific update), the URL displays as: ```text https://expo.dev/accounts/[accountName]/projects/[projectName]/updates/[updateGroupId] ``` 你可以将 `updateGroupId` 替换为 `Updates.updateId`,以直接导航到特定平台更新: ¥You can replace `updateGroupId` with the `Updates.updateId` to navigate directly to a specific platform update: ```text https://expo.dev/accounts/[accountName]/projects/[projectName]/updates/[updateId] ``` 这将打开平台特定更新的相应更新组。 ¥This opens the corresponding update group for the platform-specific update. ## 估计带宽使用情况 了解如何估算 EAS 更新的带宽使用情况。 ## 了解更新带宽使用情况 ¥Understanding update bandwidth usage EAS Update 使应用能够通过无线方式更新其自己的非原生部分(例如 JavaScript、样式和图片)。本指南解释了带宽是如何消耗的以及如何优化消耗。 ¥EAS Update enables an app to update its own non-native pieces (such as JavaScript, styling, and images) over-the-air. This guide explains how bandwidth is consumed and how consumption can be optimized. ## 带宽计算细目 ¥Bandwidth calculation breakdown 每个订阅计划除了其月活跃用户 (MAU) 分配 ([了解有关如何计算 MAU 的更多信息](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle)) 之外,还包括每个月结算期的预定义带宽分配。超出标准分配的 MAU 按 [基于使用情况的定价费率](https://expo.dev/pricing#update) 计费,并且每个额外的 MAU 都会为你的标准带宽分配增加额外的 40 MiB。此带宽决定了用户在支付额外带宽使用费之前可以下载的更新数量。 ¥Each subscription plan includes a predefined bandwidth allocation per monthly billing period in addition to its monthly active user (MAU) allocation ([learn more about how MAU are calculated](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle)). MAU's beyond the standard allocation are billed at [usage-based pricing rates](https://expo.dev/pricing#update), and each of those additional MAU add an extra 40 MiB to your standard bandwidth allocation. This bandwidth determines the number of updates your users can download before being charged for additional bandwidth usage. 以下是如何估计每次更新的带宽使用量: ¥Here's how to estimate bandwidth usage per update: * 更新大小:带宽消耗的关键因素是设备上尚未存在的更新资源的大小。如果更新仅修改了应用的 JavaScript 部分,则用户只会下载新的 JavaScript。为了开始一个例子,假设导出期间生成的未压缩的 JavaScript 部分为 10 MB。压缩将进一步减小其大小。 ¥**Update size:** The key factor in bandwidth consumption is the size of update assets that are not already on the device. If an update only modifies the JavaScript portion of your app, users will only download the new JavaScript. To begin an example, let's say the uncompressed JavaScript portion generated during export is **10 MB**. Compression will further reduce its size. * 压缩率:压缩级别取决于文件类型。JavaScript 和 Hermes 字节码(常用于 React Native 应用中)可以压缩,而图片和图标不会自动压缩。在上面的例子中,Hermes 字节码包实现了估计的 2.6 倍压缩率,将实际下载大小减少到: ¥**Compression ratio:** The level of compression depends on the file type. JavaScript and Hermes bytecode (commonly used in React Native apps) can be compressed, while images and icons are not automatically compressed. In the example above, a Hermes bytecode bundle achieves an estimated **2.6× compression ratio**, reducing the actual download size to: ```text 10 MB / 2.6 ≈ 3.85 MB update bandwidth size ``` 给定带宽分配,我们估计在每月结算期内可以下载多少更新,然后才会收取额外的带宽费用。例如,如果你的生产计划中有 60,000 个 MAU,则它包括每月 50,0000 个 MAU 和 1 TiB(1,024 GiB)带宽。通过基于使用情况的定价购买的额外 10,000 个 MAU 可获得每个 MAU 额外的 40 MiB 带宽。可以下载的更新总数为: ¥Given a bandwidth allocation, we estimate how many updates can be downloaded in a monthly billing period before additional bandwidth charges apply. For example, if you have 60,000 MAUs on a production plan, it includes 50,0000 MAU and **1 TiB (1,024 GiB) of bandwidth per month**. An additional 10,000 MAUs purchased through usage-based pricing receives an additional **40 MiB of bandwidth per MAU**. The total number of updates that can be downloaded is: ```text (1,024 GiB × 1,024 MiB/GiB) + (10,000 MAU × 40 MiB/MAU) = 1,448,576 MiB per month 1,448,576 MiB / 3.85 MiB ≈ 376,254 updates ``` ## 测量你的实际更新大小 ¥Measuring your actual update size 要确定 Hermes 包的实际压缩大小,请运行以下命令: ¥To determine the actual compressed size of your Hermes bundle, run the following commands: ```sh $ brotli -5 -k bundle.hbc $ gzip -9 -k bundle.hbc $ ls -lh bundle.hbc.br bundle.hbc.gz ``` 这将生成你的 Hermes 包 (bundle.hbc.br 和 bundle.hbc.gz) 的 Brotli 和 Gzip 压缩版本并显示其大小。你可以使用它根据应用的实际更新大小来优化带宽计算。 ¥This will generate **Brotli- and Gzip-compressed** versions of your Hermes bundle (**bundle.hbc.br** and **bundle.hbc.gz**) and display their sizes. You can use this to refine bandwidth calculations based on your app's real-world update sizes. ## 影响带宽消耗的因素 ¥Factors that affect bandwidth consumption 实际带宽使用情况因以下因素而异: ¥Actual bandwidth usage varies due to: * 用户行为:理论计算假设每个用户都会下载每个更新。但是,许多用户只有在重新打开应用时才会获得更新,通常会跳过中间更新。因此,实际带宽使用量通常远低于理论最大值。 ¥**User behavior:** Theoretical calculations assume every user downloads every update. However, many users only get updates when they reopen the app, often skipping intermediate updates. As a result, actual bandwidth usage is usually much lower than the theoretical maximum. * 缺少资源:如果更新包括字体和图片等资源,而这些资源在构建或之前下载的更新中尚未存在于设备上,则也需要下载它们。 ¥**Missing assets:** If an update includes assets such as fonts and images that are not already on the device from the build or previously downloaded updates, they will need to be downloaded as well. ## 优化带宽使用 ¥Optimizing bandwidth usage 1. 首先监控使用情况:管理带宽的最简单方法是跟踪你的 [使用指标](https://expo.dev/accounts/\[account]/settings/usage) 并识别任何异常峰值或低效率。 ¥**Monitor usage first:** The easiest way to manage bandwidth is to track your [usage metrics](https://expo.dev/accounts/\[account]/settings/usage) and identify any unusual spikes or inefficiencies. 2. 优化资源大小:使用 [本指南](/eas-update/optimize-assets) 减少资源的大小。 ¥**Optimize asset size:** Reduce the size of your assets with [this guide](/eas-update/optimize-assets). 3. 需要时排除资源:使用 [资源选择](/eas-update/asset-selection) 减少每次更新所包含的资源数量。这是一个高级优化,应首先尝试其他方法。 ¥**Exclude assets when needed:** Use [asset selection](/eas-update/asset-selection) to reduce the number of assets included with each update. This is an advanced optimization and other approaches should be tried first. ## 在现有原生应用中使用 EAS 更新 了解如何将 EAS 更新集成到你现有的原生 Android 和 iOS 应用中以启用无线更新。 > **info** 如果你的项目是一个全新的 React Native 应用 - 从一开始就主要使用 React Native 构建,并且应用的入口点是 React Native,那么请跳过本指南并继续执行 [开始使用 EAS 更新](/eas-update/getting-started/)。 > > ¥If your project is a **greenfield React Native app** — primarily built with React Native from the start, and the entry point of the app is React Native, then skip this guide and proceed to [Get started with EAS Update](/eas-update/getting-started/). 本指南讲解了如何将 EAS 更新集成到现有的原生应用(有时也称为“棕地应用”)中。它假定你使用的是 Expo SDK 52 或更高版本以及 React Native 0.76 或更高版本。 ¥This guide explains how to integrate EAS Update in an existing native app, sometimes referred to as a brownfield app. It assumes that you are using Expo SDK 52 or later, and React Native 0.76 or later. 此说明不适用于旧版 Expo SDK 和 React Native。仅向企业客户提供与旧版本集成的额外实际支持 ([联系我们](https://expo.dev/contact))。 ¥Instructions are not available for older Expo SDK and React Native versions. Additional hands-on support for integrating with older versions can only be provided for enterprise customers ([contact us](https://expo.dev/contact)). ## 先决条件 ¥Prerequisites > **warning** 以下说明可能不适用于所有项目。将 EAS 更新集成到现有项目的具体细节很大程度上取决于你的应用的具体情况,因此你可能需要根据你独特的设置调整说明。如果你遇到问题,请提交 [在 GitHub 上创建问题](https://github.com/expo/expo/issues) 或提交拉取请求以改进本指南。 > > ¥The following instructions may not work for all projects. The specifics of integrating EAS Update into existing projects depend heavily on the specifics of your app, and so you may need to adapt the instructions to your unique setup. If you encounter issues, [create an issue on GitHub](https://github.com/expo/expo/issues) or open a pull request to suggest improvements to this guide. 你应该有一个安装并配置了 React Native 的棕地原生项目来渲染根视图。如果你还没有这个,请按照 React Native 文档中的 [与现有应用集成](https://rn.nodejs.cn/docs/integration-with-existing-apps) 指南进行操作,然后在完成步骤后返回此处。 ¥You should have a brownfield native project with React Native installed and configured to render a root view. If you don't have this yet, follow the [Integration with Existing Apps](https://rn.nodejs.cn/docs/integration-with-existing-apps) guide from the React Native documentation and then come back here once you have followed the steps. * 你的应用必须使用 [最新的 Expo SDK 版本及其支持的 React Native 版本](/versions/latest/#each-expo-sdk-version-depends-on-a-react-native-version)。 ¥Your app must be using the [latest Expo SDK version and its supported React Native version](/versions/latest/#each-expo-sdk-version-depends-on-a-react-native-version). * 从你的应用中删除任何其他更新库集成,例如 react-native-code-push,并确保你的应用在你支持的平台上的调试和发布版本中都能成功编译和运行。 ¥Remove any other update library integration from your app, such as react-native-code-push, and ensure that your app compiles and runs successfully in both debug and release on your supported platforms. * 必须在项目中安装并配置对 Expo 模块的支持(通过 `expo` 软件包)。[了解更多](/brownfield/installing-expo-modules/)。 ¥Support for Expo modules (through the `expo` package) must be installed and configured in your project. [Learn more](/brownfield/installing-expo-modules/). * 你的 metro.config.js [必须扩展 `expo/metro-config`](/guides/customizing-metro/#customizing)。 ¥Your **metro.config.js** [must extend `expo/metro-config`](/guides/customizing-metro/#customizing) . * 你的 babel.config.js [必须扩展 `babel-preset-expo`](/versions/latest/config/babel/)。 ¥Your **babel.config.js** [must extend `babel-preset-expo`](/versions/latest/config/babel/). * 如果你的项目支持 Android,则命令 `npx expo export -p android` 必须在项目中成功运行;如果你的项目支持 iOS,则命令 `npx expo export -p ios` 必须在项目中成功运行。 ¥The command `npx expo export -p android` must run successfully in your project if it supports Android, and `npx expo export -p ios` if it supports iOS. ## 安装和基本配置 ¥Installation and basic configuration 请按照 [开始使用 EAS 更新](/eas-update/getting-started/) 指南中的步骤 1、2、3 和 4 进行操作。 ¥Follow steps 1, 2, 3, and 4 from the [Get started with EAS Update](/eas-update/getting-started/) guide. 完成后,你将安装 `eas-cli` 并通过身份验证,将 `expo-updates` 安装到你的项目中,初始化关联的 EAS 项目,并为原生项目添加基本配置。 ¥After this is complete, you will have installed and authenticated with `eas-cli`, installed `expo-updates` to your project, initialized an associated EAS project, and added basic configuration to your native projects. ## 退出自动设置 ¥Opt out of automatic setup 下一步是禁用 `expo-updates` 的默认行为,以便自动设置自身,从而支持全新的 React Native 项目。 ¥The next step is to disable the default behavior of `expo-updates` to automatically set itself up in a way that supports greenfield React Native projects. ### 禁用 Android 上的自动设置 ¥Disable automatic setup on Android 修改 android/settings.gradle 文件,设置禁用自动更新初始化的属性,如下例所示: ¥Modify **android/settings.gradle** to set the property that disables automatic updates initialization, as in the example below: ### iOS 上禁用自动设置 ¥Disable automatic setup on iOS 将环境变量传递给 CocoaPods 安装以禁用自动更新初始化。 ¥Pass in the environment variable to CocoaPods installation to disable automatic updates initialization. ```sh $ EX_UPDATES_CUSTOM_INIT=1 npx pod-install ``` ## 设置你的 React Native 应用以使用 expo-updates 加载发布包 ¥Set up your React Native app to use expo-updates for loading the release bundle 下一步是将 `expo-updates` 集成到你的 Android 和 iOS 项目中,以便你的应用在发布版本中使用 `expo-updates` 作为 JavaScript 代码的来源。 ¥The next step is to integrate `expo-updates` into your Android and iOS projects so that your app will use `expo-updates` as the source of your app JavaScript in release builds. Note: Example --- A complete working example is available at [this GitHub repository](https://github.com/expo/CustomRNView) . --- ### Integrating expo-updates with your React Native bundling 1. Ensure that your Metro config extends the Expo config, as in this example: !!!IG0!!! 2. If you are using a custom entry point, be sure to include Expo initialization there. This ensures that Expo libraries (including `expo-updates`) are all initialized properly. Here are two examples: !!!IG1!!! !!!IG2!!! ### Integrating expo-updates on Android The following instructions assume you have an app written in Kotlin, with one or more native activities. Open **android/app/src/main/java/com/\/MainActivity.kt** and follow the steps below. 1. Your React Native activity should subclass `com.facebook.react.ReactActivity`. 2. In this activity, add code to `onCreate()` to initialize the updates system. The initialization should not happen in the main thread (otherwise a lockup and ANR will occur). 3. Override `getMainComponentName()` to return the name of the app you registered in your JS entry point above. 4. Show the React Native view, by overriding the `createReactActivityDelegate()` method as shown below. !!!IG3!!! ### Integrating expo-updates on iOS The following instructions assume you have an app written in Swift, with one or more native screens that have custom UIViewControllers. We will add a custom view controller that renders your React Native app. !!!IG8!!! !!!IG9!!! #### AppDelegate changes 1. Modify **AppDelegate.swift** so that it extends `ExpoAppDelegate`. 2. If you are not already doing so, add a public method to get the running `AppDelegate` instance, so that your custom view controller can access it later. 3. Add a reference to the singleton instance of the `expo-updates` `AppController` class, which manages the updates system on iOS. 4. Add a new class, `CustomReactNativeFactoryDelegate`, that extends `ExpoReactNativeFactoryDelegate` and overrides the `bundleUrl()` method to return the correct bundle URL for updates, if the updates system is running. 5. The `didFinishLaunchingWithOptions()` method needs to perform two steps: 1. Initialize the `ExpoReactNativeFactory` using the `CustomReactNativeFactoryDelegate` created above. This will be used later to create the React Native root view. 2. Call `AppController.initializeWithoutStarting()`. This creates the controller instance, but defers the rest of the updates startup procedure until it is needed. !!!IG4!!! #### Implementing a custom view controller 1. The view controller should implement the updates protocol `AppControllerDelegate`. 2. The view controller initialization should 1. Set the app delegate's updates controller instance, so that its `bundleURL()` method above works correctly for updates. 2. Set the `AppController` delegate to the view controller instance 3. Start the `AppController` 3. Finally, the view controller must implement the one method in the `AppControllerDelegate` protocol, `appController(_ appController: AppControllerInterface, didStartWithSuccess success: Bool)`. This method will be called once the updates system is fully initialized, and the latest update (or the embedded bundle) is ready to be rendered. 1. Create the React Native root view using the `ExpoReactNativeFactory` created by the app delegate. The app name passed in must match the app name that you registered in your JS entry point above. 2. Add this root view to the view controller. !!!IG5!!! !!!IG10!!! !!!IG11!!! #### AppDelegate changes 1. Modify **AppDelegate.swift** so that it extends `EXAppDelegateWrapper`. 2. If you are not already doing so, add a public method to get the running `AppDelegate` instance, so that your custom view controller can access it later. 3. Add a reference to the singleton instance of the `expo-updates` `AppController` class, which manages the updates system on iOS. 4. Override the `bundleUrl()` method to return the correct bundle URL for updates, if the updates system is running. 5. The `didFinishLaunchingWithOptions()` method needs to perform two steps: 1. Initialize the root view factory used later to create the React Native root view. 2. Call `AppController.initializeWithoutStarting()` . This creates the controller instance, but defers the rest of the updates startup procedure until it is needed. !!!IG6!!! #### Implementing a custom view controller 1. The view controller should implement the updates protocol `AppControllerDelegate`. 2. The view controller initialization should 1. Set the app delegate's updates controller instance, so that its `bundleURL()` method above works correctly for updates. 2. Set the `AppController` delegate to the view controller instance 3. Start the `AppController` 3. Finally, the view controller must implement the one method in the `AppControllerDelegate` protocol, `appController(_ appController: AppControllerInterface, didStartWithSuccess success: Bool)`. This method will be called once the updates system is fully initialized, and the latest update (or the embedded bundle) is ready to be rendered. 1. Create the React Native root view using the root view factory created by the app delegate. The app name passed in must match the app name that you registered in your JS entry point above. 2. Add this root view to the view controller. !!!IG7!!! !!!IG12!!! !!!IG13!!! ## Common questions Note: How long will this take to add to my app? --- Assuming you are using the latest version of React Native supported by the Expo SDK, and you are comfortable with the React Native integration in your native projects, then you can likely integrate EAS Update in a similar amount of time as it would take you to integrate with a tool like CodePush or Sentry. The most important factor is the React Native version that your app uses. If your app uses anything older than the latest supported version by the Expo SDK (as referenced at the top of this guide), then you will want to upgrade to that version first, and the time that will take is heavily dependent on the size and complexity of the app and skill and experience level of the team working on it. --- Note: I'm migrating from CodePush, what else do I need to know? --- To learn more, see [Migrating from CodePush](/eas-update/codepush/) guide. --- ## EAS 更新常见问题解答 有关 EAS 更新的常见问题。 ### 发布更新时必须遵循哪些准则? ¥What guidelines must I follow when publishing updates? EAS 更新的规则之一是你需要遵循你正在构建的平台和应用商店的规则。这意味着你的更新需要遵循 App Store 和 Play Store 指南,包括更新的内容以及你如何使用它们。这通常意味着需要审查对应用行为的更改。 ¥One of the rules of EAS Update is that you need to follow the rules of the platforms and app stores you are building for. This means your updates need to follow the App Store and Play Store guidelines, including the content of the updates and how you use them. This usually means changes to your app's behavior need to be reviewed. App Store 规则会定期更改,就像你在没有 Expo 的情况下编写应用时可能需要遵循这些规则一样,当你使用 Expo 和 EAS Update 时也需要遵循它们。 ¥App Store rules regularly change and just like how you might need to follow them if you were writing an app without Expo, you need to follow them when you are using Expo and EAS Update. EAS 更新是向应用的用户快速提供改进的好方法。例如,考虑一个具有需要修复的严重错误的应用。通过 EAS 更新,你可以快速修复并随后提交包含内置修复的新提交。 ¥EAS Update is a great way to quickly deliver improvements to the people who use your apps. For example, consider an app that has a critical bug that needs to be fixed. With EAS Update you can quickly get the fix out and later follow up with a new submission that includes the fix built in. ### "每月活跃用户" 如何计算一个计费周期? ¥How are "monthly active users" counted for a billing cycle? > **info** 1 个月度活跃用户等于 1 个唯一安装的应用,该应用在你的结算周期内下载至少 1 个更新。 > > ¥1 monthly active user equals 1 unique installation of an app that downloads at least 1 update during your billing cycle. * 在结算周期的每一天下载新更新的应用安装计为 1 个月活跃用户。 ¥An app install that downloads a new update on each day of your billing cycle counts as 1 monthly active user. * 在结算周期内未下载任何新更新的应用安装计为 0 个月活跃用户。 ¥An app install that does not download any new updates during your billing cycle counts as 0 monthly active users. * 卸载并重新安装应用(并在你的结算周期内下载每个应用的更新)算作 2 个月活跃用户。 ¥Uninstalling and reinstalling an app (and downloading updates in each during your billing cycle) counts as 2 monthly active users. * 单个设备有两个由单个 Expo 账户拥有的应用,这两个应用都使用更新,则被视为该账户的 2 个月度活跃用户。 ¥A single device that has two apps owned by a single Expo account, both of which use updates, is considered 2 monthly active users for the account. ### 如何为我的应用实现自定义更新策略? ¥How can I implement a custom update strategy for my app? 默认情况下,`expo-updates` 在每次加载应用时检查更新。你可以使用 [更新 API](/versions/latest/sdk/updates) 和 [应用配置](/versions/latest/config/app/#updates) 实现自定义更新策略。 ¥By default, `expo-updates` checks for updates every time the app is loaded. You can implement a custom update strategy with the [Updates API](/versions/latest/sdk/updates) and [app config](/versions/latest/config/app/#updates). ### 是否仍支持经典更新? ¥Are Classic Updates still supported? 经典更新服务现已弃用。2021 年 12 月之前可用,`expo publish` 无法再运行。但是,现有应用将继续接收已发布并正在积极使用的经典更新。 ¥The Classic Updates service is now deprecated. It was available before December 2021, and `expo publish` cannot be run anymore. However, existing apps will continue to receive Classic Updates that have already been published and are actively used. 我们建议过渡到 EAS 更新或使用 [自托管更新服务](/versions/latest/sdk/updates/)。 ¥We recommend transitioning to EAS Update or using a [self-hosted update service](/versions/latest/sdk/updates/). # EAS 元数据 ## 介绍 ## 开始使用 EAS 元数据 了解如何使用 EAS 元数据从命令行自动化和维护应用商店的存在。 > **important** EAS 元数据处于预览阶段,可能会发生重大更改。 > > ¥**EAS Metadata** is in preview and subject to breaking changes. EAS 元数据使你能够从命令行自动化并维护应用商店的状态。它使用包含所有必需的应用信息的 [**store.config.json**](./config.mdx#static-store-config) 文件,而不是通过多种不同的形式。它还尝试通过内置验证来查找可能导致应用拒绝的常见陷阱。 ¥EAS Metadata enables you to automate and maintain your app store presence from the command line. It uses a [**store.config.json**](./config.mdx#static-store-config) file containing all required app information instead of going through multiple different forms. It also tries to find common pitfalls that could cause app rejections with built-in validation. ## 先决条件 ¥Prerequisites EAS 元数据目前仅支持 Apple App Store。 ¥EAS Metadata currently **only supports the Apple App Store**. > 使用 VS Code?在 store.config.json 文件中安装 [Expo 工具扩展](https://github.com/expo/vscode-expo#readme) 以实现自动补齐、建议和警告。 > > ¥Using VS Code? Install the [Expo Tools extension](https://github.com/expo/vscode-expo#readme) for auto-complete, suggestions, and warnings in your **store.config.json** files. ## 创建存储配置 ¥Create the store config 让我们首先在项目的根目录中创建 store.config.json 文件。该文件包含你要上传到应用商店的所有信息。 ¥Let's start by creating our **store.config.json** file in the root directory of your project. This file holds all the information you want to upload to the app stores. 如果你在商店中已有应用,则可以通过运行以下命令将信息提取到存储配置中: ¥If you already have an app in the stores, you can pull the information into a store config by running: ```sh $ eas metadata:pull ``` 如果你的商店中还没有应用,EAS 元数据无法为你生成存储配置。相反,创建一个新的存储配置文件。 ¥If you don't have an app in the stores yet, EAS Metadata can't generate the store config for you. Instead, create a new store config file. ```json store.config.json { "configVersion": 0, "apple": { "info": { "en-US": { "title": "Awesome App", "subtitle": "Your self-made awesome app", "description": "The most awesome app you have ever seen", "keywords": ["awesome", "app"], "marketingUrl": "https://example.com/en/promo", "supportUrl": "https://example.com/en/support", "privacyPolicyUrl": "https://example.com/en/privacy" } } } } ``` > 默认情况下,EAS 元数据使用项目根目录下的 store.config.json 文件。你可以通过设置 eas.json [`metadataPath`](../../submit/eas-json.mdx#metadatapath) 属性来更改此文件的名称和位置。 > > ¥By default, EAS Metadata uses the **store.config.json** file at the root of your project. You can change the name and location of this file by setting the **eas.json** [`metadataPath`](../../submit/eas-json.mdx#metadatapath) property. ## 更新存储配置 ¥Update the store config 现在是时候编辑 store.config.json 文件并根据你的应用需求进行自定义了。你可以在 [存储配置架构](/eas/metadata/schema/) 中找到所有可用选项。 ¥Now it's time to edit the **store.config.json** file and customize it to your app needs. You can find all available options in the [store config schema](/eas/metadata/schema/). ## 上传新的应用版本 ¥Upload a new app version 在将 store.config.json 推送到应用商店之前,你必须上传应用的新二进制文件。欲了解更多信息,请参阅 [将新的二进制文件上传到商店](/submit/introduction/)。 ¥Before pushing the **store.config.json** to the app stores, you must upload a new binary of your app. For more information, see [uploading new binaries to stores](/submit/introduction/). 提交和处理二进制文件后,你可以将存储配置推送到应用商店。 ¥After the binary is submitted and processed, you can push the store config to the app stores. ## 上传存储配置 ¥Upload the store config 当你对 store.config.json 设置感到满意时,你可以通过运行以下命令将其推送到应用商店: ¥When you are satisfied with the **store.config.json** settings, you can push it to the app stores by running the following command: ```sh $ eas metadata:push ``` 如果 EAS 元数据在你的存储配置中遇到任何问题,它会在运行此命令时向你发出警告。当没有错误,或者你确认推送但可能存在问题时,它会尝试上传尽可能多的内容。 ¥If EAS Metadata runs into any issues with your store config, it will warn you when running this command. When there are no errors, or you confirm to push it with possible issues, it will try to upload as much as possible. 当存储配置部分失败时,你可以更改存储配置并重试。`eas metadata:push` 可用于重试推送丢失的项目。 ¥When the store config partially fails, you can change the store config and retry. `eas metadata:push` can be used to retry pushing the missing items. ## 下一步 ¥Next steps # 参考 ## 配置 EAS 元数据 了解配置 EAS 元数据的不同方法。 > **important** EAS 元数据处于预览阶段,可能会发生重大更改。 > > ¥**EAS Metadata** is in preview and subject to breaking changes. EAS 元数据由项目根目录下的 store.config.json 文件配置。 ¥EAS Metadata is configured by a **store.config.json** file at the *root of your project*. 你可以使用 eas.json [`metadataPath`](../../submit/eas-json.mdx#metadatapath) 属性配置存储配置文件的路径或名称。除了默认的 JSON 格式外,EAS 元数据还支持使用 JavaScript 文件的更多动态配置。 ¥You can configure the path or name of the store config file with the **eas.json** [`metadataPath`](../../submit/eas-json.mdx#metadatapath) property. Besides the default JSON format, EAS Metadata also supports more dynamic config using JavaScript files. ## 静态存储配置 ¥Static store config EAS 元数据的默认存储配置类型是简单的 JSON 文件。下面的代码片段显示了一个示例存储配置,其中包含用英语(美国)编写的基本 App Store 信息。 ¥The default store config type for EAS Metadata is a simple JSON file. The code snippet below shows an example store config with basic App Store information written in English (U.S.). 你可以在 [存储配置架构](/eas/metadata/schema/).conf 中找到所有配置选项。 ¥You can find all configuration options in the [store config schema](/eas/metadata/schema/). > 如果你安装了 [VS Code Expo 工具扩展](https://github.com/expo/vscode-expo#readme),你将获得 store.config.json 文件的自动补齐、建议和警告。 > > ¥If you have the [VS Code Expo Tools extension](https://github.com/expo/vscode-expo#readme) installed, you get auto-complete, suggestions, and warnings for **store.config.json** files. ```json store.config.json { "configVersion": 0, "apple": { "info": { "en-US": { "title": "Awesome App", "subtitle": "Your self-made awesome app", "description": "The most awesome app you have ever seen", "keywords": ["awesome", "app"], "marketingUrl": "https://example.com/en/promo", "supportUrl": "https://example.com/en/support", "privacyPolicyUrl": "https://example.com/en/privacy" } } } } ``` ## 动态存储配置 ¥Dynamic store config 有时,元数据属性可以从动态值中受益。例如,元数据版权声明应包含当前年份。这可以通过 EAS 元数据实现自动化。 ¥At times, Metadata properties can benefit from dynamic values. For example, the Metadata **copyright notice** should contain the current year. This can be automated with EAS Metadata. 要动态生成内容,请首先创建 JavaScript 配置文件 store.config.js。然后,使用 eas.json 文件中的 [`metadataPath`](/eas/json/#metadatapath) 属性来选择 JS 配置文件。 ¥To generate content dynamically, start by creating a JavaScript config file **store.config.js**. Then, use the [`metadataPath`](/eas/json/#metadatapath) property in the **eas.json** file to pick the JS config file. > `eas metadata:pull` 无法更新动态存储配置文件。相反,它会创建一个与配置文件同名的 JSON 文件。你可以导入 JSON 文件以重用 `eas metadata:pull` 中的数据。 > > ¥`eas metadata:pull` can't update dynamic store config files. Instead, it creates a JSON file with the same name as the configured file. You can import the JSON file to reuse the data from `eas metadata:pull`. ```js // Use the data from `eas metadata:pull` const config = require('./store.config.json'); const year = new Date().getFullYear(); config.apple.copyright = `${year} Acme, Inc.`; module.exports = config; ``` ```json { "submit": { "production": { "ios": { "metadataPath": "./store.config.js" } } } } ``` ## 使用外部内容存储配置 ¥Store config with external content 使用外部服务进行本地化时,你必须获取外部内容。EAS 元数据支持从动态存储配置文件导出的同步和异步函数。在验证并与商店同步之前等待函数结果。 ¥When using external services for localizations, you have to fetch external content. EAS Metadata supports synchronous and asynchronous functions exported from dynamic store config files. The function results are awaited before validating and syncing with the stores. > store.config.js 函数在 Node.js 中进行评估。如果你需要特殊值(例如秘密),请使用环境变量。 > > ¥The **store.config.js** function is evaluated in Node.js. If you need special values, like secrets, use environment variables. ```js // Use the data from `eas metadata:pull` const config = require('./store.config.json'); module.exports = async () => { const year = new Date().getFullYear(); const info = await fetchLocalizations('...').then(response => response.json()); config.apple.copyright = `${year} Acme, Inc.`; config.apple.info = info; return config; }; ``` ```json { "submit": { "production": { "ios": { "metadataPath": "./store.config.js" } } } } ``` ## EAS 元数据架构 EAS 元数据中存储配置的参考。 > **important** EAS 元数据处于预览阶段,可能会发生重大更改。 > > ¥**EAS Metadata** is in preview and subject to breaking changes. EAS 元数据中的存储配置包含原本通过应用商店仪表板手动提供的信息。本文档概述了存储配置中对象的结构。 ¥The store config in EAS Metadata contains information that otherwise would be provided manually through the app store dashboards. This document outlines the structure of the object in your store config. > 如果你使用 [VS Code Expo 工具扩展](https://github.com/expo/vscode-expo#readme),你可以通过编辑器中的自动补齐、建议和警告获得所有这些信息。 > > ¥If you use the [VS Code Expo Tools extension](https://github.com/expo/vscode-expo#readme), you get all this information through auto-complete, suggestions, and warnings in your editor. ## 配置模式 ¥Config schema 存储配置对象中的一个重要属性是 `configVersion` 属性。应用商店可能需要更多信息或更改现有信息结构才能发布你的应用。此属性有助于对不向后兼容的更改进行版本控制。 ¥An essential property in the store config object is the `configVersion` property. App stores might require more or change existing information structures to publish your app. This property helps versioning changes that are not backward compatible. EAS 元数据目前仅支持 Apple App Store。 ¥EAS Metadata *currently* only supports the Apple App Store. {[ { name: 'configVersion', type: 'number', rules: ['enum: 0'], description: 'EAS 元数据存储配置架构版本。', }, { name: 'apple', type: 'object', description: 'App Store 的所有可配置属性。', }, { nested: 1, name: 'version', type: 'string', description: [ The app version to use when syncing all metadata defined in the store config., EAS Metadata selects the latest available version in the app stores by default. , ], }, { nested: 1, name: 'copyright', type: 'string', description: '拥有应用专有权的个人或实体的名称,前面加上获得权利的年份。 (例如,"2008 埃克米公司")', }, { nested: 1, name: 'advisory', type: AppleAdvisory, description: "The App Store questionnaire to determine the app's age rating.", }, { nested: 1, name: 'categories', type: AppleCategories, description: '应用的应用商店类别。 你可以添加主要、次要和可能的子类别。', }, { nested: 1, name: 'info', type: ( <> {'Map<'} AppleLanguage {', '} AppleInfo {'>'} ), description: '你的应用的本地化 App Store 展示情况。', }, { nested: 1, name: 'release', type: AppleRelease, description: '所选版本的应用发布策略。', }, { nested: 1, name: 'review', type: AppleReview, description: 'App Store 审核团队审核应用所需的所有信息,包括联系信息和凭据。 (如适用)', }, ]} ### 苹果咨询 ¥Apple advisory Apple 使用复杂的调查问卷来确定应用的 [年龄分级](https://help.apple.com/app-store-connect/#/dev599d50efb)。App Store 上的家长控制使用此计算出的年龄分级。默认情况下,EAS 元数据对每个问题使用限制最少的答案。 ¥Apple uses a complex questionnaire to determine the app's [age rating](https://help.apple.com/app-store-connect/#/dev599d50efb). Parental controls on the App Store use this calculated age rating. EAS Metadata uses the least restrictive answer for each of these questions by default. Note: Complete advisory with the least restrictive answers --- ```json store.config.json { "configVersion": 0, "apple": { "advisory": { "alcoholTobaccoOrDrugUseOrReferences": "NONE", "contests": "NONE", "gamblingSimulated": "NONE", "horrorOrFearThemes": "NONE", "matureOrSuggestiveThemes": "NONE", "medicalOrTreatmentInformation": "NONE", "profanityOrCrudeHumor": "NONE", "sexualContentGraphicAndNudity": "NONE", "sexualContentOrNudity": "NONE", "violenceCartoonOrFantasy": "NONE", "violenceRealistic": "NONE", "violenceRealisticProlongedGraphicOrSadistic": "NONE", "gambling": false, "unrestrictedWebAccess": false, "kidsAgeBand": null, "ageRatingOverride": "NONE", "koreaAgeRatingOverride": "NONE" } } } ``` --- {[ { name: 'alcoholTobaccoOrDrugUseOrReferences', type: AppleAgeRating, description: '该应用是否包含酒精、烟草或药物使用或参考内容?', }, { name: 'contests', type: AppleAgeRating, description: '该应用是否包含竞赛?', }, { name: 'gambling', type: 'boolean', description: '你的应用是否包含赌博内容?' }, { name: 'gamblingSimulated', type: AppleAgeRating, description: '该应用是否包含模拟赌博?', }, { name: 'horrorOrFearThemes', type: AppleAgeRating, description: '该应用是否包含恐怖或恐惧主题?', }, { name: 'kidsAgeBand', type: AppleKidsAge, description: [ When parents visit the Kids category on the App Store, they expect the apps they find will protect their children's data, provide only age-appropriate content, and require a parental gate to link out of the app, request permissions, or present purchasing opportunities. , It's critical that no personally identifiable information or device information be transmitted to third parties, and that advertisements are human-reviewed for age appropriateness to be displayed. , Learn more , ], }, { name: 'matureOrSuggestiveThemes', type: AppleAgeRating, description: '该应用是否包含成熟或暗示性的主题?', }, { name: 'medicalOrTreatmentInformation', type: AppleAgeRating, description: '该应用是否包含医疗或治疗信息?', }, { name: 'profanityOrCrudeHumor', type: AppleAgeRating, description: '该应用是否包含脏话或粗俗幽默?', }, { name: 'ageRatingOverride', type: AppleAgeRatingOverride, description: [ If your app rates 12+ or lower, and you believe its content may not be suitable for children, you can manually override the age rating. , Learn more , ], }, { name: 'koreaAgeRatingOverride', type: ( AppleKoreaAgeRatingOverride ), description: [ If your app rates 12+ or lower, and you believe its content may not be suitable for children, you can manually override the age rating. Same as `ageRatingOverride`, but for South Korea. , Learn more , ], }, { name: 'sexualContentGraphicAndNudity', type: AppleAgeRating, description: '该应用是否包含色情内容和裸体内容?', }, { name: 'sexualContentOrNudity', type: AppleAgeRating, description: '该应用是否包含色情内容或裸露内容?', }, { name: 'unrestrictedWebAccess', type: 'boolean', description: '你的应用是否包含不受限制的网络访问,例如使用嵌入式浏览器?', }, { name: 'violenceCartoonOrFantasy', type: AppleAgeRating, description: '该应用是否包含卡通或奇幻暴力内容?', }, { name: 'violenceRealistic', type: AppleAgeRating, description: '该应用是否包含现实暴力内容?', }, { name: 'violenceRealisticProlongedGraphicOrSadistic', type: AppleAgeRating, description: '该应用是否包含长时间的图形或残酷的现实暴力?', }, ]} #### Apple advisory age rating {[ { name: 'NONE', description: '对于不使用该主题的应用。' }, { name: 'INFREQUENT_OR_MILD', description: '对于提及主题或使用主题作为非主要功能的应用。', }, { name: 'FREQUENT_OR_INTENSE', description: '适用于使用主题作为主要功能的应用。', }, ]} #### Apple advisory kids age {[ { name: 'FIVE_AND_UNDER', description: '适合 5 岁及以下儿童。' }, { name: 'SIX_TO_EIGHT', description: '适合 6 至 8 岁的子级。' }, { name: 'NINE_TO_ELEVEN', description: '适合 9 至 11 岁的子级。' }, ]} #### Apple advisory age rating override {[ { name: 'NONE', description: '无年龄评级覆盖' }, { name: 'SEVENTEEN_PLUS', description: '应用包含可能不适合 17 岁以下儿童的内容。', }, { name: 'UNRATED', description: '仅限成人。此内容无法在 App Store 上发布。它可能会在 iOS 上的其他应用市场或欧盟的网站上发布。', }, ]} #### Apple advisory korea age rating override {[ { name: 'NONE', description: '无年龄评级覆盖' }, { name: 'FIFTEEN_PLUS', description: '应用包含可能不适合 15 岁以下儿童的内容。', }, { name: 'NINETEEN_PLUS', description: '应用包含可能不适合 19 岁以下儿童的内容。', }, ]} ### Apple categories The App Store helps users discover new apps by [categorizing apps into categories](https://developer.apple.com/app-store/categories/), using primary, secondary, and possible subcategories. Note: Primary and secondary category --- ```json store.config.json { "configVersion": 0, "apple": { "categories": ["FINANCE", "NEWS"] } } ``` --- Note: Primary, subcategories, and secondary category --- ```json store.config.json { "configVersion": 0, "apple": { "categories": [["GAMES", "GAMES_CARD", "GAMES_BOARD"], "ENTERTAINMENT"] } } ``` --- {[ { name: 'BOOKS', description: '应用的内容传统上以印刷形式提供,并提供额外的交互性。', }, { name: 'BUSINESS', description: '协助运营业务或提供协作、编辑或共享业务相关内容的方式的应用。', }, { name: 'DEVELOPER_TOOLS', description: '帮助用户开发、维护或共享软件的应用。', }, { name: 'EDUCATION', description: '提供特定技能或主题的交互式学习体验的应用。', }, { name: 'ENTERTAINMENT', description: '交互式应用旨在通过音频、视频或其他内容来娱乐用户。', }, { name: 'FINANCE', description: '提供金融服务或信息以帮助用户处理业务或个人财务的应用。', }, { name: 'FOOD_AND_DRINK', description: '提供与准备、消费或评论食品或饮料相关的建议、说明或评论的应用。', }, { name: 'GAMES', description: [ Apps that provide single or multiplayer interactive experiences for entertainment purposes. , This category can have up to 2 subcategories., {[ 'GAMES_ACTION', 'GAMES_ADVENTURE', 'GAMES_BOARD', 'GAMES_CARD', 'GAMES_CASINO', 'GAMES_CASUAL', 'GAMES_FAMILY', 'GAMES_MUSIC', 'GAMES_PUZZLE', 'GAMES_RACING', 'GAMES_ROLE_PLAYING', 'GAMES_SIMULATION', 'GAMES_SPORTS', 'GAMES_STRATEGY', 'GAMES_TRIVIA', 'GAMES_WORD', ]} , ], }, { name: 'GRAPHICS_AND_DESIGN', description: '提供用于创建、编辑或共享视觉内容的工具或提示的应用。', }, { name: 'HEALTH_AND_FITNESS', description: '与健康生活相关的应用,包括压力管理、健身和娱乐活动。', }, { name: 'LIFESTYLE', description: '与普遍感兴趣的主题或服务相关的应用。', }, { name: 'MAGAZINES_AND_NEWSPAPERS', description: '包含新闻内容的应用传统上以印刷形式提供,并提供额外的交互性。', }, { name: 'MEDICAL', description: '应用专注于为患者或医疗保健专业人员提供医学教育、信息或健康参考。', }, { name: 'MUSIC', description: '用于发现、聆听、录制、表演或创作音乐的应用。', }, { name: 'NAVIGATION', description: '提供信息以帮助用户到达物理位置的应用。', }, { name: 'NEWS', description: '提供有关政治、娱乐、商业、科学、技术和其他字段等感兴趣字段的时事和/或发展信息的应用。', }, { name: 'PHOTO_AND_VIDEO', description: '帮助捕获、编辑、管理、存储或共享照片和视频的应用。', }, { name: 'PRODUCTIVITY', description: '使特定流程或任务更有组织或更高效的应用。', }, { name: 'REFERENCE', description: '帮助用户访问或检索一般信息的应用。', }, { name: 'SHOPPING', description: '提供购买商品或服务方式的应用。' }, { name: 'SOCIAL_NETWORKING', description: '通过文本、语音、照片或视频将人们联系起来的应用。', }, { name: 'SPORTS', description: '与专业、业余、大学或休闲体育活动相关的应用。', }, { name: 'STICKERS', description: [ Apps that provide extended visual functionality to messaging apps., This category can have up to 2 subcategories., {[ 'STICKERS_ANIMALS', 'STICKERS_ART', 'STICKERS_CELEBRATIONS', 'STICKERS_CELEBRITIES', 'STICKERS_CHARACTERS', 'STICKERS_EATING_AND_DRINKING', 'STICKERS_EMOJI_AND_EXPRESSIONS', 'STICKERS_FASHION', 'STICKERS_GAMING', 'STICKERS_KIDS_AND_FAMILY', 'STICKERS_MOVIES_AND_TV', 'STICKERS_MUSIC', 'STICKERS_PEOPLE', 'STICKERS_PLACES_AND_OBJECTS', 'STICKERS_SPORTS_AND_ACTIVITIES', ]} , ], }, { name: 'TRAVEL', description: '帮助用户处理旅行各个方面的应用,例如计划、购买或跟踪。', }, { name: 'UTILITIES', description: '使用户能够解决问题或完成特定任务的应用。', }, { name: 'WEATHER', description: '具有特定天气相关信息的应用。' }, ]} ### Apple info The App Store is a global service used by many people in different languages. You can localize your App Store presence in [multiple languages](#apple-info-languages). Note: Minimal localized info in English (U.S.) --- ```json store.config.json { "configVersion": 0, "apple": { "info": { "en-US": { "title": "Awesome app", "privacyPolicyUrl": "https://example.com/en/privacy" } } } } ``` --- Note: Complete localized info written in English (U.S.) --- ```json store.config.json { "configVersion": 0, "apple": { "info": { "en-US": { "title": "App title", "subtitle": "Subtitle for your app", "description": "A longer description of what your app does", "keywords": ["keyword", "other-keyword"], "releaseNotes": "Bug fixes and improved stability", "promoText": "Short tagline for your app", "marketingUrl": "https://example.com/en", "supportUrl": "https://example.com/en/help", "privacyPolicyUrl": "https://example.com/en/privacy", "privacyChoicesUrl": "https://example.com/en/privacy/choices" } } } } ``` --- {[ { name: 'title', type: 'string', rules: ['length: 2..30'], description: [ Name of the app in the store. This name should be similar to the installed app name. , The name will be reviewed before it is made available on the App Store. , ], }, { name: 'subtitle', type: 'string', rules: ['length: 30'], description: [ Subtext for the app in the store. For example, "A Fun Game For Friends"., The subtitle will be reviewed before it is made available on the App Store. , ], }, { name: 'description', type: 'string', rules: ['length: 10..4000'], description: '应用功能的主要描述', }, { name: 'keywords', type: 'string[]', rules: ['unique items', 'max length item: 100'], description: '帮助用户在 App Store 中找到应用的关键字列表', }, { name: 'releaseNotes', type: 'string', rules: ['max length: 4000'], description: '自上次公开版本以来的更改', }, { name: 'promoText', type: 'string', rules: ['max length: 170'], description: '该应用的简短标语', }, { name: 'marketingUrl', type: 'string', rules: ['max length: 255'], description: '应用营销页面的 URL', }, { name: 'supportUrl', type: 'string', rules: ['max length: 255'], description: '应用支持页面的 URL', }, { name: 'privacyPolicyText', type: 'string', description: 'Apple TV 隐私政策' }, { name: 'privacyPolicyUrl', type: 'string', rules: ['max length: 255'], description: [ URL that links to your privacy policy., A privacy policy is required for all apps., ], }, { name: 'privacyChoicesUrl', type: 'string', rules: ['max length: 255'], description: '用户可以在其中修改和删除从应用收集的数据或决定如何使用和共享其数据的 URL。', }, ]} #### Apple info languages {[ { name: 'Arabic', description: 'ar-SA' }, { name: 'Catalan', description: 'ca' }, { name: 'Chinese', description: [ zh-Hans (Simplified) , zh-Hant (Traditional) , ], }, { name: 'Croatian', description: 'hr' }, { name: 'Czech', description: 'cs' }, { name: 'Danish', description: 'da' }, { name: 'Dutch', description: 'NL-NL' }, { name: 'English', description: [ en-AU (Australia) , en-CA (Canada) , en-GB (U.K.) , en-US (U.S.) , ], }, { name: 'Finnish', description: 'fi' }, { name: 'French', description: [ fr-CA (Canada) , fr-FR (France) , ], }, { name: 'German', description: '去 DE' }, { name: 'Greek', description: 'el' }, { name: 'Hebrew', description: 'he' }, { name: 'Hindi', description: 'hi' }, { name: 'Hungarian', description: 'hu' }, { name: 'Indonesian', description: 'id' }, { name: 'Italian', description: 'it' }, { name: 'Japanese', description: 'ja' }, { name: 'Korean', description: 'ko' }, { name: 'Malay', description: 'ms' }, { name: 'Norwegian', description: 'no' }, { name: 'Polish', description: 'pl' }, { name: 'Portuguese', description: [ pt-BR (Brazil) , pt-PT (Portugal) , ], }, { name: 'Romanian', description: 'ro' }, { name: 'Russian', description: 'ru' }, { name: 'Slovak', description: 'sk' }, { name: 'Spanish', description: [ es-MX (Mexico) , es-ES (Spain) , ], }, { name: 'Swedish', description: 'sv' }, { name: 'Thai', description: 'th' }, { name: 'Turkish', description: 'tr' }, { name: 'Ukrainian', description: 'uk' }, { name: 'Vietnamese', description: 'vi' }, ]} ### Apple release There are multiple strategies to put the app in the hands of your users. You can release the app automatically after store approval or gradually release an update to your users. Note: Automatic release after 25th of December, 2022 (UTC) --- ```json store.config.json { "configVersion": 0, "apple": { "release": { "automaticRelease": "2022-12-25T00:00:00+00:00" } } } ``` --- {[ { name: 'automaticRelease', type: 'boolean|Date', description: [ If and how the app should automatically be released after approval from the App Store. , false - Manually release the app after store approval. (default behavior) true - Automatically release after store approval. Date - Automatically schedule release on this date after store approval (using the{' '} RFC 3339 {' '} format). , Apple does not guarantee that your app is available at the chosen scheduled release date. , ], }, { name: 'phasedRelease', type: 'boolean', description: [ Phased release for automatic updates lets you gradually release this update over a 7-day period to users who have turned on automatic updates. , Keep in mind that this version will still be available to all users as a manual update from the App Store. , You can pause the phased release for up to 30 days or release this update to all users at any time. , Learn more , ], }, ]} ### Apple review Before publishing the app on the App Store, store approval is required. The App Store review team must have all the information to test your app, or you risk an app rejection. Note: Minimal review information --- ```json store.config.json { "configVersion": 0, "apple": { "review": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+1 123 456 7890" } } } ``` --- Note: Complete review information --- ```json store.config.json { "configVersion": 0, "apple": { "review": { "firstName": "John", "lastName": "Doe", "email": "john@example.com", "phone": "+1 123 456 7890", "demoUsername": "john", "demoPassword": "applereview", "demoRequired": false, "notes": "This is an example app primarily used for educational purposes." } } } ``` --- {[ { name: 'firstName', type: 'string', rules: ['min length: 1'], description: "The app contact's first name in case communication is needed with the App Store review team is needed.", }, { name: 'lastName', type: 'string', rules: ['min length: 1'], description: "The app contact's last name in case communication is needed with the App Store review team is needed.", }, { name: 'email', type: 'string', rules: ['email'], description: '电子邮件联系地址,以便需要与 App Store 审核团队进行沟通。', }, { name: 'phone', type: 'string', description: [ Contact phone number in case communication is needed with the App Store review team. , Preface the phone number with "+" followed by the country code. (for example, +44 844 209 0611) , ], }, { name: 'demoUsername', type: 'string', description: '用于登录你的应用以查看其功能的用户名。', }, { name: 'demoPassword', type: 'string', description: '用于登录你的应用以查看其功能的密码。', }, { name: 'demoRequired', type: 'boolean', description: [ A Boolean value indicates if sign-in information is required to review your app's features. , If users sign in using social media, provide information for an account for review. , Credentials must be valid and active for the duration of the review. , ], }, { name: 'notes', type: 'string', rules: ['length: 2..4000'], description: [ Additional information about your app that can help during the review process., Do not include demo account details in the notes. Use the demoUsername{' '} and demoPassword properties instead. , ], }, ]} ## EAS 元数据常见问题解答 有关 EAS 元数据的常见问题。 > **important** EAS 元数据处于预览阶段,可能会发生重大更改。 > > ¥**EAS Metadata** is in preview and subject to breaking changes. ## 投入 ¥Pitch 如果你要在商店中创建或维护应用,那么保持应用商店信息最新可能是一项艰巨的任务。当进行小的更改时,例如,支持新的应用商店区域,你必须在应用商店的仪表板中查找并填写表格。然后,在提供了所有必需的信息后,该内容仍然需要获得批准,批准和拒绝之间的区别可能就一个字。 ¥If you are creating or maintaining an app in the stores, keeping the app store information up to date can be a big task. When making small changes, for example, supporting a new app store region, you have to find and fill in the forms in an app store's dashboard. Then, after providing all the required information, that content still needs to be approved, where one word can be the difference between approval and rejection. EAS 元数据旨在使应用开发的这一阶段尽可能简单。使用简单的配置文件,你可以在不离开项目环境的情况下向商店提供所有信息。与内置验证相结合,你甚至可以在进行任何审核之前就获得有关你提供的内容的即时反馈。 ¥EAS Metadata aims to make this stage of app development as easy as possible. Using a simple configuration file, you can provide all the information to the stores without leaving your project environment. Combined with built-in validation, you get instant feedback about the content you provide, even before any review. ### 易于配置、更新或维护 ¥Easy to configure, update, or maintain 你可以从现有应用开始使用 [创建新的或生成存储配置](./getting-started.mdx#create-the-store-config) 的 EAS 元数据。此存储配置可让你快速更新应用商店信息,而无需离开项目环境。在将更改推送到应用商店之前,EAS 元数据会查找可能导致应用拒绝的常见陷阱。 ¥You can start using EAS Metadata by [creating a new or generating a store config](./getting-started.mdx#create-the-store-config) from an existing app. This store config lets you quickly update the app store information without leaving your project environment. Before pushing the changes to the app stores, EAS Metadata looks for common pitfalls that might result in an app rejection. ### 通过验证加快反馈循环 ¥Faster feedback loop with validation EAS 元数据带有内置验证,甚至在将任何内容发送到应用商店之前也是如此。此验证可帮助你更快地迭代信息,而无需开始审核。相反,你可以在提供所有内容并且未检测到问题时开始审核过程。 ¥EAS Metadata comes with built-in validation, even before anything is sent to the app stores. This validation helps you iterate faster over the information without starting a review. Instead, you can begin the review process when everything is provided, and no issues are detected. > 确保安装 [VS Code Expo 工具扩展](https://github.com/expo/vscode-expo#readme) 以获得 store.config.json 文件的自动补齐、建议和警告。 > > ¥Make sure to install the [VS Code Expo Tools extension](https://github.com/expo/vscode-expo#readme) to get auto-complete, suggestions, and warnings for **store.config.json** files. ### 可通过动态存储配置进行扩展 ¥Extensible with dynamic store config EAS Metadata 还支持更多的 [动态存储配置](./config.mdx#dynamic-store-config),而不是仅仅使用 JSON 文件。此动态存储配置允许你从其他地方(例如外部服务)收集信息。借助异步功能,可以不受限制地调整 EAS 元数据以适应你的首选工作流程。 ¥EAS Metadata also supports a more [dynamic store config](./config.mdx#dynamic-store-config), instead of only using JSON files. This dynamic store config allows you to gather information from other places like external services. With asynchronous functions, there are no limits to adapting EAS Metadata to suit your preferred workflow. ## 反投入 ¥Anti-pitch 以下是 EAS 元数据可能不适合项目的一些原因。 ¥Here are some reasons EAS Metadata might **not** be the right fit for a project. ### EAS 元数据支持 Google Play 商店吗? ¥Does EAS Metadata support the Google Play Store? 我们致力于 EAS 元数据,并将随着时间的推移扩展功能。这也意味着并非所有功能都在 EAS 元数据中实现。Google Play 商店是目前尚未实现的功能之一。有关所有现有功能,请参阅 [存储配置架构](./schema.mdx#config-schema)。 ¥We are committed to EAS Metadata and will expand functionality over time. This also means that not all functionality is implemented in EAS Metadata. The Google Play Store is one of those features currently not implemented. See the [store config schema](./schema.mdx#config-schema) for all existing functionality. ### 如何使用不受支持的应用商店功能? ¥How do I use unsupported app store features? EAS 元数据仅将数据从你的存储配置发送到应用商店。如果你需要 EAS 元数据尚未涵盖的功能,它不会阻止你使用应用商店仪表板。 ¥EAS Metadata only sends the data from your store config to the app stores. It does not block you from using the app store dashboards if you need a feature that EAS Metadata does not cover yet. 使用 EAS 元数据并在应用商店仪表板中编辑某些内容时,请确保在这些更改后运行 `eas metadata:pull`。如果不更新本地存储配置,EAS 元数据在推送到应用商店时可能会覆盖你的更改。 ¥When using EAS Metadata and editing something in the app store dashboards, make sure to run `eas metadata:pull` after these changes. Without updating your local store config, EAS Metadata might overwrite your changes when pushing to the app stores. ### 使用受限制的应用商店账户 ¥Using restricted app store accounts 你需要先通过应用商店进行身份验证,然后 EAS 元数据才能访问该信息。如果你使用的是大型公司账户,你可能无权使用 EAS 元数据的所有功能。虽然你可以在这些情况下使用 EAS 元数据,但由于安全限制,这通常更具挑战性。 ¥You'll need to authenticate with the app store before EAS Metadata can access the information. If you are working with a large corporate account, you might not have permission to use all functionality of EAS Metadata. While you can use EAS Metadata in these cases, it's often more challenging due to the security restrictions. # EAS Insights ## EAS Insights EAS Insights 简介,这是使用 expo-insights 库的项目的预览服务。 > **important** EAS Insights 处于预览阶段,可能会发生重大变化。在预览版中,它可以免费使用。 > > ¥**EAS Insights** is in preview and subject to breaking changes. While in preview, it is free to use. EAS Insights 是一项服务,可提供项目性能、使用情况和范围的视图。我们目前正在向所有开发者提供 Insights 预览版,并且我们将根据用户反馈和建议继续推出新特性和功能。 ¥**EAS Insights** is a service that will offer a view into a project's performance, usage, and reach. We are currently offering a preview of Insights that is available to all developers, and we will continue to roll out new features and functionality based on user feedback and suggestions. EAS Insights 可让你轻松查看应用的状态,提供有关跨平台使用情况、应用商店版本和时间范围的信息。 ¥EAS Insights makes it easy to see the state of your app, providing information about usage across platforms, app store versions, and timeframes. ## 与 EAS 更新集成 ¥Integration with EAS Update 如果你已经在使用 [EAS 更新](/eas-update/introduction/),我们将提供某些高级使用情况见解,而无需进行任何额外的客户端更改。这是可能的,因为通过将来自客户端请求的数据聚合来检查更新到有限的见解视图中,该视图显示随时间变化的使用情况以及按平台细分的使用情况。 ¥If you're already using [EAS Update](/eas-update/introduction/), we provide certain high-level usage insights without any additional client-side changes. This is possible because by aggregating data from client requests to check for an update into a limited Insights view that shows usage over time, as well as usage broken down by platform. ## 使用 `expo-insights` 库 ¥Use the `expo-insights` library 开发者可以将 `expo-insights` 库添加到他们的项目中,并获得更精确的使用指标(比仅聚合更新请求提供的指标)以及按应用商店版本划分的其他细分。目前,该库仅限于发送与应用冷启动相关的客户端事件,但将来我们将扩展 `expo-insights` 以提供新类型的事件和有效负载,以支持更高级的功能。 ¥Developers can add the `expo-insights` library to their projects and gain more precise usage metrics (than those provided by just aggregating update requests) and additional breakdowns by app store version. Currently, the library is limited to sending client events only pertaining to cold starts of the app, but in the future we will expand `expo-insights` to offer new types of events and payloads, to support more advanced functionality. ### 安装 ¥Installation 要使用 `expo-insights`,请确保你的应用通过运行 `eas init` 链接到 app.json / app.config.js 中的 EAS 项目,然后安装该库。 ¥To use the `expo-insights`, make sure your app is linked to your EAS project in **app.json** / **app.config.js** by running `eas init`, then install the library. ```sh $ npm i -g eas-cli $ eas init $ npx expo install expo-insights ``` 安装库后,使用 [EAS](/build/setup/) 或 [locally](/guides/local-app-development/) 创建构建。当应用启动时,库会自动将事件发送到 EAS Insights。 ¥After installing the library, create a build either with [EAS](/build/setup/) or [locally](/guides/local-app-development/). The library will automatically send events to EAS Insights when the app is launched. ### 查看见解 ¥View insights 要查看你应用的洞察数据,请在 EAS 仪表板中,转到项目列表,选择你的项目,然后从导航菜单中选择“洞察”。 ¥To view insights data of your app, in the EAS dashboard, go to projects list, select your project and then select **Insights** from the navigation menu. # 分发 ## 分配:概述 向应用商店或通过内部分发提交应用的概述。 通过将你的应用提交到应用商店或通过 [内部分发](/build/internal-distribution) 将其交到用户手中。 ¥Get your app into the hands of users by submitting it to the app stores or with [Internal Distribution](/build/internal-distribution). ```sh $ npm i -g eas-cli $ eas build --auto-submit $ eas submit ``` 你可以将 `eas build --auto-submit` 与 [EAS 命令行接口](/eas) 一起运行来构建你的应用,并自动上传二进制文件以在 Google Play 商店和 Apple App Store 上分发。 ¥You can run `eas build --auto-submit` with [EAS CLI](/eas) to build your app and automatically upload the binary for distribution on the Google Play Store and Apple App Store. 这会自动管理任何 React Native 应用的 Android 和 iOS 的所有原生代码签名。支付、通知、通用链接和 iCloud 等高级功能可以根据你的 [配置插件](/config-plugins/introduction/) 或原生权利自动启用,这意味着无需再与缓慢的门户进行斗争以正确设置库。 ¥This automatically manages **all native code signing** for Android and iOS for any React Native app. Advanced features such as payments, notifications, universal links, and iCloud can be automatically enabled based on your [config plugins](/config-plugins/introduction/) or native entitlements, meaning no more wrestling with slow portals to get libraries set up correctly. ### 开始使用 ¥Get started ## 应用商店最佳实践 了解向应用商店提交应用时的最佳实践。 本指南提供了将应用提交到应用商店的最佳实践。要了解如何生成原生二进制文件以供提交,请参阅 [创建你的第一个版本](/build/setup/)。 ¥This guide offers best practices for submitting your app to the app stores. To learn how to generate native binaries for submission, see [Create your first build](/build/setup/). > **warning** 免责声明:审查指南和规则经常更新,并且各种规则的执行有时可能不一致。无法保证你的特定项目会被任一平台接受,并且你最终对应用的行为负责。也就是说,你可以根据需要重新提交你的应用,以解决评论反馈。 > > ¥**Disclaimer:** Review guidelines and rules are updated frequently, and enforcement of various rules can sometimes be inconsistent. There is no guarantee that your particular project will be accepted by either platform, and you are ultimately responsible for your app's behavior. That said, you can re-submit your app as needed to address feedback from reviews. ## 响应式设计 ¥Responsive design 最好在具有小屏幕(例如 iPhone SE)和大屏幕(例如 iPhone X)的设备或模拟器上测试你的应用。确保你的组件按照你期望的方式渲染,没有按钮被阻止,并且所有文本字段都可以访问。 ¥It's a good idea to test your app on a device or simulator with a small screen (for example, an iPhone SE) and a large screen (for example, an iPhone X). Ensure your components render the way you expect, no buttons are blocked, and all text fields are accessible. 除了手机之外,还可以在平板电脑上试用你的应用。即使你配置了 `ios.supportsTablet: false`,你的应用仍将在 iPad 上以手机解析渲染并且必须可用。 ¥Try your app on tablets in addition to handsets. Even if you have `ios.supportsTablet: false` configured, your app will still render at phone resolution on iPads and must be usable. > **warning** 如果元素无法在 iPad 上正确渲染,Apple 可能会拒绝你的应用,即使你的应用不针对 iPad 外形规范。请务必在 iPad(或 iPad 模拟器)上测试你的应用。 > > ¥Apple may reject your app if elements don't render properly on an iPad, even if your app doesn't target the iPad form factor. Be sure and test your app on an iPad (or iPad simulator). ## 隐私政策 ¥Privacy policy 从 2018 年 10 月 3 日开始,所有新的 iOS 应用和应用更新都需要制定隐私政策才能通过应用商店审核指南。 ¥Starting October 3, 2018, all new iOS apps and app updates will be required to have a privacy policy to pass the App Store Review Guidelines. ### 应用隐私问题 ¥App privacy questions 从 2020 年 12 月 8 日开始,新应用提交和更新需要在 App Store Connect 中提供有关其隐私实践的信息。请参阅 [App Store 上的应用隐私详细信息](https://developer.apple.com/app-store/app-privacy-details/) 了解更多信息。 ¥Beginning December 8, 2020, new app submissions and updates are required to provide information about their privacy practices in App Store Connect. See [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/) for more information. 当你提交应用时,Apple 会询问你一系列问题。根据你使用的库,你的答案可能会有所不同。例如,如果你使用 `expo-updates`,你需要说是,我们从该应用收集数据,然后你需要选择崩溃数据。 ¥Apple will ask you a series of questions when you submit the app. Depending on which libraries you use, your answers may vary. For example, if you use `expo-updates`, you will need to say **Yes, we collect data from this app** and then you will want to select **Crash Data**. ## 应用传输 将应用的所有权转移到不同实体的概述。 将所有权移交给另一个实体时,需要考虑应用的两种不同表示形式:Expo 应用服务上存在的应用(用于使用 EAS Build 创建构建、使用 EAS Update 发送更新等)以及应用商店中的应用记录(用于将应用分发给终端用户)。以下指南解释了如何在每种情况下处理应用传输。 ¥There are two different representations of your app to consider when handing over ownership to another entity: the app as it exists on Expo Application Services (to create builds with EAS Build, send updates with EAS Update, and so on) and the app records on the app stores (to distribute the app to end-users). The following guides explain how to handle app transfers in each case. ## 通用链接 了解如何确定你的应用分发给用户时的实际大小,以及如何深入了解你的应用大小并对其进行优化。 开发者普遍关心的是他们的应用在应用商店中占用了多少空间。本指南将帮助你: ¥A common concern for developers is how much space their app takes up on the app store. This guide will help you: * 了解不同的构建工件用于什么 ¥Understand what different build artifacts are used for * 确定分发给用户时应用的实际大小 ¥Figure out the actual size of your app when distributed to users * 了解你的应用大小并对其进行优化 ¥Get insights into your app size and optimize it ## 为什么我的应用这么大? ¥Why is my app so big? 实际上,可能不是!在检查应用发布版本的结果时,不熟悉原生 Android 和 iOS 开发的开发者通常会对文件大小感到惊讶 — 通常,它比他们从应用商店下载应用时预期的要大得多。这不是你的应用的实际大小,它将在应用商店中分发!当人们谈论应用大小时,他们指的是他们将下载到设备的应用的大小,而不是将上传到应用商店或在开发和测试中共享的大小。 ¥**It probably isn't, actually!** When examining the resulting artifact of a release build for an app, it's common for developers who are unfamiliar with native Android and iOS development to be surprised by the file size — it's usually much larger than they would expect for an app if they were to download it from an app store. **This is not the actual size of your app, which will be distributed on app stores!** When people talk about app sizes, they mean the size of the app that they will download to their device, not the size that will be uploaded to app stores or shared in development and testing. 有各种类型的构建工件可用于不同的目的,它们几乎都比用户从商店下载你的应用时看到的大小要大。这是因为这些构建并未像从商店下载时那样针对特定设备进行优化,而是通常包含你的应用在各种设备上运行所需的所有代码和资源。 ¥There are various types of build artifacts that serve different purposes, and they are almost all larger than what users will see when they download your app from a store. This is because these builds are not optimized to target specific devices like they would when downloading from a store, but rather they typically include all of the code and resources that your app needs to run on a wide range of devices. ## Android 应用 ¥Android apps 你将与两种类型的 Android 构建工件交互:APK 和 AAB。 ¥There are two types of Android build artifacts that you will interact with: APKs and AABs. ### `.apk`(Android 包) ¥`.apk` (Android Package) 当你在 React Native 项目中使用 Gradle 构建 APK 时,默认行为是创建一个通用二进制文件,其中包含你的应用支持的所有不同设备类型的所有资源。例如,它包括每个屏幕尺寸、每个 CPU 架构和每种语言的资源,即使单个设备只需要其中一种。这意味着你可以与任何人共享此文件以直接安装到他们的设备上,也许直接使用 [Orbit](https://expo.dev/orbit) 或 `adb`,这样就可以了。 ¥When you build an APK with Gradle in a React Native project, the default behavior is to create a universal binary, which contains all the resources for all the different device types that your app supports. For example, it includes asset for every screen size, every CPU architecture, and every language, even though a single device will only need one of each. This means you can share this one file with anybody to install directly to their device, perhaps with [Orbit](https://expo.dev/orbit) or `adb` directly, and that will work. 当然,如果你正在运行一个为数百万用户提供服务的非常受欢迎的应用商店,你不想向每个用户发送相同的 50 MB 文件,特别是如果他们只使用 APK 中一小部分资源。这就是为什么 Google Play 商店和其他应用商店有一项名为 "应用包"(Android)的功能,允许你上传单个二进制文件,然后商店将根据每个用户的设备需求为他们生成自定义二进制文件。 ¥Of course, if you're running an incredibly popular app store that serves millions of users, you don't want to send the same 50 MB file to every single user, especially if they're only going to use a fraction of the resources in the APK. This is why the Google Play Store and other app stores have a feature called "App Bundles" (Android) that allows you to upload a single binary and then the store will generate a custom binary for each user based on their device's needs. ### `.aab`(Android 应用包) ¥`.aab` (Android App Bundle) 在 Android 上,提交到 Play Store 的所有新应用都必须作为 [Android App Bundle (.aab)](https://developer.android.com/platform/technology/app-bundle) 构建。将二进制文件提交到各自的商店后,你将能够看到各种设备类型的下载大小。 ¥On Android, all new apps submitted to the Play Store must be built as an [Android App Bundle (.aab)](https://developer.android.com/platform/technology/app-bundle). Once you have submitted the binaries to their respective stores, you will be able to see the download size for various device types. ### 确定 Android 应用下载和安装大小 ¥Determining Android app download and install size 通常,应用开发者关心 Play Store 上的 "下载大小"(用户下载应用时在商店列表中看到的内容)。这将是 Google Play 从你的 AAB 生成的 APK 的大小,该 APK 是根据用户的设备量身定制的。 ¥Typically, what app developers care about the "download size" on the Play Store (what the users see in the store listing when they go to download the app). This will be the size of the APK that Google Play generates from your AAB, which is tailored to the user's device. 查看最终将发送给用户的应用大小的唯一真正准确的方法是将你的应用上传到商店并在物理设备上下载。Google Play 还在你的开发者仪表板上提供了预期下载大小的可靠估计。你可以在 [Google Play 开发者控制台](https://play.google.com/console/) 上的 Android Vitals 中的应用大小页面下找到它。欲了解更多信息,请参阅 [优化应用大小并保持在 Google Play 应用大小限制内](https://support.google.com/googleplay/android-developer/answer/9859372?hl=en)。 ¥The only truly accurate way to see what your final app size will be shipped to users is to upload your app to the stores and download it on a physical device. Google Play also provides a reliable estimate for the expected download size on your developer dashboard. You can find this under the **App size** page in **Android vitals** on the [Google Play Developer Console](https://play.google.com/console/). For more information, see [Optimize your app’s size and stay within Google Play app size limits](https://support.google.com/googleplay/android-developer/answer/9859372?hl=en). Note: Why did my APK size increase after upgrading to React Native 0.73 and above? --- React Native 0.73 bumped the Android `minSdkVersion` to `23`. This had the side effect of changing the default value of [`extractNativeLibs`](https://developer.android.com/guide/topics/manifest/application-element#extractNativeLibs`) to `false`. > If set to `false`, your native libraries are stored uncompressed in the APK. Although your APK might be larger, your application loads faster because the libraries load directly from the APK at runtime. The following table shows that while the APK size increased, which may slightly impact download time for testers with [internal distribution](/build/internal-distribution/), the Google Play Store size remained the same. | SDK | APK (debug variant) | APK (release variant) | AAB | Google Play | | --- | ------------------- | --------------------- | ------- | ----------- | | 49 | 66 MB | 27.6 MB | 28.2 MB | 11.7 MB | | 50 | 168.1 MB | 62.1 MB | 27.4 MB | 11.7 MB | If you would like to revert to the previous behavior, you can set `useLegacyPackaging` to `true` in your **gradle.properties** or by using [`expo-build-properties`](/versions/latest/sdk/build-properties/). --- ## iOS 应用 ¥iOS apps App Store 上最小 React Native 应用(使用空白模板创建)[不到 4 MB](https://x.com/aleqsio/status/1844045829973344457) 的下载大小。 ¥The download size on the App Store of a minimal React Native app (created using the blank template) [is just under 4 MB](https://x.com/aleqsio/status/1844045829973344457). 你将与两种类型的 iOS 构建工件交互:APP 和 IPA。 ¥There are two types of iOS build artifacts that you will interact with: APPs and IPAs. ### `.app`(iOS 应用包) ¥`.app` (iOS application bundle) 这是你应用的实际应用包。当你下载并将应用的构建安装到 iOS 模拟器中时,你正在下载 `.app` 包。这些可以针对特定架构,也可以是通用二进制文件。`.app` 的大小不一定会告诉你太多有关你的应用在商店中的下载大小的信息。你无法将 `.app` 文件直接安装到物理 iOS 设备上。 ¥This is the actual application bundle for your app. When you download and install a build of your app into an iOS Simulator, you are downloading the `.app` bundle. These can either target specific architectures or be universal binaries. The size of your `.app` doesn't necessarily tell you too much about what the download size of your app will be on the store. You can't install a `.app` file directly to a physical iOS device. ### `.ipa`(iOS 应用商店包) ¥`.ipa` (iOS App Store Package) IPA 文件是 [ZIP](https://en.wikipedia.org/wiki/ZIP)) 文件,其中包含 `.app` 包和在 iOS 设备上运行应用所需的其他资源。它们用于各种类型的分发,包括 App Store、Ad Hoc、Enterprise 和 TestFlight。 ¥IPA files are [ZIP](https://en.wikipedia.org/wiki/ZIP)) files that include the `.app` bundle and other resources that are needed to run the app on an iOS device. They are used for various types of distribution, including App Store, Ad Hoc, Enterprise, and TestFlight. 它们包括安全和代码签名信息,例如配置文件和权利。App Store 将处理 IPA 文件并将其拆分为针对每种设备类型的较小二进制文件,因此 IPA 的大小也不代表应用的下载大小。 ¥They include security and code signing information, such as the provisioning profile and entitlements. The App Store will process the IPA file and split it into smaller binaries for each device type, so the size of the IPA also does not represent the download size of your app. ### 确定 iOS 应用下载和安装大小 ¥Determining iOS app download and install size 通常,应用开发者关心 App Store 上的 "下载大小"(用户下载应用时在商店列表中看到的内容)。这将是商店从你的通用 IPA 生成的拆分 IPA 的大小。 ¥Typically, app developers care about the "download size" on the App Store (what the users see in the store listing when they go to download the app). This will be the size of the split IPA generated by the store from your universal IPA. 查看最终将发送给用户的应用大小的唯一真正准确的方法是将你的应用上传到 App Store 并在物理设备上下载。你可以从 TestFlight 获得准确的估计值:在 [应用商店连接](https://appstoreconnect.apple.com/) 上,导航到 TestFlight 并通过单击版本号选择你的版本,然后切换到“构建元数据”选项卡并单击“应用文件大小”。你将看到根据设备类型估算的下载和安装大小列表。实际安装大小也可能因设备的 iOS 版本而略有不同。 ¥The only truly accurate way to see what your final app size will be shipped to users is to upload your app to the App Store and download it on a physical device. You can get accurate estimates from TestFlight: on [App Store Connect](https://appstoreconnect.apple.com/), navigate to TestFlight and select your build by clicking on the build number, then switch to the **Build Metadata** tab and click **App File Sizes**. You will see a list of estimated download and install sizes depending on the device type. Actually install sizes may also vary slightly depending on the iOS version of the device. ## 优化应用大小 ¥Optimizing app size 当你向应用添加功能时,你将添加代码、库和资源,这可能会增加其大小。如果应用大小对你和你的用户很重要,你可能需要定期检查大小并对其进行优化。以下部分将帮助你了解可以采取哪些措施来优化应用的几个方面。 ¥As you add features to your app, you will add code, libraries, and assets, which may increase its size. If app size is important to you and your users, you may want to routinely review the size and optimize it. The following sections will help you understand what you can do to optimize several aspects of your app. ### 支持富有表现力的面向对象 API ¥Static assets 应用大小膨胀的最常见来源之一是资源,例如字体、图标、图片、视频和声音。这些可以来自你直接导入代码的资源,以及 JavaScript 和原生库。你无法通过查看应用资源目录来获得完整的图片。 ¥One of the most common sources of app size bloat is assets, such as fonts, icons, images, videos, and sounds. These can come from the assets that you import directly into your code, as well as JavaScript and native libraries. You won't be able to get a complete picture by reviewing your app assets directory. 首先检查构建工件以确定其中包含哪些资源。 ¥Start by examining a build artifact to determine what assets are included in it. * 对于 Android,你可以使用 [Android APK 分析器](https://developer.android.com/studio/debug/apk-analyzer) 或 [apktool](https://apktool.org/) 检查应用的内容 ¥For Android, you can use [Android APK Analyzer](https://developer.android.com/studio/debug/apk-analyzer) or [apktool](https://apktool.org/) to inspect the contents of your app * 对于 iOS,将 IPA 文件从 `app.ipa` 重命名为 `app.zip` 并将其提取以检查内容,使用 macOS 实用程序 `assetutil` 检查 Assets.car。 ¥For iOS, rename an IPA file from `app.ipa` to `app.zip` and extract it to examine the contents, using the macOS utility `assetutil` to inspect **Assets.car**. ### JavaScript 包大小 ¥JavaScript bundle size 要分析 JavaScript 包,[使用 Expo Atlas](/guides/analyzing-bundles/)。你可能会发现你认为非常小的库实际上对打包包有很大影响,或者你在停止使用后忘记删除某个库,等等。 ¥To analyze JavaScript bundles, [use Expo Atlas](/guides/analyzing-bundles/). You may find libraries that you thought were very small actually have a large impact on the bundle, or that you forgot to remove a library after you stopped using it, and so on. ### 平台特定优化 ¥Platform-specific optimizations 独立于 React Native 和 Expo,你可以使用以下工具优化 Android 和 iOS 应用: ¥Independent of React Native and Expo, you can optimize your app for Android and iOS by using the following tools: # 参考 ## Webhook 了解如何配置 webhook 以获取有关 EAS 构建和提交完成的警报。 一旦你的构建或提交完成,EAS 就会通过 Webhook 向你发出提醒。需要为每个项目配置 Webhook。例如,如果你想在每个目录中收到 `@johndoe/awesomeApp` 和 `@johndoe/coolApp` 的警报,请运行以下命令: ¥EAS can alert you as soon as your build or submission is completed via a webhook. Webhooks need to be configured per project. For example, if you want to be alerted for both `@johndoe/awesomeApp` and `@johndoe/coolApp`, in each directory, run the command: ```sh $ eas webhook:create ``` 运行后,系统将提示你选择 Webhook 事件类型(除非你提供 `--event BUILD|SUBMIT` 参数)。接下来,提供处理 HTTP POST 请求的 Webhook URL(或使用 `--url` 标志指定)。此外,如果你尚未提供 `--secret` 标志,则必须输入 Webhook 签名密钥。它的长度必须至少为 16 个字符,并将用于计算我们作为 `expo-signature` HTTP 标头的值发送的请求正文的签名。你可以使用 [用于验证 Webhook 请求的签名](#webhook-server) 是正品。 ¥After running it, you'll be prompted to choose the webhook event type (unless you provide the `--event BUILD|SUBMIT` parameter). Next, provide the webhook URL (or specify it with the `--url` flag) that handles HTTP POST requests. Additionally, you'll have to input a webhook signing secret, if you have not already provided it with the `--secret` flag. It must be at least 16 characters long, and it will be used to calculate the signature of the request body which we send as the value of the `expo-signature` HTTP header. You can use the [signature to verify a webhook request](#webhook-server) is genuine. EAS 使用 HTTP POST 请求调用你的 Webhook。所有数据都在请求正文中传递。EAS 将数据作为 JSON 对象发送。如果 webhook 响应的 HTTP 状态代码超出 200-399 范围,则将尝试以指数回退方式多次传送。 ¥EAS calls your webhook using an HTTP POST request. All the data is passed in the request body. EAS sends the data as a JSON object. If the webhook responds with an HTTP status code outside of the 200-399 range, delivery will be attempted a few more times with exponential back-off. 此外,我们还发送带有有效负载哈希签名的 `expo-signature` HTTP 标头。你可以使用此签名来验证请求的真实性。签名是请求正文的十六进制编码的 HMAC-SHA1 摘要,使用你的 Webhook 密钥作为 HMAC 密钥。 ¥Additionally, we send an `expo-signature` HTTP header with the hash signature of the payload. You can use this signature to verify the authenticity of the request. The signature is a hex-encoded HMAC-SHA1 digest of the request body, using your webhook secret as the HMAC key. > 如果你想在本地测试上述 Webhook,你可以使用 [ngrok](https://ngrok.com/docs) 等服务通过隧道转发 `localhost:8080`,并通过 `ngrok` 提供的 URL 公开访问它。 > > ¥If you want to test the above webhook locally, you can use a service such as [ngrok](https://ngrok.com/docs) to forward `localhost:8080` via a tunnel and make it publicly accessible with the URL `ngrok` gives you. 你始终可以通过运行命令来更改你的 webhook URL 和/或 webhook 密钥: ¥You can always change your webhook URL and/or webhook secret by running command: ```sh $ eas webhook:update --id WEBHOOK_ID ``` 你可以通过运行以下命令找到 webhook ID: ¥You can find the webhook ID by running the command: ```sh $ eas webhook:list ``` 如果你希望我们停止向你的 Webhook 发送请求,请运行以下命令并从列表中选择 Webhook: ¥If you want us to stop sending requests to your webhook, run the command below and choose the webhook from the list: ```sh $ eas webhook:delete ``` ## Webhook 负载 ¥Webhook payload Note: Build webhook payload --- The build webhook payload may look as the example below: ```json { "id": "147a3212-49fd-446f-b4e3-a6519acf264a", "accountName": "dsokal", "projectName": "example", "buildDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/147a3212-49fd-446f-b4e3-a6519acf264a", "parentBuildId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for build retries "appId": "bc0a82de-65a5-4497-ad86-54ff1f53edf7", "initiatingUserId": "d1041496-1a59-423a-8caf-479bb978203a", "cancelingUserId": null, // available for canceled builds "platform": "android", // or "ios" "status": "errored", // or: "finished", "canceled" "artifacts": { "buildUrl": "https://expo.dev/artifacts/eas/wyodu9tua2ZuKKiaJ1Nbkn.aab", // available for successful builds "logsS3KeyPrefix": "production/f9609423-5072-4ea2-a0a5-c345eedf2c2a" }, "metadata": { "appName": "example", "username": "dsokal", "workflow": "managed", "appVersion": "1.0.2", "appBuildVersion": "123", "cliVersion": "0.37.0", "sdkVersion": "41.0.0", "buildProfile": "production", "distribution": "store", "appIdentifier": "com.expo.example", "gitCommitHash": "564b61ebdd403d28b5dc616a12ce160b91585b5b", "gitCommitMessage": "Add home screen", "runtimeVersion": "1.0.2", "channel": "default", // available for EAS Update "releaseChannel": "default", // available for legacy updates "reactNativeVersion": "0.60.0", "trackingContext": { "platform": "android", "account_id": "7c34cbf1-efd4-4964-84a1-c13ed297aaf9", "dev_client": false, "project_id": "bc0a82de-65a5-4497-ad86-54ff1f53edf7", "tracking_id": "a3fdefa7-d129-42f2-9432-912050ab0f10", "project_type": "managed", "dev_client_version": "0.6.2" }, "credentialsSource": "remote", "isGitWorkingTreeDirty": false, "message": "release build", // message attached to the build "runFromCI": false }, "metrics": { "memory": 895070208, "buildEndTimestamp": 1637747861168, "totalDiskReadBytes": 692224, "buildStartTimestamp": 1637747834445, "totalDiskWriteBytes": 14409728, "cpuActiveMilliseconds": 12117.540078, "buildEnqueuedTimestamp": 1637747792476, "totalNetworkEgressBytes": 355352, "totalNetworkIngressBytes": 78781667 }, // available for failed builds "error": { "message": "Unknown error. Please see logs.", "errorCode": "UNKNOWN_ERROR" }, "createdAt": "2021-11-24T09:53:01.155Z", "enqueuedAt": "2021-11-24T09:53:01.155Z", "provisioningStartedAt": "2021-11-24T09:54:01.155Z", "workerStartedAt": "2021-11-24T09:54:11.155Z", "completedAt": "2021-11-24T09:57:42.715Z", "updatedAt": "2021-11-24T09:57:42.715Z", "expirationDate": "2021-12-24T09:53:01.155Z", "priority": "high", // or: "normal", "low" "resourceClass": "android-n2-1.3-12", "actualResourceClass": "android-n2-1.3-12", "maxRetryTimeMinutes": 3600 // max retry time for failed/canceled builds } ``` --- Note: Submit webhook payload --- The submit webhook payload may look as the example below: ```json { "id": "0374430d-7776-44ad-be7d-8513629adc54", "accountName": "dsokal", "projectName": "example", "submissionDetailsPageUrl": "https://expo.dev/accounts/dsokal/projects/example/builds/0374430d-7776-44ad-be7d-8513629adc54", "parentSubmissionId": "75ac0be7-0d90-46d5-80ec-9423fa0aaa6b", // available for submission retries "appId": "23c0e405-d282-4399-b280-5689c3e1ea85", "archiveUrl": "http://archive.url/abc.apk", "initiatingUserId": "7bee4c21-3eaa-4011-a0fd-3678b6537f47", "cancelingUserId": null, // available for canceled submissions "turtleBuildId": "8c84111e-6d39-449c-9895-071d85fd3e61", // available when submitting a build from EAS "platform": "android", // or "ios" "status": "errored", // or: "finished", "canceled" "submissionInfo": { // available for failed submissions "error": { "message": "Android version code needs to be updated", "errorCode": "SUBMISSION_SERVICE_ANDROID_OLD_VERSION_CODE_ERROR" }, "logsUrl": "https://submission-service-logs.s3-us-west-1.amazonaws.com/production/submission_728aa20b-f7a9-4da7-9b64-39911d427b19.txt" }, "createdAt": "2021-11-24T10:15:32.822Z", "updatedAt": "2021-11-24T10:17:32.822Z", "completedAt": "2021-11-24T10:17:32.822Z", "maxRetryTimeMinutes": 3600 // max retry time for failed/canceled submissions } ``` --- ## Webhook 服务器 ¥Webhook server 以下是如何实现服务器的示例: ¥Here's an example of how you can implement your server: ```js server.js const crypto = require('crypto'); const express = require('express'); const bodyParser = require('body-parser'); const safeCompare = require('safe-compare'); const app = express(); app.use(bodyParser.text({ type: '*/*' })); app.post('/webhook', (req, res) => { const expoSignature = req.headers['expo-signature']; // process.env.SECRET_WEBHOOK_KEY has to match SECRET value set with `eas webhook:create` command const hmac = crypto.createHmac('sha1', process.env.SECRET_WEBHOOK_KEY); hmac.update(req.body); const hash = `sha1=${hmac.digest('hex')}`; if (!safeCompare(expoSignature, hash)) { res.status(500).send("Signatures didn't match!"); } else { // Do something here. For example, send a notification to Slack! // console.log(req.body); res.send('OK!'); } }); app.listen(8080, () => console.log('Listening on port 8080')); ``` # Expo 账户 ## 账户类型 了解不同类型的 Expo 账户以及如何使用它们。 Expo 账户是一个保存 Expo 项目并允许不同数量的协作的容器。Expo 账户有两种类型:个人和组织。 ¥An Expo account is a container that holds Expo projects and allows for different amounts of collaboration. There are two types of Expo accounts: **Personal**, and **Organization**. 你选择放置新项目的账户类型取决于项目的性质。如果你希望协作或为你的开发团队设置工作流程,请始终创建一个组织账户。对于个人或业余项目,个人账户就足够了。 ¥The type of account you choose to put a new project depends on the nature of the project. If you are looking to collaborate or set up a workflow for your development team, always create an Organization account. For personal or hobby projects, a Personal account is sufficient. ## 个人账户 ¥Personal accounts 当你使用 Expo 进行 [注册新账号](https://expo.dev/signup) 时,会自动为你创建个人账户。该账户是你处理个人项目的好地方。 ¥When you [sign up for an account](https://expo.dev/signup) with Expo, a Personal account is automatically created for you. This account is a good place to work on your personal projects. > **warning** 不要因任何原因与任何人共享你的个人账户的身份验证凭据。 > > ¥Do not share authentication credentials for your Personal account with anyone for any reason. ## 组织机构 ¥Organizations 组织账户最适合用于保存你希望与公司或开发者团队的其他成员共享的项目。它充当一个共享容器,你的团队可以在其中协作处理一个或多个项目并有权访问共享凭据。 ¥An Organization account is best used to hold projects that you wish to share with other members of a company or a group of developers. It serves as a shared container where your team can collaborate on one or multiple projects and have access to shared credentials. 你可以邀请其他成员加入你的组织账户,然后为这些成员授予组织内访问权限级别的不同角色。欲了解更多信息,请参阅 [管理访问中的角色权限](#manage-access)。 ¥You can invite other members to your Organization account, and then give these members different roles that grant a level of access within the organization. For more information, see [role privileges in Manage access](#manage-access). 在以下情况下,创建组织账户很有用: ¥Creating an organization account is useful when: * 你认为你将来可能需要转移对该组织项目的控制权。 ¥You think you may need to transfer control of that Organization's projects in the future. * 与一组协作者共享一个或多个项目。 ¥Sharing one or multiple projects with a team of collaborators. * 需要分配多个 [所有者](/accounts/account-types/#manage-access)。 ¥More than one [Owner](/accounts/account-types/#manage-access) needs to be assigned. * 费用需要隔离。 ¥Expenses need to be isolated. * 通过为组织的每个成员分配角色来授予不同级别的访问权限。 ¥Granting different levels of access by assigning a role to each member of the organization. * 针对不同背景构建项目。例如,当为不同的客户工作时,可能会为每个客户创建一个新的组织。 ¥Structuring projects for different contexts. For example, when working for different clients, a new organization may be created for each client. * 共享 [电子防盗系统订阅](/eas/)。 ¥Sharing an [EAS Subscription](/eas/). ### 创建新组织 ¥Create a new Organization 如果你登录到个人账户,则可以从仪表板创建一个新组织: ¥If you are logged in to your Personal account, you can create a new Organization from the dashboard: * 在导航菜单中选择你账户的用户名以打开下拉菜单。 ¥Select your account's username in the navigation menu to open the dropdown menu. * 在下拉菜单中的“组织”下选择“创建组织”。 ¥Select **Create Organization** under Organizations in the dropdown menu. * 为你的组织添加名称,然后选择“创建”按钮。 ¥Add a name for your Organization and select the **Create** button. 创建新组织后,你将被重定向到该组织的新仪表板页面。要将新项目与组织关联,你必须将 `expo` 键下的 [`owner` 密钥](/versions/latest/config/app/#owner) 添加到项目的 app.json 中。 ¥After creating a new Organization, you are redirected to the new dashboard page for the organization. To associate a new project with the Organization, you have to add the [`owner` key](/versions/latest/config/app/#owner) under the `expo` key to your project's **app.json**. ### 将个人账户转换为组织 ¥Convert a Personal account into an Organization 当你想要与其他成员共享项目访问权限并为每个成员分配基于角色的权限时,你可以将个人账户转换为组织账户。 ¥You can convert your Personal account into an Organization when you want to share access to projects with other members and assign each member a role-based privilege. 从你的个人账户的用户设置中,转到 [将你的账户转换为组织](https://expo.dev/settings#convert-account) 部分以开始该过程。 ¥From the **User settings** of your Personal account, go to [Convert your account into an organization](https://expo.dev/settings#convert-account) section to start the process. 当你经历此过程时,我们会非常小心地确保你和你的用户依赖的所有功能将继续按预期工作: ¥When you are going through this process, we take a lot of care to make sure that all of the functionality that you and your users rely on will continue to work as expected: * 你可以继续向用户提供更新和推送通知。 ¥You can continue to deliver updates and push notifications to your users. * 你仍然可以使用 Expo 服务器上存储的任何 Android 或 iOS 凭据。 ¥You can still use any Android or iOS credentials stored on Expo's servers. * 使用你的个人访问令牌或网络钩子的任何集成都将继续运行并转移给新的指定所有者。 ¥Any integrations using your personal access token or webhooks will continue to operate and are transferred to the new designated owner. * 你的 EAS 订阅将继续而不会中断。 ¥Your EAS subscription will continue without interruption. * 你的生产应用将继续运行而不会中断。 ¥Your production apps will continue to operate without interruption. ### 邀请成员 ¥Invite a member 可以邀请其他 Expo 用户加入你的组织。邀请新成员: ¥Other Expo users can be invited to join your Organization. To invite a new member: * 在 EAS 仪表板的“组织”设置下导航到 [**成员**](https://expo.dev/settings/members)。 ¥Navigate to [**Members**](https://expo.dev/settings/members) under **Organization settings** in the EAS dashboard. * 单击“邀请”按钮。这将打开一个邀请成员加入组织的表格。 ¥Click the button **Invite**. This will open a form to invite a member to the organization. * 在表单中,输入你要邀请的用户的电子邮件,并选择他们在加入组织后应具有的角色。欲了解更多信息,请参阅 [管理访问中的角色权限](#manage-access)。 ¥In the form, enter the email of the user you want to invite and select the role they should have upon joining the organization. For more information, see [role privileges in Manage access](#manage-access). 邀请新成员时,请记住: ¥When inviting a new member, keep in mind: * 只有具有所有者或管理员角色的成员才能邀请其他人。 ¥Only members with an Owner or an Admin role can invite others. * 具有所有者角色的成员可以向成员和受邀者授予任何角色。 ¥Members with an Owner role can grant members and invitees any role. * 具有管理员角色的成员只能授予成员和被邀请者最高级别(包括管理员角色)(除所有者之外的所有角色)。 ¥Members with an Admin role can only give members and invitees up to and including Admin role (every role but Owner). ### 更改成员角色 ¥Change the role of a member 要更改成员的角色权限,请确保你拥有 [所有者或管理员角色](/accounts/account-types/#manage-access) 并按照以下步骤操作: ¥To change the role privileges of a member, make sure you have either an [**Owner** or **Admin** role](/accounts/account-types/#manage-access) and follow the steps below: * 在 EAS 仪表板的“组织”设置下导航到 [**成员**](https://expo.dev/settings/members)。 ¥Navigate to [**Members**](https://expo.dev/settings/members) under **Organization settings** in the EAS dashboard. * 在你要更改其角色的成员旁边,单击三点菜单图标并更改角色。 ¥Next to the member whose role you want to change, click on the three-dotted menu icon and change the role. ### 删除成员 ¥Remove a member 要删除成员,请确保你拥有 [所有者或管理员角色](/accounts/account-types/#manage-access) 并按照以下步骤操作: ¥To remove a member, make sure you have either an [**Owner** or **Admin** role](/accounts/account-types/#manage-access) and follow the steps below: * 在 EAS 仪表板的“组织”设置下导航到 [**成员**](https://expo.dev/settings/members)。 ¥Navigate to [**Members**](https://expo.dev/settings/members) under **Organization settings** in the EAS dashboard. * 在要删除的成员旁边,单击三点菜单图标。 ¥Next to the member you want to remove, click on the three-dotted menu icon. * 单击删除成员。 ¥Click **Remove member**. ### 重命名账户 ¥Rename an account 账户可以重命名有限次数。只有所有者才能重命名账户。要重命名账户,请访问组织设置 > [**概述**](https://expo.dev/accounts/\[account]/settings) 并按照 [**重命名账户**](https://expo.dev/accounts/\[account]/settings#rename-account) 下的步骤操作。 ¥Accounts can be renamed a limited number of times. Only Owners can rename accounts. To rename an account, visit **Organization settings** > [**Overview**](https://expo.dev/accounts/\[account]/settings) and follow the steps under [**Rename account**](https://expo.dev/accounts/\[account]/settings#rename-account). ### 在账户之间转移项目 ¥Transfer projects between accounts 项目可以转移有限次数。用户必须是源账户和目标账户的所有者或管理员才能在它们之间传输项目。访问“[**项目设置**](https://expo.dev/accounts/\[account]/projects/\[project]/settings)”>“常规”,并按照“转移项目”下的步骤操作。 ¥Projects can be transferred a limited number of times. A user must be an Owner or Admin on both source and destination accounts to transfer projects between them. Visit [**Project settings**](https://expo.dev/accounts/\[account]/projects/\[project]/settings) > **General** and follow the steps under **Transfer project**. #### 注意事项 ¥Caveats > 如果你希望将项目的所有权从你的个人或组织账户(源)转移到另一个人或公司(目标),并且你不允许在目标账户上使用 "所有者" 或 "行政" 权限,你可以创建一个托管账户(新的组织账户)。这解决了用户必须是源账户上的 "所有者" 和目标账户上的 "所有者" 或 "行政" 才能在它们之间转移项目的问题。创建托管账户后,你可以向最终目标账户成员授予托管账户的所有者角色,并将项目安全地转移到托管账户。然后,接收人或公司可以将其从托管账户转移到其目标账户,而无需访问目标账户本身。 > > ¥If you want to transfer the ownership of a project from your Personal or Organization account (source) to another person or company (destination), and you are not allowed "Owner" or "Admin" permissions on the destination account, you can create an escrow account (a new Organization account). This solves the problem that a user must be an "Owner" on the source account and either an "Owner" or "Admin" on the destination account to transfer projects between them. Once the escrow account is created, you can grant the ultimate destination account member the Owner role on the escrow account and safely transfer the project to the escrow account. The receiving person or company can then transfer it to their destination account from the escrow account without having had access to the destination account itself. ### 管理访问权限 ¥Manage access 成员的访问权限通过基于角色的系统进行管理。用户可以在组织账户中拥有所有者、管理员、开发者或查看者角色。 ¥Access for members is managed through a role-based system. Users can have the *owner*, *admin*, *developer*, or *viewer* roles within an Organization account. | 角色 | 描述 | | ------- | ------------------------------------------ | | **所有者** | 可以对账户或任何项目执行任何操作,包括删除它们。 | | **行政** | 可以控制你账户上的大多数设置,包括注册付费服务、更改其他用户的权限以及管理编程访问。 | | **开发商** | 可以创建新项目、进行新构建、发布更新和管理凭据。 | | **观众** | 只能通过 Expo Go 查看你的项目,但不能以任何方式修改你的项目。 | ### 安全活动 ¥Security activity 无法使用 EAS Hosting 部署密钥。它包括对密码、电子邮件和 2FA 身份验证设置的更改等。 ¥Security activity is a list of changes that happened to an account's profile. It includes changes to password, email, and 2FA authentication setup, among others. 可以在概述 > [**用户设置**](https://expo.dev/settings) 下找到它。 ¥It can be found under **Overview** > [**User settings**](https://expo.dev/settings). ## 双重身份验证 了解如何利用双重身份验证 (2FA) 来保护你的 Expo 账户。 登录 expo.dev、Expo Go 应用和命令行工具时,双重身份验证提供了额外的安全层。启用双重身份验证后,除了用户名和密码之外,你还需要提供短期代码才能访问你的账户。 ¥Two-factor authentication provides an extra layer of security when logging in to expo.dev, the Expo Go app, and command line tools. With two-factor authentication enabled, you will need to provide a short-lived code in addition to your username and password to access your account. ## 启用双重身份验证 (2FA) ¥Enable two-factor authentication (2FA) 你可以从 [个人账户设置](https://expo.dev/settings#two-factor-auth) 启用双重身份验证。 ¥You can enable two-factor authentication from your [personal account settings](https://expo.dev/settings#two-factor-auth). ## 双重身份验证方法 ¥Two-factor authentication methods 你可以通过身份验证器应用接收 2FA 代码。 ¥You can receive 2FA codes through an authenticator app. ### 身份验证器应用 ¥Authenticator apps Expo 接受任何支持基于时间的一次性密码 (TOTP) 的验证器应用,包括: ¥Expo accepts any authenticator app that supports Time-based One-time Passwords (TOTP) including: * [最后一次通过验证器](https://lastpass.com/auth/) ¥[Last Pass Authenticator](https://lastpass.com/auth/) * [奥蒂](https://authy.com/) ¥[Authy](https://authy.com/) * [1 密码](https://support.1password.com/one-time-passwords/) ¥[1Password](https://support.1password.com/one-time-passwords/) * [谷歌身份验证器](https://support.google.com/accounts/answer/1066447) ¥[Google Authenticator](https://support.google.com/accounts/answer/1066447) * [微软身份验证器](https://www.microsoft.com/en-us/account/authenticator) ¥[Microsoft Authenticator](https://www.microsoft.com/en-us/account/authenticator) Expo 将提供一个二维码,以便在设置过程中使用你的身份验证器应用进行扫描。该应用将提供进入 Expo 的确认码。输入代码以通过身份验证器应用完成激活 2FA。 ¥Expo will provide a QR code to scan with your authenticator app during setup. The app will provide a confirmation code to enter on Expo. Enter the code to finish activating 2FA via your authenticator app. ### 短信 ¥SMS messages > **warning** 已弃用:新增的双重身份验证方法不再支持短信。现有的短信双重身份验证方法将继续有效,但我们建议切换到身份验证器应用,因为它提供了更好的安全性。 > > ¥**Deprecated:** SMS is no longer supported for newly-added two-factor authentication methods. Existing SMS two-factor authentication methods will continue to work, though we suggest switching to an authenticator app as it provides better security. 提供手机号码以通过短信接收短期令牌。通过短信收到的验证码的有效期至少为 10 分钟,因此你可能会在此时间内多次收到相同的验证码。如果你将短信设备设置为默认 2FA 方法,则每当你执行需要 2FA 代码的操作时,都会自动向你发送验证码。 ¥Provide a mobile phone number to receive a short-lived token via SMS. Codes received via SMS will be valid for at least 10 minutes, so you may receive the same code multiple times within this window. If you set an SMS device as your default 2FA method, you will be sent a verification code automatically whenever you take an action that requires a 2FA code. ### 恢复代码 ¥Recovery codes 当你为账户设置双重身份验证时,你将收到一组恢复代码。如果你无法访问身份验证器应用或短信设备,可以使用这些代码代替一次性密码。请记住,每个恢复代码仅适用于一次。 ¥When you set up two-factor authentication for your account, you'll receive a set of recovery codes. These codes can be used instead of a one-time password if you lose access to your authenticator app or SMS device. Keep in mind that each recovery code is only valid for one use. 如果你在创建恢复代码时选择了下载恢复代码的选项,则可以在标记为 expo-recovery-codes.txt 的文件中找到它们。 ¥If you selected the option to download your recovery codes at the time they were created, you can locate them in a file labeled as **expo-recovery-codes.txt**. > 将你的恢复代码存储在安全且难忘的地方,以确保只有你才能访问你的账户! > > ¥Store your recovery codes in a secure and memorable place to ensure you, and only you can access your account! ## 更改你的双重设置 ¥Change your two-factor settings 你可以从 [个人账户设置](https://expo.dev/settings) 更改双重设置。你可以: ¥You can make changes to your two-factor settings from your [personal account settings](https://expo.dev/settings). You can: * 添加或删除身份验证方法 ¥add or remove authentication methods * 设置你的默认方法 ¥set your default method * 重新生成你的恢复代码 ¥regenerate your recovery codes * 为你的账户禁用双重身份验证 ¥disable two-factor authentication for your account 你需要提供一次性密码才能对 2FA 设置进行任何更改。 ¥You will need to provide a one-time password to make any changes to your 2FA settings. ## 恢复你的账户 ¥Recover your account ### 恢复代码 ¥Recovery codes 当你设置账户以使用 2FA 时,Expo 会为你提供恢复代码列表。如果你丢失设备,可以使用恢复代码代替一次性密码。每个代码只能使用一次。你可以从 [个人账户设置](https://expo.dev/settings/) 重新生成恢复代码,这将使所有现有代码失效。 ¥When you set up your account to use 2FA, Expo provides you with a list of recovery codes. In the event you lose your device(s), a recovery code may be used in place of a one-time password. Each of these codes may only be used once. You may regenerate your recovery codes, which will invalidate any existing codes, from your [personal account settings](https://expo.dev/settings/). ### 二次 2FA 方法 ¥Secondary 2FA methods 通过设置与不同物理设备关联的多种身份验证方法,你可以确保在设备重置或丢失时你不会失去对账户的访问权限。 ¥By setting up multiple authentication methods associated with different physical devices, you can ensure you will not lose access to your account in the event a device is reset or lost. ### 手动恢复 ¥Manual recovery 如果你无法通过任何提供的方法访问你的账户,你可以通过与你的账户关联的电子邮件向 Expo 支持人员发送电子邮件。不幸的是,我们无法保证在这种情况下能够恢复你对账户的访问权限。 ¥If you cannot access your account through any of the supplied methods, you may email Expo support from the email associated with your account. Unfortunately, we cannot guarantee we will be able to restore your access to your account in this scenario. ## 程序化访问 了解访问令牌的类型以及如何使用它们。 在设置 CI 或编写脚本来帮助管理你的项目时,我们建议避免使用你的用户名和密码进行身份验证。有了这些凭据,任何人都可以登录并使用你的账户。 ¥When setting up CI or writing a script to help manage your projects, we recommend avoiding using your username and password to authenticate. With these credentials, anyone will be able to log in and use your account. 你可以生成令牌,而不是提供凭据,从而允许你单独管理每个集成点。有权访问这些令牌的任何人都可以对你的账户执行操作。像对待用户密码一样小心对待它们。如果发生泄露,你可以撤销这些令牌以阻止访问。 ¥Instead of providing credentials, you can generate tokens that will allow you to manage each integration point separately. Anyone who has access to these tokens will be able to perform actions against your account. Treat them with the same care as a user password. In case something is leaked, you can revoke these tokens to block access. ## 个人访问令牌 ¥Personal access tokens 你可以从仪表板上的 [访问令牌](https://expo.dev/settings/access-tokens) 创建个人访问令牌。拥有此令牌的任何人都可以代表你执行操作。这适用于你个人账户上的所有内容,以及你被授予访问权限的任何个人账户或组织。 ¥You can create Personal access tokens from the [Access tokens](https://expo.dev/settings/access-tokens) on your dashboard. Anyone with this token can perform actions on your behalf. That applies to all content on your Personal Account, as well as any Personal Accounts or Organizations that you have been granted access to. ## 机器人用户和访问令牌 ¥Robot users and access tokens 账户可以创建机器人用户以对该账户拥有的资源执行操作。可以为机器人用户分配 [一名角色](/accounts/account-types/#manage-access) 以限制他们有权执行的操作。机器人用户无法登录任何 Expo 产品,自己无法拥有任何项目,只能通过访问令牌进行身份验证。 ¥Accounts can create Robot users to take actions on resources owned by the Account. Bot Users can be assigned [a role](/accounts/account-types/#manage-access) to limit the actions they are authorized to perform. Bot users cannot sign in to any Expo products, cannot own any projects themselves, and can only authenticate via an access token. ## 访问令牌的使用 ¥Access tokens usage 你可以使用你创建的任何令牌来通过 EAS CLI 执行操作。要使用令牌,你需要在运行命令之前定义一个环境变量,例如 `EXPO_TOKEN="token"`。 ¥You can use any tokens you have created to perform actions with the EAS CLI. To use tokens, you need to define an environment variable, like `EXPO_TOKEN="token"`, before running commands. 设置 `EXPO_TOKEN` 环境变量后,你可以运行任何使用令牌进行身份验证的 EAS CLI 命令,而无需运行 `eas login` 命令。`eas login` 命令仅用于用户名和密码身份验证。如果配置了用户名和密码,则 `EXPO_TOKEN` 身份验证方法优先于用户名和密码。 ¥Once you set the `EXPO_TOKEN` environment variable, you can run any EAS CLI command authenticated with the token without running the `eas login` command. The `eas login` command is only used for username and password authentication. The `EXPO_TOKEN` auth method takes precedence over the username and password if both are configured. 例如,一旦你获得令牌,你就可以运行以下 EAS CLI 命令来触发构建: ¥For example, once you obtain a token, you can run the following EAS CLI command to trigger a build: ```sh $ EXPO_TOKEN=my_token eas build ``` 如果你使用的是 GitHub Actions,[你可以配置 `token` 属性](https://github.com/expo/expo-github-action#configuration-options) 会将此环境变量包含在所有作业步骤中。 ¥If you are using GitHub Actions, [you can configure the `token` property](https://github.com/expo/expo-github-action#configuration-options) to include this environment variable in all the job steps. 访问令牌有用的常见情况: ¥Common situations where access tokens are useful: * 从 CI 发布或构建,无需提供你的 Expo 用户名和密码 ¥Publish or build from CI without providing your Expo username and password * 更新令牌以尽可能保证其安全;无需重置密码并退出所有会话 ¥Renew a token to keep it as secure as possible; no need to reset your password and sign out of all sessions * 授予某人(或脚本)一次性访问你的项目的有限权限 ¥Give someone (or a script) one-time access to your project with limited permissions ## 撤销访问令牌 ¥Revoke access tokens 如果令牌意外泄露,你可以撤销它,而无需更改用户名和密码。当你撤销访问令牌时,你将阻止使用此令牌对你的账户进行的所有访问。为此,请转到仪表板上的 [访问令牌页面](https://expo.dev/settings/access-tokens) 并删除要撤销的令牌。 ¥In case a token is accidentally leaked, you can revoke it without changing your username and password. When you revoke the access token, you block all access to your account using this token. To do this, go to the [Access Token page](https://expo.dev/settings/access-tokens) on your dashboard and delete the token you want to revoke. ## 单点登录 (SSO) 了解你的企业组织如何使用你的身份提供者来管理团队中的 Expo 用户。 [企业计划](https://expo.dev/pricing) 客户可以使用单点登录 (SSO)。 ¥Single Sign-On (SSO) is available for [Enterprise plan](https://expo.dev/pricing) customers. 要开始,请为 Expo SSO 准备你的身份提供者 (IdP),并按照下面的 [IdP 配置指南](/#identity-provider-support) 收集信息。完成此操作后,你组织的所有者可以按照指示执行 [启用 SSO](#setting-up-sso-on-an-organization)。 ¥To get started, prepare your identity provider (IdP) for Expo SSO and gather information by following the [configuration guide for your IdP](/#identity-provider-support) below. Once you have done this, an owner of your Organization can follow instructions to [enable SSO](#setting-up-sso-on-an-organization). 如果你有疑问或问题,请联系 [联系我们](https://expo.dev/contact),我们将帮助你设置组织。 ¥If you have questions or issues, [contact us](https://expo.dev/contact) and we'll help you set up your organization. ## 身份提供商支持 ¥Identity provider support Expo SSO 支持以下身份提供商: ¥Expo SSO supports the following identity providers: | 身份提供商 | 资源 | | -------------------------------------------------------------------------------- | -------------------------------------------- | | [奥克塔](https://www.okta.com/) | [配置指南](https://expo.fyi/sso-setup-okta) | | [OneLogin](https://www.onelogin.com/) | [配置指南](https://expo.fyi/sso-setup-onelogin) | | [微软 Entra ID](https://www.microsoft.com/en-us/security/business/microsoft-entra) | [配置指南](https://expo.fyi/sso-setup-microsoft) | | [Google 工作区](https://www.google.com/) | [配置指南](https://expo.fyi/sso-setup-google-ws) | 我们实现 [OpenID 连接发现 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) 规范,并正在努力验证其他兼容的身份提供商。如果你使用其他身份提供商并且对 SSO、[让我们知道](https://expo.dev/contact) 感兴趣。 ¥We implement the [OpenID Connect Discovery 1.0](https://openid.net/specs/openid-connect-discovery-1_0.html) specification and are working to verify additional compatible identity providers. If you use another identity provider and are interested in SSO, [let us know](https://expo.dev/contact). ## 在组织上设置 SSO ¥Setting up SSO on an organization > 组织账户必须至少维护一个具有所有者角色的非单点登录用户。此用户需要进行初始 SSO 设置,并确保在 SSO 配置发生更改或停止使用 SSO 时,能够不间断地访问你的组织。 > > ¥Organization accounts must maintain at least one non-SSO user with the Owner role. This user is needed for the initial SSO setup and to ensure uninterrupted access to your organization if your SSO configuration changes or if you discontinue the use of SSO. Step 1: Log in as the Organization account owner. In your account's EAS dashboard, go to **Settings** > **Organization settings** > **Create SSO configuration for account**. Step 2: On **Create SSO configuration for account**, click the **Start** button. Step 3: Enter the configuration details for your IdP using the information you collected during the IdP setup: - Client ID - Client secret - IdP subdomain/tenant ID, if needed. Click the **?** icon above the Issuer field for help with what to enter. Step 4: Click **Create SSO Configuration**. Step 5: The **Organization settings** > **Overview** page will now display an **Update SSO configuration** option. Use this option to update the client secret if it changes. ## SSO user sign in ### Expo website Step 1: Navigate to [expo.dev/sso-login](https://expo.dev/sso-login) and enter the account name of your organization. You can create a link that pre-fills the organization name. For example, [expo.dev/sso-login/test-org](https://expo.dev/sso-login/test-org) pre-fills `test-org`. Step 2: Log in to your identity provider (IdP). Step 3: You'll be prompted to select an Expo username. This will be the username for your Expo account. ### Expo CLI When using the Expo CLI, you can run the following command to log in to your Expo account. ```sh $ npx expo login --sso ``` You will be prompted to log in via the Expo website in a browser and will be redirected back to the CLI upon completion. ### EAS CLI When using the EAS CLI, you can run the following command to log in to your Expo account. ```sh $ eas login --sso ``` You will be prompted to log in via the Expo website in a browser and will be redirected back to the CLI upon completion. ### Expo Go Step 1: Click the **Continue with SSO** button on the sign-in page when going through the sign-in flow. Step 2: Follow the [above steps](#expo-website) to sign in to the Expo website. ## SSO user restrictions SSO users are like regular users. However, there are a few known exceptions: - SSO users can only belong to their SSO organization. They also cannot create additional organizations. - SSO users cannot leave their SSO organization. Doing so deletes their SSO user. - SSO users cannot log in to the Expo forums. - SSO users cannot subscribe to EAS for their personal accounts. ## SSO administration Both new organizations and existing organizations can enable SSO as a sign in option. Organizations with existing non-SSO members can enable SSO and then direct new members to the SSO sign-in page, while existing users continue to use their current Expo credentials. To support external contributors, SSO-enabled organizations also allow inviting additional non-SSO users via email. ### Transitioning existing users to SSO Regular users may be a member of one or many personal, team, and organization accounts while SSO users belong exclusively to their organization account. Thus, existing users cannot be directly converted into SSO users. However, a regular user who's already a member of your organization may create a second user by going to the [SSO login page](https://expo.dev/sso-login). Then, their regular user can be removed from the organization. To transition from using a regular Expo account to an SSO account, follow these steps: Step 1: Check if you're already logged in at [expo.dev](https://expo.dev). If so, log out. Step 2: Go to the [SSO login page](https://expo.dev/sso-login) and follow the prompts, such as entering your organization name, creating a new Expo username, and logging in to your identity provider. Step 3: By default, your new SSO user will have the View Only role. If you need a different role, ask an Admin or Owner to update your role in [**Members**](https://expo.dev/accounts/[account]/settings/members) settings. Step 4: Run `eas login --sso` to switch to your new account on the CLI. Step 5: At this time, the Admin or Owner can remove your old user from the organization. In [**Members**](https://expo.dev/accounts/[account]/settings/members) settings, the list of organization members indicates whether a user is an SSO or non-SSO user. The Admin or Owner can click the dropdown next to the old user and click **Remove member**. Step 6: If you no longer need your old user account, log out of your new SSO account, then log in to your old account and go to [**User settings**](https://expo.dev/settings). Scroll down and click **Delete Account**. **Note that this will delete any projects under your old user account.** It will not affect any projects owned by the organization. > If you wish to reuse your old username on your new SSO user account, you can go to [**User settings**](https://expo.dev/settings) under your old user and rename it before creating your SSO account. Alternatively, you can rename your SSO user account's Expo username after deleting your old user. While Expo usernames need to be unique, it is OK if your email address on your identity provider matches the email address of your old user. ### Remove SSO users If someone has left your organization, remove or disable them in your IdP. Depending on the token refresh duration you configured with your IdP, the removed user will subsequently lose access to their Expo account. If you wish to remove them ahead of that time or you wish to remove them to clean up users on your account, you may do so on the organization **Members** settings page: Step 1: Navigate to your [organization account **Members** settings](https://expo.dev/accounts/[account]/settings/members). Click the dropdown next to the member you wish to delete, and click **Delete SSO user**. > **warning** This will delete their personal account and all data associated with it. All data in your organization account will remain unaffected. ### 更改计费或停止使用 SSO ¥Change billing or discontinue use of SSO 需要有效的企业计划才能继续使用 SSO。[联系我们](https://expo.dev/contact) 如果你希望停止使用 SSO 或更改你的计划。 ¥An active Enterprise Plan is required to continue using SSO. [Contact us](https://expo.dev/contact) if you wish to discontinue the use of SSO or change your plan. 为了确保无论是否启用 SSO,都能不间断地访问你的组织,SSO 组织必须保留至少一名具有所有者角色的非 SSO 用户作为成员。 ¥To ensure uninterrupted access to your organization whether or not SSO is enabled, SSO organizations must keep at least one non-SSO user with the Owner role as a member. ### 删除 SSO 组织 ¥Delete SSO organization 为组织配置 SSO 后,Expo 团队必须手动完成账户删除。[联系我们](https://expo.dev/contact) 寻求帮助。 ¥Once SSO is configured for an organization, account deletion must be done manually by the Expo team. [Contact us](https://expo.dev/contact) for assistance. ## 审计日志 了解如何使用审计日志跟踪和分析你账户的活动。 > **info** 审计日志可供 [企业计划](https://expo.dev/pricing) 客户使用。 > > ¥Audit logs are available for [Enterprise plan](https://expo.dev/pricing) customers. 审计日志记录账户使用 Expo 应用服务 (EAS) 执行的操作。记录的数据包括有关受影响实体的信息、对它们所做的修改类型、执行操作的人员以及活动发生的时间。 ¥Audit logs record actions made with Expo Application Services (EAS) by accounts. Recorded data includes information about the affected entities, the type of modification made to them, who performed the action, and when the activity occurred. ## 关键点 ¥Key points * 审计日志只能创建,绝不能修改或删除,它们可作为事实来源,帮助监控事件并调试账户内发生的问题。 ¥Audit logs can only be created and never modified or deleted, they serve as a source of truth to help monitor events and debug issues occurring within accounts. * 审计日志可供企业计划客户使用。订阅后,Expo 内部使用的某些日志可立即使用,而其他类型的日志将在订阅激活后开始收集。 ¥**Audit logs are available to Enterprise plan customers**. When subscribed, some of the logs used internally by Expo are immediately available, while other types of logs are starting to be collected after the subscription is activated. * 审计日志存储 1.5 年。如果删除账户,其审计日志将在 90 天后删除。 ¥Audit logs are stored for 1.5 years. If an account is deleted, its audit logs will be deleted after 90 days. * 要访问它们,请转到“账户设置/组织设置”>“[**审计日志**](https://expo.dev/accounts/\[account]/settings/audit-logs)”。 ¥To access them, go to **Account settings**/**Organization settings** > [**Audit logs**](https://expo.dev/accounts/\[account]/settings/audit-logs). ## 用例 ¥Use cases ### 权限监控 ¥Permission monitoring 审计日志可以跟踪组织内的用户邀请和权限更改。一个示例安全事件可能包括一个被入侵的员工账户,该账户邀请攻击者进入组织并将其权限更改为 [行政](/accounts/account-types/#manage-access)。 ¥Audit logs can track user invitations and permission changes within your organization. An example security event could include a compromised employee account that invites an attacker into an organization and changes their permission to [Admin](/accounts/account-types/#manage-access). 在这种情况下,审计日志将记录哪个员工账户邀请了攻击者并修改了权限。由于审计日志是不可变的,攻击者将无法删除此记录的历史记录。其他组织成员将能够查看审计日志以确定哪个账户被盗用,采取行动撤销攻击者的权限并保护员工的账户。 ¥In this scenario, audit logs would record which employee account invited the attacker and modified permissions. Since audit logs are immutable, the attacker would not be able to delete this recorded history. Other organization members will be able to review the audit logs to determine which account was compromised, take action to revoke the attacker's permissions and secure the employee's account. ### 访问历史记录 ¥Access history Expo 组织账户可以包括许多项目,其中开发访问权限由分配给各个团队的分发证书控制。当设备被允许加入这些团队时,跟踪何时授予和删除访问权限以保留历史记录非常重要。虽然设备目前可能不包含在 Apple 团队中,但在发生内部安全事件时查看谁以前有权访问该团队可能会很有用。 ¥An Expo organization account can include many projects where development access is controlled by distribution certificates assigned to individual teams. When devices are granted to join these teams, it is important to track when access is granted and removed for historical record keeping. While a device may not currently be included in an Apple team, it may be useful to see who previously had access to the team in the event of an internal security incident. Expo 团队设置中列出的 Apple 设备将仅显示当前注册到账户的设备,但通过创建审计日志,可以查看 Apple 团队和设备的历史修改。 ¥The Apple devices listed within the Expo team's settings will only show devices that are currently registered to an account, but with the creation of audit logs, historical modifications of Apple teams and devices can be viewed. ## 审计日志实体 ¥Audit log entities 虽然我们正在努力在未来添加更多实体,但以下实体已经启用: ¥While we are working on adding more entities in future, the following entities are already enabled: * 账户 ¥Account * 账户订阅 ¥Account subscription * Android 应用凭据 ¥Android App Credentials * Android 密钥库 ¥Android Keystore * App Store Connect API 密钥 ¥App Store Connect API key * Apple 设备 ¥Apple Device * Apple 分发证书 ¥Apple Distribution Certificate * Apple 配置文件 ¥Apple Provisioning Profile * 苹果团队 ¥Apple Team * EAS 主机别名 ¥EAS Hosting Alias * EAS 主机自定义域名 ¥EAS Hosting Custom Domain * EAS 主机部署 ¥EAS Hosting Deployment * EAS 更新分支 ¥EAS Update Branch * EAS 更新渠道 ¥EAS Update Channel * Google 服务账户密钥 ¥Google Service Account key * iOS 应用凭据 ¥iOS App Credentials * LogRocket 组织 ¥LogRocket Organization * LogRocket 项目 ¥LogRocket Project * 组织 SSO 配置 ¥Organization SSO Configuration * 项目 ¥Project * 用户邀请 ¥User Invitation * 用户权限 ¥User Permission * 工作流程 ¥Workflow * 工作流程修订 ¥Workflow Revision ### 结构 ¥Structure 审计日志条目包括以下字段: ¥Audit log entries include the following fields: | 字段 | 描述 | | ---- | ------------------------------------------- | | 参与者 | 执行特定操作的账户参与者。 | | 实体类型 | 使用以下修改类型之一修改的对象:`CREATE`、`UPDATE`、`DELETE`。 | | 操作类型 | 修改类型:`CREATE`、`UPDATE`、`DELETE`。 | | 消息 | 包含基于操作的信息。 | | 创建时间 | 执行特定操作的时间。 | 此外,单击审计日志行,你可以查看与该日志相关的元数据。 ¥Additionally, clicking on an Audit log row, you can view the metadata relevant to that log. ## 导出 ¥Export * 审计日志可供企业计划客户使用。订阅后,Expo 内部使用的某些日志可立即使用,而其他类型的日志将在订阅激活后收集。 ¥**Audit logs are available to Enterprise plan customers**. When subscribed, some of the logs used internally by Expo are immediately available, while other types of logs will be collected after the subscription is activated. 导出时间范围最长为 30 天。导出的文件将包括审计日志页面上显示的所有字段,但消息字段除外。 ¥Export is available with a time range of up to 30 days. The exported file will include all the fields shown on the Audit logs page except for the **Message** field. # 计费 ## 计费:概述 有关计费和订阅的信息概述,用于管理你的 EAS 账户的计划、发票、收据、付款和使用情况。 Expo 通过 Expo 应用服务 (EAS) 为集成云服务提供各种订阅计划。你可以在账户仪表板的“账单”和“收据”页面上管理和跟踪发票、付款、计划和其他与账单相关的信息。只有账户所有者和管理员才能访问此页面。 ¥Expo provides various subscription plans for integrated cloud services through Expo Application Services (EAS). You can manage and track invoices, payments, plans, and other billing-related information on the **Billing and Receipts** pages in your account's dashboard. Only account Owners and Admins have access to this page. 请参阅下面的资源列表,了解有关计费和订阅的不同方面的更多信息: ¥See our list of resources below to learn more about different aspects of billing and subscriptions: ## 计划 ¥Plans ## 管理账单 ¥Manage billing ## 基于使用情况的定价 ¥Usage-based pricing ## 常见问题 (FAQ) ¥Frequently Asked Questions (FAQs) ## 订阅、计划和附加组件 有关可用的 Expo 应用服务 (EAS) 计划及其工作原理、基于使用量的定价和附加组件的深入指南。 [Expo 应用服务(EAS)](/eas/) 为 [EAS 构建](/build/introduction/) 上的有限数量的低优先级构建提供 [免费访问](https://expo.dev/eas/fair-use#commercial-usage),并为 [EAS 更新](/eas-update/introduction/) 提供免费更新。这些限制每月重置。 ¥[Expo Application Services (EAS)](/eas/) offers [free access](https://expo.dev/eas/fair-use#commercial-usage) to a limited quantity of low-priority builds on [EAS Build](/build/introduction/) and free updates with [EAS Update](/eas-update/introduction/). These limits reset monthly. 除了免费计划外,还有不同的订阅计划来满足不同的客户及其需求。每个付费计划都提供积分,以便你优先构建 EAS Build,并通过增加月活跃用户和额外带宽来更广泛地访问 EAS Update。我们还提供补充订阅的附加组件,并启用选择加入功能以满足客户需求。 ¥Beyond the Free plan, there are different subscription plans to cater to various customers and their needs. Each paid plan offers credits to enable priority builds for EAS Build and broader access to EAS Update through more monthly active users and extra bandwidth. We also offer add-ons that complement subscriptions and enable opt-in features to amplify customer needs. 本页列出了不同的基于订阅的计划和可用的附加组件。 ¥This page lists different subscription-based plans and available add-ons. ## 订阅 ¥Subscriptions 订阅按月计费,全球价格相同(税前)。要查看你账户当前订阅的套餐,请转到 [你账户的计费](https://expo.dev/settings/billing),在“当前套餐”下,你将找到当前套餐的详细信息。 ¥Subscriptions are billed monthly and are priced the same worldwide (pre-tax). To see your account's current subscribed plan, go to [your account's Billing](https://expo.dev/settings/billing), and under **Current Plan**, you will find details of your current plan. 你也可以随时取消订阅。请参阅 [取消计划](/billing/manage/#cancel-a-plan) 了解更多信息。 ¥You can also cancel a subscription at any time. See [Cancel a plan](/billing/manage/#cancel-a-plan) for more information. 我们还根据需要提供年度合同。联系我们的 [客户支持](https://expo.dev/contact) 团队,看看年度计划是否适合你。 ¥We also offer annual contracts on an as-needed basis. Contact our [customer support](https://expo.dev/contact) team to see if an annual plan suits you. ## 计划 ¥Plans 每个计划都有特定的限制。但是,订阅者可以超越它们并使用 [基于使用情况的计费](/billing/usage-based-pricing/) 支付额外使用费用。 ¥Each plan has specific limits. However, subscribers can exceed them and pay for additional usage with [usage-based billing](/billing/usage-based-pricing/). ### 生产 ¥Production 生产计划专为专业开发者和小型企业设计。订阅此计划可让你访问: ¥The Production plan is designed for professional developers and small businesses. Subscribing to this plan gets you access to: * 可靠的生产级服务 ¥Reliable, production-grade services * EAS Build 高优先级构建的每月 [credits](/billing/usage-based-pricing/) ¥Monthly [credits](/billing/usage-based-pricing/) for high-priority builds for EAS Build * EAS 更新的更多唯一用户、更高的带宽和存储限制 ¥More unique users, higher bandwidth, and storage limit for EAS Update 如果你超出限制或用完了每月信用额度,任何进一步的使用都将按 [基于使用情况的价格](/billing/usage-based-pricing/) 收费。 ¥If you exceed the limits or use up your monthly credits, any further usage will be charged at [usage-based prices](/billing/usage-based-pricing/). ### 企业 ¥Enterprise 企业计划专为拥有大型项目并需要额外资源(如专用支持)的组织和企业而设计。订阅此计划可让你访问: ¥The Enterprise plan is designed for organizations and enterprises that have large projects and require additional resources such as dedicated support. Subscribing to this plan gets you access to: * 可靠的生产级服务 ¥Reliable, production-grade services * EAS Build 高优先级构建的每月 [credits](/billing/usage-based-pricing/) ¥Monthly [credits](/billing/usage-based-pricing/) for high-priority builds for EAS Build * EAS 更新的更多唯一用户、更高的带宽和存储限制 ¥More unique users, higher bandwidth, and storage limit for EAS Update 企业计划为 EAS Build 提供比任何其他计划更高的每月信用额度,为 EAS Update 提供更高的带宽。 ¥An Enterprise plan offers much higher monthly credits for EAS Build and bandwidth for EAS Update than any other plan. 如果你超出限制或用完了每月信用额度,任何进一步的使用都将按 [基于使用情况的价格](/billing/usage-based-pricing/) 收费。 ¥If you exceed the limits or use up your monthly credits, any further usage will be charged at [usage-based prices](/billing/usage-based-pricing/). ### 启动器 ¥Starter Starter 计划适用于准备发布实际应用的开发者。每月 19 美元,即可获得 30 美元的构建积分,用于优先构建。 ¥The Starter plan is meant for developers ready to launch real-world apps. For $19 per month, you get $30 of build credit to use for priority builds. 订阅 Starter 计划可让你访问: ¥Subscribing to the Starter plan gets you access to: * 可靠的生产级服务 ¥Reliable, production-grade services * EAS Build 高优先级构建的每月 [credits](/billing/usage-based-pricing/) ¥Monthly [credits](/billing/usage-based-pricing/) for high-priority builds for EAS Build * 能够超出 EAS 更新免费计划的限制并支付额外使用费用 ¥The ability to exceed the limits of the Free plan for EAS Update and pay for additional usage 如果你超出限制或用完了每月信用额度,任何进一步的使用都将按 [基于使用情况的价格](/billing/usage-based-pricing/) 收费。 ¥If you exceed the limits or use up your monthly credits, any further usage will be charged at [usage-based prices](/billing/usage-based-pricing/). ## 基于使用情况的计费 ¥Usage-based billing 基于使用情况的计费适用于超出其计划限制的客户。它使你能够使用我们的服务而不必担心限制或任何合同义务。 ¥Usage-based billing is applied to customers who exceed their plan limits. It enables you to use our services without worrying about limitations or any contractual obligations. 基于使用情况的计费按月计费,目前已为 EAS Build 和 EAS Update 启用。我们提供你现有使用情况的估计值以及 [你账户的计费](https://expo.dev/settings/billing) 上的任何超额费用。 ¥Usage-based billing is billed monthly and is currently enabled for EAS Build and EAS Update. We provide an estimate of your existing usage and any overage charges on [your account's Billing](https://expo.dev/settings/billing). ## 插件 ¥Add-ons ### 企业支持 ¥Enterprise Support 企业支持附加组件仅适用于新的企业计划订阅者,视供应情况而定。主要功能包括: ¥The Enterprise Support add-on is only available for new Enterprise plan subscribers, subject to availability. Key features include: * 从我们的专家那里获得专业的长期支持 ¥Receiving professional, long-term support from our experts * 通过服务级别协议 (SLA) 直接支持沟通渠道 ¥Direct communication channel support with a Service-Level Agreement (SLA) * 专门的客户经理 ¥A dedicated account manager ## 管理计划和账单 了解如何更新、降级或取消 Expo 账户的计划并管理账单详情。 EAS 仪表板中的账单会显示你账户当前订阅的套餐和每月使用情况的信息。它还允许你管理你的计划和账单详细信息。 ¥**Billing** in the EAS dashboard provides information about your account's currently subscribed plan and monthly usage. It also allows you to manage your plan and billing details. 本指南介绍如何管理你账户的计划和账单信息。 ¥This guide explains how to manage your account's plans and billing information. ## 管理计划 ¥Manage plans ### 查看当前计划 ¥View the current plan For For Organization accounts: * 从组织设置下的导航菜单中单击 [计费](https://expo.dev/settings/billing)。 ¥Click [Billing](https://expo.dev/settings/billing) from the navigation menu under **Organization settings**. * 在“当前方案”下,你可以查看你账户的当前方案。 ¥Under **Current Plan**, you can see the current plan for your account. 例如,组织账户订阅了以下免费计划: ¥For example, an Organization account is subscribed to the Free plan below: For For Personal accounts: * 从账户设置下的导航菜单中单击 [计费](https://expo.dev/settings/billing)。 ¥Click [Billing](https://expo.dev/settings/billing) from the navigation menu under **Account settings**. * 在“当前方案”下,你可以查看你账户的当前方案。 ¥Under **Current Plan**, you can see the current plan for your account. 例如,个人账户订阅了以下免费计划: ¥For example, a Personal account is subscribed to the Free plan below: ### 升级到新计划 ¥Upgrade to a new plan 要升级到其他计划: ¥To upgrade to a different plan: * 点击 EAS 信息中心导航菜单中的“[计费](https://expo.dev/settings/billing)”。 ¥Click [Billing](https://expo.dev/settings/billing) from the navigation menu in EAS dashboard. * 在“当前套餐”下,如果你已经是付费套餐,请点击“更改套餐”。如果你使用的是免费套餐,请点击“查看套餐”>“选择你的账户”。它会打开升级计划弹出窗口。 ¥Under **Current Plan**, click **Change Plan** if you are already on a paid plan. If you are on the Free plan, click **See Plans** > **Select your account**. It opens the **Upgrade plan** popup. * 在“升级方案”下,你可以查看所有可用方案的列表。选择你要升级到的计划,然后点击相应计划下的“升级”按钮。 ¥Under **Upgrade plan**, you can see a list of all available plans. Choose the plan you want to upgrade to and click the **Upgrade** button under the desired plan. * 在结帐时,系统会要求你输入电子邮件、卡详细信息和账单地址。添加这些详细信息后,单击立即付款以订阅新计划。 ¥On **Checkout**, you are asked to enter your email, card details, and billing address. After adding these details, click **Pay Now** to subscribe to the new plan. > 注意:[入门计划](/billing/plans/#starter) 每月费用为 19 美元,包含 30 美元的构建积分。 > > ¥**Note**: The [Starter plan](/billing/plans/#starter) costs $19 per month and includes $30 of build credit. ### 降级计划 ¥Downgrade a plan 如果你使用的是生产、企业或旧版套餐,则可以降级到入门套餐。 ¥If you are on a Production, Enterprise, or Legacy plan, you can downgrade to the Starter plan. 降级到入门套餐将在当前计费周期结束后生效。 ¥Downgrading to the Starter plan takes effect after your current billing period ends. 要降级,请转到 [计费](https://expo.dev/settings/billing) 并按照以下步骤操作: ¥To downgrade, go to [Billing](https://expo.dev/settings/billing) and follow the steps below: * 在“当前方案”下,点击“更改方案”。 ¥Under **Current Plan**, click **Change Plan**. * 在“选择账户”下,从下拉菜单中选择你想要降级的账户。 ¥Under **Select account**, select the account from the dropdown menu you want to downgrade * 在“升级方案”>“入门方案”下,点击“更改”。 ¥Under **Upgrade plan** > **Starter plan**, click **Change**. * 将显示一个确认对话框。点击“完成”。 ¥A confirmation dialog will be displayed. Click **Done**. * 确认你的账户将计划降级到 Starter 后,相同的信息也会显示在“账单”>“即将推出的计划”下。 ¥After confirming your account for a plan downgrade to Starter, the same information is also reflected under **Billing** > **Upcoming Plan**. ### 取消计划 ¥Cancel a plan For From Production or Enterprise plan: 取消生产或企业计划将在你当前的结算期结束后生效。 ¥Cancellation from a Production or Enterprise plan takes effect after your current billing period ends. 要取消你的套餐,请在 [计费](https://expo.dev/settings/billing) 上的“取消所有订阅”下,然后点击“继续到 Stripe”,按照当前套餐的取消流程操作。 ¥To cancel your plan, on [Billing](https://expo.dev/settings/billing), under **Cancel all subscriptions** and then click **Continue to Stripe** to follow the process of your current plan's cancellation. For From Starter to Free plan: 取消入门计划将立即生效,并将你账户的配额重置为免费计划。 ¥Cancellation for the Starter plan takes effect immediately and resets your account's quota to the Free plan. 要取消你的套餐,请在 [计费](https://expo.dev/settings/billing) 上的“取消所有订阅”下,然后点击“立即取消”。 ¥To cancel your plan, on [Billing](https://expo.dev/settings/billing), under **Cancel all subscriptions** and then click **Cancel Immediately**. > 注意:如果你取消了入门套餐,则需要支付当前计费周期内产生的任何使用费用。 > > ¥**Note**: If you unsubscribe from a **Starter plan,** you will be charged for any usage incurred during the current billing period. ## 管理账单信息 ¥Manage billing information 你可以管理与账单相关的详细信息,例如名称、电子邮件、地址和付款信息,或添加税号。所有这些信息都提到了你收到的订阅计划的 [每月发票](/billing/invoices-and-receipts/) 上。 ¥You can manage your billing-related details such as name, email, address, and payment information, or add a tax ID. All of this information is mentioned on the [monthly invoice](/billing/invoices-and-receipts/) you receive for the subscribed plan. ### 更新账单名称、电子邮件或地址 ¥Update Billing name, email, or address 要更新你的账单名称、电子邮件或地址: ¥To update your billing name, email, or address: * 在 [计费](https://expo.dev/settings/billing) 上,点击“管理账单信息”。这将打开 Stripe 的门户,你可以在其中查看付款方式、账单信息、发票历史记录并更新你的账单信息。然后,单击更新信息。 ¥On [Billing](https://expo.dev/settings/billing), click **Manage billing information**. This will open Stripe's portal where you can view payment methods, billing information, invoicing history, and update your billing information. Then, click **Update information**. * 通过输入你的新名称、电子邮件或地址来更新你的账单详细信息,然后单击保存。 ¥Update your billing details by entering your new name, email or address, then click **Save**. ### 税号 ¥Tax ID 要添加或更新你的账单税号: ¥To add or update your billing tax ID: * 在 [计费](https://expo.dev/settings/billing) 上,点击“管理账单信息”。这将打开 Stripe 的门户,你可以在其中查看和更新​​你的账单信息。然后,单击更新信息。 ¥On [Billing](https://expo.dev/settings/billing), click **Manage billing information**. This will open Stripe's portal where you can view and update your billing information. Then, click **Update information**. * 在“税号”下,选择 ID 类型,输入你的有效税号,然后单击“保存”。 ¥Under **Tax ID**, select the ID type, enter your valid tax ID, and click **Save**. ### 付款方式 ¥Payment method 要添加新的付款方式信息: ¥To add a new payment method information: * 在 [计费](https://expo.dev/settings/billing) 上,点击“管理账单信息”。这将打开 Stripe 的门户,你可以在其中查看和更新​​你的账单信息。 ¥On [Billing](https://expo.dev/settings/billing), click **Manage billing information**. This will open Stripe's portal where you can view and update your billing information. * 在“付款方式”下,单击“添加付款方式”以添加新的付款方式。 ¥Under **Payment method**, click on **Add payment method** to add a new payment method. * 输入你的新付款方式详细信息,然后单击“添加”。 ¥Enter your new payment method details and click **Add**. ## 查看付款历史记录、发票和收据 了解如何查看你账户的付款历史记录、下载发票和收据以及请求退款。 EAS 仪表板中的收据提供有关账户付款历史记录以及发票和收据访问权限的信息。它还提供有关付款日期、付款状态以及该付款总金额的信息。如果你认为费用有误,你还可以请求退款。 ¥**Receipts** in the EAS dashboard provide information about an account's payment history and access to invoices and receipts. It also provides information on payment dates, payment status, and the total amount for that payment. You can also request a refund for a charge if you believe it has been made in error. > 注意:只有当你的账户中有 [所有者或管理员访问权限](/accounts/account-types/#manage-access) 时,你才能访问收据。 > > ¥**Note**: You can only access the **Receipts** if you have [Owner or Admin access](/accounts/account-types/#manage-access) to your account. ## 收据 ¥Receipts 要查看你账户的付款历史记录,请单击“账户设置”或“组织设置”下导航菜单中的 [收据](https://expo.dev/settings/receipts)。 ¥To view your account's payment history, click [Receipts](https://expo.dev/settings/receipts) in the navigation menu under **Account settings** or **Organization settings**. 例如,[组织账户](/accounts/account-types/#organizations) 收据如下所示: ¥For example, an [Organization account's](/accounts/account-types/#organizations) receipts are shown below: ### 下载并查看发票 ¥Download and view an invoice 要下载并查看结算期的发票,请转到 [收据](https://expo.dev/settings/receipts) 并: ¥To download and view an invoice for a billing period, go to [Receipts](https://expo.dev/settings/receipts) and: * 单击与发票对应的结算期的日期。你将被导航到 Stripe 托管的页面。例如,下面的 2024 年 3 月 22 日发票链接到此页面: ¥Click the **Date** for the billing period corresponding to the invoice. You will be navigated to a page hosted by Stripe. As an example, the March 22, 2024 invoice below links to this page: * 单击下载发票。你将收到发票的 PDF 副本。 ¥Click **Download invoice**. You will receive a PDF copy of the invoice. ### 下载并查看收据 ¥Download and view a receipt 要下载并查看结算期的收据,请转到 [收据](https://expo.dev/settings/receipts) 并: ¥To download and view a receipt for a billing period, go to [Receipts](https://expo.dev/settings/receipts) and: * 单击与收据对应的结算期的日期。你将被导航到 Stripe 托管的相应收据。例如,下面的 2024 年 3 月 22 日收据链接到此页面: ¥Click the **Date** for the billing period corresponding to the receipt. You will be navigated to the appropriate receipt hosted by Stripe. As an example, the March 22, 2024 receipt below links to this page: * 单击下载收据。你将收到收据的 PDF 副本。 ¥Click on **Download receipt.** You will receive a PDF copy of the receipt. ### 请求退款 ¥Request a refund 你可以直接从 [收据](https://expo.dev/settings/receipts) 页面请求退款。审批流程是手动的,我们的团队会在提供退款之前调查任何错误。 ¥You can request a refund directly from the [Receipts](https://expo.dev/settings/receipts) page. The approval process is manual and our team investigates any errors before providing a refund. 要请求退款: ¥To request a refund: * 在收据旁边,单击三点菜单,然后单击请求退款: ¥Next to a receipt, click the three-dot menu and then click **Request Refund:** * 填写退款请求表格,其中包含退款详细信息,然后单击继续: ¥Fill the **Request a refund** form with details for the refund and click **Continue**: * 我们的计费团队接收并审查退款请求。退款获得批准后,金额将记入你的付款方式。退款通常需要 5 到 10 个工作日才能完全处理。 ¥Our billing team receives and reviews refund requests. Once a refund is approved, the amount is credited to your payment method. Refunds typically take 5 to 10 business days to fully process. ## 阅读发票 ¥Read an invoice 发票包含你合法注册的企业名称、地址、税号、发票号、到期日等。它还包括任何费用的描述和应付总额。在典型的发票中,费用分为: ¥An invoice contains your legally registered business name, address, tax ID, invoice number, due date, and more. It also includes a description of any charges and the total amount due. In a typical invoice, the charges are divided into: * 当前计划的订阅金额(如果订阅了计划) ¥Current plan's subscription amount (if subscribed to a plan) * 任何超额费用(如适用) ¥Any overage charges (if applicable) * 计划的信用额度 ¥A plan's credit limit 让我们考虑三个不同的示例来了解你的发票可能是什么样子。如果你订阅了 [生产](/billing/plans/#production)、[企业](/billing/plans/#enterprise) 或 [启动器](/billing/plans/#starter) 计划,这些场景之一可能适用于你。 ¥Let's consider three different examples to understand how your invoice might look. If you subscribe to a [Production](/billing/plans/#production), [Enterprise](/billing/plans/#enterprise), or [Starter](/billing/plans/#starter) plan, one of these scenarios may apply to you. ### 订阅费用 ¥Subscription charges 在第一个示例中,发票表显示了生产计划的订阅费用: ¥In the first example, the invoice table shows a subscription charge for a Production plan: | 描述 | 数量 | 单价 | 数量 | | ------------------------------------------------------ | -: | ------: | ---------: | | FEB 1 - MAR 1, 2025 | | | | | EAS 构建 - 构建(Android:5 个大型和 5 个中型构建;iOS:5 个大型和 5 个中型构建) | 1 | $40.00 | $40.00 | | EAS 构建 - 计划信用 | 1 | -$40.00 | -$40.00 | | MAR 1 - APR 1, 2025 | | | | | Expo 应用服务 - 生产 | 1 | $99.00 | $99.00 | | **总计(美元)** | | | **$99.00** | 在上面的例子中: ¥In the above example: * 第一行描述了 2025 年 2 月 1 日至 3 月 1 日计费周期内的 EAS 构建使用情况。它包含有关在此结算期间创建了多少个 Android 和 iOS 版本及其成本的所有详细信息。 ¥The first line item describes the EAS Build usage for the billing period of February 1 to March 1, 2025. It contains all the details about how many Android and iOS builds were created during this billing period and their cost. * 第二行项目描述了生产计划在 2025 年 2 月 1 日至 3 月 1 日结算期内的信用额度(100 美元)。 ¥The second line item describes the credit limit for the Production plan ($100) for the billing period of February 1 to March 1, 2025. * 第三行项目描述了生产计划在下一个计费周期(2025 年 3 月 1 日至 4 月 1 日)的订阅费用。 ¥The third line item describes the subscription charge for the Production plan for the next billing period of March 1 to April 1, 2025. 由于 EAS Build 用量(40 美元)未超过计划的 100 美元信用额度,因此订户只需支付生产计划的 99 美元订阅金额。 ¥Since the EAS Build usage ($40) doesn't exceed the plan's $100 credit amount, the subscriber only has to pay the $99 subscription amount for the Production plan. ### 超额费用 ¥Overage charges 在第二个示例中,发票表显示了生产计划的订阅费用和超额费用: ¥In the second example, the invoice table shows a subscription charge for a Production plan with an overage charge: | 描述 | 数量 | 单价 | 数量 | | ---------------------------------------------------------- | -: | -------: | ----------: | | FEB 1 - MAR 1, 2025 | | | | | EAS 构建 - 构建(Android:15 个大型和 10 个中型构建;iOS 10 个大型和 15 个中型构建) | 1 | $110.00 | $110.00 | | EAS 构建 - 计划信用 | 1 | -$100.00 | -$100.00 | | MAR 1 - APR 1, 2025 | | | | | Expo 应用服务 - 生产 | 1 | $99.00 | $99.00 | | **总计(美元)** | | | **$109.00** | 在上面的例子中: ¥In the above example: * 第一行描述了 2025 年 2 月 1 日至 3 月 1 日计费周期内的 EAS 构建使用情况。它包含有关在此结算期间创建了多少个 Android 和 iOS 版本及其成本的所有详细信息。 ¥The first line item describes the EAS Build usage for the billing period of February 1 to March 1, 2025. It contains all the details about how many Android and iOS builds were created during this billing period and their cost. * 第二行项目描述了生产计划在 2025 年 2 月 1 日至 3 月 1 日结算期内的信用额度。 ¥The second line item describes the credit limit for the Production plan for the billing period of February 1 to March 1, 2025. * 第三行项目描述了生产计划在下一个计费周期(2025 年 3 月 1 日至 4 月 1 日)的订阅费用。 ¥The third line item describes the subscription charge for the Production plan for the next billing period of March 1 to April 1, 2025. 由于 EAS Build 用量超过了计划在 2025 年 2 月 1 日至 3 月 1 日结算周期内的信用额度,因此订户必须支付超额费用以及下一个结算周期的生产计划订阅金额。 ¥Since the EAS Build usage exceeds the plan's credit amount for the billing period of February 1 to March 1, 2025, the subscriber has to pay the overage charge and the subscription amount for the Production plan for the next billing period. ### 启动器费用 ¥Starter charges 在第三个示例中,订阅者使用的是 Starter 套餐。发票中列出了结算期间产生的任何费用: ¥In the third example, the subscriber is on a Starter plan. Any charges incurred during the billing period are listed in the invoice: | 描述 | 数量 | 单价 | 数量 | | ------------------------------------------ | -: | ------: | ---------: | | FEB 1 - MAR 1, 2025 | | | | | EAS 构建 - 构建(Android:10 个中型构建;iOS 10 个中型构建) | 1 | $30.00 | $30.00 | | EAS 构建 - 计划信用 | 1 | -$30.00 | -$30.00 | | MAR 1 - APR 1, 2025 | | | | | Expo 应用服务 - 启动器 | 1 | $19.00 | $19.00 | | **总计(美元)** | | | **$19.00** | 在上面的例子中: ¥In the above example: * 第一行描述了 2025 年 2 月 1 日至 3 月 1 日计费周期内的 EAS 构建使用情况。它包含此计费周期内创建的 Android 和 iOS 构建数量及其费用的所有详细信息。 ¥The first line item describes the EAS Build usage for the billing period of February 1 to March 1, 2025. It contains all the details about the number of Android and iOS builds created during this billing period and their cost. * 第二行项目描述了入门计划在 2025 年 2 月 1 日至 3 月 1 日结算期内的信用额度(30 美元)。 ¥The second line item describes the credit limit for the Starter plan ($30) for the billing period of February 1 to March 1, 2025. * 第三行项目描述了入门计划在下一个计费周期(2025 年 3 月 1 日至 4 月 1 日)的订阅费用。 ¥The third line item describes the subscription charge for the Starter plan for the next billing period of March 1 to April 1, 2025. 由于 EAS Build 用量未超过计划的 30 美元信用额度,因此订户只需支付入门计划的 19 美元订阅金额。 ¥Since the EAS Build usage doesn't exceed the plan's $30 credit amount, the subscriber only has to pay the $19 subscription amount for the Starter plan. ## 基于使用情况的定价 了解 Expo 如何对超出其计划配额的客户应用基于使用量的计费以及如何监控你的 EAS 构建使用情况。 Expo 对超出其 [plan](/billing/plans/) 限额的客户采用基于使用量的计费。这使我们的客户能够使用他们需要的内容,而不必担心限制或需要合同义务。 ¥Expo applies usage-based billing for customers who exceed their [plan](/billing/plans/) allowances. This enables our customers to use what they need without worrying about limitations or requiring contractual obligations. 基于使用情况的计费已为 EAS Build 和 EAS Update 启用,并按月计费。我们提供你现有使用情况的估计值以及 [账户的账单](https://expo.dev/settings/billing) 上的任何超额费用。 ¥Usage-based billing is enabled for EAS Build and EAS Update and is billed monthly. We provide an estimate of your existing usage and any overage charges on your [account's Billing](https://expo.dev/settings/billing). ## 基于使用量的定价如何运作 ¥How usage-based pricing works ### EAS 构建 ¥EAS Build 对于 EAS Build,在更高优先级执行的单个构建将收取固定费用。这是每月总计,并在你的结算期结束时或如果你取消计划,则更早收取费用。 ¥For EAS Build, a flat fee is charged for an individual build executed at higher-priority levels. This is totaled monthly and charged at the end of your billing period or sooner if you cancel your plan. > 注意:在完成任何工作之前取消的构建不收费。 > > ¥**Note**: Builds that are canceled before any work is done are not charged. [入门、生产、企业和旧版计划](/billing/plans/#plans) 订阅者获得 EAS Build 积分。这些信用可用于抵消构建成本。它们在结算期开始时重置,并在该结算期结束时到期。访问我们的 [定价页面](https://expo.dev/pricing),了解有关受支持的构建平台和可用资源类的定价计划的更多信息。 ¥[Starter, Production, Enterprise, and Legacy plans](/billing/plans/#plans) subscribers receive credits for EAS Build. These credits can be used to offset the cost of builds. They are reset at the start of the billing period and expire at the end of that billing period. Visit our [pricing page](https://expo.dev/pricing) for more information on the pricing schedule for supported build platforms and the available resource classes. #### 示例:EAS 构建信用使用情况 ¥Example: EAS Build credit usage 考虑一个订阅了生产计划的账户,该账户在一个结算期内有 15 个中型 Android 版本和 10 个大型 iOS 版本: ¥Consider an account subscribed to the Production plan that has 15 medium Android builds, and 10 large iOS builds in a billing period: | 描述 | 价格 | 数量 | 总计 | | ------------- | -: | -: | -----: | | Android 版本(中) | $1 | 15 | $15 | | iOS 版本(大) | $4 | 10 | $40 | | EAS 构建信用 | | | -$55 | | **总计(美元)** | | | **$0** | 由于信用额度包含在生产计划中,因此订户为其 25 次构建支付 0 美元。 ¥Since the credit is included in the Production plan, the subscriber pays $0 for their 25 builds. #### 示例:EAS 构建信用已超出 ¥Example: EAS Build credit exceeded 考虑另一个超出信用额度的示例: ¥Consider another example where the credit limit is exceeded: | 描述 | 价格 | 数量 | 总计 | | ------------- | -: | -: | ------: | | Android 版本(中) | $1 | 20 | $20 | | Android 版本(大) | $2 | 10 | $20 | | iOS 版本(中) | $2 | 15 | $30 | | iOS 版本(大) | $4 | 15 | $60 | | EAS 构建信用 | | | -$100 | | **总计(美元)** | | | **$30** | 在这种情况下,订阅者支付 30 美元购买 60 个构建,而不是 130 美元,因为 EAS 构建抵扣额涵盖了 100 美元。 ¥In this scenario, the subscriber pays $30 for 60 builds instead of $130 because the EAS Build Credit covers $100. ### EAS 更新 ¥EAS Update > **info** 提示:使用 [价格计算器](https://expo.dev/pricing#update) 估算你的 EAS 更新使用量。 > > ¥**Tip:** Use the [pricing calculator](https://expo.dev/pricing#update) to estimate your EAS Update usage. EAS Update 的基于使用情况的定价包括两个指标:每月活跃用户和全球边缘带宽。 ¥Usage-based pricing for EAS Update comprises two metrics: monthly active users and global edge bandwidth. "更新用户" 反映在计费期内下载至少一次更新的唯一用户数,也称为 "每月活跃用户" (MAU)。全局边缘带宽表示超出订阅计划基本带宽分配的总带宽量。如果你的每月活跃用户超过了计划的基本 MAU 分配,则每个额外用户将包含 40 MiB 的全局边缘带宽。 ¥The "updated users" reflect the number of unique users who download at least one update in a billing period, also known as "monthly active users" (MAU). Global edge bandwidth represents the total amount of bandwidth used beyond your subscription plan's base bandwidth allocation. If your monthly active users exceed your plan's base MAU allocation, 40 MiB of global edge bandwidth is included for each additional user. > 注意:每月活跃用户每个结算期仅计算一次,无论此用户下载了多少更新。在 EAS 更新的上下文中,"user" 被视为你的应用在设备上的唯一安装。 > > ¥**Note**: A monthly active user counts only once per billing period, regardless of how many updates this user downloads. In the context of EAS Update, a "user" is considered a unique installation of your app on a device. 每个计划都包含一定数量的月活跃用户和全球边缘带宽作为订阅的一部分。这些因每个计划和最新数字而异。有关更多信息,请参阅我们的 [定价页面](https://expo.dev/pricing)。 ¥Each plan has a number of monthly active users and global edge bandwidth included as part of the subscription. These differ for each plan and the most updated numbers. See our [pricing page](https://expo.dev/pricing), for more information. #### 示例:EAS 更新使用情况 ¥Example: EAS Update usage > 注意:[入门计划](/billing/plans/#starter) 每月包含 3,000 名活跃用户和 100 GiB 存储空间,而免费计划每月包含 1,000 名活跃用户。 > > ¥**Note**: The [Starter plan](/billing/plans/#starter) includes 3,000 monthly active users and 100 GiB per month, compared to 1,000 monthly active users in the Free plan. 假设一位 Starter 计划的订阅者通过 EAS 更新向 10,000 位用户部署了 20 个更新,每个更新 5MiB。该计划的订阅包括每月 3,000 名活跃用户和每月 100 GiB 的存储空间。因此,订阅者的额外使用费用将是: ¥Consider a subscriber to the Starter plan who deploys 20 updates of 5 MiB each via EAS Update to 10,000 users. The subscription to the plan includes 3,000 monthly active users and 100 GiB per month. As a result, the subscriber's bill for extra usage will be: | 描述 | 价格 | 数量 | 总计 | | ---------- | ------------- | ---------- | ---------: | | 更新用户 | 每位用户 0.005 美元 | 7,000 | $35 | | 全球边缘带宽 | 每 GiB 0.10 美元 | 603.13 GiB | $60.31 | | **总计(美元)** | | | **$95.31** | 在 10,000 名用户中,有 3,000 名用户包含在入门计划中。因此,7,000 人将按使用量计费。支付 7,000 位更新用户的费用还包含大约 273.4 GiB(7000 位用户 * 40 MiB / 1024)。 ¥Out of the 10,000 users, 3,000 are included in the Starter plan. As a result, 7,000 are billed for as part of usage-based billing. Paying for 7,000 updated users also includes approximately 273.4 GiB (7000 users * 40 MiB / 1024). 全局边缘带宽计算为: ¥The global edge bandwidth calculation is: | 描述 | 计算 | 数量 | | ------------------ | ----------------------------- | --------------- | | 用于发送更新的带宽 | 20 个更新 \* 5 MiB \* 10,000 个用户 | 976.5625 GiB | | | | | | 计划中包含的带宽 | | 100 GiB | | 7,000 名额外更新用户包含的带宽 | 7,000 \* 40 MiB | 273.4375 GiB | | **总计** | **976.5625 - 100 - 273.4375** | **603.125 GiB** | 如果同一订户在当前计费期内向相同的 10,000 名用户发送第 21 次 5 MiB 更新,他们只需支付任何额外使用的带宽费用。 ¥If the same subscriber sends the 21st update of 5 MiB to the same 10,000 users in the current billing period, they will only pay for any extra bandwidth used. | 描述 | 计算 | 数量 | | ------------------ | ----------------------------- | -------------- | | 用于发送更新的带宽 | 21 个更新 \* 5 MiB \* 10,000 个用户 | 1,025.39 GiB | | | | | | 计划中包含的带宽 | | 100 GiB | | 7,000 名额外更新用户包含的带宽 | 7,000 \* 40 MiB | 273.4375 GiB | | **总计** | **1,025.39 - 100 - 273.4375** | **651.95 GiB** | 这是因为 Expo 只对 [每月唯一活跃用户](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle) 收费。因此,订阅者的额外使用费用将是: ¥This is because Expo only charges for [unique monthly active users](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle). As a result, the subscriber's bill for extra usage will be: | 描述 | 价格 | 数量 | 总计 | | ---------- | ------------- | ---------- | ---------: | | 更新用户 | 每位用户 0.005 美元 | 7,000 | $35 | | 全球边缘带宽 | 每 GiB 0.10 美元 | 651.95 GiB | $65.2 | | **总计(美元)** | | | **$100.2** | 如果同一个订阅者使用的是生产计划,他们将支付 0 美元,因为生产计划包括 50,000 名月活跃用户和 1 TiB(1024 GiB)。因此,没有额外的带宽使用。 ¥If the same subscriber is on a Production plan, they will pay $0 as the Production plan includes 50,000 monthly active users and 1 TiB (1024 GiB). As such, there is no extra bandwidth usage. ## 监控使用情况 ¥Monitor usage > 注意:显示的账单估算可能会延迟最多 24 小时(一天)。 > > ¥**Note**: Billing estimates shown may be delayed by up to 24 hours (one day). 要查看当前计费周期的使用情况摘要,请转到 [计费](https://expo.dev/settings/billing),在“使用情况”下,你将找到 EAS 构建和 EAS 更新使用情况的摘要。 ¥To see the current billing period's usage summary, go to the [Billing](https://expo.dev/settings/billing) and under **Usage**, you will find a summary for both EAS Build and EAS Update usage. ### EAS Build 使用历史记录 ¥EAS Build usage history 要查看当前或上一个结算期的 EAS Build 详细使用情况: ¥To see detailed EAS Build usage for a current or previous billing period: * 单击导航菜单中的使用情况。 ¥Click **Usage** in the navigation menu. * 在 EAS 构建部分下,你将找到基于其平台和资源类别的构建计数和执行构建的详细信息。 ¥Under the **EAS Build** section, you will find details on builds count and executed builds based on their platform and resource class. ### EAS 更新使用历史记录 ¥EAS Update usage history 要查看当前或上一个结算期的 EAS Update 详细使用情况: ¥To see detailed EAS Update usage for a current or previous billing period: * 单击导航菜单中的使用情况。 ¥Click **Usage** in the navigation menu. * 在 EAS 更新部分下,你将找到有关更新用户和全球边缘带宽的详细信息。 ¥Under the **EAS Update** section, you will find details on updated users and global edge bandwidth details. ### 启用 EAS Build 使用通知 ¥Enable notifications for EAS Build usage 你可以启用计划信用使用通知来密切监控你的 EAS Build 使用情况。当你的计划的 EAS Build 信用额度使用 80% 和 100% 时,它会启用电子邮件通知。 ¥You can enable **Plan credit usage** notifications to closely monitor your EAS Build usage. It enables email notifications when 80% and 100% of your plan's EAS Build credit is used. 要启用 EAS Build 信用使用通知: ¥To enable EAS Build credit usage notification: * 单击账户设置下导航菜单中的电子邮件通知: ¥Click **Email notifications** in the navigation menu under your account's settings: * 在“EAS 构建通知”下,单击“订阅计划信用使用情况通知”。 ¥Under **EAS Build notifications**, click **Subscribe** for **Plan credit usage notifications**. ### 如何优化构建使用 ¥How to optimize build usage 你可以使用 [EAS 更新](/eas-update/introduction/) 和 [开发构建](/develop/development-builds/introduction/) 来测试和部署新代码,而无需创建全新的版本。这将帮助你更快地进行迭代并减少构建使用。 ¥You can use [EAS Update](/eas-update/introduction/) and [development builds](/develop/development-builds/introduction/) to test and deploy new code without having to create an entirely new build. This will help you iterate faster and reduce build usage. 对于大多数应用,JavaScript 代码的更改频率高于底层原生代码和配置。如果你每次更改代码时都要构建新版本,请考虑在 JavaScript 和原生代码之间使用 [使用 EAS 更新以利用不同的迭代频率](/eas-update/how-it-works/)。这样,你可以将这些更改作为更新发送。 ¥For most apps, the JavaScript code changes more frequently than the underlying native code and configuration. If you are building a new build every time for code changes, consider [using EAS Update to take advantage of the different iteration frequency](/eas-update/how-it-works/) between JavaScript and native code. This way, you can ship those changes as an update instead. 当使用持续集成 (CI)/持续部署 (CD) 构建预生产代码时,你可以通过仅在对原生代码进行更改时自动执行构建过程来减少不必要的使用。你可以使用 [Expo 指纹](https://expo.dev/blog/fingerprint-your-native-runtime) 在 CI/CD 中创建工作流,以检测原生代码何时发生更改,并且仅在发生更改时执行构建。否则,如果原生代码未更改,则发布更新。 ¥When using Continuous Integration (CI)/Continuous Deployment (CD) to build pre-production code, you can reduce unnecessary usage by automating the process of building only when changes are made to the native code. You can create a workflow in your CI/CD using [Expo Fingerprint](https://expo.dev/blog/fingerprint-your-native-runtime) to detect when your native code has changed, and only execute a build if it has changed. Otherwise, publish an update if the native code has not changed. 开发版本可以运行与其原生运行时兼容的任何 EAS 更新。如果你使用带有多个测试渠道的 EAS Update,则可以通过让测试人员或测试设备使用相同的开发版本来减少创建额外版本的需要。 ¥A development build can run any EAS Update that is compatible with its native runtime. If you are using EAS Update with multiple testing channels, you can reduce the need for creating additional builds by having your testers or test devices use the same development build. ### 如何优化更新使用 ¥How to optimize update usage 你可以在使用 EAS 更新时管理要包含或排除的某些资源。这减少了从更新服务器上传或下载的资源数量以及使用的全局边缘带宽。 ¥You can manage certain assets to include or exclude when using EAS Update. This reduces the number of assets uploaded or downloaded from the updates server and the global edge bandwidth used. 为了优化存储和带宽使用,你可以选择排除尚未修改的资源。例如,可以排除未更改的图片或视频。排除的资源不会上传到更新服务器,也不会被应用下载。但是,重要的是要确保不属于更新的资源包含在应用的原生版本中。 ¥To optimize storage and bandwidth usage, you can choose to exclude assets that haven't been modified. For example, images or videos that haven't been changed can be excluded. Excluded assets won't be uploaded to the update server and won't be downloaded by the app. However, it's important to make sure that assets that are not part of an update are included in the native build of the app. > 注意:如果应用已经下载了也是新更新一部分的资源,则应用将不会重新下载该资源。这也不会增加你账户的带宽使用量。 > > ¥**Note**: If an app has already downloaded an asset that is also part of a new update, the app will not re-download that asset. This will also not add to your account's bandwidth usage. 你可以使用 `npx expo-updates assets:verify ` 检查更新中是否包含所有必需的资源。欲了解更多信息,请参阅 [资源选择和排除](/eas-update/asset-selection/)。 ¥You can use `npx expo-updates assets:verify ` to check all required assets are included in the update. For more information, see [Asset selection and exclusion](/eas-update/asset-selection/). ## 计划、账单和付款常见问题 有关 Expo 应用服务 (EAS) 计划、计费和付款的常见问题参考。 本页涵盖了有关 [Expo 应用服务(EAS)](/eas/) 的计划、计费和付款的常见问题。 ¥This page covers frequently asked questions about plans, billing, and payment for [Expo Application Services (EAS)](/eas/). ## 计划 ¥Plans ### 如何更新我的计划? ¥How can I update my plan? 要更新组织账户的计划,请确保你拥有 [所有者或管理员角色权限](/accounts/account-types/#manage-access)。对于个人账户,你始终具有所有者角色。请参阅 [更改成员角色](/accounts/account-types/#change-the-role-of-a-member) 了解更多信息。 ¥To update your Organization account's plan, make sure that you have either an [Owner or Admin role privilege](/accounts/account-types/#manage-access). For a Personal account, you always have an **Owner** role. See [Change the role of a member](/accounts/account-types/#change-the-role-of-a-member) for more information. 确认你的角色后,请参阅 [升级到新计划](/billing/manage/#upgrade-to-a-new-plan) 以升级或 [降级计划](/billing/manage/#downgrade-a-plan) 以降级现有计划。 ¥After confirming your role, see [Upgrade to a new plan](/billing/manage/#upgrade-to-a-new-plan) to upgrade or [Downgrade a plan](/billing/manage/#downgrade-a-plan) to downgrade an existing plan. ### 如何取消计划? ¥How can I cancel a plan? 请参阅 [取消计划](/billing/manage/#cancel-a-plan) 了解更多信息。 ¥See [Cancel a plan](/billing/manage/#cancel-a-plan) for more information. ### 如果我从错误的账户订阅怎么办? ¥What if I subscribe from the wrong account? 如果你从错误的账户订阅了计划: ¥If you've subscribed to a plan from the wrong account: * 在 EAS 仪表板的导航菜单中,在“账户”下,切换到你打算订阅的账户。 ¥From the EAS dashboard's navigation menu, under **Account**, switch to the account from which you intend to subscribe. * 转到 [计费](https://expo.dev/settings/billing),然后在“当前计划”下,选择 [按照步骤订阅正确的计划](/billing/manage/#upgrade-to-a-new-plan)。 ¥Go to [Billing](https://expo.dev/settings/billing) and under **Current Plan**, [follow the steps to subscribe to the right plan](/billing/manage/#upgrade-to-a-new-plan). * 从错误的账户,转到 [收据](https://expo.dev/accounts/\[account]/settings/receipts) 并启动 [请求退款](/billing/invoices-and-receipts/#request-a-refund)。 ¥From the wrong account, go to **[Receipts](https://expo.dev/accounts/\[account]/settings/receipts)** and initiate a [request for a refund](/billing/invoices-and-receipts/#request-a-refund). ### 我使用的是免费计划,只需要一些额外的构建或更新 ¥I am on a Free plan and only need a few extra builds or updates 如果你使用的是免费计划,并且已完成每月的免费构建和更新配额,请升级到 [入门计划](/billing/plans/)。每月 19 美元,即可获得 30 美元的构建积分和 3,000 名 EAS 更新月活跃用户(免费计划为 1,000 名)。满足你的要求后,你就可以 [从入门计划降级到免费计划](/billing/manage/#cancel-a-plan)。 ¥If you are on a Free plan and have completed your monthly quota of free builds and updates, upgrade to the [Starter plan](/billing/plans/). For $19 per month, you get $30 of build credit and 3,000 monthly active users for EAS Update (compared to 1,000 in the Free plan). Once your requirements are fulfilled, you can [downgrade to the Free plan from the Starter plan](/billing/manage/#cancel-a-plan). 要从免费套餐升级到入门套餐,请参阅 [升级到新计划](/billing/manage/#upgrade-to-a-new-plan)。 ¥To upgrade from the Free plan to the Starter plan, see [Upgrade to a new plan](/billing/manage/#upgrade-to-a-new-plan). 要从入门套餐降级到免费套餐,请参阅 [取消计划](/billing/manage/#cancel-a-plan)。 ¥To downgrade from the Starter to a Free plan, see [Cancel a plan](/billing/manage/#cancel-a-plan). ### 升级到付费订阅时,我可以转移未使用的免费套餐积分吗? ¥Can I transfer my unused free plan credits when upgrading to a paid subscription? 不可以,你的免费套餐积分不能转移到其他订阅套餐。所有付费套餐均提供积分,以便你能够优先构建 EAS Build,并通过增加月活跃用户数和额外带宽来更广泛地访问 EAS Update。 ¥No, your free plan credits are not transferable to another subscription plan. All paid plans provide credits to enable priority builds for EAS Build and broader access to EAS Update through more monthly active users and extra bandwidth. ## 计费 ¥Billing ### 计划的计费期从何时开始? ¥When does a billing period start for a plan? 对于免费计划,结算期从日历月的第一天开始。 ¥For the Free plan, the billing period starts on the first day of the calendar month. 对于 [所有付费计划](/billing/plans/#plans),计费期从该计划的订阅日期开始。 ¥For [all paid plans](/billing/plans/#plans), the billing period starts from the subscription date of that plan. ### 如何更新我的账单信息或添加税号? ¥How do I update my billing information or add a tax ID? 要更新你的组织账户的结算信息或添加税号,请确保你拥有 [所有者或管理员角色](/accounts/account-types/#manage-access)。对于个人账户,你始终具有所有者角色。确认你的角色后: ¥To update your Organization account's billing information or add a tax ID, make sure that you have either an [Owner or Admin role](/accounts/account-types/#manage-access). For a Personal account, you always have an **Owner** role. After confirming your role: * 前往你的 [账户的账单](https://expo.dev/settings/billing),然后点击“管理账单信息”。这将带你进入 Stripe 的门户。 ¥Go to your [account's Billing](https://expo.dev/settings/billing), and click on **Manage billing information**. This will take you to Stripe's portal. * 在 Stripe 的门户上,在账单信息下,单击更新信息以更新账单相关信息,例如账单名称、电子邮件、地址和税号。 ¥On Stripe's portal, under **Billing information**, click on **Update information** to update billing-related information such as billing name, email, address, and tax ID. 详细信息请参见 [管理账单信息](/billing/manage/#manage-billing-information)。 ¥See [Manage billing information](/billing/manage/#manage-billing-information) for more details. ### 我可以更新上一张发票上的账单信息吗? ¥Can I update the billing information on my last invoice? 否。更新账单信息只会反映在下一张发票上。 ¥No. Updating billing information will only be reflected on the next invoice. ### 你能通过电子邮件将收据发给我吗? ¥Can you email me the receipts? 否。你的账户所有者或管理员可以下载它们。请参阅 [下载发票](/billing/invoices-and-receipts/#download-and-view-an-invoice) 了解更多信息。 ¥No. Your account's Owner or Admin can download them. See [Download an invoice](/billing/invoices-and-receipts/#download-and-view-an-invoice) for more information. ### 如何使用 EAS 更新减少 EAS Build 使用量? ¥How can I reduce the amount of EAS Build usage by using EAS Update? 使用 [EAS 更新](/eas-update/introduction/) 和 [开发构建](/develop/development-builds/introduction/) 测试和部署新代码,而无需创建新版本。此选项更适合大多数应用,因为 JavaScript 代码比底层原生代码更改更频繁。你可以使用 EAS Update 创建多个测试通道,并减少为你的团队创建额外构建的需要。很快,通过使用 [Expo 指纹](https://expo.dev/blog/fingerprint-your-native-runtime),你将能够有选择地构建或更新,具体取决于你是否已经拥有兼容的原生运行时。 ¥Use [EAS Update](/eas-update/introduction/) and [development builds](/develop/development-builds/introduction/) to test and deploy new code without creating a new build. This option is better for most apps since JavaScript code changes more frequently than the underlying native code. You can create multiple test channels with EAS Update and reduce the need to create additional builds for your team. Soon, by using [Expo Fingerprint](https://expo.dev/blog/fingerprint-your-native-runtime), you will be able to build or update selectively, depending on whether you already have a compatible native runtime. 欲了解更多信息,请参阅 [如何优化构建使用](/billing/usage-based-pricing/#how-to-optimize-build-usage)。 ¥For more information, see [How to optimize build usage](/billing/usage-based-pricing/#how-to-optimize-build-usage). ### 如何估算我的下一笔账单? ¥How do I estimate my next bill? 要估算你的下一笔账单,请转到 [计费](https://expo.dev/settings/billing)。在本月使用情况下,你将找到基于 [资源类](/build/eas-json/#selecting-resource-class) 的 EAS 构建使用情况摘要、基于每月活跃用户和全球边缘带宽的 EAS 更新使用情况摘要以及两者的花费金额。 ¥To estimate your next bill, go to [Billing](https://expo.dev/settings/billing). Under **Usage this month**, you will find a summary of your EAS Build usage based on the [resource class](/build/eas-json/#selecting-resource-class), EAS Update usage based on monthly active users and global edge bandwidth, and the amount spent for both. 请参阅 [基于使用量的定价如何运作](/billing/usage-based-pricing/#how-usage-based-pricing-works) 了解更多信息。 ¥See [How usage-based pricing works](/billing/usage-based-pricing/#how-usage-based-pricing-works) for more information. ### 什么是 EAS 更新月活跃用户 (MAU)? ¥What is an EAS Update monthly active user (MAU)? 每月活跃用户 (MAU) 是你的应用的唯一用户,在单个月度结算期内通过 EAS 更新下载至少一个更新。请参阅 [如何计算结算期内的月活跃用户数](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle) 了解更多信息。 ¥A monthly active user (MAU) is a unique user of your app that downloads at least one update via EAS Update within a single monthly billing period. See [How are monthly active users counted for a billing period](/eas-update/faq/#how-are-monthly-updated-users-counted-for-a-billing-cycle) for more information. ## 付款方式 ¥Payments ### 我可以按年付款吗? ¥Can I pay annually? [企业计划](/billing/plans/#enterprise) 客户可以选择年度计划。[联系我们](https://expo.dev/contact) 了解更多信息。 ¥Annual plans are available for [Enterprise plan](/billing/plans/#enterprise) customers. [Contact us](https://expo.dev/contact) for more information. ### 如何更新我们的付款信息? ¥How can I update our payment information? 要更新组织账户的付款信息,请确保你拥有 [所有者或管理员角色](/accounts/account-types/#manage-access)。对于个人账户,你始终具有所有者角色。确认你的角色后: ¥To update your Organization account's payment information, make sure that you have either an [Owner or Admin role](/accounts/account-types/#manage-access). For a Personal account, you always have an **Owner** role. After confirming your role: * 前往你的 [账户的账单](https://expo.dev/settings/billing),然后点击“管理账单信息”。这将带你进入 Stripe 的门户。 ¥Go to your [account's **Billing**](https://expo.dev/settings/billing), and click on **Manage billing information**. This will take you to Stripe's portal. * 在 Stripe 的门户上,在付款方式下,单击添加付款方式以添加新的付款方式。 ¥On Stripe's portal, under **Payment method**, click on **Add payment method** to add a new payment method. 详细信息请参见 [管理账单信息](/billing/manage/#payment-method)。 ¥See [Manage billing information](/billing/manage/#payment-method) for more details. ### 我可以使用自动清算所 (ACH) 或银行/电汇付款吗? ¥Can I pay with an Automated Clearing House (ACH), or bank/wire transfer? [企业计划](/billing/plans/#enterprise) 年度计划的客户可以通过 ACH 付款,而不是信用卡付款。[联系我们](https://expo.dev/contact) 了解更多信息。 ¥[Enterprise plan](/billing/plans/#enterprise) customers on annual plans can pay by ACH as an alternative to credit card payments. [Contact us](https://expo.dev/contact) for more information. ### 我需要 W-9 或其他法律文件 ¥I need a W-9, or other legal documentation 要请求 W-9,请使用 [联系我们](https://expo.dev/contact)。 ¥To request a W-9, [contact us](https://expo.dev/contact). 在 [expo.dev/terms](https://expo.dev/terms) 找到我们的法律条款。 ¥Find our legal terms at [expo.dev/terms](https://expo.dev/terms). ### Expo 会存储我的卡信息吗? ¥Does Expo store my card information? 否,Expo 没有。我们使用 Stripe 来处理支付系统,他们也确实这样做了。请参阅 [Stripe 如何处理安全性](https://docs.stripe.com/security) 了解更多信息。 ¥No, Expo does not. We use Stripe to handle the payment system and they do. See [how Stripe handles security](https://docs.stripe.com/security) for more information. ### 本月我为大型构建支付了多少钱? ¥How much did I pay for a large build this month? 要查看大型构建的成本,请转到 [计费](https://expo.dev/settings/billing)。在本月使用情况下,你将找到基于 [资源类](/build/eas-json/#selecting-resource-class) 的 EAS 构建使用情况摘要和花费金额。 ¥To view the cost of a large build, go to [Billing](https://expo.dev/settings/billing). Under **Usage this month** you will find a summary of your EAS Build usage based on the [resource class](/build/eas-json/#selecting-resource-class) and the amount spent. ## 插件 ¥Add-ons ### 如何增加我账户上的构建并发数? ¥How do I increase build concurrencies on my account? 如果你已经订阅了付费 EAS 套餐,则可以在“计费”下的 [**插件**](https://expo.dev/settings/billing) 部分购买额外的并发套餐。 ¥If you're already subscribed to a paid EAS plan, you can buy additional concurrencies in the [**Add-ons**](https://expo.dev/settings/billing) section under **Billing**. 如果你使用的是免费计划,则需要设置付费订阅。[单击此处](https://expo.dev/accounts/\[account]/settings/billing/cart) 选择新计划。然后,在结帐页面上选择要添加到订阅中的其他并发数。 ¥If you're on the Free plan, you'll need to set up a paid subscription. [Click here](https://expo.dev/accounts/\[account]/settings/billing/cart) to choose a new plan. Then, select the number of additional concurrencies to add to your subscription on the checkout page. 每个计划包含的并发数量不同。如果你需要超过 5 个额外的并发,[联系我们](https://expo.dev/contact)。 ¥Each plan has different number of concurrencies included. If you need more than 5 additional concurrencies, [contact us](https://expo.dev/contact).