mirror of
https://gh-proxy.org/https://github.com/klinsmaya/workdsh.git
synced 2026-11-09 18:07:02 +08:00
feat(desktop): add Windows workspace folder picker
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
DESKTOP_DIRECTORY_PICKER_PATH,
|
||||
type DesktopDirectoryPickerResponse,
|
||||
} from '../directory-picker-contract.ts'
|
||||
|
||||
/** Window seam consumed by the patched upstream browse panel. */
|
||||
export interface DesktopDirectoryPickerWindow {
|
||||
__DSH_DESKTOP_PICK_DIRECTORY__?: () => Promise<string | null>
|
||||
}
|
||||
|
||||
type DirectoryPickerRequest = (
|
||||
input: RequestInfo | URL,
|
||||
init?: RequestInit,
|
||||
) => Promise<Response>
|
||||
|
||||
function isResponse(value: unknown): value is DesktopDirectoryPickerResponse {
|
||||
if (typeof value !== 'object' || value === null || !('path' in value)) return false
|
||||
const path = (value as { path?: unknown }).path
|
||||
return path === null || typeof path === 'string'
|
||||
}
|
||||
|
||||
/** Ask the desktop Host to open the platform folder chooser. */
|
||||
export async function requestDesktopDirectory(
|
||||
request: DirectoryPickerRequest = window.fetch.bind(window),
|
||||
): Promise<string | null> {
|
||||
const response = await request(DESKTOP_DIRECTORY_PICKER_PATH, {
|
||||
method: 'POST',
|
||||
headers: { accept: 'application/json' },
|
||||
})
|
||||
if (!response.ok) throw new Error('DSH Desktop could not open the system folder picker')
|
||||
const value: unknown = await response.json()
|
||||
if (!isResponse(value)) throw new Error('DSH Desktop received an invalid response from the system folder picker')
|
||||
return value.path
|
||||
}
|
||||
|
||||
/** Publish the Windows-only picker bridge for the browse panel's icon action. */
|
||||
export function installDesktopDirectoryPickerBridge(
|
||||
target: DesktopDirectoryPickerWindow = window as DesktopDirectoryPickerWindow,
|
||||
request: DirectoryPickerRequest = window.fetch.bind(window),
|
||||
): () => void {
|
||||
const previous = target.__DSH_DESKTOP_PICK_DIRECTORY__
|
||||
const pick = async (): Promise<string | null> => await requestDesktopDirectory(request)
|
||||
target.__DSH_DESKTOP_PICK_DIRECTORY__ = pick
|
||||
return () => {
|
||||
if (target.__DSH_DESKTOP_PICK_DIRECTORY__ !== pick) return
|
||||
if (previous === undefined) delete target.__DSH_DESKTOP_PICK_DIRECTORY__
|
||||
else target.__DSH_DESKTOP_PICK_DIRECTORY__ = previous
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
|
||||
import type {} from '@deepseek-ai/dsh-client-ui-theme/client'
|
||||
import { applyAdvancedShell } from './advanced-shell.ts'
|
||||
import { startRendererBootReporter } from './boot-health.ts'
|
||||
import { installDesktopDirectoryPickerBridge } from './directory-picker.ts'
|
||||
import { parseDesktopClientEnvironment } from './environment.ts'
|
||||
|
||||
export { applyAdvancedShell } from './advanced-shell.ts'
|
||||
@@ -35,5 +36,11 @@ export function apply(ctx: ClientContext): void {
|
||||
() => startRendererBootReporter(ctx.loader),
|
||||
'dsh-plugin-desktop: renderer boot health report',
|
||||
)
|
||||
if (environment.platform === 'win32') {
|
||||
ctx.effect(
|
||||
() => installDesktopDirectoryPickerBridge(),
|
||||
'dsh-plugin-desktop: native directory picker bridge',
|
||||
)
|
||||
}
|
||||
if (environment.mode === 'advanced') applyAdvancedShell(ctx, environment)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/** Same-origin endpoint used by the Windows browse panel's native-picker shortcut. */
|
||||
export const DESKTOP_DIRECTORY_PICKER_PATH = '/_dsh/desktop/pick-directory'
|
||||
|
||||
/** Successful native directory-picker response. */
|
||||
export interface DesktopDirectoryPickerResponse {
|
||||
/** Absolute selected path, or null when the chooser was cancelled. */
|
||||
path: string | null
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http'
|
||||
import type { DesktopDirectoryPickerResponse } from './directory-picker-contract.ts'
|
||||
|
||||
function finishJson(res: ServerResponse, statusCode: number, value: object): void {
|
||||
res.statusCode = statusCode
|
||||
res.setHeader('content-type', 'application/json; charset=utf-8')
|
||||
res.end(JSON.stringify(value))
|
||||
}
|
||||
|
||||
/** Validate and serve one native directory-picker request from the desktop renderer. */
|
||||
export async function handleDesktopDirectoryPickerRequest(
|
||||
req: IncomingMessage,
|
||||
res: ServerResponse,
|
||||
expectedOrigin: string,
|
||||
pickDirectory: () => Promise<string | null>,
|
||||
reportError: (cause: unknown) => void = () => {},
|
||||
): Promise<void> {
|
||||
if (req.method !== 'POST') return finishJson(res, 405, { error: 'method not allowed' })
|
||||
if (req.headers.origin !== expectedOrigin) return finishJson(res, 403, { error: 'forbidden' })
|
||||
try {
|
||||
const response: DesktopDirectoryPickerResponse = { path: await pickDirectory() }
|
||||
finishJson(res, 200, response)
|
||||
} catch (cause: unknown) {
|
||||
reportError(cause)
|
||||
finishJson(res, 500, { error: 'native directory picker failed' })
|
||||
}
|
||||
}
|
||||
@@ -110,6 +110,7 @@ export class ElectronDesktopRuntime implements DesktopRuntime {
|
||||
private readonly trayItems = new Map<symbol, DesktopTrayItem>()
|
||||
private terminalSpec: DesktopTerminalSpec | undefined
|
||||
private diagnosticExport: Promise<void> | undefined
|
||||
private directoryPickTask: Promise<string | null> | undefined
|
||||
private rendererBootReported = false
|
||||
|
||||
constructor(
|
||||
@@ -181,6 +182,33 @@ export class ElectronDesktopRuntime implements DesktopRuntime {
|
||||
window.focus()
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
async pickDirectory(): Promise<string | null> {
|
||||
if (this.platform !== 'win32') {
|
||||
throw new Error(`dsh-plugin-desktop: native workspace picker is unavailable on ${this.platform}`)
|
||||
}
|
||||
if (this.directoryPickTask !== undefined) return await this.directoryPickTask
|
||||
const task = this.showDirectoryPicker()
|
||||
this.directoryPickTask = task
|
||||
try {
|
||||
return await task
|
||||
} finally {
|
||||
if (this.directoryPickTask === task) this.directoryPickTask = undefined
|
||||
}
|
||||
}
|
||||
|
||||
private async showDirectoryPicker(): Promise<string | null> {
|
||||
const options: Electron.OpenDialogOptions = {
|
||||
title: this.currentLocale === 'zh' ? '选择工作区目录' : 'Select Workspace Directory',
|
||||
properties: ['openDirectory', 'dontAddToRecent'],
|
||||
}
|
||||
const window = this.window
|
||||
const result = window === undefined || window.isDestroyed()
|
||||
? await dialog.showOpenDialog(options)
|
||||
: await dialog.showOpenDialog(window, options)
|
||||
return result.canceled ? null : result.filePaths[0] ?? null
|
||||
}
|
||||
|
||||
/** @inheritdoc */
|
||||
registerTrayItem(item: DesktopTrayItem): DesktopTrayItemRegistration {
|
||||
const key = Symbol()
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
handleRendererBootRequest,
|
||||
RENDERER_BOOT_REPORT_PATH,
|
||||
} from './renderer-boot.ts'
|
||||
import { DESKTOP_DIRECTORY_PICKER_PATH } from './directory-picker-contract.ts'
|
||||
import { handleDesktopDirectoryPickerRequest } from './directory-picker-route.ts'
|
||||
import type { DesktopShellMode } from './runtime.ts'
|
||||
import type {} from './runtime.ts'
|
||||
|
||||
@@ -145,6 +147,24 @@ export function apply(ctx: Context, config: Config): void {
|
||||
}),
|
||||
'dsh-plugin-desktop: renderer boot report route',
|
||||
)
|
||||
if (runtime.platform === 'win32') {
|
||||
ctx.effect(
|
||||
() => ctx.webServer.register({
|
||||
kind: 'exact',
|
||||
path: DESKTOP_DIRECTORY_PICKER_PATH,
|
||||
handler: (req, res) => handleDesktopDirectoryPickerRequest(
|
||||
req,
|
||||
res,
|
||||
rendererOrigin,
|
||||
() => runtime.pickDirectory(),
|
||||
cause => {
|
||||
ctx.logger.error(`dsh-plugin-desktop: native directory picker failed: ${cause instanceof Error ? cause.message : String(cause)}`)
|
||||
},
|
||||
),
|
||||
}),
|
||||
'dsh-plugin-desktop: native directory picker route',
|
||||
)
|
||||
}
|
||||
ctx.effect(() => {
|
||||
let pending: ReturnType<typeof setImmediate> | undefined
|
||||
const stopWatching = settings.watch((next) => {
|
||||
|
||||
@@ -181,6 +181,9 @@ export interface DesktopRuntime {
|
||||
/** Export a diagnostics zip and reveal it in the system file manager. */
|
||||
exportDiagnostics(): Promise<void>
|
||||
|
||||
/** Open the desktop operating system's native workspace-folder chooser. */
|
||||
pickDirectory(): Promise<string | null>
|
||||
|
||||
/** Accept the terminal client Loader outcome for the mounted generation. */
|
||||
reportRendererBoot(report: RendererBootReport): void
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
installDesktopDirectoryPickerBridge,
|
||||
requestDesktopDirectory,
|
||||
type DesktopDirectoryPickerWindow,
|
||||
} from '../src/client/directory-picker.ts'
|
||||
import { DESKTOP_DIRECTORY_PICKER_PATH } from '../src/directory-picker-contract.ts'
|
||||
|
||||
describe('desktop directory picker client bridge', () => {
|
||||
it('returns a native path from the same-origin desktop route', async () => {
|
||||
const request = vi.fn(async () => new Response(JSON.stringify({ path: 'C:\\Work' }), {
|
||||
status: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
}))
|
||||
|
||||
await expect(requestDesktopDirectory(request)).resolves.toBe('C:\\Work')
|
||||
expect(request).toHaveBeenCalledWith(DESKTOP_DIRECTORY_PICKER_PATH, {
|
||||
method: 'POST',
|
||||
headers: { accept: 'application/json' },
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects invalid and failed route responses', async () => {
|
||||
await expect(requestDesktopDirectory(async () => new Response('{}')))
|
||||
.rejects.toThrow('invalid response')
|
||||
await expect(requestDesktopDirectory(async () => new Response('', { status: 500 })))
|
||||
.rejects.toThrow('could not open the system folder picker')
|
||||
})
|
||||
|
||||
it('installs and restores the window bridge consumed by the browse panel', async () => {
|
||||
const previous = vi.fn(async () => null)
|
||||
const target = { __DSH_DESKTOP_PICK_DIRECTORY__: previous } as DesktopDirectoryPickerWindow
|
||||
const request = vi.fn(async () => new Response(JSON.stringify({ path: null })))
|
||||
|
||||
const dispose = installDesktopDirectoryPickerBridge(target, request)
|
||||
expect(target.__DSH_DESKTOP_PICK_DIRECTORY__).not.toBe(previous)
|
||||
await expect(target.__DSH_DESKTOP_PICK_DIRECTORY__?.()).resolves.toBeNull()
|
||||
dispose()
|
||||
expect(target.__DSH_DESKTOP_PICK_DIRECTORY__).toBe(previous)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,79 @@
|
||||
import type { IncomingMessage, ServerResponse } from 'node:http'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { handleDesktopDirectoryPickerRequest } from '../src/directory-picker-route.ts'
|
||||
|
||||
function request(origin = 'http://127.0.0.1:43120', method = 'POST'): IncomingMessage {
|
||||
return { method, headers: { origin } } as IncomingMessage
|
||||
}
|
||||
|
||||
function response(): ServerResponse & {
|
||||
body: string
|
||||
end: ReturnType<typeof vi.fn>
|
||||
setHeader: ReturnType<typeof vi.fn>
|
||||
} {
|
||||
const res = {
|
||||
body: '',
|
||||
statusCode: 200,
|
||||
setHeader: vi.fn(),
|
||||
end: vi.fn((body?: string) => { res.body = body ?? '' }),
|
||||
}
|
||||
return res as unknown as ServerResponse & typeof res
|
||||
}
|
||||
|
||||
describe('desktop directory picker route', () => {
|
||||
it('returns the path selected by the native desktop adapter', async () => {
|
||||
const pick = vi.fn(async () => 'C:\\Work')
|
||||
const res = response()
|
||||
|
||||
await handleDesktopDirectoryPickerRequest(
|
||||
request(),
|
||||
res,
|
||||
'http://127.0.0.1:43120',
|
||||
pick,
|
||||
)
|
||||
|
||||
expect(pick).toHaveBeenCalledOnce()
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(res.setHeader).toHaveBeenCalledWith('content-type', 'application/json; charset=utf-8')
|
||||
expect(JSON.parse(res.body)).toEqual({ path: 'C:\\Work' })
|
||||
})
|
||||
|
||||
it('keeps cancellation distinct from route failure', async () => {
|
||||
const res = response()
|
||||
|
||||
await handleDesktopDirectoryPickerRequest(
|
||||
request(),
|
||||
res,
|
||||
'http://127.0.0.1:43120',
|
||||
async () => null,
|
||||
)
|
||||
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(JSON.parse(res.body)).toEqual({ path: null })
|
||||
})
|
||||
|
||||
it('rejects cross-origin and non-POST requests without opening a dialog', async () => {
|
||||
const pick = vi.fn(async () => null)
|
||||
|
||||
for (const req of [request('https://example.com'), request(undefined, 'GET')]) {
|
||||
const res = response()
|
||||
await handleDesktopDirectoryPickerRequest(req, res, 'http://127.0.0.1:43120', pick)
|
||||
expect(res.statusCode).toBe(req.method === 'GET' ? 405 : 403)
|
||||
}
|
||||
expect(pick).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('returns a stable error without exposing Electron details', async () => {
|
||||
const res = response()
|
||||
|
||||
await handleDesktopDirectoryPickerRequest(
|
||||
request(),
|
||||
res,
|
||||
'http://127.0.0.1:43120',
|
||||
async () => { throw new Error('private native failure') },
|
||||
)
|
||||
|
||||
expect(res.statusCode).toBe(500)
|
||||
expect(JSON.parse(res.body)).toEqual({ error: 'native directory picker failed' })
|
||||
})
|
||||
})
|
||||
@@ -58,6 +58,7 @@ const electron = vi.hoisted(() => {
|
||||
let zoomLevel = 0
|
||||
const dialog = {
|
||||
showErrorBox: vi.fn(),
|
||||
showOpenDialog: vi.fn(async () => ({ canceled: true, filePaths: [] as string[] })),
|
||||
showMessageBox: vi.fn(async () => ({ response: 0, checkboxChecked: false })),
|
||||
}
|
||||
const appIcon = {
|
||||
@@ -232,6 +233,7 @@ describe('Electron compatibility runtime', () => {
|
||||
electron.loadURL.mockReset()
|
||||
electron.loadURL.mockResolvedValue(undefined)
|
||||
electron.dialog.showMessageBox.mockResolvedValue({ response: 0, checkboxChecked: false })
|
||||
electron.dialog.showOpenDialog.mockResolvedValue({ canceled: true, filePaths: [] })
|
||||
electron.shell.openPath.mockResolvedValue('')
|
||||
electron.nativeTheme.themeSource = 'system'
|
||||
electron.resetZoomLevel()
|
||||
@@ -323,6 +325,26 @@ describe('Electron compatibility runtime', () => {
|
||||
expect(electron.trays[0]?.off).toHaveBeenCalledWith('click', expect.any(Function))
|
||||
})
|
||||
|
||||
it('opens one parented Windows folder chooser and returns its selected path', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
electron.dialog.showOpenDialog.mockResolvedValue({ canceled: false, filePaths: ['C:\\Work'] })
|
||||
const { ElectronDesktopRuntime } = await import('../src/electron-runtime.ts')
|
||||
const runtime = new ElectronDesktopRuntime(async () => {})
|
||||
const release = runtime.schedule(spec)
|
||||
await runtime.mountScheduled()
|
||||
|
||||
await expect(runtime.pickDirectory()).resolves.toBe('C:\\Work')
|
||||
expect(electron.dialog.showOpenDialog).toHaveBeenCalledWith(
|
||||
electron.browserWindows[0],
|
||||
{
|
||||
title: 'Select Workspace Directory',
|
||||
properties: ['openDirectory', 'dontAddToRecent'],
|
||||
},
|
||||
)
|
||||
|
||||
await release()
|
||||
})
|
||||
|
||||
it('logs renderer crashes with the Windows exception code', async () => {
|
||||
vi.spyOn(process, 'platform', 'get').mockReturnValue('win32')
|
||||
const { ElectronDesktopRuntime } = await import('../src/electron-runtime.ts')
|
||||
|
||||
@@ -118,6 +118,30 @@ describe('published package surface', () => {
|
||||
expect(manifest.optionalDependencies ?? {}).not.toHaveProperty('dshmarket')
|
||||
})
|
||||
|
||||
it('patches the browse panel with the Windows native-picker icon bridge', () => {
|
||||
const patchPath = './patches/[email protected]'
|
||||
expect(workspaceManifest.resolutions).toMatchObject({
|
||||
'@deepseek-ai/dsh-client-ui-directory-picker-browse@npm:0.1.0-rc.7': expect.stringContaining(patchPath),
|
||||
'@deepseek-ai/dsh-client-ui-directory-picker-browse@npm:^0.1.0-rc.7': expect.stringContaining(patchPath),
|
||||
})
|
||||
const patch = readFileSync(new URL(patchPath, workspaceRoot), 'utf8')
|
||||
const installedClient = readFileSync(new URL(
|
||||
'node_modules/@deepseek-ai/dsh-client-ui-directory-picker-browse/lib/client.js',
|
||||
packageRoot,
|
||||
), 'utf8')
|
||||
for (const marker of [
|
||||
'__DSH_DESKTOP_PICK_DIRECTORY__',
|
||||
'IconFolderOpen16',
|
||||
'nativePickerButton',
|
||||
'browser.nativePicker',
|
||||
'border:1px solid var(--dsw-alias-border-l2)',
|
||||
'background:var(--dsw-alias-bg-layer-2)',
|
||||
]) {
|
||||
expect(patch).toContain(marker)
|
||||
expect(installedClient).toContain(marker)
|
||||
}
|
||||
})
|
||||
|
||||
it('builds public Host plugins and their private native bootstraps', () => {
|
||||
const config = readFileSync(new URL('tsdown.config.ts', packageRoot), 'utf8')
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
type Config as DesktopConfig,
|
||||
type DesktopSettings,
|
||||
} from '../src/index.ts'
|
||||
import { DESKTOP_DIRECTORY_PICKER_PATH } from '../src/directory-picker-contract.ts'
|
||||
import type { DesktopRuntime, DesktopShellSpec } from '../src/runtime.ts'
|
||||
import { RENDERER_BOOT_REPORT_PATH, type RendererBootReport } from '../src/renderer-boot-contract.ts'
|
||||
|
||||
@@ -38,7 +39,8 @@ interface PluginHarness {
|
||||
setLocalePreference: ReturnType<typeof vi.fn<(locale: LocaleId | undefined) => void>>
|
||||
setThemeSource: ReturnType<typeof vi.fn<(source: ThemePreference) => void>>
|
||||
rendererBoot: ReturnType<typeof vi.fn<(report: RendererBootReport) => void>>
|
||||
rendererRoute(): WebRoute | undefined
|
||||
pickDirectory: ReturnType<typeof vi.fn<() => Promise<string | null>>>
|
||||
route(path: string): WebRoute | undefined
|
||||
notify(next: DesktopSettings, prev: DesktopSettings): Promise<void>
|
||||
notifyLocale(preference: LocaleId | undefined): void
|
||||
notifyTheme(preference: ThemePreference): void
|
||||
@@ -52,7 +54,8 @@ function createHarness(platform: DesktopRuntime['platform'] = 'darwin'): PluginH
|
||||
const setLocalePreference = vi.fn<(locale: LocaleId | undefined) => void>()
|
||||
const setThemeSource = vi.fn<(source: ThemePreference) => void>()
|
||||
const rendererBoot = vi.fn<(report: RendererBootReport) => void>()
|
||||
let rendererRoute: WebRoute | undefined
|
||||
const pickDirectory = vi.fn(async () => null)
|
||||
const routes = new Map<string, WebRoute>()
|
||||
const settingsUpdated = new Set<(namespace: unknown, next: unknown) => void>()
|
||||
let localePreference: LocaleId | undefined
|
||||
let themePreference: ThemePreference = 'system'
|
||||
@@ -79,6 +82,7 @@ function createHarness(platform: DesktopRuntime['platform'] = 'darwin'): PluginH
|
||||
registerTrayItem: () => ({ refresh: () => {}, dispose: () => {} }),
|
||||
openTerminal: () => {},
|
||||
exportDiagnostics: async () => {},
|
||||
pickDirectory,
|
||||
reportRendererBoot: rendererBoot,
|
||||
setLocalePreference,
|
||||
setThemeSource,
|
||||
@@ -107,8 +111,8 @@ function createHarness(platform: DesktopRuntime['platform'] = 'darwin'): PluginH
|
||||
host: '127.0.0.1',
|
||||
port: 43120,
|
||||
register: vi.fn((route: WebRoute) => {
|
||||
rendererRoute = route
|
||||
return () => { if (rendererRoute === route) rendererRoute = undefined }
|
||||
routes.set(route.path, route)
|
||||
return () => { if (routes.get(route.path) === route) routes.delete(route.path) }
|
||||
}),
|
||||
},
|
||||
settings,
|
||||
@@ -129,7 +133,8 @@ function createHarness(platform: DesktopRuntime['platform'] = 'darwin'): PluginH
|
||||
setLocalePreference,
|
||||
setThemeSource,
|
||||
rendererBoot,
|
||||
rendererRoute: () => rendererRoute,
|
||||
pickDirectory,
|
||||
route: path => routes.get(path),
|
||||
notify: async (next, prev) => { await watcher?.(next, prev) },
|
||||
notifyLocale: (preference) => {
|
||||
localePreference = preference
|
||||
@@ -220,7 +225,7 @@ describe('desktop Host plugin', () => {
|
||||
it('forwards same-origin renderer boot reports through the Host route', async () => {
|
||||
const harness = createHarness()
|
||||
apply(harness.ctx, config)
|
||||
const route = harness.rendererRoute()
|
||||
const route = harness.route(RENDERER_BOOT_REPORT_PATH)
|
||||
expect(route).toEqual(expect.objectContaining({
|
||||
kind: 'exact',
|
||||
path: RENDERER_BOOT_REPORT_PATH,
|
||||
@@ -242,6 +247,33 @@ describe('desktop Host plugin', () => {
|
||||
expect(res.statusCode).toBe(204)
|
||||
})
|
||||
|
||||
it('serves the Windows native picker through a same-origin desktop route', async () => {
|
||||
const harness = createHarness('win32')
|
||||
harness.pickDirectory.mockResolvedValue('C:\\Work')
|
||||
apply(harness.ctx, config)
|
||||
const route = harness.route(DESKTOP_DIRECTORY_PICKER_PATH)
|
||||
expect(route).toEqual(expect.objectContaining({
|
||||
kind: 'exact',
|
||||
path: DESKTOP_DIRECTORY_PICKER_PATH,
|
||||
}))
|
||||
const req = {
|
||||
method: 'POST',
|
||||
headers: { origin: 'http://127.0.0.1:43120' },
|
||||
} as unknown as IncomingMessage
|
||||
let body = ''
|
||||
const res = {
|
||||
statusCode: 200,
|
||||
setHeader: vi.fn(),
|
||||
end: vi.fn((value?: string) => { body = value ?? '' }),
|
||||
} as unknown as ServerResponse
|
||||
|
||||
await route?.handler(req, res)
|
||||
|
||||
expect(harness.pickDirectory).toHaveBeenCalledOnce()
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(JSON.parse(body)).toEqual({ path: 'C:\\Work' })
|
||||
})
|
||||
|
||||
it.each(['win32', 'linux'] as const)(
|
||||
'keeps the full-size application icon on %s',
|
||||
(platform) => {
|
||||
|
||||
@@ -319,7 +319,7 @@ describe('desktop profile composition', {
|
||||
expect(() => readDesktopShellMode({ path })).toThrow('invalid settings document')
|
||||
})
|
||||
|
||||
it('pins the Windows browse picker and desktop pwsh provider without replacing process boundaries', () => {
|
||||
it('keeps the Windows browse panel and desktop pwsh provider without replacing process boundaries', () => {
|
||||
const home = temporaryHome()
|
||||
writeFileSync(join(home, 'cordis.patch.yml'), [
|
||||
'- id: pwsh-sandbox',
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"src/client/**/*.ts",
|
||||
"src/client/**/*.tsx",
|
||||
"tests/client-boot-health.spec.ts",
|
||||
"tests/client-directory-picker.spec.ts",
|
||||
"tests/client-environment.spec.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
"app-builder-lib@npm:26.15.3": "patch:app-builder-lib@npm%3A26.15.3#./patches/[email protected]",
|
||||
"@deepseek-ai/dsh-sandbox-windows-acl@npm:0.1.0-rc.7": "patch:@deepseek-ai/dsh-sandbox-windows-acl@npm%3A0.1.0-rc.7#./patches/[email protected]",
|
||||
"@deepseek-ai/dsh-sandbox-windows-acl@npm:^0.1.0-rc.7": "patch:@deepseek-ai/dsh-sandbox-windows-acl@npm%3A0.1.0-rc.7#./patches/[email protected]",
|
||||
"@deepseek-ai/dsh-client-ui-directory-picker-browse@npm:0.1.0-rc.7": "patch:@deepseek-ai/dsh-client-ui-directory-picker-browse@npm%3A0.1.0-rc.7#./patches/[email protected]",
|
||||
"@deepseek-ai/dsh-client-ui-directory-picker-browse@npm:^0.1.0-rc.7": "patch:@deepseek-ai/dsh-client-ui-directory-picker-browse@npm%3A0.1.0-rc.7#./patches/[email protected]",
|
||||
"koffi@npm:^3.1.0": "3.1.5"
|
||||
},
|
||||
"dependenciesMeta": {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1131,7 +1131,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@deepseek-ai/dsh-client-ui-directory-picker-browse@npm:0.1.0-rc.7, @deepseek-ai/dsh-client-ui-directory-picker-browse@npm:^0.1.0-rc.7":
|
||||
"@deepseek-ai/dsh-client-ui-directory-picker-browse@npm:0.1.0-rc.7":
|
||||
version: 0.1.0-rc.7
|
||||
resolution: "@deepseek-ai/dsh-client-ui-directory-picker-browse@npm:0.1.0-rc.7"
|
||||
dependencies:
|
||||
@@ -1149,6 +1149,24 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@deepseek-ai/dsh-client-ui-directory-picker-browse@patch:@deepseek-ai/dsh-client-ui-directory-picker-browse@npm%3A0.1.0-rc.7#./patches/[email protected]::locator=deepseek-harness-desktop%40workspace%3A.":
|
||||
version: 0.1.0-rc.7
|
||||
resolution: "@deepseek-ai/dsh-client-ui-directory-picker-browse@patch:@deepseek-ai/dsh-client-ui-directory-picker-browse@npm%3A0.1.0-rc.7#./patches/[email protected]::version=0.1.0-rc.7&hash=8f4c64&locator=deepseek-harness-desktop%40workspace%3A."
|
||||
dependencies:
|
||||
clsx: "npm:^2.0.0"
|
||||
peerDependencies:
|
||||
"@deepseek-ai/cordis": ^4.0.1
|
||||
"@deepseek-ai/dsh-client-locale": ^0.1.0-rc.7
|
||||
"@deepseek-ai/dsh-client-runtime": ^0.1.0-rc.7
|
||||
"@deepseek-ai/dsh-client-ui-primitives": ^0.1.0-rc.7
|
||||
"@deepseek-ai/dsh-client-ui-slots": ^0.1.0-rc.7
|
||||
"@deepseek-ai/dsh-client-ui-workspace": ^0.1.0-rc.7
|
||||
"@deepseek-ai/dsh-invariants": ^0.1.0-rc.7
|
||||
react: ^18.2.0
|
||||
checksum: 10c0/cd5b06cee91d6b16210ca75a6d25a53fc8c60bbdfd0ec20176f02f444aeb3d9b9eab71f4dd16381363f52ae2d6f618a79cf831713fc91d2a4299846891318e13
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@deepseek-ai/dsh-client-ui-directory-picker-native@npm:0.1.0-rc.7, @deepseek-ai/dsh-client-ui-directory-picker-native@npm:^0.1.0-rc.7":
|
||||
version: 0.1.0-rc.7
|
||||
resolution: "@deepseek-ai/dsh-client-ui-directory-picker-native@npm:0.1.0-rc.7"
|
||||
|
||||
Reference in New Issue
Block a user