@xrmforge/devkit 0.6.1 → 0.7.0

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.
@@ -4,58 +4,73 @@
4
4
  * Register in D365 as: {{namespace}}.Example.onLoad
5
5
  *
6
6
  * This template demonstrates the correct patterns:
7
- * - Fields Enum for compile-time field validation
8
- * - Typed FormContext cast
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
- * - select() for Web API queries
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 { FormNotificationLevel, select } from '@xrmforge/helpers';
19
- // TODO: Import your generated types after running 'xrmforge generate'
20
- // import type { ExampleForm } from '../../generated/entities/Example.js';
21
- // import { ExampleFormFieldsEnum as Fields } from '../../generated/entities/Example.js';
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, (executionContext) => {
29
- const form = executionContext.getFormContext();
30
- // TODO: Cast to your generated form type:
31
- // const form = executionContext.getFormContext() as ExampleForm;
32
-
33
- // Example: show a notification on the form
34
- form.ui.setFormNotification(
35
- 'Form loaded successfully',
36
- FormNotificationLevel.Info,
37
- 'example-load-notification',
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
- // Example: read a field value using Fields Enum
41
- // const nameAttr = form.getAttribute(Fields.Name);
42
- // logger.debug('Name field value', { value: nameAttr.getValue() });
43
-
44
- // Example: Web API query with select()
45
- // const ref = formLookup(form.getAttribute(Fields.ParentAccountId));
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, (executionContext) => {
57
- const form = executionContext.getFormContext();
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 (must use Fields Enum)',
129
+ 'Raw field strings in getAttribute/getControl',
128
130
  formFiles,
129
131
  /(?:getAttribute|getControl)\s*\(\s*['"][a-z]/,
130
132
  );
131
133
 
132
- // 3b. console.log/warn/error outside logger.ts (must use Logger)
134
+ // 3b. Raw strings in helper wrappers (getValue, setFieldValue, setDisabled, addOnChange, setVisible, setRequiredLevel)
133
135
  checkPattern(
134
- 'console.* outside logger.ts (must use Logger)',
136
+ 'Raw field strings in helper functions (use typedForm or Fields Enum)',
135
137
  allSrcFiles,
136
- /\bconsole\.(log|warn|error|info|debug)\b/,
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. Exported handlers without wrapHandler
142
+ // 3c. Raw strings in select() (must use entity-level Fields Enum)
141
143
  checkPattern(
142
- 'Exported handlers without wrapHandler',
143
- formFiles,
144
- /^export\s+(const|async\s+function|function)\s+\w+(?!.*wrapHandler)/,
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. Xrm.Page (deprecated since D365 v9.0)
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
- // 3f. Raw $select strings (must use select() from @xrmforge/helpers)
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
  // ============================================================
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrmforge/devkit",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Build orchestration and project tooling for Dynamics 365 WebResources",
5
5
  "keywords": [
6
6
  "dynamics-365",