070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
331 lines
15 KiB
TypeScript
331 lines
15 KiB
TypeScript
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join, sep } from "node:path";
|
|
|
|
import { LAUNCHER_SCHEMA_VERSION, resolveLauncherVersionPaths } from "@open-design/launcher-proto";
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import type { PackagedConfig } from "../src/config.js";
|
|
import { confirmPackagedLauncherRuntime, 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,
|
|
webOutputMode: "server",
|
|
webSidecarEntry: null,
|
|
webStandaloneRoot: null,
|
|
};
|
|
}
|
|
|
|
describe("resolvePackagedLauncherRuntime", () => {
|
|
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");
|
|
await mkdir(join(resourcesPath, "open-design", "bin"), { 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(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);
|
|
|
|
expect(runtime.source).toBe("payload");
|
|
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"));
|
|
expect(JSON.parse(await readFile(runtime.launcherPaths.attemptsPath, "utf8"))).toMatchObject({
|
|
channel: "beta",
|
|
generation: 1,
|
|
namespace: config.namespace,
|
|
schemaVersion: LAUNCHER_SCHEMA_VERSION,
|
|
version: "1.2.3-beta.5",
|
|
});
|
|
|
|
await confirmPackagedLauncherRuntime(runtime);
|
|
await expect(readFile(runtime.launcherPaths.attemptsPath, "utf8")).rejects.toThrow();
|
|
expect(JSON.parse(await readFile(runtime.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("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 });
|
|
}
|
|
});
|
|
});
|