create-volt 0.3.2 → 0.8.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.
Files changed (44) hide show
  1. package/CHANGELOG.md +120 -0
  2. package/README.md +49 -0
  3. package/addons/auth/files/lib/auth.js +121 -0
  4. package/addons/auth/meta.json +8 -0
  5. package/addons/db/files/lib/store.js +42 -0
  6. package/addons/db/files/lib/stores/memory.js +33 -0
  7. package/addons/db/files/lib/stores/mongo.js +43 -0
  8. package/addons/db/files/lib/stores/sql.js +79 -0
  9. package/addons/db/meta.json +12 -0
  10. package/addons/mailer/files/lib/mailer.js +33 -0
  11. package/addons/mailer/meta.json +10 -0
  12. package/addons/realtime/files/lib/realtime.js +78 -0
  13. package/addons/realtime/files/public/chat-client.js +30 -0
  14. package/addons/realtime/meta.json +8 -0
  15. package/config/config-app.js +168 -0
  16. package/config/index.html +29 -0
  17. package/index.js +242 -16
  18. package/package.json +5 -2
  19. package/{template → templates/default}/README.md +20 -1
  20. package/templates/default/server.js +213 -0
  21. package/templates/default/setup/index.html +27 -0
  22. package/templates/default/setup/setup.js +121 -0
  23. package/{template → templates/default}/views/index.html +3 -0
  24. package/templates/guestbook/README.md +71 -0
  25. package/templates/guestbook/gitignore +5 -0
  26. package/templates/guestbook/lib/auth.js +63 -0
  27. package/templates/guestbook/lib/mailer.js +39 -0
  28. package/templates/guestbook/lib/store.js +43 -0
  29. package/templates/guestbook/lib/stores/memory.js +49 -0
  30. package/templates/guestbook/lib/stores/mongo.js +68 -0
  31. package/templates/guestbook/lib/stores/sql.js +120 -0
  32. package/templates/guestbook/package.json +21 -0
  33. package/templates/guestbook/public/app.js +112 -0
  34. package/templates/guestbook/public/volt.js +265 -0
  35. package/templates/guestbook/router.js +110 -0
  36. package/templates/guestbook/server.js +48 -0
  37. package/templates/guestbook/views/confirm.html +53 -0
  38. package/templates/guestbook/views/index.html +38 -0
  39. package/templates/guestbook/views/partials/header.html +4 -0
  40. package/template/server.js +0 -64
  41. /package/{template → templates/default}/gitignore +0 -0
  42. /package/{template → templates/default}/package.json +0 -0
  43. /package/{template → templates/default}/public/app.js +0 -0
  44. /package/{template → templates/default}/public/volt.js +0 -0
@@ -0,0 +1,48 @@
1
+ // server.js — guestbook dev/prod server. Express serves the static client and
2
+ // the views; Socket.io pushes new messages live; storage is pluggable
3
+ // (memory | mongodb | mysql | postgres) via the DB_DRIVER env var.
4
+ //
5
+ // npm run dev # in-memory store, magic links to console
6
+ // DB_DRIVER=postgres DATABASE_URL=... npm start
7
+ //
8
+ // Run under PM2 in production: pm2 start server.js --name guestbook ; pm2 log
9
+
10
+ import http from "node:http";
11
+ import fs from "node:fs";
12
+ import path from "node:path";
13
+ import { fileURLToPath } from "node:url";
14
+ import express from "express";
15
+ import { Server as SocketServer } from "socket.io";
16
+ import { createStore } from "./lib/store.js";
17
+ import { createMailer } from "./lib/mailer.js";
18
+ import { createRouter } from "./router.js";
19
+
20
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
+
22
+ // Auto-load .env (no --env-file flag needed; works the same on Windows). Never
23
+ // overrides a variable already set in the environment.
24
+ const ENV_PATH = path.join(__dirname, ".env");
25
+ if (fs.existsSync(ENV_PATH)) {
26
+ for (const line of fs.readFileSync(ENV_PATH, "utf8").split("\n")) {
27
+ const m = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.*?)\s*$/);
28
+ if (m && !(m[1] in process.env)) process.env[m[1]] = m[2];
29
+ }
30
+ }
31
+
32
+ const PORT = Number(process.env.PORT) || 26629;
33
+
34
+ const store = await createStore();
35
+ const mailer = await createMailer();
36
+
37
+ const app = express();
38
+ app.use(express.static(path.join(__dirname, "public")));
39
+
40
+ const server = http.createServer(app);
41
+ const io = new SocketServer(server);
42
+
43
+ app.use("/", createRouter({ store, mailer, io }));
44
+
45
+ server.listen(PORT, () => {
46
+ console.log(`📖 Guestbook → http://localhost:${PORT}`);
47
+ console.log(` storage: ${store.name} mail: ${mailer.name}`);
48
+ });
@@ -0,0 +1,53 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Confirm login — Guestbook</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <style>
9
+ body { background: #0f1115; color: #e7e9ee; }
10
+ .card-x { background: #161a22; border: 1px solid #232a36; border-radius: 14px; }
11
+ .accent { color: #ffd24a; }
12
+ a { color: #ffd24a; }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <main class="container py-5" style="max-width: 480px;">
17
+ <div class="card-x p-4 text-center">
18
+ <h1 class="h4 mb-3"><span class="accent">📖 Guestbook</span></h1>
19
+ <p class="text-muted">Click below to finish signing in. The link must be opened in the same browser you requested it from.</p>
20
+ <button id="confirm" class="btn btn-primary w-100">Confirm login</button>
21
+ <p id="status" class="mt-3 mb-0 small text-muted"></p>
22
+ </div>
23
+ </main>
24
+
25
+ <script>
26
+ const token = "{{TOKEN}}";
27
+ const btn = document.getElementById("confirm");
28
+ const status = document.getElementById("status");
29
+ btn.addEventListener("click", async () => {
30
+ btn.disabled = true;
31
+ status.textContent = "Confirming…";
32
+ try {
33
+ const res = await fetch("/api/confirm", {
34
+ method: "POST",
35
+ headers: { "Content-Type": "application/json" },
36
+ body: JSON.stringify({ token }),
37
+ });
38
+ const data = await res.json();
39
+ if (data.ok) {
40
+ status.textContent = "Signed in! Redirecting…";
41
+ location.href = "/";
42
+ } else {
43
+ status.textContent = data.error || "Could not sign in.";
44
+ btn.disabled = false;
45
+ }
46
+ } catch {
47
+ status.textContent = "Network error — try again.";
48
+ btn.disabled = false;
49
+ }
50
+ });
51
+ </script>
52
+ </body>
53
+ </html>
@@ -0,0 +1,38 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Guestbook — built with Volt</title>
7
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" />
8
+ <style>
9
+ body { background: #0f1115; color: #e7e9ee; }
10
+ .brand { font-weight: 800; letter-spacing: -.02em; }
11
+ .accent { color: #ffd24a; }
12
+ .card-x { background: #161a22; border: 1px solid #232a36; border-radius: 14px; }
13
+ .form-control, .input-group-text { background: #0f1115; color: #e7e9ee; border-color: #232a36; }
14
+ .form-control:focus { background: #0f1115; color: #e7e9ee; border-color: #ffd24a; box-shadow: none; }
15
+ .msg { border-bottom: 1px solid #232a36; }
16
+ .msg:last-child { border-bottom: 0; }
17
+ .who { color: #ffd24a; font-weight: 600; }
18
+ a { color: #ffd24a; }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <main class="container py-5" style="max-width: 680px;">
23
+ <!--#include header-->
24
+
25
+ <div id="app"></div>
26
+
27
+ <footer class="text-center text-muted mt-5">
28
+ <small>Real-time via Socket.io · magic-link auth · pluggable storage.</small>
29
+ <div class="mt-2">
30
+ <small><a href="https://github.com/MIR-2025/volt#readme" target="_blank" rel="noopener">📖 How to build a Volt app →</a></small>
31
+ </div>
32
+ </footer>
33
+ </main>
34
+
35
+ <script src="/socket.io/socket.io.js"></script>
36
+ <script type="module" src="/app.js"></script>
37
+ </body>
38
+ </html>
@@ -0,0 +1,4 @@
1
+ <header class="text-center mb-4">
2
+ <h1 class="brand display-6"><span class="accent">📖 Guestbook</span></h1>
3
+ <p class="text-muted mb-0">Sign in with a magic link and leave a message — new posts appear live for everyone.</p>
4
+ </header>
@@ -1,64 +0,0 @@
1
- // server.js — minimal dev server for a Volt app. Express serves the static
2
- // client and the index view; Socket.io carries the hot-reload signal; a file
3
- // watcher on views/ and public/ broadcasts a (debounced) reload on any save.
4
- //
5
- // Cross-platform: paths are resolved relative to this file, and the watcher
6
- // falls back to a manual recursive walk where native recursive fs.watch is
7
- // unavailable (older Linux Node builds).
8
- //
9
- // npm run dev # start the dev server
10
- // PORT=4000 npm run dev # override the port
11
-
12
- import http from "node:http";
13
- import fs from "node:fs";
14
- import path from "node:path";
15
- import { fileURLToPath } from "node:url";
16
- import express from "express";
17
- import { Server as SocketServer } from "socket.io";
18
-
19
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
- const PORT = Number(process.env.PORT) || 26628;
21
-
22
- const app = express();
23
- app.use(express.static(path.join(__dirname, "public")));
24
- app.get("/", (_req, res) => res.sendFile(path.join(__dirname, "views", "index.html")));
25
-
26
- const server = http.createServer(app);
27
- const io = new SocketServer(server);
28
-
29
- // --- Hot reload: watch views/ + public/, debounce bursts, broadcast a reload ---
30
- const watchDirs = ["views", "public"].map((d) => path.join(__dirname, d));
31
- let timer = null;
32
- function onChange(file) {
33
- clearTimeout(timer);
34
- timer = setTimeout(() => {
35
- console.log(`[volt] change: ${file ?? "?"} → reload`);
36
- io.emit("volt:reload");
37
- }, 80);
38
- }
39
-
40
- // Watch a directory recursively. Tries native recursive fs.watch first; if the
41
- // platform/runtime doesn't support it, walks the tree and watches each dir.
42
- function watchRecursive(dir) {
43
- try {
44
- fs.watch(dir, { recursive: true }, (_event, file) => onChange(file));
45
- return;
46
- } catch {
47
- // Native recursive watch unsupported — fall back to per-directory watchers.
48
- }
49
- const watchDir = (d) => {
50
- try {
51
- fs.watch(d, (_event, file) => onChange(file));
52
- } catch {
53
- /* directory vanished between walk and watch — ignore */
54
- }
55
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
56
- if (entry.isDirectory()) watchDir(path.join(d, entry.name));
57
- }
58
- };
59
- watchDir(dir);
60
- }
61
-
62
- for (const dir of watchDirs) watchRecursive(dir);
63
-
64
- server.listen(PORT, () => console.log(`⚡ Volt dev server → http://localhost:${PORT}`));
File without changes
File without changes
File without changes
File without changes