@linxin666/dsh-remote-web-ui 0.1.12 → 0.1.14
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 +6 -0
- package/README.md +17 -3
- package/README.zh.md +171 -0
- package/lib/client.js +171 -100
- package/lib/client.js.map +1 -1
- package/lib/index.js +338 -31
- package/lib/mobile.js +109 -33
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
- package/lib/types/client/PluginSettingsCard.js +3 -2
- package/lib/types/client/RemoteSettingsCard.d.ts +4 -0
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +3 -1
- package/lib/types/client/UpdateEntry.d.ts.map +1 -1
- package/lib/types/client/UpdateEntry.js +10 -2
- package/lib/types/client/deep-link.js +10 -5
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +10 -2
- package/lib/types/client/update-api.d.ts +10 -0
- package/lib/types/client/update-api.d.ts.map +1 -1
- package/lib/types/client/update-api.js +22 -2
- package/lib/types/index.d.ts +7 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +7 -1
- package/lib/types/mobile/api.d.ts +9 -2
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +7 -3
- package/lib/types/mobile/mobile-styles.d.ts +1 -1
- package/lib/types/mobile/mobile-styles.d.ts.map +1 -1
- package/lib/types/mobile/mobile-styles.js +3 -0
- package/lib/types/mobile/views/App.d.ts +3 -1
- package/lib/types/mobile/views/App.d.ts.map +1 -1
- package/lib/types/mobile/views/App.js +18 -3
- package/lib/types/mobile/views/ChatView.d.ts +6 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +95 -7
- package/lib/types/mobile/views/ChatView.test.js +161 -3
- package/lib/types/mobile/views/WorkspaceView.d.ts +3 -1
- package/lib/types/mobile/views/WorkspaceView.d.ts.map +1 -1
- package/lib/types/mobile/views/WorkspaceView.js +9 -2
- package/lib/types/mobile/views/WorkspaceView.test.d.ts +2 -0
- package/lib/types/mobile/views/WorkspaceView.test.d.ts.map +1 -0
- package/lib/types/mobile/views/WorkspaceView.test.js +56 -0
- package/lib/types/mobile-api.d.ts +2 -0
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +17 -2
- package/lib/types/update.d.ts +5 -1
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +122 -26
- package/package.json +1 -11
- package/src/client/PluginSettingsCard.tsx +30 -5
- package/src/client/RemoteSettingsCard.tsx +18 -0
- package/src/client/UpdateEntry.tsx +10 -2
- package/src/client/deep-link.ts +10 -4
- package/src/client/locales.ts +10 -2
- package/src/client/settings-card.module.css +139 -144
- package/src/client/update-api.ts +22 -2
- package/src/index.ts +14 -1
- package/src/mobile/api.ts +14 -2
- package/src/mobile/mobile-styles.ts +3 -0
- package/src/mobile/views/App.tsx +22 -3
- package/src/mobile/views/ChatView.test.tsx +185 -3
- package/src/mobile/views/ChatView.tsx +102 -8
- package/src/mobile/views/WorkspaceView.test.tsx +66 -0
- package/src/mobile/views/WorkspaceView.tsx +11 -2
- package/src/mobile-api.ts +20 -2
- package/src/update.ts +116 -26
package/src/update.ts
CHANGED
|
@@ -383,53 +383,143 @@ export interface UpdateRunDeps {
|
|
|
383
383
|
spawnImpl?: typeof spawn
|
|
384
384
|
/** Hard timeout; the child is killed on expiry. */
|
|
385
385
|
timeoutMs?: number
|
|
386
|
+
/** Platform override (defaults to process.platform; test seam). */
|
|
387
|
+
platform?: NodeJS.Platform
|
|
386
388
|
}
|
|
387
389
|
|
|
388
390
|
/** Cap on captured pnpm output (keeps error payloads bounded). */
|
|
389
391
|
const OUTPUT_CAP = 16 * 1024
|
|
390
392
|
|
|
391
393
|
/**
|
|
392
|
-
*
|
|
394
|
+
* Windows cmd command-not-found stderr. With shell:true a missing shim
|
|
395
|
+
* exits with code 1 (cmd cannot report ENOENT), so the fallback chain
|
|
396
|
+
* detects this message instead of the spawn error event.
|
|
397
|
+
*/
|
|
398
|
+
const WIN_CMD_MISSING_RE = /not recognized as an internal or external command/i
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Run the update inside the profile directory. Tries pnpm first, falls back
|
|
402
|
+
* to corepack and then npx when the previous command is missing (ENOENT);
|
|
403
|
+
* all candidates share one hard timeout and keep accumulating output.
|
|
393
404
|
* @param deps - profile dir, package list, and spawn/timeout seams.
|
|
394
405
|
* @returns the outcome with captured output.
|
|
395
406
|
*/
|
|
396
407
|
export function runUpdate(deps: UpdateRunDeps): Promise<UpdateRunResult> {
|
|
397
408
|
return new Promise((resolve) => {
|
|
398
|
-
const
|
|
409
|
+
const spawnImpl = deps.spawnImpl ?? spawn
|
|
410
|
+
const packages = deps.packages
|
|
411
|
+
const platform = deps.platform ?? process.platform
|
|
412
|
+
// Windows ships pnpm/corepack/npx as .cmd shims; Node's spawn cannot
|
|
413
|
+
// start .cmd files directly (ENOENT even when installed), so route
|
|
414
|
+
// them through cmd.exe there. POSIX spawns stay shell-free.
|
|
415
|
+
const spawnOptions: Record<string, unknown> = {
|
|
399
416
|
cwd: deps.profileDir,
|
|
400
417
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
401
|
-
|
|
418
|
+
...(platform === 'win32' ? { shell: true } : {}),
|
|
419
|
+
}
|
|
420
|
+
// Ordered fallback chain: each is tried only when the previous one is
|
|
421
|
+
// missing on PATH (ENOENT on the spawn error event, never on close).
|
|
422
|
+
const candidates: ReadonlyArray<{ command: string; args: string[] }> = [
|
|
423
|
+
{ command: 'pnpm', args: ['update', ...packages] },
|
|
424
|
+
{ command: 'corepack', args: ['pnpm', 'update', ...packages] },
|
|
425
|
+
{ command: 'npx', args: ['--yes', 'pnpm', 'update', ...packages] },
|
|
426
|
+
]
|
|
427
|
+
// `output` accumulates across candidates for UI display; `currentOutput`
|
|
428
|
+
// is reset per candidate and carries only that candidate's own diagnostics
|
|
429
|
+
// (its tail is subject to OUTPUT_CAP). The win32 missing-command test must
|
|
430
|
+
// run against `currentOutput` so a previous candidate's 'not recognized'
|
|
431
|
+
// stderr cannot misclassify a real failure in the next one.
|
|
402
432
|
let output = ''
|
|
433
|
+
let currentOutput = ''
|
|
403
434
|
const append = (chunk: Buffer): void => {
|
|
404
435
|
output += chunk.toString('utf8')
|
|
405
436
|
if (output.length > OUTPUT_CAP) output = output.slice(output.length - OUTPUT_CAP)
|
|
437
|
+
currentOutput += chunk.toString('utf8')
|
|
438
|
+
if (currentOutput.length > OUTPUT_CAP) currentOutput = currentOutput.slice(currentOutput.length - OUTPUT_CAP)
|
|
439
|
+
}
|
|
440
|
+
let currentChild: ReturnType<typeof spawnImpl> | undefined
|
|
441
|
+
// Terminal guard: once a result is produced the promise is settled and no
|
|
442
|
+
// further candidate is started (a killed child's late close must not
|
|
443
|
+
// respawn anything after a timeout resolution).
|
|
444
|
+
let finished = false
|
|
445
|
+
const finish = (result: UpdateRunResult): void => {
|
|
446
|
+
if (finished) return
|
|
447
|
+
finished = true
|
|
448
|
+
clearTimeout(timer)
|
|
449
|
+
resolve(result)
|
|
406
450
|
}
|
|
407
|
-
child.stdout?.on('data', append)
|
|
408
|
-
child.stderr?.on('data', append)
|
|
409
451
|
const timer = setTimeout(() => {
|
|
410
|
-
|
|
411
|
-
|
|
452
|
+
if (finished) return
|
|
453
|
+
if (platform === 'win32') {
|
|
454
|
+
// cmd.exe under shell:true only kills the shell wrapper; kill the whole
|
|
455
|
+
// process tree best-effort so pnpm/npx do not keep running.
|
|
456
|
+
const pid = (currentChild as { pid?: number } | undefined)?.pid
|
|
457
|
+
if (pid !== undefined && pid > 0) {
|
|
458
|
+
try {
|
|
459
|
+
spawnImpl('taskkill', ['/pid', String(pid), '/t', '/f'], { stdio: 'ignore' })
|
|
460
|
+
} catch {
|
|
461
|
+
// Best-effort kill; fall through to the timeout result.
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
currentChild?.kill('SIGTERM')
|
|
466
|
+
}
|
|
467
|
+
finish({ ok: false, exitCode: null, output, error: 'update timed out; install process killed', errorCode: 'timeout' })
|
|
412
468
|
}, deps.timeoutMs ?? 10 * 60_000)
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
469
|
+
const runCandidate = (index: number): void => {
|
|
470
|
+
if (finished) return
|
|
471
|
+
if (index >= candidates.length) {
|
|
472
|
+
finish({
|
|
473
|
+
ok: false,
|
|
474
|
+
exitCode: null,
|
|
475
|
+
output,
|
|
476
|
+
error: 'pnpm not found on PATH (tried pnpm, corepack, npx); install pnpm and restart the app',
|
|
477
|
+
errorCode: 'pnpm-missing',
|
|
478
|
+
})
|
|
479
|
+
return
|
|
480
|
+
}
|
|
481
|
+
const candidate = candidates[index]
|
|
482
|
+
currentOutput = ''
|
|
483
|
+
const child = spawnImpl(candidate.command, candidate.args, spawnOptions)
|
|
484
|
+
currentChild = child
|
|
485
|
+
let settled = false
|
|
486
|
+
const once = (fn: () => void): void => {
|
|
487
|
+
if (settled) return
|
|
488
|
+
settled = true
|
|
489
|
+
fn()
|
|
490
|
+
}
|
|
491
|
+
child.stdout?.on('data', append)
|
|
492
|
+
child.stderr?.on('data', append)
|
|
493
|
+
child.on('error', (error: Error) => {
|
|
494
|
+
once(() => {
|
|
495
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
496
|
+
// Command missing: try the next candidate in the chain.
|
|
497
|
+
runCandidate(index + 1)
|
|
498
|
+
} else {
|
|
499
|
+
finish({ ok: false, exitCode: null, output, error: error.message, errorCode: undefined })
|
|
500
|
+
}
|
|
501
|
+
})
|
|
422
502
|
})
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
503
|
+
child.on('close', (code: number | null) => {
|
|
504
|
+
if (settled || finished) return
|
|
505
|
+
// Windows + shell: a missing command reports exit 1 with
|
|
506
|
+
// 'not recognized' instead of ENOENT — keep the chain going. Checked
|
|
507
|
+
// against this candidate's own output so a prior fallback's stderr
|
|
508
|
+
// cannot misclassify a real failure here.
|
|
509
|
+
if (platform === 'win32' && code !== 0 && WIN_CMD_MISSING_RE.test(currentOutput)) {
|
|
510
|
+
runCandidate(index + 1)
|
|
511
|
+
return
|
|
512
|
+
}
|
|
513
|
+
settled = true
|
|
514
|
+
finish({
|
|
515
|
+
ok: code === 0,
|
|
516
|
+
exitCode: code,
|
|
517
|
+
output,
|
|
518
|
+
error: code === 0 ? undefined : 'pnpm exited with code ' + String(code),
|
|
519
|
+
...(code === 0 ? {} : { errorCode: 'pnpm-failed' as const }),
|
|
520
|
+
})
|
|
432
521
|
})
|
|
433
|
-
}
|
|
522
|
+
}
|
|
523
|
+
runCandidate(0)
|
|
434
524
|
})
|
|
435
525
|
}
|