Skip to content

Commit 8ebdbe0

Browse files
committed
fix(core): text files missclassified as binary
1 parent 38f7071 commit 8ebdbe0

File tree

2 files changed

+130
-3
lines changed

2 files changed

+130
-3
lines changed

packages/opencode/src/file/index.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,6 @@ export namespace File {
166166
"efi",
167167
"rom",
168168
"com",
169-
"bat",
170169
"cmd",
171170
"ps1",
172171
"sh",
@@ -203,11 +202,77 @@ export namespace File {
203202
"x3f",
204203
])
205204

205+
const textExtensions = new Set([
206+
"ts",
207+
"tsx",
208+
"mts",
209+
"cts",
210+
"mtsx",
211+
"ctsx",
212+
"js",
213+
"jsx",
214+
"mjs",
215+
"cjs",
216+
"sh",
217+
"bash",
218+
"zsh",
219+
"fish",
220+
"ps1",
221+
"psm1",
222+
"cmd",
223+
"bat",
224+
"json",
225+
"jsonc",
226+
"json5",
227+
"yaml",
228+
"yml",
229+
"toml",
230+
"md",
231+
"mdx",
232+
"txt",
233+
"xml",
234+
"html",
235+
"htm",
236+
"css",
237+
"scss",
238+
"sass",
239+
"less",
240+
"graphql",
241+
"gql",
242+
"sql",
243+
"ini",
244+
"cfg",
245+
"conf",
246+
"env",
247+
])
248+
249+
const textNames = new Set([
250+
"dockerfile",
251+
"makefile",
252+
".gitignore",
253+
".gitattributes",
254+
".editorconfig",
255+
".npmrc",
256+
".nvmrc",
257+
".prettierrc",
258+
".eslintrc",
259+
])
260+
206261
function isImageByExtension(filepath: string): boolean {
207262
const ext = path.extname(filepath).toLowerCase().slice(1)
208263
return imageExtensions.has(ext)
209264
}
210265

266+
function isTextByExtension(filepath: string): boolean {
267+
const ext = path.extname(filepath).toLowerCase().slice(1)
268+
return textExtensions.has(ext)
269+
}
270+
271+
function isTextByName(filepath: string): boolean {
272+
const name = path.basename(filepath).toLowerCase()
273+
return textNames.has(name)
274+
}
275+
211276
function getImageMimeType(filepath: string): string {
212277
const ext = path.extname(filepath).toLowerCase().slice(1)
213278
const mimeTypes: Record<string, string> = {
@@ -445,7 +510,9 @@ export namespace File {
445510
return { type: "text", content: "" }
446511
}
447512

448-
if (isBinaryByExtension(file)) {
513+
const text = isTextByExtension(file) || isTextByName(file)
514+
515+
if (isBinaryByExtension(file) && !text) {
449516
return { type: "binary", content: "" }
450517
}
451518

@@ -454,7 +521,7 @@ export namespace File {
454521
}
455522

456523
const mimeType = Filesystem.mimeType(full)
457-
const encode = await shouldEncode(mimeType)
524+
const encode = text ? false : await shouldEncode(mimeType)
458525

459526
if (encode && !isImage(mimeType)) {
460527
return { type: "binary", content: "", mimeType }

packages/opencode/test/file/index.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,66 @@ describe("file/index Bun.file patterns", () => {
283283
})
284284

285285
describe("shouldEncode() logic", () => {
286+
test("treats .ts files as text", async () => {
287+
await using tmp = await tmpdir()
288+
const filepath = path.join(tmp.path, "test.ts")
289+
await fs.writeFile(filepath, "export const value = 1", "utf-8")
290+
291+
await Instance.provide({
292+
directory: tmp.path,
293+
fn: async () => {
294+
const result = await File.read("test.ts")
295+
expect(result.type).toBe("text")
296+
expect(result.content).toBe("export const value = 1")
297+
},
298+
})
299+
})
300+
301+
test("treats .mts files as text", async () => {
302+
await using tmp = await tmpdir()
303+
const filepath = path.join(tmp.path, "test.mts")
304+
await fs.writeFile(filepath, "export const value = 1", "utf-8")
305+
306+
await Instance.provide({
307+
directory: tmp.path,
308+
fn: async () => {
309+
const result = await File.read("test.mts")
310+
expect(result.type).toBe("text")
311+
expect(result.content).toBe("export const value = 1")
312+
},
313+
})
314+
})
315+
316+
test("treats .sh files as text", async () => {
317+
await using tmp = await tmpdir()
318+
const filepath = path.join(tmp.path, "test.sh")
319+
await fs.writeFile(filepath, "#!/usr/bin/env bash\necho hello", "utf-8")
320+
321+
await Instance.provide({
322+
directory: tmp.path,
323+
fn: async () => {
324+
const result = await File.read("test.sh")
325+
expect(result.type).toBe("text")
326+
expect(result.content).toBe("#!/usr/bin/env bash\necho hello")
327+
},
328+
})
329+
})
330+
331+
test("treats Dockerfile as text", async () => {
332+
await using tmp = await tmpdir()
333+
const filepath = path.join(tmp.path, "Dockerfile")
334+
await fs.writeFile(filepath, "FROM alpine:3.20", "utf-8")
335+
336+
await Instance.provide({
337+
directory: tmp.path,
338+
fn: async () => {
339+
const result = await File.read("Dockerfile")
340+
expect(result.type).toBe("text")
341+
expect(result.content).toBe("FROM alpine:3.20")
342+
},
343+
})
344+
})
345+
286346
test("returns encoding info for text files", async () => {
287347
await using tmp = await tmpdir()
288348
const filepath = path.join(tmp.path, "test.txt")

0 commit comments

Comments
 (0)