create-ec-app 0.0.2 → 0.0.4

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.
package/dist/index.js CHANGED
@@ -6,8 +6,8 @@ import { createPortalApp } from "./creators/portal.js";
6
6
  import { createPowerPagesApp } from "./creators/powerpages.js";
7
7
  import { createMobileApp } from "./creators/mobile.js";
8
8
  const main = async () => {
9
- console.log(chalk.bold.hex("#F5AB00")("\n🚀 Welcome to EC App Creator!\n"));
10
- console.log(chalk.gray("Create different types of applications for your EC ecosystem.\n"));
9
+ console.log(chalk.bold.hex("#F5AB00")("\nEC App Creator\n"));
10
+ console.log(chalk.gray("Create applications for your EC ecosystem.\n"));
11
11
  // Prompt for project name if not provided
12
12
  let projectName = process.argv[2];
13
13
  if (!projectName) {
@@ -26,24 +26,24 @@ const main = async () => {
26
26
  // Prompt for application type
27
27
  const appTypes = [
28
28
  {
29
- name: "📱 Webresource App",
29
+ name: "Webresource App",
30
30
  value: "webresource",
31
- description: "React app for Dynamics 365 webresources with Vite, Kendo UI, and Tailwind"
31
+ description: "React app for Dynamics 365 webresources with Vite, optional Kendo UI, and Tailwind"
32
32
  },
33
33
  {
34
- name: "🌐 Portal App",
34
+ name: "Portal App",
35
35
  value: "portal",
36
36
  description: "Next.js app for customer portals with authentication and Dynamics integration"
37
37
  },
38
38
  {
39
- name: "Power Pages App",
39
+ name: "Power Pages App",
40
40
  value: "powerpages",
41
- description: "React SPA for Power Pages with specialized authentication and data services"
41
+ description: "React SPA for Power Pages with authentication context and data services"
42
42
  },
43
43
  {
44
- name: "📲 Mobile App",
44
+ name: "Mobile App",
45
45
  value: "mobile",
46
- description: "React Native Expo app with NativeWind, TypeScript, and MSAL authentication"
46
+ description: "React Native Expo app with TypeScript and NativeWind"
47
47
  }
48
48
  ];
49
49
  const { appType } = await inquirer.prompt([
@@ -54,12 +54,12 @@ const main = async () => {
54
54
  choices: appTypes.map(type => ({
55
55
  name: `${type.name}\n ${chalk.gray(type.description)}`,
56
56
  value: type.value,
57
- short: type.name.replace(/[📱🌐⚡📲]/g, '').trim()
57
+ short: type.name
58
58
  })),
59
59
  pageSize: 10
60
60
  },
61
61
  ]);
62
- console.log(`\n${chalk.green("Creating")} ${chalk.bold(appType)} app: ${chalk.cyan(projectName)}\n`);
62
+ console.log(`\n${chalk.green("Creating")} ${chalk.bold(appType)} app: ${chalk.cyan(projectName)}\n`);
63
63
  // Route to the appropriate creator
64
64
  switch (appType) {
65
65
  case "webresource":
@@ -79,17 +79,17 @@ const main = async () => {
79
79
  process.exit(1);
80
80
  }
81
81
  // Final success message
82
- console.log(chalk.green.bold("\n🎉 Project created successfully!"));
82
+ console.log(chalk.green.bold("\nProject created successfully."));
83
83
  console.log(`\n${chalk.cyan("Next steps:")}`);
84
- console.log(` ${chalk.gray("›")} cd ${projectName}`);
84
+ console.log(` - cd ${projectName}`);
85
85
  if (appType === "webresource" || appType === "powerpages") {
86
- console.log(` ${chalk.gray("›")} ${chalk.yellow("npx kendo-ui-license activate")} ${chalk.gray("(IMPORTANT: Activate your Kendo license)")}`);
86
+ console.log(` - If you selected Kendo UI: ${chalk.yellow("npx kendo-ui-license activate")}`);
87
87
  }
88
- console.log(` ${chalk.gray("›")} npm run dev`);
89
- console.log(` ${chalk.gray("›")} npm run build\n`);
88
+ console.log(` - npm run dev`);
89
+ console.log(` - npm run build\n`);
90
90
  };
91
91
  main().catch((error) => {
92
- console.error(chalk.red("An error occurred:"), error);
92
+ console.error(chalk.red("An error occurred:"), error);
93
93
  process.exit(1);
94
94
  });
95
95
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ # EC Mobile App
2
+
3
+ Expo + React Native + TypeScript template with Tailwind (NativeWind-style utilities), Expo Router, and a small set of UI utilities. Generated by `create-ec-app` for rapid mobile development.
4
+
5
+ ## Features
6
+
7
+ - Expo Router with typed layouts
8
+ - Tailwind utilities for React Native
9
+ - Theming helpers for React Navigation (`lib/theme.ts`)
10
+ - Utility helpers (`lib/utils.ts` with `cn`)
11
+ - Portal support via `@rn-primitives/portal` (included in root layout)
12
+
13
+ ## Prerequisites
14
+
15
+ - Node.js 18+
16
+ - Xcode (macOS) and/or Android Studio SDKs for local simulators
17
+ - Expo CLI (installed automatically via `npx`)
18
+
19
+ ## Getting Started
20
+
21
+ - Install dependencies: `npm install`
22
+ - Start development: `npx expo start`
23
+ - Press `i` for iOS simulator, `a` for Android, or scan the QR code with Expo Go
24
+
25
+ ## Key Files
26
+
27
+ - `app/_layout.tsx`: Root layout with theme provider, Expo Router stack, and `PortalHost`.
28
+ - `lib/theme.ts`: Light/dark palettes mapped to React Navigation theme.
29
+ - `lib/utils.ts`: `cn` helper for conditional class names.
30
+ - `global.css`: Tailwind directives and custom styles (imported by the root layout).
31
+
32
+ ## Styling
33
+
34
+ - The template uses Tailwind utilities for React Native components. Adjust Tailwind config as needed for your design system.
35
+
36
+ ## Building
37
+
38
+ - Use the Expo tooling for builds and previews:
39
+ - `npx expo prebuild` to generate native projects (if needed)
40
+ - `npx expo run:ios` / `npx expo run:android` to build and run
41
+ - See the Expo docs for EAS Build for cloud builds
42
+
@@ -0,0 +1,161 @@
1
+ # EC Portal App
2
+
3
+ Next.js (App Router) + TypeScript template for customer portals integrated with Dynamics 365/Dataverse. Generated by `create-ec-app`, it includes Tailwind CSS, TanStack Query, Azure AD authentication via NextAuth, and a choice of UI library (Kendo UI or Shadcn/ui).
4
+
5
+ ## Features
6
+
7
+ - Next.js App Router with TypeScript
8
+ - Tailwind CSS; Kendo preset applied when using Kendo UI
9
+ - UI choice: Kendo UI (with theme import) or Shadcn/ui
10
+ - TanStack Query provider (`src/app/providers.tsx`)
11
+ - Azure AD auth via NextAuth (`auth.ts`, `src/app/api/auth/[...nextauth]/route.ts`)
12
+ - Example Dynamics API route (`src/app/api/dynamics/accounts/route.ts`)
13
+ - React Query hook (`src/hooks/useDynamicsAccounts.ts`)
14
+ - `next.config.ts` set to `output: 'standalone'` (Azure-friendly)
15
+ - Example GitHub Actions workflow (`github.example.deploy.yml`)
16
+ - Example Azure DevOps pipeline (`azure-pipelines.example.yml`)
17
+
18
+ ## Prerequisites
19
+
20
+ - Node.js 18+ and npm 9+
21
+ - Azure AD app registration (client id/secret, tenant)
22
+ - Dynamics 365/Dataverse environment and base URL
23
+
24
+ ## Getting Started
25
+
26
+ 1. Install dependencies: `npm install`
27
+
28
+ 2. Configure environment in `.env.local`:
29
+
30
+ ```
31
+ NEXTAUTH_URL=http://localhost:3000
32
+ AZURE_AD_CLIENT_ID=your-azure-ad-client-id
33
+ AZURE_AD_CLIENT_SECRET=your-azure-ad-client-secret
34
+ AZURE_AD_TENANT_ID=your-azure-ad-tenant-id
35
+ DYNAMICS_BASE_URL=https://your-org.crm.dynamics.com
36
+ DYNAMICS_API_VERSION=v9.2
37
+ ```
38
+
39
+ 3. Development: `npm run dev`
40
+
41
+ 4. Production build: `npm run build` then `npm start`
42
+
43
+ If you selected Kendo UI, activate your license after install:
44
+
45
+ ```
46
+ npx kendo-ui-license activate
47
+ ```
48
+
49
+ ## Dynamics Data Access
50
+
51
+ - Library: `src/lib/dynamics.ts` exposes `getDynamicsData(entity, options)`
52
+ - API route: `src/app/api/dynamics/accounts/route.ts` fetches Accounts via the above
53
+ - Hook: `src/hooks/useDynamicsAccounts.ts` wraps the API with TanStack Query
54
+
55
+ Example component:
56
+
57
+ ```tsx
58
+ "use client";
59
+ import { useDynamicsAccounts } from "@/hooks/useDynamicsAccounts";
60
+
61
+ export default function AccountsList() {
62
+ const { data, isLoading, error } = useDynamicsAccounts();
63
+ if (isLoading) return <div>Loading…</div>;
64
+ if (error) return <div>Failed to load</div>;
65
+ return (
66
+ <ul>
67
+ {data?.map((a) => (
68
+ <li key={a.accountid}>{a.name}</li>
69
+ ))}
70
+ </ul>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ## UI Libraries
76
+
77
+ - Kendo UI: Theme CSS is imported in `src/app/layout.tsx` and components can be used from `@progress/kendo-react-*` packages.
78
+ - Shadcn/ui: Components are installed and available via the `@/components` alias.
79
+
80
+ ## Deployment
81
+
82
+ - The project is configured for standalone output (good for Azure App Service and containers).
83
+ - An example GitHub Actions workflow is provided in `example.deploy.yml`. Configure secrets and adapt steps to your environment.
84
+
85
+ ### Azure App Service (Web App)
86
+
87
+ Step 1 — Create a Web App and connect CI
88
+
89
+ - Create a new App Service in Azure and connect deployment to your repository (GitHub) or set up an Azure DevOps pipeline.
90
+
91
+ Step 2 — Environment variables
92
+
93
+ It is important to set configuration early to avoid restarts due to missing settings. Ensure you set the following app settings. Note the `SCM_DO_BUILD_DURING_DEPLOYMENT` flag controls whether App Service attempts a build during deployment.
94
+
95
+ Example app settings payload:
96
+
97
+ ```
98
+ [
99
+ {
100
+ "name": "AUTH_MICROSOFT_ENTRA_ID_ID",
101
+ "value": "",
102
+ "slotSetting": false
103
+ },
104
+ {
105
+ "name": "AUTH_MICROSOFT_ENTRA_ID_ISSUER",
106
+ "value": "<https://login.microsoftonline.com/TENANT_ID/v2.0>",
107
+ "slotSetting": false
108
+ },
109
+ {
110
+ "name": "AUTH_MICROSOFT_ENTRA_ID_SECRET",
111
+ "value": "CLIENT_SECRET",
112
+ "slotSetting": false
113
+ },
114
+ {
115
+ "name": "AUTH_MICROSOFT_ENTRA_ID_TENANT_ID",
116
+ "value": "TENANT_ID",
117
+ "slotSetting": false
118
+ },
119
+ {
120
+ "name": "AUTH_SECRET",
121
+ "value": "AUTH_SECRET",
122
+ "slotSetting": false
123
+ },
124
+ {
125
+ "name": "AUTH_URL",
126
+ "value": "https://WEBSITE_URL/",
127
+ "slotSetting": false
128
+ },
129
+ {
130
+ "name": "DYNAMICS_API_URL",
131
+ "value": "https://DYNAMICS_URL/",
132
+ "slotSetting": false
133
+ },
134
+ {
135
+ "name": "NEXT_PUBLIC_BASE_URL",
136
+ "value": "https://BASE_URL",
137
+ "slotSetting": false
138
+ },
139
+ {
140
+ "name": "SCM_DO_BUILD_DURING_DEPLOYMENT",
141
+ "value": "false",
142
+ "slotSetting": false
143
+ }
144
+ ]
145
+ ```
146
+
147
+ Notes:
148
+
149
+ - If your CI pipeline builds the app and you deploy the standalone output, set `SCM_DO_BUILD_DURING_DEPLOYMENT` to `false` to prevent an extra App Service build.
150
+ - If you push source and want App Service (Kudu/Oryx) to build, set `SCM_DO_BUILD_DURING_DEPLOYMENT` to `true`.
151
+ - This template’s `auth.ts` uses `AZURE_AD_CLIENT_ID|SECRET|TENANT_ID`. If you prefer Auth.js environment variable names (`AUTH_MICROSOFT_ENTRA_ID_*`), update `auth.ts` accordingly or map the variables in your deployment.
152
+
153
+ Step 3 — Startup command
154
+
155
+ - In App Service > Settings > Configuration, set Startup command:
156
+
157
+ ```
158
+ node server.js
159
+ ```
160
+
161
+ - This ensures the standalone Next.js server is launched on startup. Without it, you may see “next not found” in Log Stream and an Application Error on launch.
@@ -0,0 +1,108 @@
1
+ # EC Power Pages Single Page Application (SPA)
2
+
3
+ This is a Power Pages Single Page Application (SPA) built with React, Vite, and TypeScript, generated by `create-ec-app`. It follows Microsoft’s official Power Pages code site model, providing a modern, scalable, and maintainable frontend architecture tailored specifically for Power Pages environments. The app includes integrated authentication, data fetching, and UI options designed to work seamlessly within the Power Pages runtime.
4
+
5
+ ## Features
6
+
7
+ - **React + Vite + TypeScript**: Modern frontend stack for fast development and optimized builds.
8
+ - **Tailwind CSS**: Utility-first CSS framework with an optional Kendo UI preset for styling consistency.
9
+ - **UI Library Choice**: Select between Kendo UI (including theme selection) or Shadcn/ui components.
10
+ - **TanStack Query**: Preconfigured React Query provider for efficient data fetching and caching.
11
+ - **Power Pages AuthContext**: Integrated authentication context that detects the Power Pages user (`Microsoft.Dynamics365.Portal.User`) and retrieves tokens via `window.shell.getTokenDeferred()`.
12
+ - **Local Development Support**: Includes a mock token file and configuration to simulate authentication during local development.
13
+ - **Optimized Vite Build Output**: Builds optimized assets tailored for upload to Power Pages code sites.
14
+
15
+ ## Auth and API Access
16
+
17
+ This SPA integrates with Power Pages authentication and API access mechanisms. The `AuthContext` handles user detection and token retrieval using the Power Pages runtime API. Tokens are obtained asynchronously through `window.shell.getTokenDeferred()`, enabling authenticated API calls to Power Pages endpoints.
18
+
19
+ ### Example: API URL and Auth Headers
20
+
21
+ Here is an example of utility functions to build API URLs and set authentication headers for requests within the Power Pages SPA:
22
+
23
+ ```ts
24
+ // src/utils/api.ts
25
+
26
+ export function getApiUrl(entity: string, query = ''): string {
27
+ // Base path for Power Pages API (adjust if your site uses a different base)
28
+ const baseUrl = '/_api';
29
+ return `${baseUrl}/${entity}${query ? `?${query}` : ''}`;
30
+ }
31
+
32
+ export function getAuthHeaders(token: string) {
33
+ return {
34
+ Authorization: `Bearer ${token}`,
35
+ 'Content-Type': 'application/json',
36
+ Accept: 'application/json',
37
+ };
38
+ }
39
+ ```
40
+
41
+ ### Azure CLI Token Generation for Local Development
42
+
43
+ For local development and debugging outside the Power Pages runtime, you can generate an Azure AD token using the Azure CLI. This token can be used to simulate authenticated requests against your Power Pages environment:
44
+
45
+ ```bash
46
+ az account get-access-token --resource=https://<your-powerpages-environment>.crm.dynamics.com
47
+ ```
48
+
49
+ Replace `<your-powerpages-environment>` with your actual environment URL. Use the obtained token in your local mock token file or directly in API request headers.
50
+
51
+ ## Example Service Using TanStack Query
52
+
53
+ Here is an example of a service module `src/services/contacts.ts` that demonstrates fetching contacts data from the Power Pages API using TanStack Query:
54
+
55
+ ```ts
56
+ // src/services/contacts.ts
57
+ import { useQuery } from '@tanstack/react-query';
58
+ import { getApiUrl, getAuthHeaders } from '@/utils/api';
59
+ import { useAuth } from '@/context/AuthContext';
60
+
61
+ export interface Contact {
62
+ contactid: string;
63
+ firstname: string;
64
+ lastname: string;
65
+ emailaddress1?: string;
66
+ }
67
+
68
+ async function fetchContacts(token: string): Promise<Contact[]> {
69
+ const response = await fetch(getApiUrl('contacts'), {
70
+ headers: getAuthHeaders(token),
71
+ });
72
+ if (!response.ok) {
73
+ throw new Error('Failed to fetch contacts');
74
+ }
75
+ const data = await response.json();
76
+ return data.value;
77
+ }
78
+
79
+ export function useContacts() {
80
+ const { token } = useAuth();
81
+
82
+ return useQuery(['contacts'], () => fetchContacts(token!), {
83
+ enabled: !!token,
84
+ });
85
+ }
86
+ ```
87
+
88
+ This pattern leverages the `AuthContext` to access the current token and uses TanStack Query to manage caching and background updates.
89
+
90
+ ## Deployment
91
+
92
+ To deploy your Power Pages SPA:
93
+
94
+ - Build the project using `npm run build` or `npm run build:dev` for development builds.
95
+ - Upload the contents of the `dist` folder to your Power Pages site’s **code site**. This is a dedicated location for hosting SPA assets as static content.
96
+ - Reference your built JavaScript and CSS files in your Power Pages pages as needed.
97
+
98
+ For detailed guidance on Power Pages code sites and deployment, see the [Microsoft Power Pages documentation on code sites](https://learn.microsoft.com/en-us/power-pages/developer/code-sites).
99
+
100
+ Power Pages code sites support continuous deployment workflows via the Power Platform CLI, enabling streamlined updates and integration with source control.
101
+
102
+ ## Configuration
103
+
104
+ The `powerpages.config.json` file defines your site configuration, including output paths and landing page settings. Adjust this file to match your project and deployment conventions.
105
+
106
+ ---
107
+
108
+ This SPA template provides a robust foundation for building Power Pages applications with modern web development tools and best practices, ensuring smooth integration with the Power Pages platform and scalable, maintainable code.
@@ -0,0 +1,260 @@
1
+ # EC Webresource App
2
+
3
+ React + TypeScript template for Dynamics 365/Dataverse web resources. Generated by `create-ec-app`, it provides a lean setup with Vite, Tailwind CSS, and a choice of UI library (Kendo UI or Shadcn/ui), pre-configured for running inside Dynamics 365 and for local development.
4
+
5
+ ## Features
6
+
7
+ - React + TypeScript with Vite (Client side only)
8
+ - Tailwind CSS configured out of the box
9
+ - UI library choice: Kendo UI (with theme selection) or Shadcn/ui
10
+ - TanStack Query provider pre-wired (`QueryClientProvider` in `src/main.tsx`)
11
+ - XRM-aware runtime: adds `ClientGlobalContext.js.aspx` and detects `window.Xrm`
12
+ - Local development via `token.json` (excluded from bundling) and helper functions
13
+ - Vite build tuned for web resources: single JS bundle, `main.css`, deterministic names
14
+ - Zustand and `@types/xrm` included for state and typings
15
+
16
+ ## Prerequisites
17
+
18
+ - Node.js 18+ and npm 9+
19
+ - Dynamics 365/Dataverse environment for deployment
20
+ - Kendo UI license (only if you picked Kendo UI)
21
+
22
+ ## Getting Started
23
+
24
+ - Install dependencies (if needed): `npm install`
25
+ - For Kendo UI projects, activate your license: `npx kendo-ui-license activate` (you need to copy over your kendo-license.txt to the root to activate or stick with non-premium components)
26
+ - Start dev server: `npm run dev`
27
+ - Production build: `npm run build`
28
+ - Development build (readable, no minify): `npm run build:dev`
29
+
30
+ ## Key Files
31
+
32
+ - `src/main.tsx`: Sets up React, Tailwind, and TanStack Query. If Kendo UI was selected, imports the chosen theme CSS (`<theme>/dist/all.css`).
33
+ - `src/services/authService.ts`: Utilities to build API URLs and headers based on environment (inside Dynamics vs. local dev).
34
+ - `token.json`: Local development token store. Build is configured to treat this as external and not bundle it.
35
+ - `index.html`: Injects `ClientGlobalContext.js.aspx` for Dynamics runtime.
36
+ - `vite.config.ts`: Uses base `./`, disables code splitting, emits `main.css`, and places assets at the top of `dist`.
37
+
38
+ ## Auth and API Access
39
+
40
+ The app auto-detects whether it runs inside Dynamics 365 (uses `window.Xrm` and does not add an Authorization header) or locally (reads a bearer token from `token.json`).
41
+
42
+ ```ts
43
+ // src/services/authService.ts
44
+ export const getApiUrl = (): string => {
45
+ if (window.parent && window.parent.Xrm) {
46
+ const clientUrl = window.Xrm.Utility.getGlobalContext().getClientUrl();
47
+ return `${clientUrl}/api/data/v9.2`;
48
+ }
49
+ return "https://DOMAIN.REGION.dynamics.com/api/data/v9.2";
50
+ };
51
+
52
+ export const getAuthHeaders = async (): Promise<HeadersInit> => {
53
+ if (window.parent && window.parent.Xrm) {
54
+ return {
55
+ "Content-Type": "application/json",
56
+ "OData-MaxVersion": "4.0",
57
+ "OData-Version": "4.0",
58
+ Prefer: 'odata.include-annotations="*"',
59
+ };
60
+ }
61
+ const { default: token } = await import("../../token.json");
62
+ return {
63
+ Authorization: `Bearer ${token.accessToken}`,
64
+ "Content-Type": "application/json",
65
+ "OData-MaxVersion": "4.0",
66
+ "OData-Version": "4.0",
67
+ Prefer: 'odata.include-annotations="*"',
68
+ };
69
+ };
70
+ ```
71
+
72
+ Example usage:
73
+
74
+ ```ts
75
+ import { getApiUrl, getAuthHeaders } from "@/services/authService";
76
+
77
+ const res = await fetch(`${getApiUrl()}/accounts?$top=10`, {
78
+ headers: await getAuthHeaders(),
79
+ });
80
+ const data = await res.json();
81
+ ```
82
+
83
+ ## Azure CLI: Generate a Dataverse Token (Local Dev)
84
+
85
+ Use Azure CLI to obtain a bearer token for your Dataverse environment and paste it into `token.json` for local development.
86
+
87
+ Prerequisites:
88
+
89
+ - Azure CLI installed and you have access to the target tenant and environment.
90
+
91
+ Steps:
92
+
93
+ 1. Sign in to Azure (optionally targeting a specific tenant):
94
+
95
+ ```
96
+ az login --tenant <tenant-id>
97
+ ```
98
+
99
+ 2. Request an access token for your Dataverse environment URL (replace `<org>` and domain as applicable):
100
+
101
+ ```
102
+ az account get-access-token \
103
+ --tenant <tenant-id> \
104
+ --resource https://<org>.crm.dynamics.com \
105
+ ```
106
+
107
+ This prints the raw token to stdout. Copy it.
108
+
109
+ 3. Paste the token value into `token.json` under `accessToken`:
110
+
111
+ ```
112
+ {
113
+ "accessToken": "<paste-token-here>",
114
+ "expiresIn": "",
115
+ "expires_on": 0,
116
+ "subscription": "",
117
+ "tenant": "",
118
+ "tokenType": "Bearer"
119
+ }
120
+ ```
121
+
122
+ Notes:
123
+
124
+ - Tokens expire; regenerate as needed. (Max expiry is 1 hour)
125
+ - If your CLI requires scopes, use `--scope https://<org>.crm.dynamics.com/.default` instead of `--resource`.
126
+
127
+ One‑liner (writes full JSON to `token.json`):
128
+
129
+ ```
130
+ az account get-access-token --resource=https://<org>.crm.dynamics.com > token.json
131
+ ```
132
+
133
+ Replace the URL with your environment’s Dataverse URL (e.g., `https://<org>.crm.dynamics.com`, `https://<org>.crm4.dynamics.com`, etc.). This overwrites `token.json` at the project root with the full output from Azure CLI, which includes `accessToken` used by the local dev flow.
134
+
135
+ ## Accounts Data Service (TanStack Query)
136
+
137
+ The following example shows a minimal data service and hooks to fetch and update Accounts using the Dataverse Web API and TanStack Query.
138
+
139
+ Create `src/services/accounts.ts`:
140
+
141
+ ```ts
142
+ import { getApiUrl, getAuthHeaders } from "@/services/authService";
143
+ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
144
+
145
+ export interface Account {
146
+ accountid: string;
147
+ name?: string | null;
148
+ description?: string | null;
149
+ }
150
+
151
+ export const listAccounts = async (): Promise<Account[]> => {
152
+ const res = await fetch(
153
+ `${getApiUrl()}/accounts?$select=accountid,name,description&$top=50`,
154
+ { headers: await getAuthHeaders() }
155
+ );
156
+ if (!res.ok) throw new Error(`Failed to fetch accounts: ${res.status}`);
157
+ const json = await res.json();
158
+ return json.value as Account[];
159
+ };
160
+
161
+ export const patchAccount = async (
162
+ id: string,
163
+ data: Partial<Account>
164
+ ): Promise<void> => {
165
+ const headers = await getAuthHeaders();
166
+ const res = await fetch(`${getApiUrl()}/accounts(${id})`, {
167
+ method: "PATCH",
168
+ headers: {
169
+ ...headers,
170
+ },
171
+ body: JSON.stringify(data),
172
+ });
173
+ if (!res.ok) throw new Error(`Failed to update account: ${res.status}`);
174
+ };
175
+
176
+ export const useAccounts = () =>
177
+ useQuery({ queryKey: ["accounts"], queryFn: listAccounts });
178
+
179
+ export const useUpdateAccount = () => {
180
+ const qc = useQueryClient();
181
+ return useMutation({
182
+ mutationFn: ({ id, data }: { id: string; data: Partial<Account> }) =>
183
+ patchAccount(id, data),
184
+ onSuccess: () => {
185
+ qc.invalidateQueries({ queryKey: ["accounts"] });
186
+ },
187
+ });
188
+ };
189
+ ```
190
+
191
+ Usage in a component (e.g. `src/AccountsList.tsx`):
192
+
193
+ ```tsx
194
+ import { useAccounts, useUpdateAccount } from "@/services/accounts";
195
+
196
+ export function AccountsList() {
197
+ const { data, isLoading, error } = useAccounts();
198
+ const updateAccount = useUpdateAccount();
199
+
200
+ if (isLoading) return <div>Loading…</div>;
201
+ if (error) return <div>Failed to load accounts</div>;
202
+
203
+ return (
204
+ <ul className="space-y-2">
205
+ {data?.map((a) => (
206
+ <li key={a.accountid} className="flex items-center gap-2">
207
+ <span className="flex-1">{a.name ?? "(no name)"}</span>
208
+ <button
209
+ className="px-2 py-1 border rounded"
210
+ onClick={() =>
211
+ updateAccount.mutate({
212
+ id: a.accountid,
213
+ data: { name: `${a.name ?? ""}*` },
214
+ })
215
+ }
216
+ >
217
+ Rename
218
+ </button>
219
+ </li>
220
+ ))}
221
+ </ul>
222
+ );
223
+ }
224
+ ```
225
+
226
+ ## UI Libraries
227
+
228
+ - Kendo UI: Theme CSS is imported in `src/main.tsx`. Example:
229
+ ```tsx
230
+ import { Button } from "@progress/kendo-react-buttons";
231
+ <Button>Click me</Button>;
232
+ ```
233
+ - Shadcn/ui: Components are installed and available under the `@/components` alias. Example:
234
+ ```tsx
235
+ import { Button } from "@/components/ui/button";
236
+ <Button>Click me</Button>;
237
+ ```
238
+
239
+ ## Build Output
240
+
241
+ - Output directory: `dist`
242
+ - Single JavaScript bundle with deterministic name
243
+ - Single CSS file: `main.css`
244
+ - `base: "./"` to support deployment as a web resource
245
+
246
+ ## Deployment
247
+
248
+ - Upload files from `dist` to Dynamics 365/Dataverse as web resources.
249
+ - Use `index.html` as the HTML web resource; upload the JS bundle and `main.css` as script/style web resources referenced by it.
250
+ - Consider automating uploads with your preferred tooling (DevOps pipelines, XrmToolBox, etc.).
251
+ - One quick and easy way to handle deployment is with Webresource Manager.
252
+ - Open up webresource manager, and navigate to your specific solution
253
+ - Create a new root (example GlobalAccounts* or CustomDev*)
254
+ - Add a new folder for your webresources
255
+ - Upload your index.html, index.js and main.css to your folder.
256
+ - This will now allow you to use auto publisher to bind to your deployed resources.
257
+
258
+ ## Notes
259
+
260
+ - If you change the build, ensure code splitting stays disabled and asset names remain predictable to simplify web resource updates.