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
@@ -1,199 +1,199 @@
1
- /**
2
- * Write policy: who may change what, in which environment.
3
- *
4
- * The plugin stays read-only unless a person grants a capability here. The
5
- * grant lives in the same `d365.config.json` humans already edit, so it is
6
- * per-machine by default: the file is not shared, and a teammate's install
7
- * has its own policy.
8
- *
9
- * Every request consults this file: metadata reads, business-record reads and
10
- * every write. Metadata reads are the one capability granted by default, so an
11
- * install that never opened this page still behaves like a read-only tool.
12
- *
13
- * Capabilities are deliberately narrow. Every one of them maps to one official
14
- * Dataverse action, which keeps the blast radius of a mistake to the thing the
15
- * person asked for.
16
- */
17
- import { readFile, writeFile } from 'node:fs/promises'
18
-
19
- export const CAPABILITIES = {
20
- 'metadata.read': {
21
- group: '元数据',
22
- label: '读取元数据',
23
- action: 'GET',
24
- description:
25
- '允许读取表、字段、表单、视图、插件、解决方案层等元数据定义。未在配置里显式列出的环境默认开启;关掉后连元数据工具也不可用。',
26
- },
27
- 'optionValue.add': {
28
- group: '选项集',
29
- label: '新增选项值',
30
- action: 'InsertOptionValue',
31
- description: '向全局选项集追加新选项(不修改已有值)。',
32
- },
33
- 'optionValue.updateLabel': {
34
- group: '选项集',
35
- label: '修改选项标签',
36
- action: 'UpdateOptionValue',
37
- description: '只改选项的显示标签,不改值、不改绑定关系。',
38
- },
39
- 'optionValue.delete': {
40
- group: '选项集',
41
- label: '删除选项值',
42
- action: 'DeleteOptionValue',
43
- risk: 'high',
44
- description: '从选项集删除一个值;已有记录若正在使用该值,删除会失败,标签也无法找回。',
45
- },
46
- 'attribute.add': {
47
- group: '字段',
48
- label: '新增字段',
49
- action: 'CreateAttribute',
50
- description: '在指定表上新建列(文本、整数、小数、货币、布尔、日期时间)。',
51
- },
52
- 'attribute.update': {
53
- group: '字段',
54
- label: '修改字段显示名',
55
- action: 'UpdateAttribute',
56
- description: '改列的显示名、描述、必填级别;不改类型、不改数据。',
57
- },
58
- 'attribute.delete': {
59
- group: '字段',
60
- label: '删除字段',
61
- action: 'DeleteAttribute',
62
- risk: 'high',
63
- description: '永久删除列及其数据,不可恢复;有依赖(视图、表单、流程)时会失败。',
64
- },
65
- 'entity.update': {
66
- group: '表',
67
- label: '修改表显示名',
68
- action: 'UpdateEntity',
69
- description: '改表的显示名与描述;不改列、不改数据。',
70
- },
71
- 'view.columns': {
72
- group: '视图',
73
- label: '修改视图列',
74
- action: 'UpdateSavedQuery',
75
- description: '在系统视图里增删列(直接改 layoutxml);不动筛选条件,也不动个人视图。',
76
- },
77
- 'layer.remove': {
78
- group: '组件层',
79
- label: '移除自定义层',
80
- action: 'RemoveActiveCustomizations',
81
- risk: 'high',
82
- description:
83
- '删掉组件最上面的未受管自定义层(解决方案层里显示为 Active 的那一层),让下面的受管层重新生效。不可恢复;支持表、字段、全局选项集、表单、视图、流程、Web 资源、插件程序集/类型/步骤、自定义 API。',
84
- },
85
- 'message.invoke': {
86
- group: '消息',
87
- label: '调用消息',
88
- action: 'Custom Action / Function',
89
- risk: 'high',
90
- description:
91
- '允许调用表外的自定义消息(action 或 function,例如 WinOpportunity、contoso_DoSomething)。消息的行为由插件决定,可能写数据、发通知;每次调用前都会在对话里弹出确认卡片并显示请求体。默认关闭。',
92
- },
93
- 'data.read': {
94
- group: '数据',
95
- label: '读取业务数据',
96
- action: 'GET',
97
- description:
98
- '允许读取表中的业务记录(客户、订单、配置数据等),而不只是元数据定义。默认关闭;关闭时任何非元数据表都读不到。',
99
- },
100
- 'data.write': {
101
- group: '数据',
102
- label: '新增/修改业务数据',
103
- action: 'POST/PATCH',
104
- risk: 'high',
105
- description:
106
- '允许新建或修改表中的业务记录(例如补一条配置数据)。不含删除;每次写入前都会在对话里弹出确认卡片。默认关闭。',
107
- },
108
- }
109
-
110
- export const CAPABILITY_KEYS = Object.keys(CAPABILITIES)
111
-
112
- /** Read the raw config file; `write` is not part of the normalized config. */
113
- async function readRaw(configPath) {
114
- try {
115
- const text = (await readFile(configPath, 'utf8')).replace(/^\uFEFF/, '')
116
- const parsed = JSON.parse(text)
117
- return parsed && typeof parsed === 'object' ? parsed : {}
118
- } catch {
119
- return {}
120
- }
121
- }
122
-
123
- /**
124
- * Bumped when the meaning of an entry changes. Version 2 added `metadata.read`:
125
- * files written before it list only the old capabilities, so those entries get
126
- * the (previously implicit) metadata read added back rather than losing it.
127
- */
128
- const POLICY_VERSION = 2
129
-
130
- /**
131
- * @returns `{ environments: { [envKey]: capability[] }, solutionHint }` —
132
- * an environment missing here has never been configured and keeps the
133
- * default metadata read; an empty array means every capability is revoked.
134
- */
135
- export async function readPolicy(configPath) {
136
- if (!configPath) return { environments: {} }
137
- const raw = await readRaw(configPath)
138
- const version = Number(raw.write?.capabilityVersion ?? 1)
139
- const environments = {}
140
- for (const [key, value] of Object.entries(raw.write?.environments ?? {})) {
141
- if (!Array.isArray(value)) continue
142
- // An empty list is meaningful: it is how a page "revokes everything" for an
143
- // environment, including the default-on metadata read. Dropping it here
144
- // would silently turn the default back on.
145
- const allowed = value.filter((item) => CAPABILITY_KEYS.includes(item))
146
- if (version < POLICY_VERSION && !allowed.includes('metadata.read')) allowed.push('metadata.read')
147
- environments[key] = allowed
148
- }
149
- return {
150
- environments,
151
- solutionHint: raw.write?.solutionUniqueName ?? undefined,
152
- }
153
- }
154
-
155
- /** Replace the policy section, leaving the rest of the config untouched. */
156
- export async function savePolicy(configPath, policy) {
157
- const raw = await readRaw(configPath)
158
- const environments = {}
159
- for (const [key, value] of Object.entries(policy?.environments ?? {})) {
160
- // Keep an explicit empty list: absence means "not configured yet" (metadata
161
- // read on), an empty list means "everything off".
162
- environments[key] = Array.isArray(value) ? value.filter((item) => CAPABILITY_KEYS.includes(item)) : []
163
- }
164
-
165
- const write = { ...(raw.write ?? {}), environments, capabilityVersion: POLICY_VERSION }
166
- if (policy?.solutionUniqueName) write.solutionUniqueName = String(policy.solutionUniqueName).trim()
167
- else delete write.solutionUniqueName
168
-
169
- if (!Object.keys(environments).length && !write.solutionUniqueName) delete raw.write
170
- else raw.write = write
171
-
172
- await writeFile(configPath, `${JSON.stringify(raw, null, 2)}\n`, 'utf8')
173
- return readPolicy(configPath)
174
- }
175
-
176
- /** Capabilities granted to an environment that has no entry yet. */
177
- export const DEFAULT_GRANTED = ['metadata.read']
178
-
179
- /**
180
- * Whether `envKey` may run `capability`.
181
- *
182
- * An environment with no entry has never been configured, so it keeps the
183
- * metadata default. Once an entry exists it is the whole truth — an empty
184
- * array turns even that default off.
185
- */
186
- export function allows(policy, envKey, capability) {
187
- const granted = policy?.environments?.[envKey]
188
- if (Array.isArray(granted)) return granted.includes(capability)
189
- return DEFAULT_GRANTED.includes(capability)
190
- }
191
-
192
- /** Human-readable matrix for the tool and the page. */
193
- export function describePolicy(policy, environmentKeys) {
194
- const rows = environmentKeys.map((key) => ({
195
- environment: key,
196
- capabilities: CAPABILITY_KEYS.filter((capability) => allows(policy, key, capability)),
197
- }))
198
- return { capabilities: CAPABILITIES, rows }
199
- }
1
+ /**
2
+ * Write policy: who may change what, in which environment.
3
+ *
4
+ * The plugin stays read-only unless a person grants a capability here. The
5
+ * grant lives in the same `d365.config.json` humans already edit, so it is
6
+ * per-machine by default: the file is not shared, and a teammate's install
7
+ * has its own policy.
8
+ *
9
+ * Every request consults this file: metadata reads, business-record reads and
10
+ * every write. Metadata reads are the one capability granted by default, so an
11
+ * install that never opened this page still behaves like a read-only tool.
12
+ *
13
+ * Capabilities are deliberately narrow. Every one of them maps to one official
14
+ * Dataverse action, which keeps the blast radius of a mistake to the thing the
15
+ * person asked for.
16
+ */
17
+ import { readFile, writeFile } from 'node:fs/promises'
18
+
19
+ export const CAPABILITIES = {
20
+ 'metadata.read': {
21
+ group: '元数据',
22
+ label: '读取元数据',
23
+ action: 'GET',
24
+ description:
25
+ '允许读取表、字段、表单、视图、插件、解决方案层等元数据定义。未在配置里显式列出的环境默认开启;关掉后连元数据工具也不可用。',
26
+ },
27
+ 'optionValue.add': {
28
+ group: '选项集',
29
+ label: '新增选项值',
30
+ action: 'InsertOptionValue',
31
+ description: '向全局选项集追加新选项(不修改已有值)。',
32
+ },
33
+ 'optionValue.updateLabel': {
34
+ group: '选项集',
35
+ label: '修改选项标签',
36
+ action: 'UpdateOptionValue',
37
+ description: '只改选项的显示标签,不改值、不改绑定关系。',
38
+ },
39
+ 'optionValue.delete': {
40
+ group: '选项集',
41
+ label: '删除选项值',
42
+ action: 'DeleteOptionValue',
43
+ risk: 'high',
44
+ description: '从选项集删除一个值;已有记录若正在使用该值,删除会失败,标签也无法找回。',
45
+ },
46
+ 'attribute.add': {
47
+ group: '字段',
48
+ label: '新增字段',
49
+ action: 'CreateAttribute',
50
+ description: '在指定表上新建列(文本、整数、小数、货币、布尔、日期时间)。',
51
+ },
52
+ 'attribute.update': {
53
+ group: '字段',
54
+ label: '修改字段显示名',
55
+ action: 'UpdateAttribute',
56
+ description: '改列的显示名、描述、必填级别;不改类型、不改数据。',
57
+ },
58
+ 'attribute.delete': {
59
+ group: '字段',
60
+ label: '删除字段',
61
+ action: 'DeleteAttribute',
62
+ risk: 'high',
63
+ description: '永久删除列及其数据,不可恢复;有依赖(视图、表单、流程)时会失败。',
64
+ },
65
+ 'entity.update': {
66
+ group: '表',
67
+ label: '修改表显示名',
68
+ action: 'UpdateEntity',
69
+ description: '改表的显示名与描述;不改列、不改数据。',
70
+ },
71
+ 'view.columns': {
72
+ group: '视图',
73
+ label: '修改视图列',
74
+ action: 'UpdateSavedQuery',
75
+ description: '在系统视图里增删列(直接改 layoutxml);不动筛选条件,也不动个人视图。',
76
+ },
77
+ 'layer.remove': {
78
+ group: '组件层',
79
+ label: '移除自定义层',
80
+ action: 'RemoveActiveCustomizations',
81
+ risk: 'high',
82
+ description:
83
+ '删掉组件最上面的未受管自定义层(解决方案层里显示为 Active 的那一层),让下面的受管层重新生效。不可恢复;支持表、字段、全局选项集、表单、视图、流程、Web 资源、插件程序集/类型/步骤、自定义 API。',
84
+ },
85
+ 'message.invoke': {
86
+ group: '消息',
87
+ label: '调用消息',
88
+ action: 'Custom Action / Function',
89
+ risk: 'high',
90
+ description:
91
+ '允许调用表外的自定义消息(action 或 function,例如 WinOpportunity、contoso_DoSomething)。消息的行为由插件决定,可能写数据、发通知;每次调用前都会在对话里弹出确认卡片并显示请求体。默认关闭。',
92
+ },
93
+ 'data.read': {
94
+ group: '数据',
95
+ label: '读取业务数据',
96
+ action: 'GET',
97
+ description:
98
+ '允许读取表中的业务记录(客户、订单、配置数据等),而不只是元数据定义。默认关闭;关闭时任何非元数据表都读不到。',
99
+ },
100
+ 'data.write': {
101
+ group: '数据',
102
+ label: '新增/修改业务数据',
103
+ action: 'POST/PATCH',
104
+ risk: 'high',
105
+ description:
106
+ '允许新建或修改表中的业务记录(例如补一条配置数据)。不含删除;每次写入前都会在对话里弹出确认卡片。默认关闭。',
107
+ },
108
+ }
109
+
110
+ export const CAPABILITY_KEYS = Object.keys(CAPABILITIES)
111
+
112
+ /** Read the raw config file; `write` is not part of the normalized config. */
113
+ async function readRaw(configPath) {
114
+ try {
115
+ const text = (await readFile(configPath, 'utf8')).replace(/^\uFEFF/, '')
116
+ const parsed = JSON.parse(text)
117
+ return parsed && typeof parsed === 'object' ? parsed : {}
118
+ } catch {
119
+ return {}
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Bumped when the meaning of an entry changes. Version 2 added `metadata.read`:
125
+ * files written before it list only the old capabilities, so those entries get
126
+ * the (previously implicit) metadata read added back rather than losing it.
127
+ */
128
+ const POLICY_VERSION = 2
129
+
130
+ /**
131
+ * @returns `{ environments: { [envKey]: capability[] }, solutionHint }` —
132
+ * an environment missing here has never been configured and keeps the
133
+ * default metadata read; an empty array means every capability is revoked.
134
+ */
135
+ export async function readPolicy(configPath) {
136
+ if (!configPath) return { environments: {} }
137
+ const raw = await readRaw(configPath)
138
+ const version = Number(raw.write?.capabilityVersion ?? 1)
139
+ const environments = {}
140
+ for (const [key, value] of Object.entries(raw.write?.environments ?? {})) {
141
+ if (!Array.isArray(value)) continue
142
+ // An empty list is meaningful: it is how a page "revokes everything" for an
143
+ // environment, including the default-on metadata read. Dropping it here
144
+ // would silently turn the default back on.
145
+ const allowed = value.filter((item) => CAPABILITY_KEYS.includes(item))
146
+ if (version < POLICY_VERSION && !allowed.includes('metadata.read')) allowed.push('metadata.read')
147
+ environments[key] = allowed
148
+ }
149
+ return {
150
+ environments,
151
+ solutionHint: raw.write?.solutionUniqueName ?? undefined,
152
+ }
153
+ }
154
+
155
+ /** Replace the policy section, leaving the rest of the config untouched. */
156
+ export async function savePolicy(configPath, policy) {
157
+ const raw = await readRaw(configPath)
158
+ const environments = {}
159
+ for (const [key, value] of Object.entries(policy?.environments ?? {})) {
160
+ // Keep an explicit empty list: absence means "not configured yet" (metadata
161
+ // read on), an empty list means "everything off".
162
+ environments[key] = Array.isArray(value) ? value.filter((item) => CAPABILITY_KEYS.includes(item)) : []
163
+ }
164
+
165
+ const write = { ...(raw.write ?? {}), environments, capabilityVersion: POLICY_VERSION }
166
+ if (policy?.solutionUniqueName) write.solutionUniqueName = String(policy.solutionUniqueName).trim()
167
+ else delete write.solutionUniqueName
168
+
169
+ if (!Object.keys(environments).length && !write.solutionUniqueName) delete raw.write
170
+ else raw.write = write
171
+
172
+ await writeFile(configPath, `${JSON.stringify(raw, null, 2)}\n`, 'utf8')
173
+ return readPolicy(configPath)
174
+ }
175
+
176
+ /** Capabilities granted to an environment that has no entry yet. */
177
+ export const DEFAULT_GRANTED = ['metadata.read']
178
+
179
+ /**
180
+ * Whether `envKey` may run `capability`.
181
+ *
182
+ * An environment with no entry has never been configured, so it keeps the
183
+ * metadata default. Once an entry exists it is the whole truth — an empty
184
+ * array turns even that default off.
185
+ */
186
+ export function allows(policy, envKey, capability) {
187
+ const granted = policy?.environments?.[envKey]
188
+ if (Array.isArray(granted)) return granted.includes(capability)
189
+ return DEFAULT_GRANTED.includes(capability)
190
+ }
191
+
192
+ /** Human-readable matrix for the tool and the page. */
193
+ export function describePolicy(policy, environmentKeys) {
194
+ const rows = environmentKeys.map((key) => ({
195
+ environment: key,
196
+ capabilities: CAPABILITY_KEYS.filter((capability) => allows(policy, key, capability)),
197
+ }))
198
+ return { capabilities: CAPABILITIES, rows }
199
+ }