@mars-stack/cli 0.2.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/dist/index.js +137 -12
  2. package/dist/index.js.map +1 -1
  3. package/package.json +4 -3
  4. package/template/.cursor/rules/composition-patterns.mdc +186 -0
  5. package/template/.cursor/rules/data-access.mdc +29 -0
  6. package/template/.cursor/rules/project-structure.mdc +34 -0
  7. package/template/.cursor/rules/security.mdc +25 -0
  8. package/template/.cursor/rules/testing.mdc +24 -0
  9. package/template/.cursor/rules/ui-conventions.mdc +29 -0
  10. package/template/.cursor/skills/add-api-route/SKILL.md +122 -0
  11. package/template/.cursor/skills/add-audit-log/SKILL.md +375 -0
  12. package/template/.cursor/skills/add-blog/SKILL.md +447 -0
  13. package/template/.cursor/skills/add-command-palette/SKILL.md +438 -0
  14. package/template/.cursor/skills/add-component/SKILL.md +158 -0
  15. package/template/.cursor/skills/add-crud-routes/SKILL.md +221 -0
  16. package/template/.cursor/skills/add-e2e-test/SKILL.md +227 -0
  17. package/template/.cursor/skills/add-error-boundary/SKILL.md +472 -0
  18. package/template/.cursor/skills/add-feature/SKILL.md +174 -0
  19. package/template/.cursor/skills/add-middleware/SKILL.md +135 -0
  20. package/template/.cursor/skills/add-page/SKILL.md +151 -0
  21. package/template/.cursor/skills/add-prisma-model/SKILL.md +148 -0
  22. package/template/.cursor/skills/add-protected-resource/SKILL.md +192 -0
  23. package/template/.cursor/skills/add-role/SKILL.md +156 -0
  24. package/template/.cursor/skills/add-server-action/SKILL.md +167 -0
  25. package/template/.cursor/skills/add-webhook/SKILL.md +192 -0
  26. package/template/.cursor/skills/build-complete-feature/SKILL.md +227 -0
  27. package/template/.cursor/skills/build-dashboard/SKILL.md +211 -0
  28. package/template/.cursor/skills/build-data-table/SKILL.md +283 -0
  29. package/template/.cursor/skills/build-form/SKILL.md +231 -0
  30. package/template/.cursor/skills/build-landing-page/SKILL.md +248 -0
  31. package/template/.cursor/skills/configure-ai/SKILL.md +617 -0
  32. package/template/.cursor/skills/configure-analytics/SKILL.md +413 -0
  33. package/template/.cursor/skills/configure-dark-mode/SKILL.md +309 -0
  34. package/template/.cursor/skills/configure-email/SKILL.md +170 -0
  35. package/template/.cursor/skills/configure-email-verification/SKILL.md +333 -0
  36. package/template/.cursor/skills/configure-feature-flags/SKILL.md +361 -0
  37. package/template/.cursor/skills/configure-i18n/SKILL.md +518 -0
  38. package/template/.cursor/skills/configure-jobs/SKILL.md +500 -0
  39. package/template/.cursor/skills/configure-magic-links/SKILL.md +385 -0
  40. package/template/.cursor/skills/configure-multi-tenancy/SKILL.md +611 -0
  41. package/template/.cursor/skills/configure-notifications/SKILL.md +569 -0
  42. package/template/.cursor/skills/configure-oauth/SKILL.md +217 -0
  43. package/template/.cursor/skills/configure-onboarding/SKILL.md +483 -0
  44. package/template/.cursor/skills/configure-payments/SKILL.md +243 -0
  45. package/template/.cursor/skills/configure-realtime/SKILL.md +733 -0
  46. package/template/.cursor/skills/configure-search/SKILL.md +581 -0
  47. package/template/.cursor/skills/configure-storage/SKILL.md +273 -0
  48. package/template/.cursor/skills/configure-two-factor/SKILL.md +518 -0
  49. package/template/.cursor/skills/create-execution-plan/SKILL.md +204 -0
  50. package/template/.cursor/skills/create-seed/SKILL.md +191 -0
  51. package/template/.cursor/skills/deploy-to-vercel/SKILL.md +300 -0
  52. package/template/.cursor/skills/design-tokens/SKILL.md +138 -0
  53. package/template/.cursor/skills/mars-capture-conversation-context/SKILL.md +119 -0
  54. package/template/.cursor/skills/setup-billing/SKILL.md +322 -0
  55. package/template/.cursor/skills/setup-project/SKILL.md +104 -0
  56. package/template/.cursor/skills/setup-teams/SKILL.md +682 -0
  57. package/template/.cursor/skills/test-api-route/SKILL.md +219 -0
  58. package/template/.cursor/skills/update-architecture-docs/SKILL.md +99 -0
  59. package/template/AGENTS.md +104 -0
  60. package/template/ARCHITECTURE.md +102 -0
  61. package/template/docs/QUALITY_SCORE.md +20 -0
  62. package/template/docs/design-docs/conversation-as-system-record.md +70 -0
  63. package/template/docs/design-docs/core-beliefs.md +43 -0
  64. package/template/docs/design-docs/index.md +8 -0
  65. package/template/docs/exec-plans/active/.gitkeep +0 -0
  66. package/template/docs/exec-plans/completed/.gitkeep +0 -0
  67. package/template/docs/exec-plans/tech-debt.md +7 -0
  68. package/template/docs/generated/.gitkeep +0 -0
  69. package/template/docs/product-specs/index.md +7 -0
  70. package/template/docs/references/index.md +18 -0
  71. package/template/e2e/api.spec.ts +20 -0
  72. package/template/e2e/auth.spec.ts +24 -0
  73. package/template/e2e/public.spec.ts +25 -0
  74. package/template/eslint.config.mjs +24 -0
  75. package/template/next-env.d.ts +6 -0
  76. package/template/next.config.ts +45 -0
  77. package/template/package.json +80 -0
  78. package/template/playwright.config.ts +31 -0
  79. package/template/postcss.config.mjs +8 -0
  80. package/template/prisma/generated/prisma/browser.ts +49 -0
  81. package/template/prisma/generated/prisma/client.ts +73 -0
  82. package/template/prisma/generated/prisma/commonInputTypes.ts +406 -0
  83. package/template/prisma/generated/prisma/enums.ts +15 -0
  84. package/template/prisma/generated/prisma/internal/class.ts +254 -0
  85. package/template/prisma/generated/prisma/internal/prismaNamespace.ts +1240 -0
  86. package/template/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts +190 -0
  87. package/template/prisma/generated/prisma/models/Account.ts +1543 -0
  88. package/template/prisma/generated/prisma/models/File.ts +1529 -0
  89. package/template/prisma/generated/prisma/models/Session.ts +1415 -0
  90. package/template/prisma/generated/prisma/models/Subscription.ts +1455 -0
  91. package/template/prisma/generated/prisma/models/User.ts +2235 -0
  92. package/template/prisma/generated/prisma/models/VerificationToken.ts +1099 -0
  93. package/template/prisma/generated/prisma/models.ts +17 -0
  94. package/template/prisma/schema/auth.prisma +69 -0
  95. package/template/prisma/schema/base.prisma +8 -0
  96. package/template/prisma/schema/file.prisma +15 -0
  97. package/template/prisma/schema/subscription.prisma +17 -0
  98. package/template/prisma.config.ts +13 -0
  99. package/template/scripts/check-architecture.ts +221 -0
  100. package/template/scripts/check-doc-freshness.ts +242 -0
  101. package/template/scripts/ensure-db.mjs +291 -0
  102. package/template/scripts/generate-docs.ts +143 -0
  103. package/template/scripts/generate-env-example.ts +89 -0
  104. package/template/scripts/seed.ts +56 -0
  105. package/template/scripts/update-quality-score.ts +263 -0
  106. package/template/src/__tests__/architecture.test.ts +114 -0
  107. package/template/src/app/(auth)/forgotten-password/page.tsx +92 -0
  108. package/template/src/app/(auth)/layout.tsx +11 -0
  109. package/template/src/app/(auth)/register/page.tsx +162 -0
  110. package/template/src/app/(auth)/reset-password/page.tsx +109 -0
  111. package/template/src/app/(auth)/sign-in/page.tsx +122 -0
  112. package/template/src/app/(auth)/verify/[token]/page.tsx +87 -0
  113. package/template/src/app/(auth)/verify/page.tsx +56 -0
  114. package/template/src/app/(protected)/admin/page.tsx +108 -0
  115. package/template/src/app/(protected)/dashboard/loading.tsx +20 -0
  116. package/template/src/app/(protected)/dashboard/page.tsx +22 -0
  117. package/template/src/app/(protected)/layout.tsx +262 -0
  118. package/template/src/app/(protected)/settings/page.tsx +370 -0
  119. package/template/src/app/api/auth/forgot/route.ts +63 -0
  120. package/template/src/app/api/auth/login/route.ts +121 -0
  121. package/template/src/app/api/auth/logout/route.ts +19 -0
  122. package/template/src/app/api/auth/me/route.ts +30 -0
  123. package/template/src/app/api/auth/reset/route.ts +45 -0
  124. package/template/src/app/api/auth/signup/route.ts +85 -0
  125. package/template/src/app/api/auth/verify/route.ts +46 -0
  126. package/template/src/app/api/csrf/route.ts +12 -0
  127. package/template/src/app/api/health/route.ts +10 -0
  128. package/template/src/app/api/protected/admin/users/route.ts +24 -0
  129. package/template/src/app/api/protected/billing/checkout/route.ts +83 -0
  130. package/template/src/app/api/protected/billing/portal/route.ts +39 -0
  131. package/template/src/app/api/protected/files/[fileId]/route.ts +86 -0
  132. package/template/src/app/api/protected/files/upload/route.ts +64 -0
  133. package/template/src/app/api/protected/user/password/route.ts +63 -0
  134. package/template/src/app/api/protected/user/profile/route.ts +35 -0
  135. package/template/src/app/api/protected/user/sessions/[sessionId]/route.ts +33 -0
  136. package/template/src/app/api/protected/user/sessions/route.ts +22 -0
  137. package/template/src/app/api/readiness/route.ts +15 -0
  138. package/template/src/app/api/webhooks/stripe/route.ts +166 -0
  139. package/template/src/app/error.tsx +33 -0
  140. package/template/src/app/layout.tsx +29 -0
  141. package/template/src/app/not-found.tsx +20 -0
  142. package/template/src/app/page.tsx +136 -0
  143. package/template/src/app/privacy/page.tsx +178 -0
  144. package/template/src/app/providers.tsx +8 -0
  145. package/template/src/app/terms/page.tsx +139 -0
  146. package/template/src/config/app.config.ts +70 -0
  147. package/template/src/config/routes.ts +17 -0
  148. package/template/src/features/admin/index.ts +11 -0
  149. package/template/src/features/admin/permissions.ts +64 -0
  150. package/template/src/features/auth/context/AuthContext.tsx +96 -0
  151. package/template/src/features/auth/context/index.ts +2 -0
  152. package/template/src/features/auth/index.ts +3 -0
  153. package/template/src/features/auth/server/consent.ts +66 -0
  154. package/template/src/features/auth/server/session-revocation.ts +20 -0
  155. package/template/src/features/auth/server/sessions.ts +66 -0
  156. package/template/src/features/auth/server/user.ts +166 -0
  157. package/template/src/features/auth/types.ts +19 -0
  158. package/template/src/features/auth/validators.ts +29 -0
  159. package/template/src/features/billing/server/index.ts +66 -0
  160. package/template/src/features/billing/types.ts +43 -0
  161. package/template/src/features/uploads/server/index.ts +49 -0
  162. package/template/src/features/uploads/types.ts +26 -0
  163. package/template/src/lib/core/email/templates/base-layout.ts +122 -0
  164. package/template/src/lib/core/email/templates/index.ts +4 -0
  165. package/template/src/lib/core/email/templates/password-reset-email.ts +42 -0
  166. package/template/src/lib/core/email/templates/verification-email.ts +41 -0
  167. package/template/src/lib/core/email/templates/welcome-email.ts +40 -0
  168. package/template/src/lib/mars.ts +56 -0
  169. package/template/src/lib/prisma.ts +19 -0
  170. package/template/src/proxy.ts +92 -0
  171. package/template/src/styles/brand.css +15 -0
  172. package/template/src/styles/globals.css +7 -0
  173. package/template/tsconfig.json +59 -0
  174. package/template/vitest.config.ts +41 -0
  175. package/template/vitest.setup.ts +24 -0
@@ -0,0 +1,243 @@
1
+ # Skill: Configure Payments
2
+
3
+ Set up Stripe payments including checkout, subscriptions, and webhooks in a MARS application.
4
+
5
+ ## When to Use
6
+
7
+ Use this skill when the user asks to add payments, billing, subscriptions, Stripe, or checkout.
8
+
9
+ ## Prerequisites
10
+
11
+ - A Stripe account at [stripe.com](https://stripe.com)
12
+ - `appConfig.features.billing` set to `true`
13
+ - `appConfig.services.payments.provider` set to `'stripe'`
14
+
15
+ ## Step 1: Install Stripe SDK
16
+
17
+ ```bash
18
+ yarn add stripe
19
+ ```
20
+
21
+ ## Step 2: Environment Variables
22
+
23
+ ```bash
24
+ STRIPE_SECRET_KEY="sk_test_..."
25
+ STRIPE_WEBHOOK_SECRET="whsec_..."
26
+ NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY="pk_test_..."
27
+ ```
28
+
29
+ ## Step 3: Create the Stripe Service
30
+
31
+ ```typescript
32
+ // src/features/billing/server/stripe.ts
33
+ import 'server-only';
34
+
35
+ import Stripe from 'stripe';
36
+
37
+ let _stripe: Stripe | null = null;
38
+
39
+ export function getStripe(): Stripe {
40
+ if (_stripe) return _stripe;
41
+
42
+ const secretKey = process.env.STRIPE_SECRET_KEY;
43
+ if (!secretKey) {
44
+ throw new Error(
45
+ 'STRIPE_SECRET_KEY is not set.\n'
46
+ + ' → Get your key from https://dashboard.stripe.com/apikeys\n'
47
+ + ' → Add it to your .env file',
48
+ );
49
+ }
50
+
51
+ _stripe = new Stripe(secretKey, { apiVersion: '2024-12-18.acacia' });
52
+ return _stripe;
53
+ }
54
+ ```
55
+
56
+ ## Step 4: Prisma Schema for Subscriptions
57
+
58
+ ```prisma
59
+ // prisma/schema/billing.prisma
60
+ model Subscription {
61
+ id String @id @default(cuid())
62
+ userId String @unique
63
+ stripeCustomerId String @unique
64
+ stripeSubscriptionId String? @unique
65
+ stripePriceId String?
66
+ status String @default("inactive")
67
+ currentPeriodEnd DateTime?
68
+ cancelAtPeriodEnd Boolean @default(false)
69
+ createdAt DateTime @default(now())
70
+ updatedAt DateTime @updatedAt
71
+
72
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
73
+
74
+ @@index([stripeCustomerId])
75
+ @@index([status])
76
+ }
77
+ ```
78
+
79
+ ## Step 5: Checkout API Route
80
+
81
+ ```typescript
82
+ // src/app/api/protected/billing/checkout/route.ts
83
+ import { handleApiError, withAuthNoParams, type AuthenticatedRequest } from '@/lib/mars';
84
+ import { prisma } from '@/lib/prisma';
85
+ import { getStripe } from '@/features/billing/server';
86
+ import { NextResponse } from 'next/server';
87
+ import { z } from 'zod';
88
+
89
+ const checkoutSchema = z.object({
90
+ priceId: z.string().min(1),
91
+ });
92
+
93
+ export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
94
+ try {
95
+ const { priceId } = checkoutSchema.parse(await request.json());
96
+ const stripe = getStripe();
97
+ const userId = request.session.userId;
98
+
99
+ // Find or create Stripe customer
100
+ let subscription = await prisma.subscription.findUnique({ where: { userId } });
101
+
102
+ if (!subscription) {
103
+ const customer = await stripe.customers.create({
104
+ email: request.session.email,
105
+ metadata: { userId },
106
+ });
107
+
108
+ subscription = await prisma.subscription.create({
109
+ data: { userId, stripeCustomerId: customer.id },
110
+ });
111
+ }
112
+
113
+ // Create checkout session
114
+ const session = await stripe.checkout.sessions.create({
115
+ customer: subscription.stripeCustomerId,
116
+ mode: 'subscription',
117
+ payment_method_types: ['card'],
118
+ line_items: [{ price: priceId, quantity: 1 }],
119
+ success_url: `${process.env.APP_URL || 'http://localhost:3000'}/billing?success=true`,
120
+ cancel_url: `${process.env.APP_URL || 'http://localhost:3000'}/billing?canceled=true`,
121
+ metadata: { userId },
122
+ });
123
+
124
+ return NextResponse.json({ url: session.url });
125
+ } catch (error) {
126
+ return handleApiError(error, { endpoint: '/api/protected/billing/checkout' });
127
+ }
128
+ });
129
+ ```
130
+
131
+ ## Step 6: Customer Portal Route
132
+
133
+ ```typescript
134
+ // src/app/api/protected/billing/portal/route.ts
135
+ import { handleApiError, withAuthNoParams, type AuthenticatedRequest } from '@/lib/mars';
136
+ import { prisma } from '@/lib/prisma';
137
+ import { getStripe } from '@/features/billing/server';
138
+ import { NextResponse } from 'next/server';
139
+
140
+ export const POST = withAuthNoParams(async (request: AuthenticatedRequest) => {
141
+ try {
142
+ const stripe = getStripe();
143
+ const subscription = await prisma.subscription.findUnique({
144
+ where: { userId: request.session.userId },
145
+ });
146
+
147
+ if (!subscription) {
148
+ return NextResponse.json({ error: 'No subscription found' }, { status: 404 });
149
+ }
150
+
151
+ const session = await stripe.billingPortal.sessions.create({
152
+ customer: subscription.stripeCustomerId,
153
+ return_url: `${process.env.APP_URL || 'http://localhost:3000'}/billing`,
154
+ });
155
+
156
+ return NextResponse.json({ url: session.url });
157
+ } catch (error) {
158
+ return handleApiError(error, { endpoint: '/api/protected/billing/portal' });
159
+ }
160
+ });
161
+ ```
162
+
163
+ ## Step 7: Stripe Webhook
164
+
165
+ See the `add-webhook` skill for the full pattern. Key events to handle:
166
+
167
+ ```typescript
168
+ switch (event.type) {
169
+ case 'checkout.session.completed': {
170
+ const session = event.data.object;
171
+ await prisma.subscription.update({
172
+ where: { stripeCustomerId: session.customer as string },
173
+ data: {
174
+ stripeSubscriptionId: session.subscription as string,
175
+ status: 'active',
176
+ },
177
+ });
178
+ break;
179
+ }
180
+ case 'customer.subscription.updated': {
181
+ const sub = event.data.object;
182
+ await prisma.subscription.update({
183
+ where: { stripeSubscriptionId: sub.id },
184
+ data: {
185
+ status: sub.status,
186
+ stripePriceId: sub.items.data[0]?.price.id,
187
+ currentPeriodEnd: new Date(sub.current_period_end * 1000),
188
+ cancelAtPeriodEnd: sub.cancel_at_period_end,
189
+ },
190
+ });
191
+ break;
192
+ }
193
+ case 'customer.subscription.deleted': {
194
+ const sub = event.data.object;
195
+ await prisma.subscription.update({
196
+ where: { stripeSubscriptionId: sub.id },
197
+ data: { status: 'canceled' },
198
+ });
199
+ break;
200
+ }
201
+ }
202
+ ```
203
+
204
+ ## Step 8: Subscription Check Helper
205
+
206
+ ```typescript
207
+ // src/features/billing/server/index.ts
208
+ import 'server-only';
209
+
210
+ import { prisma } from '@/lib/prisma';
211
+
212
+ export async function getUserSubscription(userId: string) {
213
+ return prisma.subscription.findUnique({ where: { userId } });
214
+ }
215
+
216
+ export async function isSubscribed(userId: string): Promise<boolean> {
217
+ const sub = await getUserSubscription(userId);
218
+ return sub?.status === 'active' || sub?.status === 'trialing';
219
+ }
220
+ ```
221
+
222
+ ## Local Development
223
+
224
+ Test webhooks locally with Stripe CLI:
225
+
226
+ ```bash
227
+ stripe listen --forward-to localhost:3000/api/webhooks/stripe
228
+ ```
229
+
230
+ This prints a webhook signing secret -- use it as `STRIPE_WEBHOOK_SECRET`.
231
+
232
+ ## Checklist
233
+
234
+ - [ ] Stripe SDK installed
235
+ - [ ] Environment variables set (`STRIPE_SECRET_KEY`, `STRIPE_WEBHOOK_SECRET`, `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`)
236
+ - [ ] Lazy Stripe client created (`src/features/billing/server/stripe.ts`)
237
+ - [ ] Subscription model in Prisma schema
238
+ - [ ] Checkout session API route
239
+ - [ ] Customer portal API route
240
+ - [ ] Webhook handler for subscription events
241
+ - [ ] Subscription check helper functions
242
+ - [ ] Webhook excluded from CSRF in middleware
243
+ - [ ] Feature flag checked (`appConfig.features.billing`)