- resolve.ts: gate the display branch on linux (the native backend drives exactly darwin/win32/linux) and require a zenity/kdialog binary on PATH, probed once at boot (new probe.ts, injected predicate for tests); type bindHost as the webserver schema's closed union. - index.ts: the disposer now joins the removed entry's fiber teardown so unloading the chooser settles only after the backend quiesced; export BACKEND_PACKAGES as the runtime-string source of truth. - verify-cordis-config: a composition mounting -auto must declare both backends as dependencies (negative-tested), since keyless Linux CI only ever resolves browse and would hide a dropped -native dep. - apps/web scaffold: pin -browse via disable+insert (goldens are interaction-specific); fix the stale workspace-flow comment. - docs/module-graph.md regenerated; README + Agent Note document the ssh -L shape, the PATH-only probe, and the new gate (zh pairs re-paired). - composition spec: assert teardown quiescence without a loader await, cover external entry removal, and await the loader's self-dispose disabled-persist so it cannot race temp-dir teardown.
45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
/**
|
|
* PATH probe for the native backend's Linux chooser binaries: one boot-time
|
|
* sampled fact for the resolver, so an attended Linux host without
|
|
* zenity/kdialog keeps the working `browse` interaction instead of a backend
|
|
* whose every pick fails.
|
|
* @module @deepseek-ai/dsh-host-directory-picker-auto/probe
|
|
*/
|
|
|
|
import { accessSync, constants } from 'node:fs'
|
|
import { delimiter, join } from 'node:path'
|
|
|
|
/** The chooser binaries the native backend can drive on Linux (zenity, KDialog fallback). */
|
|
const LINUX_CHOOSER_BINARIES = ['zenity', 'kdialog'] as const
|
|
|
|
/**
|
|
* Whether the current process may execute the candidate path.
|
|
* @param candidate - absolute or PATH-joined file path.
|
|
* @returns true only for an existing executable file.
|
|
*/
|
|
export function canExecute(candidate: string): boolean {
|
|
try {
|
|
accessSync(candidate, constants.X_OK)
|
|
} catch {
|
|
// Absent or non-executable candidate — the only signals accessSync(X_OK) emits.
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
/**
|
|
* Scan a PATH value for one of the native backend's Linux chooser binaries.
|
|
* @param pathValue - the `PATH` environment value (absent or empty scans nothing).
|
|
* @param isExecutable - executability predicate ({@link canExecute} in production; injected for deterministic tests).
|
|
* @returns whether any PATH directory holds an executable chooser binary.
|
|
*/
|
|
export function hasLinuxChooserBinary(pathValue: string | undefined, isExecutable: (candidate: string) => boolean): boolean {
|
|
for (const dir of (pathValue ?? '').split(delimiter)) {
|
|
if (dir === '') continue
|
|
for (const name of LINUX_CHOOSER_BINARIES) {
|
|
if (isExecutable(join(dir, name))) return true
|
|
}
|
|
}
|
|
return false
|
|
}
|