@mars-stack/cli 6.0.2 → 6.0.4
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/package.json +3 -3
- package/template/.cursor/rules/mars-testing.mdc +6 -0
- package/template/.cursor/skills/mars-configure-payments/SKILL.md +3 -1
- package/template/.mars/sync-manifest.yaml +3 -0
- package/template/e2e/README.md +41 -0
- package/template/package.json +12 -12
- package/template/src/app/api/webhooks/stripe/route.ts +20 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mars-stack/cli",
|
|
3
|
-
"version": "6.0.
|
|
3
|
+
"version": "6.0.4",
|
|
4
4
|
"description": "MARS CLI: scaffold, configure, and maintain SaaS apps",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -48,10 +48,10 @@
|
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/fs-extra": "^11.0.0",
|
|
50
50
|
"@types/js-yaml": "^4.0.9",
|
|
51
|
-
"@types/node": "^25.
|
|
51
|
+
"@types/node": "^25.5.0",
|
|
52
52
|
"@types/prompts": "^2.4.0",
|
|
53
53
|
"tsup": "^8.0.0",
|
|
54
54
|
"typescript": "^5.7.0",
|
|
55
|
-
"vitest": "^4.1.
|
|
55
|
+
"vitest": "^4.1.1"
|
|
56
56
|
}
|
|
57
57
|
}
|
|
@@ -22,3 +22,9 @@ alwaysApply: false
|
|
|
22
22
|
- Assert both the response body and status code.
|
|
23
23
|
- Test error paths: invalid input (Zod), unauthorized, not found, database errors.
|
|
24
24
|
- Unit tests MUST NOT depend on external services (database, Redis, email). Mock everything.
|
|
25
|
+
|
|
26
|
+
## E2E (Playwright) — extending features
|
|
27
|
+
|
|
28
|
+
End-to-end specs live in **`e2e/`** (`yarn test:e2e`). When you add or materially change **user-facing** behaviour (pages, flows, CLI-generated features), extend **`e2e/*.spec.ts`** so at least one **stable happy path** is covered. Prefer role/label selectors; avoid sleep-only assertions.
|
|
29
|
+
|
|
30
|
+
**Mars monorepo:** The template’s catalog and CLI-wide CI expectations are defined in **`docs/design-docs/scaffold-testing-strategy.md`** (Decision 7) and ticket **MARS-041**; update **`template/e2e/`** and its README when you change generators or template routes.
|
|
@@ -184,7 +184,9 @@ switch (event.type) {
|
|
|
184
184
|
data: {
|
|
185
185
|
status: sub.status,
|
|
186
186
|
stripePriceId: sub.items.data[0]?.price.id,
|
|
187
|
-
currentPeriodEnd: new Date(
|
|
187
|
+
currentPeriodEnd: new Date(
|
|
188
|
+
(sub.items.data[0]?.current_period_end ?? sub.billing_cycle_anchor) * 1000,
|
|
189
|
+
),
|
|
188
190
|
cancelAtPeriodEnd: sub.cancel_at_period_end,
|
|
189
191
|
},
|
|
190
192
|
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Playwright E2E — feature coverage map
|
|
2
|
+
|
|
3
|
+
Browser-level tests for user-visible behaviour. Run: `yarn test:e2e` from the project root (`playwright.config.ts`).
|
|
4
|
+
|
|
5
|
+
## When to update
|
|
6
|
+
|
|
7
|
+
Whenever you add or change a feature that ships **UI or routes** (including features originally added via the Mars CLI’s `generate` commands), add or extend a spec here and update the tables below.
|
|
8
|
+
|
|
9
|
+
**Mars monorepo contributors:** Follow **Decision 7** in `docs/design-docs/scaffold-testing-strategy.md` and ticket **MARS-041** (CLI-wide E2E coverage + CI). Keep this inventory in sync with `packages/cli/src/index.ts` `generate` subcommands.
|
|
10
|
+
|
|
11
|
+
Use **local / console / `none`** providers so tests do not require paid third-party accounts.
|
|
12
|
+
|
|
13
|
+
## `mars generate` subcommands (catalog)
|
|
14
|
+
|
|
15
|
+
| CLI command | Spec file(s) | Status |
|
|
16
|
+
|-------------|--------------|--------|
|
|
17
|
+
| _(baseline)_ | `auth.spec.ts`, `public.spec.ts`, `api.spec.ts` | Partial (auth, public, API smoke) |
|
|
18
|
+
| `blog` | — | Missing |
|
|
19
|
+
| `dark-mode` | — | Missing |
|
|
20
|
+
| `notifications` | — | Missing |
|
|
21
|
+
| `analytics` | — | Missing |
|
|
22
|
+
| `command-palette` | — | Missing |
|
|
23
|
+
| `onboarding` | — | Missing |
|
|
24
|
+
| `search` | — | Missing |
|
|
25
|
+
| `realtime` | — | Missing |
|
|
26
|
+
| `ai` | — | Missing |
|
|
27
|
+
| `cookie-consent` | — | Missing |
|
|
28
|
+
| `coming-soon` | — | Missing |
|
|
29
|
+
| `sentry` | — | Missing |
|
|
30
|
+
| `feature-flags` | — | Missing |
|
|
31
|
+
|
|
32
|
+
## Scaffold-time features (`mars create`, user-visible)
|
|
33
|
+
|
|
34
|
+
| Area | Spec file(s) | Status |
|
|
35
|
+
|------|--------------|--------|
|
|
36
|
+
| Billing | — | Missing |
|
|
37
|
+
| File upload | — | Missing |
|
|
38
|
+
|
|
39
|
+
## Waivers
|
|
40
|
+
|
|
41
|
+
Document here if a feature intentionally has **no** E2E (e.g. server-only with no stable UI) and why.
|
package/template/package.json
CHANGED
|
@@ -32,22 +32,22 @@
|
|
|
32
32
|
"@mars-stack/ui": "*",
|
|
33
33
|
"@prisma/adapter-pg": "^7.5.0",
|
|
34
34
|
"@prisma/client": "^7.5.0",
|
|
35
|
-
"dotenv": "^
|
|
36
|
-
"@react-email/components": "^1.0.
|
|
35
|
+
"dotenv": "^17.3.1",
|
|
36
|
+
"@react-email/components": "^1.0.10",
|
|
37
37
|
"@sendgrid/mail": "^8.1.0",
|
|
38
38
|
"@upstash/ratelimit": "^2.0.0",
|
|
39
|
-
"@upstash/redis": "^1.
|
|
39
|
+
"@upstash/redis": "^1.37.0",
|
|
40
40
|
"bcryptjs": "^3.0.3",
|
|
41
41
|
"clsx": "^2.1.1",
|
|
42
|
-
"jose": "^6.2.
|
|
43
|
-
"next": "^16.1
|
|
42
|
+
"jose": "^6.2.2",
|
|
43
|
+
"next": "^16.2.1",
|
|
44
44
|
"pino": "^9.6.0",
|
|
45
45
|
"pino-pretty": "^13.0.0",
|
|
46
46
|
"react": "^19.0.0",
|
|
47
47
|
"react-dom": "^19.0.0",
|
|
48
|
-
"react-email": "^5.2.
|
|
48
|
+
"react-email": "^5.2.10",
|
|
49
49
|
"server-only": "^0.0.1",
|
|
50
|
-
"stripe": "^
|
|
50
|
+
"stripe": "^20.4.1",
|
|
51
51
|
"zod": "^4.3.6"
|
|
52
52
|
},
|
|
53
53
|
"prisma": {
|
|
@@ -57,17 +57,17 @@
|
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@eslint/eslintrc": "^3.3.5",
|
|
59
59
|
"@playwright/test": "^1.50.0",
|
|
60
|
-
"@tailwindcss/postcss": "^4.
|
|
60
|
+
"@tailwindcss/postcss": "^4.2.2",
|
|
61
61
|
"@testing-library/jest-dom": "^6.9.1",
|
|
62
62
|
"@testing-library/react": "^16.0.0",
|
|
63
63
|
"@types/bcryptjs": "^3.0.0",
|
|
64
|
-
"@types/node": "^25.
|
|
64
|
+
"@types/node": "^25.5.0",
|
|
65
65
|
"@types/react": "^19.0.0",
|
|
66
66
|
"@types/react-dom": "^19.0.0",
|
|
67
67
|
"@vitejs/plugin-react": "^5.1.4",
|
|
68
68
|
"eslint": "^9.0.0",
|
|
69
|
-
"eslint-config-next": "^16.
|
|
70
|
-
"jsdom": "^
|
|
69
|
+
"eslint-config-next": "^16.2.1",
|
|
70
|
+
"jsdom": "^29.0.1",
|
|
71
71
|
"postcss": "^8.5.0",
|
|
72
72
|
"prettier": "^3.5.0",
|
|
73
73
|
"prettier-plugin-tailwindcss": "^0.7.2",
|
|
@@ -75,6 +75,6 @@
|
|
|
75
75
|
"tailwindcss": "^4.0.0",
|
|
76
76
|
"tsx": "^4.0.0",
|
|
77
77
|
"typescript": "^5.7.0",
|
|
78
|
-
"vitest": "^4.1.
|
|
78
|
+
"vitest": "^4.1.1"
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -2,6 +2,7 @@ import { createPaymentService } from '@mars-stack/core/payments';
|
|
|
2
2
|
import { appConfig } from '@/config/app.config';
|
|
3
3
|
import { prisma } from '@/lib/prisma';
|
|
4
4
|
import { NextResponse } from 'next/server';
|
|
5
|
+
import type Stripe from 'stripe';
|
|
5
6
|
import type {
|
|
6
7
|
UpsertSubscriptionData,
|
|
7
8
|
UpdateSubscriptionStatusData,
|
|
@@ -33,6 +34,21 @@ interface StripeEvent {
|
|
|
33
34
|
};
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
/** Stripe's TS types model period bounds on subscription items; API responses may still include a root `current_period_end`. */
|
|
38
|
+
function unixSecondsForSubscriptionPeriodEnd(subscription: Stripe.Subscription): number {
|
|
39
|
+
const withLegacyRoot = subscription as Stripe.Subscription & {
|
|
40
|
+
current_period_end?: number;
|
|
41
|
+
};
|
|
42
|
+
if (typeof withLegacyRoot.current_period_end === 'number') {
|
|
43
|
+
return withLegacyRoot.current_period_end;
|
|
44
|
+
}
|
|
45
|
+
const fromFirstItem = subscription.items.data[0]?.current_period_end;
|
|
46
|
+
if (typeof fromFirstItem === 'number') {
|
|
47
|
+
return fromFirstItem;
|
|
48
|
+
}
|
|
49
|
+
return subscription.billing_cycle_anchor;
|
|
50
|
+
}
|
|
51
|
+
|
|
36
52
|
async function handleCheckoutCompleted(session: StripeCheckoutSession): Promise<void> {
|
|
37
53
|
const userId = session.metadata?.userId;
|
|
38
54
|
if (!userId) {
|
|
@@ -47,13 +63,15 @@ async function handleCheckoutCompleted(session: StripeCheckoutSession): Promise<
|
|
|
47
63
|
const stripe = new Stripe(secretKey);
|
|
48
64
|
const subscription = await stripe.subscriptions.retrieve(session.subscription);
|
|
49
65
|
|
|
66
|
+
const periodEndSeconds = unixSecondsForSubscriptionPeriodEnd(subscription);
|
|
67
|
+
|
|
50
68
|
const data: UpsertSubscriptionData = {
|
|
51
69
|
userId,
|
|
52
70
|
stripeCustomerId: session.customer,
|
|
53
71
|
stripeSubscriptionId: subscription.id,
|
|
54
72
|
stripePriceId: subscription.items.data[0]?.price.id,
|
|
55
73
|
status: subscription.status,
|
|
56
|
-
currentPeriodEnd: new Date(
|
|
74
|
+
currentPeriodEnd: new Date(periodEndSeconds * 1000),
|
|
57
75
|
cancelAtPeriodEnd: subscription.cancel_at_period_end,
|
|
58
76
|
};
|
|
59
77
|
|
|
@@ -158,9 +176,6 @@ export async function POST(request: Request): Promise<NextResponse> {
|
|
|
158
176
|
return NextResponse.json({ received: true });
|
|
159
177
|
} catch (error) {
|
|
160
178
|
console.error('[Stripe Webhook] Error processing event:', error);
|
|
161
|
-
return NextResponse.json(
|
|
162
|
-
{ error: 'Webhook processing failed' },
|
|
163
|
-
{ status: 400 },
|
|
164
|
-
);
|
|
179
|
+
return NextResponse.json({ error: 'Webhook processing failed' }, { status: 400 });
|
|
165
180
|
}
|
|
166
181
|
}
|