@khester/create-dynamics-app 2.1.0 → 2.2.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.
Files changed (121) hide show
  1. package/dist/artifacts/registry.d.ts +4 -3
  2. package/dist/artifacts/registry.d.ts.map +1 -1
  3. package/dist/artifacts/registry.js +121 -11
  4. package/dist/artifacts/registry.js.map +1 -1
  5. package/dist/artifacts/types.d.ts +1 -1
  6. package/dist/artifacts/types.d.ts.map +1 -1
  7. package/dist/index.js +2 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/injectDevTools.d.ts.map +1 -1
  10. package/dist/injectDevTools.js +4 -2
  11. package/dist/injectDevTools.js.map +1 -1
  12. package/dist/scaffold.d.ts +1 -0
  13. package/dist/scaffold.d.ts.map +1 -1
  14. package/dist/scaffold.js +3 -1
  15. package/dist/scaffold.js.map +1 -1
  16. package/package.json +3 -2
  17. package/templates/grid-starter/ARCHITECTURE.md +66 -0
  18. package/templates/grid-starter/README.md +122 -0
  19. package/templates/grid-starter/env.example +16 -0
  20. package/templates/grid-starter/gitignore +6 -0
  21. package/templates/grid-starter/index.html +16 -0
  22. package/templates/grid-starter/package.json +39 -0
  23. package/templates/grid-starter/src/App.tsx +23 -0
  24. package/templates/grid-starter/src/core/services/FetchApiService.ts +117 -0
  25. package/templates/grid-starter/src/core/services/IApiService.ts +37 -0
  26. package/templates/grid-starter/src/core/services/MockApiService.ts +72 -0
  27. package/templates/grid-starter/src/core/services/ServiceFactory.ts +58 -0
  28. package/templates/grid-starter/src/core/services/XrmApiService.ts +135 -0
  29. package/templates/grid-starter/src/core/services/crudLogging.ts +52 -0
  30. package/templates/grid-starter/src/dev-tools/DevPanel.tsx +239 -0
  31. package/templates/grid-starter/src/grid/GridPage.tsx +119 -0
  32. package/templates/grid-starter/src/index.tsx +18 -0
  33. package/templates/grid-starter/src/vite-env.d.ts +15 -0
  34. package/templates/grid-starter/tools/deploy/deploy-webresource.cjs +117 -0
  35. package/templates/grid-starter/tsconfig.json +19 -0
  36. package/templates/grid-starter/vite.config.ts +76 -0
  37. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +2 -0
  38. package/templates/pcf-field/_variants/ValueInput.date.tsx +2 -0
  39. package/templates/pcf-field/_variants/ValueInput.number.tsx +2 -0
  40. package/templates/pcf-field/_variants/ValueInput.optionset.tsx +77 -0
  41. package/templates/pcf-field/_variants/ValueInput.text.tsx +2 -0
  42. package/templates/pcf-field/index.ts +1 -1
  43. package/templates/pcf-field/package.json +3 -1
  44. package/templates/pcf-field/{{componentName}}Component.tsx +2 -0
  45. package/templates/react-custom-page/ARCHITECTURE.md +75 -0
  46. package/templates/react-custom-page/README.md +74 -568
  47. package/templates/react-custom-page/env.example +16 -0
  48. package/templates/react-custom-page/gitignore +1 -0
  49. package/templates/react-custom-page/index.html +16 -0
  50. package/templates/react-custom-page/package.json +21 -49
  51. package/templates/react-custom-page/src/App.tsx +26 -0
  52. package/templates/react-custom-page/src/core/recordContext.test.ts +30 -0
  53. package/templates/react-custom-page/src/core/recordContext.ts +51 -0
  54. package/templates/react-custom-page/src/core/services/FetchApiService.ts +117 -0
  55. package/templates/react-custom-page/src/core/services/IApiService.ts +37 -0
  56. package/templates/react-custom-page/src/core/services/MockApiService.ts +73 -0
  57. package/templates/react-custom-page/src/core/services/ServiceFactory.ts +58 -0
  58. package/templates/react-custom-page/src/core/services/XrmApiService.ts +135 -0
  59. package/templates/react-custom-page/src/core/services/crudLogging.ts +52 -0
  60. package/templates/react-custom-page/src/dev-tools/DevPanel.tsx +238 -0
  61. package/templates/react-custom-page/src/domain/diff.test.ts +87 -0
  62. package/templates/react-custom-page/src/domain/diff.ts +38 -0
  63. package/templates/react-custom-page/src/example/ExamplePage.tsx +140 -0
  64. package/templates/react-custom-page/src/example/exampleError.ts +36 -0
  65. package/templates/react-custom-page/src/example/hooks/useExampleData.ts +40 -0
  66. package/templates/react-custom-page/src/example/hooks/useExampleForm.ts +99 -0
  67. package/templates/react-custom-page/src/example/mappers/accountMapper.test.ts +38 -0
  68. package/templates/react-custom-page/src/example/mappers/accountMapper.ts +55 -0
  69. package/templates/react-custom-page/src/example/models/Account.ts +74 -0
  70. package/templates/react-custom-page/src/index.tsx +18 -128
  71. package/templates/react-custom-page/src/vite-env.d.ts +15 -0
  72. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +117 -0
  73. package/templates/react-custom-page/tsconfig.json +12 -22
  74. package/templates/react-custom-page/vite.config.ts +76 -0
  75. package/templates/starter-page/README.md +38 -0
  76. package/templates/starter-page/_variants/App.dashboard.v8.tsx +46 -0
  77. package/templates/starter-page/_variants/App.form.v8.tsx +59 -0
  78. package/templates/starter-page/_variants/App.master-detail.v8.tsx +61 -0
  79. package/templates/starter-page/_variants/App.panel.v8.tsx +99 -0
  80. package/templates/starter-page/gitignore +5 -0
  81. package/templates/starter-page/package.json +27 -0
  82. package/templates/starter-page/public/index.html +11 -0
  83. package/templates/starter-page/src/index.tsx +10 -0
  84. package/templates/starter-page/src/services/dataverse.ts +30 -0
  85. package/templates/starter-page/tsconfig.json +15 -0
  86. package/templates/starter-page/webpack.config.js +17 -0
  87. package/templates/react-custom-page/deployment/README.md +0 -484
  88. package/templates/react-custom-page/docs/ARCHITECTURE_OVERVIEW.md +0 -506
  89. package/templates/react-custom-page/docs/BEST_PRACTICES.md +0 -723
  90. package/templates/react-custom-page/docs/MIGRATION_GUIDE.md +0 -447
  91. package/templates/react-custom-page/public/index.html +0 -15
  92. package/templates/react-custom-page/scripts/custom-build.js +0 -255
  93. package/templates/react-custom-page/src/components/AccountForm.css +0 -71
  94. package/templates/react-custom-page/src/components/AccountForm.tsx +0 -541
  95. package/templates/react-custom-page/src/components/AccountManagement.css +0 -86
  96. package/templates/react-custom-page/src/components/AccountManagement.tsx +0 -370
  97. package/templates/react-custom-page/src/components/ContactForm.css +0 -48
  98. package/templates/react-custom-page/src/components/ContactForm.tsx +0 -327
  99. package/templates/react-custom-page/src/components/ContactManagement.css +0 -86
  100. package/templates/react-custom-page/src/components/ContactManagement.tsx +0 -357
  101. package/templates/react-custom-page/src/components/Logging/LogDialog.tsx +0 -291
  102. package/templates/react-custom-page/src/components/Logging/LoggingContext.tsx +0 -166
  103. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.css +0 -192
  104. package/templates/react-custom-page/src/components/Logging/LoggingDebugPanel.tsx +0 -177
  105. package/templates/react-custom-page/src/components/Logging/LoggingProvider.tsx +0 -3
  106. package/templates/react-custom-page/src/components/Logging/logger.ts +0 -193
  107. package/templates/react-custom-page/src/constants/account.ts +0 -410
  108. package/templates/react-custom-page/src/constants/contact.ts +0 -362
  109. package/templates/react-custom-page/src/models/Account.ts +0 -480
  110. package/templates/react-custom-page/src/models/BaseEntity.ts +0 -204
  111. package/templates/react-custom-page/src/models/Contact.ts +0 -580
  112. package/templates/react-custom-page/src/pcf/ContactControlWrapper.tsx +0 -107
  113. package/templates/react-custom-page/src/pcf/MultiEntityControlWrapper.tsx +0 -205
  114. package/templates/react-custom-page/src/providers/DynamicsProvider.tsx +0 -353
  115. package/templates/react-custom-page/src/services/MockApiService.ts +0 -260
  116. package/templates/react-custom-page/src/services/ServiceFactory.ts +0 -65
  117. package/templates/react-custom-page/src/services/XrmApiService.ts +0 -213
  118. package/templates/react-custom-page/src/styles/index.css +0 -171
  119. package/templates/react-custom-page/tools/metadata-sync/index.js +0 -152
  120. package/templates/react-custom-page/webpack.config.js +0 -57
  121. /package/templates/_shared/dev-tools/auth/{get-token.js → get-token.cjs} +0 -0
@@ -1,607 +1,113 @@
1
- # Dynamics 365 Enhanced Template
1
+ # React Custom Page (Dataverse)
2
2
 
3
- A sophisticated, enterprise-grade Dynamics 365 application template built with the Dynamics UI Kit.
4
- Features advanced entity management for Accounts and Contacts with comprehensive CRUD operations,
5
- smart environment detection, centralized logging, and optimized deployment capabilities.
3
+ A minimal, production-shaped starter for a React app mounted as a **Dynamics 365
4
+ custom page**. It ships one thin example form (edit a single Account) on a clean
5
+ layering you can copy: `component hook pure mapper/diff → model → IApiService`.
6
6
 
7
- ## 🚀 Features
7
+ UI primitives, theme, and logging come from
8
+ [`@khester/reusable-components`](https://www.npmjs.com/package/@khester/reusable-components);
9
+ the data-access seam (`src/core/services`) is owned by this app.
8
10
 
9
- ### **Dual Entity Management**
11
+ ## Commands
10
12
 
11
- - **Account Management**: Complete CRUD operations with industry codes, revenue tracking, and
12
- address management
13
- - **Contact Management**: Full contact lifecycle with preferred contact methods and relationship
14
- tracking
15
- - **Unified Interface**: Seamless navigation between Account and Contact management via tabbed
16
- interface
13
+ | Command | What it does |
14
+ |---------|--------------|
15
+ | `npm install` | Install dependencies |
16
+ | `npm run dev` | Run in **mock** mode — seeded data, no org needed |
17
+ | `npm run dev:token` | Run against a **live org** refreshes the token + starts Vite |
18
+ | `npm run auth:token -- --url https://<org>.crm.dynamics.com` | Acquire/refresh a Dataverse token into `.env` (needs `az login`) |
19
+ | `npm run build` | Production build → `dist/` (multi-file) |
20
+ | `npm run build:d365` | Single self-contained `dist/index.html` (the web resource) |
21
+ | `npm run deploy` | Build + upload + **publish** the web resource to your org |
22
+ | `npm test` | Unit-test the pure layers (diff, mappers, record context) |
23
+ | `npm run typecheck` | Type-check (`tsc --noEmit`) |
24
+ | `npm install @khester/reusable-components@latest` | Update the shared component library |
17
25
 
18
- ### **Enterprise Architecture**
26
+ > Requires **Node 18+**. For `dev:token` / `deploy` you also need `az login` and
27
+ > `DYNAMICS_URL` in `.env` (set once via `npm run auth:token -- --url …`).
19
28
 
20
- - **BaseEntity Pattern**: Abstract base class with static CRUD methods and validation
21
- - **ServiceFactory**: Smart environment detection (development vs production) with automatic API
22
- service selection
23
- - **Centralized Logging**: Comprehensive logging system with debug UI and export capabilities
24
- - **Entity Constants**: Complete field dictionaries with metadata for type safety
25
-
26
- ### **Advanced UI Components**
27
-
28
- - **Enhanced Forms**: Account and Contact forms with validation and error handling
29
- - **Interactive Debug Panel**: Real-time logging with filtering and export functionality
30
- - **Responsive Design**: Mobile-optimized layouts with accessibility features
31
- - **PCF Integration**: Multi-entity control wrapper for Dynamics 365 custom controls
32
-
33
- ### **Production-Ready Deployment**
34
-
35
- - **Custom Build System**: D365-optimized webpack configuration with single-file output
36
- - **Multiple Deployment Targets**: Web Resources, PCF Controls, and Custom Pages
37
- - **Environment Detection**: Automatic MockApiService vs XrmApiService selection
38
- - **Performance Optimization**: Minified production builds with deployment guidance
39
-
40
- ## 🏁 Quick Start
41
-
42
- ### Installation
29
+ ## Quick start
43
30
 
44
31
  ```bash
45
- # Using the CLI tool
46
- npx create-dynamics-app my-d365-app --template dynamics-365-starter
47
-
48
- # Or clone and install
49
- git clone <repository-url>
50
- cd dynamics-365-starter
51
32
  npm install
33
+ npm run dev # mock mode — a seeded account, no org needed
52
34
  ```
53
35
 
54
- ### Development
36
+ Open http://localhost:3000. Edit a field; **Save** enables when the form is dirty
37
+ and logs a `[CRUD] UPDATE accounts ok` line. On localhost, `window.dumpAppLogs()`
38
+ in the browser console prints the structured log buffer.
55
39
 
56
- ```bash
57
- # Start development server with mock data
58
- npm start
59
- # Opens http://localhost:3000 with Account and Contact management
40
+ A floating **🔧 Dev Tools** panel (bottom-right, localhost only) shows the active
41
+ mode (Mock / Token-proxy / Production), lets you load a record by GUID (adds `?id=`
42
+ and reloads), and views the `[CRUD]` log buffer. It renders nothing once deployed.
60
43
 
61
- # Build for development (unminified)
62
- npm run build:dev
44
+ ## Three runtime modes
63
45
 
64
- # Build for production (optimized for D365)
65
- npm run build:prod
66
-
67
- # Serve built files locally
68
- npm run serve
69
- ```
46
+ `src/core/services/ServiceFactory.ts` picks the data service per environment:
70
47
 
71
- ## 📋 Project Structure
72
-
73
- ```
74
- src/
75
- ├── components/
76
- │ ├── AccountManagement.tsx # Account CRUD interface
77
- │ ├── AccountForm.tsx # Account creation/editing form
78
- │ ├── ContactManagement.tsx # Contact CRUD interface
79
- │ ├── ContactForm.tsx # Contact creation/editing form
80
- │ └── Logging/ # Centralized logging system
81
- │ ├── logger.ts # Logger utility class
82
- │ ├── LoggingContext.tsx # React context for logs
83
- │ ├── LoggingProvider.tsx # Provider component
84
- │ └── LoggingDebugPanel.tsx # Debug UI with filtering
85
- ├── models/
86
- │ ├── BaseEntity.ts # Abstract base class with CRUD
87
- │ ├── Account.ts # Account entity with validation
88
- │ └── Contact.ts # Contact entity with validation
89
- ├── constants/
90
- │ ├── account.ts # Account field constants & metadata
91
- │ └── contact.ts # Contact field constants & metadata
92
- ├── services/
93
- │ ├── ServiceFactory.ts # Environment-aware service factory
94
- │ ├── XrmApiService.ts # Production Dynamics 365 API service
95
- │ └── MockApiService.ts # Development mock service
96
- ├── providers/
97
- │ └── DynamicsProvider.tsx # API context with environment detection
98
- ├── pcf/
99
- │ ├── ContactControlWrapper.tsx # Single-entity PCF wrapper
100
- │ └── MultiEntityControlWrapper.tsx # Multi-entity PCF wrapper
101
- ├── scripts/
102
- │ └── custom-build.js # D365-optimized build configuration
103
- ├── styles/
104
- │ └── index.css # Global styles and responsive design
105
- └── index.tsx # Application entry with navigation
106
- ```
107
-
108
- ## 🏗️ Entity Model Architecture
109
-
110
- ### BaseEntity Pattern
111
-
112
- All entities inherit from `BaseEntity` which provides:
113
-
114
- ```typescript
115
- // Static CRUD methods with validation and logging
116
- export abstract class BaseEntity {
117
- protected static async createEntity<T>(
118
- apiService: IApiService,
119
- entity: T,
120
- entityCollectionName: string,
121
- loggerContext: string
122
- ): Promise<T>;
123
-
124
- protected static async updateEntity<T>(...): Promise<T>;
125
- protected static async deleteEntity(...): Promise<void>;
126
- protected static async retrieveEntitiesByFilter<T>(...): Promise<T[]>;
127
- }
128
- ```
48
+ | Mode | When | Service |
49
+ |------|------|---------|
50
+ | **Mock** | `localhost`, no `DYNAMICS_URL` | `MockApiService` (in-memory, seeds one account) |
51
+ | **Token** | `localhost` + `DYNAMICS_URL` set | `FetchApiService` → Vite proxy → real Dataverse |
52
+ | **Production** | deployed in a model-driven app | `XrmApiService` (Web API, session auth) |
129
53
 
130
- ### Entity Implementation Example
131
-
132
- ```typescript
133
- // Account entity with complete CRUD operations
134
- export class Account extends BaseEntity implements IAccount {
135
- // Constructor and properties...
136
-
137
- // Static CRUD methods
138
- public static async create(apiService: IApiService, account: Account): Promise<Account> {
139
- const loggerContext = 'Account.create';
140
- console.log(`${loggerContext}: Creating new account`, { name: account.name });
141
-
142
- return await this.createEntity<Account>(
143
- apiService,
144
- account,
145
- AccountConstants.EntityCollectionName,
146
- loggerContext
147
- );
148
- }
149
-
150
- public static async retrieveActiveAccounts(apiService: IApiService): Promise<Account[]> {
151
- // Built-in FetchXML generation and error handling
152
- const filter = `<filter type="and">
153
- <condition attribute="${AccountConstants.StateCode}" operator="eq" value="0" />
154
- </filter>`;
155
-
156
- return await this.retrieveByFilter(apiService, filter);
157
- }
158
-
159
- // Client-side validation
160
- validate(): boolean {
161
- if (!this.name || this.name.trim().length === 0) {
162
- throw new Error('Account name is required');
163
- }
164
- // Additional validation rules...
165
- return true;
166
- }
167
- }
168
- ```
169
-
170
- ### Entity Constants
171
-
172
- Complete field dictionaries with metadata:
173
-
174
- ```typescript
175
- export class AccountConstants {
176
- /** Type: String, RequiredLevel: ApplicationRequired, MaxLength: 160 */
177
- public static readonly PrimaryName: string = 'name';
178
-
179
- /** Type: String, RequiredLevel: None, MaxLength: 100, Format: Email */
180
- public static readonly EMailAddress1: string = 'emailaddress1';
181
-
182
- /** Type: Money, RequiredLevel: None, MinValue: -922337203685477 */
183
- public static readonly Revenue: string = 'revenue';
184
- }
185
- ```
186
-
187
- ## ⚙️ Service Factory & Environment Detection
188
-
189
- The `ServiceFactory` automatically detects the runtime environment:
190
-
191
- ```typescript
192
- export class ServiceFactory {
193
- // Automatic environment detection
194
- public static get isMockEnvironment(): boolean {
195
- const hostname = window.location.hostname;
196
- return hostname === 'localhost' || hostname === '127.0.0.1';
197
- }
198
-
199
- // Smart API service creation
200
- public static createApiService(Xrm?: any): IApiService {
201
- if (this.isMockEnvironment) {
202
- return new MockApiService(); // Development with mock data
203
- }
204
- return new XrmApiService(Xrm); // Production with Dynamics 365
205
- }
206
- }
207
- ```
208
-
209
- ## 📊 Centralized Logging System
210
-
211
- Comprehensive logging with UI:
212
-
213
- ```typescript
214
- // Logger usage throughout the application
215
- Logger.log('Application initialized', 'App');
216
- Logger.userAction('Contact created', { contactId: '123' }, 'ContactForm');
217
- Logger.error('API call failed', 'ContactManagement', error);
218
- Logger.apiOperation('CREATE', 'contact', contactData, 'ContactForm.submit');
219
- Logger.validation('Contact', ['Email is required'], 'ContactForm.validate');
220
- ```
221
-
222
- The debug panel provides:
223
-
224
- - Real-time log viewing with level filtering
225
- - Search and export capabilities
226
- - Performance timing and API operation tracking
227
- - User action audit trail
228
-
229
- ## 🎯 Deployment Options
230
-
231
- ### 1. Dynamics 365 Web Resources
232
-
233
- **Optimal for**: Dashboard widgets, form sections, custom pages
234
-
235
- ```bash
236
- # Build optimized for D365
237
- npm run build:d365
238
-
239
- # Upload generated files as web resources:
240
- # - dist/main.js → Script (JScript) web resource
241
- # - dist/index.html → Web Page (HTML) web resource
242
- # - Set "Available for Dynamics 365 mobile" if needed
243
- ```
244
-
245
- The custom build process:
246
-
247
- - Disables code splitting for single-file deployment
248
- - Removes content hashes for consistent web resource naming
249
- - Optimizes bundle size and provides deployment guidance
250
- - Generates deployment info with asset details
251
-
252
- ### 2. PCF Custom Controls
253
-
254
- **Optimal for**: Form controls, view extensions, embedded components
255
-
256
- #### Single Entity Control
257
-
258
- ```typescript
259
- import { ContactControlWrapper } from './pcf/ContactControlWrapper';
260
-
261
- // In your PCF component
262
- export class ContactControl implements ComponentFramework.StandardControl<IInputs, IOutputs> {
263
- public init(context: ComponentFramework.Context<IInputs>): void {
264
- // Render ContactControlWrapper with PCF context
265
- ReactDOM.render(React.createElement(ContactControlWrapper, { context }), this.container);
266
- }
267
- }
268
- ```
269
-
270
- #### Multi-Entity Control
271
-
272
- ```typescript
273
- import { MultiEntityControlWrapper } from './pcf/MultiEntityControlWrapper';
274
-
275
- // Support both Account and Contact management in one control
276
- ReactDOM.render(
277
- React.createElement(MultiEntityControlWrapper, {
278
- context,
279
- defaultEntity: 'contact',
280
- showTabs: true,
281
- }),
282
- this.container
283
- );
284
- ```
285
-
286
- ### 3. Dynamics 365 Custom Pages
287
-
288
- **Optimal for**: Full-page applications, complex workflows
54
+ ### Token mode — test against a real org locally
289
55
 
290
56
  ```bash
291
- # Build for Custom Pages
292
- npm run build:prod
293
-
294
- # Deploy as model-driven app page:
295
- # 1. Upload main.js as web resource
296
- # 2. Create Custom Page in Power Apps
297
- # 3. Add HTML control with web resource reference
298
- ```
299
-
300
- ## 🔧 Configuration
301
-
302
- ### Environment Variables
303
-
304
- ```bash
305
- # .env.local
306
- REACT_APP_D365_URL=https://yourorg.crm.dynamics.com
307
- REACT_APP_ENVIRONMENT=development
308
- ```
309
-
310
- ### API Service Configuration
311
-
312
- ```typescript
313
- // Automatic configuration via ServiceFactory
314
- const DynamicsProvider: React.FC = ({ children }) => {
315
- useEffect(() => {
316
- // ServiceFactory handles environment detection
317
- const xrmObject = ServiceFactory.isDynamics365Context() ? window.Xrm : undefined;
318
- const service = ServiceFactory.createApiService(xrmObject);
319
- setApiService(service);
320
- }, []);
321
-
322
- // Provider implementation...
323
- };
324
- ```
325
-
326
- ### PCF Integration
327
-
328
- ```typescript
329
- // Custom API service for PCF context
330
- const createPCFApiService = () => ({
331
- createRecord: async (entityName: string, data: any) =>
332
- await context.webAPI.createRecord(entityName, data),
333
- retrieveMultipleRecords: async (entityName: string, fetchXml: string) =>
334
- await context.webAPI.retrieveMultipleRecords(
335
- entityName,
336
- `?fetchXml=${encodeURIComponent(fetchXml)}`
337
- ),
338
- // Additional PCF WebAPI methods...
339
- });
340
- ```
341
-
342
- ## 🛠️ Development Workflow
343
-
344
- ### Adding New Entities
345
-
346
- 1. **Create Entity Model**:
347
-
348
- ```typescript
349
- // src/models/Opportunity.ts
350
- export class Opportunity extends BaseEntity implements IOpportunity {
351
- // Entity properties and validation
352
- public static async create(
353
- apiService: IApiService,
354
- opportunity: Opportunity
355
- ): Promise<Opportunity> {
356
- return await this.createEntity<Opportunity>(/*...*/);
357
- }
358
- }
57
+ npm run auth:token -- --url https://<org>.crm.dynamics.com # writes .env via Azure CLI
58
+ npm run dev
359
59
  ```
360
60
 
361
- 2. **Create Entity Constants**:
61
+ The Vite dev server proxies `/api/data/*` to the org and injects the bearer token
62
+ **server-side** — the token never enters the browser bundle. Tokens last ~60 min;
63
+ re-run `npm run auth:token` to refresh (no dev-server restart needed). Requires
64
+ `az login` first. See `env.example` for the variables.
362
65
 
363
- ```typescript
364
- // src/constants/opportunity.ts
365
- export class OpportunityConstants {
366
- public static readonly EntityName: string = 'opportunity';
367
- public static readonly PrimaryName: string = 'name';
368
- // Field definitions with metadata...
369
- }
370
- ```
371
-
372
- 3. **Create Management Component**:
373
-
374
- ```typescript
375
- // src/components/OpportunityManagement.tsx
376
- export const OpportunityManagement: React.FC = () => {
377
- // Follow AccountManagement pattern
378
- const loadOpportunities = useCallback(async () => {
379
- const opportunities = await Opportunity.retrieveActiveOpportunities(apiService);
380
- // Component implementation...
381
- }, [apiService]);
382
- };
383
- ```
384
-
385
- ### Custom Validation
386
-
387
- ```typescript
388
- // In entity models
389
- validate(): boolean {
390
- const validationErrors: string[] = [];
391
-
392
- if (!this.name?.trim()) {
393
- validationErrors.push('Name is required');
394
- }
395
-
396
- if (this.revenue && this.revenue < 0) {
397
- validationErrors.push('Revenue cannot be negative');
398
- }
399
-
400
- if (validationErrors.length > 0) {
401
- Logger.validation('Account', validationErrors, 'Account.validate');
402
- throw new Error(validationErrors.join(', '));
403
- }
404
-
405
- return true;
406
- }
407
- ```
408
-
409
- ### Extending Logging
410
-
411
- ```typescript
412
- // Add custom log types
413
- Logger.performance('Data fetch completed', startTime, 'AccountManagement');
414
- Logger.businessRule('Credit limit exceeded', { accountId, creditLimit }, 'AccountForm');
415
- ```
416
-
417
- ## 📋 Available Scripts
66
+ Once `DYNAMICS_URL` is in `.env`, **`npm run dev:token`** refreshes the token and
67
+ starts the dev server in one step:
418
68
 
419
69
  ```bash
420
- # Development
421
- npm start # Start development server with hot reload
422
- npm run dev # Alias for start
423
-
424
- # Building
425
- npm run build # Standard webpack production build
426
- npm run build:dev # Unminified build for debugging
427
- npm run build:prod # Optimized build for D365 deployment
428
- npm run build:d365 # Alias for build:prod
429
-
430
- # Quality Assurance
431
- npm run typecheck # TypeScript compilation check
432
- npm run lint # ESLint code quality check
433
- npm run clean # Clean build directory
434
-
435
- # Deployment
436
- npm run serve # Serve built files locally on port 62874
437
- ```
438
-
439
- ## 🧪 Testing Workflow
440
-
441
- ### Component Testing
442
-
443
- ```typescript
444
- // Test entity models
445
- const account = new Account({ name: 'Test Account' });
446
- account.validate(); // Should pass
447
-
448
- // Test API operations
449
- const mockApiService = new MockApiService();
450
- const createdAccount = await Account.create(mockApiService, account);
70
+ npm run dev:token
451
71
  ```
452
72
 
453
- ### Environment Testing
73
+ ## Build & deploy
454
74
 
455
75
  ```bash
456
- # Test development environment
457
- npm start # Should use MockApiService
458
-
459
- # Test production build
460
- npm run build:prod
461
- npm run serve # Verify optimized bundle
462
- ```
463
-
464
- ### PCF Testing
465
-
466
- ```typescript
467
- // Test PCF wrapper with mock context
468
- const mockContext = {
469
- webAPI: {
470
- createRecord: jest.fn(),
471
- retrieveMultipleRecords: jest.fn()
472
- }
473
- };
474
-
475
- render(<ContactControlWrapper context={mockContext} />);
76
+ npm run build # standard multi-file build → dist/
77
+ npm run build:d365 # single self-contained dist/index.html (vite-plugin-singlefile)
78
+ npm run deploy # build:d365 + upload as an HTML web resource + publish
476
79
  ```
477
80
 
478
- ## 🔍 Troubleshooting
479
-
480
- ### Common Issues
481
-
482
- #### Build Errors
81
+ **`npm run deploy`** refreshes the token, builds the single-file bundle, and
82
+ upserts + publishes it as an HTML web resource via the Dataverse Web API (no PAC
83
+ CLI needed). Set the web-resource name in `.env` first — the prefix must match a
84
+ publisher that exists in your org:
483
85
 
484
86
  ```bash
485
- # Clear cache and reinstall
486
- rm -rf node_modules package-lock.json
487
- npm install
488
-
489
- # Check TypeScript compilation
490
- npm run typecheck
87
+ # .env
88
+ WEBRESOURCE_NAME=cr1a2_/myapp/index.html # use YOUR publisher prefix
89
+ # WEBRESOURCE_SOLUTION=MySolution # optional: add it to an unmanaged solution
491
90
  ```
492
91
 
493
- #### Environment Detection Issues
494
-
495
- ```typescript
496
- // Force environment for testing
497
- ServiceFactory.isMockEnvironment = true; // Development
498
- ServiceFactory.isMockEnvironment = false; // Production
499
- ```
500
-
501
- #### PCF Integration Problems
502
-
503
- - Verify `context.webAPI` is available
504
- - Check PCF manifest configuration
505
- - Ensure proper component lifecycle implementation
506
- - Review browser console for Xrm object availability
507
-
508
- #### Logging Not Working
92
+ Then host it on a model-driven **custom page**, or open it via
93
+ `Xrm.Navigation.navigateTo({ pageType: "webresource", webresourceName: "<name>" })`.
94
+ The page reads an optional record id from the URL (`?id=<guid>`); in production it
95
+ resolves `Xrm` from `window.parent`.
509
96
 
510
- ```typescript
511
- // Check logger configuration
512
- console.log('Has custom logger:', Logger.hasCustomLogger());
97
+ > Some orgs enforce a Content-Security-Policy that blocks inline `<script>`. The
98
+ > single-file bundle is one inline script — if your environment blocks it, use the
99
+ > multi-file `npm run build` output instead.
513
100
 
514
- // Manually set logger function
515
- Logger.setLoggerFunction((message, source) => {
516
- console.log(`[${source}] ${message}`);
517
- });
518
- ```
519
-
520
- ### Performance Optimization
101
+ ## Make it your own
521
102
 
522
- #### Bundle Size
103
+ 1. Replace `src/example/` with your page: `models/<Entity>.ts` (CRUD), a pure
104
+ `mappers/<entity>Mapper.ts` (+ test), `hooks/`, and the thin component.
105
+ 2. Add a seed for your entity in `src/core/services/MockApiService.ts` so
106
+ `npm run dev` works offline.
107
+ 3. Keep CRUD in the model and business rules in the pure layers — see
108
+ [ARCHITECTURE.md](./ARCHITECTURE.md).
523
109
 
524
110
  ```bash
525
- # Analyze bundle with webpack-bundle-analyzer
526
- npm install --save-dev webpack-bundle-analyzer
527
- # Add analyzer to build script and review output
528
- ```
529
-
530
- #### Entity Model Performance
531
-
532
- ```typescript
533
- // Use selective field retrieval
534
- const accounts = await Account.retrieveByFilter(apiService, filter, [
535
- AccountConstants.PrimaryName,
536
- AccountConstants.EMailAddress1,
537
- AccountConstants.Telephone1,
538
- ]);
111
+ npm run test # unit-tests the pure layers (diff, mappers)
112
+ npm run typecheck # tsc --noEmit
539
113
  ```
540
-
541
- ## 📚 Learning Resources
542
-
543
- ### Essential Documentation
544
-
545
- - [Dynamics UI Kit Components](https://github.com/your-org/dynamics-ui-kit)
546
- - [Dynamics 365 Web API Reference](https://docs.microsoft.com/dynamics365/customer-engagement/web-api/)
547
- - [PowerApps Component Framework](https://docs.microsoft.com/powerapps/developer/component-framework/)
548
- - [Fluent UI v8 Components](https://developer.microsoft.com/fluentui/get-started/web#fluent-ui-react-v8)
549
-
550
- ### Advanced Topics
551
-
552
- - [Entity Relationship Modeling in D365](https://docs.microsoft.com/dynamics365/customerengagement/on-premises/developer/introduction-entities)
553
- - [Custom Page Development](https://docs.microsoft.com/powerapps/developer/model-driven-apps/customizable-controls-custom-pages)
554
- - [FetchXML Query Examples](https://docs.microsoft.com/powerapps/developer/data-platform/use-fetchxml-construct-query)
555
-
556
- ## 🤝 Contributing
557
-
558
- ### Development Setup
559
-
560
- 1. **Fork and Clone**: Create a fork of the repository
561
- 2. **Install Dependencies**: Run `npm install` in the template directory
562
- 3. **Create Feature Branch**: `git checkout -b feature/your-feature`
563
- 4. **Follow Patterns**: Use existing entity models and components as templates
564
- 5. **Add Tests**: Include validation and component tests
565
- 6. **Update Documentation**: Add examples and update README
566
-
567
- ### Code Standards
568
-
569
- - **TypeScript**: Full type safety with strict mode
570
- - **Entity Models**: Follow BaseEntity pattern with validation
571
- - **Components**: Use functional components with hooks
572
- - **Logging**: Include comprehensive logging for debugging
573
- - **Error Handling**: Implement proper error boundaries and validation
574
-
575
- ### Pull Request Guidelines
576
-
577
- 1. Ensure TypeScript compilation is clean
578
- 2. Test in both development and production builds
579
- 3. Verify PCF wrapper functionality
580
- 4. Update documentation for new features
581
- 5. Include migration guide for breaking changes
582
-
583
- ## 📄 License
584
-
585
- This template is part of the Dynamics UI Kit and follows the same licensing terms.
586
-
587
- ## 🆘 Support
588
-
589
- ### Getting Help
590
-
591
- - **Issues**: Create issues in the Dynamics UI Kit repository
592
- - **Documentation**: Check the comprehensive guides and examples
593
- - **Community**: Join discussions in the project community
594
-
595
- ### Enterprise Support
596
-
597
- For enterprise deployments and custom development:
598
-
599
- - Architecture consulting for complex D365 integrations
600
- - Custom entity model development
601
- - Performance optimization and scaling guidance
602
- - Advanced PCF control development
603
-
604
- ---
605
-
606
- Built with ❤️ using [Dynamics UI Kit](https://github.com/your-org/dynamics-ui-kit) | Optimized for
607
- Microsoft Dynamics 365