mcpField
S
setup-pipeline

setup-pipeline

github.com/microsoft云与 DevOps1.1k162 次安装今天更新
代码仓库

关于 setup-pipeline

setup-pipeline 是用于在 Dataverse 配置 Power Platform Pipelines 的 Power Pages 技能。它通过项目与环境检查、ALM 计划门槛及管道创建来准备部署,无需 YAML 或外部 CI/CD 基础设施。

主要功能

  1. 01在 Dataverse 创建管道配置
  2. 02检查项目和解决方案上下文
  3. 03验证身份验证与 API 访问
  4. 04解析 Pipelines 主机环境
  5. 05要求确认 ALM 计划

适用场景

  1. 01设置 Power Pages 自动部署
  2. 02准备解决方案在环境间晋级
  3. 03创建管道前检查 ALM 就绪情况

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/power-platform-skills --skill setup-pipeline

安装后在 Claude Code 中输入/setup-pipeline确认已加载。

附带文件scripts/validate-pipeline.js

详细介绍

插件检查:运行 node "${PLUGIN_ROOT}/scripts/check-version.js" — 如果有输出消息,请先向用户显示,然后再继续。

setup-pipeline

为自动化 Power Pages 解决方案部署设置 Power Platform Pipeline。使用 PP Pipelines OData API 直接在 Dataverse 中创建管道配置——无需 YAML 文件,也无需外部 CI/CD 基础设施。

GitHub Actions 和 Azure DevOps Pipeline 选项在平台菜单中显示为即将推出。

有关此技能中使用的所有经 HAR 确认的 API 模式,请参阅 ${PLUGIN_ROOT}/references/cicd-pipeline-patterns.md。

前置条件

  • 项目根目录中存在 powerpages.config.json
  • 存在 .solution-manifest.json(必须先通过 setup-solution 创建解决方案)
  • Azure CLI 已登录(az account show 成功)
  • PAC CLI 已登录(pac env who 成功)
  • 已安装 Pipelines 包的 Power Platform 环境(“主机”环境)

阶段

阶段 0 — ALM 计划门控

plan-alm 是入口。 当用户表达 ALM 意图(提升 / 发布 / 部署 / 设置 CI-CD / 移至预发布环境 / 推送到生产环境)时,编排器(/power-pages:plan-alm)应先运行。此阶段 0 会强制执行这一要求;若没有计划,它会默认拒绝继续,而不是一次性检查后就允许用户永久跳过。

跳过规则。 如果调用此技能是作为正在进行的 plan-alm 编排的一部分,则完全跳过阶段 0,继续阶段 1。门控辅助程序通过其 inExecution 块提供此状态 — 当满足以下条件时,直接静默进入阶段 1:

inExecution.status === "active"

辅助程序根据 docs/.alm-plan-data.json 计算此状态 — PLAN_STATUS === "In Execution" 且 LAST_INVOCATION_AT 在过去 60 分钟内。每次调用 check-alm-plan.js 时,只要发现计划处于执行状态,就会自动刷新 LAST_INVOCATION_AT,因此链中的每个技能都会为后续技能保持链的活跃状态 — 即使部署持续数小时(仅 deploy-pipeline 每个阶段就可能需要 60 分钟),也不会超出时间窗口而被错误地重新分类。停滞的链(超过 60 分钟没有心跳)会重新分类为 stale-heartbeat,并正常触发阶段 0 门控,避免废弃的计划在没有用户确认的情况下被静默绕过。

当 inExecution.status 的值不是 "active"("not-running"、"stale-heartbeat"、"no-plan")时,运行以下阶段 0 门控流程。根据辅助程序返回的其他字段进行分支:

步骤 1 — 运行门控辅助程序。

node "${PLUGIN_ROOT}/scripts/lib/check-alm-plan.js" \
  --projectRoot "." \
  --envUrl "{devEnvUrl}" \
  --token "{token}" \
  --solutionId "{solutionId from .solution-manifest.json, if available}"

辅助程序返回 JSON,包含 { exists, stale, staleness: { reason, detail }, generatedAt, planStatus, ... }。新鲜度检查需要环境凭据和 solutionId;如果没有这些信息,辅助程序只会检查是否存在计划。

步骤 2 — 根据结果分支。

结果行为
deferred: true用户已明确推迟此项目的 ALM(存在 .alm-deferred 标记文件)。静默进入阶段 1 — 不要反复提醒。
exists: false用户尚未运行 plan-alm。请参阅步骤 3。
exists: true, stale: false计划是最新的。静默进入阶段 1。
exists: true, stale: true(原因:solution-modified)解决方案在生成计划后发生了更改。请参阅步骤 4。

步骤 3 — 无计划。 告知用户:

“此项目不存在 ALM 计划。/power-pages:plan-alm 会生成计划 — 它会检测项目状态,询问你的提升策略(PP Pipelines 或手动导出/导入),并按正确顺序编排相应技能(包括此技能)。现在要运行 plan-alm 吗?”

🚦 门控(意图 · setup-pipeline:0.no-plan): 当 check-alm-plan.js 返回 exists:false 时,默认拒绝继续的入口门控。由辅助脚本提供支持。

AskUserQuestion:

问题标题选项
是否先运行 /power-pages:plan-alm?ALM 计划门控是 — 现在运行 /power-pages:plan-alm(推荐)、不带计划继续(高级 — 我知道自己在做什么)、取消
  • 是(推荐) → 调用 /power-pages:plan-alm。该命令会生成计划并返回——plan-alm 是规划器,不会部署。此技能随后会重新运行 Phase 0 检查(此时 exists:true),并继续执行 Phase 1。
  • 不使用计划继续 → 设置 BYPASSED_PLAN_GATE = true,并继续执行 Phase 1。
  • 取消 → 正常退出。

Step 4 — 计划已过时。 告知用户:

"ALM 计划生成于 {generatedAt},但此后源解决方案已修改(修改时间:{solution.modifiedon})。组件可能已发生变化。重新运行 plan-alm 将刷新分析结果和渲染后的 HTML。"

🚦 Gate(意图 · setup-pipeline:0.stale-plan): 当 check-alm-plan.js 返回 stale:true 时,采用失败即关闭的入口门禁。由辅助脚本支持。

AskUserQuestion:

QuestionHeaderOptions
是否先刷新计划?ALM 计划时效性刷新 — 重新运行 /power-pages:plan-alm(推荐)、使用现有计划继续、取消
  • 刷新(推荐) → 调用 /power-pages:plan-alm。完成后,重新运行一次 Phase 0 辅助检查以确认计划是否最新;如果仍然过时,则向用户说明详情并继续执行 Phase 1(不要无限循环)。
  • 继续 → 设置 STALE_PLAN_ACK = true,并继续执行 Phase 1。
  • 取消 → 正常退出。

此门禁存在的原因。 直接调用此技能会绕过编排器的部署前完整性检查、主机解析决策、部署策略选择以及渲染后的 HTML 计划。直接运行 setup-pipeline 的用户经常会遗漏应添加到解决方案中的组件、错过针对大型 Web 文件的资源提示,或针对错误的主机环境构建管道。此门禁确保 plan-alm 已运行(因此上述决策均已呈现并记录),或者用户明确选择绕过。

Phase 1 — 检测项目上下文

在此阶段开始时一次性创建所有任务。

要创建的任务:

  1. “检测项目上下文”
  2. “选择 CI/CD 平台”
  3. “确认管道配置”
  4. “运行预检”
  5. “创建部署环境”
  6. “创建管道和阶段”
  7. “验证并写入构件”

步骤:

  1. 使用 detect-project-context.js 读取项目上下文:

    node "${PLUGIN_ROOT}/scripts/lib/detect-project-context.js"
    

    将输出捕获为 JSON;提取 .siteName(存储为 siteName)、.websiteRecordId、.environmentUrl(存储为 devEnvUrl)和 .solutionManifest(存储为 solutionManifest)。对于非 SPA / 数据模型(EDM)站点,devEnvUrl 为 null——detect-project-context.js 仅从这些站点没有的 powerpages.config.json 中读取环境 URL。此处不要将 devEnvUrl 为 null 视为错误;步骤 2 会通过 pac env who 解析权威的开发环境 URL。如果缺少 siteName,则停止并建议先运行 /power-pages:create-site——已下载或已部署的站点(代码站点或非 SPA 站点)始终能够解析出 siteName(来自 powerpages.config.json 或 .powerpages-site/website.yml),因此缺少 siteName 意味着此处没有检出的站点,而不只是“没有 powerpages.config.json”。如果 solutionManifest 为 null(没有 .solution-manifest.json),则停止并建议先运行 /power-pages:setup-solution。

    清单版本检查:

    • 如果 solutionManifest.schemaVersion === 2(多解决方案布局),设置 MULTI_SOLUTION_MODE = true,并将 solutionManifest.solutions[] 存储为 SOLUTIONS_LIST。参见 Phase 6b——单个管道通过按解决方案运行各阶段来部署所有解决方案(此前 v1.3.x 之前的“每个解决方案一个管道”布局已撤回,因为这会使 Pipelines UI 杂乱)。
    • 如果 schemaVersion 缺失或为 1(单解决方案),读取 solutionManifest.solution.uniqueName 和 solutionManifest.solution.solutionId。将创建一个管道(沿用现有流程)。
  2. 运行 verify-alm-prerequisites.js,确认 PAC CLI 身份验证、获取令牌并验证 API 访问权限。仅当 devEnvUrl 非 null 时才传入 --envUrl(SPA 站点)。当 devEnvUrl 为 null(步骤 1 中的非 SPA/数据模型站点)时,完全省略 --envUrl — 不要传入 --envUrl "null" 或空值:

    # SPA sites — devEnvUrl resolved from powerpages.config.json in Step 1:
    node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --envUrl "{devEnvUrl}"
    
    # Non-SPA / data-model (EDM) sites — devEnvUrl is null, omit the flag:
    node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js"
    

    将 verify-alm-prerequisites.js 的 --envUrl 视为可选参数;省略该参数时,它会通过 pac env who 解析环境(该标志仅用于覆盖 PAC CLI 环境)。将输出捕获为 JSON;提取 .envUrl 和 .token(存储为 DEV_TOKEN),然后设置 devEnvUrl = .envUrl — 这个已验证的值(来自 pac env who)是后续所有步骤中权威的开发环境 URL:它会为非 SPA 站点补全 null 值,并为 SPA 站点确认该值。如果 .envUrl 仍为空,请停止并建议用户运行 pac auth create/选择环境(pac org select)后重试。

  3. 静默运行:

    node "${PLUGIN_ROOT}/scripts/lib/list-environments.js"
    

    将 JSON 数组存储为 ENV_LIST(条目:{ displayName, environmentId, environmentUrl, uniqueName, active })。此辅助程序会解析 pac env list — 旧的 pac env list --output json 在当前 PAC CLI 中无效(pac env list 仅接受 --filter)。如果 PAC 未通过身份验证,它会打印 [] 并以 0 退出,因此此步骤可以优雅降级。

  4. 通过 ensure-pipelines-host-detect.js 解析 Pipelines 主机(与 /power-pages:ensure-pipelines-host 内部运行的流程相同 — 它会读取任何缓存的 docs/alm/last-host-check.json,然后按解析顺序检查:组织设置绑定 → BAP 环境 GET → 租户默认自定义主机 → 租户范围枚举。只读;绝不会提示用户):

    BAP_TOKEN=$(az account get-access-token --resource "https://service.powerapps.com/" --query accessToken -o tsv)
    node "${PLUGIN_ROOT}/scripts/lib/ensure-pipelines-host-detect.js" \
      --envUrl "{devEnvUrl}" \
      --token "{DEV_TOKEN}" \
      --userId "{userId}" \
      --bapToken "{BAP_TOKEN}" \
      --projectRoot "."
    

    将标准输出捕获为 JSON:const hostResult = JSON.parse(output)。读取 hostResult.resolutionStatus、hostResult.finalHostEnvUrl、hostResult.ready。

    根据 resolutionStatus 分支处理:

    • AvailableUsingPlatformHost / AvailableUsingCustomHost / AvailableUsingCustomHostByAdminDefault — 主机已建立,且 ready: true。存储 HOST_ENV_URL = hostResult.finalHostEnvUrl 并继续。第 3 阶段会与用户确认。
    • AvailableUnboundCustomHost / MultipleUnboundCustomHosts / PlatformHostExistsUnbound / NoHost — 开发环境尚未绑定主机。委托给 /power-pages:ensure-pipelines-host,让用户复用现有主机或预配新的 Custom Host(D365_ProjectHost 模板)。告知用户:"未找到绑定到 {devEnvUrl} 的 Pipelines 主机。正在调用 /power-pages:ensure-pipelines-host 进行设置 — 它会在整个租户范围内搜索现有主机;如果找不到,则会提供预配新 Custom Host 的选项。" 子技能完成后,重新读取 docs/alm/last-host-check.json;仅当新标记中 ready: true 时,才捕获 HOST_ENV_URL = finalHostEnvUrl。如果用户取消子技能,请停止此技能 — 没有主机就无法创建管道。
    • CannotRedirect — 停止,并显示 hostResult.warnings[0] 中的具体租户配置错误。告知用户:"此租户的 DefaultCustomPipelinesHostEnvForTenant 设置与源环境的 ProjectHostEnvironmentId 组织设置不一致 — 只有 Power Platform 管理员才能解决此问题。"
    • OrgSettingStale — 停止并显示警告:"{devEnvUrl} 上的 ProjectHostEnvironmentId 指向一个当前不可见的主机环境(已删除、已禁用,或你无权访问)。请通过 PPAC 清除该组织设置,或联系环境所有者。"
    • PermissionDenied — 停止并显示警告:"调用方缺少对 {devEnvUrl} 所绑定环境的 BAP 读取访问权限。请联系主机环境所有者,至少授予 Deployment Pipeline User 访问权限。"

    **为何此步骤取代旧的 discover-pipelines-host.js 调用:**该辅助工具只检查租户级别的 DefaultCustomPipelinesHostEnvForTenant 设置(四种解析信号之一)。ensure-pipelines-host-detect.js 会遍历 Power Apps UI 使用的完整解析顺序(与 ProjectHostProvider.tsx 保持一致),因此在所有情况下都与 UI 结果一致——包括此前无法检测到的 AvailableUnboundCustomHost 情况:租户中存在 Custom Host,但源环境尚未绑定。完整状态矩阵请参阅 references/cicd-pipeline-patterns.md。

  5. 检查是否存在 docs/alm/last-pipeline.json。如果存在,请读取其内容。

  6. 报告发现结果:“项目:{siteName}。解决方案:{uniqueName}。开发环境:{devEnvUrl}。主机环境:{HOST_ENV_URL ?? 'pending — will be ensured next'}({hostResult.resolutionStatus})。现有管道:已找到/未找到。”

🚦 **关卡(plan · setup-pipeline:1.existing-pipeline):**已找到现有的 docs/alm/last-pipeline.json——请选择覆盖、先检查,或取消。尚未写入 Dataverse。

如果找到现有的 docs/alm/last-pipeline.json,请通过 AskUserQuestion 提问:

“{pipelineName} 已有管道配置(创建于 {createdAt})。你想如何继续?

  1. 覆盖——创建新管道并替换标记文件
  2. 先检查现有设置,再决定
  3. 取消”
  • 如果选择检查:显示现有 docs/alm/last-pipeline.json 的内容,然后使用相同的 3 个选项再次提问。
  • 如果选择取消:停止此技能,并告知用户未进行任何更改。
  • 如果选择覆盖:继续。
阶段 1.5 — 依据当前 Pipelines 文档核实信息

参考:${PLUGIN_ROOT}/references/alm-docs-grounding.md

此步骤最多用时约 30 秒。如果 MCP 搜索/抓取出错,请记录一行说明并继续——此技能必须能够离线运行。

  1. 使用以下查询运行 microsoft_docs_search:Power Platform Pipelines setup OData API host environment deploymentenvironments。
  2. 通过 microsoft_docs_fetch 并行抓取 https://learn.microsoft.com/en-us/power-platform/alm/pipelines(以及最多一个关于主机设置或管道创建的相关页面)。
  3. 提取一段摘要,说明 Microsoft Learn 当前关于 Pipelines 主机解析、deploymentenvironments / deploymentpipelines / deploymentstages 架构以及管道生命周期的内容。与 ${PLUGIN_ROOT}/references/cicd-pipeline-patterns.md 比较,并指出任何差异(新增字段、弃用的 API、变更的验证状态代码)。
  4. 使用该摘要为阶段 2 及之后的决策提供依据。不要悄然更改技能行为——在阶段 5(向 Pipelines 主机注册环境)之前,向用户提示任何差异,作为非强制警告。
阶段 2 — 选择 CI/CD 平台

🚦 **关卡(plan · setup-pipeline:2.platform):**选择 CI/CD 平台——PP Pipelines(完整支持)或 GitHub Actions / ADO(即将推出的占位项)。

通过 AskUserQuestion 询问用户:

“你想使用哪个 CI/CD 平台?

  1. Power Platform Pipelines——Microsoft 的原生部署管道。无需外部基础设施。(推荐)
  2. GitHub Actions——即将推出
  3. Azure DevOps Pipeline——即将推出”

如果用户将 power-platform、github 或 ado 作为参数传入,则跳过此问题并使用提供的值。

将选择存储为 PLATFORM。

如果选择了 github 或 ado → 显示即将推出的路径,然后停止。


Power Platform Pipelines 路径

阶段 3 — 确认管道配置

提出任何问题之前,先整理自动检测到的信息:

设置自动检测到的值
站点名称powerpages.config.json 中的 {siteName}
解决方案唯一名称.solution-manifest.json 中的 {uniqueName}
开发环境 URL从 pac env who 获取的 {devEnvUrl}
宿主环境 URLensure-pipelines-host-detect.js 中的 {HOST_ENV_URL}(在阶段 1 的步骤 4 中解析)
BAP 环境 ID(开发)从 pac env list 获取

🚦 关卡(计划 · setup-pipeline:3.config): 确认自动检测到的管道配置 — 管道名称、宿主环境、目标环境。取消操作会在对宿主执行任何 Dataverse 写入之前退出。

通过 AskUserQuestion 向用户提问,并预填以下值:

“我已收集以下管道配置。请确认或更正:

  • 管道名称:{siteName} Pipeline(可更改)
  • 源(开发)环境:{devEnvUrl}
  • 宿主环境(安装了 Pipelines 的位置):{HOST_ENV_URL} (在阶段 1 中解析 — 此时应始终存在;否则 ensure-pipelines-host 会停止此技能)
  • 要部署的解决方案:{uniqueName}
  • 目标环境:几个?(开发 → 预发布 / 开发 → 预发布 → 生产)”

向用户收集:

  • PIPELINE_NAME(默认值:{siteName} Pipeline)
  • HOST_ENV_URL(确认 — 已在阶段 1 中解析;用户只有在想要指定其管理的其他宿主时才可覆盖,此时请先重新运行 /power-pages:ensure-pipelines-host 以验证)
  • 目标环境数量和 URL(如适用,填写 STAGING_ENV_URL、PROD_ENV_URL)
  • 每个目标环境的 BAP 环境 ID(从 pac env list 获取 — 如果找到则预填,否则询问)

运行以下命令以存储 HOST_TOKEN:

az account get-access-token --resource "{hostEnvOrigin}" --query accessToken -o tsv

展示最终确认摘要,并请用户在继续之前批准。

阶段 4 — 部署前检查

所有 Dataverse 调用都使用 Node.js https 模块(curl 在 Windows 上存在编码问题)。

4.1 验证宿主环境是否已安装 Pipelines:

GET {hostEnvUrl}/api/data/v9.1/deploymentpipelines?$top=0
Authorization: Bearer {HOST_TOKEN}

如果响应为 404 或返回“unknown entity”错误,请停止并告知用户:“所选宿主环境未安装 Power Platform Pipelines。请选择其他环境或安装 Pipelines 包。”

4.2 使用 verify-solution-exists.js 验证开发环境中是否存在解决方案:

node "${PLUGIN_ROOT}/scripts/lib/verify-solution-exists.js" \
  --envUrl "{devEnvUrl}" \
  --uniqueName "{uniqueName}" \
  --token "{DEV_TOKEN}"

将输出捕获为 JSON;检查 .found。如果为 false:警告用户,必须先从开发环境导出解决方案,才能进行部署。

4.3 检查是否存在同名管道:

GET {hostEnvUrl}/api/data/v9.1/deploymentpipelines?$filter=name eq '{PIPELINE_NAME}'&$select=deploymentpipelineid&$top=1
Authorization: Bearer {HOST_TOKEN}

🚦 关卡(计划 · setup-pipeline:4.3.name-conflict): 宿主环境中已存在同名管道。请选择:复用现有管道 ID,或使用不同名称新建管道。自动复用可能会关联到属于其他人的管道;自动覆盖会丢失其阶段历史记录。

触发条件: 阶段 4.3 的查询返回了匹配项。 询问原因: 否则,要么其他人的管道阶段会被覆盖,要么会创建重复管道,导致宿主环境的管道列表变得杂乱。 取消后状态: 无 — 尚未写入 Dataverse。

如果找到同名管道,请通过 AskUserQuestion 询问是使用现有管道 ID,还是使用不同名称新建管道。

4.4 检查源环境和所有目标环境中的 blockedattachments:

Power Pages SPA 站点的编译输出中包含 .js 文件。如果环境的 blockedattachments 设置中包含 .js,那么 pac pages upload-code-site(在源环境中)和 deploy-pipeline(在目标环境中)都会因 AttachmentBlocked 而失败。在源环境和每个目标环境中运行此检查:

node "${PLUGIN_ROOT}/scripts/lib/fix-blocked-attachments.js" \
  --envUrl "{envUrl}" \
  --extensions js \
  --dry-run

如果任何环境的 wasBlocked 不为空,请告知用户:

“{envUrl} 中禁止了 .js 文件。这会导致 Power Pages SPA 站点上传或部署失败。是否移除此限制?这会修改环境级安全设置。”

🚦 关卡(同意 · setup-pipeline:4.4.blocked-attachments): 修改环境级 blockedattachments 安全设置(影响整个租户)。影响该环境的所有用户,而不仅仅是此技能的用户。可通过 PPAC 撤销。每个存在限制的环境都会触发一次。 阶段 4.4 会检查源环境和每个目标环境;如果 N 个环境中有 M 个将 .js(或其他媒体扩展名)列入阻止列表,则此关卡会触发 M 次 — 每个环境一次。每个环境都有自己的安全设置和受影响的制作者群体。对源环境选择“是”不涵盖暂存环境;对暂存环境选择“是”不涵盖生产环境。不要跨环境批量征求同意。

通过 AskUserQuestion 询问:1. 是,移除限制(推荐)/ 2. 跳过(我会手动修复)。

如果用户同意,请在不带 --dry-run 的情况下重新运行,以应用更改。如果用户拒绝,请将其记录为警告 — 部署成功前,他们需要手动修复此问题。

报告部署前检查结果。如果有任何关键检查失败,请停止并给出明确指引。如果只有警告,请在继续之前征求用户确认。

阶段 5 — 向 Pipelines 主机注册环境

通过在主机的 Dataverse 中创建 deploymentenvironments 行,将每个环境(源环境 + 目标环境)注册到 Pipelines 主机。这是仅元数据注册——该行只是指向现有 BAP 环境的引用,并非预配调用。环境本身必须已存在于 BAP 中。主机会验证所引用的环境是否可访问,以及调用方是否拥有相应访问权限(validationstatus 从 Pending 变为 Succeeded)。先处理源环境,再处理目标环境。

对每个环境(开发源环境 + 每个目标环境)使用 create-deployment-environment.js:

node "${PLUGIN_ROOT}/scripts/lib/create-deployment-environment.js" \
  --hostEnvUrl "{HOST_ENV_URL}" \
  --token "{HOST_TOKEN}" \
  --name "{siteName} {label}" \
  --bapEnvId "{BAP_ENV_GUID}" \
  --environmentType 200000000 \
  [--environmentUrl "{environmentUrl}"]

必需参数(参见 scripts/lib/create-deployment-environment.js):

  • --bapEnvId — 要添加的环境的 BAP 环境 GUID。通过 pac env list(Environment ID 列)或针对当前源环境运行 pac env who 获取。不是组织/Dataverse URL。
  • --environmentType — 开发/源环境使用 200000000,每个目标环境使用 200000001。
  • --environmentUrl 为可选参数,仅会回显到输出标记中;不会发送到 Dataverse。

将标准输出捕获为 JSON:const envResult = JSON.parse(output)。 将 envResult.deploymentEnvironmentId 保存为 SOURCE_DEPLOYMENT_ENV_ID(用于开发源环境),或追加到 TARGET_DEPLOYMENT_ENV_IDs(用于每个目标环境)。同时保留每次调用所用的 bapEnvId 值——如果创建进入 Failed 状态,阶段 5a 的强制链接自动修复会用到该值。

注意:该脚本会向 deploymentenvironments 发送 POST 请求,使用不带前缀的字段(name、environmentid、environmenttype),从 OData-EntityId 标头中提取 deploymentenvironmentid GUID,然后每 3 秒轮询一次 validationstatus(最多 20 次),直到状态变为 200000001(Succeeded)或 200000002(Failed)。若失败,脚本会将错误详情写入标准错误并以状态码 1 退出——请停止操作并向用户报告错误。(早期预览 HAR 中使用的 msdyn_ 前缀字段形式和 192350001/192350002 状态码并非最终版本;已发布的 Pipelines 架构会拒绝带 msdyn_ 前缀的属性,并使用 2000000XX 状态码。)

失败时:停止操作并报告错误——创建部署环境是必需步骤。

5a — 检测“已关联到另一个 Pipelines 主机”(模式 15)

如果脚本的 stderr(不区分大小写)包含以下任意子字符串,则表示 BAP 环境当前已标记为使用另一个 Pipelines 主机:

  • already associated with another pipelines host
  • associated with another pipelines host
  • environment is already linked to a different host
  • environment is already bound to
  • linked to another host
  • claimed by another host

匹配时对这些内容一律不区分大小写(在调用 .includes() 前使用 String.prototype.toLowerCase()),以免 Pipelines 软件包版本之间后端措辞的变化悄无声息地导致检测失效。如果均未匹配,但脚本以底层 Dataverse 错误代码 0x80048d18 退出(或包装后的 errormessage 中包含该十六进制代码),则将其视为相同模式——即使错误消息措辞发生变化,这仍是稳定的信号。

🚦 **Gate(同意 · setup-pipeline:5a.pattern-15):**目标环境已标记为使用另一个 Pipelines 主机。提供文档中说明的强制关联自动修复——**具有破坏性:**先前的主机将失去对此环境的管线访问权限。在此取消将干净地退出 setup-pipeline。**每个触发模式 15 的环境都会触发此 Gate。**阶段 5 在向主机注册时会循环处理源环境和每个目标环境;如果两个目标环境都被发现已标记为使用不同主机,此 Gate 将触发两次——每个环境各一次。不要将同意请求合并到多个环境;破坏性影响范围仅限单个环境(每个环境都有自己的先前主机标记,并且都有一组会失去访问权限的创建者)。

这是 ${PLUGIN_ROOT}/references/deployment-error-catalog.md 中的模式 15。不要静默重试。将原始 errormessage 原样呈现给用户,并通过 AskUserQuestion 提供文档中说明的自动修复:

question: "<envLabel> is already linked to a different Pipelines host. The /power-pages:force-link-environment skill can take over the association (DESTRUCTIVE to the previous host — makers there lose pipeline access for this env). Run it now?"
header: "Force Link?"
options:
  - "Run /power-pages:force-link-environment now (Recommended)" — auto-fix per the deployment error catalog
  - "Cancel setup-pipeline" — investigate the previous host first

重要防护措施:

  • 切勿调用 /power-pages:force-link-environment,除非用户通过此提示明确同意——此操作只能通过从先前的主机再次执行 Force Link 来撤销。
  • 如果用户选择“运行 …”,调用 /power-pages:force-link-environment,并传入 --host <HOST_ENV_URL> 和 --dev-env <bapEnvId>(阶段 5 中为此环境捕获的 BAP 环境 GUID——见上文“还要保留 bapEnvId 值”说明),以便子技能跳过自身的主机/环境提示。
  • 子技能成功返回后,仅重新尝试失败的环境,使用相同参数重新运行 create-deployment-environment.js——不要从头重启整个阶段 5。创建脚本具有幂等性:对于已创建的环境,它会通过 findExistingByBapId 提前返回(并返回 reused: true);此前失败的环境现在会因主机标记已转移而解析为 Succeeded。
  • 如果用户选择“取消”,停止管线设置,并建议运行 /power-pages:ensure-pipelines-host detect-only,以便在重试前检查当前主机绑定。

对于任何其他 create-deployment-environment 失败,转入上述通用的“因错误而停止”路径。

每个环境完成验证后,都报告进度。

阶段 6 — 创建管道、关联源环境并创建阶段

使用 create-deployment-pipeline.js 一次性创建管道、关联源环境并创建所有阶段记录:

node "${PLUGIN_ROOT}/scripts/lib/create-deployment-pipeline.js" \
  --hostEnvUrl "{HOST_ENV_URL}" \
  --token "{HOST_TOKEN}" \
  --pipelineName "{PIPELINE_NAME}" \
  --description "Power Pages deployment pipeline for {siteName}" \
  --sourceDeploymentEnvironmentId "{SOURCE_DEPLOYMENT_ENV_ID}" \
  --stagesJson '[{"name":"Deploy to {targetLabel}","targetDeploymentEnvironmentId":"{TARGET_DEPLOYMENT_ENV_ID}","order":1}]'

将 stdout 捕获为 JSON:const pipelineResult = JSON.parse(output)。 提取:

  • pipelineResult.pipelineId → 保存为 PIPELINE_ID
  • pipelineResult.stages → { stageId, name, targetDeploymentEnvironmentId } 数组

脚本内部的操作(使用未加前缀的字段架构——之前带 msdyn_ 前缀的请求正文被随附的 Pipelines 架构拒绝;完整迁移映射请参阅 create-deployment-pipeline.js 顶部的注释块):

  1. 向 deploymentpipelines(v9.1)发送 POST 请求,正文为 { name, description }——从 OData-EntityId 标头中提取 deploymentpipelineid
  2. 向 deploymentpipelines({pipelineId})/deploymentpipeline_deploymentenvironment/$ref 发送 POST 请求,正文为相对路径 @odata.id,以关联源环境(经 HAR 确认——不能有前导 /,也不能使用完整 URL)
  3. 对每个阶段:向 deploymentstages 发送 POST 请求,正文为 { name, [email protected], [email protected] }——从 OData-EntityId 标头中提取 deploymentstagesid

失败时:脚本会将错误写入 stderr 并以状态码 1 退出——停止操作并向用户报告错误。

阶段 6b — 多解决方案 deploymentOrder(仅当 MULTI_SOLUTION_MODE = true 时)

设计说明(v1.3.x 更新): 单个 Power Platform Pipeline 可以通过单独的阶段运行部署多个解决方案——每次运行只需在同一条 deploymentstages 记录上指定不同的 artifactname + solutionid。为每个解决方案创建一个管道既浪费资源,又会让 Pipelines UI 杂乱不堪。现在我们只创建一个管道,并为每个目标环境创建一个阶段,然后在 docs/alm/last-pipeline.json 中记录各解决方案的部署顺序。随后,deploy-pipeline 会按该顺序循环,为每个解决方案针对同一阶段创建一次阶段运行。

当清单为 schemaVersion: 2 时,不要多次调用 create-deployment-pipeline.js。应改为:

  1. 只调用一次 create-deployment-pipeline.js,并传入:
    • pipelineName = "{siteName}-Pipeline"(例如 IdeaSphere-Pipeline)。
    • description,列出将通过该管道部署的解决方案(例如 "按顺序部署 IdeaSphere_Core → IdeaSphere_WebAssets → IdeaSphere_Future")。
    • 每个目标环境创建一条 deploymentstages 记录(而不是每个解决方案创建一条)。
  2. 根据按 order 排序后的 SOLUTIONS_LIST 构建 deploymentOrder 数组。每个条目包含 { solutionUniqueName, solutionId, order }。跳过满足 isFutureBuffer: true 且 components.length === 0 的条目——空的 Future 解决方案没有可部署的内容;它由 setup-solution 创建,但在包含内容之前不会参与部署循环。将其保留在顺序数组中,并设为 status: "SkippedEmpty",以便渲染器显示此意图。
  3. 收集唯一的 pipelineId 及其 stages[]。将 deploymentOrder 写入 docs/alm/last-pipeline.json(参见阶段 7)。
阶段 7 — 验证、写入工件并提交

7.1 验证管道已创建:

GET {hostEnvUrl}/api/data/v9.1/deploymentpipelines({PIPELINE_ID})?$select=name,statecode
Authorization: Bearer {HOST_TOKEN}

确认 statecode = 0(活动)。如果查询失败,报告“验证结果不确定——管道可能仍然有效”。

7.2 写入 docs/alm/last-pipeline.json(如果 docs/alm/ 目录不存在,请先创建——node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"):

{
  "pipelineId": "{PIPELINE_ID}",
  "pipelineName": "{PIPELINE_NAME}",
  "hostEnvUrl": "{HOST_ENV_URL}",
  "sourceDeploymentEnvironmentId": "{SOURCE_DEPLOYMENT_ENV_ID}",
  "sourceEnvironmentUrl": "{devEnvUrl}",
  "solutionName": "{uniqueName}",
  "createdAt": "{ISO timestamp}",
  "stages": [
    {
      "stageId": "{deploymentstagesid}",
      "name": "Deploy to {targetLabel}",
      "rank": 1,
      "targetDeploymentEnvironmentId": "{TARGET_DEPLOYMENT_ENV_ID}",
      "targetEnvironmentUrl": "{targetEnvUrl}"
    }
  ]
}

多解决方案标记(清单 v2): 当 MULTI_SOLUTION_MODE = true 时,docs/alm/last-pipeline.json 使用 schemaVersion: 3,其中包含一个管道和一个 deploymentOrder[],用于说明哪些解决方案将通过该管道部署,以及部署顺序:

{
  "schemaVersion": 3,
  "pipelineId": "...",
  "pipelineName": "IdeaSphere-Pipeline",
  "hostEnvUrl": "{HOST_ENV_URL}",
  "sourceDeploymentEnvironmentId": "{SOURCE_DEPLOYMENT_ENV_ID}",
  "sourceEnvironmentUrl": "{devEnvUrl}",
  "createdAt": "{ISO timestamp}",
  "stages": [
    {
      "stageId": "...",
      "name": "Deploy to Staging",
      "rank": 1,
      "targetDeploymentEnvironmentId": "...",
      "targetEnvironmentUrl": "https://staging.crm.dynamics.com"
    }
  ],
  "deploymentOrder": [
    { "solutionUniqueName": "IdeaSphere_Core", "solutionId": "...", "order": 1 },
    { "solutionUniqueName": "IdeaSphere_WebAssets", "solutionId": "...", "order": 2 },
    { "solutionUniqueName": "IdeaSphere_Future", "solutionId": "...", "order": 3, "status": "SkippedEmpty", "isFutureBuffer": true }
  ]
}

🚦 **Gate(plan · setup-pipeline:6b.v2-migration):**重新运行时检测到 v2 pipelines[] 清单。请选择:迁移到 v3(删除多出的 N-1 条流水线并合并为一条),或保留旧版布局。

**触发条件:**在 docs/alm/last-pipeline.json 的 schemaVersion 为 2 的项目上重新运行 setup-pipeline。 **询问原因:**自动迁移会删除 Dataverse 流水线记录——这会破坏宿主环境状态,且无法撤销,除非重新运行 setup-pipeline。 **取消后保留:**无——尚未删除任何流水线记录。

**迁移说明:**此技能的早期版本使用带有 pipelines[] 数组的 schemaVersion: 2(每个解决方案对应一条 Dataverse 流水线记录)。固定使用 v2 的项目可继续通过旧版 deploy-pipeline MULTI_PIPELINE_MODE 路径运行;所有新设置都应使用 v3 格式。在 v2 项目上重新运行 setup-pipeline 时,请通过 AskUserQuestion 询问是否迁移(删除多出的 N-1 条流水线并合并为一条),或保留旧版布局。

7.3 写入(或重新生成)docs/pipeline-setup.md(如有需要,创建 docs/ 目录)。

内容:

  1. 已创建的流水线 — 名称、宿主环境 URL、流水线 ID
  2. 已配置的环境 — 源环境 + 每个目标环境及其部署环境 ID
  3. 按部署顺序排列的解决方案(仅限多解决方案模式)— 对于 solutionManifest.solutions[] 中的每个条目,列出 {uniqueName, version, componentCount}。如果清单记录了 componentCount,则从每个条目的 components.length 读取;否则,从实时 Dataverse 查询读取(solutioncomponents?$filter=_solutionid_value eq '{solutionId}' and componenttype ne 380&$count=true)——不要硬编码,也不要沿用先前调用中的过期计数。
  4. 如何触发部署 — 运行 /power-pages:deploy-pipeline,或打开 Power Platform make.powerapps.com → Solutions → Pipelines
  5. 审批关卡(如适用)— 如何在 Power Platform Admin Center 中配置
  6. 故障排查 — 常见验证错误及解决方法

**同步模式下重新生成:**在 docs/alm/last-pipeline.json 已存在的项目上调用 setup-pipeline 时(在 configure-env-variables、setup-solution 同步或后续添加环境变量导致组件计数增加后重新运行),请根据当前 Dataverse 状态完整重新生成此文件——不要就地修补。已确认的故障:一个 Citizens 门户的 pipeline-setup.md 显示 Foundation 有 13 个组件,而在 configure-env-variables 为该解决方案添加 2 个环境变量定义后,Dataverse 中实际有 15 个组件;Markdown 文件始终未更新。最简单安全的做法是“始终在 Phase 7.3 中重新生成”,因为此操作会直接读取当前状态,而且文件中没有值得保留的用户可编辑部分。

7.4 提交:

git add docs/alm/last-pipeline.json docs/pipeline-setup.md
git commit -m "Add Power Platform Pipeline configuration for {siteName}"

7.5 记录技能使用情况:

参考:${PLUGIN_ROOT}/references/skill-tracking-reference.md

按照参考文件中的技能跟踪说明,记录此技能的使用情况。使用 --skillName "SetupPipeline"。

7.5b 刷新 ALM 计划(如果存在):

node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
  --projectRoot "." \
  --phase setup-pipeline \
  --render

该辅助工具会读取 docs/alm/last-host-check.json + docs/alm/last-pipeline.json,刷新 planData.hostResolution 和 planData.pipelineMeta,移除设置前“未检测到宿主”的风险,并重新生成 docs/alm-plan.html。当 docs/.alm-plan-data.json 不存在时(独立调用,不属于 ALM 计划的一部分),该辅助工具会返回 ok:false,作为软性空操作——可以无条件安全运行。 指出下一步(由用户驱动执行顺序)。 辅助程序的 stdout JSON 包含 nextStep: { name, skill: string | null } | null。当其非 null 时,根据 skill 分支处理:当 skill 非 null 时,告知用户 "计划已更新。计划中的下一步:{nextStep.name} → 准备好后运行 {nextStep.skill}。";当 skill 为 null(例如 Finalize 这样的内部步骤,没有用户命令)时,只说出步骤名称 — "计划已更新。计划中的下一步:{nextStep.name}。" — 切勿输出 run null。当其为 null(所有步骤已完成)或辅助程序返回 ok:false(无计划)时,不要提及下一步。切勿自动调用下一个技能 — 执行由用户驱动。

7.6 呈现摘要:

资源ID / URL
流水线{PIPELINE_NAME} ({PIPELINE_ID})
主机环境{HOST_ENV_URL}
源部署环境{SOURCE_DEPLOYMENT_ENV_ID}
阶段:{name}{stageId} → {targetEnvUrl}

写入的文件:

  • docs/alm/last-pipeline.json — 流水线配置标记文件
  • docs/pipeline-setup.md — 设置文档

下一步:

运行 /power-pages:deploy-pipeline,触发首次部署运行。


即将推出路径

如果选择了 GitHub Actions 或 Azure DevOps:

告知用户:

"此技能即将支持 GitHub Actions 和 Azure DevOps Pipeline。

目前,你有两个选择:

  1. 使用 Power Platform Pipelines — 选择选项 1,设置 Microsoft 原生部署流水线(推荐)
  2. 退出 — 我将根据文档手动设置 GitHub Actions / Azure DevOps"

🚦 关卡(计划 · setup-pipeline:coming-soon.exit): 用户选择了 GitHub/ADO(即将推出的占位选项)— 提供切换回 PP Pipelines 或正常退出的选项。

通过 AskUserQuestion 询问:

  1. 切换到 Power Platform Pipelines — 返回阶段 2
  2. 退出 — 我将手动设置

如果 GitHub/ADO 是通过参数传入的:显示上述消息并正常退出。


关键决策点(等待用户)

  1. 阶段 1:现有流水线文件 — 覆盖、查看或取消(仅当找到 docs/alm/last-pipeline.json 时)
  2. 阶段 2:平台选择(Power Platform Pipelines / GitHub 即将推出 / ADO 即将推出)
  3. 阶段 3:确认流水线配置 — 流水线名称、主机环境 URL、目标环境
  4. 阶段 4:预检警告 — 继续或取消
  5. 阶段 3:创建流水线前确认参数

错误处理

  • 没有 powerpages.config.json:停止,建议使用 /power-pages:create-site
  • 没有 .solution-manifest.json:停止,建议使用 /power-pages:setup-solution
  • RetrieveSetting 返回空值:请用户手动提供主机环境 URL
  • 部署环境 statecode = 1 且 errormessage 非 null(验证失败):停止并提供错误详情
  • 流水线 $ref 调用失败:停止 — 创建阶段前必须完成此关联
  • 阶段创建失败:记录失败,并继续创建其余阶段 — 部分成功也有效

进度跟踪表

任务主题当前操作描述
检测项目上下文正在检测项目上下文读取 powerpages.config.json 和 .solution-manifest.json;运行 pac env who 和 pac env list;调用 RetrieveSetting 查找宿主环境;检查是否存在 docs/alm/last-pipeline.json
选择 CI/CD 平台正在选择 CI/CD 平台询问用户:Power Platform Pipelines(完整支持)还是 GitHub/ADO(即将推出)
确认管道配置正在确认管道配置根据自动检测到的值预填管道名称、源环境、宿主环境和解决方案名称;询问目标环境;获取用户确认
运行预检正在运行预检验证宿主环境是否已安装 Pipelines;验证开发环境中是否存在解决方案;检查管道名称是否冲突
创建部署环境正在创建部署环境为源环境和每个目标环境 POST deploymentenvironments;轮询每个环境的 validationstatus,直到状态为 Succeeded
创建管道和阶段正在创建管道和阶段POST deploymentpipelines;使用 $ref 关联源环境;为每个目标环境 POST deploymentstages(通过 previousdeploymentstageid 链接)
验证并写入制品正在验证并写入制品查询管道以确认其处于活动状态;写入 docs/alm/last-pipeline.json;写入 docs/pipeline-setup.md;提交;提供包含后续步骤的摘要

评论

支持 Markdown