82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
/** Cross-platform native path and text-document openers used by the local GUI carrier. */
|
|
|
|
import { runNativeCommand, type NativeCommandRunner } from '@deepseek-ai/dsh-native-command'
|
|
|
|
/** Testable command boundary; native implementations never invoke a shell. */
|
|
export type PathOpenerRunner = NativeCommandRunner
|
|
|
|
/** Injectable platform facts for deterministic adapter tests. */
|
|
export interface PathOpenerInternals {
|
|
platform?: NodeJS.Platform
|
|
run?: PathOpenerRunner
|
|
}
|
|
|
|
/** Native path-open intent; macOS distinguishes text editing from file association. */
|
|
type PathOpenIntent = 'default' | 'text-editor'
|
|
|
|
/** PowerShell single-quoted literal (doubles embedded quotes). */
|
|
function powershellLiteral(path: string): string {
|
|
return `'${path.replace(/'/g, "''")}'`
|
|
}
|
|
|
|
/** Dispatch one shell-free platform command for the requested open intent. */
|
|
async function openNativePathWithIntent(
|
|
path: string,
|
|
signal: AbortSignal,
|
|
intent: PathOpenIntent,
|
|
internals: PathOpenerInternals = {},
|
|
): Promise<void> {
|
|
const platform = internals.platform ?? process.platform
|
|
const run = internals.run ?? runNativeCommand
|
|
|
|
if (platform === 'darwin') {
|
|
await run('open', intent === 'text-editor' ? ['-t', path] : [path], signal)
|
|
return
|
|
}
|
|
|
|
if (platform === 'win32') {
|
|
await run('powershell.exe', [
|
|
'-NoProfile',
|
|
'-Command',
|
|
`Invoke-Item -LiteralPath ${powershellLiteral(path)}`,
|
|
], signal)
|
|
return
|
|
}
|
|
|
|
if (platform === 'linux') {
|
|
await run('xdg-open', [path], signal)
|
|
return
|
|
}
|
|
|
|
throw new Error(`native path opener is unsupported on ${platform}`)
|
|
}
|
|
|
|
/**
|
|
* Open a filesystem path with the operating system's default application.
|
|
* @param path - absolute or host-resolvable path (caller owns resolution).
|
|
* @param signal - caller/connection lifetime; abort terminates the native command.
|
|
* @param internals - platform and runner seam for deterministic tests.
|
|
*/
|
|
export function openNativePath(
|
|
path: string,
|
|
signal: AbortSignal,
|
|
internals: PathOpenerInternals = {},
|
|
): Promise<void> {
|
|
return openNativePathWithIntent(path, signal, 'default', internals)
|
|
}
|
|
|
|
/**
|
|
* Open a text document for editing; macOS bypasses the file-type association
|
|
* so a YAML association with a browser cannot consume the gesture.
|
|
* @param path - absolute or host-resolvable text-document path.
|
|
* @param signal - caller/connection lifetime; abort terminates the native command.
|
|
* @param internals - platform and runner seam for deterministic tests.
|
|
*/
|
|
export function openNativeTextFile(
|
|
path: string,
|
|
signal: AbortSignal,
|
|
internals: PathOpenerInternals = {},
|
|
): Promise<void> {
|
|
return openNativePathWithIntent(path, signal, 'text-editor', internals)
|
|
}
|