@khester/create-dynamics-app 2.10.0 → 3.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 (82) hide show
  1. package/README.md +28 -3
  2. package/dist/artifacts/registry.d.ts +7 -5
  3. package/dist/artifacts/registry.d.ts.map +1 -1
  4. package/dist/artifacts/registry.js +82 -27
  5. package/dist/artifacts/registry.js.map +1 -1
  6. package/dist/artifacts/types.d.ts +15 -8
  7. package/dist/artifacts/types.d.ts.map +1 -1
  8. package/dist/index.js +7 -5
  9. package/dist/index.js.map +1 -1
  10. package/dist/scaffold.d.ts.map +1 -1
  11. package/dist/scaffold.js +9 -4
  12. package/dist/scaffold.js.map +1 -1
  13. package/dist/templatePlan.d.ts.map +1 -1
  14. package/dist/templatePlan.js +20 -3
  15. package/dist/templatePlan.js.map +1 -1
  16. package/dist/utils/copyTemplate.d.ts +1 -1
  17. package/dist/utils/copyTemplate.d.ts.map +1 -1
  18. package/dist/utils/copyTemplate.js +5 -1
  19. package/dist/utils/copyTemplate.js.map +1 -1
  20. package/package.json +1 -1
  21. package/templates/_shared/dataverse-seam/XrmApiService.ts +26 -5
  22. package/templates/host-swap/README.md +57 -2
  23. package/templates/host-swap/_variants/host.custom-page/src/App.tsx +9 -5
  24. package/templates/host-swap/_variants/host.pcf/ControlManifest.Input.xml +26 -2
  25. package/templates/host-swap/_variants/host.pcf/gitignore +7 -0
  26. package/templates/host-swap/_variants/host.pcf/index.ts +21 -3
  27. package/templates/host-swap/_variants/host.pcf/package.json +20 -4
  28. package/templates/host-swap/_variants/host.pcf/{{componentName}}.pcfproj +67 -0
  29. package/templates/host-swap/_variants/host.web-resource/src/App.tsx +5 -1
  30. package/templates/host-swap/src/Component.tsx +54 -7
  31. package/templates/host-swap/src/useHostWidth.ts +53 -0
  32. package/templates/pcf-dataset/dev/harness/dataverse.ts +82 -0
  33. package/templates/pcf-dataset/dev/index.html +20 -0
  34. package/templates/pcf-dataset/dev/main.tsx +190 -0
  35. package/templates/pcf-dataset/dev/vite-env.d.ts +1 -0
  36. package/templates/pcf-dataset/env.local.example +24 -0
  37. package/templates/pcf-dataset/eslintrc.json +11 -0
  38. package/templates/pcf-dataset/gitignore +4 -0
  39. package/templates/pcf-dataset/package.json +18 -8
  40. package/templates/pcf-dataset/pcfconfig.json +3 -0
  41. package/templates/pcf-dataset/tsconfig.dev.json +15 -0
  42. package/templates/pcf-dataset/tsconfig.json +11 -2
  43. package/templates/pcf-dataset/vite.config.ts +63 -0
  44. package/templates/pcf-dataset/{{componentName}}/ControlManifest.Input.xml +45 -0
  45. package/templates/pcf-dataset/{{componentName}}/{{componentName}}App.tsx +64 -0
  46. package/templates/pcf-dataset/{{{componentName}}Component.tsx → {{componentName}}/{{componentName}}Component.tsx} +9 -46
  47. package/templates/pcf-field/eslintrc.json +11 -0
  48. package/templates/pcf-field/package.json +1 -0
  49. package/templates/pcf-field/pcfconfig.json +3 -0
  50. package/templates/pcf-field/{{componentName}}/ControlManifest.Input.xml +42 -0
  51. package/templates/power-pages-island/README.md +93 -0
  52. package/templates/power-pages-island/package.json +27 -0
  53. package/templates/power-pages-island/public/index.html +13 -0
  54. package/templates/power-pages-island/src/App.tsx +37 -0
  55. package/templates/power-pages-island/src/Component.tsx +136 -0
  56. package/templates/power-pages-island/src/core/services/MockApiService.ts +58 -0
  57. package/templates/power-pages-island/src/core/services/portalSeam.assert.ts +14 -0
  58. package/templates/power-pages-island/src/island-env.d.ts +12 -0
  59. package/templates/power-pages-island/src/islands.tsx +38 -0
  60. package/templates/power-pages-island/src/useHostWidth.ts +53 -0
  61. package/templates/power-pages-island/tsconfig.json +15 -0
  62. package/templates/power-pages-island/webpack.config.js +22 -0
  63. package/templates/react-custom-page/tools/deploy/deploy-webresource.cjs +8 -2
  64. package/templates/pcf-dataset/ControlManifest.Input.xml +0 -16
  65. package/templates/pcf-field/ControlManifest.Input.xml +0 -17
  66. package/templates/power-pages-starter/.env.example +0 -6
  67. package/templates/power-pages-starter/README.md +0 -95
  68. package/templates/power-pages-starter/package.json +0 -43
  69. package/templates/power-pages-starter/public/index.html +0 -15
  70. package/templates/power-pages-starter/src/components/ContactForm.css +0 -84
  71. package/templates/power-pages-starter/src/components/ContactForm.tsx +0 -258
  72. package/templates/power-pages-starter/src/index.tsx +0 -32
  73. package/templates/power-pages-starter/src/providers/PowerPagesProvider.tsx +0 -162
  74. package/templates/power-pages-starter/src/styles/index.css +0 -76
  75. package/templates/power-pages-starter/tsconfig.json +0 -20
  76. package/templates/power-pages-starter/webpack.config.js +0 -57
  77. package/templates/pcf-dataset/{index.ts → {{componentName}}/index.ts} +0 -0
  78. package/templates/pcf-dataset/{strings → {{componentName}}/strings}/{{componentName}}.1033.resx +0 -0
  79. package/templates/pcf-field/{index.ts → {{componentName}}/index.ts} +0 -0
  80. package/templates/pcf-field/{strings → {{componentName}}/strings}/{{componentName}}.1033.resx +0 -0
  81. package/templates/pcf-field/{{{componentName}}Component.tsx → {{componentName}}/{{componentName}}Component.tsx} +0 -0
  82. package/templates/{power-pages-starter → power-pages-island}/gitignore +1 -1
@@ -6,6 +6,23 @@
6
6
  */
7
7
  import fs from 'fs-extra';
8
8
  import path from 'path';
9
+ /**
10
+ * Substitute `{{key}}` in a variant's `from`/`to` path.
11
+ *
12
+ * Needed because PCF templates put the control in a `{{componentName}}/` subfolder (the
13
+ * standard `pac pcf init` layout), while the `_variants/` pool stays at the project root
14
+ * so its paths remain static. So a variant reads from a fixed root path and writes into
15
+ * a per-scaffold directory: `to: '{{componentName}}/ValueInput.tsx'`.
16
+ */
17
+ function substitutePath(rel, vars) {
18
+ if (!rel.includes('{{'))
19
+ return rel;
20
+ let out = rel;
21
+ for (const [key, value] of Object.entries(vars)) {
22
+ out = out.replace(new RegExp(`{{${key}}}`, 'g'), () => value);
23
+ }
24
+ return out;
25
+ }
9
26
  /** Resolve a template-relative path, refusing anything that escapes the project dir. */
10
27
  function safeJoin(root, rel) {
11
28
  const resolvedRoot = path.resolve(root);
@@ -27,8 +44,8 @@ export async function applyTemplatePlan(projectPath, descriptor, vars) {
27
44
  throw new Error(`No variant case for ${variant.choose}=${JSON.stringify(key)} ` +
28
45
  `(available: ${Object.keys(variant.cases).join(', ')}).`);
29
46
  }
30
- const from = safeJoin(projectPath, source);
31
- const to = safeJoin(projectPath, variant.to);
47
+ const from = safeJoin(projectPath, substitutePath(source, vars));
48
+ const to = safeJoin(projectPath, substitutePath(variant.to, vars));
32
49
  if (!(await fs.pathExists(from))) {
33
50
  throw new Error(`Variant source not found in template: ${source}`);
34
51
  }
@@ -37,7 +54,7 @@ export async function applyTemplatePlan(projectPath, descriptor, vars) {
37
54
  }
38
55
  // 2. Prune scaffolding leftovers (e.g. the _variants/ directory).
39
56
  for (const rel of descriptor.prune ?? []) {
40
- await fs.remove(safeJoin(projectPath, rel));
57
+ await fs.remove(safeJoin(projectPath, substitutePath(rel, vars)));
41
58
  }
42
59
  }
43
60
  //# sourceMappingURL=templatePlan.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"templatePlan.js","sourceRoot":"","sources":["../src/templatePlan.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,wFAAwF;AACxF,SAAS,QAAQ,CAAC,IAAY,EAAE,GAAW;IACzC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;IAC7C,IAAI,IAAI,KAAK,YAAY,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvE,MAAM,IAAI,KAAK,CAAC,QAAQ,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,iCAAiC,CAAC,CAAC;IAChF,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,WAAmB,EACnB,UAA8B,EAC9B,IAA4B;IAE5B,yEAAyE;IACzE,KAAK,MAAM,OAAO,IAAI,UAAU,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CACb,qBAAqB,OAAO,CAAC,MAAM,sDAAsD,CAC1F,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,uBAAuB,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG;gBAC7D,eAAe,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC3D,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAC3C,MAAM,EAAE,GAAG,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;QAC7C,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;YACjC,MAAM,IAAI,KAAK,CAAC,yCAAyC,MAAM,EAAE,CAAC,CAAC;QACrE,CAAC;QACD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC;QACrC,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,kEAAkE;IAClE,KAAK,MAAM,GAAG,IAAI,UAAU,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;QACzC,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC;IAC9C,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"templatePlan.js","sourceRoot":"","sources":["../src/templatePlan.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,7 @@
3
3
  * placeholders in file contents and filenames.
4
4
  *
5
5
  * `templateDir` is the LITERAL directory name under `templates/` (e.g.
6
- * `react-custom-page`, `power-pages-starter`, `pcf-field`) — resolved from the
6
+ * `react-custom-page`, `power-pages-island`, `pcf-field`) — resolved from the
7
7
  * artifact registry's `templateDir`, with no `-starter` suffix heuristic.
8
8
  * Substitution runs whenever `templateVariables` is provided (no per-template
9
9
  * special-casing).
@@ -1 +1 @@
1
- {"version":3,"file":"copyTemplate.d.ts","sourceRoot":"","sources":["../../src/utils/copyTemplate.ts"],"names":[],"mappings":"AA2HA;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,iBAAiB,CAAC,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CA8Bf"}
1
+ {"version":3,"file":"copyTemplate.d.ts","sourceRoot":"","sources":["../../src/utils/copyTemplate.ts"],"names":[],"mappings":"AA+HA;;;;;;;;;GASG;AACH,wBAAsB,YAAY,CAChC,WAAW,EAAE,MAAM,EACnB,eAAe,EAAE,MAAM,EACvB,iBAAiB,CAAC,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,GAC5C,OAAO,CAAC,IAAI,CAAC,CA8Bf"}
@@ -46,6 +46,10 @@ async function safeRename(dirPath, fullPath, newName) {
46
46
  const DOTFILE_RENAMES = {
47
47
  gitignore: '.gitignore',
48
48
  npmignore: '.npmignore',
49
+ // PCF templates: `pcf-scripts start` lints the control folder and dies with
50
+ // "No ESLint configuration found in <control>/" if none resolves. Shipped
51
+ // dotless for the same publish-safety reason as the two above.
52
+ 'eslintrc.json': '.eslintrc.json',
49
53
  };
50
54
  async function renameDotfiles(dirPath) {
51
55
  for (const file of await fs.readdir(dirPath, { withFileTypes: true })) {
@@ -107,7 +111,7 @@ async function replaceTemplateVariables(dirPath, variables) {
107
111
  * placeholders in file contents and filenames.
108
112
  *
109
113
  * `templateDir` is the LITERAL directory name under `templates/` (e.g.
110
- * `react-custom-page`, `power-pages-starter`, `pcf-field`) — resolved from the
114
+ * `react-custom-page`, `power-pages-island`, `pcf-field`) — resolved from the
111
115
  * artifact registry's `templateDir`, with no `-starter` suffix heuristic.
112
116
  * Substitution runs whenever `templateVariables` is provided (no per-template
113
117
  * special-casing).
@@ -1 +1 @@
1
- {"version":3,"file":"copyTemplate.js","sourceRoot":"","sources":["../../src/utils/copyTemplate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"copyTemplate.js","sourceRoot":"","sources":["../../src/utils/copyTemplate.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khester/create-dynamics-app",
3
- "version": "2.10.0",
3
+ "version": "3.1.0",
4
4
  "license": "MIT",
5
5
  "description": "Unified CLI to scaffold Dynamics 365 / Power Platform artifacts",
6
6
  "main": "dist/index.js",
@@ -2,10 +2,14 @@ import { IApiService } from "./IApiService";
2
2
  import { logCrud } from "./crudLogging";
3
3
 
4
4
  /**
5
- * Production API service for a model-driven custom page. Every call hits the
6
- * Dataverse Web API on the same origin (the hosting org authenticates the
7
- * request via its session), addressing entities by their SET name (e.g.
8
- * "accounts") so reads and writes use one consistent identifier.
5
+ * Production API service for an app hosted INSIDE a model-driven app — an HTML web resource on a
6
+ * form or behind a sitemap SubArea, or a PCF control. Every call hits the Dataverse Web API on the
7
+ * same origin (the hosting org authenticates via its session), addressing entities by their SET
8
+ * name (e.g. "accounts") so reads and writes use one consistent identifier.
9
+ *
10
+ * NOT for a Power Apps *custom page* hosted standalone: this class needs an `Xrm` object, and a
11
+ * canvasapp does not provide one. To put a component in a real custom page, use a PCF control —
12
+ * `context.webAPI`, or (verified live 2026-07-25) a same-origin fetch, both work there.
9
13
  */
10
14
  export class XrmApiService implements IApiService {
11
15
  private Xrm: any;
@@ -15,8 +19,25 @@ export class XrmApiService implements IApiService {
15
19
  this.Xrm = Xrm;
16
20
  }
17
21
 
22
+ /**
23
+ * Resolve the org URL. `Xrm.Utility.getGlobalContext()` is the supported API and is available in
24
+ * every model-driven host; `Xrm.Page` is deprecated and only exists on a FORM, so it is a fallback
25
+ * rather than the primary. Both are probed defensively: reaching straight for
26
+ * `Xrm.Page.context.getClientUrl()` throws a bare `TypeError` when the app is opened outside a form
27
+ * (e.g. the raw /WebResources/ URL), which tells the developer nothing about the actual cause.
28
+ */
18
29
  private clientUrl(): string {
19
- return this.Xrm.Page.context.getClientUrl();
30
+ const url =
31
+ this.Xrm?.Utility?.getGlobalContext?.()?.getClientUrl?.() ??
32
+ this.Xrm?.Page?.context?.getClientUrl?.();
33
+ if (!url) {
34
+ throw new Error(
35
+ "Could not resolve the Dataverse client URL from the Xrm object. This app must run inside a " +
36
+ "model-driven app (a web resource on a form or a sitemap SubArea). Opening the web resource " +
37
+ "URL directly gives it no parent Xrm.",
38
+ );
39
+ }
40
+ return url;
20
41
  }
21
42
 
22
43
  private headers(): HeadersInit {
@@ -1,7 +1,9 @@
1
1
  # {{projectName}} — host-swap component
2
2
 
3
3
  One React component, written once against an injected `IApiService`, that you can
4
- host as a **PCF control**, a **Dynamics 365 custom page**, or a **web resource**.
4
+ host as a **PCF control** or as a **web resource** (in two flavours — webpack, or Vite under the
5
+ legacy `custom-page` host name; ★ despite the name that host emits an HTML web resource, NOT a
6
+ Power Apps custom page — for a real one, use `--host pcf` and drop the control on the page).
5
7
  You chose the host at scaffold time (`--host pcf`, `--host custom-page`, or
6
8
  `--host web-resource`); to retarget, scaffold again with another host and copy your
7
9
  `src/Component.tsx` across — the component never changes, only the thin wrapper does.
@@ -17,7 +19,7 @@ You chose the host at scaffold time (`--host pcf`, `--host custom-page`, or
17
19
 
18
20
  ## How each host supplies the data service
19
21
 
20
- - **Custom page (Vite):** `App.tsx` resolves the Xrm context and `ServiceFactory`
22
+ - **`custom-page` host (Vite web resource):** `App.tsx` resolves the Xrm context and `ServiceFactory`
21
23
  picks Mock (localhost), the token-proxy (`npm run dev` with `DYNAMICS_URL`), or
22
24
  the production `XrmApiService` (deployed).
23
25
  - **PCF control (pcf-scripts):** the control builds the service directly — a
@@ -36,3 +38,56 @@ npm install
36
38
  # pcf: npm run build (pcf-scripts build) | npm start (PCF test harness)
37
39
  # web-resource: npm run build (webpack --mode production) | npm run dev (webpack dev server)
38
40
  ```
41
+
42
+ > ★ **`pcf-scripts build` exits 0 even when the build FAILS.** If you script this in CI, grep the
43
+ > log for `[build] Succeeded` and for the absence of `compiled with N errors` — don't trust `$?`.
44
+
45
+ ## Deploy
46
+
47
+ The three hosts are **not** symmetric here. Only the PCF host has a first-class deploy path.
48
+
49
+ ### PCF
50
+
51
+ ```bash
52
+ pac pcf push --publisher-prefix <your-prefix>
53
+ ```
54
+
55
+ Builds, wraps the control in a temporary `PowerAppsToolsTemp_<prefix>` solution, imports it and
56
+ publishes. This needs the `.pcfproj` this template ships; its `ProjectGuid` is generated per
57
+ scaffold, so two controls from this template never collide inside one solution.
58
+
59
+ To ship versioned **managed + unmanaged** solution zips instead of a temp solution, wrap the
60
+ control in a solution project and build that:
61
+
62
+ ```bash
63
+ pac solution init --publisher-name <you> --publisher-prefix <p>
64
+ pac solution add-reference --path .
65
+ dotnet build -c Release -p:SolutionPackageType=Both
66
+ pac solution import --path bin/Release/<Solution>.zip
67
+ ```
68
+
69
+ (If you have the `claude-workspace` checkout, `msft/04-dynamics/scripts/deploy/package-control.sh
70
+ --source . --version 1.0.0 --output ./dist` automates exactly that — it discards this `.pcfproj`
71
+ and generates its own, so the two paths never conflict.)
72
+
73
+ ★ **Bump `ControlManifest.Input.xml`'s `version=` on every change.** Neither command does it for
74
+ you, and without a bump the platform serves the cached previous build — the single most common
75
+ "my fix didn't deploy".
76
+
77
+ ★ **The `<platform-library>` versions must be exact.** `pcf-scripts` matches a *range* and
78
+ silently remaps the bundle, but leaves your number in the manifest — so a wrong version **builds
79
+ green and fails to load in every host**. React must be `16.14.0`; Fluent `@fluentui/react` must be
80
+ `8.29.0` or `8.121.1`.
81
+
82
+ ### Web resource
83
+
84
+ No deploy script ships. Build, then upload `dist/` as a JavaScript web resource — via the maker
85
+ portal, `pac webresource push`, or the workspace's `webresource-manager.sh` if you have that
86
+ checkout — and reference it from a form or the sitemap.
87
+
88
+ ### Custom page
89
+
90
+ ★ **No deploy path ships for this host.** Despite the name it emits a Vite-built **HTML web
91
+ resource**, not a Power Apps custom page — see the registry description. If you want an uploader,
92
+ the `react-custom-page` artifact has one (`tools/deploy/deploy-webresource.cjs`); if you want a
93
+ *real* model-driven custom page, use `--host pcf` and drop the control onto the page in Studio.
@@ -1,14 +1,18 @@
1
1
  import * as React from "react";
2
2
  import { ServiceFactory } from "./core/services/ServiceFactory";
3
3
  import { Component } from "./Component";
4
+ import { useHostWidth } from "./useHostWidth";
4
5
 
5
- // Resolve the Xrm context when hosted as a Dynamics custom page (the app runs in
6
- // an iframe → window.parent.Xrm, or window.Xrm). ServiceFactory then picks the
7
- // IApiService per environment: Mock on localhost, the token-proxy in dev, or the
8
- // production XrmApiService when deployed.
6
+ // Resolve the Xrm context. This host is an HTML WEB RESOURCE (not a Power Apps
7
+ // custom page): it runs in an iframe inside a model-driven app, so Xrm comes from
8
+ // window.parent. ServiceFactory then picks the IApiService per environment: Mock on
9
+ // localhost, the token-proxy in dev, or the production XrmApiService when deployed.
9
10
  const Xrm = (window.parent as any)?.Xrm ?? (window as any).Xrm;
10
11
 
11
12
  export const App: React.FC = () => {
12
13
  const api = React.useMemo(() => ServiceFactory.createApiService(Xrm), []);
13
- return <Component api={api} />;
14
+ // Embedded in a form iframe this is often narrow on a phone, so measure rather
15
+ // than assume desktop.
16
+ const width = useHostWidth();
17
+ return <Component api={api} width={width} />;
14
18
  };
@@ -6,8 +6,32 @@
6
6
  <property name="value" display-name-key="value_Display_Key" description-key="value_Desc_Key" of-type="SingleLine.Text" usage="bound" required="false" />
7
7
  <resources>
8
8
  <code path="index.ts" order="1"/>
9
- <platform-library name="React" version="18.2.0" />
10
- <platform-library name="Fluent" version="8.115.6" />
9
+ <!--
10
+ ★ These versions MUST come from Microsoft's supported platform-library list; arbitrary
11
+ versions do not fail the build, they fail the RUNTIME. `pcf-scripts` silently maps an
12
+ unsupported version to the nearest supported one when bundling (8.115.6 was emitted as
13
+ `external "FluentUIReactv81211"`), but the MANIFEST keeps the number the platform then
14
+ cannot resolve — so the control builds green and renders "Error loading control" in every
15
+ host. Live-diagnosed 2026-07-26: this template previously declared React 18.2.0 + Fluent
16
+ 8.115.6, neither of which is supported, and could not load in a form OR a custom page.
17
+ Supported (learn.microsoft.com/power-apps/developer/component-framework/react-controls-platform-libraries):
18
+ React → 16.14.0 only (the platform loads 17.0.2 in model-driven, 16.14.0 in canvas)
19
+ Fluent → @fluentui/react 8.29.0 or 8.121.1; @fluentui/react-components >=9.4.0 <=9.46.2
20
+ v8 and v9 cannot both be declared in one manifest.
21
+
22
+ ★ The rule is EQUALITY, not range membership. pcf-scripts matches a supported
23
+ {minVersion, maxVersion} range and remaps the bundle to that row's platformVersion,
24
+ but never rewrites this file — so a version inside the range yet unequal to its
25
+ platformVersion builds green and cannot be resolved at runtime. Asserted in
26
+ src/__tests__/registry.test.ts.
27
+
28
+ ★ package.json intentionally carries react@^18 while this says 16.14.0: the shared
29
+ dataverse-seam pulls @khester/reusable-components, whose peer is react>=17. So the
30
+ types will NOT stop you writing a React 18 API that crashes only in a custom page
31
+ (canvas serves 16.14.0). Don't. See package.json `_comment_versions`.
32
+ -->
33
+ <platform-library name="React" version="16.14.0" />
34
+ <platform-library name="Fluent" version="8.121.1" />
11
35
  <resx path="strings/{{componentName}}.1033.resx" version="1.0.0" />
12
36
  </resources>
13
37
  <feature-usage>
@@ -3,3 +3,10 @@ out/
3
3
  generated/
4
4
  *.log
5
5
  .env
6
+
7
+ # MSBuild output from the .pcfproj (`pac pcf push` / `dotnet build`). Without these
8
+ # the first push litters an untracked obj/PowerAppsToolsTemp_* tree into the repo.
9
+ bin/
10
+ obj/
11
+ *.binlog
12
+ .vs/
@@ -15,12 +15,18 @@ import { MockApiService } from "./src/core/services/MockApiService";
15
15
  * the session cookie — no Xrm or clientUrl needed. On localhost (the pcf-scripts
16
16
  * test harness) it falls back to the in-memory mock so the control renders with
17
17
  * no org.
18
+ *
19
+ * ★ This holds in a model-driven CUSTOM PAGE too — verified live 2026-07-25: a
20
+ * custom page reports the org origin (not apps.powerapps.com) and a same-origin
21
+ * fetch returns 200, in a desktop browser and in the native Power Apps iOS app.
22
+ * That is observed behaviour rather than a documented contract, so if it ever
23
+ * regresses, swap this line for `context.webAPI` (the API Microsoft documents).
18
24
  */
19
25
  export class {{componentName}} implements ComponentFramework.ReactControl<IInputs, IOutputs> {
20
26
  private api!: IApiService;
21
27
 
22
28
  public init(
23
- _context: ComponentFramework.Context<IInputs>,
29
+ context: ComponentFramework.Context<IInputs>,
24
30
  _notifyOutputChanged: () => void,
25
31
  _state: ComponentFramework.Dictionary,
26
32
  ): void {
@@ -28,10 +34,22 @@ export class {{componentName}} implements ComponentFramework.ReactControl<IInput
28
34
  typeof window !== "undefined" &&
29
35
  /^(localhost|127\.0\.0\.1)$/.test(window.location.hostname);
30
36
  this.api = onLocalhost ? new MockApiService() : new FetchApiService("");
37
+
38
+ // Opt in to container-size reporting. WITHOUT THIS `allocatedWidth` never
39
+ // updates, so the control cannot respond to the device or to a resize — the
40
+ // usual reason a "responsive" PCF silently isn't.
41
+ context.mode.trackContainerResize(true);
31
42
  }
32
43
 
33
- public updateView(_context: ComponentFramework.Context<IInputs>): React.ReactElement {
34
- return React.createElement(Component, { api: this.api });
44
+ public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
45
+ // Pass the measured width down. Do NOT use context.client.getFormFactor():
46
+ // verified live on a physical iPhone in the native Power Apps app, a custom
47
+ // page reports formFactor 1 (Desktop) and client "Web", so form-factor
48
+ // branching hands every phone the desktop layout.
49
+ return React.createElement(Component, {
50
+ api: this.api,
51
+ width: context.mode.allocatedWidth,
52
+ });
35
53
  }
36
54
 
37
55
  public getOutputs(): IOutputs {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "{{projectName}}",
3
3
  "version": "1.0.0",
4
- "description": "{{componentName}} — host-swap component hosted as a PCF control",
4
+ "description": "{{componentName}} \u2014 host-swap component hosted as a PCF control",
5
5
  "scripts": {
6
6
  "build": "pcf-scripts build",
7
7
  "clean": "pcf-scripts clean",
@@ -9,8 +9,24 @@
9
9
  "start": "pcf-scripts start",
10
10
  "refreshTypes": "pcf-scripts refreshTypes"
11
11
  },
12
+ "_comment_versions": [
13
+ "\u2605 @fluentui/react is PINNED EXACTLY to the <platform-library> version in",
14
+ "ControlManifest.Input.xml and must move with it. This is a VIRTUAL control: react, react-dom",
15
+ "and @fluentui/react are externalized by pcf-scripts and supplied by the platform at runtime \u2014",
16
+ "and by pcf-start in the harness \u2014 so none of them reaches the bundle. That makes the version a",
17
+ "TYPES decision: the compiler should see the API the platform will actually serve. A caret",
18
+ "defeats it \u2014 ^8.120.0 resolved to 8.125.7 while the platform served 8.121.1.",
19
+ "",
20
+ "\u2605 react/react-dom stay at ^18 on purpose: pinning them to the manifest's 16.14.0 fails",
21
+ "npm install ERESOLVE, because the shared dataverse-seam imports @khester/reusable-components",
22
+ "(peer react>=17). But the @types ARE pinned to 16, which is what actually matters here \u2014 the",
23
+ "canvas runtime behind a custom page serves React 16.14.0, so useId/useTransition/",
24
+ "useSyncExternalStore must be a COMPILE error rather than a crash that only reproduces on a",
25
+ "phone. Since nothing from these packages is bundled, the runtime versions are inert and the",
26
+ "types carry the contract."
27
+ ],
12
28
  "dependencies": {
13
- "@fluentui/react": "^8.120.0",
29
+ "@fluentui/react": "8.121.1",
14
30
  "@types/powerapps-component-framework": "^1.3.18",
15
31
  "react": "^18.2.0",
16
32
  "react-dom": "^18.2.0"
@@ -18,8 +34,8 @@
18
34
  "devDependencies": {
19
35
  "@microsoft/eslint-config-spfx": "^1.18.2",
20
36
  "@types/node": "^18.16.9",
21
- "@types/react": "^18.2.0",
22
- "@types/react-dom": "^18.2.0",
37
+ "@types/react": "^16.14.0",
38
+ "@types/react-dom": "^16.9.0",
23
39
  "eslint-plugin-react-hooks": "^4.6.0",
24
40
  "pcf-scripts": "^1",
25
41
  "pcf-start": "^1",
@@ -0,0 +1,67 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!--
3
+ MSBuild project for the PCF control. Without this file `pac pcf push` fails
4
+ outright, so a scaffolded control has no deploy path at all — which is exactly
5
+ what this template shipped before.
6
+
7
+ Two ways to deploy (see the README's Deploy section):
8
+ • pac pcf push --publisher-prefix <p> — fastest dev loop; wraps the control
9
+ in a temporary PowerAppsToolsTemp_<p> solution and imports it.
10
+ • package-control.sh --source <dir> — emits versioned managed/unmanaged
11
+ solution zips with no temp solution. That script DISCARDS this file and
12
+ generates its own, so the two never conflict.
13
+
14
+ ProjectGuid is generated per scaffold, so two controls from this template never collide
15
+ inside one solution.
16
+ -->
17
+ <Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
18
+ <PropertyGroup>
19
+ <PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>
20
+ </PropertyGroup>
21
+
22
+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
23
+ <Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props')" />
24
+
25
+ <PropertyGroup>
26
+ <Name>{{componentName}}</Name>
27
+ <ProjectGuid>{{{projectGuid}}}</ProjectGuid>
28
+ <OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
29
+ </PropertyGroup>
30
+
31
+ <PropertyGroup>
32
+ <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
33
+ <TargetFramework>net462</TargetFramework>
34
+ <RestoreProjectStyle>PackageReference</RestoreProjectStyle>
35
+ </PropertyGroup>
36
+
37
+ <ItemGroup>
38
+ <PackageReference Include="Microsoft.PowerApps.MSBuild.Pcf" Version="1.*" />
39
+ <!-- Supplies the net462 reference assemblies on macOS/Linux so this builds
40
+ without a Windows image (otherwise MSB3644). -->
41
+ <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
42
+ </ItemGroup>
43
+
44
+ <ItemGroup>
45
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.gitignore" />
46
+ <!-- A scaffolded project always has a nested .git (initGit runs unless --skip-git); the
47
+ probe this was modelled on lives inside a repo and never had one. package-control.sh
48
+ rm -rf's it before building, which is the same signal. -->
49
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.git\**" />
50
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.vs\**" />
51
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\bin\**" />
52
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\obj\**" />
53
+ <ExcludeDirectories Include="$(OutputPath)\**" />
54
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj" />
55
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj.user" />
56
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.sln" />
57
+ <ExcludeDirectories Include="$(MSBuildThisFileDirectory)\node_modules\**" />
58
+ </ItemGroup>
59
+
60
+ <ItemGroup>
61
+ <None Include="$(MSBuildThisFileDirectory)\**" Exclude="@(ExcludeDirectories)" />
62
+ </ItemGroup>
63
+
64
+ <Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
65
+ <Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets')" />
66
+
67
+ </Project>
@@ -3,6 +3,7 @@ import type { IApiService } from "./core/services/IApiService";
3
3
  import { FetchApiService } from "./core/services/FetchApiService";
4
4
  import { MockApiService } from "./core/services/MockApiService";
5
5
  import { Component } from "./Component";
6
+ import { useHostWidth } from "./useHostWidth";
6
7
 
7
8
  // A web resource is served from the org origin, so a same-origin relative
8
9
  // /api/data/v9.2 fetch is authenticated by the session cookie — no Xrm or
@@ -14,5 +15,8 @@ export const App: React.FC = () => {
14
15
  const onLocalhost = /^(localhost|127\.0\.0\.1)$/.test(window.location.hostname);
15
16
  return onLocalhost ? new MockApiService() : new FetchApiService("");
16
17
  }, []);
17
- return <Component api={api} />;
18
+ // A web resource embedded on a form is often narrow on a phone, so measure the
19
+ // viewport rather than assuming desktop.
20
+ const width = useHostWidth();
21
+ return <Component api={api} width={width} />;
18
22
  };
@@ -17,14 +17,33 @@ initializeIcons();
17
17
 
18
18
  /**
19
19
  * The ONE host-agnostic component. It depends on nothing but an injected
20
- * `IApiService` — no PCF `context`, no Xrm, no lifecycle, no bundler globals — so
21
- * the SAME source renders unchanged inside a PCF control and a Dynamics custom
22
- * page. Each host's thin wrapper builds the right `IApiService` and mounts this.
20
+ * `IApiService` and a plain number for its width — no PCF `context`, no Xrm, no
21
+ * lifecycle, no bundler globals — so the SAME source renders unchanged inside a
22
+ * PCF control, a web resource, and a model-driven custom page. Each host's thin
23
+ * wrapper builds the right `IApiService` and supplies the width.
23
24
  */
24
25
  export interface ComponentProps {
25
26
  api: IApiService;
27
+ /**
28
+ * Width available to the control, in px. Hosts that can measure it should pass it
29
+ * (the PCF wrapper passes `context.mode.allocatedWidth`); when omitted the layout
30
+ * falls back to the roomy variant.
31
+ *
32
+ * ★ Deliberately a NUMBER, not a form factor. Do NOT branch on
33
+ * `context.client.getFormFactor()` / `getClient()`: verified live 2026-07-25 on a
34
+ * physical iPhone in the native Power Apps app, a custom page reports
35
+ * client "Web" and formFactor 1 (Desktop). Branching on those silently gives every
36
+ * phone the desktop layout, and passes every test not run on a real device.
37
+ */
38
+ width?: number;
26
39
  }
27
40
 
41
+ /**
42
+ * Below this the Fluent `DetailsList` stops being usable — columns collapse and
43
+ * horizontal scrolling takes over — so narrow hosts get a stacked card list instead.
44
+ */
45
+ const NARROW_BREAKPOINT_PX = 480;
46
+
28
47
  const ACCOUNTS_FETCHXML = [
29
48
  '<fetch top="25">',
30
49
  ' <entity name="account">',
@@ -47,10 +66,14 @@ const COLUMNS: IColumn[] = [
47
66
  { key: "telephone1", name: "Phone", fieldName: "telephone1", minWidth: 140, isResizable: true },
48
67
  ];
49
68
 
50
- export const Component: React.FC<ComponentProps> = ({ api }) => {
69
+ export const Component: React.FC<ComponentProps> = ({ api, width }) => {
51
70
  const [rows, setRows] = React.useState<AccountRow[] | null>(null);
52
71
  const [error, setError] = React.useState<string | null>(null);
53
72
 
73
+ // `width === undefined` means the host cannot measure itself; prefer the roomy
74
+ // layout rather than guessing narrow.
75
+ const isNarrow = width !== undefined && width < NARROW_BREAKPOINT_PX;
76
+
54
77
  React.useEffect(() => {
55
78
  let cancelled = false;
56
79
  api
@@ -70,12 +93,13 @@ export const Component: React.FC<ComponentProps> = ({ api }) => {
70
93
  <Stack tokens={{ childrenGap: 12 }} styles={{ root: { padding: 16 } }}>
71
94
  <Text variant="xLarge">Accounts</Text>
72
95
  <Text variant="small">
73
- One host-agnostic component — the same source renders in a PCF control and a
74
- Dynamics custom page; only the thin host wrapper changes.
96
+ One host-agnostic component — the same source renders in a PCF control, a web
97
+ resource and a model-driven custom page; only the thin host wrapper changes.
98
+ {width !== undefined && ` (${isNarrow ? "narrow" : "wide"} layout · ${width}px)`}
75
99
  </Text>
76
100
  {error && <MessageBar messageBarType={MessageBarType.error}>{error}</MessageBar>}
77
101
  {!rows && !error && <Spinner label="Loading accounts…" />}
78
- {rows && (
102
+ {rows && !isNarrow && (
79
103
  <DetailsList
80
104
  items={rows}
81
105
  columns={COLUMNS}
@@ -84,6 +108,29 @@ export const Component: React.FC<ComponentProps> = ({ api }) => {
84
108
  selectionMode={SelectionMode.none}
85
109
  />
86
110
  )}
111
+ {rows && isNarrow && (
112
+ <Stack tokens={{ childrenGap: 8 }}>
113
+ {rows.map((row) => (
114
+ <Stack
115
+ key={row.accountid}
116
+ tokens={{ childrenGap: 2 }}
117
+ styles={{
118
+ root: {
119
+ padding: 12,
120
+ border: "1px solid #edebe9",
121
+ borderRadius: 4,
122
+ // Long account names must wrap rather than force the card wider
123
+ // than the phone — the failure mode DetailsList has here.
124
+ wordBreak: "break-word",
125
+ },
126
+ }}
127
+ >
128
+ <Text variant="mediumPlus">{row.name ?? "(no name)"}</Text>
129
+ <Text variant="small">{row.telephone1 ?? "—"}</Text>
130
+ </Stack>
131
+ ))}
132
+ </Stack>
133
+ )}
87
134
  </Stack>
88
135
  );
89
136
  };