Files
nexu-io__open-design/apps/packaged/tests/launcher-runtime.test.ts
PerishFire 1ff25e5b2b fix(packaged): preserve MCP runtime integrity and sidecar delivery (#8027)
* fix(daemon): keep packaged MCP discovery out of package managers

* fix(packaged): preserve MCP payload identity and require sidecar supervisor

* fix(pack): finalize runtime manifests and report entry recovery

* ci: allow isolated full Windows smoke on dispatched revisions

* test(packaged): derive recovery versions from the release channel

* test(win): read portable installer logs and retain rollback diagnostics

* test(win): distinguish portable startup reconciliation from installer sync
2026-09-14 08:13:45 +00:00

737 lines
30 KiB
TypeScript

import { mkdir, mkdtemp, readFile, rm, symlink, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join, sep } from "node:path";
import {
LAUNCHER_SCHEMA_VERSION,
resolveLauncherVersionPaths,
type LauncherDesktopHandoffDescriptor,
} from "@open-design/launcher-proto";
import { describe, expect, it } from "vitest";
import type { PackagedConfig } from "../src/config.js";
import {
confirmPackagedLauncherRuntime,
sameExecutablePath,
type PackagedLauncherRuntime,
resolvePackagedLauncherRuntime,
} from "../src/launcher-runtime.js";
import { resolvePackagedNamespacePaths } from "../src/paths.js";
function fakeConfig(root: string, appVersion = "1.2.3-beta.4"): PackagedConfig {
return {
amrProfile: null,
appVersion,
daemonCliEntry: null,
daemonSidecarEntry: null,
namespace: "release-beta",
namespaceBaseRoot: join(root, "namespaces"),
nodeCommand: null,
posthogHost: null,
posthogKey: null,
resourceRoot: join(root, "installed", "resources", "open-design"),
telemetryRelayUrl: null,
updateMetadataUrl: null,
velaWebUrl: null,
webOutputMode: "server",
webSidecarEntry: null,
webStandaloneRoot: null,
};
}
async function writeActiveMacPayloadFixture(
root: string,
config: PackagedConfig,
telemetryRelayUrl?: string,
): Promise<void> {
const version = "1.2.3-beta.5";
const versionPaths = resolveLauncherVersionPaths({
channel: "beta",
namespace: config.namespace,
root,
version,
});
const appRoot = join(versionPaths.payloadRoot, "Open Design Beta.app");
const resourcesPath = join(appRoot, "Contents", "Resources");
await mkdir(join(appRoot, "Contents", "MacOS"), { recursive: true });
await mkdir(resourcesPath, { recursive: true });
await writeFile(join(appRoot, "Contents", "MacOS", "Open Design Beta"), "");
await writeFile(
join(resourcesPath, "open-design-config.json"),
`${JSON.stringify({
appVersion: version,
...(telemetryRelayUrl == null ? {} : { telemetryRelayUrl }),
webOutputMode: "server",
})}\n`,
);
await writeFile(
versionPaths.manifestPath,
`${JSON.stringify({
channel: "beta",
entry: {
cwd: "payload/Open Design Beta.app",
executable: "payload/Open Design Beta.app/Contents/MacOS/Open Design Beta",
},
namespace: config.namespace,
payloadRoot: "payload",
platform: "darwin",
schemaVersion: LAUNCHER_SCHEMA_VERSION,
version,
})}\n`,
);
const namespaceRoot = join(
root,
"launcher",
"channels",
"beta",
"namespaces",
config.namespace,
);
await mkdir(namespaceRoot, { recursive: true });
await writeFile(
join(namespaceRoot, "runtime.json"),
`${JSON.stringify({
active: { generation: 1, version },
channel: "beta",
lastSuccessful: { generation: 0, version: config.appVersion },
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
}
describe("resolvePackagedLauncherRuntime", () => {
it.each(["present", "missing", "undeclared"] as const)(
"uses only the selected payload's daemon CLI (%s), never the installed outer CLI",
async (state) => {
const root = await mkdtemp(join(tmpdir(), "od-payload-cli-"));
try {
const config = fakeConfig(root);
config.daemonCliEntry = join(root, "installed", "old-daemon-cli.mjs");
await writeActiveMacPayloadFixture(root, config);
const versionPaths = resolveLauncherVersionPaths({
channel: "beta", namespace: config.namespace, root, version: "1.2.3-beta.5",
});
const resources = join(versionPaths.payloadRoot, "Open Design Beta.app", "Contents", "Resources");
const cli = join(resources, "app", "prebundled", "daemon", "daemon-cli.mjs");
if (state === "present") {
await mkdir(dirname(cli), { recursive: true });
await writeFile(cli, "// selected payload CLI");
}
const configPath = join(resources, "open-design-config.json");
const raw = JSON.parse(await readFile(configPath, "utf8"));
if (state !== "undeclared") raw.daemonCliEntryRelative = "app/prebundled/daemon/daemon-cli.mjs";
await writeFile(configPath, JSON.stringify(raw));
const runtime = await resolvePackagedLauncherRuntime(config, resolvePackagedNamespacePaths(config));
expect(runtime.source).toBe("payload");
expect(runtime.config.daemonCliEntry).toBe(state === "present" ? cli : null);
} finally {
await rm(root, { recursive: true, force: true });
}
},
);
it("recognizes the same executable through a symlinked root", async () => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-executable-identity-"));
try {
const physicalRoot = join(root, "physical");
const aliasRoot = join(root, "alias");
const executable = join(physicalRoot, "Open Design.app", "Contents", "MacOS", "Open Design");
await mkdir(dirname(executable), { recursive: true });
await writeFile(executable, "");
await symlink(physicalRoot, aliasRoot, "dir");
await expect(sameExecutablePath(
join(aliasRoot, "Open Design.app", "Contents", "MacOS", "Open Design"),
executable,
)).resolves.toBe(true);
} finally {
await rm(root, { force: true, recursive: true });
}
});
it.each([
{
expected: "https://relay.payload.example/v1",
name: "uses a payload relay when the historical outer has none",
outer: null,
payload: "https://relay.payload.example/v1",
},
{
expected: "https://relay.payload.example/v2",
name: "lets a payload relay replace the historical outer relay",
outer: "https://relay.outer.example/v1",
payload: "https://relay.payload.example/v2",
},
{
expected: "https://relay.outer.example/v1",
name: "inherits the historical outer relay when the payload omits it",
outer: "https://relay.outer.example/v1",
payload: undefined,
},
{
expected: "https://relay.outer.example/v1",
name: "inherits the historical outer relay when the payload relay is blank",
outer: "https://relay.outer.example/v1",
payload: " ",
},
] as const)("$name", async ({ expected, outer, payload }) => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-relay-"));
try {
const config = { ...fakeConfig(root), telemetryRelayUrl: outer };
const paths = resolvePackagedNamespacePaths(config);
await writeActiveMacPayloadFixture(root, config, payload);
const runtime = await resolvePackagedLauncherRuntime(config, paths);
expect(runtime.source).toBe("payload");
expect(runtime.config.telemetryRelayUrl).toBe(expected);
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("initializes launcher runtime state without replacing the current installed package", async () => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-runtime-"));
try {
const config = fakeConfig(root);
const paths = resolvePackagedNamespacePaths(config);
const runtime = await resolvePackagedLauncherRuntime(config, paths);
expect(runtime.source).toBe("current-package");
expect(runtime.config).toBe(config);
expect(runtime.electronNodeCommand).toBeNull();
expect(runtime.installedLaunchPath).toEqual(expect.any(String));
expect(runtime.launcherPaths.runtimePath).toBe(
join(root, "launcher", "channels", "beta", "namespaces", "release-beta", "runtime.json"),
);
expect(JSON.parse(await readFile(runtime.launcherPaths.installPath, "utf8"))).toMatchObject({
channel: "beta",
launchPath: runtime.installedLaunchPath,
namespace: "release-beta",
schemaVersion: LAUNCHER_SCHEMA_VERSION,
});
expect(JSON.parse(await readFile(runtime.launcherPaths.runtimePath, "utf8"))).toMatchObject({
active: { generation: 0, version: "1.2.3-beta.4" },
channel: "beta",
lastSuccessful: { generation: 0, version: "1.2.3-beta.4" },
namespace: "release-beta",
schemaVersion: LAUNCHER_SCHEMA_VERSION,
});
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("uses the active launcher payload when runtime state and payload manifest are valid", async () => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-payload-"));
try {
const config = fakeConfig(root, "1.2.3-beta.4");
const paths = resolvePackagedNamespacePaths(config);
const versionPaths = resolveLauncherVersionPaths({
channel: "beta",
namespace: config.namespace,
root,
version: "1.2.3-beta.5",
});
const resourcesPath = join(versionPaths.payloadRoot, "Open Design Beta.app", "Contents", "Resources");
const payloadExecutablePath = join(
versionPaths.payloadRoot,
"Open Design Beta.app",
"Contents",
"MacOS",
"Open Design Beta",
);
await mkdir(join(resourcesPath, "open-design", "bin"), { recursive: true });
await mkdir(join(versionPaths.payloadRoot, "Open Design Beta.app", "Contents", "MacOS"), { recursive: true });
await mkdir(join(resourcesPath, "prebundled", "daemon"), { recursive: true });
await mkdir(join(resourcesPath, "prebundled", "web"), { recursive: true });
await writeFile(join(resourcesPath, "open-design", "bin", "node"), "");
await writeFile(payloadExecutablePath, "");
await writeFile(join(resourcesPath, "prebundled", "daemon", "daemon-sidecar.mjs"), "");
await writeFile(join(resourcesPath, "prebundled", "web", "web-sidecar.mjs"), "");
await writeFile(
join(resourcesPath, "open-design-config.json"),
`${JSON.stringify({
appVersion: "1.2.3-beta.5",
daemonSidecarEntryRelative: "prebundled/daemon/daemon-sidecar.mjs",
nodeCommandRelative: "open-design/bin/node",
webOutputMode: "standalone",
webSidecarEntryRelative: "prebundled/web/web-sidecar.mjs",
})}\n`,
);
await writeFile(
versionPaths.manifestPath,
`${JSON.stringify({
channel: "beta",
entry: {
cwd: "payload/Open Design Beta.app",
executable: "payload/Open Design Beta.app/Contents/MacOS/Open Design Beta",
},
namespace: config.namespace,
payloadRoot: "payload",
platform: "darwin",
schemaVersion: LAUNCHER_SCHEMA_VERSION,
version: "1.2.3-beta.5",
})}\n`,
);
await mkdir(join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace), { recursive: true });
await writeFile(
join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace, "runtime.json"),
`${JSON.stringify({
active: { generation: 1, version: "1.2.3-beta.5" },
channel: "beta",
lastSuccessful: { generation: 0, version: "1.2.3-beta.4" },
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
await writeFile(
join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace, "install.json"),
`${JSON.stringify({
channel: "beta",
launchPath: "/Applications/Open Design Beta.app",
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
const runtime = await resolvePackagedLauncherRuntime(config, paths, {
// The launcher process runs from the stable installed app bundle, so
// its stable launch path matches the persisted install descriptor and
// the payload branch keeps the persisted entry untouched.
currentExecutablePath: "/Applications/Open Design Beta.app",
});
expect(runtime.source).toBe("payload");
expect(runtime.desktopExecutablePath).toBe(payloadExecutablePath);
expect(runtime.electronNodeCommand).toBeNull();
expect(runtime.installedLaunchPath).toBe("/Applications/Open Design Beta.app");
expect(runtime.targetVersion).toBe("1.2.3-beta.5");
expect(runtime.config.appVersion).toBe("1.2.3-beta.5");
expect(runtime.config.resourceRoot).toBe(join(resourcesPath, "open-design"));
expect(runtime.config.daemonSidecarEntry).toBe(join(resourcesPath, "prebundled", "daemon", "daemon-sidecar.mjs"));
expect(runtime.config.webSidecarEntry).toBe(join(resourcesPath, "prebundled", "web", "web-sidecar.mjs"));
expect(runtime.config.webStandaloneRoot).toBe(join(resourcesPath, "open-design-web-standalone"));
expect(runtime.paths.resourceRoot).toBe(join(resourcesPath, "open-design"));
await expect(readFile(runtime.launcherPaths.attemptsPath, "utf8")).rejects.toThrow();
const payloadRuntime = await resolvePackagedLauncherRuntime(config, paths, {
currentExecutablePath: payloadExecutablePath,
});
expect(payloadRuntime.selection.reason).toBe("active");
expect(JSON.parse(await readFile(payloadRuntime.launcherPaths.attemptsPath, "utf8"))).toMatchObject({
channel: "beta",
generation: 1,
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
version: "1.2.3-beta.5",
});
const handoff: LauncherDesktopHandoffDescriptor = {
channel: "beta",
createdAt: "2026-07-15T01:00:00.000Z",
handoffId: "f5d4a712-8ba9-4c28-bcad-6dbed5db2d7c",
namespace: config.namespace,
outer: {
executablePath: process.execPath,
pid: process.pid,
},
payloadExecutablePath,
previous: { generation: 0, version: "1.2.3-beta.4" },
schemaVersion: LAUNCHER_SCHEMA_VERSION,
source: { generation: 1, version: "1.2.3-beta.5" },
state: "armed",
target: { generation: 1, version: "1.2.3-beta.5" },
updatedAt: "2026-07-15T01:00:00.000Z",
};
await writeFile(payloadRuntime.launcherPaths.handoffPath, `${JSON.stringify(handoff)}\n`);
const resumedRuntime = await resolvePackagedLauncherRuntime(config, paths, {
currentExecutablePath: payloadExecutablePath,
resume: { handoffId: handoff.handoffId },
});
expect(resumedRuntime.selection.reason).toBe("active-resume");
await confirmPackagedLauncherRuntime(resumedRuntime);
await expect(readFile(resumedRuntime.launcherPaths.attemptsPath, "utf8")).rejects.toThrow();
expect(JSON.parse(await readFile(resumedRuntime.launcherPaths.handoffPath, "utf8"))).toMatchObject({
previous: { generation: 0, version: "1.2.3-beta.4" },
source: { generation: 1, version: "1.2.3-beta.5" },
state: "confirmed",
target: { generation: 1, version: "1.2.3-beta.5" },
});
expect(JSON.parse(await readFile(resumedRuntime.launcherPaths.runtimePath, "utf8"))).toMatchObject({
active: { generation: 1, version: "1.2.3-beta.5" },
lastSuccessful: { generation: 1, version: "1.2.3-beta.5" },
});
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("refreshes install.json launchPath to the current launcher executable when the persisted path is stale", async () => {
// Issue #6494: the payload branch only READ the persisted install.json
// launchPath (written by the cold-start current-package branch) and never
// refreshed it, so after an update that moved the launcher executable
// (0.17.0 Local\Programs\... → 0.18.0 launcher payload), the stale path
// kept flowing into OD_MCP_BOOTSTRAP_COMMAND and /api/mcp/install-info.
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-install-refresh-"));
try {
const config = fakeConfig(root, "1.2.3-beta.4");
const paths = resolvePackagedNamespacePaths(config);
const versionPaths = resolveLauncherVersionPaths({
channel: "beta",
namespace: config.namespace,
root,
version: "1.2.3-beta.5",
});
const resourcesPath = join(versionPaths.payloadRoot, "Open Design Beta.app", "Contents", "Resources");
const payloadExecutablePath = join(
versionPaths.payloadRoot,
"Open Design Beta.app",
"Contents",
"MacOS",
"Open Design Beta",
);
await mkdir(join(resourcesPath, "open-design", "bin"), { recursive: true });
await mkdir(join(versionPaths.payloadRoot, "Open Design Beta.app", "Contents", "MacOS"), { recursive: true });
await mkdir(join(resourcesPath, "prebundled", "daemon"), { recursive: true });
await mkdir(join(resourcesPath, "prebundled", "web"), { recursive: true });
await writeFile(join(resourcesPath, "open-design", "bin", "node"), "");
await writeFile(payloadExecutablePath, "");
await writeFile(join(resourcesPath, "prebundled", "daemon", "daemon-sidecar.mjs"), "");
await writeFile(join(resourcesPath, "prebundled", "web", "web-sidecar.mjs"), "");
await writeFile(
join(resourcesPath, "open-design-config.json"),
`${JSON.stringify({
appVersion: "1.2.3-beta.5",
daemonSidecarEntryRelative: "prebundled/daemon/daemon-sidecar.mjs",
nodeCommandRelative: "open-design/bin/node",
webOutputMode: "standalone",
webSidecarEntryRelative: "prebundled/web/web-sidecar.mjs",
})}\n`,
);
await writeFile(
versionPaths.manifestPath,
`${JSON.stringify({
channel: "beta",
entry: {
cwd: "payload/Open Design Beta.app",
executable: "payload/Open Design Beta.app/Contents/MacOS/Open Design Beta",
},
namespace: config.namespace,
payloadRoot: "payload",
platform: "darwin",
schemaVersion: LAUNCHER_SCHEMA_VERSION,
version: "1.2.3-beta.5",
})}\n`,
);
await mkdir(join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace), { recursive: true });
await writeFile(
join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace, "runtime.json"),
`${JSON.stringify({
active: { generation: 1, version: "1.2.3-beta.5" },
channel: "beta",
lastSuccessful: { generation: 0, version: "1.2.3-beta.4" },
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
// The persisted descriptor points at the previous install location
// (e.g. the 0.17.0 Local\Programs\... exe), which no longer matches
// the launcher executable that resolved this payload.
const installPath = join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace, "install.json");
await writeFile(
installPath,
`${JSON.stringify({
channel: "beta",
launchPath: "/Applications/Open Design Legacy.app",
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
const runtime = await resolvePackagedLauncherRuntime(config, paths, {
currentExecutablePath: "/Applications/Open Design Beta.app",
});
expect(runtime.source).toBe("payload");
expect(runtime.payloadDesktopProcess).toBe(false);
expect(runtime.installedLaunchPath).toBe("/Applications/Open Design Beta.app");
expect(JSON.parse(await readFile(installPath, "utf8"))).toMatchObject({
channel: "beta",
launchPath: "/Applications/Open Design Beta.app",
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
});
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("refreshes a confirmed handoff with the current last-successful payload before advancing", async () => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-handoff-refresh-"));
try {
const config = fakeConfig(root);
const paths = resolvePackagedNamespacePaths(config);
const currentPackageRuntime = await resolvePackagedLauncherRuntime(config, paths);
const firstPayload = { generation: 1, version: "1.2.3-beta.5" };
const secondPayload = { generation: 2, version: "1.2.3-beta.6" };
const secondPayloadExecutablePath = join(
root,
"launcher",
"channels",
"beta",
"namespaces",
config.namespace,
"versions",
secondPayload.version,
"payload",
"Open Design Beta.app",
"Contents",
"MacOS",
"Open Design Beta",
);
await mkdir(currentPackageRuntime.launcherPaths.stateRoot, { recursive: true });
await writeFile(
currentPackageRuntime.launcherPaths.handoffPath,
`${JSON.stringify({
channel: "beta",
createdAt: "2026-07-15T01:00:00.000Z",
handoffId: "f5d4a712-8ba9-4c28-bcad-6dbed5db2d7c",
namespace: config.namespace,
outer: {
executablePath: process.execPath,
pid: process.pid,
},
payloadExecutablePath: join(root, "payload-v1"),
previous: { generation: 0, version: "1.2.3-beta.4" },
schemaVersion: LAUNCHER_SCHEMA_VERSION,
source: firstPayload,
state: "confirmed",
target: firstPayload,
updatedAt: "2026-07-15T02:00:00.000Z",
} satisfies LauncherDesktopHandoffDescriptor)}\n`,
);
const secondPayloadRuntime = {
...currentPackageRuntime,
desktopExecutablePath: secondPayloadExecutablePath,
descriptor: {
...currentPackageRuntime.descriptor,
active: secondPayload,
lastSuccessful: firstPayload,
},
payloadDesktopProcess: true,
selection: {
pointer: secondPayload,
reason: "active",
selected: true,
},
source: "payload",
targetVersion: secondPayload.version,
} satisfies PackagedLauncherRuntime;
await confirmPackagedLauncherRuntime(secondPayloadRuntime);
expect(JSON.parse(
await readFile(secondPayloadRuntime.launcherPaths.handoffPath, "utf8"),
)).toMatchObject({
previous: firstPayload,
source: secondPayload,
state: "confirmed",
target: secondPayload,
});
await confirmPackagedLauncherRuntime({
...secondPayloadRuntime,
descriptor: {
...secondPayloadRuntime.descriptor,
lastSuccessful: secondPayload,
},
});
expect(JSON.parse(
await readFile(secondPayloadRuntime.launcherPaths.handoffPath, "utf8"),
)).toMatchObject({
previous: firstPayload,
source: secondPayload,
state: "confirmed",
target: secondPayload,
});
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("uses the Windows payload executable as the Electron-as-Node command when payload Node is absent", async () => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-win-payload-"));
try {
const config = fakeConfig(root, "1.2.3-beta.4");
const paths = resolvePackagedNamespacePaths(config);
const versionPaths = resolveLauncherVersionPaths({
channel: "beta",
namespace: config.namespace,
root,
version: "1.2.3-beta.5",
});
const resourcesPath = join(versionPaths.versionRoot, "payload", "resources");
const payloadExePath = join(versionPaths.versionRoot, "payload", "Open Design.exe");
const webStandaloneRoot = join(resourcesPath, "open-design-web-standalone");
await mkdir(join(resourcesPath, "prebundled", "daemon"), { recursive: true });
await mkdir(join(resourcesPath, "prebundled", "web"), { recursive: true });
await mkdir(webStandaloneRoot, { recursive: true });
await mkdir(join(versionPaths.versionRoot, "payload"), { recursive: true });
await writeFile(payloadExePath, "");
await writeFile(join(resourcesPath, "prebundled", "daemon", "daemon-sidecar.mjs"), "");
await writeFile(join(resourcesPath, "prebundled", "web", "web-sidecar.mjs"), "");
await writeFile(
join(resourcesPath, "open-design-config.json"),
`${JSON.stringify({
appVersion: "1.2.3-beta.5",
daemonSidecarEntryRelative: "prebundled/daemon/daemon-sidecar.mjs",
webOutputMode: "standalone",
webSidecarEntryRelative: "prebundled/web/web-sidecar.mjs",
})}\n`,
);
await writeFile(
versionPaths.manifestPath,
`${JSON.stringify({
channel: "beta",
entry: {
cwd: "payload",
executable: "payload/Open Design.exe",
},
namespace: config.namespace,
payloadRoot: "payload",
platform: "win32",
schemaVersion: LAUNCHER_SCHEMA_VERSION,
version: "1.2.3-beta.5",
})}\n`,
);
await mkdir(join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace), { recursive: true });
await writeFile(
join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace, "runtime.json"),
`${JSON.stringify({
active: { generation: 1, version: "1.2.3-beta.5" },
channel: "beta",
lastSuccessful: { generation: 0, version: "1.2.3-beta.4" },
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
const runtime = await resolvePackagedLauncherRuntime(config, paths);
expect(runtime.source).toBe("payload");
expect(runtime.config.nodeCommand).toBeNull();
if (process.platform === "win32") {
expect(runtime.electronNodeCommand).not.toBe(payloadExePath);
expect(runtime.electronNodeCommand).toContain(`${sep}en${sep}`);
await expect(readFile(runtime.electronNodeCommand ?? "", "utf8")).resolves.toBe("");
expect(runtime.config.webStandaloneRoot).not.toBe(webStandaloneRoot);
expect(runtime.config.webStandaloneRoot).toContain(`${sep}ws${sep}`);
} else {
expect(runtime.electronNodeCommand).toBe(payloadExePath);
expect(runtime.config.webStandaloneRoot).toBe(webStandaloneRoot);
}
expect(runtime.config.daemonSidecarEntry).toBe(join(resourcesPath, "prebundled", "daemon", "daemon-sidecar.mjs"));
expect(runtime.config.webSidecarEntry).toBe(join(resourcesPath, "prebundled", "web", "web-sidecar.mjs"));
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("falls back to lastSuccessful when the active payload attempt was not confirmed", async () => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-fallback-"));
try {
const config = fakeConfig(root, "1.2.3-beta.4");
const paths = resolvePackagedNamespacePaths(config);
const namespaceRoot = join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace);
await mkdir(join(namespaceRoot, "state"), { recursive: true });
await writeFile(
join(namespaceRoot, "runtime.json"),
`${JSON.stringify({
active: { generation: 1, version: "1.2.3-beta.5" },
channel: "beta",
lastSuccessful: { generation: 0, version: "1.2.3-beta.4" },
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
await writeFile(
join(namespaceRoot, "state", "attempt.json"),
`${JSON.stringify({
channel: "beta",
generation: 1,
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
version: "1.2.3-beta.5",
})}\n`,
);
const runtime = await resolvePackagedLauncherRuntime(config, paths);
expect(runtime.selection).toMatchObject({
pointer: { generation: 0, version: "1.2.3-beta.4" },
reason: "last-successful",
selected: true,
});
expect(runtime.source).toBe("current-package");
expect(runtime.config.appVersion).toBe("1.2.3-beta.4");
} finally {
await rm(root, { force: true, recursive: true });
}
});
it("lets a newer installed package supersede stale persisted payload runtime state before target selection", async () => {
const root = await mkdtemp(join(tmpdir(), "od-packaged-launcher-installed-newer-"));
try {
const config = fakeConfig(root, "1.2.3-beta.6");
const paths = resolvePackagedNamespacePaths(config);
const namespaceRoot = join(paths.installationRoot, "launcher", "channels", "beta", "namespaces", config.namespace);
await mkdir(join(namespaceRoot, "state"), { recursive: true });
await writeFile(
join(namespaceRoot, "runtime.json"),
`${JSON.stringify({
active: { generation: 1, version: "1.2.3-beta.5" },
channel: "beta",
lastSuccessful: { generation: 0, version: "1.2.3-beta.4" },
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
})}\n`,
);
await writeFile(
join(namespaceRoot, "state", "attempt.json"),
`${JSON.stringify({
channel: "beta",
generation: 1,
namespace: config.namespace,
schemaVersion: LAUNCHER_SCHEMA_VERSION,
version: "1.2.3-beta.5",
})}\n`,
);
const runtime = await resolvePackagedLauncherRuntime(config, paths);
expect(runtime.source).toBe("current-package");
expect(runtime.config.appVersion).toBe("1.2.3-beta.6");
expect(JSON.parse(await readFile(runtime.launcherPaths.runtimePath, "utf8"))).toMatchObject({
active: { generation: 0, version: "1.2.3-beta.6" },
lastSuccessful: { generation: 0, version: "1.2.3-beta.6" },
});
await expect(readFile(runtime.launcherPaths.attemptsPath, "utf8")).rejects.toThrow();
expect(JSON.parse(await readFile(runtime.launcherPaths.cleanupPath, "utf8"))).toMatchObject({
currentVersion: "1.2.3-beta.6",
versions: expect.arrayContaining([
expect.objectContaining({ reason: "older-than-bound-package", state: "deprecated", version: "1.2.3-beta.5" }),
expect.objectContaining({ reason: "older-than-bound-package", state: "deprecated", version: "1.2.3-beta.4" }),
expect.objectContaining({ reason: "current-bound-package", state: "retained", version: "1.2.3-beta.6" }),
]),
});
} finally {
await rm(root, { force: true, recursive: true });
}
});
});