@mars-stack/cli 2.0.0 → 2.0.1

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": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "MARS CLI: scaffold, configure, and maintain SaaS apps",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,7 +40,7 @@
40
40
  "@mars-stack/ui": "*",
41
41
  "commander": "^14.0.3",
42
42
  "fs-extra": "^11.0.0",
43
- "ora": "^8.0.0",
43
+ "ora": "^9.3.0",
44
44
  "picocolors": "^1.0.0",
45
45
  "prompts": "^2.4.0"
46
46
  },
@@ -50,6 +50,6 @@
50
50
  "@types/prompts": "^2.4.0",
51
51
  "tsup": "^8.0.0",
52
52
  "typescript": "^5.7.0",
53
- "vitest": "^4.0.18"
53
+ "vitest": "^4.1.0"
54
54
  }
55
55
  }
@@ -37,10 +37,10 @@
37
37
  "@sendgrid/mail": "^8.1.0",
38
38
  "@upstash/ratelimit": "^2.0.0",
39
39
  "@upstash/redis": "^1.36.4",
40
- "bcryptjs": "^2.4.3",
40
+ "bcryptjs": "^3.0.3",
41
41
  "clsx": "^2.1.1",
42
42
  "jose": "^6.2.1",
43
- "next": "^16.0.0",
43
+ "next": "^16.1.6",
44
44
  "pino": "^9.6.0",
45
45
  "pino-pretty": "^13.0.0",
46
46
  "react": "^19.0.0",
@@ -60,7 +60,7 @@
60
60
  "@tailwindcss/postcss": "^4.0.0",
61
61
  "@testing-library/jest-dom": "^6.9.1",
62
62
  "@testing-library/react": "^16.0.0",
63
- "@types/bcryptjs": "^2.4.0",
63
+ "@types/bcryptjs": "^3.0.0",
64
64
  "@types/node": "^25.4.0",
65
65
  "@types/react": "^19.0.0",
66
66
  "@types/react-dom": "^19.0.0",
@@ -75,6 +75,6 @@
75
75
  "tailwindcss": "^4.0.0",
76
76
  "tsx": "^4.0.0",
77
77
  "typescript": "^5.7.0",
78
- "vitest": "^3.0.0"
78
+ "vitest": "^4.1.0"
79
79
  }
80
80
  }
@@ -12,6 +12,7 @@ export default function ForgottenPassword() {
12
12
  const { getCSRFHeaders } = useCSRF();
13
13
  const [serverError, setServerError] = useState('');
14
14
  const [success, setSuccess] = useState(false);
15
+ const [devLink, setDevLink] = useState<string | null>(null);
15
16
 
16
17
  const form = useZodForm({
17
18
  schema: formSchemas.forgotPassword,
@@ -34,6 +35,10 @@ export default function ForgottenPassword() {
34
35
  return;
35
36
  }
36
37
 
38
+ const data = await response.json();
39
+ if (data.devLink) {
40
+ setDevLink(data.devLink);
41
+ }
37
42
  setSuccess(true);
38
43
  } catch {
39
44
  setServerError('Network error. Please try again.');
@@ -47,6 +52,19 @@ export default function ForgottenPassword() {
47
52
  <Text.Paragraph>
48
53
  If an account exists for that email, we&apos;ve sent password reset instructions.
49
54
  </Text.Paragraph>
55
+ {devLink && (
56
+ <div className="mx-auto mt-4 max-w-sm rounded-md border border-border-warning bg-warning-muted p-3">
57
+ <Text.Paragraph className="text-sm font-medium text-text-warning">
58
+ Dev mode — console email provider
59
+ </Text.Paragraph>
60
+ <a
61
+ href={devLink}
62
+ className="mt-1 inline-block text-sm font-medium text-text-link hover:text-text-link-hover hover:underline"
63
+ >
64
+ Click here to reset your password →
65
+ </a>
66
+ </div>
67
+ )}
50
68
  <Link href={routes.signIn} className="mt-6 inline-block text-sm text-text-link hover:text-text-link-hover hover:underline">
51
69
  Back to sign in
52
70
  </Link>
@@ -52,6 +52,9 @@ function SignUpForm() {
52
52
  }
53
53
 
54
54
  sessionStorage.setItem('verify-email', values.email);
55
+ if (data.devLink) {
56
+ sessionStorage.setItem('verify-dev-link', data.devLink);
57
+ }
55
58
  router.push('/verify');
56
59
  } catch {
57
60
  setServerError('Network error. Please try again.');
@@ -7,6 +7,7 @@ import { Suspense, useEffect, useState } from 'react';
7
7
 
8
8
  function VerifyContent() {
9
9
  const [email, setEmail] = useState<string | null>(null);
10
+ const [devLink, setDevLink] = useState<string | null>(null);
10
11
 
11
12
  useEffect(() => {
12
13
  const stored = sessionStorage.getItem('verify-email');
@@ -14,6 +15,11 @@ function VerifyContent() {
14
15
  setEmail(stored);
15
16
  sessionStorage.removeItem('verify-email');
16
17
  }
18
+ const storedDevLink = sessionStorage.getItem('verify-dev-link');
19
+ if (storedDevLink) {
20
+ setDevLink(storedDevLink);
21
+ sessionStorage.removeItem('verify-dev-link');
22
+ }
17
23
  }, []);
18
24
 
19
25
  return (
@@ -37,6 +43,19 @@ function VerifyContent() {
37
43
  <Text.Paragraph className="mt-1 text-sm text-text-muted">
38
44
  Didn&apos;t receive the email? Check your spam folder.
39
45
  </Text.Paragraph>
46
+ {devLink && (
47
+ <div className="mt-4 rounded-md border border-border-warning bg-warning-muted p-3">
48
+ <Text.Paragraph className="text-sm font-medium text-text-warning">
49
+ Dev mode — console email provider
50
+ </Text.Paragraph>
51
+ <a
52
+ href={devLink}
53
+ className="mt-1 inline-block text-sm font-medium text-text-link hover:text-text-link-hover hover:underline"
54
+ >
55
+ Click here to verify your email →
56
+ </a>
57
+ </div>
58
+ )}
40
59
  <Link
41
60
  href={routes.signIn}
42
61
  className="mt-8 inline-block text-sm text-text-link hover:text-text-link-hover hover:underline"
@@ -28,6 +28,13 @@ function NavLink({ href, label, active }: { href: string; label: string; active:
28
28
  );
29
29
  }
30
30
 
31
+ function AdminLink({ pathname }: { pathname: string }) {
32
+ const { user } = useAuth();
33
+ if (!user || user.role !== 'admin') return null;
34
+
35
+ return <NavLink href={routes.admin} label="Admin" active={pathname === routes.admin} />;
36
+ }
37
+
31
38
  function MobileNavLink({
32
39
  href,
33
40
  label,
@@ -54,6 +61,15 @@ function MobileNavLink({
54
61
  );
55
62
  }
56
63
 
64
+ function MobileAdminLink({ pathname, onClick }: { pathname: string; onClick: () => void }) {
65
+ const { user } = useAuth();
66
+ if (!user || user.role !== 'admin') return null;
67
+
68
+ return (
69
+ <MobileNavLink href={routes.admin} label="Admin" active={pathname === routes.admin} onClick={onClick} />
70
+ );
71
+ }
72
+
57
73
  function UserMenu() {
58
74
  const { user, logout } = useAuth();
59
75
  const router = useRouter();
@@ -212,6 +228,7 @@ export default function ProtectedLayout({ children }: Readonly<{ children: React
212
228
  {NAV_ITEMS.map((item) => (
213
229
  <NavLink key={item.href} href={item.href} label={item.label} active={pathname === item.href} />
214
230
  ))}
231
+ <AdminLink pathname={pathname} />
215
232
  </div>
216
233
  </div>
217
234
 
@@ -248,6 +265,7 @@ export default function ProtectedLayout({ children }: Readonly<{ children: React
248
265
  onClick={() => setMobileMenuOpen(false)}
249
266
  />
250
267
  ))}
268
+ <MobileAdminLink pathname={pathname} onClick={() => setMobileMenuOpen(false)} />
251
269
  </div>
252
270
  <div className="border-t border-border-default px-4 py-3">
253
271
  <UserMenu />
@@ -1,3 +1,5 @@
1
+ import 'server-only';
2
+
1
3
  import { prisma } from '@/lib/prisma';
2
4
  import { sendEmail, handleApiError, getBaseUrl } from '@/lib/mars';
3
5
  import { checkRateLimit, getClientIP, RATE_LIMITS, rateLimitResponse } from '@mars-stack/core/rate-limit';
@@ -53,8 +55,12 @@ export async function POST(request: Request) {
53
55
  text,
54
56
  });
55
57
 
58
+ const isConsoleEmail =
59
+ process.env.NODE_ENV !== 'production' &&
60
+ appConfig.services.email.provider === 'console';
61
+
56
62
  return NextResponse.json(
57
- { message: 'If an account exists, a reset email will be sent' },
63
+ { message: 'If an account exists, a reset email will be sent', ...(isConsoleEmail && { devLink: resetUrl }) },
58
64
  { status: 200 },
59
65
  );
60
66
  } catch (error) {
@@ -76,6 +76,15 @@ export async function POST(request: Request) {
76
76
  html,
77
77
  text,
78
78
  });
79
+
80
+ const isConsoleEmail =
81
+ process.env.NODE_ENV !== 'production' &&
82
+ appConfig.services.email.provider === 'console';
83
+
84
+ return NextResponse.json(
85
+ { message: 'User created successfully', ...(isConsoleEmail && { devLink: verifyUrl }) },
86
+ { status: 201 },
87
+ );
79
88
  }
80
89
 
81
90
  return NextResponse.json({ message: 'User created successfully' }, { status: 201 });
@@ -17,15 +17,7 @@ export const appConfig = {
17
17
  address: '',
18
18
  },
19
19
  theme: {
20
- primaryColor: 'blue-600' as string,
21
- secondaryColor: 'amber-400' as string,
22
20
  font: 'Inter' as string,
23
- designDirection: 'modern-saas' as
24
- | 'modern-saas'
25
- | 'minimal'
26
- | 'enterprise'
27
- | 'creative'
28
- | 'dashboard',
29
21
  },
30
22
  features: {
31
23
  auth: true,
@@ -8,6 +8,8 @@ export const routes = {
8
8
  dashboard: '/dashboard',
9
9
  settings: '/settings',
10
10
  admin: '/admin',
11
+ onboarding: '/onboarding',
12
+ comingSoon: '/coming-soon',
11
13
  about: '/about',
12
14
  privacy: '/privacy',
13
15
  terms: '/terms',