@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
- nodemailer: {
40
- label: "Nodemailer",
41
- hint: "SMTPworks with Gmail, Outlook, any mail server",
39
+ mailgun: {
40
+ label: "Mailgun",
41
+ hint: "mailgun.compowerful 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
- nodemailer: {
81
- "# SMTP / Nodemailer": [
82
- "SMTP_HOST=smtp.gmail.com",
83
- "SMTP_PORT=587",
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
- if (!await fs.pathExists(blockSrcDir)) {
129
- throw new Error(
130
- `Block src/ folder missing: ${blockSrcDir}\n` +
131
- `Structure: templates/blocks/${feature}/${provider}/src/...`
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
- await copyDir(blockSrcDir, path.join(targetPath, "src"), ["node_modules", ".next"]);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipindays/shipindays",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "description": "Open source CLI to scaffold a Next.js SaaS. Pick your auth, email, and payments wired up and ready to ship.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,6 @@
1
+ {
2
+ "dependencies": {
3
+ "form-data": "^4.0.1",
4
+ "mailgun": "^0.5.0"
5
+ }
6
+ }
@@ -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
- }
@@ -1,8 +0,0 @@
1
- {
2
- "dependencies": {
3
- "nodemailer": "^6.9.9"
4
- },
5
- "devDependencies": {
6
- "@types/nodemailer": "^6.4.16"
7
- }
8
- }