@xrmforge/typegen 0.8.3 → 0.8.5
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/LICENSE +21 -21
- package/MIGRATION.md +194 -194
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/docs/architecture/00-README.md +26 -26
- package/docs/architecture/01-executive-summary.md +11 -11
- package/docs/architecture/02-packages.md +110 -110
- package/docs/architecture/03-generated-types.md +172 -172
- package/docs/architecture/04-cli.md +58 -58
- package/docs/architecture/05-build.md +50 -50
- package/docs/architecture/06-incremental.md +42 -42
- package/docs/architecture/07-http-client.md +59 -59
- package/docs/architecture/08-authentication.md +18 -18
- package/docs/architecture/09-testing.md +55 -55
- package/docs/architecture/10-eslint-plugin.md +82 -82
- package/docs/architecture/11-agent-md.md +38 -38
- package/docs/architecture/12-xrm-pitfalls.md +14 -14
- package/docs/architecture/13-helpers.md +50 -50
- package/docs/architecture/14-showcases.md +21 -21
- package/docs/architecture/15-ci-cd.md +49 -49
- package/docs/architecture/16-technical-debt.md +17 -17
- package/docs/architecture/17-roadmap.md +25 -25
- package/docs/architecture/18-design-principles.md +22 -22
- package/docs/architektur/00-README.md +26 -26
- package/docs/architektur/01-zusammenfassung.md +11 -11
- package/docs/architektur/02-packages.md +110 -110
- package/docs/architektur/03-generierte-typen.md +172 -172
- package/docs/architektur/04-cli.md +58 -58
- package/docs/architektur/05-build.md +50 -50
- package/docs/architektur/06-inkrementell.md +42 -42
- package/docs/architektur/07-http-client.md +59 -59
- package/docs/architektur/08-authentifizierung.md +18 -18
- package/docs/architektur/09-testing.md +55 -55
- package/docs/architektur/10-eslint-plugin.md +82 -82
- package/docs/architektur/11-agent-md.md +38 -38
- package/docs/architektur/12-xrm-fallstricke.md +14 -14
- package/docs/architektur/13-helpers.md +50 -50
- package/docs/architektur/14-showcases.md +21 -21
- package/docs/architektur/15-ci-cd.md +49 -49
- package/docs/architektur/16-technische-schulden.md +17 -17
- package/docs/architektur/17-roadmap.md +25 -25
- package/docs/architektur/18-designprinzipien.md +22 -22
- package/package.json +1 -1
|
@@ -1,82 +1,82 @@
|
|
|
1
|
-
# ESLint Plugin
|
|
2
|
-
|
|
3
|
-
### 10.1 Installation
|
|
4
|
-
|
|
5
|
-
```javascript
|
|
6
|
-
// eslint.config.js (flat config, ESLint v9)
|
|
7
|
-
import xrmforge from '@xrmforge/eslint-plugin';
|
|
8
|
-
|
|
9
|
-
export default [
|
|
10
|
-
xrmforge.configs.recommended,
|
|
11
|
-
// or pick individual rules
|
|
12
|
-
];
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### 10.2 Rules
|
|
16
|
-
|
|
17
|
-
#### no-xrm-page (error)
|
|
18
|
-
|
|
19
|
-
Forbids the deprecated `Xrm.Page` API (removed in D365 v9.0+).
|
|
20
|
-
|
|
21
|
-
```typescript
|
|
22
|
-
// Bad
|
|
23
|
-
Xrm.Page.getAttribute("name");
|
|
24
|
-
|
|
25
|
-
// Good
|
|
26
|
-
const form = executionContext.getFormContext();
|
|
27
|
-
form.getAttribute("name");
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
#### no-magic-optionset (warn)
|
|
31
|
-
|
|
32
|
-
Forbids raw numbers (>= 2) in comparisons with `.getValue()`.
|
|
33
|
-
|
|
34
|
-
```typescript
|
|
35
|
-
// Bad
|
|
36
|
-
if (attr.getValue() === 595300000) { }
|
|
37
|
-
|
|
38
|
-
// Good
|
|
39
|
-
import { StatusCode } from '../generated/optionsets/account';
|
|
40
|
-
if (attr.getValue() === StatusCode.Active) { }
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
#### no-sync-webapi (error)
|
|
44
|
-
|
|
45
|
-
Forbids synchronous XMLHttpRequest (`new XMLHttpRequest()` and `.open()` with `async=false`).
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// Bad
|
|
49
|
-
xhr.open("GET", url, false);
|
|
50
|
-
|
|
51
|
-
// Good
|
|
52
|
-
const data = await Xrm.WebApi.retrieveRecord("account", id);
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
#### require-error-handling (warn)
|
|
56
|
-
|
|
57
|
-
Requires try/catch in exported async functions starting with "on" (event handlers).
|
|
58
|
-
|
|
59
|
-
```typescript
|
|
60
|
-
// Bad
|
|
61
|
-
export async function onLoad(ctx) {
|
|
62
|
-
await fetch("/api"); // no error handling
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// Good
|
|
66
|
-
export async function onLoad(ctx) {
|
|
67
|
-
try { await fetch("/api"); }
|
|
68
|
-
catch (error) { console.error(error); }
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
#### require-namespace (warn)
|
|
73
|
-
|
|
74
|
-
Forbids direct `window.X = ...` or `globalThis.X = ...` assignments. Module exports with esbuild globalName should be used instead.
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
// Bad
|
|
78
|
-
window.Contoso = { onLoad: function() {} };
|
|
79
|
-
|
|
80
|
-
// Good
|
|
81
|
-
export function onLoad(ctx: Xrm.Events.EventContext) {}
|
|
82
|
-
```
|
|
1
|
+
# ESLint Plugin
|
|
2
|
+
|
|
3
|
+
### 10.1 Installation
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
// eslint.config.js (flat config, ESLint v9)
|
|
7
|
+
import xrmforge from '@xrmforge/eslint-plugin';
|
|
8
|
+
|
|
9
|
+
export default [
|
|
10
|
+
xrmforge.configs.recommended,
|
|
11
|
+
// or pick individual rules
|
|
12
|
+
];
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 10.2 Rules
|
|
16
|
+
|
|
17
|
+
#### no-xrm-page (error)
|
|
18
|
+
|
|
19
|
+
Forbids the deprecated `Xrm.Page` API (removed in D365 v9.0+).
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
// Bad
|
|
23
|
+
Xrm.Page.getAttribute("name");
|
|
24
|
+
|
|
25
|
+
// Good
|
|
26
|
+
const form = executionContext.getFormContext();
|
|
27
|
+
form.getAttribute("name");
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### no-magic-optionset (warn)
|
|
31
|
+
|
|
32
|
+
Forbids raw numbers (>= 2) in comparisons with `.getValue()`.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Bad
|
|
36
|
+
if (attr.getValue() === 595300000) { }
|
|
37
|
+
|
|
38
|
+
// Good
|
|
39
|
+
import { StatusCode } from '../generated/optionsets/account';
|
|
40
|
+
if (attr.getValue() === StatusCode.Active) { }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### no-sync-webapi (error)
|
|
44
|
+
|
|
45
|
+
Forbids synchronous XMLHttpRequest (`new XMLHttpRequest()` and `.open()` with `async=false`).
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// Bad
|
|
49
|
+
xhr.open("GET", url, false);
|
|
50
|
+
|
|
51
|
+
// Good
|
|
52
|
+
const data = await Xrm.WebApi.retrieveRecord("account", id);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
#### require-error-handling (warn)
|
|
56
|
+
|
|
57
|
+
Requires try/catch in exported async functions starting with "on" (event handlers).
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// Bad
|
|
61
|
+
export async function onLoad(ctx) {
|
|
62
|
+
await fetch("/api"); // no error handling
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Good
|
|
66
|
+
export async function onLoad(ctx) {
|
|
67
|
+
try { await fetch("/api"); }
|
|
68
|
+
catch (error) { console.error(error); }
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
#### require-namespace (warn)
|
|
73
|
+
|
|
74
|
+
Forbids direct `window.X = ...` or `globalThis.X = ...` assignments. Module exports with esbuild globalName should be used instead.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// Bad
|
|
78
|
+
window.Contoso = { onLoad: function() {} };
|
|
79
|
+
|
|
80
|
+
// Good
|
|
81
|
+
export function onLoad(ctx: Xrm.Events.EventContext) {}
|
|
82
|
+
```
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
# AGENT.md System
|
|
2
|
-
|
|
3
|
-
### 11.1 Purpose
|
|
4
|
-
|
|
5
|
-
The AGENT.md is a scaffolded file that teaches AI coding assistants (Claude, ChatGPT, Copilot, Cursor) how to write optimal D365 form scripts using XrmForge. It is generated by `xrmforge init` and placed in the project root.
|
|
6
|
-
|
|
7
|
-
### 11.2 Content Structure
|
|
8
|
-
|
|
9
|
-
1. **Package overview** - What each @xrmforge package does
|
|
10
|
-
2. **10 Rules: Always** - Fields Enum, OptionSet Enum, FormContext cast, EntityNames, parseLookup, select, createFormMock, module exports, Tabs/Sections enums, error handling
|
|
11
|
-
3. **Rules: Never** - Raw strings, magic numbers, Xrm.Page, sync XHR, eval, window assignments
|
|
12
|
-
4. **Before/After examples** - Field access, OptionSet comparison, testing
|
|
13
|
-
5. **Pattern Recognition table** - Legacy pattern to XrmForge replacement mapping
|
|
14
|
-
6. **OptionSet Enum creation guide** - How to create enums from magic numbers in legacy code
|
|
15
|
-
7. **Testing with setupXrmMock** - Global Xrm mock pattern
|
|
16
|
-
8. **Build commands** - xrmforge build, watch mode
|
|
17
|
-
9. **@types/xrm Pitfalls** - Known issues and workarounds
|
|
18
|
-
10. **File structure** - Expected project layout
|
|
19
|
-
|
|
20
|
-
### 11.3 Template System
|
|
21
|
-
|
|
22
|
-
The AGENT.md is stored as `src/scaffold/templates/AGENT.md` in the devkit package and loaded via `template-loader.ts` at scaffold time. No variable substitution needed (the file is static).
|
|
23
|
-
|
|
24
|
-
### 11.4 KI Comparison Test Results
|
|
25
|
-
|
|
26
|
-
Five AI models were tested converting legacy D365 JavaScript (account.js + lm_helper.js, 1,288 lines) to TypeScript with XrmForge:
|
|
27
|
-
|
|
28
|
-
| Rank | Model | Score | Tool | Strength |
|
|
29
|
-
|------|-------|-------|------|----------|
|
|
30
|
-
| 1 | Claude Opus 4.6 | 42/50 | Claude Code | Most tests (62), best code structure |
|
|
31
|
-
| 2 | Claude Sonnet 4.6 | 41/50 | Claude Code | Most bugs found (5), best DI approach |
|
|
32
|
-
| 3 | Cursor Composer 2 | 35/50 | Cursor IDE | Recognized select() Node API issue |
|
|
33
|
-
| 4 | ChatGPT GPT-4o | 30/50 | ChatGPT Web | Functional but less XrmForge-specific |
|
|
34
|
-
| 5 | MS Copilot | 12/50 | Browser Chat | No workspace access, never saw AGENT.md |
|
|
35
|
-
|
|
36
|
-
**Criteria (11, max 5 points each = 55 max):** Fields Enum usage, OptionSet Enums, FormContext typing, XrmForge helpers, module exports, tests present, test quality, error handling, code quality, bugs found, documentation.
|
|
37
|
-
|
|
38
|
-
**Key finding:** No AI consistently used `@xrmforge/helpers` imports (select, parseLookup). This remains the biggest adoption gap.
|
|
1
|
+
# AGENT.md System
|
|
2
|
+
|
|
3
|
+
### 11.1 Purpose
|
|
4
|
+
|
|
5
|
+
The AGENT.md is a scaffolded file that teaches AI coding assistants (Claude, ChatGPT, Copilot, Cursor) how to write optimal D365 form scripts using XrmForge. It is generated by `xrmforge init` and placed in the project root.
|
|
6
|
+
|
|
7
|
+
### 11.2 Content Structure
|
|
8
|
+
|
|
9
|
+
1. **Package overview** - What each @xrmforge package does
|
|
10
|
+
2. **10 Rules: Always** - Fields Enum, OptionSet Enum, FormContext cast, EntityNames, parseLookup, select, createFormMock, module exports, Tabs/Sections enums, error handling
|
|
11
|
+
3. **Rules: Never** - Raw strings, magic numbers, Xrm.Page, sync XHR, eval, window assignments
|
|
12
|
+
4. **Before/After examples** - Field access, OptionSet comparison, testing
|
|
13
|
+
5. **Pattern Recognition table** - Legacy pattern to XrmForge replacement mapping
|
|
14
|
+
6. **OptionSet Enum creation guide** - How to create enums from magic numbers in legacy code
|
|
15
|
+
7. **Testing with setupXrmMock** - Global Xrm mock pattern
|
|
16
|
+
8. **Build commands** - xrmforge build, watch mode
|
|
17
|
+
9. **@types/xrm Pitfalls** - Known issues and workarounds
|
|
18
|
+
10. **File structure** - Expected project layout
|
|
19
|
+
|
|
20
|
+
### 11.3 Template System
|
|
21
|
+
|
|
22
|
+
The AGENT.md is stored as `src/scaffold/templates/AGENT.md` in the devkit package and loaded via `template-loader.ts` at scaffold time. No variable substitution needed (the file is static).
|
|
23
|
+
|
|
24
|
+
### 11.4 KI Comparison Test Results
|
|
25
|
+
|
|
26
|
+
Five AI models were tested converting legacy D365 JavaScript (account.js + lm_helper.js, 1,288 lines) to TypeScript with XrmForge:
|
|
27
|
+
|
|
28
|
+
| Rank | Model | Score | Tool | Strength |
|
|
29
|
+
|------|-------|-------|------|----------|
|
|
30
|
+
| 1 | Claude Opus 4.6 | 42/50 | Claude Code | Most tests (62), best code structure |
|
|
31
|
+
| 2 | Claude Sonnet 4.6 | 41/50 | Claude Code | Most bugs found (5), best DI approach |
|
|
32
|
+
| 3 | Cursor Composer 2 | 35/50 | Cursor IDE | Recognized select() Node API issue |
|
|
33
|
+
| 4 | ChatGPT GPT-4o | 30/50 | ChatGPT Web | Functional but less XrmForge-specific |
|
|
34
|
+
| 5 | MS Copilot | 12/50 | Browser Chat | No workspace access, never saw AGENT.md |
|
|
35
|
+
|
|
36
|
+
**Criteria (11, max 5 points each = 55 max):** Fields Enum usage, OptionSet Enums, FormContext typing, XrmForge helpers, module exports, tests present, test quality, error handling, code quality, bugs found, documentation.
|
|
37
|
+
|
|
38
|
+
**Key finding:** No AI consistently used `@xrmforge/helpers` imports (select, parseLookup). This remains the biggest adoption gap.
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
# @types/xrm Pitfalls
|
|
2
|
-
|
|
3
|
-
Known issues when working with `@types/xrm`:
|
|
4
|
-
|
|
5
|
-
| Issue | Wrong | Correct |
|
|
6
|
-
|-------|-------|---------|
|
|
7
|
-
| Form interface | `interface extends Xrm.FormContext` | `extends Omit<Xrm.FormContext, 'getAttribute' \| 'getControl'>` |
|
|
8
|
-
| AlertDialogResponse | `Xrm.Navigation.AlertDialogResponse` | `Xrm.Async.PromiseLike<void>` (type does not exist) |
|
|
9
|
-
| ConfirmDialogResponse | `Xrm.Navigation.ConfirmDialogResponse` | `Xrm.Navigation.ConfirmResult` (type does not exist) |
|
|
10
|
-
| setNotification | `setNotification(message)` | `setNotification(message, uniqueId)` (requires 2 args) |
|
|
11
|
-
| openFile | `openFile({ fileName, ... })` | Must include `fileSize` property in FileDetails |
|
|
12
|
-
| SubmitMode | `Xrm.Attributes.SubmitMode` | `Xrm.SubmitMode` |
|
|
13
|
-
| const enum in .d.ts | `const enum` in `.d.ts` files | Use `const enum` in `.ts` ES modules (typegen 0.8.0+ generates `.ts` files, resolving this issue) |
|
|
14
|
-
| Grid.refresh() | `grid.refresh()` | `(grid as any).refresh()` (not typed in @types/xrm) |
|
|
1
|
+
# @types/xrm Pitfalls
|
|
2
|
+
|
|
3
|
+
Known issues when working with `@types/xrm`:
|
|
4
|
+
|
|
5
|
+
| Issue | Wrong | Correct |
|
|
6
|
+
|-------|-------|---------|
|
|
7
|
+
| Form interface | `interface extends Xrm.FormContext` | `extends Omit<Xrm.FormContext, 'getAttribute' \| 'getControl'>` |
|
|
8
|
+
| AlertDialogResponse | `Xrm.Navigation.AlertDialogResponse` | `Xrm.Async.PromiseLike<void>` (type does not exist) |
|
|
9
|
+
| ConfirmDialogResponse | `Xrm.Navigation.ConfirmDialogResponse` | `Xrm.Navigation.ConfirmResult` (type does not exist) |
|
|
10
|
+
| setNotification | `setNotification(message)` | `setNotification(message, uniqueId)` (requires 2 args) |
|
|
11
|
+
| openFile | `openFile({ fileName, ... })` | Must include `fileSize` property in FileDetails |
|
|
12
|
+
| SubmitMode | `Xrm.Attributes.SubmitMode` | `Xrm.SubmitMode` |
|
|
13
|
+
| const enum in .d.ts | `const enum` in `.d.ts` files | Use `const enum` in `.ts` ES modules (typegen 0.8.0+ generates `.ts` files, resolving this issue) |
|
|
14
|
+
| Grid.refresh() | `grid.refresh()` | `(grid as any).refresh()` (not typed in @types/xrm) |
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
# @xrmforge/helpers Package
|
|
2
|
-
|
|
3
|
-
### 13.1 Problem
|
|
4
|
-
|
|
5
|
-
The previous approach used a `/helpers` subpath export on `@xrmforge/typegen`. This was confusing because typegen is a Node.js code generation tool, while helpers are browser-safe runtime utilities. The subpath `@xrmforge/typegen/helpers` was non-obvious and AI coding assistants consistently failed to discover it.
|
|
6
|
-
|
|
7
|
-
### 13.2 Solution
|
|
8
|
-
|
|
9
|
-
A standalone `@xrmforge/helpers` package consolidates all browser-safe runtime code. Zero Node.js dependencies. Clean, discoverable import path:
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
// Import from the dedicated helpers package
|
|
13
|
-
import { select, parseLookup, typedForm } from '@xrmforge/helpers';
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
### 13.3 Exports
|
|
17
|
-
|
|
18
|
-
**Web API Helpers:**
|
|
19
|
-
- `select(...fields: string[]): string` - Builds `?$select=field1,field2`
|
|
20
|
-
- `selectExpand(fields: string[], expand: string): string` - Builds `?$select=...&$expand=...`
|
|
21
|
-
- `parseLookup(response: Record<string, unknown>, fieldName: string): LookupValue | null` - Parses `_fieldname_value` with OData annotations
|
|
22
|
-
- `parseLookups(response: Record<string, unknown>, fieldName: string): LookupValue[]` - Multi-value lookup parsing
|
|
23
|
-
- `parseFormattedValue(response: Record<string, unknown>, fieldName: string): string | null` - Extracts `@OData.Community.Display.V1.FormattedValue`
|
|
24
|
-
|
|
25
|
-
**Xrm Constants (8 const enums):**
|
|
26
|
-
- DisplayState, FormNotificationLevel, RequiredLevel, SubmitMode, SaveMode, ClientType, ClientState, OperationType
|
|
27
|
-
|
|
28
|
-
**typedForm() Proxy:**
|
|
29
|
-
- `typedForm<TForm>(formContext)` - Returns a proxy where `form.name` delegates to `getAttribute('name')`
|
|
30
|
-
- GET trap: Property access delegates to getAttribute(); `$context` returns raw FormContext; `$control(name)` returns getControl()
|
|
31
|
-
- SET trap: Throws TypeError forcing `.setValue()` usage
|
|
32
|
-
- HAS trap: Checks if attribute exists on the form
|
|
33
|
-
|
|
34
|
-
**Action/Function Runtime:**
|
|
35
|
-
- `createBoundAction(entityName, actionName)` - Creates a bound action executor
|
|
36
|
-
- `executeRequest(request)` - Executes an Organization Request via Xrm.WebApi.online.execute
|
|
37
|
-
- `withProgress(message, fn)` - Wraps an async operation with Xrm.Utility.showProgressIndicator
|
|
38
|
-
|
|
39
|
-
### 13.4 Migration
|
|
40
|
-
|
|
41
|
-
The old import path `@xrmforge/typegen/helpers` has been removed. Update all imports:
|
|
42
|
-
|
|
43
|
-
```typescript
|
|
44
|
-
// Old (removed)
|
|
45
|
-
import { select } from '@xrmforge/typegen/helpers';
|
|
46
|
-
import { typedForm } from '@xrmforge/formhelpers';
|
|
47
|
-
|
|
48
|
-
// New
|
|
49
|
-
import { select, typedForm } from '@xrmforge/helpers';
|
|
50
|
-
```
|
|
1
|
+
# @xrmforge/helpers Package
|
|
2
|
+
|
|
3
|
+
### 13.1 Problem
|
|
4
|
+
|
|
5
|
+
The previous approach used a `/helpers` subpath export on `@xrmforge/typegen`. This was confusing because typegen is a Node.js code generation tool, while helpers are browser-safe runtime utilities. The subpath `@xrmforge/typegen/helpers` was non-obvious and AI coding assistants consistently failed to discover it.
|
|
6
|
+
|
|
7
|
+
### 13.2 Solution
|
|
8
|
+
|
|
9
|
+
A standalone `@xrmforge/helpers` package consolidates all browser-safe runtime code. Zero Node.js dependencies. Clean, discoverable import path:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// Import from the dedicated helpers package
|
|
13
|
+
import { select, parseLookup, typedForm } from '@xrmforge/helpers';
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 13.3 Exports
|
|
17
|
+
|
|
18
|
+
**Web API Helpers:**
|
|
19
|
+
- `select(...fields: string[]): string` - Builds `?$select=field1,field2`
|
|
20
|
+
- `selectExpand(fields: string[], expand: string): string` - Builds `?$select=...&$expand=...`
|
|
21
|
+
- `parseLookup(response: Record<string, unknown>, fieldName: string): LookupValue | null` - Parses `_fieldname_value` with OData annotations
|
|
22
|
+
- `parseLookups(response: Record<string, unknown>, fieldName: string): LookupValue[]` - Multi-value lookup parsing
|
|
23
|
+
- `parseFormattedValue(response: Record<string, unknown>, fieldName: string): string | null` - Extracts `@OData.Community.Display.V1.FormattedValue`
|
|
24
|
+
|
|
25
|
+
**Xrm Constants (8 const enums):**
|
|
26
|
+
- DisplayState, FormNotificationLevel, RequiredLevel, SubmitMode, SaveMode, ClientType, ClientState, OperationType
|
|
27
|
+
|
|
28
|
+
**typedForm() Proxy:**
|
|
29
|
+
- `typedForm<TForm>(formContext)` - Returns a proxy where `form.name` delegates to `getAttribute('name')`
|
|
30
|
+
- GET trap: Property access delegates to getAttribute(); `$context` returns raw FormContext; `$control(name)` returns getControl()
|
|
31
|
+
- SET trap: Throws TypeError forcing `.setValue()` usage
|
|
32
|
+
- HAS trap: Checks if attribute exists on the form
|
|
33
|
+
|
|
34
|
+
**Action/Function Runtime:**
|
|
35
|
+
- `createBoundAction(entityName, actionName)` - Creates a bound action executor
|
|
36
|
+
- `executeRequest(request)` - Executes an Organization Request via Xrm.WebApi.online.execute
|
|
37
|
+
- `withProgress(message, fn)` - Wraps an async operation with Xrm.Utility.showProgressIndicator
|
|
38
|
+
|
|
39
|
+
### 13.4 Migration
|
|
40
|
+
|
|
41
|
+
The old import path `@xrmforge/typegen/helpers` has been removed. Update all imports:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
// Old (removed)
|
|
45
|
+
import { select } from '@xrmforge/typegen/helpers';
|
|
46
|
+
import { typedForm } from '@xrmforge/formhelpers';
|
|
47
|
+
|
|
48
|
+
// New
|
|
49
|
+
import { select, typedForm } from '@xrmforge/helpers';
|
|
50
|
+
```
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
# Showcases
|
|
2
|
-
|
|
3
|
-
### 14.1 Markant WebResources (Production Showcase)
|
|
4
|
-
|
|
5
|
-
Located in the XrmForge-Workspace repository under `docs/07_showcase/markant-webresources/`.
|
|
6
|
-
|
|
7
|
-
- **30 WebResources** in `src/forms/` (account, contact, opportunity, lead, quote, email, task, etc.)
|
|
8
|
-
- **1 shared library** (GDPR retention UI)
|
|
9
|
-
- **9 test files** with 59 tests
|
|
10
|
-
- **79 generated typings:** 25 form interfaces, 28 entity interfaces, 22 OptionSet files, 4 action executors
|
|
11
|
-
- **esbuild build** via xrmforge.config.json (32 entries)
|
|
12
|
-
- **Deploy script** (deploy.mjs) with @azure/identity auth, incremental deployment, hash-based change detection
|
|
13
|
-
- **27 entities, 236 OptionSet enums, 95 form interfaces, 7 Custom API executors**
|
|
14
|
-
|
|
15
|
-
### 14.2 LMApp WebResources (KI Comparison Showcase)
|
|
16
|
-
|
|
17
|
-
Created during the KI comparison tests (Session 9). 18 legacy JavaScript form scripts (~8,400 lines) converted to TypeScript with XrmForge patterns.
|
|
18
|
-
|
|
19
|
-
- **19 WebResources** with Fields Enums, EntityNames, OptionSet Enums
|
|
20
|
-
- **84 tests** in 8 test files
|
|
21
|
-
- **XrmForge-optimized:** All 10 AGENT.md rules applied (FormContext cast, Fields Enum, EntityNames, OptionSet Enums, shared getLookupObject, Tab Enums)
|
|
1
|
+
# Showcases
|
|
2
|
+
|
|
3
|
+
### 14.1 Markant WebResources (Production Showcase)
|
|
4
|
+
|
|
5
|
+
Located in the XrmForge-Workspace repository under `docs/07_showcase/markant-webresources/`.
|
|
6
|
+
|
|
7
|
+
- **30 WebResources** in `src/forms/` (account, contact, opportunity, lead, quote, email, task, etc.)
|
|
8
|
+
- **1 shared library** (GDPR retention UI)
|
|
9
|
+
- **9 test files** with 59 tests
|
|
10
|
+
- **79 generated typings:** 25 form interfaces, 28 entity interfaces, 22 OptionSet files, 4 action executors
|
|
11
|
+
- **esbuild build** via xrmforge.config.json (32 entries)
|
|
12
|
+
- **Deploy script** (deploy.mjs) with @azure/identity auth, incremental deployment, hash-based change detection
|
|
13
|
+
- **27 entities, 236 OptionSet enums, 95 form interfaces, 7 Custom API executors**
|
|
14
|
+
|
|
15
|
+
### 14.2 LMApp WebResources (KI Comparison Showcase)
|
|
16
|
+
|
|
17
|
+
Created during the KI comparison tests (Session 9). 18 legacy JavaScript form scripts (~8,400 lines) converted to TypeScript with XrmForge patterns.
|
|
18
|
+
|
|
19
|
+
- **19 WebResources** with Fields Enums, EntityNames, OptionSet Enums
|
|
20
|
+
- **84 tests** in 8 test files
|
|
21
|
+
- **XrmForge-optimized:** All 10 AGENT.md rules applied (FormContext cast, Fields Enum, EntityNames, OptionSet Enums, shared getLookupObject, Tab Enums)
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
# CI/CD
|
|
2
|
-
|
|
3
|
-
### 15.1 GitHub Actions CI (`.github/workflows/ci.yml`)
|
|
4
|
-
|
|
5
|
-
**Triggers:** Push to main, Pull Requests against main.
|
|
6
|
-
|
|
7
|
-
**Matrix:** Node 20, Node 22 on ubuntu-latest.
|
|
8
|
-
|
|
9
|
-
**Steps:**
|
|
10
|
-
1. Checkout
|
|
11
|
-
2. Setup pnpm (from packageManager field)
|
|
12
|
-
3. Setup Node.js (matrix version)
|
|
13
|
-
4. `pnpm install --frozen-lockfile`
|
|
14
|
-
5. `pnpm lint`
|
|
15
|
-
6. `pnpm -r exec tsc --noEmit` (typecheck all packages)
|
|
16
|
-
7. `pnpm build`
|
|
17
|
-
8. `pnpm test`
|
|
18
|
-
9. Coverage (Node 22 only): `npx vitest run --coverage` in typegen
|
|
19
|
-
|
|
20
|
-
### 15.2 Release Workflow (`.github/workflows/release.yml`)
|
|
21
|
-
|
|
22
|
-
**Triggers:** After successful CI on push to main.
|
|
23
|
-
|
|
24
|
-
**Steps:**
|
|
25
|
-
1. Checkout, setup pnpm, setup Node 22
|
|
26
|
-
2. `pnpm install --frozen-lockfile`
|
|
27
|
-
3. `pnpm build`
|
|
28
|
-
4. Changesets action: creates Release PR or publishes to npm
|
|
29
|
-
|
|
30
|
-
**Publish command:** `pnpm release` = `turbo run build && changeset publish`
|
|
31
|
-
|
|
32
|
-
### 15.3 Turbo Pipeline
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
build: dependsOn: [^build], outputs: [dist/**]
|
|
36
|
-
test: dependsOn: [build]
|
|
37
|
-
typecheck: dependsOn: [^build]
|
|
38
|
-
lint: (no dependencies)
|
|
39
|
-
dev: cache: false, persistent: true
|
|
40
|
-
clean: cache: false
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 15.4 Changesets
|
|
44
|
-
|
|
45
|
-
Configured for public npm access, auto-update internal dependencies on patch level. Publish requires NPM_TOKEN secret.
|
|
46
|
-
|
|
47
|
-
### 15.5 Publishing Order
|
|
48
|
-
|
|
49
|
-
Due to internal dependencies: typegen first, then devkit, then cli. Must use `pnpm publish` (not `npm publish`) to resolve `workspace:*` references to real versions.
|
|
1
|
+
# CI/CD
|
|
2
|
+
|
|
3
|
+
### 15.1 GitHub Actions CI (`.github/workflows/ci.yml`)
|
|
4
|
+
|
|
5
|
+
**Triggers:** Push to main, Pull Requests against main.
|
|
6
|
+
|
|
7
|
+
**Matrix:** Node 20, Node 22 on ubuntu-latest.
|
|
8
|
+
|
|
9
|
+
**Steps:**
|
|
10
|
+
1. Checkout
|
|
11
|
+
2. Setup pnpm (from packageManager field)
|
|
12
|
+
3. Setup Node.js (matrix version)
|
|
13
|
+
4. `pnpm install --frozen-lockfile`
|
|
14
|
+
5. `pnpm lint`
|
|
15
|
+
6. `pnpm -r exec tsc --noEmit` (typecheck all packages)
|
|
16
|
+
7. `pnpm build`
|
|
17
|
+
8. `pnpm test`
|
|
18
|
+
9. Coverage (Node 22 only): `npx vitest run --coverage` in typegen
|
|
19
|
+
|
|
20
|
+
### 15.2 Release Workflow (`.github/workflows/release.yml`)
|
|
21
|
+
|
|
22
|
+
**Triggers:** After successful CI on push to main.
|
|
23
|
+
|
|
24
|
+
**Steps:**
|
|
25
|
+
1. Checkout, setup pnpm, setup Node 22
|
|
26
|
+
2. `pnpm install --frozen-lockfile`
|
|
27
|
+
3. `pnpm build`
|
|
28
|
+
4. Changesets action: creates Release PR or publishes to npm
|
|
29
|
+
|
|
30
|
+
**Publish command:** `pnpm release` = `turbo run build && changeset publish`
|
|
31
|
+
|
|
32
|
+
### 15.3 Turbo Pipeline
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
build: dependsOn: [^build], outputs: [dist/**]
|
|
36
|
+
test: dependsOn: [build]
|
|
37
|
+
typecheck: dependsOn: [^build]
|
|
38
|
+
lint: (no dependencies)
|
|
39
|
+
dev: cache: false, persistent: true
|
|
40
|
+
clean: cache: false
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 15.4 Changesets
|
|
44
|
+
|
|
45
|
+
Configured for public npm access, auto-update internal dependencies on patch level. Publish requires NPM_TOKEN secret.
|
|
46
|
+
|
|
47
|
+
### 15.5 Publishing Order
|
|
48
|
+
|
|
49
|
+
Due to internal dependencies: typegen first, then devkit, then cli. Must use `pnpm publish` (not `npm publish`) to resolve `workspace:*` references to real versions.
|
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
# Technical Debt
|
|
2
|
-
|
|
3
|
-
### 16.1 Known Issues
|
|
4
|
-
|
|
5
|
-
| Issue | Status | Priority |
|
|
6
|
-
|-------|--------|----------|
|
|
7
|
-
| parseLookup/select not adopted by AI assistants | Open | High |
|
|
8
|
-
| release.yml double runs (CI triggers release, release re-triggers CI) | Open | Low |
|
|
9
|
-
| No integration tests against live Dataverse | Open (OE-4) | Medium |
|
|
10
|
-
| @xrmforge/webapi has no Action/Function support | Accepted | Low |
|
|
11
|
-
| devDependency versions in scaffolded package.json are pinned to old versions | Open | Low |
|
|
12
|
-
|
|
13
|
-
### 16.2 Accepted Limitations
|
|
14
|
-
|
|
15
|
-
- **const enum limitation:** Resolved in typegen 0.8.0. Generated output is now `.ts` ES modules, so `const enum` works directly with vitest and other test frameworks.
|
|
16
|
-
- **Grid.refresh() requires `as any`:** Not typed in @types/xrm.
|
|
17
|
-
- **Single solution per entity:** If an entity appears in multiple solutions, it is only generated once.
|
|
1
|
+
# Technical Debt
|
|
2
|
+
|
|
3
|
+
### 16.1 Known Issues
|
|
4
|
+
|
|
5
|
+
| Issue | Status | Priority |
|
|
6
|
+
|-------|--------|----------|
|
|
7
|
+
| parseLookup/select not adopted by AI assistants | Open | High |
|
|
8
|
+
| release.yml double runs (CI triggers release, release re-triggers CI) | Open | Low |
|
|
9
|
+
| No integration tests against live Dataverse | Open (OE-4) | Medium |
|
|
10
|
+
| @xrmforge/webapi has no Action/Function support | Accepted | Low |
|
|
11
|
+
| devDependency versions in scaffolded package.json are pinned to old versions | Open | Low |
|
|
12
|
+
|
|
13
|
+
### 16.2 Accepted Limitations
|
|
14
|
+
|
|
15
|
+
- **const enum limitation:** Resolved in typegen 0.8.0. Generated output is now `.ts` ES modules, so `const enum` works directly with vitest and other test frameworks.
|
|
16
|
+
- **Grid.refresh() requires `as any`:** Not typed in @types/xrm.
|
|
17
|
+
- **Single solution per entity:** If an entity appears in multiple solutions, it is only generated once.
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
# Roadmap
|
|
2
|
-
|
|
3
|
-
### 17.1 Next Steps (Priority Order)
|
|
4
|
-
|
|
5
|
-
1. **parseLookup/select Adoption** - Improve AGENT.md examples so AI assistants consistently use `/helpers` imports
|
|
6
|
-
2. **LMApp Showcase regeneration** - With latest releases (testing@0.2.0, devkit@0.4.0 with improved AGENT.md)
|
|
7
|
-
3. **KI Battle Round 3** - Re-test Sonnet vs Opus after improvements to measure progress
|
|
8
|
-
4. **Documentation website** - xrmforge.dev or xrmforge.io (OE-3)
|
|
9
|
-
|
|
10
|
-
### 17.2 Open Decisions
|
|
11
|
-
|
|
12
|
-
| ID | Decision | Status |
|
|
13
|
-
|----|----------|--------|
|
|
14
|
-
| OE-1 | npm scope availability (@xrmforge) | Open |
|
|
15
|
-
| OE-2 | GitHub org vs personal repo | Decided: personal (juergenbeck/XrmForge) |
|
|
16
|
-
| OE-3 | Documentation domain (xrmforge.dev or .io) | Open |
|
|
17
|
-
| OE-4 | Dataverse test environment for integration tests | Open |
|
|
18
|
-
| OE-5 | Publisher prefix and solution name for PCF/WebResource tests | Open |
|
|
19
|
-
|
|
20
|
-
### 17.3 Future Possibilities
|
|
21
|
-
|
|
22
|
-
- Relationship Names const enum (OE-7, low priority)
|
|
23
|
-
- @xrmforge/webapi with Action/Function support (reuse DataverseHttpClient)
|
|
24
|
-
- Plugin system for custom generators and type mappings
|
|
25
|
-
- Server-side generation (Custom API in Dataverse)
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
### 17.1 Next Steps (Priority Order)
|
|
4
|
+
|
|
5
|
+
1. **parseLookup/select Adoption** - Improve AGENT.md examples so AI assistants consistently use `/helpers` imports
|
|
6
|
+
2. **LMApp Showcase regeneration** - With latest releases (testing@0.2.0, devkit@0.4.0 with improved AGENT.md)
|
|
7
|
+
3. **KI Battle Round 3** - Re-test Sonnet vs Opus after improvements to measure progress
|
|
8
|
+
4. **Documentation website** - xrmforge.dev or xrmforge.io (OE-3)
|
|
9
|
+
|
|
10
|
+
### 17.2 Open Decisions
|
|
11
|
+
|
|
12
|
+
| ID | Decision | Status |
|
|
13
|
+
|----|----------|--------|
|
|
14
|
+
| OE-1 | npm scope availability (@xrmforge) | Open |
|
|
15
|
+
| OE-2 | GitHub org vs personal repo | Decided: personal (juergenbeck/XrmForge) |
|
|
16
|
+
| OE-3 | Documentation domain (xrmforge.dev or .io) | Open |
|
|
17
|
+
| OE-4 | Dataverse test environment for integration tests | Open |
|
|
18
|
+
| OE-5 | Publisher prefix and solution name for PCF/WebResource tests | Open |
|
|
19
|
+
|
|
20
|
+
### 17.3 Future Possibilities
|
|
21
|
+
|
|
22
|
+
- Relationship Names const enum (OE-7, low priority)
|
|
23
|
+
- @xrmforge/webapi with Action/Function support (reuse DataverseHttpClient)
|
|
24
|
+
- Plugin system for custom generators and type mappings
|
|
25
|
+
- Server-side generation (Custom API in Dataverse)
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# Design Principles
|
|
2
|
-
|
|
3
|
-
The 18 design principles that govern all XrmForge development:
|
|
4
|
-
|
|
5
|
-
1. **Extend, don't replace** - Types build on @types/xrm, never override them.
|
|
6
|
-
2. **TypeScript all the way** - 100% TypeScript-native. No .NET, no ADAL.
|
|
7
|
-
3. **Code must build** - Every work step ends with green build + tests.
|
|
8
|
-
4. **Research before speed** - Investigate, compare, decide, then implement. Never guess.
|
|
9
|
-
5. **No module without basics** - Error handling, logging, unit tests, JSDoc on all public APIs.
|
|
10
|
-
6. **Monorepo discipline** - Each package standalone, no circular deps, barrel exports.
|
|
11
|
-
7. **Enterprise resilience** - Retry + exponential backoff, rate-limit awareness, token caching, read-only default.
|
|
12
|
-
8. **esbuild-first, webpack-compatible** - Default: esbuild (fast). webpack stays supported. IIFE output for D365.
|
|
13
|
-
9. **MSAL-only authentication** - Only @azure/identity (no legacy ADAL). Three flows: client credentials, browser, device code.
|
|
14
|
-
10. **Review required** - After every step, immediate critical review (6 dimensions). No asking if review is wanted.
|
|
15
|
-
11. **Session state required** - session-state.md updated, changelog written, open questions tracked.
|
|
16
|
-
12. **No half measures** - Every step completed fully: green build + tests + review before next step.
|
|
17
|
-
13. **Informed architecture decisions** - Research, compare, recommend with pros/cons, get decision, persist.
|
|
18
|
-
14. **Abstraction over vendor lock-in** - External dependencies behind interfaces (parser, auth, bundler).
|
|
19
|
-
15. **Dual-language labels** - Primary language (1033/English) for identifiers, secondary in JSDoc. German umlauts transliterated.
|
|
20
|
-
16. **Review with research and live verification** - Internet research, live D365 verification, production code checks, cite sources.
|
|
21
|
-
17. **Challenge postponement** - "Later" check: Will it get harder? API contract? Real effort? Technical reasons?
|
|
22
|
-
18. **Read-only default for Dataverse access** - DataverseHttpClient defaults to readOnly: true. Write access is an explicit opt-in.
|
|
1
|
+
# Design Principles
|
|
2
|
+
|
|
3
|
+
The 18 design principles that govern all XrmForge development:
|
|
4
|
+
|
|
5
|
+
1. **Extend, don't replace** - Types build on @types/xrm, never override them.
|
|
6
|
+
2. **TypeScript all the way** - 100% TypeScript-native. No .NET, no ADAL.
|
|
7
|
+
3. **Code must build** - Every work step ends with green build + tests.
|
|
8
|
+
4. **Research before speed** - Investigate, compare, decide, then implement. Never guess.
|
|
9
|
+
5. **No module without basics** - Error handling, logging, unit tests, JSDoc on all public APIs.
|
|
10
|
+
6. **Monorepo discipline** - Each package standalone, no circular deps, barrel exports.
|
|
11
|
+
7. **Enterprise resilience** - Retry + exponential backoff, rate-limit awareness, token caching, read-only default.
|
|
12
|
+
8. **esbuild-first, webpack-compatible** - Default: esbuild (fast). webpack stays supported. IIFE output for D365.
|
|
13
|
+
9. **MSAL-only authentication** - Only @azure/identity (no legacy ADAL). Three flows: client credentials, browser, device code.
|
|
14
|
+
10. **Review required** - After every step, immediate critical review (6 dimensions). No asking if review is wanted.
|
|
15
|
+
11. **Session state required** - session-state.md updated, changelog written, open questions tracked.
|
|
16
|
+
12. **No half measures** - Every step completed fully: green build + tests + review before next step.
|
|
17
|
+
13. **Informed architecture decisions** - Research, compare, recommend with pros/cons, get decision, persist.
|
|
18
|
+
14. **Abstraction over vendor lock-in** - External dependencies behind interfaces (parser, auth, bundler).
|
|
19
|
+
15. **Dual-language labels** - Primary language (1033/English) for identifiers, secondary in JSDoc. German umlauts transliterated.
|
|
20
|
+
16. **Review with research and live verification** - Internet research, live D365 verification, production code checks, cite sources.
|
|
21
|
+
17. **Challenge postponement** - "Later" check: Will it get harder? API contract? Real effort? Technical reasons?
|
|
22
|
+
18. **Read-only default for Dataverse access** - DataverseHttpClient defaults to readOnly: true. Write access is an explicit opt-in.
|