nextforge-cli 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/bin/cli.js ADDED
@@ -0,0 +1,666 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import inquirer from 'inquirer';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ import fs from 'fs-extra';
8
+ import path from 'path';
9
+ import { fileURLToPath } from 'url';
10
+ import { execSync } from 'child_process';
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const TEMPLATES_DIR = path.join(__dirname, '..', 'templates');
15
+
16
+ const program = new Command();
17
+
18
+ program
19
+ .name('nextforge')
20
+ .description('Forge your next full-stack Next.js TypeScript app')
21
+ .version('1.0.0')
22
+ .argument('[project-name]', 'Name of the project')
23
+ .action(async (projectName) => {
24
+ console.log(chalk.bold.cyan('\n🔥 NextForge - Full-Stack Next.js Generator\n'));
25
+
26
+ // Get project name if not provided
27
+ if (!projectName) {
28
+ const { name } = await inquirer.prompt([
29
+ {
30
+ type: 'input',
31
+ name: 'name',
32
+ message: 'Project name:',
33
+ default: 'my-fullstack-app',
34
+ validate: (input) => input.length > 0 || 'Project name is required',
35
+ },
36
+ ]);
37
+ projectName = name;
38
+ }
39
+
40
+ // Main configuration questions
41
+ const answers = await inquirer.prompt([
42
+ {
43
+ type: 'list',
44
+ name: 'database',
45
+ message: 'Which database do you want to use?',
46
+ choices: [
47
+ { name: 'PostgreSQL', value: 'postgresql' },
48
+ { name: 'MySQL', value: 'mysql' },
49
+ { name: 'SQL Server', value: 'sqlserver' },
50
+ { name: 'SQLite (dev only)', value: 'sqlite' },
51
+ ],
52
+ default: 'postgresql',
53
+ },
54
+ {
55
+ type: 'confirm',
56
+ name: 'useAuth',
57
+ message: 'Do you need authentication?',
58
+ default: true,
59
+ },
60
+ {
61
+ type: 'list',
62
+ name: 'authType',
63
+ message: 'Which authentication type?',
64
+ choices: [
65
+ { name: 'Credentials (email/password)', value: 'credentials' },
66
+ { name: 'LDAP / Active Directory', value: 'ldap' },
67
+ { name: 'OAuth (Google, GitHub, etc.)', value: 'oauth' },
68
+ { name: 'All of the above', value: 'all' },
69
+ ],
70
+ when: (ans) => ans.useAuth,
71
+ },
72
+ {
73
+ type: 'confirm',
74
+ name: 'useStripe',
75
+ message: 'Do you need Stripe integration?',
76
+ default: false,
77
+ },
78
+ {
79
+ type: 'confirm',
80
+ name: 'useEmail',
81
+ message: 'Do you need email functionality (nodemailer)?',
82
+ default: true,
83
+ },
84
+ {
85
+ type: 'confirm',
86
+ name: 'useBullMQ',
87
+ message: 'Do you need background jobs (BullMQ + Redis)?',
88
+ default: true,
89
+ },
90
+ {
91
+ type: 'confirm',
92
+ name: 'useDocker',
93
+ message: 'Generate Docker & docker-compose files?',
94
+ default: true,
95
+ },
96
+ {
97
+ type: 'list',
98
+ name: 'packageManager',
99
+ message: 'Which package manager?',
100
+ choices: ['npm', 'yarn', 'pnpm'],
101
+ default: 'npm',
102
+ },
103
+ ]);
104
+
105
+ const projectPath = path.join(process.cwd(), projectName);
106
+
107
+ // Check if directory exists
108
+ if (fs.existsSync(projectPath)) {
109
+ const { overwrite } = await inquirer.prompt([
110
+ {
111
+ type: 'confirm',
112
+ name: 'overwrite',
113
+ message: `Directory ${projectName} already exists. Overwrite?`,
114
+ default: false,
115
+ },
116
+ ]);
117
+ if (!overwrite) {
118
+ console.log(chalk.yellow('Aborted.'));
119
+ process.exit(0);
120
+ }
121
+ fs.removeSync(projectPath);
122
+ }
123
+
124
+ const spinner = ora('Creating project structure...').start();
125
+
126
+ try {
127
+ // Create project directory
128
+ fs.mkdirSync(projectPath, { recursive: true });
129
+
130
+ // Copy base template
131
+ spinner.text = 'Copying base template...';
132
+ fs.copySync(path.join(TEMPLATES_DIR, 'base'), projectPath);
133
+
134
+ // Copy auth templates if needed
135
+ if (answers.useAuth) {
136
+ spinner.text = 'Setting up authentication...';
137
+ const authTypes = answers.authType === 'all'
138
+ ? ['credentials', 'ldap', 'oauth']
139
+ : [answers.authType];
140
+
141
+ for (const authType of authTypes) {
142
+ const authTemplatePath = path.join(TEMPLATES_DIR, 'auth', authType);
143
+ if (fs.existsSync(authTemplatePath)) {
144
+ fs.copySync(authTemplatePath, path.join(projectPath, 'features', 'auth', authType), { overwrite: true });
145
+ }
146
+ }
147
+ }
148
+
149
+ // Copy Stripe template if needed
150
+ if (answers.useStripe) {
151
+ spinner.text = 'Setting up Stripe...';
152
+ const stripeTemplatePath = path.join(TEMPLATES_DIR, 'stripe');
153
+ if (fs.existsSync(stripeTemplatePath)) {
154
+ fs.copySync(stripeTemplatePath, path.join(projectPath, 'features', 'stripe'), { overwrite: true });
155
+ }
156
+ }
157
+
158
+ // Copy email template if needed
159
+ if (answers.useEmail) {
160
+ spinner.text = 'Setting up email...';
161
+ const emailTemplatePath = path.join(TEMPLATES_DIR, 'email');
162
+ if (fs.existsSync(emailTemplatePath)) {
163
+ fs.copySync(emailTemplatePath, path.join(projectPath, 'lib', 'email'), { overwrite: true });
164
+ }
165
+ }
166
+
167
+ // Generate package.json with selected dependencies
168
+ spinner.text = 'Generating package.json...';
169
+ const packageJson = generatePackageJson(projectName, answers);
170
+ fs.writeFileSync(
171
+ path.join(projectPath, 'package.json'),
172
+ JSON.stringify(packageJson, null, 2)
173
+ );
174
+
175
+ // Generate .env.example
176
+ spinner.text = 'Generating environment files...';
177
+ const envContent = generateEnvFile(answers);
178
+ fs.writeFileSync(path.join(projectPath, '.env.example'), envContent);
179
+ fs.writeFileSync(path.join(projectPath, '.env'), envContent);
180
+
181
+ // Generate Prisma schema
182
+ spinner.text = 'Generating Prisma schema...';
183
+ const prismaSchema = generatePrismaSchema(answers);
184
+ fs.mkdirSync(path.join(projectPath, 'prisma'), { recursive: true });
185
+ fs.writeFileSync(path.join(projectPath, 'prisma', 'schema.prisma'), prismaSchema);
186
+
187
+ // Generate Docker files if needed
188
+ if (answers.useDocker) {
189
+ spinner.text = 'Generating Docker files...';
190
+ const dockerCompose = generateDockerCompose(answers);
191
+ const dockerfile = generateDockerfile();
192
+ fs.writeFileSync(path.join(projectPath, 'docker-compose.yml'), dockerCompose);
193
+ fs.writeFileSync(path.join(projectPath, 'Dockerfile'), dockerfile);
194
+ }
195
+
196
+ // Update tsconfig paths
197
+ spinner.text = 'Configuring TypeScript...';
198
+
199
+ spinner.succeed(chalk.green('Project created successfully!'));
200
+
201
+ // Print next steps
202
+ console.log(chalk.bold('\n📋 Next steps:\n'));
203
+ console.log(chalk.cyan(` cd ${projectName}`));
204
+
205
+ const installCmd = {
206
+ npm: 'npm install',
207
+ yarn: 'yarn',
208
+ pnpm: 'pnpm install',
209
+ }[answers.packageManager];
210
+
211
+ console.log(chalk.cyan(` ${installCmd}`));
212
+ console.log(chalk.cyan(' cp .env.example .env # Edit with your values'));
213
+ console.log(chalk.cyan(' npx prisma generate'));
214
+ console.log(chalk.cyan(' npx prisma db push'));
215
+
216
+ const devCmd = {
217
+ npm: 'npm run dev',
218
+ yarn: 'yarn dev',
219
+ pnpm: 'pnpm dev',
220
+ }[answers.packageManager];
221
+
222
+ console.log(chalk.cyan(` ${devCmd}`));
223
+
224
+ console.log(chalk.bold('\n🎉 Happy coding!\n'));
225
+
226
+ } catch (error) {
227
+ spinner.fail(chalk.red('Failed to create project'));
228
+ console.error(error);
229
+ process.exit(1);
230
+ }
231
+ });
232
+
233
+ function generatePackageJson(projectName, answers) {
234
+ const pkg = {
235
+ name: projectName,
236
+ version: '0.1.0',
237
+ private: true,
238
+ type: 'module',
239
+ scripts: {
240
+ dev: 'concurrently "npm run dev:api" "npm run dev:ui"',
241
+ 'dev:api': 'tsx watch index.ts',
242
+ 'dev:ui': 'next dev -p 3001',
243
+ build: 'next build',
244
+ 'build:api': 'tsc --project tsconfig.build.json',
245
+ start: 'concurrently "npm run start:api" "next start -p 3001"',
246
+ 'start:api': 'node dist/index.js',
247
+ lint: 'next lint',
248
+ 'db:push': 'prisma db push',
249
+ 'db:generate': 'prisma generate',
250
+ 'db:studio': 'prisma studio',
251
+ },
252
+ dependencies: {
253
+ // Core
254
+ next: '^14.2.22',
255
+ react: '^18.3.1',
256
+ 'react-dom': '^18.3.1',
257
+ typescript: '^5.3.3',
258
+
259
+ // API
260
+ hono: '^4.0.0',
261
+ '@hono/node-server': '^1.8.2',
262
+ '@hono/zod-validator': '^0.2.1',
263
+ zod: '^3.23.8',
264
+
265
+ // Database
266
+ '@prisma/client': '^6.19.0',
267
+ prisma: '^6.19.0',
268
+
269
+ // State & Data Fetching
270
+ '@tanstack/react-query': '^5.60.2',
271
+ zustand: '^5.0.3',
272
+
273
+ // UI Components
274
+ '@radix-ui/react-checkbox': '^1.3.3',
275
+ '@radix-ui/react-dialog': '^1.1.15',
276
+ '@radix-ui/react-dropdown-menu': '^2.1.2',
277
+ '@radix-ui/react-label': '^2.1.8',
278
+ '@radix-ui/react-popover': '^1.1.15',
279
+ '@radix-ui/react-select': '^2.2.6',
280
+ '@radix-ui/react-slot': '^1.2.4',
281
+ '@radix-ui/react-tabs': '^1.1.13',
282
+ '@radix-ui/react-toast': '^1.2.2',
283
+ 'lucide-react': '^0.446.0',
284
+ 'class-variance-authority': '^0.7.0',
285
+ clsx: '^2.1.1',
286
+ 'tailwind-merge': '^2.5.2',
287
+ 'tailwindcss-animate': '^1.0.7',
288
+ 'next-themes': '^0.4.6',
289
+
290
+ // Forms
291
+ 'react-hook-form': '^7.53.2',
292
+ '@hookform/resolvers': '^3.9.1',
293
+
294
+ // Charts
295
+ recharts: '^3.3.0',
296
+
297
+ // Utilities
298
+ 'date-fns': '^4.1.0',
299
+ dotenv: '^16.4.5',
300
+
301
+ // Logging
302
+ winston: '^3.18.3',
303
+ 'winston-daily-rotate-file': '^5.0.0',
304
+
305
+ // Styling
306
+ tailwindcss: '^3.4.1',
307
+ postcss: '^8.4.35',
308
+ autoprefixer: '^10.4.17',
309
+ },
310
+ devDependencies: {
311
+ '@types/node': '^20.11.19',
312
+ '@types/react': '^18.3.11',
313
+ '@types/react-dom': '^18.3.0',
314
+ eslint: '^8.57.1',
315
+ 'eslint-config-next': '^14.2.22',
316
+ tsx: '^4.7.1',
317
+ concurrently: '^9.0.1',
318
+ },
319
+ };
320
+
321
+ // Add auth dependencies
322
+ if (answers.useAuth) {
323
+ pkg.dependencies['jsonwebtoken'] = '^9.0.2';
324
+ pkg.dependencies['bcrypt'] = '^5.1.1';
325
+ pkg.devDependencies['@types/jsonwebtoken'] = '^9.0.10';
326
+ pkg.devDependencies['@types/bcrypt'] = '^5.0.2';
327
+
328
+ if (answers.authType === 'ldap' || answers.authType === 'all') {
329
+ pkg.dependencies['ldapjs'] = '^3.0.7';
330
+ pkg.devDependencies['@types/ldapjs'] = '^2.2.5';
331
+ }
332
+ }
333
+
334
+ // Add Stripe
335
+ if (answers.useStripe) {
336
+ pkg.dependencies['stripe'] = '^14.0.0';
337
+ pkg.dependencies['@stripe/stripe-js'] = '^2.0.0';
338
+ }
339
+
340
+ // Add Email
341
+ if (answers.useEmail) {
342
+ pkg.dependencies['nodemailer'] = '^6.10.1';
343
+ pkg.devDependencies['@types/nodemailer'] = '^6.4.21';
344
+ }
345
+
346
+ // Add BullMQ
347
+ if (answers.useBullMQ) {
348
+ pkg.dependencies['bullmq'] = '^5.64.1';
349
+ pkg.dependencies['ioredis'] = '^5.8.2';
350
+ pkg.scripts['dev:worker'] = 'tsx watch workers/queue-worker.ts';
351
+ pkg.scripts['dev:full'] = 'concurrently "npm run dev:api" "npm run dev:ui" "npm run dev:worker"';
352
+ }
353
+
354
+ return pkg;
355
+ }
356
+
357
+ function generateEnvFile(answers) {
358
+ let env = `# App Configuration
359
+ NODE_ENV=development
360
+ APP_NAME=My App
361
+ APP_URL=http://localhost:3001
362
+
363
+ # API Configuration
364
+ API_PORT=3000
365
+ NEXT_PUBLIC_API_URL=http://localhost:3000
366
+
367
+ # JWT Configuration
368
+ JWT_SECRET=your-super-secret-jwt-key-change-in-production
369
+ JWT_EXPIRES_IN=7d
370
+
371
+ `;
372
+
373
+ // Database
374
+ const dbUrls = {
375
+ postgresql: 'DATABASE_URL="postgresql://user:password@localhost:5432/mydb?schema=public"',
376
+ mysql: 'DATABASE_URL="mysql://user:password@localhost:3306/mydb"',
377
+ sqlserver: 'DATABASE_URL="sqlserver://localhost:1433;database=mydb;user=sa;password=YourPassword;encrypt=true;trustServerCertificate=true"',
378
+ sqlite: 'DATABASE_URL="file:./dev.db"',
379
+ };
380
+ env += `# Database\n${dbUrls[answers.database]}\n\n`;
381
+
382
+ // Auth
383
+ if (answers.useAuth && (answers.authType === 'ldap' || answers.authType === 'all')) {
384
+ env += `# LDAP Configuration
385
+ LDAP_URL=ldap://your-ldap-server:389
386
+ LDAP_BIND_DN=CN=Service Account,OU=Users,DC=company,DC=com
387
+ LDAP_BIND_PASSWORD=your-bind-password
388
+ LDAP_SEARCH_BASE=DC=company,DC=com
389
+ LDAP_SEARCH_FILTER=(sAMAccountName={{username}})
390
+
391
+ `;
392
+ }
393
+
394
+ if (answers.useAuth && (answers.authType === 'oauth' || answers.authType === 'all')) {
395
+ env += `# OAuth Configuration
396
+ GOOGLE_CLIENT_ID=your-google-client-id
397
+ GOOGLE_CLIENT_SECRET=your-google-client-secret
398
+ GITHUB_CLIENT_ID=your-github-client-id
399
+ GITHUB_CLIENT_SECRET=your-github-client-secret
400
+
401
+ `;
402
+ }
403
+
404
+ // Stripe
405
+ if (answers.useStripe) {
406
+ env += `# Stripe Configuration
407
+ STRIPE_SECRET_KEY=sk_test_your-secret-key
408
+ STRIPE_PUBLISHABLE_KEY=pk_test_your-publishable-key
409
+ STRIPE_WEBHOOK_SECRET=whsec_your-webhook-secret
410
+
411
+ `;
412
+ }
413
+
414
+ // Email
415
+ if (answers.useEmail) {
416
+ env += `# Email Configuration (SMTP)
417
+ SMTP_HOST=smtp.gmail.com
418
+ SMTP_PORT=587
419
+ SMTP_SECURE=false
420
+ SMTP_USER=your-email@gmail.com
421
+ SMTP_PASSWORD=your-app-password
422
+ EMAIL_FROM=noreply@yourapp.com
423
+ EMAIL_FROM_NAME=Your App
424
+
425
+ `;
426
+ }
427
+
428
+ // BullMQ
429
+ if (answers.useBullMQ) {
430
+ env += `# Redis Configuration (for BullMQ)
431
+ REDIS_HOST=localhost
432
+ REDIS_PORT=6379
433
+ REDIS_PASSWORD=
434
+
435
+ `;
436
+ }
437
+
438
+ return env;
439
+ }
440
+
441
+ function generatePrismaSchema(answers) {
442
+ const providers = {
443
+ postgresql: 'postgresql',
444
+ mysql: 'mysql',
445
+ sqlserver: 'sqlserver',
446
+ sqlite: 'sqlite',
447
+ };
448
+
449
+ let schema = `generator client {
450
+ provider = "prisma-client-js"
451
+ }
452
+
453
+ datasource db {
454
+ provider = "${providers[answers.database]}"
455
+ url = env("DATABASE_URL")
456
+ }
457
+
458
+ `;
459
+
460
+ // User model for auth
461
+ if (answers.useAuth) {
462
+ schema += `model User {
463
+ id Int @id @default(autoincrement())
464
+ username String @unique
465
+ email String? @unique
466
+ password String?
467
+ displayName String?
468
+ role String @default("user")
469
+ ldapDN String?
470
+ createdAt DateTime @default(now())
471
+ updatedAt DateTime @updatedAt
472
+ `;
473
+
474
+ if (answers.useStripe) {
475
+ schema += ` stripeCustomerId String?
476
+ subscriptions Subscription[]
477
+ `;
478
+ }
479
+
480
+ schema += `}
481
+
482
+ `;
483
+ }
484
+
485
+ // Stripe models
486
+ if (answers.useStripe) {
487
+ schema += `model Subscription {
488
+ id Int @id @default(autoincrement())
489
+ userId Int
490
+ stripeSubscriptionId String @unique
491
+ status String
492
+ priceId String
493
+ currentPeriodStart DateTime
494
+ currentPeriodEnd DateTime
495
+ createdAt DateTime @default(now())
496
+ updatedAt DateTime @updatedAt
497
+
498
+ user User @relation(fields: [userId], references: [id])
499
+ }
500
+
501
+ `;
502
+ }
503
+
504
+ return schema;
505
+ }
506
+
507
+ function generateDockerCompose(answers) {
508
+ let compose = `services:
509
+ `;
510
+
511
+ // Database
512
+ if (answers.database === 'postgresql') {
513
+ compose += ` database:
514
+ image: postgres:16-alpine
515
+ container_name: app-database
516
+ environment:
517
+ POSTGRES_USER: user
518
+ POSTGRES_PASSWORD: password
519
+ POSTGRES_DB: mydb
520
+ ports:
521
+ - "5432:5432"
522
+ volumes:
523
+ - postgres-data:/var/lib/postgresql/data
524
+ restart: unless-stopped
525
+
526
+ `;
527
+ } else if (answers.database === 'mysql') {
528
+ compose += ` database:
529
+ image: mysql:8
530
+ container_name: app-database
531
+ environment:
532
+ MYSQL_ROOT_PASSWORD: rootpassword
533
+ MYSQL_DATABASE: mydb
534
+ MYSQL_USER: user
535
+ MYSQL_PASSWORD: password
536
+ ports:
537
+ - "3306:3306"
538
+ volumes:
539
+ - mysql-data:/var/lib/mysql
540
+ restart: unless-stopped
541
+
542
+ `;
543
+ } else if (answers.database === 'sqlserver') {
544
+ compose += ` database:
545
+ image: mcr.microsoft.com/mssql/server:2022-latest
546
+ container_name: app-database
547
+ environment:
548
+ - ACCEPT_EULA=Y
549
+ - MSSQL_SA_PASSWORD=YourStrong@Passw0rd
550
+ - MSSQL_PID=Express
551
+ ports:
552
+ - "1433:1433"
553
+ volumes:
554
+ - sqlserver-data:/var/opt/mssql
555
+ restart: unless-stopped
556
+
557
+ `;
558
+ }
559
+
560
+ // Redis for BullMQ
561
+ if (answers.useBullMQ) {
562
+ compose += ` redis:
563
+ image: redis:7-alpine
564
+ container_name: app-redis
565
+ ports:
566
+ - "6379:6379"
567
+ volumes:
568
+ - redis-data:/data
569
+ restart: unless-stopped
570
+
571
+ `;
572
+ }
573
+
574
+ // App
575
+ compose += ` webapp:
576
+ build:
577
+ context: .
578
+ dockerfile: Dockerfile
579
+ container_name: app-webapp
580
+ ports:
581
+ - "3000:3000"
582
+ - "3001:3001"
583
+ environment:
584
+ - NODE_ENV=production
585
+ depends_on:
586
+ `;
587
+
588
+ if (answers.database !== 'sqlite') {
589
+ compose += ` - database
590
+ `;
591
+ }
592
+ if (answers.useBullMQ) {
593
+ compose += ` - redis
594
+ `;
595
+ }
596
+
597
+ compose += ` restart: unless-stopped
598
+
599
+ `;
600
+
601
+ // Volumes
602
+ compose += `volumes:
603
+ `;
604
+ if (answers.database === 'postgresql') {
605
+ compose += ` postgres-data:
606
+ `;
607
+ } else if (answers.database === 'mysql') {
608
+ compose += ` mysql-data:
609
+ `;
610
+ } else if (answers.database === 'sqlserver') {
611
+ compose += ` sqlserver-data:
612
+ `;
613
+ }
614
+ if (answers.useBullMQ) {
615
+ compose += ` redis-data:
616
+ `;
617
+ }
618
+
619
+ return compose;
620
+ }
621
+
622
+ function generateDockerfile() {
623
+ return `FROM node:20-alpine AS base
624
+
625
+ # Install dependencies only when needed
626
+ FROM base AS deps
627
+ RUN apk add --no-cache libc6-compat
628
+ WORKDIR /app
629
+
630
+ COPY package*.json ./
631
+ RUN npm ci
632
+
633
+ # Rebuild the source code only when needed
634
+ FROM base AS builder
635
+ WORKDIR /app
636
+ COPY --from=deps /app/node_modules ./node_modules
637
+ COPY . .
638
+
639
+ RUN npx prisma generate
640
+ RUN npm run build
641
+
642
+ # Production image
643
+ FROM base AS runner
644
+ WORKDIR /app
645
+
646
+ ENV NODE_ENV=production
647
+
648
+ RUN addgroup --system --gid 1001 nodejs
649
+ RUN adduser --system --uid 1001 nextjs
650
+
651
+ COPY --from=builder /app/public ./public
652
+ COPY --from=builder /app/.next/standalone ./
653
+ COPY --from=builder /app/.next/static ./.next/static
654
+ COPY --from=builder /app/node_modules ./node_modules
655
+ COPY --from=builder /app/dist ./dist
656
+ COPY --from=builder /app/prisma ./prisma
657
+
658
+ USER nextjs
659
+
660
+ EXPOSE 3000 3001
661
+
662
+ CMD ["node", "server.js"]
663
+ `;
664
+ }
665
+
666
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "nextforge-cli",
3
+ "version": "1.0.0",
4
+ "description": "Forge your next full-stack Next.js app with Hono, Prisma, TanStack Query, Zustand, BullMQ and more",
5
+ "type": "module",
6
+ "bin": {
7
+ "nextforge": "./bin/cli.js"
8
+ },
9
+ "scripts": {
10
+ "dev": "node bin/cli.js",
11
+ "build": "echo 'No build needed'",
12
+ "test": "node bin/cli.js --help"
13
+ },
14
+ "keywords": [
15
+ "nextjs",
16
+ "next",
17
+ "typescript",
18
+ "boilerplate",
19
+ "hono",
20
+ "prisma",
21
+ "bullmq",
22
+ "fullstack",
23
+ "tanstack",
24
+ "zustand",
25
+ "tailwind",
26
+ "shadcn",
27
+ "cli",
28
+ "scaffold",
29
+ "starter",
30
+ "template"
31
+ ],
32
+ "author": "Brandon",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/BrandonDev12345/nextforge-cli"
37
+ },
38
+ "dependencies": {
39
+ "chalk": "^5.3.0",
40
+ "commander": "^12.1.0",
41
+ "inquirer": "^9.2.23",
42
+ "ora": "^8.0.1",
43
+ "fs-extra": "^11.2.0"
44
+ }
45
+ }