create-powerapp 1.0.1 → 1.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.
package/index.js CHANGED
@@ -1,964 +1,868 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * create-powerapp
5
- * One command to scaffold any Power Apps project — Canvas App, Model-Driven App,
6
- * PCF Component, Power Pages, or Code App — with Git, AI context files, and
7
- * everything ready so you can immediately start prompting your AI assistant.
8
- */
9
-
10
- import { execSync, spawnSync } from "child_process";
11
- import fs from "fs";
12
- import path from "path";
13
- import readline from "readline";
14
- import { fileURLToPath } from "url";
15
-
16
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
-
18
- // ─── Colour helpers ──────────────────────────────────────────────────────────
19
- const c = {
20
- reset: "\x1b[0m",
21
- bold: "\x1b[1m",
22
- green: "\x1b[32m",
23
- cyan: "\x1b[36m",
24
- yellow: "\x1b[33m",
25
- red: "\x1b[31m",
26
- dim: "\x1b[2m",
27
- magenta: "\x1b[35m",
28
- };
29
- const log = {
30
- info: (m) => console.log(`${c.cyan}ℹ${c.reset} ${m}`),
31
- ok: (m) => console.log(`${c.green}✔${c.reset} ${m}`),
32
- warn: (m) => console.log(`${c.yellow}⚠${c.reset} ${m}`),
33
- error: (m) => console.log(`${c.red}✖${c.reset} ${m}`),
34
- step: (m) => console.log(`\n${c.bold}${c.magenta}▶ ${m}${c.reset}`),
35
- dim: (m) => console.log(`${c.dim} ${m}${c.reset}`),
36
- };
37
-
38
- // ─── readline helper ─────────────────────────────────────────────────────────
39
- // Single persistent readline interface — works interactively AND when piped (CI)
40
- const rl = readline.createInterface({
41
- input: process.stdin,
42
- output: process.stdout,
43
- terminal: false, // don't assume TTY — makes piped input work in CI
44
- });
45
-
46
- // Queue of lines already read from stdin (for piped / non-interactive mode)
47
- const lineQueue = [];
48
- const lineWaiters = [];
49
- rl.on("line", (line) => {
50
- if (lineWaiters.length > 0) {
51
- lineWaiters.shift()(line.trim());
52
- } else {
53
- lineQueue.push(line.trim());
54
- }
55
- });
56
-
57
- function prompt(question) {
58
- process.stdout.write(question);
59
- return new Promise((resolve) => {
60
- if (lineQueue.length > 0) {
61
- const line = lineQueue.shift();
62
- process.stdout.write(line + "\n"); // echo the pre-buffered answer
63
- resolve(line);
64
- } else {
65
- lineWaiters.push(resolve);
66
- }
67
- });
68
- }
69
-
70
- async function choose(question, options) {
71
- console.log(`\n${c.bold}${question}${c.reset}`);
72
- options.forEach((o, i) => console.log(` ${c.cyan}${i + 1}${c.reset}. ${o.label} ${c.dim}${o.desc || ""}${c.reset}`));
73
- while (true) {
74
- const answer = await prompt(`\nEnter number (1–${options.length}): `);
75
- const idx = parseInt(answer, 10) - 1;
76
- if (idx >= 0 && idx < options.length) return options[idx];
77
- log.warn("Please enter a valid number.");
78
- }
79
- }
80
-
81
- // ─── run shell command ────────────────────────────────────────────────────────
82
- function run(cmd, cwd = process.cwd(), silent = false) {
83
- const result = spawnSync(cmd, { shell: true, cwd, stdio: silent ? "pipe" : "inherit" });
84
- if (result.status !== 0 && !silent) {
85
- log.error(`Command failed: ${cmd}`);
86
- }
87
- return result.status === 0;
88
- }
89
-
90
- // ─── check a CLI tool exists ──────────────────────────────────────────────────
91
- function has(tool) {
92
- return spawnSync(`${tool} --version`, { shell: true, stdio: "pipe" }).status === 0;
93
- }
94
-
95
- // ─── write file helper ────────────────────────────────────────────────────────
96
- function write(filePath, content) {
97
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
98
- fs.writeFileSync(filePath, content, "utf8");
99
- }
100
-
101
- // ─── shared file content ──────────────────────────────────────────────────────
102
-
103
- function gitignoreContent() {
104
- return `# Dependencies
105
- node_modules/
106
- .pnp
107
- .pnp.js
108
-
109
- # Environment variables — NEVER commit these
110
- .env
111
- .env.local
112
- .env.*.local
113
-
114
- # Build outputs
115
- dist/
116
- build/
117
- out/
118
- .next/
119
- bin/
120
-
121
- # Editor files
122
- .vscode/
123
- .idea/
124
- *.swp
125
-
126
- # OS files
127
- .DS_Store
128
- Thumbs.db
129
-
130
- # Logs
131
- *.log
132
- npm-debug.log*
133
-
134
- # Power Apps / PAC CLI outputs
135
- *.msapp
136
- *.zip
137
- /obj/
138
- `;
139
- }
140
-
141
- function envContent(projectName, appType) {
142
- return `# .env — DO NOT commit this file to Git
143
- # Fill in the values for your project
144
-
145
- # Your Power Platform environment URL
146
- # Find it at: make.powerapps.com → Settings gear → Session details → Instance url
147
- POWER_PLATFORM_URL=https://yourorg.crm.dynamics.com
148
-
149
- # Project info
150
- PROJECT_NAME=${projectName}
151
- APP_TYPE=${appType}
152
-
153
- # Add any other secrets here (API keys, database URLs, etc.)
154
- # API_KEY=your-key-here
155
- `;
156
- }
157
-
158
- function readmeContent(projectName, appType, aiTool) {
159
- return `# ${projectName}
160
-
161
- **Type:** ${appType}
162
- **AI Assistant:** ${aiTool}
163
-
164
- ## Getting started
165
-
166
- Everything is already set up. Just open your AI assistant and describe what you want to build.
167
-
168
- ### Start your AI session
169
-
170
- ${aiTool === "Claude Code"
171
- ? "```bash\nclaude\n```\nThen type what you want to build in plain English."
172
- : "Click the **Copilot Chat** icon (💬) in the VS Code sidebar and type what you want to build."}
173
-
174
- ### Example first prompts
175
-
176
- > "Create the main screen for this app with a navigation menu and a welcome message."
177
-
178
- > "Add a form that lets users submit [whatever your app needs] and saves it to Dataverse."
179
-
180
- > "Explain what files are in this project and what each one does."
181
-
182
- ## Project structure
183
-
184
- See the \`instructions/\` folder for the AI context files that tell your AI assistant
185
- how this type of Power App works.
186
-
187
- ## Useful commands
188
-
189
- \`\`\`bash
190
- # Push changes to Power Platform
191
- ${appType === "Canvas App" ? "pac canvas pack --msapp ./CanvasApp.msapp --sources ./canvas-src\npac solution push" : ""}
192
- ${appType === "Model-Driven App" ? "pac solution push" : ""}
193
- ${appType === "PCF Component" ? "npm run build\npac pcf push --publisher-prefix yourprefix" : ""}
194
- ${appType === "Power Pages" ? "pac pages upload --path ." : ""}
195
- ${appType === "Code App" ? "npm run build\npac code push" : ""}
196
-
197
- # Save your work
198
- git add .
199
- git commit -m "describe what you changed"
200
- git push
201
- \`\`\`
202
- `;
203
- }
204
-
205
- // ─── App-type scaffolders ─────────────────────────────────────────────────────
206
-
207
- function scaffoldCanvas(dir, projectName, aiTool) {
208
- log.info("Setting up Canvas App structure...");
209
-
210
- // AI context files
211
- write(path.join(dir, "instructions", "canvas-instructions.md"), `# Canvas App Instructions
212
-
213
- ## Type: Canvas App (Power Apps)
214
- ## Project: ${projectName}
215
-
216
- - Screens and controls are defined in .fx.yaml files
217
- - Formulas use Power Fx — similar to Excel syntax
218
- - Use With(), Collect(), Patch() for data operations — never SQL
219
- - Navigation: Navigate(ScreenName, ScreenTransition.Fade)
220
- - Avoid nested galleries — use collections instead
221
- - PAC commands: pac canvas unpack / pac canvas pack
222
- - Never edit .msapp binary directly — always unpack first
223
- - Assets (images, icons) go in Assets/
224
- - Data source connections go in DataSources/
225
- `);
226
-
227
- write(path.join(dir, "canvas-src", "Src", ".gitkeep"), "");
228
- write(path.join(dir, "canvas-src", "Assets", ".gitkeep"), "");
229
- write(path.join(dir, "canvas-src", "DataSources", ".gitkeep"), "");
230
- write(path.join(dir, "canvas-src", "pkgs", ".gitkeep"), "");
231
-
232
- write(path.join(dir, "canvas-src", "Src", "App.fx.yaml"), `# App-level settings and global variables
233
- # The AI will help you fill this in as you build
234
- `);
235
-
236
- write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
237
-
238
- Copy and paste these into your AI assistant to get started quickly.
239
-
240
- ## Create your first screen
241
- "I'm building a Canvas App called ${projectName}. Create the Home screen with:
242
- - A header showing the app title and the current user's name
243
- - A navigation menu with buttons for the main sections of the app
244
- - A clean, professional layout using neutral colours"
245
-
246
- ## Add a data form
247
- "Add a new screen with a form that lets users enter [describe your data].
248
- Save it to a Dataverse table called [TableName]. Include validation so required fields can't be empty."
249
-
250
- ## Connect to data
251
- "Connect this app to a SharePoint list called [ListName] at [SharePoint URL].
252
- Show the items in a gallery on the Home screen, sorted by most recent first."
253
- `);
254
-
255
- log.ok("Canvas App structure created");
256
- log.dim("Next: run pac canvas unpack --msapp YourApp.msapp --sources ./canvas-src to import an existing app");
257
- log.dim("Or ask your AI: 'Create the first screen for a Canvas App called " + projectName + "'");
258
- }
259
-
260
- function scaffoldMDA(dir, projectName, aiTool) {
261
- log.info("Setting up Model-Driven App structure...");
262
-
263
- write(path.join(dir, "instructions", "mda-instructions.md"), `# Model-Driven App Instructions
264
-
265
- ## Type: Model-Driven App (Power Apps / Dataverse)
266
- ## Project: ${projectName}
267
-
268
- - All entities map to Dataverse tables
269
- - Forms are defined in XML — do not hand-edit GUIDs
270
- - Business rules go in Dataverse, not client-side JS
271
- - Use PAC CLI to push/pull: pac solution push / pac solution clone
272
- - Plugin code goes in the Plugins/ folder, registered via Plugin Registration Tool
273
- - JavaScript web resources live in WebResources/ and must be registered on forms
274
- - SiteMaps define the app navigation structure
275
- `);
276
-
277
- write(path.join(dir, "solutions", ".gitkeep"), "");
278
-
279
- write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
280
-
281
- ## Clone your solution from Power Apps
282
- Run this command first (replace MySolution with your solution name):
283
- pac solution clone --name MySolution --outputDirectory ./solutions
284
-
285
- ## Then ask the AI:
286
- "I've cloned a Model-Driven App solution into the solutions/ folder.
287
- Explain the folder structure and what each file does."
288
-
289
- "Add a new form field called [FieldName] (type: text/date/lookup) to the [EntityName] main form."
290
-
291
- "Create a business rule on the [EntityName] entity that [describe the rule]."
292
- `);
293
-
294
- log.ok("Model-Driven App structure created");
295
- log.dim("Next: pac solution clone --name YourSolution --outputDirectory ./solutions");
296
- }
297
-
298
- function scaffoldPCF(dir, projectName, aiTool) {
299
- log.info("Setting up PCF Code Component structure...");
300
-
301
- write(path.join(dir, "instructions", "pcf-instructions.md"), `# PCF Code Component Instructions
302
-
303
- ## Type: Power Apps Component Framework (PCF)
304
- ## Project: ${projectName}
305
-
306
- - Entry point is always index.ts implementing ComponentFramework.StandardControl
307
- - Properties are declared in ControlManifest.Input.xml
308
- - Use context.parameters to read input properties
309
- - Use notifyOutputChanged() to push values back to the app
310
- - Styling: use CSS modules or inline styles — no Tailwind
311
- - Testing: use @testing-library/react + Jest
312
- - Build: npm run build then pac pcf push
313
- - Never modify .pcfproj or Solution.xml by hand
314
- `);
315
-
316
- write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
317
-
318
- ## Initialise the PCF project (run this command first):
319
- pac pcf init --namespace YourCompany --name ${projectName.replace(/\s+/g, "")} --template field
320
-
321
- ## Then ask the AI:
322
- "I've initialised a PCF component called ${projectName}.
323
- Explain the files that were created and what I need to edit to build my component."
324
-
325
- "Build a PCF component that [describe what the component should do — e.g. shows a star rating control, renders a colour picker, displays a custom chart]."
326
-
327
- "Add a property to this PCF component called [PropertyName] of type [SingleLine.Text / Whole.None / etc] that [describe what it controls]."
328
- `);
329
-
330
- log.ok("PCF structure created");
331
- log.dim("Next: pac pcf init --namespace YourNamespace --name " + projectName.replace(/\s+/g, "") + " --template field");
332
- log.dim("Then: npm install && npm start watch");
333
- }
334
-
335
- function scaffoldPowerPages(dir, projectName, aiTool) {
336
- log.info("Setting up Power Pages structure...");
337
-
338
- write(path.join(dir, "instructions", "powerpages-instructions.md"), `# Power Pages Instructions
339
-
340
- ## Type: Power Pages (External Portal)
341
- ## Project: ${projectName}
342
-
343
- - Pages use Liquid templating language — similar to Jinja2
344
- - Data access uses FetchXML or Liquid tags like {% fetchxml %}
345
- - CSS and JS go in web-files/ — reference them in templates
346
- - Authentication is managed by Power Pages — do not build custom auth
347
- - Table permissions control data access — configure in Power Pages studio
348
- - Use pac pages download / pac pages upload to sync changes
349
- - Never hardcode environment URLs — use Liquid {{ request.url }} helpers
350
- `);
351
-
352
- write(path.join(dir, "site", "web-files", ".gitkeep"), "");
353
- write(path.join(dir, "site", "web-pages", ".gitkeep"), "");
354
- write(path.join(dir, "site", "web-templates", ".gitkeep"), "");
355
- write(path.join(dir, "site", "content-snippets", ".gitkeep"), "");
356
- write(path.join(dir, "site", "entity-forms", ".gitkeep"), "");
357
- write(path.join(dir, "site", "web-roles", ".gitkeep"), "");
358
-
359
- write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
360
-
361
- ## Download your site from Power Apps (run first — replace the ID):
362
- pac pages download --path ./site --webSiteId your-website-id
363
-
364
- ## Then ask the AI:
365
- "I've downloaded a Power Pages site into the site/ folder.
366
- Explain the folder structure and what each folder contains."
367
-
368
- "Create a new page called [PageName] that [describe what the page does — e.g. shows a contact form, lists products from a Dataverse table]."
369
-
370
- "Add a navigation link called [LinkName] to the site header that goes to /[page-url]."
371
- `);
372
-
373
- log.ok("Power Pages structure created");
374
- log.dim("Next: pac pages download --path ./site --webSiteId <your-site-id>");
375
- }
376
-
377
- function scaffoldCodeApp(dir, projectName, aiTool) {
378
- log.info("Setting up Code App structure...");
379
-
380
- const safeName = projectName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
381
-
382
- // ── package.json ──────────────────────────────────────────────────────────
383
- // Uses Vite (not react-scripts/CRA which is deprecated and broken).
384
- // @microsoft/power-apps is intentionally NOT listed here — it bundles
385
- // a native addon (keytar) that fails to build on most machines.
386
- // It is installed globally via: npm install -g @microsoft/power-apps
387
- // and imported in app code once the user is ready to connect to Power Platform.
388
- write(path.join(dir, "package.json"), JSON.stringify({
389
- name: safeName,
390
- version: "0.1.0",
391
- private: true,
392
- type: "module",
393
- scripts: {
394
- start: "vite",
395
- build: "tsc && vite build",
396
- preview: "vite preview",
397
- },
398
- dependencies: {
399
- react: "^18.3.1",
400
- "react-dom": "^18.3.1",
401
- },
402
- devDependencies: {
403
- "@types/react": "^18.3.3",
404
- "@types/react-dom": "^18.3.0",
405
- "@vitejs/plugin-react": "^6.0.0",
406
- typescript: "^5.5.3",
407
- vite: "^8.0.0",
408
- },
409
- }, null, 2));
410
-
411
- // ── vite.config.ts ────────────────────────────────────────────────────────
412
- write(path.join(dir, "vite.config.ts"), `import { defineConfig } from 'vite'
413
- import react from '@vitejs/plugin-react'
414
-
415
- // https://vitejs.dev/config/
416
- export default defineConfig({
417
- plugins: [react()],
418
- server: {
419
- port: 3000,
420
- open: true, // automatically opens the browser on npm start
421
- },
422
- })
423
- `);
424
-
425
- // ── tsconfig.json ─────────────────────────────────────────────────────────
426
- write(path.join(dir, "tsconfig.json"), JSON.stringify({
427
- compilerOptions: {
428
- target: "ES2020",
429
- useDefineForClassFields: true,
430
- lib: ["ES2020", "DOM", "DOM.Iterable"],
431
- module: "ESNext",
432
- skipLibCheck: true,
433
- moduleResolution: "bundler",
434
- allowImportingTsExtensions: true,
435
- isolatedModules: true,
436
- moduleDetection: "force",
437
- noEmit: true,
438
- jsx: "react-jsx",
439
- strict: true,
440
- noUnusedLocals: true,
441
- noUnusedParameters: true,
442
- noFallthroughCasesInSwitch: true,
443
- },
444
- include: ["src"],
445
- }, null, 2));
446
-
447
- // ── index.html ────────────────────────────────────────────────────────────
448
- // Vite requires index.html at the project root (not inside /public)
449
- write(path.join(dir, "index.html"), `<!DOCTYPE html>
450
- <html lang="en">
451
- <head>
452
- <meta charset="UTF-8" />
453
- <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
454
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
455
- <title>${projectName}</title>
456
- </head>
457
- <body>
458
- <div id="root"></div>
459
- <script type="module" src="/src/main.tsx"></script>
460
- </body>
461
- </html>
462
- `);
463
-
464
- // ── public/favicon.svg ────────────────────────────────────────────────────
465
- write(path.join(dir, "public", "favicon.svg"), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
466
- <rect width="32" height="32" rx="6" fill="#742774"/>
467
- <text x="16" y="22" text-anchor="middle" font-size="18" fill="white" font-family="sans-serif">⚡</text>
468
- </svg>
469
- `);
470
-
471
- // ── src/vite-env.d.ts ─────────────────────────────────────────────────────
472
- // Required: tells TypeScript what *.module.css imports look like
473
- write(path.join(dir, "src", "vite-env.d.ts"), `/// <reference types="vite/client" />
474
-
475
- // CSS Modules
476
- declare module '*.module.css' {
477
- const classes: Record<string, string>
478
- export default classes
479
- }
480
- `);
481
-
482
- // ── src/main.tsx ──────────────────────────────────────────────────────────
483
- write(path.join(dir, "src", "main.tsx"), `import { StrictMode } from 'react'
484
- import { createRoot } from 'react-dom/client'
485
- import App from './App'
486
- import './index.css'
487
-
488
- createRoot(document.getElementById('root')!).render(
489
- <StrictMode>
490
- <App />
491
- </StrictMode>
492
- )
493
- `);
494
-
495
- // ── src/App.tsx ───────────────────────────────────────────────────────────
496
- write(path.join(dir, "src", "App.tsx"), `import styles from './App.module.css'
497
-
498
- /**
499
- * ${projectName}
500
- *
501
- * This is your app's root component. Start here.
502
- *
503
- * To connect to Power Platform data and connectors:
504
- * 1. Install the CLI globally: npm install -g @microsoft/power-apps
505
- * 2. Run: npx power-apps init (creates power.config.json)
506
- * 3. Then import in your components:
507
- * import { useData } from '@microsoft/power-apps'
508
- *
509
- * Ask your AI assistant: "Build the main layout for ${projectName}"
510
- */
511
- export default function App() {
512
- return (
513
- <div className={styles.app}>
514
- <header className={styles.header}>
515
- <h1>${projectName}</h1>
516
- <p>Your Power Apps Code App is running. Open your AI assistant and describe what to build.</p>
517
- </header>
518
- <main className={styles.main}>
519
- <div className={styles.card}>
520
- <h2>🚀 Ready to build</h2>
521
- <p>Open Copilot Chat or Claude Code and paste a prompt from <code>prompts/starter.md</code></p>
522
- </div>
523
- </main>
524
- </div>
525
- )
526
- }
527
- `);
528
-
529
- // ── src/App.module.css ────────────────────────────────────────────────────
530
- write(path.join(dir, "src", "App.module.css"), `.app {
531
- min-height: 100vh;
532
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
533
- background: #f4f4f8;
534
- color: #1a1a2e;
535
- }
536
-
537
- .header {
538
- background: #742774;
539
- color: white;
540
- padding: 2rem;
541
- text-align: center;
542
- }
543
-
544
- .header h1 {
545
- margin: 0 0 0.5rem;
546
- font-size: 2rem;
547
- }
548
-
549
- .header p {
550
- margin: 0;
551
- opacity: 0.85;
552
- }
553
-
554
- .main {
555
- max-width: 800px;
556
- margin: 2rem auto;
557
- padding: 0 1rem;
558
- }
559
-
560
- .card {
561
- background: white;
562
- border-radius: 8px;
563
- padding: 1.5rem 2rem;
564
- box-shadow: 0 2px 8px rgba(0,0,0,0.08);
565
- }
566
-
567
- .card h2 { margin-top: 0; }
568
- `);
569
-
570
- // ── src/index.css ─────────────────────────────────────────────────────────
571
- write(path.join(dir, "src", "index.css"), `*, *::before, *::after { box-sizing: border-box; }
572
- body { margin: 0; }
573
- `);
574
-
575
- // ── src/components/ placeholder ───────────────────────────────────────────
576
- write(path.join(dir, "src", "components", ".gitkeep"), "");
577
-
578
- // ── instructions ─────────────────────────────────────────────────────────
579
- }
580
- write(path.join(dir, "instructions", "codeapp-instructions.md"), `# Code App Instructions
581
-
582
- ## Type: Power Apps Code App (Code-first web app)
583
- ## Project: ${projectName}
584
-
585
- <<<<<<< HEAD
586
- ### Tech stack (already installed — DO NOT change)
587
- - Vite ^8 as the dev server and bundler (npm start = vite)
588
- - React 18 + TypeScript 5
589
- - CSS Modules for styling (*.module.css files)
590
- - NO react-scripts, NO Create React App, NO webpack
591
-
592
- ### Connecting to Power Platform data
593
- The @microsoft/power-apps client library is installed globally (not in package.json)
594
- because it includes a native addon that must be compiled separately.
595
- To add Power Platform data to a component:
596
- 1. Run once: npm install -g @microsoft/power-apps
597
- 2. Run once: npx power-apps init (creates power.config.json)
598
- 3. Import in components: import { useData } from '@microsoft/power-apps'
599
-
600
- ### Coding rules
601
- - All components are React functional components with TypeScript types
602
- - CSS goes in *.module.css files import as: import styles from './Component.module.css'
603
- - New pages/screens = new .tsx files in src/components/
604
- - No class components, no .then() chains (use async/await)
605
- - Never touch vite.config.ts or tsconfig.json unless asked
606
-
607
- ### To publish to Power Platform
608
- 1. npm run build (creates dist/ folder)
609
- 2. npx power-apps push (uploads to your environment)
610
- `);
611
-
612
- // ── prompts/starter.md ────────────────────────────────────────────────────
613
- write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
614
-
615
- Your app is already running. Open your AI assistant and copy one of these prompts.
616
-
617
- ---
618
-
619
- ## Build the home screen
620
- "I'm building a Power Apps Code App called ${projectName} using React and TypeScript with Vite.
621
- Replace the placeholder home screen in src/App.tsx with a proper layout that has:
622
- - A navigation bar at the top with the app name and a user avatar placeholder
623
- - A main content area with cards showing [describe your main content]
624
- - A clean, professional design using the existing CSS module pattern"
625
-
626
- ---
627
-
628
- ## Add a new page
629
- "Add a new page called [PageName] to this React app.
630
- Create src/components/[PageName].tsx with [describe what the page does].
631
- Add a navigation link to it in App.tsx."
632
-
633
- ---
634
-
635
- ## Add a data table
636
- "Add a component called DataTable.tsx in src/components/ that displays a table of [describe your data].
637
- Use mock data for now — I'll connect it to real data later.
638
- Style it using a CSS module."
639
-
640
- ---
641
-
642
- ## Connect to Power Platform data
643
- "I want to connect this app to Power Platform.
644
- Walk me through:
645
- 1. Installing @microsoft/power-apps globally
646
- 2. Running npx power-apps init
647
- 3. Adding a useData hook to fetch records from a Dataverse table called [TableName]"
648
-
649
- ---
650
-
651
- ## Fix a TypeScript error
652
- "I got this TypeScript error in [filename]: [paste error]
653
- Explain what it means and fix it."
654
- `);
655
-
656
- // ── run npm install automatically ─────────────────────────────────────────
657
- log.info("Running npm install (this takes about 30 seconds)...");
658
- const installed = run("npm install", dir);
659
- if (installed) {
660
- log.ok("npm install completedall dependencies ready");
661
- } else {
662
- log.warn("npm install had issues — try running npm install manually inside the project folder");
663
- }
664
-
665
- log.ok("Code App structure created — run npm start to launch the app");
666
- =======
667
- - Standard web app (React/TypeScript) hosted on Power Platform
668
- - Use @microsoft/power-apps npm package to access connectors and platform APIs
669
- - Authentication is handled by Microsoft Entra — do not build custom auth
670
- - Call Power Platform connectors directly from JavaScript via the client library
671
- - Publish with pac code push — no manual deployment pipeline needed
672
- - Follows canvas app sharing limits and DLP policies set by admins
673
- - Not supported in Power Apps mobile app or Power Apps for Windows
674
- - Use npm start for local dev; the platform manages hosting in production
675
- - Admin must enable Code Apps in Power Platform Admin Center before publishing
676
- `);
677
-
678
- write(path.join(dir, "src", "components", ".gitkeep"), "");
679
- write(path.join(dir, "public", ".gitkeep"), "");
680
-
681
- write(path.join(dir, "src", "App.tsx"), `// ${projectName} Code App entry point
682
- // Ask your AI assistant: "Set up the main App component for a Power Apps Code App called ${projectName}"
683
-
684
- export default function App() {
685
- return (
686
- <div>
687
- <h1>${projectName}</h1>
688
- <p>Ask your AI to build this app. Open the chat and describe what you want.</p>
689
- </div>
690
- );
691
- }
692
- `);
693
-
694
- write(path.join(dir, "src", "index.ts"), `// Entry point — ask your AI to fill this in
695
- `);
696
-
697
- write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
698
-
699
- ## First, initialise the Code App (run this command):
700
- pac code init --outputDirectory .
701
-
702
- ## Install dependencies:
703
- npm install
704
-
705
- ## Then ask the AI:
706
- "I'm building a Power Apps Code App called ${projectName} using React and TypeScript.
707
- Set up the project with a clean layout, routing between pages, and connect it to Microsoft Entra authentication using the @microsoft/power-apps package."
708
-
709
- "Add a page called [PageName] that [describe what the page does]."
710
-
711
- "Connect this app to a Power Platform connector called [ConnectorName] and show the data in a table."
712
- `);
713
-
714
- write(path.join(dir, "package.json"), JSON.stringify({
715
- name: projectName.toLowerCase().replace(/\s+/g, "-"),
716
- version: "0.1.0",
717
- private: true,
718
- scripts: {
719
- start: "react-scripts start",
720
- build: "react-scripts build",
721
- },
722
- dependencies: {
723
- "@microsoft/power-apps": "latest",
724
- react: "^18.0.0",
725
- "react-dom": "^18.0.0",
726
- },
727
- devDependencies: {
728
- typescript: "^5.0.0",
729
- "@types/react": "^18.0.0",
730
- "@types/react-dom": "^18.0.0",
731
- },
732
- }, null, 2));
733
-
734
- write(path.join(dir, "tsconfig.json"), JSON.stringify({
735
- compilerOptions: {
736
- target: "ES2020",
737
- lib: ["ES2020", "DOM"],
738
- jsx: "react-jsx",
739
- module: "ESNext",
740
- moduleResolution: "bundler",
741
- strict: true,
742
- esModuleInterop: true,
743
- skipLibCheck: true,
744
- },
745
- include: ["src"],
746
- }, null, 2));
747
-
748
- log.ok("Code App structure created");
749
- log.dim("Next: pac code init --outputDirectory . then npm install");
750
- >>>>>>> 3a159cf4be27d864191588e82e9143d5857edcac
751
- }
752
-
753
- // ─── AI context file writer ───────────────────────────────────────────────────
754
-
755
- function writeAIContextFile(dir, projectName, appType, aiTool) {
756
- const content = `# Project: ${projectName}
757
-
758
- ## What this project does
759
- [Fill in a one-paragraph description of your app — what problem it solves, who uses it]
760
-
761
- ## App type
762
- ${appType} on Microsoft Power Platform
763
-
764
- ## AI assistant
765
- ${aiTool}
766
-
767
- ## Coding conventions
768
- - Use TypeScript (not plain JavaScript) wherever possible
769
- - Use async/await (not .then() chains)
770
- - All functions should have clear names that describe what they do
771
- - Add a short comment above any complex logic explaining what it does
772
-
773
- ## Power Platform context
774
- - See the instructions/ folder for ${appType}-specific rules
775
- - Environment URL is in the .env file (POWER_PLATFORM_URL)
776
- - Use PAC CLI to push/pull changes — see README.md for commands
777
-
778
- ## What to do when you get an error
779
- Tell the AI: "I got this error: [paste error here]. What does it mean and how do I fix it?"
780
- `;
781
-
782
- if (aiTool === "Claude Code") {
783
- write(path.join(dir, "CLAUDE.md"), content);
784
- log.ok("Created CLAUDE.md Claude Code reads this automatically on every session");
785
- } else {
786
- fs.mkdirSync(path.join(dir, ".github"), { recursive: true });
787
- write(path.join(dir, ".github", "copilot-instructions.md"), content);
788
- log.ok("Created .github/copilot-instructions.md Copilot reads this for context");
789
- }
790
- }
791
-
792
- // ─── Main ─────────────────────────────────────────────────────────────────────
793
-
794
- async function main() {
795
- console.log(`
796
- ${c.bold}${c.magenta}╔═══════════════════════════════════════════╗
797
- create-powerapp ⚡ ║
798
- ║ Scaffold any Power App in one command ║
799
- ╚═══════════════════════════════════════════╝${c.reset}
800
-
801
- Welcome! Answer a few questions and your project will be ready
802
- to open in VS Code and start prompting your AI assistant.
803
- `);
804
-
805
- // ── 1. Project name ──────────────────────────────────────────────────────
806
- log.step("Project name");
807
- log.dim("This becomes your folder name and app name. Use letters, numbers, hyphens.");
808
- let projectName = await prompt(" Project name: ");
809
- if (!projectName) projectName = "my-powerapp";
810
- const safeName = projectName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
811
-
812
- // ── 2. App type ──────────────────────────────────────────────────────────
813
- log.step("Which type of Power App are you building?");
814
- const appTypeChoice = await choose("App type:", [
815
- { label: "Canvas App", desc: "— Design every screen pixel-by-pixel. Best for custom-branded employee tools & mobile apps" },
816
- { label: "Model-Driven App", desc: "— Database-first. Power Apps builds the UI from your data model. Best for CRM / record management" },
817
- { label: "PCF Component", desc: "— A reusable custom control (widget) to embed inside other Power Apps" },
818
- { label: "Power Pages", desc: "— A public-facing website connected to your Dataverse data" },
819
- { label: "Code App", desc: "— A full custom web app (React/TypeScript) hosted on Power Platform" },
820
- ]);
821
-
822
- // ── 3. AI assistant ──────────────────────────────────────────────────────
823
- log.step("Which AI assistant are you using?");
824
- const aiChoice = await choose("AI assistant:", [
825
- { label: "GitHub Copilot", desc: "— You have a GitHub Copilot subscription or organisation license" },
826
- { label: "Claude Code", desc: "— You have an Anthropic API key or Claude Code license" },
827
- ]);
828
-
829
- // ── 4. Power Platform environment URL ────────────────────────────────────
830
- log.step("Power Platform environment URL (optional press Enter to skip)");
831
- log.dim("Find it at make.powerapps.com → Settings gear → Session details → Instance url");
832
- log.dim("Example: https://yourcompany.crm.dynamics.com");
833
- const envUrl = await prompt(" Environment URL: ");
834
-
835
- // ── 5. Create folder ─────────────────────────────────────────────────────
836
- log.step(`Creating project folder: ${safeName}/`);
837
- const dir = path.join(process.cwd(), safeName);
838
-
839
- if (fs.existsSync(dir)) {
840
- const overwrite = await prompt(` Folder "${safeName}" already exists. Overwrite? (y/N): `);
841
- if (overwrite.toLowerCase() !== "y") {
842
- log.warn("Aborted. Choose a different project name.");
843
- process.exit(0);
844
- }
845
- }
846
-
847
- fs.mkdirSync(dir, { recursive: true });
848
- log.ok(`Folder created: ${dir}`);
849
-
850
- // ── 6. Shared files ──────────────────────────────────────────────────────
851
- log.step("Creating shared project files...");
852
-
853
- write(path.join(dir, ".gitignore"), gitignoreContent());
854
- log.ok("Created .gitignore");
855
-
856
- write(path.join(dir, ".env"), envUrl
857
- ? envContent(projectName, appTypeChoice.label).replace("https://yourorg.crm.dynamics.com", envUrl)
858
- : envContent(projectName, appTypeChoice.label)
859
- );
860
- log.ok("Created .env");
861
-
862
- write(path.join(dir, "README.md"), readmeContent(projectName, appTypeChoice.label, aiChoice.label));
863
- log.ok("Created README.md");
864
-
865
- // ── 7. App-type scaffold ─────────────────────────────────────────────────
866
- log.step(`Scaffolding ${appTypeChoice.label} files...`);
867
- switch (appTypeChoice.label) {
868
- case "Canvas App": scaffoldCanvas(dir, projectName, aiChoice.label); break;
869
- case "Model-Driven App": scaffoldMDA(dir, projectName, aiChoice.label); break;
870
- case "PCF Component": scaffoldPCF(dir, projectName, aiChoice.label); break;
871
- case "Power Pages": scaffoldPowerPages(dir, projectName, aiChoice.label); break;
872
- case "Code App": scaffoldCodeApp(dir, projectName, aiChoice.label); break;
873
- }
874
-
875
- // ── 8. AI context file ───────────────────────────────────────────────────
876
- log.step("Creating AI context file...");
877
- writeAIContextFile(dir, projectName, appTypeChoice.label, aiChoice.label);
878
-
879
- // ── 9. Git init ──────────────────────────────────────────────────────────
880
- log.step("Initialising Git...");
881
- if (has("git")) {
882
- run("git init", dir, true);
883
- run("git add .", dir, true);
884
- run('git commit -m "Initial scaffold by create-powerapp"', dir, true);
885
- log.ok("Git initialised with first commit");
886
- } else {
887
- log.warn("Git not found — skipping. Install Git and run git init inside your project.");
888
- }
889
-
890
- // ── 10. PAC CLI auth ─────────────────────────────────────────────────────
891
- if (envUrl) {
892
- log.step("Connecting to Power Platform environment...");
893
- if (has("pac")) {
894
- log.info(`Running: pac auth create --url ${envUrl}`);
895
- log.dim("A browser window will open. Sign in with your Microsoft work account.");
896
- run(`pac auth create --url ${envUrl}`, dir);
897
- } else {
898
- log.warn("PAC CLI not found. Install it with: npm install -g pac");
899
- log.warn("Then run: pac auth create --url " + envUrl);
900
- }
901
- }
902
-
903
- // ── 11. Open in VS Code ──────────────────────────────────────────────────
904
- log.step("Opening in VS Code...");
905
- if (has("code")) {
906
- run(`code "${dir}"`, process.cwd(), true);
907
- log.ok("VS Code opened with your project");
908
- } else {
909
- log.warn("VS Code code command not found.");
910
- log.dim("Open VS Code manually → File → Open Folder → select: " + dir);
911
- }
912
-
913
- // ── Done ─────────────────────────────────────────────────────────────────
914
- <<<<<<< HEAD
915
- const isCodeApp = appTypeChoice.label === "Code App";
916
- =======
917
- >>>>>>> 3a159cf4be27d864191588e82e9143d5857edcac
918
- console.log(`
919
- ${c.bold}${c.green}╔═══════════════════════════════════════════════════╗
920
- ║ ✅ Your project is ready! ║
921
- ╚═══════════════════════════════════════════════════╝${c.reset}
922
-
923
- ${c.bold}Project folder:${c.reset} ${dir}
924
-
925
- ${c.bold}What to do next:${c.reset}
926
-
927
- 1. VS Code is now open (or open it manually and open the folder above)
928
- <<<<<<< HEAD
929
- ${isCodeApp ? `
930
- 2. ${c.bold}${c.green}Start the app:${c.reset} In the VS Code terminal, run:
931
- ${c.cyan}npm start${c.reset}
932
- Your browser will open automatically at http://localhost:3000
933
- ` : `
934
- 2. Read the ${c.cyan}prompts/starter.md${c.reset} file for ready-made prompts to get started
935
- `}
936
- =======
937
-
938
- 2. Read the ${c.cyan}prompts/starter.md${c.reset} file for ready-made prompts to get started
939
-
940
- >>>>>>> 3a159cf4be27d864191588e82e9143d5857edcac
941
- 3. Open your AI assistant:
942
- ${aiChoice.label === "Claude Code"
943
- ? ` • In the VS Code terminal, run: ${c.cyan}claude${c.reset}
944
- • Or click the Claude Code icon in the left sidebar`
945
- : ` • Click the Copilot Chat icon (💬) in the VS Code left sidebar`}
946
-
947
- 4. Copy a prompt from ${c.cyan}prompts/starter.md${c.reset} and paste it in — or describe your app in your own words
948
-
949
- 5. When you're happy with what the AI built, save your work:
950
- ${c.dim}git add . → git commit -m "describe what you built" → git push${c.reset}
951
-
952
- ${c.bold}${c.yellow}Tip:${c.reset} When something goes wrong, tell the AI:
953
- "I got this error: [paste error]. What does it mean and how do I fix it?"
954
-
955
- Happy building! 🚀
956
- `);
957
- rl.close();
958
- }
959
-
960
- main().catch((err) => {
961
- log.error("Unexpected error: " + err.message);
962
- rl.close();
963
- process.exit(1);
964
- });
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-powerapp
5
+ * One command to scaffold any Power Apps project — Canvas App, Model-Driven App,
6
+ * PCF Component, Power Pages, or Code App — with Git, AI context files, and
7
+ * everything ready so you can immediately start prompting your AI assistant.
8
+ */
9
+
10
+ import { execSync, spawnSync } from "child_process";
11
+ import fs from "fs";
12
+ import path from "path";
13
+ import readline from "readline";
14
+ import { fileURLToPath } from "url";
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+
18
+ // ─── Colour helpers ──────────────────────────────────────────────────────────
19
+ const c = {
20
+ reset: "\x1b[0m",
21
+ bold: "\x1b[1m",
22
+ green: "\x1b[32m",
23
+ cyan: "\x1b[36m",
24
+ yellow: "\x1b[33m",
25
+ red: "\x1b[31m",
26
+ dim: "\x1b[2m",
27
+ magenta: "\x1b[35m",
28
+ };
29
+ const log = {
30
+ info: (m) => console.log(`${c.cyan}ℹ${c.reset} ${m}`),
31
+ ok: (m) => console.log(`${c.green}✔${c.reset} ${m}`),
32
+ warn: (m) => console.log(`${c.yellow}⚠${c.reset} ${m}`),
33
+ error: (m) => console.log(`${c.red}✖${c.reset} ${m}`),
34
+ step: (m) => console.log(`\n${c.bold}${c.magenta}▶ ${m}${c.reset}`),
35
+ dim: (m) => console.log(`${c.dim} ${m}${c.reset}`),
36
+ };
37
+
38
+ // ─── readline helper ─────────────────────────────────────────────────────────
39
+ // Single persistent readline interface — works interactively AND when piped (CI)
40
+ const rl = readline.createInterface({
41
+ input: process.stdin,
42
+ output: process.stdout,
43
+ terminal: false, // don't assume TTY — makes piped input work in CI
44
+ });
45
+
46
+ // Queue of lines already read from stdin (for piped / non-interactive mode)
47
+ const lineQueue = [];
48
+ const lineWaiters = [];
49
+ rl.on("line", (line) => {
50
+ if (lineWaiters.length > 0) {
51
+ lineWaiters.shift()(line.trim());
52
+ } else {
53
+ lineQueue.push(line.trim());
54
+ }
55
+ });
56
+
57
+ function prompt(question) {
58
+ process.stdout.write(question);
59
+ return new Promise((resolve) => {
60
+ if (lineQueue.length > 0) {
61
+ const line = lineQueue.shift();
62
+ process.stdout.write(line + "\n"); // echo the pre-buffered answer
63
+ resolve(line);
64
+ } else {
65
+ lineWaiters.push(resolve);
66
+ }
67
+ });
68
+ }
69
+
70
+ async function choose(question, options) {
71
+ console.log(`\n${c.bold}${question}${c.reset}`);
72
+ options.forEach((o, i) => console.log(` ${c.cyan}${i + 1}${c.reset}. ${o.label} ${c.dim}${o.desc || ""}${c.reset}`));
73
+ while (true) {
74
+ const answer = await prompt(`\nEnter number (1–${options.length}): `);
75
+ const idx = parseInt(answer, 10) - 1;
76
+ if (idx >= 0 && idx < options.length) return options[idx];
77
+ log.warn("Please enter a valid number.");
78
+ }
79
+ }
80
+
81
+ // ─── run shell command ────────────────────────────────────────────────────────
82
+ function run(cmd, cwd = process.cwd(), silent = false) {
83
+ const result = spawnSync(cmd, { shell: true, cwd, stdio: silent ? "pipe" : "inherit" });
84
+ if (result.status !== 0 && !silent) {
85
+ log.error(`Command failed: ${cmd}`);
86
+ }
87
+ return result.status === 0;
88
+ }
89
+
90
+ // ─── check a CLI tool exists ──────────────────────────────────────────────────
91
+ function has(tool) {
92
+ return spawnSync(`${tool} --version`, { shell: true, stdio: "pipe" }).status === 0;
93
+ }
94
+
95
+ // ─── write file helper ────────────────────────────────────────────────────────
96
+ function write(filePath, content) {
97
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
98
+ fs.writeFileSync(filePath, content, "utf8");
99
+ }
100
+
101
+ // ─── shared file content ──────────────────────────────────────────────────────
102
+
103
+ function gitignoreContent() {
104
+ return `# Dependencies
105
+ node_modules/
106
+ .pnp
107
+ .pnp.js
108
+
109
+ # Environment variables — NEVER commit these
110
+ .env
111
+ .env.local
112
+ .env.*.local
113
+
114
+ # Build outputs
115
+ dist/
116
+ build/
117
+ out/
118
+ .next/
119
+ bin/
120
+
121
+ # Editor files
122
+ .vscode/
123
+ .idea/
124
+ *.swp
125
+
126
+ # OS files
127
+ .DS_Store
128
+ Thumbs.db
129
+
130
+ # Logs
131
+ *.log
132
+ npm-debug.log*
133
+
134
+ # Power Apps / PAC CLI outputs
135
+ *.msapp
136
+ *.zip
137
+ /obj/
138
+ `;
139
+ }
140
+
141
+ function envContent(projectName, appType) {
142
+ return `# .env — DO NOT commit this file to Git
143
+ # Fill in the values for your project
144
+
145
+ # Your Power Platform environment URL
146
+ # Find it at: make.powerapps.com → Settings gear → Session details → Instance url
147
+ POWER_PLATFORM_URL=https://yourorg.crm.dynamics.com
148
+
149
+ # Project info
150
+ PROJECT_NAME=${projectName}
151
+ APP_TYPE=${appType}
152
+
153
+ # Add any other secrets here (API keys, database URLs, etc.)
154
+ # API_KEY=your-key-here
155
+ `;
156
+ }
157
+
158
+ function readmeContent(projectName, appType, aiTool) {
159
+ return `# ${projectName}
160
+
161
+ **Type:** ${appType}
162
+ **AI Assistant:** ${aiTool}
163
+
164
+ ## Getting started
165
+
166
+ Everything is already set up. Just open your AI assistant and describe what you want to build.
167
+
168
+ ### Start your AI session
169
+
170
+ ${aiTool === "Claude Code"
171
+ ? "```bash\nclaude\n```\nThen type what you want to build in plain English."
172
+ : "Click the **Copilot Chat** icon (💬) in the VS Code sidebar and type what you want to build."}
173
+
174
+ ### Example first prompts
175
+
176
+ > "Create the main screen for this app with a navigation menu and a welcome message."
177
+
178
+ > "Add a form that lets users submit [whatever your app needs] and saves it to Dataverse."
179
+
180
+ > "Explain what files are in this project and what each one does."
181
+
182
+ ## Project structure
183
+
184
+ See the \`instructions/\` folder for the AI context files that tell your AI assistant
185
+ how this type of Power App works.
186
+
187
+ ## Useful commands
188
+
189
+ \`\`\`bash
190
+ # Push changes to Power Platform
191
+ ${appType === "Canvas App" ? "pac canvas pack --msapp ./CanvasApp.msapp --sources ./canvas-src\npac solution push" : ""}
192
+ ${appType === "Model-Driven App" ? "pac solution push" : ""}
193
+ ${appType === "PCF Component" ? "npm run build\npac pcf push --publisher-prefix yourprefix" : ""}
194
+ ${appType === "Power Pages" ? "pac pages upload --path ." : ""}
195
+ ${appType === "Code App" ? "npm run build\npac code push" : ""}
196
+
197
+ # Save your work
198
+ git add .
199
+ git commit -m "describe what you changed"
200
+ git push
201
+ \`\`\`
202
+ `;
203
+ }
204
+
205
+ // ─── App-type scaffolders ─────────────────────────────────────────────────────
206
+
207
+ function scaffoldCanvas(dir, projectName, aiTool) {
208
+ log.info("Setting up Canvas App structure...");
209
+
210
+ // AI context files
211
+ write(path.join(dir, "instructions", "canvas-instructions.md"), `# Canvas App Instructions
212
+
213
+ ## Type: Canvas App (Power Apps)
214
+ ## Project: ${projectName}
215
+
216
+ - Screens and controls are defined in .fx.yaml files
217
+ - Formulas use Power Fx — similar to Excel syntax
218
+ - Use With(), Collect(), Patch() for data operations — never SQL
219
+ - Navigation: Navigate(ScreenName, ScreenTransition.Fade)
220
+ - Avoid nested galleries — use collections instead
221
+ - PAC commands: pac canvas unpack / pac canvas pack
222
+ - Never edit .msapp binary directly — always unpack first
223
+ - Assets (images, icons) go in Assets/
224
+ - Data source connections go in DataSources/
225
+ `);
226
+
227
+ write(path.join(dir, "canvas-src", "Src", ".gitkeep"), "");
228
+ write(path.join(dir, "canvas-src", "Assets", ".gitkeep"), "");
229
+ write(path.join(dir, "canvas-src", "DataSources", ".gitkeep"), "");
230
+ write(path.join(dir, "canvas-src", "pkgs", ".gitkeep"), "");
231
+
232
+ write(path.join(dir, "canvas-src", "Src", "App.fx.yaml"), `# App-level settings and global variables
233
+ # The AI will help you fill this in as you build
234
+ `);
235
+
236
+ write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
237
+
238
+ Copy and paste these into your AI assistant to get started quickly.
239
+
240
+ ## Create your first screen
241
+ "I'm building a Canvas App called ${projectName}. Create the Home screen with:
242
+ - A header showing the app title and the current user's name
243
+ - A navigation menu with buttons for the main sections of the app
244
+ - A clean, professional layout using neutral colours"
245
+
246
+ ## Add a data form
247
+ "Add a new screen with a form that lets users enter [describe your data].
248
+ Save it to a Dataverse table called [TableName]. Include validation so required fields can't be empty."
249
+
250
+ ## Connect to data
251
+ "Connect this app to a SharePoint list called [ListName] at [SharePoint URL].
252
+ Show the items in a gallery on the Home screen, sorted by most recent first."
253
+ `);
254
+
255
+ log.ok("Canvas App structure created");
256
+ log.dim("Next: run pac canvas unpack --msapp YourApp.msapp --sources ./canvas-src to import an existing app");
257
+ log.dim("Or ask your AI: 'Create the first screen for a Canvas App called " + projectName + "'");
258
+ }
259
+
260
+ function scaffoldMDA(dir, projectName, aiTool) {
261
+ log.info("Setting up Model-Driven App structure...");
262
+
263
+ write(path.join(dir, "instructions", "mda-instructions.md"), `# Model-Driven App Instructions
264
+
265
+ ## Type: Model-Driven App (Power Apps / Dataverse)
266
+ ## Project: ${projectName}
267
+
268
+ - All entities map to Dataverse tables
269
+ - Forms are defined in XML — do not hand-edit GUIDs
270
+ - Business rules go in Dataverse, not client-side JS
271
+ - Use PAC CLI to push/pull: pac solution push / pac solution clone
272
+ - Plugin code goes in the Plugins/ folder, registered via Plugin Registration Tool
273
+ - JavaScript web resources live in WebResources/ and must be registered on forms
274
+ - SiteMaps define the app navigation structure
275
+ `);
276
+
277
+ write(path.join(dir, "solutions", ".gitkeep"), "");
278
+
279
+ write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
280
+
281
+ ## Clone your solution from Power Apps
282
+ Run this command first (replace MySolution with your solution name):
283
+ pac solution clone --name MySolution --outputDirectory ./solutions
284
+
285
+ ## Then ask the AI:
286
+ "I've cloned a Model-Driven App solution into the solutions/ folder.
287
+ Explain the folder structure and what each file does."
288
+
289
+ "Add a new form field called [FieldName] (type: text/date/lookup) to the [EntityName] main form."
290
+
291
+ "Create a business rule on the [EntityName] entity that [describe the rule]."
292
+ `);
293
+
294
+ log.ok("Model-Driven App structure created");
295
+ log.dim("Next: pac solution clone --name YourSolution --outputDirectory ./solutions");
296
+ }
297
+
298
+ function scaffoldPCF(dir, projectName, aiTool) {
299
+ log.info("Setting up PCF Code Component structure...");
300
+
301
+ write(path.join(dir, "instructions", "pcf-instructions.md"), `# PCF Code Component Instructions
302
+
303
+ ## Type: Power Apps Component Framework (PCF)
304
+ ## Project: ${projectName}
305
+
306
+ - Entry point is always index.ts implementing ComponentFramework.StandardControl
307
+ - Properties are declared in ControlManifest.Input.xml
308
+ - Use context.parameters to read input properties
309
+ - Use notifyOutputChanged() to push values back to the app
310
+ - Styling: use CSS modules or inline styles — no Tailwind
311
+ - Testing: use @testing-library/react + Jest
312
+ - Build: npm run build then pac pcf push
313
+ - Never modify .pcfproj or Solution.xml by hand
314
+ `);
315
+
316
+ write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
317
+
318
+ ## Initialise the PCF project (run this command first):
319
+ pac pcf init --namespace YourCompany --name ${projectName.replace(/\s+/g, "")} --template field
320
+
321
+ ## Then ask the AI:
322
+ "I've initialised a PCF component called ${projectName}.
323
+ Explain the files that were created and what I need to edit to build my component."
324
+
325
+ "Build a PCF component that [describe what the component should do — e.g. shows a star rating control, renders a colour picker, displays a custom chart]."
326
+
327
+ "Add a property to this PCF component called [PropertyName] of type [SingleLine.Text / Whole.None / etc] that [describe what it controls]."
328
+ `);
329
+
330
+ log.ok("PCF structure created");
331
+ log.dim("Next: pac pcf init --namespace YourNamespace --name " + projectName.replace(/\s+/g, "") + " --template field");
332
+ log.dim("Then: npm install && npm start watch");
333
+ }
334
+
335
+ function scaffoldPowerPages(dir, projectName, aiTool) {
336
+ log.info("Setting up Power Pages structure...");
337
+
338
+ write(path.join(dir, "instructions", "powerpages-instructions.md"), `# Power Pages Instructions
339
+
340
+ ## Type: Power Pages (External Portal)
341
+ ## Project: ${projectName}
342
+
343
+ - Pages use Liquid templating language — similar to Jinja2
344
+ - Data access uses FetchXML or Liquid tags like {% fetchxml %}
345
+ - CSS and JS go in web-files/ — reference them in templates
346
+ - Authentication is managed by Power Pages — do not build custom auth
347
+ - Table permissions control data access — configure in Power Pages studio
348
+ - Use pac pages download / pac pages upload to sync changes
349
+ - Never hardcode environment URLs — use Liquid {{ request.url }} helpers
350
+ `);
351
+
352
+ write(path.join(dir, "site", "web-files", ".gitkeep"), "");
353
+ write(path.join(dir, "site", "web-pages", ".gitkeep"), "");
354
+ write(path.join(dir, "site", "web-templates", ".gitkeep"), "");
355
+ write(path.join(dir, "site", "content-snippets", ".gitkeep"), "");
356
+ write(path.join(dir, "site", "entity-forms", ".gitkeep"), "");
357
+ write(path.join(dir, "site", "web-roles", ".gitkeep"), "");
358
+
359
+ write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
360
+
361
+ ## Download your site from Power Apps (run first — replace the ID):
362
+ pac pages download --path ./site --webSiteId your-website-id
363
+
364
+ ## Then ask the AI:
365
+ "I've downloaded a Power Pages site into the site/ folder.
366
+ Explain the folder structure and what each folder contains."
367
+
368
+ "Create a new page called [PageName] that [describe what the page does — e.g. shows a contact form, lists products from a Dataverse table]."
369
+
370
+ "Add a navigation link called [LinkName] to the site header that goes to /[page-url]."
371
+ `);
372
+
373
+ log.ok("Power Pages structure created");
374
+ log.dim("Next: pac pages download --path ./site --webSiteId <your-site-id>");
375
+ }
376
+
377
+ function scaffoldCodeApp(dir, projectName, aiTool) {
378
+ log.info("Setting up Code App structure...");
379
+
380
+ const safeName = projectName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
381
+
382
+ // ── package.json ──────────────────────────────────────────────────────────
383
+ // Uses Vite (not react-scripts/CRA which is deprecated and broken).
384
+ // @microsoft/power-apps is intentionally NOT listed here — it bundles
385
+ // a native addon (keytar) that fails to build on most machines.
386
+ // It is installed globally via: npm install -g @microsoft/power-apps
387
+ // and imported in app code once the user is ready to connect to Power Platform.
388
+ write(path.join(dir, "package.json"), JSON.stringify({
389
+ name: safeName,
390
+ version: "0.1.0",
391
+ private: true,
392
+ type: "module",
393
+ scripts: {
394
+ start: "vite",
395
+ build: "tsc && vite build",
396
+ preview: "vite preview",
397
+ },
398
+ dependencies: {
399
+ react: "^18.3.1",
400
+ "react-dom": "^18.3.1",
401
+ },
402
+ devDependencies: {
403
+ "@types/react": "^18.3.3",
404
+ "@types/react-dom": "^18.3.0",
405
+ "@vitejs/plugin-react": "^6.0.0",
406
+ typescript: "^5.5.3",
407
+ vite: "^8.0.0",
408
+ },
409
+ }, null, 2));
410
+
411
+ // ── vite.config.ts ────────────────────────────────────────────────────────
412
+ write(path.join(dir, "vite.config.ts"), `import { defineConfig } from 'vite'
413
+ import react from '@vitejs/plugin-react'
414
+
415
+ // https://vitejs.dev/config/
416
+ export default defineConfig({
417
+ plugins: [react()],
418
+ server: {
419
+ port: 3000,
420
+ open: true, // automatically opens the browser on npm start
421
+ },
422
+ })
423
+ `);
424
+
425
+ // ── tsconfig.json ─────────────────────────────────────────────────────────
426
+ write(path.join(dir, "tsconfig.json"), JSON.stringify({
427
+ compilerOptions: {
428
+ target: "ES2020",
429
+ useDefineForClassFields: true,
430
+ lib: ["ES2020", "DOM", "DOM.Iterable"],
431
+ module: "ESNext",
432
+ skipLibCheck: true,
433
+ moduleResolution: "bundler",
434
+ allowImportingTsExtensions: true,
435
+ isolatedModules: true,
436
+ moduleDetection: "force",
437
+ noEmit: true,
438
+ jsx: "react-jsx",
439
+ strict: true,
440
+ noUnusedLocals: true,
441
+ noUnusedParameters: true,
442
+ noFallthroughCasesInSwitch: true,
443
+ },
444
+ include: ["src"],
445
+ }, null, 2));
446
+
447
+ // ── index.html ────────────────────────────────────────────────────────────
448
+ // Vite requires index.html at the project root (not inside /public)
449
+ write(path.join(dir, "index.html"), `<!DOCTYPE html>
450
+ <html lang="en">
451
+ <head>
452
+ <meta charset="UTF-8" />
453
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
454
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
455
+ <title>${projectName}</title>
456
+ </head>
457
+ <body>
458
+ <div id="root"></div>
459
+ <script type="module" src="/src/main.tsx"></script>
460
+ </body>
461
+ </html>
462
+ `);
463
+
464
+ // ── public/favicon.svg ────────────────────────────────────────────────────
465
+ write(path.join(dir, "public", "favicon.svg"), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
466
+ <rect width="32" height="32" rx="6" fill="#742774"/>
467
+ <text x="16" y="22" text-anchor="middle" font-size="18" fill="white" font-family="sans-serif">⚡</text>
468
+ </svg>
469
+ `);
470
+
471
+ // ── src/vite-env.d.ts ─────────────────────────────────────────────────────
472
+ // Required: tells TypeScript what *.module.css imports look like
473
+ write(path.join(dir, "src", "vite-env.d.ts"), `/// <reference types="vite/client" />
474
+
475
+ // CSS Modules
476
+ declare module '*.module.css' {
477
+ const classes: Record<string, string>
478
+ export default classes
479
+ }
480
+ `);
481
+
482
+ // ── src/main.tsx ──────────────────────────────────────────────────────────
483
+ write(path.join(dir, "src", "main.tsx"), `import { StrictMode } from 'react'
484
+ import { createRoot } from 'react-dom/client'
485
+ import App from './App'
486
+ import './index.css'
487
+
488
+ createRoot(document.getElementById('root')!).render(
489
+ <StrictMode>
490
+ <App />
491
+ </StrictMode>
492
+ )
493
+ `);
494
+
495
+ // ── src/App.tsx ───────────────────────────────────────────────────────────
496
+ write(path.join(dir, "src", "App.tsx"), `import styles from './App.module.css'
497
+
498
+ /**
499
+ * ${projectName}
500
+ *
501
+ * This is your app's root component. Start here.
502
+ *
503
+ * To connect to Power Platform data and connectors:
504
+ * 1. Install the CLI globally: npm install -g @microsoft/power-apps
505
+ * 2. Run: npx power-apps init (creates power.config.json)
506
+ * 3. Then import in your components:
507
+ * import { useData } from '@microsoft/power-apps'
508
+ *
509
+ * Ask your AI assistant: "Build the main layout for ${projectName}"
510
+ */
511
+ export default function App() {
512
+ return (
513
+ <div className={styles.app}>
514
+ <header className={styles.header}>
515
+ <h1>${projectName}</h1>
516
+ <p>Your Power Apps Code App is running. Open your AI assistant and describe what to build.</p>
517
+ </header>
518
+ <main className={styles.main}>
519
+ <div className={styles.card}>
520
+ <h2>🚀 Ready to build</h2>
521
+ <p>Open Copilot Chat or Claude Code and paste a prompt from <code>prompts/starter.md</code></p>
522
+ </div>
523
+ </main>
524
+ </div>
525
+ )
526
+ }
527
+ `);
528
+
529
+ // ── src/App.module.css ────────────────────────────────────────────────────
530
+ write(path.join(dir, "src", "App.module.css"), `.app {
531
+ min-height: 100vh;
532
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
533
+ background: #f4f4f8;
534
+ color: #1a1a2e;
535
+ }
536
+
537
+ .header {
538
+ background: #742774;
539
+ color: white;
540
+ padding: 2rem;
541
+ text-align: center;
542
+ }
543
+
544
+ .header h1 {
545
+ margin: 0 0 0.5rem;
546
+ font-size: 2rem;
547
+ }
548
+
549
+ .header p {
550
+ margin: 0;
551
+ opacity: 0.85;
552
+ }
553
+
554
+ .main {
555
+ max-width: 800px;
556
+ margin: 2rem auto;
557
+ padding: 0 1rem;
558
+ }
559
+
560
+ .card {
561
+ background: white;
562
+ border-radius: 8px;
563
+ padding: 1.5rem 2rem;
564
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08);
565
+ }
566
+
567
+ .card h2 { margin-top: 0; }
568
+ `);
569
+
570
+ // ── src/index.css ─────────────────────────────────────────────────────────
571
+ write(path.join(dir, "src", "index.css"), `*, *::before, *::after { box-sizing: border-box; }
572
+ body { margin: 0; }
573
+ `);
574
+
575
+ // ── src/components/ placeholder ───────────────────────────────────────────
576
+ write(path.join(dir, "src", "components", ".gitkeep"), "");
577
+
578
+ // ── instructions ─────────────────────────────────────────────────────────
579
+ write(path.join(dir, "instructions", "codeapp-instructions.md"), `# Code App Instructions
580
+
581
+ ## Type: Power Apps Code App (Code-first web app)
582
+ ## Project: ${projectName}
583
+
584
+ ### Tech stack (already installed — DO NOT change)
585
+ - Vite ^8 as the dev server and bundler (npm start = vite)
586
+ - React 18 + TypeScript 5
587
+ - CSS Modules for styling (*.module.css files)
588
+ - NO react-scripts, NO Create React App, NO webpack
589
+
590
+ ### Connecting to Power Platform data
591
+ The @microsoft/power-apps client library is installed globally (not in package.json)
592
+ because it includes a native addon that must be compiled separately.
593
+ To add Power Platform data to a component:
594
+ 1. Run once: npm install -g @microsoft/power-apps
595
+ 2. Run once: npx power-apps init (creates power.config.json)
596
+ 3. Import in components: import { useData } from '@microsoft/power-apps'
597
+
598
+ ### Coding rules
599
+ - All components are React functional components with TypeScript types
600
+ - CSS goes in *.module.css files — import as: import styles from './Component.module.css'
601
+ - New pages/screens = new .tsx files in src/components/
602
+ - No class components, no .then() chains (use async/await)
603
+ - Never touch vite.config.ts or tsconfig.json unless asked
604
+
605
+ ### To publish to Power Platform
606
+ 1. npm run build (creates dist/ folder)
607
+ 2. npx power-apps push (uploads to your environment)
608
+ `);
609
+
610
+ // ── prompts/starter.md ────────────────────────────────────────────────────
611
+ write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
612
+
613
+ Your app is already running. Open your AI assistant and copy one of these prompts.
614
+
615
+ ---
616
+
617
+ ## Build the home screen
618
+ "I'm building a Power Apps Code App called ${projectName} using React and TypeScript with Vite.
619
+ Replace the placeholder home screen in src/App.tsx with a proper layout that has:
620
+ - A navigation bar at the top with the app name and a user avatar placeholder
621
+ - A main content area with cards showing [describe your main content]
622
+ - A clean, professional design using the existing CSS module pattern"
623
+
624
+ ---
625
+
626
+ ## Add a new page
627
+ "Add a new page called [PageName] to this React app.
628
+ Create src/components/[PageName].tsx with [describe what the page does].
629
+ Add a navigation link to it in App.tsx."
630
+
631
+ ---
632
+
633
+ ## Add a data table
634
+ "Add a component called DataTable.tsx in src/components/ that displays a table of [describe your data].
635
+ Use mock data for now — I'll connect it to real data later.
636
+ Style it using a CSS module."
637
+
638
+ ---
639
+
640
+ ## Connect to Power Platform data
641
+ "I want to connect this app to Power Platform.
642
+ Walk me through:
643
+ 1. Installing @microsoft/power-apps globally
644
+ 2. Running npx power-apps init
645
+ 3. Adding a useData hook to fetch records from a Dataverse table called [TableName]"
646
+
647
+ ---
648
+
649
+ ## Fix a TypeScript error
650
+ "I got this TypeScript error in [filename]: [paste error]
651
+ Explain what it means and fix it."
652
+ `);
653
+
654
+ // ── run npm install automatically ─────────────────────────────────────────
655
+ log.info("Running npm install (this takes about 30 seconds)...");
656
+ const installed = run("npm install", dir);
657
+ if (installed) {
658
+ log.ok("npm install completed — all dependencies ready");
659
+ } else {
660
+ log.warn("npm install had issues try running npm install manually inside the project folder");
661
+ }
662
+
663
+ log.ok("Code App structure created — run npm start to launch the app");
664
+ }
665
+
666
+ // ─── AI context file writer ───────────────────────────────────────────────────
667
+
668
+ function writeAIContextFile(dir, projectName, appType, aiTool) {
669
+ const content = `# Project: ${projectName}
670
+
671
+ ## What this project does
672
+ [Fill in a one-paragraph description of your app what problem it solves, who uses it]
673
+
674
+ ## App type
675
+ ${appType} on Microsoft Power Platform
676
+
677
+ ## AI assistant
678
+ ${aiTool}
679
+
680
+ ## Coding conventions
681
+ - Use TypeScript (not plain JavaScript) wherever possible
682
+ - Use async/await (not .then() chains)
683
+ - All functions should have clear names that describe what they do
684
+ - Add a short comment above any complex logic explaining what it does
685
+
686
+ ## Power Platform context
687
+ - See the instructions/ folder for ${appType}-specific rules
688
+ - Environment URL is in the .env file (POWER_PLATFORM_URL)
689
+ - Use PAC CLI to push/pull changes — see README.md for commands
690
+
691
+ ## What to do when you get an error
692
+ Tell the AI: "I got this error: [paste error here]. What does it mean and how do I fix it?"
693
+ `;
694
+
695
+ if (aiTool === "Claude Code") {
696
+ write(path.join(dir, "CLAUDE.md"), content);
697
+ log.ok("Created CLAUDE.md Claude Code reads this automatically on every session");
698
+ } else {
699
+ fs.mkdirSync(path.join(dir, ".github"), { recursive: true });
700
+ write(path.join(dir, ".github", "copilot-instructions.md"), content);
701
+ log.ok("Created .github/copilot-instructions.md — Copilot reads this for context");
702
+ }
703
+ }
704
+
705
+ // ─── Main ─────────────────────────────────────────────────────────────────────
706
+
707
+ async function main() {
708
+ console.log(`
709
+ ${c.bold}${c.magenta}╔═══════════════════════════════════════════╗
710
+ ║ create-powerapp ⚡ ║
711
+ ║ Scaffold any Power App in one command ║
712
+ ╚═══════════════════════════════════════════╝${c.reset}
713
+
714
+ Welcome! Answer a few questions and your project will be ready
715
+ to open in VS Code and start prompting your AI assistant.
716
+ `);
717
+
718
+ // ── 1. Project name ──────────────────────────────────────────────────────
719
+ log.step("Project name");
720
+ log.dim("This becomes your folder name and app name. Use letters, numbers, hyphens.");
721
+ let projectName = await prompt(" Project name: ");
722
+ if (!projectName) projectName = "my-powerapp";
723
+ const safeName = projectName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
724
+
725
+ // ── 2. App type ──────────────────────────────────────────────────────────
726
+ log.step("Which type of Power App are you building?");
727
+ const appTypeChoice = await choose("App type:", [
728
+ { label: "Canvas App", desc: "— Design every screen pixel-by-pixel. Best for custom-branded employee tools & mobile apps" },
729
+ { label: "Model-Driven App", desc: "— Database-first. Power Apps builds the UI from your data model. Best for CRM / record management" },
730
+ { label: "PCF Component", desc: "— A reusable custom control (widget) to embed inside other Power Apps" },
731
+ { label: "Power Pages", desc: "— A public-facing website connected to your Dataverse data" },
732
+ { label: "Code App", desc: "— A full custom web app (React/TypeScript) hosted on Power Platform" },
733
+ ]);
734
+
735
+ // ── 3. AI assistant ──────────────────────────────────────────────────────
736
+ log.step("Which AI assistant are you using?");
737
+ const aiChoice = await choose("AI assistant:", [
738
+ { label: "GitHub Copilot", desc: "— You have a GitHub Copilot subscription or organisation license" },
739
+ { label: "Claude Code", desc: "— You have an Anthropic API key or Claude Code license" },
740
+ ]);
741
+
742
+ // ── 4. Power Platform environment URL ────────────────────────────────────
743
+ log.step("Power Platform environment URL (optional — press Enter to skip)");
744
+ log.dim("Find it at make.powerapps.com → Settings gear → Session details → Instance url");
745
+ log.dim("Example: https://yourcompany.crm.dynamics.com");
746
+ const envUrl = await prompt(" Environment URL: ");
747
+
748
+ // ── 5. Create folder ─────────────────────────────────────────────────────
749
+ log.step(`Creating project folder: ${safeName}/`);
750
+ const dir = path.join(process.cwd(), safeName);
751
+
752
+ if (fs.existsSync(dir)) {
753
+ const overwrite = await prompt(` Folder "${safeName}" already exists. Overwrite? (y/N): `);
754
+ if (overwrite.toLowerCase() !== "y") {
755
+ log.warn("Aborted. Choose a different project name.");
756
+ process.exit(0);
757
+ }
758
+ }
759
+
760
+ fs.mkdirSync(dir, { recursive: true });
761
+ log.ok(`Folder created: ${dir}`);
762
+
763
+ // ── 6. Shared files ──────────────────────────────────────────────────────
764
+ log.step("Creating shared project files...");
765
+
766
+ write(path.join(dir, ".gitignore"), gitignoreContent());
767
+ log.ok("Created .gitignore");
768
+
769
+ write(path.join(dir, ".env"), envUrl
770
+ ? envContent(projectName, appTypeChoice.label).replace("https://yourorg.crm.dynamics.com", envUrl)
771
+ : envContent(projectName, appTypeChoice.label)
772
+ );
773
+ log.ok("Created .env");
774
+
775
+ write(path.join(dir, "README.md"), readmeContent(projectName, appTypeChoice.label, aiChoice.label));
776
+ log.ok("Created README.md");
777
+
778
+ // ── 7. App-type scaffold ─────────────────────────────────────────────────
779
+ log.step(`Scaffolding ${appTypeChoice.label} files...`);
780
+ switch (appTypeChoice.label) {
781
+ case "Canvas App": scaffoldCanvas(dir, projectName, aiChoice.label); break;
782
+ case "Model-Driven App": scaffoldMDA(dir, projectName, aiChoice.label); break;
783
+ case "PCF Component": scaffoldPCF(dir, projectName, aiChoice.label); break;
784
+ case "Power Pages": scaffoldPowerPages(dir, projectName, aiChoice.label); break;
785
+ case "Code App": scaffoldCodeApp(dir, projectName, aiChoice.label); break;
786
+ }
787
+
788
+ // ── 8. AI context file ───────────────────────────────────────────────────
789
+ log.step("Creating AI context file...");
790
+ writeAIContextFile(dir, projectName, appTypeChoice.label, aiChoice.label);
791
+
792
+ // ── 9. Git init ──────────────────────────────────────────────────────────
793
+ log.step("Initialising Git...");
794
+ if (has("git")) {
795
+ run("git init", dir, true);
796
+ run("git add .", dir, true);
797
+ run('git commit -m "Initial scaffold by create-powerapp"', dir, true);
798
+ log.ok("Git initialised with first commit");
799
+ } else {
800
+ log.warn("Git not found — skipping. Install Git and run git init inside your project.");
801
+ }
802
+
803
+ // ── 10. PAC CLI auth ─────────────────────────────────────────────────────
804
+ if (envUrl) {
805
+ log.step("Connecting to Power Platform environment...");
806
+ if (has("pac")) {
807
+ log.info(`Running: pac auth create --url ${envUrl}`);
808
+ log.dim("A browser window will open. Sign in with your Microsoft work account.");
809
+ run(`pac auth create --url ${envUrl}`, dir);
810
+ } else {
811
+ log.warn("PAC CLI not found. Install it with: npm install -g pac");
812
+ log.warn("Then run: pac auth create --url " + envUrl);
813
+ }
814
+ }
815
+
816
+ // ── 11. Open in VS Code ──────────────────────────────────────────────────
817
+ log.step("Opening in VS Code...");
818
+ if (has("code")) {
819
+ run(`code "${dir}"`, process.cwd(), true);
820
+ log.ok("VS Code opened with your project");
821
+ } else {
822
+ log.warn("VS Code code command not found.");
823
+ log.dim("Open VS Code manually File → Open Folder → select: " + dir);
824
+ }
825
+
826
+ // ── Done ─────────────────────────────────────────────────────────────────
827
+ const isCodeApp = appTypeChoice.label === "Code App";
828
+ console.log(`
829
+ ${c.bold}${c.green}╔═══════════════════════════════════════════════════╗
830
+ Your project is ready! ║
831
+ ╚═══════════════════════════════════════════════════╝${c.reset}
832
+
833
+ ${c.bold}Project folder:${c.reset} ${dir}
834
+
835
+ ${c.bold}What to do next:${c.reset}
836
+
837
+ 1. VS Code is now open (or open it manually and open the folder above)
838
+ ${isCodeApp ? `
839
+ 2. ${c.bold}${c.green}Start the app:${c.reset} In the VS Code terminal, run:
840
+ ${c.cyan}npm start${c.reset}
841
+ Your browser will open automatically at http://localhost:3000
842
+ ` : `
843
+ 2. Read the ${c.cyan}prompts/starter.md${c.reset} file for ready-made prompts to get started
844
+ `}
845
+ 3. Open your AI assistant:
846
+ ${aiChoice.label === "Claude Code"
847
+ ? ` • In the VS Code terminal, run: ${c.cyan}claude${c.reset}
848
+ Or click the Claude Code icon in the left sidebar`
849
+ : ` • Click the Copilot Chat icon (💬) in the VS Code left sidebar`}
850
+
851
+ 4. Copy a prompt from ${c.cyan}prompts/starter.md${c.reset} and paste it in — or describe your app in your own words
852
+
853
+ 5. When you're happy with what the AI built, save your work:
854
+ ${c.dim}git add . → git commit -m "describe what you built" → git push${c.reset}
855
+
856
+ ${c.bold}${c.yellow}Tip:${c.reset} When something goes wrong, tell the AI:
857
+ "I got this error: [paste error]. What does it mean and how do I fix it?"
858
+
859
+ Happy building! 🚀
860
+ `);
861
+ rl.close();
862
+ }
863
+
864
+ main().catch((err) => {
865
+ log.error("Unexpected error: " + err.message);
866
+ rl.close();
867
+ process.exit(1);
868
+ });