@sparkelf/dsh-patch-wsl-native-open 0.2.0-rc.20 → 0.2.0-rc.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/patches/wsl-native-open.patch +43 -62
package/package.json
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
"formatVersion": 1,
|
|
5
5
|
"variants": [
|
|
6
6
|
{
|
|
7
|
-
"dsh": ">=0.1.
|
|
7
|
+
"dsh": ">=0.1.7-rc.1",
|
|
8
8
|
"file": "./patches/wsl-native-open.patch",
|
|
9
9
|
"id": "wsl-native-path-translation",
|
|
10
10
|
"target": {
|
|
11
|
-
"baseRevision": "
|
|
11
|
+
"baseRevision": "46a7f68b0922371ce7144b668b90e377d8e799f4",
|
|
12
12
|
"kind": "dsh-source",
|
|
13
13
|
"paths": [
|
|
14
14
|
"packages/host/open-in-app/",
|
|
@@ -32,5 +32,5 @@
|
|
|
32
32
|
"url": "git+https://github.com/SparkElf/deepseek-harness-plus.git"
|
|
33
33
|
},
|
|
34
34
|
"type": "module",
|
|
35
|
-
"version": "0.2.0-rc.
|
|
35
|
+
"version": "0.2.0-rc.22"
|
|
36
36
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
diff --git a/packages/host/open-in-app/src/icons.ts b/packages/host/open-in-app/src/icons.ts
|
|
2
|
-
index
|
|
2
|
+
index 4b07441c8c..519ba1344b 100644
|
|
3
3
|
--- a/packages/host/open-in-app/src/icons.ts
|
|
4
4
|
+++ b/packages/host/open-in-app/src/icons.ts
|
|
5
|
-
@@ -
|
|
5
|
+
@@ -98,6 +98,41 @@ const EXTRACT_ICON_PS1 = [
|
|
6
6
|
'',
|
|
7
7
|
].join('\n')
|
|
8
8
|
|
|
@@ -44,7 +44,7 @@ index 6908ce795b..570dc45c96 100644
|
|
|
44
44
|
/** Extract one Windows executable's associated icon as a 32px PNG. */
|
|
45
45
|
async function extractExecutableIconPng(
|
|
46
46
|
executablePath: string, timeoutMs: number, internals: ResolvedInternals,
|
|
47
|
-
@@ -
|
|
47
|
+
@@ -107,8 +142,12 @@ async function extractExecutableIconPng(
|
|
48
48
|
const script = join(workDir, 'extract-icon.ps1')
|
|
49
49
|
const outPng = join(workDir, 'icon.png')
|
|
50
50
|
await writeFile(script, EXTRACT_ICON_PS1, 'utf8')
|
|
@@ -58,7 +58,7 @@ index 6908ce795b..570dc45c96 100644
|
|
|
58
58
|
], timeoutMs, internals)
|
|
59
59
|
if (ran === null) return null
|
|
60
60
|
try {
|
|
61
|
-
@@ -
|
|
61
|
+
@@ -148,6 +187,13 @@ export async function extractAppIcon(
|
|
62
62
|
): Promise<OpenInAppIcon | null> {
|
|
63
63
|
const completed = resolveInternals(internals)
|
|
64
64
|
if (completed.platform === 'linux') {
|
|
@@ -73,10 +73,10 @@ index 6908ce795b..570dc45c96 100644
|
|
|
73
73
|
return desktopId === undefined ? null : extractLinuxIcon(desktopId, completed)
|
|
74
74
|
}
|
|
75
75
|
diff --git a/packages/host/open-in-app/src/resolver.ts b/packages/host/open-in-app/src/resolver.ts
|
|
76
|
-
index
|
|
76
|
+
index 970118e22a..ad8d176da4 100644
|
|
77
77
|
--- a/packages/host/open-in-app/src/resolver.ts
|
|
78
78
|
+++ b/packages/host/open-in-app/src/resolver.ts
|
|
79
|
-
@@ -14,10 +14,
|
|
79
|
+
@@ -14,10 +14,11 @@
|
|
80
80
|
|
|
81
81
|
import { spawn } from 'node:child_process'
|
|
82
82
|
import { readdir, readFile, stat } from 'node:fs/promises'
|
|
@@ -84,12 +84,13 @@ index 7ab964b4fd..8ea7318975 100644
|
|
|
84
84
|
+import { homedir, release as osRelease, platform as osPlatform } from 'node:os'
|
|
85
85
|
import { dirname, isAbsolute, join } from 'node:path'
|
|
86
86
|
import {
|
|
87
|
-
- canOpenNativePath, openNativePath, runNativeCommand, type NativeCommandRunner,
|
|
88
|
-
+ canOpenLinuxDesktop, canOpenNativePath, openNativePath, runNativeCommand,
|
|
87
|
+
- canOpenNativePath, openNativePath, runNativeCommand, desktopEntryFields, desktopDataDirectories, type NativeCommandRunner,
|
|
88
|
+
+ canOpenLinuxDesktop, canOpenNativePath, openNativePath, runNativeCommand,
|
|
89
|
+
+ desktopEntryFields, desktopDataDirectories, type NativeCommandRunner,
|
|
89
90
|
} from '@deepseek-ai/dsh-native-command'
|
|
90
91
|
import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess'
|
|
91
92
|
import {
|
|
92
|
-
@@ -98,6 +
|
|
93
|
+
@@ -98,6 +99,8 @@ export const launchDetachedApp: OpenInAppLauncher = (command, args, options) =>
|
|
93
94
|
/** Injectable platform facts for deterministic tests. */
|
|
94
95
|
export interface OpenInAppInternals {
|
|
95
96
|
platform?: NodeJS.Platform
|
|
@@ -98,7 +99,7 @@ index 7ab964b4fd..8ea7318975 100644
|
|
|
98
99
|
/** SSH launch fact from the inherited process layer, independent of `.env` values. */
|
|
99
100
|
ssh?: boolean
|
|
100
101
|
/** Bundle-directory roots replacing `/Applications` and `~/Applications`. */
|
|
101
|
-
@@ -115,6 +
|
|
102
|
+
@@ -115,6 +118,7 @@ export interface OpenInAppInternals {
|
|
102
103
|
/** Platform facts after the one explicit defaulting step at each public entry. */
|
|
103
104
|
export interface ResolvedInternals {
|
|
104
105
|
platform: NodeJS.Platform
|
|
@@ -106,7 +107,7 @@ index 7ab964b4fd..8ea7318975 100644
|
|
|
106
107
|
ssh: boolean
|
|
107
108
|
applicationRoots: readonly string[]
|
|
108
109
|
env: Readonly<Record<string, string | undefined>>
|
|
109
|
-
@@ -140,6 +
|
|
110
|
+
@@ -140,6 +144,7 @@ export function resolveInternals(internals: OpenInAppInternals): ResolvedInterna
|
|
110
111
|
}
|
|
111
112
|
return {
|
|
112
113
|
platform: internals.platform ?? osPlatform(),
|
|
@@ -114,7 +115,7 @@ index 7ab964b4fd..8ea7318975 100644
|
|
|
114
115
|
ssh: internals.ssh ?? false,
|
|
115
116
|
applicationRoots: internals.applicationRoots ?? ['/Applications', join(home, 'Applications')],
|
|
116
117
|
env: internals.env ?? process.env,
|
|
117
|
-
@@ -
|
|
118
|
+
@@ -453,6 +458,14 @@ export function specFor(app: OpenInAppApp, platform: NodeJS.Platform): OpenInApp
|
|
118
119
|
: undefined
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -129,7 +130,7 @@ index 7ab964b4fd..8ea7318975 100644
|
|
|
129
130
|
/** Icon source for a resolved executable: Windows extracts from the binary itself. */
|
|
130
131
|
function executableIcon(path: string, internals: ResolvedInternals): OpenInAppIconSource | undefined {
|
|
131
132
|
return internals.platform === 'win32' ? { kind: 'executable', path } : undefined
|
|
132
|
-
@@ -
|
|
133
|
+
@@ -504,10 +517,29 @@ async function locate(
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
case 'cli': {
|
|
@@ -164,7 +165,7 @@ index 7ab964b4fd..8ea7318975 100644
|
|
|
164
165
|
return found === null
|
|
165
166
|
? null
|
|
166
167
|
diff --git a/packages/util/native-command/src/index.ts b/packages/util/native-command/src/index.ts
|
|
167
|
-
index
|
|
168
|
+
index 9ad8e0883c..3377ddcb6f 100644
|
|
168
169
|
--- a/packages/util/native-command/src/index.ts
|
|
169
170
|
+++ b/packages/util/native-command/src/index.ts
|
|
170
171
|
@@ -6,6 +6,7 @@
|
|
@@ -176,21 +177,13 @@ index a2e23a35be..f1ea7c6233 100644
|
|
|
176
177
|
nativeFileManager,
|
|
177
178
|
revealNativePath,
|
|
178
179
|
diff --git a/packages/util/native-command/src/path-opener.ts b/packages/util/native-command/src/path-opener.ts
|
|
179
|
-
index
|
|
180
|
+
index 67bbc35902..dd062cc003 100644
|
|
180
181
|
--- a/packages/util/native-command/src/path-opener.ts
|
|
181
182
|
+++ b/packages/util/native-command/src/path-opener.ts
|
|
182
|
-
@@ -
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
-import { pathToFileURL } from 'node:url'
|
|
187
|
-
import { runNativeCommand, type NativeCommandRunner } from './runner.ts'
|
|
188
|
-
|
|
189
|
-
/** Testable command boundary; native implementations never invoke a shell. */
|
|
190
|
-
@@ -99,13 +98,49 @@ function isWsl(internals: PathOpenerInternals): boolean {
|
|
191
|
-
return (internals.osRelease ?? osRelease()).toLowerCase().includes('microsoft')
|
|
192
|
-
}
|
|
193
|
-
|
|
183
|
+
@@ -150,8 +150,55 @@ async function runExplorer(args: readonly string[], signal: AbortSignal, run: Pa
|
|
184
|
+
* @param signal - caller lifetime; abort terminates the command.
|
|
185
|
+
* @param run - shell-free command runner.
|
|
186
|
+
*/
|
|
194
187
|
+/**
|
|
195
188
|
+ * Windows PowerShell locations to try when PATH carries no Windows entry.
|
|
196
189
|
+ *
|
|
@@ -200,37 +193,42 @@ index 8f3443b3c1..3962fd8523 100644
|
|
|
200
193
|
+ * the volume the distribution mounts, and naming it there is what keeps the Windows
|
|
201
194
|
+ * desktop reachable from those processes.
|
|
202
195
|
+ */
|
|
203
|
-
+const
|
|
204
|
-
+ '/mnt/c/Windows/
|
|
205
|
-
+ '/c/Windows/
|
|
196
|
+
+const WINDOWS_EXPLORER_CANDIDATES = [
|
|
197
|
+
+ '/mnt/c/Windows/explorer.exe',
|
|
198
|
+
+ '/c/Windows/explorer.exe',
|
|
206
199
|
+] as const
|
|
207
200
|
+
|
|
208
|
-
|
|
201
|
+
+/**
|
|
202
|
+
+ * Open one Windows-resolvable path through its registered desktop application.
|
|
203
|
+
+ *
|
|
204
|
+
+ * Explorer resolves the association, but a process whose PATH lost its Windows entries
|
|
205
|
+
+ * cannot name the shell. WSL injects those entries only for the processes it starts
|
|
206
|
+
+ * interactively, so a service or scheduled job resolves nothing by name; naming the
|
|
207
|
+
+ * shell on the volume the distribution mounts keeps the Windows desktop reachable.
|
|
208
|
+
+ * @param path - Windows-resolvable path.
|
|
209
|
+
+ * @param signal - caller lifetime; abort terminates the command.
|
|
210
|
+
+ * @param run - shell-free command runner.
|
|
211
|
+
+ */
|
|
209
212
|
async function openWindowsPath(path: string, signal: AbortSignal, run: PathOpenerRunner): Promise<void> {
|
|
210
|
-
- await
|
|
211
|
-
+ const
|
|
212
|
-
'-NoProfile',
|
|
213
|
-
'-Command',
|
|
214
|
-
`Invoke-Item -LiteralPath ${powershellLiteral(path)}`,
|
|
215
|
-
- ], signal)
|
|
216
|
-
+ ]
|
|
213
|
+
- await runExplorer([explorerTarget(path)], signal, run)
|
|
214
|
+
+ const target = explorerTarget(path)
|
|
217
215
|
+ try {
|
|
218
|
-
+ await
|
|
216
|
+
+ await runExplorer([target], signal, run)
|
|
219
217
|
+ return
|
|
220
218
|
+ } catch (error) {
|
|
221
|
-
+ // A missing executable is the one failure a mounted path can answer;
|
|
222
|
-
+ //
|
|
219
|
+
+ // A missing executable is the one failure a mounted path can answer; any other
|
|
220
|
+
+ // failure is Explorer's own and must stay reported.
|
|
223
221
|
+ if (!isMissingExecutable(error)) throw error
|
|
224
222
|
+ }
|
|
225
|
-
+ for (const candidate of
|
|
223
|
+
+ for (const candidate of WINDOWS_EXPLORER_CANDIDATES) {
|
|
226
224
|
+ try {
|
|
227
|
-
+ await run(candidate,
|
|
225
|
+
+ await run(candidate, [target], signal)
|
|
228
226
|
+ return
|
|
229
227
|
+ } catch (error) {
|
|
230
228
|
+ if (!isMissingExecutable(error)) throw error
|
|
231
229
|
+ }
|
|
232
230
|
+ }
|
|
233
|
-
+ throw new Error('Windows
|
|
231
|
+
+ throw new Error('Windows Explorer is not reachable from this WSL mount')
|
|
234
232
|
+}
|
|
235
233
|
+
|
|
236
234
|
+/** Whether a spawn failed because the executable could not be found at all. */
|
|
@@ -239,7 +237,7 @@ index 8f3443b3c1..3962fd8523 100644
|
|
|
239
237
|
}
|
|
240
238
|
|
|
241
239
|
/** Translate a WSL path before handing it to the Windows desktop. */
|
|
242
|
-
@@ -
|
|
240
|
+
@@ -200,6 +247,25 @@ async function openNativePathWithIntent(
|
|
243
241
|
throw new Error(`native path opener is unsupported on ${platform}`)
|
|
244
242
|
}
|
|
245
243
|
|
|
@@ -265,20 +263,3 @@ index 8f3443b3c1..3962fd8523 100644
|
|
|
265
263
|
/**
|
|
266
264
|
* Whether {@link openNativePath} plausibly reaches a desktop on this host.
|
|
267
265
|
*
|
|
268
|
-
@@ -244,10 +298,13 @@ export async function revealNativePath(
|
|
269
|
-
windowsPath = translated.stdout.replace(/[\r\n]+$/, '')
|
|
270
|
-
if (windowsPath === '') throw new Error('wslpath returned no Windows path')
|
|
271
|
-
}
|
|
272
|
-
- // Explorer parses commas itself; a file URI preserves commas and whitespace in the path.
|
|
273
|
-
- const target = pathToFileURL(windowsPath, { windows: true }).href.replaceAll(',', '%2C')
|
|
274
|
-
+ // Explorer takes the path itself: `wslpath -w` already produced the spelling it reads.
|
|
275
|
-
+ // A `file://` URL is not interchangeable here — a percent-encoded non-ASCII path (a CJK
|
|
276
|
-
+ // directory such as 题库) fails to resolve, and Explorer then opens its default location
|
|
277
|
-
+ // instead of selecting the file. Measured with a directory it had never opened: the native
|
|
278
|
-
+ // path selected 子目录/测试文件.txt, the URL form changed nothing.
|
|
279
|
-
try {
|
|
280
|
-
- await run('explorer.exe', ['/select,', target], signal)
|
|
281
|
-
+ await run('explorer.exe', ['/select,', windowsPath], signal)
|
|
282
|
-
} catch (error) {
|
|
283
|
-
signal.throwIfAborted()
|
|
284
|
-
// Explorer can exit 1 after delegating to the existing desktop process.
|