@khester/create-dynamics-app 1.1.0 → 2.1.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 (210) hide show
  1. package/README.md +74 -0
  2. package/dist/artifacts/registry.d.ts +18 -0
  3. package/dist/artifacts/registry.d.ts.map +1 -0
  4. package/dist/artifacts/registry.js +340 -0
  5. package/dist/artifacts/registry.js.map +1 -0
  6. package/dist/artifacts/types.d.ts +122 -0
  7. package/dist/artifacts/types.d.ts.map +1 -0
  8. package/dist/artifacts/types.js +7 -0
  9. package/dist/artifacts/types.js.map +1 -0
  10. package/dist/artifacts/validators.d.ts +16 -0
  11. package/dist/artifacts/validators.d.ts.map +1 -0
  12. package/dist/artifacts/validators.js +45 -0
  13. package/dist/artifacts/validators.js.map +1 -0
  14. package/dist/fromDesign.d.ts +5 -0
  15. package/dist/fromDesign.d.ts.map +1 -0
  16. package/dist/fromDesign.js +98 -0
  17. package/dist/fromDesign.js.map +1 -0
  18. package/dist/index.js +129 -177
  19. package/dist/index.js.map +1 -1
  20. package/dist/injectDevTools.d.ts +28 -0
  21. package/dist/injectDevTools.d.ts.map +1 -0
  22. package/dist/injectDevTools.js +148 -0
  23. package/dist/injectDevTools.js.map +1 -0
  24. package/dist/scaffold.d.ts +48 -0
  25. package/dist/scaffold.d.ts.map +1 -0
  26. package/dist/scaffold.js +180 -0
  27. package/dist/scaffold.js.map +1 -0
  28. package/dist/templatePlan.d.ts +3 -0
  29. package/dist/templatePlan.d.ts.map +1 -0
  30. package/dist/templatePlan.js +43 -0
  31. package/dist/templatePlan.js.map +1 -0
  32. package/dist/utils/copyTemplate.d.ts +13 -1
  33. package/dist/utils/copyTemplate.d.ts.map +1 -1
  34. package/dist/utils/copyTemplate.js +98 -4
  35. package/dist/utils/copyTemplate.js.map +1 -1
  36. package/dist/utils/updatePackageJson.d.ts +11 -1
  37. package/dist/utils/updatePackageJson.d.ts.map +1 -1
  38. package/dist/utils/updatePackageJson.js +12 -10
  39. package/dist/utils/updatePackageJson.js.map +1 -1
  40. package/package.json +10 -7
  41. package/templates/_shared/dev-tools/auth/get-token.js +72 -0
  42. package/templates/_shared/dev-tools/dev/mock-xrm.js +42 -0
  43. package/templates/_shared/dev-tools/metadata-sync/index.js +152 -0
  44. package/templates/_shared/dev-tools/smoke/test-retrieve.js +44 -0
  45. package/templates/dialog-form/README.md +27 -0
  46. package/templates/dialog-form/_variants/App.v8.tsx +39 -0
  47. package/templates/dialog-form/_variants/App.v9.tsx +41 -0
  48. package/templates/dialog-form/gitignore +5 -0
  49. package/templates/dialog-form/package.json +27 -0
  50. package/templates/dialog-form/public/index.html +11 -0
  51. package/templates/dialog-form/src/index.tsx +10 -0
  52. package/templates/dialog-form/src/services/dataverse.ts +30 -0
  53. package/templates/dialog-form/tsconfig.json +15 -0
  54. package/templates/dialog-form/webpack.config.js +17 -0
  55. package/templates/grid-customizer/README.md +28 -0
  56. package/templates/grid-customizer/gitignore +4 -0
  57. package/templates/grid-customizer/package.json +25 -0
  58. package/templates/grid-customizer/src/GridCustomizer.ts +28 -0
  59. package/templates/grid-customizer/src/cell-renderers.tsx +35 -0
  60. package/templates/grid-customizer/src/index.ts +4 -0
  61. package/templates/grid-customizer/src/types/grid-types.ts +30 -0
  62. package/templates/grid-customizer/src/utils/color-utils.ts +24 -0
  63. package/templates/grid-customizer/tsconfig.json +15 -0
  64. package/templates/grid-customizer/webpack.config.js +17 -0
  65. package/templates/pcf-dataset/ControlManifest.Input.xml +16 -0
  66. package/templates/pcf-dataset/README.md +21 -0
  67. package/templates/pcf-dataset/gitignore +5 -0
  68. package/templates/pcf-dataset/index.ts +39 -0
  69. package/templates/pcf-dataset/package.json +30 -0
  70. package/templates/pcf-dataset/strings/{{componentName}}.1033.resx +47 -0
  71. package/templates/pcf-dataset/tsconfig.json +8 -0
  72. package/templates/pcf-dataset/{{componentName}}Component.tsx +39 -0
  73. package/templates/pcf-field/ControlManifest.Input.xml +17 -0
  74. package/templates/pcf-field/README.md +95 -0
  75. package/templates/pcf-field/_variants/ValueInput.boolean.tsx +24 -0
  76. package/templates/pcf-field/_variants/ValueInput.date.tsx +27 -0
  77. package/templates/pcf-field/_variants/ValueInput.number.tsx +35 -0
  78. package/templates/pcf-field/_variants/ValueInput.text.tsx +27 -0
  79. package/templates/pcf-field/gitignore +5 -0
  80. package/templates/pcf-field/index.ts +61 -0
  81. package/templates/pcf-field/package.json +30 -0
  82. package/templates/pcf-field/strings/{{componentName}}.1033.resx +47 -0
  83. package/templates/pcf-field/tsconfig.json +8 -0
  84. package/templates/pcf-field/{{componentName}}Component.tsx +35 -0
  85. package/templates/power-pages-starter/gitignore +5 -0
  86. package/templates/react-custom-page/gitignore +5 -0
  87. package/templates/{dynamics-365-starter → react-custom-page}/package.json +3 -3
  88. package/templates/react-custom-page/tools/metadata-sync/index.js +152 -0
  89. package/templates/static-web-app/README.md +36 -0
  90. package/templates/static-web-app/_variants/App.v8.tsx +32 -0
  91. package/templates/static-web-app/_variants/App.v9.tsx +31 -0
  92. package/templates/static-web-app/api/host.json +12 -0
  93. package/templates/static-web-app/api/package.json +19 -0
  94. package/templates/static-web-app/api/src/functions/hello.ts +16 -0
  95. package/templates/static-web-app/api/tsconfig.json +14 -0
  96. package/templates/static-web-app/frontend/index.html +12 -0
  97. package/templates/static-web-app/frontend/package.json +23 -0
  98. package/templates/static-web-app/frontend/src/index.tsx +8 -0
  99. package/templates/static-web-app/frontend/tsconfig.json +16 -0
  100. package/templates/static-web-app/frontend/vite.config.ts +13 -0
  101. package/templates/static-web-app/gitignore +8 -0
  102. package/templates/static-web-app/package.json +15 -0
  103. package/templates/static-web-app/staticwebapp.config.json +7 -0
  104. package/templates/teams-app/README.md +27 -0
  105. package/templates/teams-app/_variants/graph.off.ts +7 -0
  106. package/templates/teams-app/_variants/graph.on.ts +22 -0
  107. package/templates/teams-app/appPackage/manifest.json +26 -0
  108. package/templates/teams-app/gitignore +5 -0
  109. package/templates/teams-app/index.html +12 -0
  110. package/templates/teams-app/package.json +26 -0
  111. package/templates/teams-app/src/App.tsx +25 -0
  112. package/templates/teams-app/src/index.tsx +8 -0
  113. package/templates/teams-app/tsconfig.json +16 -0
  114. package/templates/teams-app/vite.config.ts +9 -0
  115. package/templates/web-resource/README.md +39 -0
  116. package/templates/web-resource/_variants/App.v8.tsx +29 -0
  117. package/templates/web-resource/_variants/App.v9.tsx +28 -0
  118. package/templates/web-resource/gitignore +5 -0
  119. package/templates/web-resource/package.json +27 -0
  120. package/templates/web-resource/public/index.html +11 -0
  121. package/templates/web-resource/src/index.tsx +10 -0
  122. package/templates/web-resource/src/services/dataverse.ts +30 -0
  123. package/templates/web-resource/tsconfig.json +15 -0
  124. package/templates/web-resource/webpack.config.js +17 -0
  125. package/dist/utils/consultingHelpers.d.ts +0 -13
  126. package/dist/utils/consultingHelpers.d.ts.map +0 -1
  127. package/dist/utils/consultingHelpers.js +0 -569
  128. package/dist/utils/consultingHelpers.js.map +0 -1
  129. package/templates/dynamics-365-starter/INTEGRATION_TEST_RESULTS.md +0 -302
  130. package/templates/dynamics-365-starter/PHASE_4_COMPLETION_SUMMARY.md +0 -305
  131. package/templates/dynamics-365-starter/deployment/QUICKSTART-MAC.md +0 -507
  132. package/templates/dynamics-365-starter/deployment/QUICKSTART-WINDOWS.md +0 -372
  133. package/templates/dynamics-365-starter/deployment/pipelines/README.md +0 -375
  134. package/templates/dynamics-365-starter/deployment/pipelines/azure-pipelines.yml +0 -330
  135. package/templates/dynamics-365-starter/deployment/pipelines/github-actions.yml +0 -422
  136. package/templates/dynamics-365-starter/deployment/pipelines/jenkins.groovy +0 -636
  137. package/templates/dynamics-365-starter/deployment/scripts/deploy.ps1 +0 -417
  138. package/templates/dynamics-365-starter/deployment/scripts/deploy.sh +0 -582
  139. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.ps1 +0 -486
  140. package/templates/dynamics-365-starter/deployment/scripts/team-onboarding.sh +0 -567
  141. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.ps1 +0 -703
  142. package/templates/dynamics-365-starter/deployment/scripts/validate-setup.sh +0 -671
  143. package/templates/dynamics-365-starter/docs/team-standards/README.md +0 -273
  144. package/templates/dynamics-365-starter/docs/team-standards/client-onboarding.md +0 -577
  145. package/templates/dynamics-365-starter/docs/team-standards/code-review-checklist.md +0 -359
  146. package/templates/dynamics-365-starter/docs/team-standards/coding-standards.md +0 -700
  147. package/templates/dynamics-365-starter/docs/team-standards/cross-platform-team-guide.md +0 -736
  148. package/templates/dynamics-365-starter/docs/team-standards/development-workflows.md +0 -727
  149. package/templates/dynamics-365-starter/docs/troubleshooting/common-errors.md +0 -758
  150. package/templates/dynamics-365-starter/docs/troubleshooting/platform-specific-issues.md +0 -878
  151. package/templates/dynamics-365-starter/src/client-project-template/README.md +0 -234
  152. package/templates/dynamics-365-starter/src/client-project-template/config/client.template.json +0 -114
  153. package/templates/dynamics-365-starter/src/client-project-template/config/environments/template.json +0 -186
  154. package/templates/dynamics-365-starter/src/client-project-template/scripts/client-setup.js +0 -667
  155. package/templates/dynamics-365-starter/src/examples/README.md +0 -52
  156. package/templates/dynamics-365-starter/src/examples/component-examples/opportunity-management.tsx +0 -625
  157. package/templates/dynamics-365-starter/src/examples/entity-examples/opportunity-model.ts +0 -545
  158. package/templates/dynamics-365-starter/src/examples/integration-examples/custom-pcf-wrapper.tsx +0 -722
  159. package/templates/dynamics-365-starter/src/examples/workflow-examples/sales-workflow.ts +0 -662
  160. package/templates/dynamics-365-starter/src/page-templates/EntityDashboard.tsx +0 -519
  161. package/templates/dynamics-365-starter/src/page-templates/EntityDetailPage.tsx +0 -456
  162. package/templates/dynamics-365-starter/src/page-templates/EntityListPage.tsx +0 -406
  163. package/templates/dynamics-365-starter/src/page-templates/RelatedEntitiesPage.tsx +0 -578
  164. package/templates/dynamics-365-starter/src/page-templates/SearchPage.tsx +0 -629
  165. package/templates/dynamics-365-starter/tools/entity-generator/index.js +0 -168
  166. package/templates/dynamics-365-starter/tools/entity-generator/templates/constants.template.ts +0 -124
  167. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.css +0 -283
  168. package/templates/dynamics-365-starter/tools/entity-generator/templates/form.template.tsx +0 -275
  169. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.css +0 -204
  170. package/templates/dynamics-365-starter/tools/entity-generator/templates/management.template.tsx +0 -413
  171. package/templates/dynamics-365-starter/tools/entity-generator/templates/model.template.ts +0 -250
  172. package/templates/dynamics-365-starter/tools/metadata-sync/d365-client.js +0 -410
  173. package/templates/dynamics-365-starter/tools/metadata-sync/index.js +0 -512
  174. package/templates/dynamics-365-starter/tools/metadata-sync/type-generator.js +0 -675
  175. /package/templates/{dynamics-365-starter → react-custom-page}/README.md +0 -0
  176. /package/templates/{dynamics-365-starter → react-custom-page}/deployment/README.md +0 -0
  177. /package/templates/{dynamics-365-starter → react-custom-page}/docs/ARCHITECTURE_OVERVIEW.md +0 -0
  178. /package/templates/{dynamics-365-starter → react-custom-page}/docs/BEST_PRACTICES.md +0 -0
  179. /package/templates/{dynamics-365-starter → react-custom-page}/docs/MIGRATION_GUIDE.md +0 -0
  180. /package/templates/{dynamics-365-starter → react-custom-page}/public/index.html +0 -0
  181. /package/templates/{dynamics-365-starter → react-custom-page}/scripts/custom-build.js +0 -0
  182. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.css +0 -0
  183. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountForm.tsx +0 -0
  184. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.css +0 -0
  185. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/AccountManagement.tsx +0 -0
  186. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.css +0 -0
  187. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactForm.tsx +0 -0
  188. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.css +0 -0
  189. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/ContactManagement.tsx +0 -0
  190. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LogDialog.tsx +0 -0
  191. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingContext.tsx +0 -0
  192. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.css +0 -0
  193. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingDebugPanel.tsx +0 -0
  194. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/LoggingProvider.tsx +0 -0
  195. /package/templates/{dynamics-365-starter → react-custom-page}/src/components/Logging/logger.ts +0 -0
  196. /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/account.ts +0 -0
  197. /package/templates/{dynamics-365-starter → react-custom-page}/src/constants/contact.ts +0 -0
  198. /package/templates/{dynamics-365-starter → react-custom-page}/src/index.tsx +0 -0
  199. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Account.ts +0 -0
  200. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/BaseEntity.ts +0 -0
  201. /package/templates/{dynamics-365-starter → react-custom-page}/src/models/Contact.ts +0 -0
  202. /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/ContactControlWrapper.tsx +0 -0
  203. /package/templates/{dynamics-365-starter → react-custom-page}/src/pcf/MultiEntityControlWrapper.tsx +0 -0
  204. /package/templates/{dynamics-365-starter → react-custom-page}/src/providers/DynamicsProvider.tsx +0 -0
  205. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/MockApiService.ts +0 -0
  206. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/ServiceFactory.ts +0 -0
  207. /package/templates/{dynamics-365-starter → react-custom-page}/src/services/XrmApiService.ts +0 -0
  208. /package/templates/{dynamics-365-starter → react-custom-page}/src/styles/index.css +0 -0
  209. /package/templates/{dynamics-365-starter → react-custom-page}/tsconfig.json +0 -0
  210. /package/templates/{dynamics-365-starter → react-custom-page}/webpack.config.js +0 -0
@@ -1,410 +0,0 @@
1
- /**
2
- * Dynamics 365 Web API Client for Metadata Operations
3
- * Handles authentication and API calls to retrieve entity metadata
4
- */
5
-
6
- const https = require('https');
7
- const { AuthenticationProvider } = require('@azure/msal-node');
8
-
9
- class D365MetadataClient {
10
- constructor(config) {
11
- this.config = config;
12
- this.accessToken = null;
13
- this.tokenExpiry = null;
14
- }
15
-
16
- /**
17
- * Authenticate with Dynamics 365 using service principal
18
- */
19
- async authenticate() {
20
- try {
21
- const authUrl = `${this.config.authentication.authority}/oauth2/v2.0/token`;
22
- const authData = new URLSearchParams({
23
- client_id: this.config.authentication.clientId,
24
- client_secret: this.config.authentication.clientSecret,
25
- scope: `${this.config.dynamics365.webApiUrl}/.default`,
26
- grant_type: 'client_credentials',
27
- });
28
-
29
- const response = await this.makeHttpRequest(authUrl, {
30
- method: 'POST',
31
- headers: {
32
- 'Content-Type': 'application/x-www-form-urlencoded',
33
- },
34
- body: authData.toString(),
35
- });
36
-
37
- this.accessToken = response.access_token;
38
- this.tokenExpiry = Date.now() + response.expires_in * 1000;
39
-
40
- return true;
41
- } catch (error) {
42
- throw new Error(`Authentication failed: ${error.message}`);
43
- }
44
- }
45
-
46
- /**
47
- * Ensure we have a valid access token
48
- */
49
- async ensureAuthenticated() {
50
- if (!this.accessToken || Date.now() >= this.tokenExpiry) {
51
- await this.authenticate();
52
- }
53
- }
54
-
55
- /**
56
- * Get all entity definitions
57
- */
58
- async getAllEntities(includeSystemEntities = false) {
59
- await this.ensureAuthenticated();
60
-
61
- const filter = includeSystemEntities
62
- ? ''
63
- : '?$filter=IsCustomEntity eq true';
64
- const url = `${this.config.dynamics365.webApiUrl}/EntityDefinitions${filter}&$select=LogicalName,SchemaName,DisplayName,IsCustomEntity`;
65
-
66
- try {
67
- const response = await this.makeApiRequest(url);
68
- return response.value || [];
69
- } catch (error) {
70
- throw new Error(`Failed to retrieve entities: ${error.message}`);
71
- }
72
- }
73
-
74
- /**
75
- * Get detailed metadata for a specific entity
76
- */
77
- async getEntityMetadata(entityLogicalName) {
78
- await this.ensureAuthenticated();
79
-
80
- const url =
81
- `${this.config.dynamics365.webApiUrl}/EntityDefinitions(LogicalName='${entityLogicalName}')` +
82
- '?$expand=Attributes($select=LogicalName,SchemaName,DisplayName,AttributeType,RequiredLevel,MaxLength,IsPrimaryId,IsValidForCreate,IsValidForUpdate),' +
83
- 'OneToManyRelationships($select=SchemaName,ReferencingEntity,ReferencingAttribute,ReferencedEntity,ReferencedAttribute),' +
84
- 'ManyToOneRelationships($select=SchemaName,ReferencingEntity,ReferencingAttribute,ReferencedEntity,ReferencedAttribute)';
85
-
86
- try {
87
- const response = await this.makeApiRequest(url);
88
- return this.transformEntityMetadata(response);
89
- } catch (error) {
90
- throw new Error(
91
- `Failed to retrieve metadata for ${entityLogicalName}: ${error.message}`
92
- );
93
- }
94
- }
95
-
96
- /**
97
- * Get option set metadata
98
- */
99
- async getOptionSetMetadata(optionSetName) {
100
- await this.ensureAuthenticated();
101
-
102
- const url =
103
- `${this.config.dynamics365.webApiUrl}/GlobalOptionSetDefinitions(Name='${optionSetName}')` +
104
- '?$expand=Options($select=Value,Label)';
105
-
106
- try {
107
- const response = await this.makeApiRequest(url);
108
- return this.transformOptionSetMetadata(response);
109
- } catch (error) {
110
- throw new Error(
111
- `Failed to retrieve option set ${optionSetName}: ${error.message}`
112
- );
113
- }
114
- }
115
-
116
- /**
117
- * Get all custom entities with their basic metadata
118
- */
119
- async getCustomEntities() {
120
- await this.ensureAuthenticated();
121
-
122
- const url =
123
- `${this.config.dynamics365.webApiUrl}/EntityDefinitions` +
124
- '?$filter=IsCustomEntity eq true&$select=LogicalName,SchemaName,DisplayName,PrimaryIdAttribute,PrimaryNameAttribute,EntitySetName';
125
-
126
- try {
127
- const response = await this.makeApiRequest(url);
128
- return response.value || [];
129
- } catch (error) {
130
- throw new Error(`Failed to retrieve custom entities: ${error.message}`);
131
- }
132
- }
133
-
134
- /**
135
- * Validate entity exists and get basic info
136
- */
137
- async validateEntity(entityLogicalName) {
138
- await this.ensureAuthenticated();
139
-
140
- const url =
141
- `${this.config.dynamics365.webApiUrl}/EntityDefinitions(LogicalName='${entityLogicalName}')` +
142
- '?$select=LogicalName,SchemaName,DisplayName,IsCustomEntity,PrimaryIdAttribute,PrimaryNameAttribute';
143
-
144
- try {
145
- const response = await this.makeApiRequest(url);
146
- return {
147
- exists: true,
148
- metadata: response,
149
- };
150
- } catch (error) {
151
- if (error.status === 404) {
152
- return {
153
- exists: false,
154
- error: `Entity '${entityLogicalName}' not found`,
155
- };
156
- }
157
- throw error;
158
- }
159
- }
160
-
161
- /**
162
- * Transform D365 metadata to our standardized format
163
- */
164
- transformEntityMetadata(d365Metadata) {
165
- return {
166
- LogicalName: d365Metadata.LogicalName,
167
- SchemaName: d365Metadata.SchemaName,
168
- DisplayName: this.getLocalizedLabel(d365Metadata.DisplayName),
169
- PrimaryIdAttribute: d365Metadata.PrimaryIdAttribute,
170
- PrimaryNameAttribute: d365Metadata.PrimaryNameAttribute,
171
- CollectionSchemaName: d365Metadata.CollectionSchemaName,
172
- EntitySetName: d365Metadata.EntitySetName,
173
- IsCustomEntity: d365Metadata.IsCustomEntity,
174
- Attributes: this.transformAttributes(d365Metadata.Attributes || []),
175
- OneToManyRelationships: this.transformRelationships(
176
- d365Metadata.OneToManyRelationships || []
177
- ),
178
- ManyToOneRelationships: this.transformRelationships(
179
- d365Metadata.ManyToOneRelationships || []
180
- ),
181
- LastModified: new Date().toISOString(),
182
- Metadata: {
183
- SyncedAt: new Date().toISOString(),
184
- SyncedFrom: this.config.dynamics365.webApiUrl,
185
- Version: '1.0',
186
- },
187
- };
188
- }
189
-
190
- /**
191
- * Transform D365 attributes to our format
192
- */
193
- transformAttributes(d365Attributes) {
194
- return d365Attributes.map((attr) => ({
195
- LogicalName: attr.LogicalName,
196
- SchemaName: attr.SchemaName,
197
- DisplayName: this.getLocalizedLabel(attr.DisplayName),
198
- AttributeType: this.mapAttributeType(attr.AttributeType),
199
- RequiredLevel: this.mapRequiredLevel(attr.RequiredLevel),
200
- MaxLength: attr.MaxLength,
201
- IsPrimaryId: attr.IsPrimaryId,
202
- IsValidForCreate: attr.IsValidForCreate,
203
- IsValidForUpdate: attr.IsValidForUpdate,
204
- IsLogical: attr.IsLogical,
205
- OptionSet: attr.OptionSet?.Name,
206
- Target: attr.Targets ? attr.Targets[0] : undefined, // For lookup fields
207
- Precision: attr.Precision,
208
- MinValue: attr.MinValue,
209
- MaxValue: attr.MaxValue,
210
- }));
211
- }
212
-
213
- /**
214
- * Transform D365 relationships to our format
215
- */
216
- transformRelationships(d365Relationships) {
217
- return d365Relationships.map((rel) => ({
218
- SchemaName: rel.SchemaName,
219
- ReferencingEntity: rel.ReferencingEntity,
220
- ReferencingAttribute: rel.ReferencingAttribute,
221
- ReferencedEntity: rel.ReferencedEntity,
222
- ReferencedAttribute: rel.ReferencedAttribute,
223
- RelationshipType: rel.RelationshipType,
224
- }));
225
- }
226
-
227
- /**
228
- * Transform option set metadata
229
- */
230
- transformOptionSetMetadata(d365OptionSet) {
231
- return {
232
- Name: d365OptionSet.Name,
233
- DisplayName: this.getLocalizedLabel(d365OptionSet.DisplayName),
234
- Description: this.getLocalizedLabel(d365OptionSet.Description),
235
- Options: (d365OptionSet.Options || []).map((option) => ({
236
- Value: option.Value,
237
- Label: this.getLocalizedLabel(option.Label),
238
- Description: this.getLocalizedLabel(option.Description),
239
- })),
240
- };
241
- }
242
-
243
- /**
244
- * Map D365 attribute types to our standardized types
245
- */
246
- mapAttributeType(d365Type) {
247
- const typeMap = {
248
- String: 'String',
249
- Memo: 'Memo',
250
- Integer: 'Integer',
251
- BigInt: 'BigInt',
252
- Double: 'Double',
253
- Decimal: 'Decimal',
254
- Money: 'Money',
255
- Boolean: 'Boolean',
256
- DateTime: 'DateTime',
257
- Uniqueidentifier: 'Uniqueidentifier',
258
- Picklist: 'Picklist',
259
- State: 'State',
260
- Status: 'Status',
261
- Lookup: 'Lookup',
262
- Customer: 'Customer',
263
- Owner: 'Owner',
264
- MultiSelectPicklist: 'MultiSelectPicklist',
265
- Virtual: 'Virtual',
266
- EntityName: 'EntityName',
267
- ManagedProperty: 'ManagedProperty',
268
- };
269
-
270
- return typeMap[d365Type] || d365Type;
271
- }
272
-
273
- /**
274
- * Map D365 required levels to our format
275
- */
276
- mapRequiredLevel(d365RequiredLevel) {
277
- const levelMap = {
278
- ApplicationRequired: 'ApplicationRequired',
279
- SystemRequired: 'SystemRequired',
280
- Recommended: 'Recommended',
281
- None: 'None',
282
- };
283
-
284
- return levelMap[d365RequiredLevel] || d365RequiredLevel;
285
- }
286
-
287
- /**
288
- * Extract localized label (defaults to English)
289
- */
290
- getLocalizedLabel(labelCollection) {
291
- if (!labelCollection || !labelCollection.LocalizedLabels) {
292
- return '';
293
- }
294
-
295
- // Try to find English label first
296
- const englishLabel = labelCollection.LocalizedLabels.find(
297
- (label) => label.LanguageCode === 1033
298
- );
299
-
300
- if (englishLabel) {
301
- return englishLabel.Label;
302
- }
303
-
304
- // Fall back to first available label
305
- if (labelCollection.LocalizedLabels.length > 0) {
306
- return labelCollection.LocalizedLabels[0].Label;
307
- }
308
-
309
- return '';
310
- }
311
-
312
- /**
313
- * Make authenticated API request to D365
314
- */
315
- async makeApiRequest(url, options = {}) {
316
- const headers = {
317
- Authorization: `Bearer ${this.accessToken}`,
318
- Accept: 'application/json',
319
- 'Content-Type': 'application/json',
320
- 'OData-MaxVersion': '4.0',
321
- 'OData-Version': '4.0',
322
- ...options.headers,
323
- };
324
-
325
- return this.makeHttpRequest(url, {
326
- ...options,
327
- headers,
328
- });
329
- }
330
-
331
- /**
332
- * Make HTTP request with proper error handling
333
- */
334
- async makeHttpRequest(url, options = {}) {
335
- return new Promise((resolve, reject) => {
336
- const urlObj = new URL(url);
337
- const requestOptions = {
338
- hostname: urlObj.hostname,
339
- port: urlObj.port || 443,
340
- path: urlObj.pathname + urlObj.search,
341
- method: options.method || 'GET',
342
- headers: options.headers || {},
343
- };
344
-
345
- const req = https.request(requestOptions, (res) => {
346
- let data = '';
347
-
348
- res.on('data', (chunk) => {
349
- data += chunk;
350
- });
351
-
352
- res.on('end', () => {
353
- try {
354
- if (res.statusCode >= 200 && res.statusCode < 300) {
355
- const jsonData = data ? JSON.parse(data) : {};
356
- resolve(jsonData);
357
- } else {
358
- const error = new Error(
359
- `HTTP ${res.statusCode}: ${res.statusMessage}`
360
- );
361
- error.status = res.statusCode;
362
- error.response = data;
363
- reject(error);
364
- }
365
- } catch (parseError) {
366
- reject(
367
- new Error(`Failed to parse response: ${parseError.message}`)
368
- );
369
- }
370
- });
371
- });
372
-
373
- req.on('error', (error) => {
374
- reject(new Error(`Request failed: ${error.message}`));
375
- });
376
-
377
- if (options.body) {
378
- req.write(options.body);
379
- }
380
-
381
- req.end();
382
- });
383
- }
384
-
385
- /**
386
- * Test connection to D365
387
- */
388
- async testConnection() {
389
- try {
390
- await this.ensureAuthenticated();
391
-
392
- // Make a simple request to validate connection
393
- const url = `${this.config.dynamics365.webApiUrl}/WhoAmI()`;
394
- const response = await this.makeApiRequest(url);
395
-
396
- return {
397
- success: true,
398
- userId: response.UserId,
399
- organizationId: response.OrganizationId,
400
- };
401
- } catch (error) {
402
- return {
403
- success: false,
404
- error: error.message,
405
- };
406
- }
407
- }
408
- }
409
-
410
- module.exports = { D365MetadataClient };