create-volt 0.56.0 → 0.56.1

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,15 @@ 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.56.1] - 2026-07-05
8
+
9
+ ### Fixed
10
+ - **Media library thumbnails / editor image previews showed as broken inside the
11
+ config.** The config server served a fixed asset allowlist and had no `/media/`
12
+ route, so uploaded files 404'd in the config UI — even though they uploaded fine
13
+ and render on the live site (the running app serves them via `express.static`).
14
+ The config now serves `public/media/<name>` with a path-traversal guard.
15
+
7
16
  ## [0.56.0] - 2026-07-05
8
17
 
9
18
  ### Added
@@ -745,6 +754,7 @@ All notable changes to `create-volt` are documented here. The format follows
745
754
  watching and full-page hot reload. Supports `--skip-install` and `--force`,
746
755
  and auto-detects npm / pnpm / yarn / bun for the install step.
747
756
 
757
+ [0.56.1]: https://github.com/MIR-2025/volt/releases/tag/v0.56.1
748
758
  [0.56.0]: https://github.com/MIR-2025/volt/releases/tag/v0.56.0
749
759
  [0.55.1]: https://github.com/MIR-2025/volt/releases/tag/v0.55.1
750
760
  [0.55.0]: https://github.com/MIR-2025/volt/releases/tag/v0.55.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-volt",
3
- "version": "0.56.0",
3
+ "version": "0.56.1",
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": {
@@ -313,6 +313,20 @@ function startSetup() {
313
313
  res.setHeader("Content-Type", assets[p][0]);
314
314
  return res.end(assets[p][1]);
315
315
  }
316
+ // Serve uploaded media so library thumbnails + editor previews render inside the
317
+ // config (the running app serves these via express.static; the config didn't).
318
+ if (req.method === "GET" && p.startsWith("/media/")) {
319
+ const base = path.join(__dirname, "public", "media");
320
+ const f = path.resolve(base, decodeURIComponent(p.slice("/media/".length)));
321
+ if ((f === base || f.startsWith(base + path.sep)) && fs.existsSync(f) && fs.statSync(f).isFile()) {
322
+ const ext = (f.split(".").pop() || "").toLowerCase();
323
+ const mime = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", svg: "image/svg+xml", bmp: "image/bmp", ico: "image/x-icon", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", ogv: "video/ogg", m4v: "video/x-m4v", ogg: "audio/ogg", mp3: "audio/mpeg", wav: "audio/wav" }[ext] || "application/octet-stream";
324
+ res.setHeader("Content-Type", mime);
325
+ return res.end(fs.readFileSync(f));
326
+ }
327
+ res.statusCode = 404;
328
+ return res.end("not found");
329
+ }
316
330
  if (req.method === "GET" && p === "/setup/state") {
317
331
  res.setHeader("Content-Type", "application/json");
318
332
  return res.end(JSON.stringify({ available: availableAddons(), themes: availableThemes(), current: readEnvFile(), defaultPort: DEFAULT_PORT, configDefaultPort: CONFIG_DEFAULT_PORT }));
@@ -313,6 +313,20 @@ function startSetup() {
313
313
  res.setHeader("Content-Type", assets[p][0]);
314
314
  return res.end(assets[p][1]);
315
315
  }
316
+ // Serve uploaded media so library thumbnails + editor previews render inside the
317
+ // config (the running app serves these via express.static; the config didn't).
318
+ if (req.method === "GET" && p.startsWith("/media/")) {
319
+ const base = path.join(__dirname, "public", "media");
320
+ const f = path.resolve(base, decodeURIComponent(p.slice("/media/".length)));
321
+ if ((f === base || f.startsWith(base + path.sep)) && fs.existsSync(f) && fs.statSync(f).isFile()) {
322
+ const ext = (f.split(".").pop() || "").toLowerCase();
323
+ const mime = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", svg: "image/svg+xml", bmp: "image/bmp", ico: "image/x-icon", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", ogv: "video/ogg", m4v: "video/x-m4v", ogg: "audio/ogg", mp3: "audio/mpeg", wav: "audio/wav" }[ext] || "application/octet-stream";
324
+ res.setHeader("Content-Type", mime);
325
+ return res.end(fs.readFileSync(f));
326
+ }
327
+ res.statusCode = 404;
328
+ return res.end("not found");
329
+ }
316
330
  if (req.method === "GET" && p === "/setup/state") {
317
331
  res.setHeader("Content-Type", "application/json");
318
332
  return res.end(JSON.stringify({ available: availableAddons(), themes: availableThemes(), current: readEnvFile(), defaultPort: DEFAULT_PORT, configDefaultPort: CONFIG_DEFAULT_PORT }));
@@ -313,6 +313,20 @@ function startSetup() {
313
313
  res.setHeader("Content-Type", assets[p][0]);
314
314
  return res.end(assets[p][1]);
315
315
  }
316
+ // Serve uploaded media so library thumbnails + editor previews render inside the
317
+ // config (the running app serves these via express.static; the config didn't).
318
+ if (req.method === "GET" && p.startsWith("/media/")) {
319
+ const base = path.join(__dirname, "public", "media");
320
+ const f = path.resolve(base, decodeURIComponent(p.slice("/media/".length)));
321
+ if ((f === base || f.startsWith(base + path.sep)) && fs.existsSync(f) && fs.statSync(f).isFile()) {
322
+ const ext = (f.split(".").pop() || "").toLowerCase();
323
+ const mime = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", svg: "image/svg+xml", bmp: "image/bmp", ico: "image/x-icon", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", ogv: "video/ogg", m4v: "video/x-m4v", ogg: "audio/ogg", mp3: "audio/mpeg", wav: "audio/wav" }[ext] || "application/octet-stream";
324
+ res.setHeader("Content-Type", mime);
325
+ return res.end(fs.readFileSync(f));
326
+ }
327
+ res.statusCode = 404;
328
+ return res.end("not found");
329
+ }
316
330
  if (req.method === "GET" && p === "/setup/state") {
317
331
  res.setHeader("Content-Type", "application/json");
318
332
  return res.end(JSON.stringify({ available: availableAddons(), themes: availableThemes(), current: readEnvFile(), defaultPort: DEFAULT_PORT, configDefaultPort: CONFIG_DEFAULT_PORT }));
@@ -313,6 +313,20 @@ function startSetup() {
313
313
  res.setHeader("Content-Type", assets[p][0]);
314
314
  return res.end(assets[p][1]);
315
315
  }
316
+ // Serve uploaded media so library thumbnails + editor previews render inside the
317
+ // config (the running app serves these via express.static; the config didn't).
318
+ if (req.method === "GET" && p.startsWith("/media/")) {
319
+ const base = path.join(__dirname, "public", "media");
320
+ const f = path.resolve(base, decodeURIComponent(p.slice("/media/".length)));
321
+ if ((f === base || f.startsWith(base + path.sep)) && fs.existsSync(f) && fs.statSync(f).isFile()) {
322
+ const ext = (f.split(".").pop() || "").toLowerCase();
323
+ const mime = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", svg: "image/svg+xml", bmp: "image/bmp", ico: "image/x-icon", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", ogv: "video/ogg", m4v: "video/x-m4v", ogg: "audio/ogg", mp3: "audio/mpeg", wav: "audio/wav" }[ext] || "application/octet-stream";
324
+ res.setHeader("Content-Type", mime);
325
+ return res.end(fs.readFileSync(f));
326
+ }
327
+ res.statusCode = 404;
328
+ return res.end("not found");
329
+ }
316
330
  if (req.method === "GET" && p === "/setup/state") {
317
331
  res.setHeader("Content-Type", "application/json");
318
332
  return res.end(JSON.stringify({ available: availableAddons(), themes: availableThemes(), current: readEnvFile(), defaultPort: DEFAULT_PORT, configDefaultPort: CONFIG_DEFAULT_PORT }));
@@ -338,6 +338,20 @@ function startSetup() {
338
338
  res.setHeader("Content-Type", assets[p][0]);
339
339
  return res.end(assets[p][1]);
340
340
  }
341
+ // Serve uploaded media so library thumbnails + editor previews render inside the
342
+ // config (the running app serves these via express.static; the config didn't).
343
+ if (req.method === "GET" && p.startsWith("/media/")) {
344
+ const base = path.join(__dirname, "public", "media");
345
+ const f = path.resolve(base, decodeURIComponent(p.slice("/media/".length)));
346
+ if ((f === base || f.startsWith(base + path.sep)) && fs.existsSync(f) && fs.statSync(f).isFile()) {
347
+ const ext = (f.split(".").pop() || "").toLowerCase();
348
+ const mime = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", webp: "image/webp", avif: "image/avif", svg: "image/svg+xml", bmp: "image/bmp", ico: "image/x-icon", mp4: "video/mp4", webm: "video/webm", mov: "video/quicktime", ogv: "video/ogg", m4v: "video/x-m4v", ogg: "audio/ogg", mp3: "audio/mpeg", wav: "audio/wav" }[ext] || "application/octet-stream";
349
+ res.setHeader("Content-Type", mime);
350
+ return res.end(fs.readFileSync(f));
351
+ }
352
+ res.statusCode = 404;
353
+ return res.end("not found");
354
+ }
341
355
  if (req.method === "GET" && p === "/setup/state") {
342
356
  res.setHeader("Content-Type", "application/json");
343
357
  return res.end(JSON.stringify({ available: availableAddons(), themes: availableThemes(), current: readEnvFile(), defaultPort: DEFAULT_PORT, configDefaultPort: CONFIG_DEFAULT_PORT }));