Files
opencode/packages/desktop/electron.vite.config.test.ts

132 lines
5.5 KiB
TypeScript

import { expect, test } from "bun:test"
import { existsSync } from "node:fs"
import path from "node:path"
import { MainConfigFactory } from "electron-vite"
import { build } from "vite"
import createConfig from "./electron.vite.config"
import pkg from "./package.json"
import { CLI_BINARIES, nodePtyPackage } from "./script/utils"
const load = async (command: "build" | "serve") => ({
config: await createConfig({ command, mode: command === "build" ? "production" : "development" }),
})
// Bundled into the main process, so the manifest must not also ship them as external packages.
const bundled = ["effect", "@effect/platform-node", "@effect/platform-node-shared", "drizzle-orm"]
test("minifies only builds, previews onboarding only in development, and ships sourcemaps and public assets", async () => {
const previous = process.env.OPENCODE_TEST_ONBOARDING
const previousTarget = process.env.OPENCODE_TARGET
try {
process.env.OPENCODE_TEST_ONBOARDING = "1"
for (const command of ["build", "serve"] as const) {
const result = await load(command)
for (const target of [result.config.main, result.config.preload, result.config.renderer])
expect(target?.build?.minify).toBe(command === "build")
expect(result.config.renderer?.define?.["import.meta.env.OPENCODE_TEST_ONBOARDING"]).toBe(
JSON.stringify(command === "serve"),
)
}
delete process.env.OPENCODE_TEST_ONBOARDING
const serve = await load("serve")
expect(serve.config.renderer?.define?.["import.meta.env.OPENCODE_TEST_ONBOARDING"]).toBe("false")
for (const item of CLI_BINARIES) {
process.env.OPENCODE_TARGET = item.target
const main = (await load("build")).config.main
expect(main?.build?.externalizeDeps).toEqual({
exclude: bundled,
include: [nodePtyPackage(item.target)],
})
}
} finally {
delete process.env.OPENCODE_TEST_ONBOARDING
delete process.env.OPENCODE_TARGET
if (previous !== undefined) process.env.OPENCODE_TEST_ONBOARDING = previous
if (previousTarget !== undefined) process.env.OPENCODE_TARGET = previousTarget
}
const renderer = (await load("build")).config.renderer
// Sentry uploads the renderer source maps, then deletes them.
expect(renderer?.build?.sourcemap).toBe(true)
// Vite resolves publicDir from the renderer root, not from the config file.
const root = renderer?.root ?? ""
const publicDir = renderer?.publicDir || ""
expect(existsSync(path.resolve(import.meta.dirname, root, publicDir, "oc-theme-preload.js"))).toBe(true)
})
test("bundles one Effect runtime and Drizzle while keeping native dependencies external", async () => {
for (const name of bundled) {
expect(Object.keys(pkg.dependencies)).not.toContain(name)
expect(Object.keys(pkg.optionalDependencies)).not.toContain(name)
}
expect(Object.keys(pkg.optionalDependencies).filter((name) => name.startsWith("@lydell/node-pty-"))).toEqual([
"@lydell/node-pty-darwin-arm64",
"@lydell/node-pty-darwin-x64",
"@lydell/node-pty-linux-arm64",
"@lydell/node-pty-linux-x64",
"@lydell/node-pty-win32-arm64",
"@lydell/node-pty-win32-x64",
])
const previousTarget = process.env.OPENCODE_TARGET
// Build against the opposite architecture to prove cross-packaging resolves the target's native package, not the host's.
const crossTarget = process.arch === "arm64" ? "windows-x64" : "windows-arm64"
const crossNodePty = nodePtyPackage(crossTarget)
try {
process.env.OPENCODE_TARGET = crossTarget
const result = await load("build")
if (!result.config.main) throw new Error("Missing main-process build configuration")
const config = await new MainConfigFactory(
result.config.main,
{ configFile: false, mode: "production" },
{ root: import.meta.dirname },
).build()
config.build = { ...config.build, minify: false, write: false }
config.logLevel = "silent"
const output = await build(config)
const chunks = (Array.isArray(output) ? output : [output]).flatMap((item) =>
"output" in item ? item.output.filter((entry) => entry.type === "chunk") : [],
)
expect(chunks.length).toBeGreaterThan(0)
// Resource resolution must not depend on which lazy entry owns DesktopPaths.
expect(chunks.every((chunk) => !chunk.fileName.includes("/"))).toBe(true)
const imports = chunks.flatMap((chunk) => [...chunk.imports, ...chunk.dynamicImports])
const modules = chunks.flatMap((chunk) => Object.keys(chunk.modules))
for (const name of bundled) {
expect(imports.filter((id) => id === name || id.startsWith(`${name}/`))).toEqual([])
expect(modules.some((id) => id.includes(`/node_modules/${name}/`))).toBe(true)
}
const effect = modules.filter((id) => id.includes("/node_modules/effect/"))
expect(new Set(effect.map((id) => id.split("/node_modules/effect/")[0])).size).toBe(1)
expect(new Set(effect).size).toBe(effect.length)
expect(imports).toContain("electron")
expect(imports).toContain("node:sqlite")
expect(chunks.some((chunk) => chunk.dynamicImports.includes("@zip.js/zip.js"))).toBe(true)
expect(imports).toContain(crossNodePty)
expect(imports).not.toContain(`@lydell/node-pty-${process.platform}-${process.arch}`)
expect(modules.some((id) => id.includes("/node_modules/msgpackr"))).toBe(false)
expect(chunks.some((chunk) => chunk.code.includes("msgpackr"))).toBe(false)
} finally {
delete process.env.OPENCODE_TARGET
if (previousTarget !== undefined) process.env.OPENCODE_TARGET = previousTarget
}
}, 30_000)