关于 import-solution
此技能将解决方案 ZIP 导入目标 Dataverse 环境,并可先进行暂存导入以检查缺失依赖。它面向执行手动环境推广的 Power Platform 项目,也会在导入前检查文件并提示可能需要的后续处理。
主要功能
- 01通过 ImportSolutionAsync 导入解决方案 ZIP
- 02可选暂存导入以检查依赖
- 03检查导入目标并要求确认
- 04提示连接引用及云流相关事项
适用场景
- 01将已导出的解决方案导入目标环境
- 02在正式导入前检查缺失依赖
- 03识别导入后需要绑定连接的解决方案
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill import-solution安装后在 Claude Code 中输入/import-solution确认已加载。
附带文件scripts/validate-import.js
详细介绍
插件检查:运行
node "${PLUGIN_ROOT}/scripts/check-version.js"——如果输出消息,请在继续前展示给用户。
import-solution
通过 ImportSolutionAsync 将解决方案 ZIP 导入目标 Dataverse 环境。支持通过 StageSolution 进行可选的暂存导入,以便在提交前检查是否缺少依赖项。
前提条件
- 已安装 PAC CLI,并已通过身份验证连接到目标环境
- 已安装 Azure CLI 并完成登录
- 解决方案 ZIP 文件已存在于磁盘上(由
export-solution生成)
阶段
阶段 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 "."
辅助程序会返回 JSON,其中包含 { exists, deferred, stale, staleness: { reason, detail }, generatedAt, planStatus, ... }。如果还需要检查计划的新鲜度,请在阶段 1 获取 --envUrl、--token、--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 吗?”
🚦 门控(意图 · import-solution: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是规划器,不会执行部署。随后此技能会重新运行阶段 0 检查(此时exists:true),并进入阶段 1。 - 不使用计划继续 → 设置
BYPASSED_PLAN_GATE = true,并进入阶段 1。 - 取消 → 正常退出。
步骤 4 — 计划已过期。 告知用户:
“ALM 计划生成于
{generatedAt},但源解决方案此后已修改(修改时间为{solution.modifiedon})。组件可能发生了变化。重新运行plan-alm将刷新分析结果和渲染后的 HTML。”
🚦 门控(意图 · import-solution: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。 - 取消 → 正常退出。
此门控的存在原因。 直接调用 import-solution 会将 zip 部署到目标环境,但不会经过协调器的部署策略选择或导入后的验证步骤。单独运行此技能的用户经常会跳过分阶段导入依赖项检查、遗漏目标环境的环境变量覆盖值,也没有由计划跟踪的记录来说明哪个环境接收了哪个版本的构件。此门控可确保 plan-alm 已运行(因此已选择策略、已将各阶段的值记录在 deployment-settings.json 中,并且部署可重现),或用户明确选择绕过它。
Phase 1 — 验证先决条件
在此阶段开始时一次性创建所有任务。
要创建的任务:
- “验证先决条件”
- “定位解决方案文件”
- “配置导入”
- “暂存解决方案(依赖项检查)”
- “导入解决方案”
- “验证导入”
- “检测云端流”
- “显示摘要”
注意:如果导入因
AttachmentBlocked错误而失败,Phase 5b 补救流程会直接内联运行——无需创建额外任务(它会在“导入解决方案”任务中继续执行)。如果解决方案 zip 中没有Workflows/*.json文件,则会自动跳过“检测云端流”任务。
步骤:
- 运行
pac env who— 提取environmentUrl(确认这是目标环境) - 运行
az account get-access-token --resource "{environmentUrl}" --query accessToken -o tsv— 获取令牌 - 验证 API 访问:
GET {environmentUrl}/api/data/v9.2/WhoAmI - 显示目标环境 URL,并请用户在继续前确认其正确无误。
重要:与用户确认目标环境——导入到错误的环境可能造成干扰。
如果任何检查失败,请停止(参考 ${PLUGIN_ROOT}/references/dataverse-prerequisites.md)。
Phase 1.5 — 依据当前 ALM 文档进行核实
参考:
${PLUGIN_ROOT}/references/alm-docs-grounding.md
此步骤最多用时约 30 秒。如果 MCP 搜索或获取出错,记录一行说明并继续——此技能必须能够离线运行。
- 使用查询
Power Pages solution import staging missing dependencies ImportSolutionAsync ALM运行microsoft_docs_search。 - 通过
microsoft_docs_fetch并行获取https://learn.microsoft.com/en-us/power-platform/alm/solution-concepts-alm(以及最多一个关于分阶段导入或依赖项处理的相关页面)。 - 提炼一段摘要,说明 Microsoft Learn 当前对暂存与直接导入、依赖项解析以及组件级错误处理的介绍。与
${PLUGIN_ROOT}/references/solution-api-patterns.md对比,并标出ImportSolutionAsync/StageSolution签名是否存在差异。 - 使用该摘要为 Phase 2 及后续决策提供依据。不要悄悄更改技能行为——在 Phase 4(实际导入)之前,将任何差异作为非强制警告告知用户。
阶段 2 — 定位解决方案文件
-
如果提供了 zip 路径作为参数,请直接使用
-
否则,搜索解决方案 zip 文件:
glob('**/*.zip', { ignore: ['**/node_modules/**'] }) -
对找到的每个 zip 文件,验证其中是否包含
solution.xml:- 使用
Bash:unzip -l "{zipPath}" 2>/dev/null | grep -qi solution.xml
- 使用
-
如果找到多个有效的 zip 文件,请让用户选择:
🚦 关卡(plan · import-solution:2.multiple-zips): 在项目根目录下发现了多个有效的解决方案 zip 文件。由用户选择要导入的文件。取消操作会在与目标环境进行任何交互之前退出。
使用
AskUserQuestion,为每个有效的 zip 文件提供一个选项——显示文件名、大小和修改日期,方便用户识别正确的文件(通常应选择最近导出的文件)。 -
如果未找到有效的 zip 文件:停止并说明原因——请先运行
export-solution,或提供 zip 路径
步骤 5a — 导入前内容检查(确认 zip 文件后、向用户展示之前运行):
检查 zip 文件,列出导入后需要手动完成的事项。按顺序运行所有检查:
# 1. Connection references (require user-binding post-import)
unzip -p "{zipPath}" customizations.xml 2>/dev/null | grep -c '<connectionreference '
# 2. Bots (require republishing in target environment)
unzip -l "{zipPath}" 2>/dev/null | grep -qi "bots/" && echo "found" || echo "none"
# 3. Cloud flows with hardcoded environment URLs (will silently fail in target)
unzip -p "{zipPath}" "Workflows/*.json" 2>/dev/null | grep -o '"organization":\s*"https://[^"]*"' | head -5
根据检查结果构建 postImportWarnings 列表:
| 检查结果 | 要显示的警告 |
|---|---|
connectionreference 数量 > 0 | "⚠️ 连接引用:此解决方案包含 N 个连接(例如 Dataverse 连接器)。导入后,你必须将每个连接引用绑定到目标环境中的有效连接,否则依赖这些连接的云端流将被禁用。" |
存在 bots/ 文件夹 | "⚠️ Copilot Studio 机器人:此解决方案包含一个机器人。导入后,必须在目标环境中重新发布该机器人,才能完成预配。" |
| 找到硬编码的组织 URL | "⚠️ 硬编码的环境 URL:云端流中包含硬编码的环境 URL({foundUrl})。这会导致导入后该流仍调用源环境。请在目标环境中编辑该流以更新 URL。" |
如果 postImportWarnings 不为空,请在展示 zip 文件详情之前直接显示所有警告(不要通过 AskUserQuestion 显示)。用户应在确认之前看到这些警告。
展示所选 zip 文件的详情(名称、大小、路径)和任何导入前警告,并向用户确认。
阶段 3 — 配置导入
步骤 3.0 — 版本偏差提示(在提问前进行只读检查)。
在询问用户是采用暂存导入还是直接导入之前,查询目标环境中 zip 文件所含解决方案唯一名称对应的已安装解决方案,并将其版本与 zip 文件中的版本进行比较。目的是在用户一路确认之前提示“即将导入的版本与已安装版本相同”——这是手动导出/导入流程中最常见的静默失败模式,因为只有在源端提升版本号,才能生成明确可升级的构件。
-
从 zip 文件中的
solution.xml提取uniqueName和version:unzip -p "{zipPath}" solution.xml 2>/dev/null | head -50从 XML 中解析
<UniqueName>和<Version>。分别存储为ZIP_SOLUTION_NAME和ZIP_SOLUTION_VERSION。 -
查询目标环境中已安装的解决方案:
GET {envUrl}/api/data/v9.2/solutions?$filter=uniquename eq '{ZIP_SOLUTION_NAME}'&$select=solutionid,uniquename,version,ismanaged将结果存储为
INSTALLED(如果筛选结果返回空的value数组,则存储为null)。 -
通过共享辅助函数比较版本,然后根据结果分支处理。
前置条件——如果
INSTALLED为null,则跳过整个步骤。 这是首次安装的情况:目标环境中没有可供比较的版本。不要将null代入'{INSTALLED.version}'后调用辅助函数——这会抛出“version is required”错误,使 Agent 无法进入任何分支。直接跳到下方的暂存/直接导入提示,并将本次导入视为全新安装。否则(
INSTALLED不为null),比较版本:重要:不要使用原始的
>/</===比较版本字符串——Dataverse 版本由 4 段整数组成(1.0.0.9与1.0.0.10),而按字典序比较会将1.0.0.10判定为低于1.0.0.9,导致当天第 10 次部署时版本偏差关卡判断错误。请改用标准辅助函数:node -e "console.log(require('${PLUGIN_ROOT}/scripts/lib/bump-solution-version').compareVersions('{ZIP_SOLUTION_VERSION}', '{INSTALLED.version}'))"该辅助程序在 ZIP < INSTALLED 时返回
-1,相等时返回0,ZIP > INSTALLED 时返回1。整数比较规则与bumpPatchSegment相同(用零补齐、最多 4 个段、拒绝非整数)。捕获标准输出,去除首尾空白,并将整数存入VERSION_CMP。如果辅助程序抛出异常(任一侧的版本格式错误),请向用户显示标准错误并停止——版本比较是安全导入的前置条件。INSTALLEDVERSION_CMP行为 null(未调用辅助程序——见上文前置条件) 首次安装。保持静默,继续到下方的暂存/直接导入提示。 非 null 1(zip 版本严格更高)正常升级。报告:"目标环境已安装 v{INSTALLED.version};此 zip 的版本为 v{ZIP_SOLUTION_VERSION}。导入将进行升级。" 非 null 0(zip 版本与已安装版本相同)显示下方警告(版本不一致——适用于托管和非托管解决方案)。 非 null -1(zip 版本严格更低)显示下方警告(降级——适用于托管和非托管解决方案)。 🚦 关卡(同意 · import-solution:3.0.version-skew): 目标环境中已安装解决方案的版本等于或高于 zip 版本。导入会产生不可预测的升级语义;源端
export-solution应在每次导出时递增版本。请使用递增后的版本重新导出、强制继续导入,或取消。警告提示——
AskUserQuestion:“目标环境中已安装
{ZIP_SOLUTION_NAME}v{INSTALLED.version}({INSTALLED.ismanaged ? '托管' : '非托管'})。你即将导入的 zip 携带版本{ZIP_SOLUTION_VERSION}。导入相同或更低版本并不可靠:
- 托管:没有升级沿袭;平台可能根据内部启发式规则应用或拒绝导入。
- 非托管:行为完全取决于
OverwriteUnmanagedCustomizations: true;目标环境中任何恰好与 zip 组件 ID 匹配的编辑内容,都会在没有版本变更记录的情况下被静默覆盖。
/power-pages:export-solution在生成 zip 前始终会递增源版本(自 2026-05-25 起)。如果此 zip 是在该变更前生成的,重新导出即可获得一个严格更高的干净版本。你希望如何继续?”
问题 标题 选项 要执行什么操作? 版本不一致 使用递增后的版本重新导出(推荐——调用 /power-pages:export-solution)、仍然导入(风险自负)、取消 - 重新导出:调用
/power-pages:export-solution。完成后,使用刚生成的 zip 从阶段 2 重新启动此技能。 - 仍然导入:设置
SKEW_ACK = true,并继续到下方的暂存/直接导入提示。在docs/alm/last-import.json中的versionSkew: { zipVersion, installedVersion, isManaged, acknowledged: true }下记录已确认的版本不一致,以供审计。 - 取消:干净地停止此技能。步骤 3.0 中未对目标环境执行任何写入操作——此步骤为只读。
-
如果比较结果属于正常升级情形(或尚未安装该 zip 中的解决方案),请保持静默继续——不要显示提示,只需在最终的
docs/alm/last-import.json中记录版本差异,以供摘要使用。
🚦 关卡(计划 · import-solution:3.config): 选择暂存或直接导入,以及覆盖选项。取消将在修改目标环境前退出。
通过 AskUserQuestion 询问用户:
关键决策点:暂存还是直接导入
- 暂存(托管解决方案推荐):先运行
StageSolution检查缺失的依赖项。在提交导入前显示问题。更安全——暂存步骤完全可逆。- 直接:跳过暂存并立即导入。速度更快,但如果缺少依赖项,导入可能会在中途失败。 另外询问:
- 覆盖非托管自定义项?(默认:是)——当目标环境自定义了相同组件时需要此操作
- 导入后发布工作流?(默认:是)
阶段 4 — 暂存解决方案(有条件)
仅当用户在阶段 3 中选择暂存导入时,才运行此阶段。
请参阅 ${PLUGIN_ROOT}/references/solution-api-patterns.md 第 5a 节。
- 对 zip 文件进行 Base64 编码:
node "${PLUGIN_ROOT}/scripts/encode-solution-file.js" --zipPath "{zipPath}" - 使用
CustomizationFile: {base64}向{envUrl}/api/data/v9.2/StageSolution发送POST请求 - 解析
StageSolutionResults:- 提取
StageSolutionUploadId(在阶段 5 中使用,无需再次对文件进行编码) - 检查
MissingDependencies数组
- 提取
- 如果
MissingDependencies不为空:- 列出每个缺失依赖项的类型和名称
- 询问用户:“目标环境中缺少这些依赖项。是否仍要继续(可能会失败),还是取消以先安装依赖项?”
- 如果取消:停止操作,并建议安装缺失的依赖项
- 如果
MissingDependencies为空:报告“未发现缺失的依赖项。可以开始导入。”
阶段 5 — 导入解决方案
请参阅 ${PLUGIN_ROOT}/references/solution-api-patterns.md 第 5b 节。
-
准备请求正文(始终使用
CustomizationFile——ImportSolutionAsync不接受StageSolutionUploadId):- 对 zip 进行编码:
node "${PLUGIN_ROOT}/scripts/encode-solution-file.js" --zipPath "{zipPath}" - 使用
{ CustomizationFile: "{base64}", OverwriteUnmanagedCustomizations: {choice}, PublishWorkflows: {choice} }
- 对 zip 进行编码:
-
向
{envUrl}/api/data/v9.2/ImportSolutionAsync发送POST请求 -
提取
AsyncOperationId和ImportJobKey(注意:字段为ImportJobKey,而不是ImportJobId) -
报告:“导入作业已启动:
{AsyncOperationId}。正在轮询以检查是否完成……”
运行 scripts/poll-async-operation.js:
node "${PLUGIN_ROOT}/scripts/poll-async-operation.js" \
--asyncJobId "{AsyncOperationId}" \
--envUrl "{envUrl}" \
--intervalMs 8000 \
--maxAttempts 75
处理轮询结果:
Succeeded:继续执行阶段 6Failed,且错误为AttachmentBlocked/ 错误代码为-2147188706:继续执行下面的阶段 5bFailed(其他情况):显示错误消息,查询导入作业以获取组件级错误,然后停止Timeout:告知用户,并建议检查管理中心
阶段 5b — 解决附件限制(有条件)
仅当阶段 5 的轮询因 AttachmentBlocked(-2147188706,或消息包含 AttachmentBlocked 或 not a valid type)而失败时,才运行此阶段。
5b.1 识别解决方案 zip 中被阻止的扩展名
列出 zip 中的所有文件并提取不重复的扩展名:
unzip -l "{zipPath}" | awk '{print $4}' | grep '\.' | sed 's/.*\.//' | sort -u
从环境中获取当前被阻止的附件列表:
pac env list-settings
在输出中找到 blockedattachments 行——其中包含以分号分隔的列表(例如 ade;adp;js;zip;...)。
计算交集:找出解决方案 zip 中也出现在阻止列表里的扩展名。这些类型需要解除阻止。
5b.2 解释问题
告知用户:
“解决方案导入失败,因为目标环境阻止了此解决方案中包含的某些文件类型。解决方案中以下文件扩展名当前受到阻止:
{comma-separated list}。这是环境级别的安全设置。要导入此解决方案,需要暂时放宽这些限制。”
5b.3 请求许可
🚦 Gate (consent · import-solution:5b.blocked-attachments): 对
AttachmentBlocked进行响应式补救——修改环境级blockedattachments设置(影响整个租户)。可通过 PPAC 恢复。每次技能调用因该错误而失败时,都会重新触发。 当plan-almManual 路径编排多目标导入(先 Staging,再 Production)时,会对每个目标调用一次import-solution——如果两个目标都阻止了相同的扩展名,则每个目标都会触发一次 gate(每次都是针对不同环境的独立技能调用)。对 Staging 的许可不适用于 Production。
立即调用 AskUserQuestion——不要将此问题作为聊天消息呈现。技能继续执行前,用户必须实时作答。
| 问题 | 标题 | 选项 |
|---|---|---|
此解决方案包含被此环境附件安全设置阻止的文件类型({list})。是否要为这些特定类型解除阻止,以便导入解决方案? | 解除附件类型阻止 | 是,为本次导入解除 {list} 的阻止(推荐),否,不更改环境设置 |
如果选择“否”:停止并告知用户:“这些文件类型仍处于阻止状态,无法继续导入。要手动解除阻止,请前往:Power Platform Admin Center → Environments → {env} → Settings → Product → Features → Blocked Attachments。”
如果选择“是”:继续执行 5b.4。
5b.4 更新被阻止的附件
- 解析
blockedattachments值(以分号分隔) - 仅移除在 5b.1 中识别出的扩展名——保留其他所有扩展名
- 更新设置:
pac env update-settings --name blockedattachments --value "{updated-list-with-types-removed}" - 确认更新成功。
5b.5 重试导入
重新编码 zip 并重试 ImportSolutionAsync(重复执行阶段 5 的步骤 1–4,然后再次轮询)。
- 如果为
Succeeded:继续执行阶段 6 - 如果再次失败且错误不同:显示新的错误消息并停止——不要继续重试
阶段 6 — 验证导入
-
查询解决方案,确认其存在且版本匹配:
GET {envUrl}/api/data/v9.2/solutions?$filter=uniquename eq '{solutionName}'&$select=solutionid,uniquename,version,ismanaged -
查询导入作业以获取组件结果(使用导入响应中的
ImportJobKey):GET {envUrl}/api/data/v9.2/importjobs({ImportJobKey})?$select=solutionname,completedon,progress,data- 解析
dataXML 字段以获取各组件的结果(查找result="failure"条目) - 统计:成功导入数 / 警告数 / 失败数
- 解析
-
确保
docs/alm/存在,然后写入docs/alm/last-import.json标记(node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"):{ "importedAt": "<ISO timestamp>", "solutionName": "<name>", "version": "<version>", "targetEnvironment": "<envUrl>", "asyncOperationId": "<id>", "importJobId": "<ImportJobKey value>", "status": "<Succeeded|Failed|Partial>", "componentResults": { "success": N, "warning": N, "failure": N }, "versionSkew": null }status字段用于驱动refresh-alm-plan-data.js的步骤同步(completed与failed),以更新渲染后的 ALM 计划中各阶段的检查清单。设置如下:- 当导入作业的
statecode为 3(Succeeded)且组件结果显示failure === 0时,设为Succeeded。 - 当
statecode为 3 但componentResults.failure > 0时,设为Partial(解决方案已导入,但部分组件未能导入——通常是托管属性冲突或依赖项缺失)。 - 当
statecode为 4(Failed)、导入未达到终止状态或所有组件都导入失败时,设为Failed。如果没有此字段,渲染后的计划会将每次导入都显示为completed,无论实际结果如何。
如果用户在步骤 3.0 中确认了同版本或降级导入(
SKEW_ACK = true),将versionSkew设为:{ "zipVersion": "<ZIP_SOLUTION_VERSION>", "installedVersion": "<INSTALLED.version>", "isManaged": <bool>, "acknowledged": true }否则将
versionSkew保持为null。 - 当导入作业的
阶段 6b — 设置环境变量值(如有)
成功导入后,环境变量的定义会随解决方案一起迁移,但其值不会。目标环境中会有这些定义记录,但在明确设置之前,值为空。
查询目标环境,查看此解决方案中的环境变量定义:
GET {envUrl}/api/data/v9.2/environmentvariabledefinitions?$filter=introducedversion ne null&$select=schemaname,displayname,type,defaultvalue,environmentvariabledefinitionid
仅筛选 schemaname 以发布者前缀(来自 .solution-manifest.json)开头的定义;如果有 solutioncomponents,也可以与之交叉核对。
对于找到的每个定义,检查目标环境中是否已有值:
GET {envUrl}/api/data/v9.2/environmentvariablevalues?$filter=_environmentvariabledefinitionid_value eq '{id}'&$select=value
🚦 关卡(计划 · import-solution:6b.env-vars): 导入的环境变量定义需要为每个目标环境设置值。用户可以提供值,也可以跳过(使用默认值)。如果没有设置值,运行时会读取默认值,而该默认值可能仅用于开发环境。
如果有任何定义尚无现有值,请通过 AskUserQuestion 向用户询问:
“导入的解决方案包含 {N} 个尚未设置值的环境变量。请为每个变量输入目标值(留空则跳过并使用默认值):
{schemaname}({displayname})— 默认值:{defaultvalue ?? 'none'}- ...”
对于用户提供的每个值,POST 一条 environmentvariablevalue 记录:
POST {envUrl}/api/data/v9.2/environmentvariablevalues
{
"schemaname": "{schemaname}",
"value": "{userValue}",
"[email protected]": "/environmentvariabledefinitions({id})"
}
关于 Secret 类型(type 100000005)的说明: Secret 值会加密存储。POST 操作方式相同,但该值在 UI 中会被遮蔽。用户应提供目标环境的实际 Secret 值(例如,生产租户应用注册的 OAuth 客户端 Secret——与开发环境中的值不同)。
如果用户跳过所有值:告知他们,在设置值之前,站点可能无法正常运行,并提供用于手动设置的 Power Platform 直接 URL:
https://{targetEnvHost}/main.aspx?appid=...&etn=environmentvariabledefinition
6b.verify — 确认值已写入。 完成各变量的 POST 后,验证目标环境中是否确实存在每条 environmentvariablevalues 记录。共享辅助脚本 scripts/lib/verify-env-var-values.js 会执行此只读检查,并按架构返回结构化 JSON 结果(landed / missing-value-record / missing-definition / value-mismatch / query-error):
node "${PLUGIN_ROOT}/scripts/lib/verify-env-var-values.js" \
--envUrl "{targetEnvUrl}" \
--schemaNames "{comma-separated schema names that the user supplied values for}"
将 stdout 捕获为 JSON。如果 summary.missing > 0 或 summary.error > 0,向用户发出一条警告,并列出受影响的架构名称;但不要阻止导入摘要,导入本身已成功。同一辅助脚本也用于部署时(deploy-pipeline 阶段 7.6.5)和配置时(configure-env-variables 阶段 7);集中执行此检查可确保不同技能向用户显示的消息一致。
阶段 6c — 检测云端流(如有)
导入成功后,检查解决方案是否包含云端流 JSON 文件。使用阶段 2 中找到的 zip 路径。
unzip -l "{zipPath}" | grep -i "^.*Workflows/.*\.json"
如果没有找到匹配的文件,则完全跳过此阶段。
如果找到云端流文件:
-
从每个路径中提取流名称(格式:
Workflows/FlowName-GUID.json— 移除路径前缀和 GUID 后缀,得到显示名称) -
告知用户:
“此解决方案包含 {N} 个云端流。导入后,必须在目标环境中将云端流注册到 Power Pages 站点。”
检测到的流:
{FlowName1}{FlowName2}...
注册方法:Power Pages Management → 目标环境 → 编辑站点 → Set up → Cloud flows → 注册上面列出的每个流。
直接链接:
https://make.powerpages.microsoft.com/🚦 关卡(计划 · import-solution:6c.cloud-flow-register): 导入的解决方案中的云端流需要在目标环境中手动注册。确认 / 暂缓。
-
调用
AskUserQuestion:问题 标题 选项 你是否已将上面列出的云端流注册到 {targetEnvUrl}中的 Power Pages 站点?云端流注册 流已注册 — 继续,我稍后注册 -
将用户的回答记录为
cloudFlowStatus:"Registered"或"Pending registration"。此状态将显示在阶段 7 的摘要行中 — 无论选择哪个回答,技能都可以继续。
阶段 6d — 检查站点激活状态(如果是 Power Pages 解决方案)
仅当解决方案包含 Power Pages 网站组件(componentType 10374)时才运行此阶段:
GET {envUrl}/api/data/v9.2/solutioncomponents?$filter=_solutionid_value eq '{solutionId}' and componenttype eq 10374&$select=objectid
如果未找到 componentType 10374 记录,则完全跳过此阶段。
如果找到记录,则运行共享激活状态检查(PAC CLI 已通过目标环境的身份验证):
node "${PLUGIN_ROOT}/scripts/check-activation-status.js" --projectRoot "."
评估结果:
activated: true:保存siteUrl,供阶段 7 摘要使用。无需进一步操作。
🚦 关卡(计划 · import-solution:6d.activate): 站点已导入,但尚未在目标环境中激活。请提供立即调用 activate-site 或暂缓的选项。
-
activated: false:通过AskUserQuestion询问用户:问题 标题 选项 Power Pages 站点已导入,但尚未在 {envUrl}中激活(预配)。立即激活以使其可公开访问。激活站点 是,现在激活(推荐),否,我稍后激活 - 如果选择“是”:调用
/power-pages:activate-site。activate-site 技能将处理子域名选择、确认和预配。 - 如果选择“否”:在阶段 7 的摘要中注明激活待处理,并提醒用户准备就绪后运行
/power-pages:activate-site。
- 如果选择“是”:调用
-
存在
error:静默跳过 — 不要阻止生成摘要。在阶段 7 中注明无法确定激活状态。
阶段 7 — 显示摘要
显示摘要表:
| 项目 | 值 |
|---|---|
| 解决方案 | {solutionName} v{version} |
| 目标环境 | {envUrl} |
| 托管 | 是 / 否 |
| 已导入的组件 | N 成功,N 警告,N 失败 |
| 已设置的环境变量值 | N / N |
| 云端流 | {cloudFlowStatus}(已注册 / 待注册 / 不适用) |
| 站点激活 | 已在 {siteUrl} 激活 / 待处理 / 不适用 |
| 导入作业 | {importJobId} |
记录技能使用情况
参考:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
按照参考中的技能跟踪说明记录此技能的使用情况。使用 --skillName "ImportSolution"。
刷新 ALM 计划(如果存在)
node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
--projectRoot "." \
--phase import-solution \
--stageName "{targetLabel}" \
--render
{targetLabel} 是本次已完成导入所针对的手动路径目标阶段(例如 Staging、Production),由用户说明,或可根据目标环境 URL 推断。该辅助工具会读取 docs/alm/last-import.json,将导入结果(状态、版本、组件数量、组件失败情况)写入 planData.manualImports[targetLabel],并重新渲染 docs/alm-plan.html,使匹配的 Import to {targetLabel} 检查清单步骤显示 IMPORTED / FAILED 徽标,并在行内显示版本和组件数量。后续导入到其他目标时,每个目标都会有自己的条目——渲染器会显示各目标的历史记录,而不是在每次调用时覆盖记录。
如果省略 --stageName,该辅助工具会尝试将 docs/alm/last-import.json 中的 targetEnvironment URL 与 planData.stages[].envUrl 匹配。如果匹配失败(较少见——通常是 planData 中的阶段标签与环境 URL 不匹配),导入结果会记录在一个合成键下,以免悄然丢失;请明确传入 --stageName,以保持渲染后的计划整洁。如果不存在 docs/.alm-plan-data.json,该辅助工具会返回 ok:false,并软性地不执行任何操作。
告知用户下一步(由用户驱动顺序)。 该辅助工具的标准输出 JSON 中包含 nextStep: { name, skill: string | null } | null。如果值非空,请根据 skill 分支处理:当 skill 非空时,告知用户 “计划已更新。计划中的下一步:{nextStep.name} → 准备好后运行 {nextStep.skill}。”;当 skill 为 null(例如 Finalize 这样的内部步骤,没有用户命令)时,只说出步骤名称——“计划已更新。计划中的下一步:{nextStep.name}。”——绝不要输出 run null。(通常是导入到下一个目标,或在当前目标激活站点。)如果值为 null 或辅助工具返回 ok:false,不要提及下一步。绝不要自动调用下一个技能——由用户决定执行。
关键决策点(等待用户)
- 阶段 1:确认目标环境——托管解决方案的导入不容易撤销
- 阶段 2:如果找到多个 zip 文件,则选择文件
- 阶段 3 步骤 3.0:版本差异提示——仅当目标环境已安装该解决方案,且 zip 的版本
≤已安装版本时触发。提供重新导出并提升版本(推荐)、仍然导入(会在last-import.json中记录用户已确认)或取消等选项。此项为只读检查;在此处取消不会对目标环境进行任何写入。 - 阶段 3:选择暂存导入或直接导入;覆盖自定义项
- 阶段 4:选择是否在缺少依赖项的情况下继续
- 阶段 5b:同意解除附件类型限制——未经明确批准,绝不修改环境设置
- 阶段 6b:环境变量值——如果解决方案包含环境变量定义,而目标环境中没有现有值,则始终提示用户;Secret 类型定义需要用户提供目标环境专用的密钥值
- 阶段 6c:云流注册——不会阻塞流程;用户之后可以注册;状态会记录在摘要中
- 阶段 6d:站点激活——仅当存在 Power Pages 网站组件且站点尚未激活时执行
错误处理
- 组件级导入失败:在摘要中报告,不阻止整体流程完成
- 如果导入异步操作因
AttachmentBlocked(-2147188706) 失败:运行阶段 5b 补救流程(识别受阻类型、征求同意、解除限制、重试) - 如果导入异步操作因其他错误失败:显示异步操作记录中的
friendlyMessage,然后停止 - 绝不尝试回滚——报告成功和失败的部分
- 未经用户明确批准,绝不修改环境设置(
blockedattachments)
进度跟踪表
| 任务主题 | activeForm | 描述 |
|---|---|---|
| 验证前提条件 | 正在验证前提条件 | 确认 PAC CLI 身份验证,获取令牌,并与用户确认目标环境 |
| 定位解决方案文件 | 正在定位解决方案文件 | 查找并验证解决方案 zip,确认其中包含 Solution.xml |
| 配置导入 | 正在配置导入 | 步骤 3.0:从 solution.xml 提取 zip 的 uniqueName+version,查询目标环境中已安装的版本;如果 zip 版本 ≤ 已安装版本,则提示版本不匹配的风险(提供重新导出/仍然导入/取消选项);然后询问:暂存还是直接导入、是否覆盖自定义项、是否发布工作流 |
| 暂存解决方案(依赖项检查) | 正在暂存解决方案 | 运行 StageSolution,在提交前检查是否存在缺失的依赖项 |
| 导入解决方案 | 正在导入解决方案 | POST ImportSolutionAsync,并轮询直至完成;如果出现 AttachmentBlocked:识别受阻类型,征得用户同意,通过 pac env update-settings 解除阻止,然后重试 |
| 验证导入 | 正在验证导入 | 确认目标环境中的解决方案版本,解析组件结果,并写入 docs/alm/last-import.json |
| 检测云端流 | 正在检测云端流 | 列出 zip 中的 Workflows/*.json 条目;如果存在,则提示用户将流注册到 Power Pages 站点;记录状态(已注册/待注册) |
| 检查站点激活状态 | 正在检查站点激活状态 | 如果解决方案包含 componentType 10374:运行 check-activation-status.js;如果尚未激活,则询问用户并调用 /power-pages:activate-site |
| 展示摘要 | 正在展示摘要 | 显示组件数量(成功/警告/失败)、云端流注册状态、站点激活状态和已设置的环境变量值 |