@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
@@ -2,23 +2,25 @@ import fs from 'fs-extra';
2
2
  import path from 'path';
3
3
  import chalk from 'chalk';
4
4
  import ora from 'ora';
5
- export async function updatePackageJson(projectPath, projectName, clientName) {
5
+ export async function updatePackageJson(projectPath, projectName, options = {}) {
6
6
  const spinner = ora('Updating package.json...').start();
7
+ const { provenance } = options;
7
8
  try {
8
9
  const packageJsonPath = path.join(projectPath, 'package.json');
9
10
  if (await fs.pathExists(packageJsonPath)) {
10
11
  const packageJson = await fs.readJson(packageJsonPath);
11
12
  // Update the name
12
13
  packageJson.name = projectName;
13
- // Add client information if provided
14
- if (clientName) {
15
- packageJson.description = `${packageJson.description || 'Dynamics 365 application'} - Built for ${clientName}`;
16
- packageJson.client = clientName;
17
- packageJson.keywords = packageJson.keywords || [];
18
- if (!packageJson.keywords.includes(clientName.toLowerCase())) {
19
- packageJson.keywords.push(clientName.toLowerCase());
20
- }
21
- packageJson.keywords.push('consulting', 'enterprise');
14
+ // Stamp provenance so we can detect the generating CLI/template later.
15
+ if (provenance) {
16
+ packageJson.dynamicsKit = {
17
+ cliVersion: provenance.cliVersion,
18
+ artifact: provenance.artifact,
19
+ ...(provenance.componentLibrary
20
+ ? { componentLibrary: provenance.componentLibrary }
21
+ : {}),
22
+ templateVersion: provenance.templateVersion,
23
+ };
22
24
  }
23
25
  // Remove any template-specific fields that shouldn't be in final package
24
26
  delete packageJson.template;
@@ -1 +1 @@
1
- {"version":3,"file":"updatePackageJson.js","sourceRoot":"","sources":["../../src/utils/updatePackageJson.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,WAAmB,EACnB,WAAmB,EACnB,UAAmB;IAEnB,MAAM,OAAO,GAAG,GAAG,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;IAExD,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QAE/D,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YACzC,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;YAEvD,kBAAkB;YAClB,WAAW,CAAC,IAAI,GAAG,WAAW,CAAC;YAE/B,qCAAqC;YACrC,IAAI,UAAU,EAAE,CAAC;gBACf,WAAW,CAAC,WAAW,GAAG,GAAG,WAAW,CAAC,WAAW,IAAI,0BAA0B,gBAAgB,UAAU,EAAE,CAAC;gBAC/G,WAAW,CAAC,MAAM,GAAG,UAAU,CAAC;gBAChC,WAAW,CAAC,QAAQ,GAAG,WAAW,CAAC,QAAQ,IAAI,EAAE,CAAC;gBAClD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;oBAC7D,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,CAAC;gBACtD,CAAC;gBACD,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;YACxD,CAAC;YAED,yEAAyE;YACzE,OAAO,WAAW,CAAC,QAAQ,CAAC;YAE5B,MAAM,EAAE,CAAC,SAAS,CAAC,eAAe,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;QACzD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"updatePackageJson.js","sourceRoot":"","sources":["../../src/utils/updatePackageJson.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AActB,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,WAAmB,EACnB,WAAmB,EACnB,UAAoC,EAAE;IAEtC,MAAM,OAAO,GAAG,GAAG,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;IACxD,MAAM,EAAE,UAAU,EAAE,GAAG,OAAO,CAAC;IAE/B,IAAI,CAAC;QACH,MAAM,eAAe,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,CAAC;QAE/D,IAAI,MAAM,EAAE,CAAC,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;YACzC,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC;YAEvD,kBAAkB;YAClB,WAAW,CAAC,IAAI,GAAG,WAAW,CAAC;YAE/B,uEAAuE;YACvE,IAAI,UAAU,EAAE,CAAC;gBACf,WAAW,CAAC,WAAW,GAAG;oBACxB,UAAU,EAAE,UAAU,CAAC,UAAU;oBACjC,QAAQ,EAAE,UAAU,CAAC,QAAQ;oBAC7B,GAAG,CAAC,UAAU,CAAC,gBAAgB;wBAC7B,CAAC,CAAC,EAAE,gBAAgB,EAAE,UAAU,CAAC,gBAAgB,EAAE;wBACnD,CAAC,CAAC,EAAE,CAAC;oBACP,eAAe,EAAE,UAAU,CAAC,eAAe;iBAC5C,CAAC;YACJ,CAAC;YAED,yEAAyE;YACzE,OAAO,WAAW,CAAC,QAAQ,CAAC;YAE5B,MAAM,EAAE,CAAC,SAAS,CAAC,eAAe,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;QACzD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@khester/create-dynamics-app",
3
- "version": "1.1.0",
4
- "description": "CLI tool to scaffold new Dynamics UI Kit projects",
3
+ "version": "2.1.0",
4
+ "description": "Unified CLI to scaffold Dynamics 365 / Power Platform artifacts",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "bin": {
8
- "create-dynamics-app": "./bin/create-dynamics-app.js"
8
+ "create-dynamics-app": "bin/create-dynamics-app.js"
9
9
  },
10
10
  "files": [
11
11
  "dist",
@@ -13,15 +13,18 @@
13
13
  "templates"
14
14
  ],
15
15
  "scripts": {
16
+ "prebuild": "rimraf dist tsconfig.tsbuildinfo",
16
17
  "build": "tsc",
17
18
  "dev": "tsc --watch",
18
19
  "prepublishOnly": "npm run build",
19
- "test": "jest --passWithNoTests",
20
+ "test": "vitest run",
21
+ "test:watch": "vitest",
20
22
  "typecheck": "tsc --noEmit",
21
23
  "lint": "eslint src --ext .ts,.tsx",
22
- "clean": "rimraf dist"
24
+ "clean": "rimraf dist tsconfig.tsbuildinfo"
23
25
  },
24
26
  "dependencies": {
27
+ "@dataverse-kit/export-engine": "^1.0.0",
25
28
  "@khester/dynamics-ui-components": "^1.0.0",
26
29
  "chalk": "^5.3.0",
27
30
  "commander": "^11.1.0",
@@ -44,9 +47,9 @@
44
47
  "create-app"
45
48
  ],
46
49
  "engines": {
47
- "node": ">=18.0.0"
50
+ "node": ">=20.0.0"
48
51
  },
49
52
  "publishConfig": {
50
- "access": "restricted"
53
+ "access": "public"
51
54
  }
52
55
  }
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Acquire a Dataverse bearer token via the Azure CLI and write it to `.env`
5
+ * (DYNAMICS_URL + DYNAMICS_TOKEN — the exact names @dataverse-kit/dataverse-codegen's
6
+ * resolveConnection reads). Tokens are short-lived; re-run before a codegen/smoke run.
7
+ *
8
+ * Usage:
9
+ * node tools/auth/get-token.js --url https://org.crm.dynamics.com
10
+ * (or set DYNAMICS_URL in the environment / .env)
11
+ */
12
+ const { spawnSync } = require('child_process');
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ function arg(name) {
17
+ const i = process.argv.indexOf(name);
18
+ return i !== -1 ? process.argv[i + 1] : undefined;
19
+ }
20
+
21
+ function readEnv(file) {
22
+ if (!fs.existsSync(file)) return {};
23
+ const out = {};
24
+ for (const line of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
25
+ const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
26
+ if (m) out[m[1]] = m[2];
27
+ }
28
+ return out;
29
+ }
30
+
31
+ function writeEnv(file, env) {
32
+ const body = Object.entries(env)
33
+ .map(([k, v]) => `${k}=${v}`)
34
+ .join('\n');
35
+ fs.writeFileSync(file, body + '\n', 'utf8');
36
+ }
37
+
38
+ const envPath = path.join(process.cwd(), '.env');
39
+ const env = readEnv(envPath);
40
+ const url = arg('--url') || process.env.DYNAMICS_URL || env.DYNAMICS_URL;
41
+
42
+ if (!url) {
43
+ console.error(
44
+ '✗ No Dataverse URL. Pass --url https://org.crm.dynamics.com or set DYNAMICS_URL in .env.'
45
+ );
46
+ process.exit(1);
47
+ }
48
+
49
+ console.log(`→ Acquiring token for ${url} via Azure CLI...`);
50
+ const res = spawnSync(
51
+ 'az',
52
+ ['account', 'get-access-token', '--resource', url, '--query', 'accessToken', '-o', 'tsv'],
53
+ { encoding: 'utf8' }
54
+ );
55
+
56
+ if (res.status !== 0) {
57
+ console.error('✗ az account get-access-token failed. Run `az login` first.');
58
+ if (res.stderr) console.error(res.stderr.trim());
59
+ process.exit(1);
60
+ }
61
+
62
+ const token = (res.stdout || '').trim();
63
+ if (!token) {
64
+ console.error('✗ Azure CLI returned an empty token.');
65
+ process.exit(1);
66
+ }
67
+
68
+ // Merge keys — never clobber an existing .env wholesale.
69
+ env.DYNAMICS_URL = url;
70
+ env.DYNAMICS_TOKEN = token;
71
+ writeEnv(envPath, env);
72
+ console.log('✓ Wrote DYNAMICS_URL + DYNAMICS_TOKEN to .env');
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Minimal mock of the Dynamics `Xrm` global for local development. Lets an
3
+ * artifact that talks to `Xrm.WebApi` run in a plain browser/dev-server without a
4
+ * model-driven host. Pair with a ServiceFactory that flips to a mock service on
5
+ * localhost (see the React Custom Page template's ServiceFactory.isMockEnvironment).
6
+ *
7
+ * Usage (entry HTML or dev bootstrap):
8
+ * import { installMockXrm } from './tools/dev/mock-xrm.js';
9
+ * if (location.hostname === 'localhost') installMockXrm();
10
+ */
11
+ export function installMockXrm(seed = {}) {
12
+ const store = new Map(Object.entries(seed));
13
+ const ok = (data) => Promise.resolve(data);
14
+
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ globalThis.Xrm = {
17
+ WebApi: {
18
+ retrieveRecord: (entity, id) => ok(store.get(`${entity}:${id}`) ?? { [`${entity}id`]: id }),
19
+ retrieveMultipleRecords: () => ok({ entities: [...store.values()] }),
20
+ createRecord: (entity, data) => {
21
+ const id = `mock-${store.size + 1}`;
22
+ store.set(`${entity}:${id}`, { ...data, [`${entity}id`]: id });
23
+ return ok({ id });
24
+ },
25
+ updateRecord: (entity, id, data) => {
26
+ store.set(`${entity}:${id}`, { ...(store.get(`${entity}:${id}`) || {}), ...data });
27
+ return ok(undefined);
28
+ },
29
+ deleteRecord: (entity, id) => {
30
+ store.delete(`${entity}:${id}`);
31
+ return ok(undefined);
32
+ },
33
+ },
34
+ Utility: {
35
+ getGlobalContext: () => ({
36
+ getClientUrl: () => 'http://localhost',
37
+ userSettings: { userId: 'mock-user', userName: 'Mock User' },
38
+ }),
39
+ },
40
+ };
41
+ return globalThis.Xrm;
42
+ }
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Metadata Sync — thin wrapper around @dataverse-kit/dataverse-codegen (dvgen).
5
+ *
6
+ * Pulls REAL entity metadata from a live Dataverse org and generates field
7
+ * constants + typed models (and, optionally, the IApiService CRUD layer) into
8
+ * `src/`. All org connection + auth is delegated to dvgen, which resolves, in
9
+ * order: --url/--token/--env-file flags, the DYNAMICS_* / VITE_DYNAMICS_* /
10
+ * REACT_APP_DYNAMICS_* env vars, then the Azure CLI (`az account get-access-token`).
11
+ *
12
+ * This replaces the previous mock pipeline (which returned hard-coded metadata).
13
+ * dvgen is the same tool the rest of the toolkit uses, so output stays consistent
14
+ * with `dvgen design`/`constants`/`models` everywhere.
15
+ */
16
+
17
+ const { program } = require('commander');
18
+ const { spawnSync } = require('child_process');
19
+ const path = require('path');
20
+ const fs = require('fs');
21
+
22
+ const DVGEN_CLI = path.join(
23
+ process.cwd(),
24
+ 'node_modules',
25
+ '@dataverse-kit',
26
+ 'dataverse-codegen',
27
+ 'dist',
28
+ 'cli.cjs'
29
+ );
30
+
31
+ function ensureDvgen() {
32
+ if (!fs.existsSync(DVGEN_CLI)) {
33
+ console.error(
34
+ '✗ dvgen not found. Install it first:\n' +
35
+ ' npm install -D @dataverse-kit/dataverse-codegen'
36
+ );
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ function dvgen(args) {
42
+ const result = spawnSync(process.execPath, [DVGEN_CLI, ...args], {
43
+ stdio: 'inherit',
44
+ });
45
+ return result.status === 0;
46
+ }
47
+
48
+ function resolveEntities(opts) {
49
+ if (opts.entities) {
50
+ return opts.entities
51
+ .split(',')
52
+ .map((e) => e.trim())
53
+ .filter(Boolean);
54
+ }
55
+ return ['account', 'contact'];
56
+ }
57
+
58
+ function connectionArgs(opts) {
59
+ const args = [];
60
+ if (opts.url) args.push('--url', opts.url);
61
+ if (opts.token) args.push('--token', opts.token);
62
+ if (opts.envFile) args.push('--env-file', opts.envFile);
63
+ return args;
64
+ }
65
+
66
+ function run(opts, { diff = false } = {}) {
67
+ ensureDvgen();
68
+ const entities = resolveEntities(opts);
69
+ const out = opts.out || 'src';
70
+ const conn = connectionArgs(opts);
71
+ const extra = diff ? ['--diff'] : [];
72
+ if (opts.force && !diff) extra.push('--force');
73
+ // The starter's shipped models import IApiService from
74
+ // @khester/dynamics-ui-api-client, so point generated models at the same
75
+ // module. When the dvgen-local CRUD layer is generated (--api), use that.
76
+ const apiServiceImport = opts.api ? '../services/IApiService' : opts.apiService;
77
+
78
+ console.log(
79
+ `${diff ? '🔍 Diffing' : '🔄 Generating'} ${entities.join(
80
+ ', '
81
+ )} via dvgen → ${out}/\n`
82
+ );
83
+
84
+ // Shared CRUD layer once (offline; only when explicitly requested).
85
+ if (opts.api && !diff) {
86
+ if (!dvgen(['api', '--out', out])) process.exit(1);
87
+ }
88
+
89
+ let ok = true;
90
+ for (const entity of entities) {
91
+ console.log(`\n→ ${entity}`);
92
+ if (
93
+ !dvgen(['constants', '--entity', entity, '--out', out, ...conn, ...extra])
94
+ ) {
95
+ ok = false;
96
+ }
97
+ const modelArgs = ['models', '--entity', entity, '--out', out, ...conn, ...extra];
98
+ if (apiServiceImport) modelArgs.push('--api-service', apiServiceImport);
99
+ if (opts.withRetrieve) modelArgs.push('--with-retrieve');
100
+ if (!dvgen(modelArgs)) ok = false;
101
+ }
102
+
103
+ if (!ok) {
104
+ console.error('\n✗ One or more entities failed.');
105
+ process.exit(1);
106
+ }
107
+ console.log('\n✓ Done.');
108
+ }
109
+
110
+ function withCommon(cmd) {
111
+ return cmd
112
+ .option(
113
+ '-e, --entities <list>',
114
+ 'comma-separated entities (default: account,contact)'
115
+ )
116
+ .option('--url <url>', 'Dataverse org URL (else dvgen resolves env / Azure CLI)')
117
+ .option('--token <token>', 'bearer token (else dvgen resolves env / Azure CLI)')
118
+ .option('--env-file <path>', '.env file with DYNAMICS_URL / DYNAMICS_TOKEN')
119
+ .option('-o, --out <dir>', 'output base dir', 'src')
120
+ .option('--force', 'overwrite existing generated files (default: skip)')
121
+ .option('--with-retrieve', 'include retrieveWithRelated() FetchXML scaffolds')
122
+ .option(
123
+ '--api-service <import>',
124
+ 'IApiService module specifier for generated models',
125
+ '@khester/dynamics-ui-api-client'
126
+ )
127
+ .option('--api', 'also (re)generate the IApiService CRUD layer');
128
+ }
129
+
130
+ program
131
+ .name('metadata-sync')
132
+ .description(
133
+ 'Live Dataverse metadata → TypeScript (constants + models) via dvgen'
134
+ );
135
+
136
+ withCommon(program.command('sync'))
137
+ .description('Pull live metadata and generate constants + models')
138
+ .action((o) => run(o));
139
+
140
+ withCommon(program.command('pull'))
141
+ .description('Alias of sync (pull live metadata + generate)')
142
+ .action((o) => run(o));
143
+
144
+ withCommon(program.command('generate'))
145
+ .description('Alias of sync (generate constants + models from the live org)')
146
+ .action((o) => run(o));
147
+
148
+ withCommon(program.command('validate'))
149
+ .description('Diff generated code against the live org (no writes)')
150
+ .action((o) => run(o, { diff: true }));
151
+
152
+ program.parse();
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Live-org smoke test: retrieve a few rows of an entity to confirm the generated
5
+ * data layer + token work end-to-end before deploy. Delegates to dvgen's
6
+ * `test-retrieve --live`, which resolves the connection (flag/env/Azure CLI).
7
+ *
8
+ * Usage: node tools/smoke/test-retrieve.js --entity account
9
+ */
10
+ const { spawnSync } = require('child_process');
11
+ const path = require('path');
12
+ const fs = require('fs');
13
+
14
+ function arg(name, fallback) {
15
+ const i = process.argv.indexOf(name);
16
+ return i !== -1 ? process.argv[i + 1] : fallback;
17
+ }
18
+
19
+ const DVGEN = path.join(
20
+ process.cwd(),
21
+ 'node_modules',
22
+ '@dataverse-kit',
23
+ 'dataverse-codegen',
24
+ 'dist',
25
+ 'cli.cjs'
26
+ );
27
+
28
+ if (!fs.existsSync(DVGEN)) {
29
+ console.error('✗ dvgen not found. Install @dataverse-kit/dataverse-codegen first.');
30
+ process.exit(1);
31
+ }
32
+
33
+ const entity = arg('--entity');
34
+ if (!entity) {
35
+ console.error('✗ Pass --entity <logicalName> (e.g. --entity account).');
36
+ process.exit(1);
37
+ }
38
+
39
+ const res = spawnSync(
40
+ process.execPath,
41
+ [DVGEN, 'test-retrieve', '--entity', entity, '--live'],
42
+ { stdio: 'inherit' }
43
+ );
44
+ process.exit(res.status ?? 1);
@@ -0,0 +1,27 @@
1
+ # {{projectName}}
2
+
3
+ A Dynamics 365 **dialog form** — a single React form (Fluent UI {{componentLibrary}}) you host as a
4
+ web resource and open as a dialog (e.g. via `Xrm.Navigation.navigateTo` / `openWebResource`).
5
+
6
+ ## Develop
7
+
8
+ ```bash
9
+ npm install
10
+ npm run dev # webpack dev server on http://localhost:8080
11
+ npm run build # production bundle → dist/{{projectName}}.js
12
+ npm run typecheck # tsc --noEmit
13
+ ```
14
+
15
+ The form (`src/App.tsx`) saves via `src/services/dataverse.ts` (`Xrm.WebApi`). Outside a host, stub
16
+ Xrm with the `serve` dev-tool's `tools/dev/mock-xrm.js`.
17
+
18
+ ## Dev-tools
19
+
20
+ ```bash
21
+ npm run auth:token -- --url https://YOUR_ORG.crm.dynamics.com # token → .env
22
+ npm run metadata:pull -- --entities contact # typed entities
23
+ ```
24
+
25
+ ## Deploy
26
+
27
+ Upload `dist/{{projectName}}.js` as a web resource and open it as a dialog from a form/ribbon command.
@@ -0,0 +1,39 @@
1
+ import * as React from 'react';
2
+ import { ThemeProvider } from '@fluentui/react/lib/Theme';
3
+ import { Stack } from '@fluentui/react/lib/Stack';
4
+ import { Text } from '@fluentui/react/lib/Text';
5
+ import { TextField } from '@fluentui/react/lib/TextField';
6
+ import { PrimaryButton, DefaultButton } from '@fluentui/react/lib/Button';
7
+ import { createRecord } from './services/dataverse';
8
+
9
+ export const App: React.FC = () => {
10
+ const [name, setName] = React.useState('');
11
+ const [email, setEmail] = React.useState('');
12
+ const [saving, setSaving] = React.useState(false);
13
+
14
+ const onSave = async () => {
15
+ setSaving(true);
16
+ try {
17
+ await createRecord('contact', { fullname: name, emailaddress1: email });
18
+ alert('Saved.');
19
+ } catch (e) {
20
+ alert(`Save failed (Xrm not available here?): ${String(e)}`);
21
+ } finally {
22
+ setSaving(false);
23
+ }
24
+ };
25
+
26
+ return (
27
+ <ThemeProvider>
28
+ <Stack tokens={{ childrenGap: 12, padding: 16 }} styles={{ root: { maxWidth: 480 } }}>
29
+ <Text variant="xLarge">{{projectName}}</Text>
30
+ <TextField label="Full name" value={name} onChange={(_, v) => setName(v ?? '')} required />
31
+ <TextField label="Email" value={email} onChange={(_, v) => setEmail(v ?? '')} />
32
+ <Stack horizontal tokens={{ childrenGap: 8 }}>
33
+ <PrimaryButton text="Save" onClick={onSave} disabled={saving} />
34
+ <DefaultButton text="Cancel" onClick={() => window.history.back()} />
35
+ </Stack>
36
+ </Stack>
37
+ </ThemeProvider>
38
+ );
39
+ };
@@ -0,0 +1,41 @@
1
+ import * as React from 'react';
2
+ import { FluentProvider, webLightTheme, Text, Input, Button, Field } from '@fluentui/react-components';
3
+ import { createRecord } from './services/dataverse';
4
+
5
+ export const App: React.FC = () => {
6
+ const [name, setName] = React.useState('');
7
+ const [email, setEmail] = React.useState('');
8
+ const [saving, setSaving] = React.useState(false);
9
+
10
+ const onSave = async () => {
11
+ setSaving(true);
12
+ try {
13
+ await createRecord('contact', { fullname: name, emailaddress1: email });
14
+ alert('Saved.');
15
+ } catch (e) {
16
+ alert(`Save failed (Xrm not available here?): ${String(e)}`);
17
+ } finally {
18
+ setSaving(false);
19
+ }
20
+ };
21
+
22
+ return (
23
+ <FluentProvider theme={webLightTheme}>
24
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 12, padding: 16, maxWidth: 480 }}>
25
+ <Text size={600} weight="semibold">{{projectName}}</Text>
26
+ <Field label="Full name" required>
27
+ <Input value={name} onChange={(_, d) => setName(d.value)} />
28
+ </Field>
29
+ <Field label="Email">
30
+ <Input value={email} onChange={(_, d) => setEmail(d.value)} />
31
+ </Field>
32
+ <div style={{ display: 'flex', gap: 8 }}>
33
+ <Button appearance="primary" onClick={onSave} disabled={saving}>
34
+ Save
35
+ </Button>
36
+ <Button onClick={() => window.history.back()}>Cancel</Button>
37
+ </div>
38
+ </div>
39
+ </FluentProvider>
40
+ );
41
+ };
@@ -0,0 +1,5 @@
1
+ node_modules/
2
+ dist/
3
+ .env
4
+ .env.local
5
+ *.log
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "{{projectName}}",
3
+ "version": "1.0.0",
4
+ "description": "{{projectName}} — Dynamics 365 dialog form (React, Fluent {{componentLibrary}})",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "webpack serve --mode development",
8
+ "build": "webpack --mode production",
9
+ "typecheck": "tsc --noEmit",
10
+ "lint": "eslint src --ext .ts,.tsx"
11
+ },
12
+ "dependencies": {
13
+ "react": "^18.2.0",
14
+ "react-dom": "^18.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@types/react": "^18.2.0",
18
+ "@types/react-dom": "^18.2.0",
19
+ "@types/xrm": "^9.0.74",
20
+ "html-webpack-plugin": "^5.5.3",
21
+ "ts-loader": "^9.5.1",
22
+ "typescript": "^5.3.3",
23
+ "webpack": "^5.89.0",
24
+ "webpack-cli": "^5.1.4",
25
+ "webpack-dev-server": "^4.15.1"
26
+ }
27
+ }
@@ -0,0 +1,11 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{{projectName}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ </body>
11
+ </html>
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import { createRoot } from 'react-dom/client';
3
+ import { App } from './App';
4
+
5
+ // On localhost, `tools/dev/mock-xrm.js` (from the `serve` dev-tool) can be loaded
6
+ // to stub the Xrm global so this resource runs outside a model-driven host.
7
+ const container = document.getElementById('root');
8
+ if (container) {
9
+ createRoot(container).render(<App />);
10
+ }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Thin wrapper over Xrm.WebApi for this web resource's data access. The Xrm global
3
+ * is typed via @types/xrm; on localhost, stub it with tools/dev/mock-xrm.js.
4
+ */
5
+
6
+ export type DataverseRecord = Record<string, unknown>;
7
+
8
+ export async function retrieveMultiple(
9
+ entityLogicalName: string,
10
+ options = ''
11
+ ): Promise<DataverseRecord[]> {
12
+ const result = await Xrm.WebApi.retrieveMultipleRecords(entityLogicalName, options);
13
+ return result.entities;
14
+ }
15
+
16
+ export async function retrieveRecord(
17
+ entityLogicalName: string,
18
+ id: string,
19
+ options = ''
20
+ ): Promise<DataverseRecord> {
21
+ return Xrm.WebApi.retrieveRecord(entityLogicalName, id, options);
22
+ }
23
+
24
+ export async function createRecord(
25
+ entityLogicalName: string,
26
+ data: Record<string, unknown>
27
+ ): Promise<string> {
28
+ const res = await Xrm.WebApi.createRecord(entityLogicalName, data);
29
+ return res.id;
30
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2018",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "jsx": "react-jsx",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "skipLibCheck": true,
11
+ "lib": ["DOM", "DOM.Iterable", "ES2018"],
12
+ "types": ["xrm"]
13
+ },
14
+ "include": ["src"]
15
+ }
@@ -0,0 +1,17 @@
1
+ const path = require('path');
2
+ const HtmlWebpackPlugin = require('html-webpack-plugin');
3
+
4
+ module.exports = {
5
+ entry: './src/index.tsx',
6
+ output: {
7
+ path: path.resolve(__dirname, 'dist'),
8
+ filename: '{{projectName}}.js',
9
+ clean: true,
10
+ },
11
+ resolve: { extensions: ['.ts', '.tsx', '.js'] },
12
+ module: {
13
+ rules: [{ test: /\.tsx?$/, use: 'ts-loader', exclude: /node_modules/ }],
14
+ },
15
+ plugins: [new HtmlWebpackPlugin({ template: './public/index.html' })],
16
+ devServer: { static: './dist', port: 8080, hot: true },
17
+ };
@@ -0,0 +1,28 @@
1
+ # {{componentName}} (grid customizer)
2
+
3
+ A Dynamics 365 **editable-grid customizer** — maps column data types / logical names to custom Fluent
4
+ UI v9 cell renderers (status badge, currency, date, progress).
5
+
6
+ ## Develop
7
+
8
+ ```bash
9
+ npm install
10
+ npm run build # webpack → dist/{{projectName}}.js (UMD)
11
+ npm run typecheck # tsc --noEmit
12
+ ```
13
+
14
+ ## Customize
15
+
16
+ Edit `src/GridCustomizer.ts` to map columns to renderers, and add renderers in
17
+ `src/cell-renderers.tsx`:
18
+
19
+ ```ts
20
+ cellRendererOverrides = {
21
+ OptionSet: StatusBadgeRenderer, // by data type
22
+ prioritycode: StatusBadgeRenderer // by column logical name
23
+ };
24
+ ```
25
+
26
+ ## Deploy
27
+
28
+ Bundle and register `dist/{{projectName}}.js` on the grid control's customizer property.