fix: tfcode web fails after npm install — copy app dist in postinstall

The web command crashed because postinstall scripts never copied the
app/dist directory from the platform package. Added copyAppDir() to
both postinstall scripts, multi-path resolution in findAppDir() and
server static serving, and updated branding to use local favicon.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gab
2026-04-15 13:57:44 +10:00
parent c3e504f036
commit 3639218879
15 changed files with 251 additions and 175 deletions

View File

@@ -11,9 +11,6 @@ export const ServeCommand = cmd({
builder: (yargs) => withNetworkOptions(yargs),
describe: "starts a headless tfcode server",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
console.log("Warning: OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
const opts = await resolveNetworkOptions(args)
const server = Server.listen(opts)
console.log(`tfcode server listening on http://${server.hostname}:${server.port}`)

View File

@@ -27,8 +27,22 @@ function getNetworkIPs() {
return results
}
function findAppDir(): string {
const binDir = path.dirname(process.execPath)
const candidates = [
path.join(binDir, "app"),
path.join(binDir, "..", "app"),
path.join(import.meta.dirname, "..", "app"),
path.join(import.meta.dirname, "..", "..", "..", "..", "app"),
]
for (const dir of candidates) {
if (existsSync(path.join(dir, "dist", "index.html"))) return dir
}
return candidates[0]
}
async function ensureAppBuilt() {
const appDir = path.join(import.meta.dirname, "..", "..", "..", "..", "app")
const appDir = findAppDir()
const distDir = path.join(appDir, "dist")
if (!existsSync(distDir) || !existsSync(path.join(distDir, "index.html"))) {
@@ -44,10 +58,6 @@ export const WebCommand = cmd({
builder: (yargs) => withNetworkOptions(yargs),
describe: "start tfcode server and open web interface",
handler: async (args) => {
if (!Flag.OPENCODE_SERVER_PASSWORD) {
UI.println(UI.Style.TEXT_WARNING_BOLD + "! " + "OPENCODE_SERVER_PASSWORD is not set; server is unsecured.")
}
await ensureAppBuilt()
const opts = await resolveNetworkOptions(args)

View File

@@ -5,6 +5,7 @@ import { cors } from "hono/cors"
import { basicAuth } from "hono/basic-auth"
import z from "zod"
import path from "path"
import { existsSync } from "fs"
import { Provider } from "../provider/provider"
import { NamedError } from "@opencode-ai/util/error"
import { LSP } from "../lsp"
@@ -530,8 +531,15 @@ export namespace Server {
)
.all("/*", async (c) => {
const reqPath = c.req.path === "/" ? "/index.html" : c.req.path
const appDist = path.join(import.meta.dirname, "..", "..", "..", "app", "dist")
const publicDir = path.join(import.meta.dirname, "..", "..", "..", "app", "public")
const binDir = path.dirname(process.execPath)
const appDistCandidates = [
path.join(binDir, "app", "dist"),
path.join(binDir, "..", "app", "dist"),
path.join(import.meta.dirname, "..", "app", "dist"),
path.join(import.meta.dirname, "..", "..", "..", "app", "dist"),
]
const appDist = appDistCandidates.find((d) => existsSync(path.join(d, "index.html"))) ?? appDistCandidates[0]
const publicDir = path.join(appDist, "..", "public")
let filePath = path.join(appDist, reqPath)
let file = Bun.file(filePath)