关于 setup-pipeline
setup-pipeline 是用于在 Dataverse 配置 Power Platform Pipelines 的 Power Pages 技能。它通过项目与环境检查、ALM 计划门槛及管道创建来准备部署,无需 YAML 或外部 CI/CD 基础设施。
主要功能
- 01在 Dataverse 创建管道配置
- 02检查项目和解决方案上下文
- 03验证身份验证与 API 访问
- 04解析 Pipelines 主机环境
- 05要求确认 ALM 计划
适用场景
- 01设置 Power Pages 自动部署
- 02准备解决方案在环境间晋级
- 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:
| Question | Header | Options |
|---|---|---|
| 是否先刷新计划? | 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 — 检测项目上下文
在此阶段开始时一次性创建所有任务。
要创建的任务:
- “检测项目上下文”
- “选择 CI/CD 平台”
- “确认管道配置”
- “运行预检”
- “创建部署环境”
- “创建管道和阶段”
- “验证并写入构件”
步骤:
-
使用
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。将创建一个管道(沿用现有流程)。
- 如果
-
运行
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)后重试。 -
静默运行:
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 退出,因此此步骤可以优雅降级。 -
通过
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。 -
检查是否存在
docs/alm/last-pipeline.json。如果存在,请读取其内容。 -
报告发现结果:“项目:
{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})。你想如何继续?
- 覆盖——创建新管道并替换标记文件
- 先检查现有设置,再决定
- 取消”
- 如果选择检查:显示现有
docs/alm/last-pipeline.json的内容,然后使用相同的 3 个选项再次提问。 - 如果选择取消:停止此技能,并告知用户未进行任何更改。
- 如果选择覆盖:继续。
阶段 1.5 — 依据当前 Pipelines 文档核实信息
参考:
${PLUGIN_ROOT}/references/alm-docs-grounding.md
此步骤最多用时约 30 秒。如果 MCP 搜索/抓取出错,请记录一行说明并继续——此技能必须能够离线运行。
- 使用以下查询运行
microsoft_docs_search:Power Platform Pipelines setup OData API host environment deploymentenvironments。 - 通过
microsoft_docs_fetch并行抓取https://learn.microsoft.com/en-us/power-platform/alm/pipelines(以及最多一个关于主机设置或管道创建的相关页面)。 - 提取一段摘要,说明 Microsoft Learn 当前关于 Pipelines 主机解析、
deploymentenvironments/deploymentpipelines/deploymentstages架构以及管道生命周期的内容。与${PLUGIN_ROOT}/references/cicd-pipeline-patterns.md比较,并指出任何差异(新增字段、弃用的 API、变更的验证状态代码)。 - 使用该摘要为阶段 2 及之后的决策提供依据。不要悄然更改技能行为——在阶段 5(向 Pipelines 主机注册环境)之前,向用户提示任何差异,作为非强制警告。
阶段 2 — 选择 CI/CD 平台
🚦 **关卡(plan · setup-pipeline:2.platform):**选择 CI/CD 平台——PP Pipelines(完整支持)或 GitHub Actions / ADO(即将推出的占位项)。
通过 AskUserQuestion 询问用户:
“你想使用哪个 CI/CD 平台?
- Power Platform Pipelines——Microsoft 的原生部署管道。无需外部基础设施。(推荐)
- GitHub Actions——即将推出
- 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} |
| 宿主环境 URL | ensure-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标头中提取deploymentenvironmentidGUID,然后每 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 hostassociated with another pipelines hostenvironment is already linked to a different hostenvironment is already bound tolinked to another hostclaimed 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_IDpipelineResult.stages→{ stageId, name, targetDeploymentEnvironmentId }数组
脚本内部的操作(使用未加前缀的字段架构——之前带
msdyn_前缀的请求正文被随附的 Pipelines 架构拒绝;完整迁移映射请参阅create-deployment-pipeline.js顶部的注释块):
- 向
deploymentpipelines(v9.1)发送 POST 请求,正文为{ name, description }——从OData-EntityId标头中提取deploymentpipelineid- 向
deploymentpipelines({pipelineId})/deploymentpipeline_deploymentenvironment/$ref发送 POST 请求,正文为相对路径@odata.id,以关联源环境(经 HAR 确认——不能有前导/,也不能使用完整 URL)- 对每个阶段:向
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。应改为:
- 只调用一次
create-deployment-pipeline.js,并传入:pipelineName = "{siteName}-Pipeline"(例如IdeaSphere-Pipeline)。description,列出将通过该管道部署的解决方案(例如"按顺序部署 IdeaSphere_Core → IdeaSphere_WebAssets → IdeaSphere_Future")。- 每个目标环境创建一条
deploymentstages记录(而不是每个解决方案创建一条)。
- 根据按
order排序后的SOLUTIONS_LIST构建deploymentOrder数组。每个条目包含{ solutionUniqueName, solutionId, order }。跳过满足isFutureBuffer: true且components.length === 0的条目——空的 Future 解决方案没有可部署的内容;它由setup-solution创建,但在包含内容之前不会参与部署循环。将其保留在顺序数组中,并设为status: "SkippedEmpty",以便渲染器显示此意图。 - 收集唯一的
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-pipelineMULTI_PIPELINE_MODE 路径运行;所有新设置都应使用 v3 格式。在 v2 项目上重新运行setup-pipeline时,请通过AskUserQuestion询问是否迁移(删除多出的 N-1 条流水线并合并为一条),或保留旧版布局。
7.3 写入(或重新生成)docs/pipeline-setup.md(如有需要,创建 docs/ 目录)。
内容:
- 已创建的流水线 — 名称、宿主环境 URL、流水线 ID
- 已配置的环境 — 源环境 + 每个目标环境及其部署环境 ID
- 按部署顺序排列的解决方案(仅限多解决方案模式)— 对于
solutionManifest.solutions[]中的每个条目,列出{uniqueName, version, componentCount}。如果清单记录了componentCount,则从每个条目的components.length读取;否则,从实时 Dataverse 查询读取(solutioncomponents?$filter=_solutionid_value eq '{solutionId}' and componenttype ne 380&$count=true)——不要硬编码,也不要沿用先前调用中的过期计数。 - 如何触发部署 — 运行
/power-pages:deploy-pipeline,或打开 Power Platform make.powerapps.com → Solutions → Pipelines - 审批关卡(如适用)— 如何在 Power Platform Admin Center 中配置
- 故障排查 — 常见验证错误及解决方法
**同步模式下重新生成:**在
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。
目前,你有两个选择:
- 使用 Power Platform Pipelines — 选择选项 1,设置 Microsoft 原生部署流水线(推荐)
- 退出 — 我将根据文档手动设置 GitHub Actions / Azure DevOps"
🚦 关卡(计划 · setup-pipeline:coming-soon.exit): 用户选择了 GitHub/ADO(即将推出的占位选项)— 提供切换回 PP Pipelines 或正常退出的选项。
通过 AskUserQuestion 询问:
- 切换到 Power Platform Pipelines — 返回阶段 2
- 退出 — 我将手动设置
如果 GitHub/ADO 是通过参数传入的:显示上述消息并正常退出。
关键决策点(等待用户)
- 阶段 1:现有流水线文件 — 覆盖、查看或取消(仅当找到
docs/alm/last-pipeline.json时) - 阶段 2:平台选择(Power Platform Pipelines / GitHub 即将推出 / ADO 即将推出)
- 阶段 3:确认流水线配置 — 流水线名称、主机环境 URL、目标环境
- 阶段 4:预检警告 — 继续或取消
- 阶段 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;提交;提供包含后续步骤的摘要 |