create-volt 0.51.0 → 0.53.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/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ All notable changes to `create-volt` are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.53.0] - 2026-07-04
8
+
9
+ ### Added
10
+ - **`create-volt env`** — writes a documented `.env.example` for a deploying admin
11
+ to fill in: every var grouped and commented, structural values (PORT, SITE_NAME,
12
+ VOLT_ADDONS…) seeded from an existing `.env`, secrets left blank. `--print` to
13
+ stdout, `--force` to overwrite.
14
+
15
+ ## [0.52.0] - 2026-07-04
16
+
17
+ ### Added
18
+ - **Mailer accepts discrete SMTP vars.** The mailer add-on builds its transport
19
+ from `SMTP_HOST`/`SMTP_PORT`/`SMTP_SECURE`/`SMTP_USER`/`SMTP_PASS` when
20
+ `SMTP_URL` is not set (secure defaults on for port 465), and resolves the From
21
+ address as `MAIL_FROM` → `SMTP_FROM` → `SMTP_USER`. So a plain host/port/user/pass
22
+ config works without composing a URL.
23
+
7
24
  ## [0.51.0] - 2026-06-30
8
25
 
9
26
  ### Added
@@ -675,6 +692,8 @@ All notable changes to `create-volt` are documented here. The format follows
675
692
  watching and full-page hot reload. Supports `--skip-install` and `--force`,
676
693
  and auto-detects npm / pnpm / yarn / bun for the install step.
677
694
 
695
+ [0.53.0]: https://github.com/MIR-2025/volt/releases/tag/v0.53.0
696
+ [0.52.0]: https://github.com/MIR-2025/volt/releases/tag/v0.52.0
678
697
  [0.51.0]: https://github.com/MIR-2025/volt/releases/tag/v0.51.0
679
698
  [0.50.0]: https://github.com/MIR-2025/volt/releases/tag/v0.50.0
680
699
  [0.49.0]: https://github.com/MIR-2025/volt/releases/tag/v0.49.0
@@ -1,20 +1,31 @@
1
- // mailer.js — sends email. In dev (no SMTP_URL) it prints messages to the
2
- // console so you can see them; in production it uses nodemailer when SMTP_URL
3
- // is set and the package is installed.
1
+ // mailer.js — sends email. In dev (no SMTP config) it prints messages to the
2
+ // console so you can see them; in production it uses nodemailer when SMTP is
3
+ // configured either a single SMTP_URL, or discrete SMTP_HOST/SMTP_PORT/
4
+ // SMTP_SECURE/SMTP_USER/SMTP_PASS vars — and the package is installed.
4
5
 
5
6
  export async function createMailer() {
6
- const smtp = process.env.SMTP_URL;
7
- const from = process.env.MAIL_FROM || "App <no-reply@example.com>";
7
+ // transport: SMTP_URL wins; otherwise build it from discrete host/port vars.
8
+ const transportConfig = process.env.SMTP_URL
9
+ ? process.env.SMTP_URL
10
+ : process.env.SMTP_HOST
11
+ ? {
12
+ host: process.env.SMTP_HOST,
13
+ port: Number(process.env.SMTP_PORT) || 587,
14
+ secure: /^(1|true|yes|on)$/i.test(process.env.SMTP_SECURE || "") || Number(process.env.SMTP_PORT) === 465,
15
+ auth: process.env.SMTP_USER ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS } : undefined,
16
+ }
17
+ : null;
18
+ const from = process.env.MAIL_FROM || process.env.SMTP_FROM || process.env.SMTP_USER || "App <no-reply@example.com>";
8
19
 
9
- if (smtp) {
20
+ if (transportConfig) {
10
21
  let nodemailer;
11
22
  try {
12
23
  nodemailer = (await import("nodemailer")).default;
13
24
  } catch {
14
- console.warn("[mailer] SMTP_URL set but 'nodemailer' isn't installed — using console. Run: npm install nodemailer");
25
+ console.warn("[mailer] SMTP configured but 'nodemailer' isn't installed — using console. Run: npm install nodemailer");
15
26
  }
16
27
  if (nodemailer) {
17
- const transport = nodemailer.createTransport(smtp);
28
+ const transport = nodemailer.createTransport(transportConfig);
18
29
  return {
19
30
  name: "smtp",
20
31
  async send({ to, subject, text, html }) {
package/index.js CHANGED
@@ -318,6 +318,86 @@ if (positionals[0] === "config") {
318
318
  process.exit(res.status ?? 0);
319
319
  }
320
320
 
321
+ // --- `env` subcommand: write a documented .env.example for a deploying admin to
322
+ // fill in. Seeds structural (non-secret) values from an existing .env; secrets
323
+ // stay blank. `--print` to stdout, `--force` to overwrite. ---
324
+ if (positionals[0] === "env") {
325
+ const cwd = process.cwd();
326
+ const cur = {};
327
+ const envPath = path.join(cwd, ".env");
328
+ if (fs.existsSync(envPath)) {
329
+ for (const line of fs.readFileSync(envPath, "utf8").split("\n")) {
330
+ const m = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.*?)\s*$/);
331
+ if (m) cur[m[1]] = m[2].replace(/\s+#.*$/, "");
332
+ }
333
+ }
334
+ const v = (k, d = "") => cur[k] || d;
335
+ const tmpl = `# .env — Volt configuration. Fill in what your deployment needs, then keep this
336
+ # file OUT of git. Generated by \`create-volt env\`. Uncomment the lines you use.
337
+
338
+ # --- Core ---
339
+ PORT=${v("PORT", "3000")}
340
+ SITE_NAME=${v("SITE_NAME", "My Site")}
341
+ SITE_URL=${v("SITE_URL")} # absolute base URL (RSS/canonical/OG), e.g. https://example.com
342
+ # SITE_TZ=${v("SITE_TZ")} # IANA timezone for dates, e.g. America/New_York
343
+ # CONFIG_PORT=5050 # the --edit/--studio config UI's own port
344
+
345
+ # --- Add-ons (comma-separated backends to enable) ---
346
+ VOLT_ADDONS=${v("VOLT_ADDONS", "pages")} # pages,posts,db,auth,mailer,media,realtime
347
+
348
+ # --- Database (add-on: db) ---
349
+ # DB_DRIVER=memory # memory | mongodb | mysql | postgres
350
+ # MONGODB_URI=
351
+ # MONGODB_DATABASE=
352
+ # DATABASE_URL= # mysql:// or postgres:// URL
353
+
354
+ # --- Email (add-on: mailer) — magic-link auth, receipts ---
355
+ # One SMTP_URL, or discrete host/port/user/pass:
356
+ # SMTP_URL=smtps://user:pass@smtp.host:465
357
+ # SMTP_HOST=
358
+ # SMTP_PORT=465
359
+ # SMTP_SECURE=true # true for 465, false for 587 (STARTTLS)
360
+ # SMTP_USER=
361
+ # SMTP_PASS=
362
+ # MAIL_FROM=My Site <hello@example.com>
363
+
364
+ # --- Admin editor (volt-addon-editor) ---
365
+ # ADMIN_PATH=/your-secret-path # the gated editor lives at /<ADMIN_PATH>
366
+ # ADMIN_EMAILS=you@example.com # allowlist of who may log into the editor
367
+
368
+ # --- Media uploads (add-on: media; needs auth) ---
369
+ # MEDIA_DRIVER=local # local | s3
370
+ # S3_ENDPOINT=
371
+ # S3_REGION=
372
+ # S3_BUCKET=
373
+ # S3_KEY=
374
+ # S3_SECRET=
375
+ # S3_PUBLIC_BASE=
376
+
377
+ # --- AI (editor "write with AI") ---
378
+ # AI_PROVIDER=anthropic # anthropic | openai | gemini
379
+ # Bring your own key:
380
+ # ANTHROPIC_API_KEY=
381
+ # OPENAI_API_KEY=
382
+ # GEMINI_API_KEY=
383
+ # — or — the hosted tier (free-capped, then pay-as-you-go):
384
+ # VOLT_AI_TOKEN= # generate one in the config wizard
385
+ # VOLT_AI_GATEWAY=https://voltjs.com/api/ai
386
+ `;
387
+ if (flags.has("--print")) {
388
+ process.stdout.write(tmpl);
389
+ process.exit(0);
390
+ }
391
+ const out = path.join(cwd, ".env.example");
392
+ if (fs.existsSync(out) && !flags.has("--force")) {
393
+ die(`${cyan(".env.example")} already exists — pass ${cyan("--force")} to overwrite, or ${cyan("--print")} for stdout.`);
394
+ }
395
+ fs.writeFileSync(out, tmpl);
396
+ console.log(`\n${green("✔")} Wrote ${bold(".env.example")} — a documented template for a deploying admin.`);
397
+ console.log(dim(` Copy it to .env on the server and fill in the values you need; secrets stay blank here.`));
398
+ process.exit(0);
399
+ }
400
+
321
401
  // Write imported markdown pages to disk and print a summary (shared by both importers).
322
402
  function emitImported(imported, stats, outDir) {
323
403
  fs.mkdirSync(outDir, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-volt",
3
- "version": "0.51.0",
3
+ "version": "0.53.0",
4
4
  "description": "Scaffold a new Volt app — no-build, signals-based UI with Socket.io hot reload.",
5
5
  "type": "module",
6
6
  "bin": {