项目替换 CLI

在 CI/CD 中无需 Git 即可跨 Activepieces 部署镜像项目

ap project replace CLI 通过直接 API 调用,将工作流、表格 schema 和文件夹从一个 Activepieces 部署镜像到另一个。当基于 Git 的项目发布不适用时,可以在 CI/CD 中使用它在独立的暂存和生产实例之间推广工作。

**使用场景:** 一个定时运行的 GitHub Action 执行 `ap project replace --source-url=staging --dest-url=prod`。暂存作为事实来源;生产环境成为字节级的镜像。如果目标缺少所需的 pieces 或引用的连接,作业会在任何写入之前失败。

先决条件

  • 目标平台的计划必须启用环境功能——参见项目发布先决条件
  • 你在两个实例上都需要一个平台范围的 API 密钥SERVICE 主体)。两侧必须使用相同的项目 ID 格式。
  • 两个部署必须共享相同的 Activepieces 主版本,且目标版本必须大于或等于源版本。

镜像内容

资源行为
工作流通过 externalId 在目标上创建/更新/删除
表格 schema仅 schema——字段、名称、externalId。行数据从不复制
文件夹通过 externalId 镜像
必需 pieces在目标上自动安装源的确切固定版本(官方 + 自定义 npm pieces)。如果任何安装失败,替换操作会在其他写入之前中止
连接元数据自动镜像(externalId、pieceName、displayName);密钥值从不通过网络传输。新连接以 status: MISSING 占位符形式落在目标上。操作员在目标 UI 中逐个授权后工作流才能运行
MCP 服务器、代理、项目元数据、自定义域名、应用凭证不在范围内。不做任何修改

安装

npm install -g activepieces

activepieces CLI 随每个 Activepieces 版本发布;将版本固定为与目标实例匹配的版本,以确保请求结构一致。

命令

# 通过环境变量设置 API 密钥(推荐用于 CI——将密钥保留在进程参数和 shell 历史之外)
export AP_SOURCE_API_KEY="$STAGING_API_KEY"
export AP_DEST_API_KEY="$PROD_API_KEY"

ap project replace \
  --source-url    https://staging.activepieces.com \
  --source-project "$STAGING_PROJECT_ID" \
  --dest-url      https://prod.activepieces.com \
  --dest-project  "$PROD_PROJECT_ID" \
  [--json]

参数

参数必需用途
--source-url源实例的基础 URL(无需尾随斜杠)
--source-api-key标志环境变量源的平台 API 密钥。回退到 AP_SOURCE_API_KEY
--source-project源实例上的项目 ID
--dest-url目标实例的基础 URL
--dest-api-key标志环境变量目标的平台 API 密钥。回退到 AP_DEST_API_KEY
--dest-project目标实例上的项目 ID
--json输出机器可读的 JSON 而非人类可读的摘要
**不要通过 `--source-api-key` / `--dest-api-key` 标志在 CI 或生产脚本中传递 API 密钥。** 进程参数对主机上的其他用户可见(`ps aux`),会被捕获到 CI 日志输出中,并保留在 shell 历史中。请改用 `AP_SOURCE_API_KEY` / `AP_DEST_API_KEY` 环境变量——它们不会出现在上述任何位置。

退出码

含义
0应用成功;每个项目都干净地应用
1应用成功但至少有一个项目失败。检查响应中的 failed[]
2服务端预检失败(422)。未发生任何写入
3服务端中止:piece 安装失败(502)、通用 5xx,目标项目上已有另一个替换正在进行(409
4本地 CLI/传输错误——URL 错误、主机不可达、API 密钥无效

运行时的行为

  1. CLI 通过源实例的 REST API 列出源项目的工作流、文件夹和表格 schema。
  2. CLI 将它们打包成 ProjectReplaceRequest 并通过 POST 发送到目标的 /v1/projects/:projectId/replace
  3. 目标 获取每个项目的 NoWait 锁。如果另一个替换正在进行,则返回 409 REPLACE_IN_PROGRESS
  4. 目标预检(以下任一失败则不会写入):
    • 解析 Activepieces 版本;相同主版本且 dest >= source
    • 对于每个源连接:如果目标已有相同 externalId 的连接,其 pieceName 必须匹配
  5. 安装阶段——对于 requiredPieces 中目标尚未安装确切固定版本的每个条目,服务器从 npm 安装(packageType: REGISTRY,范围:平台)。尝试所有安装;如果有任何失败,响应以 502 中止并列出每个失败。此时尚未发生其他写入——文件夹、表格、工作流和连接仍未被触及。
  6. 应用阶段——先连接,然后文件夹,然后表格,然后工作流被创建/更新。删除按反向顺序执行。每个项目在自己的 try/catch 中运行;逐项失败被收集,系统性 5xx 错误中止运行。
  7. 审计事件 project.replaced 仅在应用阶段运行时发出——在 SUCCESSPARTIAL_FAILURE 时。被拒绝的尝试(预检失败、安装失败、锁冲突)不会被审计。

输出

人类可读(默认)

替换完成,耗时 1240ms
  pieces      : 已安装 1 个
  flows       : 创建 1 个,更新 2 个,删除 0 个,未变动 47 个
  tables      : 创建 0 个,更新 0 个,删除 0 个,未变动 5 个
  folders     : 创建 0 个,更新 1 个,删除 0 个,未变动 3 个
  connections : 创建 1 个,更新 0 个,未变动 4 个

目标上已安装 1 个 piece(s):
  - activepieces-onlinepay@0.0.7 (CUSTOM)

目标上有 1 个 connection(s) 需要授权后工作流才能运行:
  - Slack Main (@activepieces/piece-slack) [externalId=slack_main]

--json

{
  "applied": {
    "flowsCreated": 1, "flowsUpdated": 2, "flowsDeleted": 0, "flowsUnchanged": 47,
    "tablesCreated": 0, "tablesUpdated": 0, "tablesDeleted": 0, "tablesUnchanged": 5,
    "foldersCreated": 0, "foldersUpdated": 1, "foldersDeleted": 0, "foldersUnchanged": 3,
    "connectionsCreated": 1, "connectionsUpdated": 0, "connectionsUnchanged": 4
  },
  "failed": [
    { "kind": "flow", "externalId": "...", "op": "UPDATE", "error": "..." }
  ],
  "piecesInstalled": [
    { "name": "activepieces-onlinepay", "version": "0.0.7", "pieceType": "CUSTOM" }
  ],
  "connectionsAwaitingAuthorization": [
    { "externalId": "slack_main", "pieceName": "@activepieces/piece-slack", "displayName": "Slack Main" }
  ],
  "durationMs": 1240
}

预检失败(退出码 2)

{
  "errors": [
    { "kind": "AP_VERSION_MISMATCH", "sourceVersion": "1.2.0", "destVersion": "1.1.5", "message": "..." },
    { "kind": "CONNECTION_PIECE_MISMATCH", "externalId": "slack_main", "expectedPieceName": "@activepieces/piece-slack", "foundPieceName": "@activepieces/piece-discord" }
  ]
}

完整的预检错误类型:AP_VERSION_MISMATCHPIECE_VERSION_MISMATCHCONNECTION_PIECE_MISMATCH

安装失败(退出码 3,HTTP 502)

{
  "failures": [
    { "pieceName": "@activepieces/piece-slack", "version": "1.2.3", "pieceType": "OFFICIAL", "message": "ENGINE_OPERATION_FAILURE: ..." },
    { "pieceName": "pdfcrowd-piece-activepieces", "version": "0.0.5", "pieceType": "CUSTOM", "message": "..." }
  ]
}

发生这种情况时,工作流/表格/文件夹/连接尚未被触及——运行在应用阶段前已中止。常见原因:该版本的 piece 不在 npm 上、npm 注册表不可达、引擎在元数据提取期间崩溃、目标平台上缺少用于私有包的 npm 认证令牌。

幂等性与重试

在部分失败后重新运行 CLI 会收敛到源状态。前一次运行已应用的项目通过类型深度相等检查检测为未变动并跳过;失败的项目被重试。在硬失败时,目标会处于部分应用状态——这是有意设计的,因为暂停/恢复会迫使每次成功发布都停机。

应用阶段不包装在单个数据库事务中,这是经过深思熟虑的:

  • 部分成功语义(207 + failed[])要求成功应用的项目在兄弟失败时仍然持久存在。单个事务会因为一个工作流的重新发布失败而回滚每个成功的文件夹/表格/连接。
  • 工作流重新发布会调度 BullMQ 作业和触发源注册,这些无法放在 SQL 事务中。
  • 恢复通过重新运行而非回滚实现。每个操作通过 externalId 匹配,因此进程在应用阶段崩溃会使目标处于某种中间状态,下一次运行的差异检测阶段会找到并完成它。CI/CD 的自然重试机制处理进程级失败(OOM、SIGKILL、部署超时)。

如果你的 CI 确实需要工作流级别的”全有或全无”属性,请在作业步骤中驱动替换,在非零退出时重试,且仅将退出码 0 视为成功。

连接

连接元数据(externalId、pieceName、displayName)作为占位符记录镜像到目标,状态为 status: MISSING。密钥值(OAuth 令牌、API 密钥等)从不通过网络传输——每个实例各自保管。

替换后,CLI 会打印出目标上仍需要授权的连接:

目标上有 1 个 connection(s) 需要授权后工作流才能运行:
  - Slack Main (@activepieces/piece-slack) [externalId=slack_main]

操作员打开目标 UI → 连接 → 使用目标的凭据(不同的 Slack 工作区、生产级别的 API 密钥等)重新连接每个占位符。在此完成之前,任何使用该连接的工作流运行都会在执行时失败。授权后重新运行 CLI 对于已经是 ACTIVE 状态的连接是空操作。

如果目标上已存在具有相同 externalId 但不同 pieceName 的连接,替换会在预检时失败,返回 CONNECTION_PIECE_MISMATCH,以便在不会覆盖不相关的工作流的前提下解决冲突。

GitHub Actions 示例

name: 从暂存推广到生产
on:
  schedule:
    - cron: '0 2 * * *'
  workflow_dispatch:
jobs:
  replace:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm install -g activepieces
      - run: |
          ap project replace \
            --source-url   "${{ vars.STAGING_URL }}" \
            --source-project "${{ vars.STAGING_PROJECT_ID }}" \
            --dest-url     "${{ vars.PROD_URL }}" \
            --dest-project "${{ vars.PROD_PROJECT_ID }}" \
            --json
        env:
          AP_SOURCE_API_KEY: ${{ secrets.STAGING_API_KEY }}
          AP_DEST_API_KEY: ${{ secrets.PROD_API_KEY }}

非零退出码会导致作业失败——预检错误、服务器错误或传输错误都会自然地传递给 GitHub Actions,无需额外处理。

故障排查

另一个替换正在针对同一目标项目运行。等待几秒后重试——锁是每个项目独立的,前一次运行完成后立即释放。 CLI 通过了预检,但所需的 piece 无法在目标上安装。检查响应中的 `failures[*].message`——最常见的原因是 npm 上没有该版本的 piece、注册表不可达或引擎在元数据提取期间崩溃。替换在任何写入前已中止;修复安装问题后重新运行。 目标上已安装的 piece 版本与源上的不同。手动升级/降级目标上的 piece,或更新源工作流以使用与目标兼容的版本。 替换已将连接元数据作为占位符镜像;目标仍然需要密钥。打开目标 UI → **连接** → 重新连接每个占位符。替换响应和 CLI 输出列出了每个需要授权的连接。 目标上已存在具有相同 `externalId` 但针对不同 piece 的连接。要么重命名源的 externalId,要么在重新运行前删除/替换目标上冲突的连接。 目标运行着较旧版本或与源不同的主版本。请先升级目标。 目标平台的计划未启用**环境**功能。请联系你的平台所有者。