mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-20 13:53:32 +08:00
Merge pull request #66 from NanmiCoder/codex/fix-windows-window-controls
[codex] Fix Windows desktop startup without Git Bash
This commit is contained in:
commit
0a4cd17f2e
@ -5,7 +5,10 @@
|
|||||||
"windows": ["main"],
|
"windows": ["main"],
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"core:default",
|
"core:default",
|
||||||
|
"core:window:allow-close",
|
||||||
|
"core:window:allow-minimize",
|
||||||
"core:window:allow-start-dragging",
|
"core:window:allow-start-dragging",
|
||||||
|
"core:window:allow-toggle-maximize",
|
||||||
"shell:allow-open",
|
"shell:allow-open",
|
||||||
{
|
{
|
||||||
"identifier": "shell:allow-execute",
|
"identifier": "shell:allow-execute",
|
||||||
|
|||||||
@ -394,12 +394,6 @@ pub fn run() {
|
|||||||
// 让 adapter 连上动态端口。
|
// 让 adapter 连上动态端口。
|
||||||
spawn_and_track_adapters_sidecar(&app.handle());
|
spawn_and_track_adapters_sidecar(&app.handle());
|
||||||
|
|
||||||
let _window = app.get_webview_window("main").unwrap();
|
|
||||||
|
|
||||||
// Windows: hide native decorations — frontend renders custom titlebar
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
let _ = _window.set_decorations(false);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.build(tauri::generate_context!())
|
.build(tauri::generate_context!())
|
||||||
|
|||||||
@ -18,8 +18,6 @@
|
|||||||
"minWidth": 960,
|
"minWidth": 960,
|
||||||
"minHeight": 640,
|
"minHeight": 640,
|
||||||
"decorations": true,
|
"decorations": true,
|
||||||
"titleBarStyle": "Overlay",
|
|
||||||
"hiddenTitle": true,
|
|
||||||
"transparent": false,
|
"transparent": false,
|
||||||
"acceptFirstMouse": true
|
"acceptFirstMouse": true
|
||||||
}
|
}
|
||||||
|
|||||||
18
desktop/src-tauri/tauri.macos.conf.json
Normal file
18
desktop/src-tauri/tauri.macos.conf.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "Claude Code Haha",
|
||||||
|
"width": 1440,
|
||||||
|
"height": 960,
|
||||||
|
"minWidth": 960,
|
||||||
|
"minHeight": 640,
|
||||||
|
"decorations": true,
|
||||||
|
"titleBarStyle": "Overlay",
|
||||||
|
"hiddenTitle": true,
|
||||||
|
"transparent": false,
|
||||||
|
"acceptFirstMouse": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
16
desktop/src-tauri/tauri.windows.conf.json
Normal file
16
desktop/src-tauri/tauri.windows.conf.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "Claude Code Haha",
|
||||||
|
"width": 1440,
|
||||||
|
"height": 960,
|
||||||
|
"minWidth": 960,
|
||||||
|
"minHeight": 640,
|
||||||
|
"decorations": false,
|
||||||
|
"transparent": false,
|
||||||
|
"acceptFirstMouse": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -281,4 +281,32 @@ describe('MessageList nested tool calls', () => {
|
|||||||
'先看 CLI 和服务端入口。\n再看 desktop 前后端边界。'
|
'先看 CLI 和服务端入口。\n再看 desktop 前后端边界。'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('shows raw startup details under translated CLI startup errors', () => {
|
||||||
|
useChatStore.setState({
|
||||||
|
sessions: {
|
||||||
|
[ACTIVE_TAB]: makeSessionState({
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
id: 'error-1',
|
||||||
|
type: 'error',
|
||||||
|
code: 'CLI_START_FAILED',
|
||||||
|
message:
|
||||||
|
'CLI exited during startup (code 1): Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win).',
|
||||||
|
timestamp: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
render(<MessageList />)
|
||||||
|
|
||||||
|
expect(screen.getByText('Failed to start CLI process.')).toBeTruthy()
|
||||||
|
expect(
|
||||||
|
screen.getByText(
|
||||||
|
'CLI exited during startup (code 1): Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win).',
|
||||||
|
),
|
||||||
|
).toBeTruthy()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -222,9 +222,18 @@ export const MessageBlock = memo(function MessageBlock({
|
|||||||
const errorKey = message.code ? `error.${message.code}` as TranslationKey : null
|
const errorKey = message.code ? `error.${message.code}` as TranslationKey : null
|
||||||
const errorText = errorKey ? t(errorKey) : null
|
const errorText = errorKey ? t(errorKey) : null
|
||||||
const displayMessage = (errorText && errorText !== errorKey) ? errorText : message.message
|
const displayMessage = (errorText && errorText !== errorKey) ? errorText : message.message
|
||||||
|
const showRawDetail =
|
||||||
|
Boolean(message.message) &&
|
||||||
|
message.message.trim() !== '' &&
|
||||||
|
message.message !== displayMessage
|
||||||
return (
|
return (
|
||||||
<div className="mb-3 px-4 py-2.5 rounded-lg bg-red-50 border border-red-200 text-sm text-[var(--color-error)]">
|
<div className="mb-3 px-4 py-2.5 rounded-lg bg-red-50 border border-red-200 text-sm text-[var(--color-error)]">
|
||||||
<strong>Error:</strong> {displayMessage}
|
<strong>Error:</strong> {displayMessage}
|
||||||
|
{showRawDetail && (
|
||||||
|
<div className="mt-1 whitespace-pre-wrap text-xs text-red-700/85">
|
||||||
|
{message.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
69
desktop/src/components/layout/WindowControls.test.tsx
Normal file
69
desktop/src/components/layout/WindowControls.test.tsx
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
|
|
||||||
|
const minimize = vi.fn().mockResolvedValue(undefined)
|
||||||
|
const toggleMaximize = vi.fn().mockResolvedValue(undefined)
|
||||||
|
const close = vi.fn().mockResolvedValue(undefined)
|
||||||
|
const isMaximized = vi.fn().mockResolvedValue(false)
|
||||||
|
const onResized = vi.fn().mockResolvedValue(() => {})
|
||||||
|
|
||||||
|
vi.mock('@tauri-apps/api/window', () => ({
|
||||||
|
getCurrentWindow: () => ({
|
||||||
|
minimize,
|
||||||
|
toggleMaximize,
|
||||||
|
close,
|
||||||
|
isMaximized,
|
||||||
|
onResized,
|
||||||
|
}),
|
||||||
|
}))
|
||||||
|
|
||||||
|
describe('WindowControls', () => {
|
||||||
|
const originalPlatform = navigator.platform
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
minimize.mockClear()
|
||||||
|
toggleMaximize.mockClear()
|
||||||
|
close.mockClear()
|
||||||
|
isMaximized.mockClear()
|
||||||
|
onResized.mockClear()
|
||||||
|
|
||||||
|
Object.defineProperty(window, '__TAURI_INTERNALS__', {
|
||||||
|
configurable: true,
|
||||||
|
value: {},
|
||||||
|
})
|
||||||
|
Object.defineProperty(navigator, 'platform', {
|
||||||
|
configurable: true,
|
||||||
|
value: 'Win32',
|
||||||
|
})
|
||||||
|
vi.resetModules()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
Reflect.deleteProperty(window, '__TAURI_INTERNALS__')
|
||||||
|
Object.defineProperty(navigator, 'platform', {
|
||||||
|
configurable: true,
|
||||||
|
value: originalPlatform,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('invokes Tauri window APIs for custom controls on Windows', async () => {
|
||||||
|
const { WindowControls } = await import('./WindowControls')
|
||||||
|
|
||||||
|
render(<WindowControls />)
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByRole('button', { name: 'Minimize window' })).toBeInTheDocument()
|
||||||
|
})
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Minimize window' }))
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Maximize window' }))
|
||||||
|
fireEvent.click(screen.getByRole('button', { name: 'Close window' }))
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(minimize).toHaveBeenCalledTimes(1)
|
||||||
|
expect(toggleMaximize).toHaveBeenCalledTimes(1)
|
||||||
|
expect(close).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -34,13 +34,20 @@ export function WindowControls() {
|
|||||||
return () => { unlisten?.() }
|
return () => { unlisten?.() }
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const runWindowAction = (action: () => Promise<void>) => {
|
||||||
|
void action().catch((error) => {
|
||||||
|
console.error('Window control action failed', error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (!showWindowControls || !win) return null
|
if (!showWindowControls || !win) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-stretch flex-shrink-0 -my-px">
|
<div className="flex items-stretch flex-shrink-0 -my-px">
|
||||||
{/* Minimize */}
|
{/* Minimize */}
|
||||||
<button
|
<button
|
||||||
onClick={() => win.minimize()}
|
onClick={() => runWindowAction(() => win.minimize())}
|
||||||
|
aria-label="Minimize window"
|
||||||
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors"
|
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors"
|
||||||
>
|
>
|
||||||
<svg width="10" height="1" viewBox="0 0 10 1">
|
<svg width="10" height="1" viewBox="0 0 10 1">
|
||||||
@ -50,7 +57,8 @@ export function WindowControls() {
|
|||||||
|
|
||||||
{/* Maximize / Restore */}
|
{/* Maximize / Restore */}
|
||||||
<button
|
<button
|
||||||
onClick={() => win.toggleMaximize()}
|
onClick={() => runWindowAction(() => win.toggleMaximize())}
|
||||||
|
aria-label={maximized ? 'Restore window' : 'Maximize window'}
|
||||||
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors"
|
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[var(--color-surface-hover)] transition-colors"
|
||||||
>
|
>
|
||||||
{maximized ? (
|
{maximized ? (
|
||||||
@ -67,7 +75,8 @@ export function WindowControls() {
|
|||||||
|
|
||||||
{/* Close */}
|
{/* Close */}
|
||||||
<button
|
<button
|
||||||
onClick={() => win.close()}
|
onClick={() => runWindowAction(() => win.close())}
|
||||||
|
aria-label="Close window"
|
||||||
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[#e81123] hover:text-white transition-colors"
|
className="w-[46px] h-full flex items-center justify-center text-[var(--color-text-secondary)] hover:bg-[#e81123] hover:text-white transition-colors"
|
||||||
>
|
>
|
||||||
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.2">
|
<svg width="10" height="10" viewBox="0 0 10 10" fill="none" stroke="currentColor" strokeWidth="1.2">
|
||||||
|
|||||||
@ -17,7 +17,10 @@ import {
|
|||||||
} from './sessionEnvironment.js'
|
} from './sessionEnvironment.js'
|
||||||
import { subprocessEnv } from './subprocessEnv.js'
|
import { subprocessEnv } from './subprocessEnv.js'
|
||||||
import { getPlatform } from './platform.js'
|
import { getPlatform } from './platform.js'
|
||||||
import { findGitBashPath, windowsPathToPosixPath } from './windowsPaths.js'
|
import {
|
||||||
|
tryFindGitBashPath,
|
||||||
|
windowsPathToPosixPath,
|
||||||
|
} from './windowsPaths.js'
|
||||||
import { getCachedPowerShellPath } from './shell/powershellDetection.js'
|
import { getCachedPowerShellPath } from './shell/powershellDetection.js'
|
||||||
import { DEFAULT_HOOK_SHELL } from './shell/shellProvider.js'
|
import { DEFAULT_HOOK_SHELL } from './shell/shellProvider.js'
|
||||||
import { buildPowerShellArgs } from './shell/powershellProvider.js'
|
import { buildPowerShellArgs } from './shell/powershellProvider.js'
|
||||||
@ -787,9 +790,20 @@ async function execCommandHook(
|
|||||||
// PowerShell path deliberately skips the Windows-specific bash
|
// PowerShell path deliberately skips the Windows-specific bash
|
||||||
// accommodations (cygpath conversion, .sh auto-prepend, POSIX-quoted
|
// accommodations (cygpath conversion, .sh auto-prepend, POSIX-quoted
|
||||||
// SHELL_PREFIX).
|
// SHELL_PREFIX).
|
||||||
const shellType = hook.shell ?? DEFAULT_HOOK_SHELL
|
let shellType = hook.shell ?? DEFAULT_HOOK_SHELL
|
||||||
|
|
||||||
const isPowerShell = shellType === 'powershell'
|
if (isWindows && !hook.shell && shellType === 'bash' && !tryFindGitBashPath()) {
|
||||||
|
const pwshPath = await getCachedPowerShellPath()
|
||||||
|
if (pwshPath) {
|
||||||
|
shellType = 'powershell'
|
||||||
|
logForDebugging(
|
||||||
|
`Hooks: Git Bash unavailable on Windows, falling back to PowerShell for default shell on hook "${hook.command}"`,
|
||||||
|
{ level: 'warn' },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const usesPowerShell = shellType === 'powershell'
|
||||||
|
|
||||||
// --
|
// --
|
||||||
// Windows bash path: hooks run via Git Bash (Cygwin), NOT cmd.exe.
|
// Windows bash path: hooks run via Git Bash (Cygwin), NOT cmd.exe.
|
||||||
@ -806,7 +820,7 @@ async function execCommandHook(
|
|||||||
// PowerShell expects Windows paths on Windows (and native paths on
|
// PowerShell expects Windows paths on Windows (and native paths on
|
||||||
// Unix where pwsh is also available).
|
// Unix where pwsh is also available).
|
||||||
const toHookPath =
|
const toHookPath =
|
||||||
isWindows && !isPowerShell
|
isWindows && !usesPowerShell
|
||||||
? (p: string) => windowsPathToPosixPath(p)
|
? (p: string) => windowsPathToPosixPath(p)
|
||||||
: (p: string) => p
|
: (p: string) => p
|
||||||
|
|
||||||
@ -859,7 +873,7 @@ async function execCommandHook(
|
|||||||
// On Windows (bash only), auto-prepend `bash` for .sh scripts so they
|
// On Windows (bash only), auto-prepend `bash` for .sh scripts so they
|
||||||
// execute instead of opening in the default file handler. PowerShell
|
// execute instead of opening in the default file handler. PowerShell
|
||||||
// runs .ps1 files natively — no prepend needed.
|
// runs .ps1 files natively — no prepend needed.
|
||||||
if (isWindows && !isPowerShell && command.trim().match(/\.sh(\s|$|")/)) {
|
if (isWindows && !usesPowerShell && command.trim().match(/\.sh(\s|$|")/)) {
|
||||||
if (!command.trim().startsWith('bash ')) {
|
if (!command.trim().startsWith('bash ')) {
|
||||||
command = `bash ${command}`
|
command = `bash ${command}`
|
||||||
}
|
}
|
||||||
@ -870,7 +884,7 @@ async function execCommandHook(
|
|||||||
// PowerShell — see design §8.1. For now PS hooks ignore the prefix;
|
// PowerShell — see design §8.1. For now PS hooks ignore the prefix;
|
||||||
// a CLAUDE_CODE_PS_SHELL_PREFIX (or shell-aware prefix) is a follow-up.
|
// a CLAUDE_CODE_PS_SHELL_PREFIX (or shell-aware prefix) is a follow-up.
|
||||||
const finalCommand =
|
const finalCommand =
|
||||||
!isPowerShell && process.env.CLAUDE_CODE_SHELL_PREFIX
|
!usesPowerShell && process.env.CLAUDE_CODE_SHELL_PREFIX
|
||||||
? formatShellPrefixCommand(process.env.CLAUDE_CODE_SHELL_PREFIX, command)
|
? formatShellPrefixCommand(process.env.CLAUDE_CODE_SHELL_PREFIX, command)
|
||||||
: command
|
: command
|
||||||
|
|
||||||
@ -915,7 +929,7 @@ async function execCommandHook(
|
|||||||
// Skip for PS — consistent with how .sh prepend and SHELL_PREFIX are
|
// Skip for PS — consistent with how .sh prepend and SHELL_PREFIX are
|
||||||
// already bash-only above.
|
// already bash-only above.
|
||||||
if (
|
if (
|
||||||
!isPowerShell &&
|
!usesPowerShell &&
|
||||||
(hookEvent === 'SessionStart' ||
|
(hookEvent === 'SessionStart' ||
|
||||||
hookEvent === 'Setup' ||
|
hookEvent === 'Setup' ||
|
||||||
hookEvent === 'CwdChanged' ||
|
hookEvent === 'CwdChanged' ||
|
||||||
@ -948,12 +962,9 @@ async function execCommandHook(
|
|||||||
// skips user profile scripts (faster, deterministic).
|
// skips user profile scripts (faster, deterministic).
|
||||||
// -NonInteractive fails fast instead of prompting.
|
// -NonInteractive fails fast instead of prompting.
|
||||||
//
|
//
|
||||||
// The Git Bash hard-exit in findGitBashPath() is still in place for
|
// On Windows, default-shell hooks now degrade to PowerShell when Git Bash
|
||||||
// bash hooks. PowerShell hooks never call it, so a Windows user with
|
// is unavailable. Explicit bash hooks still surface an actionable error,
|
||||||
// only pwsh and shell: 'powershell' on every hook could in theory run
|
// but they no longer hard-exit the entire process.
|
||||||
// without Git Bash — but init.ts still calls setShellIfWindows() on
|
|
||||||
// startup, which will exit first. Relaxing that is phase 1 of the
|
|
||||||
// design's implementation order (separate PR).
|
|
||||||
let child: ChildProcessWithoutNullStreams
|
let child: ChildProcessWithoutNullStreams
|
||||||
if (shellType === 'powershell') {
|
if (shellType === 'powershell') {
|
||||||
const pwshPath = await getCachedPowerShellPath()
|
const pwshPath = await getCachedPowerShellPath()
|
||||||
@ -973,7 +984,14 @@ async function execCommandHook(
|
|||||||
} else {
|
} else {
|
||||||
// On Windows, use Git Bash explicitly (cmd.exe can't run bash syntax).
|
// On Windows, use Git Bash explicitly (cmd.exe can't run bash syntax).
|
||||||
// On other platforms, shell: true uses /bin/sh.
|
// On other platforms, shell: true uses /bin/sh.
|
||||||
const shell = isWindows ? findGitBashPath() : true
|
const shell = isWindows
|
||||||
|
? tryFindGitBashPath() ??
|
||||||
|
(() => {
|
||||||
|
throw new Error(
|
||||||
|
'Git Bash is required to run bash hooks on Windows. Install Git for Windows or configure the hook with shell: "powershell".',
|
||||||
|
)
|
||||||
|
})()
|
||||||
|
: true
|
||||||
child = spawn(finalCommand, [], {
|
child = spawn(finalCommand, [], {
|
||||||
env: envVars,
|
env: envVars,
|
||||||
cwd: safeCwd,
|
cwd: safeCwd,
|
||||||
|
|||||||
@ -86,26 +86,31 @@ function findExecutable(executable: string): string | null {
|
|||||||
*/
|
*/
|
||||||
export function setShellIfWindows(): void {
|
export function setShellIfWindows(): void {
|
||||||
if (getPlatform() === 'windows') {
|
if (getPlatform() === 'windows') {
|
||||||
const gitBashPath = findGitBashPath()
|
const gitBashPath = tryFindGitBashPath()
|
||||||
|
if (!gitBashPath) {
|
||||||
|
logForDebugging(
|
||||||
|
'Git Bash not found on Windows; leaving SHELL unset for lazy fallback handling',
|
||||||
|
{ level: 'warn' },
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
process.env.SHELL = gitBashPath
|
process.env.SHELL = gitBashPath
|
||||||
logForDebugging(`Using bash path: "${gitBashPath}"`)
|
logForDebugging(`Using bash path: "${gitBashPath}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the path where `bash.exe` included with git-bash exists, exiting the process if not found.
|
* Best-effort Git Bash resolution on Windows.
|
||||||
|
*
|
||||||
|
* Returns null when Git Bash is unavailable so callers can decide whether to
|
||||||
|
* fall back (for example, to PowerShell) instead of hard-exiting the process.
|
||||||
*/
|
*/
|
||||||
export const findGitBashPath = memoize((): string => {
|
export const tryFindGitBashPath = memoize((): string | null => {
|
||||||
if (process.env.CLAUDE_CODE_GIT_BASH_PATH) {
|
if (process.env.CLAUDE_CODE_GIT_BASH_PATH) {
|
||||||
if (checkPathExists(process.env.CLAUDE_CODE_GIT_BASH_PATH)) {
|
if (checkPathExists(process.env.CLAUDE_CODE_GIT_BASH_PATH)) {
|
||||||
return process.env.CLAUDE_CODE_GIT_BASH_PATH
|
return process.env.CLAUDE_CODE_GIT_BASH_PATH
|
||||||
}
|
}
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
return null
|
||||||
console.error(
|
|
||||||
`Claude Code was unable to find CLAUDE_CODE_GIT_BASH_PATH path "${process.env.CLAUDE_CODE_GIT_BASH_PATH}"`,
|
|
||||||
)
|
|
||||||
// eslint-disable-next-line custom-rules/no-process-exit
|
|
||||||
process.exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const gitPath = findExecutable('git')
|
const gitPath = findExecutable('git')
|
||||||
@ -116,6 +121,27 @@ export const findGitBashPath = memoize((): string => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the path where `bash.exe` included with git-bash exists, exiting the process if not found.
|
||||||
|
*/
|
||||||
|
export const findGitBashPath = memoize((): string => {
|
||||||
|
const gitBashPath = tryFindGitBashPath()
|
||||||
|
if (gitBashPath) {
|
||||||
|
return gitBashPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.CLAUDE_CODE_GIT_BASH_PATH) {
|
||||||
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||||
|
console.error(
|
||||||
|
`Claude Code was unable to find CLAUDE_CODE_GIT_BASH_PATH path "${process.env.CLAUDE_CODE_GIT_BASH_PATH}"`,
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line custom-rules/no-process-exit
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
||||||
console.error(
|
console.error(
|
||||||
'Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win). If installed but not in PATH, set environment variable pointing to your bash.exe, similar to: CLAUDE_CODE_GIT_BASH_PATH=C:\\Program Files\\Git\\bin\\bash.exe',
|
'Claude Code on Windows requires git-bash (https://git-scm.com/downloads/win). If installed but not in PATH, set environment variable pointing to your bash.exe, similar to: CLAUDE_CODE_GIT_BASH_PATH=C:\\Program Files\\Git\\bin\\bash.exe',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user