@mars-stack/core 0.4.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 +32 -0
- package/cursor/manifest.json +304 -0
- package/cursor/rules/mars-composition-patterns.mdc +186 -0
- package/cursor/rules/mars-data-access.mdc +26 -0
- package/cursor/rules/mars-project-structure.mdc +34 -0
- package/cursor/rules/mars-security.mdc +25 -0
- package/cursor/rules/mars-testing.mdc +24 -0
- package/cursor/rules/mars-ui-conventions.mdc +29 -0
- package/cursor/skills/mars-add-api-route/SKILL.md +120 -0
- package/cursor/skills/mars-add-audit-log/SKILL.md +373 -0
- package/cursor/skills/mars-add-blog/SKILL.md +447 -0
- package/cursor/skills/mars-add-command-palette/SKILL.md +438 -0
- package/cursor/skills/mars-add-component/SKILL.md +158 -0
- package/cursor/skills/mars-add-crud-routes/SKILL.md +221 -0
- package/cursor/skills/mars-add-e2e-test/SKILL.md +227 -0
- package/cursor/skills/mars-add-error-boundary/SKILL.md +472 -0
- package/cursor/skills/mars-add-feature/SKILL.md +174 -0
- package/cursor/skills/mars-add-middleware/SKILL.md +135 -0
- package/cursor/skills/mars-add-page/SKILL.md +153 -0
- package/cursor/skills/mars-add-prisma-model/SKILL.md +148 -0
- package/cursor/skills/mars-add-protected-resource/SKILL.md +192 -0
- package/cursor/skills/mars-add-role/SKILL.md +156 -0
- package/cursor/skills/mars-add-server-action/SKILL.md +167 -0
- package/cursor/skills/mars-add-webhook/SKILL.md +192 -0
- package/cursor/skills/mars-build-complete-feature/SKILL.md +228 -0
- package/cursor/skills/mars-build-dashboard/SKILL.md +211 -0
- package/cursor/skills/mars-build-data-table/SKILL.md +284 -0
- package/cursor/skills/mars-build-form/SKILL.md +229 -0
- package/cursor/skills/mars-build-landing-page/SKILL.md +248 -0
- package/cursor/skills/mars-capture-conversation-context/SKILL.md +119 -0
- package/cursor/skills/mars-configure-ai/SKILL.md +617 -0
- package/cursor/skills/mars-configure-analytics/SKILL.md +413 -0
- package/cursor/skills/mars-configure-dark-mode/SKILL.md +309 -0
- package/cursor/skills/mars-configure-email/SKILL.md +170 -0
- package/cursor/skills/mars-configure-email-verification/SKILL.md +333 -0
- package/cursor/skills/mars-configure-feature-flags/SKILL.md +361 -0
- package/cursor/skills/mars-configure-i18n/SKILL.md +518 -0
- package/cursor/skills/mars-configure-jobs/SKILL.md +500 -0
- package/cursor/skills/mars-configure-magic-links/SKILL.md +385 -0
- package/cursor/skills/mars-configure-multi-tenancy/SKILL.md +611 -0
- package/cursor/skills/mars-configure-notifications/SKILL.md +569 -0
- package/cursor/skills/mars-configure-oauth/SKILL.md +217 -0
- package/cursor/skills/mars-configure-onboarding/SKILL.md +483 -0
- package/cursor/skills/mars-configure-payments/SKILL.md +243 -0
- package/cursor/skills/mars-configure-realtime/SKILL.md +733 -0
- package/cursor/skills/mars-configure-search/SKILL.md +581 -0
- package/cursor/skills/mars-configure-storage/SKILL.md +273 -0
- package/cursor/skills/mars-configure-two-factor/SKILL.md +518 -0
- package/cursor/skills/mars-create-execution-plan/SKILL.md +204 -0
- package/cursor/skills/mars-create-seed/SKILL.md +191 -0
- package/cursor/skills/mars-deploy-to-vercel/SKILL.md +300 -0
- package/cursor/skills/mars-design-tokens/SKILL.md +138 -0
- package/cursor/skills/mars-setup-billing/SKILL.md +322 -0
- package/cursor/skills/mars-setup-project/SKILL.md +104 -0
- package/cursor/skills/mars-setup-teams/SKILL.md +688 -0
- package/cursor/skills/mars-test-api-route/SKILL.md +219 -0
- package/cursor/skills/mars-update-architecture-docs/SKILL.md +189 -0
- package/dist/api-error/index.d.ts +27 -0
- package/dist/api-error/index.d.ts.map +1 -0
- package/dist/api-error/index.js +2 -0
- package/dist/auth/credential-tag.d.ts +5 -0
- package/dist/auth/credential-tag.d.ts.map +1 -0
- package/dist/auth/credential-tag.js +2 -0
- package/dist/auth/crypto-utils.d.ts +43 -0
- package/dist/auth/crypto-utils.d.ts.map +1 -0
- package/dist/auth/crypto-utils.js +1 -0
- package/dist/auth/csrf.d.ts +32 -0
- package/dist/auth/csrf.d.ts.map +1 -0
- package/dist/auth/csrf.js +2 -0
- package/dist/auth/hooks/index.d.ts +4 -0
- package/dist/auth/hooks/index.d.ts.map +1 -0
- package/dist/auth/hooks/index.js +68 -0
- package/dist/auth/hooks/useCSRF.d.ts +7 -0
- package/dist/auth/hooks/useCSRF.d.ts.map +1 -0
- package/dist/auth/hooks/usePasswordStrength.d.ts +17 -0
- package/dist/auth/hooks/usePasswordStrength.d.ts.map +1 -0
- package/dist/auth/internal-api-key.d.ts +5 -0
- package/dist/auth/internal-api-key.d.ts.map +1 -0
- package/dist/auth/internal-api-key.js +30 -0
- package/dist/auth/link-utils.d.ts +13 -0
- package/dist/auth/link-utils.d.ts.map +1 -0
- package/dist/auth/link-utils.js +1 -0
- package/dist/auth/middleware.d.ts +56 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +3 -0
- package/dist/auth/password.d.ts +28 -0
- package/dist/auth/password.d.ts.map +1 -0
- package/dist/auth/password.js +1 -0
- package/dist/auth/reset-token.d.ts +3 -0
- package/dist/auth/reset-token.d.ts.map +1 -0
- package/dist/auth/reset-token.js +9 -0
- package/dist/auth/responses.d.ts +15 -0
- package/dist/auth/responses.d.ts.map +1 -0
- package/dist/auth/responses.js +2 -0
- package/dist/auth/session.d.ts +79 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +1 -0
- package/dist/auth/types.d.ts +18 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +10 -0
- package/dist/auth/validation.d.ts +146 -0
- package/dist/auth/validation.d.ts.map +1 -0
- package/dist/auth/validation.js +116 -0
- package/dist/auth/validators.d.ts +4 -0
- package/dist/auth/validators.d.ts.map +1 -0
- package/dist/auth/validators.js +27 -0
- package/dist/auth/verification.d.ts +54 -0
- package/dist/auth/verification.d.ts.map +1 -0
- package/dist/auth/verification.js +39 -0
- package/dist/chunk-4LS3QDD5.js +162 -0
- package/dist/chunk-ABBUHT5Z.js +110 -0
- package/dist/chunk-CTYAVMOF.js +15 -0
- package/dist/chunk-GVLH2GQP.js +14 -0
- package/dist/chunk-HOSMMQMA.js +109 -0
- package/dist/chunk-MXQ66RUN.js +28 -0
- package/dist/chunk-PZE3JGXO.js +149 -0
- package/dist/chunk-QAH2Y5WK.js +93 -0
- package/dist/chunk-QWMN5UJC.js +76 -0
- package/dist/chunk-ROQV54MU.js +117 -0
- package/dist/chunk-U4NZQ366.js +46 -0
- package/dist/chunk-WBJOIENS.js +22 -0
- package/dist/chunk-WO6FHJHG.js +29 -0
- package/dist/chunk-Z5BEKPJI.js +96 -0
- package/dist/chunk-ZA46T6GX.js +24 -0
- package/dist/configure-mars.d.ts +104 -0
- package/dist/configure-mars.d.ts.map +1 -0
- package/dist/database/index.d.ts +8 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +1 -0
- package/dist/email/index.d.ts +25 -0
- package/dist/email/index.d.ts.map +1 -0
- package/dist/email/index.js +2 -0
- package/dist/email/types.d.ts +18 -0
- package/dist/email/types.d.ts.map +1 -0
- package/dist/env/index.d.ts +36 -0
- package/dist/env/index.d.ts.map +1 -0
- package/dist/env/index.js +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +163 -0
- package/dist/logger/index.d.ts +80 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +1 -0
- package/dist/payments/index.d.ts +53 -0
- package/dist/payments/index.d.ts.map +1 -0
- package/dist/payments/index.js +72 -0
- package/dist/plugin/builtin/email-plugins.d.ts +10 -0
- package/dist/plugin/builtin/email-plugins.d.ts.map +1 -0
- package/dist/plugin/builtin/index.d.ts +4 -0
- package/dist/plugin/builtin/index.d.ts.map +1 -0
- package/dist/plugin/builtin/index.js +324 -0
- package/dist/plugin/builtin/payment-plugins.d.ts +4 -0
- package/dist/plugin/builtin/payment-plugins.d.ts.map +1 -0
- package/dist/plugin/builtin/storage-plugins.d.ts +5 -0
- package/dist/plugin/builtin/storage-plugins.d.ts.map +1 -0
- package/dist/plugin/index.d.ts +21 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +30 -0
- package/dist/rate-limit/index.d.ts +89 -0
- package/dist/rate-limit/index.d.ts.map +1 -0
- package/dist/rate-limit/index.js +166 -0
- package/dist/seo/faq.d.ts +37 -0
- package/dist/seo/faq.d.ts.map +1 -0
- package/dist/seo/index.d.ts +75 -0
- package/dist/seo/index.d.ts.map +1 -0
- package/dist/seo/index.js +1 -0
- package/dist/storage/index.d.ts +50 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +211 -0
- package/dist/test-utils/factories.d.ts +38 -0
- package/dist/test-utils/factories.d.ts.map +1 -0
- package/dist/test-utils/index.d.ts +6 -0
- package/dist/test-utils/index.d.ts.map +1 -0
- package/dist/test-utils/index.js +117 -0
- package/dist/test-utils/mock-auth.d.ts +25 -0
- package/dist/test-utils/mock-auth.d.ts.map +1 -0
- package/dist/test-utils/mock-prisma.d.ts +55 -0
- package/dist/test-utils/mock-prisma.d.ts.map +1 -0
- package/dist/test-utils/render.d.ts +4 -0
- package/dist/test-utils/render.d.ts.map +1 -0
- package/dist/test-utils/request-helpers.d.ts +6 -0
- package/dist/test-utils/request-helpers.d.ts.map +1 -0
- package/dist/types.d.ts +53 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/math.d.ts +2 -0
- package/dist/utils/math.d.ts.map +1 -0
- package/dist/utils/math.js +7 -0
- package/dist/utils/optional-import.d.ts +14 -0
- package/dist/utils/optional-import.d.ts.map +1 -0
- package/package.json +205 -0
- package/scripts/generate-skill-adapters.ts +146 -0
- package/scripts/postinstall.mjs +146 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Skill: Work with the Design Token System
|
|
2
|
+
|
|
3
|
+
Understand and extend the MARS three-layer design token architecture for theming and UI consistency.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
Use this skill when the user asks about theming, changing colours, adding dark mode support, creating new design tokens, or understanding the styling system.
|
|
8
|
+
|
|
9
|
+
## Architecture Overview
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Layer 1: Primitive Palette (@mars-stack/ui/styles/primitives.css)
|
|
13
|
+
↓ referenced by
|
|
14
|
+
Layer 2: Semantic Tokens (@mars-stack/ui/styles/tokens.css)
|
|
15
|
+
↓ registered as
|
|
16
|
+
Layer 3: Tailwind Theme (@mars-stack/ui/styles/theme.css via @theme)
|
|
17
|
+
↓ used in
|
|
18
|
+
Components (via Tailwind utility classes)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Layer 1: Primitive Palette (`@mars-stack/ui/styles/primitives.css`)
|
|
22
|
+
|
|
23
|
+
Raw colour scales generated from the primary colour. These are building blocks, never used directly in components.
|
|
24
|
+
|
|
25
|
+
```css
|
|
26
|
+
:root {
|
|
27
|
+
--brand-50: oklch(0.97 0.01 250);
|
|
28
|
+
--brand-100: oklch(0.93 0.03 250);
|
|
29
|
+
/* ... full 50-950 scale */
|
|
30
|
+
--gray-50: oklch(0.985 0 0);
|
|
31
|
+
--gray-100: oklch(0.97 0 0);
|
|
32
|
+
/* ... */
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Layer 2: Semantic Tokens (`@mars-stack/ui/styles/tokens.css`)
|
|
37
|
+
|
|
38
|
+
What colours *mean*. These swap between light and dark themes.
|
|
39
|
+
|
|
40
|
+
```css
|
|
41
|
+
:root {
|
|
42
|
+
--surface-card: white;
|
|
43
|
+
--text-primary: var(--gray-900);
|
|
44
|
+
--border-input: var(--gray-300);
|
|
45
|
+
--brand-primary: var(--brand-600);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.dark {
|
|
49
|
+
--surface-card: var(--gray-900);
|
|
50
|
+
--text-primary: var(--gray-100);
|
|
51
|
+
--border-input: var(--gray-700);
|
|
52
|
+
--brand-primary: var(--brand-500);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Layer 3: Tailwind Theme (`@mars-stack/ui/styles/theme.css`)
|
|
57
|
+
|
|
58
|
+
Registers semantic tokens as Tailwind utilities via `@theme`:
|
|
59
|
+
|
|
60
|
+
```css
|
|
61
|
+
@theme {
|
|
62
|
+
--color-surface-background: var(--surface-background);
|
|
63
|
+
--color-surface-card: var(--surface-card);
|
|
64
|
+
--color-text-primary: var(--text-primary);
|
|
65
|
+
--color-border-default: var(--border-default);
|
|
66
|
+
--color-brand-primary: var(--brand-primary);
|
|
67
|
+
/* ... */
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
This enables `bg-surface-card`, `text-text-primary`, `border-border-default`, etc. in components.
|
|
72
|
+
|
|
73
|
+
## Token Categories
|
|
74
|
+
|
|
75
|
+
| Category | Prefix | Examples |
|
|
76
|
+
|----------|--------|---------|
|
|
77
|
+
| Surfaces | `surface-` | `bg-surface-background`, `bg-surface-card`, `bg-surface-input` |
|
|
78
|
+
| Text | `text-` | `text-text-primary`, `text-text-secondary`, `text-text-muted` |
|
|
79
|
+
| Borders | `border-` | `border-border-default`, `border-border-input`, `border-border-focus` |
|
|
80
|
+
| Brand | `brand-` | `bg-brand-primary`, `hover:bg-brand-primary-hover` |
|
|
81
|
+
| Feedback | varies | `bg-success-muted`, `text-text-error`, `border-border-success` |
|
|
82
|
+
| Interactive | varies | `bg-ghost-hover`, `focus:ring-ring-focus`, `bg-danger-bg` |
|
|
83
|
+
| Navigation | `nav-` | `text-nav-item`, `bg-nav-item-active-bg` |
|
|
84
|
+
| Table | `table-` | `bg-table-header-bg`, `hover:bg-table-row-hover` |
|
|
85
|
+
| Avatar | `avatar-` | `bg-avatar-bg`, `text-avatar-text` |
|
|
86
|
+
|
|
87
|
+
## How to Change the Theme
|
|
88
|
+
|
|
89
|
+
### Change Primary Colour
|
|
90
|
+
|
|
91
|
+
1. Edit `@mars-stack/ui/styles/primitives.css` -- update the `--brand-*` scale to your new colour's oklch values.
|
|
92
|
+
2. Everything else (semantic tokens, components) updates automatically.
|
|
93
|
+
|
|
94
|
+
### Add a New Semantic Token
|
|
95
|
+
|
|
96
|
+
1. Define the token in `@mars-stack/ui/styles/tokens.css` for both `:root` and `.dark`:
|
|
97
|
+
```css
|
|
98
|
+
:root { --sidebar-accent: var(--brand-100); }
|
|
99
|
+
.dark { --sidebar-accent: var(--brand-900); }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
2. Register it in `@mars-stack/ui/styles/theme.css`:
|
|
103
|
+
```css
|
|
104
|
+
@theme {
|
|
105
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
3. Use in components: `bg-sidebar-accent`.
|
|
110
|
+
|
|
111
|
+
### Add a New Colour Scale
|
|
112
|
+
|
|
113
|
+
1. Add the full 50-950 scale in `@mars-stack/ui/styles/primitives.css`.
|
|
114
|
+
2. Add semantic tokens that reference it in `@mars-stack/ui/styles/tokens.css`.
|
|
115
|
+
3. Register the semantic tokens in `@mars-stack/ui/styles/theme.css`.
|
|
116
|
+
|
|
117
|
+
## Rules
|
|
118
|
+
|
|
119
|
+
- **Never use raw Tailwind colours** (`text-gray-900`, `bg-blue-500`) in components. Always use semantic tokens.
|
|
120
|
+
- **Always define dark mode counterparts** when adding new semantic tokens.
|
|
121
|
+
- **Components get their colours from Layer 3** (Tailwind utilities backed by semantic tokens). They never reference Layer 1 directly.
|
|
122
|
+
- The `app.config.ts` `theme.primaryColor` field is used by the CLI during scaffolding to generate the correct primitive palette. At runtime, the CSS tokens are the source of truth.
|
|
123
|
+
|
|
124
|
+
## Breakpoints
|
|
125
|
+
|
|
126
|
+
Responsive breakpoints are defined in `@mars-stack/ui/styles/breakpoints.css` and registered in theme.css:
|
|
127
|
+
|
|
128
|
+
```css
|
|
129
|
+
@theme {
|
|
130
|
+
--breakpoint-sm: 640px;
|
|
131
|
+
--breakpoint-md: 768px;
|
|
132
|
+
--breakpoint-lg: 1024px;
|
|
133
|
+
--breakpoint-xl: 1280px;
|
|
134
|
+
--breakpoint-2xl: 1536px;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Use as standard Tailwind responsive prefixes: `md:grid-cols-2`, `lg:px-8`.
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# Skill: Setup Billing (Meta-Skill)
|
|
2
|
+
|
|
3
|
+
Orchestrate the complete billing and subscription system: Stripe integration, webhook handling, subscription UI, customer portal, and documentation updates.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
Use this skill when the user asks to:
|
|
8
|
+
- Set up billing or subscriptions
|
|
9
|
+
- Add a paywall or premium features
|
|
10
|
+
- Integrate Stripe end-to-end
|
|
11
|
+
- Add pricing page with checkout
|
|
12
|
+
|
|
13
|
+
This meta-skill chains sub-skills in the correct order. For just the Stripe SDK setup, use `configure-payments` instead.
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- A Stripe account at [stripe.com](https://stripe.com)
|
|
18
|
+
- Read `src/config/app.config.ts` to check if billing is already flagged
|
|
19
|
+
- Read `prisma/schema/` to check for existing billing models
|
|
20
|
+
|
|
21
|
+
## Execution Order
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
┌─────────────────────────────────────────────────────┐
|
|
25
|
+
│ Phase 0: Plan │
|
|
26
|
+
│ create-execution-plan │
|
|
27
|
+
├─────────────────────────────────────────────────────┤
|
|
28
|
+
│ Phase 1: Payment Provider │
|
|
29
|
+
│ configure-payments (Stripe SDK, env vars, client) │
|
|
30
|
+
├─────────────────────────────────────────────────────┤
|
|
31
|
+
│ Phase 2: Data Layer │
|
|
32
|
+
│ add-prisma-model (Subscription, WebhookEvent) │
|
|
33
|
+
├─────────────────────────────────────────────────────┤
|
|
34
|
+
│ Phase 3: Webhook │
|
|
35
|
+
│ add-webhook (Stripe webhook handler) │
|
|
36
|
+
├─────────────────────────────────────────────────────┤
|
|
37
|
+
│ Phase 4: Feature Module │
|
|
38
|
+
│ add-feature (billing service, checkout, portal) │
|
|
39
|
+
├─────────────────────────────────────────────────────┤
|
|
40
|
+
│ Phase 5: API Routes │
|
|
41
|
+
│ add-api-route (checkout, portal, subscription) │
|
|
42
|
+
├─────────────────────────────────────────────────────┤
|
|
43
|
+
│ Phase 6: UI │
|
|
44
|
+
│ add-page (pricing, billing dashboard) │
|
|
45
|
+
│ ↳ If pricing page needed: build-landing-page │
|
|
46
|
+
├─────────────────────────────────────────────────────┤
|
|
47
|
+
│ Phase 7: Testing & Docs │
|
|
48
|
+
│ test-api-route → update-architecture-docs │
|
|
49
|
+
└─────────────────────────────────────────────────────┘
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Phase 0: Create Execution Plan
|
|
53
|
+
|
|
54
|
+
**Skill:** `create-execution-plan`
|
|
55
|
+
|
|
56
|
+
Create `docs/exec-plans/active/setup-billing.md` with all tasks listed below.
|
|
57
|
+
|
|
58
|
+
## Phase 1: Payment Provider Setup
|
|
59
|
+
|
|
60
|
+
**Skill:** `configure-payments`
|
|
61
|
+
|
|
62
|
+
1. Install Stripe SDK: `yarn add stripe`
|
|
63
|
+
2. Set environment variables:
|
|
64
|
+
- `STRIPE_SECRET_KEY`
|
|
65
|
+
- `STRIPE_WEBHOOK_SECRET`
|
|
66
|
+
- `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY`
|
|
67
|
+
3. Create the lazy Stripe client at `src/features/billing/server/stripe.ts`
|
|
68
|
+
4. Enable the billing feature flag:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// src/config/app.config.ts
|
|
72
|
+
features: {
|
|
73
|
+
billing: true,
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Decision: Payment model?**
|
|
78
|
+
|
|
79
|
+
| Model | Use when | Stripe mode |
|
|
80
|
+
|-------|----------|-------------|
|
|
81
|
+
| Subscription | Monthly/annual recurring billing | `mode: 'subscription'` |
|
|
82
|
+
| One-time | Single purchases, credits | `mode: 'payment'` |
|
|
83
|
+
| Usage-based | Pay per API call, storage, etc. | `mode: 'subscription'` + metered billing |
|
|
84
|
+
|
|
85
|
+
## Phase 2: Data Layer
|
|
86
|
+
|
|
87
|
+
**Skill:** `add-prisma-model`
|
|
88
|
+
|
|
89
|
+
### Subscription model
|
|
90
|
+
|
|
91
|
+
```prisma
|
|
92
|
+
// prisma/schema/billing.prisma
|
|
93
|
+
model Subscription {
|
|
94
|
+
id String @id @default(cuid())
|
|
95
|
+
userId String @unique
|
|
96
|
+
stripeCustomerId String @unique
|
|
97
|
+
stripeSubscriptionId String? @unique
|
|
98
|
+
stripePriceId String?
|
|
99
|
+
status String @default("inactive")
|
|
100
|
+
currentPeriodEnd DateTime?
|
|
101
|
+
cancelAtPeriodEnd Boolean @default(false)
|
|
102
|
+
createdAt DateTime @default(now())
|
|
103
|
+
updatedAt DateTime @updatedAt
|
|
104
|
+
|
|
105
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
106
|
+
|
|
107
|
+
@@index([stripeCustomerId])
|
|
108
|
+
@@index([status])
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Webhook event model (for idempotency)
|
|
113
|
+
|
|
114
|
+
```prisma
|
|
115
|
+
model WebhookEvent {
|
|
116
|
+
id String @id @default(cuid())
|
|
117
|
+
externalId String @unique
|
|
118
|
+
type String
|
|
119
|
+
processedAt DateTime @default(now())
|
|
120
|
+
|
|
121
|
+
@@index([externalId])
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Run `yarn db:push`.
|
|
126
|
+
|
|
127
|
+
## Phase 3: Webhook Handler
|
|
128
|
+
|
|
129
|
+
**Skill:** `add-webhook`
|
|
130
|
+
|
|
131
|
+
Create `src/app/api/webhooks/stripe/route.ts`:
|
|
132
|
+
|
|
133
|
+
1. Verify Stripe signature using `stripe.webhooks.constructEvent`
|
|
134
|
+
2. Check idempotency (has this event been processed?)
|
|
135
|
+
3. Handle key events:
|
|
136
|
+
|
|
137
|
+
| Event | Handler | Action |
|
|
138
|
+
|-------|---------|--------|
|
|
139
|
+
| `checkout.session.completed` | `handleCheckoutCompleted` | Create/update subscription, set status to active |
|
|
140
|
+
| `customer.subscription.updated` | `handleSubscriptionUpdated` | Sync status, price, period end, cancel flag |
|
|
141
|
+
| `customer.subscription.deleted` | `handleSubscriptionDeleted` | Set status to canceled |
|
|
142
|
+
| `invoice.payment_failed` | `handlePaymentFailed` | Notify user, set status to past_due |
|
|
143
|
+
|
|
144
|
+
4. Exclude webhook route from CSRF in middleware
|
|
145
|
+
5. Return 200 quickly — do heavy processing inline (Vercel has 10s limit)
|
|
146
|
+
|
|
147
|
+
**If payment model is one-time instead of subscription:**
|
|
148
|
+
|
|
149
|
+
Handle `checkout.session.completed` to record the purchase. Skip subscription update/delete events.
|
|
150
|
+
|
|
151
|
+
**If payment model is usage-based:**
|
|
152
|
+
|
|
153
|
+
Additionally handle `invoice.created` to add usage line items before the invoice is finalized.
|
|
154
|
+
|
|
155
|
+
## Phase 4: Feature Module
|
|
156
|
+
|
|
157
|
+
**Skill:** `add-feature`
|
|
158
|
+
|
|
159
|
+
Create `src/features/billing/`:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
src/features/billing/
|
|
163
|
+
├── components/
|
|
164
|
+
│ ├── PricingCard.tsx # Individual plan card
|
|
165
|
+
│ ├── PricingTable.tsx # All plans side by side
|
|
166
|
+
│ ├── SubscriptionStatus.tsx # Current plan display
|
|
167
|
+
│ └── index.ts
|
|
168
|
+
├── server/
|
|
169
|
+
│ ├── stripe.ts # Lazy Stripe client (from Phase 1)
|
|
170
|
+
│ ├── index.ts # Subscription queries + helpers
|
|
171
|
+
│ └── checkout.ts # Checkout session creation
|
|
172
|
+
├── validation/
|
|
173
|
+
│ └── schemas.ts # Zod schemas for billing inputs
|
|
174
|
+
└── types.ts # Billing TypeScript types
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Key server functions:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
// src/features/billing/server/index.ts
|
|
181
|
+
export { getStripe } from './stripe';
|
|
182
|
+
|
|
183
|
+
export async function getUserSubscription(userId: string) { ... }
|
|
184
|
+
export async function isSubscribed(userId: string): Promise<boolean> { ... }
|
|
185
|
+
export async function getSubscriptionStatus(userId: string) { ... }
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Phase 5: API Routes
|
|
189
|
+
|
|
190
|
+
**Skill:** `add-api-route`
|
|
191
|
+
|
|
192
|
+
| Route | Method | Auth | Purpose |
|
|
193
|
+
|-------|--------|------|---------|
|
|
194
|
+
| `/api/protected/billing/checkout` | POST | `withAuthNoParams` | Create Stripe Checkout session |
|
|
195
|
+
| `/api/protected/billing/portal` | POST | `withAuthNoParams` | Create Stripe Customer Portal session |
|
|
196
|
+
| `/api/protected/billing/subscription` | GET | `withAuthNoParams` | Get current subscription status |
|
|
197
|
+
|
|
198
|
+
### Checkout route
|
|
199
|
+
|
|
200
|
+
Accepts `{ priceId: string }`. Creates or retrieves Stripe customer, then creates a Checkout Session with success/cancel URLs.
|
|
201
|
+
|
|
202
|
+
### Portal route
|
|
203
|
+
|
|
204
|
+
Creates a Billing Portal session for subscription management. Requires an existing Stripe customer.
|
|
205
|
+
|
|
206
|
+
### Subscription route
|
|
207
|
+
|
|
208
|
+
Returns the current subscription status, plan, and renewal date for display in the UI.
|
|
209
|
+
|
|
210
|
+
## Phase 6: UI
|
|
211
|
+
|
|
212
|
+
**Skill:** `add-page`
|
|
213
|
+
|
|
214
|
+
### Pricing page (public)
|
|
215
|
+
|
|
216
|
+
Create `src/app/(public)/pricing/page.tsx` or include in the landing page:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
// Uses PricingTable component
|
|
220
|
+
// Shows plans with features and CTAs
|
|
221
|
+
// Unauthenticated users → redirect to signup with plan param
|
|
222
|
+
// Authenticated users → redirect to checkout
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Billing dashboard (protected)
|
|
226
|
+
|
|
227
|
+
Create `src/app/(protected)/billing/page.tsx`:
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
// Shows current subscription status
|
|
231
|
+
// "Manage Subscription" → opens Stripe Customer Portal
|
|
232
|
+
// "Upgrade" → creates checkout session
|
|
233
|
+
// Shows billing history (if available)
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Conditional: If a pricing page is needed on the landing page:**
|
|
237
|
+
|
|
238
|
+
**Skill:** `build-landing-page` — Include a pricing section with plan comparison.
|
|
239
|
+
|
|
240
|
+
### Subscription gate component
|
|
241
|
+
|
|
242
|
+
```tsx
|
|
243
|
+
// src/features/billing/components/SubscriptionGate.tsx
|
|
244
|
+
'use client';
|
|
245
|
+
|
|
246
|
+
export function SubscriptionGate({
|
|
247
|
+
children,
|
|
248
|
+
fallback,
|
|
249
|
+
}: {
|
|
250
|
+
children: React.ReactNode;
|
|
251
|
+
fallback?: React.ReactNode;
|
|
252
|
+
}) {
|
|
253
|
+
// Check subscription status
|
|
254
|
+
// If subscribed, render children
|
|
255
|
+
// If not, render fallback (upgrade prompt)
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Phase 7: Testing & Documentation
|
|
260
|
+
|
|
261
|
+
**Skill:** `test-api-route` + `update-architecture-docs`
|
|
262
|
+
|
|
263
|
+
### Tests
|
|
264
|
+
|
|
265
|
+
1. Unit test checkout route (mock Stripe, verify session creation)
|
|
266
|
+
2. Unit test portal route (mock Stripe, verify portal session)
|
|
267
|
+
3. Unit test webhook handler (mock signature verification, verify DB updates)
|
|
268
|
+
4. Unit test `isSubscribed` helper
|
|
269
|
+
5. E2E test: full checkout flow (requires Stripe test mode)
|
|
270
|
+
|
|
271
|
+
### Webhook local testing
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
stripe listen --forward-to localhost:3000/api/webhooks/stripe
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Documentation updates
|
|
278
|
+
|
|
279
|
+
1. Update `docs/QUALITY_SCORE.md`:
|
|
280
|
+
|
|
281
|
+
```markdown
|
|
282
|
+
| Billing | B | Stripe checkout, portal, webhook, subscription gate |
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
2. Update `ARCHITECTURE.md` if this is the first payment integration
|
|
286
|
+
3. Mark execution plan as complete
|
|
287
|
+
|
|
288
|
+
## Environment Variables Summary
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
# Required
|
|
292
|
+
STRIPE_SECRET_KEY=sk_test_...
|
|
293
|
+
STRIPE_WEBHOOK_SECRET=whsec_...
|
|
294
|
+
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_...
|
|
295
|
+
|
|
296
|
+
# Optional
|
|
297
|
+
STRIPE_PRICE_ID_MONTHLY=price_...
|
|
298
|
+
STRIPE_PRICE_ID_YEARLY=price_...
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Checklist
|
|
302
|
+
|
|
303
|
+
- [ ] Execution plan created
|
|
304
|
+
- [ ] Stripe SDK installed
|
|
305
|
+
- [ ] Environment variables set
|
|
306
|
+
- [ ] Lazy Stripe client created
|
|
307
|
+
- [ ] Subscription + WebhookEvent Prisma models
|
|
308
|
+
- [ ] Webhook handler with signature verification and idempotency
|
|
309
|
+
- [ ] Webhook excluded from CSRF in middleware
|
|
310
|
+
- [ ] Billing feature module with server/components/validation
|
|
311
|
+
- [ ] Checkout API route
|
|
312
|
+
- [ ] Portal API route
|
|
313
|
+
- [ ] Subscription status API route
|
|
314
|
+
- [ ] Pricing page (public or landing page section)
|
|
315
|
+
- [ ] Billing dashboard page (protected)
|
|
316
|
+
- [ ] SubscriptionGate component for premium features
|
|
317
|
+
- [ ] `isSubscribed` helper for server-side gating
|
|
318
|
+
- [ ] Feature flag `appConfig.features.billing` set to `true`
|
|
319
|
+
- [ ] Unit tests for API routes and webhook
|
|
320
|
+
- [ ] Local webhook testing verified
|
|
321
|
+
- [ ] QUALITY_SCORE.md updated
|
|
322
|
+
- [ ] Execution plan completed
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Skill: Set Up a MARS Project
|
|
2
|
+
|
|
3
|
+
Guide for setting up a new MARS project from scratch or getting an existing one running locally.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
Use this skill when the user asks how to get started, set up their project, run locally, or troubleshoot a fresh install.
|
|
8
|
+
|
|
9
|
+
## New Project (via CLI)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @mars-stack/cli create
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The CLI will prompt for project name, features, services, and theme, then scaffold a ready-to-run project.
|
|
16
|
+
|
|
17
|
+
## Existing Project Setup
|
|
18
|
+
|
|
19
|
+
### 1. Install Dependencies
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
yarn install
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### 2. Environment Variables
|
|
26
|
+
|
|
27
|
+
Copy the example and fill in required values:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cp .env.example .env
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Required variables:**
|
|
34
|
+
- `DATABASE_URL` -- Postgres connection string
|
|
35
|
+
- `JWT_SECRET` -- Minimum 32 characters (generate with `openssl rand -hex 32`)
|
|
36
|
+
|
|
37
|
+
**Optional (based on features):**
|
|
38
|
+
- `SENDGRID_API_KEY` / `SENDGRID_FROM_EMAIL` -- if email provider is SendGrid
|
|
39
|
+
- `STRIPE_SECRET_KEY` / `STRIPE_WEBHOOK_SECRET` -- if payments enabled
|
|
40
|
+
- `UPSTASH_REDIS_REST_URL` / `UPSTASH_REDIS_REST_TOKEN` -- for production rate limiting
|
|
41
|
+
|
|
42
|
+
### 3. Database
|
|
43
|
+
|
|
44
|
+
**Option A: Local Docker (recommended for development)**
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
yarn dev:services # Starts Postgres + Redis via Docker Compose
|
|
48
|
+
yarn db:push # Push schema to database
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Option B: Cloud database (Neon, Supabase, etc.)**
|
|
52
|
+
|
|
53
|
+
Set `DATABASE_URL` in `.env` to your cloud connection string. For Neon:
|
|
54
|
+
```
|
|
55
|
+
DATABASE_URL="postgresql://user:pass@ep-xxx.region.aws.neon.tech/dbname?sslmode=require"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then: `yarn db:push`
|
|
59
|
+
|
|
60
|
+
### 4. Start Development
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
yarn dev # Starts Next.js with Turbopack
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
The app will be available at `http://localhost:3000`.
|
|
67
|
+
|
|
68
|
+
## Health Check
|
|
69
|
+
|
|
70
|
+
Run the built-in doctor to verify your setup:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
npx @mars-stack/cli doctor
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This checks: Node.js version, Yarn, Docker, Git, `.env` file, Prisma schema, database connection, and JWT_SECRET.
|
|
77
|
+
|
|
78
|
+
## Common Issues
|
|
79
|
+
|
|
80
|
+
### "Cannot connect to the database"
|
|
81
|
+
- Is Docker running? `docker ps`
|
|
82
|
+
- Start services: `yarn dev:services`
|
|
83
|
+
- Check `DATABASE_URL` in `.env`
|
|
84
|
+
|
|
85
|
+
### "Database tables are missing"
|
|
86
|
+
- Run `yarn db:push` to create tables from the Prisma schema
|
|
87
|
+
|
|
88
|
+
### "JWT_SECRET is required"
|
|
89
|
+
- Generate one: `openssl rand -hex 32`
|
|
90
|
+
- Add to `.env`: `JWT_SECRET="<generated-value>"`
|
|
91
|
+
|
|
92
|
+
### Build fails with environment errors
|
|
93
|
+
- The env validation is lazy and skips during build. Ensure `.env` exists for runtime.
|
|
94
|
+
- `APP_URL` is optional (falls back to `localhost:3000` in dev).
|
|
95
|
+
|
|
96
|
+
## Key Files
|
|
97
|
+
|
|
98
|
+
| File | Purpose |
|
|
99
|
+
|------|---------|
|
|
100
|
+
| `src/config/app.config.ts` | App identity, features, services, theme |
|
|
101
|
+
| `.env` | Environment secrets (never commit) |
|
|
102
|
+
| `docker-compose.yml` | Local Postgres + Redis |
|
|
103
|
+
| `prisma/schema/` | Database models (multi-file) |
|
|
104
|
+
| `src/middleware.ts` | Auth redirects, CSRF, coming-soon mode |
|