@khester/create-dynamics-app
create-dynamics-app
Unified CLI to scaffold Dynamics 365 / Power Platform artifacts — one tool, per-artifact templates, a Fluent v8/v9 choice, integrated dev-tools, and an optional from-design mode.
create-dynamics-app <project-name> --artifact <id> [--v8|--v9]
Quick start
Prerequisite: Node 20+ (engines.node >= 20). Every package is public on npm — no login, no auth, no private registry.
Scaffold a Dynamics custom page and run it (the scaffold step runs npm install for you):
npx @khester/create-dynamics-app my-custom-page --artifact react-custom-page --v8
cd my-custom-page
npm run dev # webpack dev server + mock Xrm — open in the browser
npm run build # production bundle → dist/ (deployable web resource)
- Run with no flags (
npx @khester/create-dynamics-app my-custom-page) for an interactive picker. - Use
--v9for Fluent UI v9 (default is v8 — the safe choice for Power Pages / Dynamics). - Add
--with-componentsto also install the@khester/reusable-componentsFluent v8 set (see Flags). - Pin a version (
@khester/create-dynamics-app@2.1.0) for reproducible builds.
Connect to a live Dataverse org (optional)
The scaffold ships dev-tools that pull real metadata. Needs the Azure CLI (az login into the tenant) or DYNAMICS_* env vars:
npm run auth:token # az → token written to .env (git-ignored)
npm run metadata:pull -- --entities account,contact # live metadata → typed src/constants + src/models
Artifacts (--artifact)
| id | What it scaffolds | Fluent |
|---|---|---|
react-custom-page |
React SPA for a model-driven custom page | v8 |
host-swap |
One component, three hosts — one host-agnostic Component.tsx (coded against an injected IApiService) + a thin per-host wrapper; pick PCF, a custom page, or a web resource with --host (see below) |
v8 |
grid-starter |
React custom page rendering a grid-kit <DataGrid> over a cell-renderer registry |
v8 |
starter-page |
Dynamics page built on a selectable @dataverse-kit/surface-kit surface (--surface) |
v8 |
power-pages |
Power Pages portal starter | v8 |
pcf-field |
PCF field control (property-type aware: text/number/boolean/date) | v8 |
pcf-dataset |
PCF dataset/grid control — grid-kit <ReadOnlyGrid> (sort/filter/columns/search), model-driven, React 18 |
v8 |
grid-customizer |
Editable-grid cell-renderer customizer | v9 |
web-resource |
React web resource (webpack bundle) | v8 or v9 |
dialog-form |
Single React form host (opened as a dialog) | v8 or v9 |
static-web-app |
Azure SWA — React frontend + Functions api/ |
v8 or v9 |
teams-app |
Teams tab (React + teams-js), optional Graph (--graph) |
v9 |
Run with no flags for an interactive picker.
Host-swap — write once, switch host
The host-swap artifact is the host-portability story: you write one src/Component.tsx
against the injected IApiService data-access seam, and a thin per-host wrapper supplies the
service and mounts it. The component source never changes — only the wrapper does. Pick the
host with --host:
npx @khester/create-dynamics-app my-control --artifact host-swap --host pcf
# --host custom-page → Vite app mounted as a Dynamics custom page (ServiceFactory ladder)
# --host pcf → pcf-scripts virtual ReactControl (same-origin FetchApiService, React 18)
# --host web-resource → webpack-bundled web resource (same-origin FetchApiService)
Each host builds the same component under its own toolchain (Vite / pcf-scripts / webpack);
all three are exercised by CI build gates. To retarget, scaffold with another --host and copy
your src/Component.tsx across.
Flags
-a, --artifact <id>— artifact to scaffold (see table).--v8/--v9(or-l, --library fluent-v8|fluent-v9) — Fluent version for artifacts that support a choice. Only the chosen library is installed.-n, --namespace,--component-name,--property-type— PCF options.--host <custom-page|pcf|web-resource>— host target for thehost-swapartifact (defaultcustom-page).--surface <form|panel|dashboard|master-detail>— surface for thestarter-pageartifact.--graph— include Microsoft Graph (teams-app).--with-components— install@khester/reusable-components(a curated Fluent v8 React set:ReusableCard,CustomSection,FileUploadField,SearchComponent,DetailsListComponent, the localhost-gatedlogger, …) plus its@fluentui/reactv8 peer. Opt-in for the v8 React hosts that don't already bundle it —power-pages,web-resource,dialog-form(react-custom-page,grid-starter, andhost-swapship it as a base dependency already); warned-and-ignored elsewhere; errors if combined with--v9.--from-design <file>— generate from a form-builder export (aFormBuilderProjectJSON, e.g. from/generate-form). Wraps@dataverse-kit/export-engineto produce a deployable project (host + form code + a design-derived data layer). Supported targets:web-resource,pcf,static-web-app. From-design output is generated by the export engine and is structurally different from a blank starter (it ships its own token tooling);package.json.dynamicsKit.artifactrecords which.--skip-git,--skip-install.
Dev-tools (in generated projects)
Blank starters get package.json scripts for: auth:token (Azure CLI → .env), metadata:pull (typed entities via dvgen), smoke:live (live-org retrieve), plus a mock-Xrm helper for local serve.
Breaking changes in 2.0.0
See CHANGELOG.md. In short: removed the consulting flags (-c/-e/-p/-d/--generate-sample); --template dynamics-365|pcf-control are removed (use --artifact react-custom-page / --artifact pcf-field); --template power-pages still works as a deprecated alias.
Testing & coverage boundaries
- Unit + smoke tests (
npm test) run the scaffold pipeline for all 12 artifacts (zero-residual-token + variant/prune checks) but do notnpm installor build the generated projects, except the build gates below. - Build gates (
npm run test:e2e) scaffold +npm install+ build the React custom-page templates (Vite), the PCF templates (pcf-scripts), and all threehost-swaphosts (custom-page → Vite, pcf → pcf-scripts, web-resource → webpack) — the host-swap gates prove oneComponent.tsxbuilds under every host toolchain. - React artifacts are verified to
tsc --noEmitclean for v8 and v9 via manual installs; PCF type-correctness (against generatedManifestTypesfrompcf-scripts refreshTypes) is verified manually, not in CI. - The
--from-designgenerated-project build is verified manually.