@stonerzju/opencode 1.2.17 → 1.2.19
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.
- package/bin/opencode +29 -157
- package/package.json +29 -29
- package/src/acp/agent.ts +4 -4
- package/src/acp/session.ts +1 -1
- package/src/agent/agent.ts +3 -3
- package/src/bun/index.ts +2 -2
- package/src/cli/cmd/acp.ts +3 -3
- package/src/cli/cmd/debug/file.ts +1 -1
- package/src/cli/cmd/github.ts +2 -2
- package/src/cli/cmd/pr.ts +1 -1
- package/src/cli/cmd/tui/app.tsx +24 -24
- package/src/cli/cmd/tui/attach.ts +3 -3
- package/src/cli/cmd/tui/component/dialog-agent.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-command.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +5 -5
- package/src/cli/cmd/tui/component/dialog-model.tsx +4 -4
- package/src/cli/cmd/tui/component/dialog-provider.tsx +4 -4
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +5 -5
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-skill.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-stash.tsx +3 -3
- package/src/cli/cmd/tui/component/dialog-status.tsx +2 -2
- package/src/cli/cmd/tui/component/dialog-tag.tsx +3 -3
- package/src/cli/cmd/tui/component/logo.tsx +2 -2
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +6 -6
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +2 -2
- package/src/cli/cmd/tui/component/prompt/history.tsx +2 -2
- package/src/cli/cmd/tui/component/prompt/index.tsx +14 -14
- package/src/cli/cmd/tui/component/prompt/stash.tsx +2 -2
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +1 -1
- package/src/cli/cmd/tui/component/tips.tsx +1 -1
- package/src/cli/cmd/tui/context/directory.ts +1 -1
- package/src/cli/cmd/tui/context/exit.tsx +1 -1
- package/src/cli/cmd/tui/context/keybind.tsx +2 -2
- package/src/cli/cmd/tui/context/kv.tsx +2 -2
- package/src/cli/cmd/tui/context/local.tsx +6 -6
- package/src/cli/cmd/tui/context/sync.tsx +4 -4
- package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
- package/src/cli/cmd/tui/context/theme.tsx +2 -2
- package/src/cli/cmd/tui/context/tui-config.tsx +1 -1
- package/src/cli/cmd/tui/event.ts +2 -2
- package/src/cli/cmd/tui/routes/home.tsx +6 -6
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +6 -6
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +6 -6
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +2 -2
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +3 -3
- package/src/cli/cmd/tui/routes/session/header.tsx +5 -5
- package/src/cli/cmd/tui/routes/session/index.tsx +32 -32
- package/src/cli/cmd/tui/routes/session/permission.tsx +4 -4
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +4 -4
- package/src/cli/cmd/tui/thread.ts +9 -9
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +1 -1
- package/src/cli/cmd/tui/ui/dialog-help.tsx +2 -2
- package/src/cli/cmd/tui/ui/dialog-select.tsx +5 -5
- package/src/cli/cmd/tui/ui/dialog.tsx +3 -3
- package/src/cli/cmd/tui/ui/toast.tsx +1 -1
- package/src/cli/cmd/tui/util/editor.ts +3 -3
- package/src/cli/cmd/tui/util/transcript.ts +1 -1
- package/src/cli/cmd/tui/worker.ts +10 -10
- package/src/cli/error.ts +1 -1
- package/src/cli/ui.ts +1 -1
- package/src/cli/upgrade.ts +4 -4
- package/src/command/index.ts +1 -1
- package/src/config/config.ts +10 -10
- package/src/config/markdown.ts +1 -1
- package/src/config/migrate-tui-config.ts +5 -5
- package/src/config/paths.ts +4 -4
- package/src/config/tui.ts +4 -4
- package/src/control/control.sql.ts +1 -1
- package/src/control/index.ts +1 -1
- package/src/control-plane/adaptors/worktree.ts +1 -1
- package/src/control-plane/session-proxy-middleware.ts +1 -1
- package/src/control-plane/workspace.sql.ts +1 -1
- package/src/control-plane/workspace.ts +7 -7
- package/src/file/index.ts +1 -1
- package/src/file/ripgrep.ts +2 -2
- package/src/file/watcher.ts +5 -5
- package/src/format/formatter.ts +1 -1
- package/src/ide/index.ts +3 -3
- package/src/index.ts +1 -1
- package/src/installation/index.ts +3 -3
- package/src/lsp/client.ts +3 -3
- package/src/lsp/index.ts +3 -3
- package/src/mcp/index.ts +4 -4
- package/src/permission/index.ts +2 -2
- package/src/permission/next.ts +10 -10
- package/src/plugin/codex.ts +1 -1
- package/src/plugin/copilot.ts +2 -2
- package/src/plugin/index.ts +1 -1
- package/src/project/bootstrap.ts +2 -2
- package/src/project/instance.ts +4 -4
- package/src/project/project.sql.ts +1 -1
- package/src/project/project.ts +5 -5
- package/src/project/state.ts +1 -1
- package/src/project/vcs.ts +4 -4
- package/src/provider/auth.ts +4 -4
- package/src/provider/error.ts +1 -1
- package/src/provider/models-snapshot.ts +2 -0
- package/src/provider/models.ts +1 -1
- package/src/provider/provider.ts +2 -2
- package/src/provider/transform.ts +2 -2
- package/src/pty/index.ts +5 -5
- package/src/question/index.ts +5 -5
- package/src/server/event.ts +1 -1
- package/src/server/mdns.ts +1 -1
- package/src/server/routes/global.ts +3 -3
- package/src/server/routes/permission.ts +1 -1
- package/src/server/routes/pty.ts +1 -1
- package/src/server/routes/session.ts +4 -4
- package/src/server/routes/tui.ts +1 -1
- package/src/server/server.ts +3 -3
- package/src/session/compaction.ts +7 -7
- package/src/session/index.ts +10 -10
- package/src/session/instruction.ts +1 -1
- package/src/session/llm.ts +11 -11
- package/src/session/message-v2.ts +10 -10
- package/src/session/message.ts +1 -1
- package/src/session/processor.ts +10 -10
- package/src/session/prompt.ts +8 -8
- package/src/session/retry.ts +2 -2
- package/src/session/revert.ts +1 -1
- package/src/session/session.sql.ts +3 -3
- package/src/session/status.ts +3 -3
- package/src/session/summary.ts +5 -5
- package/src/session/system.ts +1 -1
- package/src/session/todo.ts +2 -2
- package/src/share/share-next.ts +7 -7
- package/src/share/share.sql.ts +1 -1
- package/src/shell/shell.ts +3 -3
- package/src/skill/skill.ts +6 -6
- package/src/storage/db.ts +1 -1
- package/src/storage/storage.ts +1 -1
- package/src/tool/bash.ts +6 -6
- package/src/tool/edit.ts +1 -1
- package/src/tool/registry.ts +2 -2
- package/src/tool/skill.ts +1 -1
- package/src/tool/task.ts +3 -3
- package/src/util/array.ts +10 -0
- package/src/util/binary.ts +41 -0
- package/src/util/encode.ts +51 -0
- package/src/util/error.ts +54 -0
- package/src/util/identifier.ts +48 -0
- package/src/util/lazy.ts +4 -16
- package/src/util/path.ts +37 -0
- package/src/util/retry.ts +41 -0
- package/src/util/slug.ts +74 -0
- package/src/worktree/index.ts +3 -3
- package/AGENTS.md +0 -10
- package/BUN_SHELL_MIGRATION_PLAN.md +0 -136
- package/Dockerfile +0 -18
- package/README.md +0 -15
- package/bunfig.toml +0 -7
- package/drizzle.config.ts +0 -10
- package/script/build.ts +0 -224
- package/script/check-migrations.ts +0 -16
- package/script/postinstall.mjs +0 -131
- package/script/publish.ts +0 -181
- package/script/schema.ts +0 -63
- package/script/seed-e2e.ts +0 -50
- package/sst-env.d.ts +0 -10
- package/test/AGENTS.md +0 -81
- package/test/acp/agent-interface.test.ts +0 -51
- package/test/acp/event-subscription.test.ts +0 -683
- package/test/agent/agent.test.ts +0 -689
- package/test/bun.test.ts +0 -53
- package/test/cli/github-action.test.ts +0 -197
- package/test/cli/github-remote.test.ts +0 -80
- package/test/cli/import.test.ts +0 -38
- package/test/cli/plugin-auth-picker.test.ts +0 -120
- package/test/cli/tui/transcript.test.ts +0 -322
- package/test/config/agent-color.test.ts +0 -71
- package/test/config/config.test.ts +0 -1886
- package/test/config/fixtures/empty-frontmatter.md +0 -4
- package/test/config/fixtures/frontmatter.md +0 -28
- package/test/config/fixtures/markdown-header.md +0 -11
- package/test/config/fixtures/no-frontmatter.md +0 -1
- package/test/config/fixtures/weird-model-id.md +0 -13
- package/test/config/markdown.test.ts +0 -228
- package/test/config/tui.test.ts +0 -510
- package/test/control-plane/session-proxy-middleware.test.ts +0 -147
- package/test/control-plane/sse.test.ts +0 -56
- package/test/control-plane/workspace-server-sse.test.ts +0 -65
- package/test/control-plane/workspace-sync.test.ts +0 -97
- package/test/file/ignore.test.ts +0 -10
- package/test/file/index.test.ts +0 -394
- package/test/file/path-traversal.test.ts +0 -198
- package/test/file/ripgrep.test.ts +0 -39
- package/test/file/time.test.ts +0 -361
- package/test/fixture/db.ts +0 -11
- package/test/fixture/fixture.ts +0 -45
- package/test/fixture/lsp/fake-lsp-server.js +0 -77
- package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
- package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
- package/test/fixture/skills/cloudflare/SKILL.md +0 -211
- package/test/fixture/skills/index.json +0 -6
- package/test/ide/ide.test.ts +0 -82
- package/test/keybind.test.ts +0 -421
- package/test/lsp/client.test.ts +0 -95
- package/test/mcp/headers.test.ts +0 -153
- package/test/mcp/oauth-browser.test.ts +0 -249
- package/test/memory/abort-leak.test.ts +0 -136
- package/test/patch/patch.test.ts +0 -348
- package/test/permission/arity.test.ts +0 -33
- package/test/permission/next.test.ts +0 -689
- package/test/permission-task.test.ts +0 -319
- package/test/plugin/auth-override.test.ts +0 -44
- package/test/plugin/codex.test.ts +0 -123
- package/test/preload.ts +0 -80
- package/test/project/project.test.ts +0 -348
- package/test/project/worktree-remove.test.ts +0 -65
- package/test/provider/amazon-bedrock.test.ts +0 -446
- package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
- package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
- package/test/provider/gitlab-duo.test.ts +0 -262
- package/test/provider/provider.test.ts +0 -2220
- package/test/provider/transform.test.ts +0 -2353
- package/test/pty/pty-output-isolation.test.ts +0 -140
- package/test/question/question.test.ts +0 -300
- package/test/scheduler.test.ts +0 -73
- package/test/server/global-session-list.test.ts +0 -89
- package/test/server/session-list.test.ts +0 -90
- package/test/server/session-select.test.ts +0 -78
- package/test/session/compaction.test.ts +0 -423
- package/test/session/instruction.test.ts +0 -170
- package/test/session/llm.test.ts +0 -667
- package/test/session/message-v2.test.ts +0 -924
- package/test/session/prompt.test.ts +0 -211
- package/test/session/retry.test.ts +0 -188
- package/test/session/revert-compact.test.ts +0 -285
- package/test/session/session.test.ts +0 -71
- package/test/session/structured-output-integration.test.ts +0 -233
- package/test/session/structured-output.test.ts +0 -385
- package/test/skill/discovery.test.ts +0 -110
- package/test/skill/skill.test.ts +0 -388
- package/test/snapshot/snapshot.test.ts +0 -1180
- package/test/storage/json-migration.test.ts +0 -846
- package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
- package/test/tool/apply_patch.test.ts +0 -566
- package/test/tool/bash.test.ts +0 -402
- package/test/tool/edit.test.ts +0 -496
- package/test/tool/external-directory.test.ts +0 -127
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +0 -38413
- package/test/tool/grep.test.ts +0 -110
- package/test/tool/question.test.ts +0 -107
- package/test/tool/read.test.ts +0 -504
- package/test/tool/registry.test.ts +0 -122
- package/test/tool/skill.test.ts +0 -112
- package/test/tool/truncation.test.ts +0 -160
- package/test/tool/webfetch.test.ts +0 -100
- package/test/tool/write.test.ts +0 -348
- package/test/util/filesystem.test.ts +0 -443
- package/test/util/format.test.ts +0 -59
- package/test/util/glob.test.ts +0 -164
- package/test/util/iife.test.ts +0 -36
- package/test/util/lazy.test.ts +0 -50
- package/test/util/lock.test.ts +0 -72
- package/test/util/process.test.ts +0 -59
- package/test/util/timeout.test.ts +0 -21
- package/test/util/wildcard.test.ts +0 -90
- package/tsconfig.json +0 -16
package/test/tool/write.test.ts
DELETED
|
@@ -1,348 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "bun:test"
|
|
2
|
-
import path from "path"
|
|
3
|
-
import fs from "fs/promises"
|
|
4
|
-
import { WriteTool } from "../../src/tool/write"
|
|
5
|
-
import { Instance } from "../../src/project/instance"
|
|
6
|
-
import { tmpdir } from "../fixture/fixture"
|
|
7
|
-
|
|
8
|
-
const ctx = {
|
|
9
|
-
sessionID: "test-write-session",
|
|
10
|
-
messageID: "",
|
|
11
|
-
callID: "",
|
|
12
|
-
agent: "build",
|
|
13
|
-
abort: AbortSignal.any([]),
|
|
14
|
-
messages: [],
|
|
15
|
-
metadata: () => {},
|
|
16
|
-
ask: async () => {},
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe("tool.write", () => {
|
|
20
|
-
describe("new file creation", () => {
|
|
21
|
-
test("writes content to new file", async () => {
|
|
22
|
-
await using tmp = await tmpdir()
|
|
23
|
-
const filepath = path.join(tmp.path, "newfile.txt")
|
|
24
|
-
|
|
25
|
-
await Instance.provide({
|
|
26
|
-
directory: tmp.path,
|
|
27
|
-
fn: async () => {
|
|
28
|
-
const write = await WriteTool.init()
|
|
29
|
-
const result = await write.execute(
|
|
30
|
-
{
|
|
31
|
-
filePath: filepath,
|
|
32
|
-
content: "Hello, World!",
|
|
33
|
-
},
|
|
34
|
-
ctx,
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
expect(result.output).toContain("Wrote file successfully")
|
|
38
|
-
expect(result.metadata.exists).toBe(false)
|
|
39
|
-
|
|
40
|
-
const content = await fs.readFile(filepath, "utf-8")
|
|
41
|
-
expect(content).toBe("Hello, World!")
|
|
42
|
-
},
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
test("creates parent directories if needed", async () => {
|
|
47
|
-
await using tmp = await tmpdir()
|
|
48
|
-
const filepath = path.join(tmp.path, "nested", "deep", "file.txt")
|
|
49
|
-
|
|
50
|
-
await Instance.provide({
|
|
51
|
-
directory: tmp.path,
|
|
52
|
-
fn: async () => {
|
|
53
|
-
const write = await WriteTool.init()
|
|
54
|
-
await write.execute(
|
|
55
|
-
{
|
|
56
|
-
filePath: filepath,
|
|
57
|
-
content: "nested content",
|
|
58
|
-
},
|
|
59
|
-
ctx,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
const content = await fs.readFile(filepath, "utf-8")
|
|
63
|
-
expect(content).toBe("nested content")
|
|
64
|
-
},
|
|
65
|
-
})
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
test("handles relative paths by resolving to instance directory", async () => {
|
|
69
|
-
await using tmp = await tmpdir()
|
|
70
|
-
|
|
71
|
-
await Instance.provide({
|
|
72
|
-
directory: tmp.path,
|
|
73
|
-
fn: async () => {
|
|
74
|
-
const write = await WriteTool.init()
|
|
75
|
-
await write.execute(
|
|
76
|
-
{
|
|
77
|
-
filePath: "relative.txt",
|
|
78
|
-
content: "relative content",
|
|
79
|
-
},
|
|
80
|
-
ctx,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
const content = await fs.readFile(path.join(tmp.path, "relative.txt"), "utf-8")
|
|
84
|
-
expect(content).toBe("relative content")
|
|
85
|
-
},
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
describe("existing file overwrite", () => {
|
|
91
|
-
test("overwrites existing file content", async () => {
|
|
92
|
-
await using tmp = await tmpdir()
|
|
93
|
-
const filepath = path.join(tmp.path, "existing.txt")
|
|
94
|
-
await fs.writeFile(filepath, "old content", "utf-8")
|
|
95
|
-
|
|
96
|
-
// First read the file to satisfy FileTime requirement
|
|
97
|
-
await Instance.provide({
|
|
98
|
-
directory: tmp.path,
|
|
99
|
-
fn: async () => {
|
|
100
|
-
const { FileTime } = await import("../../src/file/time")
|
|
101
|
-
FileTime.read(ctx.sessionID, filepath)
|
|
102
|
-
|
|
103
|
-
const write = await WriteTool.init()
|
|
104
|
-
const result = await write.execute(
|
|
105
|
-
{
|
|
106
|
-
filePath: filepath,
|
|
107
|
-
content: "new content",
|
|
108
|
-
},
|
|
109
|
-
ctx,
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
expect(result.output).toContain("Wrote file successfully")
|
|
113
|
-
expect(result.metadata.exists).toBe(true)
|
|
114
|
-
|
|
115
|
-
const content = await fs.readFile(filepath, "utf-8")
|
|
116
|
-
expect(content).toBe("new content")
|
|
117
|
-
},
|
|
118
|
-
})
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
test("returns diff in metadata for existing files", async () => {
|
|
122
|
-
await using tmp = await tmpdir()
|
|
123
|
-
const filepath = path.join(tmp.path, "file.txt")
|
|
124
|
-
await fs.writeFile(filepath, "old", "utf-8")
|
|
125
|
-
|
|
126
|
-
await Instance.provide({
|
|
127
|
-
directory: tmp.path,
|
|
128
|
-
fn: async () => {
|
|
129
|
-
const { FileTime } = await import("../../src/file/time")
|
|
130
|
-
FileTime.read(ctx.sessionID, filepath)
|
|
131
|
-
|
|
132
|
-
const write = await WriteTool.init()
|
|
133
|
-
const result = await write.execute(
|
|
134
|
-
{
|
|
135
|
-
filePath: filepath,
|
|
136
|
-
content: "new",
|
|
137
|
-
},
|
|
138
|
-
ctx,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
|
-
// Diff should be in metadata
|
|
142
|
-
expect(result.metadata).toHaveProperty("filepath", filepath)
|
|
143
|
-
expect(result.metadata).toHaveProperty("exists", true)
|
|
144
|
-
},
|
|
145
|
-
})
|
|
146
|
-
})
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
describe("file permissions", () => {
|
|
150
|
-
test("sets file permissions when writing sensitive data", async () => {
|
|
151
|
-
await using tmp = await tmpdir()
|
|
152
|
-
const filepath = path.join(tmp.path, "sensitive.json")
|
|
153
|
-
|
|
154
|
-
await Instance.provide({
|
|
155
|
-
directory: tmp.path,
|
|
156
|
-
fn: async () => {
|
|
157
|
-
const write = await WriteTool.init()
|
|
158
|
-
await write.execute(
|
|
159
|
-
{
|
|
160
|
-
filePath: filepath,
|
|
161
|
-
content: JSON.stringify({ secret: "data" }),
|
|
162
|
-
},
|
|
163
|
-
ctx,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
// On Unix systems, check permissions
|
|
167
|
-
if (process.platform !== "win32") {
|
|
168
|
-
const stats = await fs.stat(filepath)
|
|
169
|
-
expect(stats.mode & 0o777).toBe(0o644)
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
describe("content types", () => {
|
|
177
|
-
test("writes JSON content", async () => {
|
|
178
|
-
await using tmp = await tmpdir()
|
|
179
|
-
const filepath = path.join(tmp.path, "data.json")
|
|
180
|
-
const data = { key: "value", nested: { array: [1, 2, 3] } }
|
|
181
|
-
|
|
182
|
-
await Instance.provide({
|
|
183
|
-
directory: tmp.path,
|
|
184
|
-
fn: async () => {
|
|
185
|
-
const write = await WriteTool.init()
|
|
186
|
-
await write.execute(
|
|
187
|
-
{
|
|
188
|
-
filePath: filepath,
|
|
189
|
-
content: JSON.stringify(data, null, 2),
|
|
190
|
-
},
|
|
191
|
-
ctx,
|
|
192
|
-
)
|
|
193
|
-
|
|
194
|
-
const content = await fs.readFile(filepath, "utf-8")
|
|
195
|
-
expect(JSON.parse(content)).toEqual(data)
|
|
196
|
-
},
|
|
197
|
-
})
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
test("writes binary-safe content", async () => {
|
|
201
|
-
await using tmp = await tmpdir()
|
|
202
|
-
const filepath = path.join(tmp.path, "binary.bin")
|
|
203
|
-
const content = "Hello\x00World\x01\x02\x03"
|
|
204
|
-
|
|
205
|
-
await Instance.provide({
|
|
206
|
-
directory: tmp.path,
|
|
207
|
-
fn: async () => {
|
|
208
|
-
const write = await WriteTool.init()
|
|
209
|
-
await write.execute(
|
|
210
|
-
{
|
|
211
|
-
filePath: filepath,
|
|
212
|
-
content,
|
|
213
|
-
},
|
|
214
|
-
ctx,
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
const buf = await fs.readFile(filepath)
|
|
218
|
-
expect(buf.toString()).toBe(content)
|
|
219
|
-
},
|
|
220
|
-
})
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
test("writes empty content", async () => {
|
|
224
|
-
await using tmp = await tmpdir()
|
|
225
|
-
const filepath = path.join(tmp.path, "empty.txt")
|
|
226
|
-
|
|
227
|
-
await Instance.provide({
|
|
228
|
-
directory: tmp.path,
|
|
229
|
-
fn: async () => {
|
|
230
|
-
const write = await WriteTool.init()
|
|
231
|
-
await write.execute(
|
|
232
|
-
{
|
|
233
|
-
filePath: filepath,
|
|
234
|
-
content: "",
|
|
235
|
-
},
|
|
236
|
-
ctx,
|
|
237
|
-
)
|
|
238
|
-
|
|
239
|
-
const content = await fs.readFile(filepath, "utf-8")
|
|
240
|
-
expect(content).toBe("")
|
|
241
|
-
|
|
242
|
-
const stats = await fs.stat(filepath)
|
|
243
|
-
expect(stats.size).toBe(0)
|
|
244
|
-
},
|
|
245
|
-
})
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
test("writes multi-line content", async () => {
|
|
249
|
-
await using tmp = await tmpdir()
|
|
250
|
-
const filepath = path.join(tmp.path, "multiline.txt")
|
|
251
|
-
const lines = ["Line 1", "Line 2", "Line 3", ""].join("\n")
|
|
252
|
-
|
|
253
|
-
await Instance.provide({
|
|
254
|
-
directory: tmp.path,
|
|
255
|
-
fn: async () => {
|
|
256
|
-
const write = await WriteTool.init()
|
|
257
|
-
await write.execute(
|
|
258
|
-
{
|
|
259
|
-
filePath: filepath,
|
|
260
|
-
content: lines,
|
|
261
|
-
},
|
|
262
|
-
ctx,
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
const content = await fs.readFile(filepath, "utf-8")
|
|
266
|
-
expect(content).toBe(lines)
|
|
267
|
-
},
|
|
268
|
-
})
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
test("handles different line endings", async () => {
|
|
272
|
-
await using tmp = await tmpdir()
|
|
273
|
-
const filepath = path.join(tmp.path, "crlf.txt")
|
|
274
|
-
const content = "Line 1\r\nLine 2\r\nLine 3"
|
|
275
|
-
|
|
276
|
-
await Instance.provide({
|
|
277
|
-
directory: tmp.path,
|
|
278
|
-
fn: async () => {
|
|
279
|
-
const write = await WriteTool.init()
|
|
280
|
-
await write.execute(
|
|
281
|
-
{
|
|
282
|
-
filePath: filepath,
|
|
283
|
-
content,
|
|
284
|
-
},
|
|
285
|
-
ctx,
|
|
286
|
-
)
|
|
287
|
-
|
|
288
|
-
const buf = await fs.readFile(filepath)
|
|
289
|
-
expect(buf.toString()).toBe(content)
|
|
290
|
-
},
|
|
291
|
-
})
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
describe("error handling", () => {
|
|
296
|
-
test("throws error when OS denies write access", async () => {
|
|
297
|
-
await using tmp = await tmpdir()
|
|
298
|
-
const readonlyPath = path.join(tmp.path, "readonly.txt")
|
|
299
|
-
|
|
300
|
-
// Create a read-only file
|
|
301
|
-
await fs.writeFile(readonlyPath, "test", "utf-8")
|
|
302
|
-
await fs.chmod(readonlyPath, 0o444)
|
|
303
|
-
|
|
304
|
-
await Instance.provide({
|
|
305
|
-
directory: tmp.path,
|
|
306
|
-
fn: async () => {
|
|
307
|
-
const { FileTime } = await import("../../src/file/time")
|
|
308
|
-
FileTime.read(ctx.sessionID, readonlyPath)
|
|
309
|
-
|
|
310
|
-
const write = await WriteTool.init()
|
|
311
|
-
await expect(
|
|
312
|
-
write.execute(
|
|
313
|
-
{
|
|
314
|
-
filePath: readonlyPath,
|
|
315
|
-
content: "new content",
|
|
316
|
-
},
|
|
317
|
-
ctx,
|
|
318
|
-
),
|
|
319
|
-
).rejects.toThrow()
|
|
320
|
-
},
|
|
321
|
-
})
|
|
322
|
-
})
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
describe("title generation", () => {
|
|
326
|
-
test("returns relative path as title", async () => {
|
|
327
|
-
await using tmp = await tmpdir()
|
|
328
|
-
const filepath = path.join(tmp.path, "src", "components", "Button.tsx")
|
|
329
|
-
await fs.mkdir(path.dirname(filepath), { recursive: true })
|
|
330
|
-
|
|
331
|
-
await Instance.provide({
|
|
332
|
-
directory: tmp.path,
|
|
333
|
-
fn: async () => {
|
|
334
|
-
const write = await WriteTool.init()
|
|
335
|
-
const result = await write.execute(
|
|
336
|
-
{
|
|
337
|
-
filePath: filepath,
|
|
338
|
-
content: "export const Button = () => {}",
|
|
339
|
-
},
|
|
340
|
-
ctx,
|
|
341
|
-
)
|
|
342
|
-
|
|
343
|
-
expect(result.title).toEndWith(path.join("src", "components", "Button.tsx"))
|
|
344
|
-
},
|
|
345
|
-
})
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
})
|