@deepseek-ai/dsh-native-command 0.1.7-rc.1 → 0.1.7-rc.2
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/README.i18n.yaml +51 -5
- package/README.md +2 -1
- package/README.zh.md +2 -1
- package/lib/index.js +57 -15
- package/lib/types/file-applications-windows.d.ts +2 -2
- package/lib/types/file-applications-windows.js +55 -10
- package/package.json +1 -1
package/README.i18n.yaml
CHANGED
|
@@ -1,6 +1,52 @@
|
|
|
1
|
-
# Bilingual-pair consistency record (docs/i18n/README.md):
|
|
2
|
-
#
|
|
3
|
-
#
|
|
1
|
+
# Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
|
|
2
|
+
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
|
3
|
+
# After editing either side, bring the other along and re-record with:
|
|
4
4
|
# pnpm run verify-translation-pairing --write packages/util/native-command/README.md
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/:
|
|
6
|
+
en: 3e0acbcc586810bb
|
|
7
|
+
zh: 688d34f126d1450e
|
|
8
|
+
/deepseek-ai-dsh-native-command:
|
|
9
|
+
en: 5ebca99c0488e1c8
|
|
10
|
+
zh: 24b2f8f341ff648d
|
|
11
|
+
/deepseek-ai-dsh-native-command/summary:
|
|
12
|
+
en: 18e2f1f3798079dd
|
|
13
|
+
zh: 6f5579374e208624
|
|
14
|
+
/deepseek-ai-dsh-native-command/table-of-contents:
|
|
15
|
+
en: d152484eb41ac6b4
|
|
16
|
+
zh: 09388d293f9be9cb
|
|
17
|
+
/deepseek-ai-dsh-native-command/use-this-package:
|
|
18
|
+
en: aba695cba233905f
|
|
19
|
+
zh: c6359da30eb8e372
|
|
20
|
+
/deepseek-ai-dsh-native-command/use-this-package/running-a-command:
|
|
21
|
+
en: 3a499bda33e33102
|
|
22
|
+
zh: d01abbf863810607
|
|
23
|
+
/deepseek-ai-dsh-native-command/use-this-package/injecting-the-command-boundary:
|
|
24
|
+
en: 987bed629737b59d
|
|
25
|
+
zh: 258e30f813b0edcb
|
|
26
|
+
/deepseek-ai-dsh-native-command/use-this-package/opening-a-host-path:
|
|
27
|
+
en: b4ec677605b12ff8
|
|
28
|
+
zh: f35256957eea141a
|
|
29
|
+
/deepseek-ai-dsh-native-command/understand-the-implementation:
|
|
30
|
+
en: 78e86818bea36f9f
|
|
31
|
+
zh: 71dc1ad3e4cbe9f1
|
|
32
|
+
/deepseek-ai-dsh-native-command/understand-the-implementation/source-map:
|
|
33
|
+
en: a19bb4cb9f6de785
|
|
34
|
+
zh: cc930d8facf1ca07
|
|
35
|
+
/deepseek-ai-dsh-native-command/understand-the-implementation/what-execfile-gives-the-runner:
|
|
36
|
+
en: b16da99b2ed73bfd
|
|
37
|
+
zh: 5e806c31a6296366
|
|
38
|
+
/deepseek-ai-dsh-native-command/further-exploration:
|
|
39
|
+
en: a3af562edb6e0385
|
|
40
|
+
zh: e26e2c311c07d1c5
|
|
41
|
+
/deepseek-ai-dsh-native-command/model-experience:
|
|
42
|
+
en: d478457568b9517e
|
|
43
|
+
zh: 0460368e7567a13f
|
|
44
|
+
/deepseek-ai-dsh-native-command/model-experience/kv-cache-effect:
|
|
45
|
+
en: 296c060e95197cc5
|
|
46
|
+
zh: b9d3fdf11b5ebe1e
|
|
47
|
+
/deepseek-ai-dsh-native-command/known-limitations-and-deferred-work:
|
|
48
|
+
en: d3af15e86d679b8e
|
|
49
|
+
zh: ee5aa3c8cbd9d2db
|
|
50
|
+
/deepseek-ai-dsh-native-command/known-limitations-and-deferred-work/dev-note:
|
|
51
|
+
en: e67f633dc6fe7773
|
|
52
|
+
zh: 3bb0c20c93f16f98
|
package/README.md
CHANGED
|
@@ -49,7 +49,7 @@ The `NativeCommandRunner` type is the injectable command boundary for host integ
|
|
|
49
49
|
|
|
50
50
|
`revealNativePath(path, signal)` selects the file in Finder or Explorer, including WSL path translation, and opens its parent directory through `xdg-open` on desktop Linux. `nativeFileManager()` identifies that action for Host-derived UI labels; desktop availability remains a separate `canOpenNativePath()` check. Callers must authorize the absolute file path before invoking either operation. Platform dispatch is covered by injected-runner tests; native desktop verification belongs to the corresponding platform. Explorer receives an encoded file URI as a separate argument. Its exit code 1 is accepted as a delegated handoff; cancellation, missing executables, and other exit codes still reject. This acknowledgement does not prove that an application opened the file or that a window selected it.
|
|
51
51
|
|
|
52
|
-
`nativeFileApplications(path, signal)` returns registered applications, localized names, icons, and the current default. macOS 12 and later use LaunchServices; Windows uses Shell association handlers; Linux uses GIO with shared XDG desktop-entry and icon lookup. On macOS, copies sharing a bundle identifier and display name (staged self-updates, per-version installs) collapse to the system default, else the highest version; deliberate side-by-side installs keep distinct display names and both entries. `openNativeFileApplication(path, application, signal)` revalidates the handler against the complete registered list, so copies collapsed out of the display list stay openable, and never changes the system default. Windows delegates invocation to the Shell, and Linux delegates argument expansion to `gio launch`. WSL translates the path and uses the Windows adapter. Callers authorize the local file path. Native integration tests use private Windows file associations and Linux XDG roots on their respective platforms.
|
|
52
|
+
`nativeFileApplications(path, signal)` returns registered applications, localized names, icons, and the current default. macOS 12 and later use LaunchServices; Windows uses Shell association handlers and resolves a packaged application’s indirect icon reference before extraction; Linux uses GIO with shared XDG desktop-entry and icon lookup. On macOS, copies sharing a bundle identifier and display name (staged self-updates, per-version installs) collapse to the system default, else the highest version; deliberate side-by-side installs keep distinct display names and both entries. `openNativeFileApplication(path, application, signal)` revalidates the handler against the complete registered list, so copies collapsed out of the display list stay openable, and never changes the system default. Windows delegates invocation to the Shell, and Linux delegates argument expansion to `gio launch`. WSL translates the path and uses the Windows adapter. Callers authorize the local file path. Native integration tests use private Windows file associations and Linux XDG roots on their respective platforms.
|
|
53
53
|
|
|
54
54
|
-----
|
|
55
55
|
|
|
@@ -109,6 +109,7 @@ Linux association discovery and explicit launching require GIO. Missing native c
|
|
|
109
109
|
These limits define when this runner is not the right tool. They are current package constraints, not a task backlog.
|
|
110
110
|
|
|
111
111
|
- Command output uses Node’s `execFile` buffer limit; oversized replies reject. Use the subprocess capability for streaming output.
|
|
112
|
+
- A Windows handler icon appears only when the Shell resolves its indirect reference; an unresolved reference falls back to the handler executable, and a handler that names none — a packaged handler may report an AUMID or family name — yields no icon.
|
|
112
113
|
- A Windows host needs an interactive desktop session. In a non-interactive session — a service, or a scheduled task without an interactive logon — `explorer.exe` invokes no file association and still exits 1 after about a second (an SSH session 0 on Windows 11 ARM64 build 26200), so an open reports success having opened nothing; a caller's abort signal turns that wait into a timeout rather than a hang. An `Invoke-Item` command resolves the association in-process there, and its directory failure arrives as a non-zero exit code. No fallback for that session is implemented.
|
|
113
114
|
|
|
114
115
|
<a id="dev-note"></a>
|
package/README.zh.md
CHANGED
|
@@ -49,7 +49,7 @@ const { stdout, stderr } = await runNativeCommand('osascript', ['-e', script], s
|
|
|
49
49
|
|
|
50
50
|
`revealNativePath(path, signal)` 在 Finder 或文件资源管理器中选中文件,包含 WSL 路径转换;在桌面 Linux 上通过 `xdg-open` 打开上层目录。`nativeFileManager()` 标识该操作,供 UI 根据 Host 选择文案;桌面是否可用仍由独立的 `canOpenNativePath()` 检查决定。调用方必须先授权绝对文件路径,再执行操作。平台分派由注入运行器的测试覆盖;原生桌面验证由对应平台负责。 Explorer 接收独立参数中的编码文件 URI。退出码 1 按已转交请求处理;取消、找不到可执行文件和其他退出码仍然报错。该确认不能证明应用已打开文件,也不能证明桌面窗口已选中文件。
|
|
51
51
|
|
|
52
|
-
`nativeFileApplications(path, signal)` 返回关联应用、本地化名称、图标和当前默认项。macOS 12 及以上版本使用 LaunchServices,Windows 使用 Shell
|
|
52
|
+
`nativeFileApplications(path, signal)` 返回关联应用、本地化名称、图标和当前默认项。macOS 12 及以上版本使用 LaunchServices,Windows 使用 Shell 关联处理器并在提取前解析打包应用的间接图标引用,Linux 使用 GIO,并共用 XDG 桌面文件和图标读取逻辑。macOS 上 bundle 标识符与显示名都相同的多份拷贝(自更新暂存副本、按版本安装的拷贝)合并为系统默认项,否则保留最高版本;显示名不同的并存安装两项都保留。`openNativeFileApplication(path, application, signal)` 按完整的当前注册列表重新验证关联应用,展示列表中被合并掉的拷贝仍可打开,且不修改系统默认值。Windows 交给 Shell 启动应用,Linux 交给 `gio launch` 展开参数。WSL 转换路径后使用 Windows 适配器。调用方负责验证本地文件路径。原生集成测试分别在相应平台使用独立的 Windows 文件关联和 Linux XDG 目录。
|
|
53
53
|
|
|
54
54
|
-----
|
|
55
55
|
|
|
@@ -109,6 +109,7 @@ Linux 的关联查询和指定应用启动需要 GIO。缺少原生命令时查
|
|
|
109
109
|
这些限制说明本运行器何时不是合适的工具。它们是当前包约束,不是任务积压。
|
|
110
110
|
|
|
111
111
|
- 命令输出受 Node 的 `execFile` 缓冲上限约束,超过上限时拒绝结果。流式输出由 subprocess 能力处理。
|
|
112
|
+
- Windows 关联处理器的图标只在 Shell 能解析其间接引用时显示;解析失败时回退到处理器的可执行文件,处理器没有给出可执行文件(打包处理器可能只报 AUMID 或家族名)时则没有图标。
|
|
112
113
|
- Windows 主机需要交互桌面会话。在非交互会话(服务,或没有交互登录的计划任务)中,`explorer.exe` 不调用任何文件关联,且仍在约 1 秒后以退出码 1 返回(Windows 11 ARM64 build 26200 的 SSH session 0 实测),于是「打开」会报告成功却什么都没打开;调用方的 abort 信号把这段等待变成超时而不是挂死。同一目标改用 `Invoke-Item` 命令时可在该会话内解析关联,目录失败也会以非零退出码报出。该会话下的兜底尚未实现。
|
|
113
114
|
|
|
114
115
|
<a id="dev-note"></a>
|
package/lib/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { execFile } from "node:child_process";
|
|
2
|
-
import { homedir, release } from "node:os";
|
|
2
|
+
import { homedir, release, tmpdir } from "node:os";
|
|
3
3
|
import { dirname, extname, isAbsolute, join, relative, sep } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { readFile, readdir, stat } from "node:fs/promises";
|
|
5
|
+
import { mkdtemp, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
|
|
6
6
|
//#region lib/types/runner.js
|
|
7
7
|
/**
|
|
8
8
|
* Shared no-shell `execFile` runner for host-native OS integrations.
|
|
@@ -431,6 +431,7 @@ async function linuxFileApplications(path, signal, run, env) {
|
|
|
431
431
|
}
|
|
432
432
|
//#endregion
|
|
433
433
|
//#region lib/types/file-applications-windows.js
|
|
434
|
+
/** Windows Shell association queries and invocation; paths are encoded data, never PowerShell expressions. */
|
|
434
435
|
/** Shell interfaces are declared in their native vtable order; Invoke preserves packaged-app and DDE handling. */
|
|
435
436
|
const WINDOWS_ASSOCIATIONS = String.raw`
|
|
436
437
|
using System;
|
|
@@ -471,6 +472,8 @@ public static class DshFileAssociations {
|
|
|
471
472
|
static extern int SHDefExtractIcon(string path, int index, uint flags, out IntPtr large, out IntPtr small, uint size);
|
|
472
473
|
[DllImport("user32.dll")]
|
|
473
474
|
static extern bool DestroyIcon(IntPtr icon);
|
|
475
|
+
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
|
476
|
+
static extern int SHLoadIndirectString(string source, StringBuilder output, uint size, IntPtr reserved);
|
|
474
477
|
|
|
475
478
|
[DllImport("shlwapi.dll", PreserveSig = false)]
|
|
476
479
|
static extern void SHCreateThreadRef(IntPtr count, out IntPtr reference);
|
|
@@ -548,12 +551,18 @@ public static class DshFileAssociations {
|
|
|
548
551
|
var text = new StringBuilder((int)size);
|
|
549
552
|
return AssocQueryString(0, kind, extension, null, text, ref size) == 0 ? text.ToString() : null;
|
|
550
553
|
}
|
|
551
|
-
|
|
554
|
+
// Resolve a packaged application's indirect icon reference, for example
|
|
555
|
+
// @{Microsoft.WindowsNotepad_...?ms-resource://.../NotepadAppList.png}, to
|
|
556
|
+
// the resource file the Shell would draw; an unresolved reference is null.
|
|
557
|
+
static string ResolveIndirectIcon(string source) {
|
|
558
|
+
var output = new StringBuilder(1024);
|
|
559
|
+
return SHLoadIndirectString(source, output, (uint)output.Capacity, IntPtr.Zero) == 0 ? output.ToString() : null;
|
|
560
|
+
}
|
|
561
|
+
// Read one icon source into a 32px PNG: an image file directly, otherwise the
|
|
562
|
+
// resource the Shell extracts at the given index. A missing resource is null.
|
|
563
|
+
static string PngFromIconSource(string source, int index) {
|
|
552
564
|
IntPtr large = IntPtr.Zero, small = IntPtr.Zero;
|
|
553
565
|
try {
|
|
554
|
-
string source; int index;
|
|
555
|
-
handler.GetIconLocation(out source, out index);
|
|
556
|
-
source = Environment.ExpandEnvironmentVariables(source);
|
|
557
566
|
if (Path.GetExtension(source).Equals(".png", StringComparison.OrdinalIgnoreCase)) {
|
|
558
567
|
using (var original = Image.FromFile(source))
|
|
559
568
|
using (var resized = new Bitmap(original, new Size(32, 32)))
|
|
@@ -577,6 +586,26 @@ public static class DshFileAssociations {
|
|
|
577
586
|
if (small != IntPtr.Zero) DestroyIcon(small);
|
|
578
587
|
}
|
|
579
588
|
}
|
|
589
|
+
// Best-effort fallback: some handlers return their executable path from GetName, but a
|
|
590
|
+
// packaged handler's name may be an AUMID or family name, which extracts no icon.
|
|
591
|
+
static string IconData(IHandler handler, string id) {
|
|
592
|
+
string source = null; int index = 0;
|
|
593
|
+
try { handler.GetIconLocation(out source, out index); } catch (Exception) {
|
|
594
|
+
// A handler without a drawable icon location falls back to its name.
|
|
595
|
+
}
|
|
596
|
+
if (!String.IsNullOrEmpty(source)) {
|
|
597
|
+
source = Environment.ExpandEnvironmentVariables(source);
|
|
598
|
+
if (source.StartsWith("@{", StringComparison.Ordinal) && source.EndsWith("}", StringComparison.Ordinal)) {
|
|
599
|
+
var resolved = ResolveIndirectIcon(source);
|
|
600
|
+
if (!String.IsNullOrEmpty(resolved)) source = resolved;
|
|
601
|
+
}
|
|
602
|
+
var icon = PngFromIconSource(source, index);
|
|
603
|
+
if (icon != null) return icon;
|
|
604
|
+
}
|
|
605
|
+
return String.IsNullOrEmpty(id) || String.Equals(id, source, StringComparison.OrdinalIgnoreCase)
|
|
606
|
+
? null
|
|
607
|
+
: PngFromIconSource(id, 0);
|
|
608
|
+
}
|
|
580
609
|
static void Visit(string path, Action<IHandler> visit) {
|
|
581
610
|
var extension = Path.GetExtension(path);
|
|
582
611
|
if (extension.Length == 0) return;
|
|
@@ -602,7 +631,7 @@ public static class DshFileAssociations {
|
|
|
602
631
|
string id, name;
|
|
603
632
|
handler.GetName(out id); handler.GetUIName(out name);
|
|
604
633
|
if (!seen.Add(id)) return;
|
|
605
|
-
apps.Add(new Application { id = id, name = name, icon = IconData(handler),
|
|
634
|
+
apps.Add(new Application { id = id, name = name, icon = IconData(handler, id),
|
|
606
635
|
@default = String.Equals(id, executable, StringComparison.OrdinalIgnoreCase) || String.Equals(id, appId, StringComparison.OrdinalIgnoreCase) });
|
|
607
636
|
});
|
|
608
637
|
return apps.ToArray();
|
|
@@ -634,7 +663,8 @@ public static class DshFileAssociations {
|
|
|
634
663
|
}
|
|
635
664
|
}`;
|
|
636
665
|
/**
|
|
637
|
-
* Execute the Windows Shell adapter in a Unicode STA PowerShell process
|
|
666
|
+
* Execute the Windows Shell adapter in a Unicode STA PowerShell process; the adapter
|
|
667
|
+
* source is a private temporary script file, removed once the call settles.
|
|
638
668
|
* @param path - Windows file path, translated by the caller for WSL.
|
|
639
669
|
* @param application - registered handler to invoke; null requests the application list.
|
|
640
670
|
* @param signal - caller cancellation.
|
|
@@ -651,13 +681,25 @@ $path = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${Buffer.fr
|
|
|
651
681
|
$application = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${Buffer.from(application ?? "").toString("base64")}'))
|
|
652
682
|
${application === null ? "ConvertTo-Json -InputObject @([DshFileAssociations]::List($path)) -Depth 4 -Compress" : "[DshFileAssociations]::Open($path, $application)"}
|
|
653
683
|
`;
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
"
|
|
658
|
-
"
|
|
659
|
-
|
|
660
|
-
|
|
684
|
+
const directory = await mkdtemp(join(tmpdir(), "dsh-native-command-"));
|
|
685
|
+
const scriptPath = join(directory, "associations.ps1");
|
|
686
|
+
try {
|
|
687
|
+
await writeFile(scriptPath, `\uFEFF${script}`, "utf8");
|
|
688
|
+
return (await run("powershell.exe", [
|
|
689
|
+
"-NoProfile",
|
|
690
|
+
"-NonInteractive",
|
|
691
|
+
"-STA",
|
|
692
|
+
"-ExecutionPolicy",
|
|
693
|
+
"Bypass",
|
|
694
|
+
"-File",
|
|
695
|
+
scriptPath
|
|
696
|
+
], signal)).stdout;
|
|
697
|
+
} finally {
|
|
698
|
+
await rm(directory, {
|
|
699
|
+
recursive: true,
|
|
700
|
+
force: true
|
|
701
|
+
});
|
|
702
|
+
}
|
|
661
703
|
}
|
|
662
704
|
//#endregion
|
|
663
705
|
//#region lib/types/types.js
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
/** Windows Shell association queries and invocation; paths are encoded data, never PowerShell expressions. */
|
|
2
1
|
import type { NativeCommandRunner } from './runner.ts';
|
|
3
2
|
/**
|
|
4
|
-
* Execute the Windows Shell adapter in a Unicode STA PowerShell process
|
|
3
|
+
* Execute the Windows Shell adapter in a Unicode STA PowerShell process; the adapter
|
|
4
|
+
* source is a private temporary script file, removed once the call settles.
|
|
5
5
|
* @param path - Windows file path, translated by the caller for WSL.
|
|
6
6
|
* @param application - registered handler to invoke; null requests the application list.
|
|
7
7
|
* @param signal - caller cancellation.
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/** Windows Shell association queries and invocation; paths are encoded data, never PowerShell expressions. */
|
|
2
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
1
5
|
/** Shell interfaces are declared in their native vtable order; Invoke preserves packaged-app and DDE handling. */
|
|
2
6
|
const WINDOWS_ASSOCIATIONS = String.raw `
|
|
3
7
|
using System;
|
|
@@ -38,6 +42,8 @@ public static class DshFileAssociations {
|
|
|
38
42
|
static extern int SHDefExtractIcon(string path, int index, uint flags, out IntPtr large, out IntPtr small, uint size);
|
|
39
43
|
[DllImport("user32.dll")]
|
|
40
44
|
static extern bool DestroyIcon(IntPtr icon);
|
|
45
|
+
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
|
|
46
|
+
static extern int SHLoadIndirectString(string source, StringBuilder output, uint size, IntPtr reserved);
|
|
41
47
|
|
|
42
48
|
[DllImport("shlwapi.dll", PreserveSig = false)]
|
|
43
49
|
static extern void SHCreateThreadRef(IntPtr count, out IntPtr reference);
|
|
@@ -115,12 +121,18 @@ public static class DshFileAssociations {
|
|
|
115
121
|
var text = new StringBuilder((int)size);
|
|
116
122
|
return AssocQueryString(0, kind, extension, null, text, ref size) == 0 ? text.ToString() : null;
|
|
117
123
|
}
|
|
118
|
-
|
|
124
|
+
// Resolve a packaged application's indirect icon reference, for example
|
|
125
|
+
// @{Microsoft.WindowsNotepad_...?ms-resource://.../NotepadAppList.png}, to
|
|
126
|
+
// the resource file the Shell would draw; an unresolved reference is null.
|
|
127
|
+
static string ResolveIndirectIcon(string source) {
|
|
128
|
+
var output = new StringBuilder(1024);
|
|
129
|
+
return SHLoadIndirectString(source, output, (uint)output.Capacity, IntPtr.Zero) == 0 ? output.ToString() : null;
|
|
130
|
+
}
|
|
131
|
+
// Read one icon source into a 32px PNG: an image file directly, otherwise the
|
|
132
|
+
// resource the Shell extracts at the given index. A missing resource is null.
|
|
133
|
+
static string PngFromIconSource(string source, int index) {
|
|
119
134
|
IntPtr large = IntPtr.Zero, small = IntPtr.Zero;
|
|
120
135
|
try {
|
|
121
|
-
string source; int index;
|
|
122
|
-
handler.GetIconLocation(out source, out index);
|
|
123
|
-
source = Environment.ExpandEnvironmentVariables(source);
|
|
124
136
|
if (Path.GetExtension(source).Equals(".png", StringComparison.OrdinalIgnoreCase)) {
|
|
125
137
|
using (var original = Image.FromFile(source))
|
|
126
138
|
using (var resized = new Bitmap(original, new Size(32, 32)))
|
|
@@ -144,6 +156,26 @@ public static class DshFileAssociations {
|
|
|
144
156
|
if (small != IntPtr.Zero) DestroyIcon(small);
|
|
145
157
|
}
|
|
146
158
|
}
|
|
159
|
+
// Best-effort fallback: some handlers return their executable path from GetName, but a
|
|
160
|
+
// packaged handler's name may be an AUMID or family name, which extracts no icon.
|
|
161
|
+
static string IconData(IHandler handler, string id) {
|
|
162
|
+
string source = null; int index = 0;
|
|
163
|
+
try { handler.GetIconLocation(out source, out index); } catch (Exception) {
|
|
164
|
+
// A handler without a drawable icon location falls back to its name.
|
|
165
|
+
}
|
|
166
|
+
if (!String.IsNullOrEmpty(source)) {
|
|
167
|
+
source = Environment.ExpandEnvironmentVariables(source);
|
|
168
|
+
if (source.StartsWith("@{", StringComparison.Ordinal) && source.EndsWith("}", StringComparison.Ordinal)) {
|
|
169
|
+
var resolved = ResolveIndirectIcon(source);
|
|
170
|
+
if (!String.IsNullOrEmpty(resolved)) source = resolved;
|
|
171
|
+
}
|
|
172
|
+
var icon = PngFromIconSource(source, index);
|
|
173
|
+
if (icon != null) return icon;
|
|
174
|
+
}
|
|
175
|
+
return String.IsNullOrEmpty(id) || String.Equals(id, source, StringComparison.OrdinalIgnoreCase)
|
|
176
|
+
? null
|
|
177
|
+
: PngFromIconSource(id, 0);
|
|
178
|
+
}
|
|
147
179
|
static void Visit(string path, Action<IHandler> visit) {
|
|
148
180
|
var extension = Path.GetExtension(path);
|
|
149
181
|
if (extension.Length == 0) return;
|
|
@@ -169,7 +201,7 @@ public static class DshFileAssociations {
|
|
|
169
201
|
string id, name;
|
|
170
202
|
handler.GetName(out id); handler.GetUIName(out name);
|
|
171
203
|
if (!seen.Add(id)) return;
|
|
172
|
-
apps.Add(new Application { id = id, name = name, icon = IconData(handler),
|
|
204
|
+
apps.Add(new Application { id = id, name = name, icon = IconData(handler, id),
|
|
173
205
|
@default = String.Equals(id, executable, StringComparison.OrdinalIgnoreCase) || String.Equals(id, appId, StringComparison.OrdinalIgnoreCase) });
|
|
174
206
|
});
|
|
175
207
|
return apps.ToArray();
|
|
@@ -201,7 +233,8 @@ public static class DshFileAssociations {
|
|
|
201
233
|
}
|
|
202
234
|
}`;
|
|
203
235
|
/**
|
|
204
|
-
* Execute the Windows Shell adapter in a Unicode STA PowerShell process
|
|
236
|
+
* Execute the Windows Shell adapter in a Unicode STA PowerShell process; the adapter
|
|
237
|
+
* source is a private temporary script file, removed once the call settles.
|
|
205
238
|
* @param path - Windows file path, translated by the caller for WSL.
|
|
206
239
|
* @param application - registered handler to invoke; null requests the application list.
|
|
207
240
|
* @param signal - caller cancellation.
|
|
@@ -220,9 +253,21 @@ $path = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedPa
|
|
|
220
253
|
$application = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${encodedApplication}'))
|
|
221
254
|
${application === null ? 'ConvertTo-Json -InputObject @([DshFileAssociations]::List($path)) -Depth 4 -Compress' : '[DshFileAssociations]::Open($path, $application)'}
|
|
222
255
|
`;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
256
|
+
// The script travels as a file, not as command-line data: -EncodedCommand grew with the
|
|
257
|
+
// embedded C# and overflowed the 32767-character CreateProcess limit for long paths.
|
|
258
|
+
// PowerShell 5.1 decodes a -File script as ANSI unless it carries a BOM; the BOM keeps
|
|
259
|
+
// the embedded UTF-8 source intact.
|
|
260
|
+
const directory = await mkdtemp(join(tmpdir(), 'dsh-native-command-'));
|
|
261
|
+
const scriptPath = join(directory, 'associations.ps1');
|
|
262
|
+
try {
|
|
263
|
+
await writeFile(scriptPath, `\uFEFF${script}`, 'utf8');
|
|
264
|
+
const result = await run('powershell.exe', [
|
|
265
|
+
'-NoProfile', '-NonInteractive', '-STA', '-ExecutionPolicy', 'Bypass', '-File', scriptPath,
|
|
266
|
+
], signal);
|
|
267
|
+
return result.stdout;
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
await rm(directory, { recursive: true, force: true });
|
|
271
|
+
}
|
|
227
272
|
}
|
|
228
273
|
//# sourceMappingURL=file-applications-windows.js.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepseek-ai/dsh-native-command",
|
|
3
3
|
"description": "Host-native command and path-opening utilities with shell-free execution, cancellation, desktop detection, and WSL handoff",
|
|
4
|
-
"version": "0.1.7-rc.
|
|
4
|
+
"version": "0.1.7-rc.2",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
7
7
|
},
|