@shipindays/shipindays 0.1.12 → 0.1.13
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/index.js
CHANGED
|
@@ -36,9 +36,9 @@ const EMAIL_PROVIDERS = {
|
|
|
36
36
|
label: "Resend",
|
|
37
37
|
hint: "resend.com — best DX, generous free tier",
|
|
38
38
|
},
|
|
39
|
-
|
|
40
|
-
label: "
|
|
41
|
-
hint: "
|
|
39
|
+
mailgun: {
|
|
40
|
+
label: "Mailgun",
|
|
41
|
+
hint: "mailgun.com — powerful API, great for scaling",
|
|
42
42
|
},
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -77,14 +77,10 @@ const ENV_VARS = {
|
|
|
77
77
|
"RESEND_API_KEY=",
|
|
78
78
|
],
|
|
79
79
|
},
|
|
80
|
-
|
|
81
|
-
"#
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"SMTP_SECURE=false",
|
|
85
|
-
"SMTP_USER=",
|
|
86
|
-
"SMTP_PASS=",
|
|
87
|
-
"SMTP_FROM=you@yourdomain.com",
|
|
80
|
+
mailgun: {
|
|
81
|
+
"# Mailgun (mailgun.com → Sending → Domains)": [
|
|
82
|
+
"MAILGUN_API_KEY=",
|
|
83
|
+
"MAILGUN_DOMAIN=",
|
|
88
84
|
],
|
|
89
85
|
},
|
|
90
86
|
},
|
|
@@ -97,13 +93,19 @@ const ENV_VARS = {
|
|
|
97
93
|
async function copyDir(src, dest, skipNames = []) {
|
|
98
94
|
await fs.ensureDir(dest);
|
|
99
95
|
const entries = await fs.readdir(src, { withFileTypes: true });
|
|
96
|
+
|
|
100
97
|
for (const entry of entries) {
|
|
101
98
|
if (skipNames.includes(entry.name)) continue;
|
|
99
|
+
|
|
102
100
|
const srcPath = path.join(src, entry.name);
|
|
103
101
|
const destPath = path.join(dest, entry.name);
|
|
102
|
+
|
|
104
103
|
if (entry.isDirectory()) {
|
|
104
|
+
// Create sub-directory in destination and recurse
|
|
105
|
+
await fs.ensureDir(destPath);
|
|
105
106
|
await copyDir(srcPath, destPath, skipNames);
|
|
106
107
|
} else {
|
|
108
|
+
// Copy the file
|
|
107
109
|
await fs.copy(srcPath, destPath, { overwrite: true });
|
|
108
110
|
}
|
|
109
111
|
}
|
|
@@ -119,20 +121,26 @@ async function injectBlock(feature, provider, targetPath) {
|
|
|
119
121
|
const blockSrcDir = path.join(blockRoot, "src");
|
|
120
122
|
|
|
121
123
|
if (!await fs.pathExists(blockRoot)) {
|
|
122
|
-
throw new Error(
|
|
123
|
-
`Block not found: ${blockRoot}\n` +
|
|
124
|
-
`Make sure templates/blocks/${feature}/${provider}/ exists.`
|
|
125
|
-
);
|
|
124
|
+
throw new Error(`Block not found: ${blockRoot}`);
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
);
|
|
127
|
+
// 1. Copy everything inside block/src/ to target/src/
|
|
128
|
+
if (await fs.pathExists(blockSrcDir)) {
|
|
129
|
+
// We skip node_modules and .next if they accidentally exist in the block
|
|
130
|
+
await copyDir(blockSrcDir, path.join(targetPath, "src"), ["node_modules", ".next"]);
|
|
133
131
|
}
|
|
134
132
|
|
|
135
|
-
|
|
133
|
+
// 2. Handle non-src files (like public/ or drizzle/ if the block has them)
|
|
134
|
+
// Check if there are other folders in the block root that aren't 'src' or 'package.json'
|
|
135
|
+
const blockEntries = await fs.readdir(blockRoot, { withFileTypes: true });
|
|
136
|
+
for (const entry of blockEntries) {
|
|
137
|
+
if (entry.isDirectory() && entry.name !== "src" && entry.name !== "node_modules") {
|
|
138
|
+
await copyDir(
|
|
139
|
+
path.join(blockRoot, entry.name),
|
|
140
|
+
path.join(targetPath, entry.name)
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
136
144
|
}
|
|
137
145
|
|
|
138
146
|
// Reads the block's package.json and merges its deps into the project's package.json.
|
|
@@ -147,10 +155,13 @@ async function mergePackageJson(targetPath, feature, provider) {
|
|
|
147
155
|
const targetPkg = await fs.readJson(targetPkgPath);
|
|
148
156
|
const blockPkg = await fs.readJson(blockPkgPath);
|
|
149
157
|
|
|
158
|
+
// Merge dependencies
|
|
150
159
|
targetPkg.dependencies = {
|
|
151
160
|
...(targetPkg.dependencies ?? {}),
|
|
152
161
|
...(blockPkg.dependencies ?? {}),
|
|
153
162
|
};
|
|
163
|
+
|
|
164
|
+
// Merge devDependencies
|
|
154
165
|
targetPkg.devDependencies = {
|
|
155
166
|
...(targetPkg.devDependencies ?? {}),
|
|
156
167
|
...(blockPkg.devDependencies ?? {}),
|
|
@@ -377,7 +388,7 @@ async function main() {
|
|
|
377
388
|
run(`git commit -m "chore: scaffold from shipindays"`, targetPath);
|
|
378
389
|
spin.stop("Git initialised.");
|
|
379
390
|
} catch {
|
|
380
|
-
spin.stop(chalk.yellow("Git skipped — run manually."));
|
|
391
|
+
spin.stop(chalk.yellow("Git skipped — please run manually."));
|
|
381
392
|
}
|
|
382
393
|
}
|
|
383
394
|
|
package/package.json
CHANGED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
// FILE: src/lib/email/index.ts
|
|
2
|
-
// ROUTE: not a route — imported anywhere that sends email
|
|
3
|
-
// ROLE: Nodemailer provider implementation
|
|
4
|
-
//
|
|
5
|
-
// INJECTED BY CLI when user picks "Nodemailer" as their email provider.
|
|
6
|
-
// Replaces templates/base/src/lib/email/index.ts
|
|
7
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
8
|
-
|
|
9
|
-
import nodemailer from "nodemailer";
|
|
10
|
-
|
|
11
|
-
// Nodemailer transport — reads SMTP config from env
|
|
12
|
-
// Works with Gmail, Outlook, Mailgun SMTP, any SMTP server
|
|
13
|
-
const transporter = nodemailer.createTransport({
|
|
14
|
-
host: process.env.SMTP_HOST,
|
|
15
|
-
port: Number(process.env.SMTP_PORT ?? 587),
|
|
16
|
-
secure: process.env.SMTP_SECURE === "true",
|
|
17
|
-
auth: {
|
|
18
|
-
user: process.env.SMTP_USER,
|
|
19
|
-
pass: process.env.SMTP_PASS,
|
|
20
|
-
},
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const FROM = process.env.SMTP_FROM ?? "you@yourdomain.com";
|
|
24
|
-
|
|
25
|
-
// ─── sendWelcomeEmail ─────────────────────────────────────────────────────────
|
|
26
|
-
export async function sendWelcomeEmail({ to, name }: { to: string; name: string }) {
|
|
27
|
-
await transporter.sendMail({
|
|
28
|
-
from: FROM,
|
|
29
|
-
to,
|
|
30
|
-
subject: "Welcome! 🎉",
|
|
31
|
-
html: `
|
|
32
|
-
<div style="font-family:sans-serif;max-width:560px;margin:0 auto;padding:40px 20px">
|
|
33
|
-
<h1 style="font-size:24px;color:#111">Hey ${name}, welcome aboard!</h1>
|
|
34
|
-
<p style="color:#555;line-height:1.7">Your account is ready. Click below to get started.</p>
|
|
35
|
-
<a href="${process.env.NEXT_PUBLIC_APP_URL}/dashboard"
|
|
36
|
-
style="display:inline-block;margin-top:20px;padding:12px 24px;background:#111;color:#fff;border-radius:6px;text-decoration:none">
|
|
37
|
-
Go to Dashboard →
|
|
38
|
-
</a>
|
|
39
|
-
</div>
|
|
40
|
-
`,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ─── sendPasswordResetEmail ───────────────────────────────────────────────────
|
|
45
|
-
export async function sendPasswordResetEmail({ to, resetUrl }: { to: string; resetUrl: string }) {
|
|
46
|
-
await transporter.sendMail({
|
|
47
|
-
from: FROM,
|
|
48
|
-
to,
|
|
49
|
-
subject: "Reset your password",
|
|
50
|
-
html: `
|
|
51
|
-
<div style="font-family:sans-serif;max-width:560px;margin:0 auto;padding:40px 20px">
|
|
52
|
-
<h1 style="font-size:24px;color:#111">Reset your password</h1>
|
|
53
|
-
<p style="color:#555;line-height:1.7">Click below to reset your password. Link expires in 1 hour.</p>
|
|
54
|
-
<a href="${resetUrl}"
|
|
55
|
-
style="display:inline-block;margin-top:20px;padding:12px 24px;background:#111;color:#fff;border-radius:6px;text-decoration:none">
|
|
56
|
-
Reset Password →
|
|
57
|
-
</a>
|
|
58
|
-
<p style="margin-top:24px;color:#999;font-size:12px">If you didn't request this, ignore this email.</p>
|
|
59
|
-
</div>
|
|
60
|
-
`,
|
|
61
|
-
});
|
|
62
|
-
}
|