dsh-unlag 1.9.21 → 1.9.23

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 CHANGED
@@ -65,6 +65,8 @@ dsh plugin install dsh-unlag
65
65
 
66
66
  官方的插件资源路由对未知修订返回 404。本插件对旧页面记着的旧修订返回当前内容:桌面版在窗口启动时固定了启动清单,页面按旧修订请求脚本是常态,返回 404 会让新页面起不来、App 直接退出。这是本插件的兼容措施,不是官方行为;不需要时可以整个关掉(设置里两行都关)。
67
67
 
68
+ 同一原因下的第二种情况:启动清单里记着的插件在配置里已经被关掉或删掉了,官方路由对那一行只会 404。本插件改为返回一段空脚本占位,页面起来时不再加载这个插件,App 照常起来;日志会写明哪个插件被留下,`/dsh-unlag/verify` 的结论里也带 `leftOut` 字段。
69
+
68
70
  ## 排错
69
71
 
70
72
  - 日志:`~/.dsh/unlag-reload.log`,换代码/跳过/被拦各一行,含原因。
package/client.js CHANGED
@@ -606,7 +606,15 @@ window.__ModuleLoader__.load({
606
606
  : BUTTON_STYLE;
607
607
 
608
608
  const onClick = async () => {
609
- if (pending || pressInFlight) return;
609
+ // A press already running is joined, and the press that joined says so instead of nothing.
610
+ if (pressInFlight) {
611
+ setNotice(COPY.applying);
612
+ return;
613
+ }
614
+ if (pending) {
615
+ setNotice(COPY.applying);
616
+ return;
617
+ }
610
618
  // A press the page already answered is not answered twice. Saying so beats a dead button.
611
619
  if (Date.now() - stampRead() < 3000) { // ui-copy-ignore
612
620
  setNotice(COPY.applying);
package/index.js CHANGED
@@ -940,6 +940,35 @@ async function regroupedBundleAnswer(clientModules, graph, request) {
940
940
  return { body, contentType: 'text/javascript; charset=utf-8', ids: request.ids }
941
941
  }
942
942
 
943
+ /**
944
+ * A stale request can name a plugin the profile no longer has at all: it was turned off or removed
945
+ * after the document that asks was made. Every plugin in the request is still served on its own, so
946
+ * the ones that remain are answered and the ones that are gone take a comment's place, which a
947
+ * script can hold without registering anything. The app's own answer here is 404, and a document
948
+ * booting from an old startup list that meets one ends the whole app: a boot that comes up without
949
+ * a plugin it no longer has beats that.
950
+ */
951
+ async function survivingBundleAnswer(clientModules, graph, request) {
952
+ if (request.sourceMap) return null
953
+ const entries = Array.isArray(graph?.entries) ? graph.entries : []
954
+ const served = new Set(entries.filter((row) => typeof row?.id === 'string').map((row) => row.id))
955
+ const gone = request.ids.filter((id) => !served.has(id))
956
+ if (gone.length === 0) return null
957
+ const parts = []
958
+ for (const id of request.ids) {
959
+ if (served.has(id)) {
960
+ const body = await currentEntryBody(clientModules, graph, id)
961
+ if (body === null) return null
962
+ parts.push(body)
963
+ continue
964
+ }
965
+ parts.push(Buffer.from(`/* ${id.replace(/[^a-z0-9@/._-]/giu, '')} is not part of the plugin set served now */`))
966
+ }
967
+ if (parts.length === 0) return null
968
+ const body = Buffer.concat(parts.flatMap((part, index) => (index === parts.length - 1 ? [part] : [part, Buffer.from('\n;\n')])))
969
+ return { body, contentType: 'text/javascript; charset=utf-8', ids: request.ids, gone }
970
+ }
971
+
943
972
  /**
944
973
  * The plugin set one stale request names, answered at the revision the app serves now. Only a body
945
974
  * that really contains every plugin it claims is handed back; anything unsure answers null, so the
@@ -971,7 +1000,7 @@ async function currentBundleAnswer(clientModules, method, url) {
971
1000
  }
972
1001
  }
973
1002
  }
974
- return regroupedBundleAnswer(clientModules, graph, request)
1003
+ return (await regroupedBundleAnswer(clientModules, graph, request)) ?? survivingBundleAnswer(clientModules, graph, request)
975
1004
  }
976
1005
 
977
1006
  /** Answer one stale request over the plain request/response route an app registers. */
@@ -988,7 +1017,16 @@ async function serveCurrentBundle(clientModules, req, res) {
988
1017
  'content-length': String(answer.body.length),
989
1018
  })
990
1019
  res.end(method === 'HEAD' ? undefined : answer.body)
991
- log(`served ${answer.ids.join(', ')} at the current revision: a page asked for an older one`)
1020
+ if (Array.isArray(answer.gone) && answer.gone.length > 0) {
1021
+ const kept = answer.ids.filter((id) => !answer.gone.includes(id))
1022
+ log(
1023
+ kept.length === 0
1024
+ ? `answered an older set with an empty script in place of ${answer.gone.join(', ')}: the app would have ended on the table's 404`
1025
+ : `served ${kept.join(', ')} at the current revision and left out ${answer.gone.join(', ')}: a page asked for an older set`,
1026
+ )
1027
+ } else {
1028
+ log(`served ${answer.ids.join(', ')} at the current revision: a page asked for an older one`)
1029
+ }
992
1030
  return true
993
1031
  } catch (error) {
994
1032
  log(`cannot serve a bundle from an older revision: ${error?.message ?? error}`)
@@ -1037,7 +1075,16 @@ async function serveCurrentBundleResource(clientModules, request) {
1037
1075
  if (method !== 'GET' && method !== 'HEAD') return null
1038
1076
  const answer = await currentBundleAnswer(clientModules, method, request?.url)
1039
1077
  if (answer === null) return null
1040
- log(`served ${answer.ids.join(', ')} at the current revision: a page asked for an older one`)
1078
+ if (Array.isArray(answer.gone) && answer.gone.length > 0) {
1079
+ const kept = answer.ids.filter((id) => !answer.gone.includes(id))
1080
+ log(
1081
+ kept.length === 0
1082
+ ? `answered an older set with an empty script in place of ${answer.gone.join(', ')}: the app would have ended on the table's 404`
1083
+ : `served ${kept.join(', ')} at the current revision and left out ${answer.gone.join(', ')}: a page asked for an older set`,
1084
+ )
1085
+ } else {
1086
+ log(`served ${answer.ids.join(', ')} at the current revision: a page asked for an older one`)
1087
+ }
1041
1088
  return new Response(method === 'HEAD' ? null : answer.body, {
1042
1089
  status: 200,
1043
1090
  headers: {
@@ -1361,10 +1408,19 @@ export async function verifyStartupRows(graph, fetchBody, rows) {
1361
1408
  wanted.push({ url, ids: request === null ? [] : request.ids })
1362
1409
  }
1363
1410
  if (wanted.length === 0) return { status: 'blocked', reason: 'list', detail: 'the startup list names no script' }
1411
+ /**
1412
+ * A row can name a plugin the profile no longer has: the document that made it was composed
1413
+ * before that plugin was turned off. The route answers such a row with a stand-in that registers
1414
+ * nothing, so the boot comes up without the plugin; the check reads the same way and does not
1415
+ * demand a registration the set cannot give.
1416
+ */
1417
+ const served = new Set(entries.keys())
1418
+ const leftOut = new Set()
1364
1419
  const listed = new Set()
1365
1420
  const bodies = []
1366
1421
  for (const item of wanted) {
1367
- for (const id of item.ids) listed.add(id)
1422
+ for (const id of item.ids) if (served.has(id)) listed.add(id)
1423
+ for (const id of item.ids) if (!served.has(id)) leftOut.add(id)
1368
1424
  let response = null
1369
1425
  try {
1370
1426
  response = await fetchBody(item.url)
@@ -1380,8 +1436,9 @@ export async function verifyStartupRows(graph, fetchBody, rows) {
1380
1436
  const complaint = parseAsScript(text, item.url)
1381
1437
  if (complaint !== null) return { status: 'blocked', reason: 'parse', detail: `${item.url}: ${complaint}` }
1382
1438
  const registered = registeredIds(text)
1383
- const missing = item.ids.filter((id) => !registered.has(id))
1384
- if (item.ids.length > 0 && missing.length > 0) {
1439
+ const required = item.ids.filter((id) => served.has(id))
1440
+ const missing = required.filter((id) => !registered.has(id))
1441
+ if (required.length > 0 && missing.length > 0) {
1385
1442
  return { status: 'blocked', reason: 'register', detail: `${item.url} does not register ${missing.join(', ')}` }
1386
1443
  }
1387
1444
  bodies.push({
@@ -1418,6 +1475,7 @@ export async function verifyStartupRows(graph, fetchBody, rows) {
1418
1475
  reason: 'ok', // ui-copy-ignore
1419
1476
  revision: typeof graph?.rev === 'string' ? graph.rev : null,
1420
1477
  scripts: bodies.map((body) => ({ url: body.url, digest: body.digest, ids: body.ids })),
1478
+ ...(leftOut.size > 0 ? { leftOut: [...leftOut] } : {}),
1421
1479
  }
1422
1480
  }
1423
1481
 
@@ -1508,6 +1566,9 @@ function serveButton(ctx, controller, config) {
1508
1566
  } catch (error) {
1509
1567
  verdict = { status: 'blocked', reason: 'list', detail: String(error?.message ?? error) }
1510
1568
  }
1569
+ if (Array.isArray(verdict?.leftOut) && verdict.leftOut.length > 0) {
1570
+ log(`a startup row names a plugin this app no longer serves: ${verdict.leftOut.join(', ')}; the page comes up without it`)
1571
+ }
1511
1572
  sendJson(res, 200, verdict)
1512
1573
  } catch (error) {
1513
1574
  log(`cannot answer ${BASE_PATH}/verify: ${error?.message ?? error}`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-unlag",
3
- "version": "1.9.21",
3
+ "version": "1.9.23",
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",