From 0b868418cb173206ac8c566ce1aedba1c3e82db9 Mon Sep 17 00:00:00 2001 From: Reasonix Date: Fri, 14 Aug 2026 16:01:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E7=A6=BB=E7=BA=BF?= =?UTF-8?q?=E8=BF=90=E8=A1=8C=E6=97=B6=EF=BC=88runtime=20=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=EF=BC=89+=20=E5=8A=A8=E6=80=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E8=87=AA=E5=90=AF=20vbs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FindNodeExe/FindDshBinJs/FindNpmCli:标准安装或应用旁 runtime 目录均可 - WriteServiceVbs 按当前环境动态生成(跨设备路径正确),启动时刷新 - .gitignore 增加 EBWebView/(WebView2 用户数据,避免误提交缓存) --- .gitignore | 3 + app/dsh-app.exe | Bin 59904 -> 61440 bytes app/src/dsh-app.cs | 138 ++++++++++++++++++++++++++++++++------ app/start-dsh-service.vbs | 3 +- 4 files changed, 120 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 042cb69..c7591ed 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ start-error.log # 临时文件 *.tmp *.log.* + +# WebView2 用户数据(仓库根下的缓存目录) +EBWebView/ diff --git a/app/dsh-app.exe b/app/dsh-app.exe index f611ab8b92bb700827168072c741db143f9ef0b0..35c42c851d82dbc84d35a87316d8169283da60ca 100644 GIT binary patch delta 7775 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/app、/runtime) + static string AppRoot() + { + string d = AppDir(); + return d != null ? Path.GetDirectoryName(d) : null; + } + + // ---- 运行时查找(标准安装 或 离线包 runtime 目录)---- + + static string FindNodeExe() + { + if (File.Exists(StdNodeExe)) return StdNodeExe; + string root = AppRoot(); + if (root != null) + { + string local = Path.Combine(root, "runtime", "node", "node.exe"); + if (File.Exists(local)) return local; + } + return null; + } + + static string GetNodeDir() + { + string exe = FindNodeExe(); + return exe != null ? Path.GetDirectoryName(exe) : null; + } + + static string FindNpmCli() + { + string nodeDir = GetNodeDir(); + if (nodeDir != null) + { + string cli = Path.Combine(nodeDir, "node_modules", "npm", "bin", "npm-cli.js"); + if (File.Exists(cli)) return cli; + } + if (File.Exists(StdNpmCli)) return StdNpmCli; + return null; + } + + static string FindDshBinJs() + { + // 1) npm 全局(联网安装 / 离线复制到 APPDATA 的情况) + string npmDir = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm"); + string p1 = Path.Combine(npmDir, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"); + if (File.Exists(p1)) return p1; + // 2) 离线包布局 /runtime/dsh/node_modules + string root = AppRoot(); + if (root != null) + { + string p2 = Path.Combine(root, "runtime", "dsh", "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"); + if (File.Exists(p2)) return p2; + } + return null; + } + static bool IsPortOpen(int port) { try @@ -201,14 +261,18 @@ static class Program { string appDir = AppDir(); string logFile = Path.Combine(appDir, "web.log"); - string npmDir = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm"); - string binJs = Path.Combine(npmDir, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js"); + string nodeExe = FindNodeExe(); + string binJs = FindDshBinJs(); try { - if (!File.Exists(NodeExe) || !File.Exists(binJs)) return; + if (nodeExe == null || binJs == null) + { + try { File.WriteAllText(Path.Combine(AppDir(), "start-error.log"), + "Node 或 dsh 未找到。nodeExe=" + (nodeExe ?? "null") + " binJs=" + (binJs ?? "null")); } catch { } + return; + } - var psi = new ProcessStartInfo(NodeExe) + var psi = new ProcessStartInfo(nodeExe) { UseShellExecute = false, CreateNoWindow = true, @@ -220,10 +284,10 @@ static class Program // 确保 node 与 npm 命令可用(dsh 插件可能用到) string path = psi.EnvironmentVariables["PATH"] ?? ""; - if (path.IndexOf("nodejs", StringComparison.OrdinalIgnoreCase) < 0) - path = @"C:\Program Files\nodejs;" + path; - if (path.IndexOf("npm", StringComparison.OrdinalIgnoreCase) < 0) - path = npmDir + ";" + path; + string nodeDir = Path.GetDirectoryName(nodeExe); + if (nodeDir != null && path.IndexOf("nodejs", StringComparison.OrdinalIgnoreCase) < 0 + && path.IndexOf(nodeDir, StringComparison.OrdinalIgnoreCase) < 0) + path = nodeDir + ";" + path; psi.EnvironmentVariables["PATH"] = path; // 禁用 dsh 启动遥测(可能小幅加速冷启动) @@ -310,6 +374,7 @@ static class Program if (enable) { string vbs = Path.Combine(AppDir(), "start-dsh-service.vbs"); + WriteServiceVbs(vbs); key.SetValue(RunValueName, "wscript.exe \"" + vbs + "\""); } else @@ -321,6 +386,25 @@ static class Program catch { } } + // 按当前环境动态生成隐藏启动器 vbs(路径随用户/安装位置变化) + static void WriteServiceVbs(string path) + { + try + { + string nodeExe = FindNodeExe(); + string binJs = FindDshBinJs(); + if (nodeExe == null || binJs == null) return; + string logFile = Path.Combine(AppDir(), "web.log"); + // cmd /c ""node" "bin.js" web > "log" 2>&1" (CreateProcess 剥一层引号后 cmd 仍收到带引号命令) + string inner = "cmd /c \"\"" + nodeExe + "\" \"" + binJs + "\" web > \"" + logFile + "\" 2>&1\""; + string vbs = "' Start dsh service (hidden, auto-generated)\r\n" + + "Set ws = CreateObject(\"WScript.Shell\")\r\n" + + "ws.Run \"" + inner.Replace("\"", "\"\"") + "\", 0, False\r\n"; + File.WriteAllText(path, vbs); + } + catch { } + } + // ---- DSH 版本检查与升级引导(应用内完成,无需浏览器/终端)---- static void CheckUpdateAsync() @@ -369,14 +453,18 @@ static class Program { try { - string pkg = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - "npm", "node_modules", "@deepseek-ai", "dsh", "package.json"); - if (File.Exists(pkg)) + string binJs = FindDshBinJs(); + if (binJs != null) { - var doc = new System.Text.RegularExpressions.Regex("\"version\"\\s*:\\s*\"([^\"]+)\""); - var m = doc.Match(File.ReadAllText(pkg)); - if (m.Success) return m.Groups[1].Value; + string pkg = Path.Combine( + Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(binJs))), + "package.json"); + if (File.Exists(pkg)) + { + var doc = new System.Text.RegularExpressions.Regex("\"version\"\\s*:\\s*\"([^\"]+)\""); + var m = doc.Match(File.ReadAllText(pkg)); + if (m.Success) return m.Groups[1].Value; + } } } catch { } @@ -387,14 +475,17 @@ static class Program { try { + string nodeExe = FindNodeExe(); + string cli = FindNpmCli(); + if (nodeExe == null || cli == null) return ""; string stdout = ""; - var psi = new ProcessStartInfo(NodeExe) + var psi = new ProcessStartInfo(nodeExe) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardOutput = true, RedirectStandardError = true, - Arguments = "\"" + NpmCli + "\" view @deepseek-ai/dsh version", + Arguments = "\"" + cli + "\" view @deepseek-ai/dsh version", }; using (var p = Process.Start(psi)) { @@ -413,14 +504,17 @@ static class Program string logFile = Path.Combine(AppDir(), "upgrade.log"); try { - var psi = new ProcessStartInfo(NodeExe) + string nodeExe = FindNodeExe(); + string cli = FindNpmCli(); + if (nodeExe == null || cli == null) return false; + var psi = new ProcessStartInfo(nodeExe) { UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden, RedirectStandardOutput = true, RedirectStandardError = true, - Arguments = "\"" + NpmCli + "\" install -g @deepseek-ai/dsh@latest", + Arguments = "\"" + cli + "\" install -g @deepseek-ai/dsh@latest", }; using (var p = Process.Start(psi)) { diff --git a/app/start-dsh-service.vbs b/app/start-dsh-service.vbs index d765672..db8847b 100644 --- a/app/start-dsh-service.vbs +++ b/app/start-dsh-service.vbs @@ -1,4 +1,3 @@ -' Start dsh (DeepSeek Harness) service in background, no window. -' Used by "autostart" tray option (registry Run entry via wscript). +' Start dsh service (hidden, auto-generated) Set ws = CreateObject("WScript.Shell") ws.Run "cmd /c """"C:\Program Files\nodejs\node.exe"" ""C:\Users\VanuSystems-Mu\AppData\Roaming\npm\node_modules\@deepseek-ai\dsh\lib\bin.js"" web > ""C:\Users\VanuSystems-Mu\AppData\Local\DeepSeekHarness\app\web.log"" 2>&1""", 0, False