create-volt 0.11.0 → 0.13.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.
@@ -0,0 +1,27 @@
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>Set up your Volt app</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
+ .accent { color: #ffd24a; }
11
+ .card-x { background: #161a22; border: 1px solid #232a36; border-radius: 14px; }
12
+ .form-control, .form-select { background: #0f1115; color: #e7e9ee; border-color: #232a36; }
13
+ .form-control:focus, .form-select:focus { background: #0f1115; color: #e7e9ee; border-color: #ffd24a; box-shadow: none; }
14
+ code { color: #ffd24a; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <main class="container py-5" style="max-width: 640px;">
19
+ <header class="mb-4">
20
+ <h1 class="h3"><span class="accent">⚡ Set up your Volt app</span></h1>
21
+ <p class="text-muted mb-0">Fill these in and the app starts. This page is disposable — it disappears once you click Apply. Re-open it anytime with <code>npm run dev -- --edit</code>.</p>
22
+ </header>
23
+ <div id="app"></div>
24
+ </main>
25
+ <script type="module" src="/setup.js"></script>
26
+ </body>
27
+ </html>
@@ -0,0 +1,163 @@
1
+ // setup.js — first-run / --edit wizard, built with Volt. Tick add-ons + fill
2
+ // settings → writes .env (a VOLT_ADDONS list + settings), adds any needed
3
+ // packages, installs, and starts the app. Add-on code is bundled; enabling is
4
+ // just config.
5
+ import { signal, computed, html, mount } from "/volt.js";
6
+
7
+ const { available, current, defaultPort } = await (await fetch("/setup/state")).json();
8
+ const depsOf = Object.fromEntries(available.map((a) => [a.name, a.dependsOn || []]));
9
+ const order = available.map((a) => a.name);
10
+ const enabledNow = new Set(String(current.VOLT_ADDONS || "").split(",").map((s) => s.trim()).filter(Boolean));
11
+
12
+ const state = signal({
13
+ addons: Object.fromEntries(available.map((a) => [a.name, enabledNow.has(a.name)])),
14
+ dbDriver: current.DB_DRIVER || "memory",
15
+ mongoUri: current.MONGODB_URI || "",
16
+ mongoDb: current.MONGODB_DATABASE || "",
17
+ dbUrl: current.DATABASE_URL || "",
18
+ smtpUrl: current.SMTP_URL || "",
19
+ mailFrom: current.MAIL_FROM || "",
20
+ adminEmails: current.ADMIN_EMAILS || "",
21
+ port: current.PORT || String(defaultPort),
22
+ });
23
+ const set = (patch) => state({ ...state(), ...patch });
24
+ const toggle = (n) => state({ ...state(), addons: { ...state().addons, [n]: !state().addons[n] } });
25
+ const status = signal("");
26
+
27
+ // selected add-ons, dependencies expanded, in display order
28
+ function effective(s) {
29
+ const want = new Set();
30
+ const visit = (n) => {
31
+ if (want.has(n)) return;
32
+ want.add(n);
33
+ (depsOf[n] || []).forEach(visit);
34
+ };
35
+ for (const n of order) if (s.addons[n]) visit(n);
36
+ return order.filter((n) => want.has(n));
37
+ }
38
+
39
+ const clean = (v) => String(v).replace(/[\r\n]/g, "").trim(); // one value per line; no injection
40
+ function genEnv(s) {
41
+ const eff = effective(s);
42
+ const out = [`VOLT_ADDONS=${eff.join(",")}`, `PORT=${clean(s.port)}`];
43
+ if (eff.includes("db")) {
44
+ out.push(`DB_DRIVER=${clean(s.dbDriver)}`);
45
+ if (s.dbDriver === "mongodb") {
46
+ out.push(`MONGODB_URI=${clean(s.mongoUri)}`);
47
+ if (s.mongoDb) out.push(`MONGODB_DATABASE=${clean(s.mongoDb)}`);
48
+ } else if (s.dbDriver === "mysql" || s.dbDriver === "postgres") {
49
+ out.push(`DATABASE_URL=${clean(s.dbUrl)}`);
50
+ }
51
+ }
52
+ if (eff.includes("mailer")) {
53
+ if (s.smtpUrl) out.push(`SMTP_URL=${clean(s.smtpUrl)}`);
54
+ else out.push("# SMTP_URL= # unset → emails print to the console");
55
+ if (s.mailFrom) out.push(`MAIL_FROM=${clean(s.mailFrom)}`);
56
+ }
57
+ if (eff.includes("admin")) out.push(`ADMIN_EMAILS=${clean(s.adminEmails)}`);
58
+ return out.join("\n") + "\n";
59
+ }
60
+ const env = computed(() => genEnv(state()));
61
+ const eff = computed(() => effective(state()));
62
+
63
+ async function testDb() {
64
+ const s = state();
65
+ const e = { DB_DRIVER: s.dbDriver };
66
+ if (s.dbDriver === "mongodb") {
67
+ e.MONGODB_URI = s.mongoUri;
68
+ e.MONGODB_DATABASE = s.mongoDb;
69
+ } else if (s.dbDriver === "mysql" || s.dbDriver === "postgres") {
70
+ e.DATABASE_URL = s.dbUrl;
71
+ }
72
+ status("Testing connection…");
73
+ try {
74
+ const r = await (await fetch("/setup/test-db", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ env: e }) })).json();
75
+ status(r.ok ? `✓ Connected (${r.driver}).` : `✗ ${r.error}`);
76
+ } catch {
77
+ status("Network error testing connection.");
78
+ }
79
+ }
80
+
81
+ async function apply() {
82
+ status("Saving…");
83
+ let d;
84
+ try {
85
+ d = await (await fetch("/setup/apply", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ addons: eff(), env: env() }) })).json();
86
+ } catch {
87
+ return status("Network error.");
88
+ }
89
+ if (!d.ok) return status("Error: " + d.error);
90
+ status(d.installing?.length ? `Installing ${d.installing.join(", ")}, then starting…` : "Starting the app…");
91
+ const target = `http://localhost:${d.port}/`;
92
+ const tries = d.installing?.length ? 90 : 20; // npm install can take a while
93
+ const go = async (n) => {
94
+ try {
95
+ await fetch(target, { mode: "no-cors" });
96
+ location.href = target;
97
+ } catch {
98
+ if (n > 0) setTimeout(() => go(n - 1), 500);
99
+ else location.href = target;
100
+ }
101
+ };
102
+ setTimeout(() => go(tries), 600);
103
+ }
104
+
105
+ // --- views ---
106
+ const field = (label, key, placeholder = "") =>
107
+ html`<div class="mb-2">
108
+ <label class="form-label small mb-1">${label}</label>
109
+ <input class="form-control" placeholder=${placeholder} value=${() => state()[key]} oninput=${(e) => set({ [key]: e.target.value })} />
110
+ </div>`;
111
+
112
+ const addonRow = (a) =>
113
+ html`<div class="form-check mb-2">
114
+ <input class="form-check-input" type="checkbox" id=${"x-" + a.name} checked=${() => state().addons[a.name]} onchange=${() => toggle(a.name)} />
115
+ <label class="form-check-label" for=${"x-" + a.name}>
116
+ <span class="accent">${a.name}</span>${a.dependsOn?.length ? html` <span class="text-muted small">(needs ${a.dependsOn.join(", ")})</span>` : ""}
117
+ <div class="small text-muted">${a.description}</div>
118
+ </label>
119
+ </div>`;
120
+
121
+ const dbSettings = () =>
122
+ html`<div class="mb-2">
123
+ <label class="form-label small mb-1">Database (DB_DRIVER)</label>
124
+ <select class="form-select" value=${() => state().dbDriver} onchange=${(e) => set({ dbDriver: e.target.value })}>
125
+ <option value="memory">memory (no setup)</option>
126
+ <option value="mongodb">mongodb</option>
127
+ <option value="mysql">mysql</option>
128
+ <option value="postgres">postgres</option>
129
+ </select>
130
+ </div>
131
+ ${() =>
132
+ state().dbDriver === "mongodb"
133
+ ? html`${field("MONGODB_URI", "mongoUri", "mongodb://user:pass@host:27017/db")}${field("MONGODB_DATABASE", "mongoDb", "db")}`
134
+ : state().dbDriver === "mysql" || state().dbDriver === "postgres"
135
+ ? field("DATABASE_URL", "dbUrl", state().dbDriver + "://user:pass@host/db")
136
+ : null}
137
+ ${() => (state().dbDriver !== "memory" ? html`<button class="btn btn-sm btn-outline-secondary mb-2" onclick=${testDb}>Test connection</button>` : null)}`;
138
+
139
+ mount(
140
+ "#app",
141
+ available.length
142
+ ? html`<div class="card-x p-4 mb-3">
143
+ <h2 class="h6 mb-3">Features</h2>
144
+ ${available.map(addonRow)}
145
+ <p class="small text-muted mb-0">Enabling a feature wires its backend automatically. Frontend UI (login form, chat) is yours to build — or start from <code>--template guestbook</code>.</p>
146
+ </div>`
147
+ : null,
148
+ html`<div class="card-x p-4 mb-3">
149
+ <h2 class="h6 mb-3">Settings</h2>
150
+ ${field("PORT", "port", String(defaultPort))}
151
+ ${() => (eff().includes("db") ? dbSettings() : null)}
152
+ ${() => (eff().includes("mailer") ? html`${field("SMTP_URL (optional)", "smtpUrl", "smtp://user:pass@smtp.host:587")}${field("MAIL_FROM", "mailFrom", "App <no-reply@you.com>")}` : null)}
153
+ ${() => (eff().includes("admin") ? field("ADMIN_EMAILS (comma-separated)", "adminEmails", "you@example.com") : null)}
154
+ </div>`,
155
+ html`<div class="card-x p-4 mb-3">
156
+ <div class="d-flex justify-content-between align-items-center mb-2">
157
+ <h2 class="h6 mb-0">.env</h2>
158
+ <button class="btn btn-primary btn-sm" onclick=${apply}>Apply & start →</button>
159
+ </div>
160
+ <pre class="mb-0" style="background:#0b0d11;border:1px solid #232a36;border-radius:10px;padding:12px;color:#cfe3ff;white-space:pre-wrap">${env}</pre>
161
+ </div>`,
162
+ () => (status() ? html`<p class="small accent">${status}</p>` : null),
163
+ );
@@ -0,0 +1,28 @@
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>Volt Studio</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
+ .accent { color: #ffd24a; }
11
+ .card-x { background: #161a22; border: 1px solid #232a36; border-radius: 14px; }
12
+ </style>
13
+ </head>
14
+ <body>
15
+ <main class="container py-5" style="max-width: 820px;">
16
+ <header class="mb-4">
17
+ <h1 class="h3"><span class="accent">⚡ Volt Studio</span> <small class="text-muted">data browser</small></h1>
18
+ <p class="text-muted mb-0">Browse the database in your <code>.env</code>. Disposable &amp; localhost-only — close the terminal when done.</p>
19
+ </header>
20
+ <div id="app"></div>
21
+ </main>
22
+ <script type="module">
23
+ import { mount } from "/volt.js";
24
+ import { dbAdminPanel } from "/db-admin-ui.js";
25
+ mount("#app", dbAdminPanel());
26
+ </script>
27
+ </body>
28
+ </html>
@@ -0,0 +1,26 @@
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>Volt starter</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, .form-select { background: #0f1115; color: #e7e9ee; border-color: #232a36; }
14
+ .form-control:focus, .form-select:focus { background: #0f1115; color: #e7e9ee; border-color: #ffd24a; box-shadow: none; }
15
+ .navx { background: #11151c; border-bottom: 1px solid #232a36; }
16
+ .navx .btn-link { color: #9aa4b2; text-decoration: none; }
17
+ .navx .btn-link.active { color: #ffd24a; }
18
+ a { color: #ffd24a; }
19
+ </style>
20
+ </head>
21
+ <body>
22
+ <div id="app"></div>
23
+ <script src="/socket.io/socket.io.js"></script>
24
+ <script type="module" src="/app.js"></script>
25
+ </body>
26
+ </html>