create-volt 0.14.0 → 0.15.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,16 @@ 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.15.0] - 2026-06-28
8
+
9
+ ### Added
10
+ - **`pages` add-on** — markdown pages, no database and no admin. Drop `.md`
11
+ files in `pages/` and each is served as HTML at `/<slug>`; front-matter
12
+ `title:` sets the page title. Author them in your editor or with AI. Pulls in
13
+ `marked` (added on enable, tracked by the dependency auto-updater); the
14
+ `pages/` directory is auto-created with a sample on first run. Mounted last,
15
+ so your own app routes always win.
16
+
7
17
  ## [0.14.0] - 2026-06-28
8
18
 
9
19
  ### Changed
@@ -203,6 +213,7 @@ All notable changes to `create-volt` are documented here. The format follows
203
213
  watching and full-page hot reload. Supports `--skip-install` and `--force`,
204
214
  and auto-detects npm / pnpm / yarn / bun for the install step.
205
215
 
216
+ [0.15.0]: https://github.com/MIR-2025/volt/releases/tag/v0.15.0
206
217
  [0.14.0]: https://github.com/MIR-2025/volt/releases/tag/v0.14.0
207
218
  [0.13.0]: https://github.com/MIR-2025/volt/releases/tag/v0.13.0
208
219
  [0.12.0]: https://github.com/MIR-2025/volt/releases/tag/v0.12.0
@@ -0,0 +1,79 @@
1
+ // pages.js — markdown pages. Drop *.md files in pages/ and each is served as
2
+ // HTML at /<slug>. No database, no admin: author them in your editor or with AI.
3
+ // Pages are code-owned files (trusted), so their markdown HTML is served as-is.
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import express from "express";
7
+ import { marked } from "marked";
8
+
9
+ const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;" })[c]);
10
+
11
+ const SAMPLE = `---
12
+ title: Welcome
13
+ ---
14
+
15
+ # Your Volt pages
16
+
17
+ This page is a markdown file at \`pages/welcome.md\`, served at \`/welcome\`.
18
+
19
+ - Drop more \`.md\` files in \`pages/\` — each becomes a page at \`/<filename>\`.
20
+ - Add front-matter to set the title:
21
+
22
+ \`\`\`
23
+ ---
24
+ title: About us
25
+ ---
26
+ \`\`\`
27
+
28
+ Author them in your editor, or ask an AI to write them. No database, no admin.
29
+ `;
30
+
31
+ function ensure(dir) {
32
+ if (!fs.existsSync(dir)) {
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ fs.writeFileSync(path.join(dir, "welcome.md"), SAMPLE);
35
+ }
36
+ }
37
+
38
+ const FM = /^---\r?\n([\s\S]*?)\r?\n---\r?\n/;
39
+ function parse(src) {
40
+ const m = src.match(FM);
41
+ if (!m) return { meta: {}, body: src };
42
+ const meta = {};
43
+ for (const line of m[1].split(/\r?\n/)) {
44
+ const i = line.indexOf(":");
45
+ if (i > 0) meta[line.slice(0, i).trim()] = line.slice(i + 1).trim();
46
+ }
47
+ return { meta, body: src.slice(m[0].length) };
48
+ }
49
+
50
+ function shell(title, inner) {
51
+ return `<!doctype html><html lang="en"><head><meta charset="utf-8" />
52
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
53
+ <title>${esc(title)}</title>
54
+ <style>
55
+ :root { color-scheme: light dark }
56
+ body { max-width: 720px; margin: 2.5rem auto; padding: 0 1.1rem; font: 17px/1.7 system-ui, -apple-system, sans-serif; }
57
+ h1, h2, h3 { line-height: 1.25; margin: 1.6rem 0 .6rem }
58
+ pre { background: #0b0d11; color: #cfe3ff; padding: 1rem; border-radius: 10px; overflow: auto }
59
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em }
60
+ :not(pre) > code { background: rgba(127,127,127,.18); padding: .1em .35em; border-radius: 5px }
61
+ img { max-width: 100% } a { color: #0b67d6 }
62
+ blockquote { border-left: 3px solid #ccc; margin: 1rem 0; padding: .2rem 1rem; opacity: .8 }
63
+ table { border-collapse: collapse } td, th { border: 1px solid #ccc; padding: .4rem .7rem }
64
+ </style></head><body>${inner}</body></html>`;
65
+ }
66
+
67
+ export function pagesRouter({ dir }) {
68
+ ensure(dir);
69
+ const r = express.Router();
70
+ r.get("/:slug", (req, res, next) => {
71
+ const slug = req.params.slug;
72
+ if (!/^[a-z0-9][a-z0-9-]*$/i.test(slug)) return next(); // safe slug only — no traversal
73
+ const file = path.join(dir, slug + ".md");
74
+ if (!fs.existsSync(file)) return next();
75
+ const { meta, body } = parse(fs.readFileSync(file, "utf8"));
76
+ res.type("html").send(shell(meta.title || slug, marked.parse(body)));
77
+ });
78
+ return r;
79
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "description": "Markdown pages: drop .md files in pages/ and each is served as HTML at /<slug>. No database, no admin — author them in your editor or with AI.",
3
+ "dependsOn": [],
4
+ "sentinel": "lib/pages.js",
5
+ "install": ["marked"],
6
+ "optional": {},
7
+ "wiring": "Add markdown files to the pages/ directory (created automatically on first run). pages/about.md is served at /about; front-matter `title:` sets the page title."
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-volt",
3
- "version": "0.14.0",
3
+ "version": "0.15.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": {
@@ -21,8 +21,8 @@ const ENV_PATH = path.join(__dirname, ".env");
21
21
  const PKG_PATH = path.join(__dirname, "package.json");
22
22
  const ADDONS_DIR = path.join(__dirname, ".volt", "addons"); // bundled add-on sources
23
23
  const DEFAULT_PORT = 26628; // create-volt stamps this with the project's date-port
24
- const PKG_VERSIONS = { mongodb: "^6.21.0", mysql2: "^3.22.5", pg: "^8.22.0", nodemailer: "^6.10.1" };
25
- const LIB_FILE = { db: "store.js", mailer: "mailer.js", auth: "auth.js", realtime: "realtime.js" };
24
+ const PKG_VERSIONS = { mongodb: "^6.21.0", mysql2: "^3.22.5", pg: "^8.22.0", nodemailer: "^6.10.1", marked: "^18.0.5" };
25
+ const LIB_FILE = { db: "store.js", mailer: "mailer.js", auth: "auth.js", realtime: "realtime.js", pages: "pages.js" };
26
26
 
27
27
  // --- tiny .env loader (no dependency); never overrides an existing env var ---
28
28
  function readEnvFile() {
@@ -111,6 +111,9 @@ async function startApp() {
111
111
 
112
112
  app.get("/", (_req, res) => res.sendFile(path.join(__dirname, "views", "index.html")));
113
113
 
114
+ // markdown pages (/<slug> ← pages/<slug>.md) — mounted last, so app routes win
115
+ if (enabled.has("pages")) app.use((await addonMod("pages")).pagesRouter({ dir: path.join(__dirname, "pages") }));
116
+
114
117
  const server = http.createServer(app);
115
118
  const io = new SocketServer(server);
116
119
  if (enabled.has("realtime") && store) (await addonMod("realtime")).attachRealtime(io, { store });
@@ -156,6 +159,7 @@ function neededPackages(env) {
156
159
  if (driver === "mysql") want.push("mysql2");
157
160
  if (driver === "postgres") want.push("pg");
158
161
  if (/^\s*SMTP_URL\s*=\s*\S/m.test(env)) want.push("nodemailer");
162
+ if (/^\s*VOLT_ADDONS\s*=.*\bpages\b/m.test(env)) want.push("marked");
159
163
  return want.filter((p) => !deps[p]);
160
164
  }
161
165
 
@@ -22,8 +22,8 @@ const ENV_PATH = path.join(__dirname, ".env");
22
22
  const PKG_PATH = path.join(__dirname, "package.json");
23
23
  const ADDONS_DIR = path.join(__dirname, ".volt", "addons"); // bundled add-on sources
24
24
  const DEFAULT_PORT = 26628; // create-volt stamps this with the project's date-port
25
- const PKG_VERSIONS = { mongodb: "^6.21.0", mysql2: "^3.22.5", pg: "^8.22.0", nodemailer: "^6.10.1" };
26
- const LIB_FILE = { db: "store.js", mailer: "mailer.js", auth: "auth.js", realtime: "realtime.js" };
25
+ const PKG_VERSIONS = { mongodb: "^6.21.0", mysql2: "^3.22.5", pg: "^8.22.0", nodemailer: "^6.10.1", marked: "^18.0.5" };
26
+ const LIB_FILE = { db: "store.js", mailer: "mailer.js", auth: "auth.js", realtime: "realtime.js", pages: "pages.js" };
27
27
 
28
28
  // --- tiny .env loader (no dependency); never overrides an existing env var ---
29
29
  function readEnvFile() {
@@ -137,6 +137,9 @@ async function startApp() {
137
137
 
138
138
  app.get("/", (_req, res) => res.sendFile(path.join(__dirname, "views", "index.html")));
139
139
 
140
+ // markdown pages (/<slug> ← pages/<slug>.md) — mounted last, so app routes win
141
+ if (enabled.has("pages")) app.use((await addonMod("pages")).pagesRouter({ dir: path.join(__dirname, "pages") }));
142
+
140
143
  const server = http.createServer(app);
141
144
  const io = new SocketServer(server);
142
145
  if (enabled.has("realtime") && store) (await addonMod("realtime")).attachRealtime(io, { store });
@@ -182,6 +185,7 @@ function neededPackages(env) {
182
185
  if (driver === "mysql") want.push("mysql2");
183
186
  if (driver === "postgres") want.push("pg");
184
187
  if (/^\s*SMTP_URL\s*=\s*\S/m.test(env)) want.push("nodemailer");
188
+ if (/^\s*VOLT_ADDONS\s*=.*\bpages\b/m.test(env)) want.push("marked");
185
189
  return want.filter((p) => !deps[p]);
186
190
  }
187
191