@mars-stack/cli 8.0.4 → 8.0.5
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/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/template/package.json +6 -6
- package/template/playwright.config.ts +7 -10
- package/template/scripts/seed.ts +1 -1
- package/template/src/app/(protected)/settings/page.tsx +16 -3
- package/template/src/app/api/csrf/route.test.ts +4 -1
- package/template/src/app/api/csrf/route.ts +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mars-stack/cli",
|
|
3
|
-
"version": "8.0.
|
|
3
|
+
"version": "8.0.5",
|
|
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.
|
|
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",
|
package/template/package.json
CHANGED
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
"@mars-stack/ui": "*",
|
|
33
33
|
"@prisma/adapter-pg": "^7.6.0",
|
|
34
34
|
"@prisma/client": "^7.6.0",
|
|
35
|
-
"dotenv": "^17.
|
|
36
|
-
"@react-email/components": "^1.0.
|
|
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.
|
|
43
|
+
"next": "^16.2.2",
|
|
44
44
|
"pino": "^9.6.0",
|
|
45
45
|
"pino-pretty": "^13.0.0",
|
|
46
46
|
"react": "^19.0.0",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
58
|
"@eslint/eslintrc": "^3.3.5",
|
|
59
|
-
"@playwright/test": "^1.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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
|
? {
|
package/template/scripts/seed.ts
CHANGED
|
@@ -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
|
|
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={
|
|
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
|
|
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);
|