diff --git a/packages/app/src/context/layout.tsx b/packages/app/src/context/layout.tsx index e2fd0a7f45eb..95c68d50fa9f 100644 --- a/packages/app/src/context/layout.tsx +++ b/packages/app/src/context/layout.tsx @@ -8,6 +8,7 @@ import { Project } from "@opencode-ai/sdk/v2" import { Persist, persisted, removePersisted } from "@/utils/persist" import { same } from "@/utils/same" import { createScrollPersistence, type SessionScroll } from "./layout-scroll" +import { normalizePathForComparison } from "@/utils/path" const AVATAR_COLOR_KEYS = ["pink", "mint", "orange", "purple", "cyan", "lime"] as const export type AvatarColorKey = (typeof AVATAR_COLOR_KEYS)[number] @@ -299,7 +300,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( for (const project of globalSync.data.project) { const sandboxes = project.sandboxes ?? [] for (const sandbox of sandboxes) { - map.set(sandbox, project.worktree) + // 使用规范化路径作为键,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + map.set(normalizePathForComparison(sandbox), project.worktree) } } return map @@ -316,7 +319,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext( const current = chain[chain.length - 1] if (!current) return directory - const next = map.get(current) + // 使用规范化路径查找,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const next = map.get(normalizePathForComparison(current)) if (!next) return current if (visited.has(next)) return directory diff --git a/packages/app/src/context/server.tsx b/packages/app/src/context/server.tsx index c307f6e72abd..99472cefd9c0 100644 --- a/packages/app/src/context/server.tsx +++ b/packages/app/src/context/server.tsx @@ -4,6 +4,7 @@ import { batch, createEffect, createMemo, onCleanup } from "solid-js" import { createStore } from "solid-js/store" import { usePlatform } from "@/context/platform" import { Persist, persisted } from "@/utils/persist" +import { normalizePathForComparison } from "@/utils/path" type StoredProject = { worktree: string; expanded: boolean } @@ -164,38 +165,54 @@ export const { use: useServer, provider: ServerProvider } = createSimpleContext( const key = origin() if (!key) return const current = store.projects[key] ?? [] - if (current.find((x) => x.worktree === directory)) return + // 使用规范化路径检查项目是否已存在,避免Windows上的重复项目 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) + const existing = current.find((x) => normalizePathForComparison(x.worktree) === normalizedDirectory) + if (existing) return setStore("projects", key, [{ worktree: directory, expanded: true }, ...current]) }, close(directory: string) { const key = origin() if (!key) return const current = store.projects[key] ?? [] + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) setStore( "projects", key, - current.filter((x) => x.worktree !== directory), + current.filter((x) => normalizePathForComparison(x.worktree) !== normalizedDirectory), ) }, expand(directory: string) { const key = origin() if (!key) return const current = store.projects[key] ?? [] - const index = current.findIndex((x) => x.worktree === directory) + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) + const index = current.findIndex((x) => normalizePathForComparison(x.worktree) === normalizedDirectory) if (index !== -1) setStore("projects", key, index, "expanded", true) }, collapse(directory: string) { const key = origin() if (!key) return const current = store.projects[key] ?? [] - const index = current.findIndex((x) => x.worktree === directory) + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) + const index = current.findIndex((x) => normalizePathForComparison(x.worktree) === normalizedDirectory) if (index !== -1) setStore("projects", key, index, "expanded", false) }, move(directory: string, toIndex: number) { const key = origin() if (!key) return const current = store.projects[key] ?? [] - const fromIndex = current.findIndex((x) => x.worktree === directory) + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) + const fromIndex = current.findIndex((x) => normalizePathForComparison(x.worktree) === normalizedDirectory) if (fromIndex === -1 || fromIndex === toIndex) return const result = [...current] const [item] = result.splice(fromIndex, 1) diff --git a/packages/app/src/pages/layout.tsx b/packages/app/src/pages/layout.tsx index 903b9eaa1fab..90960d9d192f 100644 --- a/packages/app/src/pages/layout.tsx +++ b/packages/app/src/pages/layout.tsx @@ -75,6 +75,7 @@ import { DialogEditProject } from "@/components/dialog-edit-project" import { Titlebar } from "@/components/titlebar" import { useServer } from "@/context/server" import { useLanguage, type Locale } from "@/context/language" +import { normalizePathForComparison } from "@/utils/path" export default function Layout(props: ParentProps) { const [store, setStore, , ready] = persisted( @@ -553,11 +554,14 @@ export default function Layout(props: ParentProps) { if (!directory) return const projects = layout.projects.list() + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) const sandbox = projects.find((p) => p.sandboxes?.includes(directory)) if (sandbox) return sandbox - const direct = projects.find((p) => p.worktree === directory) + const direct = projects.find((p) => normalizePathForComparison(p.worktree) === normalizedDirectory) if (direct) return direct const [child] = globalSync.child(directory, { bootstrap: false }) @@ -568,7 +572,7 @@ export default function Layout(props: ParentProps) { const root = meta?.worktree if (!root) return - return projects.find((p) => p.worktree === root) + return projects.find((p) => normalizePathForComparison(p.worktree) === normalizePathForComparison(root)) }) createEffect( @@ -614,11 +618,16 @@ export default function Layout(props: ParentProps) { ), ) - const workspaceKey = (directory: string) => directory.replace(/[\\/]+$/, "") + /** + * 生成工作区唯一标识键 + * 使用规范化路径确保在Windows上同一物理路径不会产生重复键 + * 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + */ + const workspaceKey = (directory: string) => normalizePathForComparison(directory) const workspaceName = (directory: string, projectId?: string, branch?: string) => { const key = workspaceKey(directory) - const direct = store.workspaceName[key] ?? store.workspaceName[directory] + const direct = store.workspaceName[key] if (direct) return direct if (!projectId) return if (!branch) return @@ -1219,12 +1228,32 @@ export default function Layout(props: ParentProps) { const deepLinkEvent = "opencode:deep-link" - const parseDeepLink = (input: string) => { + /** + * 解析深度链接URL + * 返回原始项目目录路径,保留原始格式 + * 在需要比较路径时再进行规范化 +<<<<<<< HEAD + * + * @param input - 深度链接URL字符串 + * @returns 原始目录路径,如果URL无效则返回undefined + * +======= + * + * @param input - 深度链接URL字符串 + * @returns 原始目录路径,如果URL无效则返回undefined + * +>>>>>>> 065844dbe (优化: 重构路径规范化逻辑,消除代码重复) + * @example + * parseDeepLink('opencode://open-project?directory=C:\\Users\\Project') + * // 返回: 'C:\\Users\\Project' + */ + const parseDeepLink = (input: string): string | undefined => { if (!input.startsWith("opencode://")) return const url = new URL(input) if (url.hostname !== "open-project") return const directory = url.searchParams.get("directory") if (!directory) return + // 返回原始路径,保留原始格式 return directory } @@ -1279,7 +1308,10 @@ export default function Layout(props: ParentProps) { } function closeProject(directory: string) { - const index = layout.projects.list().findIndex((x) => x.worktree === directory) + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) + const index = layout.projects.list().findIndex((x) => normalizePathForComparison(x.worktree) === normalizedDirectory) const next = layout.projects.list()[index + 1] layout.projects.close(directory) if (next) navigateToProject(next.worktree) @@ -1617,8 +1649,12 @@ export default function Layout(props: ParentProps) { const { draggable, droppable } = event if (draggable && droppable) { const projects = layout.projects.list() - const fromIndex = projects.findIndex((p) => p.worktree === draggable.id.toString()) - const toIndex = projects.findIndex((p) => p.worktree === droppable.id.toString()) + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDraggable = normalizePathForComparison(draggable.id.toString()) + const normalizedDroppable = normalizePathForComparison(droppable.id.toString()) + const fromIndex = projects.findIndex((p) => normalizePathForComparison(p.worktree) === normalizedDraggable) + const toIndex = projects.findIndex((p) => normalizePathForComparison(p.worktree) === normalizedDroppable) if (fromIndex !== toIndex && toIndex !== -1) { layout.projects.move(draggable.id.toString(), toIndex) } @@ -1634,7 +1670,11 @@ export default function Layout(props: ParentProps) { const local = project.worktree const dirs = [local, ...(project.sandboxes ?? [])] const active = currentProject() - const directory = active?.worktree === project.worktree ? decode64(params.dir) : undefined + // 使用规范化路径比较,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedActiveWorktree = active?.worktree ? normalizePathForComparison(active.worktree) : undefined + const normalizedProjectWorktree = normalizePathForComparison(project.worktree) + const directory = normalizedActiveWorktree === normalizedProjectWorktree ? decode64(params.dir) : undefined const extra = directory && directory !== local && !dirs.includes(directory) ? directory : undefined const pending = extra ? WorktreeState.get(extra)?.status === "pending" : false @@ -1670,8 +1710,12 @@ export default function Layout(props: ParentProps) { if (!project) return const ids = workspaceIds(project) - const fromIndex = ids.findIndex((dir) => dir === draggable.id.toString()) - const toIndex = ids.findIndex((dir) => dir === droppable.id.toString()) + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDraggable = normalizePathForComparison(draggable.id.toString()) + const normalizedDroppable = normalizePathForComparison(droppable.id.toString()) + const fromIndex = ids.findIndex((dir) => normalizePathForComparison(dir) === normalizedDraggable) + const toIndex = ids.findIndex((dir) => normalizePathForComparison(dir) === normalizedDroppable) if (fromIndex === -1 || toIndex === -1) return if (fromIndex === toIndex) return @@ -1980,7 +2024,12 @@ export default function Layout(props: ParentProps) { } const ProjectDragOverlay = (): JSX.Element => { - const project = createMemo(() => layout.projects.list().find((p) => p.worktree === store.activeProject)) + const project = createMemo(() => { + // 使用规范化路径查找项目,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedActiveProject = store.activeProject ? normalizePathForComparison(store.activeProject) : undefined + return layout.projects.list().find((p) => normalizedActiveProject && normalizePathForComparison(p.worktree) === normalizedActiveProject) + }) return ( {(p) => ( @@ -2042,10 +2091,20 @@ export default function Layout(props: ParentProps) { } return map }) - const local = createMemo(() => props.directory === props.project.worktree) + const local = createMemo(() => { + // 使用规范化路径比较,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(props.directory) + const normalizedWorktree = normalizePathForComparison(props.project.worktree) + return normalizedDirectory === normalizedWorktree + }) const active = createMemo(() => { const current = decode64(params.dir) ?? "" - return current === props.directory + // 使用规范化路径比较,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedCurrent = normalizePathForComparison(current) + const normalizedDirectory = normalizePathForComparison(props.directory) + return normalizedCurrent === normalizedDirectory }) const workspaceValue = createMemo(() => { const branch = workspaceStore.vcs?.branch @@ -2257,7 +2316,11 @@ export default function Layout(props: ParentProps) { const sortable = createSortable(props.project.worktree) const selected = createMemo(() => { const current = decode64(params.dir) ?? "" - return props.project.worktree === current || props.project.sandboxes?.includes(current) + // 使用规范化路径比较,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedCurrent = normalizePathForComparison(current) + const normalizedWorktree = normalizePathForComparison(props.project.worktree) + return normalizedWorktree === normalizedCurrent || props.project.sandboxes?.includes(current) }) const workspaces = createMemo(() => workspaceIds(props.project).slice(0, 2)) @@ -2269,9 +2332,13 @@ export default function Layout(props: ParentProps) { const preview = createMemo(() => !props.mobile && layout.sidebar.opened()) const overlay = createMemo(() => !props.mobile && !layout.sidebar.opened()) - const active = createMemo( - () => menu() || (preview() ? open() : overlay() && state.hoverProject === props.project.worktree), - ) + const active = createMemo(() => { + // 使用规范化路径比较,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedHoverProject = state.hoverProject ? normalizePathForComparison(state.hoverProject) : undefined + const normalizedWorktree = normalizePathForComparison(props.project.worktree) + return (preview() ? open() : overlay() && normalizedHoverProject === normalizedWorktree) + }) createEffect(() => { if (preview()) return @@ -2281,8 +2348,12 @@ export default function Layout(props: ParentProps) { const label = (directory: string) => { const [data] = globalSync.child(directory, { bootstrap: false }) + // 使用规范化路径比较,避免Windows上的路径不一致问题 + // 修复Issue #11666: Windows路径规范化不一致导致重复创建项目 + const normalizedDirectory = normalizePathForComparison(directory) + const normalizedWorktree = normalizePathForComparison(props.project.worktree) const kind = - directory === props.project.worktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox") + normalizedDirectory === normalizedWorktree ? language.t("workspace.type.local") : language.t("workspace.type.sandbox") const name = workspaceLabel(directory, data.vcs?.branch, props.project.id) return `${kind} : ${name}` } @@ -2581,9 +2652,6 @@ export default function Layout(props: ParentProps) { setBusy(created.directory, true) WorktreeState.pending(created.directory) setStore("workspaceExpanded", key, true) - if (key !== created.directory) { - setStore("workspaceExpanded", created.directory, true) - } setStore("workspaceOrder", project.worktree, (prev) => { const existing = prev ?? [] const next = existing.filter((item) => { diff --git a/packages/app/src/utils/path.ts b/packages/app/src/utils/path.ts new file mode 100644 index 000000000000..8aa85f0b2dde --- /dev/null +++ b/packages/app/src/utils/path.ts @@ -0,0 +1,61 @@ +/** + * 文件用途: 路径工具模块 + * 作者: TRAE, 创建日期: 2026-02-02 + * + * 输入输出签名: + * - normalizePathForComparison(path: string): string - 规范化路径用于比较 + * + * 依赖列表: 无 + * + * 与其他模块交互方式: + * - 被 packages/app/src/context/server.tsx 导入使用 + * - 被 packages/app/src/pages/layout.tsx 导入使用 + * + * 其他备注: + * - 此模块提供路径规范化功能,用于解决Windows路径大小写不一致问题 + * - 与 packages/opencode/src/util/filesystem.ts 中的 normalizePath 不同,后者使用 realpathSync.native 获取文件系统真实路径 + */ + +/** + * 规范化路径用于比较,避免在Windows上重复创建项目 + * + * @param path - 需要规范化的路径 + * @returns 规范化后的路径,格式为:正斜杠、无末尾斜杠、Windows上为小写 + * @throws {Error} 如果路径为空或无效 + * + * @example + * // Windows + * normalizePathForComparison('C:\\Users\\Project') // 'c:/users/project' + * normalizePathForComparison('C:/Users/Project/') // 'c:/users/project' + * + * @example + * // macOS/Linux + * normalizePathForComparison('/Users/Project') // '/Users/Project' + * normalizePathForComparison('/Users/Project/') // '/Users/Project' + */ +export function normalizePathForComparison(path: string): string { + // 输入验证 + if (!path || typeof path !== 'string') { + throw new Error('Invalid path: path must be a non-empty string') + } + + let normalized = path.trim() + + // 检查空路径 + if (normalized.length === 0) { + throw new Error('Invalid path: path cannot be empty') + } + + // 统一斜杠为正斜杠 + normalized = normalized.replace(/\\/g, '/') + + // 移除末尾斜杠 + normalized = normalized.replace(/\/$/, '') + + // 在Windows上,统一为小写以进行不区分大小写的比较 + if (process.platform === 'win32') { + normalized = normalized.toLowerCase() + } + + return normalized +} diff --git a/packages/opencode/src/cli/cmd/tui/app.tsx b/packages/opencode/src/cli/cmd/tui/app.tsx index 7442037604bd..713def2e5af4 100644 --- a/packages/opencode/src/cli/cmd/tui/app.tsx +++ b/packages/opencode/src/cli/cmd/tui/app.tsx @@ -169,7 +169,6 @@ export function tui(input: { gatherStats: false, exitOnCtrlC: false, useKittyKeyboard: {}, - autoFocus: false, consoleOptions: { keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }], onCopySelection: (text) => { diff --git a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts index 4be6787346df..b98baa831995 100644 --- a/packages/opencode/src/cli/cmd/tui/util/clipboard.ts +++ b/packages/opencode/src/cli/cmd/tui/util/clipboard.ts @@ -153,7 +153,6 @@ export namespace Clipboard { }) export async function copy(text: string): Promise { - writeOsc52(text) await getCopyMethod()(text) } } diff --git a/packages/opencode/src/installation/index.ts b/packages/opencode/src/installation/index.ts index d18c9e31a13b..9c55046f90ea 100644 --- a/packages/opencode/src/installation/index.ts +++ b/packages/opencode/src/installation/index.ts @@ -6,6 +6,7 @@ import { NamedError } from "@opencode-ai/util/error" import { Log } from "../util/log" import { iife } from "@/util/iife" import { Flag } from "../flag/flag" +import { withTimeout } from "@/util/timeout" declare global { const OPENCODE_VERSION: string @@ -15,6 +16,9 @@ declare global { export namespace Installation { const log = Log.create({ service: "installation" }) + // TODO: Move to config file + const TIMEOUT_MS = 5000 + export type Method = Awaited> export const Event = { @@ -102,11 +106,21 @@ export namespace Installation { }) for (const check of checks) { - const output = await check.command() - const installedName = - check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai" - if (output.includes(installedName)) { - return check.name + try { + log.debug("Checking package manager", { name: check.name }) + const output = await withTimeout(check.command(), TIMEOUT_MS) + const installedName = + check.name === "brew" || check.name === "choco" || check.name === "scoop" ? "opencode" : "opencode-ai" + if (output.includes(installedName)) { + log.info("Detected installation method", { method: check.name }) + return check.name + } + } catch (e) { + log.warn("Package manager check failed", { + name: check.name, + error: e instanceof Error ? e.message : String(e), + }) + continue } } diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts index 67559b78c085..b6411fe33943 100644 --- a/packages/opencode/src/tool/bash.ts +++ b/packages/opencode/src/tool/bash.ts @@ -1,3 +1,29 @@ +/** + * 文件用途:Bash工具实现,提供命令执行、权限管理、输出截断等功能 + * 作者:TRAE + * 创建日期:2026-02-01 + * + * 输入输出签名: + * - 输入:command(命令字符串)、timeout(超时毫秒)、workdir(工作目录)、description(命令描述) + * - 输出:{ title, metadata: { output, exit, description }, output } + * + * 依赖列表: + * - zod@latest + * - web-tree-sitter@latest + * - tree-sitter-bash@latest + * - bun@latest + * + * 与其他模块交互方式: + * - 调用Instance.directory获取项目根目录 + * - 调用ctx.ask()请求权限(external_directory、bash) + * - 调用ctx.metadata()更新元数据 + * - 调用ctx.abort监听中止事件 + * - 调用Shell.killTree()终止进程树 + * + * 其他备注: + * - 相关文件:bash.txt(工具描述模板) + * - 支持Windows、Linux、macOS多平台 + */ import z from "zod" import { spawn } from "child_process" import { Tool } from "./tool" @@ -23,6 +49,35 @@ const DEFAULT_TIMEOUT = Flag.OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS || 2 export const log = Log.create({ service: "bash-tool" }) +const WINDOWS_RESERVED_DEVICE_NAMES = new Set([ + "CON", "PRN", "AUX", "NUL", + "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", + "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", +]) + +/** + * 实现说明:Windows保留设备名称检查 + * + * Windows系统保留以下设备名称,不能作为文件名使用: + * - CON, PRN, AUX, NUL(标准设备) + * - COM1-9(串行端口) + * - LPT1-9(并行端口) + * + * 检查逻辑: + * 1. 提取路径的基本名称(去除目录部分) + * 2. 转换为大写(Windows文件系统不区分大小写) + * 3. 检查是否在保留名称集合中 + * + * 注意事项: + * - 此检查应在调用realpath之前进行,避免无效命令 + * - 只检查基本名称,不检查路径中间的保留名称 + * - 适用于Windows平台(process.platform === 'win32') + */ +const isWindowsReservedDeviceName = (name: string): boolean => { + const baseName = path.basename(name).toUpperCase() + return WINDOWS_RESERVED_DEVICE_NAMES.has(baseName) +} + const resolveWasm = (asset: string) => { if (asset.startsWith("file://")) return fileURLToPath(asset) if (asset.startsWith("/") || /^[a-z]:/i.test(asset)) return asset @@ -72,7 +127,7 @@ export const BashTool = Tool.define("bash", async () => { description: z .string() .describe( - "Clear, concise description of what this command does in 5-10 words. Examples:\nInput: ls\nOutput: Lists files in current directory\n\nInput: git status\nOutput: Shows working tree status\n\nInput: npm install\nOutput: Installs package dependencies\n\nInput: mkdir foo\nOutput: Creates directory 'foo'", + "Clear, concise description of what this command does in 5-10 words", ), }), async execute(params, ctx) { @@ -116,6 +171,10 @@ export const BashTool = Tool.define("bash", async () => { if (["cd", "rm", "cp", "mv", "mkdir", "touch", "chmod", "chown", "cat"].includes(command[0])) { for (const arg of command.slice(1)) { if (arg.startsWith("-") || (command[0] === "chmod" && arg.startsWith("+"))) continue + if (process.platform === "win32" && isWindowsReservedDeviceName(arg)) { + log.info("skipping Windows reserved device name", { arg }) + continue + } const resolved = await $`realpath ${arg}` .cwd(cwd) .quiet() diff --git a/packages/plugin/script/publish.ts b/packages/plugin/script/publish.ts index 647b56e5e2dd..f094633ae4ca 100755 --- a/packages/plugin/script/publish.ts +++ b/packages/plugin/script/publish.ts @@ -1,8 +1,12 @@ #!/usr/bin/env bun + +import { fileURLToPath } from "node:url" +import path from "path" import { Script } from "@opencode-ai/script" import { $ } from "bun" -const dir = new URL("..", import.meta.url).pathname +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const dir = path.resolve(scriptDir, "..") process.chdir(dir) await $`bun tsc` diff --git a/packages/sdk/js/package.json b/packages/sdk/js/package.json index 19f4401a64e2..b8cbe2d1c57e 100644 --- a/packages/sdk/js/package.json +++ b/packages/sdk/js/package.json @@ -6,7 +6,7 @@ "license": "MIT", "scripts": { "typecheck": "tsgo --noEmit", - "build": "./script/build.ts" + "build": "bun run script/build.ts" }, "exports": { ".": "./src/index.ts", diff --git a/packages/sdk/js/script/build.ts b/packages/sdk/js/script/build.ts index 7568c54b0f2a..f413d437d576 100755 --- a/packages/sdk/js/script/build.ts +++ b/packages/sdk/js/script/build.ts @@ -1,14 +1,17 @@ #!/usr/bin/env bun -const dir = new URL("..", import.meta.url).pathname +import { fileURLToPath } from "node:url" +import path from "path" + +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const dir = path.resolve(scriptDir, "..") process.chdir(dir) import { $ } from "bun" -import path from "path" import { createClient } from "@hey-api/openapi-ts" -await $`bun dev generate > ${dir}/openapi.json`.cwd(path.resolve(dir, "../../opencode")) +await $`bun dev generate > ${path.join(dir, "openapi.json")}`.cwd(path.resolve(dir, "../../opencode")) await createClient({ input: "./openapi.json", diff --git a/packages/sdk/js/script/publish.ts b/packages/sdk/js/script/publish.ts index 46dd42b700d1..d60d239542ad 100755 --- a/packages/sdk/js/script/publish.ts +++ b/packages/sdk/js/script/publish.ts @@ -1,9 +1,12 @@ #!/usr/bin/env bun +import { fileURLToPath } from "node:url" +import path from "path" import { Script } from "@opencode-ai/script" import { $ } from "bun" -const dir = new URL("..", import.meta.url).pathname +const scriptDir = path.dirname(fileURLToPath(import.meta.url)) +const dir = path.resolve(scriptDir, "..") process.chdir(dir) const pkg = await import("../package.json").then((m) => m.default) diff --git a/script/publish.ts b/script/publish.ts index 1294f8d793e1..1c80fa97cca2 100755 --- a/script/publish.ts +++ b/script/publish.ts @@ -1,5 +1,7 @@ #!/usr/bin/env bun +import { fileURLToPath } from "node:url" +import path from "path" import { $ } from "bun" import { Script } from "@opencode-ai/script" @@ -46,7 +48,7 @@ for (const file of pkgjsons) { await Bun.file(file).write(pkg) } -const extensionToml = new URL("../packages/extensions/zed/extension.toml", import.meta.url).pathname +const extensionToml = path.resolve(fileURLToPath(import.meta.url), "../packages/extensions/zed/extension.toml") let toml = await Bun.file(extensionToml).text() toml = toml.replace(/^version = "[^"]+"/m, `version = "${Script.version}"`) toml = toml.replaceAll(/releases\/download\/v[^/]+\//g, `releases/download/v${Script.version}/`) @@ -75,5 +77,5 @@ await import(`../packages/sdk/js/script/publish.ts`) console.log("\n=== plugin ===\n") await import(`../packages/plugin/script/publish.ts`) -const dir = new URL("..", import.meta.url).pathname +const dir = path.resolve(fileURLToPath(import.meta.url), "..") process.chdir(dir)