create-volt 0.24.0 → 0.25.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,17 @@ 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.25.0] - 2026-06-29
8
+
9
+ ### Added
10
+ - **Per-page SEO on the `pages` add-on** — front-matter now drives the page head:
11
+ `description` (meta description + og:description), `image` (og:image), `type`
12
+ (og:type), `canonical`, and **`jsonld`** (a one-line JSON string rendered into a
13
+ validated `<script type="application/ld+json">` block, with `<` escaped to
14
+ prevent breakout). Open Graph + Twitter + JSON-LD per page — the Yoast-style SEO
15
+ a migrated WordPress site expects. `volt-addon-editor` 0.4.0 adds a SEO panel to
16
+ set these from the editor.
17
+
7
18
  ## [0.24.0] - 2026-06-29
8
19
 
9
20
  ### Added
@@ -343,6 +354,7 @@ All notable changes to `create-volt` are documented here. The format follows
343
354
  watching and full-page hot reload. Supports `--skip-install` and `--force`,
344
355
  and auto-detects npm / pnpm / yarn / bun for the install step.
345
356
 
357
+ [0.25.0]: https://github.com/MIR-2025/volt/releases/tag/v0.25.0
346
358
  [0.24.0]: https://github.com/MIR-2025/volt/releases/tag/v0.24.0
347
359
  [0.23.0]: https://github.com/MIR-2025/volt/releases/tag/v0.23.0
348
360
  [0.22.0]: https://github.com/MIR-2025/volt/releases/tag/v0.22.0
@@ -50,10 +50,39 @@ export function parseFrontMatter(src) {
50
50
  // slugs are restricted to a safe charset — no dots/slashes → no path traversal
51
51
  export const isSafeSlug = (s) => /^[a-z0-9][a-z0-9-]*$/i.test(s);
52
52
 
53
- function shell(title, inner) {
53
+ // SEO/social head from front-matter: description, image, type, canonical, jsonld.
54
+ function metaHead(meta) {
55
+ const t = [];
56
+ const title = meta.title || "";
57
+ const desc = meta.description || "";
58
+ if (title) t.push(`<meta property="og:title" content="${esc(title)}" />`);
59
+ if (desc) {
60
+ t.push(`<meta name="description" content="${esc(desc)}" />`);
61
+ t.push(`<meta property="og:description" content="${esc(desc)}" />`);
62
+ }
63
+ t.push(`<meta property="og:type" content="${esc(meta.type || "website")}" />`);
64
+ if (meta.image) t.push(`<meta property="og:image" content="${esc(meta.image)}" />`);
65
+ if (meta.url || meta.canonical) t.push(`<meta property="og:url" content="${esc(meta.url || meta.canonical)}" />`);
66
+ if (meta.canonical) t.push(`<link rel="canonical" href="${esc(meta.canonical)}" />`);
67
+ t.push(`<meta name="twitter:card" content="${meta.image ? "summary_large_image" : "summary"}" />`);
68
+ if (meta.jsonld) {
69
+ let ok = false;
70
+ try {
71
+ JSON.parse(meta.jsonld);
72
+ ok = true;
73
+ } catch {
74
+ /* invalid JSON-LD → skip */
75
+ }
76
+ if (ok) t.push(`<script type="application/ld+json">${meta.jsonld.replace(/</g, "\\u003c")}</script>`);
77
+ }
78
+ return t.join("\n");
79
+ }
80
+
81
+ function shell(meta, inner) {
54
82
  return `<!doctype html><html lang="en"><head><meta charset="utf-8" />
55
83
  <meta name="viewport" content="width=device-width, initial-scale=1" />
56
- <title>${esc(title)}</title>
84
+ <title>${esc(meta.title || "")}</title>
85
+ ${metaHead(meta)}
57
86
  <style>
58
87
  :root { color-scheme: light dark }
59
88
  body { max-width: 720px; margin: 2.5rem auto; padding: 0 1.1rem; font: 17px/1.7 system-ui, -apple-system, sans-serif; }
@@ -81,7 +110,7 @@ export async function pagesRouter({ dir }) {
81
110
  // `format: html` pages (e.g. from the WYSIWYG editor) are served verbatim to
82
111
  // preserve complex layouts; everything else is markdown rendered with marked.
83
112
  const inner = meta.format === "html" ? body : marked.parse(body);
84
- res.type("html").send(shell(meta.title || slug, inner));
113
+ res.type("html").send(shell({ ...meta, title: meta.title || slug }, inner));
85
114
  });
86
115
  return r;
87
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-volt",
3
- "version": "0.24.0",
3
+ "version": "0.25.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": {