create-volt 0.53.0 → 0.54.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 +12 -0
- package/package.json +1 -1
- package/templates/blog/server.js +27 -1
- package/templates/blog/setup/setup.js +3 -1
- package/templates/default/server.js +27 -1
- package/templates/default/setup/setup.js +3 -1
- package/templates/docs/server.js +27 -1
- package/templates/docs/setup/setup.js +3 -1
- package/templates/starter/server.js +27 -1
- package/templates/starter/setup/setup.js +3 -1
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.54.0] - 2026-07-04
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- **Config editor renders themed.** The in-config WYSIWYG loads the active theme's
|
|
11
|
+
CSS (new `/setup/theme-css` → RTEPro `exportCSS`), so pages look like the published
|
|
12
|
+
site as you edit — new pages included.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- **Log analytics bot/attack counts were always 0** — they read `.bot`/`.attack`,
|
|
16
|
+
but mir-sentinel's parseLine returns `.isBot`/`.isAttack`. Fixed in `--logs`.
|
|
17
|
+
|
|
7
18
|
## [0.53.0] - 2026-07-04
|
|
8
19
|
|
|
9
20
|
### Added
|
|
@@ -692,6 +703,7 @@ All notable changes to `create-volt` are documented here. The format follows
|
|
|
692
703
|
watching and full-page hot reload. Supports `--skip-install` and `--force`,
|
|
693
704
|
and auto-detects npm / pnpm / yarn / bun for the install step.
|
|
694
705
|
|
|
706
|
+
[0.54.0]: https://github.com/MIR-2025/volt/releases/tag/v0.54.0
|
|
695
707
|
[0.53.0]: https://github.com/MIR-2025/volt/releases/tag/v0.53.0
|
|
696
708
|
[0.52.0]: https://github.com/MIR-2025/volt/releases/tag/v0.52.0
|
|
697
709
|
[0.51.0]: https://github.com/MIR-2025/volt/releases/tag/v0.51.0
|
package/package.json
CHANGED
package/templates/blog/server.js
CHANGED
|
@@ -440,6 +440,32 @@ function startSetup() {
|
|
|
440
440
|
});
|
|
441
441
|
return;
|
|
442
442
|
}
|
|
443
|
+
// --- active theme's CSS, so the in-config editor renders pages themed ---
|
|
444
|
+
if (req.method === "GET" && p === "/setup/theme-css") {
|
|
445
|
+
res.setHeader("Content-Type", "text/css; charset=utf-8");
|
|
446
|
+
(async () => {
|
|
447
|
+
const theme = (readEnvFile().THEME || "").trim();
|
|
448
|
+
const load = async (rel) => {
|
|
449
|
+
try {
|
|
450
|
+
return (await imp(rel)).css || "";
|
|
451
|
+
} catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
let css = null;
|
|
456
|
+
if (theme) css = await load(path.join(".volt", "themes", theme, "index.js"));
|
|
457
|
+
if (css == null && fs.existsSync(path.join(__dirname, "pages", "_theme.js"))) css = await load(path.join("pages", "_theme.js"));
|
|
458
|
+
if (css == null && theme) {
|
|
459
|
+
try {
|
|
460
|
+
css = (await import(`volt-theme-${theme}`)).css || "";
|
|
461
|
+
} catch {
|
|
462
|
+
css = null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
res.end(css || "");
|
|
466
|
+
})();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
443
469
|
// --- content manager: list / read / write / delete pages + posts ---
|
|
444
470
|
if (req.method === "GET" && p === "/setup/content") {
|
|
445
471
|
const list = (type) => {
|
|
@@ -785,7 +811,7 @@ async function startLogs() {
|
|
|
785
811
|
const f = sources()[u.searchParams.get("source")];
|
|
786
812
|
if (!f) return json(res, { ok: false });
|
|
787
813
|
const parsed = tail(f, 5000).map((l) => parseLine(l));
|
|
788
|
-
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.
|
|
814
|
+
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.isBot).length, attacks: parsed.filter((x) => x && x.isAttack).length });
|
|
789
815
|
}
|
|
790
816
|
// add/remove a source ("add servers") — written to .volt/logs.json
|
|
791
817
|
if (req.method === "POST" && (p === "/api/source" || p === "/api/source/remove")) {
|
|
@@ -323,6 +323,8 @@ async function genToken() {
|
|
|
323
323
|
const items = signal({ pages: [], posts: [] });
|
|
324
324
|
const editing = signal(null); // { type, slug, title, isNew } — set only on open/save/close
|
|
325
325
|
let ed = null; // live RTEPro instance for the open editor
|
|
326
|
+
let themeCss = ""; // active theme's CSS, so the editor renders pages themed
|
|
327
|
+
fetch("/setup/theme-css").then((r) => r.text()).then((c) => { themeCss = c; }).catch(() => {});
|
|
326
328
|
const loadItems = async () => items(await (await fetch("/setup/content")).json());
|
|
327
329
|
// raw .md → { title, body, isHtml }; RTEPro takes markdown directly (setMarkdown),
|
|
328
330
|
// so no markdown library is needed.
|
|
@@ -333,7 +335,7 @@ function parseDoc(raw) {
|
|
|
333
335
|
return { title, body: fm ? raw.slice(fm[0].length) : raw, isHtml: /^format:\s*html\s*$/m.test(front) };
|
|
334
336
|
}
|
|
335
337
|
function mountEditor(doc) {
|
|
336
|
-
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic" });
|
|
338
|
+
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic", exportCSS: themeCss });
|
|
337
339
|
if (doc && doc.isHtml) ed.setHTML(doc.body || "");
|
|
338
340
|
else ed.setMarkdown((doc && doc.body) || "");
|
|
339
341
|
}
|
|
@@ -440,6 +440,32 @@ function startSetup() {
|
|
|
440
440
|
});
|
|
441
441
|
return;
|
|
442
442
|
}
|
|
443
|
+
// --- active theme's CSS, so the in-config editor renders pages themed ---
|
|
444
|
+
if (req.method === "GET" && p === "/setup/theme-css") {
|
|
445
|
+
res.setHeader("Content-Type", "text/css; charset=utf-8");
|
|
446
|
+
(async () => {
|
|
447
|
+
const theme = (readEnvFile().THEME || "").trim();
|
|
448
|
+
const load = async (rel) => {
|
|
449
|
+
try {
|
|
450
|
+
return (await imp(rel)).css || "";
|
|
451
|
+
} catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
let css = null;
|
|
456
|
+
if (theme) css = await load(path.join(".volt", "themes", theme, "index.js"));
|
|
457
|
+
if (css == null && fs.existsSync(path.join(__dirname, "pages", "_theme.js"))) css = await load(path.join("pages", "_theme.js"));
|
|
458
|
+
if (css == null && theme) {
|
|
459
|
+
try {
|
|
460
|
+
css = (await import(`volt-theme-${theme}`)).css || "";
|
|
461
|
+
} catch {
|
|
462
|
+
css = null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
res.end(css || "");
|
|
466
|
+
})();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
443
469
|
// --- content manager: list / read / write / delete pages + posts ---
|
|
444
470
|
if (req.method === "GET" && p === "/setup/content") {
|
|
445
471
|
const list = (type) => {
|
|
@@ -785,7 +811,7 @@ async function startLogs() {
|
|
|
785
811
|
const f = sources()[u.searchParams.get("source")];
|
|
786
812
|
if (!f) return json(res, { ok: false });
|
|
787
813
|
const parsed = tail(f, 5000).map((l) => parseLine(l));
|
|
788
|
-
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.
|
|
814
|
+
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.isBot).length, attacks: parsed.filter((x) => x && x.isAttack).length });
|
|
789
815
|
}
|
|
790
816
|
// add/remove a source ("add servers") — written to .volt/logs.json
|
|
791
817
|
if (req.method === "POST" && (p === "/api/source" || p === "/api/source/remove")) {
|
|
@@ -323,6 +323,8 @@ async function genToken() {
|
|
|
323
323
|
const items = signal({ pages: [], posts: [] });
|
|
324
324
|
const editing = signal(null); // { type, slug, title, isNew } — set only on open/save/close
|
|
325
325
|
let ed = null; // live RTEPro instance for the open editor
|
|
326
|
+
let themeCss = ""; // active theme's CSS, so the editor renders pages themed
|
|
327
|
+
fetch("/setup/theme-css").then((r) => r.text()).then((c) => { themeCss = c; }).catch(() => {});
|
|
326
328
|
const loadItems = async () => items(await (await fetch("/setup/content")).json());
|
|
327
329
|
// raw .md → { title, body, isHtml }; RTEPro takes markdown directly (setMarkdown),
|
|
328
330
|
// so no markdown library is needed.
|
|
@@ -333,7 +335,7 @@ function parseDoc(raw) {
|
|
|
333
335
|
return { title, body: fm ? raw.slice(fm[0].length) : raw, isHtml: /^format:\s*html\s*$/m.test(front) };
|
|
334
336
|
}
|
|
335
337
|
function mountEditor(doc) {
|
|
336
|
-
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic" });
|
|
338
|
+
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic", exportCSS: themeCss });
|
|
337
339
|
if (doc && doc.isHtml) ed.setHTML(doc.body || "");
|
|
338
340
|
else ed.setMarkdown((doc && doc.body) || "");
|
|
339
341
|
}
|
package/templates/docs/server.js
CHANGED
|
@@ -440,6 +440,32 @@ function startSetup() {
|
|
|
440
440
|
});
|
|
441
441
|
return;
|
|
442
442
|
}
|
|
443
|
+
// --- active theme's CSS, so the in-config editor renders pages themed ---
|
|
444
|
+
if (req.method === "GET" && p === "/setup/theme-css") {
|
|
445
|
+
res.setHeader("Content-Type", "text/css; charset=utf-8");
|
|
446
|
+
(async () => {
|
|
447
|
+
const theme = (readEnvFile().THEME || "").trim();
|
|
448
|
+
const load = async (rel) => {
|
|
449
|
+
try {
|
|
450
|
+
return (await imp(rel)).css || "";
|
|
451
|
+
} catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
};
|
|
455
|
+
let css = null;
|
|
456
|
+
if (theme) css = await load(path.join(".volt", "themes", theme, "index.js"));
|
|
457
|
+
if (css == null && fs.existsSync(path.join(__dirname, "pages", "_theme.js"))) css = await load(path.join("pages", "_theme.js"));
|
|
458
|
+
if (css == null && theme) {
|
|
459
|
+
try {
|
|
460
|
+
css = (await import(`volt-theme-${theme}`)).css || "";
|
|
461
|
+
} catch {
|
|
462
|
+
css = null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
res.end(css || "");
|
|
466
|
+
})();
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
443
469
|
// --- content manager: list / read / write / delete pages + posts ---
|
|
444
470
|
if (req.method === "GET" && p === "/setup/content") {
|
|
445
471
|
const list = (type) => {
|
|
@@ -785,7 +811,7 @@ async function startLogs() {
|
|
|
785
811
|
const f = sources()[u.searchParams.get("source")];
|
|
786
812
|
if (!f) return json(res, { ok: false });
|
|
787
813
|
const parsed = tail(f, 5000).map((l) => parseLine(l));
|
|
788
|
-
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.
|
|
814
|
+
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.isBot).length, attacks: parsed.filter((x) => x && x.isAttack).length });
|
|
789
815
|
}
|
|
790
816
|
// add/remove a source ("add servers") — written to .volt/logs.json
|
|
791
817
|
if (req.method === "POST" && (p === "/api/source" || p === "/api/source/remove")) {
|
|
@@ -323,6 +323,8 @@ async function genToken() {
|
|
|
323
323
|
const items = signal({ pages: [], posts: [] });
|
|
324
324
|
const editing = signal(null); // { type, slug, title, isNew } — set only on open/save/close
|
|
325
325
|
let ed = null; // live RTEPro instance for the open editor
|
|
326
|
+
let themeCss = ""; // active theme's CSS, so the editor renders pages themed
|
|
327
|
+
fetch("/setup/theme-css").then((r) => r.text()).then((c) => { themeCss = c; }).catch(() => {});
|
|
326
328
|
const loadItems = async () => items(await (await fetch("/setup/content")).json());
|
|
327
329
|
// raw .md → { title, body, isHtml }; RTEPro takes markdown directly (setMarkdown),
|
|
328
330
|
// so no markdown library is needed.
|
|
@@ -333,7 +335,7 @@ function parseDoc(raw) {
|
|
|
333
335
|
return { title, body: fm ? raw.slice(fm[0].length) : raw, isHtml: /^format:\s*html\s*$/m.test(front) };
|
|
334
336
|
}
|
|
335
337
|
function mountEditor(doc) {
|
|
336
|
-
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic" });
|
|
338
|
+
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic", exportCSS: themeCss });
|
|
337
339
|
if (doc && doc.isHtml) ed.setHTML(doc.body || "");
|
|
338
340
|
else ed.setMarkdown((doc && doc.body) || "");
|
|
339
341
|
}
|
|
@@ -466,6 +466,32 @@ function startSetup() {
|
|
|
466
466
|
});
|
|
467
467
|
return;
|
|
468
468
|
}
|
|
469
|
+
// --- active theme's CSS, so the in-config editor renders pages themed ---
|
|
470
|
+
if (req.method === "GET" && p === "/setup/theme-css") {
|
|
471
|
+
res.setHeader("Content-Type", "text/css; charset=utf-8");
|
|
472
|
+
(async () => {
|
|
473
|
+
const theme = (readEnvFile().THEME || "").trim();
|
|
474
|
+
const load = async (rel) => {
|
|
475
|
+
try {
|
|
476
|
+
return (await imp(rel)).css || "";
|
|
477
|
+
} catch {
|
|
478
|
+
return null;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
let css = null;
|
|
482
|
+
if (theme) css = await load(path.join(".volt", "themes", theme, "index.js"));
|
|
483
|
+
if (css == null && fs.existsSync(path.join(__dirname, "pages", "_theme.js"))) css = await load(path.join("pages", "_theme.js"));
|
|
484
|
+
if (css == null && theme) {
|
|
485
|
+
try {
|
|
486
|
+
css = (await import(`volt-theme-${theme}`)).css || "";
|
|
487
|
+
} catch {
|
|
488
|
+
css = null;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
res.end(css || "");
|
|
492
|
+
})();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
469
495
|
// --- content manager: list / read / write / delete pages + posts ---
|
|
470
496
|
if (req.method === "GET" && p === "/setup/content") {
|
|
471
497
|
const list = (type) => {
|
|
@@ -811,7 +837,7 @@ async function startLogs() {
|
|
|
811
837
|
const f = sources()[u.searchParams.get("source")];
|
|
812
838
|
if (!f) return json(res, { ok: false });
|
|
813
839
|
const parsed = tail(f, 5000).map((l) => parseLine(l));
|
|
814
|
-
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.
|
|
840
|
+
return json(res, { ok: true, total: parsed.length, paths: top(parsed, "path"), statuses: top(parsed, "status"), ips: top(parsed, "ip"), bots: parsed.filter((x) => x && x.isBot).length, attacks: parsed.filter((x) => x && x.isAttack).length });
|
|
815
841
|
}
|
|
816
842
|
// add/remove a source ("add servers") — written to .volt/logs.json
|
|
817
843
|
if (req.method === "POST" && (p === "/api/source" || p === "/api/source/remove")) {
|
|
@@ -323,6 +323,8 @@ async function genToken() {
|
|
|
323
323
|
const items = signal({ pages: [], posts: [] });
|
|
324
324
|
const editing = signal(null); // { type, slug, title, isNew } — set only on open/save/close
|
|
325
325
|
let ed = null; // live RTEPro instance for the open editor
|
|
326
|
+
let themeCss = ""; // active theme's CSS, so the editor renders pages themed
|
|
327
|
+
fetch("/setup/theme-css").then((r) => r.text()).then((c) => { themeCss = c; }).catch(() => {});
|
|
326
328
|
const loadItems = async () => items(await (await fetch("/setup/content")).json());
|
|
327
329
|
// raw .md → { title, body, isHtml }; RTEPro takes markdown directly (setMarkdown),
|
|
328
330
|
// so no markdown library is needed.
|
|
@@ -333,7 +335,7 @@ function parseDoc(raw) {
|
|
|
333
335
|
return { title, body: fm ? raw.slice(fm[0].length) : raw, isHtml: /^format:\s*html\s*$/m.test(front) };
|
|
334
336
|
}
|
|
335
337
|
function mountEditor(doc) {
|
|
336
|
-
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic" });
|
|
338
|
+
ed = window.RTEPro.init("#mg-editor", { height: "60vh", placeholder: "Write…", aiProxy: "/setup/ai", aiProvider: state().aiProvider || "anthropic", exportCSS: themeCss });
|
|
337
339
|
if (doc && doc.isHtml) ed.setHTML(doc.body || "");
|
|
338
340
|
else ed.setMarkdown((doc && doc.body) || "");
|
|
339
341
|
}
|