@xrmforge/devkit 0.5.1 → 0.5.2

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.
@@ -20,40 +20,106 @@ Run `xrmforge generate` to create:
20
20
  - `generated/entity-names.ts` - EntityNames const enum
21
21
  - `generated/index.ts` - Barrel file with `export * from` re-exports
22
22
 
23
- ## Rules: Always
23
+ ## Rules: MANDATORY (every violation is a bug)
24
24
 
25
- 1. **Fields Enum** for getAttribute/getControl (not raw strings):
26
- `form.getAttribute(Fields.AccountName)` not `form.getAttribute("name")`
25
+ 1. **Fields Enum** for ALL getAttribute/getControl calls. Never raw strings.
26
+ ```typescript
27
+ import { AccountMainFormFieldsEnum as Fields } from '../generated/forms/account.js';
28
+ form.getAttribute(Fields.Name) // CORRECT
29
+ form.getAttribute("name") // BUG - raw string
30
+ ```
27
31
 
28
- 2. **OptionSet Enum** for comparisons (not magic numbers):
29
- `status === StatusCode.Active` not `status === 0`
32
+ 2. **OptionSet Enum** for ALL value comparisons. Never magic numbers.
33
+ ```typescript
34
+ import { StatusCode } from '../generated/optionsets/invoice.js';
35
+ if (status === StatusCode.Active) // CORRECT
36
+ if (status === 0) // BUG - magic number
37
+ ```
30
38
 
31
- 3. **Cast formContext** to generated form interface:
32
- `const form = ctx.getFormContext() as AccountMainForm;`
39
+ 3. **FormContext Cast** to generated form interface in every onLoad:
40
+ ```typescript
41
+ import type { AccountMainForm } from '../generated/forms/account.js';
42
+ const form = ctx.getFormContext() as AccountMainForm;
43
+ ```
33
44
 
34
- 4. **EntityNames Enum** for Web API calls:
35
- `Xrm.WebApi.retrieveRecord(EntityNames.Account, id)`
45
+ 4. **EntityNames Enum** in ALL Xrm.WebApi calls:
46
+ ```typescript
47
+ import { EntityNames } from '../generated/entity-names.js';
48
+ Xrm.WebApi.retrieveRecord(EntityNames.Account, id)
49
+ ```
36
50
 
37
- 5. **parseLookup()** from @xrmforge/helpers for lookup values
51
+ 5. **parseLookup()** from @xrmforge/helpers for ALL lookup value access:
52
+ ```typescript
53
+ import { parseLookup } from '@xrmforge/helpers';
54
+ const customer = parseLookup(form.getAttribute(Fields.CustomerId));
55
+ ```
38
56
 
39
- 6. **select()** from @xrmforge/helpers for $select queries
57
+ 6. **select()** from @xrmforge/helpers for ALL $select queries:
58
+ ```typescript
59
+ import { select } from '@xrmforge/helpers';
60
+ Xrm.WebApi.retrieveRecord(EntityNames.Account, id, select(Fields.Name, Fields.Revenue))
61
+ ```
40
62
 
41
- 7. **createFormMock()** from @xrmforge/testing for tests
63
+ 7. **wrapHandler()** around EVERY exported async event handler:
64
+ ```typescript
65
+ import { createLogger } from '../shared/logger';
66
+ import { wrapHandler } from '../shared/error-handler';
67
+ const logger = createLogger('Namespace.Entity');
68
+ export const onLoad = wrapHandler('Namespace.Entity.onLoad', logger, async (ctx) => {
69
+ // handler code
70
+ });
71
+ ```
42
72
 
43
- 8. **Module exports** (not window/global assignments). esbuild globalName handles namespacing.
73
+ 8. **createFormMock()** from @xrmforge/testing for ALL form tests:
74
+ ```typescript
75
+ import { createFormMock, fireOnChange, setupXrmMock } from '@xrmforge/testing';
76
+ ```
44
77
 
45
- 9. **Tabs/Sections/Subgrids Enums** for UI access
78
+ 9. **Module exports** (not window/global assignments). esbuild globalName handles namespacing.
46
79
 
47
- 10. **Error handling** in all async event handlers (try/catch)
80
+ 10. **Structured Logger** instead of console.* (except in logger.ts itself):
81
+ ```typescript
82
+ import { createLogger } from '../shared/logger';
83
+ const logger = createLogger('Namespace.Entity');
84
+ logger.info('Form loaded', { recordId });
85
+ ```
48
86
 
49
- ## Rules: Never
87
+ ## Rules: NEVER (every occurrence is a bug)
50
88
 
51
89
  - Never `getAttribute("raw_string")` when Fields enum exists
52
- - Never magic numbers for OptionSet values
90
+ - Never magic numbers for OptionSet values (use OptionSet enums)
53
91
  - Never `Xrm.Page` (deprecated since D365 v9.0)
54
92
  - Never synchronous XMLHttpRequest
55
93
  - Never `eval()`
56
94
  - Never `window.X = ...` (use module exports)
95
+ - Never `console.log/warn/error` in form scripts (use shared logger)
96
+ - Never export async handlers without wrapHandler()
97
+ - Never `Xrm.WebApi.retrieveRecord("account", ...)` with raw entity name (use EntityNames)
98
+ - Never `"?$select=name,revenue"` as raw string (use select() from @xrmforge/helpers)
99
+ - Never `.getValue()[0].id.replace(...)` for lookups (use parseLookup() from @xrmforge/helpers)
100
+
101
+ ## Mandatory Shared Utilities
102
+
103
+ Every XrmForge project MUST have these in `src/shared/`:
104
+
105
+ ### logger.ts
106
+ ```typescript
107
+ export interface Logger { debug(msg: string, data?: unknown): void; info(...); warn(...); error(...); }
108
+ export function createLogger(namespace: string): Logger;
109
+ // Only file allowed to use console.*
110
+ ```
111
+
112
+ ### error-handler.ts
113
+ ```typescript
114
+ export function wrapHandler<T>(name: string, logger: Logger, handler: T): T;
115
+ // Catches sync+async errors, shows form notification, never rethrows
116
+ ```
117
+
118
+ ### constants.ts
119
+ ```typescript
120
+ export const NOTIFICATION_IDS = { ... } as const;
121
+ export const MESSAGES = { ... } as const;
122
+ ```
57
123
 
58
124
  ## Before/After Examples
59
125
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrmforge/devkit",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Build orchestration and project tooling for Dynamics 365 WebResources",
5
5
  "keywords": [
6
6
  "dynamics-365",