dsh-wisp 1.25.0 → 1.26.0
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.md +30 -0
- package/lib/client.js +7 -3
- package/lib/client.template.js +7 -3
- package/lib/index.js +61 -22
- package/package.json +1 -1
- package/verify-wisp.mjs +57 -14
package/README.md
CHANGED
|
@@ -404,6 +404,13 @@ node tools/audit-log.mjs --confirm <manifest> # 生成后:并入代理登
|
|
|
404
404
|
- **宿主半包**跑在 **vm 沙箱**里:`harness` 只有 `defineTool / registerTool / handle`,
|
|
405
405
|
沙箱里还有 `nodeApiTraps()` 把 Node API 全挡了,允许的 `ctx` 动词白名单里**没有网络与文件系统**。
|
|
406
406
|
|
|
407
|
+
**为什么要读两个源(npm + GitHub)取更高版本**:npm 的注册表是权威源,但发布可能长时间卡在它的
|
|
408
|
+
**暂存区**里 —— 实测:`PUT 202`(已受理)之后等了 18 分钟,注册表和 tarball **仍然 404**,
|
|
409
|
+
而同版本号重发被拒(`409 Cannot publish over previously staged version`);
|
|
410
|
+
同期 GitHub 的 `package.json` 是**立刻**更新的。
|
|
411
|
+
所以检查同时读两个源、**取版本号更高的那个** —— 这样无论 npm 有没有追上都是对的。
|
|
412
|
+
「npm 优先、GitHub 兜底」在这里是错的 ✗:它会读到 npm 那个旧号,然后告诉你"已经是最新的"。
|
|
413
|
+
|
|
407
414
|
所以网络只能走**服务**:宿主半包 `inject: { optional: ['web'] }`,用 `ctx.web.fetch(...)` 抓远端
|
|
408
415
|
`package.json`,再用 `harness.handle('checkUpdate', …)` 注册;浏览器半包 `host.call('checkUpdate')` 调。
|
|
409
416
|
**客户端那边一点网络都没碰** —— `fetch` 陷阱原样留着。
|
|
@@ -441,6 +448,29 @@ node tools/audit-log.mjs --confirm <manifest> # 生成后:并入代理登
|
|
|
441
448
|
|
|
442
449
|
## 变更
|
|
443
450
|
|
|
451
|
+
### 1.26.0
|
|
452
|
+
|
|
453
|
+
- **检查更新改为同时读两个源,取版本号更高的那个**(npm 注册表 + GitHub 的 package.json)。
|
|
454
|
+
|
|
455
|
+
起因是这次发布踩到的事:`npm publish` 返回 `PUT 202`(已受理)却**长时间不进注册表** ——
|
|
456
|
+
等了 18 分钟,`dsh-wisp/1.25.0` 与 tarball 都还是 404;同号重发被拒
|
|
457
|
+
(`409 Cannot publish over previously staged version`);npm 状态页一切正常。
|
|
458
|
+
而 GitHub 上的 `package.json` 是立刻更新的。
|
|
459
|
+
|
|
460
|
+
于是设计成**取更高版本**而不是「npm 优先、GitHub 兜底」:
|
|
461
|
+
后者会读到 npm 那个旧号(当时是 0.6.0),然后告诉你「已经是最新的」✗。
|
|
462
|
+
取更高则无论 npm 有没有追上都是对的 ✓
|
|
463
|
+
|
|
464
|
+
- 宿主半包:`readVersion()` 单源 → `readPublishedVersion()` 多源并发,返回
|
|
465
|
+
`{ ok, latest, from, sources }`;每个源的成败**分别上报**,一个挂了不影响另一个;
|
|
466
|
+
- 客户端:把 `from` / `sources` 一起记进 `doctor().update.lastCheck` ——
|
|
467
|
+
两个源不一致时,「这个数字来自哪」比数字本身更需要能查;
|
|
468
|
+
- 预检 377 → **383 项**,含四种组合:两源一致 / npm 旧 GitHub 新 / npm 新 GitHub 旧 / 一个挂一个活。
|
|
469
|
+
|
|
470
|
+
- 上一版 1.25.1 只是换号重发(内容与 1.25.0 完全一致),代码无改动。
|
|
471
|
+
|
|
472
|
+
### 1.25.1
|
|
473
|
+
|
|
444
474
|
### 1.25.0
|
|
445
475
|
|
|
446
476
|
- **她真的会检查更新了。** 上一版只说"我做不到" —— 那一版没找对通道,这一版找到了:
|
package/lib/client.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.26.0'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -65,6 +65,8 @@ const WHATS_NEW = {
|
|
|
65
65
|
'1.23.0': '滚轮调大小、方向键挪位置、躲起来也能开菜单。',
|
|
66
66
|
'1.24.0': '她会报自己的版本,更新过会告诉你,还能把包名复制给你。',
|
|
67
67
|
'1.25.0': '她真的会去查有没有新版本了。',
|
|
68
|
+
'1.25.1': '内容与 1.25.0 相同,重新发布以修好安装源。',
|
|
69
|
+
'1.26.0': '检查更新同时读 npm 和 GitHub,取更高的那个版本。',
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -2050,9 +2052,11 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2050
2052
|
if (!res || res.ok !== true || typeof res.latest !== 'string') {
|
|
2051
2053
|
updateCheck = { state: 'failed', reason: (res && res.reason) || 'no-result', at: nowMs() }
|
|
2052
2054
|
} else if (compareVersions(VERSION, res.latest) < 0) {
|
|
2053
|
-
|
|
2055
|
+
/* from / sources 一起留着:两个源不一致时(npm 暂存、GitHub 已更新),
|
|
2056
|
+
"这个数字是哪儿来的"比数字本身更需要能查。 */
|
|
2057
|
+
updateCheck = { state: 'available', latest: res.latest, from: res.from ?? null, sources: res.sources ?? null, at: nowMs() }
|
|
2054
2058
|
} else {
|
|
2055
|
-
updateCheck = { state: 'current', latest: res.latest, at: nowMs() }
|
|
2059
|
+
updateCheck = { state: 'current', latest: res.latest, from: res.from ?? null, sources: res.sources ?? null, at: nowMs() }
|
|
2056
2060
|
}
|
|
2057
2061
|
return updateCheck
|
|
2058
2062
|
})
|
package/lib/client.template.js
CHANGED
|
@@ -51,7 +51,7 @@ window.__ModuleLoader__.load({
|
|
|
51
51
|
var exports = module.exports
|
|
52
52
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
|
|
53
53
|
|
|
54
|
-
const VERSION = '1.
|
|
54
|
+
const VERSION = '1.26.0'
|
|
55
55
|
|
|
56
56
|
/* 每一版一句话。**必须写当前版本** —— 预检里有一条断言盯着它,
|
|
57
57
|
忘了更新就会红,不会静默过期。 */
|
|
@@ -65,6 +65,8 @@ const WHATS_NEW = {
|
|
|
65
65
|
'1.23.0': '滚轮调大小、方向键挪位置、躲起来也能开菜单。',
|
|
66
66
|
'1.24.0': '她会报自己的版本,更新过会告诉你,还能把包名复制给你。',
|
|
67
67
|
'1.25.0': '她真的会去查有没有新版本了。',
|
|
68
|
+
'1.25.1': '内容与 1.25.0 相同,重新发布以修好安装源。',
|
|
69
|
+
'1.26.0': '检查更新同时读 npm 和 GitHub,取更高的那个版本。',
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
/* 更新这件事**这个插件自己做不到**,如实写在代码里,免得以后有人以为能:
|
|
@@ -2004,9 +2006,11 @@ body[data-ds-dark-theme] .wisp-layer{--wisp-say-fg:#eaf2f8;--wisp-say-bg:rgba(20
|
|
|
2004
2006
|
if (!res || res.ok !== true || typeof res.latest !== 'string') {
|
|
2005
2007
|
updateCheck = { state: 'failed', reason: (res && res.reason) || 'no-result', at: nowMs() }
|
|
2006
2008
|
} else if (compareVersions(VERSION, res.latest) < 0) {
|
|
2007
|
-
|
|
2009
|
+
/* from / sources 一起留着:两个源不一致时(npm 暂存、GitHub 已更新),
|
|
2010
|
+
"这个数字是哪儿来的"比数字本身更需要能查。 */
|
|
2011
|
+
updateCheck = { state: 'available', latest: res.latest, from: res.from ?? null, sources: res.sources ?? null, at: nowMs() }
|
|
2008
2012
|
} else {
|
|
2009
|
-
updateCheck = { state: 'current', latest: res.latest, at: nowMs() }
|
|
2013
|
+
updateCheck = { state: 'current', latest: res.latest, from: res.from ?? null, sources: res.sources ?? null, at: nowMs() }
|
|
2010
2014
|
}
|
|
2011
2015
|
return updateCheck
|
|
2012
2016
|
})
|
package/lib/index.js
CHANGED
|
@@ -13,6 +13,14 @@
|
|
|
13
13
|
// 2. Nothing else. `web` is declared as an OPTIONAL inject on purpose: a
|
|
14
14
|
// required inject that nothing provides would leave this fiber `waiting`
|
|
15
15
|
// forever, and the companion would simply disappear from the UI.
|
|
16
|
+
//
|
|
17
|
+
// WHY TWO SOURCES. npm's registry is the authoritative one, but a publish can sit
|
|
18
|
+
// in npm's staging queue for a long time (observed: `PUT 202`, still 404 after 18
|
|
19
|
+
// minutes, and re-publishing the same version is refused with 409). GitHub's raw
|
|
20
|
+
// manifest updated immediately in the same situation. So the check reads BOTH and
|
|
21
|
+
// reports the HIGHER version — which is correct whether or not npm has caught up.
|
|
22
|
+
// "npm first, GitHub as fallback" would read the stale npm number and claim you
|
|
23
|
+
// were up to date.
|
|
16
24
|
|
|
17
25
|
/** Cordis plugin name used by loader diagnostics. */
|
|
18
26
|
export const name = 'wisp'
|
|
@@ -23,42 +31,43 @@ export const name = 'wisp'
|
|
|
23
31
|
*/
|
|
24
32
|
export const inject = { required: [], optional: ['web'] }
|
|
25
33
|
|
|
26
|
-
/**
|
|
27
|
-
export const
|
|
34
|
+
/** The two places a published version can be read from. Overridable per install. */
|
|
35
|
+
export const DEFAULT_SOURCES = {
|
|
36
|
+
npm: 'https://registry.npmjs.org/dsh-wisp/latest',
|
|
37
|
+
github: 'https://raw.githubusercontent.com/969246694/dsh-wisp/main/package.json',
|
|
38
|
+
}
|
|
28
39
|
|
|
29
|
-
/** How long
|
|
40
|
+
/** How long the whole check may take before it is reported as a timeout. */
|
|
30
41
|
const CHECK_TIMEOUT_MS = 8000
|
|
31
42
|
|
|
32
43
|
/**
|
|
33
|
-
* Fetch
|
|
44
|
+
* Fetch one manifest and pull `version` out of it.
|
|
34
45
|
* @param web - the `web` service, or undefined when it is not loaded.
|
|
35
46
|
* @param url - the manifest URL.
|
|
36
47
|
* @param signal - optional cancellation signal.
|
|
37
|
-
* @returns
|
|
48
|
+
* @returns `{ ok, version }` or `{ ok: false, reason }` — never throws.
|
|
38
49
|
*/
|
|
39
|
-
export async function
|
|
40
|
-
if (!web || typeof web.fetch !== 'function') {
|
|
41
|
-
return { ok: false, reason: 'no-web-service', url }
|
|
42
|
-
}
|
|
50
|
+
export async function readVersion(web, url, signal) {
|
|
51
|
+
if (!web || typeof web.fetch !== 'function') return { ok: false, reason: 'no-web-service' }
|
|
43
52
|
try {
|
|
44
53
|
const res = await web.fetch({ url }, signal)
|
|
45
|
-
if (!res || typeof res.statusCode !== 'number') return { ok: false, reason: 'bad-result'
|
|
54
|
+
if (!res || typeof res.statusCode !== 'number') return { ok: false, reason: 'bad-result' }
|
|
46
55
|
/* Per the service contract a non-2xx response is a RESULT, not a throw —
|
|
47
56
|
so the status must be checked here instead of caught below. */
|
|
48
|
-
if (res.statusCode !== 200) return { ok: false, reason: 'http-' + res.statusCode
|
|
57
|
+
if (res.statusCode !== 200) return { ok: false, reason: 'http-' + res.statusCode }
|
|
49
58
|
const text = res.body && typeof res.body.content === 'string' ? res.body.content : ''
|
|
50
|
-
let
|
|
59
|
+
let version = null
|
|
51
60
|
try {
|
|
52
61
|
const parsed = JSON.parse(text)
|
|
53
|
-
if (parsed && typeof parsed.version === 'string')
|
|
62
|
+
if (parsed && typeof parsed.version === 'string') version = parsed.version
|
|
54
63
|
} catch (error) {
|
|
55
|
-
return { ok: false, reason: 'not-json'
|
|
64
|
+
return { ok: false, reason: 'not-json' }
|
|
56
65
|
}
|
|
57
|
-
if (
|
|
58
|
-
return { ok: true,
|
|
66
|
+
if (version === null) return { ok: false, reason: 'no-version-field' }
|
|
67
|
+
return { ok: true, version }
|
|
59
68
|
} catch (error) {
|
|
60
69
|
const detail = error && error.message ? String(error.message) : String(error)
|
|
61
|
-
return { ok: false, reason: 'fetch-failed', detail
|
|
70
|
+
return { ok: false, reason: 'fetch-failed', detail }
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -78,11 +87,41 @@ export function compareVersions(a, b) {
|
|
|
78
87
|
return 0
|
|
79
88
|
}
|
|
80
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Read every source and report the highest version found.
|
|
92
|
+
* @param web - the `web` service.
|
|
93
|
+
* @param urls - `{ npm, github }` (or any other set of named sources).
|
|
94
|
+
* @param signal - optional cancellation signal.
|
|
95
|
+
* @returns a plain, JSON-serializable verdict (it crosses the sandbox boundary).
|
|
96
|
+
*/
|
|
97
|
+
export async function readPublishedVersion(web, urls, signal) {
|
|
98
|
+
const names = Object.keys(urls ?? {})
|
|
99
|
+
const results = await Promise.all(names.map((key) => readVersion(web, urls[key], signal)))
|
|
100
|
+
const sources = {}
|
|
101
|
+
let best = null
|
|
102
|
+
let from = null
|
|
103
|
+
let anyOk = false
|
|
104
|
+
names.forEach((key, i) => {
|
|
105
|
+
const r = results[i]
|
|
106
|
+
sources[key] = r.ok ? { ok: true, version: r.version } : { ok: false, reason: r.reason, detail: r.detail }
|
|
107
|
+
if (!r.ok) return
|
|
108
|
+
anyOk = true
|
|
109
|
+
/* The HIGHER version wins, so a stale mirror cannot mask a newer release. */
|
|
110
|
+
if (best === null || compareVersions(best, r.version) < 0) {
|
|
111
|
+
best = r.version
|
|
112
|
+
from = key
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
if (!anyOk) {
|
|
116
|
+
const first = results.find((r) => r && !r.ok)
|
|
117
|
+
return { ok: false, reason: first ? first.reason : 'no-sources', sources }
|
|
118
|
+
}
|
|
119
|
+
return { ok: true, latest: best, from, sources }
|
|
120
|
+
}
|
|
121
|
+
|
|
81
122
|
/** Register the handler the browser half reaches through `host.call`. */
|
|
82
123
|
export function registerHandlers(ctx, options = {}) {
|
|
83
|
-
const
|
|
84
|
-
? options.updateUrl
|
|
85
|
-
: DEFAULT_UPDATE_URL
|
|
124
|
+
const urls = options.urls && typeof options.urls === 'object' ? options.urls : DEFAULT_SOURCES
|
|
86
125
|
/* `harness` is a sandbox global: the host half is evaluated inside a vm whose
|
|
87
126
|
context carries it. Guard the reference so this module also loads — and is
|
|
88
127
|
testable — outside that sandbox. */
|
|
@@ -99,13 +138,13 @@ export function registerHandlers(ctx, options = {}) {
|
|
|
99
138
|
timer = setTimeout(() => controller.abort(), CHECK_TIMEOUT_MS)
|
|
100
139
|
}
|
|
101
140
|
try {
|
|
102
|
-
const verdict = await readPublishedVersion(ctx && ctx.web,
|
|
141
|
+
const verdict = await readPublishedVersion(ctx && ctx.web, urls, controller ? controller.signal : undefined)
|
|
103
142
|
return { ...verdict, current, checkedAt: new Date().toISOString() }
|
|
104
143
|
} finally {
|
|
105
144
|
if (timer !== null && typeof clearTimeout === 'function') clearTimeout(timer)
|
|
106
145
|
}
|
|
107
146
|
})
|
|
108
|
-
return { registered: true, dispose,
|
|
147
|
+
return { registered: true, dispose, urls }
|
|
109
148
|
}
|
|
110
149
|
|
|
111
150
|
export function apply(ctx, config) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wisp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"description": "DeepSeek娘 — an unofficial floating desktop companion for the DeepSeek Harness UI. Zero dependencies; sprites inlined as data URIs; schedules on the Client timer service instead of the trapped browser timer globals.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
package/verify-wisp.mjs
CHANGED
|
@@ -531,8 +531,10 @@ head('1b. the host half answers checkUpdate through the platform web service')
|
|
|
531
531
|
check(Boolean(declaresWeb) && Array.isArray(inject.required) && !inject.required.includes('web'),
|
|
532
532
|
'and not as a required one — a missing service must not hide her', JSON.stringify(inject))
|
|
533
533
|
|
|
534
|
-
|
|
534
|
+
/* 每个 URL 一个态度:可以按 URL 给不同的结果,才能测"两个源"的组合。 */
|
|
535
|
+
const webWith = (byUrl) => ({
|
|
535
536
|
fetch: async (request) => {
|
|
537
|
+
const outcome = byUrl[request.url] ?? { status: 404, body: 'nope' }
|
|
536
538
|
if (outcome.throw) throw new Error(outcome.throw)
|
|
537
539
|
return {
|
|
538
540
|
url: request.url,
|
|
@@ -542,29 +544,69 @@ head('1b. the host half answers checkUpdate through the platform web service')
|
|
|
542
544
|
}
|
|
543
545
|
},
|
|
544
546
|
})
|
|
547
|
+
const U = { npm: 'https://npm/x', github: 'https://gh/y' }
|
|
548
|
+
const only = (key, outcome) => webWith({ [U[key]]: outcome })
|
|
545
549
|
|
|
546
|
-
const good = await readPublishedVersion(
|
|
547
|
-
check(good.ok === true && good.latest === '9.9.9'
|
|
550
|
+
const good = await readPublishedVersion(only('npm', { body: '{"name":"dsh-wisp","version":"9.9.9"}' }), U)
|
|
551
|
+
check(good.ok === true && good.latest === '9.9.9' && good.from === 'npm',
|
|
552
|
+
'a 200 with a version field is read correctly', JSON.stringify(good))
|
|
548
553
|
|
|
549
|
-
const notFound = await readPublishedVersion(
|
|
554
|
+
const notFound = await readPublishedVersion(only('npm', { status: 404, body: 'nope' }), U)
|
|
550
555
|
check(notFound.ok === false && notFound.reason === 'http-404',
|
|
551
556
|
'a non-2xx is a result, not a throw — and is reported as such', JSON.stringify(notFound))
|
|
552
557
|
|
|
553
|
-
const noWeb = await readPublishedVersion(undefined,
|
|
558
|
+
const noWeb = await readPublishedVersion(undefined, U)
|
|
554
559
|
check(noWeb.ok === false && noWeb.reason === 'no-web-service',
|
|
555
560
|
'without the web service it says so instead of throwing', JSON.stringify(noWeb))
|
|
556
561
|
|
|
557
|
-
const blewUp = await readPublishedVersion(
|
|
558
|
-
check(blewUp.ok === false && blewUp.reason === 'fetch-failed'
|
|
562
|
+
const blewUp = await readPublishedVersion(only('npm', { throw: 'socket closed' }), U)
|
|
563
|
+
check(blewUp.ok === false && blewUp.reason === 'fetch-failed',
|
|
559
564
|
'a transport failure is caught and reported', JSON.stringify(blewUp))
|
|
560
565
|
|
|
561
|
-
const notJson = await readPublishedVersion(
|
|
566
|
+
const notJson = await readPublishedVersion(only('npm', { body: '<html>404</html>' }), U)
|
|
562
567
|
check(notJson.ok === false && notJson.reason === 'not-json', 'a non-JSON body is reported', JSON.stringify(notJson))
|
|
563
568
|
|
|
564
|
-
const noField = await readPublishedVersion(
|
|
569
|
+
const noField = await readPublishedVersion(only('npm', { body: '{"name":"dsh-wisp"}' }), U)
|
|
565
570
|
check(noField.ok === false && noField.reason === 'no-version-field',
|
|
566
571
|
'a manifest without a version is reported', JSON.stringify(noField))
|
|
567
572
|
|
|
573
|
+
/* ---- 两个源:取更高的那个,而不是"谁先谁赢" ---- */
|
|
574
|
+
const bothAgree = await readPublishedVersion(webWith({
|
|
575
|
+
[U.npm]: { body: '{"version":"1.25.1"}' },
|
|
576
|
+
[U.github]: { body: '{"version":"1.25.1"}' },
|
|
577
|
+
}), U)
|
|
578
|
+
check(bothAgree.ok === true && bothAgree.latest === '1.25.1',
|
|
579
|
+
'when both sources agree the answer is that version', JSON.stringify(bothAgree))
|
|
580
|
+
check(bothAgree.sources.npm.ok === true && bothAgree.sources.github.ok === true,
|
|
581
|
+
'and both sources are reported individually', JSON.stringify(bothAgree.sources))
|
|
582
|
+
|
|
583
|
+
/* 真实情况:npm 卡在旧的 0.6.0,GitHub 已经是 1.25.1 —— 必须报 1.25.1 */
|
|
584
|
+
const staleNpm = await readPublishedVersion(webWith({
|
|
585
|
+
[U.npm]: { body: '{"version":"0.6.0"}' },
|
|
586
|
+
[U.github]: { body: '{"version":"1.25.1"}' },
|
|
587
|
+
}), U)
|
|
588
|
+
check(staleNpm.ok === true && staleNpm.latest === '1.25.1' && staleNpm.from === 'github',
|
|
589
|
+
'a stale npm cannot mask a newer GitHub release', JSON.stringify(staleNpm))
|
|
590
|
+
|
|
591
|
+
/* 反过来也一样:npm 更新时必须报 npm 那个 */
|
|
592
|
+
const newerNpm = await readPublishedVersion(webWith({
|
|
593
|
+
[U.npm]: { body: '{"version":"2.0.0"}' },
|
|
594
|
+
[U.github]: { body: '{"version":"1.25.1"}' },
|
|
595
|
+
}), U)
|
|
596
|
+
check(newerNpm.ok === true && newerNpm.latest === '2.0.0' && newerNpm.from === 'npm',
|
|
597
|
+
'and a newer npm wins over an older GitHub', JSON.stringify(newerNpm))
|
|
598
|
+
|
|
599
|
+
/* 一个挂了、另一个还在:仍然要有答案,并且说清哪个挂了 */
|
|
600
|
+
const oneDown = await readPublishedVersion(webWith({
|
|
601
|
+
[U.github]: { body: '{"version":"1.25.1"}' },
|
|
602
|
+
}), U)
|
|
603
|
+
check(oneDown.ok === true && oneDown.latest === '1.25.1' && oneDown.sources.npm.ok === false,
|
|
604
|
+
'one dead source does not break the check', JSON.stringify(oneDown))
|
|
605
|
+
|
|
606
|
+
const bothDown = await readPublishedVersion(webWith({ [U.npm]: { throw: 'x' } }), U)
|
|
607
|
+
check(bothDown.ok === false && bothDown.sources.npm.ok === false && bothDown.sources.github.ok === false,
|
|
608
|
+
'when every source fails the check fails — with per-source reasons', JSON.stringify(bothDown))
|
|
609
|
+
|
|
568
610
|
check(compareVersions('1.2.3', '1.2.4') === -1 && compareVersions('1.2.3', '1.2.3') === 0
|
|
569
611
|
&& compareVersions('1.3.0', '1.2.9') === 1, 'version comparison is numeric, not lexicographic',
|
|
570
612
|
`${compareVersions('1.10.0', '1.9.0')} for 1.10.0 vs 1.9.0`)
|
|
@@ -573,16 +615,17 @@ head('1b. the host half answers checkUpdate through the platform web service')
|
|
|
573
615
|
const previousSeat = globalThis.harness
|
|
574
616
|
const handlers = new Map()
|
|
575
617
|
globalThis.harness = { handle: (method, fn) => { handlers.set(method, fn); return () => handlers.delete(method) } }
|
|
576
|
-
const registered = registerHandlers({ web:
|
|
618
|
+
const registered = registerHandlers({ web: only('npm', { body: '{"version":"9.9.9"}' }) }, { urls: U })
|
|
577
619
|
check(registered.registered === true && handlers.has('checkUpdate'),
|
|
578
620
|
'registerHandlers puts checkUpdate on the harness seat', JSON.stringify({ registered: registered.registered }))
|
|
579
621
|
const answer = await handlers.get('checkUpdate')({ current: '1.0.0' })
|
|
580
622
|
check(answer.ok === true && answer.latest === '9.9.9' && answer.current === '1.0.0'
|
|
581
623
|
&& typeof answer.checkedAt === 'string',
|
|
582
624
|
'the handler returns a plain, serializable verdict', JSON.stringify(answer))
|
|
583
|
-
const
|
|
584
|
-
check(typeof
|
|
585
|
-
|
|
625
|
+
const urls = registered.urls ?? {}
|
|
626
|
+
check(typeof urls.npm === 'string' && urls.npm.startsWith('https://')
|
|
627
|
+
&& typeof urls.github === 'string' && urls.github.startsWith('https://'),
|
|
628
|
+
'and it reads from two real https sources', JSON.stringify(urls))
|
|
586
629
|
|
|
587
630
|
/* 座位不在时不许抛:宿主半包在别的运行时里也要能 import */
|
|
588
631
|
delete globalThis.harness
|
|
@@ -596,7 +639,7 @@ head('1b. the host half answers checkUpdate through the platform web service')
|
|
|
596
639
|
const effects = []
|
|
597
640
|
let appliedThrew = false
|
|
598
641
|
try {
|
|
599
|
-
mod.apply({ web:
|
|
642
|
+
mod.apply({ web: only('npm', { body: '{"version":"9.9.9"}' }), effect: (fn, label) => { effects.push(label); return () => {} } }, {})
|
|
600
643
|
} catch (error) { appliedThrew = true }
|
|
601
644
|
check(appliedThrew === false && handlers.has('checkUpdate') && effects.length === 1,
|
|
602
645
|
'apply() registers the handler and keeps its disposer on the fiber', effects.join(', '))
|