diff --git a/.opencode/command/commit.md b/.opencode/command/commit.md index 46673d95a1..c318ed54b1 100644 --- a/.opencode/command/commit.md +++ b/.opencode/command/commit.md @@ -1,5 +1,6 @@ --- description: git commit and push +model: opencode/glm-4.6 --- commit and push diff --git a/packages/opencode/src/project/project.ts b/packages/opencode/src/project/project.ts index ce5129d573..c5c92983dd 100644 --- a/packages/opencode/src/project/project.ts +++ b/packages/opencode/src/project/project.ts @@ -119,7 +119,29 @@ export namespace Project { return existing } - async function discover(input: Pick) {} + export async function discover(input: Pick) { + const glob = new Bun.Glob("**/{favicon,icon,logo}.{ico,png,svg,jpg,jpeg,webp}") + for await (const match of glob.scan({ + cwd: input.worktree, + absolute: true, + onlyFiles: true, + followSymlinks: false, + dot: false, + })) { + const file = Bun.file(match) + const buffer = await file.arrayBuffer() + const base64 = Buffer.from(buffer).toString("base64") + const mime = file.type || "image/png" + const url = `data:${mime};base64,${base64}` + await Storage.update(["project", input.id], (draft) => { + draft.icon = { + url, + color: draft.icon?.color ?? "#000000", + } + }) + return + } + } async function migrateFromGlobal(newProjectID: string, worktree: string) { const globalProject = await Storage.read(["project", "global"]).catch(() => undefined) diff --git a/packages/opencode/test/project/project.test.ts b/packages/opencode/test/project/project.test.ts index da5af31b98..c3474ca577 100644 --- a/packages/opencode/test/project/project.test.ts +++ b/packages/opencode/test/project/project.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test" import { Project } from "../../src/project/project" import { Log } from "../../src/util/log" +import { Storage } from "../../src/storage/storage" import { $ } from "bun" import path from "path" import { tmpdir } from "../fixture/fixture" @@ -39,3 +40,79 @@ describe("Project.fromDirectory", () => { expect(fileExists).toBe(true) }) }) + +describe("Project.discover", () => { + test("should discover favicon.png in root", async () => { + await using tmp = await tmpdir({ git: true }) + const project = await Project.fromDirectory(tmp.path) + + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) + await Bun.write(path.join(tmp.path, "favicon.png"), pngData) + + await Project.discover({ id: project.id, worktree: tmp.path }) + + const updated = await Storage.read(["project", project.id]) + expect(updated.icon).toBeDefined() + expect(updated.icon?.url).toStartWith("data:") + expect(updated.icon?.url).toContain("base64") + expect(updated.icon?.color).toBe("#000000") + }) + + test("should discover icon.svg in subdirectory", async () => { + await using tmp = await tmpdir({ git: true }) + const project = await Project.fromDirectory(tmp.path) + + await $`mkdir -p ${path.join(tmp.path, "public")}`.quiet() + await Bun.write(path.join(tmp.path, "public", "icon.svg"), "") + + await Project.discover({ id: project.id, worktree: tmp.path }) + + const updated = await Storage.read(["project", project.id]) + expect(updated.icon).toBeDefined() + expect(updated.icon?.url).toStartWith("data:") + expect(updated.icon?.url).toContain("base64") + }) + + test("should discover logo.ico", async () => { + await using tmp = await tmpdir({ git: true }) + const project = await Project.fromDirectory(tmp.path) + + const icoData = Buffer.from([0x00, 0x00, 0x01, 0x00]) + await Bun.write(path.join(tmp.path, "logo.ico"), icoData) + + await Project.discover({ id: project.id, worktree: tmp.path }) + + const updated = await Storage.read(["project", project.id]) + expect(updated.icon).toBeDefined() + expect(updated.icon?.url).toStartWith("data:") + }) + + test("should not discover non-image files", async () => { + await using tmp = await tmpdir({ git: true }) + const project = await Project.fromDirectory(tmp.path) + + await Bun.write(path.join(tmp.path, "favicon.txt"), "not an image") + + await Project.discover({ id: project.id, worktree: tmp.path }) + + const updated = await Storage.read(["project", project.id]) + expect(updated.icon).toBeUndefined() + }) + + test("should preserve existing color when discovering icon", async () => { + await using tmp = await tmpdir({ git: true }) + const project = await Project.fromDirectory(tmp.path) + + await Storage.update(["project", project.id], (draft) => { + draft.icon = { url: "", color: "#ff0000" } + }) + + const pngData = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]) + await Bun.write(path.join(tmp.path, "favicon.png"), pngData) + + await Project.discover({ id: project.id, worktree: tmp.path }) + + const updated = await Storage.read(["project", project.id]) + expect(updated.icon?.color).toBe("#ff0000") + }) +})