@xrmforge/devkit 0.6.1 → 0.7.1
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.
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/templates/AGENT.md +572 -450
- package/dist/templates/example-form.ts +48 -33
- package/dist/templates/validate-form.mjs +93 -11
- package/package.json +1 -1
|
@@ -4,58 +4,73 @@
|
|
|
4
4
|
* Register in D365 as: {{namespace}}.Example.onLoad
|
|
5
5
|
*
|
|
6
6
|
* This template demonstrates the correct patterns:
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
7
|
+
* - typedForm() for direct typed field access
|
|
8
|
+
* - Fields Enum for addOnChange and $control
|
|
9
|
+
* - Entity-level Fields for Web API select() queries
|
|
9
10
|
* - wrapHandler for unified error handling
|
|
10
11
|
* - Logger instead of console.log
|
|
11
12
|
* - FormNotificationLevel constant instead of raw string
|
|
12
|
-
* -
|
|
13
|
+
* - pickLang() for localized UI strings
|
|
13
14
|
*
|
|
14
15
|
* Replace this file with your actual form logic after running 'xrmforge generate'.
|
|
15
16
|
*/
|
|
16
17
|
import { createLogger } from '../shared/logger.js';
|
|
17
18
|
import { wrapHandler } from '../shared/error-handler.js';
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
//
|
|
21
|
-
// import {
|
|
19
|
+
import { MESSAGES, pickLang } from '../shared/constants.js';
|
|
20
|
+
import { typedForm, FormNotificationLevel, select, formLookupId } from '@xrmforge/helpers';
|
|
21
|
+
// TODO: After 'xrmforge generate', replace with your actual imports:
|
|
22
|
+
// import type { ExampleForm } from '../../generated/forms/example.js';
|
|
23
|
+
// import { ExampleFormFieldsEnum as Fields } from '../../generated/forms/example.js';
|
|
24
|
+
// import { ExampleFields } from '../../generated/fields/example.js';
|
|
25
|
+
// import { EntityNames } from '../../generated/entity-names.js';
|
|
22
26
|
|
|
23
27
|
const logger = createLogger('{{namespace}}.Example');
|
|
24
28
|
|
|
25
29
|
/**
|
|
26
30
|
* Called when the form loads.
|
|
27
31
|
*/
|
|
28
|
-
export const onLoad = wrapHandler('{{namespace}}.Example.onLoad', logger, (
|
|
29
|
-
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//
|
|
34
|
-
form.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
export const onLoad = wrapHandler('{{namespace}}.Example.onLoad', logger, async (ctx) => {
|
|
33
|
+
// TODO: Replace 'Xrm.FormContext' with your generated form type:
|
|
34
|
+
// const form = typedForm<ExampleForm>(ctx.getFormContext());
|
|
35
|
+
const form = typedForm<Xrm.FormContext>(ctx.getFormContext());
|
|
36
|
+
|
|
37
|
+
// Direct field access via typedForm proxy (fully typed):
|
|
38
|
+
// const name = form.name.getValue(); // string | null
|
|
39
|
+
// form.revenue.setValue(150000); // NumberAttribute
|
|
40
|
+
|
|
41
|
+
// addOnChange uses Fields Enum via $context:
|
|
42
|
+
// form.$context.getAttribute(Fields.Name).addOnChange(() => {
|
|
43
|
+
// logger.debug('Name changed', { value: form.name.getValue() });
|
|
44
|
+
// });
|
|
45
|
+
|
|
46
|
+
// Web API queries use entity-level Fields:
|
|
47
|
+
// const result = await Xrm.WebApi.retrieveRecord(
|
|
48
|
+
// EntityNames.Account, id,
|
|
49
|
+
// select(ExampleFields.Name, ExampleFields.WebsiteUrl)
|
|
50
|
+
// );
|
|
51
|
+
|
|
52
|
+
// Lookup access:
|
|
53
|
+
// const parentId = formLookupId(form.parentaccountid);
|
|
54
|
+
|
|
55
|
+
// Localized UI strings:
|
|
56
|
+
const lang = pickLang(
|
|
57
|
+
Xrm.Utility.getGlobalContext().userSettings.languageId,
|
|
58
|
+
MESSAGES,
|
|
38
59
|
);
|
|
60
|
+
logger.info('Form loaded', { language: lang });
|
|
39
61
|
|
|
40
|
-
//
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
// if (ref) {
|
|
47
|
-
// const result = await Xrm.WebApi.retrieveRecord(
|
|
48
|
-
// EntityNames.Account, ref.id, select(AccountFields.Name, AccountFields.WebsiteUrl)
|
|
49
|
-
// );
|
|
50
|
-
// }
|
|
62
|
+
// Form notifications use constants:
|
|
63
|
+
form.$context.ui.setFormNotification(
|
|
64
|
+
'Form loaded',
|
|
65
|
+
FormNotificationLevel.Info,
|
|
66
|
+
'example-load',
|
|
67
|
+
);
|
|
51
68
|
});
|
|
52
69
|
|
|
53
70
|
/**
|
|
54
71
|
* Called when the form is saved.
|
|
55
72
|
*/
|
|
56
|
-
export const onSave = wrapHandler('{{namespace}}.Example.onSave', logger, (
|
|
57
|
-
const form =
|
|
58
|
-
|
|
59
|
-
// Clear the load notification on save
|
|
60
|
-
form.ui.clearFormNotification('example-load-notification');
|
|
73
|
+
export const onSave = wrapHandler('{{namespace}}.Example.onSave', logger, (ctx) => {
|
|
74
|
+
const form = typedForm<Xrm.FormContext>(ctx.getFormContext());
|
|
75
|
+
form.$context.ui.clearFormNotification('example-load');
|
|
61
76
|
});
|
|
@@ -122,28 +122,33 @@ function checkPattern(label, files, regex, excludeFiles = []) {
|
|
|
122
122
|
const formFiles = collectTsFiles('src/forms');
|
|
123
123
|
const allSrcFiles = collectTsFiles('src');
|
|
124
124
|
|
|
125
|
+
// ── Field Access ─────────────────────────────────────────────────────────────
|
|
126
|
+
|
|
125
127
|
// 3a. Raw strings in getAttribute/getControl (must use Fields Enum)
|
|
126
128
|
checkPattern(
|
|
127
|
-
'Raw field strings in getAttribute/getControl
|
|
129
|
+
'Raw field strings in getAttribute/getControl',
|
|
128
130
|
formFiles,
|
|
129
131
|
/(?:getAttribute|getControl)\s*\(\s*['"][a-z]/,
|
|
130
132
|
);
|
|
131
133
|
|
|
132
|
-
// 3b.
|
|
134
|
+
// 3b. Raw strings in helper wrappers (getValue, setFieldValue, setDisabled, addOnChange, setVisible, setRequiredLevel)
|
|
133
135
|
checkPattern(
|
|
134
|
-
'
|
|
136
|
+
'Raw field strings in helper functions (use typedForm or Fields Enum)',
|
|
135
137
|
allSrcFiles,
|
|
136
|
-
|
|
137
|
-
['logger.ts'],
|
|
138
|
+
/(?:getValue|setFieldValue|setDisabled|addOnChange|setVisible|setRequiredLevel|addPreSearch)\s*\(\s*\w+\s*,\s*['"][a-z]/,
|
|
139
|
+
['generated/', 'logger.ts'],
|
|
138
140
|
);
|
|
139
141
|
|
|
140
|
-
// 3c.
|
|
142
|
+
// 3c. Raw strings in select() (must use entity-level Fields Enum)
|
|
141
143
|
checkPattern(
|
|
142
|
-
'
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
'Raw field strings in select() (use entity-level Fields Enum)',
|
|
145
|
+
allSrcFiles,
|
|
146
|
+
/\bselect\s*\(\s*['"][a-z]/,
|
|
147
|
+
['generated/'],
|
|
145
148
|
);
|
|
146
149
|
|
|
150
|
+
// ── Entity Names ─────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
147
152
|
// 3d. Raw entity name strings in WebApi calls (must use EntityNames Enum)
|
|
148
153
|
checkPattern(
|
|
149
154
|
'Raw entity names in WebApi (must use EntityNames Enum)',
|
|
@@ -152,14 +157,73 @@ checkPattern(
|
|
|
152
157
|
['generated/'],
|
|
153
158
|
);
|
|
154
159
|
|
|
155
|
-
// 3e.
|
|
160
|
+
// 3e. SystemEntities workaround (must extend generation instead)
|
|
161
|
+
checkPattern(
|
|
162
|
+
'SystemEntities workaround (extend generation with --entities instead)',
|
|
163
|
+
allSrcFiles,
|
|
164
|
+
/SystemEntities\./,
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
// ── Magic Values ─────────────────────────────────────────────────────────────
|
|
168
|
+
|
|
169
|
+
// 3f. Magic numbers in OptionSet comparisons (must use OptionSet Enum)
|
|
170
|
+
checkPattern(
|
|
171
|
+
'Magic numbers in value comparisons (use OptionSet Enum)',
|
|
172
|
+
allSrcFiles,
|
|
173
|
+
/getValue\(\)\s*===?\s*\d{3,}/,
|
|
174
|
+
['generated/'],
|
|
175
|
+
);
|
|
176
|
+
|
|
177
|
+
// 3g. Magic number 86400000 (must use named constant MS_PER_DAY)
|
|
178
|
+
checkPattern(
|
|
179
|
+
'Magic number 86400000 (use named constant MS_PER_DAY)',
|
|
180
|
+
allSrcFiles,
|
|
181
|
+
/86400000/,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// 3h. Raw notification level strings (must use FormNotificationLevel)
|
|
185
|
+
checkPattern(
|
|
186
|
+
'Raw notification level strings (use FormNotificationLevel from @xrmforge/helpers)',
|
|
187
|
+
allSrcFiles,
|
|
188
|
+
/setFormNotification\s*\([^)]*['"](?:ERROR|WARNING|INFO)['"]/,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
// ── Deprecated / Unsafe ──────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
// 3i. Xrm.Page (deprecated since D365 v9.0)
|
|
156
194
|
checkPattern(
|
|
157
195
|
'Xrm.Page (deprecated since D365 v9.0)',
|
|
158
196
|
allSrcFiles,
|
|
159
197
|
/\bXrm\.Page\b/,
|
|
160
198
|
);
|
|
161
199
|
|
|
162
|
-
//
|
|
200
|
+
// 3j. eval() usage
|
|
201
|
+
checkPattern(
|
|
202
|
+
'eval() usage (use Number() or JSON.parse())',
|
|
203
|
+
allSrcFiles,
|
|
204
|
+
/\beval\s*\(/,
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// 3k. console.log/warn/error outside logger.ts (must use Logger)
|
|
208
|
+
checkPattern(
|
|
209
|
+
'console.* outside logger.ts (must use Logger)',
|
|
210
|
+
allSrcFiles,
|
|
211
|
+
/\bconsole\.(log|warn|error|info|debug)\b/,
|
|
212
|
+
['logger.ts'],
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// ── Handler Pattern ──────────────────────────────────────────────────────────
|
|
216
|
+
|
|
217
|
+
// 3l. Exported handlers without wrapHandler
|
|
218
|
+
checkPattern(
|
|
219
|
+
'Exported handlers without wrapHandler',
|
|
220
|
+
formFiles,
|
|
221
|
+
/^export\s+(const|async\s+function|function)\s+\w+(?!.*wrapHandler)/,
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// ── Raw $select ──────────────────────────────────────────────────────────────
|
|
225
|
+
|
|
226
|
+
// 3m. Raw $select strings (must use select() from @xrmforge/helpers)
|
|
163
227
|
checkPattern(
|
|
164
228
|
'Raw $select strings (must use select() from @xrmforge/helpers)',
|
|
165
229
|
allSrcFiles,
|
|
@@ -167,6 +231,24 @@ checkPattern(
|
|
|
167
231
|
['generated/'],
|
|
168
232
|
);
|
|
169
233
|
|
|
234
|
+
// ── FetchXML ─────────────────────────────────────────────────────────────────
|
|
235
|
+
|
|
236
|
+
// 3n. Raw field names in FetchXML attribute= (should use Fields Enum interpolation)
|
|
237
|
+
checkPattern(
|
|
238
|
+
'Raw field names in FetchXML (use Fields Enum interpolation)',
|
|
239
|
+
allSrcFiles,
|
|
240
|
+
/attribute\s*=\s*'[a-z][a-z0-9_]+'/,
|
|
241
|
+
['generated/'],
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
// 3o. Magic numbers in FetchXML <value> (should use OptionSet Enum)
|
|
245
|
+
checkPattern(
|
|
246
|
+
'Magic numbers in FetchXML values (use OptionSet Enum)',
|
|
247
|
+
allSrcFiles,
|
|
248
|
+
/<value>\d{3,}<\/value>/,
|
|
249
|
+
['generated/'],
|
|
250
|
+
);
|
|
251
|
+
|
|
170
252
|
// ============================================================
|
|
171
253
|
// Result
|
|
172
254
|
// ============================================================
|