feat: 支持离线运行时(runtime 目录)+ 动态生成自启 vbs

- FindNodeExe/FindDshBinJs/FindNpmCli:标准安装或应用旁 runtime 目录均可
- WriteServiceVbs 按当前环境动态生成(跨设备路径正确),启动时刷新
- .gitignore 增加 EBWebView/(WebView2 用户数据,避免误提交缓存)
This commit is contained in:
Reasonix committed 2026-08-14 16:01:57 +08:00
1 parent deba852624
commit 0b868418cb
4 files changed
+115 -19

No files matched your search

+3
View File
@@ -6,3 +6,6 @@ start-error.log
# 临时文件 # 临时文件
*.tmp *.tmp
*.log.* *.log.*
# WebView2 用户数据(仓库根下的缓存目录)
EBWebView/
BIN
View File
Binary file not shown.
+111 -17
View File
@@ -21,8 +21,9 @@ static class Program
private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run"; private const string RunKeyPath = @"Software\Microsoft\Windows\CurrentVersion\Run";
private const string RunValueName = "DeepSeekHarnessService"; private const string RunValueName = "DeepSeekHarnessService";
private const string NodeExe = @"C:\Program Files\nodejs\node.exe"; // 标准安装路径(联网安装的 Node 默认位置);离线包使用应用旁的 runtime 目录
private const string NpmCli = @"C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js"; private const string StdNodeExe = @"C:\Program Files\nodejs\node.exe";
private const string StdNpmCli = @"C:\Program Files\nodejs\node_modules\npm\bin\npm-cli.js";
private static Process serviceProcess; // 本应用拉起的 dsh 服务进程 private static Process serviceProcess; // 本应用拉起的 dsh 服务进程
private static bool startedByUs; // 服务是否由本应用启动 private static bool startedByUs; // 服务是否由本应用启动
@@ -175,6 +176,9 @@ static class Program
// 后台检查 DSH 版本更新(有新版才提示,不阻塞启动) // 后台检查 DSH 版本更新(有新版才提示,不阻塞启动)
CheckUpdateAsync(); CheckUpdateAsync();
// 刷新开机自启启动器(路径随当前环境,跨设备可用)
try { WriteServiceVbs(Path.Combine(AppDir(), "start-dsh-service.vbs")); } catch { }
Application.Run(mainForm); Application.Run(mainForm);
} }
@@ -183,6 +187,62 @@ static class Program
return Path.GetDirectoryName(Application.ExecutablePath); return Path.GetDirectoryName(Application.ExecutablePath);
} }
// 应用根目录(AppDir 的上一级,离线包布局 <root>/app、<root>/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) 离线包布局 <root>/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) static bool IsPortOpen(int port)
{ {
try try
@@ -201,14 +261,18 @@ static class Program
{ {
string appDir = AppDir(); string appDir = AppDir();
string logFile = Path.Combine(appDir, "web.log"); string logFile = Path.Combine(appDir, "web.log");
string npmDir = Path.Combine( string nodeExe = FindNodeExe();
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "npm"); string binJs = FindDshBinJs();
string binJs = Path.Combine(npmDir, "node_modules", "@deepseek-ai", "dsh", "lib", "bin.js");
try 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, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,
@@ -220,10 +284,10 @@ static class Program
// 确保 node 与 npm 命令可用(dsh 插件可能用到) // 确保 node 与 npm 命令可用(dsh 插件可能用到)
string path = psi.EnvironmentVariables["PATH"] ?? ""; string path = psi.EnvironmentVariables["PATH"] ?? "";
if (path.IndexOf("nodejs", StringComparison.OrdinalIgnoreCase) < 0) string nodeDir = Path.GetDirectoryName(nodeExe);
path = @"C:\Program Files\nodejs;" + path; if (nodeDir != null && path.IndexOf("nodejs", StringComparison.OrdinalIgnoreCase) < 0
if (path.IndexOf("npm", StringComparison.OrdinalIgnoreCase) < 0) && path.IndexOf(nodeDir, StringComparison.OrdinalIgnoreCase) < 0)
path = npmDir + ";" + path; path = nodeDir + ";" + path;
psi.EnvironmentVariables["PATH"] = path; psi.EnvironmentVariables["PATH"] = path;
// 禁用 dsh 启动遥测(可能小幅加速冷启动) // 禁用 dsh 启动遥测(可能小幅加速冷启动)
@@ -310,6 +374,7 @@ static class Program
if (enable) if (enable)
{ {
string vbs = Path.Combine(AppDir(), "start-dsh-service.vbs"); string vbs = Path.Combine(AppDir(), "start-dsh-service.vbs");
WriteServiceVbs(vbs);
key.SetValue(RunValueName, "wscript.exe \"" + vbs + "\""); key.SetValue(RunValueName, "wscript.exe \"" + vbs + "\"");
} }
else else
@@ -321,6 +386,25 @@ static class Program
catch { } 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 版本检查与升级引导(应用内完成,无需浏览器/终端)---- // ---- DSH 版本检查与升级引导(应用内完成,无需浏览器/终端)----
static void CheckUpdateAsync() static void CheckUpdateAsync()
@@ -368,10 +452,13 @@ static class Program
static string GetLocalVersion() static string GetLocalVersion()
{ {
try try
{
string binJs = FindDshBinJs();
if (binJs != null)
{ {
string pkg = Path.Combine( string pkg = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), Path.GetDirectoryName(Path.GetDirectoryName(Path.GetDirectoryName(binJs))),
"npm", "node_modules", "@deepseek-ai", "dsh", "package.json"); "package.json");
if (File.Exists(pkg)) if (File.Exists(pkg))
{ {
var doc = new System.Text.RegularExpressions.Regex("\"version\"\\s*:\\s*\"([^\"]+)\""); var doc = new System.Text.RegularExpressions.Regex("\"version\"\\s*:\\s*\"([^\"]+)\"");
@@ -379,6 +466,7 @@ static class Program
if (m.Success) return m.Groups[1].Value; if (m.Success) return m.Groups[1].Value;
} }
} }
}
catch { } catch { }
return ""; return "";
} }
@@ -387,14 +475,17 @@ static class Program
{ {
try try
{ {
string nodeExe = FindNodeExe();
string cli = FindNpmCli();
if (nodeExe == null || cli == null) return "";
string stdout = ""; string stdout = "";
var psi = new ProcessStartInfo(NodeExe) var psi = new ProcessStartInfo(nodeExe)
{ {
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = true, RedirectStandardError = true,
Arguments = "\"" + NpmCli + "\" view @deepseek-ai/dsh version", Arguments = "\"" + cli + "\" view @deepseek-ai/dsh version",
}; };
using (var p = Process.Start(psi)) using (var p = Process.Start(psi))
{ {
@@ -413,14 +504,17 @@ static class Program
string logFile = Path.Combine(AppDir(), "upgrade.log"); string logFile = Path.Combine(AppDir(), "upgrade.log");
try 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, UseShellExecute = false,
CreateNoWindow = true, CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden, WindowStyle = ProcessWindowStyle.Hidden,
RedirectStandardOutput = true, RedirectStandardOutput = true,
RedirectStandardError = 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)) using (var p = Process.Start(psi))
{ {
+1 -2
View File
@@ -1,4 +1,3 @@
' Start dsh (DeepSeek Harness) service in background, no window. ' Start dsh service (hidden, auto-generated)
' Used by "autostart" tray option (registry Run entry via wscript).
Set ws = CreateObject("WScript.Shell") 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 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