@codecanvascollective/scaffold 0.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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +67 -0
  3. package/dist/chunk-2A65KFCS.js +76 -0
  4. package/dist/chunk-2A65KFCS.js.map +1 -0
  5. package/dist/file-5IKT7CEX.js +24 -0
  6. package/dist/file-5IKT7CEX.js.map +1 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.js +833 -0
  9. package/dist/index.js.map +1 -0
  10. package/package.json +70 -0
  11. package/src/templates/angular/base/angular.json.hbs +35 -0
  12. package/src/templates/angular/base/package.json.hbs +36 -0
  13. package/src/templates/angular/base/src/app/app.component.ts.hbs +19 -0
  14. package/src/templates/angular/base/src/app/app.config.ts.hbs +5 -0
  15. package/src/templates/angular/base/src/main.ts.hbs +7 -0
  16. package/src/templates/angular/base/tsconfig.json.hbs +26 -0
  17. package/src/templates/express/base/package.json.hbs +32 -0
  18. package/src/templates/express/base/src/index.ts.hbs +20 -0
  19. package/src/templates/express/base/src/middleware/errorHandler.ts.hbs +15 -0
  20. package/src/templates/express/base/src/routes/index.ts.hbs +11 -0
  21. package/src/templates/express/base/tsconfig.json.hbs +18 -0
  22. package/src/templates/express/with-prisma/prisma/schema.prisma.hbs +16 -0
  23. package/src/templates/express/with-prisma/prisma/seed.ts.hbs +27 -0
  24. package/src/templates/express/with-prisma/src/lib/db.ts.hbs +11 -0
  25. package/src/templates/fastapi/base/app/main.py.hbs +13 -0
  26. package/src/templates/fastapi/base/app/models/__init__.py.hbs +1 -0
  27. package/src/templates/fastapi/base/app/routes/__init__.py.hbs +1 -0
  28. package/src/templates/fastapi/base/pyproject.toml.hbs +8 -0
  29. package/src/templates/fastapi/base/requirements.txt.hbs +7 -0
  30. package/src/templates/nextjs/base/next.config.ts.hbs +5 -0
  31. package/src/templates/nextjs/base/package.json.hbs +33 -0
  32. package/src/templates/nextjs/base/src/app/globals.css.hbs +22 -0
  33. package/src/templates/nextjs/base/src/app/layout.tsx.hbs +19 -0
  34. package/src/templates/nextjs/base/src/app/page.tsx.hbs +8 -0
  35. package/src/templates/nextjs/base/tsconfig.json.hbs +25 -0
  36. package/src/templates/nextjs/with-auth/src/app/api/auth/[...nextauth]/route.ts.hbs +6 -0
  37. package/src/templates/nextjs/with-auth/src/lib/auth.ts.hbs +14 -0
  38. package/src/templates/nextjs/with-prisma/prisma/schema.prisma.hbs +16 -0
  39. package/src/templates/nextjs/with-prisma/prisma/seed.ts.hbs +27 -0
  40. package/src/templates/nextjs/with-prisma/src/lib/db.ts.hbs +11 -0
  41. package/src/templates/react/base/index.html.hbs +12 -0
  42. package/src/templates/react/base/package.json.hbs +36 -0
  43. package/src/templates/react/base/src/App.tsx.hbs +10 -0
  44. package/src/templates/react/base/src/index.css.hbs +17 -0
  45. package/src/templates/react/base/src/main.tsx.hbs +10 -0
  46. package/src/templates/react/base/tests/App.test.tsx.hbs +10 -0
  47. package/src/templates/react/base/tsconfig.json.hbs +16 -0
  48. package/src/templates/react/base/vite.config.ts.hbs +13 -0
  49. package/src/templates/react/with-shadcn/components.json.hbs +16 -0
  50. package/src/templates/react/with-shadcn/src/lib/utils.ts.hbs +6 -0
  51. package/src/templates/react/with-tailwind/postcss.config.js.hbs +6 -0
  52. package/src/templates/react/with-tailwind/src/index.css.hbs +3 -0
  53. package/src/templates/react/with-tailwind/tailwind.config.ts.hbs +9 -0
  54. package/src/templates/shared/docker/Dockerfile.hbs +42 -0
  55. package/src/templates/shared/docker/docker-compose.yml.hbs +30 -0
  56. package/src/templates/shared/eslint.config.js.hbs +17 -0
  57. package/src/templates/shared/github-ci.yml.hbs +58 -0
  58. package/src/templates/shared/gitignore.hbs +36 -0
  59. package/src/templates/shared/license.hbs +21 -0
  60. package/src/templates/shared/prettier.config.js.hbs +7 -0
  61. package/src/templates/shared/readme.md.hbs +43 -0
  62. package/src/templates/shared/tsconfig.base.json.hbs +14 -0
package/dist/index.js ADDED
@@ -0,0 +1,833 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ createDir,
4
+ directoryExists,
5
+ getTemplatesDir,
6
+ renderAndWrite
7
+ } from "./chunk-2A65KFCS.js";
8
+
9
+ // src/cli.ts
10
+ import { Command as Command4 } from "commander";
11
+
12
+ // src/commands/create.ts
13
+ import { Command } from "commander";
14
+ import path4 from "path";
15
+ import fs2 from "fs-extra";
16
+
17
+ // src/prompts/framework.ts
18
+ import inquirer from "inquirer";
19
+ async function promptProjectType() {
20
+ const { projectType } = await inquirer.prompt([
21
+ {
22
+ type: "list",
23
+ name: "projectType",
24
+ message: "What type of project?",
25
+ choices: [
26
+ { name: "Frontend (React, Next.js, Angular)", value: "frontend" },
27
+ { name: "Backend (Express, FastAPI)", value: "backend" },
28
+ { name: "Full-Stack (Frontend + Backend)", value: "fullstack" }
29
+ ]
30
+ }
31
+ ]);
32
+ return projectType;
33
+ }
34
+ async function promptFramework(projectType) {
35
+ if (projectType === "frontend") {
36
+ const { framework: framework2 } = await inquirer.prompt([
37
+ {
38
+ type: "list",
39
+ name: "framework",
40
+ message: "Which frontend framework?",
41
+ choices: [
42
+ { name: "React + Vite", value: "react" },
43
+ { name: "Next.js (App Router)", value: "nextjs" },
44
+ { name: "Angular 18", value: "angular" }
45
+ ]
46
+ }
47
+ ]);
48
+ return framework2;
49
+ }
50
+ if (projectType === "backend") {
51
+ const { framework: framework2 } = await inquirer.prompt([
52
+ {
53
+ type: "list",
54
+ name: "framework",
55
+ message: "Which backend framework?",
56
+ choices: [
57
+ { name: "Express.js", value: "express" },
58
+ { name: "FastAPI (Python)", value: "fastapi" }
59
+ ]
60
+ }
61
+ ]);
62
+ return framework2;
63
+ }
64
+ const { framework } = await inquirer.prompt([
65
+ {
66
+ type: "list",
67
+ name: "framework",
68
+ message: "Which frontend framework?",
69
+ choices: [
70
+ { name: "React + Vite", value: "react" },
71
+ { name: "Next.js", value: "nextjs" },
72
+ { name: "Angular 18", value: "angular" }
73
+ ]
74
+ }
75
+ ]);
76
+ return framework;
77
+ }
78
+
79
+ // src/prompts/features.ts
80
+ import inquirer2 from "inquirer";
81
+ async function promptFeatures(projectType, framework) {
82
+ const isPython = framework === "fastapi";
83
+ const choices = [
84
+ {
85
+ name: "TypeScript",
86
+ value: "typescript",
87
+ checked: true,
88
+ disabled: isPython ? "N/A for Python" : false
89
+ },
90
+ { name: "ESLint + Prettier", value: "eslint", checked: !isPython, disabled: isPython ? "N/A for Python" : false },
91
+ { name: "Tailwind CSS", value: "tailwind", checked: false },
92
+ { name: "shadcn/ui components", value: "shadcn", checked: false },
93
+ { name: isPython ? "Pytest testing" : "Vitest testing", value: "testing", checked: true },
94
+ { name: "GitHub Actions CI/CD", value: "githubActions", checked: false },
95
+ { name: "Docker setup", value: "docker", checked: false },
96
+ { name: "Husky pre-commit hooks", value: "husky", checked: false, disabled: isPython ? "N/A for Python" : false },
97
+ { name: ".env example file", value: "envExample", checked: false }
98
+ ];
99
+ const filteredChoices = projectType === "backend" ? choices.filter((c) => !["tailwind", "shadcn"].includes(c.value)) : choices;
100
+ const { features } = await inquirer2.prompt([
101
+ {
102
+ type: "checkbox",
103
+ name: "features",
104
+ message: "Select features:",
105
+ choices: filteredChoices
106
+ }
107
+ ]);
108
+ const selectedFeatures = features;
109
+ return {
110
+ typescript: !isPython && selectedFeatures.includes("typescript"),
111
+ eslint: !isPython && selectedFeatures.includes("eslint"),
112
+ prettier: !isPython && selectedFeatures.includes("eslint"),
113
+ tailwind: selectedFeatures.includes("tailwind"),
114
+ shadcn: selectedFeatures.includes("shadcn"),
115
+ testing: selectedFeatures.includes("testing"),
116
+ githubActions: selectedFeatures.includes("githubActions"),
117
+ docker: selectedFeatures.includes("docker"),
118
+ husky: !isPython && selectedFeatures.includes("husky"),
119
+ envExample: selectedFeatures.includes("envExample")
120
+ };
121
+ }
122
+
123
+ // src/prompts/config.ts
124
+ import inquirer3 from "inquirer";
125
+
126
+ // src/constants.ts
127
+ var FRAMEWORKS = {
128
+ react: {
129
+ name: "react",
130
+ displayName: "React + Vite",
131
+ description: "React 19 with Vite and TypeScript",
132
+ type: "frontend",
133
+ variants: ["base", "with-tailwind", "with-shadcn"],
134
+ language: "typescript"
135
+ },
136
+ nextjs: {
137
+ name: "nextjs",
138
+ displayName: "Next.js (App Router)",
139
+ description: "Next.js 15 with App Router and TypeScript",
140
+ type: "frontend",
141
+ variants: ["base", "with-auth", "with-prisma"],
142
+ language: "typescript"
143
+ },
144
+ angular: {
145
+ name: "angular",
146
+ displayName: "Angular 18",
147
+ description: "Angular 18 standalone with TypeScript",
148
+ type: "frontend",
149
+ variants: ["base"],
150
+ language: "typescript"
151
+ },
152
+ express: {
153
+ name: "express",
154
+ displayName: "Express.js",
155
+ description: "Express 5 with TypeScript",
156
+ type: "backend",
157
+ variants: ["base", "with-prisma"],
158
+ language: "typescript"
159
+ },
160
+ fastapi: {
161
+ name: "fastapi",
162
+ displayName: "FastAPI",
163
+ description: "FastAPI with Pydantic and Python",
164
+ type: "backend",
165
+ variants: ["base"],
166
+ language: "python"
167
+ }
168
+ };
169
+ var VARIANT_DISPLAY_NAMES = {
170
+ base: "Base (minimal setup)",
171
+ "with-tailwind": "With Tailwind CSS",
172
+ "with-shadcn": "With shadcn/ui",
173
+ "with-auth": "With NextAuth.js",
174
+ "with-prisma": "With Prisma ORM"
175
+ };
176
+
177
+ // src/prompts/config.ts
178
+ async function promptVariant(framework) {
179
+ const meta = FRAMEWORKS[framework];
180
+ if (meta.variants.length <= 1) {
181
+ return "base";
182
+ }
183
+ const { variant } = await inquirer3.prompt([
184
+ {
185
+ type: "list",
186
+ name: "variant",
187
+ message: "Select a template variant:",
188
+ choices: meta.variants.map((v) => ({
189
+ name: VARIANT_DISPLAY_NAMES[v],
190
+ value: v
191
+ }))
192
+ }
193
+ ]);
194
+ return variant;
195
+ }
196
+ async function promptPackageManager() {
197
+ const { packageManager } = await inquirer3.prompt([
198
+ {
199
+ type: "list",
200
+ name: "packageManager",
201
+ message: "Package manager?",
202
+ choices: [
203
+ { name: "npm", value: "npm" },
204
+ { name: "yarn", value: "yarn" },
205
+ { name: "pnpm", value: "pnpm" }
206
+ ]
207
+ }
208
+ ]);
209
+ return packageManager;
210
+ }
211
+ async function promptGitInit() {
212
+ const { gitInit } = await inquirer3.prompt([
213
+ {
214
+ type: "confirm",
215
+ name: "gitInit",
216
+ message: "Initialize git repository?",
217
+ default: true
218
+ }
219
+ ]);
220
+ return gitInit;
221
+ }
222
+
223
+ // src/prompts/index.ts
224
+ async function runPrompts(projectName) {
225
+ const type = await promptProjectType();
226
+ const framework = await promptFramework(type);
227
+ const variant = await promptVariant(framework);
228
+ const features = await promptFeatures(type, framework);
229
+ const isPython = framework === "fastapi";
230
+ const packageManager = isPython ? "npm" : await promptPackageManager();
231
+ const gitInit = await promptGitInit();
232
+ return {
233
+ name: projectName,
234
+ type,
235
+ framework,
236
+ variant,
237
+ features,
238
+ packageManager,
239
+ gitInit
240
+ };
241
+ }
242
+
243
+ // src/generators/react.ts
244
+ import path2 from "path";
245
+
246
+ // src/generators/base.ts
247
+ import path from "path";
248
+ import fs from "fs-extra";
249
+ import { execSync } from "child_process";
250
+
251
+ // src/utils/logger.ts
252
+ import chalk from "chalk";
253
+ import ora from "ora";
254
+ var verbose = false;
255
+ function setVerbose(value) {
256
+ verbose = value;
257
+ }
258
+ function success(message) {
259
+ console.log(chalk.green("\u2714"), message);
260
+ }
261
+ function error(message) {
262
+ console.log(chalk.red("\u2716"), message);
263
+ }
264
+ function debug(message) {
265
+ if (verbose) {
266
+ console.log(chalk.gray("\u2B25"), chalk.gray(message));
267
+ }
268
+ }
269
+ function spinner(text) {
270
+ return ora({ text, color: "cyan" });
271
+ }
272
+ function newLine() {
273
+ console.log();
274
+ }
275
+ function banner(text) {
276
+ console.log(chalk.bold.cyan(text));
277
+ }
278
+
279
+ // src/utils/validator.ts
280
+ var VALID_PROJECT_NAME = /^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
281
+ function validateProjectName(name) {
282
+ if (!name || name.trim().length === 0) {
283
+ return "Project name is required.";
284
+ }
285
+ if (!VALID_PROJECT_NAME.test(name)) {
286
+ return "Project name must be a valid npm package name (lowercase, no spaces, can use hyphens and dots).";
287
+ }
288
+ if (name.length > 214) {
289
+ return "Project name must be less than 214 characters.";
290
+ }
291
+ return true;
292
+ }
293
+ async function validateDirectory(dirPath) {
294
+ if (await directoryExists(dirPath)) {
295
+ return `Directory "${dirPath}" already exists. Use --force to overwrite.`;
296
+ }
297
+ return true;
298
+ }
299
+
300
+ // src/generators/base.ts
301
+ var BaseGenerator = class {
302
+ config;
303
+ targetDir;
304
+ templatesDir;
305
+ result = { filesCreated: [], warnings: [] };
306
+ constructor(config, targetDir) {
307
+ this.config = config;
308
+ this.targetDir = targetDir;
309
+ this.templatesDir = getTemplatesDir();
310
+ }
311
+ async generate() {
312
+ await this.createProjectDir();
313
+ await this.generateBase();
314
+ await this.applyVariant();
315
+ await this.applySharedConfigs();
316
+ await this.applyFeatures();
317
+ await this.initGit();
318
+ return this.result;
319
+ }
320
+ async applyVariant() {
321
+ if (this.config.variant === "base") return;
322
+ const variantDir = this.getVariantTemplateDir();
323
+ if (await fs.pathExists(variantDir)) {
324
+ const s = spinner(`Applying ${this.config.variant} variant`);
325
+ s.start();
326
+ await this.renderDirectory(variantDir, this.targetDir);
327
+ s.stop();
328
+ success(`Applied ${this.config.variant} variant`);
329
+ }
330
+ }
331
+ async applySharedConfigs() {
332
+ const sharedDir = path.join(this.templatesDir, "shared");
333
+ if (this.config.features.eslint) {
334
+ const s = spinner("Setting up ESLint + Prettier");
335
+ s.start();
336
+ await this.renderTemplateFile(
337
+ path.join(sharedDir, "eslint.config.js.hbs"),
338
+ path.join(this.targetDir, "eslint.config.js")
339
+ );
340
+ await this.renderTemplateFile(
341
+ path.join(sharedDir, "prettier.config.js.hbs"),
342
+ path.join(this.targetDir, ".prettierrc")
343
+ );
344
+ s.stop();
345
+ success("Setting up ESLint + Prettier");
346
+ }
347
+ await this.renderTemplateFile(
348
+ path.join(sharedDir, "gitignore.hbs"),
349
+ path.join(this.targetDir, ".gitignore")
350
+ );
351
+ const s2 = spinner("Generating README.md");
352
+ s2.start();
353
+ await this.renderTemplateFile(
354
+ path.join(sharedDir, "readme.md.hbs"),
355
+ path.join(this.targetDir, "README.md")
356
+ );
357
+ s2.stop();
358
+ success("Generating README.md");
359
+ await this.renderTemplateFile(
360
+ path.join(sharedDir, "license.hbs"),
361
+ path.join(this.targetDir, "LICENSE")
362
+ );
363
+ }
364
+ async applyFeatures() {
365
+ const sharedDir = path.join(this.templatesDir, "shared");
366
+ if (this.config.features.githubActions) {
367
+ const s = spinner("Creating GitHub Actions workflow");
368
+ s.start();
369
+ await this.renderTemplateFile(
370
+ path.join(sharedDir, "github-ci.yml.hbs"),
371
+ path.join(this.targetDir, ".github", "workflows", "ci.yml")
372
+ );
373
+ s.stop();
374
+ success("Creating GitHub Actions workflow");
375
+ }
376
+ if (this.config.features.docker) {
377
+ const s = spinner("Adding Docker configuration");
378
+ s.start();
379
+ await this.renderTemplateFile(
380
+ path.join(sharedDir, "docker", "Dockerfile.hbs"),
381
+ path.join(this.targetDir, "Dockerfile")
382
+ );
383
+ await this.renderTemplateFile(
384
+ path.join(sharedDir, "docker", "docker-compose.yml.hbs"),
385
+ path.join(this.targetDir, "docker-compose.yml")
386
+ );
387
+ s.stop();
388
+ success("Adding Docker configuration");
389
+ }
390
+ }
391
+ async createProjectDir() {
392
+ const s = spinner("Creating project structure");
393
+ s.start();
394
+ await createDir(this.targetDir);
395
+ s.stop();
396
+ success("Creating project structure");
397
+ }
398
+ async initGit() {
399
+ if (!this.config.gitInit) return;
400
+ const s = spinner("Initializing git repository");
401
+ s.start();
402
+ try {
403
+ execSync("git init", { cwd: this.targetDir, stdio: "pipe" });
404
+ execSync("git add -A", { cwd: this.targetDir, stdio: "pipe" });
405
+ execSync('git commit -m "Initial commit from scaffold"', {
406
+ cwd: this.targetDir,
407
+ stdio: "pipe"
408
+ });
409
+ s.stop();
410
+ success("Initializing git repository");
411
+ } catch {
412
+ s.stop();
413
+ debug("Git initialization skipped (git not available)");
414
+ }
415
+ }
416
+ async renderTemplateFile(templatePath, outputPath) {
417
+ const templateData = this.getTemplateData();
418
+ if (await fs.pathExists(templatePath)) {
419
+ await renderAndWrite(templatePath, outputPath, templateData);
420
+ this.result.filesCreated.push(outputPath);
421
+ } else {
422
+ debug(`Template not found: ${templatePath}`);
423
+ }
424
+ }
425
+ async renderDirectory(srcDir, destDir) {
426
+ const entries = await fs.readdir(srcDir, { withFileTypes: true });
427
+ for (const entry of entries) {
428
+ const srcPath = path.join(srcDir, entry.name);
429
+ const destName = entry.name.replace(/\.hbs$/, "");
430
+ const destPath = path.join(destDir, destName);
431
+ if (entry.isDirectory()) {
432
+ await this.renderDirectory(srcPath, destPath);
433
+ } else if (entry.name.endsWith(".hbs")) {
434
+ await this.renderTemplateFile(srcPath, destPath);
435
+ } else {
436
+ await fs.ensureDir(path.dirname(destPath));
437
+ await fs.copy(srcPath, destPath);
438
+ this.result.filesCreated.push(destPath);
439
+ }
440
+ }
441
+ }
442
+ getTemplateData() {
443
+ return {
444
+ ...this.config,
445
+ year: (/* @__PURE__ */ new Date()).getFullYear()
446
+ };
447
+ }
448
+ getBaseTemplateDir() {
449
+ return path.join(this.templatesDir, this.config.framework, "base");
450
+ }
451
+ getVariantTemplateDir() {
452
+ return path.join(this.templatesDir, this.config.framework, this.config.variant);
453
+ }
454
+ };
455
+
456
+ // src/generators/react.ts
457
+ var ReactGenerator = class extends BaseGenerator {
458
+ async generateBase() {
459
+ const baseDir = this.getBaseTemplateDir();
460
+ const s = spinner("Configuring React + Vite + TypeScript");
461
+ s.start();
462
+ await this.renderDirectory(baseDir, this.targetDir);
463
+ s.stop();
464
+ success("Configuring React + Vite + TypeScript");
465
+ if (this.config.features.testing) {
466
+ success("Configuring Vitest");
467
+ }
468
+ }
469
+ getVariantPath(variant) {
470
+ return path2.join(this.templatesDir, "react", variant);
471
+ }
472
+ };
473
+
474
+ // src/generators/nextjs.ts
475
+ var NextjsGenerator = class extends BaseGenerator {
476
+ async generateBase() {
477
+ const baseDir = this.getBaseTemplateDir();
478
+ const s = spinner("Configuring Next.js + App Router + TypeScript");
479
+ s.start();
480
+ await this.renderDirectory(baseDir, this.targetDir);
481
+ s.stop();
482
+ success("Configuring Next.js + App Router + TypeScript");
483
+ if (this.config.features.testing) {
484
+ success("Configuring Vitest");
485
+ }
486
+ }
487
+ };
488
+
489
+ // src/generators/angular.ts
490
+ var AngularGenerator = class extends BaseGenerator {
491
+ async generateBase() {
492
+ const baseDir = this.getBaseTemplateDir();
493
+ const s = spinner("Configuring Angular 18 + TypeScript");
494
+ s.start();
495
+ await this.renderDirectory(baseDir, this.targetDir);
496
+ s.stop();
497
+ success("Configuring Angular 18 + TypeScript");
498
+ }
499
+ };
500
+
501
+ // src/generators/express.ts
502
+ var ExpressGenerator = class extends BaseGenerator {
503
+ async generateBase() {
504
+ const baseDir = this.getBaseTemplateDir();
505
+ const s = spinner("Configuring Express 5 + TypeScript");
506
+ s.start();
507
+ await this.renderDirectory(baseDir, this.targetDir);
508
+ s.stop();
509
+ success("Configuring Express 5 + TypeScript");
510
+ if (this.config.features.testing) {
511
+ success("Configuring Vitest");
512
+ }
513
+ }
514
+ };
515
+
516
+ // src/generators/fastapi.ts
517
+ var FastAPIGenerator = class extends BaseGenerator {
518
+ async generateBase() {
519
+ const baseDir = this.getBaseTemplateDir();
520
+ const s = spinner("Configuring FastAPI + Pydantic");
521
+ s.start();
522
+ await this.renderDirectory(baseDir, this.targetDir);
523
+ s.stop();
524
+ success("Configuring FastAPI + Pydantic");
525
+ if (this.config.features.testing) {
526
+ success("Configuring Pytest");
527
+ }
528
+ }
529
+ async applySharedConfigs() {
530
+ const path5 = await import("path");
531
+ const sharedDir = path5.join(this.templatesDir, "shared");
532
+ await this.renderTemplateFile(
533
+ path5.join(sharedDir, "gitignore.hbs"),
534
+ path5.join(this.targetDir, ".gitignore")
535
+ );
536
+ const s = spinner("Generating README.md");
537
+ s.start();
538
+ await this.renderTemplateFile(
539
+ path5.join(sharedDir, "readme.md.hbs"),
540
+ path5.join(this.targetDir, "README.md")
541
+ );
542
+ s.stop();
543
+ success("Generating README.md");
544
+ await this.renderTemplateFile(
545
+ path5.join(sharedDir, "license.hbs"),
546
+ path5.join(this.targetDir, "LICENSE")
547
+ );
548
+ }
549
+ };
550
+
551
+ // src/generators/fullstack.ts
552
+ import path3 from "path";
553
+ var FullstackGenerator = class extends BaseGenerator {
554
+ async generateBase() {
555
+ const s = spinner("Setting up full-stack monorepo");
556
+ s.start();
557
+ const webDir = path3.join(this.targetDir, "apps", "web");
558
+ const apiDir = path3.join(this.targetDir, "apps", "api");
559
+ await createDir(webDir);
560
+ await createDir(apiDir);
561
+ s.stop();
562
+ success("Setting up full-stack monorepo");
563
+ const frontendConfig = {
564
+ ...this.config,
565
+ type: "frontend",
566
+ gitInit: false
567
+ };
568
+ const FrontendGenerator = this.getFrontendGeneratorClass();
569
+ const frontendGen = new FrontendGenerator(frontendConfig, webDir);
570
+ await frontendGen.generateBase();
571
+ const backendConfig = {
572
+ ...this.config,
573
+ type: "backend",
574
+ framework: "express",
575
+ gitInit: false
576
+ };
577
+ const backendGen = new ExpressGenerator(backendConfig, apiDir);
578
+ await backendGen.generateBase();
579
+ const rootPkgPath = path3.join(this.targetDir, "package.json");
580
+ const rootPkg = {
581
+ name: this.config.name,
582
+ private: true,
583
+ workspaces: ["apps/*"],
584
+ scripts: {
585
+ dev: "npm run --workspaces dev",
586
+ build: "npm run --workspaces build",
587
+ lint: "npm run --workspaces lint",
588
+ test: "npm run --workspaces test"
589
+ }
590
+ };
591
+ const { writeFile: writeFile2 } = await import("./file-5IKT7CEX.js");
592
+ await writeFile2(rootPkgPath, JSON.stringify(rootPkg, null, 2) + "\n");
593
+ }
594
+ getFrontendGeneratorClass() {
595
+ switch (this.config.framework) {
596
+ case "nextjs":
597
+ return NextjsGenerator;
598
+ case "angular":
599
+ return AngularGenerator;
600
+ case "react":
601
+ default:
602
+ return ReactGenerator;
603
+ }
604
+ }
605
+ };
606
+
607
+ // src/generators/index.ts
608
+ function createGenerator(config, targetDir) {
609
+ if (config.type === "fullstack") {
610
+ return new FullstackGenerator(config, targetDir);
611
+ }
612
+ switch (config.framework) {
613
+ case "react":
614
+ return new ReactGenerator(config, targetDir);
615
+ case "nextjs":
616
+ return new NextjsGenerator(config, targetDir);
617
+ case "angular":
618
+ return new AngularGenerator(config, targetDir);
619
+ case "express":
620
+ return new ExpressGenerator(config, targetDir);
621
+ case "fastapi":
622
+ return new FastAPIGenerator(config, targetDir);
623
+ default:
624
+ throw new Error(`Unknown framework: ${config.framework}`);
625
+ }
626
+ }
627
+
628
+ // src/types/config.ts
629
+ var DEFAULT_FEATURES = {
630
+ typescript: true,
631
+ eslint: true,
632
+ prettier: true,
633
+ tailwind: false,
634
+ shadcn: false,
635
+ testing: true,
636
+ githubActions: false,
637
+ docker: false,
638
+ husky: false,
639
+ envExample: false
640
+ };
641
+ var DEFAULT_CONFIG = {
642
+ name: "",
643
+ type: "frontend",
644
+ framework: "react",
645
+ variant: "base",
646
+ features: { ...DEFAULT_FEATURES },
647
+ packageManager: "npm",
648
+ gitInit: true
649
+ };
650
+
651
+ // src/commands/create.ts
652
+ function createCommand() {
653
+ const cmd = new Command("create").description("Create a new project").argument("<project-name>", "Name of the project").option("--react", "Create a React + Vite project").option("--nextjs", "Create a Next.js project").option("--angular", "Create an Angular project").option("--express", "Create an Express.js project").option("--fastapi", "Create a FastAPI project").option("-y, --yes", "Skip prompts and use defaults").option("--from <path>", "Create from a config file").option("--force", "Overwrite existing directory").action(async (projectName, options) => {
654
+ try {
655
+ await handleCreate(projectName, options);
656
+ } catch (err) {
657
+ error(err instanceof Error ? err.message : String(err));
658
+ process.exit(1);
659
+ }
660
+ });
661
+ return cmd;
662
+ }
663
+ async function handleCreate(projectName, options) {
664
+ const nameValidation = validateProjectName(projectName);
665
+ if (nameValidation !== true) {
666
+ throw new Error(nameValidation);
667
+ }
668
+ const targetDir = path4.resolve(process.cwd(), projectName);
669
+ if (!options.force) {
670
+ const dirValidation = await validateDirectory(targetDir);
671
+ if (dirValidation !== true) {
672
+ throw new Error(dirValidation);
673
+ }
674
+ }
675
+ let config;
676
+ if (options.from) {
677
+ config = await loadConfigFromFile(options.from, projectName);
678
+ } else if (options.yes || hasFrameworkFlag(options)) {
679
+ config = buildQuickConfig(projectName, options);
680
+ } else {
681
+ config = await runPrompts(projectName);
682
+ }
683
+ newLine();
684
+ banner(`Creating project "${config.name}"...`);
685
+ newLine();
686
+ const generator = createGenerator(config, targetDir);
687
+ await generator.generate();
688
+ newLine();
689
+ success(`Project "${config.name}" created successfully! \u{1F389}`);
690
+ newLine();
691
+ console.log("Next steps:");
692
+ console.log(` cd ${config.name}`);
693
+ if (config.framework === "fastapi") {
694
+ console.log(" pip install -r requirements.txt");
695
+ console.log(" uvicorn app.main:app --reload");
696
+ } else {
697
+ console.log(` ${config.packageManager} install`);
698
+ console.log(` ${config.packageManager} run dev`);
699
+ }
700
+ newLine();
701
+ console.log("Happy coding! \u{1F680}");
702
+ newLine();
703
+ }
704
+ function hasFrameworkFlag(options) {
705
+ return !!(options.react || options.nextjs || options.angular || options.express || options.fastapi);
706
+ }
707
+ function buildQuickConfig(projectName, options) {
708
+ const config = { ...DEFAULT_CONFIG, name: projectName };
709
+ if (options.react) {
710
+ config.type = "frontend";
711
+ config.framework = "react";
712
+ } else if (options.nextjs) {
713
+ config.type = "frontend";
714
+ config.framework = "nextjs";
715
+ } else if (options.angular) {
716
+ config.type = "frontend";
717
+ config.framework = "angular";
718
+ } else if (options.express) {
719
+ config.type = "backend";
720
+ config.framework = "express";
721
+ } else if (options.fastapi) {
722
+ config.type = "backend";
723
+ config.framework = "fastapi";
724
+ }
725
+ return config;
726
+ }
727
+ async function loadConfigFromFile(configPath, projectName) {
728
+ const resolvedPath = path4.resolve(process.cwd(), configPath);
729
+ const content = await fs2.readJSON(resolvedPath);
730
+ return { ...DEFAULT_CONFIG, ...content, name: projectName };
731
+ }
732
+
733
+ // src/commands/list.ts
734
+ import { Command as Command2 } from "commander";
735
+ import chalk2 from "chalk";
736
+ function listCommand() {
737
+ return new Command2("list").description("List all available templates").action(() => {
738
+ newLine();
739
+ console.log(chalk2.bold.cyan("Available Templates"));
740
+ console.log(chalk2.gray("\u2500".repeat(60)));
741
+ newLine();
742
+ const categories = {
743
+ Frontend: ["react", "nextjs", "angular"],
744
+ Backend: ["express", "fastapi"]
745
+ };
746
+ for (const [category, frameworks] of Object.entries(categories)) {
747
+ console.log(chalk2.bold.white(` ${category}`));
748
+ newLine();
749
+ for (const fw of frameworks) {
750
+ const meta = FRAMEWORKS[fw];
751
+ console.log(` ${chalk2.green(meta.displayName.padEnd(25))} ${chalk2.gray(meta.description)}`);
752
+ for (const variant of meta.variants) {
753
+ const label = VARIANT_DISPLAY_NAMES[variant];
754
+ console.log(` ${chalk2.gray("\u2022")} ${label}`);
755
+ }
756
+ newLine();
757
+ }
758
+ }
759
+ console.log(chalk2.gray("\u2500".repeat(60)));
760
+ console.log(
761
+ chalk2.gray(" Use"),
762
+ chalk2.cyan("scaffold create <name> --<framework>"),
763
+ chalk2.gray("for quick setup")
764
+ );
765
+ newLine();
766
+ });
767
+ }
768
+
769
+ // src/commands/doctor.ts
770
+ import { Command as Command3 } from "commander";
771
+ import { execSync as execSync2 } from "child_process";
772
+ import chalk3 from "chalk";
773
+ var CHECKS = [
774
+ { name: "Node.js", command: "node", versionFlag: "--version", required: true },
775
+ { name: "npm", command: "npm", versionFlag: "--version", required: false },
776
+ { name: "yarn", command: "yarn", versionFlag: "--version", required: false },
777
+ { name: "pnpm", command: "pnpm", versionFlag: "--version", required: false },
778
+ { name: "git", command: "git", versionFlag: "--version", required: true },
779
+ { name: "Python", command: "python3", versionFlag: "--version", required: false }
780
+ ];
781
+ function doctorCommand() {
782
+ return new Command3("doctor").description("Check system dependencies").action(() => {
783
+ newLine();
784
+ console.log(chalk3.bold.cyan("System Check"));
785
+ console.log(chalk3.gray("\u2500".repeat(40)));
786
+ newLine();
787
+ let allGood = true;
788
+ for (const check of CHECKS) {
789
+ try {
790
+ const version = execSync2(`${check.command} ${check.versionFlag ?? "--version"}`, {
791
+ encoding: "utf-8",
792
+ stdio: ["pipe", "pipe", "pipe"]
793
+ }).trim();
794
+ const versionStr = version.split("\n")[0];
795
+ console.log(` ${chalk3.green("\u2714")} ${check.name.padEnd(12)} ${chalk3.gray(versionStr)}`);
796
+ } catch {
797
+ if (check.required) {
798
+ console.log(` ${chalk3.red("\u2716")} ${check.name.padEnd(12)} ${chalk3.red("not found (required)")}`);
799
+ allGood = false;
800
+ } else {
801
+ console.log(` ${chalk3.yellow("\u2013")} ${check.name.padEnd(12)} ${chalk3.gray("not found (optional)")}`);
802
+ }
803
+ }
804
+ }
805
+ newLine();
806
+ if (allGood) {
807
+ console.log(chalk3.green(" All required dependencies are installed!"));
808
+ } else {
809
+ console.log(chalk3.red(" Some required dependencies are missing."));
810
+ }
811
+ newLine();
812
+ });
813
+ }
814
+
815
+ // src/cli.ts
816
+ function createCli() {
817
+ const program2 = new Command4();
818
+ program2.name("scaffold").description("CLI tool for scaffolding modern full-stack projects").version("0.1.0").option("--verbose", "Enable verbose output").hook("preAction", (thisCommand) => {
819
+ const opts = thisCommand.opts();
820
+ if (opts.verbose) {
821
+ setVerbose(true);
822
+ }
823
+ });
824
+ program2.addCommand(createCommand());
825
+ program2.addCommand(listCommand());
826
+ program2.addCommand(doctorCommand());
827
+ return program2;
828
+ }
829
+
830
+ // src/index.ts
831
+ var program = createCli();
832
+ program.parse();
833
+ //# sourceMappingURL=index.js.map