@linxin666/dsh-remote-web-ui 0.4.2 → 0.4.3
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 +2 -2
- package/README.md +8 -6
- package/README.zh.md +8 -6
- package/lib/client.js +154 -594
- package/lib/client.js.map +1 -1
- package/lib/index.js +323 -908
- package/lib/types/client/RemoteEntry.d.ts.map +1 -1
- package/lib/types/client/RemoteEntry.js +8 -9
- package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
- package/lib/types/client/RemoteSettingsCard.js +1 -1
- package/lib/types/client/index.d.ts +32 -3
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +51 -8
- package/lib/types/client/locales.d.ts +0 -84
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +0 -84
- package/lib/types/client/settings-entry-form.d.ts +50 -0
- package/lib/types/client/settings-entry-form.d.ts.map +1 -0
- package/lib/types/client/settings-entry-form.js +98 -0
- package/lib/types/index.d.ts +99 -7
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +104 -137
- package/lib/types/lan-bind-plan.d.ts +14 -0
- package/lib/types/lan-bind-plan.d.ts.map +1 -1
- package/lib/types/lan-bind-plan.js +16 -0
- package/lib/types/lan-bind.d.ts +1 -1
- package/lib/types/lan-bind.js +1 -1
- package/lib/types/mount-once.d.ts +38 -7
- package/lib/types/mount-once.d.ts.map +1 -1
- package/lib/types/mount-once.js +96 -12
- package/lib/types/tunnel.d.ts +60 -3
- package/lib/types/tunnel.d.ts.map +1 -1
- package/lib/types/tunnel.js +136 -3
- package/package.json +2 -2
- package/src/client/RemoteEntry.tsx +7 -9
- package/src/client/RemoteSettingsCard.tsx +0 -2
- package/src/client/index.ts +53 -9
- package/src/client/locales.ts +0 -84
- package/src/client/remote.module.css +50 -200
- package/src/client/settings-entry-form.ts +142 -0
- package/src/index.ts +156 -152
- package/src/lan-bind-plan.ts +22 -0
- package/src/lan-bind.ts +1 -1
- package/src/mount-once.ts +107 -13
- package/src/tunnel.ts +142 -3
- package/lib/types/client/UpdateEntry.d.ts +0 -14
- package/lib/types/client/UpdateEntry.d.ts.map +0 -1
- package/lib/types/client/UpdateEntry.js +0 -112
- package/lib/types/client/UpdatePanel.d.ts +0 -42
- package/lib/types/client/UpdatePanel.d.ts.map +0 -1
- package/lib/types/client/UpdatePanel.js +0 -101
- package/lib/types/client/update-api.d.ts +0 -29
- package/lib/types/client/update-api.d.ts.map +0 -1
- package/lib/types/client/update-api.js +0 -47
- package/lib/types/console-output.d.ts +0 -41
- package/lib/types/console-output.d.ts.map +0 -1
- package/lib/types/console-output.js +0 -89
- package/lib/types/update-routes.d.ts +0 -30
- package/lib/types/update-routes.d.ts.map +0 -1
- package/lib/types/update-routes.js +0 -47
- package/lib/types/update.d.ts +0 -282
- package/lib/types/update.d.ts.map +0 -1
- package/lib/types/update.js +0 -773
- package/src/client/UpdateEntry.tsx +0 -144
- package/src/client/UpdatePanel.tsx +0 -256
- package/src/client/update-api.ts +0 -49
- package/src/console-output.ts +0 -97
- package/src/update-routes.ts +0 -65
- package/src/update.ts +0 -915
package/src/tunnel.ts
CHANGED
|
@@ -12,9 +12,16 @@
|
|
|
12
12
|
*
|
|
13
13
|
* The cloudflared package's Tunnel is a thin spawn wrapper; this manager
|
|
14
14
|
* owns the lifecycle policy (binary readiness, URL timeout, restart
|
|
15
|
-
* backoff) around it. All seams — the
|
|
16
|
-
*
|
|
17
|
-
* a real binary or network.
|
|
15
|
+
* backoff, and the public-URL readiness watchdog) around it. All seams — the
|
|
16
|
+
* tunnel factory, binary readiness, the URL probe, timers — are injectable
|
|
17
|
+
* so the whole lifecycle is unit-testable without a real binary or network.
|
|
18
|
+
*
|
|
19
|
+
* 'running' is not a terminal state: the connector can lose its edge
|
|
20
|
+
* registration while the process keeps living, which leaves a hostname that
|
|
21
|
+
* no longer resolves. The watchdog below probes the public URL of a running
|
|
22
|
+
* tunnel and reuses the crash-restart path when it stops answering, so the
|
|
23
|
+
* relay is re-registered against a freshly minted URL instead of proxying a
|
|
24
|
+
* dead one (issue #1723).
|
|
18
25
|
*/
|
|
19
26
|
|
|
20
27
|
import { existsSync } from 'node:fs'
|
|
@@ -108,6 +115,45 @@ export interface TunnelHandle {
|
|
|
108
115
|
stop(): boolean
|
|
109
116
|
}
|
|
110
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Probe one public tunnel URL from the host. Resolves true when the URL is
|
|
120
|
+
* served (any HTTP answer below 500 — a harness without credentials answers
|
|
121
|
+
* 401/403, which is a healthy round trip), false for a DNS failure, a refused
|
|
122
|
+
* connection, a timeout, or a Cloudflare 5xx (530 "Origin DNS error" / 1033)
|
|
123
|
+
* — the shapes a tunnel that lost its edge registration produces.
|
|
124
|
+
*
|
|
125
|
+
* The probe must never reject: the watchdog reads a rejection as a dead
|
|
126
|
+
* tunnel and restarts on it, so a local programming error would otherwise
|
|
127
|
+
* recycle working tunnels forever.
|
|
128
|
+
* @param url - the tunnel's public URL.
|
|
129
|
+
* @param timeoutMs - how long one probe may take.
|
|
130
|
+
* @returns whether the URL answered as a live origin.
|
|
131
|
+
*/
|
|
132
|
+
export async function probeTunnelUrl(url: string, timeoutMs: number): Promise<boolean> {
|
|
133
|
+
const controller = new AbortController()
|
|
134
|
+
const timer = setTimeout(() => { controller.abort() }, timeoutMs)
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(url, {
|
|
137
|
+
method: 'GET',
|
|
138
|
+
redirect: 'manual',
|
|
139
|
+
cache: 'no-store',
|
|
140
|
+
signal: controller.signal,
|
|
141
|
+
})
|
|
142
|
+
// Drain so the connection is reusable instead of half-read.
|
|
143
|
+
await response.body?.cancel().catch(() => undefined)
|
|
144
|
+
return response.status < 500
|
|
145
|
+
} catch {
|
|
146
|
+
return false
|
|
147
|
+
} finally {
|
|
148
|
+
clearTimeout(timer)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Default probe: the real network round trip. */
|
|
153
|
+
async function defaultProbe(url: string, timeoutMs: number): Promise<boolean> {
|
|
154
|
+
return await probeTunnelUrl(url, timeoutMs)
|
|
155
|
+
}
|
|
156
|
+
|
|
111
157
|
/** Injectable seams (defaults are the real cloudflared package + node timers). */
|
|
112
158
|
export interface TunnelManagerOptions {
|
|
113
159
|
/** Spawn one tunnel process for the target (quick or named). */
|
|
@@ -122,6 +168,14 @@ export interface TunnelManagerOptions {
|
|
|
122
168
|
restartMaxMs?: number
|
|
123
169
|
/** Timer source (injected in tests). */
|
|
124
170
|
timer?: { setTimeout(fn: () => void, ms: number): unknown; clearTimeout(t: unknown): void }
|
|
171
|
+
/** Probe one public URL; false means the tunnel stopped answering. */
|
|
172
|
+
probe?: (url: string, timeoutMs: number) => Promise<boolean>
|
|
173
|
+
/** How often to probe the public URL of a running tunnel (0 disables it). */
|
|
174
|
+
healthCheckIntervalMs?: number
|
|
175
|
+
/** Consecutive probe failures after which the tunnel is restarted. */
|
|
176
|
+
healthCheckFailures?: number
|
|
177
|
+
/** How long one probe may take. */
|
|
178
|
+
probeTimeoutMs?: number
|
|
125
179
|
}
|
|
126
180
|
|
|
127
181
|
/** Time box for the `--version` probe that validates a staged binary. */
|
|
@@ -195,6 +249,13 @@ export function createBinaryReadiness(executable: string, seams: BinaryReadiness
|
|
|
195
249
|
/** How many archive downloads one process may attempt before giving up. */
|
|
196
250
|
export const MAX_BINARY_INSTALL_ATTEMPTS = 3
|
|
197
251
|
|
|
252
|
+
/** How often a running tunnel's public URL is probed (issue #1723). */
|
|
253
|
+
export const DEFAULT_HEALTH_CHECK_INTERVAL_MS = 60_000
|
|
254
|
+
/** Consecutive failed probes before the tunnel is declared dead. */
|
|
255
|
+
export const DEFAULT_HEALTH_CHECK_FAILURES = 2
|
|
256
|
+
/** How long one public-URL probe may take. */
|
|
257
|
+
export const DEFAULT_PROBE_TIMEOUT_MS = 10_000
|
|
258
|
+
|
|
198
259
|
/** The readiness policy the manager uses by default: the package's real binary path. */
|
|
199
260
|
const defaultBinaryReadiness = createBinaryReadiness(bin)
|
|
200
261
|
|
|
@@ -289,6 +350,10 @@ export class TunnelManager {
|
|
|
289
350
|
private readonly restartBaseMs: number
|
|
290
351
|
private readonly restartMaxMs: number
|
|
291
352
|
private readonly timer: { setTimeout(fn: () => void, ms: number): unknown; clearTimeout(t: unknown): void }
|
|
353
|
+
private readonly probe: (url: string, timeoutMs: number) => Promise<boolean>
|
|
354
|
+
private readonly healthCheckIntervalMs: number
|
|
355
|
+
private readonly healthCheckFailures: number
|
|
356
|
+
private readonly probeTimeoutMs: number
|
|
292
357
|
|
|
293
358
|
private phase: TunnelPhase = 'stopped'
|
|
294
359
|
private url: string | undefined
|
|
@@ -297,6 +362,8 @@ export class TunnelManager {
|
|
|
297
362
|
private handle: TunnelHandle | undefined
|
|
298
363
|
private urlTimer: unknown | undefined
|
|
299
364
|
private restartTimer: unknown | undefined
|
|
365
|
+
private healthTimer: unknown | undefined
|
|
366
|
+
private healthFailures = 0
|
|
300
367
|
private attempts = 0
|
|
301
368
|
// Generation counter: a stale ensureBinary resolution from an earlier
|
|
302
369
|
// start() must not spawn a second handle after a stop/start cycle.
|
|
@@ -315,6 +382,10 @@ export class TunnelManager {
|
|
|
315
382
|
this.restartBaseMs = options.restartBaseMs ?? 5_000
|
|
316
383
|
this.restartMaxMs = options.restartMaxMs ?? 60_000
|
|
317
384
|
this.timer = options.timer ?? nodeTimer
|
|
385
|
+
this.probe = options.probe ?? defaultProbe
|
|
386
|
+
this.healthCheckIntervalMs = options.healthCheckIntervalMs ?? DEFAULT_HEALTH_CHECK_INTERVAL_MS
|
|
387
|
+
this.healthCheckFailures = options.healthCheckFailures ?? DEFAULT_HEALTH_CHECK_FAILURES
|
|
388
|
+
this.probeTimeoutMs = options.probeTimeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS
|
|
318
389
|
}
|
|
319
390
|
|
|
320
391
|
/** The current status frame. */
|
|
@@ -418,6 +489,9 @@ export class TunnelManager {
|
|
|
418
489
|
this.error = undefined
|
|
419
490
|
this.attempts = 0
|
|
420
491
|
this.setPhase('running')
|
|
492
|
+
// The URL is out; from here the connector can still lose its edge
|
|
493
|
+
// registration without the process exiting, so readiness is supervised.
|
|
494
|
+
this.startHealthWatch(value)
|
|
421
495
|
for (const listener of this.urlListeners) {
|
|
422
496
|
try {
|
|
423
497
|
listener(value)
|
|
@@ -427,6 +501,69 @@ export class TunnelManager {
|
|
|
427
501
|
}
|
|
428
502
|
}
|
|
429
503
|
|
|
504
|
+
/**
|
|
505
|
+
* Watch the readiness of a running tunnel. cloudflared can keep its process
|
|
506
|
+
* (and its metrics port) alive while every edge connection is gone, leaving
|
|
507
|
+
* a minted hostname that no longer resolves: the manager used to report
|
|
508
|
+
* 'running' forever and the relay kept forwarding to that dead address, so
|
|
509
|
+
* the phone hit Cloudflare 1016 instead of the relay's offline page
|
|
510
|
+
* (issue #1723). A bounded number of consecutive probe failures ends the
|
|
511
|
+
* attempt through the ordinary fail/backoff path, which mints a new URL and
|
|
512
|
+
* re-announces the relay.
|
|
513
|
+
*/
|
|
514
|
+
private startHealthWatch(url: string): void {
|
|
515
|
+
this.stopHealthWatch()
|
|
516
|
+
// A phase listener may have stopped the tunnel or replaced its target
|
|
517
|
+
// while this URL was being announced; only a live running phase schedules.
|
|
518
|
+
if (this.stopping || this.phase !== 'running') return
|
|
519
|
+
if (this.healthCheckIntervalMs <= 0 || this.healthCheckFailures <= 0) return
|
|
520
|
+
this.healthFailures = 0
|
|
521
|
+
const schedule = (): void => {
|
|
522
|
+
this.healthTimer = this.timer.setTimeout(() => {
|
|
523
|
+
this.healthTimer = undefined
|
|
524
|
+
void this.healthCheck(url)
|
|
525
|
+
}, this.healthCheckIntervalMs)
|
|
526
|
+
}
|
|
527
|
+
schedule()
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** One probe round of the running tunnel's public URL. */
|
|
531
|
+
private async healthCheck(url: string): Promise<void> {
|
|
532
|
+
// The tunnel may have been stopped, failed, or replaced while the probe
|
|
533
|
+
// was in flight; only a still-running tunnel toward this same URL is
|
|
534
|
+
// judged by the result.
|
|
535
|
+
if (this.stopping || this.phase !== 'running' || this.url !== url) return
|
|
536
|
+
let alive = false
|
|
537
|
+
try {
|
|
538
|
+
alive = await this.probe(url, this.probeTimeoutMs)
|
|
539
|
+
} catch {
|
|
540
|
+
alive = false
|
|
541
|
+
}
|
|
542
|
+
if (this.stopping || this.phase !== 'running' || this.url !== url) return
|
|
543
|
+
if (alive) {
|
|
544
|
+
this.healthFailures = 0
|
|
545
|
+
} else {
|
|
546
|
+
this.healthFailures += 1
|
|
547
|
+
if (this.healthFailures >= this.healthCheckFailures) {
|
|
548
|
+
this.fail('the tunnel stopped answering on its public URL')
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
this.healthTimer = this.timer.setTimeout(() => {
|
|
553
|
+
this.healthTimer = undefined
|
|
554
|
+
void this.healthCheck(url)
|
|
555
|
+
}, this.healthCheckIntervalMs)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/** Cancel the readiness watchdog (teardown, restart, stop). */
|
|
559
|
+
private stopHealthWatch(): void {
|
|
560
|
+
if (this.healthTimer !== undefined) {
|
|
561
|
+
this.timer.clearTimeout(this.healthTimer)
|
|
562
|
+
this.healthTimer = undefined
|
|
563
|
+
}
|
|
564
|
+
this.healthFailures = 0
|
|
565
|
+
}
|
|
566
|
+
|
|
430
567
|
private handleExit(): void {
|
|
431
568
|
// The exit handler is detached during teardown, so reaching this point
|
|
432
569
|
// means an unexpected death: fail the current phase and schedule a retry.
|
|
@@ -446,6 +583,7 @@ export class TunnelManager {
|
|
|
446
583
|
this.timer.clearTimeout(this.urlTimer)
|
|
447
584
|
this.urlTimer = undefined
|
|
448
585
|
}
|
|
586
|
+
this.stopHealthWatch()
|
|
449
587
|
this.setPhase('failed')
|
|
450
588
|
this.attempts += 1
|
|
451
589
|
const delay = Math.min(this.restartBaseMs * 2 ** (this.attempts - 1), this.restartMaxMs)
|
|
@@ -458,6 +596,7 @@ export class TunnelManager {
|
|
|
458
596
|
/** Stop the current process and cancel every pending timer (no phase change). */
|
|
459
597
|
private teardown(): void {
|
|
460
598
|
this.stopping = true
|
|
599
|
+
this.stopHealthWatch()
|
|
461
600
|
if (this.urlTimer !== undefined) {
|
|
462
601
|
this.timer.clearTimeout(this.urlTimer)
|
|
463
602
|
this.urlTimer = undefined
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
|
-
/** Entry props: the sidebar column state + the standard locale seat. */
|
|
3
|
-
export interface UpdateEntryProps {
|
|
4
|
-
/** Whether the sidebar renders wide content (false = 56px rail). */
|
|
5
|
-
wide: boolean;
|
|
6
|
-
t: TranslateNS<'remote'>;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Render the update trigger and panel.
|
|
10
|
-
* @param props - column state and locale seat.
|
|
11
|
-
* @returns the entry element tree.
|
|
12
|
-
*/
|
|
13
|
-
export declare function UpdateEntry({ wide, t }: UpdateEntryProps): import("react").JSX.Element;
|
|
14
|
-
//# sourceMappingURL=UpdateEntry.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UpdateEntry.d.ts","sourceRoot":"","sources":["../../../src/client/UpdateEntry.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAOnE,wEAAwE;AACxE,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,IAAI,EAAE,OAAO,CAAA;IACb,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;CACzB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,gBAAgB,+BAkHxD"}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* The sidebar update seat: the download trigger beside the remote-control
|
|
4
|
-
* trigger plus the update panel modal. Owns the flow — probe the registry
|
|
5
|
-
* on open, show the result, and let the user start the update from the
|
|
6
|
-
* result view (#507), then report the outcome (restart hint on success,
|
|
7
|
-
* translated failure on error).
|
|
8
|
-
* Component-local state per the client stack rules.
|
|
9
|
-
*/
|
|
10
|
-
import { useCallback, useEffect, useRef, useState } from "react";
|
|
11
|
-
import { createPortal } from "react-dom";
|
|
12
|
-
import { IconDownloadOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
13
|
-
import { fetchUpdateStatus, runUpdate, UpdateStatusError } from "./update-api.js";
|
|
14
|
-
import { UpdatePanel } from "./UpdatePanel.js";
|
|
15
|
-
import css from "./remote.module.css";
|
|
16
|
-
/**
|
|
17
|
-
* Render the update trigger and panel.
|
|
18
|
-
* @param props - column state and locale seat.
|
|
19
|
-
* @returns the entry element tree.
|
|
20
|
-
*/
|
|
21
|
-
export function UpdateEntry({ wide, t }) {
|
|
22
|
-
const [open, setOpen] = useState(false);
|
|
23
|
-
const [view, setView] = useState({ kind: "checking" });
|
|
24
|
-
const [updateAvailable, setUpdateAvailable] = useState(false);
|
|
25
|
-
const runToken = useRef(0);
|
|
26
|
-
const availabilityToken = useRef(0);
|
|
27
|
-
const mounted = useRef(false);
|
|
28
|
-
const probeAvailability = useCallback(async () => {
|
|
29
|
-
const token = ++availabilityToken.current;
|
|
30
|
-
try {
|
|
31
|
-
const status = await fetchUpdateStatus();
|
|
32
|
-
if (token === availabilityToken.current) {
|
|
33
|
-
setUpdateAvailable(status.mode === "npm" && status.outdated);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
if (token === availabilityToken.current)
|
|
38
|
-
setUpdateAvailable(false);
|
|
39
|
-
}
|
|
40
|
-
}, []);
|
|
41
|
-
const check = useCallback(async () => {
|
|
42
|
-
const availabilityCheck = ++availabilityToken.current;
|
|
43
|
-
setView({ kind: "checking" });
|
|
44
|
-
let status;
|
|
45
|
-
try {
|
|
46
|
-
status = await fetchUpdateStatus();
|
|
47
|
-
}
|
|
48
|
-
catch (error) {
|
|
49
|
-
if (availabilityCheck === availabilityToken.current)
|
|
50
|
-
setUpdateAvailable(false);
|
|
51
|
-
// HTTP 404: the update route is not mounted — the host process runs an
|
|
52
|
-
// older plugin build (client refreshed, host did not). Restarting dsh
|
|
53
|
-
// web loads the new plugin; a plain network failure gets the generic
|
|
54
|
-
// offline copy instead of a misleading "cannot reach update source".
|
|
55
|
-
if (error instanceof UpdateStatusError && error.status === 404) {
|
|
56
|
-
setView({ kind: "error", message: t("update.unmounted"), detail: t("update.unmountedDetail") });
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
setView({ kind: "error", message: t("update.offline"), detail: t("update.offlineDetail") });
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (availabilityCheck === availabilityToken.current) {
|
|
63
|
-
setUpdateAvailable(status.mode === "npm" && status.outdated);
|
|
64
|
-
}
|
|
65
|
-
if (status.error === "registry-unreachable") {
|
|
66
|
-
setView({ kind: "result", status });
|
|
67
|
-
return;
|
|
68
|
-
}
|
|
69
|
-
// Checking never starts an update by itself (#507): the result view
|
|
70
|
-
// carries the confirmation button that calls startUpdate.
|
|
71
|
-
setView({ kind: "result", status });
|
|
72
|
-
}, [t]);
|
|
73
|
-
const startUpdate = useCallback(async (status) => {
|
|
74
|
-
if (status.mode !== "npm" || !status.outdated)
|
|
75
|
-
return;
|
|
76
|
-
setView({ kind: "updating", status });
|
|
77
|
-
const token = ++runToken.current;
|
|
78
|
-
try {
|
|
79
|
-
const result = await runUpdate();
|
|
80
|
-
if (result.ok && mounted.current)
|
|
81
|
-
setUpdateAvailable(false);
|
|
82
|
-
if (token !== runToken.current)
|
|
83
|
-
return;
|
|
84
|
-
setView({ kind: "done", result });
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
if (token !== runToken.current)
|
|
88
|
-
return;
|
|
89
|
-
setView({ kind: "error", message: t("update.error"), detail: t("update.offlineDetail") });
|
|
90
|
-
}
|
|
91
|
-
}, [t]);
|
|
92
|
-
const openPanel = useCallback(() => {
|
|
93
|
-
setOpen(true);
|
|
94
|
-
void check();
|
|
95
|
-
}, [check]);
|
|
96
|
-
const closePanel = useCallback(() => {
|
|
97
|
-
runToken.current++;
|
|
98
|
-
setOpen(false);
|
|
99
|
-
}, []);
|
|
100
|
-
// Check once after mount without opening the panel or starting an update.
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
mounted.current = true;
|
|
103
|
-
void probeAvailability();
|
|
104
|
-
return () => {
|
|
105
|
-
mounted.current = false;
|
|
106
|
-
runToken.current++;
|
|
107
|
-
availabilityToken.current++;
|
|
108
|
-
};
|
|
109
|
-
}, [probeAvailability]);
|
|
110
|
-
const updateLabel = updateAvailable ? t("update.availableLabel") : t("update.label");
|
|
111
|
-
return (_jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: css.trigger, "data-wide": wide ? (updateAvailable ? "wide" : undefined) : "rail", "data-update-available": updateAvailable ? "true" : undefined, "aria-label": updateLabel, "aria-expanded": open, title: updateLabel, onClick: openPanel, children: [_jsx(IconDownloadOutlineRegular, { size: wide ? 16 : 18 }), updateAvailable && wide && (_jsx("span", { className: css.updateBadgeText, children: t("update.badge") }))] }), open && createPortal((_jsxs("div", { className: css.overlay, role: "presentation", children: [_jsx("div", { className: css.mask, "aria-hidden": "true", onClick: closePanel }), _jsx(UpdatePanel, { t: t, view: view, onClose: closePanel, onRecheck: () => { void check(); }, onStartUpdate: (status) => { void startUpdate(status); } })] })), document.body)] }));
|
|
112
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The update panel body: check progress, version comparison, the
|
|
3
|
-
* auto-update in-flight state, and the outcome (restart hint on success,
|
|
4
|
-
* translated failure on error). Pure presentation — all state arrives
|
|
5
|
-
* through props from the entry behavior component.
|
|
6
|
-
*/
|
|
7
|
-
import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots';
|
|
8
|
-
import type { UpdateRunResult, UpdateStatus } from '../update.ts';
|
|
9
|
-
/** The panel view state, owned by the entry component. */
|
|
10
|
-
export type UpdateView = {
|
|
11
|
-
kind: 'checking';
|
|
12
|
-
} | {
|
|
13
|
-
kind: 'result';
|
|
14
|
-
status: UpdateStatus;
|
|
15
|
-
} | {
|
|
16
|
-
kind: 'updating';
|
|
17
|
-
status: UpdateStatus;
|
|
18
|
-
} | {
|
|
19
|
-
kind: 'done';
|
|
20
|
-
result: UpdateRunResult;
|
|
21
|
-
} | {
|
|
22
|
-
kind: 'error';
|
|
23
|
-
message: string;
|
|
24
|
-
detail?: string;
|
|
25
|
-
};
|
|
26
|
-
/** Full panel props: copy + view state + actions. */
|
|
27
|
-
export interface UpdatePanelProps {
|
|
28
|
-
t: TranslateNS<'remote'>;
|
|
29
|
-
view: UpdateView;
|
|
30
|
-
onClose(): void;
|
|
31
|
-
/** Re-run the check from a terminal state. */
|
|
32
|
-
onRecheck(): void;
|
|
33
|
-
/** Start the update from the result view (the confirmation step, #507). */
|
|
34
|
-
onStartUpdate(status: UpdateStatus): void;
|
|
35
|
-
}
|
|
36
|
-
/**
|
|
37
|
-
* Render the update panel.
|
|
38
|
-
* @param props - copy, view state, and actions.
|
|
39
|
-
* @returns the panel element tree.
|
|
40
|
-
*/
|
|
41
|
-
export declare function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }: UpdatePanelProps): import("react").JSX.Element;
|
|
42
|
-
//# sourceMappingURL=UpdatePanel.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"UpdatePanel.d.ts","sourceRoot":"","sources":["../../../src/client/UpdatePanel.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kCAAkC,CAAA;AAEnE,OAAO,KAAK,EAAsB,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAGrF,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACxC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,qDAAqD;AACrD,MAAM,WAAW,gBAAgB;IAC/B,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAA;IACxB,IAAI,EAAE,UAAU,CAAA;IAChB,OAAO,IAAI,IAAI,CAAA;IACf,8CAA8C;IAC9C,SAAS,IAAI,IAAI,CAAA;IACjB,2EAA2E;IAC3E,aAAa,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAA;CAC1C;AAYD;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,EAAE,gBAAgB,+BAiD3F"}
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { IconCloseOutlineRegular, IconRefreshOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives';
|
|
3
|
-
import css from "./remote.module.css";
|
|
4
|
-
/** The anchor package name (aggregate first) for copy purposes. */
|
|
5
|
-
function anchorName(status) {
|
|
6
|
-
return status?.anchor ?? status?.packages[0]?.name;
|
|
7
|
-
}
|
|
8
|
-
/** The latest npm release of the anchor, for reference copy. */
|
|
9
|
-
function anchorLatest(status) {
|
|
10
|
-
return status?.packages[0]?.latest;
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Render the update panel.
|
|
14
|
-
* @param props - copy, view state, and actions.
|
|
15
|
-
* @returns the panel element tree.
|
|
16
|
-
*/
|
|
17
|
-
export function UpdatePanel({ t, view, onClose, onRecheck, onStartUpdate }) {
|
|
18
|
-
const status = view.kind === "result" || view.kind === "updating" ? view.status : undefined;
|
|
19
|
-
const title = view.kind === "done" && view.result.ok ? t("update.done") : t("update.title");
|
|
20
|
-
const subtitle = subtitleOf(t, view);
|
|
21
|
-
return (_jsxs("div", { className: css.panel, role: "dialog", "aria-modal": "true", "aria-label": title, children: [_jsxs("div", { className: css.header, children: [_jsxs("div", { className: css.heading, children: [_jsx("h2", { className: css.title, children: title }), subtitle !== undefined && _jsx("p", { className: css.subtitle, children: subtitle })] }), _jsx("button", { type: "button", className: css.close, "aria-label": t("update.close"), onClick: onClose, children: _jsx(IconCloseOutlineRegular, {}) })] }), view.kind === "checking" && _jsx("p", { className: css.updateStatus, children: t("update.checking") }), view.kind === "result" && status !== undefined && _jsx(ResultBody, { t: t, status: status }), view.kind === "result" && status !== undefined && status.mode === "npm" && status.outdated && status.error === undefined && (_jsx("div", { className: css.updateActions, children: _jsx("button", { type: "button", className: css.updateRetry, onClick: () => onStartUpdate(status), children: t("update.start") }) })), view.kind === "updating" && status !== undefined && (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.updating", { name: anchorName(status) ?? "", version: anchorLatest(status) ?? "" }) }), _jsx("p", { className: css.updateDetail, children: t("update.cooldownNotice") }), _jsx(PackageSummary, { t: t, status: status })] })), view.kind === "done" && _jsx(DoneBody, { t: t, result: view.result }), view.kind === "error" && (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: view.message }), view.detail !== undefined && _jsx("pre", { className: css.updateOutput, children: view.detail })] })), (view.kind === "done" || view.kind === "error") && (_jsx("div", { className: css.updateActions, children: _jsxs("button", { type: "button", className: css.updateRetry, onClick: onRecheck, children: [_jsx(IconRefreshOutlineRegular, {}), " ", t("update.retry")] }) }))] }));
|
|
22
|
-
}
|
|
23
|
-
/** The subtitle copy per view state (absent on plain results). */
|
|
24
|
-
function subtitleOf(t, view) {
|
|
25
|
-
switch (view.kind) {
|
|
26
|
-
case "checking":
|
|
27
|
-
return t("update.checking");
|
|
28
|
-
case "updating":
|
|
29
|
-
return t("update.updatingTitle");
|
|
30
|
-
case "result":
|
|
31
|
-
return undefined;
|
|
32
|
-
case "done":
|
|
33
|
-
return view.result.ok ? t("update.doneDetail") : t("update.error");
|
|
34
|
-
case "error":
|
|
35
|
-
// The error body below carries the message; no subtitle duplication.
|
|
36
|
-
return undefined;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
/** The checked result body: mode banner + version list. */
|
|
40
|
-
function ResultBody({ t, status }) {
|
|
41
|
-
const anchor = anchorName(status);
|
|
42
|
-
const latest = anchorLatest(status);
|
|
43
|
-
if (status.mode === "link") {
|
|
44
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.linkMode") }), _jsx("p", { className: css.updateDetail, children: t("update.linkModeDetail", { version: latest ?? "-" }) })] }));
|
|
45
|
-
}
|
|
46
|
-
if (status.mode === "missing") {
|
|
47
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.missing") }), _jsx("p", { className: css.updateDetail, children: t("update.missingDetail") })] }));
|
|
48
|
-
}
|
|
49
|
-
if (status.error === "registry-unreachable") {
|
|
50
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.offline") }), _jsx("p", { className: css.updateDetail, children: t("update.offlineDetail") })] }));
|
|
51
|
-
}
|
|
52
|
-
if (status.outdated) {
|
|
53
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.found") }), _jsx("p", { className: css.updateDetail, children: anchor !== undefined ? t("update.foundDetail", { name: anchor, version: latest ?? "" }) : "" }), _jsx("p", { className: css.updateDetail, children: t("update.cooldownNotice") }), _jsx(PackageSummary, { t: t, status: status })] }));
|
|
54
|
-
}
|
|
55
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateStatus, children: t("update.upToDate") }), anchor !== undefined && latest !== undefined && (_jsx("p", { className: css.updateDetail, children: t("update.upToDateDetail", { name: anchor, version: latest }) })), _jsx(PackageSummary, { t: t, status: status })] }));
|
|
56
|
-
}
|
|
57
|
-
/** Release-note summary when available; otherwise fall back to the package list. */
|
|
58
|
-
function PackageSummary({ t, status }) {
|
|
59
|
-
if (status.notes === undefined)
|
|
60
|
-
return _jsx(PackageList, { status: status });
|
|
61
|
-
return (_jsxs("div", { children: [_jsx(ReleaseNotes, { t: t, notes: status.notes }), _jsxs("details", { className: css.updateVersions, children: [_jsx("summary", { className: css.updateVersionsSummary, children: t("update.componentVersions") }), _jsx(PackageList, { status: status })] })] }));
|
|
62
|
-
}
|
|
63
|
-
/** Render GitHub Release sections as a compact three-group list. */
|
|
64
|
-
function ReleaseNotes({ t, notes }) {
|
|
65
|
-
const sections = [
|
|
66
|
-
{ key: 'features', title: t("update.releaseFeatures"), items: notes.features },
|
|
67
|
-
{ key: 'fixes', title: t("update.releaseFixes"), items: notes.fixes },
|
|
68
|
-
{ key: 'other', title: t("update.releaseOther"), items: notes.other },
|
|
69
|
-
].filter(section => section.items.length > 0);
|
|
70
|
-
if (sections.length === 0)
|
|
71
|
-
return _jsx("p", { className: css.updateDetail, children: t("update.releaseUnavailable") });
|
|
72
|
-
return (_jsxs("div", { className: css.updateNotes, children: [_jsx("h3", { className: css.updateNotesTitle, children: t("update.releaseNotes", { version: notes.version }) }), sections.map(section => (_jsxs("section", { className: css.updateNoteSection, children: [_jsx("h4", { className: css.updateNoteHeading, children: section.title }), _jsx("ul", { className: css.updateNoteList, children: section.items.map((item, index) => (_jsx("li", { className: css.updateNoteItem, children: item }, index))) })] }, section.key)))] }));
|
|
73
|
-
}
|
|
74
|
-
/** The per-package current → latest comparison list. */
|
|
75
|
-
function PackageList({ status }) {
|
|
76
|
-
if (status.packages.length === 0)
|
|
77
|
-
return null;
|
|
78
|
-
return (_jsx("ul", { className: css.updateList, children: status.packages.map(packageStatus => (_jsxs("li", { className: css.updateListItem, children: [_jsx("span", { className: css.updateListName, children: packageStatus.name }), _jsxs("span", { className: css.updateListVersions, children: [packageStatus.current, packageStatus.latest !== undefined && packageStatus.latest !== packageStatus.current && (_jsxs(_Fragment, { children: [" \u2192 ", packageStatus.latest] }))] })] }, packageStatus.name))) }));
|
|
79
|
-
}
|
|
80
|
-
/** The outcome body: success + restart hint, or the translated failure. */
|
|
81
|
-
function DoneBody({ t, result }) {
|
|
82
|
-
if (result.ok) {
|
|
83
|
-
// The title already reads "Update complete"; the body carries the details.
|
|
84
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateDetail, children: t("update.doneDetail") }), _jsx("p", { className: css.updateDetail, children: t("update.restartHint") })] }));
|
|
85
|
-
}
|
|
86
|
-
const message = errorMessageOf(t, result);
|
|
87
|
-
return (_jsxs("div", { children: [_jsx("p", { className: css.updateError, children: message }), result.output.trim() !== "" && _jsx("pre", { className: css.updateOutput, children: result.output.trim() })] }));
|
|
88
|
-
}
|
|
89
|
-
/** Translate a structured failure code; fall back to the raw message. */
|
|
90
|
-
function errorMessageOf(t, result) {
|
|
91
|
-
switch (result.errorCode) {
|
|
92
|
-
case "pnpm-missing": return t("update.error.pnpmMissing");
|
|
93
|
-
case "timeout": return t("update.error.timeout");
|
|
94
|
-
case "not-found": return t("update.error.notFound");
|
|
95
|
-
case "link": return t("update.error.link");
|
|
96
|
-
case "pnpm-failed": return t("update.error.pnpmFailed", { code: String(result.exitCode ?? "?") });
|
|
97
|
-
case "stale": return t("update.error.stale");
|
|
98
|
-
case "verify-failed": return t("update.error.verifyFailed");
|
|
99
|
-
default: return result.error ?? t("update.error.unknown");
|
|
100
|
-
}
|
|
101
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-side wire helpers for the /api/update surface. Plain fetch over
|
|
3
|
-
* same-origin /api like the pairing client; the host half enforces the
|
|
4
|
-
* loopback-only fence and owns the pnpm run.
|
|
5
|
-
*/
|
|
6
|
-
import type { UpdateRunResult, UpdateStatus } from '../update.ts';
|
|
7
|
-
/**
|
|
8
|
-
* Error thrown when the update status probe fails: carries the HTTP status
|
|
9
|
-
* so the panel can tell "the update route is not mounted" (404 — the host
|
|
10
|
-
* process runs an older plugin build) apart from real network failures.
|
|
11
|
-
*/
|
|
12
|
-
export declare class UpdateStatusError extends Error {
|
|
13
|
-
/** HTTP status of the failed response (0 when the fetch never returned). */
|
|
14
|
-
readonly status: number;
|
|
15
|
-
constructor(status: number);
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Probe the update status: install mode, owning profile, and the
|
|
19
|
-
* current-vs-latest comparison for every family package.
|
|
20
|
-
* @returns the status snapshot.
|
|
21
|
-
*/
|
|
22
|
-
export declare function fetchUpdateStatus(): Promise<UpdateStatus>;
|
|
23
|
-
/**
|
|
24
|
-
* Run the update (pnpm update in the owning profile). Blocks until pnpm
|
|
25
|
-
* exits — the panel shows an in-flight state meanwhile.
|
|
26
|
-
* @returns the run outcome.
|
|
27
|
-
*/
|
|
28
|
-
export declare function runUpdate(): Promise<UpdateRunResult>;
|
|
29
|
-
//# sourceMappingURL=update-api.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"update-api.d.ts","sourceRoot":"","sources":["../../../src/client/update-api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEjE;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,4EAA4E;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;gBACX,MAAM,EAAE,MAAM;CAI3B;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,YAAY,CAAC,CAU/D;AAED;;;;GAIG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAI1D"}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser-side wire helpers for the /api/update surface. Plain fetch over
|
|
3
|
-
* same-origin /api like the pairing client; the host half enforces the
|
|
4
|
-
* loopback-only fence and owns the pnpm run.
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Error thrown when the update status probe fails: carries the HTTP status
|
|
8
|
-
* so the panel can tell "the update route is not mounted" (404 — the host
|
|
9
|
-
* process runs an older plugin build) apart from real network failures.
|
|
10
|
-
*/
|
|
11
|
-
export class UpdateStatusError extends Error {
|
|
12
|
-
/** HTTP status of the failed response (0 when the fetch never returned). */
|
|
13
|
-
status;
|
|
14
|
-
constructor(status) {
|
|
15
|
-
super('update status unavailable (HTTP ' + String(status) + ')');
|
|
16
|
-
this.status = status;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Probe the update status: install mode, owning profile, and the
|
|
21
|
-
* current-vs-latest comparison for every family package.
|
|
22
|
-
* @returns the status snapshot.
|
|
23
|
-
*/
|
|
24
|
-
export async function fetchUpdateStatus() {
|
|
25
|
-
let response;
|
|
26
|
-
try {
|
|
27
|
-
response = await fetch('api/update/status');
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
// Network-level failure: nothing came back at all.
|
|
31
|
-
throw new UpdateStatusError(0);
|
|
32
|
-
}
|
|
33
|
-
if (!response.ok)
|
|
34
|
-
throw new UpdateStatusError(response.status);
|
|
35
|
-
return await response.json();
|
|
36
|
-
}
|
|
37
|
-
/**
|
|
38
|
-
* Run the update (pnpm update in the owning profile). Blocks until pnpm
|
|
39
|
-
* exits — the panel shows an in-flight state meanwhile.
|
|
40
|
-
* @returns the run outcome.
|
|
41
|
-
*/
|
|
42
|
-
export async function runUpdate() {
|
|
43
|
-
const response = await fetch('api/update/run', { method: 'POST' });
|
|
44
|
-
if (!response.ok)
|
|
45
|
-
throw new Error('update run unavailable');
|
|
46
|
-
return await response.json();
|
|
47
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Console-output capture shared by the family's Host halves.
|
|
3
|
-
*
|
|
4
|
-
* A child process's stdout/stderr are raw bytes, and on Windows a console
|
|
5
|
-
* program writes the OEM code page (CP936/GBK on a zh-CN system), not UTF-8.
|
|
6
|
-
* Decoding each chunk on its own corrupts the text twice over: the
|
|
7
|
-
* encoding is guessed wrong, and a multi-byte character split across two reads
|
|
8
|
-
* decodes into two replacement characters. This capture accumulates raw bytes
|
|
9
|
-
* under a byte budget and decodes exactly once, at read time.
|
|
10
|
-
*/
|
|
11
|
-
/** Bounded byte accumulator for one child process's interleaved stdout/stderr. */
|
|
12
|
-
export interface OutputCapture {
|
|
13
|
-
/** Append one raw chunk (the bytes are copied into the budget). */
|
|
14
|
-
push(chunk: Uint8Array): void;
|
|
15
|
-
/**
|
|
16
|
-
* Decode everything captured so far. Idempotent, so a caller may read the
|
|
17
|
-
* tail both after a failure branch and at settlement.
|
|
18
|
-
*/
|
|
19
|
-
read(): string;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Decode one console byte buffer. Strict UTF-8 wins whenever it is valid;
|
|
23
|
-
* otherwise the bytes are not UTF-8 at all and the console wrote its own code
|
|
24
|
-
* page, so the candidate that loses the least text is taken.
|
|
25
|
-
* @param bytes - the captured bytes.
|
|
26
|
-
* @returns the decoded text.
|
|
27
|
-
*/
|
|
28
|
-
export declare function decodeConsoleBytes(bytes: Uint8Array): string;
|
|
29
|
-
/**
|
|
30
|
-
* Create a capture bounded to a byte budget. Overflow drops from the FRONT
|
|
31
|
-
* (the tail is what carries a failure reason), and the cut never lands inside
|
|
32
|
-
* a character: leading UTF-8 continuation bytes are dropped with it, and a
|
|
33
|
-
* legacy code page's dangling lead byte — indistinguishable from a real byte
|
|
34
|
-
* without knowing the encoding until the whole buffer is decoded — is removed
|
|
35
|
-
* with the one replacement character it produced, so an over-long stream still
|
|
36
|
-
* reads cleanly at the seam.
|
|
37
|
-
* @param maxBytes - maximum retained bytes.
|
|
38
|
-
* @returns the capture.
|
|
39
|
-
*/
|
|
40
|
-
export declare function createOutputCapture(maxBytes: number): OutputCapture;
|
|
41
|
-
//# sourceMappingURL=console-output.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"console-output.d.ts","sourceRoot":"","sources":["../../src/console-output.ts"],"names":[],"mappings":"AACA;;;;;;;;;GASG;AAEH,kFAAkF;AAClF,MAAM,WAAW,aAAa;IAC5B,mEAAmE;IACnE,IAAI,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IAC7B;;;OAGG;IACH,IAAI,IAAI,MAAM,CAAA;CACf;AAWD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CA2B5D;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAiBnE"}
|