@stonerzju/opencode 1.2.17 → 1.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (261) hide show
  1. package/bin/opencode +29 -157
  2. package/package.json +29 -29
  3. package/src/acp/agent.ts +4 -4
  4. package/src/acp/session.ts +1 -1
  5. package/src/agent/agent.ts +3 -3
  6. package/src/bun/index.ts +2 -2
  7. package/src/cli/cmd/acp.ts +3 -3
  8. package/src/cli/cmd/debug/file.ts +1 -1
  9. package/src/cli/cmd/github.ts +2 -2
  10. package/src/cli/cmd/pr.ts +1 -1
  11. package/src/cli/cmd/tui/app.tsx +24 -24
  12. package/src/cli/cmd/tui/attach.ts +3 -3
  13. package/src/cli/cmd/tui/component/dialog-agent.tsx +3 -3
  14. package/src/cli/cmd/tui/component/dialog-command.tsx +3 -3
  15. package/src/cli/cmd/tui/component/dialog-mcp.tsx +5 -5
  16. package/src/cli/cmd/tui/component/dialog-model.tsx +4 -4
  17. package/src/cli/cmd/tui/component/dialog-provider.tsx +4 -4
  18. package/src/cli/cmd/tui/component/dialog-session-list.tsx +5 -5
  19. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +3 -3
  20. package/src/cli/cmd/tui/component/dialog-skill.tsx +3 -3
  21. package/src/cli/cmd/tui/component/dialog-stash.tsx +3 -3
  22. package/src/cli/cmd/tui/component/dialog-status.tsx +2 -2
  23. package/src/cli/cmd/tui/component/dialog-tag.tsx +3 -3
  24. package/src/cli/cmd/tui/component/logo.tsx +2 -2
  25. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +6 -6
  26. package/src/cli/cmd/tui/component/prompt/frecency.tsx +2 -2
  27. package/src/cli/cmd/tui/component/prompt/history.tsx +2 -2
  28. package/src/cli/cmd/tui/component/prompt/index.tsx +14 -14
  29. package/src/cli/cmd/tui/component/prompt/stash.tsx +2 -2
  30. package/src/cli/cmd/tui/component/textarea-keybindings.ts +1 -1
  31. package/src/cli/cmd/tui/component/tips.tsx +1 -1
  32. package/src/cli/cmd/tui/context/directory.ts +1 -1
  33. package/src/cli/cmd/tui/context/exit.tsx +1 -1
  34. package/src/cli/cmd/tui/context/keybind.tsx +2 -2
  35. package/src/cli/cmd/tui/context/kv.tsx +2 -2
  36. package/src/cli/cmd/tui/context/local.tsx +6 -6
  37. package/src/cli/cmd/tui/context/sync.tsx +4 -4
  38. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  39. package/src/cli/cmd/tui/context/theme.tsx +2 -2
  40. package/src/cli/cmd/tui/context/tui-config.tsx +1 -1
  41. package/src/cli/cmd/tui/event.ts +2 -2
  42. package/src/cli/cmd/tui/routes/home.tsx +6 -6
  43. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +6 -6
  44. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +6 -6
  45. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +2 -2
  46. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +3 -3
  47. package/src/cli/cmd/tui/routes/session/header.tsx +5 -5
  48. package/src/cli/cmd/tui/routes/session/index.tsx +32 -32
  49. package/src/cli/cmd/tui/routes/session/permission.tsx +4 -4
  50. package/src/cli/cmd/tui/routes/session/sidebar.tsx +4 -4
  51. package/src/cli/cmd/tui/thread.ts +9 -9
  52. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +1 -1
  53. package/src/cli/cmd/tui/ui/dialog-help.tsx +2 -2
  54. package/src/cli/cmd/tui/ui/dialog-select.tsx +5 -5
  55. package/src/cli/cmd/tui/ui/dialog.tsx +3 -3
  56. package/src/cli/cmd/tui/ui/toast.tsx +1 -1
  57. package/src/cli/cmd/tui/util/editor.ts +3 -3
  58. package/src/cli/cmd/tui/util/transcript.ts +1 -1
  59. package/src/cli/cmd/tui/worker.ts +10 -10
  60. package/src/cli/error.ts +1 -1
  61. package/src/cli/ui.ts +1 -1
  62. package/src/cli/upgrade.ts +4 -4
  63. package/src/command/index.ts +1 -1
  64. package/src/config/config.ts +10 -10
  65. package/src/config/markdown.ts +1 -1
  66. package/src/config/migrate-tui-config.ts +5 -5
  67. package/src/config/paths.ts +4 -4
  68. package/src/config/tui.ts +4 -4
  69. package/src/control/control.sql.ts +1 -1
  70. package/src/control/index.ts +1 -1
  71. package/src/control-plane/adaptors/worktree.ts +1 -1
  72. package/src/control-plane/session-proxy-middleware.ts +1 -1
  73. package/src/control-plane/workspace.sql.ts +1 -1
  74. package/src/control-plane/workspace.ts +7 -7
  75. package/src/file/index.ts +1 -1
  76. package/src/file/ripgrep.ts +2 -2
  77. package/src/file/watcher.ts +5 -5
  78. package/src/format/formatter.ts +1 -1
  79. package/src/ide/index.ts +3 -3
  80. package/src/index.ts +1 -1
  81. package/src/installation/index.ts +3 -3
  82. package/src/lsp/client.ts +3 -3
  83. package/src/lsp/index.ts +3 -3
  84. package/src/mcp/index.ts +4 -4
  85. package/src/permission/index.ts +2 -2
  86. package/src/permission/next.ts +10 -10
  87. package/src/plugin/codex.ts +1 -1
  88. package/src/plugin/copilot.ts +2 -2
  89. package/src/plugin/index.ts +1 -1
  90. package/src/project/bootstrap.ts +2 -2
  91. package/src/project/instance.ts +4 -4
  92. package/src/project/project.sql.ts +1 -1
  93. package/src/project/project.ts +5 -5
  94. package/src/project/state.ts +1 -1
  95. package/src/project/vcs.ts +4 -4
  96. package/src/provider/auth.ts +4 -4
  97. package/src/provider/error.ts +1 -1
  98. package/src/provider/models-snapshot.ts +2 -0
  99. package/src/provider/models.ts +1 -1
  100. package/src/provider/provider.ts +2 -2
  101. package/src/provider/transform.ts +2 -2
  102. package/src/pty/index.ts +5 -5
  103. package/src/question/index.ts +5 -5
  104. package/src/server/event.ts +1 -1
  105. package/src/server/mdns.ts +1 -1
  106. package/src/server/routes/global.ts +3 -3
  107. package/src/server/routes/permission.ts +1 -1
  108. package/src/server/routes/pty.ts +1 -1
  109. package/src/server/routes/session.ts +4 -4
  110. package/src/server/routes/tui.ts +1 -1
  111. package/src/server/server.ts +3 -3
  112. package/src/session/compaction.ts +7 -7
  113. package/src/session/index.ts +10 -10
  114. package/src/session/instruction.ts +1 -1
  115. package/src/session/llm.ts +11 -11
  116. package/src/session/message-v2.ts +10 -10
  117. package/src/session/message.ts +1 -1
  118. package/src/session/processor.ts +10 -10
  119. package/src/session/prompt.ts +8 -8
  120. package/src/session/retry.ts +2 -2
  121. package/src/session/revert.ts +1 -1
  122. package/src/session/session.sql.ts +3 -3
  123. package/src/session/status.ts +3 -3
  124. package/src/session/summary.ts +5 -5
  125. package/src/session/system.ts +1 -1
  126. package/src/session/todo.ts +2 -2
  127. package/src/share/share-next.ts +7 -7
  128. package/src/share/share.sql.ts +1 -1
  129. package/src/shell/shell.ts +3 -3
  130. package/src/skill/skill.ts +6 -6
  131. package/src/storage/db.ts +1 -1
  132. package/src/storage/storage.ts +1 -1
  133. package/src/tool/bash.ts +6 -6
  134. package/src/tool/edit.ts +1 -1
  135. package/src/tool/registry.ts +2 -2
  136. package/src/tool/skill.ts +1 -1
  137. package/src/tool/task.ts +3 -3
  138. package/src/util/array.ts +10 -0
  139. package/src/util/binary.ts +41 -0
  140. package/src/util/encode.ts +51 -0
  141. package/src/util/error.ts +54 -0
  142. package/src/util/identifier.ts +48 -0
  143. package/src/util/lazy.ts +4 -16
  144. package/src/util/path.ts +37 -0
  145. package/src/util/retry.ts +41 -0
  146. package/src/util/slug.ts +74 -0
  147. package/src/worktree/index.ts +3 -3
  148. package/AGENTS.md +0 -10
  149. package/BUN_SHELL_MIGRATION_PLAN.md +0 -136
  150. package/Dockerfile +0 -18
  151. package/README.md +0 -15
  152. package/bunfig.toml +0 -7
  153. package/drizzle.config.ts +0 -10
  154. package/script/build.ts +0 -224
  155. package/script/check-migrations.ts +0 -16
  156. package/script/postinstall.mjs +0 -131
  157. package/script/publish.ts +0 -181
  158. package/script/schema.ts +0 -63
  159. package/script/seed-e2e.ts +0 -50
  160. package/sst-env.d.ts +0 -10
  161. package/test/AGENTS.md +0 -81
  162. package/test/acp/agent-interface.test.ts +0 -51
  163. package/test/acp/event-subscription.test.ts +0 -683
  164. package/test/agent/agent.test.ts +0 -689
  165. package/test/bun.test.ts +0 -53
  166. package/test/cli/github-action.test.ts +0 -197
  167. package/test/cli/github-remote.test.ts +0 -80
  168. package/test/cli/import.test.ts +0 -38
  169. package/test/cli/plugin-auth-picker.test.ts +0 -120
  170. package/test/cli/tui/transcript.test.ts +0 -322
  171. package/test/config/agent-color.test.ts +0 -71
  172. package/test/config/config.test.ts +0 -1886
  173. package/test/config/fixtures/empty-frontmatter.md +0 -4
  174. package/test/config/fixtures/frontmatter.md +0 -28
  175. package/test/config/fixtures/markdown-header.md +0 -11
  176. package/test/config/fixtures/no-frontmatter.md +0 -1
  177. package/test/config/fixtures/weird-model-id.md +0 -13
  178. package/test/config/markdown.test.ts +0 -228
  179. package/test/config/tui.test.ts +0 -510
  180. package/test/control-plane/session-proxy-middleware.test.ts +0 -147
  181. package/test/control-plane/sse.test.ts +0 -56
  182. package/test/control-plane/workspace-server-sse.test.ts +0 -65
  183. package/test/control-plane/workspace-sync.test.ts +0 -97
  184. package/test/file/ignore.test.ts +0 -10
  185. package/test/file/index.test.ts +0 -394
  186. package/test/file/path-traversal.test.ts +0 -198
  187. package/test/file/ripgrep.test.ts +0 -39
  188. package/test/file/time.test.ts +0 -361
  189. package/test/fixture/db.ts +0 -11
  190. package/test/fixture/fixture.ts +0 -45
  191. package/test/fixture/lsp/fake-lsp-server.js +0 -77
  192. package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
  193. package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
  194. package/test/fixture/skills/cloudflare/SKILL.md +0 -211
  195. package/test/fixture/skills/index.json +0 -6
  196. package/test/ide/ide.test.ts +0 -82
  197. package/test/keybind.test.ts +0 -421
  198. package/test/lsp/client.test.ts +0 -95
  199. package/test/mcp/headers.test.ts +0 -153
  200. package/test/mcp/oauth-browser.test.ts +0 -249
  201. package/test/memory/abort-leak.test.ts +0 -136
  202. package/test/patch/patch.test.ts +0 -348
  203. package/test/permission/arity.test.ts +0 -33
  204. package/test/permission/next.test.ts +0 -689
  205. package/test/permission-task.test.ts +0 -319
  206. package/test/plugin/auth-override.test.ts +0 -44
  207. package/test/plugin/codex.test.ts +0 -123
  208. package/test/preload.ts +0 -80
  209. package/test/project/project.test.ts +0 -348
  210. package/test/project/worktree-remove.test.ts +0 -65
  211. package/test/provider/amazon-bedrock.test.ts +0 -446
  212. package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
  213. package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
  214. package/test/provider/gitlab-duo.test.ts +0 -262
  215. package/test/provider/provider.test.ts +0 -2220
  216. package/test/provider/transform.test.ts +0 -2353
  217. package/test/pty/pty-output-isolation.test.ts +0 -140
  218. package/test/question/question.test.ts +0 -300
  219. package/test/scheduler.test.ts +0 -73
  220. package/test/server/global-session-list.test.ts +0 -89
  221. package/test/server/session-list.test.ts +0 -90
  222. package/test/server/session-select.test.ts +0 -78
  223. package/test/session/compaction.test.ts +0 -423
  224. package/test/session/instruction.test.ts +0 -170
  225. package/test/session/llm.test.ts +0 -667
  226. package/test/session/message-v2.test.ts +0 -924
  227. package/test/session/prompt.test.ts +0 -211
  228. package/test/session/retry.test.ts +0 -188
  229. package/test/session/revert-compact.test.ts +0 -285
  230. package/test/session/session.test.ts +0 -71
  231. package/test/session/structured-output-integration.test.ts +0 -233
  232. package/test/session/structured-output.test.ts +0 -385
  233. package/test/skill/discovery.test.ts +0 -110
  234. package/test/skill/skill.test.ts +0 -388
  235. package/test/snapshot/snapshot.test.ts +0 -1180
  236. package/test/storage/json-migration.test.ts +0 -846
  237. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  238. package/test/tool/apply_patch.test.ts +0 -566
  239. package/test/tool/bash.test.ts +0 -402
  240. package/test/tool/edit.test.ts +0 -496
  241. package/test/tool/external-directory.test.ts +0 -127
  242. package/test/tool/fixtures/large-image.png +0 -0
  243. package/test/tool/fixtures/models-api.json +0 -38413
  244. package/test/tool/grep.test.ts +0 -110
  245. package/test/tool/question.test.ts +0 -107
  246. package/test/tool/read.test.ts +0 -504
  247. package/test/tool/registry.test.ts +0 -122
  248. package/test/tool/skill.test.ts +0 -112
  249. package/test/tool/truncation.test.ts +0 -160
  250. package/test/tool/webfetch.test.ts +0 -100
  251. package/test/tool/write.test.ts +0 -348
  252. package/test/util/filesystem.test.ts +0 -443
  253. package/test/util/format.test.ts +0 -59
  254. package/test/util/glob.test.ts +0 -164
  255. package/test/util/iife.test.ts +0 -36
  256. package/test/util/lazy.test.ts +0 -50
  257. package/test/util/lock.test.ts +0 -72
  258. package/test/util/process.test.ts +0 -59
  259. package/test/util/timeout.test.ts +0 -21
  260. package/test/util/wildcard.test.ts +0 -90
  261. package/tsconfig.json +0 -16
@@ -1,443 +0,0 @@
1
- import { describe, test, expect } from "bun:test"
2
- import path from "path"
3
- import fs from "fs/promises"
4
- import { Filesystem } from "../../src/util/filesystem"
5
- import { tmpdir } from "../fixture/fixture"
6
-
7
- describe("filesystem", () => {
8
- describe("exists()", () => {
9
- test("returns true for existing file", async () => {
10
- await using tmp = await tmpdir()
11
- const filepath = path.join(tmp.path, "test.txt")
12
- await fs.writeFile(filepath, "content", "utf-8")
13
-
14
- expect(await Filesystem.exists(filepath)).toBe(true)
15
- })
16
-
17
- test("returns false for non-existent file", async () => {
18
- await using tmp = await tmpdir()
19
- const filepath = path.join(tmp.path, "does-not-exist.txt")
20
-
21
- expect(await Filesystem.exists(filepath)).toBe(false)
22
- })
23
-
24
- test("returns true for existing directory", async () => {
25
- await using tmp = await tmpdir()
26
- const dirpath = path.join(tmp.path, "subdir")
27
- await fs.mkdir(dirpath)
28
-
29
- expect(await Filesystem.exists(dirpath)).toBe(true)
30
- })
31
- })
32
-
33
- describe("isDir()", () => {
34
- test("returns true for directory", async () => {
35
- await using tmp = await tmpdir()
36
- const dirpath = path.join(tmp.path, "testdir")
37
- await fs.mkdir(dirpath)
38
-
39
- expect(await Filesystem.isDir(dirpath)).toBe(true)
40
- })
41
-
42
- test("returns false for file", async () => {
43
- await using tmp = await tmpdir()
44
- const filepath = path.join(tmp.path, "test.txt")
45
- await fs.writeFile(filepath, "content", "utf-8")
46
-
47
- expect(await Filesystem.isDir(filepath)).toBe(false)
48
- })
49
-
50
- test("returns false for non-existent path", async () => {
51
- await using tmp = await tmpdir()
52
- const filepath = path.join(tmp.path, "does-not-exist")
53
-
54
- expect(await Filesystem.isDir(filepath)).toBe(false)
55
- })
56
- })
57
-
58
- describe("size()", () => {
59
- test("returns file size", async () => {
60
- await using tmp = await tmpdir()
61
- const filepath = path.join(tmp.path, "test.txt")
62
- const content = "Hello, World!"
63
- await fs.writeFile(filepath, content, "utf-8")
64
-
65
- expect(await Filesystem.size(filepath)).toBe(content.length)
66
- })
67
-
68
- test("returns 0 for non-existent file", async () => {
69
- await using tmp = await tmpdir()
70
- const filepath = path.join(tmp.path, "does-not-exist.txt")
71
-
72
- expect(await Filesystem.size(filepath)).toBe(0)
73
- })
74
-
75
- test("returns directory size", async () => {
76
- await using tmp = await tmpdir()
77
- const dirpath = path.join(tmp.path, "testdir")
78
- await fs.mkdir(dirpath)
79
-
80
- // Directories have size on some systems
81
- const size = await Filesystem.size(dirpath)
82
- expect(typeof size).toBe("number")
83
- })
84
- })
85
-
86
- describe("readText()", () => {
87
- test("reads file content", async () => {
88
- await using tmp = await tmpdir()
89
- const filepath = path.join(tmp.path, "test.txt")
90
- const content = "Hello, World!"
91
- await fs.writeFile(filepath, content, "utf-8")
92
-
93
- expect(await Filesystem.readText(filepath)).toBe(content)
94
- })
95
-
96
- test("throws for non-existent file", async () => {
97
- await using tmp = await tmpdir()
98
- const filepath = path.join(tmp.path, "does-not-exist.txt")
99
-
100
- await expect(Filesystem.readText(filepath)).rejects.toThrow()
101
- })
102
-
103
- test("reads UTF-8 content correctly", async () => {
104
- await using tmp = await tmpdir()
105
- const filepath = path.join(tmp.path, "unicode.txt")
106
- const content = "Hello 世界 🌍"
107
- await fs.writeFile(filepath, content, "utf-8")
108
-
109
- expect(await Filesystem.readText(filepath)).toBe(content)
110
- })
111
- })
112
-
113
- describe("readJson()", () => {
114
- test("reads and parses JSON", async () => {
115
- await using tmp = await tmpdir()
116
- const filepath = path.join(tmp.path, "test.json")
117
- const data = { key: "value", nested: { array: [1, 2, 3] } }
118
- await fs.writeFile(filepath, JSON.stringify(data), "utf-8")
119
-
120
- const result: typeof data = await Filesystem.readJson(filepath)
121
- expect(result).toEqual(data)
122
- })
123
-
124
- test("throws for invalid JSON", async () => {
125
- await using tmp = await tmpdir()
126
- const filepath = path.join(tmp.path, "invalid.json")
127
- await fs.writeFile(filepath, "{ invalid json", "utf-8")
128
-
129
- await expect(Filesystem.readJson(filepath)).rejects.toThrow()
130
- })
131
-
132
- test("throws for non-existent file", async () => {
133
- await using tmp = await tmpdir()
134
- const filepath = path.join(tmp.path, "does-not-exist.json")
135
-
136
- await expect(Filesystem.readJson(filepath)).rejects.toThrow()
137
- })
138
-
139
- test("returns typed data", async () => {
140
- await using tmp = await tmpdir()
141
- const filepath = path.join(tmp.path, "typed.json")
142
- interface Config {
143
- name: string
144
- version: number
145
- }
146
- const data: Config = { name: "test", version: 1 }
147
- await fs.writeFile(filepath, JSON.stringify(data), "utf-8")
148
-
149
- const result = await Filesystem.readJson<Config>(filepath)
150
- expect(result.name).toBe("test")
151
- expect(result.version).toBe(1)
152
- })
153
- })
154
-
155
- describe("readBytes()", () => {
156
- test("reads file as buffer", async () => {
157
- await using tmp = await tmpdir()
158
- const filepath = path.join(tmp.path, "test.txt")
159
- const content = "Hello, World!"
160
- await fs.writeFile(filepath, content, "utf-8")
161
-
162
- const buffer = await Filesystem.readBytes(filepath)
163
- expect(buffer).toBeInstanceOf(Buffer)
164
- expect(buffer.toString("utf-8")).toBe(content)
165
- })
166
-
167
- test("throws for non-existent file", async () => {
168
- await using tmp = await tmpdir()
169
- const filepath = path.join(tmp.path, "does-not-exist.bin")
170
-
171
- await expect(Filesystem.readBytes(filepath)).rejects.toThrow()
172
- })
173
- })
174
-
175
- describe("write()", () => {
176
- test("writes text content", async () => {
177
- await using tmp = await tmpdir()
178
- const filepath = path.join(tmp.path, "test.txt")
179
- const content = "Hello, World!"
180
-
181
- await Filesystem.write(filepath, content)
182
-
183
- expect(await fs.readFile(filepath, "utf-8")).toBe(content)
184
- })
185
-
186
- test("writes buffer content", async () => {
187
- await using tmp = await tmpdir()
188
- const filepath = path.join(tmp.path, "test.bin")
189
- const content = Buffer.from([0x00, 0x01, 0x02, 0x03])
190
-
191
- await Filesystem.write(filepath, content)
192
-
193
- const read = await fs.readFile(filepath)
194
- expect(read).toEqual(content)
195
- })
196
-
197
- test("writes with permissions", async () => {
198
- await using tmp = await tmpdir()
199
- const filepath = path.join(tmp.path, "protected.txt")
200
- const content = "secret"
201
-
202
- await Filesystem.write(filepath, content, 0o600)
203
-
204
- const stats = await fs.stat(filepath)
205
- // Check permissions on Unix
206
- if (process.platform !== "win32") {
207
- expect(stats.mode & 0o777).toBe(0o600)
208
- }
209
- })
210
-
211
- test("creates parent directories", async () => {
212
- await using tmp = await tmpdir()
213
- const filepath = path.join(tmp.path, "nested", "deep", "file.txt")
214
- const content = "nested content"
215
-
216
- await Filesystem.write(filepath, content)
217
-
218
- expect(await fs.readFile(filepath, "utf-8")).toBe(content)
219
- })
220
- })
221
-
222
- describe("writeJson()", () => {
223
- test("writes JSON data", async () => {
224
- await using tmp = await tmpdir()
225
- const filepath = path.join(tmp.path, "data.json")
226
- const data = { key: "value", number: 42 }
227
-
228
- await Filesystem.writeJson(filepath, data)
229
-
230
- const content = await fs.readFile(filepath, "utf-8")
231
- expect(JSON.parse(content)).toEqual(data)
232
- })
233
-
234
- test("writes formatted JSON", async () => {
235
- await using tmp = await tmpdir()
236
- const filepath = path.join(tmp.path, "pretty.json")
237
- const data = { key: "value" }
238
-
239
- await Filesystem.writeJson(filepath, data)
240
-
241
- const content = await fs.readFile(filepath, "utf-8")
242
- expect(content).toContain("\n")
243
- expect(content).toContain(" ")
244
- })
245
-
246
- test("writes with permissions", async () => {
247
- await using tmp = await tmpdir()
248
- const filepath = path.join(tmp.path, "config.json")
249
- const data = { secret: "data" }
250
-
251
- await Filesystem.writeJson(filepath, data, 0o600)
252
-
253
- const stats = await fs.stat(filepath)
254
- if (process.platform !== "win32") {
255
- expect(stats.mode & 0o777).toBe(0o600)
256
- }
257
- })
258
- })
259
-
260
- describe("mimeType()", () => {
261
- test("returns correct MIME type for JSON", () => {
262
- expect(Filesystem.mimeType("test.json")).toContain("application/json")
263
- })
264
-
265
- test("returns correct MIME type for JavaScript", () => {
266
- expect(Filesystem.mimeType("test.js")).toContain("javascript")
267
- })
268
-
269
- test("returns MIME type for TypeScript (or video/mp2t due to extension conflict)", () => {
270
- const mime = Filesystem.mimeType("test.ts")
271
- // .ts is ambiguous: TypeScript vs MPEG-2 TS video
272
- expect(mime === "video/mp2t" || mime === "application/typescript" || mime === "text/typescript").toBe(true)
273
- })
274
-
275
- test("returns correct MIME type for images", () => {
276
- expect(Filesystem.mimeType("test.png")).toContain("image/png")
277
- expect(Filesystem.mimeType("test.jpg")).toContain("image/jpeg")
278
- })
279
-
280
- test("returns default for unknown extension", () => {
281
- expect(Filesystem.mimeType("test.unknown")).toBe("application/octet-stream")
282
- })
283
-
284
- test("handles files without extension", () => {
285
- expect(Filesystem.mimeType("Makefile")).toBe("application/octet-stream")
286
- })
287
- })
288
-
289
- describe("windowsPath()", () => {
290
- test("converts Git Bash paths", () => {
291
- if (process.platform === "win32") {
292
- expect(Filesystem.windowsPath("/c/Users/test")).toBe("C:/Users/test")
293
- expect(Filesystem.windowsPath("/d/dev/project")).toBe("D:/dev/project")
294
- } else {
295
- expect(Filesystem.windowsPath("/c/Users/test")).toBe("/c/Users/test")
296
- }
297
- })
298
-
299
- test("converts Cygwin paths", () => {
300
- if (process.platform === "win32") {
301
- expect(Filesystem.windowsPath("/cygdrive/c/Users/test")).toBe("C:/Users/test")
302
- expect(Filesystem.windowsPath("/cygdrive/x/dev/project")).toBe("X:/dev/project")
303
- } else {
304
- expect(Filesystem.windowsPath("/cygdrive/c/Users/test")).toBe("/cygdrive/c/Users/test")
305
- }
306
- })
307
-
308
- test("converts WSL paths", () => {
309
- if (process.platform === "win32") {
310
- expect(Filesystem.windowsPath("/mnt/c/Users/test")).toBe("C:/Users/test")
311
- expect(Filesystem.windowsPath("/mnt/z/dev/project")).toBe("Z:/dev/project")
312
- } else {
313
- expect(Filesystem.windowsPath("/mnt/c/Users/test")).toBe("/mnt/c/Users/test")
314
- }
315
- })
316
-
317
- test("ignores normal Windows paths", () => {
318
- expect(Filesystem.windowsPath("C:/Users/test")).toBe("C:/Users/test")
319
- expect(Filesystem.windowsPath("D:\\dev\\project")).toBe("D:\\dev\\project")
320
- })
321
- })
322
-
323
- describe("writeStream()", () => {
324
- test("writes from Web ReadableStream", async () => {
325
- await using tmp = await tmpdir()
326
- const filepath = path.join(tmp.path, "streamed.txt")
327
- const content = "Hello from stream!"
328
- const encoder = new TextEncoder()
329
- const stream = new ReadableStream({
330
- start(controller) {
331
- controller.enqueue(encoder.encode(content))
332
- controller.close()
333
- },
334
- })
335
-
336
- await Filesystem.writeStream(filepath, stream)
337
-
338
- expect(await fs.readFile(filepath, "utf-8")).toBe(content)
339
- })
340
-
341
- test("writes from Node.js Readable stream", async () => {
342
- await using tmp = await tmpdir()
343
- const filepath = path.join(tmp.path, "node-streamed.txt")
344
- const content = "Hello from Node stream!"
345
- const { Readable } = await import("stream")
346
- const stream = Readable.from([content])
347
-
348
- await Filesystem.writeStream(filepath, stream)
349
-
350
- expect(await fs.readFile(filepath, "utf-8")).toBe(content)
351
- })
352
-
353
- test("writes binary data from Web ReadableStream", async () => {
354
- await using tmp = await tmpdir()
355
- const filepath = path.join(tmp.path, "binary.dat")
356
- const binaryData = new Uint8Array([0x00, 0x01, 0x02, 0x03, 0xff])
357
- const stream = new ReadableStream({
358
- start(controller) {
359
- controller.enqueue(binaryData)
360
- controller.close()
361
- },
362
- })
363
-
364
- await Filesystem.writeStream(filepath, stream)
365
-
366
- const read = await fs.readFile(filepath)
367
- expect(Buffer.from(read)).toEqual(Buffer.from(binaryData))
368
- })
369
-
370
- test("writes large content in chunks", async () => {
371
- await using tmp = await tmpdir()
372
- const filepath = path.join(tmp.path, "large.txt")
373
- const chunks = ["chunk1", "chunk2", "chunk3", "chunk4", "chunk5"]
374
- const stream = new ReadableStream({
375
- start(controller) {
376
- for (const chunk of chunks) {
377
- controller.enqueue(new TextEncoder().encode(chunk))
378
- }
379
- controller.close()
380
- },
381
- })
382
-
383
- await Filesystem.writeStream(filepath, stream)
384
-
385
- expect(await fs.readFile(filepath, "utf-8")).toBe(chunks.join(""))
386
- })
387
-
388
- test("creates parent directories", async () => {
389
- await using tmp = await tmpdir()
390
- const filepath = path.join(tmp.path, "nested", "deep", "streamed.txt")
391
- const content = "nested stream content"
392
- const stream = new ReadableStream({
393
- start(controller) {
394
- controller.enqueue(new TextEncoder().encode(content))
395
- controller.close()
396
- },
397
- })
398
-
399
- await Filesystem.writeStream(filepath, stream)
400
-
401
- expect(await fs.readFile(filepath, "utf-8")).toBe(content)
402
- })
403
-
404
- test("writes with permissions", async () => {
405
- await using tmp = await tmpdir()
406
- const filepath = path.join(tmp.path, "protected-stream.txt")
407
- const content = "secret stream content"
408
- const stream = new ReadableStream({
409
- start(controller) {
410
- controller.enqueue(new TextEncoder().encode(content))
411
- controller.close()
412
- },
413
- })
414
-
415
- await Filesystem.writeStream(filepath, stream, 0o600)
416
-
417
- const stats = await fs.stat(filepath)
418
- if (process.platform !== "win32") {
419
- expect(stats.mode & 0o777).toBe(0o600)
420
- }
421
- })
422
-
423
- test("writes executable with permissions", async () => {
424
- await using tmp = await tmpdir()
425
- const filepath = path.join(tmp.path, "script.sh")
426
- const content = "#!/bin/bash\necho hello"
427
- const stream = new ReadableStream({
428
- start(controller) {
429
- controller.enqueue(new TextEncoder().encode(content))
430
- controller.close()
431
- },
432
- })
433
-
434
- await Filesystem.writeStream(filepath, stream, 0o755)
435
-
436
- const stats = await fs.stat(filepath)
437
- if (process.platform !== "win32") {
438
- expect(stats.mode & 0o777).toBe(0o755)
439
- }
440
- expect(await fs.readFile(filepath, "utf-8")).toBe(content)
441
- })
442
- })
443
- })
@@ -1,59 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import { formatDuration } from "../../src/util/format"
3
-
4
- describe("util.format", () => {
5
- describe("formatDuration", () => {
6
- test("returns empty string for zero or negative values", () => {
7
- expect(formatDuration(0)).toBe("")
8
- expect(formatDuration(-1)).toBe("")
9
- expect(formatDuration(-100)).toBe("")
10
- })
11
-
12
- test("formats seconds under a minute", () => {
13
- expect(formatDuration(1)).toBe("1s")
14
- expect(formatDuration(30)).toBe("30s")
15
- expect(formatDuration(59)).toBe("59s")
16
- })
17
-
18
- test("formats minutes under an hour", () => {
19
- expect(formatDuration(60)).toBe("1m")
20
- expect(formatDuration(61)).toBe("1m 1s")
21
- expect(formatDuration(90)).toBe("1m 30s")
22
- expect(formatDuration(120)).toBe("2m")
23
- expect(formatDuration(330)).toBe("5m 30s")
24
- expect(formatDuration(3599)).toBe("59m 59s")
25
- })
26
-
27
- test("formats hours under a day", () => {
28
- expect(formatDuration(3600)).toBe("1h")
29
- expect(formatDuration(3660)).toBe("1h 1m")
30
- expect(formatDuration(7200)).toBe("2h")
31
- expect(formatDuration(8100)).toBe("2h 15m")
32
- expect(formatDuration(86399)).toBe("23h 59m")
33
- })
34
-
35
- test("formats days under a week", () => {
36
- expect(formatDuration(86400)).toBe("~1 day")
37
- expect(formatDuration(172800)).toBe("~2 days")
38
- expect(formatDuration(259200)).toBe("~3 days")
39
- expect(formatDuration(604799)).toBe("~6 days")
40
- })
41
-
42
- test("formats weeks", () => {
43
- expect(formatDuration(604800)).toBe("~1 week")
44
- expect(formatDuration(1209600)).toBe("~2 weeks")
45
- expect(formatDuration(1609200)).toBe("~2 weeks")
46
- })
47
-
48
- test("handles boundary values correctly", () => {
49
- expect(formatDuration(59)).toBe("59s")
50
- expect(formatDuration(60)).toBe("1m")
51
- expect(formatDuration(3599)).toBe("59m 59s")
52
- expect(formatDuration(3600)).toBe("1h")
53
- expect(formatDuration(86399)).toBe("23h 59m")
54
- expect(formatDuration(86400)).toBe("~1 day")
55
- expect(formatDuration(604799)).toBe("~6 days")
56
- expect(formatDuration(604800)).toBe("~1 week")
57
- })
58
- })
59
- })
@@ -1,164 +0,0 @@
1
- import { describe, test, expect } from "bun:test"
2
- import path from "path"
3
- import fs from "fs/promises"
4
- import { Glob } from "../../src/util/glob"
5
- import { tmpdir } from "../fixture/fixture"
6
-
7
- describe("Glob", () => {
8
- describe("scan()", () => {
9
- test("finds files matching pattern", async () => {
10
- await using tmp = await tmpdir()
11
- await fs.writeFile(path.join(tmp.path, "a.txt"), "", "utf-8")
12
- await fs.writeFile(path.join(tmp.path, "b.txt"), "", "utf-8")
13
- await fs.writeFile(path.join(tmp.path, "c.md"), "", "utf-8")
14
-
15
- const results = await Glob.scan("*.txt", { cwd: tmp.path })
16
-
17
- expect(results.sort()).toEqual(["a.txt", "b.txt"])
18
- })
19
-
20
- test("returns absolute paths when absolute option is true", async () => {
21
- await using tmp = await tmpdir()
22
- await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
23
-
24
- const results = await Glob.scan("*.txt", { cwd: tmp.path, absolute: true })
25
-
26
- expect(results[0]).toBe(path.join(tmp.path, "file.txt"))
27
- })
28
-
29
- test("excludes directories by default", async () => {
30
- await using tmp = await tmpdir()
31
- await fs.mkdir(path.join(tmp.path, "subdir"))
32
- await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
33
-
34
- const results = await Glob.scan("*", { cwd: tmp.path })
35
-
36
- expect(results).toEqual(["file.txt"])
37
- })
38
-
39
- test("excludes directories when include is 'file'", async () => {
40
- await using tmp = await tmpdir()
41
- await fs.mkdir(path.join(tmp.path, "subdir"))
42
- await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
43
-
44
- const results = await Glob.scan("*", { cwd: tmp.path, include: "file" })
45
-
46
- expect(results).toEqual(["file.txt"])
47
- })
48
-
49
- test("includes directories when include is 'all'", async () => {
50
- await using tmp = await tmpdir()
51
- await fs.mkdir(path.join(tmp.path, "subdir"))
52
- await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
53
-
54
- const results = await Glob.scan("*", { cwd: tmp.path, include: "all" })
55
-
56
- expect(results.sort()).toEqual(["file.txt", "subdir"])
57
- })
58
-
59
- test("handles nested patterns", async () => {
60
- await using tmp = await tmpdir()
61
- await fs.mkdir(path.join(tmp.path, "nested"), { recursive: true })
62
- await fs.writeFile(path.join(tmp.path, "nested", "deep.txt"), "", "utf-8")
63
-
64
- const results = await Glob.scan("**/*.txt", { cwd: tmp.path })
65
-
66
- expect(results).toEqual([path.join("nested", "deep.txt")])
67
- })
68
-
69
- test("returns empty array for no matches", async () => {
70
- await using tmp = await tmpdir()
71
-
72
- const results = await Glob.scan("*.nonexistent", { cwd: tmp.path })
73
-
74
- expect(results).toEqual([])
75
- })
76
-
77
- test("does not follow symlinks by default", async () => {
78
- await using tmp = await tmpdir()
79
- await fs.mkdir(path.join(tmp.path, "realdir"))
80
- await fs.writeFile(path.join(tmp.path, "realdir", "file.txt"), "", "utf-8")
81
- await fs.symlink(path.join(tmp.path, "realdir"), path.join(tmp.path, "linkdir"))
82
-
83
- const results = await Glob.scan("**/*.txt", { cwd: tmp.path })
84
-
85
- expect(results).toEqual([path.join("realdir", "file.txt")])
86
- })
87
-
88
- test("follows symlinks when symlink option is true", async () => {
89
- await using tmp = await tmpdir()
90
- await fs.mkdir(path.join(tmp.path, "realdir"))
91
- await fs.writeFile(path.join(tmp.path, "realdir", "file.txt"), "", "utf-8")
92
- await fs.symlink(path.join(tmp.path, "realdir"), path.join(tmp.path, "linkdir"))
93
-
94
- const results = await Glob.scan("**/*.txt", { cwd: tmp.path, symlink: true })
95
-
96
- expect(results.sort()).toEqual([path.join("linkdir", "file.txt"), path.join("realdir", "file.txt")])
97
- })
98
-
99
- test("includes dotfiles when dot option is true", async () => {
100
- await using tmp = await tmpdir()
101
- await fs.writeFile(path.join(tmp.path, ".hidden"), "", "utf-8")
102
- await fs.writeFile(path.join(tmp.path, "visible"), "", "utf-8")
103
-
104
- const results = await Glob.scan("*", { cwd: tmp.path, dot: true })
105
-
106
- expect(results.sort()).toEqual([".hidden", "visible"])
107
- })
108
-
109
- test("excludes dotfiles when dot option is false", async () => {
110
- await using tmp = await tmpdir()
111
- await fs.writeFile(path.join(tmp.path, ".hidden"), "", "utf-8")
112
- await fs.writeFile(path.join(tmp.path, "visible"), "", "utf-8")
113
-
114
- const results = await Glob.scan("*", { cwd: tmp.path, dot: false })
115
-
116
- expect(results).toEqual(["visible"])
117
- })
118
- })
119
-
120
- describe("scanSync()", () => {
121
- test("finds files matching pattern synchronously", async () => {
122
- await using tmp = await tmpdir()
123
- await fs.writeFile(path.join(tmp.path, "a.txt"), "", "utf-8")
124
- await fs.writeFile(path.join(tmp.path, "b.txt"), "", "utf-8")
125
-
126
- const results = Glob.scanSync("*.txt", { cwd: tmp.path })
127
-
128
- expect(results.sort()).toEqual(["a.txt", "b.txt"])
129
- })
130
-
131
- test("respects options", async () => {
132
- await using tmp = await tmpdir()
133
- await fs.mkdir(path.join(tmp.path, "subdir"))
134
- await fs.writeFile(path.join(tmp.path, "file.txt"), "", "utf-8")
135
-
136
- const results = Glob.scanSync("*", { cwd: tmp.path, include: "all" })
137
-
138
- expect(results.sort()).toEqual(["file.txt", "subdir"])
139
- })
140
- })
141
-
142
- describe("match()", () => {
143
- test("matches simple patterns", () => {
144
- expect(Glob.match("*.txt", "file.txt")).toBe(true)
145
- expect(Glob.match("*.txt", "file.js")).toBe(false)
146
- })
147
-
148
- test("matches directory patterns", () => {
149
- expect(Glob.match("**/*.js", "src/index.js")).toBe(true)
150
- expect(Glob.match("**/*.js", "src/index.ts")).toBe(false)
151
- })
152
-
153
- test("matches dot files", () => {
154
- expect(Glob.match(".*", ".gitignore")).toBe(true)
155
- expect(Glob.match("**/*.md", ".github/README.md")).toBe(true)
156
- })
157
-
158
- test("matches brace expansion", () => {
159
- expect(Glob.match("*.{js,ts}", "file.js")).toBe(true)
160
- expect(Glob.match("*.{js,ts}", "file.ts")).toBe(true)
161
- expect(Glob.match("*.{js,ts}", "file.py")).toBe(false)
162
- })
163
- })
164
- })