EAS 工作流语法
编辑页面
EAS Workflows 配置文件语法参考指南。
For the complete documentation index, see llms.txt. Use this file to discover all available pages.
一个 workflow 是由一个或多个 job 组成的可配置自动化流程。你必须创建一个 YAML 文件来定义你的 workflow 配置。
要开始使用 workflows,请参阅 开始使用 EAS Workflows,或查看 示例 以获取完整的 workflow 配置。
Workflow 文件
Workflow 文件使用 YAML 语法,且必须具有 .yml 或 .yaml 文件扩展名。如果你刚接触 YAML,并想了解更多,请参阅 在 1 分钟内学习 YAML。
Workflow 文件位于项目中的 .eas/workflows 目录下。.eas 目录应与 eas.json 文件位于同一级别。
例如:
my-app.easworkflowscreate-development-builds.ymlpublish-preview-update.ymldeploy-to-production.ymleas.json配置参考
下面是 workflow 配置文件语法的参考。
name
workflow 的易读名称。它会显示在 EAS 仪表盘的 workflows 列表页上,并作为 workflow 详情页的标题。
name: 我的 workflow
on
on 键定义哪些 GitHub 事件会触发 workflow。无论 on 键如何,任何 workflow 都可以通过 eas workflow:run 命令触发。
on: # 在推送到 main 分支时触发 push: branches: - main # 以及在以 'version-' 开头的 pull request 上触发 pull_request: branches: - version-*
你可以在提交信息中包含[eas skip]、[skip eas]或[no eas]来跳过由push和pull_request触发的 workflow 运行。
on.push
当你向匹配的分支和/或标签推送提交时运行你的 workflow。
使用 branches 列表时,你可以仅在推送到这些指定分支时触发 workflow。例如,如果使用 branches: ['main'],只有推送到 main 分支才会触发 workflow。支持 glob。通过使用 ! 前缀,你可以指定要忽略的分支(你仍然需要提供至少一个不带 ! 的分支模式)。
使用 tags 列表时,你可以仅在推送这些指定标签时触发 workflow。例如,如果使用 tags: ['v1'],只有推送 v1 标签才会触发 workflow。支持 glob。通过使用 ! 前缀,你可以指定要忽略的标签(你仍然需要提供至少一个不带 ! 的标签模式)。
使用 paths 列表时,你可以仅在对匹配指定路径的文件进行更改时触发 workflow。例如,如果使用 paths: ['apps/mobile/**'],只有对 apps/mobile 目录中文件的更改才会触发 workflow。支持 glob。默认情况下,对任意路径的更改都会触发 workflow。
当未提供 branches 或 tags 时,branches 默认为 ['*'],tags 默认为 [],这意味着 workflow 会在所有分支的 push 事件上触发,而不会在 tag push 上触发。如果只提供其中一个列表,另一个默认为 []。
on: push: branches: - main - feature/** - !feature/test-** # 其他分支名称和 glob tags: - v1 - v2* - !v2-preview** # 其他标签名称和 glob paths: - apps/mobile/** - packages/shared/** - !**/*.md # 忽略 markdown 文件
on.pull_request
当你创建或更新一个目标分支匹配的 pull request 时运行你的 workflow。
使用 branches 列表时,你可以仅在 pull request 的目标分支为这些指定分支时触发 workflow。例如,如果使用 branches: ['main'],只有合并到 main 分支的 pull request 才会触发 workflow。支持 glob。未提供时默认为 ['*'],这意味着 workflow 会在所有分支的 pull request 事件上触发。通过使用 ! 前缀,你可以指定要忽略的分支(你仍然需要提供至少一个不带 ! 的分支模式)。
使用 types 列表时,你可以仅在指定的 pull request 事件类型上触发 workflow。例如,如果使用 types: ['opened'],只有 pull_request.opened 事件(在 pull request 首次打开时发送)才会触发 workflow。未提供时默认为 ['opened', 'reopened', 'synchronize']。支持的事件类型:
openedready_for_reviewreopenedsynchronizelabeled
使用 paths 列表时,你可以仅在对匹配指定路径的文件进行更改时触发 workflow。例如,如果使用 paths: ['apps/mobile/**'],只有对 apps/mobile 目录中文件的更改才会触发 workflow。支持 glob。默认情况下,对任意路径的更改都会触发 workflow。
on: pull_request: branches: - main - feature/** - !feature/test-** # 其他分支名称和 glob types: - opened # 其他事件类型 paths: - apps/mobile/** - packages/shared/** - !**/*.md # 忽略 markdown 文件
on.pull_request_labeled
当 pull request 被匹配的标签标记时运行你的 workflow。
使用 labels 列表时,你可以指定当哪些标签被添加到 pull request 上时会触发 workflow。例如,如果使用 labels: ['Test'],只有为 pull request 添加 Test 标签才会触发 workflow。未提供时默认为 [],这意味着没有任何标签会触发 workflow。
你也可以直接向 on.pull_request_labeled 提供匹配标签列表,以获得更简洁的语法。
on: pull_request_labeled: labels: - Test - Preview # 其他标签
或者:
on: pull_request_labeled: - Test - Preview # 其他标签
on.app_store_connect
当所选的 App Store Connect 事件之一发生时运行你的 workflow。
要使用此触发器,请在 EAS 仪表盘 中配置你的 App Store Connect 连接。设置步骤请参阅 App Store Connect 触发器设置 部分。
当存在 on.app_store_connect 时,你必须至少指定一个事件域(app_version、build_upload、external_beta 或 beta_feedback)。在已配置的事件域中,你可以指定哪些状态应触发 workflow。
on.app_store_connect.app_version.states
过滤应用商店应用版本状态变更事件。未提供时默认为所有受支持的应用版本状态。
支持的值:
accepteddeveloper_rejectedin_reviewinvalid_binarymetadata_rejectedpending_apple_releasepending_developer_releaseprepare_for_submissionprocessing_for_distributionready_for_distributionready_for_reviewrejectedreplaced_with_new_versionwaiting_for_export_compliancewaiting_for_review
on.app_store_connect.build_upload.states
按状态过滤构建上传事件。未提供时默认为所有受支持的构建上传状态。
支持的值:
completefailedprocessingawaiting_upload
on.app_store_connect.external_beta.states
按状态过滤外部测试版事件。未提供时默认为所有受支持的外部测试版状态。
支持的值:
processingprocessing_exceptionmissing_export_complianceready_for_beta_testingin_beta_testingexpiredready_for_beta_submissionin_export_compliance_reviewwaiting_for_beta_reviewin_beta_reviewbeta_rejectedbeta_approved
on.app_store_connect.beta_feedback.types
按测试人员报告的 beta 反馈类型进行过滤。未提供时默认为所有受支持的 beta 反馈类型。
支持的值:
crashscreenshot
所有过滤值都必须为小写,且区分大小写。
# 在以下情况触发: # - 应用版本进入审核, # - 构建上传完成或失败, # - 外部测试版构建已准备好测试或已批准, # - 测试人员通过 TestFlight 报告崩溃。 on: app_store_connect: app_version: states: - ready_for_review - waiting_for_review build_upload: states: - complete - failed external_beta: states: - ready_for_beta_testing - beta_approved beta_feedback: types: - crash
# 在任何应用版本或构建上传状态变更时触发。 on: app_store_connect: app_version: {} build_upload: {}
on.schedule.cron
使用 unix-cron 语法按计划运行你的 workflow。你可以使用 crontab guru 及其 示例 来生成 cron 字符串。
- 计划任务 workflow 只会在仓库的默认分支上运行。在许多情况下,这意味着位于
main分支上的 workflow 文件中的 cron 会被调度,而位于功能分支中的 workflow 文件中的 cron 则不会被调度。 - 在高负载期间,计划任务 workflow 可能会延迟。高负载时段包括每小时的开始时间。在极少数情况下,job 可能会被跳过或运行多次。请确保你的 workflow 具有幂等性,并且不会产生有害的副作用。
- 一个 workflow 可以有多个
cron计划。 - 计划任务 workflow 以 GMT 时区运行。
on: schedule: - cron: '0 0 * * *' # 每天 GMT 午夜运行
on.workflow_dispatch.inputs
定义通过使用 eas workflow:run 命令手动触发 workflow 时可提供的输入。这使你能够创建可参数化的 workflows,每次运行时都可以接受不同的值。
on: workflow_dispatch: inputs: name: type: string required: false description: '要问候的人的名字' default: 'World' choice_example: type: choice options: - to be - not to be required: true
| 属性 | 类型 | 必填 | 描述 |
|---|---|---|---|
type | string | 输入类型(string、boolean、number、choice 或 environment)。 | |
description | string | 输入的描述。 | |
required | boolean | 该输入是否必填。默认为 false。 | |
default | varies | 输入的默认值。必须与输入类型匹配。 | |
options | string[] | (针对 type: choice) | choice 输入可用的选项。 |
提供输入
运行带输入的 workflow 时,你可以通过以下几种方式提供输入:
-
命令行标志:
Terminal-eas workflow:run .eas/workflows/deploy.yml -F environment=production -F debug=true -F version=1.2.3 -
通过 stdin 传入 JSON:
Terminal-echo '{"environment": "production", "debug": true, "version": "1.2.3"}' | eas workflow:run .eas/workflows/deploy.yml -
交互式提示: 如果缺少必填输入且你没有使用
--non-interactive,CLI 将提示你输入这些值:Terminal-eas workflow:run .eas/workflows/deploy.yml
用法
输入值可通过 ${{ inputs.<input_name> }} 语法在 workflow 的 jobs 中使用:
on: workflow_dispatch: inputs: name: type: string required: true description: '要问候的人的名字' jobs: deploy: steps: - name: Deploy to environment run: | echo "Hello, ${{ inputs.name }}!" # 注意:你可以使用 `||` 提供默认值 # 适用于非 eas-workflow:run-run workflows。 echo "Hello, ${{ inputs.name || 'World' }}!"
jobs
一个 workflow 运行由一个或多个 job 组成。
jobs: job_1: # ... job_2: # ...
jobs.<job_id>
每个 job 都必须有一个 ID。该 ID 在 workflow 内应唯一,并且可以包含字母数字字符和下划线。例如,下面 YAML 中的 my_job:
jobs: my_job: # ...
jobs.<job_id>.name
在 workflow 详情页上显示的、便于人类理解的 job 名称。
jobs: my_job: name: Build app
jobs.<job_id>.environment
为该 job 设置 EAS 环境变量 环境。有三种可选值:
production(默认)previewdevelopment
environment 键可用于所有 job。
jobs: my_job: environment: production | preview | development
jobs.<job_id>.env
为该 job 设置环境变量。该属性可用于所有运行在 VM 上的 job(除预封装的 apple-device-registration-request、require-approval、doc、get-build 和 slack jobs 外的所有 job)。
jobs: my_job: env: APP_VARIANT: staging RETRY_COUNT: 3 PREV_JOB_OUTPUT: ${{ needs.previous_job.outputs.some_output }}
jobs.<job_id>.hooks
某些预封装 job 支持 hooks,用于在主 job 动作之前或之后运行额外步骤(例如 maestro-cloud、maestro 和 submit)。hook 名称因 job 而异。可用的 hook 键请参阅该 job 的文档。
jobs: maestro_test: type: maestro-cloud params: build_id: ${{ needs.build.outputs.build_id }} maestro_project_id: proj_xyz flows: ./maestro/flows hooks: before_maestro_cloud: - run: echo "Before upload" after_maestro_cloud: - run: echo "After upload"
jobs.<job_id>.defaults.run.working_directory
设置该 job 中所有步骤执行命令的目录。
jobs: my_job: defaults: run: working_directory: ./my-app steps: - name: My first step run: pwd # 输出:/home/expo/workingdir/build/my-app
defaults
用于作为 workflow 配置中所有 job 默认值的参数。
defaults.run.working_directory
运行脚本的默认工作目录。诸如 "./assets" 或 "assets" 这样的相对路径会从应用的基础目录解析。
defaults.tools
该 workflow 配置中所定义 job 应使用的工具的特定版本。可用值请参阅各工具的文档。
| 工具 | 说明 |
|---|---|
node | 通过 nvm 安装的 Node.js 版本。 |
yarn | 通过 npm -g 安装的 Yarn 版本。 |
corepack | 如果设置为 true,则会在构建过程开始时启用 corepack。默认值为 false。 |
pnpm | 通过 npm -g 安装的 pnpm 版本。 |
bun | 通过在 Bun 安装脚本中传入 bun-v$VERSION 安装的 Bun 版本。 |
ndk | 通过 sdkmanager 安装的 Android NDK 版本。 |
bundler | 将传递给 gem install -v 的 Bundler 版本。 |
fastlane | 将传递给 gem install -v 的 fastlane 版本。 |
cocoapods | 将传递给 gem install -v 的 CocoaPods 版本。 |
使用 defaults.tools 的 workflow 示例:
name: 设置自定义版本 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: 检查 Node 版本 run: node --version # 应输出一个具体版本,例如 23.9.0 - name: 检查 Yarn 版本 run: yarn --version # 应输出一个具体版本,例如 2.4.3
concurrency
并发控制配置。目前仅允许为同分支 workflow 设置 cancel_in_progress。
concurrency: cancel_in_progress: true group: ${{ workflow.filename }}-${{ github.ref }}
| 属性 | 类型 | 说明 |
|---|---|---|
cancel_in_progress | true | 如果为 true,GitHub 新启动的 workflow 运行将取消当前正在进行的同分支运行。 |
group | string | 我们目前还不支持自定义并发组。请设置此占位值,以便当我们未来支持自定义组时,你的 workflow 仍保持兼容。 |
控制流
你可以使用 needs 和 after 关键字控制 job 何时运行。此外,你还可以使用 if 关键字根据条件控制某个 job 是否应运行。
jobs.<job_id>.needs
一个 job ID 列表,这些 job 必须先成功完成,此 job 才会运行。
jobs: test: steps: - uses: eas/checkout - uses: eas/use_npm_token - uses: eas/install_node_modules - name: tsc run: yarn tsc build: needs: [test] # 只有当 'test' job 成功时,这个 job 才会运行 type: build params: platform: ios
jobs.<job_id>.after
一个 job ID 列表,这些 job 必须先完成(无论成功与否),此 job 才会运行。
jobs: build: type: build params: platform: ios notify: after: [build] # 这个 job 会在 build 完成后运行(无论 build 成功还是失败)
jobs.<job_id>.if
if 条件用于决定某个 job 是否应运行。当满足 if 条件时,job 将运行;当条件不满足时,job 将被跳过。被跳过的 job 不会被视为成功完成,且任何将该 job 包含在 needs 列表中的下游 job 都不会运行。
jobs: my_job: if: ${{ github.ref_name == 'main' }}
插值
你可以基于 workflow 运行的上下文,自定义 workflow 的行为——包括要执行的命令、控制流、环境变量、构建配置、应用版本等。
使用 ${{ expression }} 语法访问上下文属性和函数。例如:${{ github.ref_name }} 或 ${{ needs.build_ios.outputs.build_id }}。
上下文属性
以下属性可在插值上下文中使用:
after
当前 job 的 after 列表中指定的所有上游 job 的记录。每个 job 提供:
{ "status": "success" | "failure" | "skipped", "outputs": {} }
示例:
jobs: build: type: build params: platform: ios notify: after: [build] steps: - run: echo "构建状态:${{ after.build.status }}"
needs
当前 job 的 needs 列表中指定的所有上游 job 的记录。每个 job 提供:
{ "status": "success" | "failure" | "skipped", "outputs": {} }
大多数预封装 job 会暴露特定输出。你可以使用 set-output 函数在自定义 job 中设置输出。
示例:
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: # 你可以使用 process.env.VERSION_SUFFIX # 在动态 app config 中自定义应用版本。 VERSION_SUFFIX: ${{ needs.setup.outputs.date }} params: platform: ios profile: development
steps
当前 job 中所有步骤的记录。每个步骤都会提供其使用 set-output 函数设置的输出。
注意:
steps上下文仅在 job 的步骤内部可用,而不在 workflow 级别可用。要将某个步骤的输出暴露给其他 job,请使用set-output函数以及该 job 的outputs配置。
示例:
jobs: my_job: outputs: value: ${{ steps.step_1.outputs.value }} steps: - id: step_1 run: set-output value "hello" - run: echo ${{ steps.step_1.outputs.value }} another_job: needs: [my_job] steps: - run: echo "Value: ${{ needs.my_job.outputs.value }}"
inputs
手动使用 workflow_dispatch 触发 workflow 时提供的输入记录。当 workflow 通过带输入参数的 eas workflow:run 命令触发时可用。
示例:
on: workflow_dispatch: inputs: name: type: string required: true jobs: greet: steps: - run: echo "Hello, ${{ inputs.name }}!"
github
为了便于从 GitHub Actions 迁移到 EAS Workflows,我们暴露了一些你可能会觉得有用的上下文字段。
type GitHubContext = { triggering_actor?: string; event_name: 'pull_request' | 'push' | 'schedule' | 'workflow_dispatch'; sha: string; ref: string; // 例如 refs/heads/main ref_name: string; // 例如 main ref_type: 'branch' | 'tag' | 'other'; commit_message?: string; // 仅适用于 push 和 schedule 事件 label?: string; repository?: string; repository_owner?: string; event?: { label?: { name: string; }; // 仅适用于 push 和 schedule 事件 head_commit?: { message: string; id: string; }; pull_request?: { number: number; title: string; body: string | null; state: 'open' | 'closed'; draft: boolean; merged: boolean | null; // ... 来自 GitHub Pull Request webhook payload 的其他字段 }; number?: number; schedule?: string; inputs?: Record<string, string | number | boolean>; }; };
信息
event对象包含完整的 GitHub webhook payload。对于pull_request事件,event.pull_request包含 GitHub Pull Request webhook payload 中的所有字段。上面展示的是常用字段,但诸如user、labels、milestone等其他字段也可用。
如果 workflow 运行是通过 eas workflow:run 启动的,那么其 event_name 将是 workflow_dispatch,其余所有属性都将为空。
示例:
jobs: build_ios: type: build if: ${{ github.ref_name == 'main' }} params: platform: ios profile: production
查看 ${{ github }} 定义的源代码。
workflow
有关当前 workflow 的信息。
type WorkflowContext = { id: string; name: string; filename: string; url: string; };
示例:
jobs: notify_slack: after: [...] type: slack params: message: | Workflow run completed: ${{ workflow.name }} View details: ${{ workflow.url }}
app_store_connect
有关与 workflow 运行相关联的 App Store Connect 实体的信息。此上下文仅适用于由 App Store Connect 事件触发的 workflow。参见 on.app_store_connect。
type AppStoreConnectContext = { app: { id: string; }; build_upload?: { id: string; state: 'awaiting_upload' | 'processing' | 'failed' | 'complete'; }; };
仅当 workflow 由 build_upload 事件域触发时,app_store_connect.build_upload 对象才会存在。
示例:
on: app_store_connect: build_upload: states: - complete jobs: notify: type: slack params: webhook_url: ${{ env.SLACK_WEBHOOK_URL }} message: | App Store Connect 应用上传完成:${{ app_store_connect.app.id }} 上传 ID:${{ app_store_connect.build_upload.id }} 上传状态:${{ app_store_connect.build_upload.state }}
env
当前 job 上下文中可用的环境变量记录。
注意:
env上下文仅在 job 的上下文中可用,而不在 workflow 级别可用。
示例:
jobs: my_job: steps: - run: echo "API URL: ${{ env.API_URL }}"
上下文函数
以下函数可在插值上下文中使用:
success()
返回所有之前的 job 是否都已成功。
jobs: notify: if: ${{ success() }} steps: - run: echo "所有 job 都已成功"
failure()
返回之前是否有任何 job 失败。
jobs: notify: if: ${{ failure() }} steps: - run: echo "有一个 job 失败了"
fromJSON(value)
解析一个 JSON 字符串。等同于 JSON.parse()。
示例:
jobs: publish_update: type: update print_debug_info: needs: [publish_update] steps: - run: | echo "第一个 update group:${{ needs.publish_update.outputs.first_update_group_id }}" echo "第二个 update group:${{ fromJSON(needs.publish_update.outputs.updates_json || '[]')[1].group }}"
toJSON(value)
将一个值转换为 JSON 字符串。等同于 JSON.stringify()。
示例:
jobs: my_job: steps: - run: echo '${{ toJSON(github.event) }}'
contains(value, substring)
检查 value 是否包含 substring。
示例:
jobs: my_job: if: ${{ contains(github.ref_name, 'feature') }} steps: - run: echo "Feature 分支"
startsWith(value, prefix)
检查 value 是否以 prefix 开头。
示例:
jobs: my_job: if: ${{ startsWith(github.ref_name, 'release') }} steps: - run: echo "Release 分支"
endsWith(value, suffix)
检查 value 是否以 suffix 结尾。
示例:
jobs: my_job: if: ${{ endsWith(github.ref_name, '-production') }} steps: - run: echo "Production 分支"
hashFiles(...globs)
返回与所提供 glob 模式匹配的文件的哈希值。适用于缓存键。
注意:
hashFiles函数仅在 job 的步骤内部可用,而不在 workflow 级别可用。
示例:
jobs: my_job: steps: - run: echo "依赖哈希:${{ hashFiles('package-lock.json', 'yarn.lock') }}"
replaceAll(input, stringToReplace, replacementString)
将 input 中 stringToReplace 的所有出现位置替换为 replacementString。
示例:
jobs: my_job: steps: - run: echo "${{ replaceAll(github.ref_name, '/', '-') }}"
substring(input, start, end)
从 input 中提取子字符串,起始位置为 start,结束位置为 end。如果未提供 end,则会从 start 提取到 input 末尾。底层使用 String#substring。
示例:
jobs: my_job: steps: - run: echo "${{ substring(github.ref_name, 0, 50) }}"
预置作业
jobs.<job_id>.type
指定要运行的预置作业类型。预置作业会根据工作流详情页中的作业类型生成专门的界面。
jobs: my_job: type: build
下面了解不同的预置作业。
build
使用 EAS Build 为你的项目创建 Android 或 iOS 构建。有关详细信息和示例,请参阅 Build 作业文档。
jobs: my_job: type: build params: platform: ios | android # 必需 profile: string # 可选,默认值:production message: string # 可选 refresh_ad_hoc_provisioning_profile: boolean # 可选
此作业输出以下属性:
{ "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 Hosting 部署你的应用。有关详细信息和示例,请参阅 Deploy 作业文档。
jobs: my_job: type: deploy params: alias: string # 可选 prod: boolean # 可选
此作业输出以下属性:
{ "deploy_json": string, // 包含部署详情的 JSON 对象(`npx eas-cli deploy --json` 的输出)。 "deploy_url": string, // 部署的 URL。如果这是生产部署,则使用生产 URL。否则,使用第一个别名 URL 或部署 URL。 "deploy_alias_url": string, // 部署的别名 URL(例如,`https://account-project--alias.expo.app`)。 "deploy_deployment_url": string, // 部署的唯一 URL(例如,`https://account-project--uniqueid.expo.app`)。 "deploy_identifier": string, // 部署标识符。 "deploy_dashboard_url": string, // 部署仪表板的 URL(例如,`https://expo.dev/projects/[project]/hosting/deployments`)。 }
fingerprint
计算你项目的指纹。有关详细信息和示例,请参阅 Fingerprint 作业文档。
jobs: my_job: type: fingerprint environment: production # 应与你的构建配置文件匹配
此作业输出以下属性:
{ "android_fingerprint_hash": string, "ios_fingerprint_hash": string, }
注意: 为了准确匹配指纹,请确保 fingerprint 作业的
environment与你的构建配置文件一致。为了在各作业之间获得更好的一致性,建议使用 EAS 环境变量 而不是env。
get-build
从 EAS 中检索与所提供参数匹配的现有构建。有关详细信息和示例,请参阅 Get Build 作业文档。
jobs: my_job: type: get-build params: platform: ios | android # 可选 profile: string # 可选 distribution: store | internal | simulator # 可选 channel: string # 可选 app_identifier: string # 可选 app_build_version: string # 可选 app_version: string # 可选 git_commit_hash: string # 可选 fingerprint_hash: string # 可选 sdk_version: string # 可选 runtime_version: string # 可选 simulator: boolean # 可选 wait_for_in_progress: boolean # 可选
此作业输出以下属性:
{ "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 将 Android 或 iOS 构建提交到应用商店。有关详细信息和示例,请参阅 Submit 作业文档。
jobs: my_job: type: submit params: build_id: string # 必需 profile: string # 可选,默认值:production groups: string[] # 可选 hooks: before_submit: step[] # 可选 after_submit: step[] # 可选
此作业输出以下属性:
{ "apple_app_id": string | null, // Apple App ID。https://expo.fyi/asc-app-id "ios_bundle_identifier": string | null, // 已提交构建的 iOS bundle 标识符。https://expo.fyi/bundle-identifier "android_package_id": string | null // 已提交的 Android 包名 ID。https://expo.fyi/android-package }
testflight
将 iOS 构建分发到 TestFlight 的内部和外部测试组。有关详细信息和示例,请参阅 TestFlight 作业文档。
jobs: my_job: type: testflight params: build_id: string # 必需 profile: string # 可选,默认值:production internal_groups: string[] # 可选 external_groups: string[] # 可选 changelog: string # 可选 submit_beta_review: boolean # 可选 wait_processing_timeout_seconds: number # 可选,默认值:1800(30 分钟)
此作业输出以下属性:
{ "apple_app_id": string | null, // Apple App ID。https://expo.fyi/asc-app-id "ios_bundle_identifier": string | null // 已提交构建的 iOS bundle 标识符。https://expo.fyi/bundle-identifier }
update
使用 EAS Update 发布更新。有关详细信息和示例,请参阅 Update 作业文档。
jobs: my_job: type: update params: message: string # 可选 platform: string # 可选 - android | ios | all,默认值为 all branch: string # 可选 channel: string # 可选 - 不能与 branch 同时使用 private_key_path: string # 可选 upload_sentry_sourcemaps: boolean # 可选 - 默认行为是“尝试上传,但如果失败则不让作业失败”
此作业输出以下属性:
{ "first_update_group_id": string, // 第一个更新组的 ID。你可以将其用于例如为开发客户端深度链接构造更新 URL。 "updates_json": string // 更新组的字符串化 JSON 数组。`eas update --json` 的输出。 }
maestro
在构建上运行 Maestro 测试。有关详细信息和示例,请参阅 Maestro 作业文档。
重要: Maestro 测试处于 alpha 阶段。
jobs: my_job: type: maestro environment: production | preview | development # 可选,默认值为 preview image: string # 可选。有关可用镜像列表,请参阅 https://docs.expo.dev/eas/workflows/syntax/#jobsjob_idruns_on params: build_id: string # 必需 flow_path: string | string[] # 必需 shards: number # 可选,默认值为 1 retries: number # 可选,默认值为 0 retry_failed_only: boolean # 可选,默认值为 true。为 true 时,在适用情况下,重试只会重新运行上一次尝试中失败的流程。 record_screen: boolean # 可选,默认值为 false。为 true 时,会上传测试的录屏。 include_tags: string | string[] # 可选。要包含在测试中的标签。将作为 `--include-tags` 传递给 Maestro。 exclude_tags: string | string[] # 可选。要从测试中排除的标签。将作为 `--exclude-tags` 传递给 Maestro。 maestro_version: string # 可选。测试要使用的 Maestro 版本。如果未提供,则使用最新版本。 android_system_image_package: string # 可选。要使用的 Android Emulator 系统镜像包。 device_identifier: string | { android?: string, ios?: string } # 可选。测试要使用的设备标识符。 output_format?: string # 可选,默认值为 junit。Maestro 测试报告格式。将作为 `--format` 传递给 Maestro。可以是 `junit` 或其他受支持的格式。 hooks: before_maestro_tests: step[] # 可选 after_maestro_tests: step[] # 可选
maestro-cloud
在 Maestro Cloud 中的构建上运行 Maestro 测试。有关详细信息和示例,请参阅 Maestro Cloud 作业文档。
重要: 在 Maestro Cloud 中运行测试需要 Maestro Cloud 账户和 Cloud Plan 订阅。前往 Maestro 文档 了解更多。
jobs: my_job: type: maestro-cloud environment: production | preview | development # 可选,默认值为 preview image: string # 可选。有关可用镜像列表,请参阅 https://docs.expo.dev/eas/workflows/syntax/#jobsjob_idruns_on params: build_id: string # 必需。要测试的构建 ID。 maestro_project_id: string # 必需。Maestro Cloud 项目 ID。示例:`proj_01jw6hxgmdffrbye9fqn0pyzm0`。 flows: string # 必需。Maestro flow 文件或包含要运行的 flows 的目录路径。对应 `maestro cloud` 的 `--flows` 参数。 maestro_api_key: string # 可选。用于 Maestro 项目的 API 密钥。默认情况下,将使用 `MAESTRO_CLOUD_API_KEY` 环境变量。对应 `maestro cloud` 的 `--api-key` 参数。 include_tags: string | string[] # 可选。要包含在测试中的标签。将作为 `--include-tags` 传递给 Maestro。 exclude_tags: string | string[] # 可选。要从测试中排除的标签。将作为 `--exclude-tags` 传递给 Maestro。 maestro_version: string # 可选。测试要使用的 Maestro 版本。如果未提供,则使用最新版本。 maestro_config: string # 可选。测试要使用的 Maestro `config.yaml` 文件路径。将作为 `--config` 传递给 Maestro。 device_locale: string # 可选。测试要使用的设备区域设置。将作为 `--device-locale` 传递给 Maestro。 device_model: string # 可选。测试要使用的设备型号。将作为 `--device-model` 传递给 Maestro。运行 `maestro list-cloud-devices` 查看支持的值列表。 device_os: string # 可选。测试要使用的设备操作系统。将作为 `--device-os` 传递给 Maestro。运行 `maestro list-cloud-devices` 查看支持的值列表。 name: string # 可选。Maestro Cloud 上传的名称。对应 `maestro cloud` 的 `--name` 参数。 branch: string # 可选。覆盖 Maestro Cloud 上传来源的分支。默认情况下,如果工作流运行是从 GitHub 触发的,则使用工作流运行的分支。对应 `maestro cloud` 的 `--branch` 参数。 async: boolean # 可选。异步运行 Maestro Cloud 测试。如果为 true,则作业状态仅表示上传是否成功,*不*表示测试是否成功。对应 `maestro cloud` 的 `--async` 参数。 hooks: before_maestro_cloud: step[] # 可选。在上传到 Maestro Cloud 之前运行的步骤。 after_maestro_cloud: step[] # 可选。在上传到 Maestro Cloud 之后运行的步骤。
slack
使用 webhook URL 向 Slack 频道发送消息。有关详细信息和示例,请参阅 Slack 作业文档。
jobs: my_job: type: slack params: webhook_url: string # 必需 message: string # 必需,如果未提供 payload payload: object # 必需,如果未提供 message
github-comment
自动将你工作流中已完成的构建、更新和部署的综合报告发布到 GitHub pull request,或你提供的内容中。有关详细信息和示例,请参阅 GitHub Comment 作业文档。
jobs: my_job: type: github-comment params: message: string # 可选 - 要包含在报告中的自定义消息 build_ids: string[] # 可选 - 要包含的特定构建 ID,默认包含与正在运行的工作流相关的全部构建 update_group_ids: string[] # 可选 - 要包含的特定更新组 ID,默认包含与工作流相关的全部更新组 deployment_ids: string[] # 可选 - 要包含的特定部署 ID,默认包含与工作流相关的全部部署 # 不使用 message 以及构建、更新和部署表格时,你也可以用 `payload` 覆盖评论内容 custom_github_comment: type: github-comment params: payload: string # 可选 - 用于完全自定义评论的原始 markdown/HTML 内容
此作业输出以下属性:
{ "comment_url": string | undefined // 已发布的 GitHub 评论 URL }
apple-device-registration-request
暂停工作流,直到某个 iOS 设备为 Apple Team 注册,并且团队成员批准该注册。有关详细信息和示例,请参阅 Apple device registration request 作业文档。
jobs: register_device: type: apple-device-registration-request params: apple_team_identifier: string # 可选
require-approval
在继续工作流之前,需要用户批准。用户可以批准或拒绝,这将分别转换为作业成功或失败。有关详细信息和示例,请参阅 Require Approval 作业文档。
jobs: confirm: type: require-approval
doc
在工作流日志中显示一个 Markdown 区块。有关详细信息和示例,请参阅 Doc 作业文档。
jobs: next_steps: type: doc params: md: string
repack
从现有构建重新打包一个应用。此作业会重新打包应用的元数据和 JavaScript bundle,而不执行完整的原生重新构建,这对于创建与特定指纹兼容的更快构建很有用。有关详细信息和示例,请参阅 Repack 作业文档。
jobs: next_steps: type: repack params: build_id: string # 必需 profile: string # 可选 embed_bundle_assets: boolean # 可选 message: string # 可选 repack_version: string # 可选
自定义任务
运行自定义代码,并可使用内置 EAS 函数。不需要 type 字段。
jobs: my_job: steps: # ...
jobs.<job_id>.steps
一个任务包含一个名为 steps 的任务序列。步骤可以运行命令。steps 只能在自定义任务和 build 任务中提供。
jobs: my_job: steps: - name: My first step run: echo "Hello World"
jobs.<job_id>.outputs
由任务定义的输出列表。这些输出可供所有依赖此任务的下游任务访问。要设置输出,请在任务步骤中使用 set-output 函数。
下游任务可以在 插值上下文 中使用以下表达式访问这些输出:
needs.<job_id>.outputs.<output_name>after.<job_id>.outputs.<output_name>
这里,<job_id> 指的是上游任务的标识符,而 <output_name> 指的是你想访问的特定输出变量。
在下面的示例中,set-output 函数在 job_1 的 step_1 步骤中将名为 test 的输出设置为值 hello world。之后,在 job_2 中,可以在 step_2 里使用 needs.job_1.outputs.output_1 访问它。
jobs: job_1: outputs: output_1: ${{ steps.step_1.outputs.test }} steps: - id: step_1 run: set-output test "hello world" job_2: needs: [job_1] steps: - id: step_2 run: echo ${{ needs.job_1.outputs.output_1 }}
jobs.<job_id>.image
指定用于该任务的 VM 镜像。可用镜像请参见 Infrastructure。
jobs: my_job: image: auto | string # optional, defaults to 'auto'
jobs.<job_id>.runs_on
指定将执行该任务的 worker。仅适用于自定义任务。
jobs: my_job: runs_on: linux-medium | linux-large | linux-medium-nested-virtualization | linux-large-nested-virtualization | macos-medium | macos-large # optional, defaults to linux-medium
| 工作器 | 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 模拟器。 |
| 工作器 | 效率核心数 | 统一内存(GiB RAM) | SSD(GiB) | 备注 |
|---|---|---|---|---|
| macos-medium | 5 | 20 | 125 | 运行 iOS 作业,包括模拟器。 |
| macos-large | 10 | 40 | 125 | 运行 iOS 作业,包括模拟器。 |
注意: 对于 Android Emulator 任务,必须使用
linux-*-nested-virtualizationworker。对于 iOS 构建和 iOS Simulator 任务,必须使用macos-*worker。
jobs.<job_id>.steps.<step>.id
id 属性用于在作业中引用该步骤。可用于在下游作业中使用该步骤的输出。
jobs: my_job: outputs: test: ${{ steps.step_1.outputs.test }} # 引用 step_1 的输出 steps: - id: step_1 run: set-output test "hello world"
jobs.<job_id>.steps.<step>.name
该步骤的人类可读名称,会显示在作业日志中。当步骤未提供名称时,会使用 run 命令作为步骤名称。
jobs: my_job: steps: - name: 我的第一步 run: echo "Hello World"
jobs.<job_id>.steps.<step>.run
在该步骤中运行的 shell 命令。
jobs: my_job: steps: - run: echo "Hello World"
jobs.<job_id>.steps.<step>.shell
用于运行命令的 shell。默认值为 bash。
jobs: my_job: steps: - run: echo "Hello World" shell: bash
jobs.<job_id>.steps.<step>.working_directory
运行命令的目录。当在步骤级别定义时,如果作业中也定义了 jobs.<job_id>.defaults.run.working_directory 设置,则该设置会被覆盖。
jobs: my_job: steps: - uses: eas/checkout - run: pwd # 输出:/home/expo/workingdir/build/my-app working_directory: ./my-app
jobs.<job_id>.steps.<step>.uses
EAS 提供了一组内置的可复用函数,可在工作流步骤中使用。uses 关键字用于指定要使用的函数。所有内置函数都以 eas/ 前缀开头。
jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules - uses: eas/prebuild - name: 列出文件 run: ls -la
以下是你可以在工作流步骤中使用的内置函数列表。
eas/checkout
检出你的项目源文件。
jobs: my_job: steps: - uses: eas/checkout
在 GitHub 上查看 eas/checkout 函数的源代码。
eas/install_node_modules
使用根据你的项目检测到的包管理器(bun、npm、pnpm 或 Yarn)安装 node_modules。适用于 monorepo。
jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules
在 GitHub 上查看 eas/install_node_modules 函数的源代码。
eas/download_build
下载给定构建的应用归档。默认情况下,下载的产物可以是 .apk、.aab、.ipa 或 .app 文件,或者是一个包含这些文件中一个或多个的 .tar.gz 归档。如果产物是 .tar.gz 归档,它将被解压,并返回匹配指定扩展名的第一个文件。如果构建未生成任何应用归档,该步骤将失败。
jobs: my_job: steps: - uses: eas/download_build with: build_id: string # 必填。要下载的构建 ID。 extensions: [apk, aab, ipa, app] # 可选。要查找的文件扩展名列表。默认为 ["apk", "aab", "ipa", "app"]。
| 属性 | 类型 | 必需 | 默认 | 描述 |
|---|---|---|---|---|
build_id | string | – | 要下载的构建 ID。必须是有效的 UUID。 | |
extensions | string[] | ["apk", "aab", "ipa", "app"] | 在下载的产物或归档中要查找的文件扩展名列表。 |
输出:
artifact_path:匹配的应用归档的绝对路径。此输出可作为工作流中其他步骤的输入。例如,可用于进一步上传或处理该产物。
示例用法:
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: 打印产物路径 run: | echo "Artifact path: ${{ steps.download_build.outputs.artifact_path }}"
在 GitHub 上查看 eas/download_build 函数的源代码。
eas/prebuild
使用根据你的项目检测到的包管理器(bun、npm、pnpm 或 Yarn),并采用最适合你的构建类型和构建环境的命令来运行 expo prebuild 命令。
jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules - uses: eas/prebuild
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 - uses: eas/prebuild with: clean: false apple_team_id: ${{ steps.resolve_apple_team_id_from_credentials.outputs.apple_team_id }}
| 属性 | 类型 | 描述 |
|---|---|---|
clean | boolean | 可选属性,定义在运行命令时该函数是否应使用 --clean 标志。默认值为 false。 |
apple_team_id | string | 可选属性,定义在进行 prebuild 时应使用的 Apple team ID。使用凭据进行 iOS 构建时应指定此项。 |
在 GitHub 上查看 eas/prebuild 函数的源代码。
eas/restore_cache
从指定键恢复之前保存的缓存。这对于通过重用已缓存的产物(如已编译的依赖项、构建工具或其他中间构建输出)来加快构建速度很有用。
jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules - uses: eas/prebuild - uses: eas/restore_cache with: key: cache-${{ hashFiles('package-lock.json') }} restore_keys: cache path: /path/to/cache
jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules - uses: eas/prebuild - uses: eas/restore_cache with: key: cache-${{ hashFiles('package-lock.json') }} path: /path/to/cache
| 属性 | 类型 | 必需 | 描述 |
|---|---|---|---|
key | string | 要恢复的缓存键。你可以使用诸如 ${{ hashFiles('package-lock.json') }} 之类的表达式,根据文件哈希创建动态键。 | |
restore_keys | string | 如果找不到精确键时使用的回退键或前缀。如果提供,缓存系统将查找任何以该前缀开头的缓存条目。 | |
path | string | 应恢复缓存的路径。应与保存缓存时使用的路径相匹配。 |
在 GitHub 上查看 eas/restore_cache 函数的源代码。
eas/save_cache
将缓存保存到指定键。这使你可以持久化构建产物、已编译的依赖项或其他中间输出,以便在后续构建中重复使用,从而加快构建过程。
jobs: my_job: steps: - uses: eas/checkout - uses: eas/install_node_modules - uses: eas/prebuild - uses: eas/restore_cache with: key: cache-${{ hashFiles('package-lock.json') }} path: /path/to/cache - name: 构建 Android 应用 run: cd android && ./gradlew assembleRelease - uses: eas/save_cache with: key: cache-${{ hashFiles('package-lock.json') }} path: /path/to/cache
| 属性 | 类型 | 必需 | 描述 |
|---|---|---|---|
key | string | 要将缓存保存到其下的缓存键。你可以使用诸如 ${{ hashFiles('package-lock.json') }} 之类的表达式,根据文件哈希创建动态键。这应与恢复缓存时使用的键一致。 | |
path | string | 应缓存的目录或文件路径。这应与恢复缓存时使用的路径一致。 |
在 GitHub 上查看 eas/save_cache 函数的源代码。
eas/send_slack_message
向已配置的 Slack webhook URL 发送指定消息,然后它会将消息发布到相关的 Slack 频道。消息可以指定为纯文本或 Slack Block Kit 消息。
在这两种情况下,你都可以在消息中引用构建作业属性,并且可以 使用其他步骤的输出 进行动态求值。例如,Build URL: https://expo.dev/builds/${{ needs.build_ios.outputs.build_id }}、Build finished with status: ${{ after.build_android.status }}。
必须指定 message 或 payload 其中之一,但不能同时指定两者。
jobs: my_job: steps: - uses: eas/send_slack_message with: message: '这是发送到普通输入 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 Block Kit 布局定义。 注意: 必须提供 message 或 payload 其中之一,但不能同时提供两者。 |
slack_hook_url | string | 之前配置好的 Slack webhook URL,它会将你的消息发布到指定频道。你可以提供类似 slack_hook_url: 'https://hooks.slack.com/services/[rest_of_hook_url]' 的普通 URL,使用 EAS 环境变量 例如 slack_hook_url: ${{ env.ANOTHER_SLACK_HOOK_URL }},或者设置 SLACK_HOOK_URL 环境变量,它将作为默认 webhook URL(在后一种情况下,无需提供 slack_hook_url 属性)。 |
在 GitHub 上查看 eas/send_slack_message 函数的源代码。
eas/use_npm_token
配置 Node 包管理器(bun、npm、pnpm 或 Yarn)以便与私有包一起使用,这些包可以发布到 npm 或私有注册表。
在项目的 secrets 中设置 NPM_TOKEN,该函数将通过使用该令牌创建 .npmrc 来配置构建环境。
jobs: my_job: name: 安装私有 npm 模块 steps: - uses: eas/checkout - uses: eas/use_npm_token - name: 安装依赖 run: npm install # <---- 现在可以安装私有包了
在 GitHub 上查看 eas/use_npm_token 函数的源代码。
eas/download_artifact
根据工件的 ID 或名称从 EAS 下载一个工件。适用于将前面作业中的工件发送到其他服务。
jobs: my_job: steps: - uses: eas/download_artifact with: name: string # 如果未提供 artifact_id,则为必填。要下载的工件名称。 artifact_id: string # 如果未提供 artifact_name,则为必填。要下载的工件 ID。
属性
| 属性 | 类型 | 必需 | 描述 |
|---|---|---|---|
name | string | 要下载的工件名称。如果未提供 artifact_id,则为必填。 | |
artifact_id | string | 要下载的工件 ID。如果未提供 name,则为必填。 |
输出
| 属性 | 类型 | 描述 |
|---|---|---|
artifact_path | string | 已下载工件的路径。此输出可作为工作流中其他步骤的输入。例如,可用于发送或处理该工件。 |
示例
jobs: maestro_tests: type: maestro params: build_id: '123-abc' flow_path: 'path/to/flow.yaml' my_job: needs: [maestro_tests] steps: - uses: eas/download_artifact id: download_artifact with: name: 'iOS Maestro Test Report (junit)' - name: 打印 Maestro 输出 run: echo ${{ steps.download_artifact.outputs.artifact_path }}
在 GitHub 上查看 eas/download_artifact 函数的源代码。
内置 shell 函数
EAS Workflows 提供了以下 shell 函数,你可以在工作流步骤中使用它们来设置变量输出。
set-output
设置一个输出变量,其他步骤或工作流中的其他任务可以访问它。
set-output <name> <value>
用于与另一个步骤共享变量的示例:
jobs: my_job: steps: - id: step_1 run: set-output variable_1 "变量 1" - id: step_2 run: echo ${{ steps.step_1.outputs.variable_1 }} # 输出:变量 1
用于与另一个任务共享变量的示例:
jobs: job_1: outputs: variable_1: ${{ steps.step_1.outputs.variable_1 }} steps: - id: step_1 run: set-output variable_1 "变量 1" job_2: needs: [job_1] steps: - run: echo ${{ needs.job_1.outputs.variable_1 }} # 输出:变量 1
set-env
设置一个环境变量,该变量可供同一任务中的后续步骤使用。在一个步骤的命令中使用 export 导出的环境变量不会自动暴露给其他步骤。若要与其他步骤共享环境变量,请使用 set-env 可执行文件。
set-env <name> <value>
set-env 需要传入两个参数:环境变量的名称和值。例如,set-env NPM_TOKEN "abcdef" 会将 $NPM_TOKEN 变量及其值 abcdef 暴露给后续步骤。
注意: 使用
set-env共享的变量不会自动在本地导出。如果你想在当前步骤中使用该变量,需要自行调用export。
用于与另一个步骤共享环境变量的示例:
jobs: my_job: steps: - name: 设置环境变量 run: | # 仅使用 export 只会让它在当前步骤中可用 export LOCAL_VAR="仅在此步骤中" # 使用 set-env 会让它在后续步骤中可用 set-env SHARED_VAR "在后续步骤中可用" # SHARED_VAR 目前在当前步骤的环境中还不可用 echo "LOCAL_VAR: $LOCAL_VAR" # 输出:仅在此步骤中 echo "SHARED_VAR: $SHARED_VAR" # 输出:(空) - name: 使用共享变量 run: | # SHARED_VAR 现在可用了 # @info # echo "SHARED_VAR: $SHARED_VAR" # 输出:在后续步骤中可用 # @end #
在任务之间共享环境变量
set-env 函数只会在同一任务内与其他步骤共享环境变量。若要在不同任务之间共享值,请使用任务的 outputs 配合 set-output,并通过接收任务上的 env 属性传递它们:
jobs: job_1: outputs: my_value: ${{ steps.step_1.outputs.my_value }} steps: - id: step_1 run: set-output my_value "来自 job_1 的值" job_2: needs: [job_1] env: MY_VALUE: ${{ needs.job_1.outputs.my_value }} steps: - run: echo "MY_VALUE: $MY_VALUE" # 输出:来自 job_1 的值