diff --git a/packages/opencode/src/file/index.ts b/packages/opencode/src/file/index.ts index bfe120f13..d1d24c364 100644 --- a/packages/opencode/src/file/index.ts +++ b/packages/opencode/src/file/index.ts @@ -166,7 +166,6 @@ export namespace File { "efi", "rom", "com", - "bat", "cmd", "ps1", "sh", @@ -203,11 +202,77 @@ export namespace File { "x3f", ]) + const textExtensions = new Set([ + "ts", + "tsx", + "mts", + "cts", + "mtsx", + "ctsx", + "js", + "jsx", + "mjs", + "cjs", + "sh", + "bash", + "zsh", + "fish", + "ps1", + "psm1", + "cmd", + "bat", + "json", + "jsonc", + "json5", + "yaml", + "yml", + "toml", + "md", + "mdx", + "txt", + "xml", + "html", + "htm", + "css", + "scss", + "sass", + "less", + "graphql", + "gql", + "sql", + "ini", + "cfg", + "conf", + "env", + ]) + + const textNames = new Set([ + "dockerfile", + "makefile", + ".gitignore", + ".gitattributes", + ".editorconfig", + ".npmrc", + ".nvmrc", + ".prettierrc", + ".eslintrc", + ]) + function isImageByExtension(filepath: string): boolean { const ext = path.extname(filepath).toLowerCase().slice(1) return imageExtensions.has(ext) } + function isTextByExtension(filepath: string): boolean { + const ext = path.extname(filepath).toLowerCase().slice(1) + return textExtensions.has(ext) + } + + function isTextByName(filepath: string): boolean { + const name = path.basename(filepath).toLowerCase() + return textNames.has(name) + } + function getImageMimeType(filepath: string): string { const ext = path.extname(filepath).toLowerCase().slice(1) const mimeTypes: Record = { @@ -445,7 +510,9 @@ export namespace File { return { type: "text", content: "" } } - if (isBinaryByExtension(file)) { + const text = isTextByExtension(file) || isTextByName(file) + + if (isBinaryByExtension(file) && !text) { return { type: "binary", content: "" } } @@ -454,7 +521,7 @@ export namespace File { } const mimeType = Filesystem.mimeType(full) - const encode = await shouldEncode(mimeType) + const encode = text ? false : await shouldEncode(mimeType) if (encode && !isImage(mimeType)) { return { type: "binary", content: "", mimeType } diff --git a/packages/opencode/test/file/index.test.ts b/packages/opencode/test/file/index.test.ts index 758886bd5..053a64e20 100644 --- a/packages/opencode/test/file/index.test.ts +++ b/packages/opencode/test/file/index.test.ts @@ -283,6 +283,66 @@ describe("file/index Bun.file patterns", () => { }) describe("shouldEncode() logic", () => { + test("treats .ts files as text", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "test.ts") + await fs.writeFile(filepath, "export const value = 1", "utf-8") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const result = await File.read("test.ts") + expect(result.type).toBe("text") + expect(result.content).toBe("export const value = 1") + }, + }) + }) + + test("treats .mts files as text", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "test.mts") + await fs.writeFile(filepath, "export const value = 1", "utf-8") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const result = await File.read("test.mts") + expect(result.type).toBe("text") + expect(result.content).toBe("export const value = 1") + }, + }) + }) + + test("treats .sh files as text", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "test.sh") + await fs.writeFile(filepath, "#!/usr/bin/env bash\necho hello", "utf-8") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const result = await File.read("test.sh") + expect(result.type).toBe("text") + expect(result.content).toBe("#!/usr/bin/env bash\necho hello") + }, + }) + }) + + test("treats Dockerfile as text", async () => { + await using tmp = await tmpdir() + const filepath = path.join(tmp.path, "Dockerfile") + await fs.writeFile(filepath, "FROM alpine:3.20", "utf-8") + + await Instance.provide({ + directory: tmp.path, + fn: async () => { + const result = await File.read("Dockerfile") + expect(result.type).toBe("text") + expect(result.content).toBe("FROM alpine:3.20") + }, + }) + }) + test("returns encoding info for text files", async () => { await using tmp = await tmpdir() const filepath = path.join(tmp.path, "test.txt")