create-ec-app 0.0.5 → 0.0.6

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.
@@ -1,755 +0,0 @@
1
- import { execSync } from "node:child_process";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import chalk from "chalk";
5
- import fs from "fs-extra";
6
- import inquirer from "inquirer";
7
- import ora from "ora";
8
- // Helper function to run commands and show a spinner
9
- const runCommand = (command, spinnerMessage) => {
10
- const spinner = ora(spinnerMessage).start();
11
- try {
12
- execSync(command, { stdio: "pipe" });
13
- spinner.succeed();
14
- }
15
- catch (error) {
16
- spinner.fail();
17
- console.error(chalk.red(`Failed to execute command: ${command}`));
18
- console.error(error);
19
- process.exit(1);
20
- }
21
- };
22
- export const createPowerPagesApp = async (projectName) => {
23
- // Prompt for UI library choice
24
- const { uiLibrary } = await inquirer.prompt([
25
- {
26
- type: "list",
27
- name: "uiLibrary",
28
- message: "Which UI library would you like to use?",
29
- choices: [
30
- { name: "Kendo UI (React components with themes)", value: "kendo" },
31
- { name: "Shadcn/ui (Modern React components)", value: "shadcn" },
32
- ],
33
- },
34
- ]);
35
- let kendoThemePackage = "";
36
- if (uiLibrary === "kendo") {
37
- // Prompt for Kendo theme
38
- const kendoThemes = [
39
- { name: "Default", value: "@progress/kendo-theme-default" },
40
- { name: "Bootstrap (v5)", value: "@progress/kendo-theme-bootstrap" },
41
- { name: "Material (v3)", value: "@progress/kendo-theme-material" },
42
- { name: "Fluent", value: "@progress/kendo-theme-fluent" },
43
- { name: "Classic", value: "@progress/kendo-theme-classic" },
44
- ];
45
- const { kendoThemePackage: selectedTheme } = await inquirer.prompt([
46
- {
47
- type: "list",
48
- name: "kendoThemePackage",
49
- message: "Which Kendo UI theme would you like to install?",
50
- choices: kendoThemes,
51
- },
52
- ]);
53
- kendoThemePackage = selectedTheme;
54
- }
55
- const projectDir = path.resolve(process.cwd(), projectName);
56
- console.log(`\nScaffolding a new project in ${chalk.green(projectDir)}...\n`);
57
- // Create React + Vite (TypeScript) project
58
- runCommand(`npm create vite@latest ${projectName} -- --template react-ts`, "Creating Vite + React + TS project...");
59
- process.chdir(projectDir);
60
- // Update package.json with overrides for Vite 7 compatibility
61
- const packageJsonPath = path.join(projectDir, "package.json");
62
- const packageJson = fs.readJsonSync(packageJsonPath);
63
- // Add custom build script
64
- packageJson.scripts["build:dev"] = "tsc -b && vite build --mode development";
65
- fs.writeJsonSync(packageJsonPath, packageJson, { spaces: 2 });
66
- ora("Updated package.json with Vite 7 overrides and custom build script").succeed();
67
- // Install dependencies based on UI library choice
68
- const dependencies = [
69
- "tailwindcss@^4",
70
- "@tailwindcss/vite@^4",
71
- "@tanstack/react-query",
72
- "zustand",
73
- "@types/xrm",
74
- "@types/node",
75
- ];
76
- if (uiLibrary === "kendo") {
77
- dependencies.push("@progress/kendo-react-buttons", "@progress/kendo-licensing", kendoThemePackage);
78
- }
79
- const devDependencies = [];
80
- if (uiLibrary === "shadcn") {
81
- devDependencies.push("tailwindcss-animate");
82
- }
83
- const installMessage = uiLibrary === "kendo"
84
- ? `Installing dependencies (Kendo Theme: ${kendoThemePackage.split("/")[1]})...`
85
- : "Installing dependencies (Shadcn/ui)...";
86
- runCommand(`npm install ${dependencies.join(" ")}`, installMessage);
87
- if (devDependencies.length > 0) {
88
- runCommand(`npm install -D ${devDependencies.join(" ")}`, "Installing dev dependencies...");
89
- }
90
- if (uiLibrary === "kendo") {
91
- // Create kendo-tw-preset.js
92
- const kendoPresetPath = path.join(projectDir, "kendo-tw-preset.js");
93
- const kendoPresetContent = `module.exports = {
94
- theme: {
95
- extend: {
96
- spacing: {
97
- 1: "var( --kendo-spacing-1 )",
98
- 1.5: "var( --kendo-spacing-1.5 )",
99
- 2: "var( --kendo-spacing-2 )",
100
- 2.5: "var( --kendo-spacing-2.5 )",
101
- 3: "var( --kendo-spacing-3 )",
102
- 3.5: "var( --kendo-spacing-3.5 )",
103
- 4: "var( --kendo-spacing-4 )",
104
- 4.5: "var( --kendo-spacing-4.5 )",
105
- 5: "var( --kendo-spacing-5 )",
106
- 5.5: "var( --kendo-spacing-5.5 )",
107
- 6: "var( --kendo-spacing-6 )",
108
- 6.5: "var( --kendo-spacing-6.5 )",
109
- 7: "var( --kendo-spacing-7 )",
110
- 7.5: "var( --kendo-spacing-7.5 )",
111
- 8: "var( --kendo-spacing-8 )",
112
- 9: "var( --kendo-spacing-9 )",
113
- 10: "var( --kendo-spacing-10 )",
114
- 11: "var( --kendo-spacing-11 )",
115
- 12: "var( --kendo-spacing-12 )",
116
- 13: "var( --kendo-spacing-13 )",
117
- 14: "var( --kendo-spacing-14 )",
118
- 15: "var( --kendo-spacing-15 )",
119
- 16: "var( --kendo-spacing-16 )",
120
- 17: "var( --kendo-spacing-17 )",
121
- 18: "var( --kendo-spacing-18 )",
122
- 19: "var( --kendo-spacing-19 )",
123
- 20: "var( --kendo-spacing-20 )",
124
- 21: "var( --kendo-spacing-21 )",
125
- 22: "var( --kendo-spacing-22 )",
126
- 23: "var( --kendo-spacing-23 )",
127
- 24: "var( --kendo-spacing-24 )",
128
- 25: "var( --kendo-spacing-25 )",
129
- 26: "var( --kendo-spacing-26 )",
130
- 27: "var( --kendo-spacing-27 )",
131
- 28: "var( --kendo-spacing-28 )",
132
- 29: "var( --kendo-spacing-29 )",
133
- 30: "var( --kendo-spacing-30 )",
134
- },
135
- borderRadius: {
136
- none: "var( --kendo-border-radius-none )",
137
- sm: "var( --kendo-border-radius-sm )",
138
- DEFAULT: "var( --kendo-border-radius-md )",
139
- lg: "var( --kendo-border-radius-lg )",
140
- xl: "var( --kendo-border-radius-xl )",
141
- "2xl": "var( --kendo-border-radius-xxl )",
142
- "3xl": "var( --kendo-border-radius-xxxl )",
143
- full: "var( --kendo-border-radius-none )",
144
- },
145
- boxShadow: {
146
- sm: "var( --kendo-elevation-2 )",
147
- DEFAULT: "var( --kendo-elevation-4 )",
148
- lg: "var( --kendo-elevation-6 )",
149
- xl: "var( --kendo-elevation-8 )",
150
- "2xl": "var( --keno-elevation-9 )",
151
- },
152
- colors: {
153
- "app-surface": "var( --kendo-color-app-surface )",
154
- "on-app-surface": "var( --kendo-color-on-app-surface )",
155
- subtle: "var( --kendo-color-subtle )",
156
- surface: "var( --kendo-color-surface )",
157
- "surface-alt": "var( --kendo-color-surface-alt )",
158
- border: "var( --kendo-color-border )",
159
- "border-alt": "var( --kendo-color-border-alt )",
160
- },
161
- },
162
- },
163
- };`;
164
- fs.writeFileSync(kendoPresetPath, kendoPresetContent, "utf8");
165
- ora("Created kendo-tw-preset.js").succeed();
166
- // Create tailwind.config.js with Kendo preset
167
- const tailwindConfigPath = path.join(projectDir, "tailwind.config.js");
168
- const tailwindConfigContent = `/** @type {import('tailwindcss').Config} */
169
-
170
- import kendoTwPreset from "./kendo-tw-preset.js";
171
-
172
- export default {
173
- content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
174
- presets: [kendoTwPreset],
175
- theme: {
176
- extend: {
177
- colors: {},
178
- },
179
- },
180
- plugins: [],
181
- };`;
182
- fs.writeFileSync(tailwindConfigPath, tailwindConfigContent, "utf8");
183
- ora("Created tailwind.config.js with Kendo preset").succeed();
184
- }
185
- else {
186
- // Basic Tailwind config for Shadcn/ui
187
- const tailwindConfigPath = path.join(projectDir, "tailwind.config.js");
188
- const tailwindConfigContent = `/** @type {import('tailwindcss').Config} */
189
-
190
- export default {
191
- content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
192
- theme: { extend: {} },
193
- plugins: [],
194
- };`;
195
- fs.writeFileSync(tailwindConfigPath, tailwindConfigContent, "utf8");
196
- ora("Created tailwind.config.js").succeed();
197
- // Align TypeScript config with Shadcn setup (like webresource creator)
198
- const tsconfigPath = path.join(projectDir, "tsconfig.json");
199
- const tsConfigContent = `{
200
- "files": [],
201
- "references": [
202
- {
203
- "path": "./tsconfig.app.json"
204
- },
205
- {
206
- "path": "./tsconfig.node.json"
207
- }
208
- ],
209
- "compilerOptions": {
210
- "baseUrl": ".",
211
- "paths": {
212
- "@/*": [
213
- "./src/*"
214
- ]
215
- }
216
- }
217
- }`;
218
- fs.writeFileSync(tsconfigPath, tsConfigContent, "utf8");
219
- ora("Updated tsconfig.json with baseUrl and paths").succeed();
220
- const tsconfigAppPath = path.join(projectDir, "tsconfig.app.json");
221
- const tsConfigAppContent = `{
222
- "compilerOptions": {
223
- "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
224
- "target": "ES2022",
225
- "useDefineForClassFields": true,
226
- "lib": ["ES2022", "DOM", "DOM.Iterable"],
227
- "module": "ESNext",
228
- "skipLibCheck": true,
229
-
230
- /* Shadcn */
231
- "baseUrl": ".",
232
- "paths": {
233
- "@/*": [
234
- "./src/*"
235
- ]
236
- },
237
-
238
- /* Bundler mode */
239
- "moduleResolution": "bundler",
240
- "allowImportingTsExtensions": true,
241
- "moduleDetection": "force",
242
- "noEmit": true,
243
- "jsx": "react-jsx",
244
-
245
- /* Linting */
246
- "strict": true,
247
- "noUnusedLocals": true,
248
- "noUnusedParameters": true,
249
- "noFallthroughCasesInSwitch": true,
250
- },
251
- "include": ["src"]
252
- }
253
- `;
254
- fs.writeFileSync(tsconfigAppPath, tsConfigAppContent, "utf8");
255
- ora("Created tsconfig.app.json").succeed();
256
- }
257
- // Overwrite Vite config with advanced build options
258
- const viteConfigPath = path.join(projectDir, "vite.config.ts");
259
- const newViteConfigContent = `import path from "path";
260
- import tailwindcss from "@tailwindcss/vite";
261
- import { defineConfig } from "vite";
262
- import react from "@vitejs/plugin-react";
263
-
264
- // https://vite.dev/config/
265
- export default defineConfig({
266
- plugins: [react(), tailwindcss()],
267
- resolve: {
268
- alias: {
269
- "@": path.resolve(__dirname, "./src"),
270
- },
271
- },
272
- });
273
- `;
274
- fs.writeFileSync(viteConfigPath, newViteConfigContent, "utf8");
275
- ora("Replaced vite.config.ts with custom build config").succeed();
276
- // Update CSS entry point
277
- const indexCssPath = path.join(projectDir, "src", "index.css");
278
- fs.writeFileSync(indexCssPath, `@import "tailwindcss";`, "utf8");
279
- ora("Updated CSS entry point").succeed();
280
- // Add global type declarations for asset modules
281
- const globalDtsPath = path.join(projectDir, "src", "global.d.ts");
282
- const globalDtsContent = `declare module "*.css";
283
- declare module "*.scss";
284
- declare module "*.svg";
285
- declare module "*.png";
286
- `;
287
- fs.writeFileSync(globalDtsPath, globalDtsContent, "utf8");
288
- ora("Created src/global.d.ts").succeed();
289
- // Clear App.css
290
- const appCssPath = path.join(projectDir, "src", "App.css");
291
- fs.writeFileSync(appCssPath, "", "utf8");
292
- ora("Cleared App.css").succeed();
293
- // Create shared AuthButton component
294
- const componentsDir = path.join(projectDir, "src", "components");
295
- const sharedDir = path.join(componentsDir, "shared");
296
- fs.ensureDirSync(sharedDir);
297
- const authButtonPath = path.join(sharedDir, "AuthButton.tsx");
298
- const authButtonContent = `import { useAuth } from '../../context/AuthContext';
299
-
300
- export const AuthButton = () => {
301
- const { user, isAuthenticated, isLoading, tenantId, token } = useAuth();
302
-
303
- if (isLoading) {
304
- return (
305
- <div className="flex items-center gap-2 text-sm text-gray-600">
306
- <span className="w-4 h-4 border-2 border-gray-300 rounded-full animate-spin border-t-transparent" />
307
- <span>Loading...</span>
308
- </div>
309
- );
310
- }
311
-
312
- if (isAuthenticated && user) {
313
- return (
314
- <div className="flex items-center gap-3">
315
- <span className="text-sm text-gray-700">
316
- Welcome {user.firstName} {user.lastName}
317
- </span>
318
- <button
319
- type="button"
320
- className="px-3 py-1 text-sm font-medium text-gray-700 transition border border-gray-300 rounded hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2"
321
- onClick={() => { window.location.href = '/Account/Login/LogOff?returnUrl=%2F'; }}
322
- >
323
- Logout
324
- </button>
325
- </div>
326
- );
327
- }
328
-
329
- return (
330
- <form action="/Account/Login/ExternalLogin" method="post" className="flex items-center gap-3">
331
- <input name="__RequestVerificationToken" type="hidden" value={token ?? ''} />
332
- <button
333
- name="provider"
334
- type="submit"
335
- className="px-3 py-1 text-sm font-medium text-gray-700 transition border border-gray-300 rounded hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2"
336
- value={\`https://login.windows.net/\${tenantId}/\`}
337
- >
338
- Log In
339
- </button>
340
- </form>
341
- );
342
- };
343
- `;
344
- fs.writeFileSync(authButtonPath, authButtonContent, "utf8");
345
- ora("Created AuthButton.tsx in src/components/shared").succeed();
346
- // Replace App.tsx with custom content
347
- const appTsxPath = path.join(projectDir, "src", "App.tsx");
348
- let newAppTsxContent = `import "./App.css";
349
- import { AuthProvider } from './context/AuthContext'
350
- import { AuthButton } from './components/shared/AuthButton'
351
-
352
- function App() {
353
- return (
354
- <>
355
- <AuthProvider>
356
- <div className="flex flex-col items-center justify-center h-screen gap-4">
357
- <div>Hello World!</div>
358
- <AuthButton />
359
- </div>
360
- </AuthProvider>
361
- </>
362
- );
363
- }
364
-
365
- export default App;
366
- `;
367
- if (uiLibrary === "shadcn") {
368
- newAppTsxContent = `import "./App.css";
369
- import { AuthProvider } from './context/AuthContext'
370
- import { AuthButton } from './components/shared/AuthButton'
371
- import { Button } from "@/components/ui/button";
372
-
373
- function App() {
374
- return (
375
- <>
376
- <AuthProvider>
377
- <div className="flex flex-col items-center justify-center h-screen gap-4">
378
- <div>Hello World!</div>
379
- <Button>Click me</Button>
380
- <AuthButton />
381
- </div>
382
- </AuthProvider>
383
- </>
384
- );
385
- }
386
-
387
- export default App;
388
- `;
389
- }
390
- fs.writeFileSync(appTsxPath, newAppTsxContent, "utf8");
391
- ora("Replaced App.tsx with custom template").succeed();
392
- // Generate main.tsx from template
393
- const mainTsxPath = path.join(projectDir, "src", "main.tsx");
394
- const kendoImport = uiLibrary === "kendo" ? `import "${kendoThemePackage}/dist/all.css";` : "";
395
- const newMainTsxContent = `import { StrictMode } from "react";
396
- import { createRoot } from "react-dom/client";
397
- import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
398
- ${kendoImport}
399
- import "./index.css";
400
- import App from "./App.tsx";
401
-
402
- const queryClient = new QueryClient({
403
- defaultOptions: {
404
- queries: {
405
- refetchOnWindowFocus: false,
406
- retry: 3,
407
- staleTime: 5 * 60 * 1000, // 5 minutes
408
- },
409
- mutations: {
410
- retry: 1,
411
- },
412
- },
413
- });
414
-
415
- const root = createRoot(document.getElementById("root")!);
416
-
417
- root.render(
418
- <StrictMode>
419
- <QueryClientProvider client={queryClient}>
420
- <App />
421
- </QueryClientProvider>
422
- </StrictMode>
423
- );
424
- `;
425
- fs.writeFileSync(mainTsxPath, newMainTsxContent, "utf8");
426
- ora("Generated custom main.tsx with providers").succeed();
427
- // Initialize Shadcn/ui if selected
428
- if (uiLibrary === "shadcn") {
429
- runCommand("npx shadcn@latest init --force --silent --yes --base-color neutral", "Initializing Shadcn/ui...");
430
- runCommand("npx shadcn@latest add --all", "Installing all Shadcn components...");
431
- }
432
- // Modify index.html
433
- const indexPath = path.join(projectDir, "index.html");
434
- let indexContent = fs.readFileSync(indexPath, "utf8");
435
- indexContent = indexContent.replace("<title>Vite + React + TS</title>", "<title>EC | Vite + React + TS + Kendo UI + Tailwind</title>");
436
- fs.writeFileSync(indexPath, indexContent, "utf8");
437
- ora("Updated index.html").succeed();
438
- // Add .prettierrc in root directory
439
- const prettierRcPath = path.join(projectDir, ".prettierrc");
440
- const prettierRcContent = `{
441
- "tabWidth": 4,
442
- "useTabs": true,
443
- "semi": true,
444
- "singleQuote": false,
445
- "trailingComma": "es5",
446
- "bracketSpacing": true,
447
- "jsxBracketSameLine": false,
448
- "arrowParens": "always",
449
- "printWidth": 120
450
- }`;
451
- fs.writeFileSync(prettierRcPath, prettierRcContent, "utf8");
452
- ora("Added .prettierrc").succeed();
453
- // Ensure TS path alias for '@' exists (for Kendo branch, Shadcn gets explicit files above)
454
- if (uiLibrary === "kendo") {
455
- try {
456
- const tsConfigPath = path.join(projectDir, "tsconfig.json");
457
- const raw = fs.readFileSync(tsConfigPath, "utf8");
458
- const tsConfig = JSON.parse(raw);
459
- tsConfig.compilerOptions = tsConfig.compilerOptions || {};
460
- tsConfig.compilerOptions.baseUrl = tsConfig.compilerOptions.baseUrl || ".";
461
- tsConfig.compilerOptions.paths = {
462
- ...(tsConfig.compilerOptions.paths || {}),
463
- "@/*": ["./src/*"],
464
- };
465
- fs.writeFileSync(tsConfigPath, JSON.stringify(tsConfig, null, 2), "utf8");
466
- ora("Updated tsconfig.json with path alias '@'").succeed();
467
- }
468
- catch (_) {
469
- ora("Warning: could not update tsconfig.json with alias").warn();
470
- }
471
- }
472
- // Add in the AuthContext
473
- const contextDir = path.join(projectDir, "src", "context");
474
- fs.ensureDirSync(contextDir);
475
- const authContextPath = path.join(contextDir, "AuthContext.tsx");
476
- const authContextContent = `import React, { createContext, useContext, useState, useEffect } from 'react';
477
- import type { ReactNode } from 'react';
478
-
479
- interface User {
480
- username: string;
481
- firstName: string;
482
- lastName: string;
483
- email?: string;
484
- userRoles: string[];
485
- }
486
-
487
- interface AuthContextType {
488
- user: User | null;
489
- isAuthenticated: boolean;
490
- isLoading: boolean;
491
- tenantId: string;
492
- token: string;
493
- refreshToken: () => Promise<void>;
494
- }
495
-
496
- const AuthContext = createContext<AuthContextType | undefined>(undefined);
497
-
498
- interface AuthProviderProps {
499
- children: ReactNode;
500
- }
501
-
502
- export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
503
- const [user, setUser] = useState<User | null>(null);
504
- const [isLoading, setIsLoading] = useState(true);
505
- const [token, setToken] = useState<string>('');
506
-
507
- const refreshToken = async (): Promise<void> => {
508
- try {
509
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
510
- if ((window as any).shell?.getTokenDeferred) {
511
- const newToken = await (window as any).shell.getTokenDeferred();
512
- setToken(newToken || '');
513
- } else if (import.meta.env.DEV) {
514
- // Local development: read from token.json via fetch
515
- const resp = await fetch('/token.json');
516
- const data = await resp.json();
517
- setToken(data.accessToken || '');
518
- }
519
- } catch (error) {
520
- console.error('Error fetching token:', error);
521
- setToken('');
522
- }
523
- };
524
-
525
- useEffect(() => {
526
- const initializeAuth = async () => {
527
- try {
528
- // Check if Microsoft Dynamics Portal object exists
529
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
530
- const portalUser = (window as any)?.Microsoft?.Dynamic365?.Portal?.User;
531
-
532
- if (portalUser) {
533
- const username = portalUser.userName || '';
534
- const firstName = portalUser.firstName || '';
535
- const lastName = portalUser.lastName || '';
536
- const email = portalUser.email || '';
537
- const userRoles = portalUser.userRoles || [];
538
-
539
- if (username) {
540
- setUser({
541
- username,
542
- firstName,
543
- lastName,
544
- email,
545
- userRoles
546
- });
547
- }
548
- }
549
-
550
- // Get authentication token
551
- await refreshToken();
552
- } catch (error) {
553
- console.error('Error initializing authentication:', error);
554
- } finally {
555
- setIsLoading(false);
556
- }
557
- };
558
-
559
- initializeAuth();
560
- }, []);
561
-
562
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
563
- const tenantId = (window as any)?.Microsoft?.Dynamic365?.Portal?.tenant || '';
564
- const isAuthenticated = user !== null && user.username !== '';
565
-
566
- const contextValue: AuthContextType = {
567
- user,
568
- isAuthenticated,
569
- isLoading,
570
- tenantId,
571
- token,
572
- refreshToken
573
- };
574
-
575
- return (
576
- <AuthContext.Provider value={contextValue}>
577
- {children}
578
- </AuthContext.Provider>
579
- );
580
- };
581
-
582
- // eslint-disable-next-line react-refresh/only-export-components
583
- export const useAuth = (): AuthContextType => {
584
- const context = useContext(AuthContext);
585
- if (context === undefined) {
586
- throw new Error('useAuth must be used within an AuthProvider');
587
- }
588
- return context;
589
- };`;
590
- fs.writeFileSync(authContextPath, authContextContent, "utf8");
591
- ora("Created AuthContext.tsx in src/context").succeed();
592
- // Create authService.ts for API URL and Auth Headers logic
593
- const servicesDir = path.join(projectDir, "src", "services");
594
- fs.ensureDirSync(servicesDir);
595
- const authServicePath = path.join(servicesDir, "authService.ts");
596
- const authServiceContent = `
597
- /// <reference types="vite/client" />
598
-
599
- const getAuthToken = async (): Promise<string> => {
600
- const tokenModule = await import("../../token.json");
601
- const token = tokenModule.default.accessToken;
602
- return token;
603
- };
604
-
605
- /**
606
- * AuthService: Helper functions for API URL and Auth headers.
607
- * Supports Power Pages runtime and local development with token.json.
608
- */
609
-
610
- // Returns either '/_api' (Power Pages) or a remote URL (from token.json) in local dev
611
- export async function getApiUrl(): Promise<string> {
612
- // Power Pages runtime
613
- if (typeof window !== 'undefined' && (window as any).shell?.getTokenDeferred) {
614
- return '/_api';
615
- }
616
-
617
- // Local dev use Dynamics API directly
618
- if (typeof window !== 'undefined' && import.meta.env.DEV) {
619
- return "https://DOMAIN.ENV.dynamics.com/api/data/v9.2";
620
- }
621
- // fallback
622
- return '/_api';
623
- }
624
-
625
- // Returns authentication headers for fetch requests
626
- export async function getAuthHeaders(): Promise<HeadersInit> {
627
- // Power Pages runtime
628
- if (typeof window !== 'undefined' && (window as any).shell?.getTokenDeferred) {
629
- const token = await (window as any).shell.getTokenDeferred();
630
- return {
631
- Authorization: \`Bearer \${token}\`
632
- };
633
- }
634
-
635
- // Local dev: read from token.json
636
- if (typeof window !== 'undefined' && import.meta.env.DEV) {
637
- try {
638
- const token = await getAuthToken();
639
- if (token) {
640
- return {
641
- Authorization: \`Bearer \${token}\`
642
- };
643
- }
644
- } catch { }
645
- }
646
- // fallback
647
- return {};
648
- }
649
-
650
- `;
651
- fs.writeFileSync(authServicePath, authServiceContent, "utf8");
652
- ora("Created authService.ts in src/services").succeed();
653
- // Create token.json at project root with placeholder token for local dev
654
- const tokenJsonPath = path.join(projectDir, "token.json");
655
- const tokenJsonContent = `{
656
- "accessToken": "<your-token-here>",
657
- "expiresIn": "",
658
- "expires_on": 0,
659
- "tenant": "",
660
- "tokenType": "Bearer"
661
- }
662
- `;
663
- fs.writeFileSync(tokenJsonPath, tokenJsonContent, "utf8");
664
- ora("Created token.json for local development").succeed();
665
- // Add token.json to .gitignore
666
- const gitignorePath = path.join(projectDir, ".gitignore");
667
- let gitignoreContent = "";
668
- if (fs.existsSync(gitignorePath)) {
669
- gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
670
- if (!gitignoreContent.includes("token.json")) {
671
- gitignoreContent += "\ntoken.json\n";
672
- fs.writeFileSync(gitignorePath, gitignoreContent, "utf8");
673
- ora("Added token.json to .gitignore").succeed();
674
- }
675
- }
676
- else {
677
- fs.writeFileSync(gitignorePath, "token.json\n", "utf8");
678
- ora("Created .gitignore and added token.json").succeed();
679
- }
680
- // Print reminder for user to fill in token.json
681
- console.log(chalk.yellowBright("\nReminder: For local development, please populate 'token.json' at the project root with a valid access token and remoteUrl if needed.\n"));
682
- // Create DataService.ts using getApiUrl/getAuthHeaders (for future-proofing)
683
- const dataServicePath = path.join(servicesDir, "DataService.ts");
684
- const dataServiceContent = `import { getApiUrl, getAuthHeaders } from "./authService";
685
-
686
- export interface T {
687
- entityId: string;
688
- [key: string]: unknown;
689
- }
690
-
691
- export interface ApiResponse<T> {
692
- value: T[];
693
- }
694
-
695
- export async function getEntity(): Promise<T[]> {
696
- const baseUrl = await getApiUrl();
697
- const headers = await getAuthHeaders();
698
- const response = await fetch(\`\${baseUrl}/entity\`, {
699
- method: 'GET',
700
- headers: {
701
- ...headers,
702
- 'Accept': 'application/json',
703
- 'Content-Type': 'application/json',
704
- },
705
- });
706
-
707
- if (!response.ok) {
708
- throw new Error(\`Failed to fetch entity: \${response.status}\`);
709
- }
710
-
711
- const data: ApiResponse<T> = await response.json();
712
- return data.value;
713
- }
714
- `;
715
- fs.writeFileSync(dataServicePath, dataServiceContent, "utf8");
716
- ora("Created DataService.ts in src/services").succeed();
717
- // Add Power Pages configuration file
718
- const powerPagesConfigPath = path.join(projectDir, "powerpages.config.json");
719
- const powerPagesConfigContent = `{
720
- "compiledPath": "dist",
721
- "siteName": "MySite",
722
- "defaultLandingPage": "index.html"
723
- }
724
- `;
725
- fs.writeFileSync(powerPagesConfigPath, powerPagesConfigContent, "utf8");
726
- ora("Created powerpages.config.json").succeed();
727
- // Replace README.md with template
728
- try {
729
- const currentFileDir = path.dirname(fileURLToPath(import.meta.url));
730
- const candidates = [
731
- path.resolve(currentFileDir, "../readmes/powerpages.md"), // dist layout
732
- path.resolve(currentFileDir, "../../src/readmes/powerpages.md"), // repo layout
733
- ];
734
- let templatePath = "";
735
- for (const c of candidates) {
736
- if (fs.existsSync(c)) {
737
- templatePath = c;
738
- break;
739
- }
740
- }
741
- const readmeContent = templatePath
742
- ? fs.readFileSync(templatePath, "utf8")
743
- : `# EC Power Pages App\n\nSee documentation inside create-ec-app (powerpages README template).`;
744
- fs.writeFileSync(path.join(projectDir, "README.md"), readmeContent, "utf8");
745
- ora("Added README.md from template").succeed();
746
- }
747
- catch (_) {
748
- ora("Failed to add README.md from template; keeping default README").warn();
749
- }
750
- // Initialize Git
751
- runCommand("git init", "Initializing Git repository...");
752
- runCommand("git add .", "Staging files for initial commit...");
753
- runCommand(`git commit -m "Initial commit from create-ec-app"`, "Creating initial commit...");
754
- };
755
- //# sourceMappingURL=powerpages.js.map