Compare commits
1 Commits
companion-
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ec18ac0c4 |
@ -1,5 +1,10 @@
|
||||
# Lumi changelog
|
||||
|
||||
## 0.2.27
|
||||
|
||||
- Completed bundled-plugin synchronization automatically on the first startup after a legacy core-only update, allowing production hosts on 0.2.25 to receive the Companion transcription and Song Overlay plugins in the same update flow.
|
||||
- Added a second snapshot-backed plugin synchronization boundary with automatic restoration, exact-release verification, preserved plugin data, and an idempotent completion marker.
|
||||
|
||||
## 0.2.26
|
||||
|
||||
- Added stable Lumi Companion distribution with a durable per-user installer, shared paired-device authentication, managed OBS integration, preserved identity, user-approved updates, complete legal notices, and matching application/tray branding.
|
||||
|
||||
@ -30,9 +30,12 @@ new branch.
|
||||
## Test an experimental branch
|
||||
|
||||
First update production on `main` to the latest stable release through
|
||||
**Update from repository**. Stable `0.2.26` and newer synchronize core and
|
||||
bundled plugins in one snapshot-backed transaction, so future switches in either
|
||||
direction use the same verified workflow.
|
||||
**Update from repository**. Stable `0.2.27` and newer synchronize core and
|
||||
bundled plugins in one snapshot-backed transaction. When the update begins on
|
||||
`0.2.25`, the newly started core automatically completes the exact-release
|
||||
bundled-plugin phase under a second rollback snapshot before plugins load, with
|
||||
no repair action required. Future switches in either direction then use the same
|
||||
verified workflow.
|
||||
If a `0.2.24` host enters Experimental before taking the stable update, the
|
||||
guarded compatibility bootstrap completes bundled-plugin synchronization after
|
||||
the older updater installs the newest `experimental-*` branch; expect a second
|
||||
|
||||
@ -155,6 +155,12 @@ cannot leave those layers on different releases. Core code still accesses
|
||||
optional plugin capabilities through registered framework APIs, and plugins can
|
||||
be repaired, upgraded, or downgraded independently from their version picker.
|
||||
|
||||
Hosts updating from a legacy core-only updater complete the bundled-plugin phase
|
||||
at the start of the new core, before plugins are loaded. Lumi verifies the same
|
||||
immutable release ref, takes a second core-and-plugin snapshot, preserves plugin
|
||||
data and local-only plugins, restores automatically if synchronization fails,
|
||||
and records an idempotent per-version completion marker.
|
||||
|
||||
## Runtime Dependencies
|
||||
|
||||
Start Lumi through `run.js` (the `npm start` command does this). Before starting
|
||||
|
||||
@ -14,7 +14,7 @@ editable: false
|
||||
Lumi is the core web UI and bot runtime.
|
||||
## Runtime
|
||||
Package: lumi-bot
|
||||
Version: 0.2.26
|
||||
Version: 0.2.27
|
||||
## Routes
|
||||
- POST /api/diagnostics/v1/run
|
||||
- GET /api/events
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "lumi-bot",
|
||||
"version": "0.2.26",
|
||||
"version": "0.2.27",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "lumi-bot",
|
||||
"version": "0.2.26",
|
||||
"version": "0.2.27",
|
||||
"dependencies": {
|
||||
"adm-zip": "^0.6.0",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lumi-bot",
|
||||
"version": "0.2.26",
|
||||
"version": "0.2.27",
|
||||
"private": true,
|
||||
"type": "commonjs",
|
||||
"scripts": {
|
||||
|
||||
@ -2,6 +2,38 @@
|
||||
"schema_version": 1,
|
||||
"channel": "stable",
|
||||
"releases": [
|
||||
{
|
||||
"version": "0.2.27",
|
||||
"ref": "refs/tags/v0.2.27",
|
||||
"released_at": "2026-07-24",
|
||||
"installable": true,
|
||||
"rollback_safe": true,
|
||||
"replaces_versions": [
|
||||
"1.2.0"
|
||||
],
|
||||
"data_policy": "preserve",
|
||||
"dependency_policy": "sync_on_restart",
|
||||
"migration_notes": "Completes exact-release bundled-plugin synchronization automatically after an update performed by the legacy 0.2.25 core-only updater. The second phase is snapshot-backed, restores automatically on failure, preserves plugin data and local-only plugins, and is idempotent after completion.",
|
||||
"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",
|
||||
"lumi_transcription": "0.1.0",
|
||||
"moderation": "0.1.5",
|
||||
"now_playing": "0.1.2",
|
||||
"okf": "0.1.2",
|
||||
"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.26",
|
||||
"ref": "refs/tags/v0.2.26",
|
||||
|
||||
@ -4,9 +4,9 @@ const path = require("path");
|
||||
const { findSafeTarget } = require("../src/services/versioning");
|
||||
|
||||
const root = path.join(__dirname, "..");
|
||||
const releaseVersion = "0.2.26";
|
||||
const previousStableVersion = "0.2.25";
|
||||
const priorStableVersion = "0.2.24";
|
||||
const releaseVersion = "0.2.27";
|
||||
const previousStableVersion = "0.2.26";
|
||||
const priorStableVersion = "0.2.25";
|
||||
const earliestCompatibleCoreVersion = "0.1.9";
|
||||
const introducedPlugins = {
|
||||
lumi_transcription: { version: "0.1.0", knowledge: "lumi-transcription" },
|
||||
@ -82,4 +82,4 @@ assert.equal(webSearch.minimum_lumi_version, "0.2.0");
|
||||
assert.equal(webSearch.minimum_lumi_ai_version, "0.8.2");
|
||||
assert.equal(hasVersionHeading(readText("plugins/lumi_ai_web_search/CHANGELOG.md"), webSearch.version), true);
|
||||
|
||||
console.log("Release metadata verification passed: stable core 0.2.26 after 0.2.25 with synchronized Companion plugin metadata.");
|
||||
console.log("Release metadata verification passed: stable core 0.2.27 after 0.2.26 with synchronized Companion plugin metadata.");
|
||||
|
||||
@ -13,7 +13,10 @@ const {
|
||||
parseDeploymentBranches
|
||||
} = require("../src/services/update-repository");
|
||||
const { buildCorePreservePaths } = require("../src/services/update-manager");
|
||||
const { pendingDeploymentBranch } = require("../src/services/updater");
|
||||
const {
|
||||
pendingBundledPluginSync,
|
||||
pendingDeploymentBranch
|
||||
} = require("../src/services/updater");
|
||||
|
||||
function readJson(relativePath) {
|
||||
return JSON.parse(fs.readFileSync(path.join(root, relativePath), "utf8"));
|
||||
@ -21,7 +24,7 @@ function readJson(relativePath) {
|
||||
|
||||
const releaseIndex = readJson("release-index.json");
|
||||
const releaseVersions = releaseIndex.releases.map((release) => release.version);
|
||||
assert.deepEqual(releaseVersions, ["0.2.26", "0.2.25", "0.2.24", "0.2.23", "0.2.22", "0.2.21", "0.2.20", "0.2.19", "0.2.18", "0.2.17", "0.2.16", "0.2.15", "0.2.14", "0.2.13", "0.2.12", "0.2.11", "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.deepEqual(releaseVersions, ["0.2.27", "0.2.26", "0.2.25", "0.2.24", "0.2.23", "0.2.22", "0.2.21", "0.2.20", "0.2.19", "0.2.18", "0.2.17", "0.2.16", "0.2.15", "0.2.14", "0.2.13", "0.2.12", "0.2.11", "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");
|
||||
for (const release of releaseIndex.releases) {
|
||||
assert.equal(normalizeRepositoryRef(release.ref), release.ref);
|
||||
@ -34,8 +37,8 @@ const packageVersion = readJson("package.json").version;
|
||||
const coreManifest = readJson("update-manifest.json");
|
||||
assert.equal(packageVersion, coreManifest.version);
|
||||
assert.equal(coreManifest.channel, "stable");
|
||||
assert.equal(packageVersion, "0.2.26");
|
||||
assert.equal(currentRelease.version, "0.2.26");
|
||||
assert.equal(packageVersion, "0.2.27");
|
||||
assert.equal(currentRelease.version, "0.2.27");
|
||||
assert.deepEqual(currentRelease.replaces_versions, ["1.2.0"]);
|
||||
for (const [pluginId, version] of Object.entries(currentRelease.plugins)) {
|
||||
assert.equal(readJson(`plugins/${pluginId}/plugin.json`).version, version, `${pluginId} release catalog version`);
|
||||
@ -47,6 +50,7 @@ for (const [toolId, version] of Object.entries(currentRelease.tools)) {
|
||||
const baseTarget = {
|
||||
current_version: "0.2.4",
|
||||
available_versions: [
|
||||
{ version: "0.2.27", ref: "refs/tags/v0.2.27", rollback_safe: true },
|
||||
{ version: "0.2.26", ref: "refs/tags/v0.2.26", rollback_safe: true },
|
||||
{ version: "0.2.25", ref: "refs/tags/v0.2.25", rollback_safe: true },
|
||||
{ version: "0.2.24", ref: "refs/tags/v0.2.24", rollback_safe: true },
|
||||
@ -109,6 +113,31 @@ assert.equal(pendingDeploymentBranch({ branch: "experimental-ux" }, "experimenta
|
||||
assert.equal(pendingDeploymentBranch({ ...completedExperimentalUpdate, deployed_branch: "experimental-ux" }, "experimental", "0.2.25"), null, "completed deployments must not loop");
|
||||
assert.equal(pendingDeploymentBranch({ ...completedExperimentalUpdate, deployed_branch: "main" }, "experimental", "0.2.25"), "experimental-ux", "stable re-entry must complete the experimental deployment");
|
||||
assert.equal(pendingDeploymentBranch(completedExperimentalUpdate, "main", "0.2.25"), null, "stable configuration must not bootstrap experimental code");
|
||||
const completedLegacyCoreUpdate = {
|
||||
branch: "refs/tags/v0.2.27",
|
||||
last_update_status: "complete",
|
||||
last_target_kind: "core",
|
||||
last_target_version: "0.2.27",
|
||||
last_snapshot_id: "snapshot-legacy-core"
|
||||
};
|
||||
assert.equal(
|
||||
pendingBundledPluginSync(completedLegacyCoreUpdate, "0.2.27"),
|
||||
"refs/tags/v0.2.27",
|
||||
"a core update completed by the legacy updater must synchronize its bundled plugins on startup"
|
||||
);
|
||||
assert.equal(
|
||||
pendingBundledPluginSync({
|
||||
...completedLegacyCoreUpdate,
|
||||
bundled_plugins_synced_for_version: "0.2.27"
|
||||
}, "0.2.27"),
|
||||
null,
|
||||
"completed bundled-plugin synchronization must be idempotent"
|
||||
);
|
||||
assert.equal(
|
||||
pendingBundledPluginSync({ branch: "refs/tags/v0.2.27" }, "0.2.27"),
|
||||
null,
|
||||
"repository metadata alone must never trigger startup writes"
|
||||
);
|
||||
|
||||
const corrected = buildStatus({
|
||||
kind: "core",
|
||||
@ -119,7 +148,7 @@ const corrected = buildStatus({
|
||||
channel: "stable"
|
||||
});
|
||||
assert.equal(corrected.version_correction, true);
|
||||
assert.equal(corrected.safe_target_version, "0.2.26");
|
||||
assert.equal(corrected.safe_target_version, "0.2.27");
|
||||
assert.equal(corrected.update_available, true);
|
||||
assert.equal(corrected.blocked, false);
|
||||
|
||||
@ -146,6 +175,11 @@ assert.match(
|
||||
/async function applyCoreUpdate[\s\S]*include_plugin_code: true[\s\S]*target_plugin_ids: targetPluginIds[\s\S]*applyCoreFilesFromDirectory\(managed\.path\)[\s\S]*applyPluginFiles\(path\.join\(managed\.path, "plugins", pluginId\), pluginId, \{ preserveData: true \}\)[\s\S]*syncPluginRegistry\(\)/,
|
||||
"stable core updates must transactionally carry bundled plugins and newly introduced Companion plugins"
|
||||
);
|
||||
assert.match(
|
||||
repoUpdateSource,
|
||||
/async function synchronizeBundledPluginsForCompletedCoreUpdate[\s\S]*ensureManagedRepo\(configuredRemote, sourceRef\)[\s\S]*include_plugin_code: true[\s\S]*applyPluginFiles\(path\.join\(managed\.path, "plugins", pluginId\), pluginId, \{ preserveData: true \}\)[\s\S]*bundled_plugins_synced_for_version: currentVersion[\s\S]*restoreSnapshot\(snapshotRecord\.id/,
|
||||
"legacy core updates must complete bundled-plugin synchronization transactionally on startup"
|
||||
);
|
||||
const updateManagerSource = fs.readFileSync(path.join(root, "src", "services", "update-manager.js"), "utf8");
|
||||
assert.match(updateManagerSource, /from_source_branch: snapshot\.metadata\?\.from_source_branch/);
|
||||
assert.match(updateManagerSource, /target_commit: snapshot\.metadata\?\.target_commit/);
|
||||
@ -157,9 +191,11 @@ const updaterSource = fs.readFileSync(path.join(root, "src", "services", "update
|
||||
const mainSource = fs.readFileSync(path.join(root, "src", "main.js"), "utf8");
|
||||
assert.match(updaterSource, /function registerRestartHandler/);
|
||||
assert.match(updaterSource, /async function completePendingBranchDeployment/);
|
||||
assert.match(updaterSource, /async function completePendingBundledPluginSync/);
|
||||
assert.match(updaterSource, /state\.last_update_status === "complete"[\s\S]*state\.last_target_kind === "core"[\s\S]*state\.last_target_version === localVersion/);
|
||||
assert.match(mainSource, /registerRestartHandler\(\(\) => shutdown\(10\)\)/);
|
||||
assert.match(mainSource, /await completePendingBranchDeployment\(configuredRemote, configuredBranch\)/);
|
||||
assert.match(mainSource, /await completePendingBundledPluginSync\(configuredRemote\)/);
|
||||
assert.match(mainSource, /overlayConnectorManager\.stop\(\)[\s\S]*stopPlugins\(\)[\s\S]*process\.exit\(Number\(exitCode\) === 10 \? 10 : 0\)/);
|
||||
const updatesView = fs.readFileSync(path.join(root, "src", "web", "views", "admin-updates.ejs"), "utf8");
|
||||
assert.match(serverSource, /app\.post\("\/admin\/updates\/core\/deploy-branch"/);
|
||||
|
||||
16
src/main.js
16
src/main.js
@ -11,7 +11,14 @@ const stopPlugins =
|
||||
typeof pluginService.stopPlugins === "function"
|
||||
? pluginService.stopPlugins
|
||||
: async () => {};
|
||||
const { checkForUpdates, completePendingBranchDeployment, pullUpdates, registerRestartHandler, requestRestart } = require("./services/updater");
|
||||
const {
|
||||
checkForUpdates,
|
||||
completePendingBranchDeployment,
|
||||
completePendingBundledPluginSync,
|
||||
pullUpdates,
|
||||
registerRestartHandler,
|
||||
requestRestart
|
||||
} = require("./services/updater");
|
||||
const { createCommandRouter } = require("./services/command-router");
|
||||
const { registerTopCommand } = require("./services/top");
|
||||
const logger = require("./services/logger");
|
||||
@ -37,6 +44,13 @@ async function main() {
|
||||
requestRestart({ delayMs: 0 });
|
||||
return;
|
||||
}
|
||||
const completedPluginSync = await completePendingBundledPluginSync(configuredRemote);
|
||||
if (completedPluginSync) {
|
||||
console.log(
|
||||
`Completed bundled plugin synchronization for Lumi ${completedPluginSync.version} ` +
|
||||
`(${completedPluginSync.plugin_ids.length} plugins).`
|
||||
);
|
||||
}
|
||||
registerCorePlaceholders();
|
||||
logger.hookConsole();
|
||||
const runtimeLog = logger.createLogger("core:runtime", { category: "lifecycle" });
|
||||
|
||||
@ -205,7 +205,11 @@ async function applyCoreUpdate({ source = "stable", remote = null, version = nul
|
||||
last_error: null,
|
||||
last_snapshot_id: record.id,
|
||||
last_target_kind: "core",
|
||||
last_target_version: target.safe_target_version
|
||||
last_target_version: target.safe_target_version,
|
||||
bundled_plugins_sync_status: "complete",
|
||||
bundled_plugins_synced_for_version: target.safe_target_version,
|
||||
bundled_plugins_synced_commit: managed.commit,
|
||||
bundled_plugins_synced_at: new Date().toISOString()
|
||||
});
|
||||
emitProgress(publish, "update:restart_required", { target: "core" });
|
||||
emitProgress(publish, "update:complete", { target: "core", snapshot_id: record.id });
|
||||
@ -241,6 +245,107 @@ async function applyCoreUpdate({ source = "stable", remote = null, version = nul
|
||||
});
|
||||
}
|
||||
|
||||
async function synchronizeBundledPluginsForCompletedCoreUpdate({
|
||||
remote = null,
|
||||
sourceRef,
|
||||
expectedVersion
|
||||
} = {}) {
|
||||
return withOperation("core:bundled-plugin-sync", async () => {
|
||||
const currentVersion = verifyCoreFiles(repoRoot, expectedVersion);
|
||||
const configuredRemote = remote || getSetting("git_remote", "origin");
|
||||
let managed = null;
|
||||
let marker = null;
|
||||
let snapshot = null;
|
||||
let snapshotRecord = null;
|
||||
let targetPluginIds = [];
|
||||
let stage = "opening the completed core release";
|
||||
try {
|
||||
managed = ensureManagedRepo(configuredRemote, sourceRef);
|
||||
verifyCoreFiles(managed.path, currentVersion);
|
||||
targetPluginIds = bundledPluginIds(managed.path);
|
||||
for (const pluginId of targetPluginIds) verifyPluginFiles(pluginId, managed.path);
|
||||
|
||||
stage = "preparing a bundled-plugin rollback snapshot";
|
||||
marker = createRecoveryMarker({
|
||||
target_kind: "core",
|
||||
target_id: "bundled_plugins",
|
||||
from_version: currentVersion,
|
||||
to_version: currentVersion,
|
||||
source_branch: managed.branch,
|
||||
update_method: "startup_bundled_plugin_sync",
|
||||
rollback_safe: true,
|
||||
major_crossing: false
|
||||
});
|
||||
updateRecoveryMarker({ status: "applying" });
|
||||
snapshot = await createSnapshot({
|
||||
type: "bot",
|
||||
metadata: {
|
||||
target_kind: "core",
|
||||
target_id: "bundled_plugins",
|
||||
from_version: currentVersion,
|
||||
to_version: currentVersion,
|
||||
source_branch: managed.branch,
|
||||
target_commit: managed.commit,
|
||||
update_method: "startup_bundled_plugin_sync",
|
||||
rollback_safe: true,
|
||||
recovery_marker_id: marker.id,
|
||||
include_plugin_code: true,
|
||||
target_plugin_ids: targetPluginIds
|
||||
}
|
||||
});
|
||||
snapshotRecord = finalizeSnapshot(snapshot);
|
||||
|
||||
stage = "synchronizing bundled plugins";
|
||||
for (const pluginId of targetPluginIds) {
|
||||
applyPluginFiles(path.join(managed.path, "plugins", pluginId), pluginId, { preserveData: true });
|
||||
}
|
||||
updateRecoveryMarker({ status: "verifying" });
|
||||
for (const pluginId of targetPluginIds) verifyPluginFiles(pluginId, repoRoot);
|
||||
syncPluginRegistry();
|
||||
markRecoveryMarkerComplete({ snapshot_id: snapshotRecord.id });
|
||||
writeUpdateState({
|
||||
bundled_plugins_sync_status: "complete",
|
||||
bundled_plugins_sync_error: null,
|
||||
bundled_plugins_synced_for_version: currentVersion,
|
||||
bundled_plugins_synced_commit: managed.commit,
|
||||
bundled_plugins_synced_at: new Date().toISOString()
|
||||
});
|
||||
return {
|
||||
status: "complete",
|
||||
version: currentVersion,
|
||||
source_ref: managed.branch,
|
||||
commit: managed.commit,
|
||||
plugin_ids: targetPluginIds,
|
||||
snapshot: snapshotRecord
|
||||
};
|
||||
} catch (error) {
|
||||
const originalMessage = error?.message || String(error || "Unknown synchronization error.");
|
||||
error.message = `Bundled plugin startup synchronization failed while ${stage}: ${originalMessage}`;
|
||||
if (snapshotRecord) {
|
||||
try {
|
||||
restoreSnapshot(snapshotRecord.id, {
|
||||
expectedType: "bot",
|
||||
currentVersion,
|
||||
allowUnsafeMajorRollback: true
|
||||
});
|
||||
syncPluginRegistry();
|
||||
} catch (restoreError) {
|
||||
error.message += ` Automatic restore also failed: ${restoreError.message}`;
|
||||
}
|
||||
} else if (snapshot) {
|
||||
try { discardSnapshot(snapshot); } catch {}
|
||||
}
|
||||
if (marker) markRecoveryMarkerFailed(error);
|
||||
writeUpdateState({
|
||||
bundled_plugins_sync_status: "failed",
|
||||
bundled_plugins_sync_error: error.message,
|
||||
bundled_plugins_sync_failed_at: new Date().toISOString()
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function deployCoreBranch({ branch, remote = null, publish } = {}) {
|
||||
return withOperation("core", async () => {
|
||||
const targetBranch = normalizeDeploymentBranch(branch);
|
||||
@ -346,7 +451,11 @@ async function deployCoreBranch({ branch, remote = null, publish } = {}) {
|
||||
last_snapshot_id: snapshotRecord.id,
|
||||
last_target_kind: "core",
|
||||
last_target_version: target.version,
|
||||
last_deployment_method: "branch_deploy"
|
||||
last_deployment_method: "branch_deploy",
|
||||
bundled_plugins_sync_status: "complete",
|
||||
bundled_plugins_synced_for_version: target.version,
|
||||
bundled_plugins_synced_commit: managed.commit,
|
||||
bundled_plugins_synced_at: new Date().toISOString()
|
||||
});
|
||||
emitProgress(publish, "update:restart_required", { target: "core", deployment });
|
||||
emitProgress(publish, "update:complete", { target: "core", snapshot_id: snapshotRecord.id, deployment });
|
||||
@ -564,6 +673,7 @@ function disablePluginForRecovery(pluginId, publish) {
|
||||
|
||||
module.exports = {
|
||||
applyCoreUpdate,
|
||||
synchronizeBundledPluginsForCompletedCoreUpdate,
|
||||
deployCoreBranch,
|
||||
applyPluginUpdateFromRepo,
|
||||
targetForRequestedVersion,
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
const { getUpdateStatus } = require("./update-index");
|
||||
const { applyCoreUpdate, deployCoreBranch } = require("./repo-update");
|
||||
const {
|
||||
applyCoreUpdate,
|
||||
deployCoreBranch,
|
||||
synchronizeBundledPluginsForCompletedCoreUpdate
|
||||
} = require("./repo-update");
|
||||
const {
|
||||
normalizeRepositoryTarget,
|
||||
readUpdateState,
|
||||
@ -57,6 +61,32 @@ async function completePendingBranchDeployment(remote, configuredBranch) {
|
||||
return deployCoreBranch({ branch: targetBranch, remote });
|
||||
}
|
||||
|
||||
function pendingBundledPluginSync(state, localVersion) {
|
||||
const confirmedCoreUpdate =
|
||||
state.last_update_status === "complete" &&
|
||||
state.last_target_kind === "core" &&
|
||||
state.last_target_version === localVersion &&
|
||||
Boolean(state.last_snapshot_id);
|
||||
if (
|
||||
!confirmedCoreUpdate ||
|
||||
state.bundled_plugins_synced_for_version === localVersion
|
||||
) return null;
|
||||
const sourceRef = String(state.branch || "").trim();
|
||||
return sourceRef || null;
|
||||
}
|
||||
|
||||
async function completePendingBundledPluginSync(remote) {
|
||||
const state = readUpdateState();
|
||||
const localVersion = require("../../package.json").version;
|
||||
const sourceRef = pendingBundledPluginSync(state, localVersion);
|
||||
if (!sourceRef) return null;
|
||||
return synchronizeBundledPluginsForCompletedCoreUpdate({
|
||||
remote,
|
||||
sourceRef,
|
||||
expectedVersion: localVersion
|
||||
});
|
||||
}
|
||||
|
||||
function registerRestartHandler(handler) {
|
||||
restartHandler = typeof handler === "function" ? handler : null;
|
||||
return () => {
|
||||
@ -81,9 +111,11 @@ function requestRestart(options = {}) {
|
||||
module.exports = {
|
||||
checkForUpdates,
|
||||
completePendingBranchDeployment,
|
||||
completePendingBundledPluginSync,
|
||||
pullUpdates,
|
||||
normalizeRepositoryTarget,
|
||||
pendingDeploymentBranch,
|
||||
pendingBundledPluginSync,
|
||||
resolveUpdateBranch,
|
||||
registerRestartHandler,
|
||||
requestRestart
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Lumi Core",
|
||||
"version": "0.2.26",
|
||||
"version": "0.2.27",
|
||||
"channel": "stable",
|
||||
"released_at": "2026-07-24",
|
||||
"compatible_from": "0.1.9",
|
||||
@ -8,7 +8,7 @@
|
||||
"replaces_versions": [
|
||||
"1.2.0"
|
||||
],
|
||||
"migration_notes": "Adds the stable Lumi Companion, server-hosted transcription, Song Overlay, shared paired-device authentication, and synchronized core-plus-bundled-plugin updates. Existing settings, databases, pairing records, tokens, overlays, uploads, models, secrets, plugin data, and local-only plugins are preserved.",
|
||||
"migration_notes": "Completes bundled-plugin synchronization automatically after updates performed by the legacy 0.2.25 core-only updater. The synchronization is snapshot-backed, exact-release verified, idempotent, and preserves settings, databases, pairing records, tokens, overlays, uploads, models, secrets, plugin data, and local-only plugins.",
|
||||
"rollback_safe": true,
|
||||
"requirements": [
|
||||
"Node.js 18 or newer"
|
||||
@ -325,6 +325,18 @@
|
||||
],
|
||||
"rollback_safe": true,
|
||||
"migration_notes": "Adds the shared DesignMotionHQ UX foundation, repaired overlay and resource-library workflows, and verified snapshot-backed switching between main and exact experimental branches. Existing content, settings, tokens, overlays, databases, uploads, models, feedback, secrets, plugin data, and local-only plugins are preserved."
|
||||
},
|
||||
{
|
||||
"version": "0.2.26",
|
||||
"channel": "stable",
|
||||
"released_at": "2026-07-24",
|
||||
"compatible_from": "0.1.9",
|
||||
"migration_kind": "patch",
|
||||
"replaces_versions": [
|
||||
"1.2.0"
|
||||
],
|
||||
"rollback_safe": true,
|
||||
"migration_notes": "Adds the stable Lumi Companion, server-hosted transcription, Song Overlay, shared paired-device authentication, and synchronized core-plus-bundled-plugin updates. Existing settings, databases, pairing records, tokens, overlays, uploads, models, secrets, plugin data, and local-only plugins are preserved."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user