@xrmforge/typegen 0.5.0 → 0.5.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/MIGRATION.md +154 -0
- package/package.json +3 -2
package/MIGRATION.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# XrmForge Migration Guide
|
|
2
|
+
|
|
3
|
+
How to convert legacy Dynamics 365 JavaScript to type-safe TypeScript with XrmForge.
|
|
4
|
+
|
|
5
|
+
## Step 1: Initialize Project
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @xrmforge/cli init my-project --prefix contoso
|
|
9
|
+
cd my-project
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Step 2: Generate Types from Dataverse
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx xrmforge generate \
|
|
17
|
+
--url https://YOUR-ORG.crm4.dynamics.com \
|
|
18
|
+
--auth interactive \
|
|
19
|
+
--tenant-id YOUR-TENANT-ID \
|
|
20
|
+
--client-id YOUR-CLIENT-ID \
|
|
21
|
+
--entities account,contact,opportunity \
|
|
22
|
+
--output ./typings
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This generates:
|
|
26
|
+
- `typings/entities/*.d.ts` - Entity interfaces with typed attributes
|
|
27
|
+
- `typings/forms/*.d.ts` - Form interfaces with Fields enum, Tabs enum, Subgrid enum
|
|
28
|
+
- `typings/optionsets/*.d.ts` - OptionSet const enums with labels
|
|
29
|
+
- `typings/entity-names.d.ts` - EntityNames const enum
|
|
30
|
+
|
|
31
|
+
## Step 3: Convert Form Scripts
|
|
32
|
+
|
|
33
|
+
### Before (legacy JavaScript):
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
// account.js - global functions, raw strings, no type safety
|
|
37
|
+
var LM = LM || {};
|
|
38
|
+
LM.Account = {
|
|
39
|
+
onLoad: function(executionContext) {
|
|
40
|
+
var formContext = executionContext.getFormContext();
|
|
41
|
+
var name = formContext.getAttribute("name"); // generic Attribute
|
|
42
|
+
var status = formContext.getAttribute("statuscode");
|
|
43
|
+
if (status.getValue() === 1) { // magic number!
|
|
44
|
+
formContext.getControl("revenue").setVisible(true);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### After (XrmForge TypeScript):
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
// account-form.ts - typed, safe, autocomplete everywhere
|
|
54
|
+
import { AccountMainFormFieldsEnum as Fields } from '../../typings/forms/account';
|
|
55
|
+
|
|
56
|
+
export function onLoad(executionContext: Xrm.Events.EventContext): void {
|
|
57
|
+
const form = executionContext.getFormContext() as XrmForge.Forms.Account.AccountMainForm;
|
|
58
|
+
|
|
59
|
+
// Fields enum: compile error on typos, autocomplete in IDE
|
|
60
|
+
const name = form.getAttribute(Fields.AccountName); // StringAttribute, not generic
|
|
61
|
+
const status = form.getAttribute(Fields.StatusCode); // OptionSetAttribute
|
|
62
|
+
|
|
63
|
+
// OptionSet enum: no magic numbers
|
|
64
|
+
if (status.getValue() === XrmForge.OptionSets.Account.StatusCode.Active) {
|
|
65
|
+
form.getControl(Fields.Revenue).setVisible(true);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Key Differences:
|
|
71
|
+
|
|
72
|
+
| Legacy | XrmForge |
|
|
73
|
+
|--------|----------|
|
|
74
|
+
| `getAttribute("name")` | `getAttribute(Fields.AccountName)` |
|
|
75
|
+
| `getValue() === 1` | `getValue() === OptionSets.StatusCode.Active` |
|
|
76
|
+
| `formContext` (untyped) | `form as AccountMainForm` (typed) |
|
|
77
|
+
| `getControl("revenue")` | `getControl(Fields.Revenue)` |
|
|
78
|
+
| No compile-time checks | Typos are compile errors |
|
|
79
|
+
|
|
80
|
+
## Step 4: Replace Common Patterns
|
|
81
|
+
|
|
82
|
+
### Lookup Values
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Before:
|
|
86
|
+
var value = formContext.getAttribute("primarycontactid").getValue();
|
|
87
|
+
var id = value[0].id.replace("{","").replace("}","");
|
|
88
|
+
|
|
89
|
+
// After: use parseLookup from @xrmforge/typegen
|
|
90
|
+
import { parseLookup } from '@xrmforge/typegen';
|
|
91
|
+
const contact = parseLookup(form.getAttribute(Fields.PrimaryContactId));
|
|
92
|
+
if (contact) {
|
|
93
|
+
console.log(contact.id); // already clean GUID
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Web API Queries
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Before:
|
|
101
|
+
Xrm.WebApi.retrieveMultipleRecords("account",
|
|
102
|
+
"?$select=name,revenue&$filter=statecode eq 0");
|
|
103
|
+
|
|
104
|
+
// After: use Fields enum for $select
|
|
105
|
+
import { select } from '@xrmforge/typegen';
|
|
106
|
+
import { AccountFields } from '../../typings/entities/account';
|
|
107
|
+
Xrm.WebApi.retrieveMultipleRecords("account",
|
|
108
|
+
`?$select=${select(AccountFields.Name, AccountFields.Revenue)}&$filter=statecode eq 0`);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Form Testing
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Before: no tests, or complex manual mocks
|
|
115
|
+
|
|
116
|
+
// After: @xrmforge/testing
|
|
117
|
+
import { createFormMock, fireOnChange } from '@xrmforge/testing';
|
|
118
|
+
import type { AccountMainForm, AccountMainFormMockValues } from '../../typings/forms/account';
|
|
119
|
+
|
|
120
|
+
const mock = createFormMock<AccountMainForm, AccountMainFormMockValues>({
|
|
121
|
+
name: 'Contoso Ltd',
|
|
122
|
+
revenue: 1000000,
|
|
123
|
+
statuscode: 1,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
onLoad(mock.executionContext);
|
|
127
|
+
expect(mock.formContext.getControl('revenue').getVisible()).toBe(true);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Step 5: Build
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
npx xrmforge build # IIFE bundles for D365
|
|
134
|
+
npx xrmforge build --watch # Watch mode
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## Step 6: Replace Magic Numbers
|
|
138
|
+
|
|
139
|
+
Search your code for patterns like:
|
|
140
|
+
- `getValue() === 123` or `getValue() !== 456`
|
|
141
|
+
- `setValue("statuscode", 1)`
|
|
142
|
+
- Raw OptionSet values in if/switch statements
|
|
143
|
+
|
|
144
|
+
Replace with generated const enums from `typings/optionsets/`.
|
|
145
|
+
|
|
146
|
+
## Checklist
|
|
147
|
+
|
|
148
|
+
- [ ] All `getAttribute("string")` calls use Fields enum
|
|
149
|
+
- [ ] All OptionSet comparisons use const enums (no magic numbers)
|
|
150
|
+
- [ ] All `Xrm.Page` calls replaced with `formContext`
|
|
151
|
+
- [ ] Form scripts export functions (not global namespace objects)
|
|
152
|
+
- [ ] Each form script has tests using `@xrmforge/testing`
|
|
153
|
+
- [ ] `xrmforge build` produces IIFE bundles
|
|
154
|
+
- [ ] `tsc --noEmit` passes with zero errors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xrmforge/typegen",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "TypeScript declaration generator for Dynamics 365 / Dataverse",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dynamics-365",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"files": [
|
|
35
|
-
"dist"
|
|
35
|
+
"dist",
|
|
36
|
+
"MIGRATION.md"
|
|
36
37
|
],
|
|
37
38
|
"sideEffects": false,
|
|
38
39
|
"scripts": {
|