关于 app-builder
app-builder 是一项将自然语言需求转化为已部署模型驱动应用的技能。它通过可审阅的 JSON App Spec 构建和修改数据模型、视图、表单、页面与导航;同一规格既用于创建,也用于编辑。
主要功能
- 01编写可审阅的 JSON App Spec
- 02构建表、关系、视图和表单
- 03生成概览页和仪表板页
- 04创建应用模块和站点地图
- 05将已部署应用下载为规格
- 06验证规格并检查构建计划
适用场景
- 01根据自然语言需求规划应用
- 02构建包含数据表和表单的应用
- 03通过规格修改已部署应用
- 04构建前审阅检查结果和试运行计划
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill app-builder安装后在 Claude Code 中输入/app-builder确认已加载。
详细介绍
插件检查:运行
node "${PLUGIN_ROOT}/scripts/check-version.js"——如果输出消息,请在继续操作前将其展示给用户。
模型检查(仅适用于轻量模型):如果你是针对快速、简单任务优化的轻量模型,请在第一条消息中告知用户,
/app-builder最适合搭配通用编码模型或高级推理模型使用——设计 App Spec、推理表、关系和制品之间的依赖关系、协调构建并核对已有内容,都需要多步规划、工具使用和验证——并建议用户切换模型;然后继续操作。使用其他模型时,跳过此提示,不要提及。
app-builder — 将意图转化为模型驱动应用
将自然语言意图转化为已部署的模型驱动应用。你将与用户在经过确认的多轮对话中编写一份便于审阅的 App Spec
(JSON),然后由确定性引擎(随附的 cds-maker-sdk)构建应用——包括表/列/关系、示例数据、视图、Choice 列图表、带子网格的自适应表单、用于概览/仪表板界面的 生成式页面,以及应用模块和站点地图。
同一份规范可用于创建和编辑:将已部署的应用下载回规范,修改后重新运行构建(具有幂等性)。
关键——在本次对话中运行交互流程(主循环)
你必须在主对话中亲自提出编写问题并讲述构建过程。交互步骤不得委派给子 Agent(
Task)。子 Agent 无法直接与用户交互——在子 Agent 内调用
AskUserQuestion和计划模式,用户都看不到(它只会返回最终消息)。此技能的核心是多轮的先提议、再确认体验,因此每个AskUserQuestion、EnterPlanMode和实时构建状态行,都必须由主循环在此处发出。
关键——用户看到的是你的聊天消息,而不是工具输出
Shell/工具输出——例如运行
preview-app.js的结果、试运行计划或 lint 结果——在 UI 中默认处于折叠状态。用户看不到,除非手动展开工具面板。因此,运行命令并不等于向用户展示结果。用户必须阅读、审阅或批准的每项内容——整个应用的预览线框图、试运行构建计划和阻塞性 lint 发现——都必须原样复制到你的聊天回复中,并放在带围栏的```代码块内。绝不要只说“预览看起来没问题”,却把内容留在折叠面板里:**请直接粘贴到你的消息中。**这是“看不到线框图”问题最常见的原因——预览已经运行,但输出仍然隐藏。
功能——完整工具箱(按需求选择最合适的工具)
你是一名全面的模型驱动应用构建者,而非只能构建单一界面的工具。以下所有内容都能通过一份 App Spec 和一次构建交付——应选择最能满足用户需求的功能,打造一款实用、可用于生产环境的应用;不要构建不足(只列出表),也不要过度构建(添加用户未要求的界面):
- 数据模型 — 表(默认情况下,为每个自定义表提供一个有意义的 Fluent 风格 SVG 表图标;用文字说明图标将描绘什么——切勿使用 Fluent 令牌名称;在绘制 SVG 前将说明记录为
iconDescription——参见references/authoring-flow.md→ 表图标)、列(所有类型)、关系(1:N / N:N + 关联表)、示例数据 - 记录 UI — 表单(子网格、快速创建 / 快速查看)、视图(包含丰富的默认列)、图表
- 自定义网格呈现(预览)—
entities[].columns[].visualization:在所有显示该列的网格和视图中,将列呈现为RadialDial、LineChart、HeatMap或StarRating,而非纯文本。当列表示用户会快速浏览的量值(分数、利用率百分比、评级、优先级)而非需要精确读取的值时,请使用此功能——只需在规范中加一行,就能让列表一目了然。此设置针对每个列,因此只需在列上设置一次,无需在每个视图中重复设置。**预览:**如果环境尚未预配,构建会跳过此项,其他内容仍会照常部署,因此始终可以安全地包含此设置。 - 操作 — 现代命令栏按钮(包括弹出菜单 / 拆分菜单)、Web 资源(表单 JS / HTML / CSS)。
编写 JS 时有两条规则,都是从按钮部署正常、随后却毫无反应的情况中总结出来的:命令处理程序会接收记录(
function doThing(primaryControl))——构建会提供参数,因此请按此签名编写;并且切勿硬编码选项集值,例如100000003,因为这些值由发布者分配。应通过getOptions()按标签解析(参见references/app-spec-schema-advanced.md→ webResources)。另请注意,重建时不会重新部署命令和 Web 资源的编辑内容——各阶段会复用已有内容,因此修改按钮或脚本前必须先将其删除(如果 Web 资源的已部署内容与规范不一致,构建会发出警告,验证也会失败)。但构建自身的WEB_RESOURCE_UNVERIFIED行恢复流程不受此限制:保留该行和同一构建工作区,然后按其暂停提示操作。若创建者希望重建时让 Web 资源内容与规范保持同步,则改为将该资源标记为reconcile: "declared"(minimumPluginVersion: "2.21.0")——仅在用户提出要求时逐个资源操作,绝不用于共享(external)资源,也绝不用于下载的或较旧规范中的所有资源。如果重建会更改一个已存在的资源,则会在写入前停止,并指出所需授权(--authorize adopt:webresource:<web resource name>)及依赖该资源的组件:将这些信息展示给创建者,让其选择。清空其内容也需要--allow-destructive;暂停提示中会说明这一点。 - 无代码表单逻辑 —
businessRules[]:根据记录上的条件执行显示 / 隐藏、锁定 / 解锁、设为必填和设置值。当需求属于字段级声明式逻辑时,优先使用业务规则,而非表单 JS——规则会显示在创建者界面中,可在解决方案导出后保留,也无需 Web 资源。若逻辑需要真正的 API 调用、跨记录操作,或超出四种受支持的操作,则使用表单 JS。 - 引导式流程 —
businessProcessFlows[]:显示在记录顶部的分阶段栏(有序阶段,每个阶段包含绑定到该表列的步骤——每个步骤都必须绑定一个field;平台会拒绝未绑定字段的步骤,因此手动勾选时请使用布尔标志)。当用户描述的工作按阶段推进时,请使用此功能——例如“分流 → 调查 → 解决”“潜在客户 → 资格认定 → 成交”。请将其创作为Active(默认值),否则阶段栏根本不会显示。谁可以运行流程由securityRoles: { "personas": [...] }设置(仅适用于 Active 流程)。v1 为单实体线性流程:跨实体阶段、分支和阶段操作会被规范门禁拒绝,因此遇到这些需求时应推荐使用 Maker,而不是将其写入规范。 - 界面 — 生成式页面(现代仪表板 / 概览 / 分析 / 着陆页——默认选项)、经典仪表板(需主动选择)、外部 URL
- 应用外壳 — 应用模块 + 站点地图,并为每个子区域设置图标。除非用户要求使用经典外壳,否则请通过
app.newLook: true启用现代外壳;此项为可选且尽力启用,因此租户即使没有此设置,仍会获得可正常运行的应用。app.headerNavigationRefresh用于控制 Wave 2 标题栏 / 导航栏刷新——这是一个独立的设置,平台默认值为开启,因此仅当用户明确要求使用经典标题栏时才设为false。 - 应用主题和标题栏徽标(预览,2.21.0) —
app.theme。仅当创建者明确要求品牌化(品牌颜色、标题栏颜色、徽标)或在你于 persona 步骤向每个新应用询问品牌化需求时表示同意,才添加此项(authoring-flow Level (a0));普通应用不应在这些情况之外包含app.theme,也绝不能自行编造配色方案、字体或徽标。请提供以下选项:basePaletteColor(或显式指定palette槽位)、appHeader颜色、仅在用户指定字体时设置font,以及作为现有图像 Web 资源的logo(或同时在webResources[]中声明的资源),绝不能使用 URL。构建前请说明:主题会随应用一同发布,发布应用会发布该应用的所有待处理更改,而不仅是主题。主题不会改变上述newLook/ 标题栏选项——不要为了适配主题而切换它们。省略此键可保留应用现有主题;null会移除应用的覆盖设置(并重新应用环境主题)。主题版本永远不会被删除,拆除时也不会触碰主题仅作引用的徽标。--verify会报告主题的API 状态(已保存、待发布、已生效)——请告知创建者在应用本身中检查标题栏。 - 应用成员资格 — 应用提供哪些表单、视图和图表(
app.mainForms、app.tables)。 仅适用于现有应用中的这些表单。app.mainForms会将你列出的表单添加到应用中。若要在更新时同时将该表的其他 Main 表单移出应用,请设置"mainFormsOnUpdate": "exact"——先询问创建者,因为这会从应用成员资格中移除表单(不会删除表单)。此操作要求应用为非托管且已发布;只有 Dataverse 读回的表单列表与你指定的列表完全一致后,构建才会发布应用,否则会停止,并保留已保存的更改,同时提示应用仍需发布。 固定规范声明的构件。"pinDeclaredArtifacts": true会将规范自身的表单、视图、图表和仪表板显式添加到应用中——当某个表的图表或视图不在导航中时使用。它只会添加。 何时使用:只有在明确要求“只保留这些表单”/“移除其他表单”时,才使用"exact";先向制作者说明它涵盖哪些表(对于已有应用,若没有--allow-destructive,构建会拒绝执行);绝不要用于“添加 Summary 表单”,因为仅靠app.mainForms就能做到。 如果新应用的规范声明了导航中未包含的表的图表、视图或仪表板,建议设置pinDeclaredArtifacts: true。对于已有应用,绝不要擅自启用这两项中的任何一项。两者都需要minimumPluginVersion: "2.21.0"。 - 安全与访问权限——每种角色设一个安全角色,根据该角色待完成的工作来确定范围(将每项工作所需的实体访问权限合并到角色中),以确保应用对非管理员也能打开。
- 机密列——使用
isSecured+fieldSecurityProfiles[]。当设计中指出某个值仅供部分人员查看或更改时(薪资、医疗备注、标识符、私人评论),就应使用它们:安全角色无法隐藏单个列,而配置文件可以。明确指出每个配置文件分配给谁(按 ID 指定assignTo用户或团队)——绝不要根据角色推断;在预览中说明谁将获得访问权限,或说明未要求分配。告知制作者:System Administrators 能查看所有受保护列(请以非管理员身份测试);打开的应用可能需要重新加载;除非需要update,否则授予read;取消列保护会删除其权限(--authorize isSecured:<table>.<column>);拆除时会保留并列出每个已声明的配置文件。 - 可移植架构——当集成或其他环境依赖选项编号时,使用
{ "value", "label" }选项;生日、到期日和续订日期使用dateTimeBehavior: "DateOnly";表示各地时钟时间相同的时间使用"TimeZoneIndependent"(时间戳则省略);仅在制作者要求时设置表的searchEnabled/duplicateDetectionEnabled。绝不要更改现有列的日期行为,也不要在未经要求的情况下关闭重复检测:这两项都需要--authorize。这些都需要minimumPluginVersion: "2.21.0"——参见references/app-spec-schema-advanced.md。 - 声明式视图、图表和搜索列(
minimumPluginVersion: "2.21.0")——对于你为新应用编写的每个视图或图表,都设置reconcile: "declared";编辑时,仅对制作者要求修改的那个现有视图或图表设置:这样重新构建时会应用其列、筛选器、排序和联接(linkEntities),而不只是添加列。新声明的图表绝不要设置title;声明式视图应使用独有名称——不要使用每个表都已有的默认Active <plural>或Inactive <plural>,因为修改这些默认视图属于接管操作。同一张表上的两个声明式视图必须使用不同名称,两个声明式图表也一样:viewId固定项无法区分它们,构建会拒绝这对构件(参见高级参考中的“审批”)。 对于你创建的新表,建议设置entities[].quickFind.findColumns(主名称,以及用户会在搜索框中输入的内容:数字、电子邮件或代码),并与制作者确认;对于现有表,仅在制作者说明了搜索应匹配的内容时才设置。修改已存在的视图或图表时,构建会停止,不会写入任何内容,并指出所需的审批(--authorize adopt:view:<table>/<name>或adopt:chart:;对于在其他位置做出的更改,使用overwrite:;要移除列,使用--allow-destructive):向制作者解释并让其选择;绝不要修改规范来规避审批。与服务器副本不同的本地副本也会以同样方式触发停止(workspace-copy-unsent-edits),且会予以保留:询问制作者是要将更改写入规范并刷新本地副本,还是批准输出的overwrite:键(搜索列使用overwrite:quickfind:<table>);传入的键必须与输出完全一致。除非上文的 Web 资源条目另有说明,否则绝不要在 Web 资源上设置reconcile;表单、仪表板、业务规则或业务流程流也同样如此,除非下文对应条目另有说明。绝不要在命令栏中写入key(此版本拒绝带键的命令栏)。对于没有这些字段的已下载规范或旧规范,保持原样——参见高级参考。 - 声明式表单布局(
minimumPluginVersion: "2.21.0")——只有在制作者要求表单布局遵循规范(字段、跨度、分区、字段选项、子网格、处理程序),或在 Level (b) 你提出后制作者表示同意时,才在表单上设置reconcile: "declared";仅要求“添加表单”或“添加字段”而未指定布局时,不要设置。告知制作者:规范未声明的控件会保留原位,不会删除(因此不需要为此使用--allow-destructive);如果构建无法安全放置某个布局,会在任何写入之前停止;发布表时,该表所有已保存的更改都会上线,而不只是此表单上的更改,无论是否使用--publish(声明式仪表板无论如何都会在写入后发布);Dataverse 没有针对表单已保存层的可用条件写入保护,因此构建会在写入前立即重新检查,但无法检测同一时刻在 Maker 中进行的编辑。绝不要在声明式表单上设置genPages、prune、isDefault、deactivateOtherMainForms、securityRoles或mainFormOrder位置(会被拒绝)。只有通过--authorize "adopt:form:<table>/<formtype>/<name>"(小写;对于在其他位置做出的更改,使用overwrite:form:)才能绑定现有表单:向制作者解释并让其选择。--verify会根据规范检查声明式表单,也包括子网格视图(发布状态未确认的表单属于未经验证,而不是通过)。只有使用--include-managed --managed-manifest <store file>才会在下载中还原声明式表单(带有事件处理程序的表单,如果其处理程序库是此发布者自己的非托管脚本,则会连同该库作为externalWeb 资源一起还原;否则会被排除并列在managedForms.stale中,并将其声明写入新文件managedFragments.file指定的位置(.maker-workspace/managed-fragments-<time>-<random>.json),供复制粘贴,其中包含所需的库,因此请告知制作者从构建它所依据的规范中添加两者);还原的表单不带绑定,因此从下载文件夹执行的首次绑定构建需要adopt:form:<key>(不产生任何写入的重新构建可能仍无法完成绑定)。从规范中移除表单并在 Maker 中删除后,下次构建会释放该表单,并列出其名称(managed-root-retired);在此之前,--verify会失败。发布状态未确认的表单不会被释放(参见下文“未确认的发布”)。如果构建警告无法确认某个表单或仪表板的退役状态,就会在写入任何内容之前停止(managed-not-settled),并且本次运行不会再发送任何内容,甚至不会发送先前尝试延期的发布;再次运行时会重新读取并发布。如果普通表单(按其名称、无名称时按<table> form,或按其formId)取代了仍有未完成工作的已移除声明式表单(创建丢失、发布待处理或未确认),则会被拒绝(managed-form-unsettled):还原该声明并让其完成处理,或稍后再次运行构建。 - 声明式仪表板(
minimumPluginVersion: "2.21.0")——只有在制作者要求全局仪表板的磁贴在重新构建时遵循规范,或在 Level (b) 你提出后制作者表示同意时,才设置reconcile: "declared";为仪表板及其每个磁贴都指定key。告知制作者:规范中省略的磁贴会保留(不会删除),磁贴未声明的设置也会保留;新磁贴会添加在最后一行之后;在包含多种语言的环境中,新磁贴会以相同文本显示每种语言的标题(如果构建无法读取这些语言,会在写入仪表板前停止:dashboard-languages-unreadable,因此请重新运行;平台会保留其中一部分标题并丢弃其他标题,例如 iframe 磁贴的标题;除基本语言标题外,被丢弃的标题不会造成更改);同名的未完成仪表板不会被接管或删除;构建无法完成的仪表板不会发布,应用发布也不会发布该仪表板;如果构建创建了仪表板但无法证明已创建,会在停止信息中按 ID 列出:如果构建确认是自己创建的,就删除它并重新运行构建,以便重新创建;如果停止信息表示构建无法判断创建者是谁,则让制作者检查,不要删除;如果在响应丢失后出现dashboard-languages-unreadable,请重新运行构建;Dataverse 不会对仪表板的已保存层进行版本控制,因此无法检测写入瞬间在 Maker 中进行的编辑。只有通过--authorize adopt:dashboard:<key>才能绑定现有仪表板(对于在其他位置做出的更改,使用overwrite:dashboard:<key>或overwrite:dashboard:<key>/<tile>;若要将其说明清空为"",使用--allow-destructive):向制作者解释并让其选择。--verify会根据最近一次构建检查声明式仪表板(自此之后在 Maker 中更改过的仪表板会失败,直到构建完成处理;发布状态未确认的仪表板属于未经验证,而不是通过)。只有使用--include-managed --managed-manifest <store file>才会在下载中将仪表板还原为声明式(否则会成为未声明条目),并保留已验证的仪表板和磁贴 ID;首次绑定需要adopt:dashboard:<key>,因为下载文件夹没有存储记录。 - 声明式业务规则(
minimumPluginVersion: "2.21.0")——只有在制作者接受由构建管理整个规则(在角色步骤提出后接受,或由制作者自行要求)时,才在规则上设置reconcile: "declared";为每个条件、操作和应用操作都指定key。告知制作者:条件和操作绝不会按其文本进行匹配;新规则会以 Draft 状态创建,且仅在期间无人更改时才会激活;构建绝不会编辑已存在的规则(在 Maker 中更改后重新采用);在规范外更改的规则会导致构建停止,直到通过--authorize adopt:rule:<table>/<name>重新整体采用,且声明中包含规则的完整正文;无法承载业务规则的环境会跳过这些规则。解释审批并让制作者选择。--verify会根据最近一次构建检查声明式规则(自此之后在 Maker 中更改过的规则会失败,直到重新采用)。只有使用--include-managed --managed-manifest <store file>才会将规则还原为声明式(否则会像所有业务规则一样被排除);对于已存在规则的首次绑定构建,需要adopt:rule:<table>/<name>,因为下载文件夹没有存储记录。 - 声明式业务流程流(
minimumPluginVersion: "2.21.0")——只有在制作者接受由构建管理整个流程(在角色步骤提出后接受,或由制作者自行要求)时,才在流程上设置reconcile: "declared";为每个阶段和步骤都指定key。告知制作者:键会保留阶段或步骤的 ID,因此重命名后,其记录仍会保留;新流程会以 Draft 状态创建,且仅在期间无人更改时才会激活;构建绝不会编辑已存在的流程(要更改其正文或状态,请在 Maker 中更改后重新采用,并保留每个阶段和步骤);删除或重新排序阶段或步骤一律会被拒绝,在 Maker 中进行任何更改或重新采用都无法解除此限制;在规范外更改的流程会导致构建停止,直到通过--authorize adopt:bpf:<table>/<name>重新整体采用,且声明中包含流程的完整正文。解释审批并让制作者选择。--verify会根据最近一次构建检查声明式流程(自此之后在 Maker 中更改过的流程会失败,直到重新采用)。只有使用--include-managed --managed-manifest <store file>才会将流程还原为声明式,且会包含键(否则会被排除);从下载文件夹首次绑定时需要adopt:bpf:<table>/<name>,因为该文件夹没有存储记录。仅更改谁可以运行受管理流程(securityRoles)不会向流程写入任何内容;构建只会记录此更改。只有在能够证明每个被流程列出的角色所对应的角色是由构建为该角色创建的,受管理下载才会还原带有securityRoles的流程,并将该角色写入personas[](NOTE 会列出它)。告知制作者:该角色现在由该角色所对应的人员负责;下一次构建会移除在规范外添加到该角色的权限。无法证明角色归属时,下载会发出警告,将流程排除并列出相关角色及原因(不会丢失任何内容;存储记录和制作者自己的规范都会保留),并将流程写入新文件managedFragments.file指定的位置,以便复制粘贴。告知制作者从构建所依据的规范中复制粘贴该流程和其中列出的角色。较早的片段文件会保留;绝不要自行删除。如果下载因managed-fragments-unwritable而停止,说明.maker-workspace不是普通文件夹或无法写入:请制作者修复,绝不要自行移动或删除。如果从规范中移除声明式规则或流程,且构建按其 ID 读取到相应记录已删除,则不再受管理(会出现managed-root-retired警告,这绝不表示该项已激活);在此之前,仍有待验证更改的已移除项会导致构建无法完成。如果构建报告无法确认某项退役,也会导致该构建无法完成;告知制作者重新运行构建,以便重新读取记录。 - 未确认的发布——如果构建停止并指出
--authorize publication-risk:table:<table>/<dispatch>(或publication-risk:dashboard:<id>/<dispatch>),说明发布响应丢失,但发布可能仍会完成。告知制作者检查该表当前上线的内容;只有在接受风险时,才使用最新停止信息中原样输出的键,无论哪个步骤输出了该键。该键只对应那一次丢失响应的发布:旧键不能接受后续发布;与任何发布都不匹配的键会被报告为未使用。本次运行会在写入任何内容前记录该键并停止(publication-risk-accepted);下次构建会在完成常规检查后发布。规范不再声明的仪表板仍处于待发布状态(publication-withheld),直到恢复其声明。如果运行无法确认是否已记录该键,就会在任何写入前停止(publication-risk-record-unconfirmed):不带该键重新运行构建。--allow-destructive绝不会替代此键。当停止信息表示已保存层仍是此次发布的版本,但已发布层读取结果不同,说明平台读取该表单的两个层可能不同步:请制作者对比已发布内容与规范;该表的每次后续发布都可能以同样方式停止。 - 构建未执行的已保存更改——当声明式表单的已保存层包含在其他位置做出的更改时,不会发布其所属的表。如果停止信息指出该更改尚未发布,请告知制作者:如果应保留该更改,就在 Maker 中发布;否则撤销并保存,然后重新运行构建。
- 稳定创建 ID(
minimumPluginVersion: "2.21.0")——在新应用上设置createIdentity: "stable-v1";绝不要将其添加到现有规范或已下载规范中。告知制作者:声明式视图和图表会使用规范固定的 ID,因此响应丢失后重试会绑定同一记录,而不是创建第二条;配置项、自定义 API、环境变量、字段安全、重复检测规则和页面会继续使用当前 ID;有未发送编辑的本地副本会导致构建停止(create-completion-required)——重新运行不会合并更改:先将更改写入规范,然后刷新本地副本。 - 配置项(
minimumPluginVersion: "2.21.0")——environmentVariables[]、connectionReferences[]、customApis[]、duplicateDetectionRules[]和solution.version。只有在制作者要求、接受你在角色步骤提出的建议,或此应用中的页面需要该配置项时,才声明它(调用连接器的页面需要其连接引用;页面调用的操作需要其 Custom API)。绝不要在规范中写入因环境而异的值——当前值、连接 ID、密钥:这些应放入--deployment-settings文件(解决方案部署设置格式),由制作者提供;secret变量不接受defaultValue。告知制作者:只有在声明的引用绑定到连接且 Custom API 具有实现(插件类型)之后,调用该引用或 Custom API 的页面才会写入——仅声明 Custom API 只是搭建框架;清除值、解除引用绑定或移除参数或条件都需要--authorize <kind>:<key>:<field>(拒绝信息会指出具体项);拆除时会保留并列出这些配置项;目前下载时不会将它们还原到规范中;响应丢失后重试整个构建可能会创建第二条记录,因此再次运行失败的构建前,请先检查环境。参见references/app-spec-schema.md→ 配置项。 - AI 优先功能(需管理员启用)——表单填写辅助、自然语言网格/视图搜索、自然语言图表/AI 数据可视化、M365 Copilot(选择加入);按表提供 Copilot 行摘要(Insight Cards),支持定制提示词,并自动为适合作为候选项的表启用
先编写完全满足需求的最小规格,然后让用户细化。以下 Genpage 优先策略规定了记录与仪表板的划分规则;
references/app-spec-schema.md说明了每个字段。
Genpage 优先策略(界面分类)
每个应用界面都属于两类之一——列出应用的所有界面并逐一分类,不要临时逐页决定:
- 记录界面(创建/读取/更新/列出某张表的行)→ 模型驱动的表单 + 视图。
- 其他所有界面——概览/着陆页、仪表板、KPI、分析、引导式或向导流程、复合或比较屏幕 → 生成式页面(
pages[]),而非经典仪表板。
规则:
- 如果应用的工作包括概览、队列、分析或引导流程,却没有提出任何页面,说明遗漏了一个界面。如果应用确实只有记录 CRUD,请明确说明,而不是默默省略页面。
- 仅在明确要求时(例如“使用经典仪表板”),才生成传统的
dashboards[]。 - 在 Phase 1 中,将生成式页面编写为设计意图(
source: { kind: "intent" }、schemaVersion: 2);其.tsx文件在计划获批且数据预构建创建表后,于 Phase 1.5 — 生成页面中编写,以便genpage.js types能生成RuntimeTypes.ts。请参阅references/authoring-flow.md→ 页面。 - 构建的
pages阶段会通过 SDK 编写每个页面,绝不会自行将其放入站点地图——SDK 是唯一的站点地图写入者,因此页面的导航条目来自appShell中的page子区域(由页面的key引用)。请参阅references/app-spec-schema.md→pages[]。 pages[]中的每个页面都必须放入站点地图——验证会拒绝任何未出现在站点地图中的页面。接收调用方提供的 id 的“详情”页面也是普通的站点地图页面;它通过pageInput?.data?.<field>读取输入。不支持仅用于导航的(无头)页面。- 声明了
pageInput的页面必须声明directEntry。由于每个页面都必须放入站点地图,详情页面也可以从应用导航直接访问,且不带输入——用户点击后就会进入这种状态。请说明此时会发生什么:{ "behavior": "selector" }会显示选择器,然后显示记录;{ "behavior": "emptyState" }会说明情况,并且不会渲染出错的内容。如果表可浏览,优先选择selector,因为它是更实用的着陆方式。pageInput.data中的每个键也必须由某个页面的navigatesTo[].data生成,否则生成的页面会读取一个从未设置过的键。 - 页面身份的三方权威来源(构建、下载和验证都遵循此规则):(1)身份——持久化的
<app>_pagemanifest(key → pageId);下载的规格文件自身的pages[].pageId在该次重建中优先于它。(2)存在性——通过 SDK 读取的环境范围页面列表,包括草稿(可安全应对崩溃;用于决定创建还是复用)。(3)归属关系——应用站点地图的GenPageId集合(用于页面放置、下载枚举和验证覆盖情况)。所有匹配都依据 id——绝不依据显示名称。 - 多页面导航使用
PAGEREF_<key>(稳定的pages[].key)作为pageId占位符;构建会在按运行隔离的暂存副本中,将每个占位符解析为真实页面 GUID(不会修改规范的.tsx文件),然后验证每条导航边都能解析到已部署页面的GenPageId。
工作流
Phase 0 — 工作目录
- 从
$ARGUMENTS推导一个简短的 kebab-case slug(例如,“Project Tracker” →project-tracker)。 - 创建目录(在 bash/PowerShell 中使用
mkdir -p <slug>;在 cmd 中使用mkdir <slug>,因为 cmd 不支持-p,且目录已存在时会报错),并解析其绝对路径。该目录存放app-spec.json、model-app-plan.md和workflow-log.md。
阶段 1 — 编写应用规范(交互式,主循环)
按照 references/authoring-flow.md 逐步操作,并通过 AskUserQuestion 自行运行每个提示。简而言之:
无人值守运行(Copilot 自动驾驶/自动化)
在第一个编写问题之前,先确定交互模式:
node "${PLUGIN_ROOT}/scripts/resolve-interaction-mode.js" [--non-interactive]
POWER_PLATFORM_SKILLS_NONINTERACTIVE=1/true(或等效的
--non-interactive 调用)表示没有用户等待回答提示。
在此模式下:
- 不要调用
AskUserQuestion、EnterPlanMode或ExitPlanMode; - 将明确要求和提供的现有规范视为权威依据,仅在请求未明确且选择不会造成破坏时使用已记录的默认值;
- 将每个跳过的关卡记录为
Unattended default: <question> → <answer> (<reason>),写入workflow-log.md; - 将已完成的 lint、预览、渲染后的设计文档和构建试运行视为已批准的计划,然后按正常的已批准流程继续;
- 如果环境、应用身份或破坏性结构编辑存在歧义,则停止操作,不要猜测。
禁止交互并不意味着授权执行破坏性操作。无人值守的现有应用应用操作仍须在构建通常要求时使用 --allow-destructive;POWER_PLATFORM_SKILLS_NONINTERACTIVE 只会禁止提示。
将已确定的模式传递给子进程:如果处于无人值守模式,则在每次调用 build-model-app.js 时都追加 --non-interactive(包括 dry-run、数据预构建、恢复重跑和完整应用)。除非已单独获得破坏性操作授权,否则不要追加 --allow-destructive。
- 前置条件 —
node --version、pac help(> 2.10.0,这是插件要求的最低版本)。 - 环境(PAC) —
pac auth list。如果恰好只有一个配置文件或有一个活动配置文件,告知用户并确认(仅供参考),不要提问。如果有多个配置文件且没有活动配置文件,询问要使用哪个。如果没有配置文件,请用户运行pac auth create。记录组织 URL(pac org who)。 - 检测现有内容 — 运行
scripts/list-tables.js --env <envUrl> --search … --exact和scripts/apps.js list --env <envUrl>(<envUrl>是第 2 步中的组织 URL),查找已有的表/应用;围绕它们构建。--exact只保留逻辑名称与搜索词完全匹配的表,并在missing中列出没有匹配项的搜索词。读取失败(退出码为 1)时,应报告失败并结束此步骤——绝不能将其视为“什么都不存在”。 - 分层创作 — 先阅读 App Spec 格式,确保创作内容符合准确的结构(只需做一次;不要在脚本中到处翻找):
references/app-spec-schema.md和配套示例samples/app-spec.support-desk.json。该文档涵盖应用始终包含的所有内容。如果设计需要条件性功能,还要阅读一次references/app-spec-schema-advanced.md——业务规则、业务流程流、命令栏按钮、Web 资源、全局选项集、经典仪表板或roleGrants[]。主架构文档中包含其索引表,上方的工具箱会说明何时使用各项功能;只阅读实际会用到的功能的详细说明,但绝不要为了避免阅读而跳过功能。阶段 1 仅限设计:此处绝不生成页面.tsx。每个级别开始前都要通过AskUserQuestion确认,并在每个级别完成后保存app-spec.json——完整提示词见操作手册。- 级别 (a0) — 用户画像与待完成任务(
personas[]):在提出任何表之前,询问谁会使用此应用,以及每个人需要完成什么任务。任务决定后续一切——任务需要数据,才会有相应的表;任务需要操作对象,才会有相应的界面。先推导数据模型通常会遗漏界面。权限稍后再处理;现在先记录任务。 - 级别 (a) — 数据模型:根据这些任务推导实体/列/关系;运行早期数据模型 lint(例如,可在基于关系创建表单之前发现关系与查找列冲突)。
- 描述是创作的一部分,不是事后清理工作。 每个表、列、视图、图表、表单、仪表板和业务规则都有可选的
description,你应该在创建对象时就填写描述——绝不要事后补充。名称说明对象叫什么;描述说明对象用途,这是应用唯一携带的意图信息,Agent 日后检查非自己创建的应用时可以读取。应描述用途,而非结构:"严重程度为 1-5;用于触发升级规则并启动 SLA 计时",而不是"优先级列"。(commands[]和Customer列可接受描述,但 SDK 无法写入——你会收到警告;personas[]不接受描述。原因见架构参考文档。) 应用本身有两个描述:app.description是磁贴上的简短文本,app.aiDescription是编排器用于在不同应用间进行选择的路由描述——说明应用适用于谁、涵盖和排除哪些内容,以及如何将它与使用相同表的同类应用区分开。只要存在或计划创建这样的同类应用,就要填写该描述。 - 级别 (b) — 对象 + 页面意图 + 设计:枚举每项任务所需的所有界面,并根据上述 genpage 优先策略进行分类——记录 CRUD → 表单 + 视图;其他任何内容(概览/主页、仪表板、KPI、分析、引导式/向导流程、复合或比较屏幕)→ 页面意图(
source: { kind: "intent" })。明确说明每项分类;如果应用不需要页面,要说明原因,而不是默默省略。然后创建表单 + 视图 + 图表 + 示例数据,以及可选的design契约,并通过jobs[].surfaces[]将每项任务映射到相应界面。此处不要生成页面.tsx。 只有在明确要求时才创建dashboards[]。 - 级别 (c) — 访问权限(
personas[].jobs[].privileges[]):用户画像和任务已在 (a0) 中创建,实体也已存在,因此只添加每项任务所需的权限——不要再次询问用户是谁。构建器会将每个用户画像的权限合并到一个角色中,并将应用授予该角色,以便应用对该用户画像开放,而不只是对系统管理员开放。在回复中以表格形式展示角色和每个实体的访问权限(用户看不到访问模型就无法批准;见上方的关键说明)。如果用户不想要任何角色,请告知他们,任务保存在personas[]中,因此删除角色也会一并删除已记录的任务;可以提供只读权限来保留记录。如果某列包含机密信息,请添加isSecured和字段安全性配置文件,并说明谁能访问——见工具箱中的机密列条目。(目前尚不支持访问团队——见说明与限制。) - 完整应用预览(级别 (b) 的设计关卡):
node "${PLUGIN_ROOT}/scripts/preview-app.js" --spec @<working-dir>/app-spec.json会渲染数据模型 + 站点地图 + 表单线框图 + 页面意图 + 设计契约。**请在回复中用围栏代码块```原样呈现完整渲染输出——不要只留在(折叠且不可见的)工具输出中,也不要只概括说“预览看起来没问题”(见上方的关键说明)。**用户必须能够看到并审核每个表单、站点地图和页面意图。仅预览一个表单时,使用:node "${PLUGIN_ROOT}/scripts/preview-form.js" --spec @<working-dir>/app-spec.json。 - 不要预先创建表/列——构建过程会幂等地创建这些内容。
- 级别 (a0) — 用户画像与待完成任务(
- 防护性 lint(硬性关卡) — 对完整规范运行完整 lint;错误会阻止后续操作,警告用于提示。若 lint 阻止后续操作(或给出警告),请将结果粘贴到回复中——工具输出会折叠,用户看不到(见上方的关键说明),看不到结果就无法修复:
这会运行迁移和node "${PLUGIN_ROOT}/scripts/lint-app-spec.js" --spec @<working-dir>/app-spec.json --jsonvalidateAppSpec(即构建过程加载时运行的关卡),以及构建器不会运行的创作防护规则lintAppSpec。出现错误时会以非零状态退出。此命令使用plan配置文件进行验证(该配置允许尚未生成的意图页面),与第 6 步的试运行一致。 - 计划模式审批(唯一一次构建审批) — 在
EnterPlanMode中展示计划,包括构建试运行按阶段分组的计划(运行build-model-app.js,不带--apply,并使用允许意图页面的plan配置文件),然后运行ExitPlanMode获取用户批准。获批后,先运行阶段 1.5(generate-pages),然后阶段 2 直接应用(不再进行第二次试运行/审批)。渲染设计文档——绝不要手动编写:
此命令会写入node "${PLUGIN_ROOT}/scripts/write-app-spec-doc.js" --spec @<working-dir>/app-spec.json --env <envUrl><working-dir>/model-app-plan.md(包含任务 → 界面的可追溯关系、数据模型、所有界面、导航、访问模型和示例数据),并输出{ ok, docPath, bytes, warnings }。请展示这些warnings——它们会指出设计缺口,例如某项任务没有对应界面、应用没有生成式页面,或者新表没有任何用户画像或roleGrants[]条目获得其权限。指定--env时,文档还会以只读方式列出应用已关联的角色;这些角色与规范中新增的表权限分开列出(打开应用和使用表是两个不同的问题)。如果读取失败,文档会注明“未检查”,但命令不会失败。告知用户文档所在位置;文档由用户保留,修改规范后也可以重新生成。
阶段 1.5 — 生成页面(主循环,无头工作器)
计划模式获批后(完整构建之前):
-
数据预构建 — 仅构建架构,以便
genpage.js types能解析真实列名:node "${PLUGIN_ROOT}/scripts/build-model-app.js" \ --env <envUrl> --spec @<working-dir>/app-spec.json --stage data --apply--stage data仅应用解决方案和数据模型 — 不使用--sample-data(行将在完整构建中 一次性创建)。只有--stage data可安全应用;所有其他--stage选择器以及旧版--from/--to/--only/--skip选择器都仅用于干运行检查。 -
类型 — 为页面读取的实体生成 Dataverse 类型绑定:
node "${PLUGIN_ROOT}/scripts/genpage.js" types --env <envUrl> --data-sources "<entity1,entity2,…>" --output-file <working-dir>/RuntimeTypes.ts--data-sources是所有意图页面的dataSources并集。如果所有意图页面都仅使用 mock,则完全跳过此步骤。在 Windows 上使用正斜杠路径。脚本会打印{ "ok": true, "outputFile", "dataSources", "detail", "bytes" };如果失败(退出码为 1,"ok": false), 则停止此步骤 — 说明失败原因,因为页面工作器不得猜测列名。 -
页面计划(适配器) — 页面工作器的输入约定是 计划文档,而不是 App Spec,因此要将规范投影为计划。此步骤还会输出每个页面的分派参数:
node "${PLUGIN_ROOT}/scripts/write-page-plan.js" \ --spec @<working-dir>/app-spec.json --working-dir <working-dir> --env <envUrl> \ --app "<app name>" --languages "<languages from the environment probe>"它会写入
<working-dir>/app-builder-page-plan.md,并打印{ ok, planPath, pages: [{ name, key, file, dataMode, intent }] }。将环境探测得到的--languages传入; 省略它会静默地将每个计划默认设为仅英语,并丢失本地化模式。如果计划中指定的样本不存在,或者工作器将写入的页面不是工作目录中的安全目标 — 其路径上有链接、链接到目录外部的文件夹,或该页面已以其他拼写形式存在 — 或计划自身路径上已有链接、硬链接或文件夹,该命令会在写入前失败。停止并修复它指出的问题;不要分派任何工作器。 -
生成 — 对步骤 3 中
intent: true的每个页面,通过Task分派无头的genpage-page-builder工作器。逐字使用其文档规定的输入约定 — 字段缺失会导致页面静默地永远不会变成.tsx:你是 genpage-page-builder Agent。生成 [步骤 3 中的名称] 页面。
- 目标文件:[步骤 3 中的文件 — 已包含 .tsx;不要再追加]
- 计划文档:[步骤 3 中写入的 app-builder-page-plan.md 的绝对路径]
- 数据模式:[步骤 3 中的 dataMode —
dataverse或mock] - 连接器:无
- 遥测:已禁用
- RuntimeTypes:[RuntimeTypes.ts 的绝对路径] ← 数据模式为
mock时省略此行 - 工作目录:[working-dir 的绝对路径]
- 插件根目录:${PLUGIN_ROOT}
遵循 Agent 文件中的说明。写入 [file],完成后返回结果。
计划的
## Environment中包含Mode: app-builder,且每个页面行都包含 Key,因此工作器会为跨页面导航生成"PAGEREF_<key>"(绝不使用由文件推导的标记 — 下载页面的codeFile是路径,而非其身份)。自定义导航 ID 放在data:中 — 绝不放在recordId中。Connectors: none和Telemetry: disabled是此处的常量:/app-builder不会编写连接器代码(App Spec 的connectorBindings/actionBindings是下载页面已经持有的列表,会在重建时沿用,且不会在此处创建,因此投影后的计划始终会写明No connector bindings.),而且/app-builder从不运行阶段 4.7 的custom-telemetry探测。两项都要明确写出,而不能省略 — 页面构建器会将缺失的行视为相同的失败关闭值,但明确写出才能让分派约定可检查。 -
检查样式,然后验证并提交迁移(事务性) — worker 没有 shell,因此先逐页检查生成的页面是否包含 Griffel 的简写属性;
makeStyles会在运行时丢弃这些属性,每页执行一条命令(<file>是步骤 3 中该页面的文件):node "${PLUGIN_ROOT}/scripts/check-griffel-styles.js" --file <working-dir>/<file>Griffel 不支持十种 CSS 简写属性,
makeStyles会在运行时丢弃它们:页面仍会通过类型检查、转译并部署,随后渲染时却没有边框、颜色或重置样式。它们是all、borderColor、borderStyle、borderWidth、borderBlock、borderBlockStart、borderBlockEnd、borderInline、borderInlineStart和borderInlineEnd。请改用长属性:borderColor、borderStyle和borderWidth分别改为每侧一个长属性,即border+Top、Right、Bottom或Left+Color、Style或Width(borderWidth→borderTopWidth、borderRightWidth、borderBottomWidth、borderLeftWidth);borderBlock、borderInline及其Start和End形式,改为它们所指边的Width、Style和Color长属性 (borderBlockStart→borderBlockStartWidth、borderBlockStartStyle、borderBlockStartColor;borderBlock→ 这些属性以及三个borderBlockEnd…属性);all没有 对应的长属性,因此请逐个设置所需属性。其他所有简写属性(padding、margin、border、borderRadius、gap、outline)均受支持,而内联style={{ … }}根本不属于 Griffel。check-griffel-styles.js会打印发现的每个问题:在makeStyles调用中任意深度出现、会被 Griffel 丢弃的键及其行号、列号和对应长属性;或者无法读取的调用或样式对象,并说明应如何修改。只要还有问题,脚本就会以状态码 3 退出:请修复报告的问题并重新运行,在脚本以状态码 0 退出之前,不要转译或上传页面。它会读取调用中内联编写的样式对象,即makeStyles({ … }),因此请将样式写在调用中:从其他位置传入或展开的对象(makeStyles(styles)、...base)会被报告,因为检查器无法看到其中的键;表达式计算得出的键([key]: …)也会被报告,因为检查器无法看到键名(以--、&、:或@开头的模板字符串可以读取);Fluent 的...shorthands.*(…)和...typographyStyles.*可以照常展开。还要检查每个生成的页面,避免使用可能被其他页面编译器丢弃的迭代操作:
此插件上传的页面会启用迭代器支持进行编译(ES5 并启用
downlevelIteration),因此,对 Map、Set、迭代器、生成器或字符串进行展开、for...of循环或数组解构时,会像现代 JavaScript 那样读取数据。不启用此支持而再次编译页面的工具(例如较旧的 Maker 门户或pac)则会改为读取长度和索引:值会消失,默认值会替代它们,而剩余解构或展开字符串会抛出异常;这些情况都不会导致编译失败。请确保页面在所有编译器下都正确:在展开、循环或解构之前,先用Array.from(...)转换,例如[...new Set(xs)]、[...m.values()]、[...seen.values()]、for (const [k, v] of m)、new Set([...prev, id])、[...selected]和[...text.matchAll(re)];复制 Set 时,先用new Set(prev)创建副本,再调用.add(id)。普通数组展开和循环仍然有效。Object.keys、Object.values和Object.entries会返回数组。对未调用slice的非可迭代类数组进行剩余解构时,所有编译器都会抛出异常。解构默认值不能修改正在解构的数组:const [a = (values[1] = 3), b] = values在此处会让b保留旧值,这与现代 JavaScript 不同。相关代码组合请参阅references/rules.md中的 常见错误 > 5. 可能被其他页面编译器丢弃的迭代操作。 然后验证并提交这次转换 — 切勿手动翻转source,也切勿在工作线程返回时逐页翻转。运行:node "${PLUGIN_ROOT}/scripts/promote-intent-pages.js" \ --spec @<working-dir>/app-spec.json --working-dir <working-dir>它会检查每个生成的页面(文件已写入、结构上是一个模块、
PAGEREF_标记符合规范且与规范中的navigatesTo边完全一致),然后才会通过一次原子写入,将所有页面从intent翻转为{ kind: "tsx", codeFile }。如有任何失败,它会以状态码 3 退出,并保持app-spec.json不变,同时打印失败页面及原因 — 只需重新生成这些页面并再次运行。坚持全有或全无:部分翻转的规范会指向一个从未写入的.tsx,而 Phase 2 的deploy配置文件会在发现任何剩余的source.kind === "intent"时快速失败。 -
进入 Phase 2(完整的幂等构建)。
⚠️ 交互式作者绝不会在
Task子 Agent 中运行。这里只会派发纯粹的无头代码生成工作线程 — 所有面向用户的提示都来自主循环。
Phase 2 — 构建(主循环讲解)
始终使用
scripts/build-model-app.js。切勿手写构建器。 它具有幂等性 (会跳过已有的解决方案/表/列/关系 — 因此新环境、现有环境和混合环境都能直接运行),所以无需预先创建任何内容,也无需针对现有表进行特殊处理。
构建计划已在 plan 模式中展示并获批(Phase 1 Step 6 显示引擎的实际试运行计划),因此获批后直接应用 — 只需一次构建审批,无需再次确认。添加 --verify,以便构建在应用后进行自检(参见 Phase 3):
node "${PLUGIN_ROOT}/scripts/build-model-app.js" \
--env <envUrl> --spec @<working-dir>/app-spec.json --apply --verify [--sample-data] [--publish]
每个步骤都会实时输出状态([n/total] ✓ created / ⊘ skipped / ✗ failed — <error>),最后输出汇总:✓ build complete — X created, Y skipped, Z failed。
保持构建进度可见。 完整构建需要运行数分钟。让输出持续流式显示 — 不要通过
Select-Object -First/-Last N或Select-String的行数限制来管道处理输出,因为这些操作会缓冲输出,直到运行结束才显示进度(还可能截断仍在运行的管道)。要捕获日志,请使用Tee-Object -FilePath <log>(不设行数限制)。构建还会打印一行▸ live progress:,指向<workspace>/.maker-workspace/build-status.json— 其中的快照(state、steps、lastPhase、lastLabel)会在每个步骤后覆盖更新。随时读取此文件(或查看build-log.jsonl的尾部),即可汇报长时间构建的进度,即使标准输出被缓冲也不受影响。
(如果没有新的 plan 模式审批就进入 Phase 2 — 例如恢复失败的构建,或快速编辑后重新运行 — 请先进行试运行(移除 --apply),在聊天回复中原样粘贴按阶段分组的计划(工具输出会折叠 — 请参阅上方的 CRITICAL 提示),并在应用前获得确认。)
推送预览(仅限 --preview-push 试运行)。 对现有应用进行复杂更改时(涉及托管或破坏性功能、多个视图),请在试运行中使用 --preview-push,查看每个工件的推送会创建、更新还是不做更改,以及阻止推送的原因(拒绝、过时、未知)、解决方案层和发布范围。此操作只读。“ready”不代表已获批准或已锁定;尽管直接 SDK 推送仍可能写入标记为“noChange”的工件,但此构建会跳过不会更改的现有视图的推送。预览会显示现有视图将发生的列和描述更改,以及构建将创建的图表。其他所有内容(现有表单、图表或应用,构建将创建的视图或表单,命令栏)都会列为未评估,因为构建只有在运行时才会确定这些更改:请明确说明这一点,绝不要将预览报告为完整结果。如果预览失败,它会打印错误代码,且试运行结果仍然有效。不要在每次应用时都使用它;与 --apply 和 --no-live-plan 一起使用时会被拒绝。
**阶段选择器(--stage <data|ui|app|publish>)**映射到对应的阶段范围。使用 --apply 时,只接受
--stage data(solution + data-model,第 1 次运行不包含行;第 2 次运行是完整构建)。其他所有
阶段以及旧版 --from/--to/--only/--skip 选择器仅用于试运行检查——其阶段范围未闭合依赖关系,因此在使用 --apply 时会被拒绝。
语言(--language-code / --languageCode <lcid>)——通常不需要传入此参数。
数据模型标签会使用组织自身的基础语言,每次构建时读取一次。仅在要用其他已预配语言编写
标签,或构建警告无法确定基础语言并回退到 1033 时,才传入此参数。
如果传入的 LCID 尚未由组织预配,构建现在会在数据模型阶段停止,不会写入任何标签,并列出组织已预配的语言。之所以进行此检查,是因为 Dataverse 对此的处理不一致:表和选项标签会被接受(HTTP 204),并被静默存储为组织的基础语言;而 DateTime 和 Memo 列会被拒绝,并提示 "The language code N is not a valid language for this organization"。因此,如果没有此检查,构建会先创建带有错误标签的表,然后在处理列的过程中途失败;问题出在距离导致问题的标志很远的阶段,看起来就像是环境故障。使用
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" <envUrl> GET RetrieveProvisionedLanguages 列出
已预配的语言。此检查尽力而为:如果读取失败,构建将按原样继续。
App Spec 字段 languageCode 会在多次运行中固定使用同一值。它现在适用于整个构建:
数据模型标签,以及表单、仪表板和站点地图标签——过去会将 1033 写死的 SDK 序列化器,现在会将创作语言作为选项。
使用相同的标志进行验证。 使用 --language-code <lcid> 运行构建时,会以该语言写入表单的选项卡和节
标题,因此运行独立验证器时也要使用相同的 --language-code <lcid>(或在规格中固定
languageCode):如果不传入,--verify 会使用组织的基础语言查找标题,因此无法确认已正确发布表单的标题(会将其列为未经验证或报告为缺失)。构建自身的 --verify 会自动获知语言。如果 verify 完全无法确定语言(无法读取组织),则会将这些标题列为未经验证——既不通过也不失败——并说明原因:请使用该标志重新运行。
环境读取的表单未显示的语言。 平台决定表单的 FormXML 会携带哪些语言的标签,而且返回的语言可能少于表单中存储的语言:在英文组织中构建的西班牙语(3082)标题表单,读取时仍会带回已存储的西班牙语标题,但标签只有英文。只要读取结果带有其他语言的标签,Verify 就不会将读取结果未携带的语言中的标题视为缺失,而是从应用解决方案的一次翻译导出中读取这些标题(仅当存在此类标题时才会发出一次额外请求;单表解决方案耗时 7–11 s,解决方案越大耗时越长;它显示已发布层),因此,正确的标题会通过,过时或空白的标题则会像其他标题一样失败;{ "1033": …, "3082": … } 标签映射也会以相同方式逐个语言进行比较。如果导出失败或无法给出结果(请求失败、无法读取的存档、没有该表单的行),Verify 会将该语言中参与比较的标签列为未验证(既不算通过也不算失败;运行结果和退出状态不变),并说明原因以及读取结果实际携带的语言。请说明这一点,并让用户在 Power Apps 中切换到该语言检查这些标题;不要通过重建来“修复”它们。读取结果实际携带的语言仍会严格依据读取结果进行比较:其中过时的标题会失败。读取结果完全不带任何标签的表单不属于这种情况:规范中为其声明的标题均视为缺失,并会失败。
运行时需报告进度。对于暂时性的环境错误(429 或 503、自定义项锁定、并发操作保护,或服务通过错误代码指出的 SQL 超时、连接超时或重置),--apply 会自动重试并采用退避策略(构建具有幂等性,因此重试会复用已创建的内容)。服务触发限流时,会按其要求等待(Retry-After)——包括 HTTP 客户端重发被限流的读取请求或受服务保护限制的发布请求、页面写入,以及构建重新运行某个阶段——所有限流等待共用每次运行五分钟的预算:如果下一次等待超出预算,构建会停止,并以 throttle-budget-exhausted 说明服务要求的等待时间;稍后再次运行即可继续。(5xx、连接重置或死锁不属于限流:它们使用各自更短的重试计划,不计入此预算。)如果构建仍因不可恢复的错误而停止(BuildHalt),请向用户说明错误,并通过 AskUserQuestion 询问如何继续(调整规范/取消),然后重新运行。所有内容都限定在专用的非托管解决方案中;--publish 控制最终的批量发布,该发布也会包含对现有视图和表单所做的修改。不使用它时,这些修改以及现有应用的站点地图重新同步仍会发布各自对应的目标项目,以使更改生效(参见说明与限制)。
从构建失败或中断中恢复:重新运行完整构建。 构建具有幂等性——每个阶段都会重复使用已创建的内容,只补齐缺失部分。SDK 元数据会保存在 <working-dir>/.maker-workspace/ 下(可通过 --workspace 覆盖)。没有可安全应用的 --from <phase> 快捷方式;重新运行完整构建才是正确且安全的恢复方式——即使是在 WEB_RESOURCE_UNVERIFIED
中断之后也如此:某个 Web 资源已写入,但无法确认其是否已发布(通常是因为其他自定义操作占用了环境锁);构建已记录该资源,而使用同一个可写工作区重新运行时,会再次按其 ID 写入(只有在已发布的副本尚未包含该内容时,才会写入页面清单;否则重新运行会保留该副本);请勿删除或重命名该资源。如果中断提示无法保存恢复记录,或报告名称/ID 冲突,请按提示进行明确处理——没有该身份信息时,自动重试并不安全。
另一种例外:如果是 already-exists 中断,重新运行时会再次中断,直到删除该工作区目录——请按提示操作。
还有一点:该工作区属于最初为其打开的环境。只有 --apply 才会打开它——试运行会使用临时工作区,因此不会绑定任何环境,可从任意文件夹预览,并会在该处运行 --apply 将被阻止时发出警告。如果指定了不同的 --env,构建会在读取或写入该环境中的任何内容之前中断,并指出两个环境。系统绝不会替你清除或重新绑定工作区——那会丢弃 SDK 保存的另一个环境的制品副本,包括其中尚未推送的编辑;存放在这些副本旁边的页面所有权回执和基线也属于那个环境。请针对中断提示的环境进行构建,或使用新的应用文件夹(复制 app-spec.json 及其指定的文件),或者使用新的 --workspace。只有在不再需要该环境中的应用副本时,才在该环境中通过中断提示的 --clear-workspace 命令将其拆除;只要仍有页面所有权回执,该命令就会保留文件夹。中断会在单独一行打印文件夹路径,该命令将其作为 --workspace 参数——请完全按打印内容传入,并按 shell 要求加引号(在双引号内,PowerShell 和 bash 会展开路径中的 $)。如果文件夹中没有构建保存的内容——只有 SDK 创建的空文件夹,而这些文件夹可能由先前的预览或在构建启动时就停止的构建留下——提示会明确说明这一点:该环境中未构建任何内容,因此没有内容可恢复或拆除,可以直接删除该文件夹或将其移到其他位置。如果 manifest.json 未指定可用环境,或不是有效的 JSON,构建也会中断:请将其移到其他位置(重命名而不要删除——其中可能包含页面所有权回执和基线),然后重新运行。同一环境使用其另一种主机名(<org>.api.crm… 对应 <org>.crm…)时也会中断:请使用中断提示的主机重新运行。独立验证器也不会打开该工作区,因此可以验证应用部署到的任意环境。
在应用的工作区中,一次只能运行一个 apply 命令,包括其数据阶段和完整构建。如果另一个构建持有 build 或 pages 锁,请等待它完成,然后重新运行相同的完整命令;不要反复发起重试、使用第二个工作区绕过锁或删除工作区。锁拒绝会输出到 stderr,退出代码为 1,且不会输出 stdout JSON;不要将 stdout 为空视为未知崩溃,也不要因此重置工作区。teardown、download、独立的 /genpage 或 apps.js 修改,或清空工作区,不受此锁限制;不要在 apply 运行期间对同一应用/工作区执行这些操作。只有确认锁的所有者不再运行后,才能判定锁已失效;仅凭锁的存在时长绝不足以作出判断。如果无法自动回收锁,请检查所有者证据,并在确认后考虑仅手动删除指定的锁文件/回收文件。保留工作区及其恢复记录。为防止并发构建,所有重叠运行的构建命令都必须使用新版本。在混合版本过渡期间,先完成较旧版本的构建,并保持已下载应用的唯一名称拼写不变。
使用 --publish 时,构建所更改的表,以及应用持有或即将持有表单或图表的表,会在构建写入或发布应用之前,通过一次请求一并发布:包括构建编辑了现有视图和表单的表、构建写入图表所属的表、每当构建推送应用时导航中涉及的表(SDK 会在构建每次推送时将这些表的活动 Main 表单添加到应用中,因此,如果某个表单已上线、标签已保存但尚未发布,它原本会在其所属表之前随应用一起固定并发布:首次构建和固定表单的构建都采用相同处理方式;仅重新上传有变更的页面时,如果必须固定默认视图,应用会通过页面后端推送并发布应用,该后端的推送会添加所固定的视图而不会添加表单,因此不会指定导航中的任何表),以及——对于已存在的应用——其已保存的组件列表所列出的表单和图表组件所属的表,无论规范是否指定该表(例如,制作者添加到应用中的表单,即使从未发布也包括在内)。否则,某人在这些表单之一上保存但未发布的标签会在应用发布后仍未发布。应用已知不持有表单或图表的表不会由应用发布,因此制作者在那里尚未完成的内容会保持未完成状态:仅在 entities 中声明的表(例如查找列的目标表)、仅由某个页面读取的表,或仅由 app.tables 固定的表。如果无法完整读取应用组件,或读取结果存在歧义(包括无法确定归属的组件列表行),构建只会发布其更改的表和导航中的表,并说明读取失败的位置;清除原因后重新运行即可。对于未编辑任何现有视图或表单的构建(包括首次构建),这会多进行一次发布;最终发布会涵盖构建生成了工件的表。
如果该请求被拒绝,构建仍会继续,并说明情况:它会列出在这些表上保存的内容、应用在这些表上持有的表单,或其写入操作新增的表单(其他人可能已在这些表单上保存标签),以及因此应用发布可能遗漏的内容。
如果构建因错误而停止,它会尽力发布已延后的视图和表单,然后——如果它在发布阶段之前停止——列出规范所涉表(包括其页面读取的表)中仍已保存但尚未发布的表单、视图和图表,以及构建创建的内容和任何人保存在其中的内容;如果无法读取这些内容,则会说明情况并列出需要发布的表。列出的内容不包括列和其他类型的更改。在发布阶段之后停止则不会提供此类信息。
如果构建在这些编辑已保存但尚未发布时被终止——例如按下 Ctrl-C、发生崩溃或终端关闭——则无法发布,因此这些编辑会保持已保存但未上线状态,直到下一次构建。再次运行相同的完整构建即可;它会发布这些内容,而不会再次写入。
阶段 3 — 验证并迭代
--apply --verify 已根据已部署内容完成规范对账(阶段 2)——构建会附加一行 verify PASS / verify FAIL — N missing,并且在静默部分完成时以非零状态退出(工件已创建但未接入,或某个阶段悄然未生成任何内容)。若要稍后重新检查——例如 Maker 更改后,无需重新构建——运行独立的只读验证器(实体/列/视图/图表/表单和站点地图子区域 + 图标;以非零状态退出并列出所有缺失项):
node "${PLUGIN_ROOT}/scripts/verify-model-app.js" --env <envUrl> --spec @<working-dir>/app-spec.json [--language-code <lcid>]
仅当构建时使用了 --language-code,才传入该参数。判定结果下方以 ⊘ 开头的行表示某项检查无法判定(它位于 JSON 结果的 unverified 列表中):这既不算通过,也不算失败,因此要说明具体是什么,以及如何确认——可以查看字幕所用的语言(由 --language-code 指定);也可以查看环境的表单读取功能无法读取的语言所写的字幕,而翻译导出无法确认这一点(该行会说明原因),这种情况只能在 Power Apps 中查看。如果列出了此类项目,绝不能把运行结果报告为“一切已部署”。
stderr 中的 WARNING: 行会逐条重复列在 JSON 结果的 warnings 列表中;没有警告时则不会出现该列表。这些是建议,不是判定结果:可能是早期版本接受、而当前构建会拒绝的规格(会指出列或关系以及下次构建的修复方法),也可能是运行时无法移除的页面工作区(会指出需要手动删除的文件夹)。警告不会改变 ok 或退出状态,因此运行即使通过,也可能仍有警告:请将每条警告转告用户。运行因意外错误而停止时,会打印该错误,不会输出 JSON。
命令按钮(--inspect-components)。 命令按钮出现异常时,或修改命令后,使用 --inspect-components 运行验证。它会根据记录的行,在你指定的界面上检查每个已声明的控件(表单上的 MainTab 按钮、主网格上的 HomeTab、子网格上的 ContextualTab)。仅匹配标题、隐藏或禁用的控件,以及位于其他界面或应用中的控件,都不能通过检查。它检查的是已存储的定义,而不是浏览器渲染的内容。目前构建不会记录控件的行 ID,因此所有找到的控件都会报告为 UNVERIFIED … found by caption only,运行也会失败:要告诉用户哪些行显示 FAIL(控件缺失、位于其他界面或存在多个匹配项)——这些都是真正的问题;还要说明,仅按标题匹配的行意味着无法证明该按钮属于此应用本身,而且它也可能掩盖按钮的问题:按标题找到的隐藏控件(或位于隐藏父级下的控件)会报告为仅按标题匹配,而不是 FAIL;禁用的控件仍会失败(stored disabled)。没有 FAIL 行,并不能证明按钮可见。
然后在浏览器中打开应用。完善 app-spec.json 并重新运行 Phase 2,以便迭代。
拆除(清理)。 若要移除 App Spec 构建的所有内容,例如实时验证探针或失败的构建,请运行分类器安全的拆除操作。它只会按依赖顺序删除规范声明的构件(应用模块 → 仪表板 → 命令栏 → 表单 → 安全角色 → 图表 → 视图 → 重置已扩充的默认视图以移除父查找 → 关系 → AI 行摘要 → 表 [先子表] → Web 资源(生成的应用图标 + 页面清单 + 已声明资源)→ 全局选项 → 解决方案)。表单/图表/视图/关系会在表之前显式删除(删除表不会可靠地级联删除交叉引用;但会删除表自身的列)。Dataverse 会拒绝删除已构建表的唯一主表单(“You must have at least one active Main form”);删除表时会将其移除,只有在重新读取其 ID 并确认它已消失后,运行才会将其计为已移除;否则,该拒绝仍会被视为失败。安全
角色会在表单之后删除,因为分配给某个角色的表单会在自身的 formxml 中引用该角色,平台会将其视为依赖项;安全角色会在关系/表之前删除,因为持有表权限的角色可能会阻止删除该表。命令拆除会移除规范曾为其创作命令的任何实体的整个命令栏。拆除只会删除此构建创建的表——系统/标准表(account、contact 等)会被自动检测并跳过;如果实体标有 "existing": true,则会跳过已复用的自定义表,以保留预先存在的数据。同一标志也会保护关系和全局选项——下载会为恢复的每项内容设置此标志,因此拆除下载的规范时,绝不会从保留的表中移除查找列,也不会删除共享选项集。仪表板按名称查找,因此拆除只会删除应用解决方案所持有的仪表板,不会删除其他应用的同名仪表板;如果规范没有可查询的实际解决方案(下载可能留下的 Default 不算),则不会删除任何仪表板。只要有任何步骤失败,就会保留解决方案,以便再次运行时仍能识别;如果生成式页面步骤失败,也会保留页面清单,以便再次运行时仍能找到应用创作的页面。如果某个表上的业务规则无法删除,则会保留该表及其图标 Web 资源、列所绑定的全局选项(只要表还存在,平台就拒绝删除该选项),以及它与自身的关系(表一旦删除,已激活的业务规则副本便无法删除);再次运行时会先删除规则,然后再删除该表及随其保留的内容。它绝不会删除解决方案的发布者(构建会按前缀复用发布者,其他解决方案也可能共用该发布者);结束摘要会用一行说明这一点,因此请告知用户发布者会保留,待无人使用后可在 Maker 中移除。它也会保留所有已声明的字段安全配置文件,并逐一列出名称(用户和团队可能会在此应用之外继续使用这些配置文件)。表单上的生成式页面: 如果表单仍嵌入某个页面(MscrmControls.UxAgentControl 控件),拆除会保留该页面,并在删除任何内容之前停止并报错,指出相关表单。试运行也会指出同一表单并报告失败。请告知用户,让其从该表单中移除页面(如果表单属于用户,也可以删除表单),然后再次运行拆除。默认进行试运行;添加 --apply --allow-destructive 才会实际删除(--clear-workspace 还会清理 .maker-workspace/)。
teardown --apply 必须使用 --allow-destructive——否则拆除会拒绝执行且不做任何更改。
node "${PLUGIN_ROOT}/scripts/teardown-model-app.js" \
--env <envUrl> --spec @<working-dir>/app-spec.json [--apply] [--allow-destructive] [--clear-workspace]
安全标志(构建 + 拆除)。 应用路径会对破坏性操作采取默认拒绝策略:
--allow-destructive— 授权执行破坏性操作。对于build --apply:授权在无人值守模式下覆盖现有应用,并允许移除显式布局中的表单字段或删除站点地图目标;也授权解除pages-removed页面的导航子区域关联(页面记录仍保留在已部署状态,不会被删除)。对于表单和站点地图移除操作,此授权涵盖上次拒绝或获批运行记录的移除项;如果实时状态中还会丢失更多内容,则应用操作会停止,仅列出新增的破坏性移除项,并记录该新列表供审核。运行期间出现的字段或站点地图目标会被保留,或在推送前停止运行,绝不会在未被发现的情况下移除。只有在完整构建成功、执行了移除阶段且未保留任何内容后,记录才会被清除;失败、部分运行、仅运行变更项、记录无法读取或发现失败时,记录都会保留。对于teardown --apply:必需 — 所有删除操作本质上都是破坏性的,因此未设置此标志时,teardown 会在触及任何内容之前停止。- Pages 阶段安全 HALT。 如果违反身份或安全要求,构建会停止,而不是继续并可能产生错误状态。查看 HALT 原因并按照恢复提示操作:
pages-identity-conflict— 规范中的pageId与清单对某个键的记录不一致,或同一 ID 跨两个键重复出现。请手动解决冲突(重新下载或删除清单)。pages-manifest-corrupt— 无法解析清单(两个键映射到同一 ID)。请删除清单 Web 资源,然后从头重新构建。pages-removed— 规范中移除了一个线上页面。请将其重新添加,或传入--allow-destructive将其从导航中解除关联(页面记录仍保留在已部署状态;重新构建会完成站点地图的最终处理)。pages-shared-across-apps— 该页面出现在另一个应用的站点地图中,无论该站点地图已发布还是仅已保存(尚未发布;从未发布过的应用也计入)。请先征求用户同意,再将页面从该应用中解除关联,因为这会更改另一个应用。HALT 会指出应用名称,并打印已填入应用id的命令(如果无法读取 ID,则会显示<id of app …>;可通过scripts/apps.js list --env <envUrl> --search <name>获取 ID);运行node "${PLUGIN_ROOT}/scripts/genpage.js" remove-from-app --env <envUrl> --app-id <that app's id> --page-id <page id>会将页面从该应用的导航中移除,并发布该应用(该应用已保存的所有内容都会随之上线;页面本身仍会保留)。也可以在 Maker 中解除关联。--allow-destructive不会绕过此 HALT。构建会在即将就地更新页面之前再次检查,因此如果构建期间另一个应用接纳了该页面,也会在此处停止(提示“构建期间页面已被另一个应用共享”);如果页面是在上传过程中被另一个应用接纳,则会在上传后报告警告并指出应用名称,因为届时页面已经写入。pages-shared-check-failed/pages-existence-failed/pages-sitemap-read-failed— 前置读取失败;构建无法安全继续。临时错误请重试;持续性错误请检查权限。pages-griffel-shorthand— 页面的makeStyles写入了 Griffel 运行时会丢弃的 CSS 简写属性,因此页面部署后会缺少其边框、颜色或重置样式。消息会指出页面、每个键的行列位置,以及应改用的长属性;请修复页面源代码并重新运行。没有任何内容被上传。对于构建将要创建的页面,以及源代码自上次构建后发生变更的页面,都会触发此错误;对于未修改且已存在该问题的页面,则会原样重新发送,并附带警告(目前该页面的渲染结果不包含该键)。删除该键会使页面保持当前外观;改用长属性则会使样式开始生效。同一 HALT 也会将检查无法确定读取的makeStyles调用报告为此错误,而不是报告为 Griffel 会丢弃的 CSS,并说明代码中应更改的内容。pages-locked— 此应用的另一次构建,或在此文件夹中运行的genpage.js add-to-app/remove-from-app/genpage-upload.js --app-id编辑操作,正在持有应用的 pages 租约。没有页面被写入。构建只有到达 pages 阶段时才会获取该租约,因此更早的阶段(solution、tables、forms、app shell)可能已经保存了变更:请告知用户这一点,绝不要说没有任何变更。请等待另一次构建或编辑完成后,再次运行构建;该次运行会协调已保存的内容。(单独运行的genpage.js/genpage-upload.js编辑操作如果收到此拒绝,会先获取租约,因此确实没有写入任何内容。此类编辑操作也会持有应用的 build 租约,所以在其运行期间启动的构建会立即因build-locked被拒绝,且不会写入任何内容:编辑结束后重新运行构建。)这是此文件夹.maker-workspace中的单机锁,不会阻止来自另一台机器的编辑。form-page-embeds-incomplete— 规范要求嵌入表单的页面(forms[].genPages)未能嵌入。消息会指出每个键及原因(halt.notApplied);请同时告知用户这两点,绝不要报告构建已完成。在此版本中,CLI 构建无法将页面嵌入表单(它不会记录自己创建的嵌入,因此在完成其他所有构建后会因managed-bindings-unavailable而安全失败):用户要求将页面放在表单上时,请说明这一点,并建议改为将页面放入应用导航中(将placement保留为sitemap)。预检阶段出现相同代码意味着本次运行执行了 forms 阶段,但未执行 pages 阶段 — 请同时运行这两个阶段。
--non-interactive— 禁用交互式提示(用于自动化 / CI)。如果非交互式构建遇到现有应用,会失败,而不是发出警告后继续,除非同时设置了--allow-destructive。此标志本身不会授予破坏性操作权限 — 只有--allow-destructive才能授予该权限。POWER_PLATFORM_SKILLS_NONINTERACTIVE=1(或true)— 等同于--non-interactive的环境变量。语义相同:仅禁用提示,绝不授权破坏性操作。在 CI 作业环境中设置此变量,以避免交互式提示导致作业挂起。- 在 autopilot / eval 模式(
--non-interactive+--allow-destructive)下,preview-app.js会在执行计划前写入磁盘,作为设计产物;交互式授权关卡会被跳过,并且构建会以安全失败的方式阻止任何未经明确授权的破坏性操作。
AI 优先功能
App Spec 中的 ai 块控制四项应用级功能和按表配置的 Copilot 行摘要。所有功能均受管理员控制:只有环境管理员在 Power Platform Admin Center(Environments → Settings → Product → Features)中启用相应功能后,这些功能才会启用。ai-features 构建阶段会通过 SDK(RetrieveSetting)预检每项设置;对于任何已关闭的设置,都会跳过并发出警告——不会导致构建失败,也无法自行切换管理员或租户设置。
预检(独立运行):
node "${PLUGIN_ROOT}/scripts/ai-preflight.js" --env <envUrl> [--app <uniqueName>]
显示每项功能的开关状态,以及启用任何已关闭功能所需的确切管理员操作。 永不失败。
应用级功能(ai.appFeatures)——formFill(Copilot 辅助填写表单)、nlSearch(自然语言网格/视图搜索)、nlChart(自然语言图表/AI 数据可视化)、m365(M365 Copilot)。除 m365 保留平台默认设置外,其他功能默认均为开启;false 会为此应用显式写入关闭状态(各设置的取值见:references/app-spec-schema.md → ai)。
按表配置的行摘要(ai.summaries):
default: "auto"— 技能会自动选择合适的候选表(跳过仅含查找字段的表、配置表、联结表,以及 Dynamics 365 自有的incident、lead、opportunity)。default: "off"— 除非某个表显式选择启用,否则禁用摘要。- 在
summaries.tables中为各表设置覆盖项:设置enabled、定制的instruction和columns[](摘要读取的字段)。{ "enabled": false }条目可为指定表选择退出。
提示词编写指南(适用于 instruction):着眼于有意义的洞见,而非重复字段和值;切勿包含记录 GUID;相关时纳入近期活动;采用适合受众的语气;尽量明确输出形式(例如一段简短文字)。
拆除除标准构件外,还会移除 ai-features 阶段创建的 AI 记录(摘要配置行和已发布的 AI 模型)。
编辑已部署的应用(下载 → 编辑 → 重建)
同一份 App Spec 用于编辑——没有单独的编辑路径。当用户想要更改现有应用(添加字段/视图/页面、编辑页面代码、重命名/调整导航顺序、更换图标)时,先将已部署的应用拉取到最新的 Spec 中,然后编辑该 Spec 并重新运行第 2 阶段:
node "${PLUGIN_ROOT}/scripts/download-model-app.js" --env <envUrl> --app <appId|uniqueName|displayName> --out <working-dir>
--app 按以下标识顺序解析:应用 ID(GUID)→ uniquename → 显示名称。优先使用唯一名称——它不可更改,而显示名称可以重命名且不唯一。如果某个显示名称匹配多个应用,则会拒绝操作,并列出候选唯一名称,而不会猜测。
此操作会将应用重建到 <working-dir>/app-spec.json。往返范围——请务必准确理解,并非所有内容:
- **可往返:**站点地图 →
appShell(所有子区域和图标)、所有生成式页面(通过 SDK 读取;这些页面是站点地图中的GenPage子区域,因此也包括 Maker 添加的页面,且每个页面都会保留自己的名称、模型、连接器和 Custom API 绑定——connectorBindings/actionBindings),并将其写入pages[]及对应的.tsx文件;被引用的实体(最小化——构建会复用现有表)、经典仪表板(带有已部署视图/图表 ID 的 ID 直通磁贴)、图标 Web 资源和解决方案。 - 不可往返:
forms[]、views[]、charts[]、commands[]——这些内容会变为空(只有使用--include-managed --managed-manifest <store file>时,声明的表单才会保留;参见声明的表单布局)。 这四类内容在实时应用中都会保留(重建时会通过发现机制保留),因此直接编辑是安全的;只是无法通过下载的规范编辑它们。请在 Maker 中或通过新的规范更改它们。下载结果会在其NOTE和结果中的notRoundTripped字段中列出所有未包含的内容——包括应用各表的命令栏按钮——并在规范的descriptionInventory下列出这些内容:请在用户于其他环境中重建规范前告知他们,因为这些内容不会随之迁移。 - 规范中不包含角色,重建时也会保持角色原样。下载内容不包含
personas[]:与应用关联的角色会保持环境中的现状。扩展由他人管理角色的应用时,请勿包含personas[]——persona 的角色权限会被收敛为所声明的精确权限,而且构建未创建的角色会遭到拒绝。通过添加roleGrants[]条目,为现有角色授予新表的权限(这是增量操作;不会将该角色关联到应用)。Lint 和设计文档会列出没有任何 persona 或授权覆盖的每个新表,write-app-spec-doc.js --env则会列出应用拥有的角色。 页面绑定与另一个环境。 页面连接器和 Custom API 绑定位于规范中,字段采用参考文档所述的属性——连接器绑定使用logicalName、connectorId、dataset、tables、tableDisplayNames、operations;Custom API 绑定使用name、isFunction、boundEntityLogicalName、displayName、parameterKinds——每个字段的值都采用已知安全的格式,除此之外不包含任何内容,因此重建时会将它们写回当前环境和另一个环境。任何绑定包含其他内容(连接 ID、凭据、规范未说明的属性、带查询参数的 URL)的列表,都不属于规范:拉取结果中的pageBindings.notCarried会列出页面、列表以及条目的位置(绝不包含值),页面会原样保留该列表。请告知用户这一点,并说明页面文件夹自己的config.json仍然包含所有内容——如果其中有凭据,他们绝不能提交或分享该文件夹。若规范声明了此类绑定,则会被拒绝。重建无法创建绑定所指向的对象。如果拉取结果包含pageBindings,在重建到另一个环境之前,请先告知用户其中的内容:neededInTarget列出该环境必须已存在的连接引用(以及它们背后的连接)、Custom API 和服务资源(例如 SharePoint 站点及其列表 ID);notCarried则列出规范无法声明的列表(重建到同一环境时会保留原样;重建到另一个环境时,会通过/genpage绑定)。重建过程本身会先读取该环境中的引用,再写入任何内容:如果发现同名连接引用属于另一个连接器,则会停止(pages-binding-reference-conflict,不会写入任何内容)——请为该绑定指定一个属于正确连接器的引用的逻辑名称,或将绑定的connectorId设置为该环境已有的值,绝不要替用户选择引用。重建过程的警告会逐一列出尚不存在的引用或 Custom API 的名称、没有连接的引用、该自定义连接器未定义的操作,以及无法检查的内容(未说明连接器或连接的行,或无法通过自定义连接器定义确认的操作):请转达这些警告。名称恰好为WhoAmI或RetrieveVersion的绑定是经过审核的内置 Dataverse 操作,而非 Custom API:拉取结果会在neededInTarget.dataverseOperations下列出它们(无需部署),重建过程不会读取任何内容,并会提示These are reviewed built-in operation names; their availability in this environment or player and the signed-in user's permissions were not checked.——也请转达这一点,绝不能称其为“缺失”或“兼容”(references/dataverse-operations.md)。不要自行将绑定写入规范——它们来自拉取操作读取的页面;将页面重置为intent以重新生成时,请从规范中删除其绑定:生成器不会写入连接器代码。
然后:
- 每次开始编辑时都要先拉取最新内容 — 有人可能已经在 Maker 中修改了应用。构建会在填充数据时读取 etag,因此,如果写入的对象自上次拉取后已发生变化,就会产生版本冲突 → 重新拉取并重试,绝不要覆盖。每次都拉取到同一个
<working-dir>:页面会保留早先下载时分配的key(从已有的app-spec.json中读取;前提是该文件的app.uniqueName指向此应用,并按页面的 id 匹配),而部署到其他环境的规范副本会通过这些 key 与在那里构建的页面绑定 — 如果 key 发生变化,就会更新到其他页面。如果新建文件夹,就会重新生成 key。如果拉取返回"code": "page-keys-ambiguous",说明先前的app-spec.json将同一个 key 分配给了两个页面(或将两个 key 分配给了同一个页面),并且未写入任何内容:向用户显示消息,其中会列出页面和 key;然后修正文件并重新拉取,或者 — 只有在用户明确要求时才可执行,因为新 key 可能导致部署在其他环境的副本更新错误的页面 — 使用--reset-page-keys重新拉取,此操作只会为这些页面重新分配 key。如果返回"code": "navigation-language-unknown",说明无法读取环境的基础语言,因此未知应用导航标题所用的语言,并且未写入任何内容:重新拉取,或传入--language-code <lcid>(环境的基础语言 — 这是构建所需的标志;如果文件夹中已有的规范文件已设置languageCode,则不需要传入)。不要猜测:如果读取标题时使用了错误的语言,下一次构建会用这些标题覆盖正确的标题。 - 根据所需更改编辑
app-spec.json(以及任何页面.tsx)。手动编辑的页面会像生成的页面一样接受检查:重新构建前,对其运行node "${PLUGIN_ROOT}/scripts/check-griffel-styles.js" --file <page.tsx>(规则和十种简写见阶段 1.5 步骤 5),并修复检查报告的问题。页面下载时已有的 key 也会被报告:当前部署的页面不带它也能正常显示,因此删除它会保持页面现有的外观,而写入完整属性则会让样式开始生效 — 除非用户希望进行此更改,否则请删除它。构建会对所有上传的页面运行相同检查:包括新增页面,以及自此应用上次成功应用以来发生更改的页面;若检查未通过,就会停止并返回pages-griffel-shorthand— 指明页面、每个 key 的行号和列号,以及对应的完整属性 — 因此,已更改的页面不会带着简写进入应用。更改状态依据.maker-workspace/last-applied.json中记录的该页面源代码来判定(成功的完整应用或仅应用更改、或下载操作会写入该文件;失败或部分构建不会记录)。未修改的页面会原样再次发送,而已有的 key 只会触发警告 — 在不含该页面记录的工作区中(例如新建或移动的页面,或另一个环境中)修改过的页面也会如此,因为构建无法判断之前发送了什么:请自行运行检查器。手动编辑的页面也遵循阶段 1.5 步骤 5 中的迭代器规则(references/rules.md中的 常见错误 > 5. 另一个页面编译器可能丢失的迭代器):在展开、循环或解构 Map、Set 或迭代器之前,先用Array.from转换。上传时会使用迭代器支持再次编译更改过的页面,因此,页面中已有的此类形式现在可以读取其值,而较早版本即使在转换后也会丢失这些值;只有当所需更改需要转换时,才转换此类形式。 - 如果编辑会新增页面(或将页面的
source重置回intent以重新生成),则必须在阶段 2 之前重新运行阶段 1.5 — 下载的规范文件只包含kind: "tsx"页面,因此新增的intent页面没有代码,阶段 2 的deploy配置文件会拒绝它。可以安全地重新运行阶段 1.5:write-page-plan.js会投影所有页面(以便 worker 能看到真实的导航图),只有回显内容显示intent: true的页面才会派发 worker,而promote-intent-pages.js不会改动已构建的页面。手动编辑现有页面的.tsx文件不需要重新生成。 - 重新运行构建(阶段 2)。该操作具有幂等性:会复用现有的应用、表和视图,就地更新每个页面(按名称匹配 →
--page-id,因此不会产生重复项),并且保留现有的GenPage子区域(下载时已将它们枚举到pages[]/appShell中,因此完整替换站点地图时不会丢弃它们)。 - 执行验证(阶段 3),确认只应用了预期的更改。
按照 genpage-first 策略,优先使用生成式页面,而非经典仪表板。经典仪表板支持往返同步(id 透传磁贴),但磁贴指向的视图和图表不支持,因此只能在 Maker 中编辑。
修复行、查看解决方案内容、迁移应用
这三个专用工具用于处理非构建类请求。它们都不会从 App Spec 运行;只有 Maker 提出相应需求时,才使用对应工具。
修复现有行中的值(“修复这 30 行的状态”“为旧记录设置缺失的区域”)。先规划更改:
node "${PLUGIN_ROOT}/scripts/data-fix.js" plan --env <envUrl> --app-dir <dir> --table <name> --filter <odata> --set <json> --max-rows <n>
计划仅输出计数、计划 ID 和摘要:打开其中指定的审查文件,向 Maker 展示文件列出的更改前后值及行数。只有 Maker 批准这些值后,才应用更改:
node "${PLUGIN_ROOT}/scripts/data-fix.js" apply --env <envUrl> --app-dir <dir> --plan-id <id> --approve <planDigest> --app <appUniqueName>
如果应用的构建使用了工作区,请添加 --workspace <dir>,并在 resume 和 revert 中传入相同的 --app/--workspace。
- 它不会等待构建完成:如果应用正在构建,它会拒绝执行(
DATA_FIX_BUSY);请在构建结束后重试。 - 退出码非零表示并非所有更改都已完成。计划生成后被其他 Maker 修改的行会报告为冲突,绝不会被覆盖。
- 部分运行可以通过
resume继续,它只会重试尚未尝试的行。结果未知的行不会自动重新发送,并会导致运行退出码非零。 revert会生成自己的审查文件,并要求对该文件重新审批。如果它在生成文件前停止,请再次运行revert,以重新审查剩余内容。- 结果未知的行,或写入时未包含
revert所需信息的行,无法撤销:撤销审查文件会列出这些行,revert会保持其原样并以非零状态退出。请手动检查这些行。如果没有任何已应用的行可以撤销,revert不会生成审查文件,只会输出计数并以非零状态退出:计划日志(<app-dir>/.data-fix/<planId>/)会记录已写入的行。 - 此操作不是原子的。
查看解决方案中的内容(“解决方案 X 中有什么”):
node "${PLUGIN_ROOT}/scripts/solutions.js" inspect --env <envUrl> --solution <name>
它会按类型列出受支持的构件、未展开子组件的表,以及已跳过或无法读取的组件,并附上请求计数。它不会列出解决方案拥有的所有内容,也不是一组可移植的组件。无法读取的组件只会附带错误代码和 HTTP 状态,不会包含服务的错误消息:没有消息可供转述。
检查应用能否迁移到其他环境(“我能把这个应用迁移到生产环境吗”):
node "${PLUGIN_ROOT}/scripts/plan-app-promotion.js" --source-env <envUrl> --target-env <envUrl> --package <solution.zip> --app <name>
它只读取信息。它会列出无法提升的组件(目前包括大多数架构、表单、视图、流程及类似组件),以及目标环境所需的内容。
- 没有应用步骤。它绝不会回退到导入操作。
- 只有在 Maker 接受导入可能部分成功时,才传入
--acknowledge-non-atomic-import。 - 现有的解决方案导出/导入仍是独立流程,有其自身的限制。
构建器的工作内容(按顺序)
解决方案(幂等)→ 数据模型——通过 SDK 发现现有表/列/关系(findTables / findColumns / fetchEntityMetadata),仅创建缺少的内容(createTable
/ createColumn / createRelationship)→ 示例数据(可选;按关系/拓扑顺序,使用实体集名称将
$parent→@odata.bind)→ Web 资源(可选;使用 createWebResource 创建表单 JS/HTML/CSS)→ 视图 → 图表 → 表单(主表单 + 排列好的列,遵循显式指定的 tabs;子网格、快速视图和表单 JS(events[])通过 SDK 的通用 addElement 接口应用为规范的控件单元格 / /bag/c 事件区域)
→ 业务规则(businessRules[]——通过绑定的 CreateProcessWithWfomJson 成员编写为工作流对象模型并激活;若环境未声明该成员或已将其关闭,则跳过并发出警告——HTTP 400 0x80040216,生产环境中很常见)
→ 业务流程流(businessProcessFlows[];根据编写的阶段编译并激活类别为 4 的流程——这通过 SDK 的通用构件接口实现,即普通的 workflows 行,因此不受上述绑定成员门控限制)
→ 命令栏(commands[])→ 经典仪表板(可选)
→ 应用模块 + 站点地图 → 生成式页面(每个页面的 .tsx 都在第 1.5 阶段生成;构建过程通过 SDK 写入 pages[] 中的每个页面,但不将其放置到任何位置;随后 SDK 重写一次站点地图,添加 GenPage 子区域)→ AI 功能(可选)→ 安全
(为 personas[] 中的每个条目创建一个角色,根据其任务声明的访问权限设定权限;注入应用模块读取权限,并将应用关联到每个角色,以便该角色对应的 persona 可以打开应用)→ 发布(可选)。如果应用包含生成式页面子区域,则会先创建不含这些子区域的应用模块(页面上传前无法解析这些子区域),随后在页面阶段重写站点地图——但对于导航中仅包含页面子区域的新应用,会先创建页面,再创建应用,并在其初始站点地图中加入这些页面。所有
Dataverse 访问都通过 SDK 完成,因此下载的元数据会保存到 .maker-workspace/ 中。独立操作(列、视图/图表/表单)采用有界并行处理;发布时,每个实体和应用各进行一次往返请求。视图/图表在表单之前构建,以便子网格可以引用子视图 ID。每个步骤都会输出 [n/total]。
注意事项与限制
- 无头模式,无需浏览器。 SDK(
cds-maker-sdk,随附于scripts/vendor/)通过复用设计器自身的序列化器生成设计器级别的 FormXML/FetchXML/站点地图,并使用基于az令牌的 HttpClient 通过 Web API 写入。无需中继,也无需打开设计器标签页。 - 每个应用使用专用的非托管解决方案(便于审查/拆除)。
--publish控制应用实体和应用自定义项的最终 批量 发布(对每个实体和应用各执行一次PublishXml)。使用--publish时,该次发布也会包含默认视图增强,以及构建所更改的表;构建更改的表,以及应用持有或即将持有其表单或图表的表,会在应用之前一起再发布一次,并在一个请求中完成:协调现有表单或视图、表单事件和快速视图放置时涉及的表,因此它们不会各自单独发布;构建写入的图表所关联的表(图表的说明保存在未发布层);每当构建推送应用时,导航所涉及的表(SDK 会将它们的活动 Main 表单添加到应用中;如果一个表单处于上线状态、标签已保存但尚未发布,那么它本来会被应用固定,并随应用先于其所属表发布;若表的app.mainForms列表中没有表单,则不会添加任何表单;重新上传仅更改页面、且必须固定应用已丢失的默认视图时也会涵盖在内——其推送会添加视图而不会添加表单,因此它在应用之前发布的内容,是应用持有的表单和图表所关联的表;若没有需要固定的内容,则不会发布任何内容);以及对于已存在的应用,其已保存组件列表中列出的表单和图表组件所关联的表,包括制作者添加到应用中、但从未发布的表单,这样已保存但未发布的标签就不会被应用发布阻挡。若无法确认应用持有某个表单或图表,则不会列出该表(仅声明、查找列的目标、页面只读,或仅固定在app.tables中的表):发布一个表会发布其上所有已保存的内容,无论由谁保存。如果无法完整读取应用组件,构建只会发布它更改的内容和写入所添加的内容,并说明哪次读取失败。它不会抑制编辑/完成流程必须执行、以使更改生效的小型定向发布:不使用--publish时,协调现有表单或视图、配置表单事件和放置快速视图时,都会各自发布对应的单个项目;无论是否使用该选项,重新同步现有应用的站点地图,以及生成式页面发布应用后完成站点地图,都需要发布应用;修复仍带有旧默认值的查找列时会发布其表(对上线项目的未发布编辑不可见)。不使用--publish的全新构建仍会将新表/列/关系暂存于解决方案中但不发布,不过,如果声明的表单(reconcile: "declared")所在的表被发布,则该表上的所有已保存更改都会上线,包括新列(声明的仪表板也会在写入后发布)。 - 幂等,但不会完全收敛到期望状态。 会检测并复用现有解决方案/表/列/关系/视图/图表/表单/命令/仪表板,因此重新运行以及使用现有表的环境都不会发生冲突。编辑方面的注意事项:重建不会删除你从规格中移除的项目,而且对于已存在的项目,只会重新应用部分编辑。表单会使字段收敛(添加字段;对于显式布局,会清除已移除的字段——见下文的
prune: false)、列数和跨度。视图只会添加列:不会应用从规格中移除的列或新的列顺序,也不会应用筛选器或排序。列类型更改、命令编辑或仪表板编辑都不会重新应用;现有图表只会应用变更后的说明;现有应用只会应用变更后的名称和说明(从未发布的应用会保留其文本,构建会警告未应用;名称或说明已本地化的应用也一样——只有该文本会被保留,其他文本和路由说明仍会应用;SDK 只写入变更的内容,而且即使 SDK 自身读取标头失败,也绝不会发送非纯文本;应用图标在创建时设置:若app.icon不同,会报告未应用);现有业务流程则会应用不会移动记录的阶段和步骤编辑——添加或重命名阶段、添加、重命名、重新绑定或移动步骤——并会拒绝可能删除阶段或步骤、或重新排列阶段的编辑,同时说明原因(此时不会应用任何阶段或步骤编辑;请求的 Active/Draft 状态会独立处理)。 若要应用这些编辑之一,请在 Maker 中更改,或运行teardown --apply,然后重新全新构建。--verify会对缺失内容进行检查,并以醒目的verify FAIL报错——包括规格中的视图列、筛选条件或排序键(编写的排序键也必须保持顺序)、关系、表的命令栏、状态原因、备用键或仪表板。它容忍已部署内容中的额外项目,因此即使从规格中移除的视图列、筛选器或排序键仍存在,也会通过检查,就像制作者手动添加的内容一样。不会验证单个命令控件。 - 暂不支持(后续再做):条件式命令可见性(仅限 Power Fx)、带标题的命令组(从零创建——需要 SDK 合成父行)、查找/关联视图、多区域站点地图,以及访问团队/层级安全性(已跟踪的 SDK 后续事项)。目前的安全功能支持按角色划分用户类型、按表单分配角色,并从 2.21.0 开始支持列级安全性(
isSecured+fieldSecurityProfiles[])。此外,规格验证器会拒绝的 BPF 设置也不支持(跨实体阶段、分支、阶段操作)。 - 受环境限制(在你运行时所在的环境中可能无法工作):
- 业务规则(
businessRules[])。SDK 通过绑定的CreateProcessWithWfomJson成员写入规则——现代业务规则设计器使用的也是该成员——且没有回退方案:它之前回退使用的客户端工作流 XAML 编译器已被上游移除,因为它只覆盖 7 种操作类型中的 4 种,而且只支持单个子句,导致规则会被悄悄缩减,无法表达作者所写的内容。因此,未声明该成员的环境完全无法托管业务规则——这才是常见情况,而非边缘情况。另一种常见情况是生产环境:它们声明了该成员,但处理第一个规则时会返回 HTTP 4000x80040216(“发生意外错误。”),这是平台限制,并且不会创建任何内容。在这两种情况下,构建都会跳过规则,只警告一次并指出成员或错误代码,然后正常构建其他所有内容,因此应用不会处于创建了一半的状态。如果业务规则不可或缺,请先验证环境。 此限制不影响业务流程——BPF 通过 SDK 的通用项目接口写入为普通的workflows行,不涉及任何绑定成员。
- 业务规则(
- 支持:完整的数据模型——所有列类型、AutoNumber 主列、全局选项、状态原因、备用键、N:N + 带负载的联结表;对现有列协调
required(true、false或"recommended"——验证时拒绝任何其他值;显式指定的required会在重建时收敛;省略时绝不会降级),并会在创建新表时将其应用于主列;仅在 SDK 确认后才报告已应用;BooleandefaultValue、整数型**integerFormat(包括Duration)以及每列写入权限**(isValidForCreate/isValidForUpdate/isValidForRead——可据此将列设为只读),这些设置在创建和重建时都会应用;自适应主表单,支持 1:N/N:N 子网格(可放置在节之间,或使用tab/column/after/before放置在标签页/表单列中,并在重建时移至指定位置);逐字段表单控件(readOnly/hidden/after定位,以及使用prune: false非破坏性地编辑表单子集);快速创建/快速视图表单(formType)+ 快速视图放置(forms[].quickViews[]);按表单分配安全角色(forms[].securityRoles——指定此表单面向的personas[],或设置everyone: true;在角色创建后应用。未分配角色的表单对所有角色可见,因此此设置用于限制表单,而非授予访问权限);设置表打开时使用的表单(forms[].isDefault/entities[].mainFormOrder用于设置表的 Main Form Set 顺序,对现有表也有效——但用户记住的表单仍会优先为该用户打开;见 app-spec-schema.md);选项列图表;业务规则(businessRules[]——以现代工作流对象模型编写并激活;见上文的环境限制);安全角色(personas[]——每种用户类型对应一个角色,按其待完成工作量身定制,并授予应用访问权限,使非管理员也能打开应用);仪表板(dashboards[]——图表/列表/iframe/webresource 磁贴)+ 仪表板站点地图放置;生成式页面(pages[]——默认采用 genpage 优先方式,通过 SDK 写入,并作为GenPage站点地图子区域呈现;支持通过download-model-app.js完整创建 + 编辑往返);现代命令栏按钮(commands[]),包括飞出菜单/拆分按钮菜单;丰富的视图筛选器(eq-userid/this-week/in/not-in);自定义网格渲染(entities[].columns[].visualization——径向刻度盘/折线图/热力图/星级评分,支持预览);Web 资源 + 表单 JS 事件处理程序;带有多父级$parents+statusReason的示例数据。请参阅docs/app-builder-capabilities.md和references/app-spec-schema.md(以及其所链接的条件字段说明app-spec-schema-advanced.md)——请根据这些文档编写;编写规格时无需阅读 SDK、lint 或引擎。