@linxin666/dsh-remote-web-ui 0.3.2 → 0.3.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 +22 -8
- package/README.zh.md +12 -7
- package/lib/client.js +174 -16
- package/lib/client.js.map +1 -1
- package/lib/index.js +104 -7
- package/lib/mobile.js +62 -7
- package/lib/mobile.js.map +1 -1
- package/lib/types/client/RemoteEntry.js +1 -1
- package/lib/types/client/UpdatePanel.d.ts.map +1 -1
- package/lib/types/client/UpdatePanel.js +20 -3
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/index.js +4 -0
- package/lib/types/client/locales.d.ts +12 -0
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/locales.js +22 -10
- package/lib/types/client/settings-form.d.ts +1 -1
- package/lib/types/client/settings-form.js +1 -1
- package/lib/types/client/telemetry.d.ts +27 -0
- package/lib/types/client/telemetry.d.ts.map +1 -0
- package/lib/types/client/telemetry.js +97 -0
- package/lib/types/http.d.ts +1 -1
- package/lib/types/http.js +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +14 -3
- package/lib/types/loopback.d.ts +1 -1
- package/lib/types/loopback.js +1 -1
- package/lib/types/mobile/api.d.ts +7 -0
- package/lib/types/mobile/api.d.ts.map +1 -1
- package/lib/types/mobile/api.js +7 -0
- 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 +10 -0
- package/lib/types/mobile/views/ChatView.d.ts +1 -0
- package/lib/types/mobile/views/ChatView.d.ts.map +1 -1
- package/lib/types/mobile/views/ChatView.js +32 -5
- package/lib/types/mobile/views/ChatView.stream.test.js +28 -2
- package/lib/types/mobile/views/ChatView.test.js +152 -2
- package/lib/types/mobile-api.d.ts.map +1 -1
- package/lib/types/mobile-api.js +3 -0
- package/lib/types/mount-once.d.ts +1 -1
- package/lib/types/mount-once.js +2 -2
- package/lib/types/update.d.ts +35 -4
- package/lib/types/update.d.ts.map +1 -1
- package/lib/types/update.js +98 -4
- package/package.json +2 -2
- package/src/client/RemoteEntry.tsx +1 -1
- package/src/client/UpdatePanel.tsx +43 -3
- package/src/client/index.ts +6 -1
- package/src/client/locales.ts +22 -10
- package/src/client/remote.module.css +56 -3
- package/src/client/settings-form.ts +1 -1
- package/src/client/telemetry.ts +105 -0
- package/src/http.ts +1 -1
- package/src/index.ts +16 -2
- package/src/loopback.ts +1 -1
- package/src/mobile/api.ts +8 -0
- package/src/mobile/mobile-styles.ts +10 -0
- package/src/mobile/views/ChatView.stream.test.tsx +34 -2
- package/src/mobile/views/ChatView.test.tsx +177 -2
- package/src/mobile/views/ChatView.tsx +44 -6
- package/src/mobile-api.ts +2 -0
- package/src/mount-once.ts +2 -2
- package/src/update.ts +117 -4
package/src/update.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Remote update support for the dsh-web
|
|
3
|
-
* installed aggregate package (@linxin666/dsh-web-
|
|
2
|
+
* Remote update support for the dsh-web family — host half. Detects the
|
|
3
|
+
* installed aggregate package (@linxin666/dsh-web-all), or the directly
|
|
4
4
|
* installed family packages when the aggregate is absent, probes npm for newer
|
|
5
5
|
* releases, and runs `pnpm update --latest` inside the owning dsh profile.
|
|
6
6
|
*
|
|
@@ -16,15 +16,21 @@ import { spawn } from 'node:child_process'
|
|
|
16
16
|
/** npm registry base used for version probes. */
|
|
17
17
|
export const NPM_REGISTRY = 'https://registry.npmjs.org'
|
|
18
18
|
|
|
19
|
-
/** The family scope every dsh-web
|
|
19
|
+
/** The family scope every dsh-web package is published under. */
|
|
20
20
|
export const FAMILY_SCOPE = '@linxin666/'
|
|
21
21
|
|
|
22
22
|
/** The aggregate package that is the canonical update entry point. */
|
|
23
|
-
export const AGGREGATE_PACKAGE = '@linxin666/dsh-web-
|
|
23
|
+
export const AGGREGATE_PACKAGE = '@linxin666/dsh-web-all'
|
|
24
24
|
|
|
25
25
|
/** Fallback anchor: this plugin's own package when the aggregate is absent. */
|
|
26
26
|
export const SELF_PACKAGE = '@linxin666/dsh-remote-web-ui'
|
|
27
27
|
|
|
28
|
+
/** GitHub repository used to surface human-readable release notes. */
|
|
29
|
+
export const UPDATE_RELEASE_REPO = 'zhu1090093659/dsh-web'
|
|
30
|
+
|
|
31
|
+
/** Release-notes cache freshness window (host process). */
|
|
32
|
+
export const RELEASE_NOTES_CACHE_TTL_MS = 10 * 60_000
|
|
33
|
+
|
|
28
34
|
/** A profile manifest `name` prefix (e.g. `dsh-profile-web`). */
|
|
29
35
|
const PROFILE_NAME_PREFIX = 'dsh-profile-'
|
|
30
36
|
|
|
@@ -168,6 +174,18 @@ export interface UpdatePackageStatus {
|
|
|
168
174
|
outdated: boolean
|
|
169
175
|
}
|
|
170
176
|
|
|
177
|
+
/** Structured release-note sections shown in the update panel. */
|
|
178
|
+
export interface UpdateReleaseNotes {
|
|
179
|
+
/** Version the notes describe. */
|
|
180
|
+
version: string
|
|
181
|
+
/** New feature bullets. */
|
|
182
|
+
features: string[]
|
|
183
|
+
/** Bug-fix bullets. */
|
|
184
|
+
fixes: string[]
|
|
185
|
+
/** Other-change bullets. */
|
|
186
|
+
other: string[]
|
|
187
|
+
}
|
|
188
|
+
|
|
171
189
|
/** The full update-status snapshot served to the browser half. */
|
|
172
190
|
export interface UpdateStatus {
|
|
173
191
|
/** npm = registry-managed (updatable); link = local dev install; missing = no anchor package. */
|
|
@@ -180,6 +198,8 @@ export interface UpdateStatus {
|
|
|
180
198
|
packages: UpdatePackageStatus[]
|
|
181
199
|
/** True when any package has a newer npm release. */
|
|
182
200
|
outdated: boolean
|
|
201
|
+
/** Human-readable release notes for the target release, when available. */
|
|
202
|
+
notes?: UpdateReleaseNotes
|
|
183
203
|
/** Whole-check failure (e.g. registry unreachable). */
|
|
184
204
|
error?: string
|
|
185
205
|
}
|
|
@@ -192,6 +212,8 @@ export interface UpdateCheckDeps {
|
|
|
192
212
|
resolve(specifier: string): string | undefined
|
|
193
213
|
/** Probe one package's latest npm version; undefined on failure. */
|
|
194
214
|
fetchLatest(name: string): Promise<string | undefined>
|
|
215
|
+
/** Fetch structured release notes for one target version, when available. */
|
|
216
|
+
fetchReleaseNotes?(version: string): Promise<UpdateReleaseNotes | undefined>
|
|
195
217
|
}
|
|
196
218
|
|
|
197
219
|
/**
|
|
@@ -313,6 +335,92 @@ export async function fetchLatestVersion(
|
|
|
313
335
|
}
|
|
314
336
|
}
|
|
315
337
|
|
|
338
|
+
/** Normalize one release-note bullet. */
|
|
339
|
+
function cleanNoteItem(text: string): string {
|
|
340
|
+
// Keep the visible label and drop Markdown link targets; issue references
|
|
341
|
+
// become plain #123 because the panel has no Markdown renderer.
|
|
342
|
+
return text.replace(/\[([^\]]+)\]\([^)]*\)/g, '$1').replace(/^[-*]\s+/, '').trim()
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** Determine the section kind for a Markdown heading line. */
|
|
346
|
+
function noteSectionOf(line: string): keyof Pick<UpdateReleaseNotes, 'features' | 'fixes' | 'other'> | undefined {
|
|
347
|
+
const heading = /^#{1,6}\s+(.+)$/.exec(line.trim())
|
|
348
|
+
if (heading === null) return undefined
|
|
349
|
+
const title = heading[1].replace(/[::].*$/, '').trim().toLowerCase()
|
|
350
|
+
if (title === '新功能' || title === 'new features') return 'features'
|
|
351
|
+
if (title === '修复' || title === 'bug fixes' || title === 'bugfixes') return 'fixes'
|
|
352
|
+
if (title === '其他改动' || title === 'other changes') return 'other'
|
|
353
|
+
return undefined
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Parse the GitHub Release body into the three conventional sections.
|
|
358
|
+
*
|
|
359
|
+
* The committed release notes are bilingual: Chinese is the default view and
|
|
360
|
+
* English lives inside a `<details>` block. Prefer the Chinese sections when
|
|
361
|
+
* present and fall back to the whole body for English-only releases. Bullets
|
|
362
|
+
* are escaped as plain text for the panel (no Markdown renderer).
|
|
363
|
+
*/
|
|
364
|
+
function parseReleaseNotesSource(version: string, source: string): UpdateReleaseNotes | undefined {
|
|
365
|
+
const notes: UpdateReleaseNotes = { version, features: [], fixes: [], other: [] }
|
|
366
|
+
let current: keyof Pick<UpdateReleaseNotes, 'features' | 'fixes' | 'other'> | undefined
|
|
367
|
+
for (const rawLine of source.split(/\r?\n/)) {
|
|
368
|
+
const line = rawLine.trim()
|
|
369
|
+
const section = noteSectionOf(line)
|
|
370
|
+
if (section !== undefined) {
|
|
371
|
+
current = section
|
|
372
|
+
continue
|
|
373
|
+
}
|
|
374
|
+
if (current === undefined || !/^-\s+/.test(line)) continue
|
|
375
|
+
const item = cleanNoteItem(line)
|
|
376
|
+
if (item !== '') notes[current].push(item)
|
|
377
|
+
}
|
|
378
|
+
if (notes.features.length === 0 && notes.fixes.length === 0 && notes.other.length === 0) return undefined
|
|
379
|
+
return notes
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
export function parseReleaseNotesBody(version: string, body: string): UpdateReleaseNotes | undefined {
|
|
383
|
+
// The committed release notes put Chinese in the default view and English in
|
|
384
|
+
// a <details> block. Parse only the default view first, then fall back to
|
|
385
|
+
// the full body for English-only releases.
|
|
386
|
+
return parseReleaseNotesSource(version, body.split('<details>')[0] ?? body)
|
|
387
|
+
?? parseReleaseNotesSource(version, body)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Fetch and parse the GitHub Release for one `dsh-web` version.
|
|
392
|
+
* @param version - the npm release version (without the `v` prefix).
|
|
393
|
+
* @param fetchImpl - injected fetch; global `fetch` in the host.
|
|
394
|
+
* @param timeoutMs - hard timeout for the GitHub API probe.
|
|
395
|
+
* @returns structured notes, or undefined when unavailable.
|
|
396
|
+
*/
|
|
397
|
+
export async function fetchGitHubReleaseNotes(
|
|
398
|
+
version: string,
|
|
399
|
+
fetchImpl: (url: string, init?: RequestInit) => Promise<{ ok: boolean; json(): Promise<unknown> }> = fetch,
|
|
400
|
+
timeoutMs = 10_000,
|
|
401
|
+
): Promise<UpdateReleaseNotes | undefined> {
|
|
402
|
+
const controller = new AbortController()
|
|
403
|
+
const timer = setTimeout(() => { controller.abort() }, timeoutMs)
|
|
404
|
+
try {
|
|
405
|
+
const response = await fetchImpl(
|
|
406
|
+
`https://api.github.com/repos/${UPDATE_RELEASE_REPO}/releases/tags/v${version}`,
|
|
407
|
+
{
|
|
408
|
+
headers: { accept: 'application/vnd.github+json', 'user-agent': 'dsh-web-update' },
|
|
409
|
+
signal: controller.signal,
|
|
410
|
+
},
|
|
411
|
+
)
|
|
412
|
+
if (!response.ok) return undefined
|
|
413
|
+
const body = await response.json()
|
|
414
|
+
if (typeof body !== 'object' || body === null) return undefined
|
|
415
|
+
const text = (body as Record<string, unknown>).body
|
|
416
|
+
return typeof text === 'string' ? parseReleaseNotesBody(version, text) : undefined
|
|
417
|
+
} catch {
|
|
418
|
+
return undefined
|
|
419
|
+
} finally {
|
|
420
|
+
clearTimeout(timer)
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
316
424
|
/**
|
|
317
425
|
* Sentinel for an installed version that could not be read (missing manifest
|
|
318
426
|
* or resolve failure). It is a real-looking version so `checkUpdates` can
|
|
@@ -392,12 +500,17 @@ export async function checkUpdates(deps: UpdateCheckDeps): Promise<UpdateStatus>
|
|
|
392
500
|
// Registry unreachable: every probe failed — report the outage distinctly
|
|
393
501
|
// instead of a misleading "all up to date" (the panel needs the reason).
|
|
394
502
|
const error = probeFailures === names.length && names.length > 0 ? 'registry-unreachable' : undefined
|
|
503
|
+
const targetVersion = packages.find(packageStatus => packageStatus.latest !== undefined)?.latest
|
|
504
|
+
const notes = targetVersion !== undefined && deps.fetchReleaseNotes !== undefined
|
|
505
|
+
? await deps.fetchReleaseNotes(targetVersion).catch(() => undefined)
|
|
506
|
+
: undefined
|
|
395
507
|
return {
|
|
396
508
|
mode: linked ? 'link' : 'npm',
|
|
397
509
|
profileName: profile.name,
|
|
398
510
|
anchor,
|
|
399
511
|
packages,
|
|
400
512
|
outdated: packages.some(packageStatus => packageStatus.outdated),
|
|
513
|
+
...(notes !== undefined ? { notes } : {}),
|
|
401
514
|
...(error !== undefined ? { error } : {}),
|
|
402
515
|
}
|
|
403
516
|
}
|