create-powerapp 1.0.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 ADDED
@@ -0,0 +1,667 @@
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
+ write(path.join(dir, "instructions", "codeapp-instructions.md"), `# Code App Instructions
381
+
382
+ ## Type: Power Apps Code App (Code-first web app)
383
+ ## Project: ${projectName}
384
+
385
+ - Standard web app (React/TypeScript) hosted on Power Platform
386
+ - Use @microsoft/power-apps npm package to access connectors and platform APIs
387
+ - Authentication is handled by Microsoft Entra — do not build custom auth
388
+ - Call Power Platform connectors directly from JavaScript via the client library
389
+ - Publish with pac code push — no manual deployment pipeline needed
390
+ - Follows canvas app sharing limits and DLP policies set by admins
391
+ - Not supported in Power Apps mobile app or Power Apps for Windows
392
+ - Use npm start for local dev; the platform manages hosting in production
393
+ - Admin must enable Code Apps in Power Platform Admin Center before publishing
394
+ `);
395
+
396
+ write(path.join(dir, "src", "components", ".gitkeep"), "");
397
+ write(path.join(dir, "public", ".gitkeep"), "");
398
+
399
+ write(path.join(dir, "src", "App.tsx"), `// ${projectName} — Code App entry point
400
+ // Ask your AI assistant: "Set up the main App component for a Power Apps Code App called ${projectName}"
401
+
402
+ export default function App() {
403
+ return (
404
+ <div>
405
+ <h1>${projectName}</h1>
406
+ <p>Ask your AI to build this app. Open the chat and describe what you want.</p>
407
+ </div>
408
+ );
409
+ }
410
+ `);
411
+
412
+ write(path.join(dir, "src", "index.ts"), `// Entry point — ask your AI to fill this in
413
+ `);
414
+
415
+ write(path.join(dir, "prompts", "starter.md"), `# Starter Prompts for ${projectName}
416
+
417
+ ## First, initialise the Code App (run this command):
418
+ pac code init --outputDirectory .
419
+
420
+ ## Install dependencies:
421
+ npm install
422
+
423
+ ## Then ask the AI:
424
+ "I'm building a Power Apps Code App called ${projectName} using React and TypeScript.
425
+ Set up the project with a clean layout, routing between pages, and connect it to Microsoft Entra authentication using the @microsoft/power-apps package."
426
+
427
+ "Add a page called [PageName] that [describe what the page does]."
428
+
429
+ "Connect this app to a Power Platform connector called [ConnectorName] and show the data in a table."
430
+ `);
431
+
432
+ write(path.join(dir, "package.json"), JSON.stringify({
433
+ name: projectName.toLowerCase().replace(/\s+/g, "-"),
434
+ version: "0.1.0",
435
+ private: true,
436
+ scripts: {
437
+ start: "react-scripts start",
438
+ build: "react-scripts build",
439
+ },
440
+ dependencies: {
441
+ "@microsoft/power-apps": "latest",
442
+ react: "^18.0.0",
443
+ "react-dom": "^18.0.0",
444
+ },
445
+ devDependencies: {
446
+ typescript: "^5.0.0",
447
+ "@types/react": "^18.0.0",
448
+ "@types/react-dom": "^18.0.0",
449
+ },
450
+ }, null, 2));
451
+
452
+ write(path.join(dir, "tsconfig.json"), JSON.stringify({
453
+ compilerOptions: {
454
+ target: "ES2020",
455
+ lib: ["ES2020", "DOM"],
456
+ jsx: "react-jsx",
457
+ module: "ESNext",
458
+ moduleResolution: "bundler",
459
+ strict: true,
460
+ esModuleInterop: true,
461
+ skipLibCheck: true,
462
+ },
463
+ include: ["src"],
464
+ }, null, 2));
465
+
466
+ log.ok("Code App structure created");
467
+ log.dim("Next: pac code init --outputDirectory . then npm install");
468
+ }
469
+
470
+ // ─── AI context file writer ───────────────────────────────────────────────────
471
+
472
+ function writeAIContextFile(dir, projectName, appType, aiTool) {
473
+ const content = `# Project: ${projectName}
474
+
475
+ ## What this project does
476
+ [Fill in a one-paragraph description of your app — what problem it solves, who uses it]
477
+
478
+ ## App type
479
+ ${appType} on Microsoft Power Platform
480
+
481
+ ## AI assistant
482
+ ${aiTool}
483
+
484
+ ## Coding conventions
485
+ - Use TypeScript (not plain JavaScript) wherever possible
486
+ - Use async/await (not .then() chains)
487
+ - All functions should have clear names that describe what they do
488
+ - Add a short comment above any complex logic explaining what it does
489
+
490
+ ## Power Platform context
491
+ - See the instructions/ folder for ${appType}-specific rules
492
+ - Environment URL is in the .env file (POWER_PLATFORM_URL)
493
+ - Use PAC CLI to push/pull changes — see README.md for commands
494
+
495
+ ## What to do when you get an error
496
+ Tell the AI: "I got this error: [paste error here]. What does it mean and how do I fix it?"
497
+ `;
498
+
499
+ if (aiTool === "Claude Code") {
500
+ write(path.join(dir, "CLAUDE.md"), content);
501
+ log.ok("Created CLAUDE.md — Claude Code reads this automatically on every session");
502
+ } else {
503
+ fs.mkdirSync(path.join(dir, ".github"), { recursive: true });
504
+ write(path.join(dir, ".github", "copilot-instructions.md"), content);
505
+ log.ok("Created .github/copilot-instructions.md — Copilot reads this for context");
506
+ }
507
+ }
508
+
509
+ // ─── Main ─────────────────────────────────────────────────────────────────────
510
+
511
+ async function main() {
512
+ console.log(`
513
+ ${c.bold}${c.magenta}╔═══════════════════════════════════════════╗
514
+ ║ create-powerapp ⚡ ║
515
+ ║ Scaffold any Power App in one command ║
516
+ ╚═══════════════════════════════════════════╝${c.reset}
517
+
518
+ Welcome! Answer a few questions and your project will be ready
519
+ to open in VS Code and start prompting your AI assistant.
520
+ `);
521
+
522
+ // ── 1. Project name ──────────────────────────────────────────────────────
523
+ log.step("Project name");
524
+ log.dim("This becomes your folder name and app name. Use letters, numbers, hyphens.");
525
+ let projectName = await prompt(" Project name: ");
526
+ if (!projectName) projectName = "my-powerapp";
527
+ const safeName = projectName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
528
+
529
+ // ── 2. App type ──────────────────────────────────────────────────────────
530
+ log.step("Which type of Power App are you building?");
531
+ const appTypeChoice = await choose("App type:", [
532
+ { label: "Canvas App", desc: "— Design every screen pixel-by-pixel. Best for custom-branded employee tools & mobile apps" },
533
+ { label: "Model-Driven App", desc: "— Database-first. Power Apps builds the UI from your data model. Best for CRM / record management" },
534
+ { label: "PCF Component", desc: "— A reusable custom control (widget) to embed inside other Power Apps" },
535
+ { label: "Power Pages", desc: "— A public-facing website connected to your Dataverse data" },
536
+ { label: "Code App", desc: "— A full custom web app (React/TypeScript) hosted on Power Platform" },
537
+ ]);
538
+
539
+ // ── 3. AI assistant ──────────────────────────────────────────────────────
540
+ log.step("Which AI assistant are you using?");
541
+ const aiChoice = await choose("AI assistant:", [
542
+ { label: "GitHub Copilot", desc: "— You have a GitHub Copilot subscription or organisation license" },
543
+ { label: "Claude Code", desc: "— You have an Anthropic API key or Claude Code license" },
544
+ ]);
545
+
546
+ // ── 4. Power Platform environment URL ────────────────────────────────────
547
+ log.step("Power Platform environment URL (optional — press Enter to skip)");
548
+ log.dim("Find it at make.powerapps.com → Settings gear → Session details → Instance url");
549
+ log.dim("Example: https://yourcompany.crm.dynamics.com");
550
+ const envUrl = await prompt(" Environment URL: ");
551
+
552
+ // ── 5. Create folder ─────────────────────────────────────────────────────
553
+ log.step(`Creating project folder: ${safeName}/`);
554
+ const dir = path.join(process.cwd(), safeName);
555
+
556
+ if (fs.existsSync(dir)) {
557
+ const overwrite = await prompt(` Folder "${safeName}" already exists. Overwrite? (y/N): `);
558
+ if (overwrite.toLowerCase() !== "y") {
559
+ log.warn("Aborted. Choose a different project name.");
560
+ process.exit(0);
561
+ }
562
+ }
563
+
564
+ fs.mkdirSync(dir, { recursive: true });
565
+ log.ok(`Folder created: ${dir}`);
566
+
567
+ // ── 6. Shared files ──────────────────────────────────────────────────────
568
+ log.step("Creating shared project files...");
569
+
570
+ write(path.join(dir, ".gitignore"), gitignoreContent());
571
+ log.ok("Created .gitignore");
572
+
573
+ write(path.join(dir, ".env"), envUrl
574
+ ? envContent(projectName, appTypeChoice.label).replace("https://yourorg.crm.dynamics.com", envUrl)
575
+ : envContent(projectName, appTypeChoice.label)
576
+ );
577
+ log.ok("Created .env");
578
+
579
+ write(path.join(dir, "README.md"), readmeContent(projectName, appTypeChoice.label, aiChoice.label));
580
+ log.ok("Created README.md");
581
+
582
+ // ── 7. App-type scaffold ─────────────────────────────────────────────────
583
+ log.step(`Scaffolding ${appTypeChoice.label} files...`);
584
+ switch (appTypeChoice.label) {
585
+ case "Canvas App": scaffoldCanvas(dir, projectName, aiChoice.label); break;
586
+ case "Model-Driven App": scaffoldMDA(dir, projectName, aiChoice.label); break;
587
+ case "PCF Component": scaffoldPCF(dir, projectName, aiChoice.label); break;
588
+ case "Power Pages": scaffoldPowerPages(dir, projectName, aiChoice.label); break;
589
+ case "Code App": scaffoldCodeApp(dir, projectName, aiChoice.label); break;
590
+ }
591
+
592
+ // ── 8. AI context file ───────────────────────────────────────────────────
593
+ log.step("Creating AI context file...");
594
+ writeAIContextFile(dir, projectName, appTypeChoice.label, aiChoice.label);
595
+
596
+ // ── 9. Git init ──────────────────────────────────────────────────────────
597
+ log.step("Initialising Git...");
598
+ if (has("git")) {
599
+ run("git init", dir, true);
600
+ run("git add .", dir, true);
601
+ run('git commit -m "Initial scaffold by create-powerapp"', dir, true);
602
+ log.ok("Git initialised with first commit");
603
+ } else {
604
+ log.warn("Git not found — skipping. Install Git and run git init inside your project.");
605
+ }
606
+
607
+ // ── 10. PAC CLI auth ─────────────────────────────────────────────────────
608
+ if (envUrl) {
609
+ log.step("Connecting to Power Platform environment...");
610
+ if (has("pac")) {
611
+ log.info(`Running: pac auth create --url ${envUrl}`);
612
+ log.dim("A browser window will open. Sign in with your Microsoft work account.");
613
+ run(`pac auth create --url ${envUrl}`, dir);
614
+ } else {
615
+ log.warn("PAC CLI not found. Install it with: npm install -g pac");
616
+ log.warn("Then run: pac auth create --url " + envUrl);
617
+ }
618
+ }
619
+
620
+ // ── 11. Open in VS Code ──────────────────────────────────────────────────
621
+ log.step("Opening in VS Code...");
622
+ if (has("code")) {
623
+ run(`code "${dir}"`, process.cwd(), true);
624
+ log.ok("VS Code opened with your project");
625
+ } else {
626
+ log.warn("VS Code code command not found.");
627
+ log.dim("Open VS Code manually → File → Open Folder → select: " + dir);
628
+ }
629
+
630
+ // ── Done ─────────────────────────────────────────────────────────────────
631
+ console.log(`
632
+ ${c.bold}${c.green}╔═══════════════════════════════════════════════════╗
633
+ ║ ✅ Your project is ready! ║
634
+ ╚═══════════════════════════════════════════════════╝${c.reset}
635
+
636
+ ${c.bold}Project folder:${c.reset} ${dir}
637
+
638
+ ${c.bold}What to do next:${c.reset}
639
+
640
+ 1. VS Code is now open (or open it manually and open the folder above)
641
+
642
+ 2. Read the ${c.cyan}prompts/starter.md${c.reset} file for ready-made prompts to get started
643
+
644
+ 3. Open your AI assistant:
645
+ ${aiChoice.label === "Claude Code"
646
+ ? ` • In the VS Code terminal, run: ${c.cyan}claude${c.reset}
647
+ • Or click the Claude Code icon in the left sidebar`
648
+ : ` • Click the Copilot Chat icon (💬) in the VS Code left sidebar`}
649
+
650
+ 4. Copy a prompt from ${c.cyan}prompts/starter.md${c.reset} and paste it in — or describe your app in your own words
651
+
652
+ 5. When you're happy with what the AI built, save your work:
653
+ ${c.dim}git add . → git commit -m "describe what you built" → git push${c.reset}
654
+
655
+ ${c.bold}${c.yellow}Tip:${c.reset} When something goes wrong, tell the AI:
656
+ "I got this error: [paste error]. What does it mean and how do I fix it?"
657
+
658
+ Happy building! 🚀
659
+ `);
660
+ rl.close();
661
+ }
662
+
663
+ main().catch((err) => {
664
+ log.error("Unexpected error: " + err.message);
665
+ rl.close();
666
+ process.exit(1);
667
+ });