@mars-stack/cli 8.0.4 → 8.0.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mars-stack/cli",
3
- "version": "8.0.4",
3
+ "version": "8.0.7",
4
4
  "description": "MARS CLI: scaffold, configure, and maintain SaaS apps",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -48,7 +48,7 @@
48
48
  "devDependencies": {
49
49
  "@types/fs-extra": "^11.0.0",
50
50
  "@types/js-yaml": "^4.0.9",
51
- "@types/node": "^25.5.0",
51
+ "@types/node": "^25.5.2",
52
52
  "@types/prompts": "^2.4.0",
53
53
  "tsup": "^8.0.0",
54
54
  "typescript": "^6.0.2",
@@ -32,22 +32,22 @@
32
32
  "@mars-stack/ui": "*",
33
33
  "@prisma/adapter-pg": "^7.6.0",
34
34
  "@prisma/client": "^7.6.0",
35
- "dotenv": "^17.3.1",
36
- "@react-email/components": "^1.0.10",
35
+ "dotenv": "^17.4.1",
36
+ "@react-email/components": "^1.0.11",
37
37
  "@sendgrid/mail": "^8.1.0",
38
38
  "@upstash/ratelimit": "^2.0.0",
39
39
  "@upstash/redis": "^1.37.0",
40
40
  "bcryptjs": "^3.0.3",
41
41
  "clsx": "^2.1.1",
42
42
  "jose": "^6.2.2",
43
- "next": "^16.2.1",
43
+ "next": "^16.2.2",
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
48
  "react-email": "^5.2.10",
49
49
  "server-only": "^0.0.1",
50
- "stripe": "^21.0.1",
50
+ "stripe": "^22.0.1",
51
51
  "zod": "^4.3.6"
52
52
  },
53
53
  "prisma": {
@@ -56,17 +56,17 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@eslint/eslintrc": "^3.3.5",
59
- "@playwright/test": "^1.50.0",
59
+ "@playwright/test": "^1.59.1",
60
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.5.0",
64
+ "@types/node": "^25.5.2",
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.2.1",
69
+ "eslint-config-next": "^16.2.2",
70
70
  "jsdom": "^29.0.1",
71
71
  "postcss": "^8.5.0",
72
72
  "prettier": "^3.5.0",
@@ -23,16 +23,13 @@ export default defineConfig({
23
23
  trace: 'on-first-retry',
24
24
  screenshot: 'only-on-failure',
25
25
  },
26
- projects: [
27
- {
28
- name: 'chromium',
29
- use: { ...devices['Desktop Chrome'] },
30
- },
31
- {
32
- name: 'mobile',
33
- use: { ...devices['iPhone 14'] },
34
- },
35
- ],
26
+ // CI (`template-e2e`) installs Chromium only — match that here so dogfood/automation does not fail on WebKit.
27
+ projects: process.env.CI
28
+ ? [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }]
29
+ : [
30
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
31
+ { name: 'mobile', use: { ...devices['iPhone 14'] } },
32
+ ],
36
33
  webServer:
37
34
  process.env.E2E_USE_CI_SERVER === '1'
38
35
  ? {
@@ -35,8 +35,9 @@ function parseUserAgent(ua: string | null): string {
35
35
  }
36
36
 
37
37
  export default function Settings() {
38
- const { getCSRFHeaders } = useCSRFContext();
38
+ const { getCSRFHeaders, csrfToken, isLoading: csrfLoading } = useCSRFContext();
39
39
  const { user, isLoading: authLoading, updateUser } = useAuth();
40
+ const csrfReady = !csrfLoading && csrfToken !== null;
40
41
 
41
42
  const [name, setName] = useState('');
42
43
  const [nameInitialized, setNameInitialized] = useState(false);
@@ -98,6 +99,7 @@ export default function Settings() {
98
99
 
99
100
  async function handleNameSubmit(event: FormEvent<HTMLFormElement>) {
100
101
  event.preventDefault();
102
+ if (!csrfReady) return;
101
103
  setNameStatus(null);
102
104
  setNameSaving(true);
103
105
 
@@ -126,6 +128,7 @@ export default function Settings() {
126
128
  }
127
129
 
128
130
  async function handleRevokeSession(sessionId: string) {
131
+ if (!csrfReady) return;
129
132
  setRevokingId(sessionId);
130
133
  setSessionsStatus(null);
131
134
  try {
@@ -149,6 +152,7 @@ export default function Settings() {
149
152
  }
150
153
 
151
154
  async function handleRevokeAllSessions() {
155
+ if (!csrfReady) return;
152
156
  setRevokingAll(true);
153
157
  setSessionsStatus(null);
154
158
  try {
@@ -173,6 +177,7 @@ export default function Settings() {
173
177
 
174
178
  async function handlePasswordSubmit(event: FormEvent<HTMLFormElement>) {
175
179
  event.preventDefault();
180
+ if (!csrfReady) return;
176
181
  setPasswordStatus(null);
177
182
 
178
183
  if (newPassword !== confirmPassword) {
@@ -238,7 +243,11 @@ export default function Settings() {
238
243
  </p>
239
244
  )}
240
245
 
241
- <Button type="submit" loading={nameSaving} disabled={!name.trim()}>
246
+ <Button
247
+ type="submit"
248
+ loading={nameSaving}
249
+ disabled={!name.trim() || !csrfReady}
250
+ >
242
251
  Save Name
243
252
  </Button>
244
253
  </form>
@@ -301,7 +310,9 @@ export default function Settings() {
301
310
  <Button
302
311
  type="submit"
303
312
  loading={passwordSaving}
304
- disabled={!currentPassword || !newPassword || !confirmPassword}
313
+ disabled={
314
+ !currentPassword || !newPassword || !confirmPassword || !csrfReady
315
+ }
305
316
  >
306
317
  Change Password
307
318
  </Button>
@@ -316,6 +327,7 @@ export default function Settings() {
316
327
  variant="subtle"
317
328
  size="sm"
318
329
  loading={revokingAll}
330
+ disabled={!csrfReady}
319
331
  onClick={handleRevokeAllSessions}
320
332
  >
321
333
  Revoke All
@@ -361,6 +373,7 @@ export default function Settings() {
361
373
  variant="subtle"
362
374
  size="sm"
363
375
  loading={revokingId === session.id}
376
+ disabled={!csrfReady}
364
377
  onClick={() => handleRevokeSession(session.id)}
365
378
  >
366
379
  Revoke
@@ -1,11 +1,13 @@
1
1
  // @vitest-environment node
2
2
  import { describe, it, expect, vi, beforeEach } from 'vitest';
3
3
 
4
- const { mockRequireCSRFToken } = vi.hoisted(() => ({
4
+ const { mockRequireCSRFToken, mockGetSession } = vi.hoisted(() => ({
5
5
  mockRequireCSRFToken: vi.fn(),
6
+ mockGetSession: vi.fn(),
6
7
  }));
7
8
 
8
9
  vi.mock('@/lib/mars', () => ({
10
+ getSession: mockGetSession,
9
11
  requireCSRFToken: mockRequireCSRFToken,
10
12
  }));
11
13
 
@@ -14,6 +16,7 @@ import { GET } from './route';
14
16
  describe('GET /api/csrf', () => {
15
17
  beforeEach(() => {
16
18
  vi.clearAllMocks();
19
+ mockGetSession.mockResolvedValueOnce(null);
17
20
  });
18
21
 
19
22
  it('returns token and csrfToken with the same value', async () => {
@@ -1,9 +1,10 @@
1
- import { requireCSRFToken } from '@/lib/mars';
1
+ import { getSession, requireCSRFToken } from '@/lib/mars';
2
2
  import { NextResponse } from 'next/server';
3
3
 
4
4
  export async function GET() {
5
5
  try {
6
- const token = await requireCSRFToken();
6
+ const session = await getSession();
7
+ const token = await requireCSRFToken(session?.fingerprint);
7
8
  return NextResponse.json({ token, csrfToken: token }, { status: 200 });
8
9
  } catch (error) {
9
10
  console.error('CSRF token generation error:', error);