---
url: /zh/build/grammar.md
---
## 概述

CNB 流水线通过 `.cnb.yml` 配置文件来定义自动化构建流程。

## 基本结构

CNB 流水线采用层级结构，从外到内依次是：

* **触发分支**：指定哪些分支的代码会触发流水线（如 `main`、`dev`）
* **触发事件**：指定什么操作会触发流水线（如 `push`、`pull_request`）
* **Pipeline（流水线）**：一次完整的构建流程，包含多个阶段
* **Stage（阶段）**：流水线中的一个步骤，可包含一个或多个任务
* **Job（任务）**：最小的执行单元，执行具体的命令或插件

### 执行流程示意

```text
触发分支 (main)
  └─ 触发事件 (push)
      ├─ Pipeline (流水线)
      │   └─ Stage 阶段）
      │       └─ Job（任务）
      └─ Pipeline (流水线)
          └─ Stage（阶段）
              └─ Job（任务）
```

### 完整示例

以下是一个包含所有层级的完整示例：

```yaml
main: # 触发分支：在 main 分支上触发
  push: # 触发事件：代码推送时触发
    - name: pipeline-1 # 流水线名称
      stages:
        - name: stage-1 # 阶段名称
          jobs:
            - name: job-1 # 任务名称
              script: echo "in pipeline-1"
    - name: pipeline-2 # 流水线名称
      stages:
        - name: stage-1 # 阶段名称
          jobs:
            - name: job-1 # 任务名称
              script: echo "in pipeline-2"
```

也支持通过对象的形式声明多个流水线：

```yaml
main: # 触发分支
  push: # 触发事件
    pipeline-1: # 流水线标识
      stages:
        - name: stage-1
          jobs:
            - name: job-1
              script: echo "Hello World"
    pipeline-2: # 流水线标识
      stages:
        - name: stage-1
          jobs:
            - name: job-1
              script: echo "Hello World"
```

## 触发分支 {#trigger-branch}

**作用**：指定哪些分支的代码会触发流水线。

**类型**：字符串

**支持模式**：

* **精确匹配**：`main`、`dev`、`release`
* **通配符匹配**（glob语法）：`feature/*`、`hotfix/*`
* **兜底匹配**：使用 `$` 匹配所有未明确指定的分支

**示例**：

```yaml
# 只在 main 分支触发
main:
  push:
    - stages:
        - echo "main branch"

# 匹配所有 feature 开头的分支
feature/*:
  push:
    - stages:
        - echo "feature branch"

# 兜底：匹配其他所有分支
$:
  push:
    - stages:
        - echo "other branches"
```

**更多详情**：[触发机制#触发分支](./trigger-rule.md#trigger-branch)

## 触发事件 {#trigger-event}

**作用**：指定什么操作会触发流水线执行。

**常用事件**：

* `push`：代码推送到分支时触发
* `pull_request`：创建或更新 Pull Request 时触发
* `tag_push`：推送标签时触发
* `branch.delete`：删除分支时触发

**示例**：

```yaml
main:
  # 代码推送时执行测试
  push:
    - stages:
        - npm test

  # PR 时执行代码检查
  pull_request:
    - stages:
        - npm run lint
```

**更多详情**：[触发机制#触发事件](./trigger-rule.md#trigger-event)

## Pipeline {#pipeline}

`Pipeline` 表示一个流水线，包含一个或多个阶段 `Stage`，每个 `Stage` 依次执行。

以下是一个包含所有可用配置项的完整示例（实际使用时根据需要选择）：

```yaml
name: 流水线名字
docker:
  image: node
  build: dev/Dockerfile
  volumes:
    - /root/.npm:copy-on-write
git:
  enable: true
  submodules: true
  lfs: true
services:
  - docker
env:
  TEST_KEY: TEST_VALUE
imports:
  - https://cnb.build/<your-repo-slug>/-/blob/main/xxx/envs.yml
  - ./env.txt
label:
  type: MASTER
  class: MAIN
stages:
  - name: stage 1
    script: echo "stage 1"
  - name: stage 2
    script: echo "stage 2"
  - name: stage 3
    script: echo "stage 3"
failStages:
  - name: fail stage 1
    script: echo "fail stage 1"
  - name: fail stage 2
    script: echo "fail stage 2"
endStages:
  - name: end stage 1
    script: echo "end stage 1"
  - name: end stage 2
    script: echo "end stage 2"
ifModify:
  - a.txt
  - "src/**/*"
retry: 3
allowFailure: false
```

### name {#pipeline-name}

* type: `String`

指定流水线名，默认为 `pipeline`。当有多条并行流水线时，
默认流水线名为 `pipeline`、`pipeline-1`、`pipeline-2` 依此类推，
可定义 `name` 指定流水线名来区分不同流水线。

### runner

* type: `Object`

指定构建节点相关参数。

* `tags`: 可选，指定使用具备哪些标签的构建节点
* `cpus`: 可选，指定构建需使用的 cpu 核数

#### tags

* type: `String` ｜ `Array<String>`
* default: `cnb:arch:default`

指定使用具备哪些标签的构建节点。详见[构建节点](/zh/build/build-node.md)。

示例：

```yaml
main:
  push:
    - runner:
        tags: cnb:arch:amd64
      stages:
        - name: uname
          script: uname -a
```

#### cpus

* type: `Number`

指定构建需使用的最大 cpu 核数（memory = cpu 核数 \* 2 G），
其中 cpu 和 memory 不超过 runner 机器实际大小。

未配置，则最大可用 cpu 核数由分配到的 runner 机器配置来指定。

示例：

```yaml
# cpus = 1，memory = 2G
main:
  push:
    - runner:
        cpus: 1
      stages:
        - name: echo
          script: echo "hello world"
```

### docker

* type: `Object`

指定 `docker` 相关的参数。详情见[构建环境](./build-env.md)

* `image`: 当前 `Pipeline`  的环境镜像，在当前 `Pipeline` 下的所有任务都将在这个镜像环境中执行。
* `build`: 指定一个 `Dockerfile`，构建一个临时镜像，做为 `image` 的值使用。
* `devcontainer`: 指定 `devcontainer.json` 文件所在路径, 将使用 `devcontainer.json` 文件内容作为流水线容器镜像。
* `volumes`: 声明数据卷，用于缓存场景。

注： 当同时指定 `image`, `build`, `devcontainer`, 优先级为 `build` > `devcontainer` > `image`。

#### image {#pipeline-image}

* type: `Object` | `String`

指定当前 `Pipeline`  的环境镜像，在当前 `Pipeline` 下的所有任务都将在这个镜像环境中执行。

该属性以及其下的属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

* `image.name`: `String` 镜像名，如 `node:20`。
* `image.dockerUser`: `String` 指定 Docker 用户名，用于拉取指定的镜像。
* `image.dockerPassword`: `String` 指定 Docker 用户密码，用于拉取指定的镜像。

如果指定 `image` 为字符串，则等同于指定了 `image.name`。

如果使用 `云原生构建` 的 Docker 制品库的镜像且未设置 `image.dockerPassword`，该参数会设为环境变量 [CNB\_TOKEN](./build-in-env.md#cnb_token) 的值。

示例一，使用公开镜像：

```yaml
main:
  push:
    - docker:
        # 取 docker 官方镜像仓库中的 node:20 镜像作为构建容器
        image: node:20
      stages:
        - name: show version
          script: node -v
```

示例二，使用 CNB 制品库私有镜像：

```yaml
main:
  push:
    - docker:
        # 取非公开镜像作为构建容器，需传入 docker 用户名和密码
        image:
          name: docker.cnb.build/images/pipeline-env:1.0
          # 使用 CI 构建时默认注入的环境变量
          dockerUser: $CNB_TOKEN_USER_NAME
          dockerPassword: $CNB_TOKEN
      stages:
        - name: echo
          script: echo "hello world"
```

示例三，使用 docker 官方镜像源私有镜像：

```yaml
main:
  push:
    - imports: https://cnb.build/<your-repo-slug>/-/blob/main/xxx/docker.yml
      docker:
        # 取非公开镜像作为构建容器，需传入 docker 用户名和密码
        image:
          name: images/pipeline-env:1.0
          # docker.yml 中导入的环境变量
          dockerUser: $DOCKER_USER
          dockerPassword: $DOCKER_PASSWORD
      stages:
        - name: echo
          script: echo "hello world"
```

docker.yml

```yaml
DOCKER_USER: user
DOCKER_PASSWORD: password
```

#### build

* type: `Object` | `String`

指定一个 `Dockerfile`，构建一个临时镜像，做为 `image` 的值使用。

该属性以及其下的属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

使用 `build` 声明构建环境的完整示例可参考[docker-build-with-by](https://cnb.cool/examples/ecosystem/docker-build-with-by)。

以下是 `build` 下各参数的说明：

* `build.dockerfile`:

  * type: `String`

  `Dockerfile`路径。

  该属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

* `build.target`:

  * type: `String`

  对应 docker build 中的 --target 参数，可以选择性地构建 Dockerfile 中的特定阶段，而不是构建整个 Dockerfile。

* `build.by`:

  * type: `Array<String>` | `String`

  用来声明缓存构建过程中依赖的文件列表。

  **注意：未出现在 `by` 列表中的文件，除了 Dockerfile，其他在构建镜像过程中，都当不存在处理。**

  `String` 类型时，多个文件可用英文逗号分隔。

* `build.versionBy`:

  * type: `Array<String>` | `String`

  用来进行版本控制，所指向的文件内容发生变化，我们就会认为是一个新的版本。
  具体的计算逻辑见这个表达式：sha1(dockerfile + versionBy + buildArgs)。

  支持直接传入文件夹路径，会直接使用该文件夹的`git tree id`参与版本号计算。

  `String`类型时，多个文件可用英文逗号分隔。

* `build.buildArgs`:

  * type: `Object`

  在 build 时插入额外的构建参数 (`--build-arg $key=$value`)，value 值为 null 时只加入 key (`--build-arg $key`)。

* `build.ignoreBuildArgsInVersion`:

  * type: `Boolean`

  版本计算是否忽略buildArgs。详见 `versionBy`。

* `build.sync`:

  * type: `String`

  是否等待 `docker push` 成功后才继续。默认为`false`。

如果指定 `build` 为字符串，则等同于指定了 `build.dockerfile`。

**Dockerfile 用法：**

```yaml
main:
  push:
    - docker:
        # `build` 为字符串，则等同于指定了 `build.dockerfile`
        build: ./image/Dockerfile
      stages:
        - stage1
        - stage2
        - stage3
```

```yaml
main:
  push:
    - docker:
        # 指定了 `build` 的类型为 `Object`，可对构建镜像过程进行更多控制
        build:
          dockerfile: ./image/Dockerfile
          # 只构建 builder，而不是整个Dockerfile
          target: builder
      stages:
        - stage1
        - stage2
        - stage3
```

**Dockerfile versionBy 用法：**

示例：将 pnpm 缓存到环境镜像中，加速后续pnpm i过程

```dockerfile title="Dockerfile"
FROM node:22

RUN npm config set registry https://mirrors.cloud.tencent.com/npm/ \
  && npm i -g pnpm

WORKDIR /data/cache

COPY package.json package-lock.json ./

RUN pnpm i
```

```yaml title=".cnb.yml"
main:
  push:
    # 通过 Dockerfile 指定构建环境
    - docker:
        build:
          dockerfile: ./Dockerfile
          by:
            - package.json
            - package-lock.json
          versionBy:
            - package-lock.json
      stages:
        - name: cp node_modules
          # 从容器中将 node_modules 复制到流水线工作目录
          script: cp -r /data/cache/node_modules ./
        - name: check node_modules
          script: |
            if [ -d "node_modules" ]; then
                cd node_modules
                ls
            else
                echo "node_modules directory does not exist."
            fi
```

#### devcontainer

* type: `String`

指定 `devcontainer.json` 文件所在路径, 将使用 `devcontainer.json` 文件内容作为流水线容器镜像。

仅支持相对于当前仓库的的路径，如: `.devcontainer/devcontainer.json`

具体 `devcontainer.json` 规范可查看: [devcontainer.json](https://containers.dev/implementors/json_reference/)

由于 CNB 平台特性，目前仅提供有限的支持。当前支持的属性：

* image
* build.dockerfile
* build.context
* build.args
* build.target

#### volumes

* type: `Array<String>` | `String`

声明数据卷，多个数据卷可用通过数组或者用`,`号做分割符传入，可引用环境变量，支持的格式有：

1. `<group>:<path>:<type>`
2. `<path>:<type>`
3. `<path>`

各项含义：

* `group`: 可选，数据卷分组，不同组间相互隔离
* `path`: 必填，数据卷挂载绝对路径，支持绝对路径（`/` 开头） 或 相对路径（`./` 开头），相对于工作区
* `type`: 可选，数据卷类型，**缺省值为 `copy-on-write`**，支持以下类型：
  * `read-write` 或 `rw` : 读写，并发写冲突需自行处理，适用于串行构建场景
  * `read-only` 或 `ro` : 只读，写操作抛出异常
  * `copy-on-write` 或 `cow` : 读写，变更（新增、修改、删除）在 **流水线成功** 后被合并，适用于并发构建场景
  * `copy-on-write-read-only` : 只读，变更（新增、删除、修改）在流水线结束后丢弃
  * `data` : 创建一个临时数据卷，该数据卷在流水线结束时会自动清理

##### copy-on-write

用于缓存场景，支持并发

`copy-on-write` 技术允许系统在需要修改数据之前共享相同的数据副本，从而实现高效的缓存复制。
在并发环境中，这种方法避免了缓存的读写冲突，因为只有在实际需要修改数据时，才会创建数据的私有副本。
这样，只有写操作会导致数据复制，而读操作可以安全地并行进行，无需担心数据一致性问题。
这种机制显著提高了性能，尤其是在读多写少的缓存场景中。

##### data

用于共享数据，将容器中的指定目录，共享给其他容器中使用。

通过创建数据卷，然后 mount 到各容器中。
与直接将构建节点上目录 mount 到容器中方式不同的是：当指定的目录在容器中已经存在，
会先把容器中内容自动复制到数据卷，而不是将数据卷内容直接覆盖容器中目录。

##### volumes 示例

示例1 : 挂载构建节点上目录到容器中，实现本地缓存效果

```yaml
main:
  push:
    - docker:
        image: node:20
        # 声明数据卷
        volumes:
          - /data/config:read-only
          - /data/mydata:read-write

          # 使用缓存，同时更新
          - /root/.npm

          # 使用 main 缓存，同时更新
          - main:/root/.gradle:copy-on-write

      stages:
        - stage1
        - stage2
        - stage3
  pull_request:
    - docker:
        image: node:20

        # 声明数据卷
        volumes:
          - /data/config:read-only
          - /data/mydata:read-write

          # 使用 copy-on-write 缓存
          - /root/.npm
          - node_modules

          # pr 使用 main 缓存，但不更新
          - main:/root/.gradle:copy-on-write-read-only

      stages:
        - stage1
        - stage2
        - stage3
```

示例2：将打包在容器中的文件，共享到其他容器中使用

```yaml
# .cnb.yml
main:
  push:
    - docker:
        image: go-app-cli # 假设有个go应用在镜像的/go-app/cli路径下
        # 声明数据卷
        volumes:
          # 此路径在go-app-cli镜像存在，所以执行环境镜像时，会将此路径内容复制到临时数据卷中，可共享给其他任务容器里使用
          - /go-app
      stages:
        - name: show /go-app-cli in job container
          image: alpine
          script: ls /go-app
```

### git

* type: `Object`

提供 Git 仓库相关配置。

#### git.enable

* type: `Boolean`
* default: `true`

指定是否拉取代码。

`branch.delete` 事件，默认值为 `false`。其他事件，默认值为 `true`。

#### git.submodules

* type: `Object` | `Boolean`
* default:
  * enable: `true`
  * remote: `false`

指定是否要拉取子项目（submodules）。

当值为 `Boolean` 类型时，相当于指定 `git.submodules.enable` 为 `git.submodules` 的值，`git.submodules.remote` 为默认值 `false`。

##### git.submodules.enable

* type: `Boolean`
* default: `true`

是否指定是否要拉取子项目 `submodules`。

##### git.submodules.remote

* type: `Boolean`
* default: `false`

执行 `git submodule update` 时是否添加 `--remote` 参数，用于每次拉取 `submodule` 最新的代码

**基本用法：**

```yaml
main:
  push:
    - git:
        enable: true
        submodules: true
      stages:
        - name: echo
          script: echo "hello world"
    - git:
        enable: true
        submodules:
          enable: true
          remote: true
      stages:
        - name: echo
          script: echo "hello world"
```

#### git.lfs

* type: `Object` | `Boolean`
* default: `true`

指定是否要拉取 LFS 文件。

支持 `Object` 形式指定具体参数，字段缺省时，默认值为：

```json
{
  "enable": true
}
```

**基本用法：**

```yaml
main:
  push:
    - git:
        enable: true
        lfs: true
      stages:
        - name: echo
          script: echo "hello world"
    - git:
        enable: true
        lfs:
          enable: true
      stages:
        - name: echo
          script: echo "hello world"
```

##### git.lfs.enable

是否指定是否要拉取 LFS 文件。

### services

* type: `Array<String>` | `Array<Object>`

用于声明构建时需要的服务，格式：`name:[version]`, `version` 是可选的。

目前支持的服务有：

* docker
* vscode

#### service:docker

用于开启 `dind` 服务，

当构建过程中需要使用 `docker build`，`docker login` 等操作时声明，
会自动在环境注入 `docker daemon` 和 `docker cli`。

示例：

```yaml
main:
  push:
    - services:
        - docker
      docker:
        image: alpine
      stages:
        - name: docker info
          script:
            - docker info
            - docker ps
```

该服务会自动 `docker login` 到 CNB Docker 制品库的镜像源(\_\_ENV\_\_CNB\_DOCKER\_REGISTRY)，
在后续任务中可直接 `docker push` 到当前仓库 Docker 制品库。

示例：

```yaml
main:
  push:
    - services:
        - docker
      stages:
        - name: build and push
          script: |
            # 根目录存在 Dockerfile 文件
            docker build -t ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest .
            docker push ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest
```

若需要使用`buildx`构建多种架构/平台的镜像，可以开启`rootlessBuildkitd`特性。需要在服务中声明：

```yaml
main:
  push:
    - docker:
        image: golang:1.24
      services:
        - name: docker
          options:
            rootlessBuildkitd:
              enabled: true
      env:
        IMAGE_TAG: ${CNB_DOCKER_REGISTRY}/${CNB_REPO_SLUG_LOWERCASE}:latest
      stages:
        - name: go build
          script: ./build.sh
        - name: docker login
          script: docker login -u ${CNB_TOKEN_USER_NAME} -p "${CNB_TOKEN}" ${CNB_DOCKER_REGISTRY}
        - name: docker build and push
          script: docker buildx build -t ${IMAGE_TAG} --platform linux/amd64,linux/amd64/v2,linux/amd64/v3,linux/arm64,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/loong64,linux/arm/v7,linux/arm/v6 --push .
```

#### service:vscode

需要远程开发时声明。

示例：

```yaml
$:
  vscode:
    - services:
        - vscode
        - docker
      docker:
        image: alpine
      stages:
        - name: uname
          script: uname -a
```

如需开启仅预览模式，请参考[文档](../workspaces/only-preview.md)。

如需指定开发环境离线保活时间（超过指定时长，则回收开发环境），可使用 `keepAliveTimeout` 参数：

```yaml
# .cnb.yml
$:
  vscode:
    - docker:
        build: .ide/Dockerfile
      services:
        - docker
        - name: vscode
          options:
            # 保活时间，单位毫秒，不设置默认 10 分钟没有心跳（检测不到开发环境内的 http/ssh 连接）就关闭开发环境
            keepAliveTimeout: 10m
      # 开发环境启动后会执行的任务
      stages:
        - name: ls
          script: ls -al
```

其中 `keepAliveTimeout` 参数定义如下：

* 类型: `Number` | `String`（单位默认为：毫秒）
* 默认值: `600000`（ms, 10分钟）
* 说明: 开发环境的离线保活时间。当检测不到开发环境内的 HTTP/SSH 连接时，超过设定时间后将自动关闭开发环境
* 示例: `3600000` 表示 1 小时

可直接写数字，表示多少毫秒，也可以写成带单位的字符串格式，例如：`10m` 表示 10 分钟，支持以下单位：

* `ms`: 毫秒(默认)
* `s`: 秒
* `m`: 分钟
* `h`: 小时

### env {#pipeline-env}

* type: `Object`

声明一个对象作为环境变量：属性值为环境变量名，属性值为环境变量值。

在任务执行中使用，对当前 `Pipeline` 内的非插件任务均有效。

示例：

```yaml title=".cnb.yml"
main:
  push:
    - services:
        - docker
      env:
        some_key: some_value
      stages:
        - name: some job
          script: echo "$some_value"
```

### imports {#pipeline-imports}

* type: `Array<String>` | `String`

指定 CNB 仓库文件路径（文件相对路径或页面地址），作为环境变量来源，作用同 `env`。

文件内容会被解析为对象，属性名为环境变量名，属性值为环境变量值。

在任务执行中使用，对当前 `Pipeline` 内的非插件任务均有效。

本地相对路径如 `./env.yml` 会被拼接成文件页面地址进行加载。
即：**本地存在但远端不存在的文件不会被引用**。

**`云原生构建` 现支持[密钥仓库](../repo/secret.md)，安全性更高，支持文件引用审计。**
**一般使用一个密钥仓库来存放诸如 `npm` 、 `docker` 等账号密码。**

#### 同名 key 优先级

* 当配置 imports 为数组时，如遇到参数重复的情况，后面的配置会覆盖前面的。
* 如果和 `env` 参数中重复，那么 `env` 中的参数会覆盖掉 `imports` 文件中的。

#### 变量替换

`imports` 文件路径可读取环境变量。若是数组，下面的文件路径可读取上面文件中的变量。

```yaml title="env1.yml"
FILE: "https://cnb.build/<your-repo-slug>/-/blob/main/xxx/env2.yml"
```

```yaml title="env2.yml"
# env2.yml
TEST_TOKEN: some token
```

```yaml title=".cnb.yml"
main:
  push:
    - imports:
        - ./env1.yml
        # FILE 为 env1.yml 中声明的环境变量
        - $FILE
      stages:
        - name: echo
          # TEST_TOKEN 为 env2.yml 中声明的环境变量
          script: echo $TEST_TOKEN
```

#### 引用鉴权

被引用文件可声明可被访问范围，参考 [配置文件引用鉴权](./file-reference.md#权限检查)。

示例：

`team_name/project_name/**`，匹配一个项目下面的所有仓库：

```yaml
key: value

allow_slugs:
  - team_name/project_name/**
```

允许被所有仓库引用

```yaml
key: value

allow_slugs:
  - "**"
```

#### 文件解析规则

支持解析多种文件格式并将其转换为环境变量。文件格式包括：

* YAML 文件

按 YAML 格式解析为对象，支持的文件格式为： `.yaml`、`.yml`。

```yaml title="env.yml"
DOCKER_USER: "username"
DOCKER_TOKEN: "token"
DOCKER_REGISTRY: "https://xxx/xxx"
```

* JSON 文件

按 JSON 格式解析为对象，支持的文件格式为： `.json`。

```json title="env.json"
{
  "DOCKER_USER": "username",
  "DOCKER_TOKEN": "token",
  "DOCKER_REGISTRY": "https://xxx/xxx"
}
```

* 证书文件

证书文件以文件名（`.`被替换为`_`）作为属性名，文件内容作为属性值解析为对象。

支持的文件格式包括 `.crt`、`.cer`、`.key`、`.pem`、`.pub`、`.pk`、`.ppk`。

如：

```bash title="server.crt"
-----BEGIN CERTIFICATE-----
MIIE...
-----END CERTIFICATE-----
```

```yaml title=".cnb.yml"
main:
  push:
    - imports: https://cnb.build/<your-repo-slug>/-/blob/main/server.crt
      stages:
        - echo "$server_crt" > server.crt
        - cat server.crt
```

* 其他文本文件

除以上文件格式外，其他文本文件按 `key=value` 的格式解析为对象。

示例：

```bash title="dev.env"
DB_HOST=localhost
DB_PORT=5432
```

##### 深层属性

大部份场景配置文件是简单的单层属性，比如：

```javascript
// env.json
{
  "token": "private token",
  "password": "private password"
}
```

为了应对复杂的配置文件和场景，深层属性（第一层不能为数组），会平铺成单层对象，规则是：

1. 属性名保留，属性值会转成字符串
2. 属性值若为对象（包括数组），则会递归平铺，属性路径以 `_` 连接

```javascript title="env.json"
{
  "key1": [
    "value1",
    "value2"
  ],
  "key2": {
    "subkey1": [
      "value3",
      "value4"
    ],
    "subkey2": "value5"
  },
  "key3": [
    "value6",
    {
      "subsubkey1": "value7"
    }
  ],
  "key4": "value8"
}
```

会平铺成

```javascript
{
      // 原属性值转成字符串
      "key1": "value1,value2",
      // 属性值若为对象，则额外进行递归平铺操作增加属性
      "key1_0": "value1",
      "key1_1": "value2",
      "key2": "[object Object]",
      "key2_subkey1": "value3,value4",
      "key2_subkey1_0": "value3",
      "key2_subkey1_1": "value4",
      "key2_subkey2": "value5",
      "key3": "value6,[object Object]",
      "key3_0": "value6",
      "key3_1": "[object Object]",
      "key3_1_subsubkey1": "value7",
      "key4": "value8"
    }
```

```yaml title=".cnb.yml"
main:
  push:
    - imports:
        - ./env.json
      stages:
        - name: echo
          script: echo $key3_1_subsubkey1
```

### label

* type: `Object`

为流水线指定标签。每个标签的值可以是一个字符串，也可以是一个字符串数组。该标签可用于后续流水线记录筛选等功能。

这里举一种工作流的例子：**main 分支合并即发布预发布环境，打 tag 后发布正式环境**。

```yaml
main:
  push:
    - label:
        # Master 分支的常规流水线
        type:
          - MASTER
          - PREVIEW
      stages:
        - name: install
          script: npm install
        - name: CCK-lint
          script: npm run lint
        - name: BVT-build
          script: npm run build
        - name: UT-test
          script: npm run test
        - name: pre release
          script: ./pre-release.sh

$:
  tag_push:
    - label:
        # 产品发布分支的常规流水线
        type: RELEASE
      stages:
        - name: install
          script: npm install
        - name: build
          script: npm run build
        - name: DELIVERY-release
          script: ./release.sh
```

### stages

* type: `Array<Stage|Job>`

定义一组阶段任务，每个阶段串行运行。

### failStages

* type: `Array<Stage|Job>`

定义一组失败阶段任务。当正常流程失败，会依次执行此阶段任务。

### endStages

* type: `Array<Stage|Job>`

定义流水线结束阶段执行的一组任务。当流水线 stages/failStages 执行完，流水线结束前，会依次执行此阶段任务。

当流水线 prepare 阶段成功，无论 stages 是否成功，endStages 都将执行。
且 endStages 是否成功不影响流水线状态（即 endStages 失败，流水线状态也可能是成功）。

### ifNewBranch {#pipeline-ifnewbranch}

* type: `Boolean`
* default: `false`

为 `true` 表示当前分支属于新分支（即 `CNB_IS_NEW_BRANCH` 为 `true`）时，才执行此 `Pipeline`。

> 当同时存在 `ifNewBranch` / `ifModify` 时，其中有一个条件满足，此 `Pipeline` 就会执行。

### ifModify {#pipeline-ifmodify}

* type: `Array<String>` | `String`

指定只有相应文件变动时，才执行此 `Pipeline`。
是一个 `glob` 表达式字符串或字符串数组。

#### 支持事件

* 非新建分支的 `push` 事件，会对比 `before` 和 `after`， 统计变更文件。
* `commit.add` 事件，会统计新增 commit 中变更的文件。
* 非新建分支的 `push`、`commit.add` 事件流水线中通过 `cnb:apply` 触发的事件，变更文件统计规则同上。
* `PR` 触发的事件，统计 `PR` 中的变更文件。
* `PR` 触发的事件通过 `cnb:apply` 触发的事件，统计 `PR` 中的变更文件。

因为文件变更可能非常多，变更文件的统计限制为最多300个。

上述情况外，不适合统计文件变更，会忽略 `ifModify` 检查。

#### 示例 {#pipeline-ifmodify-examples}

* 示例1：

当修改文件列表中包含 `a.js` 或者 `b.js`，会执行此 `Pipeline`。

```yaml
ifModify:
  - a.js
  - b.js
```

* 示例2：

当修改文件列表中包含有 `js` 后缀的文件时，会执行此 `Pipeline`。
其中 `**/*.js` 表示匹配所有子目录中的 `js` 后缀文件，`*.js` 表示所有根目录中的 `js` 后缀文件。

```yaml
ifModify:
  - "**/*.js"
  - "*.js"
```

* 示例3：

反向匹配，排除目录 legacy 和排除所有 Markdown 文件，有其他文件变更时触发

```yaml
ifModify:
  - "**"
  - "!(legacy/**)"
  - "!(**/*.md)"
  - "!*.md"
```

* 示例4：

反向匹配，src 目录并且除目录 src/legacy 以外有变更时触发

```yaml
ifModify:
  - "src/**"
  - "!(src/legacy/**)"
```

### breakIfModify {#pipeline-breakifmodify}

* type: `Boolean`
* default: `false`

`Job` 执行前，如果源分支已更新，则终止构建。

### retry {#pipeline-retry}

* type: `Number`
* default: `0`

失败重试次数，`0` 表示不重试。

重试间隔为1s,2s,4s,8s...

### allowFailure {#pipeline-allowfailure}

* type: `Boolean`
* default: `false`

是否允许当前流水线 失败。

当此参数设置为 `true` 时，流水线的失败的状态不会上报到 CNB 上。

### lock {#pipeline-lock}

* type: `Object` | `Boolean`

给 `pipeline` 设置锁，`pipeline` 执行完后自动释放锁，锁不能跨仓库使用。

表现： 流水线A 获取到锁后，流水线B 再申请锁，可以终止A或等待A执行完释放锁后，获取到锁再继续执行任务。

* key:

  * type: `String`

  自定义锁名，默认为 `分支名-流水线名`，既锁范围默认为当前 `pipeline`

* expires:

  * type: `Number`
  * default: `3600`(一小时)

  锁过期时间，过期后自动释放锁，单位“秒”。

* timeout:

  * type: `Number`
  * default: `3600`(一小时)

  超时时间，用于等待锁的场景下，单位“秒”。

* cancel-in-progress:

  * type: `Boolean`
  * default: `false`

  是否终止占用锁或等待锁的流水线，让当前流水线获取锁并执行

* wait:

  * type: `Boolean`
  * default: `false`

  锁被占用是否等待（不占用流水线资源和耗时），为 false 则直接报错，不能与 `cancel-in-progress` 同时使用

* cancel-in-wait:

  * type: `Boolean`
  * default: `false`

  是否终止正在等待锁的流水线，让当前流水线加入等待锁队列。需配合`wait`属性使用。

若 `lock` 为 true，则 `key`、 `expires`、 `timeout`、 `cancel-in-progress`、 `wait`、 `cancel-in-wait`为各自默认值。

例1: lock 是 Boolean 格式

```yaml
main:
  push:
    - lock: true
      stages:
        - name: stage1
          script: echo "stage1"
```

例2: lock 是 Object 格式

```yaml
main:
  push:
    - lock:
        key: key
        expires: 600 # 10分钟
        wait: true
        timeout: 60 # 最多等待 1分钟
      stages:
        - name: stage1
          script: echo "stage1"
```

例3: 停止 pull\_request 下上一条正在进行的流水线

```yaml
main:
  pull_request:
    - lock:
        key: pr
        cancel-in-progress: true
      stages:
        - name: echo hello
          script: echo "stage1"
```

## Stage

* type: `Job` | `Object<name: Job>`

`Stage` 表示流水线中的一个阶段，可包含一个或多个 `Job`。详见 [Job 介绍](#job)。

### 结构说明

#### 单个 Job

当 `Stage` 只有一个 `Job` 时，可省略 `Stage` 层级，直接声明 `Job`。

```yaml
stages:
  - name: stage1
    jobs:
      - name: job A
        script: echo hello
```

可以简化为：

```yaml
- stages:
    - name: job A
      script: echo hello
```

**当 `Job` 为字符串时，可视作脚本任务，name 和 script 都取该字符串，可继续简化为：**

```yaml
- stages:
    - echo hello
```

#### 串行 Job

当 `jobs` 为数组时，这组 `Job` 会按顺序串行执行。

```yaml
# 串行执行
stages:
  - name: install
    jobs:
      - name: job1
        script: echo "job1"
      - name: job2
        script: echo "job2"
```

#### 并行 Job

当 `jobs` 为对象时，这组 `Job` 会并行执行。

```yaml
# 并行执行
stages:
  - name: install
    jobs:
      job1:
        script: echo "job1"
      job2:
        script: echo "job2"
```

多个 `Job` 可灵活组织串行和并行。
先串行后并行的示例：

```yaml
main:
  push:
    - stages:
        - name: serial first
          script: echo "serial"
        - name: parallel
          jobs:
            parallel job 1:
              script: echo "1"
            parallel job 2:
              script: echo "2"
        - name: serial next
          script: echo "serial next"
```

### name {#Stage-name}

* type: `String`

阶段名称。

### ifNewBranch {#stage-ifnewbranch}

* type: `Boolean`
* default: `false`

为 `true` 表示当前分支属于新分支（即 `CNB_IS_NEW_BRANCH` 为 `true`）时，才执行此 `Stage`。

> 当同时存在 `ifNewBranch` / `ifModify` / `if` 时，其中有一个条件满足，此 `Stage` 就会执行。

### ifModify {#stage-ifmodify}

* type: `Array<String>` | `String`

指定只有相应文件变动时，才执行此 `Stage`。使用 `glob` 匹配表达式。

### if {#stage-if}

* type: `Array<String>` | `String`

指定一个 shell 脚本，当脚本退出码为 `0` 时，执行此 `Stage`。

#### 示例1：判断变量值

[shell表达式语法](https://linux.die.net/man/1/test)

```yaml
main:
  push:
    - env:
        IS_NEW: true
      stages:
        - name: is new
          if: |
            [ "$IS_NEW" = "true" ]
          script: echo is new
        - name: is not new
          if: |
            [ "$IS_NEW" != "true" ]
          script: echo not new
```

#### 示例2：判断任务输出

```yaml
main:
  push:
    - stages:
        - name: make info
          script: echo 'haha'
          exports:
            info: RESULT
        - name: run if RESULT is haha
          if: |
            [ "$RESULT" = "haha" ]
          script: echo $RESULT
```

### env {#stage-env}

* type: `Object`

声明环境变量，仅对当前 `Stage` 生效。

`Stage env` 优先级比 `Pipeline env` 高。

### imports {#stage-imports}

* type: `Array<String>` | `String`

从文件导入环境变量，仅对当前 `Stage` 生效。用法同 [Pipeline imports](#pipeline-imports)。

### retry {#stage-retry}

* type: `Number`
* default: `0`

失败重试次数，`0` 表示不重试。

重试间隔为1s,2s,4s,8s...

### lock {#stage-lock}

* type: `Boolean` ｜`Object`

给 `Stage` 设置锁，`Stage` 执行完后自动释放锁。

表现：任务A 获取到锁后，任务B 需等待锁释放后才能继续执行。

参数说明：

* `lock.key`: `String` - 自定义锁名，默认为 `分支名-流水线名-stage下标`
* `lock.expires`: `Number` - 锁过期时间（秒），默认 3600
* `lock.wait`: `Boolean` - 锁被占用时是否等待，默认 `false`
* `lock.timeout`: `Number` - 等待锁的超时时间（秒），默认 3600

若 `lock` 为 `true`，则所有参数为默认值。

例1: `lock` 是 `Boolean` 格式

```yaml
main:
  push:
    - stages:
        - name: stage1
          lock: true
          jobs:
            - name: job1
              script: echo "job1"
```

例2: `lock` 是 `Object` 格式

```yaml
main:
  push:
    - stages:
        - name: stage1
          lock:
            key: key
            expires: 600 # 10分钟
            wait: true
            timeout: 60 # 最多等待 1分钟
          jobs:
            - name: job1
              script: echo "job1"
```

### image {#Stage-image}

* type: `Object` | `String`

指定当前 `Stage` 的环境镜像，在当前 `Stage` 下的所有任务默认都在这个镜像环境中执行。

该属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

* `image.name`: `String` 镜像名，如 `node:20`。
* `image.dockerUser`: `String` 指定 Docker 用户名，用于拉取指定的镜像。
* `image.dockerPassword`: `String` 指定 Docker 用户密码，用于拉取指定的镜像。

如果指定 `image` 为字符串，则等同于指定了 `image.name`。

如果使用 `云原生构建` 的 Docker 制品库的镜像且未设置 `image.dockerPassword`，该参数会设为环境变量 [CNB\_TOKEN](./build-in-env.md#cnb_token) 的值。

### jobs

* type: `Array<Job>` | `Object<name,Job>`

定义一组任务。数组形式为串行执行，对象形式为并行执行。

## Job

`Job` 是最小的任务执行单元，可以是`脚本`,`插件`,`内置任务`

### 任务类型

#### 脚本任务

执行 Shell 脚本命令。

参数说明：

* `script`: `Array<String>` | `String` - 要执行的 Shell 脚本，数组会用 `&&` 连接
* `image`: `String` - 可选，指定脚本运行环境

**示例**：

```yaml
- name: install
  image: node:20
  script: npm install
```

**简化写法**：脚本任务可简化为字符串，此时 `script` 和 `name` 都取该字符串：

```yaml
- echo hello
```

等同于：

```yaml
- name: echo hello
  script: echo hello
```

#### 插件任务

插件即 Docker 镜像，也可称为镜像任务。

特点：

* 执行环境灵活
* 易于分享和复用
* 可跨 CI 平台使用

工作原理：通过向 `ENTRYPOINT` 传递环境变量来配置插件行为。

注意：通过 `imports`、`env` 设置的自定义环境变量不会传递给插件，但可在 `settings`、`args` 中使用变量替换。CNB 系统环境变量会传递给插件。

参数说明：

* `name`: `String` - 任务名称
* `image`: `Object` | `String` - 插件镜像，详见 [job.image](#job-image)
* `settings`: `Object` - 插件参数，按插件文档填写，支持 `$VAR` 或 `${VAR}` 引用环境变量
* `settingsFrom`: `Array<String>` | `String` - 指定本地或 Git 仓库文件路径，加载为插件任务参数。详见 [job.settingsFrom](#settingsFrom)

##### 示例 {#plugin-example}

with imports：

```yaml
- name: npm publish
  image: plugins/npm
  imports: https://cnb.build/<your-repo-slug>/-/blob/main/xxx/npm.json
  settings:
    username: $NPM_USER
    password: $NPM_PASS
    email: $NPM_EMAIL
    registry: https://mirrors.xxx.com/npm/
    folder: ./
```

```json
{
  "username": "xxx",
  "password": "xxx",
  "email": "xxx@emai.com",
  "allow_slugs": ["cnb/**/**"],
  "allow_images": ["plugins/npm"]
}
```

with settingsFrom：

```yaml
- name: npm publish
  image: plugins/npm
  settingsFrom: https://cnb.build/<your-repo-slug>/-/blob/main/xxx/npm-settings.json
  settings:
    # username: $NPM_USER
    # password: $NPM_PASS
    # email: $NPM_EMAIL
    registry: https://mirrors.xxx.com/npm/
    folder: ./
```

```json
{
  "username": "xxx",
  "password": "xxx",
  "email": "xxx@emai.com",
  "allow_slugs": ["cnb/cnb"],
  "allow_images": ["plugins/npm"]
}
```

##### 内置任务

使用 CNB 提供的内置功能。

参数说明：

* `type`: `String` - 指定要执行的 [内置任务](./internal-steps/) 类型
* `options`: `Object` - 内置任务的参数
* `optionsFrom`: `Array<String>` | `String` - 从文件加载参数

`options` 中的字段优先级高于 `optionsFrom`。

引用配置文件权限控制参考 [配置文件引用鉴权](./file-reference.md#权限检查)。

**示例**：

```yaml
name: install
type: INTERNAL_JOB_NAME
optionsFrom: ./options.json
options:
  key1: value1
  key2: value2
```

```json
// ./options.json
{
  "key1": "value1",
  "key2": "value2"
}
```

### name {#job-name}

* type: `String`

任务名称。

### ifModify {#job-ifmodify}

* type: `Array<String>` | `String`

指定只有相应文件变动时，才执行此任务。用法同 [Stage ifModify](#stage-ifmodify)。

### ifNewBranch {#job-ifnewbranch}

* type: `Boolean`
* default: `false`

为 `true` 表示当前分支属于新分支（即 `CNB_IS_NEW_BRANCH` 为 `true`）时，才执行此任务。用法同 [Stage ifNewBranch](#stage-ifnewbranch)。

### if {#job-if}

* type: `Array<String>` | `String`

指定一个 shell 脚本，当脚本退出码为 `0` 时，执行此任务。用法同 [Stage if](#stage-if)。

### breakIfModify {#job-breakifmodify}

* type: `Boolean`
* default: `false`

`Job` 执行前，如果源分支已更新，则终止当前任务。

### skipIfModify {#job-skipifmodify}

* type: `Boolean`
* default: `false`

`Job` 执行前，如果源分支已更新，则跳过当前任务。

### env {#job-env}

* type: `Object`

声明环境变量，仅对当前 `Job` 生效。

`Job env` 优先级最高。

### imports {#job-imports}

* type: `Array<String>` | `String`

从文件导入环境变量，仅对当前 `Job` 生效。用法同 [Stage imports](#stage-imports)。

### exports {#job-exports}

* type: `Object`

将任务执行结果导出为环境变量，供后续任务使用。生命周期为当前 `Pipeline`。

详情请见 [环境变量](./env.md#导出环境变量)

### timeout

* type: `Number` | `String`

设置单个任务的超时时间和无输出超时时间，默认单个任务的超时时间为 1 小时，最大不能超过 12 小时，无输出超时时间为 10 分钟。

对脚本任务和插件任务有效。

支持的时间单位：

* `ms`: 毫秒（默认）
* `s` : 秒
* `m` : 分钟
* `h` : 小时

示例：

```yaml
name: timeout job
script: sleep 1d
timeout: 100s # 任务将在100秒后超时退出
```

详见 [超时策略](./timeout.md)

### allowFailure {#job-allowfailure}

* type: `Boolean` | `String`
* default: `false`

是否允许当前任务失败。

当此参数设置为 `true` 时，任务失败不会影响后续流程的执行和最终结果。

值为 `String` 类型时可读取环境变量。

### lock {#job-lock}

* type: `Object` | `Boolean`

给 `Job` 设置锁，`Job` 执行完后自动释放锁。

表现：任务A 获取到锁后，任务B 需等待锁释放后才能继续执行。

参数说明：

* `lock.key`: `String` - 自定义锁名，默认为 `分支名-流水线名-stage下标-job名`
* `lock.expires`: `Number` - 锁过期时间（秒），默认 3600
* `lock.wait`: `Boolean` - 锁被占用时是否等待，默认 `false`
* `lock.timeout`: `Number` - 等待锁的超时时间（秒），默认 3600

若 `lock` 为 `true`，则所有参数为默认值。

示例1：简化写法

```yaml
name: 锁
lock: true
script: echo 'job 锁'
```

示例2：详细配置

```yaml
name: 锁
lock:
  key: key
  expires: 10
  wait: true
script: echo 'job 锁'
```

### retry {#job-retry}

* type: `Number`
* default: `0`

失败重试次数，`0` 表示不重试。

重试间隔为1s,2s,4s,8s...

### type

* type: `String`

指定要执行的 [内置任务](./internal-steps/) 类型。

### options

* type: `Object`

内置任务的参数配置。

### optionsFrom

* type: `Array<String>` | `String`

从文件加载内置任务参数。与 `imports` 参数类似，配置为数组时，后面的配置会覆盖前面的。

`options` 中的字段优先级高于 `optionsFrom`。

### script

* type: `Array<String>` | `String`

要执行的 Shell 脚本。为数组时会自动使用 `&&` 拼接。脚本的退出码作为任务的退出码。

注意：流水线默认使用 `sh` 作为命令行解释器，不同的 `image` 可能使用不同的解释器。

### commands

* type: `Array<String>` | `String`

作用同 `script` 参数，优先级比 `script` 高。主要为了兼容 `Drone CI` 语法。

### image {#job-image}

* type: `Object` | `String`

指定任务的运行环境镜像。可用于脚本任务或插件任务。

该属性支持引用环境变量，参考[变量替换](./env.md#变量替换)。

* `image.name`: `String` 镜像名，如 `node:20`。
* `image.dockerUser`: `String` 指定 Docker 用户名，用于拉取指定的镜像。
* `image.dockerPassword`: `String` 指定 Docker 用户密码，用于拉取指定的镜像。

如果指定 `image` 为字符串，则等同于指定了 `image.name`。

如果使用 `云原生构建` 的 Docker 制品库的镜像且未设置 `image.dockerPassword`，该参数会设为环境变量 [CNB\_TOKEN](./build-in-env.md#cnb_token) 的值。

### settings

* type: `Object`

插件任务的参数配置。按照插件文档填写，支持通过 `$VAR` 或 `${VAR}` 引用环境变量。

### settingsFrom

* type: `Array<String>` | `String`

从文件加载插件任务参数。与 `imports` 参数类似。

优先级：

* 配置为数组时，后面的配置会覆盖前面的
* `settings` 中的字段优先级高于 `settingsFrom`

引用配置文件权限控制参考 [配置文件引用鉴权](./file-reference.md#权限检查)。

### args

* type: `Array<String>`

执行插件镜像时传递的参数，内容会追加到 `ENTRYPOINT` 中，仅支持数组。

示例：

```yaml
- name: npm publish
  image: plugins/npm
  args:
    - ls
```

等同于执行：

```bash
docker run plugins/npm ls
```

### 任务退出码说明

任务执行完成后会返回一个退出码，不同的退出码有不同的含义：

* **0**：任务成功，继续执行后续任务
* **78**：任务成功，但中断当前流水线的执行（可在脚本中主动执行 `exit 78` 来中断流水线）
* **其他数字**：任务失败，同时中断当前流水线的执行

## 配置复用

### include

使用 `include` 关键字，可以在当前配置文件中导入当前仓库或其他仓库中的 YAML 文件。这有助于对大型配置进行拆分，提高可维护性和复用性。

#### 使用示例

template.yml (被引用的文件)

```yaml title="template.yml"
main:
  push:
    pipeline_2:
      env:
        ENV_KEY1: xxx
        ENV_KEY3: inner
      services:
        - docker
      stages:
        - name: echo
          script: echo 222
```

.cnb.yml (主配置文件)

```yaml title=".cnb.yml"
include:
  - https://cnb.build/<your-repo-slug>/-/blob/main/xxx/template.yml

main:
  push:
    pipeline_1:
      stages:
        - name: echo
          script: echo 111
    pipeline_2:
      env:
        ENV_KEY2: xxx # 新增环境变量
        ENV_KEY3: outer # 覆盖 template.yml 中的 ENV_KEY3
      stages:
        - name: echo
          script: echo 333 # 新增步骤
```

**合并后的等效配置:**

```yaml
main:
  push:
    pipeline_1: # key不存在，合并时新增
      stages:
        - name: echo
          script: echo 111
    pipeline_2: # key已存在，合并内容
      env: # 对象合并：同名键覆盖，新键添加
        ENV_KEY1: xxx # 来自 template.yml
        ENV_KEY2: xxx # 来自 .cnb.yml (新增)
        ENV_KEY3: outer # 来自 .cnb.yml (覆盖)
      services: # 数组合并：追加元素
        - docker # 来自 template.yml
      stages: # 数组合并：追加元素
        - name: echo # 来自 template.yml
          script: echo 222
        - name: echo # 来自 .cnb.yml
          script: echo 333
```

#### 语法说明

`include` 支持三种引入方式：

```yaml title=".cnb.yml"
include:
  # 1. 直接传入配置文件路径 (字符串)
  - "https://cnb.build/<your-repo-slug>/-/blob/main/xxx/template.yml"
  - "template.yml" # 相对路径（相对于仓库根目录）

  # 2. 传入对象，提供更多控制
  # path: 配置文件路径
  # ignoreError: 未找到文件时是否报错。true-忽略错误；false-报错（默认）
  - path: "template1.yml"
    ignoreError: true

  # 3. 直接传入内联的 YAML 配置对象
  - config:
      main:
        push:
          - stages:
              - name: echo
                script: echo "hello world"
```

#### 合并规则

不同文件间的配置按以下规则合并：

* **数组 + 数组**: 合并所有元素（追加）。
* **对象 + 对象**: 合并键，同名键的值会被**覆盖**。
* **数组 + 对象** 或 **对象 + 数组**: 最终结果仅为**数组**（对象被忽略）。
* 合并顺序：本地 `.cnb.yml` 配置**覆盖** `include` 引入的配置；`include` 数组中**后面**的配置覆盖**前面**的配置。

**权限说明：**
出于安全考虑，与敏感信息保护原则一致，`include` **无法**引用存储在**密钥仓库**中的文件，因为合并后的完整配置会展示在构建详情页中。

:::tip 注意事项

1. 支持嵌套 include，但最多不能超过 50 个配置文件。
2. include 的本地文件路径相对于项目根目录。
3. 不支持引用 git submodule 中的文件。
4. **不支持**跨文件使用 YAML 锚点 (`&`, `*`)。
   :::
