@lenorin/dsh-tauri-launcher 1.0.10 → 1.0.11
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/launcher/bin/dsh-launcher.exe +0 -0
- package/lib/index.js +66 -1
- package/package.json +1 -1
|
Binary file
|
package/lib/index.js
CHANGED
|
@@ -27,6 +27,18 @@ export const inject = ['webServer', 'fs', 'subprocess']
|
|
|
27
27
|
|
|
28
28
|
const STDIO = { stdin: 'ignore', stdout: 'inherit', stderr: 'inherit' }
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* 拉起桌面应用时使用的 stdio(**不能用 inherit**)。
|
|
32
|
+
*
|
|
33
|
+
* DSH 自身可能是被桌面应用拉起的:它的 stdout/stderr 就是桌面应用当时创建的管道
|
|
34
|
+
* (`harness.rs` 用 `Stdio::piped()` 拉起 `dsh web`)。桌面应用退出后这根管道的读端
|
|
35
|
+
* 消失,此后以 inherit 拉起的子进程继承的是**已断开的手柄**,一写即 panic 退出
|
|
36
|
+
* (2026-09-15 实测:exit code 101,现象为「设置里开关打开但桌面端起不来」,而关闭
|
|
37
|
+
* 方向正常——关闭只写标记文件)。收集模式让管道由 DSH 这一侧持有,既避开坏句柄,
|
|
38
|
+
* 又把桌面应用启动期的输出留下来供诊断使用。
|
|
39
|
+
*/
|
|
40
|
+
const APP_STDIO = { stdin: 'ignore', stdout: { maxBytes: 16384 }, stderr: { maxBytes: 16384 } }
|
|
41
|
+
|
|
30
42
|
/** 本包随附的预编译桌面应用 exe 目录(无论安装到哪个 profile 都能定位)。 */
|
|
31
43
|
const PACKAGE_BIN = fileURLToPath(new URL('../launcher/bin', import.meta.url))
|
|
32
44
|
|
|
@@ -157,6 +169,10 @@ export function apply(ctx, config) {
|
|
|
157
169
|
let lastProbeError = ''
|
|
158
170
|
let spawnHandle = null
|
|
159
171
|
let shortcutCache = { t: 0, value: false }
|
|
172
|
+
/** 最近一次拉起桌面应用的退出事实(未退出为 null),用于把「起来即死」报出来。 */
|
|
173
|
+
let appExit = null
|
|
174
|
+
/** 该桌面应用进程启动期的输出(收集模式),仅在启动失败时用于诊断。 */
|
|
175
|
+
let appOutput = ''
|
|
160
176
|
|
|
161
177
|
/**
|
|
162
178
|
* 已在本次插件运行中发现过的候选目录(最近使用在前)。实例退出后仍保留:
|
|
@@ -191,6 +207,31 @@ export function apply(ctx, config) {
|
|
|
191
207
|
}
|
|
192
208
|
}
|
|
193
209
|
|
|
210
|
+
/** 收集模式下该句柄的输出(stdout/stderr 合并、限长),用于诊断「起来即死」。 */
|
|
211
|
+
function collectedText(handle) {
|
|
212
|
+
if (!handle || !handle.collected) return ''
|
|
213
|
+
const parts = []
|
|
214
|
+
for (const stream of ['stdout', 'stderr']) {
|
|
215
|
+
const reader = handle.collected[stream]
|
|
216
|
+
if (!reader) continue
|
|
217
|
+
try {
|
|
218
|
+
const text = String(reader.readFrom(0).text || '').trim()
|
|
219
|
+
if (text) parts.push(stream + ': ' + text)
|
|
220
|
+
} catch {
|
|
221
|
+
// 收集流不可读取时忽略:诊断信息缺一行不影响主流程。
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return parts.join('\n').slice(0, 2000)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** 退出事实的可读描述(exit code / signal / 未知原因)。 */
|
|
228
|
+
function describeExit(outcome) {
|
|
229
|
+
if (!outcome) return '未知原因'
|
|
230
|
+
if (typeof outcome.exitCode === 'number') return 'exit code ' + outcome.exitCode
|
|
231
|
+
if (outcome.signal) return 'signal ' + outcome.signal
|
|
232
|
+
return outcome.error ? String(outcome.error) : '未知原因'
|
|
233
|
+
}
|
|
234
|
+
|
|
194
235
|
/** 记住一个已发现的候选目录(去重、最近使用在前、限量)。 */
|
|
195
236
|
function rememberDir(dir) {
|
|
196
237
|
if (!dir) return
|
|
@@ -494,6 +535,8 @@ export function apply(ctx, config) {
|
|
|
494
535
|
if (lastFsError) lines.push('fsError: ' + lastFsError)
|
|
495
536
|
if (lastProbeError) lines.push('probeError: ' + lastProbeError)
|
|
496
537
|
lines.push('markerWrite: ' + (lastMarkerError || 'ok'))
|
|
538
|
+
if (appExit) lines.push('appExit: ' + describeExit(appExit))
|
|
539
|
+
if (appOutput) lines.push('appOutput: ' + appOutput)
|
|
497
540
|
for (const dir of dirs) {
|
|
498
541
|
lines.push('heartbeat(' + dir.split('\\').slice(-2).join('\\') + '): ' + String(await hbState(dir)))
|
|
499
542
|
}
|
|
@@ -513,6 +556,8 @@ export function apply(ctx, config) {
|
|
|
513
556
|
}
|
|
514
557
|
|
|
515
558
|
async function setDesktop(enabled) {
|
|
559
|
+
appExit = null
|
|
560
|
+
appOutput = ''
|
|
516
561
|
if (enabled) {
|
|
517
562
|
if (await quitMarkerIsOne()) await writeQuitMarker('0')
|
|
518
563
|
const exe = await pickExe()
|
|
@@ -526,7 +571,16 @@ export function apply(ctx, config) {
|
|
|
526
571
|
const dir = dirOf(exe)
|
|
527
572
|
// 拉起前记住该目录:即使探测缓存仍是旧的,心跳扫描也已覆盖新实例的目录。
|
|
528
573
|
rememberDir(dir)
|
|
529
|
-
|
|
574
|
+
const handle = subprocess.spawn({ argv: [exe], cwd: dir, stdio: APP_STDIO, graceMs: 3000 })
|
|
575
|
+
spawnHandle = handle
|
|
576
|
+
// 记录退出事实:桌面应用「起来即死」时(例如继承了坏句柄而 panic),
|
|
577
|
+
// 只有退出码 + 它自己的输出能说明原因,否则用户只看到开关弹回而没有解释。
|
|
578
|
+
if (handle && handle.done) {
|
|
579
|
+
handle.done.then(
|
|
580
|
+
(outcome) => { appExit = outcome || { exitCode: null, signal: null } },
|
|
581
|
+
(error) => { appExit = { exitCode: null, signal: null, error: String((error && error.message) || error) } },
|
|
582
|
+
)
|
|
583
|
+
}
|
|
530
584
|
} catch (error) {
|
|
531
585
|
return { ok: false, error: '启动桌面应用失败:' + String((error && error.message) || error), diag: await buildDiag() }
|
|
532
586
|
}
|
|
@@ -544,6 +598,17 @@ export function apply(ctx, config) {
|
|
|
544
598
|
}
|
|
545
599
|
return { ok: true, desktop: true, shortcut: true, diag: '' }
|
|
546
600
|
}
|
|
601
|
+
// 未在就绪窗口内看到新鲜心跳:若进程已退出,把退出事实与输出报出来。
|
|
602
|
+
appOutput = collectedText(spawnHandle)
|
|
603
|
+
if (appExit) {
|
|
604
|
+
return {
|
|
605
|
+
ok: false,
|
|
606
|
+
desktop: false,
|
|
607
|
+
shortcut: await shortcutExists(),
|
|
608
|
+
error: '桌面应用启动后立即退出(' + describeExit(appExit) + ')。',
|
|
609
|
+
diag: await buildDiag(),
|
|
610
|
+
}
|
|
611
|
+
}
|
|
547
612
|
if (state === null) return { ok: true, desktop: null, shortcut: await shortcutExists(), diag: '' }
|
|
548
613
|
return { ok: true, desktop: false, shortcut: await shortcutExists(), diag: '' }
|
|
549
614
|
}
|