Fix Windows local dependency startup

This commit is contained in:
Franz Rolfsvaag 2026-07-18 18:58:35 +02:00
parent 3274778e40
commit 21248043c7
11 changed files with 103 additions and 20 deletions

View File

@ -1,5 +1,10 @@
# Lumi changelog # Lumi changelog
## 0.2.10
- Fixed local Windows startup under Node.js 24 by invoking npm through its JavaScript CLI instead of directly spawning `npm.cmd`.
- Added a safe command-shell fallback and focused Windows dependency-launch regression coverage.
## 0.2.9 ## 0.2.9
- Fixed forms containing fields named `action` or `method` being able to shadow their own submission endpoint and send requests to an invalid URL. - Fixed forms containing fields named `action` or `method` being able to shadow their own submission endpoint and send requests to an invalid URL.

View File

@ -62,7 +62,9 @@ Admins may also install or repair a specific core or plugin release. These
choices use immutable version tags and verify the selected version before files choices use immutable version tags and verify the selected version before files
are replaced. `npm start` and `npm run run` launch the update-aware wrapper, are replaced. `npm start` and `npm run run` launch the update-aware wrapper,
which repairs required packages from the lockfile after a core update while which repairs required packages from the lockfile after a core update while
allowing unavailable optional integrations to remain disabled. allowing unavailable optional integrations to remain disabled. On Windows the
wrapper invokes npm through the active Node installation, including Node.js 24,
so dependency repair does not rely on spawning a command script directly.
Recovery mode can be started with `LUMI_SAFE_MODE=1 npm run run`, Recovery mode can be started with `LUMI_SAFE_MODE=1 npm run run`,
`node run.js --safe-mode`, or `data/recovery/safe-mode.flag`. See `node run.js --safe-mode`, or `data/recovery/safe-mode.flag`. See

View File

@ -677,6 +677,7 @@ This section is for Lumi chat answer feedback and AI Improvement Center work, no
## Done ## Done
- 2026-07-18: Fixed core 0.2.10 local Windows startup under Node.js 24 by resolving npm's JavaScript CLI through the active Node installation, retaining a command-shell fallback, and verifying automatic dependency repair through the real launcher on port 3000.
- 2026-07-18: Fixed core 0.2.9 and Lumi AI 0.8.5 forms whose `action` or `method` fields could shadow the form endpoint, added a shared clobber-safe resolver, and migrated the affected feedback/settings requests to it. - 2026-07-18: Fixed core 0.2.9 and Lumi AI 0.8.5 forms whose `action` or `method` fields could shadow the form endpoint, added a shared clobber-safe resolver, and migrated the affected feedback/settings requests to it.
- 2026-07-18: Fixed Lumi AI 0.8.4 Improvement Center state changes for no-change, dismiss, edit, and restore actions with an explicit authenticated JSON contract, actionable errors, safe diagnostics, and focused regression coverage. - 2026-07-18: Fixed Lumi AI 0.8.4 Improvement Center state changes for no-change, dismiss, edit, and restore actions with an explicit authenticated JSON contract, actionable errors, safe diagnostics, and focused regression coverage.
- 2026-07-18: Fixed private reverse-proxy HTTPS recognition for production diagnostics in core 0.2.5 without globally trusting client forwarding headers, and taught the local client to consume the repository's env-style `.secrets` file and short diagnostic variable names directly. - 2026-07-18: Fixed private reverse-proxy HTTPS recognition for production diagnostics in core 0.2.5 without globally trusting client forwarding headers, and taught the local client to consume the repository's env-style `.secrets` file and short diagnostic variable names directly.

View File

@ -14,7 +14,7 @@ editable: false
Lumi is the core web UI and bot runtime. Lumi is the core web UI and bot runtime.
## Runtime ## Runtime
Package: lumi-bot Package: lumi-bot
Version: 0.2.9 Version: 0.2.10
## Routes ## Routes
- GET /api/events - GET /api/events
- POST /api/destructive-confirmations - POST /api/destructive-confirmations

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{ {
"name": "lumi-bot", "name": "lumi-bot",
"version": "0.2.9", "version": "0.2.10",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "lumi-bot", "name": "lumi-bot",
"version": "0.2.9", "version": "0.2.10",
"dependencies": { "dependencies": {
"adm-zip": "^0.5.12", "adm-zip": "^0.5.12",
"better-sqlite3": "^11.5.0", "better-sqlite3": "^11.5.0",

View File

@ -1,6 +1,6 @@
{ {
"name": "lumi-bot", "name": "lumi-bot",
"version": "0.2.9", "version": "0.2.10",
"private": true, "private": true,
"type": "commonjs", "type": "commonjs",
"scripts": { "scripts": {

View File

@ -2,6 +2,36 @@
"schema_version": 1, "schema_version": 1,
"channel": "stable", "channel": "stable",
"releases": [ "releases": [
{
"version": "0.2.10",
"ref": "refs/tags/v0.2.10",
"released_at": "2026-07-18",
"installable": true,
"rollback_safe": true,
"replaces_versions": [
"1.2.0"
],
"data_policy": "preserve",
"dependency_policy": "sync_on_restart",
"migration_notes": "Fixes local Windows startup and automatic dependency repair under Node.js 24 while preserving all settings, databases, plugin data, models, uploads, logs, feedback, and secrets.",
"plugins": {
"auto-vc": "0.1.6",
"birthday": "0.1.3",
"economy-framework": "0.2.10",
"economy-games": "0.1.7",
"expression-interaction": "0.2.1",
"lumi_ai": "0.8.5",
"moderation": "0.1.5",
"okf": "0.1.1",
"quotes": "0.1.2",
"sample-plugin": "0.1.0",
"throne_wishlist": "0.1.2",
"welcome_messages": "0.1.1"
},
"tools": {
"lumi_ai_web_search": "0.1.1"
}
},
{ {
"version": "0.2.9", "version": "0.2.9",
"ref": "refs/tags/v0.2.9", "ref": "refs/tags/v0.2.9",

View File

@ -4,8 +4,8 @@ const path = require("path");
const { findSafeTarget } = require("../src/services/versioning"); const { findSafeTarget } = require("../src/services/versioning");
const root = path.join(__dirname, ".."); const root = path.join(__dirname, "..");
const releaseVersion = "0.2.9"; const releaseVersion = "0.2.10";
const previousCoreVersion = "0.2.8"; const previousCoreVersion = "0.2.9";
const earliestCompatibleCoreVersion = "0.1.9"; const earliestCompatibleCoreVersion = "0.1.9";
const changedPlugins = { const changedPlugins = {
"auto-vc": { from: "0.1.5", to: "0.1.6", knowledge: "auto-vc" }, "auto-vc": { from: "0.1.5", to: "0.1.6", knowledge: "auto-vc" },
@ -87,4 +87,4 @@ assert.equal(webSearch.minimum_lumi_version, "0.2.0");
assert.equal(webSearch.minimum_lumi_ai_version, "0.8.2"); assert.equal(webSearch.minimum_lumi_ai_version, "0.8.2");
assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true); assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true);
console.log("Release metadata verification passed: core 0.2.9, Lumi AI 0.8.5, and synchronized package metadata."); console.log("Release metadata verification passed: core 0.2.10, Lumi AI 0.8.5, and synchronized package metadata.");

View File

@ -4,7 +4,7 @@ const os = require("os");
const path = require("path"); const path = require("path");
const root = path.join(__dirname, ".."); const root = path.join(__dirname, "..");
const { dependencyIssues, ensureRuntimeDependencies } = require("../src/services/dependency-manager"); const { dependencyIssues, ensureRuntimeDependencies, npmInvocation } = require("../src/services/dependency-manager");
const { targetForRequestedVersion } = require("../src/services/repo-update"); const { targetForRequestedVersion } = require("../src/services/repo-update");
const { buildStatus } = require("../src/services/update-index"); const { buildStatus } = require("../src/services/update-index");
const { normalizeRepositoryRef } = require("../src/services/update-repository"); const { normalizeRepositoryRef } = require("../src/services/update-repository");
@ -16,7 +16,7 @@ function readJson(relativePath) {
const releaseIndex = readJson("release-index.json"); const releaseIndex = readJson("release-index.json");
const releaseVersions = releaseIndex.releases.map((release) => release.version); const releaseVersions = releaseIndex.releases.map((release) => release.version);
assert.deepEqual(releaseVersions, ["0.2.9", "0.2.8", "0.2.7", "0.2.6", "0.2.5", "0.2.4", "0.2.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]); assert.deepEqual(releaseVersions, ["0.2.10", "0.2.9", "0.2.8", "0.2.7", "0.2.6", "0.2.5", "0.2.4", "0.2.3", "0.2.2", "0.2.1", "0.2.0", "0.1.9"]);
assert.equal(new Set(releaseVersions).size, releaseVersions.length, "release versions must be unique"); assert.equal(new Set(releaseVersions).size, releaseVersions.length, "release versions must be unique");
for (const release of releaseIndex.releases) { for (const release of releaseIndex.releases) {
assert.equal(normalizeRepositoryRef(release.ref), release.ref); assert.equal(normalizeRepositoryRef(release.ref), release.ref);
@ -37,6 +37,7 @@ for (const [toolId, version] of Object.entries(currentRelease.tools)) {
const baseTarget = { const baseTarget = {
current_version: "0.2.4", current_version: "0.2.4",
available_versions: [ available_versions: [
{ version: "0.2.10", ref: "refs/tags/v0.2.10", rollback_safe: true },
{ version: "0.2.9", ref: "refs/tags/v0.2.9", rollback_safe: true }, { version: "0.2.9", ref: "refs/tags/v0.2.9", rollback_safe: true },
{ version: "0.2.8", ref: "refs/tags/v0.2.8", rollback_safe: true }, { version: "0.2.8", ref: "refs/tags/v0.2.8", rollback_safe: true },
{ version: "0.2.7", ref: "refs/tags/v0.2.7", rollback_safe: true }, { version: "0.2.7", ref: "refs/tags/v0.2.7", rollback_safe: true },
@ -69,7 +70,7 @@ const corrected = buildStatus({
channel: "stable" channel: "stable"
}); });
assert.equal(corrected.version_correction, true); assert.equal(corrected.version_correction, true);
assert.equal(corrected.safe_target_version, "0.2.9"); assert.equal(corrected.safe_target_version, "0.2.10");
assert.equal(corrected.update_available, true); assert.equal(corrected.update_available, true);
assert.equal(corrected.blocked, false); assert.equal(corrected.blocked, false);
@ -118,6 +119,20 @@ try {
fs.rmSync(fixture, { recursive: true, force: true }); fs.rmSync(fixture, { recursive: true, force: true });
} }
const windowsNpmFixture = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-windows-npm-"));
try {
const fakeNode = path.join(windowsNpmFixture, "node.exe");
const fakeNpmCli = path.join(windowsNpmFixture, "node_modules", "npm", "bin", "npm-cli.js");
fs.mkdirSync(path.dirname(fakeNpmCli), { recursive: true });
fs.writeFileSync(fakeNpmCli, "");
const invocation = npmInvocation({ platform: "win32", execPath: fakeNode, env: {} });
assert.equal(invocation.command, fakeNode);
assert.deepEqual(invocation.args, [fakeNpmCli]);
assert.equal(invocation.args.includes("npm.cmd"), false);
} finally {
fs.rmSync(windowsNpmFixture, { recursive: true, force: true });
}
const optionalFixture = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-optional-dependency-")); const optionalFixture = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-optional-dependency-"));
try { try {
fs.writeFileSync(path.join(optionalFixture, "package.json"), JSON.stringify({ optionalDependencies: { optional: "^1.0.0" } })); fs.writeFileSync(path.join(optionalFixture, "package.json"), JSON.stringify({ optionalDependencies: { optional: "^1.0.0" } }));

View File

@ -49,13 +49,30 @@ function writeState(rootPath, values) {
} }
function npmInvocation(options = {}) { function npmInvocation(options = {}) {
if (process.env.npm_execpath && fs.existsSync(process.env.npm_execpath)) { const environment = options.env || process.env;
return { command: process.execPath, args: [process.env.npm_execpath] }; const execPath = path.resolve(options.execPath || process.execPath);
const configuredCli = String(environment.npm_execpath || "").trim();
if (configuredCli && fs.existsSync(configuredCli)) {
return { command: execPath, args: [configuredCli] };
} }
const platform = options.platform || process.platform;
if (platform === "win32") {
const candidates = [
path.join(path.dirname(execPath), "node_modules", "npm", "bin", "npm-cli.js"),
environment.ProgramFiles
? path.join(environment.ProgramFiles, "nodejs", "node_modules", "npm", "bin", "npm-cli.js")
: null
].filter(Boolean);
const npmCli = candidates.find((candidate) => fs.existsSync(candidate));
if (npmCli) return { command: execPath, args: [npmCli] };
return { return {
command: options.platform === "win32" || process.platform === "win32" ? "npm.cmd" : "npm", command: String(environment.ComSpec || environment.COMSPEC || "cmd.exe"),
args: [] args: ["/d", "/s", "/c", "npm.cmd"]
}; };
}
return { command: "npm", args: [] };
} }
function ensureRuntimeDependencies(options = {}) { function ensureRuntimeDependencies(options = {}) {
@ -118,5 +135,6 @@ function ensureRuntimeDependencies(options = {}) {
module.exports = { module.exports = {
dependencyFingerprint, dependencyFingerprint,
dependencyIssues, dependencyIssues,
ensureRuntimeDependencies ensureRuntimeDependencies,
npmInvocation
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "Lumi Core", "name": "Lumi Core",
"version": "0.2.9", "version": "0.2.10",
"channel": "stable", "channel": "stable",
"released_at": "2026-07-18", "released_at": "2026-07-18",
"compatible_from": "0.1.9", "compatible_from": "0.1.9",
@ -8,7 +8,7 @@
"replaces_versions": [ "replaces_versions": [
"1.2.0" "1.2.0"
], ],
"migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, secured read-only production diagnostics, Windows/network-share-safe plugin code replacement, and clobber-safe shared form submission. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, feedback, and secrets.", "migration_notes": "Includes the 1.2.0 version correction, production plugin-update diagnostics, secured read-only production diagnostics, Windows/network-share-safe plugin replacement, clobber-safe shared form submission, and Node.js 24-compatible Windows dependency startup. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, feedback, and secrets.",
"rollback_safe": true, "rollback_safe": true,
"requirements": [ "requirements": [
"Node.js 18 or newer" "Node.js 18 or newer"
@ -133,6 +133,18 @@
], ],
"rollback_safe": true, "rollback_safe": true,
"migration_notes": "Fixes shared browser form endpoint shadowing and Lumi AI feedback 404 responses; existing feedback, settings, models, corrections, and plugin data are preserved." "migration_notes": "Fixes shared browser form endpoint shadowing and Lumi AI feedback 404 responses; existing feedback, settings, models, corrections, and plugin data are preserved."
},
{
"version": "0.2.10",
"channel": "stable",
"released_at": "2026-07-18",
"compatible_from": "0.1.9",
"migration_kind": "patch",
"replaces_versions": [
"1.2.0"
],
"rollback_safe": true,
"migration_notes": "Fixes local Windows startup and automatic dependency repair under Node.js 24; all existing data remains preserved."
} }
] ]
} }