dsh-d365-kit 0.34.1 → 0.34.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/CHANGELOG.md +318 -308
  2. package/CHANGELOG.zh-CN.md +317 -307
  3. package/INSTALL.md +94 -94
  4. package/INSTALL.zh-CN.md +93 -93
  5. package/LICENSE +21 -21
  6. package/README.md +336 -327
  7. package/README.zh-CN.md +336 -327
  8. package/client.js +1818 -1818
  9. package/cordis.patch.yml +44 -44
  10. package/docs/en/architecture.md +172 -172
  11. package/docs/en/checks.md +184 -184
  12. package/docs/en/installation.md +151 -151
  13. package/docs/en/permissions.md +161 -161
  14. package/docs/en/scenarios.md +202 -202
  15. package/docs/en/tools.md +665 -665
  16. package/docs/en/troubleshooting.md +131 -131
  17. package/docs/zh/architecture.md +172 -172
  18. package/docs/zh/checks.md +184 -184
  19. package/docs/zh/installation.md +151 -151
  20. package/docs/zh/permissions.md +161 -161
  21. package/docs/zh/scenarios.md +202 -202
  22. package/docs/zh/tools.md +665 -665
  23. package/docs/zh/troubleshooting.md +131 -131
  24. package/examples/prompts.en.md +206 -206
  25. package/examples/prompts.zh-CN.md +206 -206
  26. package/examples/waivers.example.json +20 -20
  27. package/icon.svg +6 -6
  28. package/index.js +139 -139
  29. package/lib/auth.js +181 -181
  30. package/lib/client.js +496 -496
  31. package/lib/code-check.js +549 -549
  32. package/lib/config.js +323 -323
  33. package/lib/contract-history.js +49 -49
  34. package/lib/contract.js +583 -583
  35. package/lib/csharp-check.js +493 -493
  36. package/lib/data-migrate.js +1318 -1318
  37. package/lib/data-policy.js +78 -78
  38. package/lib/error-codes.js +80 -80
  39. package/lib/gitlab.js +161 -161
  40. package/lib/health.js +205 -205
  41. package/lib/migrate.js +732 -732
  42. package/lib/preflight.js +116 -116
  43. package/lib/refs.js +191 -191
  44. package/lib/resources.js +320 -320
  45. package/lib/retention-defaults.js +17 -17
  46. package/lib/retention.js +384 -384
  47. package/lib/runtime.js +232 -232
  48. package/lib/settings-http.js +512 -512
  49. package/lib/settings-store.js +371 -371
  50. package/lib/snapshot.js +149 -149
  51. package/lib/tool-code-check.js +133 -133
  52. package/lib/tool-configure.js +169 -169
  53. package/lib/tool-contract.js +376 -376
  54. package/lib/tool-data-sync.js +559 -559
  55. package/lib/tool-data-write.js +203 -203
  56. package/lib/tool-data.js +282 -282
  57. package/lib/tool-diff.js +484 -484
  58. package/lib/tool-environments.js +106 -106
  59. package/lib/tool-flows.js +540 -540
  60. package/lib/tool-health.js +127 -127
  61. package/lib/tool-layers.js +497 -497
  62. package/lib/tool-metadata.js +417 -417
  63. package/lib/tool-migrate.js +102 -102
  64. package/lib/tool-refs.js +206 -206
  65. package/lib/tool-reports.js +299 -299
  66. package/lib/tool-sync.js +430 -430
  67. package/lib/tool-write.js +563 -563
  68. package/lib/waivers.js +83 -83
  69. package/lib/write-actions.js +1374 -1374
  70. package/lib/write-policy.js +199 -199
  71. package/lib/write-store.js +167 -167
  72. package/locale/en.json +68 -68
  73. package/locale/zh.json +68 -68
  74. package/package.json +2 -2
  75. package/skills/d365-troubleshooting/SKILL.md +115 -115
package/docs/zh/checks.md CHANGED
@@ -1,184 +1,184 @@
1
- # 检查规则
2
-
3
- **[English](../en/checks.md)** | 简体中文
4
-
5
- 三类检查工具回答不同问题:
6
-
7
- | 工具 | 问题 | 数据来源 |
8
- | --- | --- | --- |
9
- | `d365_migrate` | 把这个解决方案导进目标环境会发生什么 | 实时读,逐组件查层 |
10
- | `d365_contract` | 上游的注册与引用,下游是否还在履行 | 实时读(漂移要看当下) |
11
- | `d365_code_check` | 这次 MR 的代码改动在目标环境跑得起来吗 | GitLab MR + 实时读环境 |
12
-
13
- 共同约定:**高置信直接阻断,中置信降为警告,静态判不了的进人工确认**——宁可让人多看一眼,也不要用一条误报把工具变成噪声。
14
-
15
- ---
16
-
17
- ## 迁移评估(d365_migrate assess)
18
-
19
- 逐组件比对,产生这些发现:
20
-
21
- | 规则 | 判据 | 级别 |
22
- | --- | --- | --- |
23
- | `component-missing` | 组件在目标环境不存在 | info |
24
- | `solution-absent` | 目标没有这个解决方案(首次导入) | warning |
25
- | `version-not-newer` | 目标版本不低于源版本(导入不会覆盖成新的) | warning |
26
- | `managed-mismatch` | 源与目标的受管状态不一致 | warning |
27
- | `source-unmanaged` | 源是未受管,导入后形成无法卸载清除的层 | warning |
28
- | `option-values-lost` | 目标有、源没有的选项值——导入后会被删掉 | **critical** |
29
- | `option-label-drift` | 同一值标签不同 | warning |
30
- | `shadowed` | 目标该组件最上层是别的解决方案,导入后其定义失效 | 汇总列出 |
31
-
32
- ### 组件类型与层
33
-
34
- 层表能按名称查的类型只有六种:
35
-
36
- | `msdyn_solutioncomponentname` | componenttype |
37
- | --- | --- |
38
- | `Entity` | 1 |
39
- | `Attribute` | 2 |
40
- | `OptionSet` | 9 |
41
- | `EntityRelationship` | 10 |
42
- | `Workflow` | 29 |
43
- | `WebResource` | 61 |
44
-
45
- 其余类型(36 报表、50 图表/安全类、70 应用类…)在层表里查不到对应名称,只做**存在性检查**,报告里标「未评估」。
46
-
47
- 跨环境识别的键来自层记录的 `msdyn_componentjson`:Entity → `logicalname`,Attribute → `logicalname` + 父表,OptionSet → `name`,EntityRelationship → `schemaname`,Workflow → `name` + `category`,WebResource → `name`。
48
-
49
- ### 为什么慢
50
-
51
- `msdyn_componentlayer` 是虚拟实体,只能按「组件 id + 组件类型」逐个查,无法按解决方案批量取;单组件约 2 秒。所以有 `maxComponents`(默认 150)与 `concurrency`(默认 4,上限 8),`full: true` 才评估全部。
52
-
53
- ### 读报告的顺序
54
-
55
- 1. 先看**严重**:有没有丢选项值;
56
- 2. 再看**遮蔽**:导入后谁的定义会失效,是不是自己家的受管 patch;
57
- 3. 最后看**缺失与版本**:目标缺组件通常意味着发布链路上少了一步;
58
- 4. 结论落地:能并入受管解决方案随升级走,就不要单独发一个未受管的小解决方案——层是按导入时间叠的,下一次谁的 patch 进来就会把它盖回去。
59
-
60
- ---
61
-
62
- ## 契约漂移(d365_contract)
63
-
64
- 把上游的插件注册与可视化组件引用当成**契约**,检查下游是否还满足。
65
-
66
- | 规则 | 判据 | 级别 |
67
- | --- | --- | --- |
68
- | `assembly-missing` | 上游有、下游没有该程序集 | 阻断 |
69
- | `assembly-version-older` | 下游版本低于上游 | 警告 |
70
- | `plugin-type-missing` | 上游注册的类型,下游没有 | 阻断 |
71
- | `plugin-step-missing` | 类型在,但缺「消息 + stage + mode」组合的步骤 | 阻断 |
72
- | `form-library-missing` | 下游表单引用的 Web 资源不存在 | 阻断 |
73
- | `sitemap-missing` | 上游的自定义站点地图在下游没有 | 警告 |
74
- | `handler-missing` | 表单 Handler 指定的函数在项目脚本里找不到 | 警告 |
75
- | `option-value-missing` | 下游缺上游的选项值(或多出上游没有的值) | 警告 |
76
-
77
- 细节:
78
-
79
- - 默认只比**未受管**程序集;受管程序集来自微软或受管解决方案,本地改不了;
80
- - 步骤按「类型 + 消息 + stage + mode」比对:async 与 sync 是两条不同的契约;
81
- - 选项值比对需要显式传 `optionSets`(逐个读 `GlobalOptionSetDefinitions`),不传就不比;
82
- - 下游比上游**多**的内容不报阻断,只在选项集多值时提示「确认是否已回灌」。
83
-
84
- ### Handler 检查
85
-
86
- `handlers: true` 时下载表单引用的项目脚本,核对 `functionName` 是否存在。
87
-
88
- - 只判定**项目脚本**。前缀默认从源环境的未受管解决方案推断——取占多数的那个发布者前缀,再加 `new_`——所以换一个组织、换一个前缀都不用改配置;要精确指定就在配置里写 `handlerPrefixes`。合作方与示例解决方案故意排除:它们的包是压缩过的,内部名字被混淆,全算进来每个 Handler 都会像缺失(某真实环境上:94 条而不是 14 条);
89
- - 先查 Handler 自己的库,找不到再扩大到该表单加载的其它项目库(表单级共享作用域是真实存在的:一个库提供函数,另一个库绑定);
90
- - 平台注入的 Handler(`absoluteurl_onchange`、`Main_Form_onload` 等)来自系统脚本,不参与判定;
91
- - 逐个下载有上限(400 个脚本/环境),超出的部分按「读不到内容、无法确认」处理,不误报成阻断。
92
-
93
- 首版按「表单加载的全部脚本库」判定时产生 306 条警告,收敛到「项目库 + 自己的库优先」后是 14 条,全部是读完了确实没有。**这个数字本身就是设计目标:可行动的少量,而不是不可读的大量。**
94
-
95
- ### 豁免与趋势
96
-
97
- ```json
98
- { "action": "waive", "rule": "form-library-missing", "env": "sit",
99
- "object": "/_static/_common/scripts/CommandbarActions.js", "reason": "该环境不用 SLA 对话框", "days": 30 }
100
- ```
101
-
102
- - 写在 `.d365/waivers.json`;`env` 支持 `*`,`object` 支持 `*` 与前缀 `x*`;
103
- - 必须有过期时间(默认 30 天),过期自动重新出现在报告里;
104
- - 报告显示「已豁免 N 条」,被豁免的条目不进阻断列表;
105
- - 每次 drift 追加一条到 `.d365/reports/contract-history.json`(保留 60 条),下次跑同一组 `source → targets` 给出「新增 / 已消失」。
106
-
107
- ### 为什么读实时数据
108
-
109
- 插件注册规模:一个环境约 376 个程序集、6400 个类型、5900 个有步骤的类型(约 9.4 万条 step),几十秒读完。用几天前的快照做漂移会得出假结论,所以这里不用快照。
110
-
111
- ---
112
-
113
- ## 代码检查(d365_code_check)
114
-
115
- 读一次 MR,只分析它改到的文件。需要 GitLab 只读令牌(`read_api`),令牌所在用户至少 **Reporter**。
116
-
117
- ### JS / TS
118
-
119
- 检查链:改动文件 → 按**文件名**匹配环境里的 Web 资源(路径不一致时按同名候选取最接近的一个,报告里标「推断,待确认」)→ 找出引用该资源的表单 → 逐条比对改动行。
120
-
121
- | 检查 | 结论 |
122
- | --- | --- |
123
- | 新增行读取的字段不在**任何**引用该库的表单上 | 阻断一次 |
124
- | 字段只在部分表单上(脚本可能不会在这些表单走到该分支) | 警告,列出缺的表单(最多 3 个) |
125
- | 引用的实体不存在(表单 `datafieldname`、`Xrm.WebApi` 的实体集) | 阻断 |
126
- | 删除的函数仍被表单 Handler 引用 | 阻断 |
127
- | 删掉的文件仍被表单引用 | 阻断(没有表单引用时不报) |
128
- | `columns.get('x')` | 人工确认(视图/子网格列,不在表单字段里是正常的) |
129
- | 带前缀的字符串下标 `["contoso_x"]` | 人工确认(静态判不出是字段还是普通键) |
130
- | 新出现硬编码 GUID、选项值、环境地址 | 警告或人工确认 |
131
- | 配置数据、字段级安全、运行用户权限 | 人工确认(静态判不了) |
132
-
133
- 取值写法覆盖:`getAttribute`、`getControl`、`attributes.get`、`formContext.data.entity.attributes.get`、`Xrm.Page.getAttribute`、`attributes[...]`、带前缀字符串下标。
134
- 只靠 `getAttribute` 一条正则会漏掉大量编译产物(实测一个 103 KB 的 TS 编译脚本命中 0 次),所以按写法分成三组:确定是字段、确定是列、需要人判。
135
-
136
- ### C# / 工程文件
137
-
138
- 按路径里**最靠内的工程目录**推断程序集名(`.csproj` 里有 `<AssemblyName>` 时以它为准)→ 与环境里的 `pluginassemblies` 做**精确匹配** → 查 `plugintypes`、`sdkmessageprocessingsteps`、`sdkmessageprocessingstepimages`。
139
-
140
- | 检查 | 结论 |
141
- | --- | --- |
142
- | 程序集在目标环境不存在(共享工程如 `.Processes` 属于这种情况) | 警告 + 人工确认该文件最终随哪个程序集发布 |
143
- | 源码版本与环境程序集版本不一致 | 警告 |
144
- | `IPlugin` 类在目标环境没有对应插件类型 | 阻断 |
145
- | 类型存在但没有任何步骤(改了也不会被触发) | 警告 |
146
- | 代码读 `PreEntityImages["X"]` / `PostEntityImages["X"]`,对应步骤没注册该镜像 | 阻断(Create 步骤不检查 pre-image) |
147
- | 步骤的 `filteringattributes` 引用该实体上不存在的字段 | 阻断 |
148
- | 枚举 `contoso_*` 字面量在目标既不是表也不是全局选项集 | 人工确认 |
149
- | 同一 `message\|stage` 上 ≥2 个步骤 | 警告(rank 重复时执行顺序不确定) |
150
- | 代码用到 `secureConfig` / 构造函数两个 string 参数 | 人工确认(Web API 不返回 secure configuration 内容) |
151
- | 沙箱可能禁止的 API(`System.IO`、`System.Net`、`Thread.Sleep`…) | 警告 |
152
-
153
- 程序集名只做精确匹配,不猜近似的——猜错会把类型/步骤核对到别的程序集上,比不核对更糟。
154
-
155
- ### 报告结构
156
-
157
- ```
158
- 改动文件表
159
- 每文件影响面(映射到哪个资源/程序集、哪些表单/类型)
160
- 阻断(必须修)
161
- 警告(建议修,或登记豁免)
162
- 人工确认(HC-01…,每条写清为什么机器判不了)
163
- ```
164
-
165
- ### 已知边界
166
-
167
- - 只分析 MR 的增删行,不读仓库其它文件(不解析 import、不跟踪调用链);
168
- - 路径 → Web 资源名靠文件名推断,仓库路径与资源名不一致时会标「推断」;
169
- - C# 的过滤属性、镜像、类型只看注册信息;secure/unsecure 配置内容与 rank 之外的隐式顺序仍需人工确认;
170
- - 不判断运行用户权限与实际数据形态。
171
-
172
- ---
173
-
174
- ## 三类检查的分工
175
-
176
- ```
177
- 代码改动 ──d365_code_check──> 这次改动能不能上线(单次、面向 MR)
178
- 上游 vs 下游 ──d365_contract──> 环境之间的契约有没有破(持续、面向版本)
179
- 解决方案 ──d365_migrate──> 这次导入会不会压掉别人(单次、面向发布)
180
- 谁覆盖了它 ──d365_layers──> 为什么这个环境是现在这个样子
181
- 谁在用它 ──d365_refs──> 改/删它的影响面
182
- ```
183
-
184
- 它们不互相替代:`d365_layers` 告诉你「谁覆盖」,`d365_refs` 告诉你「谁引用」,`d365_code_check` 告诉你「这次改的行有没有落在不存在的东西上」。
1
+ # 检查规则
2
+
3
+ **[English](../en/checks.md)** | 简体中文
4
+
5
+ 三类检查工具回答不同问题:
6
+
7
+ | 工具 | 问题 | 数据来源 |
8
+ | --- | --- | --- |
9
+ | `d365_migrate` | 把这个解决方案导进目标环境会发生什么 | 实时读,逐组件查层 |
10
+ | `d365_contract` | 上游的注册与引用,下游是否还在履行 | 实时读(漂移要看当下) |
11
+ | `d365_code_check` | 这次 MR 的代码改动在目标环境跑得起来吗 | GitLab MR + 实时读环境 |
12
+
13
+ 共同约定:**高置信直接阻断,中置信降为警告,静态判不了的进人工确认**——宁可让人多看一眼,也不要用一条误报把工具变成噪声。
14
+
15
+ ---
16
+
17
+ ## 迁移评估(d365_migrate assess)
18
+
19
+ 逐组件比对,产生这些发现:
20
+
21
+ | 规则 | 判据 | 级别 |
22
+ | --- | --- | --- |
23
+ | `component-missing` | 组件在目标环境不存在 | info |
24
+ | `solution-absent` | 目标没有这个解决方案(首次导入) | warning |
25
+ | `version-not-newer` | 目标版本不低于源版本(导入不会覆盖成新的) | warning |
26
+ | `managed-mismatch` | 源与目标的受管状态不一致 | warning |
27
+ | `source-unmanaged` | 源是未受管,导入后形成无法卸载清除的层 | warning |
28
+ | `option-values-lost` | 目标有、源没有的选项值——导入后会被删掉 | **critical** |
29
+ | `option-label-drift` | 同一值标签不同 | warning |
30
+ | `shadowed` | 目标该组件最上层是别的解决方案,导入后其定义失效 | 汇总列出 |
31
+
32
+ ### 组件类型与层
33
+
34
+ 层表能按名称查的类型只有六种:
35
+
36
+ | `msdyn_solutioncomponentname` | componenttype |
37
+ | --- | --- |
38
+ | `Entity` | 1 |
39
+ | `Attribute` | 2 |
40
+ | `OptionSet` | 9 |
41
+ | `EntityRelationship` | 10 |
42
+ | `Workflow` | 29 |
43
+ | `WebResource` | 61 |
44
+
45
+ 其余类型(36 报表、50 图表/安全类、70 应用类…)在层表里查不到对应名称,只做**存在性检查**,报告里标「未评估」。
46
+
47
+ 跨环境识别的键来自层记录的 `msdyn_componentjson`:Entity → `logicalname`,Attribute → `logicalname` + 父表,OptionSet → `name`,EntityRelationship → `schemaname`,Workflow → `name` + `category`,WebResource → `name`。
48
+
49
+ ### 为什么慢
50
+
51
+ `msdyn_componentlayer` 是虚拟实体,只能按「组件 id + 组件类型」逐个查,无法按解决方案批量取;单组件约 2 秒。所以有 `maxComponents`(默认 150)与 `concurrency`(默认 4,上限 8),`full: true` 才评估全部。
52
+
53
+ ### 读报告的顺序
54
+
55
+ 1. 先看**严重**:有没有丢选项值;
56
+ 2. 再看**遮蔽**:导入后谁的定义会失效,是不是自己家的受管 patch;
57
+ 3. 最后看**缺失与版本**:目标缺组件通常意味着发布链路上少了一步;
58
+ 4. 结论落地:能并入受管解决方案随升级走,就不要单独发一个未受管的小解决方案——层是按导入时间叠的,下一次谁的 patch 进来就会把它盖回去。
59
+
60
+ ---
61
+
62
+ ## 契约漂移(d365_contract)
63
+
64
+ 把上游的插件注册与可视化组件引用当成**契约**,检查下游是否还满足。
65
+
66
+ | 规则 | 判据 | 级别 |
67
+ | --- | --- | --- |
68
+ | `assembly-missing` | 上游有、下游没有该程序集 | 阻断 |
69
+ | `assembly-version-older` | 下游版本低于上游 | 警告 |
70
+ | `plugin-type-missing` | 上游注册的类型,下游没有 | 阻断 |
71
+ | `plugin-step-missing` | 类型在,但缺「消息 + stage + mode」组合的步骤 | 阻断 |
72
+ | `form-library-missing` | 下游表单引用的 Web 资源不存在 | 阻断 |
73
+ | `sitemap-missing` | 上游的自定义站点地图在下游没有 | 警告 |
74
+ | `handler-missing` | 表单 Handler 指定的函数在项目脚本里找不到 | 警告 |
75
+ | `option-value-missing` | 下游缺上游的选项值(或多出上游没有的值) | 警告 |
76
+
77
+ 细节:
78
+
79
+ - 默认只比**未受管**程序集;受管程序集来自微软或受管解决方案,本地改不了;
80
+ - 步骤按「类型 + 消息 + stage + mode」比对:async 与 sync 是两条不同的契约;
81
+ - 选项值比对需要显式传 `optionSets`(逐个读 `GlobalOptionSetDefinitions`),不传就不比;
82
+ - 下游比上游**多**的内容不报阻断,只在选项集多值时提示「确认是否已回灌」。
83
+
84
+ ### Handler 检查
85
+
86
+ `handlers: true` 时下载表单引用的项目脚本,核对 `functionName` 是否存在。
87
+
88
+ - 只判定**项目脚本**。前缀默认从源环境的未受管解决方案推断——取占多数的那个发布者前缀,再加 `new_`——所以换一个组织、换一个前缀都不用改配置;要精确指定就在配置里写 `handlerPrefixes`。合作方与示例解决方案故意排除:它们的包是压缩过的,内部名字被混淆,全算进来每个 Handler 都会像缺失(某真实环境上:94 条而不是 14 条);
89
+ - 先查 Handler 自己的库,找不到再扩大到该表单加载的其它项目库(表单级共享作用域是真实存在的:一个库提供函数,另一个库绑定);
90
+ - 平台注入的 Handler(`absoluteurl_onchange`、`Main_Form_onload` 等)来自系统脚本,不参与判定;
91
+ - 逐个下载有上限(400 个脚本/环境),超出的部分按「读不到内容、无法确认」处理,不误报成阻断。
92
+
93
+ 首版按「表单加载的全部脚本库」判定时产生 306 条警告,收敛到「项目库 + 自己的库优先」后是 14 条,全部是读完了确实没有。**这个数字本身就是设计目标:可行动的少量,而不是不可读的大量。**
94
+
95
+ ### 豁免与趋势
96
+
97
+ ```json
98
+ { "action": "waive", "rule": "form-library-missing", "env": "sit",
99
+ "object": "/_static/_common/scripts/CommandbarActions.js", "reason": "该环境不用 SLA 对话框", "days": 30 }
100
+ ```
101
+
102
+ - 写在 `.d365/waivers.json`;`env` 支持 `*`,`object` 支持 `*` 与前缀 `x*`;
103
+ - 必须有过期时间(默认 30 天),过期自动重新出现在报告里;
104
+ - 报告显示「已豁免 N 条」,被豁免的条目不进阻断列表;
105
+ - 每次 drift 追加一条到 `.d365/reports/contract-history.json`(保留 60 条),下次跑同一组 `source → targets` 给出「新增 / 已消失」。
106
+
107
+ ### 为什么读实时数据
108
+
109
+ 插件注册规模:一个环境约 376 个程序集、6400 个类型、5900 个有步骤的类型(约 9.4 万条 step),几十秒读完。用几天前的快照做漂移会得出假结论,所以这里不用快照。
110
+
111
+ ---
112
+
113
+ ## 代码检查(d365_code_check)
114
+
115
+ 读一次 MR,只分析它改到的文件。需要 GitLab 只读令牌(`read_api`),令牌所在用户至少 **Reporter**。
116
+
117
+ ### JS / TS
118
+
119
+ 检查链:改动文件 → 按**文件名**匹配环境里的 Web 资源(路径不一致时按同名候选取最接近的一个,报告里标「推断,待确认」)→ 找出引用该资源的表单 → 逐条比对改动行。
120
+
121
+ | 检查 | 结论 |
122
+ | --- | --- |
123
+ | 新增行读取的字段不在**任何**引用该库的表单上 | 阻断一次 |
124
+ | 字段只在部分表单上(脚本可能不会在这些表单走到该分支) | 警告,列出缺的表单(最多 3 个) |
125
+ | 引用的实体不存在(表单 `datafieldname`、`Xrm.WebApi` 的实体集) | 阻断 |
126
+ | 删除的函数仍被表单 Handler 引用 | 阻断 |
127
+ | 删掉的文件仍被表单引用 | 阻断(没有表单引用时不报) |
128
+ | `columns.get('x')` | 人工确认(视图/子网格列,不在表单字段里是正常的) |
129
+ | 带前缀的字符串下标 `["contoso_x"]` | 人工确认(静态判不出是字段还是普通键) |
130
+ | 新出现硬编码 GUID、选项值、环境地址 | 警告或人工确认 |
131
+ | 配置数据、字段级安全、运行用户权限 | 人工确认(静态判不了) |
132
+
133
+ 取值写法覆盖:`getAttribute`、`getControl`、`attributes.get`、`formContext.data.entity.attributes.get`、`Xrm.Page.getAttribute`、`attributes[...]`、带前缀字符串下标。
134
+ 只靠 `getAttribute` 一条正则会漏掉大量编译产物(实测一个 103 KB 的 TS 编译脚本命中 0 次),所以按写法分成三组:确定是字段、确定是列、需要人判。
135
+
136
+ ### C# / 工程文件
137
+
138
+ 按路径里**最靠内的工程目录**推断程序集名(`.csproj` 里有 `<AssemblyName>` 时以它为准)→ 与环境里的 `pluginassemblies` 做**精确匹配** → 查 `plugintypes`、`sdkmessageprocessingsteps`、`sdkmessageprocessingstepimages`。
139
+
140
+ | 检查 | 结论 |
141
+ | --- | --- |
142
+ | 程序集在目标环境不存在(共享工程如 `.Processes` 属于这种情况) | 警告 + 人工确认该文件最终随哪个程序集发布 |
143
+ | 源码版本与环境程序集版本不一致 | 警告 |
144
+ | `IPlugin` 类在目标环境没有对应插件类型 | 阻断 |
145
+ | 类型存在但没有任何步骤(改了也不会被触发) | 警告 |
146
+ | 代码读 `PreEntityImages["X"]` / `PostEntityImages["X"]`,对应步骤没注册该镜像 | 阻断(Create 步骤不检查 pre-image) |
147
+ | 步骤的 `filteringattributes` 引用该实体上不存在的字段 | 阻断 |
148
+ | 枚举 `contoso_*` 字面量在目标既不是表也不是全局选项集 | 人工确认 |
149
+ | 同一 `message\|stage` 上 ≥2 个步骤 | 警告(rank 重复时执行顺序不确定) |
150
+ | 代码用到 `secureConfig` / 构造函数两个 string 参数 | 人工确认(Web API 不返回 secure configuration 内容) |
151
+ | 沙箱可能禁止的 API(`System.IO`、`System.Net`、`Thread.Sleep`…) | 警告 |
152
+
153
+ 程序集名只做精确匹配,不猜近似的——猜错会把类型/步骤核对到别的程序集上,比不核对更糟。
154
+
155
+ ### 报告结构
156
+
157
+ ```
158
+ 改动文件表
159
+ 每文件影响面(映射到哪个资源/程序集、哪些表单/类型)
160
+ 阻断(必须修)
161
+ 警告(建议修,或登记豁免)
162
+ 人工确认(HC-01…,每条写清为什么机器判不了)
163
+ ```
164
+
165
+ ### 已知边界
166
+
167
+ - 只分析 MR 的增删行,不读仓库其它文件(不解析 import、不跟踪调用链);
168
+ - 路径 → Web 资源名靠文件名推断,仓库路径与资源名不一致时会标「推断」;
169
+ - C# 的过滤属性、镜像、类型只看注册信息;secure/unsecure 配置内容与 rank 之外的隐式顺序仍需人工确认;
170
+ - 不判断运行用户权限与实际数据形态。
171
+
172
+ ---
173
+
174
+ ## 三类检查的分工
175
+
176
+ ```
177
+ 代码改动 ──d365_code_check──> 这次改动能不能上线(单次、面向 MR)
178
+ 上游 vs 下游 ──d365_contract──> 环境之间的契约有没有破(持续、面向版本)
179
+ 解决方案 ──d365_migrate──> 这次导入会不会压掉别人(单次、面向发布)
180
+ 谁覆盖了它 ──d365_layers──> 为什么这个环境是现在这个样子
181
+ 谁在用它 ──d365_refs──> 改/删它的影响面
182
+ ```
183
+
184
+ 它们不互相替代:`d365_layers` 告诉你「谁覆盖」,`d365_refs` 告诉你「谁引用」,`d365_code_check` 告诉你「这次改的行有没有落在不存在的东西上」。