关于 export-solution
面向 Power Platform 开发者的导出工作流,可生成并验证 Dataverse 解决方案 ZIP,并检查 ALM 计划和缺失的网站组件。
主要功能
- 01ALM 计划有效性检查
- 02依据清单选择解决方案
- 03检查网站组件完整性
- 04异步导出与轮询
- 05下载并验证解决方案 ZIP
适用场景
- 01导出解决方案以用于预发布或生产环境
- 02查找解决方案中缺失的网站组件
- 03导出前刷新过期的 ALM 计划
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill export-solution安装后在 Claude Code 中输入/export-solution确认已加载。
附带文件scripts/validate-export.js
详细介绍
插件检查:运行
node "${PLUGIN_ROOT}/scripts/check-version.js"— 如果输出了消息,请先向用户显示该消息,然后再继续。
export-solution
触发异步 Dataverse 解决方案导出,轮询直至完成,下载解决方案 zip 文件并进行验证。读取 .solution-manifest.json 以识别解决方案;如果找不到,则询问用户。
前提条件
- 已安装并通过身份验证的 PAC CLI
- 已安装并登录的 Azure CLI
- 环境中已存在解决方案(如有需要,请先运行
setup-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 "." \
--envUrl "{envUrl from .solution-manifest.json or pac env who, if available}" \
--token "{token, if Phase 1 already acquired one}" \
--solutionId "{solutionId from .solution-manifest.json, if available}"
辅助程序会返回包含 { exists, deferred, 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会创建计划 — 检测项目状态,询问你的提升策略(PP Pipelines 还是手动导出/导入),并按正确顺序编排相应技能(包括此技能)。现在要我运行 plan-alm 吗?”
🚦 门控(意图 · export-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。”
🚦 Gate(意图 · export-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。 - 取消 → 正常退出。
此关卡存在的原因。 直接调用 export-solution 会生成 zip,但不会执行编排器的导出前完整性检查。单独运行此技能的用户常会漏掉本应添加到解决方案中的组件(云端流、网站设置引用的环境变量值、示例数据引用),导致生成的 zip 能顺利导入暂存环境,却在部署后造成网站故障。导出前的计划完整性检查会在生成任何 zip 之前指出这些缺漏。此关卡可确保 plan-alm 已运行(因此已验证完整性,并且导出范围对应正确的解决方案沿袭),或者用户已明确选择跳过检查。
Phase 1 — 验证前置条件
在此阶段开始时创建所有任务。
要创建的任务:
- “验证前置条件”
- “识别解决方案”
- “配置导出”
- “触发异步导出”
- “下载解决方案 zip”
- “验证导出”
- “呈现摘要”
步骤:
- 运行
verify-alm-prerequisites.js并使用--require-manifest,确认 PAC CLI 身份验证,获取令牌,验证 API 访问权限,并确认.solution-manifest.json存在:
将输出捕获为 JSON;提取node "${PLUGIN_ROOT}/scripts/lib/verify-alm-prerequisites.js" --require-manifest.envUrl(存储为envUrl)和.token(存储为token)。如果脚本以非零状态码退出,则停止并说明缺少的内容(参考${PLUGIN_ROOT}/references/dataverse-prerequisites.md)。
Phase 1.5 — 以当前 ALM 文档为依据
参考:
${PLUGIN_ROOT}/references/alm-docs-grounding.md
此步骤最多耗时约 30 秒。如果 MCP 搜索或获取出错,记录一行说明并继续 — 此技能必须能在离线状态下运行。
- 使用查询
Power Pages solution export managed unmanaged ExportSolutionAsync 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对比,并标记ExportSolutionAsync/DownloadSolutionExportData签名的任何差异。 - 根据此摘要为 Phase 2 及后续阶段的决策提供依据。不要悄然改变技能行为 — 在 Phase 3 之前向用户指出任何差异,并将其作为非强制警告。
第 2 阶段 — 识别解决方案
🚦 关卡(计划 · export-solution:2.identify): 项目根目录中没有
.solution-manifest.json— 用户必须在继续导出前选择或粘贴解决方案唯一名称。仅在「未找到」分支中触发(下方第 3 步)。触发条件: 第 2 阶段第 1 步未找到清单。 询问原因: 自动选择错误的解决方案,会导出包含错误表/站点/流集合的托管 zip 并部署到暂存环境。 取消后状态: 无 — 尚未调用 ExportSolutionAsync。
- 在项目根目录中查找
.solution-manifest.json(使用findProjectRoot或glob('**/.solution-manifest.json')) - 如果找到:读取
solution.uniqueName、solution.solutionId、environmentUrl- 验证环境 URL 是否匹配(如果不同则警告 — 可能是跨环境导出)
- 如果未找到,使用
AskUserQuestion选择解决方案:- 查询 Dataverse,获取可用的非托管解决方案并将其作为选项展示
- 提供自由文本备用选项(「其他」),以便直接粘贴唯一名称
- 确认解决方案在环境中存在:
GET {envUrl}/api/data/v9.2/solutions?$filter=uniquename eq '{solutionName}'&$select=solutionid,uniquename,friendlyname,version,ismanaged - 展示解决方案详细信息并请用户确认。
第 2.5 阶段 — 导出前完整性检查
导出前,运行共享站点清单辅助程序,检测站点上存在但不在解决方案中的组件。此处发现问题可避免将不完整的包部署到暂存/生产环境。
node "${PLUGIN_ROOT}/scripts/lib/discover-site-components.js" \
--envUrl "{envUrl}" --token "{token}" \
--siteId "{websiteRecordId}" \
--publisherPrefix "{publisherPrefix from .solution-manifest.json}" \
--solutionId "{solutionId}" \
--projectRoot "."
解析标准输出并评估 missing。在进行任何其他操作之前,先捕获同步前状态,以便后续的同步后重新确认关卡展示发生了哪些变化:
PRE_SYNC_VERSION = solutionManifest.solution.version // from .solution-manifest.json read in Phase 2
PRE_SYNC_MISSING = { siteComponents, siteLanguages, cloudFlows, envVarDefinitions, customTables, ... } // from the discovery stdout above
然后:
-
所有
missing.*数组均为空 → 报告「解决方案内容与站点一致 — 未发现缺失项。」继续进入第 3 阶段。 -
任意
missing.*数组非空 → 展示简明摘要:「解决方案中缺少 {N} 个站点上存在的组件:
- {X} 个站点组件(例如 {前 3 个名称},…)
- {Y} 个云流
- {Z} 个带有你的发布者前缀的环境变量定义
- {W} 个自定义表」
🚦 关卡(进度 · export-solution:2.5.completeness): 源解决方案相对于实时站点不完整。先同步、按原样导出(记录缺失项),或中止。
然后通过
AskUserQuestion询问:「你希望如何继续?
- 现在以同步模式运行
/power-pages:setup-solution— 纳入缺失组件、递增解决方案版本,然后在导出前再次向你确认(推荐) - 按原样导出 — 部署当前解决方案中的内容;缺失组件不会随包一起部署
- 中止 — 我想先调查,再进行导出」
-
选项 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)。 - 重新读取
.solution-manifest.json并捕获POST_SYNC_VERSION = solutionManifest.solution.version。 - 重新运行发现辅助程序。如果仍有任何
missing.*非空,则重复上述第 2.5 阶段提示。 - 将
NEWLY_ADOPTED计算为PRE_SYNC_MISSING与第二次发现运行的missing.*按类别进行集合差异比较(消失的项目即为 setup-solution 刚刚纳入解决方案的项目)。总数 = 所有类别长度之和。
🚦 关卡(进度 · export-solution:2.5.post-sync): 同步后重新确认。解决方案版本已递增且组件已纳入 — 用户检查差异后,方可继续导出。
-
继续进入第 3 阶段前,先与用户重新确认 — 即将导出的解决方案已不同于用户开始导出时看到的版本。使用
AskUserQuestion:"同步完成。
{solutionUniqueName} 现在为 v{POST_SYNC_VERSION}(之前为 v{PRE_SYNC_VERSION}),新增纳入了 {NEWLY_ADOPTED.total} 个组件:
- {按类别列出排名前 3-5 的名称 — 优先选择信号较强的类别:云流、服务器逻辑、环境变量定义,然后是站点组件}
- {如果还有更多:
+ {N} more across {category list}}
即将把此更新后的解决方案导出为 zip 文件。
继续导出吗?"
问题 标题 选项 继续导出吗? 同步后审批 是 — 导出 v{POST_SYNC_VERSION}(推荐),暂停 — 我想先查看新解决方案的内容,取消 — 中止导出 - 是 → 使用同步后的解决方案继续执行阶段 3。
- 暂停 → 正常退出 export-solution,并附上简短说明("同步后已暂停。准备导出 v{POST_SYNC_VERSION} 时,请重新运行
/power-pages:export-solution。"),以便用户检查同步后的清单 / Dataverse 状态,并手动恢复。不要写入任何导出产物 — 尚未执行导出。也跳过技能跟踪调用。 - 取消 → 停止技能。适用相同的不生成产物 / 不进行跟踪规则。
- 调用
-
选项 2 — 在导出清单中记录这一缺漏(见阶段 7 摘要),以便用户留存有审计记录,了解哪些内容是有意排除的。
-
选项 3 — 停止技能。
为何需要同步后关卡:当同步模式在导出过程中运行时,会生成一个与用户调用技能时预期不同的解决方案版本。在同步后再次确认,用户便有明确机会在生成 zip 并(通常通过
import-solution)继续交付之前,检查版本升级以及新增纳入的组件列表。阶段 2.5 的触发时机是有意安排的;同步后的再次确认则是在此基础上的安全措施。这与deploy-pipeline阶段 3.5 中的同一关卡保持一致 — 结构相同、选项相同、审计跟踪规则相同 — 因此无论用户选择 PP Pipelines 路径还是手动导出 / 导入路径,都能获得一致的行为。
最初为何需要阶段 2.5:过去,在
setup-solution之后创建的组件(来自add-server-logic的服务器逻辑、来自add-cloud-flow的流、来自configure-env-variables/setup-auth的环境变量)会被悄悄排除在导出 zip 之外,因此不会迁移到目标环境。AGENTS.md中的默认支持 ALM 原则要求在解决方案离开其源环境的每个关卡都执行此检查。
阶段 3 — 配置导出
🚦 关卡(同意 · export-solution:3.export-type): 托管与非托管——对生成的 zip 不可逆。托管解决方案无法在目标环境中编辑;非托管解决方案可以。与阶段策略不匹配会导致下游收到错误的制品。
立即调用 AskUserQuestion——不要在聊天文本中描述此选项。导出继续前,用户必须实时作答。
| 问题 | 标题 | 选项 |
|---|---|---|
| 您希望如何导出此解决方案?托管解决方案无法在目标环境中编辑,并支持干净的升级/删除周期——推荐用于预发布和生产环境。非托管解决方案可以在目标环境中编辑——用于开发环境之间的部署。 | 导出类型 | 托管 — 用于预发布/生产环境(推荐),非托管 — 用于开发环境 |
根据回答,在 ExportSolutionAsync 请求正文中设置 "Managed": true 或 "Managed": false。
🚦 关卡(计划 · export-solution:3.overwrite): 生成的 zip 的输出目录,以及覆盖还是使用新名称的决定。如果在目标路径检测到现有 zip,提示将提供覆盖 / 选择新名称 / 取消选项。
触发条件: 阶段 3 中选择托管/非托管后。 询问原因: 自动覆盖会替换可能已经经过手动测试的先前导出文件。 取消后保留: 无——不会写入 zip。
还要通过 AskUserQuestion 询问:
- 输出目录(默认:当前项目根目录)
- 如果解析后的输出路径中已存在 zip:"覆盖 / 选择新名称 / 取消"
阶段 4 — 触发异步导出
步骤 4.0 — 提升源解决方案版本(始终执行)。
导出前,提升源解决方案版本的补丁段(第 4 段)。如果不这样做,连续两次导出之间没有执行 setup-solution 同步,就会生成版本字符串相同的 zip 文件——将第二个 zip 导入已安装第一个 zip 的目标环境时,对于托管解决方案而言并不可靠(没有干净的升级路径);对于非托管解决方案,则取决于 OverwriteUnmanagedCustomizations: true。
为什么始终执行,而不是“仅在同步模式添加了组件时执行”:
setup-solution只有在需要添加新组件时才会提升版本。用户修改了解决方案中已有组件的内容(网页模板、站点设置值、网页文件),然后重新导出时,仍然必须获得严格递增的版本标签——否则,手动导出/导入流程会悄悄发布版本过时的 zip 文件。请参阅setup-solution阶段 4 中的Why this step exists提示框。
node "${PLUGIN_ROOT}/scripts/lib/bump-solution-version.js" \
--envUrl "{envUrl}" \
--token "{token}" \
--uniqueName "{solutionUniqueName}" \
--projectRoot "."
将输出捕获为 JSON;将 .previous 存储为 PRE_EXPORT_VERSION,将 .next 存储为 EXPORT_VERSION,并检查 .manifestUpdated / .manifestUpdateReason,确认清单同步成功。报告:“已将解决方案 {solutionUniqueName} 的版本从 v{PRE_EXPORT_VERSION} 提升至 v{EXPORT_VERSION},以供导出。”
--projectRoot "." 会在提升版本的操作中,原子地更新 .solution-manifest.json 的 solution.version(单解决方案)或匹配的 solutions[].version(多解决方案)字段——无需单独执行 Edit 步骤。如果清单不存在或没有匹配的条目,manifestUpdated: false,且 manifestUpdateReason 会说明原因(no-manifest、no-matching-entry 等);版本提升本身仍然成功。
如果本会话早些时候已提升版本(例如,
setup-solution在阶段 2.5 以同步模式运行,采用了组件并提升版本,然后返回此处):仍会运行此帮助程序并再次提升版本。这是有意为之——同步时的版本提升对应新组件;导出时的版本提升对应生成的 zip。两者是相互独立的事项,重复提升的成本很低(只多一个补丁段)。这里有意不添加“清单版本已与实时源版本匹配”时跳过步骤的逻辑;否则会出现一类故障:“我修改了内容,但不需要同步,也没有提升版本,因此导出发布了过时版本”。
步骤 4.1 — 触发异步导出。
运行 scripts/lib/export-solution-async.js,以 POST 方式调用 ExportSolutionAsync,轮询直至进入终止状态,并返回 AsyncOperationId:
node "${PLUGIN_ROOT}/scripts/lib/export-solution-async.js" \
--envUrl "{envUrl}" \
--token "{token}" \
--solutionName "{solutionUniqueName}" \
--managed {true|false}
将 stdout 捕获为 JSON;提取 .asyncOperationId(存储为 asyncOperationId)。
报告:“导出任务已启动。正在轮询等待完成……”
处理脚本退出代码:
- 退出代码 0:任务成功 — 使用
asyncOperationId继续阶段 5 - 退出代码 1:stderr 包含失败消息 — 报告消息并停止
- 超时 / 轮询次数用尽:告知用户导出仍在运行,并建议检查管理中心
阶段 5 — 下载解决方案 Zip
运行 scripts/lib/download-export-data.js,以 POST 方式调用 DownloadSolutionExportData,解码 base64 zip,并将其写入磁盘:
node "${PLUGIN_ROOT}/scripts/lib/download-export-data.js" \
--envUrl "{envUrl}" \
--token "{token}" \
--asyncOperationId "{asyncOperationId}" \
--outputPath "{outputDir}/{SolutionUniqueName}_{managed|unmanaged}.zip"
将 stdout 捕获为 JSON;提取 .zipPath(存储为 zipPath)和 .fileSizeBytes。
报告:“正在下载解决方案 zip……”
处理脚本退出代码:
- 退出代码 0:已写入 zip — 使用
zipPath和fileSizeBytes继续阶段 6 - 退出代码 1:stderr 包含失败消息 — 报告消息并停止
阶段 6 — 验证导出
- 确认 zip 文件存在于磁盘上:检查
fs.existsSync(zipPath) - 确认文件大小大于 1000 字节
- 验证 zip 中包含
Solution.xml:- 运行
unzip -l "{zipPath}" | grep -i solution.xml,或通过 Node.js 读取 zip 的目录表(使用Bash和 unzip) - 如果未找到 solution.xml:报告错误 — zip 可能已损坏,或下载不完整
- 运行
阶段 7 — 展示摘要
步骤 7.1 — 写入 docs/alm/last-export.json 标记文件。
确保 docs/alm/ 存在,然后写入标记文件,以便下游技能(import-solution 偏差提示、渲染手动路径标签页的 refresh-alm-plan-data.js、未来的“自上次导出以来已修改”门禁、审计跟踪)能够判断此源上次发布的内容。
node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"
然后写入 docs/alm/last-export.json:
{
"exportedAt": "<ISO timestamp>",
"solutionUniqueName": "<solutionUniqueName>",
"solutionId": "<solutionId from .solution-manifest.json or Phase 2 query>",
"previousVersion": "<PRE_EXPORT_VERSION from Step 4.0>",
"version": "<EXPORT_VERSION from Step 4.0>",
"managed": <true|false>,
"sourceEnvironmentUrl": "<envUrl>",
"zipPath": "<zipPath>",
"fileSizeBytes": <fileSizeBytes>,
"asyncOperationId": "<asyncOperationId>"
}
此路径已在 scripts/lib/alm-paths.js 中注册,键为 lastExport — 程序化使用方应通过 almPath(projectRoot, 'lastExport') 解析,而不是重新内联路径字符串。(为便于阅读,技能正文会原样内联该路径,这与 last-deploy.json、last-import.json 和其他 ALM 标记文件的惯例一致。)
步骤 7.2 — 显示摘要。
| 项目 | 值 |
|---|---|
| 解决方案 | {solutionUniqueName} v{EXPORT_VERSION}(原为 v{PRE_EXPORT_VERSION}) |
| 导出类型 | 托管 / 非托管 |
| 文件 | {zipPath} |
| 文件大小 | {size} KB |
| 导出作业 | {AsyncJobId} |
| 已写入标记文件 | docs/alm/last-export.json |
建议的后续步骤:
- 运行
/power-pages:import-solution,将此 zip 部署到另一个环境 - 运行
/power-pages:setup-pipeline,在 CI/CD 中自动执行此流程
记录技能使用情况
参考:
${PLUGIN_ROOT}/references/skill-tracking-reference.md
按照参考文档中的技能跟踪说明,记录此技能的使用情况。使用 --skillName "ExportSolution"。
刷新 ALM 计划(如果存在)
node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
--projectRoot "." \
--phase export-solution \
--render
重新渲染 docs/alm-plan.html,使 Agent 在此技能执行期间所做的步骤状态更新(Export solution → status-completed)能够同步显示。如果不存在 docs/.alm-plan-data.json(独立调用,不属于 ALM 计划),该辅助程序会返回 ok:false,并作为软性空操作安全退出 — 可无条件运行。
向用户指出下一步(由用户决定执行顺序)。 辅助程序的标准输出 JSON 包含 nextStep: { name, skill: string | null } | null。当其值非空时,根据 skill 分支处理:当 skill 非空时,告知用户 “计划已更新。计划中的下一步:{nextStep.name} → 准备就绪后运行 {nextStep.skill}。”;当 skill 为 null(例如 Finalize 这样的内部步骤,没有用户命令)时,只列出步骤名称 — “计划已更新。计划中的下一步:{nextStep.name}。” — 绝不要输出 run null。(通常是先检查导出的 zip,然后运行 /power-pages:import-solution 导入到第一个目标环境。)当 nextStep 为 null 或辅助程序返回 ok:false 时,不要提及下一步。绝不自动调用下一项技能 — 执行由用户决定。
关键决策点(等待用户)
- 阶段 2:识别解决方案 — 在触发导出前确认
- 阶段 2.5:完整性缺口提示(优先同步 / 按当前状态导出 / 中止),当实时站点包含解决方案中缺失的组件时触发
- 阶段 2.5:同步后审批门禁 — 仅在导出过程中执行中途同步(选项 1)后触发。显示新的解决方案版本和新纳入的组件,并要求用户在导出 zip 前确认同步后的解决方案。暂停会正常退出;取消会中止。
- 阶段 3:托管还是非托管 — 会影响下游导入能力(本次导出中不可撤销的选择)
- 阶段 4 步骤 4.0:不提示用户 — 版本递增会在
ExportSolutionAsync前自动执行。递增后的版本(PRE_EXPORT_VERSION → EXPORT_VERSION)会显示在阶段 7 摘要中,以便用户查看 zip 中包含的版本。
错误处理
- 如果导出作业失败:显示异步操作中的
message和friendlyMessage - 如果下载返回的
ExportSolutionFile为空:报告错误并建议重新导出 - 绝不自动重试 — 报告失败并由用户决定
进度跟踪表
| 任务主题 | 进行中的任务 | 描述 |
|---|---|---|
| 验证先决条件 | 正在验证先决条件 | 确认 PAC CLI 身份验证,获取 Azure CLI 令牌,验证 API 访问权限 |
| 确定解决方案 | 正在确定解决方案 | 读取 .solution-manifest.json 或询问用户,确认环境中存在该解决方案 |
| 配置导出 | 正在配置导出 | 询问用户:托管还是非托管、输出目录 |
| 触发异步导出 | 正在触发异步导出 | 通过 bump-solution-version.js 提升源解决方案版本(步骤 4.0),使 zip 包含严格递增的版本标签;POST ExportSolutionAsync,捕获 AsyncJobId,轮询直至完成 |
| 下载解决方案 zip | 正在下载解决方案 zip | POST DownloadSolutionExportData,解码 base64,将 zip 写入磁盘 |
| 验证导出 | 正在验证导出 | 确认 zip 存在、大小 > 0,且其中包含 Solution.xml |
| 显示摘要 | 正在显示摘要 | 写入 docs/alm/last-export.json 标记(通过 alm-paths.js);显示 zip 路径、大小、类型、版本提升信息和建议的后续步骤 |