miragedev-sdk 0.1.0 → 0.2.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/README.md CHANGED
@@ -16,75 +16,61 @@ AI-first SDK for building SAAS applications with Next.js. Build production-ready
16
16
  - 📦 **Modular** - Import only what you need
17
17
  - 🔒 **Type-Safe** - Full TypeScript support with Zod validation
18
18
 
19
- ## Quick Start (5 minutes)
19
+ ## Quick Start (2 minutes)
20
20
 
21
- ### 1. Installation
21
+ ### Option 1: Create New Project (Recommended)
22
22
 
23
23
  ```bash
24
- npm install miragedev-sdk
25
- # or
26
- pnpm add miragedev-sdk
24
+ npx miragedev create my-saas
25
+ cd my-saas
26
+ npm run dev
27
27
  ```
28
28
 
29
- ### 2. Initialize with CLI
29
+ That's it! You now have a full-featured SAAS with:
30
+ - ✅ Next.js 14+ with App Router
31
+ - ✅ Authentication (login page included)
32
+ - ✅ Protected dashboard
33
+ - ✅ Stripe billing setup
34
+ - ✅ Email templates
35
+ - ✅ Pricing page
36
+ - ✅ PWA ready
37
+
38
+ ### Option 2: Add to Existing Project
30
39
 
31
40
  ```bash
41
+ # In your existing Next.js project
42
+ npm install miragedev-sdk
32
43
  npx miragedev init
33
44
  ```
34
45
 
35
- The CLI will guide you through selecting providers and generating configuration files.
36
-
37
46
  **What gets created:**
38
- - `miragedev.config.ts` - SDK configuration
47
+ - `lib/miragedev.config.ts` - SDK configuration
39
48
  - `.env.local.example` - Environment variables template
40
- - `miragedev.init.ts` - Initialization file
49
+ - `lib/miragedev.init.ts` - Initialization file
41
50
  - `AGENTS.md` - Instructions for AI assistants (Copilot, Cursor, etc.)
42
51
 
43
- ### 3. Configure Environment
52
+ ### Configure Environment
44
53
 
45
- Create `.env.local`:
54
+ Copy `.env.local.example` to `.env.local` and fill in your API keys:
55
+
56
+ ```bash
57
+ cp .env.local.example .env.local
58
+ ```
46
59
 
47
60
  ```env
48
61
  # Auth
49
- AUTH_SECRET=your-secret-key-here
62
+ AUTH_SECRET=your-secret-key-here # Generate with: openssl rand -base64 32
50
63
 
51
64
  # Billing (Stripe)
52
- STRIPE_SECRET_KEY=sk_test_xxx
53
- STRIPE_WEBHOOK_SECRET=whsec_xxx
65
+ STRIPE_SECRET_KEY=sk_test_xxx # From https://dashboard.stripe.com/test/apikeys
66
+ STRIPE_WEBHOOK_SECRET=whsec_xxx # From https://dashboard.stripe.com/test/webhooks
54
67
 
55
68
  # Email (Resend)
56
- RESEND_API_KEY=re_xxx
69
+ RESEND_API_KEY=re_xxx # From https://resend.com/api-keys
57
70
  EMAIL_FROM=noreply@yourapp.com
58
71
  ```
59
72
 
60
- ### 4. Initialize SDK
61
-
62
- Create `lib/miragedev.ts`:
63
-
64
- ```typescript
65
- import { initMirageDev } from 'miragedev-sdk'
66
-
67
- initMirageDev({
68
- auth: {
69
- provider: 'nextauth',
70
- sessionSecret: process.env.AUTH_SECRET!,
71
- },
72
- billing: {
73
- provider: 'stripe',
74
- stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
75
- webhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
76
- },
77
- email: {
78
- provider: 'resend',
79
- apiKey: process.env.RESEND_API_KEY!,
80
- from: process.env.EMAIL_FROM!,
81
- },
82
- })
83
- ```
84
-
85
- Import this in your root layout (`app/layout.tsx`).
86
-
87
- ### 5. Start Building
73
+ ### Start Building
88
74
 
89
75
  ```typescript
90
76
  // Server Component - Protected Page
@@ -0,0 +1,591 @@
1
+ 'use strict';
2
+
3
+ var inquirer = require('inquirer');
4
+ var chalk = require('chalk');
5
+ var ora = require('ora');
6
+ var child_process = require('child_process');
7
+ var fs = require('fs/promises');
8
+ var path = require('path');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var inquirer__default = /*#__PURE__*/_interopDefault(inquirer);
13
+ var chalk__default = /*#__PURE__*/_interopDefault(chalk);
14
+ var ora__default = /*#__PURE__*/_interopDefault(ora);
15
+ var fs__default = /*#__PURE__*/_interopDefault(fs);
16
+ var path__default = /*#__PURE__*/_interopDefault(path);
17
+
18
+ // cli/commands/create.ts
19
+ async function createCommand(projectName) {
20
+ console.log(chalk__default.default.blue.bold("\n\u{1F680} Create New SAAS with MirageDev\n"));
21
+ const answers = await inquirer__default.default.prompt([
22
+ {
23
+ type: "input",
24
+ name: "projectName",
25
+ message: "What is your project name?",
26
+ default: projectName || "my-saas",
27
+ when: !projectName,
28
+ validate: (input) => {
29
+ if (!input.trim()) return "Project name is required";
30
+ if (!/^[a-z0-9-]+$/.test(input)) {
31
+ return "Project name can only contain lowercase letters, numbers, and hyphens";
32
+ }
33
+ return true;
34
+ }
35
+ },
36
+ {
37
+ type: "list",
38
+ name: "authProvider",
39
+ message: "Choose your auth provider:",
40
+ choices: [
41
+ { name: "NextAuth.js (Recommended)", value: "nextauth" },
42
+ { name: "Clerk", value: "clerk" },
43
+ { name: "Supabase Auth", value: "supabase" }
44
+ ],
45
+ default: "nextauth"
46
+ },
47
+ {
48
+ type: "list",
49
+ name: "billingProvider",
50
+ message: "Choose your billing provider:",
51
+ choices: [
52
+ { name: "Stripe (Recommended)", value: "stripe" },
53
+ { name: "Paddle (Coming soon)", value: "paddle", disabled: true },
54
+ { name: "LemonSqueezy (Coming soon)", value: "lemonsqueezy", disabled: true }
55
+ ],
56
+ default: "stripe"
57
+ },
58
+ {
59
+ type: "list",
60
+ name: "emailProvider",
61
+ message: "Choose your email provider:",
62
+ choices: [
63
+ { name: "Resend (Recommended)", value: "resend" },
64
+ { name: "SendGrid", value: "sendgrid" },
65
+ { name: "Postmark (Coming soon)", value: "postmark", disabled: true }
66
+ ],
67
+ default: "resend"
68
+ },
69
+ {
70
+ type: "checkbox",
71
+ name: "features",
72
+ message: "Select additional features:",
73
+ choices: [
74
+ { name: "PWA Support (Progressive Web App)", value: "pwa", checked: true },
75
+ { name: "Biometric Authentication", value: "biometric", checked: false },
76
+ { name: "Mobile Optimizations", value: "mobile", checked: true }
77
+ ]
78
+ },
79
+ {
80
+ type: "confirm",
81
+ name: "includeExamples",
82
+ message: "Include example pages? (Login, Dashboard, Pricing)",
83
+ default: true
84
+ }
85
+ ]);
86
+ const finalProjectName = projectName || answers.projectName;
87
+ const projectPath = path__default.default.resolve(process.cwd(), finalProjectName);
88
+ console.log(chalk__default.default.cyan(`
89
+ \u{1F4E6} Creating project at: ${projectPath}
90
+ `));
91
+ try {
92
+ await createNextjsProject(finalProjectName);
93
+ await installSDK(finalProjectName);
94
+ await generateProjectFiles(finalProjectName, answers);
95
+ if (answers.includeExamples) {
96
+ await createExamplePages(finalProjectName, answers);
97
+ }
98
+ console.log(chalk__default.default.green.bold("\n\u2705 Project created successfully!\n"));
99
+ console.log(chalk__default.default.cyan("\u{1F4DD} Next steps:\n"));
100
+ console.log(chalk__default.default.white(` 1. cd ${finalProjectName}`));
101
+ console.log(chalk__default.default.white(" 2. Copy .env.local.example to .env.local"));
102
+ console.log(chalk__default.default.white(" 3. Fill in your API keys in .env.local"));
103
+ console.log(chalk__default.default.white(" 4. npm run dev\n"));
104
+ console.log(chalk__default.default.gray("Documentation: https://github.com/yourusername/miragedev-sdk\n"));
105
+ } catch (error) {
106
+ console.error(chalk__default.default.red("\n\u274C Error creating project:"), error);
107
+ console.log(chalk__default.default.yellow("\nTrying to clean up..."));
108
+ try {
109
+ await fs__default.default.rm(projectPath, { recursive: true, force: true });
110
+ } catch {
111
+ }
112
+ process.exit(1);
113
+ }
114
+ }
115
+ async function createNextjsProject(projectName) {
116
+ const spinner = ora__default.default("Creating Next.js project...").start();
117
+ try {
118
+ child_process.execSync(
119
+ `npx create-next-app@latest ${projectName} --typescript --tailwind --app --no-git --eslint --src-dir --import-alias "@/*"`,
120
+ {
121
+ stdio: "pipe",
122
+ cwd: process.cwd()
123
+ }
124
+ );
125
+ spinner.succeed(chalk__default.default.green("Next.js project created"));
126
+ } catch (error) {
127
+ spinner.fail(chalk__default.default.red("Failed to create Next.js project"));
128
+ throw error;
129
+ }
130
+ }
131
+ async function installSDK(projectName) {
132
+ const spinner = ora__default.default("Installing MirageDev SDK...").start();
133
+ const projectPath = path__default.default.resolve(process.cwd(), projectName);
134
+ try {
135
+ child_process.execSync("npm install miragedev-sdk", {
136
+ stdio: "pipe",
137
+ cwd: projectPath
138
+ });
139
+ spinner.succeed(chalk__default.default.green("MirageDev SDK installed"));
140
+ } catch (error) {
141
+ spinner.fail(chalk__default.default.red("Failed to install SDK"));
142
+ throw error;
143
+ }
144
+ }
145
+ async function generateProjectFiles(projectName, answers) {
146
+ const spinner = ora__default.default("Generating configuration files...").start();
147
+ const projectPath = path__default.default.resolve(process.cwd(), projectName);
148
+ try {
149
+ await generateConfigFile(projectPath, answers);
150
+ await generateEnvTemplate(projectPath, answers);
151
+ await generateInitFile(projectPath, answers);
152
+ await generateAgentRules(projectPath, answers);
153
+ await updateRootLayout(projectPath);
154
+ spinner.succeed(chalk__default.default.green("Configuration files generated"));
155
+ } catch (error) {
156
+ spinner.fail(chalk__default.default.red("Failed to generate configuration"));
157
+ throw error;
158
+ }
159
+ }
160
+ async function generateConfigFile(projectPath, answers) {
161
+ const config = `// MirageDev SDK Configuration
162
+ import { initMirageDev } from 'miragedev-sdk'
163
+
164
+ export function initializeMirageDev() {
165
+ initMirageDev({
166
+ auth: {
167
+ provider: '${answers.authProvider}',
168
+ sessionSecret: process.env.AUTH_SECRET!,
169
+ },
170
+ billing: {
171
+ provider: '${answers.billingProvider}',
172
+ ${answers.billingProvider === "stripe" ? "stripeSecretKey: process.env.STRIPE_SECRET_KEY!," : ""}
173
+ webhookSecret: process.env.${answers.billingProvider.toUpperCase()}_WEBHOOK_SECRET!,
174
+ },
175
+ email: {
176
+ provider: '${answers.emailProvider}',
177
+ apiKey: process.env.${answers.emailProvider.toUpperCase()}_API_KEY!,
178
+ from: process.env.EMAIL_FROM!,
179
+ },
180
+ })
181
+ }
182
+ `;
183
+ await fs__default.default.writeFile(path__default.default.join(projectPath, "lib", "miragedev.config.ts"), config, "utf-8");
184
+ }
185
+ async function generateEnvTemplate(projectPath, answers) {
186
+ const env = `# MirageDev SDK Environment Variables
187
+ # Copy this file to .env.local and fill in your actual values
188
+
189
+ # Auth
190
+ AUTH_SECRET=your-secret-key-here-generate-with-openssl-rand-base64-32
191
+
192
+ # Billing (${answers.billingProvider})
193
+ ${answers.billingProvider === "stripe" ? "STRIPE_SECRET_KEY=sk_test_xxx" : ""}
194
+ ${answers.billingProvider.toUpperCase()}_WEBHOOK_SECRET=whsec_xxx
195
+
196
+ # Email (${answers.emailProvider})
197
+ ${answers.emailProvider.toUpperCase()}_API_KEY=your-api-key-here
198
+ EMAIL_FROM=noreply@yourapp.com
199
+
200
+ # Next.js
201
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
202
+ `;
203
+ await fs__default.default.writeFile(path__default.default.join(projectPath, ".env.local.example"), env, "utf-8");
204
+ }
205
+ async function generateInitFile(projectPath, answers) {
206
+ const hasFeature = (feature) => answers.features.includes(feature);
207
+ const imports = [];
208
+ const setup = [];
209
+ if (hasFeature("pwa")) {
210
+ imports.push("import { configurePWA } from 'miragedev-sdk/pwa'");
211
+ setup.push(`
212
+ // PWA Configuration
213
+ export const pwaConfig = configurePWA({
214
+ name: 'Your SAAS App',
215
+ shortName: 'SAAS',
216
+ theme: '#000000',
217
+ backgroundColor: '#ffffff',
218
+ offline: { enabled: true, pages: ['/'] }
219
+ })`);
220
+ }
221
+ const content = `// MirageDev SDK Initialization
222
+ import { initializeMirageDev } from './miragedev.config'
223
+ ${imports.join("\n")}
224
+
225
+ // Initialize SDK
226
+ initializeMirageDev()
227
+
228
+ ${setup.join("\n\n")}
229
+ `;
230
+ await fs__default.default.writeFile(path__default.default.join(projectPath, "lib", "miragedev.init.ts"), content, "utf-8");
231
+ }
232
+ async function generateAgentRules(projectPath, answers) {
233
+ const modules = ["auth", "billing", "email"];
234
+ if (answers.features.includes("pwa")) modules.push("pwa");
235
+ if (answers.features.includes("mobile")) modules.push("mobile");
236
+ if (answers.features.includes("biometric")) modules.push("auth/biometric");
237
+ const content = `# AI Agent Instructions
238
+
239
+ > **For AI Assistants (GitHub Copilot, OpenCode, Cursor, etc.):**
240
+ > This project uses **MirageDev SDK**. Always prefer SDK functions over manual implementations.
241
+
242
+ ## Installed SDK Modules
243
+
244
+ ${modules.map((mod) => `- \`miragedev-sdk/${mod}\` - ${getModuleDescription(mod)}`).join("\n")}
245
+
246
+ ## Configuration
247
+
248
+ - **Auth Provider:** ${answers.authProvider}
249
+ - **Billing Provider:** ${answers.billingProvider}
250
+ - **Email Provider:** ${answers.emailProvider}
251
+ ${answers.features.includes("pwa") ? "- **PWA:** Enabled" : ""}
252
+ ${answers.features.includes("biometric") ? "- **Biometric Auth:** Enabled" : ""}
253
+ ${answers.features.includes("mobile") ? "- **Mobile Optimizations:** Enabled" : ""}
254
+
255
+ ## Code Generation Rules
256
+
257
+ ### \u2705 DO: Use MirageDev SDK
258
+
259
+ When developer needs authentication:
260
+ \`\`\`typescript
261
+ // \u2705 CORRECT
262
+ import { requireAuth } from 'miragedev-sdk/auth'
263
+
264
+ export default async function Page() {
265
+ const session = await requireAuth()
266
+ return <div>Hello {session.user.name}</div>
267
+ }
268
+ \`\`\`
269
+
270
+ ### \u274C DON'T: Manual Implementation
271
+
272
+ \`\`\`typescript
273
+ // \u274C WRONG - Don't do this when SDK exists
274
+ import Stripe from 'stripe'
275
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
276
+ \`\`\`
277
+
278
+ For full documentation, see: https://github.com/yourusername/miragedev-sdk
279
+ `;
280
+ await fs__default.default.writeFile(path__default.default.join(projectPath, "AGENTS.md"), content, "utf-8");
281
+ }
282
+ async function updateRootLayout(projectPath) {
283
+ const layoutPath = path__default.default.join(projectPath, "src", "app", "layout.tsx");
284
+ let layout = await fs__default.default.readFile(layoutPath, "utf-8");
285
+ const importStatement = "import '@/lib/miragedev.init'\n";
286
+ if (!layout.includes("miragedev.init")) {
287
+ const lines = layout.split("\n");
288
+ const firstImportIndex = lines.findIndex((line) => line.startsWith("import"));
289
+ if (firstImportIndex !== -1) {
290
+ lines.splice(firstImportIndex, 0, importStatement);
291
+ layout = lines.join("\n");
292
+ await fs__default.default.writeFile(layoutPath, layout, "utf-8");
293
+ }
294
+ }
295
+ }
296
+ async function createExamplePages(projectPath, answers) {
297
+ const spinner = ora__default.default("Creating example pages...").start();
298
+ try {
299
+ await createLoginPage(projectPath, answers);
300
+ await createDashboardPage(projectPath);
301
+ await createPricingPage(projectPath);
302
+ await createMiddleware(projectPath);
303
+ await createWebhookRoute(projectPath, answers);
304
+ spinner.succeed(chalk__default.default.green("Example pages created"));
305
+ } catch (error) {
306
+ spinner.fail(chalk__default.default.red("Failed to create example pages"));
307
+ throw error;
308
+ }
309
+ }
310
+ async function createLoginPage(projectPath, _answers) {
311
+ const authDir = path__default.default.join(projectPath, "src", "app", "(auth)", "login");
312
+ await fs__default.default.mkdir(authDir, { recursive: true });
313
+ const content = `'use client'
314
+
315
+ import { signIn } from 'miragedev-sdk/auth/client'
316
+ import { useState } from 'react'
317
+
318
+ export default function LoginPage() {
319
+ const [isLoading, setIsLoading] = useState(false)
320
+
321
+ const handleSignIn = async (provider: string) => {
322
+ setIsLoading(true)
323
+ try {
324
+ await signIn(provider)
325
+ } catch (error) {
326
+ console.error('Sign in error:', error)
327
+ setIsLoading(false)
328
+ }
329
+ }
330
+
331
+ return (
332
+ <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
333
+ <div className="w-full max-w-md space-y-8 rounded-2xl bg-white p-8 shadow-xl">
334
+ <div className="text-center">
335
+ <h1 className="text-3xl font-bold text-gray-900">Welcome Back</h1>
336
+ <p className="mt-2 text-gray-600">Sign in to your account</p>
337
+ </div>
338
+
339
+ <div className="space-y-4">
340
+ <button
341
+ onClick={() => handleSignIn('google')}
342
+ disabled={isLoading}
343
+ className="w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white hover:bg-blue-700 disabled:opacity-50"
344
+ >
345
+ {isLoading ? 'Signing in...' : 'Sign in with Google'}
346
+ </button>
347
+
348
+ <button
349
+ onClick={() => handleSignIn('github')}
350
+ disabled={isLoading}
351
+ className="w-full rounded-lg bg-gray-900 px-4 py-3 font-medium text-white hover:bg-gray-800 disabled:opacity-50"
352
+ >
353
+ {isLoading ? 'Signing in...' : 'Sign in with GitHub'}
354
+ </button>
355
+ </div>
356
+
357
+ <p className="text-center text-sm text-gray-500">
358
+ By signing in, you agree to our Terms of Service
359
+ </p>
360
+ </div>
361
+ </div>
362
+ )
363
+ }
364
+ `;
365
+ await fs__default.default.writeFile(path__default.default.join(authDir, "page.tsx"), content, "utf-8");
366
+ }
367
+ async function createDashboardPage(projectPath) {
368
+ const dashboardDir = path__default.default.join(projectPath, "src", "app", "(protected)", "dashboard");
369
+ await fs__default.default.mkdir(dashboardDir, { recursive: true });
370
+ const content = `import { requireAuth } from 'miragedev-sdk/auth'
371
+ import { UpgradeButton } from '@/components/UpgradeButton'
372
+
373
+ export default async function DashboardPage() {
374
+ const session = await requireAuth()
375
+
376
+ return (
377
+ <div className="min-h-screen bg-gray-50 p-8">
378
+ <div className="mx-auto max-w-4xl">
379
+ <h1 className="text-3xl font-bold text-gray-900">
380
+ Welcome back, {session.user.name || 'User'}!
381
+ </h1>
382
+
383
+ <div className="mt-6 grid gap-6 md:grid-cols-2">
384
+ <div className="rounded-lg bg-white p-6 shadow">
385
+ <h2 className="text-xl font-semibold">Your Account</h2>
386
+ <p className="mt-2 text-gray-600">Email: {session.user.email}</p>
387
+ </div>
388
+
389
+ <div className="rounded-lg bg-white p-6 shadow">
390
+ <h2 className="text-xl font-semibold">Subscription</h2>
391
+ <div className="mt-4">
392
+ <UpgradeButton />
393
+ </div>
394
+ </div>
395
+ </div>
396
+ </div>
397
+ </div>
398
+ )
399
+ }
400
+ `;
401
+ await fs__default.default.writeFile(path__default.default.join(dashboardDir, "page.tsx"), content, "utf-8");
402
+ const componentsDir = path__default.default.join(projectPath, "src", "components");
403
+ await fs__default.default.mkdir(componentsDir, { recursive: true });
404
+ const buttonContent = `'use client'
405
+
406
+ import { useSubscription } from 'miragedev-sdk/billing/client'
407
+
408
+ export function UpgradeButton() {
409
+ const { subscription, isLoading } = useSubscription()
410
+
411
+ if (isLoading) {
412
+ return <div className="text-gray-500">Loading subscription...</div>
413
+ }
414
+
415
+ if (subscription?.status === 'active') {
416
+ return (
417
+ <div className="rounded-lg bg-green-50 p-4 text-green-800">
418
+ \u2705 You're a Pro subscriber!
419
+ </div>
420
+ )
421
+ }
422
+
423
+ return (
424
+ <button className="w-full rounded-lg bg-purple-600 px-6 py-3 font-medium text-white hover:bg-purple-700">
425
+ Upgrade to Pro
426
+ </button>
427
+ )
428
+ }
429
+ `;
430
+ await fs__default.default.writeFile(path__default.default.join(componentsDir, "UpgradeButton.tsx"), buttonContent, "utf-8");
431
+ }
432
+ async function createPricingPage(projectPath) {
433
+ const pricingDir = path__default.default.join(projectPath, "src", "app", "pricing");
434
+ await fs__default.default.mkdir(pricingDir, { recursive: true });
435
+ const content = `export default function PricingPage() {
436
+ return (
437
+ <div className="min-h-screen bg-gradient-to-br from-purple-50 to-blue-50 py-16">
438
+ <div className="mx-auto max-w-6xl px-4">
439
+ <h1 className="text-center text-4xl font-bold text-gray-900">
440
+ Simple, Transparent Pricing
441
+ </h1>
442
+ <p className="mt-4 text-center text-xl text-gray-600">
443
+ Choose the plan that's right for you
444
+ </p>
445
+
446
+ <div className="mt-12 grid gap-8 md:grid-cols-3">
447
+ {/* Free Plan */}
448
+ <div className="rounded-2xl bg-white p-8 shadow-lg">
449
+ <h3 className="text-2xl font-bold">Free</h3>
450
+ <p className="mt-2 text-gray-600">Perfect for getting started</p>
451
+ <div className="mt-4 text-4xl font-bold">$0</div>
452
+ <p className="text-gray-500">per month</p>
453
+
454
+ <ul className="mt-6 space-y-3">
455
+ <li className="flex items-center">
456
+ <span className="mr-2">\u2713</span> Basic features
457
+ </li>
458
+ <li className="flex items-center">
459
+ <span className="mr-2">\u2713</span> 1 user
460
+ </li>
461
+ </ul>
462
+
463
+ <button className="mt-8 w-full rounded-lg border-2 border-gray-300 px-6 py-3 font-medium hover:bg-gray-50">
464
+ Get Started
465
+ </button>
466
+ </div>
467
+
468
+ {/* Pro Plan */}
469
+ <div className="rounded-2xl bg-gradient-to-br from-purple-600 to-blue-600 p-8 text-white shadow-2xl ring-4 ring-purple-300">
470
+ <div className="text-sm font-semibold uppercase tracking-wide">
471
+ Most Popular
472
+ </div>
473
+ <h3 className="mt-2 text-2xl font-bold">Pro</h3>
474
+ <p className="mt-2 opacity-90">For growing businesses</p>
475
+ <div className="mt-4 text-4xl font-bold">$29</div>
476
+ <p className="opacity-80">per month</p>
477
+
478
+ <ul className="mt-6 space-y-3">
479
+ <li className="flex items-center">
480
+ <span className="mr-2">\u2713</span> All Free features
481
+ </li>
482
+ <li className="flex items-center">
483
+ <span className="mr-2">\u2713</span> Unlimited users
484
+ </li>
485
+ <li className="flex items-center">
486
+ <span className="mr-2">\u2713</span> Priority support
487
+ </li>
488
+ </ul>
489
+
490
+ <button className="mt-8 w-full rounded-lg bg-white px-6 py-3 font-medium text-purple-600 hover:bg-gray-50">
491
+ Subscribe Now
492
+ </button>
493
+ </div>
494
+
495
+ {/* Enterprise Plan */}
496
+ <div className="rounded-2xl bg-white p-8 shadow-lg">
497
+ <h3 className="text-2xl font-bold">Enterprise</h3>
498
+ <p className="mt-2 text-gray-600">For large organizations</p>
499
+ <div className="mt-4 text-4xl font-bold">Custom</div>
500
+ <p className="text-gray-500">contact us</p>
501
+
502
+ <ul className="mt-6 space-y-3">
503
+ <li className="flex items-center">
504
+ <span className="mr-2">\u2713</span> All Pro features
505
+ </li>
506
+ <li className="flex items-center">
507
+ <span className="mr-2">\u2713</span> Custom integrations
508
+ </li>
509
+ <li className="flex items-center">
510
+ <span className="mr-2">\u2713</span> Dedicated support
511
+ </li>
512
+ </ul>
513
+
514
+ <button className="mt-8 w-full rounded-lg border-2 border-gray-300 px-6 py-3 font-medium hover:bg-gray-50">
515
+ Contact Sales
516
+ </button>
517
+ </div>
518
+ </div>
519
+ </div>
520
+ </div>
521
+ )
522
+ }
523
+ `;
524
+ await fs__default.default.writeFile(path__default.default.join(pricingDir, "page.tsx"), content, "utf-8");
525
+ }
526
+ async function createMiddleware(projectPath) {
527
+ const content = `import { authMiddleware } from 'miragedev-sdk/auth/middleware'
528
+
529
+ export default authMiddleware({
530
+ publicRoutes: ['/', '/login', '/pricing'],
531
+ redirectTo: '/login'
532
+ })
533
+
534
+ export const config = {
535
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
536
+ }
537
+ `;
538
+ await fs__default.default.writeFile(path__default.default.join(projectPath, "src", "middleware.ts"), content, "utf-8");
539
+ }
540
+ async function createWebhookRoute(projectPath, _answers) {
541
+ const webhookDir = path__default.default.join(projectPath, "src", "app", "api", "webhooks", "stripe");
542
+ await fs__default.default.mkdir(webhookDir, { recursive: true });
543
+ const content = `import { handleWebhook } from 'miragedev-sdk/billing/webhook'
544
+
545
+ export async function POST(req: Request) {
546
+ return handleWebhook(req, {
547
+ onSubscriptionCreated: async (data) => {
548
+ console.log('\u2705 Subscription created:', data.subscriptionId)
549
+ // TODO: Update your database
550
+ // await db.user.update({
551
+ // where: { id: data.userId },
552
+ // data: { subscriptionId: data.subscriptionId, isPro: true }
553
+ // })
554
+ },
555
+
556
+ onSubscriptionUpdated: async (data) => {
557
+ console.log('\u{1F504} Subscription updated:', data.subscriptionId)
558
+ // TODO: Update your database
559
+ },
560
+
561
+ onSubscriptionCanceled: async (data) => {
562
+ console.log('\u274C Subscription canceled:', data.subscriptionId)
563
+ // TODO: Update your database
564
+ // await db.user.update({
565
+ // where: { id: data.userId },
566
+ // data: { isPro: false }
567
+ // })
568
+ },
569
+
570
+ onPaymentFailed: async (data) => {
571
+ console.log('\u{1F4B3} Payment failed:', data.subscriptionId)
572
+ // TODO: Send notification to user
573
+ },
574
+ })
575
+ }
576
+ `;
577
+ await fs__default.default.writeFile(path__default.default.join(webhookDir, "route.ts"), content, "utf-8");
578
+ }
579
+ function getModuleDescription(module) {
580
+ const descriptions = {
581
+ "auth": "requireAuth(), getSession(), middleware",
582
+ "auth/biometric": "enableBiometric(), signInWithBiometric()",
583
+ "billing": "createCheckout(), createPortal(), webhooks",
584
+ "email": "sendEmail(), sendTemplateEmail() with 5 templates",
585
+ "pwa": "configurePWA(), manifest + service worker",
586
+ "mobile": "useNetworkStatus(), useInstallPrompt(), etc"
587
+ };
588
+ return descriptions[module] || "Available functions";
589
+ }
590
+
591
+ exports.createCommand = createCommand;
@@ -0,0 +1,581 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { execSync } from 'child_process';
5
+ import fs from 'fs/promises';
6
+ import path from 'path';
7
+
8
+ // cli/commands/create.ts
9
+ async function createCommand(projectName) {
10
+ console.log(chalk.blue.bold("\n\u{1F680} Create New SAAS with MirageDev\n"));
11
+ const answers = await inquirer.prompt([
12
+ {
13
+ type: "input",
14
+ name: "projectName",
15
+ message: "What is your project name?",
16
+ default: projectName || "my-saas",
17
+ when: !projectName,
18
+ validate: (input) => {
19
+ if (!input.trim()) return "Project name is required";
20
+ if (!/^[a-z0-9-]+$/.test(input)) {
21
+ return "Project name can only contain lowercase letters, numbers, and hyphens";
22
+ }
23
+ return true;
24
+ }
25
+ },
26
+ {
27
+ type: "list",
28
+ name: "authProvider",
29
+ message: "Choose your auth provider:",
30
+ choices: [
31
+ { name: "NextAuth.js (Recommended)", value: "nextauth" },
32
+ { name: "Clerk", value: "clerk" },
33
+ { name: "Supabase Auth", value: "supabase" }
34
+ ],
35
+ default: "nextauth"
36
+ },
37
+ {
38
+ type: "list",
39
+ name: "billingProvider",
40
+ message: "Choose your billing provider:",
41
+ choices: [
42
+ { name: "Stripe (Recommended)", value: "stripe" },
43
+ { name: "Paddle (Coming soon)", value: "paddle", disabled: true },
44
+ { name: "LemonSqueezy (Coming soon)", value: "lemonsqueezy", disabled: true }
45
+ ],
46
+ default: "stripe"
47
+ },
48
+ {
49
+ type: "list",
50
+ name: "emailProvider",
51
+ message: "Choose your email provider:",
52
+ choices: [
53
+ { name: "Resend (Recommended)", value: "resend" },
54
+ { name: "SendGrid", value: "sendgrid" },
55
+ { name: "Postmark (Coming soon)", value: "postmark", disabled: true }
56
+ ],
57
+ default: "resend"
58
+ },
59
+ {
60
+ type: "checkbox",
61
+ name: "features",
62
+ message: "Select additional features:",
63
+ choices: [
64
+ { name: "PWA Support (Progressive Web App)", value: "pwa", checked: true },
65
+ { name: "Biometric Authentication", value: "biometric", checked: false },
66
+ { name: "Mobile Optimizations", value: "mobile", checked: true }
67
+ ]
68
+ },
69
+ {
70
+ type: "confirm",
71
+ name: "includeExamples",
72
+ message: "Include example pages? (Login, Dashboard, Pricing)",
73
+ default: true
74
+ }
75
+ ]);
76
+ const finalProjectName = projectName || answers.projectName;
77
+ const projectPath = path.resolve(process.cwd(), finalProjectName);
78
+ console.log(chalk.cyan(`
79
+ \u{1F4E6} Creating project at: ${projectPath}
80
+ `));
81
+ try {
82
+ await createNextjsProject(finalProjectName);
83
+ await installSDK(finalProjectName);
84
+ await generateProjectFiles(finalProjectName, answers);
85
+ if (answers.includeExamples) {
86
+ await createExamplePages(finalProjectName, answers);
87
+ }
88
+ console.log(chalk.green.bold("\n\u2705 Project created successfully!\n"));
89
+ console.log(chalk.cyan("\u{1F4DD} Next steps:\n"));
90
+ console.log(chalk.white(` 1. cd ${finalProjectName}`));
91
+ console.log(chalk.white(" 2. Copy .env.local.example to .env.local"));
92
+ console.log(chalk.white(" 3. Fill in your API keys in .env.local"));
93
+ console.log(chalk.white(" 4. npm run dev\n"));
94
+ console.log(chalk.gray("Documentation: https://github.com/yourusername/miragedev-sdk\n"));
95
+ } catch (error) {
96
+ console.error(chalk.red("\n\u274C Error creating project:"), error);
97
+ console.log(chalk.yellow("\nTrying to clean up..."));
98
+ try {
99
+ await fs.rm(projectPath, { recursive: true, force: true });
100
+ } catch {
101
+ }
102
+ process.exit(1);
103
+ }
104
+ }
105
+ async function createNextjsProject(projectName) {
106
+ const spinner = ora("Creating Next.js project...").start();
107
+ try {
108
+ execSync(
109
+ `npx create-next-app@latest ${projectName} --typescript --tailwind --app --no-git --eslint --src-dir --import-alias "@/*"`,
110
+ {
111
+ stdio: "pipe",
112
+ cwd: process.cwd()
113
+ }
114
+ );
115
+ spinner.succeed(chalk.green("Next.js project created"));
116
+ } catch (error) {
117
+ spinner.fail(chalk.red("Failed to create Next.js project"));
118
+ throw error;
119
+ }
120
+ }
121
+ async function installSDK(projectName) {
122
+ const spinner = ora("Installing MirageDev SDK...").start();
123
+ const projectPath = path.resolve(process.cwd(), projectName);
124
+ try {
125
+ execSync("npm install miragedev-sdk", {
126
+ stdio: "pipe",
127
+ cwd: projectPath
128
+ });
129
+ spinner.succeed(chalk.green("MirageDev SDK installed"));
130
+ } catch (error) {
131
+ spinner.fail(chalk.red("Failed to install SDK"));
132
+ throw error;
133
+ }
134
+ }
135
+ async function generateProjectFiles(projectName, answers) {
136
+ const spinner = ora("Generating configuration files...").start();
137
+ const projectPath = path.resolve(process.cwd(), projectName);
138
+ try {
139
+ await generateConfigFile(projectPath, answers);
140
+ await generateEnvTemplate(projectPath, answers);
141
+ await generateInitFile(projectPath, answers);
142
+ await generateAgentRules(projectPath, answers);
143
+ await updateRootLayout(projectPath);
144
+ spinner.succeed(chalk.green("Configuration files generated"));
145
+ } catch (error) {
146
+ spinner.fail(chalk.red("Failed to generate configuration"));
147
+ throw error;
148
+ }
149
+ }
150
+ async function generateConfigFile(projectPath, answers) {
151
+ const config = `// MirageDev SDK Configuration
152
+ import { initMirageDev } from 'miragedev-sdk'
153
+
154
+ export function initializeMirageDev() {
155
+ initMirageDev({
156
+ auth: {
157
+ provider: '${answers.authProvider}',
158
+ sessionSecret: process.env.AUTH_SECRET!,
159
+ },
160
+ billing: {
161
+ provider: '${answers.billingProvider}',
162
+ ${answers.billingProvider === "stripe" ? "stripeSecretKey: process.env.STRIPE_SECRET_KEY!," : ""}
163
+ webhookSecret: process.env.${answers.billingProvider.toUpperCase()}_WEBHOOK_SECRET!,
164
+ },
165
+ email: {
166
+ provider: '${answers.emailProvider}',
167
+ apiKey: process.env.${answers.emailProvider.toUpperCase()}_API_KEY!,
168
+ from: process.env.EMAIL_FROM!,
169
+ },
170
+ })
171
+ }
172
+ `;
173
+ await fs.writeFile(path.join(projectPath, "lib", "miragedev.config.ts"), config, "utf-8");
174
+ }
175
+ async function generateEnvTemplate(projectPath, answers) {
176
+ const env = `# MirageDev SDK Environment Variables
177
+ # Copy this file to .env.local and fill in your actual values
178
+
179
+ # Auth
180
+ AUTH_SECRET=your-secret-key-here-generate-with-openssl-rand-base64-32
181
+
182
+ # Billing (${answers.billingProvider})
183
+ ${answers.billingProvider === "stripe" ? "STRIPE_SECRET_KEY=sk_test_xxx" : ""}
184
+ ${answers.billingProvider.toUpperCase()}_WEBHOOK_SECRET=whsec_xxx
185
+
186
+ # Email (${answers.emailProvider})
187
+ ${answers.emailProvider.toUpperCase()}_API_KEY=your-api-key-here
188
+ EMAIL_FROM=noreply@yourapp.com
189
+
190
+ # Next.js
191
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
192
+ `;
193
+ await fs.writeFile(path.join(projectPath, ".env.local.example"), env, "utf-8");
194
+ }
195
+ async function generateInitFile(projectPath, answers) {
196
+ const hasFeature = (feature) => answers.features.includes(feature);
197
+ const imports = [];
198
+ const setup = [];
199
+ if (hasFeature("pwa")) {
200
+ imports.push("import { configurePWA } from 'miragedev-sdk/pwa'");
201
+ setup.push(`
202
+ // PWA Configuration
203
+ export const pwaConfig = configurePWA({
204
+ name: 'Your SAAS App',
205
+ shortName: 'SAAS',
206
+ theme: '#000000',
207
+ backgroundColor: '#ffffff',
208
+ offline: { enabled: true, pages: ['/'] }
209
+ })`);
210
+ }
211
+ const content = `// MirageDev SDK Initialization
212
+ import { initializeMirageDev } from './miragedev.config'
213
+ ${imports.join("\n")}
214
+
215
+ // Initialize SDK
216
+ initializeMirageDev()
217
+
218
+ ${setup.join("\n\n")}
219
+ `;
220
+ await fs.writeFile(path.join(projectPath, "lib", "miragedev.init.ts"), content, "utf-8");
221
+ }
222
+ async function generateAgentRules(projectPath, answers) {
223
+ const modules = ["auth", "billing", "email"];
224
+ if (answers.features.includes("pwa")) modules.push("pwa");
225
+ if (answers.features.includes("mobile")) modules.push("mobile");
226
+ if (answers.features.includes("biometric")) modules.push("auth/biometric");
227
+ const content = `# AI Agent Instructions
228
+
229
+ > **For AI Assistants (GitHub Copilot, OpenCode, Cursor, etc.):**
230
+ > This project uses **MirageDev SDK**. Always prefer SDK functions over manual implementations.
231
+
232
+ ## Installed SDK Modules
233
+
234
+ ${modules.map((mod) => `- \`miragedev-sdk/${mod}\` - ${getModuleDescription(mod)}`).join("\n")}
235
+
236
+ ## Configuration
237
+
238
+ - **Auth Provider:** ${answers.authProvider}
239
+ - **Billing Provider:** ${answers.billingProvider}
240
+ - **Email Provider:** ${answers.emailProvider}
241
+ ${answers.features.includes("pwa") ? "- **PWA:** Enabled" : ""}
242
+ ${answers.features.includes("biometric") ? "- **Biometric Auth:** Enabled" : ""}
243
+ ${answers.features.includes("mobile") ? "- **Mobile Optimizations:** Enabled" : ""}
244
+
245
+ ## Code Generation Rules
246
+
247
+ ### \u2705 DO: Use MirageDev SDK
248
+
249
+ When developer needs authentication:
250
+ \`\`\`typescript
251
+ // \u2705 CORRECT
252
+ import { requireAuth } from 'miragedev-sdk/auth'
253
+
254
+ export default async function Page() {
255
+ const session = await requireAuth()
256
+ return <div>Hello {session.user.name}</div>
257
+ }
258
+ \`\`\`
259
+
260
+ ### \u274C DON'T: Manual Implementation
261
+
262
+ \`\`\`typescript
263
+ // \u274C WRONG - Don't do this when SDK exists
264
+ import Stripe from 'stripe'
265
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
266
+ \`\`\`
267
+
268
+ For full documentation, see: https://github.com/yourusername/miragedev-sdk
269
+ `;
270
+ await fs.writeFile(path.join(projectPath, "AGENTS.md"), content, "utf-8");
271
+ }
272
+ async function updateRootLayout(projectPath) {
273
+ const layoutPath = path.join(projectPath, "src", "app", "layout.tsx");
274
+ let layout = await fs.readFile(layoutPath, "utf-8");
275
+ const importStatement = "import '@/lib/miragedev.init'\n";
276
+ if (!layout.includes("miragedev.init")) {
277
+ const lines = layout.split("\n");
278
+ const firstImportIndex = lines.findIndex((line) => line.startsWith("import"));
279
+ if (firstImportIndex !== -1) {
280
+ lines.splice(firstImportIndex, 0, importStatement);
281
+ layout = lines.join("\n");
282
+ await fs.writeFile(layoutPath, layout, "utf-8");
283
+ }
284
+ }
285
+ }
286
+ async function createExamplePages(projectPath, answers) {
287
+ const spinner = ora("Creating example pages...").start();
288
+ try {
289
+ await createLoginPage(projectPath, answers);
290
+ await createDashboardPage(projectPath);
291
+ await createPricingPage(projectPath);
292
+ await createMiddleware(projectPath);
293
+ await createWebhookRoute(projectPath, answers);
294
+ spinner.succeed(chalk.green("Example pages created"));
295
+ } catch (error) {
296
+ spinner.fail(chalk.red("Failed to create example pages"));
297
+ throw error;
298
+ }
299
+ }
300
+ async function createLoginPage(projectPath, _answers) {
301
+ const authDir = path.join(projectPath, "src", "app", "(auth)", "login");
302
+ await fs.mkdir(authDir, { recursive: true });
303
+ const content = `'use client'
304
+
305
+ import { signIn } from 'miragedev-sdk/auth/client'
306
+ import { useState } from 'react'
307
+
308
+ export default function LoginPage() {
309
+ const [isLoading, setIsLoading] = useState(false)
310
+
311
+ const handleSignIn = async (provider: string) => {
312
+ setIsLoading(true)
313
+ try {
314
+ await signIn(provider)
315
+ } catch (error) {
316
+ console.error('Sign in error:', error)
317
+ setIsLoading(false)
318
+ }
319
+ }
320
+
321
+ return (
322
+ <div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100">
323
+ <div className="w-full max-w-md space-y-8 rounded-2xl bg-white p-8 shadow-xl">
324
+ <div className="text-center">
325
+ <h1 className="text-3xl font-bold text-gray-900">Welcome Back</h1>
326
+ <p className="mt-2 text-gray-600">Sign in to your account</p>
327
+ </div>
328
+
329
+ <div className="space-y-4">
330
+ <button
331
+ onClick={() => handleSignIn('google')}
332
+ disabled={isLoading}
333
+ className="w-full rounded-lg bg-blue-600 px-4 py-3 font-medium text-white hover:bg-blue-700 disabled:opacity-50"
334
+ >
335
+ {isLoading ? 'Signing in...' : 'Sign in with Google'}
336
+ </button>
337
+
338
+ <button
339
+ onClick={() => handleSignIn('github')}
340
+ disabled={isLoading}
341
+ className="w-full rounded-lg bg-gray-900 px-4 py-3 font-medium text-white hover:bg-gray-800 disabled:opacity-50"
342
+ >
343
+ {isLoading ? 'Signing in...' : 'Sign in with GitHub'}
344
+ </button>
345
+ </div>
346
+
347
+ <p className="text-center text-sm text-gray-500">
348
+ By signing in, you agree to our Terms of Service
349
+ </p>
350
+ </div>
351
+ </div>
352
+ )
353
+ }
354
+ `;
355
+ await fs.writeFile(path.join(authDir, "page.tsx"), content, "utf-8");
356
+ }
357
+ async function createDashboardPage(projectPath) {
358
+ const dashboardDir = path.join(projectPath, "src", "app", "(protected)", "dashboard");
359
+ await fs.mkdir(dashboardDir, { recursive: true });
360
+ const content = `import { requireAuth } from 'miragedev-sdk/auth'
361
+ import { UpgradeButton } from '@/components/UpgradeButton'
362
+
363
+ export default async function DashboardPage() {
364
+ const session = await requireAuth()
365
+
366
+ return (
367
+ <div className="min-h-screen bg-gray-50 p-8">
368
+ <div className="mx-auto max-w-4xl">
369
+ <h1 className="text-3xl font-bold text-gray-900">
370
+ Welcome back, {session.user.name || 'User'}!
371
+ </h1>
372
+
373
+ <div className="mt-6 grid gap-6 md:grid-cols-2">
374
+ <div className="rounded-lg bg-white p-6 shadow">
375
+ <h2 className="text-xl font-semibold">Your Account</h2>
376
+ <p className="mt-2 text-gray-600">Email: {session.user.email}</p>
377
+ </div>
378
+
379
+ <div className="rounded-lg bg-white p-6 shadow">
380
+ <h2 className="text-xl font-semibold">Subscription</h2>
381
+ <div className="mt-4">
382
+ <UpgradeButton />
383
+ </div>
384
+ </div>
385
+ </div>
386
+ </div>
387
+ </div>
388
+ )
389
+ }
390
+ `;
391
+ await fs.writeFile(path.join(dashboardDir, "page.tsx"), content, "utf-8");
392
+ const componentsDir = path.join(projectPath, "src", "components");
393
+ await fs.mkdir(componentsDir, { recursive: true });
394
+ const buttonContent = `'use client'
395
+
396
+ import { useSubscription } from 'miragedev-sdk/billing/client'
397
+
398
+ export function UpgradeButton() {
399
+ const { subscription, isLoading } = useSubscription()
400
+
401
+ if (isLoading) {
402
+ return <div className="text-gray-500">Loading subscription...</div>
403
+ }
404
+
405
+ if (subscription?.status === 'active') {
406
+ return (
407
+ <div className="rounded-lg bg-green-50 p-4 text-green-800">
408
+ \u2705 You're a Pro subscriber!
409
+ </div>
410
+ )
411
+ }
412
+
413
+ return (
414
+ <button className="w-full rounded-lg bg-purple-600 px-6 py-3 font-medium text-white hover:bg-purple-700">
415
+ Upgrade to Pro
416
+ </button>
417
+ )
418
+ }
419
+ `;
420
+ await fs.writeFile(path.join(componentsDir, "UpgradeButton.tsx"), buttonContent, "utf-8");
421
+ }
422
+ async function createPricingPage(projectPath) {
423
+ const pricingDir = path.join(projectPath, "src", "app", "pricing");
424
+ await fs.mkdir(pricingDir, { recursive: true });
425
+ const content = `export default function PricingPage() {
426
+ return (
427
+ <div className="min-h-screen bg-gradient-to-br from-purple-50 to-blue-50 py-16">
428
+ <div className="mx-auto max-w-6xl px-4">
429
+ <h1 className="text-center text-4xl font-bold text-gray-900">
430
+ Simple, Transparent Pricing
431
+ </h1>
432
+ <p className="mt-4 text-center text-xl text-gray-600">
433
+ Choose the plan that's right for you
434
+ </p>
435
+
436
+ <div className="mt-12 grid gap-8 md:grid-cols-3">
437
+ {/* Free Plan */}
438
+ <div className="rounded-2xl bg-white p-8 shadow-lg">
439
+ <h3 className="text-2xl font-bold">Free</h3>
440
+ <p className="mt-2 text-gray-600">Perfect for getting started</p>
441
+ <div className="mt-4 text-4xl font-bold">$0</div>
442
+ <p className="text-gray-500">per month</p>
443
+
444
+ <ul className="mt-6 space-y-3">
445
+ <li className="flex items-center">
446
+ <span className="mr-2">\u2713</span> Basic features
447
+ </li>
448
+ <li className="flex items-center">
449
+ <span className="mr-2">\u2713</span> 1 user
450
+ </li>
451
+ </ul>
452
+
453
+ <button className="mt-8 w-full rounded-lg border-2 border-gray-300 px-6 py-3 font-medium hover:bg-gray-50">
454
+ Get Started
455
+ </button>
456
+ </div>
457
+
458
+ {/* Pro Plan */}
459
+ <div className="rounded-2xl bg-gradient-to-br from-purple-600 to-blue-600 p-8 text-white shadow-2xl ring-4 ring-purple-300">
460
+ <div className="text-sm font-semibold uppercase tracking-wide">
461
+ Most Popular
462
+ </div>
463
+ <h3 className="mt-2 text-2xl font-bold">Pro</h3>
464
+ <p className="mt-2 opacity-90">For growing businesses</p>
465
+ <div className="mt-4 text-4xl font-bold">$29</div>
466
+ <p className="opacity-80">per month</p>
467
+
468
+ <ul className="mt-6 space-y-3">
469
+ <li className="flex items-center">
470
+ <span className="mr-2">\u2713</span> All Free features
471
+ </li>
472
+ <li className="flex items-center">
473
+ <span className="mr-2">\u2713</span> Unlimited users
474
+ </li>
475
+ <li className="flex items-center">
476
+ <span className="mr-2">\u2713</span> Priority support
477
+ </li>
478
+ </ul>
479
+
480
+ <button className="mt-8 w-full rounded-lg bg-white px-6 py-3 font-medium text-purple-600 hover:bg-gray-50">
481
+ Subscribe Now
482
+ </button>
483
+ </div>
484
+
485
+ {/* Enterprise Plan */}
486
+ <div className="rounded-2xl bg-white p-8 shadow-lg">
487
+ <h3 className="text-2xl font-bold">Enterprise</h3>
488
+ <p className="mt-2 text-gray-600">For large organizations</p>
489
+ <div className="mt-4 text-4xl font-bold">Custom</div>
490
+ <p className="text-gray-500">contact us</p>
491
+
492
+ <ul className="mt-6 space-y-3">
493
+ <li className="flex items-center">
494
+ <span className="mr-2">\u2713</span> All Pro features
495
+ </li>
496
+ <li className="flex items-center">
497
+ <span className="mr-2">\u2713</span> Custom integrations
498
+ </li>
499
+ <li className="flex items-center">
500
+ <span className="mr-2">\u2713</span> Dedicated support
501
+ </li>
502
+ </ul>
503
+
504
+ <button className="mt-8 w-full rounded-lg border-2 border-gray-300 px-6 py-3 font-medium hover:bg-gray-50">
505
+ Contact Sales
506
+ </button>
507
+ </div>
508
+ </div>
509
+ </div>
510
+ </div>
511
+ )
512
+ }
513
+ `;
514
+ await fs.writeFile(path.join(pricingDir, "page.tsx"), content, "utf-8");
515
+ }
516
+ async function createMiddleware(projectPath) {
517
+ const content = `import { authMiddleware } from 'miragedev-sdk/auth/middleware'
518
+
519
+ export default authMiddleware({
520
+ publicRoutes: ['/', '/login', '/pricing'],
521
+ redirectTo: '/login'
522
+ })
523
+
524
+ export const config = {
525
+ matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)']
526
+ }
527
+ `;
528
+ await fs.writeFile(path.join(projectPath, "src", "middleware.ts"), content, "utf-8");
529
+ }
530
+ async function createWebhookRoute(projectPath, _answers) {
531
+ const webhookDir = path.join(projectPath, "src", "app", "api", "webhooks", "stripe");
532
+ await fs.mkdir(webhookDir, { recursive: true });
533
+ const content = `import { handleWebhook } from 'miragedev-sdk/billing/webhook'
534
+
535
+ export async function POST(req: Request) {
536
+ return handleWebhook(req, {
537
+ onSubscriptionCreated: async (data) => {
538
+ console.log('\u2705 Subscription created:', data.subscriptionId)
539
+ // TODO: Update your database
540
+ // await db.user.update({
541
+ // where: { id: data.userId },
542
+ // data: { subscriptionId: data.subscriptionId, isPro: true }
543
+ // })
544
+ },
545
+
546
+ onSubscriptionUpdated: async (data) => {
547
+ console.log('\u{1F504} Subscription updated:', data.subscriptionId)
548
+ // TODO: Update your database
549
+ },
550
+
551
+ onSubscriptionCanceled: async (data) => {
552
+ console.log('\u274C Subscription canceled:', data.subscriptionId)
553
+ // TODO: Update your database
554
+ // await db.user.update({
555
+ // where: { id: data.userId },
556
+ // data: { isPro: false }
557
+ // })
558
+ },
559
+
560
+ onPaymentFailed: async (data) => {
561
+ console.log('\u{1F4B3} Payment failed:', data.subscriptionId)
562
+ // TODO: Send notification to user
563
+ },
564
+ })
565
+ }
566
+ `;
567
+ await fs.writeFile(path.join(webhookDir, "route.ts"), content, "utf-8");
568
+ }
569
+ function getModuleDescription(module) {
570
+ const descriptions = {
571
+ "auth": "requireAuth(), getSession(), middleware",
572
+ "auth/biometric": "enableBiometric(), signInWithBiometric()",
573
+ "billing": "createCheckout(), createPortal(), webhooks",
574
+ "email": "sendEmail(), sendTemplateEmail() with 5 templates",
575
+ "pwa": "configurePWA(), manifest + service worker",
576
+ "mobile": "useNetworkStatus(), useInstallPrompt(), etc"
577
+ };
578
+ return descriptions[module] || "Available functions";
579
+ }
580
+
581
+ export { createCommand };
@@ -0,0 +1,11 @@
1
+ 'use strict';
2
+
3
+ var chunkOIWPIRJY_cjs = require('../../chunk-OIWPIRJY.cjs');
4
+ require('../../chunk-75ZPJI57.cjs');
5
+
6
+
7
+
8
+ Object.defineProperty(exports, "createCommand", {
9
+ enumerable: true,
10
+ get: function () { return chunkOIWPIRJY_cjs.createCommand; }
11
+ });
@@ -0,0 +1,3 @@
1
+ declare function createCommand(projectName?: string): Promise<void>;
2
+
3
+ export { createCommand };
@@ -0,0 +1,3 @@
1
+ declare function createCommand(projectName?: string): Promise<void>;
2
+
3
+ export { createCommand };
@@ -0,0 +1,2 @@
1
+ export { createCommand } from '../../chunk-PDNLOOI2.js';
2
+ import '../../chunk-MLKGABMK.js';
@@ -2,10 +2,12 @@
2
2
  'use strict';
3
3
 
4
4
  var chunkM26EDKMY_cjs = require('../chunk-M26EDKMY.cjs');
5
+ var chunkOIWPIRJY_cjs = require('../chunk-OIWPIRJY.cjs');
5
6
  require('../chunk-75ZPJI57.cjs');
6
7
  var commander = require('commander');
7
8
 
8
9
  var program = new commander.Command();
9
10
  program.name("miragedev").description("MirageDev SDK CLI - Build SAAS apps faster").version("0.1.0");
10
- program.command("init").description("Initialize MirageDev SDK in your project").action(chunkM26EDKMY_cjs.initCommand);
11
+ program.command("create [project-name]").description("Create a new SAAS project with MirageDev SDK").action(chunkOIWPIRJY_cjs.createCommand);
12
+ program.command("init").description("Initialize MirageDev SDK in existing project").action(chunkM26EDKMY_cjs.initCommand);
11
13
  program.parse();
package/dist/cli/index.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { initCommand } from '../chunk-DZDDLA4G.js';
3
+ import { createCommand } from '../chunk-PDNLOOI2.js';
3
4
  import '../chunk-MLKGABMK.js';
4
5
  import { Command } from 'commander';
5
6
 
6
7
  var program = new Command();
7
8
  program.name("miragedev").description("MirageDev SDK CLI - Build SAAS apps faster").version("0.1.0");
8
- program.command("init").description("Initialize MirageDev SDK in your project").action(initCommand);
9
+ program.command("create [project-name]").description("Create a new SAAS project with MirageDev SDK").action(createCommand);
10
+ program.command("init").description("Initialize MirageDev SDK in existing project").action(initCommand);
9
11
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miragedev-sdk",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "AI-first SDK for building SAAS applications with Next.js",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",