dsh-unlag 1.9.50 → 1.9.52
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 +1 -1
- package/package.json +2 -1
- package/scripts/live-smoke.mjs +105 -0
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ dsh plugin install dsh-unlag
|
|
|
81
81
|
npm test
|
|
82
82
|
```
|
|
83
83
|
|
|
84
|
-
对着正在运行的 App 走一遍真实判定(页面半、宿主半都是线上代码,用的是 App
|
|
84
|
+
对着正在运行的 App 走一遍真实判定(页面半、宿主半都是线上代码,用的是 App 现在渲染的启动清单;只回答「能不能刷」,绝不按按钮),并核对路由契约:读接口 200、写方法 405、不带页面头的按键路由 403:
|
|
85
85
|
|
|
86
86
|
```sh
|
|
87
87
|
npm run smoke
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-unlag",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.52",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A press applies plugin edits right away and brings a laggy interface back; automatic reloading stays off unless you turn it on, so the press is the only trigger, and nothing restarts.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"client.js",
|
|
16
16
|
"cordis.patch.yml",
|
|
17
17
|
"locale/*.json",
|
|
18
|
+
"scripts/live-smoke.mjs",
|
|
18
19
|
"README.md",
|
|
19
20
|
"icon.svg"
|
|
20
21
|
],
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { runInNewContext } from 'node:vm'
|
|
3
|
+
|
|
4
|
+
// The shipped page half, the shipped host half, and the running app: this smoke asks the real
|
|
5
|
+
// button whether it could rebuild right now, using the startup list the app really renders, and
|
|
6
|
+
// prints the answer. It never presses: only a person may swap plugin code. A host that is not
|
|
7
|
+
// listening is a skip, so the script is safe to run anywhere the app is not.
|
|
8
|
+
const HOST = process.env.DSH_UNLAG_HOST ?? 'http://127.0.0.1:52847'
|
|
9
|
+
const source = readFileSync(new URL('../client.js', import.meta.url), 'utf8')
|
|
10
|
+
|
|
11
|
+
async function json(path) {
|
|
12
|
+
const response = await fetch(`${HOST}${path}`, { headers: { 'x-dsh-unlag': '1' } })
|
|
13
|
+
if (!response.ok) throw new Error(`${path} answered ${response.status}`)
|
|
14
|
+
return response.json()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const stated = await json('/dsh-unlag/state').catch(() => null)
|
|
18
|
+
if (stated === null) {
|
|
19
|
+
console.log(`skip: no host at ${HOST}`)
|
|
20
|
+
process.exit(0)
|
|
21
|
+
}
|
|
22
|
+
const graph = (await json('/dsh-unlag/graph')).graph
|
|
23
|
+
const stale = (url) => String(url).replace(/rev=[0-9a-f]+/u, 'rev=0000000000ff')
|
|
24
|
+
const rows = [
|
|
25
|
+
{ kind: 'script', placement: 'head', text: 'window.__ModuleLoader__={};' },
|
|
26
|
+
...graph.batches.map((batch) => ({ kind: 'script-preload', src: stale(batch.url) })),
|
|
27
|
+
...graph.batches.map((batch) => ({ kind: 'script-src', placement: 'head', src: stale(batch.url) })),
|
|
28
|
+
{ kind: 'global', name: '__DSH_BOOT__', value: { ...graph, rev: '0000000000ff' } },
|
|
29
|
+
]
|
|
30
|
+
const frozen = { ...graph, rev: '0000000000ff' }
|
|
31
|
+
|
|
32
|
+
const requests = []
|
|
33
|
+
const fetchForPage = async (url, init = {}) => {
|
|
34
|
+
const target = String(url).startsWith('/') ? `${HOST}${url}` : String(url)
|
|
35
|
+
requests.push(target.replace(HOST, ''))
|
|
36
|
+
return fetch(target, init)
|
|
37
|
+
}
|
|
38
|
+
const location = { href: `${HOST}/`, replace: () => { throw new Error('the smoke never navigates') } }
|
|
39
|
+
let registration
|
|
40
|
+
const window = {
|
|
41
|
+
__ModuleLoader__: { load: (value) => { registration = value } },
|
|
42
|
+
__DSH_UNLAG_PROBE__: true,
|
|
43
|
+
__DSH_BOOT__: frozen,
|
|
44
|
+
dshDesktopBoot: { ready: async () => ({ injections: rows }) },
|
|
45
|
+
sessionStorage: { getItem: () => '0', setItem() {} },
|
|
46
|
+
location,
|
|
47
|
+
dispatchEvent: () => true,
|
|
48
|
+
}
|
|
49
|
+
const sandbox = {
|
|
50
|
+
window,
|
|
51
|
+
navigator: { language: 'en-US', userAgent: 'Mozilla/5.0 (Macintosh) Electron/44.7.0' },
|
|
52
|
+
location,
|
|
53
|
+
document: { activeElement: null, querySelector: () => null },
|
|
54
|
+
sessionStorage: window.sessionStorage,
|
|
55
|
+
fetch: fetchForPage,
|
|
56
|
+
URL,
|
|
57
|
+
Response,
|
|
58
|
+
Buffer,
|
|
59
|
+
console,
|
|
60
|
+
setTimeout,
|
|
61
|
+
clearTimeout,
|
|
62
|
+
requestAnimationFrame: (fn) => fn(),
|
|
63
|
+
Event,
|
|
64
|
+
CustomEvent,
|
|
65
|
+
JSON,
|
|
66
|
+
RegExp,
|
|
67
|
+
}
|
|
68
|
+
sandbox.globalThis = sandbox
|
|
69
|
+
runInNewContext(source, sandbox)
|
|
70
|
+
const probe = registration.factory(() => ({ createElement: () => null })).probe
|
|
71
|
+
|
|
72
|
+
// The routes' own contract, proven against the running app: the two reads answer reads only, a
|
|
73
|
+
// press route without the page's header refuses, and the verify route answers a verdict. None of
|
|
74
|
+
// this swaps plugin code; the reload route is never called here.
|
|
75
|
+
const contract = []
|
|
76
|
+
const status = async (path, init) => {
|
|
77
|
+
try {
|
|
78
|
+
const response = await fetch(`${HOST}${path}`, init)
|
|
79
|
+
await response.arrayBuffer()
|
|
80
|
+
return response.status
|
|
81
|
+
} catch {
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
contract.push(['state GET', await status('/dsh-unlag/state'), 200])
|
|
86
|
+
contract.push(['state DELETE', await status('/dsh-unlag/state', { method: 'DELETE' }), 405])
|
|
87
|
+
contract.push(['graph PUT', await status('/dsh-unlag/graph', { method: 'PUT' }), 405])
|
|
88
|
+
contract.push(['reload without header', await status('/dsh-unlag/reload', { method: 'POST' }), 403])
|
|
89
|
+
contract.push(['verify without header', await status('/dsh-unlag/verify', { method: 'POST' }), 403])
|
|
90
|
+
const contractHeld = contract.every(([, got, want]) => got === want)
|
|
91
|
+
|
|
92
|
+
const running = { version: stated.version, code: stated.code, shim: stated.shim }
|
|
93
|
+
const verdict = await probe.startupLooksServable()
|
|
94
|
+
console.log(JSON.stringify({
|
|
95
|
+
host: HOST,
|
|
96
|
+
version: running.version,
|
|
97
|
+
shimWrapped: running.shim?.wrapped === true,
|
|
98
|
+
batches: graph.batches.length,
|
|
99
|
+
rows: rows.length,
|
|
100
|
+
verdict,
|
|
101
|
+
holdReason: verdict ? null : probe.startupHoldReason(),
|
|
102
|
+
asked: requests,
|
|
103
|
+
contract: Object.fromEntries(contract.map(([label, got, want]) => [label, `${got}${got === want ? '' : ` (wanted ${want})`}`])),
|
|
104
|
+
}, null, 2))
|
|
105
|
+
process.exitCode = verdict === true && contractHeld ? 0 : 1
|