create-volt 0.26.0 → 0.28.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,25 @@ 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.28.0] - 2026-06-29
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
- Wizard **Test connection** now installs the selected DB driver on demand
|
|
11
|
+
(mongodb / mysql2 / pg, at the pinned version) before testing — it used to fail
|
|
12
|
+
with "<driver> isn't installed" because the package is only added on Apply.
|
|
13
|
+
Also adds the missing `spawnSync` import.
|
|
14
|
+
|
|
15
|
+
## [0.27.0] - 2026-06-29
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **Reactive crash on conditional re-render** (`volt.js`) — a signal write
|
|
19
|
+
notified a *snapshot* of subscribers, so a parent reactive block that disposed
|
|
20
|
+
a nested one mid-update would still run the stale nested effect on detached DOM
|
|
21
|
+
("Cannot read properties of null (reading 'insertBefore')"). Disposed effects
|
|
22
|
+
now skip their queued run, and `renderRange` guards against a detached range.
|
|
23
|
+
Fixes the **setup wizard** crashing when toggling add-ons or changing the DB
|
|
24
|
+
driver. Regression-tested in the headless-browser suite.
|
|
25
|
+
|
|
7
26
|
## [0.26.0] - 2026-06-29
|
|
8
27
|
|
|
9
28
|
### Added
|
|
@@ -370,6 +389,8 @@ All notable changes to `create-volt` are documented here. The format follows
|
|
|
370
389
|
watching and full-page hot reload. Supports `--skip-install` and `--force`,
|
|
371
390
|
and auto-detects npm / pnpm / yarn / bun for the install step.
|
|
372
391
|
|
|
392
|
+
[0.28.0]: https://github.com/MIR-2025/volt/releases/tag/v0.28.0
|
|
393
|
+
[0.27.0]: https://github.com/MIR-2025/volt/releases/tag/v0.27.0
|
|
373
394
|
[0.26.0]: https://github.com/MIR-2025/volt/releases/tag/v0.26.0
|
|
374
395
|
[0.25.0]: https://github.com/MIR-2025/volt/releases/tag/v0.25.0
|
|
375
396
|
[0.24.0]: https://github.com/MIR-2025/volt/releases/tag/v0.24.0
|
package/package.json
CHANGED
|
@@ -47,7 +47,11 @@ export function effect(fn) {
|
|
|
47
47
|
deps: new Set(),
|
|
48
48
|
children: new Set(),
|
|
49
49
|
parent: activeEffect,
|
|
50
|
+
disposed: false,
|
|
50
51
|
run() {
|
|
52
|
+
// A signal write notifies a *snapshot* of subscribers; a parent re-render
|
|
53
|
+
// can dispose this effect before its turn in that snapshot — so skip if so.
|
|
54
|
+
if (eff.disposed) return;
|
|
51
55
|
disposeChildren(eff);
|
|
52
56
|
cleanupDeps(eff);
|
|
53
57
|
const prev = activeEffect;
|
|
@@ -59,6 +63,7 @@ export function effect(fn) {
|
|
|
59
63
|
}
|
|
60
64
|
},
|
|
61
65
|
dispose() {
|
|
66
|
+
eff.disposed = true;
|
|
62
67
|
disposeChildren(eff);
|
|
63
68
|
cleanupDeps(eff);
|
|
64
69
|
if (eff.parent) eff.parent.children.delete(eff);
|
|
@@ -152,6 +157,7 @@ function appendChild(parent, child) {
|
|
|
152
157
|
|
|
153
158
|
// Replace everything between the start/end anchors with `value`'s nodes.
|
|
154
159
|
function renderRange(start, end, value) {
|
|
160
|
+
if (!end.parentNode) return; // range detached (parent re-rendered) — nothing to do
|
|
155
161
|
let n = start.nextSibling;
|
|
156
162
|
while (n && n !== end) {
|
|
157
163
|
const t = n.nextSibling;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
import http from "node:http";
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
|
-
import { spawn } from "node:child_process";
|
|
14
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
15
15
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
16
16
|
import express from "express";
|
|
17
17
|
import { Server as SocketServer } from "socket.io";
|
|
@@ -208,6 +208,15 @@ function neededPackages(env) {
|
|
|
208
208
|
return want.filter((p) => !deps[p]);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
// Install a DB driver's package on demand (pinned), so the wizard's "Test
|
|
212
|
+
// connection" works before Apply has installed it.
|
|
213
|
+
function ensureDriverInstalled(driver) {
|
|
214
|
+
const pkg = { mongodb: "mongodb", mongo: "mongodb", mysql: "mysql2", postgres: "pg", postgresql: "pg", pg: "pg" }[String(driver || "").toLowerCase()];
|
|
215
|
+
if (!pkg || fs.existsSync(path.join(__dirname, "node_modules", pkg))) return;
|
|
216
|
+
console.log(`[volt] installing ${pkg} for the connection test…`);
|
|
217
|
+
spawnSync("npm", ["install", `${pkg}@${PKG_VERSIONS[pkg] || "latest"}`], { cwd: __dirname, stdio: "inherit", shell: process.platform === "win32" });
|
|
218
|
+
}
|
|
219
|
+
|
|
211
220
|
// --- the disposable setup wizard (localhost only) ---
|
|
212
221
|
function startSetup() {
|
|
213
222
|
const PORT = Number(process.env.PORT) || Number(readEnvFile().PORT) || DEFAULT_PORT;
|
|
@@ -244,6 +253,7 @@ function startSetup() {
|
|
|
244
253
|
if (env[k]) process.env[k] = env[k];
|
|
245
254
|
else delete process.env[k];
|
|
246
255
|
}
|
|
256
|
+
ensureDriverInstalled(process.env.DB_DRIVER); // install the driver first, so the test can connect
|
|
247
257
|
const store = await (await addonMod("db")).createStore();
|
|
248
258
|
await store.collection("__voltcheck").all();
|
|
249
259
|
res.setHeader("Content-Type", "application/json");
|
|
@@ -47,7 +47,11 @@ export function effect(fn) {
|
|
|
47
47
|
deps: new Set(),
|
|
48
48
|
children: new Set(),
|
|
49
49
|
parent: activeEffect,
|
|
50
|
+
disposed: false,
|
|
50
51
|
run() {
|
|
52
|
+
// A signal write notifies a *snapshot* of subscribers; a parent re-render
|
|
53
|
+
// can dispose this effect before its turn in that snapshot — so skip if so.
|
|
54
|
+
if (eff.disposed) return;
|
|
51
55
|
disposeChildren(eff);
|
|
52
56
|
cleanupDeps(eff);
|
|
53
57
|
const prev = activeEffect;
|
|
@@ -59,6 +63,7 @@ export function effect(fn) {
|
|
|
59
63
|
}
|
|
60
64
|
},
|
|
61
65
|
dispose() {
|
|
66
|
+
eff.disposed = true;
|
|
62
67
|
disposeChildren(eff);
|
|
63
68
|
cleanupDeps(eff);
|
|
64
69
|
if (eff.parent) eff.parent.children.delete(eff);
|
|
@@ -152,6 +157,7 @@ function appendChild(parent, child) {
|
|
|
152
157
|
|
|
153
158
|
// Replace everything between the start/end anchors with `value`'s nodes.
|
|
154
159
|
function renderRange(start, end, value) {
|
|
160
|
+
if (!end.parentNode) return; // range detached (parent re-rendered) — nothing to do
|
|
155
161
|
let n = start.nextSibling;
|
|
156
162
|
while (n && n !== end) {
|
|
157
163
|
const t = n.nextSibling;
|
|
@@ -47,7 +47,11 @@ export function effect(fn) {
|
|
|
47
47
|
deps: new Set(),
|
|
48
48
|
children: new Set(),
|
|
49
49
|
parent: activeEffect,
|
|
50
|
+
disposed: false,
|
|
50
51
|
run() {
|
|
52
|
+
// A signal write notifies a *snapshot* of subscribers; a parent re-render
|
|
53
|
+
// can dispose this effect before its turn in that snapshot — so skip if so.
|
|
54
|
+
if (eff.disposed) return;
|
|
51
55
|
disposeChildren(eff);
|
|
52
56
|
cleanupDeps(eff);
|
|
53
57
|
const prev = activeEffect;
|
|
@@ -59,6 +63,7 @@ export function effect(fn) {
|
|
|
59
63
|
}
|
|
60
64
|
},
|
|
61
65
|
dispose() {
|
|
66
|
+
eff.disposed = true;
|
|
62
67
|
disposeChildren(eff);
|
|
63
68
|
cleanupDeps(eff);
|
|
64
69
|
if (eff.parent) eff.parent.children.delete(eff);
|
|
@@ -152,6 +157,7 @@ function appendChild(parent, child) {
|
|
|
152
157
|
|
|
153
158
|
// Replace everything between the start/end anchors with `value`'s nodes.
|
|
154
159
|
function renderRange(start, end, value) {
|
|
160
|
+
if (!end.parentNode) return; // range detached (parent re-rendered) — nothing to do
|
|
155
161
|
let n = start.nextSibling;
|
|
156
162
|
while (n && n !== end) {
|
|
157
163
|
const t = n.nextSibling;
|
|
@@ -12,7 +12,7 @@ import http from "node:http";
|
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import crypto from "node:crypto";
|
|
15
|
-
import { spawn } from "node:child_process";
|
|
15
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
16
16
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
17
17
|
import express from "express";
|
|
18
18
|
import { Server as SocketServer } from "socket.io";
|
|
@@ -234,6 +234,15 @@ function neededPackages(env) {
|
|
|
234
234
|
return want.filter((p) => !deps[p]);
|
|
235
235
|
}
|
|
236
236
|
|
|
237
|
+
// Install a DB driver's package on demand (pinned), so the wizard's "Test
|
|
238
|
+
// connection" works before Apply has installed it.
|
|
239
|
+
function ensureDriverInstalled(driver) {
|
|
240
|
+
const pkg = { mongodb: "mongodb", mongo: "mongodb", mysql: "mysql2", postgres: "pg", postgresql: "pg", pg: "pg" }[String(driver || "").toLowerCase()];
|
|
241
|
+
if (!pkg || fs.existsSync(path.join(__dirname, "node_modules", pkg))) return;
|
|
242
|
+
console.log(`[volt] installing ${pkg} for the connection test…`);
|
|
243
|
+
spawnSync("npm", ["install", `${pkg}@${PKG_VERSIONS[pkg] || "latest"}`], { cwd: __dirname, stdio: "inherit", shell: process.platform === "win32" });
|
|
244
|
+
}
|
|
245
|
+
|
|
237
246
|
// --- the disposable setup wizard (localhost only) ---
|
|
238
247
|
function startSetup() {
|
|
239
248
|
const PORT = Number(process.env.PORT) || Number(readEnvFile().PORT) || DEFAULT_PORT;
|
|
@@ -270,6 +279,7 @@ function startSetup() {
|
|
|
270
279
|
if (env[k]) process.env[k] = env[k];
|
|
271
280
|
else delete process.env[k];
|
|
272
281
|
}
|
|
282
|
+
ensureDriverInstalled(process.env.DB_DRIVER); // install the driver first, so the test can connect
|
|
273
283
|
const store = await (await addonMod("db")).createStore();
|
|
274
284
|
await store.collection("__voltcheck").all();
|
|
275
285
|
res.setHeader("Content-Type", "application/json");
|