@stonerzju/opencode 1.2.16-offline.1
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/AGENTS.md +10 -0
- package/BUN_SHELL_MIGRATION_PLAN.md +136 -0
- package/Dockerfile +18 -0
- package/README.md +15 -0
- package/bin/opencode +179 -0
- package/bunfig.toml +7 -0
- package/drizzle.config.ts +10 -0
- package/migration/20260127222353_familiar_lady_ursula/migration.sql +90 -0
- package/migration/20260127222353_familiar_lady_ursula/snapshot.json +796 -0
- package/migration/20260211171708_add_project_commands/migration.sql +1 -0
- package/migration/20260211171708_add_project_commands/snapshot.json +806 -0
- package/migration/20260213144116_wakeful_the_professor/migration.sql +11 -0
- package/migration/20260213144116_wakeful_the_professor/snapshot.json +897 -0
- package/migration/20260225215848_workspace/migration.sql +7 -0
- package/migration/20260225215848_workspace/snapshot.json +959 -0
- package/package.json +140 -0
- package/package.json.bak +140 -0
- package/parsers-config.ts +254 -0
- package/script/build.ts +224 -0
- package/script/check-migrations.ts +16 -0
- package/script/postinstall.mjs +131 -0
- package/script/publish.ts +181 -0
- package/script/schema.ts +63 -0
- package/script/seed-e2e.ts +50 -0
- package/src/acp/README.md +174 -0
- package/src/acp/agent.ts +1741 -0
- package/src/acp/session.ts +116 -0
- package/src/acp/types.ts +23 -0
- package/src/agent/agent.ts +339 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +14 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +44 -0
- package/src/auth/index.ts +68 -0
- package/src/bun/index.ts +131 -0
- package/src/bun/registry.ts +50 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +70 -0
- package/src/cli/cmd/agent.ts +257 -0
- package/src/cli/cmd/auth.ts +449 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/db.ts +118 -0
- package/src/cli/cmd/debug/agent.ts +167 -0
- package/src/cli/cmd/debug/config.ts +16 -0
- package/src/cli/cmd/debug/file.ts +97 -0
- package/src/cli/cmd/debug/index.ts +48 -0
- package/src/cli/cmd/debug/lsp.ts +52 -0
- package/src/cli/cmd/debug/ripgrep.ts +87 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +16 -0
- package/src/cli/cmd/debug/snapshot.ts +52 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1631 -0
- package/src/cli/cmd/import.ts +170 -0
- package/src/cli/cmd/mcp.ts +754 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +625 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +156 -0
- package/src/cli/cmd/stats.ts +410 -0
- package/src/cli/cmd/tui/app.tsx +845 -0
- package/src/cli/cmd/tui/attach.ts +88 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +147 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +165 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +259 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +108 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-skill.tsx +36 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +167 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/logo.tsx +85 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +667 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +90 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1155 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/spinner.tsx +24 -0
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
- package/src/cli/cmd/tui/component/tips.tsx +152 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +15 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/exit.tsx +53 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +102 -0
- package/src/cli/cmd/tui/context/kv.tsx +52 -0
- package/src/cli/cmd/tui/context/local.tsx +406 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +46 -0
- package/src/cli/cmd/tui/context/sdk.tsx +101 -0
- package/src/cli/cmd/tui/context/sync.tsx +488 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +249 -0
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1152 -0
- package/src/cli/cmd/tui/context/tui-config.tsx +9 -0
- package/src/cli/cmd/tui/event.ts +48 -0
- package/src/cli/cmd/tui/routes/home.tsx +145 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +135 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +2219 -0
- package/src/cli/cmd/tui/routes/session/permission.tsx +685 -0
- package/src/cli/cmd/tui/routes/session/question.tsx +466 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +321 -0
- package/src/cli/cmd/tui/thread.ts +199 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +59 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +85 -0
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +207 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +40 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +80 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +401 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +182 -0
- package/src/cli/cmd/tui/ui/link.tsx +28 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +164 -0
- package/src/cli/cmd/tui/util/editor.ts +33 -0
- package/src/cli/cmd/tui/util/selection.ts +25 -0
- package/src/cli/cmd/tui/util/signal.ts +7 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/util/transcript.ts +98 -0
- package/src/cli/cmd/tui/win32.ts +129 -0
- package/src/cli/cmd/tui/worker.ts +157 -0
- package/src/cli/cmd/uninstall.ts +356 -0
- package/src/cli/cmd/upgrade.ts +73 -0
- package/src/cli/cmd/web.ts +81 -0
- package/src/cli/cmd/workspace-serve.ts +16 -0
- package/src/cli/error.ts +57 -0
- package/src/cli/logo.ts +6 -0
- package/src/cli/network.ts +60 -0
- package/src/cli/ui.ts +116 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +150 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +101 -0
- package/src/config/config.ts +1408 -0
- package/src/config/markdown.ts +99 -0
- package/src/config/migrate-tui-config.ts +155 -0
- package/src/config/paths.ts +174 -0
- package/src/config/tui-schema.ts +34 -0
- package/src/config/tui.ts +118 -0
- package/src/control/control.sql.ts +22 -0
- package/src/control/index.ts +67 -0
- package/src/control-plane/adaptors/index.ts +10 -0
- package/src/control-plane/adaptors/types.ts +7 -0
- package/src/control-plane/adaptors/worktree.ts +26 -0
- package/src/control-plane/config.ts +10 -0
- package/src/control-plane/session-proxy-middleware.ts +46 -0
- package/src/control-plane/sse.ts +66 -0
- package/src/control-plane/workspace-server/routes.ts +33 -0
- package/src/control-plane/workspace-server/server.ts +24 -0
- package/src/control-plane/workspace.sql.ts +12 -0
- package/src/control-plane/workspace.ts +160 -0
- package/src/env/index.ts +28 -0
- package/src/file/ignore.ts +82 -0
- package/src/file/index.ts +646 -0
- package/src/file/ripgrep.ts +372 -0
- package/src/file/time.ts +71 -0
- package/src/file/watcher.ts +128 -0
- package/src/flag/flag.ts +109 -0
- package/src/format/formatter.ts +395 -0
- package/src/format/index.ts +140 -0
- package/src/global/index.ts +54 -0
- package/src/id/id.ts +84 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +210 -0
- package/src/installation/index.ts +266 -0
- package/src/lsp/client.ts +251 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +120 -0
- package/src/lsp/server.ts +2142 -0
- package/src/mcp/auth.ts +130 -0
- package/src/mcp/index.ts +937 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +176 -0
- package/src/patch/index.ts +680 -0
- package/src/permission/arity.ts +163 -0
- package/src/permission/index.ts +210 -0
- package/src/permission/next.ts +286 -0
- package/src/plugin/codex.ts +624 -0
- package/src/plugin/copilot.ts +327 -0
- package/src/plugin/index.ts +143 -0
- package/src/project/bootstrap.ts +33 -0
- package/src/project/instance.ts +114 -0
- package/src/project/project.sql.ts +15 -0
- package/src/project/project.ts +441 -0
- package/src/project/state.ts +70 -0
- package/src/project/vcs.ts +76 -0
- package/src/provider/auth.ts +147 -0
- package/src/provider/error.ts +189 -0
- package/src/provider/models.ts +146 -0
- package/src/provider/provider.ts +1338 -0
- package/src/provider/sdk/copilot/README.md +5 -0
- package/src/provider/sdk/copilot/chat/convert-to-openai-compatible-chat-messages.ts +164 -0
- package/src/provider/sdk/copilot/chat/get-response-metadata.ts +15 -0
- package/src/provider/sdk/copilot/chat/map-openai-compatible-finish-reason.ts +17 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-api-types.ts +64 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-language-model.ts +780 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-chat-options.ts +28 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-metadata-extractor.ts +44 -0
- package/src/provider/sdk/copilot/chat/openai-compatible-prepare-tools.ts +87 -0
- package/src/provider/sdk/copilot/copilot-provider.ts +100 -0
- package/src/provider/sdk/copilot/index.ts +2 -0
- package/src/provider/sdk/copilot/openai-compatible-error.ts +27 -0
- package/src/provider/sdk/copilot/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/copilot/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/copilot/responses/openai-config.ts +18 -0
- package/src/provider/sdk/copilot/responses/openai-error.ts +22 -0
- package/src/provider/sdk/copilot/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/copilot/responses/openai-responses-language-model.ts +1732 -0
- package/src/provider/sdk/copilot/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/copilot/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/copilot/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/copilot/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/copilot/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/copilot/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/copilot/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/copilot/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +955 -0
- package/src/pty/index.ts +324 -0
- package/src/question/index.ts +171 -0
- package/src/scheduler/index.ts +61 -0
- package/src/server/error.ts +36 -0
- package/src/server/event.ts +7 -0
- package/src/server/mdns.ts +60 -0
- package/src/server/routes/config.ts +92 -0
- package/src/server/routes/experimental.ts +270 -0
- package/src/server/routes/file.ts +197 -0
- package/src/server/routes/global.ts +185 -0
- package/src/server/routes/mcp.ts +225 -0
- package/src/server/routes/permission.ts +68 -0
- package/src/server/routes/project.ts +82 -0
- package/src/server/routes/provider.ts +165 -0
- package/src/server/routes/pty.ts +200 -0
- package/src/server/routes/question.ts +98 -0
- package/src/server/routes/session.ts +974 -0
- package/src/server/routes/tui.ts +379 -0
- package/src/server/routes/workspace.ts +104 -0
- package/src/server/server.ts +623 -0
- package/src/session/compaction.ts +261 -0
- package/src/session/index.ts +877 -0
- package/src/session/instruction.ts +192 -0
- package/src/session/llm.ts +279 -0
- package/src/session/message-v2.ts +899 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +421 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex_header.txt +79 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/gemini.txt +155 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +26 -0
- package/src/session/prompt/qwen.txt +109 -0
- package/src/session/prompt/trinity.txt +97 -0
- package/src/session/prompt.ts +1959 -0
- package/src/session/retry.ts +101 -0
- package/src/session/revert.ts +138 -0
- package/src/session/session.sql.ts +88 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +161 -0
- package/src/session/system.ts +54 -0
- package/src/session/todo.ts +56 -0
- package/src/share/share-next.ts +210 -0
- package/src/share/share.sql.ts +13 -0
- package/src/shell/shell.ts +68 -0
- package/src/skill/discovery.ts +98 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +189 -0
- package/src/snapshot/index.ts +297 -0
- package/src/sql.d.ts +4 -0
- package/src/storage/db.ts +155 -0
- package/src/storage/json-migration.ts +425 -0
- package/src/storage/schema.sql.ts +10 -0
- package/src/storage/schema.ts +5 -0
- package/src/storage/storage.ts +220 -0
- package/src/tool/apply_patch.ts +281 -0
- package/src/tool/apply_patch.txt +33 -0
- package/src/tool/bash.ts +274 -0
- package/src/tool/bash.txt +115 -0
- package/src/tool/batch.ts +181 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +132 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +654 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/external-directory.ts +32 -0
- package/src/tool/glob.ts +78 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +156 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +121 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp.ts +97 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +131 -0
- package/src/tool/question.ts +33 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/read.ts +293 -0
- package/src/tool/read.txt +14 -0
- package/src/tool/registry.ts +173 -0
- package/src/tool/skill.ts +123 -0
- package/src/tool/task.ts +165 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +53 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +89 -0
- package/src/tool/truncation.ts +107 -0
- package/src/tool/webfetch.ts +206 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +14 -0
- package/src/tool/write.ts +84 -0
- package/src/tool/write.txt +8 -0
- package/src/util/abort.ts +35 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +189 -0
- package/src/util/fn.ts +11 -0
- package/src/util/format.ts +20 -0
- package/src/util/git.ts +35 -0
- package/src/util/glob.ts +34 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +103 -0
- package/src/util/lazy.ts +23 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +182 -0
- package/src/util/process.ts +126 -0
- package/src/util/proxied.ts +3 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +66 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +59 -0
- package/src/worktree/index.ts +643 -0
- package/sst-env.d.ts +10 -0
- package/test/AGENTS.md +81 -0
- package/test/acp/agent-interface.test.ts +51 -0
- package/test/acp/event-subscription.test.ts +683 -0
- package/test/agent/agent.test.ts +689 -0
- package/test/bun.test.ts +53 -0
- package/test/cli/github-action.test.ts +197 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/cli/import.test.ts +38 -0
- package/test/cli/plugin-auth-picker.test.ts +120 -0
- package/test/cli/tui/transcript.test.ts +322 -0
- package/test/config/agent-color.test.ts +71 -0
- package/test/config/config.test.ts +1886 -0
- package/test/config/fixtures/empty-frontmatter.md +4 -0
- package/test/config/fixtures/frontmatter.md +28 -0
- package/test/config/fixtures/markdown-header.md +11 -0
- package/test/config/fixtures/no-frontmatter.md +1 -0
- package/test/config/fixtures/weird-model-id.md +13 -0
- package/test/config/markdown.test.ts +228 -0
- package/test/config/tui.test.ts +510 -0
- package/test/control-plane/session-proxy-middleware.test.ts +147 -0
- package/test/control-plane/sse.test.ts +56 -0
- package/test/control-plane/workspace-server-sse.test.ts +65 -0
- package/test/control-plane/workspace-sync.test.ts +97 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/file/index.test.ts +394 -0
- package/test/file/path-traversal.test.ts +198 -0
- package/test/file/ripgrep.test.ts +39 -0
- package/test/file/time.test.ts +361 -0
- package/test/fixture/db.ts +11 -0
- package/test/fixture/fixture.ts +45 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/fixture/skills/agents-sdk/SKILL.md +152 -0
- package/test/fixture/skills/agents-sdk/references/callable.md +92 -0
- package/test/fixture/skills/cloudflare/SKILL.md +211 -0
- package/test/fixture/skills/index.json +6 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +421 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/mcp/oauth-browser.test.ts +249 -0
- package/test/memory/abort-leak.test.ts +136 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/permission/arity.test.ts +33 -0
- package/test/permission/next.test.ts +689 -0
- package/test/permission-task.test.ts +319 -0
- package/test/plugin/auth-override.test.ts +44 -0
- package/test/plugin/codex.test.ts +123 -0
- package/test/preload.ts +80 -0
- package/test/project/project.test.ts +348 -0
- package/test/project/worktree-remove.test.ts +65 -0
- package/test/provider/amazon-bedrock.test.ts +446 -0
- package/test/provider/copilot/convert-to-copilot-messages.test.ts +523 -0
- package/test/provider/copilot/copilot-chat-model.test.ts +592 -0
- package/test/provider/gitlab-duo.test.ts +262 -0
- package/test/provider/provider.test.ts +2220 -0
- package/test/provider/transform.test.ts +2353 -0
- package/test/pty/pty-output-isolation.test.ts +140 -0
- package/test/question/question.test.ts +300 -0
- package/test/scheduler.test.ts +73 -0
- package/test/server/global-session-list.test.ts +89 -0
- package/test/server/session-list.test.ts +90 -0
- package/test/server/session-select.test.ts +78 -0
- package/test/session/compaction.test.ts +423 -0
- package/test/session/instruction.test.ts +170 -0
- package/test/session/llm.test.ts +667 -0
- package/test/session/message-v2.test.ts +924 -0
- package/test/session/prompt.test.ts +211 -0
- package/test/session/retry.test.ts +188 -0
- package/test/session/revert-compact.test.ts +285 -0
- package/test/session/session.test.ts +71 -0
- package/test/session/structured-output-integration.test.ts +233 -0
- package/test/session/structured-output.test.ts +385 -0
- package/test/skill/discovery.test.ts +110 -0
- package/test/skill/skill.test.ts +388 -0
- package/test/snapshot/snapshot.test.ts +1180 -0
- package/test/storage/json-migration.test.ts +846 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/apply_patch.test.ts +566 -0
- package/test/tool/bash.test.ts +402 -0
- package/test/tool/edit.test.ts +496 -0
- package/test/tool/external-directory.test.ts +127 -0
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +38413 -0
- package/test/tool/grep.test.ts +110 -0
- package/test/tool/question.test.ts +107 -0
- package/test/tool/read.test.ts +504 -0
- package/test/tool/registry.test.ts +122 -0
- package/test/tool/skill.test.ts +112 -0
- package/test/tool/truncation.test.ts +160 -0
- package/test/tool/webfetch.test.ts +100 -0
- package/test/tool/write.test.ts +348 -0
- package/test/util/filesystem.test.ts +443 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/glob.test.ts +164 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/lock.test.ts +72 -0
- package/test/util/process.test.ts +59 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +90 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,443 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
})
|
|
@@ -0,0 +1,164 @@
|
|
|
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
|
+
})
|