mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-17 22:05:50 +00:00
feat: integrate fork work onto upstream main (squashed)
Squash-merge of feat/autofix-pr-test (69 commits) onto upstream/main with -X ours strategy (upstream as authoritative for content conflicts). Key features brought in from fork: - LocalMemoryRecall + VaultHttpFetch tools (end-to-end wired) - /local-memory, /local-vault, /memory-stores, /skill-store interactive panels - /agents-platform, /schedule, /vault command scaffolding - /login: switch / replace / remove of workspace API key - statusline refactor (built-in status row, /statusline as info command) - autofix-pr command + workflow Conflict resolutions (upstream-wins): - 10 .js command stubs kept from upstream (alongside fork's .ts implementations) - src/components/BuiltinStatusLine.tsx accepted upstream's deletion (fork's wire-up references in StatusLine.tsx will be cleaned up next) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
192
src/commands/_shared/__tests__/launchCommand.test.ts
Normal file
192
src/commands/_shared/__tests__/launchCommand.test.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Regression tests for launchCommand factory (H2 finding).
|
||||
* Tests MUST fail before the factory is created, then pass after.
|
||||
*/
|
||||
import { describe, test, expect, mock } from 'bun:test'
|
||||
import { logMock } from '../../../../tests/mocks/log.js'
|
||||
|
||||
mock.module('src/utils/log.ts', logMock)
|
||||
mock.module('bun:bundle', () => ({ feature: () => false }))
|
||||
|
||||
import React from 'react'
|
||||
import type {
|
||||
LocalJSXCommandCall,
|
||||
LocalJSXCommandOnDone,
|
||||
} from '../../../types/command.js'
|
||||
import type { LaunchCommandOptions } from '../launchCommand.js'
|
||||
|
||||
let launchCommand: typeof import('../launchCommand.js').launchCommand
|
||||
|
||||
// Lazy import so mocks are in place first
|
||||
const loadModule = async () => {
|
||||
const mod = await import('../launchCommand.js')
|
||||
launchCommand = mod.launchCommand
|
||||
}
|
||||
|
||||
// Simple parsed union for tests
|
||||
type TestParsed =
|
||||
| { action: 'greet'; name: string }
|
||||
| { action: 'invalid'; reason: string }
|
||||
|
||||
type TestViewProps = { greeting: string }
|
||||
|
||||
const TestView: React.FC<TestViewProps> = ({ greeting }) =>
|
||||
React.createElement('span', null, greeting)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type AnyOpts = LaunchCommandOptions<any, any>
|
||||
|
||||
const makeOpts = (overrides: Partial<AnyOpts> = {}): AnyOpts => ({
|
||||
commandName: 'test-cmd',
|
||||
parseArgs: (
|
||||
raw: string,
|
||||
): TestParsed | { action: 'invalid'; reason: string } => {
|
||||
if (raw.trim() === '') return { action: 'invalid', reason: 'empty args' }
|
||||
return { action: 'greet', name: raw.trim() }
|
||||
},
|
||||
dispatch: async (parsed: TestParsed, onDone: LocalJSXCommandOnDone) => {
|
||||
if (parsed.action !== 'greet') return null
|
||||
onDone(`Hello ${parsed.name}`)
|
||||
return { greeting: `Hello, ${parsed.name}!` }
|
||||
},
|
||||
View: TestView as React.FC<unknown>,
|
||||
errorView: (msg: string) =>
|
||||
React.createElement('span', null, `Error: ${msg}`),
|
||||
...overrides,
|
||||
})
|
||||
|
||||
describe('launchCommand factory', () => {
|
||||
test('module loads and exports launchCommand function', async () => {
|
||||
await loadModule()
|
||||
expect(typeof launchCommand).toBe('function')
|
||||
})
|
||||
|
||||
test('launchCommand returns a LocalJSXCommandCall function', async () => {
|
||||
await loadModule()
|
||||
const call = launchCommand(makeOpts())
|
||||
expect(typeof call).toBe('function')
|
||||
})
|
||||
|
||||
test('happy path: parseArgs + dispatch succeed → View rendered, onDone called', async () => {
|
||||
await loadModule()
|
||||
const call: LocalJSXCommandCall = launchCommand(makeOpts())
|
||||
const onDone = mock(() => {})
|
||||
const result = await call(onDone, {} as never, 'Alice')
|
||||
expect(result).not.toBeNull()
|
||||
expect(onDone).toHaveBeenCalledTimes(1)
|
||||
const [msg] = onDone.mock.calls[0] as unknown as [string]
|
||||
expect(msg).toContain('Alice')
|
||||
})
|
||||
|
||||
test('parseArgs returns invalid → errorView returned, onDone called with reason', async () => {
|
||||
await loadModule()
|
||||
const call: LocalJSXCommandCall = launchCommand(makeOpts())
|
||||
const onDone = mock(() => {})
|
||||
const result = await call(onDone, {} as never, '')
|
||||
expect(onDone).toHaveBeenCalledTimes(1)
|
||||
const [msg] = onDone.mock.calls[0] as unknown as [string]
|
||||
expect(msg).toContain('empty args')
|
||||
// errorView should return something (not null from dispatch)
|
||||
expect(result).not.toBeUndefined()
|
||||
})
|
||||
|
||||
test('dispatch throws → errorView returned, onDone called with error message', async () => {
|
||||
await loadModule()
|
||||
const call: LocalJSXCommandCall = launchCommand(
|
||||
makeOpts({
|
||||
dispatch: async () => {
|
||||
throw new Error('dispatch failed')
|
||||
},
|
||||
}),
|
||||
)
|
||||
const onDone = mock(() => {})
|
||||
const result = await call(onDone, {} as never, 'Bob')
|
||||
expect(onDone).toHaveBeenCalledTimes(1)
|
||||
const [msg] = onDone.mock.calls[0] as unknown as [string]
|
||||
expect(msg).toContain('dispatch failed')
|
||||
expect(result).not.toBeUndefined()
|
||||
})
|
||||
|
||||
test('dispatch returns null → null returned from call', async () => {
|
||||
await loadModule()
|
||||
const call: LocalJSXCommandCall = launchCommand(
|
||||
makeOpts({
|
||||
dispatch: async (_parsed, onDone) => {
|
||||
onDone('done')
|
||||
return null
|
||||
},
|
||||
}),
|
||||
)
|
||||
const onDone = mock(() => {})
|
||||
const result = await call(onDone, {} as never, 'Charlie')
|
||||
expect(result).toBeNull()
|
||||
})
|
||||
|
||||
test('onDispatchError hook is called when dispatch throws', async () => {
|
||||
await loadModule()
|
||||
const onDispatchError = mock((_err: unknown) => {})
|
||||
const call: LocalJSXCommandCall = launchCommand(
|
||||
makeOpts({
|
||||
dispatch: async () => {
|
||||
throw new Error('boom')
|
||||
},
|
||||
onDispatchError,
|
||||
}),
|
||||
)
|
||||
const onDone = mock(() => {})
|
||||
await call(onDone, {} as never, 'Dave')
|
||||
expect(onDispatchError).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('invalid args: onDone display option is system', async () => {
|
||||
await loadModule()
|
||||
const call: LocalJSXCommandCall = launchCommand(makeOpts())
|
||||
const capturedOpts: unknown[] = []
|
||||
const onDone = mock((_msg?: string, opts?: unknown) => {
|
||||
capturedOpts.push(opts)
|
||||
})
|
||||
await call(onDone, {} as never, '')
|
||||
expect(capturedOpts[0]).toEqual({ display: 'system' })
|
||||
})
|
||||
|
||||
test('dispatch error: onDone is called exactly once with commandName in message', async () => {
|
||||
await loadModule()
|
||||
const call: LocalJSXCommandCall = launchCommand(
|
||||
makeOpts({
|
||||
commandName: 'my-special-cmd',
|
||||
dispatch: async () => {
|
||||
throw new Error('network timeout')
|
||||
},
|
||||
}),
|
||||
)
|
||||
const onDone = mock(() => {})
|
||||
await call(onDone, {} as never, 'Eve')
|
||||
expect(onDone).toHaveBeenCalledTimes(1)
|
||||
const [msg] = onDone.mock.calls[0] as unknown as [string]
|
||||
expect(msg).toContain('my-special-cmd')
|
||||
expect(msg).toContain('network timeout')
|
||||
})
|
||||
|
||||
test('errorView receives the error message string', async () => {
|
||||
await loadModule()
|
||||
const capturedMsgs: string[] = []
|
||||
const call: LocalJSXCommandCall = launchCommand(
|
||||
makeOpts({
|
||||
dispatch: async () => {
|
||||
throw new Error('specific-error-text')
|
||||
},
|
||||
errorView: (msg: string) => {
|
||||
capturedMsgs.push(msg)
|
||||
return React.createElement('span', null, msg)
|
||||
},
|
||||
}),
|
||||
)
|
||||
await call(
|
||||
mock(() => {}),
|
||||
{} as never,
|
||||
'Frank',
|
||||
)
|
||||
expect(capturedMsgs).toHaveLength(1)
|
||||
expect(capturedMsgs[0]).toBe('specific-error-text')
|
||||
})
|
||||
})
|
||||
122
src/commands/_shared/launchCommand.ts
Normal file
122
src/commands/_shared/launchCommand.ts
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* launchCommand — generic factory for local-jsx command implementations.
|
||||
*
|
||||
* Encapsulates the repeated boilerplate across the 6 command launch files:
|
||||
* - args parsing + invalid-args handling
|
||||
* - dispatch error capture + onDone error message
|
||||
* - errorView rendering
|
||||
* - React.createElement call for the happy-path View
|
||||
*
|
||||
* Usage (H2 finding — cuts boilerplate ~50%):
|
||||
*
|
||||
* export const callMyCmd: LocalJSXCommandCall = launchCommand<MyParsed, MyViewProps>({
|
||||
* commandName: 'my-cmd',
|
||||
* parseArgs: parseMyArgs,
|
||||
* dispatch: async (parsed, onDone, context) => { ... return viewProps },
|
||||
* View: MyCmdView,
|
||||
* errorView: (msg) => React.createElement(MyCmdView, { mode: 'error', message: msg }),
|
||||
* })
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import type {
|
||||
LocalJSXCommandCall,
|
||||
LocalJSXCommandOnDone,
|
||||
} from '../../types/command.js'
|
||||
import type { ToolUseContext } from '../../Tool.js'
|
||||
|
||||
/** Shape returned by parseArgs when args are invalid. */
|
||||
export interface InvalidParsed {
|
||||
action: 'invalid'
|
||||
reason: string
|
||||
}
|
||||
|
||||
export interface LaunchCommandOptions<TParsed, TViewProps> {
|
||||
/**
|
||||
* Command name used in error messages (e.g. "local-vault").
|
||||
* Appears in the onDone text when dispatch throws.
|
||||
*/
|
||||
commandName: string
|
||||
|
||||
/**
|
||||
* Parse raw args string into a typed action union or an invalid sentinel.
|
||||
* Must return `{ action: 'invalid'; reason: string }` when args are bad.
|
||||
*/
|
||||
parseArgs: (rawArgs: string) => TParsed | InvalidParsed
|
||||
|
||||
/**
|
||||
* Perform the command operation.
|
||||
* - Call onDone with the user-visible summary text.
|
||||
* - Return the View props to render, or null to render nothing.
|
||||
* - Throw to trigger the error path.
|
||||
*/
|
||||
dispatch: (
|
||||
parsed: TParsed,
|
||||
onDone: LocalJSXCommandOnDone,
|
||||
context: ToolUseContext,
|
||||
) => Promise<TViewProps | null>
|
||||
|
||||
/**
|
||||
* React component rendered with the props returned by dispatch.
|
||||
*/
|
||||
View: React.FC<TViewProps>
|
||||
|
||||
/**
|
||||
* Render an error node when parseArgs returns invalid or dispatch throws.
|
||||
* Receives the human-readable error message string.
|
||||
*/
|
||||
errorView: (message: string) => React.ReactNode
|
||||
|
||||
/**
|
||||
* Optional hook called when dispatch throws, before the error is surfaced.
|
||||
* Useful for analytics logEvent calls.
|
||||
* Default: no-op.
|
||||
*/
|
||||
onDispatchError?: (err: unknown) => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a LocalJSXCommandCall that wraps the provided parse / dispatch / View
|
||||
* triple with uniform error handling.
|
||||
*/
|
||||
export function launchCommand<TParsed, TViewProps>(
|
||||
opts: LaunchCommandOptions<TParsed, TViewProps>,
|
||||
): LocalJSXCommandCall {
|
||||
return async (
|
||||
onDone: LocalJSXCommandOnDone,
|
||||
context: ToolUseContext,
|
||||
args: string,
|
||||
): Promise<React.ReactNode> => {
|
||||
// ── Parse args ────────────────────────────────────────────────────────────
|
||||
const parsed = opts.parseArgs(args ?? '')
|
||||
|
||||
if (isInvalid(parsed)) {
|
||||
onDone(`Invalid args: ${parsed.reason}`, { display: 'system' })
|
||||
return opts.errorView(parsed.reason)
|
||||
}
|
||||
|
||||
// ── Dispatch ──────────────────────────────────────────────────────────────
|
||||
try {
|
||||
const viewProps = await opts.dispatch(parsed as TParsed, onDone, context)
|
||||
if (viewProps === null) return null
|
||||
return React.createElement(
|
||||
opts.View as React.ComponentType<object>,
|
||||
viewProps as object,
|
||||
)
|
||||
} catch (err: unknown) {
|
||||
const msg = err instanceof Error ? err.message : String(err)
|
||||
opts.onDispatchError?.(err)
|
||||
onDone(`${opts.commandName} failed: ${msg}`, { display: 'system' })
|
||||
return opts.errorView(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isInvalid(parsed: unknown): parsed is InvalidParsed {
|
||||
return (
|
||||
typeof parsed === 'object' &&
|
||||
parsed !== null &&
|
||||
'action' in parsed &&
|
||||
(parsed as InvalidParsed).action === 'invalid'
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user