create-solostack 1.0.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/LICENSE +21 -0
- package/README.md +262 -0
- package/bin/cli.js +13 -0
- package/package.json +45 -0
- package/src/constants.js +94 -0
- package/src/generators/auth.js +595 -0
- package/src/generators/base.js +592 -0
- package/src/generators/database.js +365 -0
- package/src/generators/emails.js +404 -0
- package/src/generators/payments.js +541 -0
- package/src/generators/ui.js +368 -0
- package/src/index.js +374 -0
- package/src/utils/files.js +81 -0
- package/src/utils/git.js +69 -0
- package/src/utils/logger.js +62 -0
- package/src/utils/packages.js +75 -0
- package/src/utils/validate.js +17 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { writeFile, ensureDir } from '../utils/files.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Generates Resend email configuration and templates
|
|
6
|
+
* @param {string} projectPath - Path where the project is located
|
|
7
|
+
* @param {string} emailProvider - Email provider type
|
|
8
|
+
*/
|
|
9
|
+
export async function generateEmails(projectPath, emailProvider) {
|
|
10
|
+
// Create emails directory
|
|
11
|
+
await ensureDir(path.join(projectPath, 'src/lib/email'));
|
|
12
|
+
await ensureDir(path.join(projectPath, 'src/lib/email/templates'));
|
|
13
|
+
|
|
14
|
+
// Generate Resend config
|
|
15
|
+
const resendConfig = `import { Resend } from 'resend';
|
|
16
|
+
|
|
17
|
+
if (!process.env.RESEND_API_KEY) {
|
|
18
|
+
throw new Error('RESEND_API_KEY is not set');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const resend = new Resend(process.env.RESEND_API_KEY);
|
|
22
|
+
|
|
23
|
+
export const FROM_EMAIL = process.env.FROM_EMAIL || 'onboarding@resend.dev';
|
|
24
|
+
`;
|
|
25
|
+
|
|
26
|
+
await writeFile(path.join(projectPath, 'src/lib/email/config.ts'), resendConfig);
|
|
27
|
+
|
|
28
|
+
// Generate welcome email template
|
|
29
|
+
const welcomeEmail = `import {
|
|
30
|
+
Body,
|
|
31
|
+
Container,
|
|
32
|
+
Head,
|
|
33
|
+
Heading,
|
|
34
|
+
Html,
|
|
35
|
+
Link,
|
|
36
|
+
Preview,
|
|
37
|
+
Text,
|
|
38
|
+
} from '@react-email/components';
|
|
39
|
+
|
|
40
|
+
interface WelcomeEmailProps {
|
|
41
|
+
name: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function WelcomeEmail({ name }: WelcomeEmailProps) {
|
|
45
|
+
return (
|
|
46
|
+
<Html>
|
|
47
|
+
<Head />
|
|
48
|
+
<Preview>Welcome to our platform!</Preview>
|
|
49
|
+
<Body style={main}>
|
|
50
|
+
<Container style={container}>
|
|
51
|
+
<Heading style={h1}>Welcome{name ? \`, \${name}\` : ''}! 👋</Heading>
|
|
52
|
+
<Text style={text}>
|
|
53
|
+
Thank you for signing up! We're excited to have you on board.
|
|
54
|
+
</Text>
|
|
55
|
+
<Text style={text}>
|
|
56
|
+
Get started by exploring your dashboard and setting up your profile.
|
|
57
|
+
</Text>
|
|
58
|
+
<Link href={\`\${process.env.NEXTAUTH_URL}/dashboard\`} style={button}>
|
|
59
|
+
Go to Dashboard
|
|
60
|
+
</Link>
|
|
61
|
+
<Text style={footer}>
|
|
62
|
+
If you have any questions, feel free to reach out to our support team.
|
|
63
|
+
</Text>
|
|
64
|
+
</Container>
|
|
65
|
+
</Body>
|
|
66
|
+
</Html>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const main = {
|
|
71
|
+
backgroundColor: '#f6f9fc',
|
|
72
|
+
fontFamily:
|
|
73
|
+
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const container = {
|
|
77
|
+
backgroundColor: '#ffffff',
|
|
78
|
+
margin: '0 auto',
|
|
79
|
+
padding: '20px 0 48px',
|
|
80
|
+
marginBottom: '64px',
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const h1 = {
|
|
84
|
+
color: '#333',
|
|
85
|
+
fontSize: '24px',
|
|
86
|
+
fontWeight: 'bold',
|
|
87
|
+
margin: '40px 0',
|
|
88
|
+
padding: '0',
|
|
89
|
+
textAlign: 'center' as const,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const text = {
|
|
93
|
+
color: '#333',
|
|
94
|
+
fontSize: '16px',
|
|
95
|
+
lineHeight: '26px',
|
|
96
|
+
margin: '16px 0',
|
|
97
|
+
padding: '0 40px',
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const button = {
|
|
101
|
+
backgroundColor: '#5469d4',
|
|
102
|
+
borderRadius: '4px',
|
|
103
|
+
color: '#fff',
|
|
104
|
+
display: 'block',
|
|
105
|
+
fontSize: '16px',
|
|
106
|
+
fontWeight: 'bold',
|
|
107
|
+
margin: '24px auto',
|
|
108
|
+
padding: '12px 24px',
|
|
109
|
+
textAlign: 'center' as const,
|
|
110
|
+
textDecoration: 'none',
|
|
111
|
+
width: '200px',
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const footer = {
|
|
115
|
+
color: '#8898aa',
|
|
116
|
+
fontSize: '14px',
|
|
117
|
+
lineHeight: '24px',
|
|
118
|
+
margin: '32px 0 0',
|
|
119
|
+
padding: '0 40px',
|
|
120
|
+
textAlign: 'center' as const,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default WelcomeEmail;
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
await writeFile(
|
|
127
|
+
path.join(projectPath, 'src/lib/email/templates/welcome.tsx'),
|
|
128
|
+
welcomeEmail
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Generate verify email template
|
|
132
|
+
const verifyEmail = `import {
|
|
133
|
+
Body,
|
|
134
|
+
Container,
|
|
135
|
+
Head,
|
|
136
|
+
Heading,
|
|
137
|
+
Html,
|
|
138
|
+
Link,
|
|
139
|
+
Preview,
|
|
140
|
+
Text,
|
|
141
|
+
} from '@react-email/components';
|
|
142
|
+
|
|
143
|
+
interface VerifyEmailProps {
|
|
144
|
+
verificationUrl: string;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function VerifyEmail({ verificationUrl }: VerifyEmailProps) {
|
|
148
|
+
return (
|
|
149
|
+
<Html>
|
|
150
|
+
<Head />
|
|
151
|
+
<Preview>Verify your email address</Preview>
|
|
152
|
+
<Body style={main}>
|
|
153
|
+
<Container style={container}>
|
|
154
|
+
<Heading style={h1}>Verify your email 📧</Heading>
|
|
155
|
+
<Text style={text}>
|
|
156
|
+
Please verify your email address by clicking the button below.
|
|
157
|
+
</Text>
|
|
158
|
+
<Link href={verificationUrl} style={button}>
|
|
159
|
+
Verify Email
|
|
160
|
+
</Link>
|
|
161
|
+
<Text style={footer}>
|
|
162
|
+
If you didn't request this email, you can safely ignore it.
|
|
163
|
+
</Text>
|
|
164
|
+
<Text style={footer}>
|
|
165
|
+
This link will expire in 24 hours.
|
|
166
|
+
</Text>
|
|
167
|
+
</Container>
|
|
168
|
+
</Body>
|
|
169
|
+
</Html>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const main = {
|
|
174
|
+
backgroundColor: '#f6f9fc',
|
|
175
|
+
fontFamily:
|
|
176
|
+
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const container = {
|
|
180
|
+
backgroundColor: '#ffffff',
|
|
181
|
+
margin: '0 auto',
|
|
182
|
+
padding: '20px 0 48px',
|
|
183
|
+
marginBottom: '64px',
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const h1 = {
|
|
187
|
+
color: '#333',
|
|
188
|
+
fontSize: '24px',
|
|
189
|
+
fontWeight: 'bold',
|
|
190
|
+
margin: '40px 0',
|
|
191
|
+
padding: '0',
|
|
192
|
+
textAlign: 'center' as const,
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const text = {
|
|
196
|
+
color: '#333',
|
|
197
|
+
fontSize: '16px',
|
|
198
|
+
lineHeight: '26px',
|
|
199
|
+
margin: '16px 0',
|
|
200
|
+
padding: '0 40px',
|
|
201
|
+
textAlign: 'center' as const,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const button = {
|
|
205
|
+
backgroundColor: '#5469d4',
|
|
206
|
+
borderRadius: '4px',
|
|
207
|
+
color: '#fff',
|
|
208
|
+
display: 'block',
|
|
209
|
+
fontSize: '16px',
|
|
210
|
+
fontWeight: 'bold',
|
|
211
|
+
margin: '24px auto',
|
|
212
|
+
padding: '12px 24px',
|
|
213
|
+
textAlign: 'center' as const,
|
|
214
|
+
textDecoration: 'none',
|
|
215
|
+
width: '200px',
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const footer = {
|
|
219
|
+
color: '#8898aa',
|
|
220
|
+
fontSize: '14px',
|
|
221
|
+
lineHeight: '24px',
|
|
222
|
+
margin: '16px 0',
|
|
223
|
+
padding: '0 40px',
|
|
224
|
+
textAlign: 'center' as const,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export default VerifyEmail;
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
await writeFile(
|
|
231
|
+
path.join(projectPath, 'src/lib/email/templates/verify-email.tsx'),
|
|
232
|
+
verifyEmail
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Generate payment failed email template
|
|
236
|
+
const paymentFailedEmail = `import {
|
|
237
|
+
Body,
|
|
238
|
+
Container,
|
|
239
|
+
Head,
|
|
240
|
+
Heading,
|
|
241
|
+
Html,
|
|
242
|
+
Link,
|
|
243
|
+
Preview,
|
|
244
|
+
Text,
|
|
245
|
+
} from '@react-email/components';
|
|
246
|
+
|
|
247
|
+
interface PaymentFailedEmailProps {
|
|
248
|
+
name: string;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function PaymentFailedEmail({ name }: PaymentFailedEmailProps) {
|
|
252
|
+
return (
|
|
253
|
+
<Html>
|
|
254
|
+
<Head />
|
|
255
|
+
<Preview>Payment failed - Action required</Preview>
|
|
256
|
+
<Body style={main}>
|
|
257
|
+
<Container style={container}>
|
|
258
|
+
<Heading style={h1}>Payment Failed ⚠️</Heading>
|
|
259
|
+
<Text style={text}>
|
|
260
|
+
Hi {name},
|
|
261
|
+
</Text>
|
|
262
|
+
<Text style={text}>
|
|
263
|
+
We were unable to process your recent payment. This could be due to:
|
|
264
|
+
</Text>
|
|
265
|
+
<ul style={list}>
|
|
266
|
+
<li>Insufficient funds</li>
|
|
267
|
+
<li>Expired card</li>
|
|
268
|
+
<li>Card issuer declined the transaction</li>
|
|
269
|
+
</ul>
|
|
270
|
+
<Text style={text}>
|
|
271
|
+
Please update your payment method to continue using our service.
|
|
272
|
+
</Text>
|
|
273
|
+
<Link href={\`\${process.env.NEXTAUTH_URL}/dashboard/billing\`} style={button}>
|
|
274
|
+
Update Payment Method
|
|
275
|
+
</Link>
|
|
276
|
+
<Text style={footer}>
|
|
277
|
+
If you have questions, please contact our support team.
|
|
278
|
+
</Text>
|
|
279
|
+
</Container>
|
|
280
|
+
</Body>
|
|
281
|
+
</Html>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const main = {
|
|
286
|
+
backgroundColor: '#f6f9fc',
|
|
287
|
+
fontFamily:
|
|
288
|
+
'-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Ubuntu,sans-serif',
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const container = {
|
|
292
|
+
backgroundColor: '#ffffff',
|
|
293
|
+
margin: '0 auto',
|
|
294
|
+
padding: '20px 0 48px',
|
|
295
|
+
marginBottom: '64px',
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
const h1 = {
|
|
299
|
+
color: '#dc2626',
|
|
300
|
+
fontSize: '24px',
|
|
301
|
+
fontWeight: 'bold',
|
|
302
|
+
margin: '40px 0',
|
|
303
|
+
padding: '0',
|
|
304
|
+
textAlign: 'center' as const,
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const text = {
|
|
308
|
+
color: '#333',
|
|
309
|
+
fontSize: '16px',
|
|
310
|
+
lineHeight: '26px',
|
|
311
|
+
margin: '16px 0',
|
|
312
|
+
padding: '0 40px',
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const list = {
|
|
316
|
+
color: '#333',
|
|
317
|
+
fontSize: '16px',
|
|
318
|
+
lineHeight: '26px',
|
|
319
|
+
margin: '0 40px',
|
|
320
|
+
padding: '0 0 0 20px',
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const button = {
|
|
324
|
+
backgroundColor: '#dc2626',
|
|
325
|
+
borderRadius: '4px',
|
|
326
|
+
color: '#fff',
|
|
327
|
+
display: 'block',
|
|
328
|
+
fontSize: '16px',
|
|
329
|
+
fontWeight: 'bold',
|
|
330
|
+
margin: '24px auto',
|
|
331
|
+
padding: '12px 24px',
|
|
332
|
+
textAlign: 'center' as const,
|
|
333
|
+
textDecoration: 'none',
|
|
334
|
+
width: '240px',
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const footer = {
|
|
338
|
+
color: '#8898aa',
|
|
339
|
+
fontSize: '14px',
|
|
340
|
+
lineHeight: '24px',
|
|
341
|
+
margin: '32px 0 0',
|
|
342
|
+
padding: '0 40px',
|
|
343
|
+
textAlign: 'center' as const,
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export default PaymentFailedEmail;
|
|
347
|
+
`;
|
|
348
|
+
|
|
349
|
+
await writeFile(
|
|
350
|
+
path.join(projectPath, 'src/lib/email/templates/payment-failed.tsx'),
|
|
351
|
+
paymentFailedEmail
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
// Generate email helpers
|
|
355
|
+
const emailHelpers = `import { resend, FROM_EMAIL } from './config';
|
|
356
|
+
import { WelcomeEmail } from './templates/welcome';
|
|
357
|
+
import { VerifyEmail } from './templates/verify-email';
|
|
358
|
+
import { PaymentFailedEmail } from './templates/payment-failed';
|
|
359
|
+
|
|
360
|
+
export async function sendWelcomeEmail(to: string, name: string) {
|
|
361
|
+
try {
|
|
362
|
+
await resend.emails.send({
|
|
363
|
+
from: FROM_EMAIL,
|
|
364
|
+
to,
|
|
365
|
+
subject: 'Welcome to our platform!',
|
|
366
|
+
react: WelcomeEmail({ name }),
|
|
367
|
+
});
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.error('Failed to send welcome email:', error);
|
|
370
|
+
throw error;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export async function sendVerificationEmail(to: string, verificationUrl: string) {
|
|
375
|
+
try {
|
|
376
|
+
await resend.emails.send({
|
|
377
|
+
from: FROM_EMAIL,
|
|
378
|
+
to,
|
|
379
|
+
subject: 'Verify your email address',
|
|
380
|
+
react: VerifyEmail({ verificationUrl }),
|
|
381
|
+
});
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('Failed to send verification email:', error);
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export async function sendPaymentFailedEmail(to: string, name: string) {
|
|
389
|
+
try {
|
|
390
|
+
await resend.emails.send({
|
|
391
|
+
from: FROM_EMAIL,
|
|
392
|
+
to,
|
|
393
|
+
subject: 'Payment Failed - Action Required',
|
|
394
|
+
react: PaymentFailedEmail({ name }),
|
|
395
|
+
});
|
|
396
|
+
} catch (error) {
|
|
397
|
+
console.error('Failed to send payment failed email:', error);
|
|
398
|
+
throw error;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
`;
|
|
402
|
+
|
|
403
|
+
await writeFile(path.join(projectPath, 'src/lib/email/index.ts'), emailHelpers);
|
|
404
|
+
}
|