mcpField
D
deploy-pipeline

deploy-pipeline

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

关于 deploy-pipeline

deploy-pipeline 是用于执行已配置 Power Platform Pipeline 部署的 Power Pages 技能。它选择目标阶段、验证解决方案包、部署到目标环境,并记录部署信息。

主要功能

  1. 01选择目标部署阶段
  2. 02验证解决方案包
  3. 03通过 Power Platform Pipelines 部署
  4. 04检查环境与身份验证前提
  5. 05记录部署信息

适用场景

  1. 01将 Power Pages 解决方案晋级到其他阶段
  2. 02向目标环境部署已验证的包
  3. 03部署前查看可用阶段

安装

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

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

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

附带文件assets/deploy-history-template.htmlscripts/validate-deploy-pipeline.js

详细介绍

插件检查:运行 node "${PLUGIN_ROOT}/scripts/check-version.js" — 如果输出消息,请在继续之前向用户展示。

deploy-pipeline

触发一次 Power Platform Pipeline 部署运行。读取 docs/alm/last-pipeline.json 中现有的管道配置,选择目标阶段,验证解决方案包,并将其部署到目标环境。

前置条件:先运行 /power-pages:setup-pipeline 创建管道配置。

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

前置条件

重要:源(开发)环境必须配置 Power Platform Pipelines 主机环境。可在 Power Platform Admin Center(Environments → select env → Pipelines)中设置,也可通过租户级别的 DefaultCustomPipelinesHostEnvForTenant 设置。若未配置,pac pipeline deploy 将失败。setup-pipeline 技能会在主机中创建管道定义;此管理步骤会将开发环境连接到该主机。

  • docs/alm/last-pipeline.json 存在(由 setup-pipeline 创建)
  • .solution-manifest.json 存在
  • 已登录 Azure CLI(az account show 成功)
  • 已登录 PAC CLI(pac env who 成功)

阶段

阶段 0 — ALM 计划门禁

plan-alm 是入口。 当用户表达 ALM 意图(提升 / 交付 / 部署 / 移至暂存环境 / 推送到生产环境 / 发布此版本)时,协调器(/power-pages:plan-alm)应先运行。直接调用 deploy-pipeline 会绕过协调器的预先计划完整性检查、按阶段解析环境变量、激活步骤和验证运行。此门禁会明确提示这一绕过行为。

跳过规则。 如果此技能是作为正在执行的 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}"

辅助程序返回包含 { exists, stale, staleness: { reason, detail }, generatedAt, planStatus, ... } 的 JSON。新鲜度检查需要环境凭据和 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 可以创建计划 — 它会检测项目状态、询问你的推广策略,并按照正确顺序协调此技能与 setup-solution / setup-pipeline / activate-site / test-site。要我现在运行 plan-alm 吗?” 🚦 门禁(意图 · deploy-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。部署仍可正常进行,但不会编排各阶段的环境变量值、激活和部署后验证。
  • 取消 → 正常退出。

步骤 4 — 计划已过期。 告知用户:

"ALM 计划生成于 {generatedAt},但源解决方案在此之后已修改(时间为 {solution.modifiedon})。计划中的组件数量、大小分析和拆分决策可能已过时。重新运行 plan-alm 将刷新分析。"

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

AskUserQuestion:

问题标题选项
是否先刷新计划?ALM 计划时效性刷新 — 重新运行 /power-pages:plan-alm(推荐),使用现有计划继续,取消
  • 刷新(推荐) → 调用 /power-pages:plan-alm。完成后,再运行一次 Phase 0 辅助脚本以确认计划仍有效;如果仍已过期,显示详细信息并继续执行 Phase 1(不要无限循环)。
  • 继续 → 设置 STALE_PLAN_ACK = true 并继续执行 Phase 1。
  • 取消 → 正常退出。

与 Phase 3.5(部署前完整性检查)的关系。 Phase 3.5(本技能后续部分)会在部署前立即检查解决方案是否存在缺项。Phase 0 会捕捉更大的疏漏:用户从未运行编排器,却准备直接推送尚未准备完善的部署。两者相辅相成。

Phase 1 — 验证先决条件

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

要创建的任务:

  1. “验证先决条件”
  2. “选择目标阶段”
  3. “解析管道信息”
  4. “验证包” — 在 MULTI_RUN_MODE 中,此任务会变为一个并行批次(Phase 3.6),覆盖所有 N 个未跳过的解决方案;在单解决方案 / legacy v2 模式中,则会在 Phase 4 的每次迭代中内联执行
  5. “配置部署设置”
  6. “部署并监控”
  7. “编写部署记录”

步骤:

  1. 先解析项目配置的环境 URL,然后断言 PAC 确实已连接到该环境。 verify-alm-prerequisites.js 会从 PAC 的当前组织上下文(pac env who)解析环境,该上下文不一定与项目匹配。如果 PAC 已切换到其他环境(例如存在多个处于活动状态的 pac auth 配置文件,或外部进程切换了当前环境),技能就会静默地针对错误的环境(可能是 PROD)运行发现,随后运行 pac pipeline deploy。断言二者匹配,并在不匹配时立即停止,而不是固定使用 --envUrl:固定 URL 只能修正 Dataverse API 调用,而后续 PAC-CLI 操作仍会遵循已切换的上下文,因此,断言 PAC 自身已连接到正确环境是更安全的门禁。

    读取项目记录的环境 URL(采用第一个匹配项):.solution-manifest.json → 顶层 environmentUrl;否则读取 powerpages.config.json → environmentUrl。保存为 CONFIGURED_ENV_URL。(这两个字段都是顶层的 environmentUrl 字符串;非 SPA/EDM 站点没有 powerpages.config.json,因此这类站点应使用清单中的值。)

    仅当 CONFIGURED_ENV_URL 确实解析为 URL 时,才传入 --expectedEnvUrl。 存在已记录的环境 URL 时使用第一种形式;两个文件都未记录环境 URL 时使用第二种形式 — 不要传入空的或未解析的 --expectedEnvUrl "{CONFIGURED_ENV_URL}":

    # CONFIGURED_ENV_URL resolved (recorded in manifest/config) — assert PAC is on it:
    node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --require-manifest --expectedEnvUrl "{CONFIGURED_ENV_URL}"
    
    # Neither file records an env URL — omit the flag, fall back to the pac-context default:
    node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --require-manifest
    

    --expectedEnvUrl 会让此辅助程序将 PAC 解析出的环境(仅源)与 CONFIGURED_ENV_URL 进行比较;如果不匹配,则以非零状态退出,并报错 "Environment mismatch: PAC CLI is connected to {X} but this project targets {Y} — run pac env select …"。(作为安全保障,如果该值不是可解析的环境 URL,辅助程序会跳过断言——空字符串或尚未替换的占位符不会导致流程强制停止——但如果没有记录的 URL,最好直接省略此标志。)将输出捕获为 JSON;提取 .envUrl(存储为 devEnvUrl)和 .token(存储为 DEV_TOKEN)。如果脚本以非零状态退出,请停止并显示错误——这表示环境不匹配,或者 az login / pac auth / WhoAmI 失败。

  2. 运行 detect-project-context.js 以读取项目配置和解决方案清单:

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

    将输出捕获为 JSON;提取 .solutionManifest(存储为 solutionManifest)、.siteName(存储为 siteName)和 .websiteRecordId。如果 solutionManifest 为 null,请继续——此步骤并非严格要求清单(解决方案信息将从 docs/alm/last-pipeline.json 获取)。

  3. 定位 docs/alm/last-pipeline.json——如果未找到,请停止并建议先运行 /power-pages:setup-pipeline。

    清单版本检查:

    • 如果 schemaVersion === 3,将 MULTI_RUN_MODE = true,并将 deploymentOrder[] 存储为 DEPLOYMENT_ORDER。这是单个管道,具有一组阶段;多解决方案通过针对同一阶段运行 N 次阶段运行来表示,每个解决方案对应一次,顺序由 order 指定。这是当前推荐的布局。
    • 如果 schemaVersion === 2(旧版),将 MULTI_PIPELINE_MODE = true,并将 pipelines[] 存储为 PIPELINES_LIST。该技能会回退到旧版“循环处理 N 条独立 deploymentpipelines 记录”的行为。建议用户重新运行 setup-pipeline,迁移到 v3。
    • 否则,读取 pipelineId、pipelineName、hostEnvUrl、sourceDeploymentEnvironmentId、solutionName、stages[](单解决方案模式——现有行为)。

    在 MULTI_RUN_MODE 下,针对 DEPLOYMENT_ORDER 的每次迭代解析 solutionName + solutionId。status === "SkippedEmpty" 的条目(通常是 {Prefix}_Future 缓冲区)会被提前跳过——不会为其创建阶段运行。所有运行都使用同一个 pipelineId / hostEnvUrl / sourceDeploymentEnvironmentId。

    在 MULTI_PIPELINE_MODE 下(旧版 v2),在循环中针对每个管道解析 solutionName(而非全局解析)。所有管道共享同一个 hostEnvUrl 和 sourceDeploymentEnvironmentId。

  4. 获取主机环境令牌:

    az account get-access-token --resource "{hostEnvOrigin}" --query accessToken -o tsv 2>/dev/null
    

    其中 hostEnvOrigin = hostEnvUrl 的协议 + 主机。存储为 HOST_TOKEN。如果获取失败,请停止并提示检查 Azure CLI 身份验证。

  5. 如果 solutionManifest 可用,从检测到的上下文中读取 solutionManifest.solution.solutionId 和 solutionManifest.solution.uniqueName。否则,使用 docs/alm/last-pipeline.json 中的 solutionName。

  6. 报告:“管道:{pipelineName}。解决方案:{solutionName}。可用阶段:{stage names}。”

阶段 1.5 — 以当前 Pipelines 部署文档为依据

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

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

  1. 使用查询 Power Platform Pipelines stage run validation ValidatePackageAsync DeployPackageAsync approval 运行 microsoft_docs_search。
  2. 通过 microsoft_docs_fetch 并行获取 https://learn.microsoft.com/en-us/power-platform/alm/pipelines(以及至多一个关于阶段运行、验证或审批关卡的相关页面)。
  3. 提取一段摘要,说明 Microsoft Learn 当前对阶段运行生命周期、验证结果、审批关卡工作流以及 deploymentsettingsjson 覆盖项的描述。与 ${PLUGIN_ROOT}/references/cicd-pipeline-patterns.md 对比,并标记任何差异(新的状态代码、变更后的 stagerunstatus 终止值、新的审批关卡 API)。
  4. 使用此摘要为阶段 2 及后续阶段的决策提供依据。不要悄悄更改技能行为 — 在阶段 4(创建阶段运行 + 验证包)之前,向用户提示任何差异。
阶段 2 — 选择目标阶段

如果用户通过参数传入了阶段名称或环境标签(例如 staging),则将其与 docs/alm/last-pipeline.json 中的阶段进行匹配,并跳过此问题。

🚦 关卡(计划 · deploy-pipeline:2.stage): 选择目标阶段 — Staging / Production / 等。此处选错阶段是此技能最主要的单一失败原因。

否则,通过 AskUserQuestion 提问:

“你要部署到哪个环境? {docs/alm/last-pipeline.json 中的阶段编号列表,例如:

  1. 部署到 Staging → {stagingEnvUrl}
  2. 部署到 Production → {prodEnvUrl}}”

将所选阶段存储为 SELECTED_STAGE(包含 stageId、name、targetDeploymentEnvironmentId、targetEnvironmentUrl)。

设计原因——部署循环按设计采用串行方式。 当 DEPLOYMENT_ORDER 有 N 个条目时(例如,对于拆分为 2 个解决方案并预留未来缓冲区的情况,顺序为 Core → WebAssets → Future),循环会一次处理一个解决方案,按 order 升序执行,遇到首个失败即停止。这是有意为之。四项约束叠加,使并行调用 DeployPackageAsync 不仅没有好处,反而会造成实际危害:

  1. 目标环境中的 Dataverse 导入锁。 ImportSolutionAsync 会获取环境级锁——每个环境同一时间只能主动导入一个解决方案。即使技能并行发起 N 个 DeployPackageAsync 调用,宿主也会将它们排队并串行执行。因此,并行部署在实际耗时上的收益几乎为零。
  2. 4 种拆分策略中有 3 种存在拆分间依赖。 变更频率(Foundation → Integration → Config → Content)、架构(Domain_1..N → Site)和分层(Core → WebAssets,其中 WebAssets 的 ppc 行引用 Core 中的 powerpagesite 记录)都规定了严格的顺序。调整顺序会导致导入失败——如果某个流引用的表尚未存在于目标环境中,就会因 MissingDependency 而失败。
  3. 每次迭代都需要获得同意。 Phase 6.0(deploy-pipeline:6.0.final-consent)会在每次 DeployPackageAsync 之前触发。Phase 5 的环境变量提示也会在每次迭代中触发。并行执行要么需要批量处理这些同意门控(明确禁止——见下方每次迭代的说明),要么需要并发运行多个 AskUserQuestion,这两种方式都不受执行框架支持。
  4. 可控的失败处理。 串行执行并在首个失败时停止,意味着 docs/alm/last-deploy.json 中可以清晰地记录每个解决方案的 status。重试时,循环会从头开始;Dataverse 对相同版本的幂等处理会让已成功部署的解决方案成为空操作。

不要通过用 Promise.all / Promise.allSettled / await Promise.race 包装各次迭代来“优化”此循环。 验证阶段(Phase 3.6)确实会并行执行——ValidatePackageAsync 不会获取导入锁——但部署阶段有意采用串行方式。如果未来某个 Dataverse 版本移除了环境级导入锁,请重新评估此设计原因;在此之前,这一约束至关重要,并非疏忽。

在 MULTI_RUN_MODE 中(v3——推荐): 从唯一的 stages[] 数组中查找一次所选阶段。随后,技能会按 order 遍历 DEPLOYMENT_ORDER,为每个解决方案针对同一个 stageId 创建一次阶段运行:

  1. 先运行 Phase 3.6(在循环开始前运行一次)——并行对每个未跳过的解决方案发起 create-stage-run + ValidatePackageAsync + poll-validation-status。如果任一解决方案未通过验证,则停止整个部署。将每个解决方案对应的 stageRunId 存入 VALIDATED_STAGE_RUNS,以便串行部署循环复用。
  2. 对于 DEPLOYMENT_ORDER 中 status !== "SkippedEmpty" 的每个条目:解析其 solutionUniqueName + solutionId,从 VALIDATED_STAGE_RUNS[solutionUniqueName] 中获取其 stageRunId,设置 ARTIFACT_SOLUTION_NAME / ARTIFACT_SOLUTION_ID / STAGE_RUN_ID,然后针对同一个管道运行 Phases 4.4(获取部署说明)→ 5(配置)→ 6.0(每次迭代都会触发同意门控)→ 6.1(部署)→ 6.2(轮询)。跳过 Phase 4.1–4.3(创建阶段运行 + 验证 + 轮询验证)——Phase 3.6 已并行完成这些工作。
  3. 如果任一次迭代(部署)失败,则停止循环,并报告失败的解决方案以及已经成功部署的解决方案。
  4. 最后写入一个 docs/alm/last-deploy.json,总结所选阶段的所有运行情况。记录每个解决方案的 status(Succeeded / Failed / NotAttempted / SkippedEmpty),以及共享的 pipelineId。

⚠ 每次迭代都必须触发门控,不容妥协。 在循环内,每个解决方案都必须完整执行 Phase 3 → 3.5 → 4 → 5 → 6.0 → 6.1 → 6.2 → 7 序列。请勿跨解决方案批量验证,勿批量执行 Phase 6.0 同意门控,也勿将任何上游答案(Phase 2 阶段选择、--stage 参数、上一轮迭代中的“立即部署”)视为适用于后续迭代。对于 DEPLOYMENT_ORDER 中的 N 个未跳过解决方案,Phase 6.0 门控会触发 N 次。如果你发现自己在迭代 1 成功后,未经新的 Phase 6.0 提示就直接进入迭代 2 的 DeployPackageAsync,说明你跳过了门控。

在 MULTI_PIPELINE_MODE(v2 — 旧版)中: 将选定的阶段标签(例如“Staging”)与每个管道的 stages[] 匹配——对于相同的目标环境,每个管道都有自己的 stageId。后续所有阶段(验证、部署、轮询)都按 order 循环遍历 PIPELINES_LIST:

  1. 循环迭代 i:使用与 SELECTED_STAGE.name 阶段标签匹配的 pipelines[i].stageId、pipelines[i].solutionName 等。
  2. 如果任何一次迭代失败(验证或部署失败),则停止循环,并报告失败的管道以及已部署的管道。
  3. 在最后写入一个 docs/alm/last-deploy.json,汇总此阶段的所有管道运行情况。记录每个管道的 status(Succeeded / Failed / NotAttempted),以便重试时判断哪些管道仍需运行。

⚠ 此处同样必须逐次迭代触发门控。 规则与 MULTI_RUN_MODE 相同:循环中的每个管道都必须各自执行 Phase 4 / 5 / 6.0 / 6.1 / 6.2 序列。每个管道都要触发一次 Phase 6.0 同意门控。请勿批量执行。

部分部署风险。 循环停止时(例如 Core 成功,WebAssets 失败),目标环境会处于混合状态——已导入的解决方案不会自动回滚。docs/alm/last-deploy.json 中按解决方案(v3)或按管道(v2)记录的 status 是确认实际部署内容的唯一依据。用户修复故障后重新运行 deploy-pipeline 时,循环会从头再次遍历所有条目;应依赖解决方案导入的幂等性(版本相同则不执行任何操作),而不是跳过条目。在开始向生产环境进行多解决方案部署前,请向用户说明这一点。

检查 docs/alm/last-deploy.json——如果最近一次向此阶段的部署失败,请警告用户:

“上次向 {stageName} 的部署状态为:Failed。是否要重试?1. 是,重试 / 2. 否,取消”

Phase 2.5 — 预检:检查目标环境是否屏蔽附件

Power Pages SPA 解决方案几乎总是包含 .js 捆绑包分块(Vite/Rollup 输出),作为 Web File 组件。如果目标环境的 blockedattachments 设置包含 .js,ImportSolutionAsync 会拒绝所有 Web File 写入——对于包含数千个捆绑包分块的网站,通常会在导入开始 50-75 分钟后才发生(真实案例:Content 解决方案在 Staging 中因 3,909 个被拒绝的 .js 文件而失败,而同一问题此前已在 Dev 中修复)。响应式 Phase 7.6 处理程序会检测到此问题并提供解除屏蔽后重试的选项,但用户已经浪费了一小时。此预检可在 10 秒内发现问题。

跳过规则。 此检查仅适用于 Power Pages 项目。如果 powerpages.config.json 不含 websiteRecordId,则跳过(非 Power Pages ALM 运行,例如纯数据模型解决方案等)。如果用户计划或清单表明待部署的解决方案均不含 Web File componentType,也跳过(SPA 项目中罕见,但纯后端解决方案可能如此)。

检测信号。 在 MULTI_RUN_MODE / MULTI_PIPELINE_MODE 中:读取 .solution-manifest.json,检查 solutions[] 中是否有任何条目的 componentTypes 包含 "Web File"。在单解决方案模式下:对于任何 Power Pages 项目,均假设为 true(总解决方案包含 Web File)。

步骤:

  1. 将 PAC CLI 上下文切换到目标环境,以便 fix-blocked-attachments.js 查询正确的环境:

    pac env select --environment "{SELECTED_STAGE.targetEnvironmentUrl}"
    
  2. 以试运行模式运行辅助程序,以检测当前状态:

    node "${PLUGIN_ROOT}/scripts/lib/fix-blocked-attachments.js" \
      --envUrl "{SELECTED_STAGE.targetEnvironmentUrl}" \
      --extensions js,css \
      --dry-run
    
  3. 将输出保存为 JSON。检查 wasBlocked[]:

    • wasBlocked: [] → 目标环境未阻止相关扩展名。将 PAC CLI 切回源环境(pac env select --environment "{sourceEnvUrl}"),然后继续执行阶段 3。无需提示用户,也不会产生额外干扰。

    🚦 关卡(同意 · deploy-pipeline:2.5.blocked-attachments): 部署前检查 — 修改目标环境的 blockedattachments 安全设置(影响整个租户)。可通过 PPAC 撤销。跳过此步骤会浪费 50–75 分钟。

    • wasBlocked: ["js"] 或包含其他与媒体相关的扩展名 → 部署将在导入过程中失败。立即通过 AskUserQuestion 提示用户(不要把这件事埋在聊天内容中 — 必须以此作为进入阶段 3 的关卡):

      部署前检查发现问题。 目标环境 {targetEnvName} 当前阻止了此解决方案所需的文件类型:{wasBlocked.join(', ')}。Power Pages SPA 站点会将 .js 捆绑文件块作为 Web 文件发布 — 如果不解除阻止就继续,部署将运行约 50-75 分钟,然后失败(可通过阶段 7.6 的重试路径恢复,但浪费的时间无法挽回)。解除阻止只需 10 秒。

      注意:此操作会修改环境级安全设置,影响 {targetEnvName} 的所有用户。可通过 PPAC → Environments → {targetEnvName} → Settings → Product → Features → Blocked Attachments 撤销。

      问题标题选项
      是否允许解除 {targetEnvName} 环境中对 {wasBlocked.join(', ')} 的阻止,以便继续部署?解除附件阻止是 — 解除这些类型的阻止并继续,否 — 仍然继续(阶段 7.6 会在部署后检测到失败并再次提示),取消部署
  4. 根据回答分支处理:

    • 是 — 解除阻止并继续:调用 fix-blocked-attachments.js,不要使用 --dry-run(使用相同的 --extensions)。读取结果并确认 changed: true 且 removed[] 非空。将 PAC CLI 切回源环境(pac env select --environment "{sourceEnvUrl}")。继续执行阶段 3。在最终的 docs/alm/last-deploy.json preflightActions[] 块中记录解除阻止的操作,以便部署摘要包含审计记录。

    • 否 — 仍然继续:保持设置不变。将 PAC CLI 切回源环境。继续执行阶段 3。向用户明确说明:“继续操作,不解除阻止。如果部署因 AttachmentBlocked 失败,阶段 7.6 会提供相同的解除阻止提示 — 但届时你已为此耗费约 50-75 分钟。”

    • 取消部署:干净地停止。不要修改任何环境设置。不要创建阶段运行记录。告知用户准备好后如何重新调用。

  5. 始终在退出此阶段前将 PAC CLI 切回源环境。后续阶段默认 PAC 指向源环境,除非明确切换到目标环境。

为何同时采用部署前检查和响应式处理。 响应式的阶段 7.6 处理程序仍需保留,因为:(a) 部署前检查只会检查环境级 blockedattachments 设置 — 还有其他少见的附件阻止原因(按表设置的文件列附件策略),只有在实际导入期间才会显现;(b) 从部署前检查到导入期间,环境的阻止列表可能发生变化(虽然罕见,但如果其他管理员同时编辑,就有可能);(c) 这可向后兼容现有流程:用户直接调用 deploy-pipeline,通过旧版 SKILL.md 跳过阶段 2.5。两条路径相辅相成,并非重复。

阶段 3 — 解析管道信息

调用 RetrieveDeploymentPipelineInfo,获取权威的源环境 ID 和可用解决方案构件:

GET {hostEnvUrl}/api/data/v9.1/RetrieveDeploymentPipelineInfo(DeploymentPipelineId={pipelineId},SourceEnvironmentId='{BAP_SOURCE_ENV_ID}',ArtifactName='{solutionName}')
Authorization: Bearer {HOST_TOKEN}
OData-MaxVersion: 4.0
OData-Version: 4.0
Accept: application/json

其中,BAP_SOURCE_ENV_ID = 开发环境的 BAP GUID(来自 pac env list,存储在 docs/alm/last-pipeline.json 中,或可通过 pac env who 获取)。

提取:

  • SourceDeploymentEnvironmentId — 在阶段运行中用作 devdeploymentenvironment 绑定。保存为 sourceDeploymentEnvironmentId。
  • StageRunsDetails[].DeploymentStage — 确认可用阶段及其 ID
  • EnableAIDeploymentNotes — 保存为 AI_NOTES_ENABLED(布尔值)

使用 .solution-manifest.json 中的 solutionId 作为 ARTIFACT_SOLUTION_ID,使用 uniqueName 作为 ARTIFACT_SOLUTION_NAME。

如果 RetrieveDeploymentPipelineInfo 返回 404(较旧的 Pipelines 包):使用导航属性查找源部署环境:

GET {hostEnvUrl}/api/data/v9.1/deploymentpipelines({pipelineId})/deploymentpipeline_deploymentenvironment?$select=deploymentenvironmentid,name,environmenttype

筛选 environmenttype = 200000000,以获取源记录。将 deploymentenvironmentid 用作 sourceDeploymentEnvironmentId。对于构件/解决方案列表,使用 docs/alm/last-pipeline.json 中的 sourceDeploymentEnvironmentId 和 .solution-manifest.json 中的 solutionName 作为后备值。设置标志 VALIDATE_PACKAGE_UNAVAILABLE = true,跳过阶段 4.2–4.3,并使用阶段 6 中的 PAC CLI 路径。

如果 RetrieveDeploymentPipelineInfo 返回非 404 错误(例如 400/4xx/5xx) — 已观察到:某些 Pipelines 包即使 ValidatePackageAsync 正常工作,也会对此调用返回 400 — 不要设置 VALIDATE_PACKAGE_UNAVAILABLE。上面的 404 分支专门用于缺少 OData 验证 API 的较旧包;400 仅表示此元数据调用失败,并不意味着验证 API 不存在。应改用 docs/alm/last-pipeline.json 中的 sourceDeploymentEnvironmentId(以及 .solution-manifest.json 中的 solutionName)作为后备值,并继续正常的 ValidatePackageAsync 流程(从阶段 4 开始)。如果 docs/alm/last-pipeline.json 中缺少 sourceDeploymentEnvironmentId,请使用上面 404 分支中所示的同一个 deploymentpipeline_deploymentenvironment 导航属性 GET 来恢复该值(仍然不要设置 VALIDATE_PACKAGE_UNAVAILABLE)。只有真正的 404 — 或之后 ValidatePackageAsync 返回 404(阶段 4.2)— 才会转到 PAC CLI 路径。

阶段 3.5 — 部署前完整性检查

管道的 ValidatePackageAsync 会确认解决方案 zip 可导入目标环境,但不会告知解决方案 zip 本身是否包含源站点上的所有组件。在 setup-solution 上次运行后添加的组件(服务器逻辑、云端流、机器人、环境变量等)可能会被悄悄遗漏。

针对源(开发)环境运行共享的站点清单辅助工具:

node "${PLUGIN_ROOT}/scripts/lib/discover-site-components.js" \
  --envUrl "{devEnvUrl}" --token "{DEV_TOKEN}" \
  --siteId "{websiteRecordId from .solution-manifest.json}" \
  --publisherPrefix "{publisherPrefix from .solution-manifest.json}" \
  --solutionId "{solutionId from .solution-manifest.json}" \
  --projectRoot "."

解析 stdout 并检查 missing.*。在执行任何其他操作之前,先捕获同步前状态,以便同步后重新确认的关卡能够显示发生了哪些变化:

PRE_SYNC_VERSION = solutionManifest.solution.version   // from .solution-manifest.json read in Phase 1
PRE_SYNC_MISSING = { siteComponents, siteLanguages, cloudFlows, envVarDefinitions, customTables, ... }   // from the discovery stdout above

然后:

  • 所有 missing.* 均为空 → 继续阶段 4。

🚦 关卡(进度 · deploy-pipeline:3.5.completeness): 源解决方案不完整,与线上站点不一致。先同步、仍然部署(会发布缺失内容),或取消。

  • 任一项非空 → 报告简短摘要(“解决方案缺少 {N} 个组件”)。通过 AskUserQuestion 询问:

    “源解决方案似乎不完整,与线上站点不一致。您想怎么做?

    1. 现在运行 /power-pages:setup-solution(同步模式)— 纳入缺失组件并提升版本,然后在部署前再次向您确认(推荐)
    2. 仍然部署 — 缺失组件不会到达目标环境
    3. 取消 — 我会先调查”
    • 选项 1 — 先同步,再在部署前重新确认:

      1. 调用 /power-pages:setup-solution(自动检测现有清单,进入同步模式,纳入缺失组件,并提升版本号)。等待完成。setup-solution 的最后刷新步骤会将 LAST_SYNC_AT 写入 docs/.alm-plan-data.json,因此后续调用 check-alm-plan.js 时,不会仅因同步将 solutions.modifiedon 更新至 GENERATED_AT 之后,就错误地将计划标记为过期——新鲜度参考值将变为 max(GENERATED_AT, LAST_SYNC_AT)。
      2. 重新读取 .solution-manifest.json,并记录 POST_SYNC_VERSION = solutionManifest.solution.version。
      3. 重新运行发现辅助程序。如果仍有任何 missing.* 非空,则重复上方 Phase 3.5 中的提示。
      4. 否则,将 NEWLY_ADOPTED 计算为 PRE_SYNC_MISSING 与第二次发现运行所得 missing.* 之间按类别进行的集合差异(消失的项目就是 setup-solution 刚纳入解决方案的项目)。总数 = 所有类别长度之和。

      🚦 关卡(进度 · deploy-pipeline:3.5.post-sync): 同步后重新确认。解决方案版本已提升,组件已纳入——部署继续前由用户检查差异。

      1. 继续进入 Phase 4 前与用户重新确认——即将交付的解决方案已不同于用户开始部署时看到的内容。使用 AskUserQuestion:

        "同步完成。

        {solutionUniqueName} 现在为 v{POST_SYNC_VERSION}(原为 v{PRE_SYNC_VERSION}),包含 {NEWLY_ADOPTED.total} 个新纳入的组件:

        • {按类别列出前 3-5 个名称——优先选择重要性较高的类别:云流、服务器逻辑、环境变量定义,然后是站点组件}
        • {如还有更多:+ {N} 个,分布于 {category list}}

        即将将此更新后的解决方案部署到 {SELECTED_STAGE.name}({targetEnvUrl})。

        继续部署吗?"

        问题标题选项
        继续部署吗?同步后批准是 — 将 v{POST_SYNC_VERSION} 部署到 {SELECTED_STAGE.name}(推荐),暂停 — 我想先检查新的解决方案内容,取消 — 中止部署
        • 是 → 使用同步后的解决方案继续进入 Phase 4。
        • 暂停 → 干净地退出 deploy-pipeline,并附上简短说明("同步后已暂停。准备好将 v{POST_SYNC_VERSION} 部署到 {SELECTED_STAGE.name} 时,请重新运行 /power-pages:deploy-pipeline。"),以便用户检查已同步的清单 / Dataverse 状态并手动恢复。不要写入 docs/alm/last-deploy.json——未发生部署。也跳过技能跟踪调用。
        • 取消 → 停止此技能。相同的无标记 / 无跟踪规则也适用。
    • 选项 2 — 在 docs/alm/last-deploy.json 的 knownGaps 字段下记录这一有意保留的差异,以保留审计轨迹。

    • 选项 3 — 停止。

为何需要同步后关卡:此技能负责在解决方案晋级到暂存或生产环境时把关——晋级到暂存环境的时刻是最后一个可以发现意外的时机。在部署过程中运行同步模式,会生成一个不同于用户调用此技能时所设想的解决方案版本。同步后重新确认,让用户有明确机会检查版本提升和新纳入组件列表,再将其部署到目标环境。设置 Phase 3.5 触发条件是有意为之;同步后的重新确认则是在此基础上增加的安全措施。从 plan-alm 编排中调用此技能时也适用相同原则——plan-alm 的计划批准(Phase 4)涵盖同步前状态;此关卡则涵盖部署过程中同步引入的差异。

为何一开始就需要 Phase 3.5:AGENTS.md 中默认遵循 ALM 感知规则,要求在解决方案离开其源环境的每个关卡执行完整性检查。

Phase 3.6 — 并行验证批次(仅限 MULTI_RUN_MODE)

当 MULTI_RUN_MODE = false 时,跳过整个阶段(单解决方案模式,或旧版 MULTI_PIPELINE_MODE v2)。单解决方案模式和 v2 都会在下方的阶段 4 中创建并验证且仅验证一个阶段运行,因此无需并行处理。继续执行阶段 4。

此阶段通过并发运行全部 N 个解决方案,压缩每个解决方案的 create-stage-run → ValidatePackageAsync → poll-validation-status 链路。ValidatePackageAsync 不会获取环境级导入锁(该锁会使 DeployPackageAsync 按顺序执行),而是对解决方案包和每个阶段运行状态进行结构检查,因此平台可以顺利并行处理 N 项验证。对于典型的 5 个解决方案拆分(每项验证需要 60–180 秒),此举可将验证阶段从 N × ~120s(≈10 分钟)缩短到大致等于最慢的一项验证所需的时间(≈3 分钟)。

部署阶段(6.1 / 6.2)仍严格按顺序执行 — 参见阶段 2 中的 Design rationale 提示框。

3.6.1 构建输入文件。

筛选 DEPLOYMENT_ORDER,只保留需要验证的条目:

  • 包含:status !== "SkippedEmpty" 且 isFutureBuffer !== true 的条目。
  • 排除:SkippedEmpty 和 isFutureBuffer: true 条目 — Future 缓冲区是预留的 0 组件占位符,无需验证。

如果某个条目尚未包含 solutionId,则从 .solution-manifest.json(schemaVersion: 2 的 solutions[])中解析该条目的 solutionId。写入临时文件:

node -e "require('fs').writeFileSync('./docs/alm/.validation-batch.json', JSON.stringify({{VALIDATION_SPECS}}))"

其中,{{VALIDATION_SPECS}} 是数组 [{ solutionUniqueName, solutionId }, …]。

3.6.2 运行批量验证器。

node "${PLUGIN_ROOT}/scripts/lib/validate-stage-runs-batch.js" \
  --hostEnvUrl "{hostEnvUrl}" \
  --token "{HOST_TOKEN}" \
  --pipelineId "{pipelineId}" \
  --stageId "{SELECTED_STAGE.stageId}" \
  --sourceDeploymentEnvironmentId "{sourceDeploymentEnvironmentId}" \
  --solutionsFile ./docs/alm/.validation-batch.json

将标准输出捕获为 JSON:const batch = JSON.parse(output)。删除临时文件(./docs/alm/.validation-batch.json)— 该文件是临时的。根据 batch.results 构建 VALIDATED_STAGE_RUNS = { [solutionUniqueName]: { stageRunId, validationResults } }。

如果传播了 VALIDATE_PACKAGE_UNAVAILABLE(任一解决方案的 error 与 ValidatePackageAsync not available on this Pipelines package 匹配):全局设置 VALIDATE_PACKAGE_UNAVAILABLE = true,跳过阶段 3.6 的其余部分(已创建的阶段运行会保留 — 它们是无害的、已验证但尚未部署的记录),并以单解决方案回退形式继续阶段 4,该形式会在阶段 6 中将每次迭代导向 pac pipeline deploy CLI 回退路径。这与较旧版本 Pipelines 包采用的代码路径相同。

3.6.3 根据批量操作结果分支。

batch.allPassedbatch.pendingApprovalbatch.failed + batch.timedOut行为
true00所有验证均已通过。输出一行:“已并行验证 {N} 个解决方案 — 全部通过。” 继续执行阶段 4。
false> 00一个或多个验证正在等待审批。参见下方的 3.6.4(待审批批量处理)。
false—> 0一个或多个验证失败或超时。参见下方的 3.6.5(批量验证失败时暂停)。

3.6.4 待审批批量处理。

🚦 关卡(暂停 · deploy-pipeline:3.6.batch-pending-approval): 外部等待 — 一个或多个解决方案在并行验证期间遇到 stagerunstatus=200000005(待审批)。用户在 PPAC 中批准全部解决方案后,我们会重新轮询。取消操作会使主机上保留 N 个已验证但待审批的阶段运行(用户可以稍后批准并重新调用,也可以在 PPAC 中取消这些运行)。每批触发一次 — 而非针对每个待审批的解决方案分别触发。

在一条消息中列出受影响的解决方案(而非逐个解决方案显示),然后暂停。使用 AskUserQuestion:

{batch.total} 个解决方案中有 {batch.pendingApproval} 个的验证正在等待批准,批准后才能完成: {bulleted list of result.solutionUniqueName where status === 'PendingApproval'}

在 Power Platform 中批准所有这些解决方案:make.powerapps.com → Solutions → Pipelines → 上面列出的每个阶段运行 → Approve。然后返回此处。

其他 {batch.succeeded} 个解决方案已成功通过验证 — 批准后它们将部署。"

问题标题选项
已完成批准?批次验证批准是 — 我已全部批准;重新轮询,否 — 取消部署
  • 是:以 --rePoll 模式重新运行 validate-stage-runs-batch.js。该辅助工具接受现有的阶段运行 ID,并跳过 create-stage-run + ValidatePackageAsync 调用 — 它只会针对每个 stageRunId 运行轮询和探测模式。用户批准后,阶段运行会从 200000005 (PendingApproval) → 200000006 (Validating) → 200000007 (ValidationSucceeded) 迁移;辅助工具的探测可以正确区分“仍在等待批准”(用户过早点击了“是”)和“真正超时”,因此 Agent 无需判断通用的超时错误。

    创建一个 tmp 文件,只包含此前处于等待状态的条目(从原始批次结果中保留 stageRunId):

    node -e "require('fs').writeFileSync('./docs/alm/.repoll-batch.json', JSON.stringify({{PENDING_SPECS_WITH_STAGERUNIDS}}))"
    node "${PLUGIN_ROOT}/scripts/lib/validate-stage-runs-batch.js" \
      --hostEnvUrl "{hostEnvUrl}" \
      --token "{HOST_TOKEN}" \
      --rePoll \
      --solutionsFile ./docs/alm/.repoll-batch.json
    

    其中 {{PENDING_SPECS_WITH_STAGERUNIDS}} 是数组 [{ solutionUniqueName, solutionId, stageRunId }, …],筛选自原始批次中 status === 'PendingApproval' 的条目。

    将 stdout 捕获为 JSON;删除 tmp 文件(./docs/alm/.repoll-batch.json)。按 solutionUniqueName 将更新后的结果合并到 VALIDATED_STAGE_RUNS 中。然后根据重新轮询批次的统计结果分支:

    • 全部成功 → 使用完整的已验证阶段运行集继续进入阶段 4。
    • 一个或多个仍为 PendingApproval → 再次触发相同的关卡(批准尚未生效;用户会收到新的“在 PPAC 中批准,然后重新轮询”提示)。循环,直到全部获批或用户取消。
    • 一个或多个为 Failed / Timeout / Error → 转到 3.6.5(视为批次验证失败)。
  • 否:干净地停止。已验证的阶段运行和等待中的阶段运行仍保留在主机上;重新调用此技能会从这里继续(用户可以批准后重新运行)。

3.6.5 批次验证失败时停止。

🚦 关卡(plan · deploy-pipeline:3.6.batch-validation-failed): 一个或多个解决方案在并行批次中未通过验证。显示失败条目各自的 validationResults,并让用户决定是中止整个部署,还是仅继续部署成功的解决方案(高级操作 — 会在目标环境中留下已知的依赖缺口)。取消会使 N 个已验证的阶段运行留在主机上;用户可以在 PPAC 中清理它们,或修复源后重新调用。

显示失败条目及其 validationResults(这是 Dataverse validationresults 字段中的双重编码 JSON 字符串 — 显示时使用两次 JSON.parse 提取 SolutionValidationResults[].Message)。使用 AskUserQuestion:

{batch.failed + batch.timedOut} 个解决方案(共 {batch.total} 个)未通过并行验证:

{for each failing result: a short block with solutionUniqueName, status, top Message from validationResults}

其他 {batch.succeeded} 个解决方案已通过验证,并且已准备好部署的阶段运行。仅部署成功的子集可能会在目标环境中留下依赖缺口 — 例如,在没有前置依赖 _Foundation 的情况下发布 _Content 会导致网站行为异常。建议:中止、修复源,然后重新调用。

你想如何处理?"

问题标题选项
下一步?批次验证失败中止整个部署(推荐),仅部署成功的子集(高级 — 接受依赖缺口),取消并调查
  • 中止 / 取消:干净地停止。写入最简的 docs/alm/last-deploy.json,为每个失败的解决方案设置 status: "ValidationFailed",其余解决方案设置 status: "NotAttempted",以便下一次调用可以了解发生了什么。不要调用 DeployPackageAsync。
  • 仅部署成功的子集:设置 DEPLOY_SUBSET_ACK = true,并将 DEPLOYMENT_ORDER 筛选为 VALIDATED_STAGE_RUNS 中 status === 'Succeeded' 的条目。将跳过的条目记录到 docs/alm/last-deploy.json 的 knownGaps 中。继续执行第 4 阶段。绝不要将此选项作为默认选项——这很容易出错,推荐的做法始终是中止。

3.6.6 持久化批次摘要。

将 batchValidation 对象追加到内存状态中,该状态将在第 7 阶段写入 docs/alm/last-deploy.json。大部分字段直接取自 validate-stage-runs-batch.js 返回的 batch JSON:

{
  "totalSolutions": <batch.total>,
  "succeeded": <batch.succeeded>,
  "failed": <batch.failed>,
  "pendingApproval": <batch.pendingApproval>,
  "timedOut": <batch.timedOut>,
  "elapsedSeconds": <batch.elapsedSeconds>,
  "perSolutionStageRunIds": { "<solutionUniqueName>": "<stageRunId>", ... }
}

通过归并 batch.results 来构建 perSolutionStageRunIds,生成 { [r.solutionUniqueName]: r.stageRunId },涵盖 stageRunId 非 null 的所有条目(包括验证失败但已创建阶段运行的条目——这会保留指向 PPAC 的深层链接,便于调试)。elapsedSeconds 直接取自辅助脚本(它会在内部测量扇出操作的实际耗时,无需为 bash 调用套用计时器)。

这样,下一次调用(以及任何审计使用方)就能获得与串行部署结果不同的并行验证结果记录。refresh-alm-plan-data.js 的 deploy-pipeline 阶段会将此区块导入 planData.pipelineMeta.lastDeploy.batchValidation,以便呈现的 ALM 计划可以显示并行验证耗时。

第 4 阶段 — 创建阶段运行并验证包

在 MULTI_RUN_MODE 中,第 3.6 阶段已为当前迭代的解决方案创建阶段运行并执行验证,同时并行处理其他解决方案。 在每次迭代的循环中,完全跳过步骤 4.1–4.3:从 VALIDATED_STAGE_RUNS[solutionUniqueName].stageRunId 获取 STAGE_RUN_ID,并从同一记录获取 validationResults。直接跳转到步骤 4.4(获取部署说明),然后进入第 5 阶段。

在单解决方案模式和旧版 MULTI_PIPELINE_MODE(v2)中,按下文说明在线执行步骤 4.1–4.4。

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

4.1 创建阶段运行,使用 create-stage-run.js:

node "${PLUGIN_ROOT}/scripts/lib/create-stage-run.js" \
  --hostEnvUrl "{hostEnvUrl}" \
  --token "{HOST_TOKEN}" \
  --pipelineId "{pipelineId}" \
  --stageId "{SELECTED_STAGE.stageId}" \
  --sourceDeploymentEnvironmentId "{sourceDeploymentEnvironmentId}" \
  --solutionId "{ARTIFACT_SOLUTION_ID}" \
  --artifactName "{ARTIFACT_SOLUTION_NAME}"

将标准输出捕获为 JSON:const result = JSON.parse(output)。提取 result.stageRunId 并存储为 STAGE_RUN_ID。

如果脚本以非零状态码退出,则报告错误——可能是管道配置问题(400)或冲突(409)。两者的错误消息都包含 Dataverse 错误正文。

关于字段绑定的说明:该脚本使用 v9.2 API 和带 msdyn_ 前缀的导航属性([email protected]、[email protected]、[email protected])。这些是通过 HAR 确认的当前 Pipelines 包所用名称。较旧的包版本使用了不同字段名称(例如 deploymentstageid);该脚本可处理 201(JSON 正文)和 204(OData-EntityId 标头)两种响应码。

存储为 STAGE_RUN_ID。

4.2 触发包验证(返回 204,而不是 200):

POST {hostEnvUrl}/api/data/v9.0/ValidatePackageAsync
Content-Type: application/json
Authorization: Bearer {HOST_TOKEN}

{"StageRunId": "{STAGE_RUN_ID}"}

将 HTTP 204 视为成功。

如果 ValidatePackageAsync 返回 404:此 Pipelines 包版本不支持直接 OData 验证 API。设置 VALIDATE_PACKAGE_UNAVAILABLE = true。跳过第 4.2–4.3 阶段,直接进入第 5 阶段(部署设置),然后在第 6 阶段使用 pac pipeline deploy CLI 回退方案。

4.3 轮询验证状态,使用 poll-validation-status.js:

node "${PLUGIN_ROOT}/scripts/lib/poll-validation-status.js" \
  --hostEnvUrl "{hostEnvUrl}" \
  --token "{HOST_TOKEN}" \
  --stageRunId "{STAGE_RUN_ID}" \
  --intervalMs 5000 \
  --maxAttempts 36

将标准输出捕获为 JSON:const result = JSON.parse(output)。如果退出状态码非零,错误消息将包含验证失败详情或超时消息。 脚本会轮询阶段运行记录上的 msdyn_operation。只要其值为 200000201,验证就仍在进行;值发生变化后,脚本会检查 msdyn_stagerunstatus——如果为 200000003(失败),则抛出包含 msdyn_validationresults 的错误;否则返回 { stageRunId, validationResults, stageRunStatus }。

验证结束状态:

  • stageRunStatus 200000007(验证成功)→ 继续执行第 5 阶段
  • 200000003(失败)时脚本会抛出错误——停止,并显示错误消息中的 validationresults
  • 超时时脚本会抛出错误——停止并显示错误消息

重要:validationresults 是一个双重编码的 JSON 字符串——对其调用两次 JSON.parse() 即可获取对象。对象的结构为:{ ValidationStatus, SolutionValidationResults: [{ SolutionValidationResultType, Message, ErrorCode }], SolutionDetails, MissingDependencies }。

将所有 SolutionValidationResults 条目作为警告显示给用户。请特别留意:

  • ErrorCode: -2147188672 — 托管/非托管冲突:“The solution is already installed as unmanaged but this package is managed.” 用户必须先从目标环境中卸载现有解决方案,然后重试。
  • 缺少连接引用或环境变量

🚦 关卡(暂停 · deploy-pipeline:4.pending-approval): 外部等待 — PP Pipelines stagerunstatus=200000005(待审批)。用户必须在 PPAC 中批准。取消会使运行保持在主机上的待处理状态。在 MULTI_RUN_MODE / MULTI_PIPELINE_MODE 中,每次循环迭代遇到待审批状态时都会触发此关卡,而不是每次技能调用只触发一次。

如果 stageRunStatus = 200000005(待审批):告知用户需要在 Power Platform 中批准(make.powerapps.com → Solutions → Pipelines → 找到此次运行 → Approve)。通过 AskUserQuestion 询问:“验证已批准吗?1. 是,继续 / 2. 否,取消”

4.4 获取 AI 生成的部署说明(如果 AI_NOTES_ENABLED = true):

GET {hostEnvUrl}/api/data/v9.0/deploymentstageruns({STAGE_RUN_ID})?$select=aigenerateddeploymentnotes,deploymentstagerunid
Authorization: Bearer {HOST_TOKEN}

将 aigenerateddeploymentnotes 保存为 AI_DEPLOY_NOTES。

第 5 阶段 — 配置部署设置

5.0a 检查 deployment-settings.json:

检查项目根目录中是否存在 deployment-settings.json:

  • 如果存在:读取该文件,并显示已配置阶段的摘要和环境变量数量。说明:“找到现有的 deployment-settings.json,其中配置了 {N} 个阶段。”(将顶层键计为阶段名称;统计每个阶段中的 EnvironmentVariables 条目数。)

  • 如果不存在,且解决方案清单中有环境变量定义(如果可用,则来自 solutionManifest.envVars[];否则来自 5.1 中的查询):生成模板文件并告知用户。模板结构:

    {
      "{stageName}": {
        "EnvironmentVariables": [
          { "SchemaName": "{envVarSchemaName}", "Value": "" }
        ],
        "ConnectionReferences": []
      }
    }
    

    使用 SELECTED_STAGE.name 中的阶段名称,以及 .solution-manifest.json 中的环境变量架构名称(如果可用),或使用 5.1 查询中的名称。在项目根目录下写入 deployment-settings.json。说明:“已生成 deployment-settings.json 模板。请在部署前填写数值,或在提示时提供数值。”

    注意:如果此时(5.0a 在 5.1 之前运行)尚未确定环境变量,则在 5.1 完成并发现环境变量后生成模板文件,然后在继续 5.1 中的提示之前告知用户。

  • 如果不存在,且解决方案中没有环境变量:说明“无需覆盖环境变量”并跳过。

5.0b 显示文件路径:

始终显示已解析的路径 {projectRoot}/deployment-settings.json,让用户知道文件位置,无论文件是刚创建的还是已存在。

5.1 在解决方案中发现环境变量定义并解析各阶段的值:

在源环境中查询解决方案组件,查找所有环境变量定义(componenttype 380):

GET {sourceEnvUrl}/api/data/v9.2/solutioncomponents?$filter=_solutionid_value eq '{solutionId}' and componenttype eq 380&$select=objectid
Authorization: Bearer {SOURCE_TOKEN}

针对每个 objectid,获取架构名称:

GET {sourceEnvUrl}/api/data/v9.2/environmentvariabledefinitions({objectid})?$select=schemaname,displayname,type,defaultvalue

这将得到 SOLUTION_ENV_VARS——即将传输到目标环境的环境变量列表。 读取 deployment-settings.json(如果项目根目录中存在),并查找所选阶段名称以获取预配置值:

const stageSettings = deploymentSettings?.stages?.[selectedStageName] || {};
const preconfigured = stageSettings.EnvironmentVariables || []; // [{ SchemaName, Value }]

识别未配置的环境变量——即 SOLUTION_ENV_VARS 中没有对应 preconfigured 条目的变量:

const unconfigured = SOLUTION_ENV_VARS.filter(v =>
  !preconfigured.find(p => p.SchemaName === v.schemaname)
);

🚦 关卡(plan · deploy-pipeline:5.env-vars): 每个阶段未配置的环境变量——由用户提供值或跳过(使用默认值)。如果没有提供值,运行时会读取默认值,该值可能仅适用于开发环境。在 MULTI_RUN_MODE / MULTI_PIPELINE_MODE 中,每次循环迭代遇到未配置的环境变量时都会触发此关卡——为第 1 次迭代提供的值不会延续到第 2 次迭代。

如果存在未配置的环境变量,通过 AskUserQuestion 向用户展示:

“此解决方案有 {N} 个环境变量尚未为 {stageName} 配置值。请为每个变量输入值(留空以使用默认值,或在不适用时跳过):

  1. {schemaname}({displayname})——默认值:{defaultvalue ?? 'none'}
  2. ...”

收集回复并与 preconfigured 合并,形成最终的 ENV_VAR_OVERRIDES 数组。询问是否要将这些值保存回 deployment-settings.json,以供后续运行使用:

“是否将这些值保存到 deployment-settings.json,以便后续部署到 {stageName} 时使用?

  1. 是——保存以供下次使用
  2. 否——仅使用一次”

如果选择“是”:将收集到的值写入或更新 deployment-settings.json 中的 stages.{stageName}.EnvironmentVariables。

如果所有环境变量都已预配置(或没有环境变量):跳过提示,直接使用 preconfigured。

5.1b 对 deployment-settings.json 中的 Secret 引用进行 PATCH 前验证。

为何需要此关卡。 Power Platform Pipelines 处理程序会在导入时验证 deploymentsettingsjson PATCH,此时阶段运行已排队,而且在串行导入队列中可能已经等待了很久。错误的 Secret 引用值——例如 @KeyVault(vaultName=...;secretName=...) 这类占位符语法、提交到文件中的原始 Secret 值、格式错误的 URI——会导致导入失败,并报错 “ImportAsHolding failed: The value provided as a secret reference does not match a valid secret reference format.”。近期会话中的实际证据:排队等待 4 小时 41 分钟后失败。在这里捕获错误引用,可将数小时的计算资源浪费转变为不到一秒的明确中止,并提供精确的修复指引。

如果 ENV_VAR_OVERRIDES 为空且 deployment-settings.json 不存在,则完全跳过此步骤——无需验证任何内容。

否则,运行共享帮助程序:

node "${PLUGIN_ROOT}/scripts/lib/validate-deployment-settings.js" \
  --settingsFile "./deployment-settings.json" \
  --envUrl "{sourceEnvUrl}" \
  --stageLabel "{SELECTED_STAGE.name}"

将标准输出捕获为 JSON。帮助程序根据 valueFormat(kv-uri / kv-resource-id / kv-placeholder / empty / plain-text / invalid-uri)和 status(valid / invalid / unknown-type / skipped)对每个 EnvironmentVariables[] 条目进行分类。提供 --envUrl 时(如上所示),会根据规范的 Key Vault 引用格式验证 Secret 类型的条目;其他类型则进行结构验证。

根据 summary 分支处理:

  • summary.invalid === 0 → 所有条目均有效,继续执行 Phase 5.2。

  • summary.invalid > 0 → 停止。向用户显示无效的 findings[],并列出每个条目的 schemaName、valueFormat、value 和 message。然后显示修复说明:

    “部署前验证发现 deployment-settings.json 中有 {summary.invalid} 个环境变量值无效。Power Platform Pipelines 处理程序会在导入期间拒绝这些值(可能是在队列中等待很久之后)。请在重新运行此技能之前修复该文件。

    Secret 环境变量的规范格式:

    1. Key Vault Secret 标识符 URI:https://<vault>.vault.azure.net/secrets/<name>[/<version>]
    2. Azure 资源 ID:/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>/secrets/<name>
    3. 留空(使用环境变量定义中的默认值)

    有关端到端流程(选择保管库 → 存储 Secret → 传递 URI),请参阅 configure-env-variables Phase 3.B 和 add-server-logic Phase 7.2a。” 不要提示用户“仍要继续”——不存在通过强行执行错误的 PATCH 就能成功导入的部分有效情况。部署会失败;变化的只有等待时间。

  • summary.invalid === 0 && summary.['unknown-type'] > 0 → 记录一行警告,说明有些架构的类型无法查询(可能是身份验证错误或暂时性错误),然后继续。下游处理程序仍会拒绝明显无效的内容;PATCH 前检查是快速通道,而非唯一的防线。

5.2 PATCH 阶段:运行时附带构件版本、部署说明和环境变量(始终运行):

首先,确定源(开发)环境中当前的解决方案版本——必须完全匹配:

GET {sourceEnvUrl}/api/data/v9.0/solutions?$filter=uniquename eq '{SOLUTION_NAME}'&$select=version
Authorization: Bearer {SOURCE_TOKEN}

将返回的 version 用作 artifactdevcurrentversion。不要使用 .solution-manifest.json 中的版本——它可能已过时。

对于 artifactversion,将源版本的修订号加 1(例如,1.0.0.2 → 1.0.0.3)。该版本必须严格高于目标阶段中已部署的版本。如果要多次部署到同一阶段,请检查 docs/alm/last-deploy.json 中上次使用的 artifactVersion,并使用更高的值。

然后执行 PATCH(仅当 ENV_VAR_OVERRIDES 非空时才包含 deploymentsettingsjson):

PATCH {hostEnvUrl}/api/data/v9.0/deploymentstageruns({STAGE_RUN_ID})
Content-Type: application/json
Authorization: Bearer {HOST_TOKEN}

{
  "artifactdevcurrentversion": "{current version from source env — must match exactly}",
  "artifactversion": "{new version — must be > current version in target stage}",
  "deploymentnotes": "{AI_DEPLOY_NOTES if available, otherwise a brief description of what is being deployed}",
  "deploymentsettingsjson": "{JSON.stringify({ EnvironmentVariables: ENV_VAR_OVERRIDES, ConnectionReferences: [] })}"
}

deploymentsettingsjson 的值必须是经过 JSON 编码的字符串(双重序列化):

const deploymentsettingsjson = JSON.stringify({
  EnvironmentVariables: ENV_VAR_OVERRIDES,
  ConnectionReferences: stageSettings.ConnectionReferences || [],
});

如果 ENV_VAR_OVERRIDES 为空且没有连接引用,则完全省略 deploymentsettingsjson。

响应为 HTTP 204。如果 PATCH 因版本冲突错误而失败,请检查两个版本值后重试。

注意——deploymentsettingsjson 并不总会在部署后于目标环境写入 environmentvariablevalues 记录。 实际观察到:Power Pages 站点的环境变量定义若尚未关联到 mspp_sitesetting 记录,即使 deploymentsettingsjson 已填充且部署成功,目标环境的 environmentvariablevalues 表中这些条目仍可能为空。Power Platform Pipelines 处理程序似乎只会为已有站点设置绑定(或平台认可的其他使用方)的定义写入值。这可能是平台缺陷,也可能是有意设计,等待后续单独通过 Power Pages Management UI 执行操作。如果你交付 Secret 环境变量,或任何用户预期在目标环境运行 setup-auth/configure-env-variables 之前就已写入的其他类型,请在下方第 7 阶段确认部署完成后目标环境中存在 environmentvariablevalues 记录;如果不存在,请明确提示用户。解决方法:部署后在目标环境调用 configure-env-variables(或针对每个定义运行 link-site-setting-to-env-var.js),以显式创建这些值。如果这成为反复出现的阻塞问题,请在下一次 PR 中跟进。

阶段 6 — 部署并监控

🚦 关卡(最终 · deploy-pipeline:6.0.final-consent): 每次调用 DeployPackageAsync / pac pipeline deploy 之前,进行最后确认。验证已通过。导入不可事务回滚——部分失败会使已导入的内容保留在目标环境中。

⚠ 在 MULTI_RUN_MODE / MULTI_PIPELINE_MODE 下,每次循环迭代都会触发。 deploymentOrder 中有三个解决方案 → 显示三次阶段 6.0 提示(每次迭代调用 DeployPackageAsync 之前各一次)。上游阶段 2 的阶段选择——无论通过交互式 AskUserQuestion 还是 --stage 参数——都只涵盖阶段选择,不涵盖单个部署的授权。绝不能批量执行部署循环。 跳过每次迭代的提示,直接从阶段 5 进入阶段 6.1 来部署第 2 至 N 个解决方案,正是此关卡旨在防止的违规操作。 6.0 最终部署确认关卡。 启动每次部署之前——无论是通过 DeployPackageAsync(6.1)还是通过 pac pipeline deploy PAC CLI 后备方案,也无论这是 MULTI_RUN_MODE 中的第一个解决方案还是第 N 个——都必须明确向用户确认。较早的关卡(Phase 2 阶段选择、Phase 2.5 解除阻塞、Phase 3.5 完整性确认、Phase 5 环境变量值)各自只涵盖其对应的决策,但它们都不是针对每次迭代询问“准备好部署这个了吗?”的提示,而且部署本身不是事务性的——部分失败会使已导入的内容留在目标环境中。此关卡确保每次生产环境升级都经过明确确认。使用 AskUserQuestion:

"准备将 {ARTIFACT_SOLUTION_NAME}(v{newVersion})部署到 {SELECTED_STAGE.name}({targetEnvUrl})吗?

根据解决方案的大小,此操作约需 3–60 分钟。导入不是事务性的——如果中途失败,已导入的内容会留在目标环境中,需要手动清理(或使用更高的工件版本重新部署)才能恢复。无论结果如何,都会写入 docs/alm/last-deploy.json。

  1. 立即部署 — POST DeployPackageAsync(或在 CLI 后备方案中运行 pac pipeline deploy)并轮询,直到状态终止
  2. 取消 — 保持阶段运行当前状态(已验证但未部署);准备好后,重新运行 /power-pages:deploy-pipeline"

根据回答分支处理:

  • 立即部署 → 继续执行下方的 6.1(或 PAC CLI 后备方案框)。
  • 取消 → 正常停止。不要调用 DeployPackageAsync 或 pac pipeline deploy。告知用户:"已取消。阶段运行 {STAGE_RUN_ID} 已通过验证,但尚未部署。重新调用 /power-pages:deploy-pipeline 以继续。如果重新调用前源解决方案版本发生变化,则部署前需要对阶段运行的 artifactversion 字段执行新的 PATCH(Phase 5.2)——重新运行此技能会自动处理。" 取消时不要写入 docs/alm/last-deploy.json——没有部署结果可记录。

对于 Production 目标,请将此关卡视为此技能中最重要的提示——相比导入到错误阶段,取消的代价(阶段运行已通过验证)很小。

如果 ValidatePackageAsync 不可用(VALIDATE_PACKAGE_UNAVAILABLE = true):使用 PAC CLI 作为主要部署机制,而不是执行 6.1。上方相同的 Phase 6.0 确认关卡也适用于此路径——用户回答“立即部署”之前,不要运行 pac pipeline deploy:

询问用户 currentVersion(如果 .solution-manifest.json 中有 solution.version,则预先填入该值)和 newVersion(建议递增补丁版本号,例如 1.0.0.0 → 1.0.0.1)。

pac pipeline deploy \
  --environment "{devEnvUrl}" \
  --solutionName "{ARTIFACT_SOLUTION_NAME}" \
  --stageId "{SELECTED_STAGE.stageId}" \
  --currentVersion "{currentSolutionVersion}" \
  --newVersion "{newVersion}" \
  --wait

如果 CLI 返回 "Resource not found for the segment 'deploymentenvironments'":说明开发环境未连接到 Pipelines 主机。建议用户在 Power Platform Admin Center(Environments → select env → Pipelines)中配置主机,然后重试。

如果 CLI 成功:解析输出中的阶段运行状态,写入 docs/alm/last-deploy.json,其中 status: "Succeeded"(或解析得到的状态),并跳过 6.1–6.2 中的 DeployPackageAsync 调用和轮询。

6.1 触发部署(如果 VALIDATE_PACKAGE_UNAVAILABLE = true,则跳过——使用上方的 PAC CLI 路径)。仅当 Phase 6.0 确认关卡返回“立即部署”后才执行:

POST {hostEnvUrl}/api/data/v9.0/DeployPackageAsync
Content-Type: application/json
Authorization: Bearer {HOST_TOKEN}

{"StageRunId": "{STAGE_RUN_ID}"}

注意:在较旧版本的 Pipelines 包中,DeployPackageAsync 也会返回 404。如果出现这种情况,请使用上方的 pac pipeline deploy CLI 路径。

6.2 使用 poll-deployment-status.js 轮询 stagerunstatus,直到状态终止:

node "${PLUGIN_ROOT}/scripts/lib/poll-deployment-status.js" \
  --hostEnvUrl "{hostEnvUrl}" \
  --token "{HOST_TOKEN}" \
  --stageRunId "{STAGE_RUN_ID}" \
  --intervalMs 8000 \
  --maxAttempts 75

将 stdout 捕获为 JSON:const result = JSON.parse(output)。 脚本会轮询 msdyn_stagerunstatus,直到出现终止状态:

  • result.status === 'Succeeded' → 继续进入阶段 7
  • result.status === 'Awaiting' → 审批关卡(见下文);不要将其视为错误
  • 状态为 200000003(Failed)或 200000004(Canceled)时,脚本会抛出错误 — 错误消息中包含 msdyn_errordetails
  • 超时约 10 分钟后,脚本会抛出错误

suboperation 字段(脚本不会轮询此字段,但在 Power Platform 中可见)会显示详细进度:

  • 200000100 = None(开始/完成)
  • 200000105 = Deploying Artifact(正在安装解决方案)

🚦 关卡(暂停 · deploy-pipeline:6.pending-approval): 外部等待 — PP Pipelines 在部署过程中出现 stagerunstatus=200000005。用户在 PPAC 中批准。在此处取消会对运行记录 PATCH iscanceled: true。在 MULTI_RUN_MODE / MULTI_PIPELINE_MODE 下,每次循环迭代遇到 Pending Approval 时都会触发此关卡。

审批关卡处理:如果 result.status === 'Awaiting'(msdyn_stagerunstatus = 200000005):

  • 告知用户:“此部署正在等待审批。请在 Power Platform 中批准:make.powerapps.com → Solutions → Pipelines → 找到 {STAGE_RUN_ID} 对应的部署 → Approve。”
  • 通过 AskUserQuestion 询问:“您是否已批准此部署?1. 是,我已批准 — 继续轮询 / 2. 取消部署”
  • 如果选择“是”:重新运行 poll-deployment-status.js 以继续轮询。
  • 如果选择“取消”:PATCH 阶段运行记录以取消它:
    PATCH {hostEnvUrl}/api/data/v9.0/deploymentstageruns({STAGE_RUN_ID})
    {"iscanceled": true}
    
    然后将状态记录为“Canceled”。

令牌刷新:每经过 10 个轮询周期(约 80 秒),通过 az account get-access-token 刷新 HOST_TOKEN,并在新的 poll-deployment-status.js 调用中传入更新后的令牌,同时降低 --maxAttempts。

轮询期间报告部署进度更新。

阶段 7 — 写入部署记录和摘要

7.1 确定最终状态字符串:

  • 200000002 → "Succeeded"
  • 200000003 → "Failed"
  • 200000004 → "Canceled"
  • 200000005 → "PendingApproval"(如果用户取消等待)
  • 轮询超时 → "Unknown"

7.2 部署后警告(仅当部署成功时):

使用阶段 1 中从 detect-project-context.js 获取的 solutionManifest,检查目标环境中是否有需要手动跟进的组件。

连接引用警告 — 如果 solutionManifest.cloudFlows 存在且非空:

⚠️ 可能需要绑定连接引用 此解决方案包含云端流程。如果这些流程使用连接引用(例如 Dataverse、SharePoint),则必须在目标环境中将其绑定到有效连接,否则这些流程将保持禁用状态。

绑定方法:Power Automate → 目标环境 → 每个流程 → Edit → 绑定连接。

如果 solutionManifest.cloudFlows 不存在或为空,则完全跳过此警告。

Bot 重新发布警告 — 如果 solutionManifest.botComponents 存在且非空:

⚠️ 需要重新发布 Bot 此解决方案包含 Copilot Studio Bot。部署后,必须在目标环境中重新发布 Bot 才能完成预配。

重新发布方法:Power Pages Management → 目标环境 → Edit site → Copilot → 重新发布。

如果 solutionManifest.botComponents 不存在或为空,则完全跳过此警告。

这些警告仅供参考 — 不要因此阻止生成摘要,也不要使用 AskUserQuestion。

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

{
  "pipelineId": "{pipelineId}",
  "pipelineName": "{pipelineName}",
  "stageId": "{SELECTED_STAGE.stageId}",
  "stageRunId": "{STAGE_RUN_ID}",
  "stageName": "{SELECTED_STAGE.name}",
  "solutionName": "{ARTIFACT_SOLUTION_NAME}",
  "solutionId": "{ARTIFACT_SOLUTION_ID}",
  "status": "{final status string}",
  "deployedAt": "{ISO timestamp}",
  "hostEnvUrl": "{hostEnvUrl}",
  "targetEnvironmentUrl": "{SELECTED_STAGE.targetEnvironmentUrl}",
  "artifactVersion": "{artifactVersion from Phase 5.2 PATCH}",
  "deployHistoryFile": "docs/deploy-history/{YYYY-MM-DD}-{stageName}-{artifactVersion}.html",
  "activationStatus": null,
  "siteUrl": null
}

activationStatus 和 siteUrl 初始值为 null,待激活结果明确后,在阶段 7.7 结束时进行补丁更新。

其中 {YYYY-MM-DD} 是 deployedAt 的日期部分,{stageName} 是将空格替换为连字符并转为小写后的阶段名称,例如 2026-04-06-staging-1.0.0.3.md。

重试会作为同级条目写入 runs[],而不是嵌套在 runs[i].lastAttempt 下。 在 MULTI_RUN_MODE(多解决方案)中,标记包含一个 runs[] 数组——每个解决方案的每次尝试各占一个条目。当用户重试失败的解决方案时(例如修正环境变量值后,在同一阶段重新运行 deploy-pipeline),重试会作为一个新的同级条目写入 runs[],并带有自己的 artifactVersion 和 attemptedAt。不要将重试嵌套为原始失败条目下的 runs[i].lastAttempt: {...}——这会混淆架构,使审计遍历变得模糊(1.0.0.4 是已部署的版本,还是仅仅是最新一次尝试?)。扁平的同级数组结构让所有使用方(refresh-alm-plan-data、渲染后的 Pipelines 标签页、部署历史 HTML)只需遍历一次 runs[],即可看到完整时间线。

此技能中的标记写入器应追加条目,而不是修改条目。每次重试失败的解决方案时,都要在 runs[] 中写入一个新条目,状态为 status='Succeeded'(持续失败时为 'Failed'),并填写递增后的 artifactVersion。原始失败条目应保持不变,作为历史记录。

7.4 写入部署历史条目(HTML):

计算历史文件名:{YYYY-MM-DD}-{stageName}-{artifactVersion}.html(与 docs/alm/last-deploy.json 的 deployHistoryFile 字段采用相同的生成方式——将空格替换为连字符,并将阶段名称转换为小写)。

如果 docs/deploy-history/ 不存在,则创建它:

mkdir -p docs/deploy-history

读取模板 ${PLUGIN_ROOT}/skills/deploy-pipeline/assets/deploy-history-template.html,并替换以下 __PLACEHOLDER__ 标记:

Overview 标签页:

占位符值
__SOLUTION_FRIENDLY_NAME__解决方案友好名称(来自 .solution-manifest.json),或 {solutionUniqueName}
__SOLUTION_NAME__{ARTIFACT_SOLUTION_NAME}
__STAGE_NAME__{SELECTED_STAGE.name}
__TARGET_ENV_URL__{SELECTED_STAGE.targetEnvironmentUrl}
__STAGE_RUN_ID__{STAGE_RUN_ID}
__PIPELINE_NAME__{pipelineName}
__DEPLOYED_AT__{deployedAt ISO string}
__ARTIFACT_VERSION__{来自 Phase 5.2 的 artifactVersion}
__PREV_ARTIFACT_VERSION__{artifactDevCurrentVersion} — 本次部署前开发环境中的版本
__STATUS_CLASS__succeeded / failed / pending-approval
__STATUS_ICON__成功时为 ✓,失败时为 ✗,待审批时为 ⏳
__STATUS_LABEL__Succeeded / Failed / Pending Approval
__ACTIVATION_SECTION__初始值为 ''——在 Phase 7.7 中确定激活结果后替换

Solution 标签页——读取 .solution-manifest.json,构建以下部分:

占位符值
__SOLUTION_META_ROWS__以下内容对应的 <tr> 行:友好名称、唯一名称、版本(新版本 → 旧版本)、类型(托管/非托管)、发布者、组件总数。来源:清单和 Phase 6 中的 validationResults.SolutionDetails。
__VALIDATION_SECTION__如果验证通过:<div class="note-box succeeded"><span class="validation-badge passed">✓ Validation Passed</span> — No missing dependencies.</div>。如果验证失败或存在依赖项:<div class="note-box warning">,列出每个缺失依赖项的名称。
__SOLUTION_CONTENTS_SECTION__根据 .solution-manifest.json 构建:一个 <div class="contents-grid">,其中包含两个 <div class="contents-card"> 块——Dataverse 表(每个表对应一个 <span class="table-chip">)和 Bot 组件(以逗号分隔的名称)。在网格下方,添加一个 <div class="note-box neutral">,内容为:{totalAdded} 个组件已添加到解决方案(来自 components.totalAdded)。如果清单不可用,则显示一条中性提示。

Config & Notes 标签页:

占位符值
__ENV_VARS_SECTION__如果 ENV_VAR_OVERRIDES 非空:一个 <div class="card"><h3>Environment Variable Overrides</h3> 表格,包含架构名称列和覆盖值列。否则:<div class="note-box neutral">No environment variable overrides applied.</div>
__DEPLOYMENT_NOTES_SECTION__如果 AI_DEPLOY_NOTES 可用:<div class="card"><h3>AI Deployment Notes</h3><p>…</p></div>。否则:''
__POST_DEPLOY_WARNINGS__每条部署后警告(连接引用、机器人重新发布)对应一个 <div class="note-box warning">。如果没有,则为空字符串。

将渲染后的 HTML 写入 docs/deploy-history/{filename}.html。

然后将以下内容添加到暂存区:

git add docs/alm/last-deploy.json docs/deploy-history/{filename}.html
git commit -m "Deploy {solutionUniqueName} v{artifactVersion} to {stageName} ({status})"

如果项目根目录中尚未初始化 git(即 git rev-parse --git-dir 执行失败),则静默跳过提交。

7.5 记录技能使用情况:

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

按照技能跟踪说明记录此技能的使用情况。使用 --skillName "DeployPipeline"。

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

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

此辅助工具会读取刚写入的 docs/alm/last-deploy.json,将其载入 planData.pipelineMeta.lastDeploy,合并各阶段的环境变量值(来自 deployment-settings.json,以及通过 --live 获取的计划中各阶段的实时 environmentvariablevalues 表;以文件中的值为准),移除所有部署前的“尚未预配主机”风险,并重新渲染 docs/alm-plan.html,让 Pipelines 选项卡显示实际运行状态(状态、版本、组件数量、激活情况、站点 URL)。当 docs/.alm-plan-data.json 不存在时(即独立调用此技能,而非作为 ALM 计划的一部分),辅助工具会返回 ok:false,并作为软性空操作安全地无条件运行。

此步骤可确保渲染后的计划保持最新状态。plan-alm 是规划工具,不会自行刷新计划,因此每个执行技能都负责在运行后刷新计划。重复运行此步骤不会改变结果(相同输入 → 相同输出)。

显示实时查询失败。 如果辅助工具的标准输出 JSON 中 liveEnvVars.errors[] 非空,请逐行告知用户哪些阶段无法读取:"无法读取 {stage} 的实时环境变量值:{error}。Values by Environment 矩阵中的该列保持不变,因此在 Power Platform Admin Center 中为其设置的值可能缺失或已过时。" 如果错误为 HTTP 401/403,或提示无法获取令牌,最可能的原因是 Azure CLI 登录的租户与该环境的租户不同(az 会为其默认订阅的租户签发令牌,该租户可能与 PAC CLI 配置文件中的租户不同)。因此,请建议运行 az login --tenant <environment tenant id>(或 az account set --subscription <a subscription in that tenant>),然后重新运行上面的刷新命令。这只是提示信息——不要因此使部署失败或阻止 7.6 总结。若 liveEnvVars 不存在或 errors 为空,则无需说明。

告知用户下一步(由用户决定执行顺序)。 辅助工具的标准输出 JSON 包含 nextStep: { name, skill: string | null } | null。当其非空时,根据 skill 分支处理:当 skill 非空时,告知用户 "计划已更新。计划中的下一步:{nextStep.name} → 准备好后运行 {nextStep.skill}。";当 skill 为 null(例如 Finalize 这类内部步骤,没有用户命令)时,只说明步骤名称——"计划已更新。计划中的下一步:{nextStep.name}。"——且绝不要输出 run null。(对于多阶段流水线,通常是下一阶段的部署,或下一阶段单独设置的激活/测试步骤。)当其为 null(所有步骤均已完成)或辅助工具返回 ok:false(没有计划)时,不要提及下一步。绝不要自动调用下一技能——由用户决定执行。

7.6 显示摘要:

如果成功:

✓ Deployment succeeded

  Solution:     {solutionName}
  Stage:        {stageName}
  Target:       {targetEnvironmentUrl}
  Completed at: {deployedAt}
  Stage run ID: {STAGE_RUN_ID}
  Site URL:     {siteUrl from 7.7, or "— activation pending" if not yet activated, or "— checking…" before 7.7 runs}

如果失败:

✗ Deployment failed

  Stage run ID: {STAGE_RUN_ID}
  Status:       Failed

  To investigate: open Power Platform make.powerapps.com → Solutions → Pipelines
  and find the failed run for details on what caused the failure.

7.6.1 诊断失败原因。 在询问用户如何处理之前,查询阶段运行记录,获取错误详情,以便提供有针对性的修复提示:

GET {hostEnvUrl}/api/data/v9.1/deploymentstageruns({STAGE_RUN_ID})?$select=errordetails,validationresults,stagerunstatus
Authorization: Bearer {HOST_TOKEN}

将 errordetails 和 validationresults 解析为 JSON / 文本。检查是否存在以下模式(不区分大小写):

错误文本中的模式诊断结果修复提示
AttachmentBlocked OR -2147188706 OR not a valid type OR \.js.*blocked附件被阻止 — 目标环境的 blockedattachments 设置拒绝解决方案中的文件类型见下文 7.6.2
secret reference does not match a valid OR ImportAsHolding failed.*secret reference OR KeyVault.*formatSecret 引用无效 — deployment-settings.json 中的 Secret 值格式不符合规范(例如,@KeyVault(vaultName=...;secretName=...)、原始 Secret 文本或格式错误的 URI)见下文 7.6.4
MissingDependency OR Missing dependency目标环境中缺少解决方案依赖项列出这些依赖项;建议用户安装后重试
(其他任何情况)未知失败转到通用重试 / 退出提示

7.6.2 附件被阻止时的修复(需授权)。 仅当 7.6.1 的诊断结果匹配附件被阻止模式时才执行。未经用户通过 AskUserQuestion 明确授权,绝不修改环境的 blockedattachments 设置。 这是租户级安全设置;自动修改会在其他应用使用同一环境时悄然削弱其安全态势。

  1. 将 PAC CLI 切换到目标环境,以便辅助工具查询正确环境的设置,然后确认被阻止的文件类型。默认情况下,辅助工具会检查 js;如果错误提到了其他类型(例如 js,css),请传入 --extensions:

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

    阅读输出 JSON。需要关注的字段:wasBlocked[](环境中当前被阻止、且位于 --extensions 列表中的扩展名 — 这些扩展名将被移除)和 unchanged[](你要求移除但实际上并未被阻止的扩展名,即无操作项)。

    如果 wasBlocked 为空,则此次失败并非由此列表中的附件被阻止造成 — 回退到 7.6.3 的通用重试提示,并展示 errordetails 中的原始错误文本。

  2. 明确告知用户:

    “部署失败,因为目标环境 {targetEnvName} 会阻止此解决方案所需的文件类型:{wasBlocked.join(', ')}。这是环境级安全设置,会影响该环境的所有用户。若要继续,必须解除对这些特定类型的阻止。此更改可通过 Power Platform 管理中心 → 环境 → {targetEnvName} → 设置 → 产品 → 功能 → 阻止附件进行还原。”

🚦 授权关卡(同意 · deploy-pipeline:7.6.2.blocked-attachments): 针对 AttachmentBlocked 的响应式修复 — 修改环境级 blockedattachments 设置(影响整个租户)。可通过 PPAC 还原。每次匹配 AttachmentBlocked 模式的失败都会触发此关卡。 在 MULTI_RUN_MODE 中,此情况很少见 — 第 2.5 阶段的预检通常会在循环开始前发现阻止情况 — 但如果第 N 次迭代在第 2.5 阶段成功后仍因 AttachmentBlocked 而失败,则该次失败会再次触发此关卡。每次失败都是一次独立的授权决策(涉及不同的解决方案,也可能涉及不同的被阻止扩展名)。不得将一次失败的授权沿用到其他失败。

  1. 调用 AskUserQuestion(不要将此问题埋在聊天内容中 — 用户必须先回答,才能进行任何更改):

    问题标题选项
    是否允许解除 {targetEnvName} 环境对 {wasBlocked.join(', ')} 的阻止,以便重试部署?这会修改租户级安全设置。解除附件阻止是 — 解除对这些类型的阻止并重试;否 — 保持设置不变(我会手动调查);取消
  2. 根据回答采取相应操作:

    • 是 — 解除阻止并重试:在不带 --dry-run 的情况下调用 fix-blocked-attachments.js(使用相同的 --extensions),然后调用 RetryFailedDeploymentAsync,并从第 6.2 阶段恢复轮询。在部署摘要中说明所做的更改,确保用户有清晰的审计记录。重试完成后,将 PAC CLI 切回源环境,以便后续技能步骤默认对源环境运行。
    • 否 / 取消:停止并提供修复指引:

      “手动解除阻止:打开 Power Platform Admin Center → Environments → {targetEnvName} → Settings → Product → Features → Blocked Attachments → 移除 {blockedTypesPresent.join(', ')} → Save。然后重新运行 /power-pages:deploy-pipeline。”

7.6.3 通用重试/退出提示(当 7.6.1 未匹配到已知模式,或用户拒绝针对性修复时):

🚦 关卡(计划 · deploy-pipeline:7.6.3.retry-exit): 部署失败,且未匹配到已知模式——调用 RetryFailedDeploymentAsync,或退出以便手动调查。

通过 AskUserQuestion 询问:

“部署失败。你想怎么做?

  1. 重试 — 调用 RetryFailedDeploymentAsync,重试同一阶段运行
  2. 退出 — 我会手动调查”

如果选择 重试:调用:

POST {hostEnvUrl}/api/data/v9.1/RetryFailedDeploymentAsync
Content-Type: application/json
Authorization: Bearer {HOST_TOKEN}

{"StageRunId": "{STAGE_RUN_ID}"}

然后从第 6.2 阶段恢复轮询。

如果选择 退出:停止并显示上述失败摘要。

7.6.4 无效 Secret 引用修复(需授权)。 仅当 7.6.1 中的诊断匹配 Secret 引用模式时运行。这是原始 Citizens-portal 验证报告中描述的清除并重试路径:当导入操作拒绝 deployment-settings.json 中的 Secret 值时,将其就地替换为 ""(Dataverse 会将其解释为“使用环境变量定义的默认值”),然后重试部署。文件修改会被保留,因此下次运行时不会再次发送错误值。

这是兜底措施。 configure-env-variables 的第 6.1 阶段预写入验证器,以及本技能第 5.1b 阶段的 PATCH 前验证器,应能在上游捕获大多数无效 Secret 值。第 7.6.4 阶段用于处理以下情况:

  • 用户在 configure-env-variables 运行后手动编辑了 deployment-settings.json。
  • 提交了一个在第 6.1 阶段之前就已存在的旧文件。
  • PATCH 前关卡对某个条目返回了 status: "unknown-type",因为在 PATCH 时无法解析其 Dataverse 类型,但主机验证器在导入时拒绝了该条目。

步骤:

  1. 确定哪些架构包含错误值。 对当前文件运行 validate-deployment-settings.js(与第 5.1b 阶段使用的辅助程序相同),以找出所有无效条目:

    node "${PLUGIN_ROOT}/scripts/lib/validate-deployment-settings.js" \
      --settingsFile "./deployment-settings.json" \
      --envUrl "{sourceEnvUrl}" \
      --stageLabel "{SELECTED_STAGE.name}"
    

    将标准输出捕获为 JSON;收集满足 status === "invalid" 且 valueFormat 为 kv-placeholder / plain-text / invalid-uri 的 findings[]。收集到的 schemaName 列表即为需要清除值的目标。

  2. 如果 validate-deployment-settings.js 未发现无效条目(主机错误提到了 Secret 格式,但本地文件看起来正常——可能是并发编辑,也可能是错误匹配了该模式,但实际针对的是其他字段),则转到 7.6.3(通用重试/退出提示)。

  3. 否则,征求用户同意。

    🚦 关卡(同意 · deploy-pipeline:7.6.4.strip-secret-values): 清除 deployment-settings.json 中无效的 Secret 引用值并重试部署。错误值将被替换为空字符串(使用定义默认值),且文件修改会被保留——下次运行时不会再次发送错误值。取消则会保持文件不变,方便用户使用规范的 Key Vault URI 手动修复。

    使用 AskUserQuestion:

    问题标题选项
    部署失败,因为 deployment-settings.json 中的 Secret 值格式无效:{list of schema names}。要移除这些值(设为 "" — 使用定义默认值)并重试部署吗?文件将会更新;稍后可通过 /power-pages:configure-env-variables 提供规范的 Key Vault URI。移除无效的 Secret 值是 — 移除并重试(推荐),否 — 退出,以便我手动使用规范 URI 修复 deployment-settings.json
  4. 根据回答采取相应操作:

    • 是 — 移除并重试:调用文件修改辅助工具,然后使用更正后的 deploymentsettingsjson 重新 PATCH 阶段运行,再调用 RetryFailedDeploymentAsync 并从阶段 6.2 恢复轮询。

      node "${PLUGIN_ROOT}/scripts/lib/strip-invalid-secret-values.js" \
        --settingsFile "./deployment-settings.json" \
        --schemaNames "{comma-separated schema names from step 1}" \
        --stageLabel "{SELECTED_STAGE.name}"
      

      确认 stripped.length > 0,并记录列表以用于审计摘要(阶段 7.5b 刷新会将其纳入渲染后的计划)。从更正后的 deployment-settings.json 重新构建 ENV_VAR_OVERRIDES(重新运行阶段 5.1 中“读取 deployment-settings.json”步骤的相同逻辑),然后再次 PATCH 阶段运行:

      PATCH {hostEnvUrl}/api/data/v9.0/deploymentstageruns({STAGE_RUN_ID})
      Content-Type: application/json
      Authorization: Bearer {HOST_TOKEN}
      
      {
        "deploymentsettingsjson": "{JSON.stringify({ EnvironmentVariables: ENV_VAR_OVERRIDES, ConnectionReferences: ... })}"
      }
      

      然后重试导入:

      POST {hostEnvUrl}/api/data/v9.1/RetryFailedDeploymentAsync
      {"StageRunId": "{STAGE_RUN_ID}"}
      

      从阶段 6.2 恢复轮询。在 docs/alm/last-deploy.json 的 secretRemediation[] 中记录移除操作,以便部署摘要显示移除了哪些架构,以及它们之前的值。

    • 否 — 退出:停止并提供修复指引。告知用户 Secret 引用的规范格式,并指向 /power-pages:configure-env-variables 以进行引导式修复:

      “手动修复方法:打开 deployment-settings.json,将无效的 Secret 值替换为以下任一格式:

      1. Key Vault Secret 标识符 URI:https://<vault>.vault.azure.net/secrets/<name>[/<version>]
      2. Azure 资源 ID:/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>/secrets/<name>
      3. 空字符串(使用环境变量定义的默认值)

      然后重新运行 /power-pages:deploy-pipeline。或者调用 /power-pages:configure-env-variables,以进行引导式编辑并重新运行阶段 6.1 的写入前验证。”

7.6.5 验证 environmentvariablevalues 是否已写入目标环境(仅当部署成功、ENV_VAR_OVERRIDES 非空且 deploymentsettingsjson 已在阶段 5.2 中执行 PATCH 时):

即使阶段运行报告成功,Power Platform Pipelines 处理程序也不一定会为 deploymentsettingsjson 中的每个环境变量定义都写入 environmentvariablevalues 记录 — 未绑定到 mspp_sitesetting(或平台识别的其他使用方)的定义可能会在目标环境中成为零值。有关实际证据,请参阅阶段 6 上方的注意事项。本步骤通过在部署后查询目标环境并指出任何未成功写入的变量来弥补这一缺口。

使用共享辅助工具 scripts/lib/verify-env-var-values.js。该工具从 deployment-settings.json 读取架构名称和每个阶段的预期值,并为每个架构返回结构化 JSON 结果(landed / missing-value-record / missing-definition / value-mismatch / query-error):

node "${PLUGIN_ROOT}/scripts/lib/verify-env-var-values.js" \
  --envUrl "{targetEnvUrl}" \
  --settingsFile "./deployment-settings.json" \
  --stageLabel "{SELECTED_STAGE.name}"

将 stdout 作为 JSON 捕获:const verifyResult = JSON.parse(output)。该辅助程序通过 Azure CLI 获取自己的令牌,作用域为 {targetEnvUrl} — 此调用不需要切换 PAC CLI(该辅助程序完全只读,且完全绕过 PAC)。

根据 verifyResult.summary 分支处理:

  • summary.missing === 0 && summary.mismatched === 0 → 所有环境变量值均已成功写入。不发出警告。继续。

  • summary.missing > 0 OR summary.mismatched > 0 → 在 docs/alm/last-deploy.json 的新 envVarLandingWarnings[] 数组下记录结构化警告。根据 verifyResult.results 中状态不是 landed 的条目构建此数组 — 保留 schemaName、status,以及(若存在)expected / value 字段。然后通过部署摘要告知用户:

    "{summary.missing + summary.mismatched} 个来自 deploymentsettingsjson 的环境变量值未能成功写入 {targetEnvName}。missing-value-record 最可能的原因:目标环境中的定义尚未关联到站点设置。value-mismatch 最可能的原因:deployment-settings.json 中的阶段值与目标环境 environmentvariablevalues 表中的当前值不同(有人可能在部署后通过 PPAC 编辑了它)。对 {targetEnvName} 运行 /power-pages:configure-env-variables 以进行协调,或者通过 Power Platform 管理中心 → 解决方案 → 环境变量设置值。"

  • summary.error > 0(身份验证、暂时性错误、5xx)→ 记录一行说明:"环境变量写入检查无法得出结论(有 {summary.error} 个架构查询出错)。请在 PPAC 中手动验证。" 不要因结果无法确定而阻止部署摘要 — 部署本身已成功。

为什么使用辅助程序而不是内联 OData? 早期的内联说明版本很脆弱:每次 Agent 运行都要手动重建查询,PAC CLI 环境切换需要手动操作,而且结果形状从未结构化。此辅助程序有 21 项测试,覆盖所有结果状态分支(landed / missing-value-record / missing-definition / value-mismatch / query-error);它直接读取 deployment-settings.json,避免调用方与辅助程序之间出现偏差,并生成稳定的 JSON 契约,调用方无需重新解析说明文字即可将其保存到 last-deploy.json。

7.7 检查站点是否已激活(仅当部署成功且这是一个 Power Pages 项目时):

触发规则(已于 2026-04-22 更新): 只要源项目的 powerpages.config.json 中存在 websiteRecordId,就运行此检查 — 即此项目是一个 Power Pages 站点项目。早期规则(“仅当刚部署的解决方案包含 componentType 为 10374 的 website 时”)会漏掉一个现实情况:目标环境中可能已存在该站点,而刚部署的解决方案可能只包含表/流/机器人。如果目标环境中的站点未激活,那么 Power Pages 项目在部署后的状态始终不完整,无论这次具体部署了哪个解决方案。

从 powerpages.config.json 读取 websiteRecordId。如果没有此字段,则跳过 7.7 的其余部分(这不是 Power Pages ALM 运行 — 例如,纯数据模型解决方案)。

可选的次要检查:查询源解决方案中是否包含 componentType 为 10374 的 website,但仅用于记录该站点是否包含在此次特定解决方案中 — 仅供参考,不作为门控条件:

GET {sourceEnvUrl}/api/data/v9.2/solutioncomponents?$filter=_solutionid_value eq '{solutionId}' and componenttype eq 10374&$select=objectid
Authorization: Bearer {SOURCE_TOKEN}

如果找到,则临时将 PAC CLI 切换到目标环境,以便 check-activation-status.js 查询正确的环境:

pac env select --environment "{SELECTED_STAGE.targetEnvironmentUrl}"

运行激活检查:

node "${PLUGIN_ROOT}/scripts/check-activation-status.js" --projectRoot "."

然后无论结果如何,都将 PAC CLI 切回源(开发)环境:

pac env select --environment "{sourceEnvUrl}"

评估结果并根据情况采取行动。所有情况下,在结果处理完毕后,都要更新 docs/alm/last-deploy.json 和部署历史文件(如下所述)。

  • activated: true:站点已上线。设置 ACTIVATION_OUTCOME = { status: "Activated", siteUrl: "{result.websiteUrl}" }。

🚦 关卡(计划 · deploy-pipeline:7.7.activate): 站点已部署到目标环境,但尚未激活。请建议调用 activate-site,或暂缓激活。

  • activated: false:通过 AskUserQuestion 询问用户:

    问题标题选项
    Power Pages 站点已部署到 {SELECTED_STAGE.targetEnvironmentUrl},但尚未激活(预配)。现在激活即可让站点公开访问。激活站点是,现在激活(推荐),否,我稍后激活
    • 如果选择“是”:调用 /power-pages:activate-site。activate-site 技能会处理子域名选择、确认和预配。完成后,设置 ACTIVATION_OUTCOME = { status: "Activated", siteUrl: "{site URL from activate-site}" }。
    • 如果选择“否”:设置 ACTIVATION_OUTCOME = { status: "Pending", siteUrl: null }。
  • 存在 error:设置 ACTIVATION_OUTCOME = null — 静默跳过以下更新步骤。

激活结果确定后,就地编辑 docs/alm/last-deploy.json,写入实际值:

  • "activationStatus": "{ACTIVATION_OUTCOME.status}"(如果 ACTIVATION_OUTCOME 为 null,则保持 null)
  • "siteUrl": "{ACTIVATION_OUTCOME.siteUrl}"(如果 ACTIVATION_OUTCOME 为 null,则保持 null)

重新刷新 ALM 计划,以便将激活结果同步到计划中。 Phase 7.5b 的刷新在激活结果确定之前运行,因此即使 last-deploy.json 现在记录了 activationStatus,计划中的“在 {stage} 激活站点”步骤仍处于待处理状态。标记更新后立即重新运行刷新 — 当 activationStatus 为 "Activated" 时,deploy-pipeline 阶段会自动完成激活步骤(如果暂缓激活,"Pending" 会使该步骤继续保持待处理状态):

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

(如果不存在 ALM 计划,则会软性空操作 — 当 docs/.alm-plan-data.json 不存在时,refresh-alm-plan-data.js 会返回 ok:false。)

然后就地编辑部署历史 HTML 文件 — 将 __ACTIVATION_SECTION__ 替换为相应的 HTML:

  • status: "Activated":

    <div class="card">
      <h2>Site Activation</h2>
      <table><tbody>
        <tr><td class="label-col">Status</td><td style="color:var(--succeeded);font-weight:600;">✓ Activated</td></tr>
        <tr><td class="label-col">Site URL</td><td><a href="__SITE_URL__" style="color:var(--accent);">__SITE_URL__</a></td></tr>
      </tbody></table>
    </div>
    

    (将 __SITE_URL__ 替换为 ACTIVATION_OUTCOME.siteUrl)

  • status: "Pending":

    <div class="note-box neutral">
      <strong>Site activation pending.</strong> The solution was deployed but the site has not yet been provisioned in this environment. Run <code>/power-pages:activate-site</code> (with PAC CLI authenticated to the target environment) to activate it.
    </div>
    

如果 ACTIVATION_OUTCOME 为 null(检查期间出错),则将 __ACTIVATION_SECTION__ 占位符留为空字符串(从文件中移除)。

7.8 检测并指导云端流注册(仅当部署成功时):

在主机环境中查询解决方案组件,查找云端流(componenttype 29 = Workflow):

GET {hostEnvUrl}/api/data/v9.2/solutioncomponents?$filter=solutionid eq '{ARTIFACT_SOLUTION_ID}' and componenttype eq 29&$select=objectid,componenttype
Authorization: Bearer {HOST_TOKEN}
  • 如果没有结果:完全跳过此步骤 — 该解决方案不包含云端流。

  • 如果找到结果:

    1. 统计流数量:将结果数量存储为 N。

    2. 对每个 objectid,尝试在主机环境中通过查询 workflows({objectid})?$select=name 解析流名称。如果任何查询失败或未返回名称,则改为显示原始对象 ID。

    3. 告知用户:

      “该解决方案包含 {N} 个云端流。部署后,必须在目标环境中将云端流注册到 Power Pages 站点,才能正常运行。

      检测到的流: {流名称或 ID 的项目列表}

      注册方法:打开 Power Pages → 选择目标环境 → 打开站点 → 设置 → 云端流 → 注册上面列出的每个流。”

    🚦 关卡(计划 · deploy-pipeline:7.cloud-flow-register): 已部署解决方案中的云端流需要在目标环境中手动注册。请确认已完成或暂缓处理(非阻塞)。

    1. 通过 AskUserQuestion 询问:

      问题标题选项
      你是否已在目标环境中注册云端流?云端流注册已注册流 — 继续,我稍后注册
    2. 非阻塞:无论用户如何回答,都继续执行摘要步骤(Phase 7.6)。在摘要表中记录云端流注册状态:

      • 回答“流已注册 — 继续” → 在摘要中显示已注册
      • 回答“我稍后再注册” → 在摘要中显示待注册

    注意:跳过或延后注册并不表示部署失败。它只会影响调用已注册流的页面的实时网站功能。

关键决策点(等待用户)

  1. Phase 2:选择目标阶段(要部署到哪个环境)
  2. Phase 2:如果上次部署到此阶段失败,确认是否重试
  3. Phase 2.5:解除预检阻止的附件限制(Yes — unblock / No — proceed anyway / Cancel deploy)。仅当目标环境的 blockedattachments 设置阻止解决方案所需的扩展名(通常是 SPA 网站所需的 .js)时触发。此操作会修改租户范围的安全设置;必须获得明确同意。
  4. Phase 3.5:当实时网站包含解决方案中缺失的组件时,提示处理完整性差距(优先同步 / 仍然部署 / 取消)
  5. Phase 3.5:同步后审批关卡——仅在部署过程中执行同步(选项 1)后触发。显示新的解决方案版本和新纳入的组件,并要求用户确认同步后的解决方案,再将其提升到目标阶段。暂停会正常退出;取消会中止。
  6. Phase 3.6(仅限 MULTI_RUN_MODE):批量验证结果——此处可能触发两个关卡:
    • 批次待审批:并行验证批次中有一个或多个解决方案进入待审批状态。用户在 PPAC 中批准所有解决方案后,我们会重新轮询。取消会留下 N 个待处理的阶段运行。
    • 批量验证失败:一个或多个解决方案验证失败。默认操作是中止——重新导出并重新调用。高级选项允许用户仅部署成功的子集(在 last-deploy.json 的 knownGaps 中记录差距)。
  7. Phase 4:验证审批关卡——仅在单解决方案 / v2 模式下触发(在 MULTI_RUN_MODE 中由 Phase 3.6 处理)
  8. Phase 5:提前显示 deployment-settings.json(5.0a:显示摘要或生成模板;5.0b:显示文件路径)。环境变量值——如果解决方案包含环境变量定义,且所选阶段没有预配置值,则始终显示;同时提供保存这些值以供后续运行使用的选项
  9. Phase 6.0:最终部署同意关卡——Deploy now / Cancel。在调用 DeployPackageAsync(或回退到 pac pipeline deploy)前立即触发。明确呈现提升到生产环境的时刻;如果没有此关卡,验证顺利通过时可能会从 Phase 5 直接进入 Phase 6.1,而没有最终确认。对于 Production 目标,将此项视为最重要的提示。
  10. Phase 6:部署审批关卡——如果部署过程中进入待审批状态,则等待用户批准
  11. Phase 7.6.2:响应式解除附件限制(Yes — unblock and retry / No — leave settings unchanged / Cancel)。仅当部署因 AttachmentBlocked 失败,且预检(Phase 2.5)被跳过或拒绝时触发。与 Phase 2.5 一样,必须同意修改安全设置。
  12. Phase 7.7:网站激活——仅当部署成功、解决方案包含 Power Pages 网站组件,且目标环境中的网站尚未激活时触发。结果(activationStatus、siteUrl)会写回 docs/alm/last-deploy.json 和部署历史 HTML。
  13. Phase 7.8:云端流注册——仅当部署成功且解决方案包含云端流组件(componenttype 29)时触发;无论用户如何回答都不会阻塞

错误处理

  • 没有 docs/alm/last-pipeline.json:停止,并提示使用 /power-pages:setup-pipeline
  • 主机环境令牌失败:停止并提供 az login 指引
  • RetrieveDeploymentPipelineInfo 失败:使用 docs/alm/last-pipeline.json 中的 sourceDeploymentEnvironmentId 作为备用方案;警告无法检索构件列表,并请用户确认解决方案
  • 创建阶段运行失败(4xx):报告完整错误正文——可能是管道配置问题
  • ValidatePackageAsync 失败:报告错误——通常表示解决方案尚未准备好部署
  • 验证时 stagerunstatus = 200000003(Failed):停止并提供验证详情——用户必须先解决问题才能重试(必须创建新的阶段运行)
  • 部署时 stagerunstatus = 200000003(Failed):停止前,提供通过 RetryFailedDeploymentAsync 重试的选项(POST /api/data/v9.1/RetryFailedDeploymentAsync {"StageRunId": "..."})
  • DeployPackageAsync 调用失败:报告错误并停止
  • 轮询超时(达到最大尝试次数):停止并提示“部署耗时超出预期。请在 Power Platform 中检查状态。”

进度跟踪表

任务主题当前操作描述
验证先决条件正在验证先决条件运行 verify-alm-prerequisites.js(--require-manifest --expectedEnvUrl 来自项目配置;如果 PAC 环境不一致则立即停止),用于 PAC/az/WhoAmI;运行 detect-project-context.js 获取 solutionManifest/siteName;读取 docs/alm/last-pipeline.json 获取 pipelineId/stages;获取主机环境令牌
选择目标阶段正在选择目标阶段显示 docs/alm/last-pipeline.json 中的可用阶段;请用户选择目标阶段;如果上次部署到此阶段失败,则发出警告
解析管道信息正在解析管道信息调用 RetrieveDeploymentPipelineInfo(v9.1)获取 SourceDeploymentEnvironmentId 和 DeployableArtifacts;匹配解决方案
验证包正在验证包MULTI_RUN_MODE:运行一次第 3.6 阶段(并行批次)—— validate-stage-runs-batch.js 并行处理所有未跳过的解决方案,分别执行 create-stage-run + ValidatePackageAsync + poll-validation-status;如果任何验证失败或批次处于待审批状态,则停止部署;为串行部署循环保存每个解决方案的 stageRunIds,以便重复使用。单解决方案 / 旧版 v2:第 4 阶段内联执行——POST deploymentstageruns(→ 201 或 204+header);POST ValidatePackageAsync 顶层操作(204);轮询 stagerunstatus,直到值不再是 200000006;对 validationresults 执行两次 JSON.parse;获取 aigenerateddeploymentnotes;PATCH artifactversion + deploymentnotes + deploymentsettingsjson(来自 deployment-settings.json)
配置部署设置正在配置部署设置检查/显示 deployment-settings.json(5.0a:读取或生成模板;5.0b:显示路径);查询解决方案的环境变量定义(componenttype 380);将所选阶段的定义与 deployment-settings.json 进行比较;提示用户填写任何未配置的值;提供将更改保存回 deployment-settings.json 的选项;PATCH 阶段运行中的 deploymentsettingsjson
部署并监控正在部署并监控POST DeployPackageAsync 顶层操作(204);通过筛选 GET 每隔 10s 轮询,直到 stagerunstatus 进入终态;处理审批关卡(通过 PATCH iscanceled=true 取消);失败时提供 RetryFailedDeploymentAsync 选项;每 10 个周期刷新令牌
写入部署记录正在写入部署记录写入 docs/alm/last-deploy.json(包含 artifactVersion + deployHistoryFile 字段);写入 docs/deploy-history/{date}-{stage}-{version}.md;git add + commit 历史文件;运行技能跟踪;如果状态为 Succeeded:显示连接引用警告(如果 solutionManifest.cloudFlows 非空)和 Bot 重新发布警告(如果 solutionManifest.botComponents 非空);显示摘要;如果状态为 Succeeded 且包含 Power Pages 组件:将 PAC 切换到目标环境,运行 check-activation-status.js,再切回原环境;如果尚未预配,则询问用户是否要激活;如果状态为 Succeeded 且包含云流组件(componenttype 29):查询 solutioncomponents,解析流名称,告知用户,调用 AskUserQuestion(非阻塞),并在摘要中注明注册状态

评论

支持 Markdown