create-volt 0.33.0 → 0.35.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,23 @@ 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.35.0] - 2026-06-29
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- **Setup wizard: dependency add-ons now show as checked.** Enabling `auth` pulls
|
|
11
|
+
in `db` + `mailer` (its dependencies), which were added to `VOLT_ADDONS` but
|
|
12
|
+
whose checkboxes stayed *unchecked* — so the generated `.env` looked like it had
|
|
13
|
+
add-ons you never picked. Pulled-in dependencies now render **checked + disabled**
|
|
14
|
+
with a "required by <add-on>" note, so the checkboxes always match `VOLT_ADDONS`.
|
|
15
|
+
|
|
16
|
+
## [0.34.0] - 2026-06-29
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- **Timezone detection.** The setup wizard detects the admin's timezone from
|
|
20
|
+
their browser (`Intl`) and writes `SITE_TZ` to `.env`. The `posts` add-on then
|
|
21
|
+
renders full timestamps in `SITE_TZ` rather than the server's zone (usually UTC
|
|
22
|
+
on a host); date-only values render as that calendar day either way.
|
|
23
|
+
|
|
7
24
|
## [0.33.0] - 2026-06-29
|
|
8
25
|
|
|
9
26
|
### Added
|
|
@@ -446,6 +463,8 @@ All notable changes to `create-volt` are documented here. The format follows
|
|
|
446
463
|
watching and full-page hot reload. Supports `--skip-install` and `--force`,
|
|
447
464
|
and auto-detects npm / pnpm / yarn / bun for the install step.
|
|
448
465
|
|
|
466
|
+
[0.35.0]: https://github.com/MIR-2025/volt/releases/tag/v0.35.0
|
|
467
|
+
[0.34.0]: https://github.com/MIR-2025/volt/releases/tag/v0.34.0
|
|
449
468
|
[0.33.0]: https://github.com/MIR-2025/volt/releases/tag/v0.33.0
|
|
450
469
|
[0.32.0]: https://github.com/MIR-2025/volt/releases/tag/v0.32.0
|
|
451
470
|
[0.31.0]: https://github.com/MIR-2025/volt/releases/tag/v0.31.0
|
|
@@ -17,11 +17,15 @@ const tagsOf = (meta) => String(meta.tags || "").split(",").map((s) => s.trim())
|
|
|
17
17
|
|
|
18
18
|
function fmtDate(d) {
|
|
19
19
|
if (!d) return "";
|
|
20
|
-
|
|
21
|
-
//
|
|
20
|
+
const opts = { year: "numeric", month: "long", day: "numeric" };
|
|
21
|
+
// A date-only value (YYYY-MM-DD) is a calendar day with no timezone — parse it
|
|
22
|
+
// as local midnight and render that day (new Date("2026-06-28") would be UTC →
|
|
23
|
+
// off by a day in negative-offset zones). A full timestamp is rendered in the
|
|
24
|
+
// admin's timezone (SITE_TZ, detected by the setup wizard), not the server's.
|
|
22
25
|
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(String(d).trim());
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
if (m) return new Date(+m[1], +m[2] - 1, +m[3]).toLocaleDateString("en-US", opts);
|
|
27
|
+
const t = new Date(d);
|
|
28
|
+
return isNaN(t.getTime()) ? esc(d) : t.toLocaleDateString("en-US", process.env.SITE_TZ ? { ...opts, timeZone: process.env.SITE_TZ } : opts);
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
function excerpt(p) {
|
package/package.json
CHANGED
|
@@ -25,6 +25,9 @@ const state = signal({
|
|
|
25
25
|
s3Secret: current.S3_SECRET || "",
|
|
26
26
|
s3PublicBase: current.S3_PUBLIC_BASE || "",
|
|
27
27
|
port: current.PORT || String(defaultPort),
|
|
28
|
+
// detect the admin's timezone from their browser (the wizard runs here), so
|
|
29
|
+
// dates render in their zone — not the server's (usually UTC on a host).
|
|
30
|
+
tz: current.SITE_TZ || Intl.DateTimeFormat().resolvedOptions().timeZone || "",
|
|
28
31
|
});
|
|
29
32
|
const set = (patch) => state({ ...state(), ...patch });
|
|
30
33
|
const toggle = (n) => state({ ...state(), addons: { ...state().addons, [n]: !state().addons[n] } });
|
|
@@ -42,10 +45,28 @@ function effective(s) {
|
|
|
42
45
|
return order.filter((n) => want.has(n));
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
// which *enabled* add-ons pull in `name` as a (transitive) dependency
|
|
49
|
+
function requiredBy(s, name) {
|
|
50
|
+
const causes = [];
|
|
51
|
+
for (const n of order) {
|
|
52
|
+
if (n === name || !s.addons[n]) continue;
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
const visit = (x) => {
|
|
55
|
+
if (seen.has(x)) return;
|
|
56
|
+
seen.add(x);
|
|
57
|
+
(depsOf[x] || []).forEach(visit);
|
|
58
|
+
};
|
|
59
|
+
(depsOf[n] || []).forEach(visit);
|
|
60
|
+
if (seen.has(name)) causes.push(n);
|
|
61
|
+
}
|
|
62
|
+
return causes;
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
const clean = (v) => String(v).replace(/[\r\n]/g, "").trim(); // one value per line; no injection
|
|
46
66
|
function genEnv(s) {
|
|
47
67
|
const eff = effective(s);
|
|
48
68
|
const out = [`VOLT_ADDONS=${eff.join(",")}`, `PORT=${clean(s.port)}`];
|
|
69
|
+
if (s.tz) out.push(`SITE_TZ=${clean(s.tz)}`); // admin's timezone, for date display
|
|
49
70
|
if (eff.includes("db")) {
|
|
50
71
|
out.push(`DB_DRIVER=${clean(s.dbDriver)}`);
|
|
51
72
|
if (s.dbDriver === "mongodb") {
|
|
@@ -133,11 +154,18 @@ const field = (label, key, placeholder = "") =>
|
|
|
133
154
|
<input class="form-control" placeholder=${placeholder} value=${() => state()[key]} oninput=${(e) => set({ [key]: e.target.value })} />
|
|
134
155
|
</div>`;
|
|
135
156
|
|
|
157
|
+
// A dependency pulled in by another enabled add-on shows as checked + disabled
|
|
158
|
+
// (you can't turn it off while something needs it), with a "required by" note —
|
|
159
|
+
// so the .env's VOLT_ADDONS always matches what the boxes show.
|
|
136
160
|
const addonRow = (a) =>
|
|
137
161
|
html`<div class="form-check mb-2">
|
|
138
|
-
<input class="form-check-input" type="checkbox" id=${"x-" + a.name}
|
|
162
|
+
<input class="form-check-input" type="checkbox" id=${"x-" + a.name}
|
|
163
|
+
checked=${() => eff().includes(a.name)}
|
|
164
|
+
disabled=${() => !state().addons[a.name] && eff().includes(a.name)}
|
|
165
|
+
onchange=${() => toggle(a.name)} />
|
|
139
166
|
<label class="form-check-label" for=${"x-" + a.name}>
|
|
140
|
-
<span class="accent">${a.name}</span>${a.dependsOn?.length ? html` <span class="text-muted small">(needs ${a.dependsOn.join(", ")})</span>` : ""}
|
|
167
|
+
<span class="accent">${a.name}</span>${a.dependsOn?.length ? html` <span class="text-muted small">(needs ${a.dependsOn.join(", ")})</span>` : ""}${() =>
|
|
168
|
+
!state().addons[a.name] && eff().includes(a.name) ? html` <span class="text-muted small">· required by ${requiredBy(state(), a.name).join(", ")}</span>` : ""}
|
|
141
169
|
<div class="small text-muted">${a.description}</div>
|
|
142
170
|
</label>
|
|
143
171
|
</div>`;
|
|
@@ -25,6 +25,9 @@ const state = signal({
|
|
|
25
25
|
s3Secret: current.S3_SECRET || "",
|
|
26
26
|
s3PublicBase: current.S3_PUBLIC_BASE || "",
|
|
27
27
|
port: current.PORT || String(defaultPort),
|
|
28
|
+
// detect the admin's timezone from their browser (the wizard runs here), so
|
|
29
|
+
// dates render in their zone — not the server's (usually UTC on a host).
|
|
30
|
+
tz: current.SITE_TZ || Intl.DateTimeFormat().resolvedOptions().timeZone || "",
|
|
28
31
|
});
|
|
29
32
|
const set = (patch) => state({ ...state(), ...patch });
|
|
30
33
|
const toggle = (n) => state({ ...state(), addons: { ...state().addons, [n]: !state().addons[n] } });
|
|
@@ -42,10 +45,28 @@ function effective(s) {
|
|
|
42
45
|
return order.filter((n) => want.has(n));
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
// which *enabled* add-ons pull in `name` as a (transitive) dependency
|
|
49
|
+
function requiredBy(s, name) {
|
|
50
|
+
const causes = [];
|
|
51
|
+
for (const n of order) {
|
|
52
|
+
if (n === name || !s.addons[n]) continue;
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
const visit = (x) => {
|
|
55
|
+
if (seen.has(x)) return;
|
|
56
|
+
seen.add(x);
|
|
57
|
+
(depsOf[x] || []).forEach(visit);
|
|
58
|
+
};
|
|
59
|
+
(depsOf[n] || []).forEach(visit);
|
|
60
|
+
if (seen.has(name)) causes.push(n);
|
|
61
|
+
}
|
|
62
|
+
return causes;
|
|
63
|
+
}
|
|
64
|
+
|
|
45
65
|
const clean = (v) => String(v).replace(/[\r\n]/g, "").trim(); // one value per line; no injection
|
|
46
66
|
function genEnv(s) {
|
|
47
67
|
const eff = effective(s);
|
|
48
68
|
const out = [`VOLT_ADDONS=${eff.join(",")}`, `PORT=${clean(s.port)}`];
|
|
69
|
+
if (s.tz) out.push(`SITE_TZ=${clean(s.tz)}`); // admin's timezone, for date display
|
|
49
70
|
if (eff.includes("db")) {
|
|
50
71
|
out.push(`DB_DRIVER=${clean(s.dbDriver)}`);
|
|
51
72
|
if (s.dbDriver === "mongodb") {
|
|
@@ -133,11 +154,18 @@ const field = (label, key, placeholder = "") =>
|
|
|
133
154
|
<input class="form-control" placeholder=${placeholder} value=${() => state()[key]} oninput=${(e) => set({ [key]: e.target.value })} />
|
|
134
155
|
</div>`;
|
|
135
156
|
|
|
157
|
+
// A dependency pulled in by another enabled add-on shows as checked + disabled
|
|
158
|
+
// (you can't turn it off while something needs it), with a "required by" note —
|
|
159
|
+
// so the .env's VOLT_ADDONS always matches what the boxes show.
|
|
136
160
|
const addonRow = (a) =>
|
|
137
161
|
html`<div class="form-check mb-2">
|
|
138
|
-
<input class="form-check-input" type="checkbox" id=${"x-" + a.name}
|
|
162
|
+
<input class="form-check-input" type="checkbox" id=${"x-" + a.name}
|
|
163
|
+
checked=${() => eff().includes(a.name)}
|
|
164
|
+
disabled=${() => !state().addons[a.name] && eff().includes(a.name)}
|
|
165
|
+
onchange=${() => toggle(a.name)} />
|
|
139
166
|
<label class="form-check-label" for=${"x-" + a.name}>
|
|
140
|
-
<span class="accent">${a.name}</span>${a.dependsOn?.length ? html` <span class="text-muted small">(needs ${a.dependsOn.join(", ")})</span>` : ""}
|
|
167
|
+
<span class="accent">${a.name}</span>${a.dependsOn?.length ? html` <span class="text-muted small">(needs ${a.dependsOn.join(", ")})</span>` : ""}${() =>
|
|
168
|
+
!state().addons[a.name] && eff().includes(a.name) ? html` <span class="text-muted small">· required by ${requiredBy(state(), a.name).join(", ")}</span>` : ""}
|
|
141
169
|
<div class="small text-muted">${a.description}</div>
|
|
142
170
|
</label>
|
|
143
171
|
</div>`;
|