fix: clarify update checks and version pickers
This commit is contained in:
parent
46187137e6
commit
a6e49d1248
@ -1,5 +1,10 @@
|
|||||||
# Lumi changelog
|
# Lumi changelog
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
- Fixed repository update checks being handled twice and incorrectly ending in a failed button state after a successful result.
|
||||||
|
- Simplified update-result wording and moved exact core/plugin version selection into collapsed, module-specific advanced sections.
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
- Added the OBS overlay management and Browser Source system, including scenes, live updates, visual editing, multimedia, external web sources, and OBS bridge extension points.
|
- Added the OBS overlay management and Browser Source system, including scenes, live updates, visual editing, multimedia, external web sources, and OBS bridge extension points.
|
||||||
|
|||||||
1
TODO.md
1
TODO.md
@ -677,6 +677,7 @@ This section is for Lumi chat answer feedback and AI Improvement Center work, no
|
|||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
|
- 2026-07-18: Released the follow-up core 0.2.1 update-page fix: update checks now have one client-side handler, successful results use clear module-specific wording, and exact core/plugin version controls are collapsed and list only the selected module’s versions.
|
||||||
- 2026-07-18: Audited the 0.1.9-to-0.2.0 update boundary, removed core's direct dependency on OKF plugin files, made OBS WebSocket optional, added locked runtime dependency repair, corrected 1.2.0 to 0.2.0, added immutable release metadata and exact-version core/plugin installs, and produced a checksummed data-preserving core repair patch.
|
- 2026-07-18: Audited the 0.1.9-to-0.2.0 update boundary, removed core's direct dependency on OKF plugin files, made OBS WebSocket optional, added locked runtime dependency repair, corrected 1.2.0 to 0.2.0, added immutable release metadata and exact-version core/plugin installs, and produced a checksummed data-preserving core repair patch.
|
||||||
|
|
||||||
### 2026-07-18
|
### 2026-07-18
|
||||||
|
|||||||
@ -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.0
|
Version: 0.2.1
|
||||||
## Routes
|
## Routes
|
||||||
- GET /api/events
|
- GET /api/events
|
||||||
- POST /api/destructive-confirmations
|
- POST /api/destructive-confirmations
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "lumi-bot",
|
"name": "lumi-bot",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "lumi-bot",
|
"name": "lumi-bot",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.12",
|
"adm-zip": "^0.5.12",
|
||||||
"better-sqlite3": "^11.5.0",
|
"better-sqlite3": "^11.5.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "lumi-bot",
|
"name": "lumi-bot",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@ -2,6 +2,36 @@
|
|||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"releases": [
|
"releases": [
|
||||||
|
{
|
||||||
|
"version": "0.2.1",
|
||||||
|
"ref": "refs/tags/v0.2.1",
|
||||||
|
"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 update-check status handling and makes exact version selection collapsed, module-specific, and easier to understand.",
|
||||||
|
"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.2",
|
||||||
|
"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.0",
|
"version": "0.2.0",
|
||||||
"ref": "refs/tags/v0.2.0",
|
"ref": "refs/tags/v0.2.0",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ const path = require("path");
|
|||||||
const AdmZip = require("adm-zip");
|
const AdmZip = require("adm-zip");
|
||||||
|
|
||||||
const root = path.join(__dirname, "..");
|
const root = path.join(__dirname, "..");
|
||||||
const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.0-repair.zip");
|
const destination = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.1-repair.zip");
|
||||||
const files = [
|
const files = [
|
||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
"README.md",
|
"README.md",
|
||||||
@ -21,6 +21,7 @@ const files = [
|
|||||||
"scripts/build-core-repair-patch.js",
|
"scripts/build-core-repair-patch.js",
|
||||||
"scripts/verify-core-repair-patch.js",
|
"scripts/verify-core-repair-patch.js",
|
||||||
"scripts/verify-update-system.js",
|
"scripts/verify-update-system.js",
|
||||||
|
"scripts/verify-webui.js",
|
||||||
"src/services/dependency-manager.js",
|
"src/services/dependency-manager.js",
|
||||||
"src/services/overlay-connectors.js",
|
"src/services/overlay-connectors.js",
|
||||||
"src/services/repo-update.js",
|
"src/services/repo-update.js",
|
||||||
@ -28,6 +29,7 @@ const files = [
|
|||||||
"src/services/update-manager.js",
|
"src/services/update-manager.js",
|
||||||
"src/services/update-repository.js",
|
"src/services/update-repository.js",
|
||||||
"src/web/server.js",
|
"src/web/server.js",
|
||||||
|
"src/web/public/app.js",
|
||||||
"src/web/views/admin-updates.ejs"
|
"src/web/views/admin-updates.ejs"
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -45,10 +47,10 @@ for (const relativePath of files) {
|
|||||||
|
|
||||||
const manifest = {
|
const manifest = {
|
||||||
schema_version: 1,
|
schema_version: 1,
|
||||||
name: "Lumi core 0.2.0 repair",
|
name: "Lumi core 0.2.1 repair",
|
||||||
target: "core",
|
target: "core",
|
||||||
from_versions: ["0.1.9", "1.2.0", "0.2.0"],
|
from_versions: ["0.1.9", "1.2.0", "0.2.0", "0.2.1"],
|
||||||
to_version: "0.2.0",
|
to_version: "0.2.1",
|
||||||
data_policy: "preserve",
|
data_policy: "preserve",
|
||||||
dependency_policy: "sync_on_restart",
|
dependency_policy: "sync_on_restart",
|
||||||
created_at: new Date().toISOString(),
|
created_at: new Date().toISOString(),
|
||||||
|
|||||||
@ -7,7 +7,7 @@ const AdmZip = require("adm-zip");
|
|||||||
const { verifyPatchPackage } = require("../src/services/update-manager");
|
const { verifyPatchPackage } = require("../src/services/update-manager");
|
||||||
|
|
||||||
const root = path.join(__dirname, "..");
|
const root = path.join(__dirname, "..");
|
||||||
const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.0-repair.zip");
|
const archivePath = path.join(root, "dist", "lumi-core-1.2.0-to-0.2.1-repair.zip");
|
||||||
assert.equal(fs.existsSync(archivePath), true, "build the repair patch first");
|
assert.equal(fs.existsSync(archivePath), true, "build the repair patch first");
|
||||||
const zip = new AdmZip(archivePath);
|
const zip = new AdmZip(archivePath);
|
||||||
const entries = zip.getEntries().filter((entry) => !entry.isDirectory);
|
const entries = zip.getEntries().filter((entry) => !entry.isDirectory);
|
||||||
@ -15,9 +15,9 @@ const names = new Set(entries.map((entry) => entry.entryName.replace(/\\/g, "/")
|
|||||||
assert.equal(names.has("patch-manifest.json"), true);
|
assert.equal(names.has("patch-manifest.json"), true);
|
||||||
const manifest = JSON.parse(zip.readAsText("patch-manifest.json"));
|
const manifest = JSON.parse(zip.readAsText("patch-manifest.json"));
|
||||||
assert.equal(manifest.target, "core");
|
assert.equal(manifest.target, "core");
|
||||||
assert.equal(manifest.to_version, "0.2.0");
|
assert.equal(manifest.to_version, "0.2.1");
|
||||||
assert.equal(manifest.data_policy, "preserve");
|
assert.equal(manifest.data_policy, "preserve");
|
||||||
assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0"]);
|
assert.deepEqual(manifest.from_versions, ["0.1.9", "1.2.0", "0.2.0", "0.2.1"]);
|
||||||
|
|
||||||
const forbidden = /^(?:data|plugins|node_modules|config|storage|uploads|logs|database|databases|knowledge\/(?:community|corrections))(?:\/|$)|^\.env(?:\.|$)|^\.secrets$/;
|
const forbidden = /^(?:data|plugins|node_modules|config|storage|uploads|logs|database|databases|knowledge\/(?:community|corrections))(?:\/|$)|^\.env(?:\.|$)|^\.secrets$/;
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
@ -29,7 +29,7 @@ for (const [relativePath, expected] of Object.entries(manifest.files)) {
|
|||||||
assert.equal(actual, expected, `${relativePath} checksum`);
|
assert.equal(actual, expected, `${relativePath} checksum`);
|
||||||
}
|
}
|
||||||
assert.equal(Object.keys(manifest.files).length + 1, entries.length, "every repair file must be checksummed");
|
assert.equal(Object.keys(manifest.files).length + 1, entries.length, "every repair file must be checksummed");
|
||||||
assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.0");
|
assert.equal(JSON.parse(zip.readAsText("package.json")).version, "0.2.1");
|
||||||
|
|
||||||
const simulation = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-repair-simulation-"));
|
const simulation = fs.mkdtempSync(path.join(os.tmpdir(), "lumi-repair-simulation-"));
|
||||||
try {
|
try {
|
||||||
@ -47,11 +47,11 @@ try {
|
|||||||
fs.writeFileSync(target, `preserve:${sentinel}`);
|
fs.writeFileSync(target, `preserve:${sentinel}`);
|
||||||
}
|
}
|
||||||
zip.extractAllTo(simulation, true);
|
zip.extractAllTo(simulation, true);
|
||||||
assert.equal(verifyPatchPackage(simulation).to_version, "0.2.0");
|
assert.equal(verifyPatchPackage(simulation).to_version, "0.2.1");
|
||||||
for (const sentinel of sentinels) {
|
for (const sentinel of sentinels) {
|
||||||
assert.equal(fs.readFileSync(path.join(simulation, sentinel), "utf8"), `preserve:${sentinel}`);
|
assert.equal(fs.readFileSync(path.join(simulation, sentinel), "utf8"), `preserve:${sentinel}`);
|
||||||
}
|
}
|
||||||
assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.0");
|
assert.equal(JSON.parse(fs.readFileSync(path.join(simulation, "package.json"), "utf8")).version, "0.2.1");
|
||||||
assert.equal(fs.existsSync(path.join(simulation, "src", "services", "dependency-manager.js")), true);
|
assert.equal(fs.existsSync(path.join(simulation, "src", "services", "dependency-manager.js")), true);
|
||||||
} finally {
|
} finally {
|
||||||
fs.rmSync(simulation, { recursive: true, force: true });
|
fs.rmSync(simulation, { recursive: true, force: true });
|
||||||
|
|||||||
@ -4,8 +4,9 @@ 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.0";
|
const releaseVersion = "0.2.1";
|
||||||
const previousCoreVersion = "0.1.9";
|
const previousCoreVersion = "0.2.0";
|
||||||
|
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" },
|
||||||
birthday: { from: "0.1.2", to: "0.1.3", knowledge: "birthday" },
|
birthday: { from: "0.1.2", to: "0.1.3", knowledge: "birthday" },
|
||||||
@ -44,13 +45,14 @@ assert.equal(packageLock.version, releaseVersion);
|
|||||||
assert.equal(packageLock.packages?.[""]?.version, releaseVersion);
|
assert.equal(packageLock.packages?.[""]?.version, releaseVersion);
|
||||||
assert.equal(coreManifest.version, releaseVersion);
|
assert.equal(coreManifest.version, releaseVersion);
|
||||||
assert.equal(coreManifest.channel, "stable");
|
assert.equal(coreManifest.channel, "stable");
|
||||||
assert.equal(coreManifest.compatible_from, previousCoreVersion);
|
assert.equal(coreManifest.compatible_from, earliestCompatibleCoreVersion);
|
||||||
assert.equal(coreManifest.rollback_safe, true);
|
assert.equal(coreManifest.rollback_safe, true);
|
||||||
assert.deepEqual(coreManifest.replaces_versions, ["1.2.0"]);
|
assert.deepEqual(coreManifest.replaces_versions, ["1.2.0"]);
|
||||||
assert.equal(releaseIndex.releases[0].version, releaseVersion);
|
assert.equal(releaseIndex.releases[0].version, releaseVersion);
|
||||||
assert.equal(releaseIndex.releases[0].ref, `refs/tags/v${releaseVersion}`);
|
assert.equal(releaseIndex.releases[0].ref, `refs/tags/v${releaseVersion}`);
|
||||||
assert.equal(releaseIndex.releases[1].version, previousCoreVersion);
|
assert.equal(releaseIndex.releases[1].version, previousCoreVersion);
|
||||||
assert.equal(releaseIndex.releases[1].ref, `refs/tags/v${previousCoreVersion}`);
|
assert.equal(releaseIndex.releases[1].ref, `refs/tags/v${previousCoreVersion}`);
|
||||||
|
assert.equal(releaseIndex.releases[2].version, earliestCompatibleCoreVersion);
|
||||||
assert.equal(hasVersionHeading(readText("CHANGELOG.md"), releaseVersion), true);
|
assert.equal(hasVersionHeading(readText("CHANGELOG.md"), releaseVersion), true);
|
||||||
assert.match(readText("knowledge/core/lumi-core.md"), new RegExp(`^Version: ${escapeRegex(releaseVersion)}$`, "m"));
|
assert.match(readText("knowledge/core/lumi-core.md"), new RegExp(`^Version: ${escapeRegex(releaseVersion)}$`, "m"));
|
||||||
|
|
||||||
@ -81,8 +83,8 @@ for (const [pluginId, expected] of Object.entries(changedPlugins)) {
|
|||||||
|
|
||||||
const webSearch = readJson("plugins/lumi_ai_web_search/tool_info.json");
|
const webSearch = readJson("plugins/lumi_ai_web_search/tool_info.json");
|
||||||
assert.equal(webSearch.version, "0.1.1");
|
assert.equal(webSearch.version, "0.1.1");
|
||||||
assert.equal(webSearch.minimum_lumi_version, releaseVersion);
|
assert.equal(webSearch.minimum_lumi_version, "0.2.0");
|
||||||
assert.equal(webSearch.minimum_lumi_ai_version, changedPlugins.lumi_ai.to);
|
assert.equal(webSearch.minimum_lumi_ai_version, changedPlugins.lumi_ai.to);
|
||||||
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.0 and 11 changed plugin/tool packages.");
|
console.log("Release metadata verification passed: core 0.2.1 and 11 changed plugin/tool packages.");
|
||||||
|
|||||||
@ -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.0", "0.1.9"]);
|
assert.deepEqual(releaseVersions, ["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.0",
|
current_version: "0.2.0",
|
||||||
available_versions: [
|
available_versions: [
|
||||||
|
{ version: "0.2.1", ref: "refs/tags/v0.2.1", rollback_safe: true },
|
||||||
{ version: "0.2.0", ref: "refs/tags/v0.2.0", rollback_safe: true },
|
{ version: "0.2.0", ref: "refs/tags/v0.2.0", rollback_safe: true },
|
||||||
{ version: "0.1.9", ref: "refs/tags/v0.1.9", rollback_safe: true }
|
{ version: "0.1.9", ref: "refs/tags/v0.1.9", rollback_safe: true }
|
||||||
],
|
],
|
||||||
@ -60,7 +61,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.0");
|
assert.equal(corrected.safe_target_version, "0.2.1");
|
||||||
assert.equal(corrected.update_available, true);
|
assert.equal(corrected.update_available, true);
|
||||||
assert.equal(corrected.blocked, false);
|
assert.equal(corrected.blocked, false);
|
||||||
|
|
||||||
|
|||||||
@ -191,15 +191,23 @@ function verifyUpdateCachePreserved() {
|
|||||||
function verifySharedUpdateActions() {
|
function verifySharedUpdateActions() {
|
||||||
const appSource = fs.readFileSync(path.join(root, "src", "web", "public", "app.js"), "utf8");
|
const appSource = fs.readFileSync(path.join(root, "src", "web", "public", "app.js"), "utf8");
|
||||||
const serverSource = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
|
const serverSource = fs.readFileSync(path.join(root, "src", "web", "server.js"), "utf8");
|
||||||
|
const updates = fs.readFileSync(path.join(root, "src", "web", "views", "admin-updates.ejs"), "utf8");
|
||||||
const dashboard = fs.readFileSync(path.join(root, "src", "web", "views", "admin-dashboard.ejs"), "utf8");
|
const dashboard = fs.readFileSync(path.join(root, "src", "web", "views", "admin-dashboard.ejs"), "utf8");
|
||||||
const settings = fs.readFileSync(path.join(root, "src", "web", "views", "admin-settings.ejs"), "utf8");
|
const settings = fs.readFileSync(path.join(root, "src", "web", "views", "admin-settings.ejs"), "utf8");
|
||||||
assert(appSource.includes('button[data-update-action]'));
|
assert(appSource.includes('button[data-update-action]'));
|
||||||
assert(appSource.includes("submitter?.formAction || form.action"));
|
assert(appSource.includes("submitter?.formAction || form.action"));
|
||||||
assert(appSource.includes('actionPath.endsWith("/check")'));
|
assert(appSource.includes('actionPath.endsWith("/check")'));
|
||||||
|
assert(appSource.includes('form.matches("[data-update-check-form]")'));
|
||||||
assert(dashboard.includes('action="/admin/check-update" class="inline-form" data-update-action'));
|
assert(dashboard.includes('action="/admin/check-update" class="inline-form" data-update-action'));
|
||||||
assert(settings.includes('formaction=\\"/admin/check-update\\" formmethod=\\"post\\" data-update-action'));
|
assert(settings.includes('formaction=\\"/admin/check-update\\" formmethod=\\"post\\" data-update-action'));
|
||||||
assert(serverSource.includes('sendUpdateResult(req, res, {\n status,'));
|
assert(serverSource.includes('sendUpdateResult(req, res, {\n status,'));
|
||||||
assert(serverSource.includes('message: "Core update applied. Lumi will restart in a few seconds."'));
|
assert(serverSource.includes('message: "Core update applied. Lumi will restart in a few seconds."'));
|
||||||
|
assert(serverSource.includes('`Core version ${status.core.safe_target_version} is available.`'));
|
||||||
|
assert(serverSource.includes('`${plugin.name} version ${plugin.safe_target_version} is available.`'));
|
||||||
|
assert(updates.includes("data-update-check-form"));
|
||||||
|
assert(updates.includes('class="lumi-expandable-settings update-version-picker"'));
|
||||||
|
assert(!updates.includes("reinstall current"));
|
||||||
|
assert(!updates.includes(" · core <%= release.core_version %>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
function verifyHomepageEmbeds() {
|
function verifyHomepageEmbeds() {
|
||||||
|
|||||||
@ -292,9 +292,9 @@ function buildStatus({ kind, id, name, currentVersion, manifest, changelog, sour
|
|||||||
rollback_safe: false,
|
rollback_safe: false,
|
||||||
unversioned: true
|
unversioned: true
|
||||||
};
|
};
|
||||||
const replacementTarget = versions.find((entry) =>
|
const replacementTarget = versions.filter((entry) =>
|
||||||
Array.isArray(entry.replaces_versions) && entry.replaces_versions.includes(currentVersion)
|
Array.isArray(entry.replaces_versions) && entry.replaces_versions.includes(currentVersion)
|
||||||
);
|
).at(-1);
|
||||||
const targetResult = replacementTarget
|
const targetResult = replacementTarget
|
||||||
? {
|
? {
|
||||||
target: replacementTarget,
|
target: replacementTarget,
|
||||||
|
|||||||
@ -1515,6 +1515,7 @@
|
|||||||
form.addEventListener("submit", async (event) => {
|
form.addEventListener("submit", async (event) => {
|
||||||
const submitter = event.submitter || form.querySelector("button[type='submit']");
|
const submitter = event.submitter || form.querySelector("button[type='submit']");
|
||||||
if (!form.matches("[data-update-action]") && !submitter?.matches?.("[data-update-action]")) return;
|
if (!form.matches("[data-update-action]") && !submitter?.matches?.("[data-update-action]")) return;
|
||||||
|
if (form.matches("[data-update-check-form]")) return;
|
||||||
const action = submitter?.formAction || form.action;
|
const action = submitter?.formAction || form.action;
|
||||||
const actionPath = normalizeDestructiveAction(action);
|
const actionPath = normalizeDestructiveAction(action);
|
||||||
if (updateLog && actionPath.startsWith("/admin/updates/") && actionPath.endsWith("/check")) return;
|
if (updateLog && actionPath.startsWith("/admin/updates/") && actionPath.endsWith("/check")) return;
|
||||||
|
|||||||
@ -6594,10 +6594,10 @@ function createWebServer({ loadPlugins, discordClient }) {
|
|||||||
sendUpdateResult(req, res, {
|
sendUpdateResult(req, res, {
|
||||||
status,
|
status,
|
||||||
message: status.core.update_available
|
message: status.core.update_available
|
||||||
? `Safe core target ${status.core.safe_target_version} is available.`
|
? `Core version ${status.core.safe_target_version} is available.`
|
||||||
: status.core.blocked
|
: status.core.blocked
|
||||||
? status.core.blocked_reason
|
? status.core.blocked_reason
|
||||||
: "No core updates found."
|
: "Lumi core is already up to date."
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sendUpdateError(req, res, error);
|
sendUpdateError(req, res, error);
|
||||||
@ -6751,10 +6751,10 @@ function createWebServer({ loadPlugins, discordClient }) {
|
|||||||
sendUpdateResult(req, res, {
|
sendUpdateResult(req, res, {
|
||||||
plugin,
|
plugin,
|
||||||
message: plugin.update_available
|
message: plugin.update_available
|
||||||
? `Safe plugin target ${plugin.safe_target_version} is available.`
|
? `${plugin.name} version ${plugin.safe_target_version} is available.`
|
||||||
: plugin.blocked
|
: plugin.blocked
|
||||||
? plugin.blocked_reason
|
? plugin.blocked_reason
|
||||||
: "No plugin updates found."
|
: `${plugin.name} is already up to date.`
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sendUpdateError(req, res, error);
|
sendUpdateError(req, res, error);
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
const toolBadgeClass = (item) => item?.blocked ? "danger" : item?.update_available || item?.installed === false ? "warning" : "success";
|
const toolBadgeClass = (item) => item?.blocked ? "danger" : item?.update_available || item?.installed === false ? "warning" : "success";
|
||||||
const toolBadgeText = (item) => item?.blocked ? "Blocked" : item?.installed === false ? "Available" : item?.update_available ? "Update available" : "Current";
|
const toolBadgeText = (item) => item?.blocked ? "Blocked" : item?.installed === false ? "Available" : item?.update_available ? "Update available" : "Current";
|
||||||
const changelogItems = (item) => Array.isArray(item?.changelog_range) ? item.changelog_range : [];
|
const changelogItems = (item) => Array.isArray(item?.changelog_range) ? item.changelog_range : [];
|
||||||
const applyLabel = (item) => item?.unversioned_update ? "Review and update" : "Use recommended version";
|
const applyLabel = (item) => item?.unversioned_update ? "Review and continue" : item?.installed === false ? "Install plugin" : "Apply update";
|
||||||
const applyConfirmText = (item, label) => {
|
const applyConfirmText = (item, label) => {
|
||||||
const base = `${label}: create a backup, prepare recovery, apply ${item?.safe_target_version || "the selected update"}, and verify before finishing.`;
|
const base = `${label}: create a backup, prepare recovery, apply ${item?.safe_target_version || "the selected update"}, and verify before finishing.`;
|
||||||
if (!item?.unversioned_update) return base;
|
if (!item?.unversioned_update) return base;
|
||||||
@ -129,7 +129,7 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-actions update-action-row">
|
<div class="inline-actions update-action-row">
|
||||||
<form method="post" action="/admin/updates/core/check" data-update-action>
|
<form method="post" action="/admin/updates/core/check" data-update-action data-update-check-form>
|
||||||
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
||||||
<button class="button subtle" type="submit" data-update-check-button>Check for updates</button>
|
<button class="button subtle" type="submit" data-update-check-button>Check for updates</button>
|
||||||
</form>
|
</form>
|
||||||
@ -146,22 +146,26 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<% if (core.available_versions?.length) { %>
|
<% if (core.available_versions?.length) { %>
|
||||||
<fieldset class="lumi-fieldset">
|
<details class="lumi-expandable-settings update-version-picker">
|
||||||
<legend>Install a specific core version</legend>
|
<summary>
|
||||||
<form method="post" action="/admin/updates/core/apply" class="input-action-row" data-update-action data-confirm-mode="modal" data-confirm-title="Install selected core version" data-confirm-text="Create a backup, preserve local data, install the exact tagged core release, verify its version, and restart Lumi. Older code may not understand database changes made by a newer release." data-confirm-label="Install version">
|
<span><strong>Install a specific core version</strong><span class="hint">Advanced version selection</span></span>
|
||||||
|
</summary>
|
||||||
|
<div class="lumi-expandable-body">
|
||||||
|
<form method="post" action="/admin/updates/core/apply" class="input-action-row" data-update-action data-confirm-mode="modal" data-confirm-title="Install selected core version" data-confirm-text="Create a backup, preserve local data, install the exact tagged core release, verify its version, and restart Lumi. Older code may not understand database changes made by a newer release." data-confirm-label="Install selected version">
|
||||||
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Version</span>
|
<span>Core version</span>
|
||||||
<select name="version" required>
|
<select name="version" required>
|
||||||
<% core.available_versions.forEach((release) => { %>
|
<% core.available_versions.forEach((release) => { %>
|
||||||
<option value="<%= release.version %>"><%= release.version %><%= release.version === core.current_version ? " (reinstall current)" : "" %> · <%= release.ref.replace(/^refs\/tags\//, "") %></option>
|
<option value="<%= release.version %>"><%= release.version %></option>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<button class="button subtle" type="submit">Install selected version</button>
|
<button class="button subtle" type="submit">Install selected version</button>
|
||||||
</form>
|
</form>
|
||||||
<p class="hint">Versions are resolved from immutable repository tags and checked before any live files are replaced. Local databases, settings, plugins, models, uploads, and knowledge remain in place.</p>
|
<p class="hint">The selected core version is read from its immutable repository tag and verified before files are replaced. Local data remains in place.</p>
|
||||||
</fieldset>
|
</div>
|
||||||
|
</details>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
@ -259,7 +263,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<% } %>
|
<% } %>
|
||||||
<div class="inline-actions update-action-row">
|
<div class="inline-actions update-action-row">
|
||||||
<form method="post" action="/admin/updates/plugins/<%= plugin.id %>/check" data-update-action>
|
<form method="post" action="/admin/updates/plugins/<%= plugin.id %>/check" data-update-action data-update-check-form>
|
||||||
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
||||||
<button class="button subtle" type="submit" data-update-check-button>Check for updates</button>
|
<button class="button subtle" type="submit" data-update-check-button>Check for updates</button>
|
||||||
</form>
|
</form>
|
||||||
@ -281,22 +285,26 @@
|
|||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<% if (plugin.available_versions?.length) { %>
|
<% if (plugin.available_versions?.length) { %>
|
||||||
<fieldset class="lumi-fieldset">
|
<details class="lumi-expandable-settings update-version-picker">
|
||||||
<legend>Install a specific plugin version</legend>
|
<summary>
|
||||||
<form method="post" action="/admin/updates/plugins/<%= plugin.id %>/apply" class="input-action-row" data-update-action data-confirm-mode="modal" data-confirm-title="Install selected <%= plugin.name %> version" data-confirm-text="Back up this plugin, preserve its data directory, install the exact tagged release, verify the plugin version, and restart Lumi. Older plugin code may not understand newer data changes." data-confirm-label="Install version">
|
<span><strong>Install a specific <%= plugin.name %> version</strong><span class="hint">Advanced version selection</span></span>
|
||||||
|
</summary>
|
||||||
|
<div class="lumi-expandable-body">
|
||||||
|
<form method="post" action="/admin/updates/plugins/<%= plugin.id %>/apply" class="input-action-row" data-update-action data-confirm-mode="modal" data-confirm-title="Install selected <%= plugin.name %> version" data-confirm-text="Back up this plugin, preserve its data directory, install the exact tagged release, verify the plugin version, and restart Lumi. Older plugin code may not understand newer data changes." data-confirm-label="Install selected version">
|
||||||
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
<input type="hidden" name="source" value="<%= selectedSource %>" />
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span>Version</span>
|
<span><%= plugin.name %> version</span>
|
||||||
<select name="version" required>
|
<select name="version" required>
|
||||||
<% plugin.available_versions.forEach((release) => { %>
|
<% plugin.available_versions.forEach((release) => { %>
|
||||||
<option value="<%= release.version %>"><%= release.version %><%= plugin.installed !== false && release.version === plugin.current_version ? " (reinstall current)" : "" %> · core <%= release.core_version %></option>
|
<option value="<%= release.version %>"><%= release.version %></option>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
<button class="button subtle" type="submit">Install selected version</button>
|
<button class="button subtle" type="submit">Install selected version</button>
|
||||||
</form>
|
</form>
|
||||||
<p class="hint">The plugin’s <code>data</code> folder is retained. Lumi restarts so the selected plugin code is actually loaded.</p>
|
<p class="hint">Only versions published for <%= plugin.name %> are listed. The plugin’s <code>data</code> folder is retained, and Lumi restarts to load the selected code.</p>
|
||||||
</fieldset>
|
</div>
|
||||||
|
</details>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
@ -471,6 +479,7 @@
|
|||||||
if (!form.action.endsWith("/check")) return;
|
if (!form.action.endsWith("/check")) return;
|
||||||
form.addEventListener("submit", async (event) => {
|
form.addEventListener("submit", async (event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
event.stopImmediatePropagation();
|
||||||
const button = event.submitter || form.querySelector("[data-update-check-button]");
|
const button = event.submitter || form.querySelector("[data-update-check-button]");
|
||||||
const row = form.closest(".plugin-update-row, [data-update-target='core']");
|
const row = form.closest(".plugin-update-row, [data-update-target='core']");
|
||||||
setButtonLoading(button, true);
|
setButtonLoading(button, true);
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "Lumi Core",
|
"name": "Lumi Core",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"channel": "stable",
|
"channel": "stable",
|
||||||
"released_at": "2026-07-18",
|
"released_at": "2026-07-18",
|
||||||
"compatible_from": "0.1.9",
|
"compatible_from": "0.1.9",
|
||||||
"migration_kind": "version_correction",
|
"migration_kind": "patch",
|
||||||
"replaces_versions": [
|
"replaces_versions": [
|
||||||
"1.2.0"
|
"1.2.0"
|
||||||
],
|
],
|
||||||
"migration_notes": "Corrects the mistakenly published 1.2.0 version to 0.2.0. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, and secrets.",
|
"migration_notes": "Includes the 1.2.0 version correction and improves update-page behavior. Lumi synchronizes runtime dependencies on restart and preserves settings, databases, plugin data, community knowledge, AI models, runtimes, uploads, logs, and secrets.",
|
||||||
"rollback_safe": true,
|
"rollback_safe": true,
|
||||||
"requirements": [
|
"requirements": [
|
||||||
"Node.js 18 or newer"
|
"Node.js 18 or newer"
|
||||||
@ -24,7 +24,19 @@
|
|||||||
"1.2.0"
|
"1.2.0"
|
||||||
],
|
],
|
||||||
"rollback_safe": true,
|
"rollback_safe": true,
|
||||||
"migration_notes": "Automatic dependency synchronization and version correction only; preserved local data is not replaced."
|
"migration_notes": "Corrected the mistakenly published 1.2.0 version and repaired core/plugin update boundaries."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"version": "0.2.1",
|
||||||
|
"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": "Update-page behavior and wording fixes only; preserved local data is not replaced."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user