dsh-x-core 0.1.21 → 0.1.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/assets/dsh-preview.png +0 -0
- package/assets/tray.png +0 -0
- package/core-npm.js +2 -2
- package/filemanager-base.js +7 -8
- package/filemanager-linux.js +124 -0
- package/filemanager.js +4 -7
- package/node/node +2 -1
- package/package.json +1 -1
- package/platform.js +1 -0
- package/public/index.html +3 -0
- package/server.js +4 -3
|
Binary file
|
package/assets/tray.png
ADDED
|
Binary file
|
package/core-npm.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
//
|
|
3
3
|
// 安装包只负责外壳和 Electron;核心作为一个 npm 包独立发版,启动器在运行时从 registry
|
|
4
4
|
// 取新版本,解到暂存目录后仍走 apply-update.mjs 换核心目录。
|
|
5
|
-
//
|
|
5
|
+
// 核心包跨平台(三套 filemanager 和 node 包装都在里面),Windows / mac / Linux 用同一个
|
|
6
6
|
// dsh-x-core;只有外壳/Electron 按平台分开。
|
|
7
7
|
// 完整性用 npm 的 dist.integrity(sha512)校验;registry 走设置里的下载源(官方/镜像)。
|
|
8
8
|
|
|
@@ -14,7 +14,7 @@ import { join } from 'node:path'
|
|
|
14
14
|
import { netFetch } from './proxy.js'
|
|
15
15
|
import { cmpVer, parseVer, VERSION_CHANNEL } from './version.js'
|
|
16
16
|
|
|
17
|
-
/** 核心包跨平台:Windows
|
|
17
|
+
/** 核心包跨平台:Windows / mac / Linux 共用同一个 dsh-x-core。 */
|
|
18
18
|
export const CORE_PACKAGE = String(process.env.DSH_CORE_PACKAGE || 'dsh-x-core').trim()
|
|
19
19
|
|
|
20
20
|
export const CORE_REGISTRIES = {
|
package/filemanager-base.js
CHANGED
|
@@ -15,14 +15,13 @@ import { RUNTIME_NODE } from './platform.js'
|
|
|
15
15
|
* 里,每处各写一次 `process.platform === 'win32' ? … : …`,加一个平台就得把整份仓库
|
|
16
16
|
* 重新翻一遍。现在业务代码只认 `fileManager.xxx()`,平台差异全压在实现类里。
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* 实现类都随核心包发:Windows / mac / Linux 各一套,运行时由 filemanager.js
|
|
19
|
+
* 动态 import 当前平台那一套;核心 npm 包本身是跨平台的(见 scripts/pack-common.mjs
|
|
20
|
+
* 的 FILE_MANAGERS)。
|
|
21
21
|
*
|
|
22
|
-
*
|
|
23
|
-
* 的注释里),在 `pickFileManager()` 里挂上,再把它加进
|
|
24
|
-
*
|
|
25
|
-
* 和这次重构之前的行为一致。
|
|
22
|
+
* 要加新平台:照着三个实现类写一个 `filemanager-<平台>.js`(整份契约都在下面这个基类
|
|
23
|
+
* 的注释里),在 `pickFileManager()` 里挂上,再把它加进 FILE_MANAGERS。没有专门实现的
|
|
24
|
+
* POSIX 平台落到 PosixFileManager:xdg-open 能用,系统对话框和自启直接报不支持。
|
|
26
25
|
*/
|
|
27
26
|
export class FileManager {
|
|
28
27
|
/**
|
|
@@ -184,5 +183,5 @@ export class FileManager {
|
|
|
184
183
|
ignoreOpenError() { return false }
|
|
185
184
|
}
|
|
186
185
|
|
|
187
|
-
/**
|
|
186
|
+
/** 没有专门实现的其他 POSIX 平台:xdg-open 这条路能走,其余照契约报不支持。 */
|
|
188
187
|
export class PosixFileManager extends FileManager {}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { mkdir, rm, writeFile } from 'node:fs/promises'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { PosixFileManager } from './filemanager-base.js'
|
|
4
|
+
import { APP_DIR_NAME, IN_ELECTRON } from './platform.js'
|
|
5
|
+
|
|
6
|
+
/** 对话框等用户的时间:目录 5 分钟、文件 10 分钟,和其他平台一致。 */
|
|
7
|
+
const DIRECTORY_TIMEOUT = 5 * 60 * 1000
|
|
8
|
+
const FILE_TIMEOUT = 10 * 60 * 1000
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Linux 这一套:xdg-open、zenity/kdialog 的系统对话框、XDG autostart。
|
|
12
|
+
*
|
|
13
|
+
* 目录放在 $XDG_CONFIG_HOME/DSH(默认 ~/.config/DSH),和 AppImage 的只读挂载分开。
|
|
14
|
+
* 对话框优先 zenity,其次 kdialog;两个都没装时提示用户直接输入路径。
|
|
15
|
+
* 自启写 ~/.config/autostart/dsh-x.desktop,不依赖 systemd。
|
|
16
|
+
*/
|
|
17
|
+
export class LinuxFileManager extends PosixFileManager {
|
|
18
|
+
constructor(deps = {}) {
|
|
19
|
+
super(deps)
|
|
20
|
+
this.writeFile = deps.writeFile || writeFile
|
|
21
|
+
this.mkdir = deps.mkdir || mkdir
|
|
22
|
+
this.rm = deps.rm || rm
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get platform() { return 'linux' }
|
|
26
|
+
|
|
27
|
+
get directoryOpener() { return 'xdg-open' }
|
|
28
|
+
|
|
29
|
+
/** $XDG_CONFIG_HOME/DSH(默认 ~/.config/DSH)。 */
|
|
30
|
+
userAppDir() {
|
|
31
|
+
return join(this.env.XDG_CONFIG_HOME || join(this.home, '.config'), APP_DIR_NAME)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 登录自启要执行的外壳:AppImage 用 APPIMAGE 本身(挂载目录每次启动都会变),
|
|
36
|
+
* 安装版用当前 Electron 可执行文件;源码运行返回空串。
|
|
37
|
+
*/
|
|
38
|
+
launcherExecutable() {
|
|
39
|
+
const appImage = String(this.env.APPIMAGE || '').trim()
|
|
40
|
+
if (appImage && this.exists(appImage)) return appImage
|
|
41
|
+
if (IN_ELECTRON && !process.defaultApp) return process.execPath
|
|
42
|
+
return ''
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** XDG autostart 的 .desktop 文件。 */
|
|
46
|
+
get autoStartPath() {
|
|
47
|
+
return join(this.env.XDG_CONFIG_HOME || join(this.home, '.config'), 'autostart', 'dsh-x.desktop')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
autoStartEntry() {
|
|
51
|
+
return [
|
|
52
|
+
'[Desktop Entry]',
|
|
53
|
+
'Type=Application',
|
|
54
|
+
'Name=DSH-X',
|
|
55
|
+
'Comment=DeepSeek Harness launcher',
|
|
56
|
+
`Exec=${this.launchCommand()}`,
|
|
57
|
+
'Terminal=false',
|
|
58
|
+
'X-GNOME-Autostart-enabled=true',
|
|
59
|
+
'',
|
|
60
|
+
].join('\n')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async autoStartEnabled() {
|
|
64
|
+
return this.exists(this.autoStartPath)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async setAutoStart(enabled) {
|
|
68
|
+
if (!enabled) {
|
|
69
|
+
await this.rm(this.autoStartPath, { force: true })
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
await this.mkdir(dirname(this.autoStartPath), { recursive: true })
|
|
73
|
+
await this.writeFile(this.autoStartPath, this.autoStartEntry())
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async pickDirectory(initialPath = '') {
|
|
77
|
+
const start = initialPath && this.exists(initialPath) ? initialPath : ''
|
|
78
|
+
const zenity = await this.dialog('zenity', [
|
|
79
|
+
'--file-selection', '--directory', '--title=选择目录',
|
|
80
|
+
...(start ? [`--filename=${start}${start.endsWith('/') ? '' : '/'}`] : []),
|
|
81
|
+
], DIRECTORY_TIMEOUT)
|
|
82
|
+
if (zenity !== null) return zenity
|
|
83
|
+
const kdialog = await this.dialog('kdialog', ['--getexistingdirectory', start || this.home, '--title', '选择目录'], DIRECTORY_TIMEOUT)
|
|
84
|
+
if (kdialog !== null) return kdialog
|
|
85
|
+
throw new Error('目录选择需要安装 zenity 或 kdialog;也可以直接输入路径')
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async pickFile({ save = false, defaultName = '', title = '', prompt = '' } = {}) {
|
|
89
|
+
const ask = prompt || title || (save ? '选择保存位置' : '选择文件')
|
|
90
|
+
const zenity = await this.dialog('zenity', [
|
|
91
|
+
'--file-selection', '--title', ask,
|
|
92
|
+
...(save ? ['--save', '--confirm-overwrite'] : []),
|
|
93
|
+
...(defaultName ? [`--filename=${defaultName}`] : []),
|
|
94
|
+
], FILE_TIMEOUT)
|
|
95
|
+
if (zenity !== null) return zenity
|
|
96
|
+
const kdialog = await this.dialog('kdialog', save
|
|
97
|
+
? ['--getsavefilename', defaultName || this.home, '--title', ask]
|
|
98
|
+
: ['--getopenfilename', this.home, '--title', ask], FILE_TIMEOUT)
|
|
99
|
+
if (kdialog !== null) return kdialog
|
|
100
|
+
throw new Error('文件选择需要安装 zenity 或 kdialog;也可以直接输入路径')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async revealPath(path) {
|
|
104
|
+
await this.openWith('xdg-open', [dirname(String(path))], { windowsHide: true })
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async openUrl(url) {
|
|
108
|
+
await this.openWith('xdg-open', [String(url)], {})
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 跑 zenity / kdialog:返回路径;用户取消返回空串;命令不存在返回 null(换下一个)。
|
|
113
|
+
* 两个都没装时调用方给出明确提示。
|
|
114
|
+
*/
|
|
115
|
+
async dialog(command, args, timeout) {
|
|
116
|
+
try {
|
|
117
|
+
return String(await this.exec(command, args, { timeout, encoding: 'utf8' })).trim()
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error?.code === 'ENOENT') return null
|
|
120
|
+
if (error?.code === 1) return ''
|
|
121
|
+
throw error
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
package/filemanager.js
CHANGED
|
@@ -1,23 +1,20 @@
|
|
|
1
|
-
import { IS_MAC, IS_WINDOWS } from './platform.js'
|
|
1
|
+
import { IS_LINUX, IS_MAC, IS_WINDOWS } from './platform.js'
|
|
2
2
|
import { FileManager, PosixFileManager } from './filemanager-base.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* 按平台挑一套文件管理实现,业务代码只认这里导出的 `fileManager`。
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* 三套实现都随核心包发(核心 npm 包是跨平台的,见 scripts/pack-common.mjs 的
|
|
8
8
|
* FILE_MANAGERS);运行时用动态 import 只加载当前平台那一套。这样同一个 dsh-x-core
|
|
9
|
-
* 在 Windows
|
|
9
|
+
* 在 Windows / mac / Linux 上都能用,只有外壳/Electron 按平台分开。
|
|
10
10
|
*
|
|
11
11
|
* 契约、基类和 Linux 的兜底实现都在 filemanager-base.js:实现类静态 import 的是它,
|
|
12
12
|
* 不是本文件——本文件有顶层 await,实现类再回头 import 它就会死锁。
|
|
13
|
-
*
|
|
14
|
-
* 要加 Linux:照着两个实现类写一个 `filemanager-linux.js`(整份契约在基类的注释里),
|
|
15
|
-
* 在下面挂上,再把它加进 FILE_MANAGERS。在那之前 Linux 落到 PosixFileManager:
|
|
16
|
-
* xdg-open 能用,系统对话框和自启直接报不支持——和这次重构之前的行为一致。
|
|
17
13
|
*/
|
|
18
14
|
async function pickFileManager() {
|
|
19
15
|
if (IS_WINDOWS) return (await import('./filemanager-windows.js')).WindowsFileManager
|
|
20
16
|
if (IS_MAC) return (await import('./filemanager-mac.js')).MacFileManager
|
|
17
|
+
if (IS_LINUX) return (await import('./filemanager-linux.js')).LinuxFileManager
|
|
21
18
|
return PosixFileManager
|
|
22
19
|
}
|
|
23
20
|
|
package/node/node
CHANGED
|
@@ -2,4 +2,5 @@
|
|
|
2
2
|
if [ -n "$DSH_ELECTRON_BIN" ]; then ELECTRON_RUN_AS_NODE=1 exec "$DSH_ELECTRON_BIN" "$@"; fi
|
|
3
3
|
dir="$(cd "$(dirname "$0")" && pwd)"
|
|
4
4
|
if [ -x "$dir/../../../DSH.exe" ]; then ELECTRON_RUN_AS_NODE=1 exec "$dir/../../../DSH.exe" "$@"; fi
|
|
5
|
-
ELECTRON_RUN_AS_NODE=1 exec "$dir/../../../MacOS/DSH" "$@"
|
|
5
|
+
if [ -x "$dir/../../../MacOS/DSH" ]; then ELECTRON_RUN_AS_NODE=1 exec "$dir/../../../MacOS/DSH" "$@"; fi
|
|
6
|
+
ELECTRON_RUN_AS_NODE=1 exec "$dir/../../../DSH" "$@"
|
package/package.json
CHANGED
package/platform.js
CHANGED
|
@@ -14,6 +14,7 @@ const ROOT = dirname(fileURLToPath(import.meta.url))
|
|
|
14
14
|
|
|
15
15
|
export const IS_WINDOWS = process.platform === 'win32'
|
|
16
16
|
export const IS_MAC = process.platform === 'darwin'
|
|
17
|
+
export const IS_LINUX = process.platform === 'linux'
|
|
17
18
|
|
|
18
19
|
/** 管理服务是不是跑在 Electron 主进程里(Windows/macOS 外壳都是,见 shell/main.mjs)。 */
|
|
19
20
|
export const IN_ELECTRON = Boolean(process.versions.electron)
|
package/public/index.html
CHANGED
|
@@ -4772,6 +4772,9 @@
|
|
|
4772
4772
|
if (wasLive) {
|
|
4773
4773
|
// 跟点卡片启动一样带上启动项的固定端口:端口随版本挪到新版,不然更新后这次落在随机端口上。
|
|
4774
4774
|
await startVersion(latest, targetProfile, entry ? entry.port : null, target?.launchId)
|
|
4775
|
+
// 这条分支直接 return,不在这里收尾的话首页会一直停在「正在安装」、按钮全锁死。
|
|
4776
|
+
action = ''
|
|
4777
|
+
render()
|
|
4775
4778
|
return
|
|
4776
4779
|
}
|
|
4777
4780
|
action = ''
|
package/server.js
CHANGED
|
@@ -127,7 +127,7 @@ import {
|
|
|
127
127
|
import { netFetch, proxyEnv, resetProxyCache, resolveProxy } from './proxy.js'
|
|
128
128
|
import { APP_DIR, fileManager } from './filemanager.js'
|
|
129
129
|
import { checkCoreUpdate, downloadCoreTarball, extractCoreTarball, stagingExists, stagingStats } from './core-npm.js'
|
|
130
|
-
import { IN_ELECTRON, IS_MAC, IS_WINDOWS, LAUNCHER_NAME, MAC_APP_NAME, NPM_MODE, PACKAGED, RUNTIME_NODE, RUNTIME_NODE_ENV } from './platform.js'
|
|
130
|
+
import { IN_ELECTRON, IS_LINUX, IS_MAC, IS_WINDOWS, LAUNCHER_NAME, MAC_APP_NAME, NPM_MODE, PACKAGED, RUNTIME_NODE, RUNTIME_NODE_ENV } from './platform.js'
|
|
131
131
|
|
|
132
132
|
const execFileAsync = promisify(execFile)
|
|
133
133
|
|
|
@@ -2765,7 +2765,7 @@ export async function refreshShortcuts({ installRoot, appDir = APP_DIR, version
|
|
|
2765
2765
|
*/
|
|
2766
2766
|
async function checkAppUpdate({ force = false } = {}) {
|
|
2767
2767
|
if (NPM_MODE) return { available: false, reason: 'npm' }
|
|
2768
|
-
if (!PACKAGED || (!IS_WINDOWS && !IS_MAC)) return { available: false, reason: PACKAGED ? 'platform' : 'source' }
|
|
2768
|
+
if (!PACKAGED || (!IS_WINDOWS && !IS_MAC && !IS_LINUX)) return { available: false, reason: PACKAGED ? 'platform' : 'source' }
|
|
2769
2769
|
if (!force && appUpdateCache.data && Date.now() - appUpdateCache.at < APP_UPDATE_CACHE_MS) return appUpdateCache.data
|
|
2770
2770
|
const remember = (data) => { appUpdateCache = { at: Date.now(), data }; return data }
|
|
2771
2771
|
const latest = APP_VERSION
|
|
@@ -5557,7 +5557,8 @@ async function handleApi(req, res, url) {
|
|
|
5557
5557
|
const child = spawn(RUNTIME_NODE, [helper,
|
|
5558
5558
|
'--app', ROOT,
|
|
5559
5559
|
'--staged', stagedApp.dir,
|
|
5560
|
-
|
|
5560
|
+
// AppImage 的 process.execPath 是挂载目录里的临时二进制,重启要用 APPIMAGE 本身
|
|
5561
|
+
'--exe', process.env.APPIMAGE || process.execPath,
|
|
5561
5562
|
'--pid', String(process.pid),
|
|
5562
5563
|
'--backup', `${ROOT}.bak-${APP_VERSION}`,
|
|
5563
5564
|
'--marker', join(APP_DIR, 'update-ok'),
|