create-solostack 1.2.3 → 1.3.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.
@@ -0,0 +1,521 @@
1
+ /**
2
+ * Advanced Stripe Integration Generator - Pro Feature
3
+ * Generates subscription management, webhooks, and billing page
4
+ */
5
+
6
+ import path from 'path';
7
+ import { writeFile, ensureDir } from '../../utils/files.js';
8
+
9
+ export async function generateAdvancedStripe(projectPath) {
10
+ // Ensure directories
11
+ await ensureDir(path.join(projectPath, 'src/app/api/stripe/webhook'));
12
+ await ensureDir(path.join(projectPath, 'src/app/api/stripe/create-subscription'));
13
+ await ensureDir(path.join(projectPath, 'src/app/api/stripe/cancel-subscription'));
14
+ await ensureDir(path.join(projectPath, 'src/app/api/stripe/customer-portal'));
15
+ await ensureDir(path.join(projectPath, 'src/app/dashboard/billing'));
16
+
17
+ // Generate Stripe utility library
18
+ const stripeLib = `import Stripe from 'stripe';
19
+
20
+ if (!process.env.STRIPE_SECRET_KEY) {
21
+ throw new Error('STRIPE_SECRET_KEY is not set');
22
+ }
23
+
24
+ export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {
25
+ apiVersion: '2024-11-20.acacia',
26
+ typescript: true,
27
+ });
28
+
29
+ /**
30
+ * Pricing Plans Configuration
31
+ */
32
+ export const PLANS = {
33
+ pro: {
34
+ name: 'Pro',
35
+ priceId: process.env.STRIPE_PRO_PRICE_ID || 'price_pro',
36
+ price: 29,
37
+ features: [
38
+ 'Unlimited projects',
39
+ 'Priority support',
40
+ 'API access',
41
+ 'Advanced analytics',
42
+ ],
43
+ },
44
+ enterprise: {
45
+ name: 'Enterprise',
46
+ priceId: process.env.STRIPE_ENTERPRISE_PRICE_ID || 'price_enterprise',
47
+ price: 99,
48
+ features: [
49
+ 'Everything in Pro',
50
+ 'Custom integrations',
51
+ 'Dedicated account manager',
52
+ 'SLA guarantee',
53
+ ],
54
+ },
55
+ };
56
+
57
+ /**
58
+ * Get or create Stripe customer for user
59
+ */
60
+ export async function getOrCreateCustomer(userId: string, email: string) {
61
+ const { db } = await import('@/lib/db');
62
+
63
+ const user = await db.user.findUnique({
64
+ where: { id: userId },
65
+ select: { stripeCustomerId: true },
66
+ });
67
+
68
+ if (user?.stripeCustomerId) {
69
+ return user.stripeCustomerId;
70
+ }
71
+
72
+ const customer = await stripe.customers.create({
73
+ email,
74
+ metadata: { userId },
75
+ });
76
+
77
+ await db.user.update({
78
+ where: { id: userId },
79
+ data: { stripeCustomerId: customer.id },
80
+ });
81
+
82
+ return customer.id;
83
+ }
84
+ `;
85
+
86
+ await writeFile(path.join(projectPath, 'src/lib/stripe.ts'), stripeLib);
87
+
88
+ // Generate webhook handler
89
+ const webhookHandler = `import { NextRequest, NextResponse } from 'next/server';
90
+ import { stripe } from '@/lib/stripe';
91
+ import Stripe from 'stripe';
92
+
93
+ const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
94
+
95
+ export async function POST(req: NextRequest) {
96
+ const body = await req.text();
97
+ const signature = req.headers.get('stripe-signature')!;
98
+
99
+ let event: Stripe.Event;
100
+
101
+ try {
102
+ event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
103
+ } catch (err: any) {
104
+ console.error('Webhook signature verification failed:', err.message);
105
+ return NextResponse.json({ error: 'Invalid signature' }, { status: 400 });
106
+ }
107
+
108
+ const { db } = await import('@/lib/db');
109
+
110
+ // Check for idempotency
111
+ const existingEvent = await db.stripeEvent.findUnique({
112
+ where: { id: event.id },
113
+ });
114
+
115
+ if (existingEvent?.processed) {
116
+ return NextResponse.json({ received: true, duplicate: true });
117
+ }
118
+
119
+ // Record the event
120
+ await db.stripeEvent.upsert({
121
+ where: { id: event.id },
122
+ update: {},
123
+ create: { id: event.id, type: event.type },
124
+ });
125
+
126
+ try {
127
+ switch (event.type) {
128
+ case 'customer.subscription.created':
129
+ case 'customer.subscription.updated': {
130
+ const subscription = event.data.object as Stripe.Subscription;
131
+ await handleSubscriptionChange(subscription, db);
132
+ break;
133
+ }
134
+
135
+ case 'customer.subscription.deleted': {
136
+ const subscription = event.data.object as Stripe.Subscription;
137
+ await handleSubscriptionDeleted(subscription, db);
138
+ break;
139
+ }
140
+
141
+ case 'invoice.payment_succeeded': {
142
+ const invoice = event.data.object as Stripe.Invoice;
143
+ await handlePaymentSucceeded(invoice, db);
144
+ break;
145
+ }
146
+
147
+ case 'invoice.payment_failed': {
148
+ const invoice = event.data.object as Stripe.Invoice;
149
+ await handlePaymentFailed(invoice, db);
150
+ break;
151
+ }
152
+ }
153
+
154
+ // Mark event as processed
155
+ await db.stripeEvent.update({
156
+ where: { id: event.id },
157
+ data: { processed: true },
158
+ });
159
+
160
+ } catch (error) {
161
+ console.error('Error processing webhook:', error);
162
+ return NextResponse.json({ error: 'Webhook handler failed' }, { status: 500 });
163
+ }
164
+
165
+ return NextResponse.json({ received: true });
166
+ }
167
+
168
+ async function handleSubscriptionChange(subscription: Stripe.Subscription, db: any) {
169
+ const customerId = subscription.customer as string;
170
+ const user = await db.user.findFirst({
171
+ where: { stripeCustomerId: customerId },
172
+ });
173
+
174
+ if (!user) return;
175
+
176
+ const status = mapSubscriptionStatus(subscription.status);
177
+
178
+ await db.subscription.upsert({
179
+ where: { userId: user.id },
180
+ update: {
181
+ stripeSubscriptionId: subscription.id,
182
+ stripePriceId: subscription.items.data[0].price.id,
183
+ status,
184
+ currentPeriodStart: new Date(subscription.current_period_start * 1000),
185
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
186
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
187
+ },
188
+ create: {
189
+ userId: user.id,
190
+ stripeSubscriptionId: subscription.id,
191
+ stripePriceId: subscription.items.data[0].price.id,
192
+ status,
193
+ currentPeriodStart: new Date(subscription.current_period_start * 1000),
194
+ currentPeriodEnd: new Date(subscription.current_period_end * 1000),
195
+ cancelAtPeriodEnd: subscription.cancel_at_period_end,
196
+ },
197
+ });
198
+ }
199
+
200
+ async function handleSubscriptionDeleted(subscription: Stripe.Subscription, db: any) {
201
+ await db.subscription.delete({
202
+ where: { stripeSubscriptionId: subscription.id },
203
+ }).catch(() => {}); // Ignore if not found
204
+ }
205
+
206
+ async function handlePaymentSucceeded(invoice: Stripe.Invoice, db: any) {
207
+ if (!invoice.customer) return;
208
+
209
+ const user = await db.user.findFirst({
210
+ where: { stripeCustomerId: invoice.customer as string },
211
+ });
212
+
213
+ if (!user) return;
214
+
215
+ await db.payment.create({
216
+ data: {
217
+ userId: user.id,
218
+ stripePaymentId: invoice.payment_intent as string,
219
+ amount: invoice.amount_paid,
220
+ currency: invoice.currency,
221
+ status: 'succeeded',
222
+ },
223
+ });
224
+ }
225
+
226
+ async function handlePaymentFailed(invoice: Stripe.Invoice, db: any) {
227
+ // Send email notification about failed payment
228
+ // await sendPaymentFailedEmail(user.email);
229
+ console.log('Payment failed for invoice:', invoice.id);
230
+ }
231
+
232
+ function mapSubscriptionStatus(status: string) {
233
+ const statusMap: Record<string, string> = {
234
+ active: 'ACTIVE',
235
+ canceled: 'CANCELED',
236
+ past_due: 'PAST_DUE',
237
+ trialing: 'TRIALING',
238
+ incomplete: 'INCOMPLETE',
239
+ };
240
+ return statusMap[status] || 'INCOMPLETE';
241
+ }
242
+ `;
243
+
244
+ await writeFile(
245
+ path.join(projectPath, 'src/app/api/stripe/webhook/route.ts'),
246
+ webhookHandler
247
+ );
248
+
249
+ // Generate create subscription endpoint
250
+ const createSubscription = `import { NextRequest, NextResponse } from 'next/server';
251
+ import { auth } from '@/lib/auth';
252
+ import { stripe, getOrCreateCustomer, PLANS } from '@/lib/stripe';
253
+
254
+ export async function POST(req: NextRequest) {
255
+ try {
256
+ const session = await auth();
257
+
258
+ if (!session?.user?.id) {
259
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
260
+ }
261
+
262
+ const { priceId } = await req.json();
263
+
264
+ if (!priceId) {
265
+ return NextResponse.json({ error: 'Price ID required' }, { status: 400 });
266
+ }
267
+
268
+ const customerId = await getOrCreateCustomer(session.user.id, session.user.email!);
269
+
270
+ const checkoutSession = await stripe.checkout.sessions.create({
271
+ customer: customerId,
272
+ mode: 'subscription',
273
+ payment_method_types: ['card'],
274
+ line_items: [
275
+ {
276
+ price: priceId,
277
+ quantity: 1,
278
+ },
279
+ ],
280
+ success_url: \`\${process.env.NEXTAUTH_URL}/dashboard/billing?success=true\`,
281
+ cancel_url: \`\${process.env.NEXTAUTH_URL}/dashboard/billing?canceled=true\`,
282
+ metadata: {
283
+ userId: session.user.id,
284
+ },
285
+ });
286
+
287
+ return NextResponse.json({ url: checkoutSession.url });
288
+ } catch (error: any) {
289
+ console.error('Create subscription error:', error);
290
+ return NextResponse.json({ error: error.message }, { status: 500 });
291
+ }
292
+ }
293
+ `;
294
+
295
+ await writeFile(
296
+ path.join(projectPath, 'src/app/api/stripe/create-subscription/route.ts'),
297
+ createSubscription
298
+ );
299
+
300
+ // Generate customer portal endpoint
301
+ const customerPortal = `import { NextRequest, NextResponse } from 'next/server';
302
+ import { auth } from '@/lib/auth';
303
+ import { stripe } from '@/lib/stripe';
304
+ import { db } from '@/lib/db';
305
+
306
+ export async function POST(req: NextRequest) {
307
+ try {
308
+ const session = await auth();
309
+
310
+ if (!session?.user?.id) {
311
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
312
+ }
313
+
314
+ const user = await db.user.findUnique({
315
+ where: { id: session.user.id },
316
+ select: { stripeCustomerId: true },
317
+ });
318
+
319
+ if (!user?.stripeCustomerId) {
320
+ return NextResponse.json({ error: 'No billing account found' }, { status: 400 });
321
+ }
322
+
323
+ const portalSession = await stripe.billingPortal.sessions.create({
324
+ customer: user.stripeCustomerId,
325
+ return_url: \`\${process.env.NEXTAUTH_URL}/dashboard/billing\`,
326
+ });
327
+
328
+ return NextResponse.json({ url: portalSession.url });
329
+ } catch (error: any) {
330
+ console.error('Customer portal error:', error);
331
+ return NextResponse.json({ error: error.message }, { status: 500 });
332
+ }
333
+ }
334
+ `;
335
+
336
+ await writeFile(
337
+ path.join(projectPath, 'src/app/api/stripe/customer-portal/route.ts'),
338
+ customerPortal
339
+ );
340
+
341
+ // Generate billing page
342
+ const billingPage = `'use client';
343
+
344
+ import { useState, useEffect } from 'react';
345
+ import { useSearchParams } from 'next/navigation';
346
+ import { CreditCard, Check, Loader2, ExternalLink } from 'lucide-react';
347
+
348
+ interface Subscription {
349
+ status: string;
350
+ stripePriceId: string;
351
+ currentPeriodEnd: string;
352
+ cancelAtPeriodEnd: boolean;
353
+ }
354
+
355
+ export default function BillingPage() {
356
+ const [subscription, setSubscription] = useState<Subscription | null>(null);
357
+ const [loading, setLoading] = useState(true);
358
+ const [actionLoading, setActionLoading] = useState(false);
359
+ const searchParams = useSearchParams();
360
+ const success = searchParams.get('success');
361
+ const canceled = searchParams.get('canceled');
362
+
363
+ useEffect(() => {
364
+ fetchSubscription();
365
+ }, []);
366
+
367
+ async function fetchSubscription() {
368
+ try {
369
+ const res = await fetch('/api/user/subscription');
370
+ if (res.ok) {
371
+ const data = await res.json();
372
+ setSubscription(data.subscription);
373
+ }
374
+ } finally {
375
+ setLoading(false);
376
+ }
377
+ }
378
+
379
+ async function handleSubscribe(priceId: string) {
380
+ setActionLoading(true);
381
+ try {
382
+ const res = await fetch('/api/stripe/create-subscription', {
383
+ method: 'POST',
384
+ headers: { 'Content-Type': 'application/json' },
385
+ body: JSON.stringify({ priceId }),
386
+ });
387
+ const data = await res.json();
388
+ if (data.url) {
389
+ window.location.href = data.url;
390
+ }
391
+ } finally {
392
+ setActionLoading(false);
393
+ }
394
+ }
395
+
396
+ async function handleManageBilling() {
397
+ setActionLoading(true);
398
+ try {
399
+ const res = await fetch('/api/stripe/customer-portal', {
400
+ method: 'POST',
401
+ });
402
+ const data = await res.json();
403
+ if (data.url) {
404
+ window.location.href = data.url;
405
+ }
406
+ } finally {
407
+ setActionLoading(false);
408
+ }
409
+ }
410
+
411
+ if (loading) {
412
+ return (
413
+ <div className="flex items-center justify-center min-h-[400px]">
414
+ <Loader2 className="h-8 w-8 animate-spin text-indigo-600" />
415
+ </div>
416
+ );
417
+ }
418
+
419
+ return (
420
+ <div className="max-w-4xl mx-auto p-6">
421
+ <h1 className="text-3xl font-bold mb-2">Billing</h1>
422
+ <p className="text-gray-600 mb-8">Manage your subscription and billing</p>
423
+
424
+ {success && (
425
+ <div className="bg-green-50 border border-green-200 rounded-lg p-4 mb-6">
426
+ <p className="text-green-800">šŸŽ‰ Subscription activated successfully!</p>
427
+ </div>
428
+ )}
429
+
430
+ {canceled && (
431
+ <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 mb-6">
432
+ <p className="text-yellow-800">Checkout was canceled. Your subscription was not changed.</p>
433
+ </div>
434
+ )}
435
+
436
+ {subscription ? (
437
+ <div className="bg-white rounded-xl border p-6 mb-8">
438
+ <div className="flex items-center justify-between mb-4">
439
+ <div>
440
+ <h2 className="text-xl font-semibold">Current Plan</h2>
441
+ <p className="text-gray-600">
442
+ Status: <span className="font-medium text-green-600">{subscription.status}</span>
443
+ </p>
444
+ </div>
445
+ <CreditCard className="h-8 w-8 text-indigo-600" />
446
+ </div>
447
+
448
+ <p className="text-sm text-gray-500 mb-4">
449
+ Next billing date: {new Date(subscription.currentPeriodEnd).toLocaleDateString()}
450
+ {subscription.cancelAtPeriodEnd && (
451
+ <span className="ml-2 text-amber-600">(Cancels at period end)</span>
452
+ )}
453
+ </p>
454
+
455
+ <button
456
+ onClick={handleManageBilling}
457
+ disabled={actionLoading}
458
+ className="inline-flex items-center gap-2 px-4 py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 disabled:opacity-50"
459
+ >
460
+ {actionLoading ? <Loader2 className="h-4 w-4 animate-spin" /> : <ExternalLink className="h-4 w-4" />}
461
+ Manage Billing
462
+ </button>
463
+ </div>
464
+ ) : (
465
+ <div className="grid md:grid-cols-2 gap-6">
466
+ {/* Pro Plan */}
467
+ <div className="bg-white rounded-xl border-2 border-indigo-500 p-6 relative">
468
+ <div className="absolute -top-3 left-4 bg-indigo-500 text-white text-xs px-2 py-1 rounded">
469
+ POPULAR
470
+ </div>
471
+ <h3 className="text-xl font-bold mb-2">Pro</h3>
472
+ <p className="text-3xl font-bold mb-4">$29<span className="text-lg text-gray-500">/mo</span></p>
473
+ <ul className="space-y-2 mb-6">
474
+ {['Unlimited projects', 'Priority support', 'API access', 'Advanced analytics'].map((feature) => (
475
+ <li key={feature} className="flex items-center gap-2 text-sm">
476
+ <Check className="h-4 w-4 text-green-500" />
477
+ {feature}
478
+ </li>
479
+ ))}
480
+ </ul>
481
+ <button
482
+ onClick={() => handleSubscribe(process.env.NEXT_PUBLIC_STRIPE_PRO_PRICE_ID || 'price_pro')}
483
+ disabled={actionLoading}
484
+ className="w-full py-2 bg-indigo-600 text-white rounded-lg hover:bg-indigo-500 disabled:opacity-50"
485
+ >
486
+ {actionLoading ? 'Loading...' : 'Subscribe to Pro'}
487
+ </button>
488
+ </div>
489
+
490
+ {/* Enterprise Plan */}
491
+ <div className="bg-white rounded-xl border p-6">
492
+ <h3 className="text-xl font-bold mb-2">Enterprise</h3>
493
+ <p className="text-3xl font-bold mb-4">$99<span className="text-lg text-gray-500">/mo</span></p>
494
+ <ul className="space-y-2 mb-6">
495
+ {['Everything in Pro', 'Custom integrations', 'Dedicated account manager', 'SLA guarantee'].map((feature) => (
496
+ <li key={feature} className="flex items-center gap-2 text-sm">
497
+ <Check className="h-4 w-4 text-green-500" />
498
+ {feature}
499
+ </li>
500
+ ))}
501
+ </ul>
502
+ <button
503
+ onClick={() => handleSubscribe(process.env.NEXT_PUBLIC_STRIPE_ENTERPRISE_PRICE_ID || 'price_enterprise')}
504
+ disabled={actionLoading}
505
+ className="w-full py-2 bg-gray-900 text-white rounded-lg hover:bg-gray-800 disabled:opacity-50"
506
+ >
507
+ {actionLoading ? 'Loading...' : 'Subscribe to Enterprise'}
508
+ </button>
509
+ </div>
510
+ </div>
511
+ )}
512
+ </div>
513
+ );
514
+ }
515
+ `;
516
+
517
+ await writeFile(
518
+ path.join(projectPath, 'src/app/dashboard/billing/page.tsx'),
519
+ billingPage
520
+ );
521
+ }
package/src/index.js CHANGED
@@ -4,9 +4,9 @@ import chalk from 'chalk';
4
4
  import ora from 'ora';
5
5
  import path from 'path';
6
6
  import { validateProjectName } from './utils/validate.js';
7
- import { printSuccess, printError } from './utils/logger.js';
7
+ import { printSuccess, printError, printProSuccess } from './utils/logger.js';
8
8
  import { ensureDir, writeFile } from './utils/files.js';
9
- import { installPackages } from './utils/packages.js';
9
+ import { installPackages, installProPackages } from './utils/packages.js';
10
10
  import { initGit } from './utils/git.js';
11
11
  import { generateBase } from './generators/base.js';
12
12
  import { generateDatabase } from './generators/database.js';
@@ -15,6 +15,14 @@ import { generatePayments } from './generators/payments.js';
15
15
  import { generateEmails } from './generators/emails.js';
16
16
  import { generateSetup } from './generators/setup.js';
17
17
  import { generateUI } from './generators/ui.js';
18
+ import { validateLicense, isLicenseKeyFormat } from './utils/license.js';
19
+ // Pro generators
20
+ import { generateOAuth } from './generators/pro/oauth.js';
21
+ import { generateFullDatabase } from './generators/pro/database-full.js';
22
+ import { generateAdvancedStripe } from './generators/pro/stripe-advanced.js';
23
+ import { generateAdvancedEmails } from './generators/pro/emails.js';
24
+ import { generateAdmin } from './generators/pro/admin.js';
25
+ import { generateApiKeys } from './generators/pro/api-keys.js';
18
26
  import {
19
27
  AUTH_PROVIDERS,
20
28
  DATABASES,
@@ -112,6 +120,68 @@ export async function main() {
112
120
 
113
121
  const projectPath = path.join(process.cwd(), projectName);
114
122
 
123
+ // Pro upgrade prompt
124
+ let hasProLicense = false;
125
+ let licenseData = null;
126
+
127
+ const proAnswer = await inquirer.prompt([
128
+ {
129
+ type: 'confirm',
130
+ name: 'wantsPro',
131
+ message: chalk.cyan('šŸ’Ž Upgrade to Pro? (Admin panel, OAuth, API keys, advanced features)'),
132
+ default: false,
133
+ }
134
+ ]);
135
+
136
+ if (proAnswer.wantsPro) {
137
+ let retryLicense = true;
138
+
139
+ while (retryLicense && !hasProLicense) {
140
+ const licenseAnswer = await inquirer.prompt([
141
+ {
142
+ type: 'input',
143
+ name: 'licenseKey',
144
+ message: 'Enter your Pro license key (get one at https://solostack.dev/pro):',
145
+ validate: (input) => {
146
+ if (!input) {
147
+ return 'License key required. Visit https://solostack.dev/pro to purchase.';
148
+ }
149
+ if (!isLicenseKeyFormat(input)) {
150
+ return 'Invalid license key format. Expected: sk_live_XXXXX...';
151
+ }
152
+ return true;
153
+ }
154
+ }
155
+ ]);
156
+
157
+ let spinner = ora('Validating Pro license...').start();
158
+ licenseData = await validateLicense(licenseAnswer.licenseKey);
159
+
160
+ if (licenseData.valid) {
161
+ hasProLicense = true;
162
+ spinner.succeed(chalk.green(`Pro license validated! Welcome ${licenseData.email} šŸ’Ž`));
163
+ } else {
164
+ spinner.fail(chalk.red('Invalid license key'));
165
+
166
+ const retryAnswer = await inquirer.prompt([
167
+ {
168
+ type: 'confirm',
169
+ name: 'retry',
170
+ message: 'Would you like to try a different license key?',
171
+ default: true,
172
+ }
173
+ ]);
174
+
175
+ retryLicense = retryAnswer.retry;
176
+
177
+ if (!retryLicense) {
178
+ console.log(chalk.yellow('\nšŸ’” Generating free version instead.'));
179
+ console.log(chalk.yellow(' Get Pro at: https://solostack.dev/pro\n'));
180
+ }
181
+ }
182
+ }
183
+ }
184
+
115
185
  try {
116
186
  // Start generation
117
187
  console.log(chalk.cyan('\\nāš™ļø Creating your SaaS boilerplate...\\n'));
@@ -156,6 +226,40 @@ export async function main() {
156
226
  await generateSetup(projectPath, config);
157
227
  spinner.succeed('Added diagnostics page (/setup)');
158
228
 
229
+ // Pro Features Generation
230
+ if (hasProLicense) {
231
+ console.log(chalk.cyan('\nšŸ’Ž Adding Pro features...\n'));
232
+
233
+ spinner = ora('Adding OAuth providers (Google, GitHub)').start();
234
+ await generateOAuth(projectPath);
235
+ spinner.succeed('OAuth providers configured');
236
+
237
+ spinner = ora('Upgrading database schema').start();
238
+ await generateFullDatabase(projectPath);
239
+ spinner.succeed('Full database schema created');
240
+
241
+ spinner = ora('Setting up advanced Stripe integration').start();
242
+ await generateAdvancedStripe(projectPath);
243
+ spinner.succeed('Stripe subscriptions and webhooks configured');
244
+
245
+ spinner = ora('Adding email templates').start();
246
+ await generateAdvancedEmails(projectPath);
247
+ spinner.succeed('Email templates created');
248
+
249
+ spinner = ora('Creating admin panel').start();
250
+ await generateAdmin(projectPath);
251
+ spinner.succeed('Admin panel created');
252
+
253
+ spinner = ora('Setting up API key system').start();
254
+ await generateApiKeys(projectPath);
255
+ spinner.succeed('API key system configured');
256
+
257
+ // Install Pro-specific packages
258
+ spinner = ora('Installing Pro packages...').start();
259
+ await installProPackages(projectPath);
260
+ spinner.succeed('Pro packages installed');
261
+ }
262
+
159
263
  // Install dependencies
160
264
  spinner = ora('Installing dependencies (this may take a minute...)').start();
161
265
  await installPackages(projectPath);
@@ -169,7 +273,11 @@ export async function main() {
169
273
  }
170
274
 
171
275
  // Success message
172
- printSuccess(projectName, projectPath);
276
+ if (hasProLicense) {
277
+ printProSuccess(projectName, projectPath);
278
+ } else {
279
+ printSuccess(projectName, projectPath);
280
+ }
173
281
 
174
282
  // Optional setup wizard
175
283
  console.log(); // Empty line for spacing