dsh-unlag 1.9.85 → 1.9.87
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 -0
- package/package.json +1 -1
- package/scripts/live-smoke.mjs +83 -1
package/README.md
CHANGED
|
@@ -72,6 +72,7 @@ dsh plugin install dsh-unlag
|
|
|
72
72
|
## 排错
|
|
73
73
|
|
|
74
74
|
- 日志:`~/.dsh/unlag-reload.log`,换代码/跳过/被拦各一行,含原因。
|
|
75
|
+
- 演练:`npm run live-smoke` 不按按钮,只按当前启动清单把下一个文档要加载的字节在沙箱里跑一遍,看每个插件是否都注册(`rehearsal.ok`)——它证明的是「按下去之后新页面能起来」,不换任何代码。
|
|
75
76
|
- 状态:`GET /dsh-unlag/state` → `{"version":"<当前版本>","code":"<index.js 写入时间>","plugins":true,"page":true,"shim":{...}}`。`code` 用来分清「版本号没刷」和「旧代码真的还在跑」。
|
|
76
77
|
- 重建后的日志带起页面代次:`interface rebuilt; the page runs the plugins as served now (page <rev>)`,据此判断新文档加载的是新脚本还是旧脚本。
|
|
77
78
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-unlag",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.87",
|
|
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",
|
package/scripts/live-smoke.mjs
CHANGED
|
@@ -89,6 +89,87 @@ contract.push(['reload without header', await status('/dsh-unlag/reload', { meth
|
|
|
89
89
|
contract.push(['verify without header', await status('/dsh-unlag/verify', { method: 'POST' }), 403])
|
|
90
90
|
const contractHeld = contract.every(([, got, want]) => got === want)
|
|
91
91
|
|
|
92
|
+
// Rehearsal, not a press: run the bytes the next document would load for the startup list the
|
|
93
|
+
// desktop shell froze, with the same loader surface a document gives them, and see whether every
|
|
94
|
+
// plugin in the list registers. Nothing is swapped here; when this passes, the press's own page
|
|
95
|
+
// load is the only unproven step.
|
|
96
|
+
async function rehearseBoot() {
|
|
97
|
+
const stamped = (url) => {
|
|
98
|
+
const path = String(url).startsWith('/') ? String(url) : `/${String(url)}`
|
|
99
|
+
return `${HOST}${path}`
|
|
100
|
+
}
|
|
101
|
+
const registered = new Set()
|
|
102
|
+
const load = (row) => {
|
|
103
|
+
if (row !== null && typeof row === 'object' && typeof row.id === 'string') registered.add(row.id)
|
|
104
|
+
}
|
|
105
|
+
const noop = () => {}
|
|
106
|
+
const element = () => ({ style: {}, setAttribute: noop, append() {}, appendChild() {}, remove() {}, addEventListener: noop, removeEventListener: noop, classList: { add: noop, remove: noop }, querySelector: () => null })
|
|
107
|
+
const rehearsalWindow = {
|
|
108
|
+
__ModuleLoader__: { load, ensure: () => ({}) },
|
|
109
|
+
document: { head: element(), body: element(), createElement: element, querySelector: () => null, querySelectorAll: () => [], getElementById: () => null, addEventListener: noop, removeEventListener: noop },
|
|
110
|
+
location: { href: `${HOST}/`, origin: HOST, pathname: '/', search: '', hash: '', replace: noop, assign: noop, reload: noop },
|
|
111
|
+
navigator: { language: 'en-US', userAgent: 'Mozilla/5.0 (Macintosh) Electron/44.7.0', platform: 'MacIntel' },
|
|
112
|
+
sessionStorage: { getItem: () => null, setItem: noop, removeItem: noop },
|
|
113
|
+
localStorage: { getItem: () => null, setItem: noop, removeItem: noop },
|
|
114
|
+
matchMedia: () => ({ matches: false, addEventListener: noop, removeEventListener: noop }),
|
|
115
|
+
requestAnimationFrame: (fn) => { try { fn(0) } catch {} return 0 },
|
|
116
|
+
cancelAnimationFrame: noop,
|
|
117
|
+
MutationObserver: class { observe() {} disconnect() {} takeRecords() { return [] } },
|
|
118
|
+
IntersectionObserver: class { observe() {} disconnect() {} unobserve() {} },
|
|
119
|
+
ResizeObserver: class { observe() {} disconnect() {} unobserve() {} },
|
|
120
|
+
addEventListener: noop,
|
|
121
|
+
removeEventListener: noop,
|
|
122
|
+
dispatchEvent: () => true,
|
|
123
|
+
fetch: async (url, init) => fetch(stamped(url), init),
|
|
124
|
+
URL,
|
|
125
|
+
Response,
|
|
126
|
+
Request,
|
|
127
|
+
Headers,
|
|
128
|
+
AbortController,
|
|
129
|
+
Blob,
|
|
130
|
+
TextEncoder,
|
|
131
|
+
TextDecoder,
|
|
132
|
+
URLSearchParams,
|
|
133
|
+
console,
|
|
134
|
+
setTimeout,
|
|
135
|
+
clearTimeout,
|
|
136
|
+
queueMicrotask,
|
|
137
|
+
performance,
|
|
138
|
+
Event,
|
|
139
|
+
CustomEvent,
|
|
140
|
+
JSON,
|
|
141
|
+
Math,
|
|
142
|
+
Date,
|
|
143
|
+
Promise,
|
|
144
|
+
RegExp,
|
|
145
|
+
Error,
|
|
146
|
+
Buffer,
|
|
147
|
+
}
|
|
148
|
+
rehearsalWindow.window = rehearsalWindow
|
|
149
|
+
rehearsalWindow.globalThis = rehearsalWindow
|
|
150
|
+
rehearsalWindow.self = rehearsalWindow
|
|
151
|
+
rehearsalWindow.document.defaultView = rehearsalWindow
|
|
152
|
+
const ran = []
|
|
153
|
+
for (const batch of graph.batches) {
|
|
154
|
+
const path = String(batch.url).startsWith('/') ? String(batch.url) : `/${String(batch.url)}`
|
|
155
|
+
const response = await fetch(`${HOST}${path}`, { headers: { 'x-dsh-unlag': '1' } })
|
|
156
|
+
const body = await response.text()
|
|
157
|
+
ran.push({ url: path, status: response.status, bytes: body.length })
|
|
158
|
+
if (!response.ok) continue
|
|
159
|
+
try {
|
|
160
|
+
runInNewContext(body, rehearsalWindow, { timeout: 5000 })
|
|
161
|
+
} catch (error) {
|
|
162
|
+
ran[ran.length - 1].threw = String(error?.message ?? error)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const wanted = graph.entries.map((entry) => (typeof entry === 'string' ? entry : entry?.id)).filter((id) => typeof id === 'string')
|
|
166
|
+
const missing = wanted.filter((id) => !registered.has(id))
|
|
167
|
+
return { registered: registered.size, wanted: wanted.length, missing, ran }
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const rehearsal = await rehearseBoot().catch((error) => ({ threw: String(error?.message ?? error) }));
|
|
171
|
+
const rehearsalOk = rehearsal.threw === undefined && rehearsal.missing !== undefined && rehearsal.missing.length === 0 && rehearsal.ran.every((row) => row.threw === undefined)
|
|
172
|
+
|
|
92
173
|
const running = { version: stated.version, code: stated.code, shim: stated.shim }
|
|
93
174
|
const verdict = await probe.startupLooksServable()
|
|
94
175
|
console.log(JSON.stringify({
|
|
@@ -99,7 +180,8 @@ console.log(JSON.stringify({
|
|
|
99
180
|
rows: rows.length,
|
|
100
181
|
verdict,
|
|
101
182
|
holdReason: verdict ? null : probe.startupHoldReason(),
|
|
183
|
+
rehearsal: { ok: rehearsalOk, ...rehearsal },
|
|
102
184
|
asked: requests,
|
|
103
185
|
contract: Object.fromEntries(contract.map(([label, got, want]) => [label, `${got}${got === want ? '' : ` (wanted ${want})`}`])),
|
|
104
186
|
}, null, 2))
|
|
105
|
-
process.exitCode = verdict === true && contractHeld ? 0 : 1
|
|
187
|
+
process.exitCode = verdict === true && contractHeld && rehearsalOk ? 0 : 1
|