dsh-md-plus 0.1.0 → 0.2.1

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/CHANGELOG.md ADDED
@@ -0,0 +1,22 @@
1
+ # 更新日志
2
+
3
+ ## 0.2.1 —— 2026-10-16
4
+
5
+ - 修掉「同一份代码显示两遍」:`DSH_LANGS` 还是老版本的语言表,`dart`、`scala`、`vue`、`powershell`、`csv`、`diff` 等 66 个别名被当成「DSH 不认识」,插件抢先补了一层高亮;随后 DSH 的懒加载语法到位、React 把 plain `<pre>` 换成自己的高亮节点,插件那份副本成了野节点留在原地,于是两份都在。现在语言表对齐 108 条别名。
6
+ - 加了一层兜底:DSH 接管某块后,插件撤掉自己插的副本(清 `data-md-plus` 标记)。语言表以后再落后也不会留永久双份,最多白干一次。
7
+ - README:修正「`dart` 不在 DSH 语言表里」的错误说法,示例换成 DSH 真不认的 `gdscript`。
8
+ - 测试扩到 53 条,新增「DSH 接管后不留副本」的回归用例;补高亮的用例换成插件独有语言,不再拿 `dart` 当反例。
9
+
10
+ ## 0.2.0 —— 2026-10-08
11
+
12
+ - 修掉 mermaid 围栏在 DSH 0.2.0-rc.2 上画不出来(issue #1):头部语言名被占位文案「代码块」顶掉,插件读不到围栏语言;现在认不出标签就看正文的类型声明。
13
+ - 修掉渲染失败在页面底部堆积「炸弹图」,也不再让 mermaid 的错误卡片被当成图。
14
+ - 修掉 ≥4 列的表格出血到面板宽、右边被裁掉、滚动条要悬停才出现的问题:现在跟正文同宽,超出在表内横滚。
15
+ - mermaid 引擎 11.17.0 → 12.1.0:新增 usecase-beta / agentflow-beta,旧图种实测无回归;运行时约 3.4 MB → 5.0 MB。
16
+ - 测试扩到 52 条,测试与预览页改用聊天区的真实头部形状。
17
+ - 注意:mermaid 12 的新图种标识符只认字母数字下划线,中文要写进标签,`actor Customer("用户")` 可以。
18
+ - 注意:DSH 0.2.0-rc.2 上「给 DSH 不认识的语言补高亮」仍然失效,得靠宿主露出语言名,详见 README。
19
+
20
+ ## 0.1.0 —— 2026-09-29
21
+
22
+ 首次发布:代码块卡片重做、补 Shiki 语法高亮、自带 mermaid 渲染、表格样式重做。
package/README.md CHANGED
@@ -1,102 +1,77 @@
1
- # dsh-md-plus
2
-
3
- 给 DeepSeek Harness Web / Desktop 的 Markdown 显示增强插件。目前做四件事:**代码块卡片重做**(含消掉头部空白)、**补上 DSH 不认识的语言的语法高亮**、**自己渲染 mermaid 图形**、**表格样式重做**。
4
-
5
- ![dsh-md-plus 显示效果预览](<docs/preview.png>)
6
-
7
- ## 它解决什么
8
-
9
- ### 一、代码块样式
10
-
11
- DSH 原生的代码块头部是「小灰字 + 文字复制按钮」,没写语言时左边整片是空的,卡顶到第一行代码之间还留着一大段纯空白。本插件把它重做成一张完整的卡片:
12
-
13
- | 元素 | 改前 | 改后 |
14
- |---|---|---|
15
- | 头部左侧 | 空(无语言时) | `</>` 图标 + 语言名;无语言显示 `Plain text` |
16
- | 头部右侧 | 文字「复制」 | 图标按钮:不换行 + 复制 |
17
- | 顶栏背景 | 与卡片之间有一条白带 | 与卡片同色,整块一个卡片 |
18
- | 卡顶到代码 | 一大段空白 | 约一行代码高 |
19
-
20
- 样式是纯 CSS 打在 `.md-code-block` 上,所以**所有语言**(包括 DSH 自己高亮的 JSON/TS/Python…)都吃同一套外观。想微调间距就改 `src/client.js` 里 `banner` 的 `padding` 末位和 `pre` 的 `padding` 首位。
21
-
22
- ### 二、语法高亮
23
-
24
- DSH 只认死 26 种语言,其余走 `pre.plain` 纯文本回退——**没有颜色,看上去就是一堆源码**。`dart` 就不在表里:
25
-
26
- ```dart
27
- class CodeCard extends StatelessWidget {
28
- const CodeCard({super.key, required this.language});
29
-
30
- final String language;
31
- }
32
- ```
33
-
34
- 本插件把这类块捡出来用自带的 Shiki 重新上色;DSH 认的语言一律不碰(但样式照样吃)。
35
-
36
- ### 三、mermaid 图形
37
-
38
- mermaid 围栏由本插件自己渲染成 SVG,**不需要 dsh-mermaid**。头部右侧可以在「图形 / 代码」之间切换,复制按钮复制的仍是图源。配色与节点圆角 token 都在 `src/mermaid-theme.js`。
39
-
40
- 顺带修掉了 dsh-mermaid 一个真 bug:它靠**代码第一个词**判断图类型,而那 25 个词的名单里漏了 `graph`(`graph LR` 和 `flowchart LR` 等价),所以 `graph LR` 的图它一律画不出来。本插件改成读头部显示的语言名,两种写法都能画。
41
-
42
- ### 四、表格样式
43
-
44
- DSH 原生的表格是「无边框 + 半透明分隔线 + 表头没有底色」,表头和数据分不开,首列还贴着左边缘。照参考图重做成:
45
-
46
- | 元素 | 取值 |
47
- |---|---|
48
- | 表头底 / 字 | `#f7f7f7` / `#181818` 加粗 |
49
- | 边框 | `#ebebeb` 1px,只有横线、无竖线 |
50
- | 圆角 | 外 8px / 内 7px,只收外圈四角 |
51
- | 首 / 末列内边距 | 16px(DSH 原本清零) |
52
- | 悬停行 | `#fafafa` 轻微高亮 |
53
-
54
- 两个坑:`border-collapse: collapse` 下 Chrome 会忽略单元格圆角,所以走 `separate` + `border-spacing: 0`;另外裸写 `tr:last-child` 会连表头一起收圆(表头行是 `thead` 的唯一一行),四个角必须按上沿 `thead`、下沿 `tbody` 分开写。想调圆角改 `TABLE_VARS` 里的 `--md-plus-tbl-radius`(内圆角跟着减 1px)。
55
-
56
- 深色主题另配了一套(`#1f1f1f` 表头 / `#2f2f2f` 线)。抓手用的是结构选择器 `div:has(> table)` 而不是 DSH 的哈希类名,所以 DSH 换哈希也不会失效。
57
-
58
- ## 安装
59
-
60
- ```powershell
61
- dsh plugin --profile desktop add dsh-md-plus
62
- ```
63
-
64
- 或者:
65
-
66
- ```powershell
67
- dsh plugin --profile desktop add github:JosephHy/dsh-md-plus
68
- ```
69
-
70
-
71
- ## 卸载
72
-
73
- ```powershell
74
- dsh plugin --profile desktop remove dsh-md-plus
75
- ```
76
-
77
- ## 开发
78
-
79
- ```powershell
80
- npm install # 会顺带跑 prepare → build,装完 lib/ 就是新的
81
- npm run build # 产出 lib/ 下四个文件
82
- npm run check # 语法检查 + 45 条 host/client 行为测试
83
- npm run preview # 离线生成 preview.html(含真渲出来的 mermaid 图)
84
- npm run shot # 用 headless Chrome 把 preview.html 截成 docs/preview.png
85
- ```
86
-
87
-
88
- 注意测试跑的是 `lib/` 里的构建产物,所以顺序永远是 **先 `npm run build` 再 `npm test`**(`npm run check` 不含 build)。
89
-
90
-
91
- ## 已知边界
92
-
93
- - 两个运行时按需加载:高亮约 6.5 MB(171 门语言语法),mermaid 约 3.4 MB。都只在真正用到时才下载;**首次**遇到新语言时多花约 100ms 解析。
94
- - 依赖 DSH 的 `.md-code-block`、`[data-code-block-content]`、`[data-code-block-banner]`、`data-streaming` 这几个稳定钩子。DSH 升级若改了它们,需要跟着改 `src/client.js` 顶部的选择器常量。
95
- - 头部图标按钮里那个「不换行」是插件自己加的(`decorate()` 往 `.action` 里插一个 `<button>`)。参考图里那个 `→|` 图标原功能未知,先做成了横向滚动切换,要换功能改一处即可。
96
- - **滚轮缩放会接管页面滚动**:鼠标悬在图上时滚轮用来缩放,页面不会跟着滚。想改成按住 `Ctrl/⌘` 才缩放,只需在 `bindViewport()` 的 wheel 回调开头加一句 `if (!event.ctrlKey && !event.metaKey) return;`。
97
- - 很宽的图会被缩到容器宽度,字会变小;放大后靠拖拽看细节。
98
- - 目前只做代码块、mermaid 与表格。引用、列表间距、标题层级、目录等属于后续阶段。
99
-
100
- ## 许可证
101
-
102
- MIT,见 [LICENSE](./LICENSE)。
1
+ # dsh-md-plus
2
+
3
+ 给 DeepSeek Harness Web / Desktop 的 Markdown 显示增强插件,做四件事:重做代码块卡片、给 DSH 不认识的语言补语法高亮、自己渲染 mermaid 图形、重做表格样式。
4
+
5
+ ![dsh-md-plus 显示效果预览](<docs/preview.png>)
6
+
7
+ ## 它解决什么
8
+
9
+ ### 代码块样式
10
+
11
+ DSH 原生的头部是「小灰字 + 文字复制按钮」,没写语言时左边空一片,卡顶到第一行代码之间还留着大段空白。重做成一张完整的卡片:
12
+
13
+ | 元素 | 改前 | 改后 |
14
+ |---|---|---|
15
+ | 头部左侧 | 空(无语言时) | `</>` 图标 + 语言名,无语言显示 `Plain text` |
16
+ | 头部右侧 | 文字「复制」 | 图标按钮:不换行 + 复制 |
17
+ | 顶栏背景 | 与卡片之间有一条白带 | 与卡片同色 |
18
+ | 卡顶到代码 | 一大段空白 | 约一行代码高 |
19
+
20
+ 样式是纯 CSS 打在 `.md-code-block` 上,所以所有语言(包括 DSH 自己高亮的 JSON/TS/Python)都吃同一套外观。想调间距,改 `src/client.js` 里 banner 的 `padding` 末位和 pre 的 `padding` 首位。
21
+
22
+
23
+
24
+ ### mermaid 图形
25
+
26
+ mermaid 围栏由插件渲染成 SVG,头部右侧可在「图形 / 代码」之间切换,复制按钮复制的仍是图源。配色和节点圆角在 `src/mermaid-theme.js`。
27
+
28
+ 认图先看头部语言名(`mermaid` / `mmd` / `mermaidjs`),认不出再看正文里的类型声明(`flowchart`、`graph`、`sequenceDiagram` 等)。0.2.0-rc.2 上头部是占位文案,走的就是正文这条。
29
+
30
+ ### 表格样式
31
+
32
+ DSH 原生表格无边框、分隔线半透明、表头没底色,首列还贴着左边缘。重做成:
33
+
34
+ | 元素 | 取值 |
35
+ |---|---|
36
+ | 表头底 / 字 | `#f7f7f7` / `#181818` 加粗 |
37
+ | 边框 | `#ebebeb` 1px,只有横线 |
38
+ | 圆角 | 外 8px / 内 7px,只收外圈四角 |
39
+ | 首 / 末列内边距 | 16px(DSH 原本清零) |
40
+ | 悬停行 | `#fafafa` |
41
+
42
+ 深色主题另有一套(`#1f1f1f` 表头 / `#2f2f2f` 线)。抓手用结构选择器 `div:has(> table)` 而不是 DSH 的哈希类名,DSH 换哈希也不失效。
43
+
44
+ ## 安装与卸载
45
+
46
+ ```powershell
47
+ dsh plugin --profile desktop add dsh-md-plus # 也可以用 github:JosephHy/dsh-md-plus
48
+ dsh plugin --profile desktop remove dsh-md-plus
49
+ ```
50
+
51
+ ## 开发
52
+
53
+ ```powershell
54
+ npm install # 顺带跑 prepare → build
55
+ npm run build # 产出 lib/ 下四个文件
56
+ npm run check # 语法检查 + 53 条测试
57
+ npm run preview # 生成 preview.html(含真渲出来的 mermaid 图)
58
+ npm run shot # 用 headless Chrome 把 preview.html 截成 docs/preview.png
59
+ ```
60
+
61
+ 测试跑的是 `lib/` 里的产物,所以先 `npm run build` 再 `npm test`(`npm run check` 不含 build)。
62
+
63
+ 测试和预览页里的代码块都按聊天区的真实头部形状搭(`CodeToolbar`,语言名走占位回退)。
64
+
65
+ ## 已知边界
66
+
67
+ - 0.2.0-rc.2 起,头部那行语言名由宿主决定显示什么:认这门语言就显示语言名,不认就显示本地化占位文案(中文「代码块」)。认不出来就读不到围栏语言,插件无法转化,且还不报错。mermaid 这条改看正文:跳过 `%%` 注释和开头的 `---` frontmatter,正文里有行以 mermaid 类型关键字开头就当图。这条只管认图,不参与补高亮的判断。
68
+ - 同一个原因让补高亮在 0.2.0-rc.2 上基本失效:`gdscript`、`nushell` 这类 DSH 不认的语言,头部是占位文案,插件问不到 Shiki 该用哪套语法,只能没颜色。反过来 DSH 认得的那 108 种(`dart`、`vue`、`scala` …)由 DSH 自己上色,插件不碰。
69
+ - `DSH_LANGS` 是 DSH `LANG_ALIASES` 的快照(对齐 108 条)。DSH 加语言后这份快照会落后,落后只会让它多管一门 DSH 也在管的高亮 —— 撤回兜底能保证不出现双份,但会白干一次高亮;想干净就跟着同步。
70
+ - mermaid 12 的新图种(`usecase-beta`、`agentflow-beta`)标识符只认字母数字下划线,中文得写进标签:`actor Customer("用户")` 可以,`actor 用户` 会解析失败。
71
+ - 高亮约 6.4 MB、mermaid 约 5.0 MB,按需下载,第一次用到时多花约 100ms 解析。
72
+ - 依赖宿主 `.md-code-block`、`[data-code-block-content]`、`[data-code-block-banner]`、`data-streaming` 这几个钩子,DSH 改了要跟着改 `src/client.js` 顶部的常量和 `MMD_TYPE`。
73
+ - 目前只管代码块、mermaid 和表格。
74
+
75
+ ## 许可证
76
+
77
+ MIT,见 [LICENSE](./LICENSE)。
package/lib/client.js CHANGED
@@ -70,9 +70,76 @@ window.__ModuleLoader__.load({
70
70
  "less",
71
71
  "sql",
72
72
  "xml",
73
- "lua"
73
+ "lua",
74
+ "bat",
75
+ "batch",
76
+ "powershell",
77
+ "ps1",
78
+ "ps",
79
+ "fish",
80
+ "properties",
81
+ "dotenv",
82
+ "env",
83
+ "log",
84
+ "csv",
85
+ "diff",
86
+ "patch",
87
+ "http",
88
+ "rst",
89
+ "latex",
90
+ "tex",
91
+ "bibtex",
92
+ "bib",
93
+ "asciidoc",
94
+ "adoc",
95
+ "r",
96
+ "julia",
97
+ "jl",
98
+ "dart",
99
+ "scala",
100
+ "clojure",
101
+ "clj",
102
+ "erlang",
103
+ "erl",
104
+ "elixir",
105
+ "ex",
106
+ "exs",
107
+ "haskell",
108
+ "hs",
109
+ "fsharp",
110
+ "fs",
111
+ "fsi",
112
+ "fsx",
113
+ "vb",
114
+ "vbnet",
115
+ "perl",
116
+ "pl",
117
+ "pm",
118
+ "verilog",
119
+ "v",
120
+ "system-verilog",
121
+ "systemverilog",
122
+ "sv",
123
+ "svh",
124
+ "graphql",
125
+ "gql",
126
+ "proto",
127
+ "protobuf",
128
+ "hcl",
129
+ "tf",
130
+ "tfvars",
131
+ "nix",
132
+ "vue",
133
+ "svelte",
134
+ "make",
135
+ "makefile",
136
+ "mk",
137
+ "cmake",
138
+ "groovy",
139
+ "gradle"
74
140
  ]);
75
141
  var MMD_LANGS = /* @__PURE__ */ new Set(["mermaid", "mermaidjs", "mmd"]);
142
+ var MMD_TYPE = /^\s*(flowchart|graph|sequenceDiagram|classDiagram|stateDiagram(-v2)?|erDiagram|journey|gantt|pie|mindmap|timeline|gitGraph|quadrantChart|xychart-beta|sankey-beta|block-beta|packet-beta|architecture-beta|usecase-beta|agentflow-beta|C4Context|requirementDiagram|kanban|radar|treemap|venn|ishikawa|wardley|cynefin|treeView|zenuml)\b/im;
76
143
  function icon(paths) {
77
144
  var svg = "<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'>" + paths + "</svg>";
78
145
  return 'url("data:image/svg+xml,' + encodeURIComponent(svg) + '")';
@@ -177,7 +244,18 @@ window.__ModuleLoader__.load({
177
244
  // 最后一行的下边线交给 table 自己的边框,免得叠成双线
178
245
  TABLE + " > table tr:last-child td{border-bottom:0}",
179
246
  // 宽表横向扫读时给个落点
180
- TABLE + " > table tbody tr:hover td{background:var(--md-plus-tbl-hover)}"
247
+ TABLE + " > table tbody tr:hover td{background:var(--md-plus-tbl-hover)}",
248
+ // 宽表(≥4 列)不收着不行。宿主给这类表加了 .md-table-wide,并让它「出血」:
249
+ // 包裹层撑到面板宽,再用 padding-left 把表推到正文栏左侧。那个 padding 会吃掉
250
+ // 可用宽度,结果是表格比正文宽出一截、右边还被裁掉,而滚动条只在鼠标悬停时
251
+ // 才出现——文字表落进这个区间就很难读。这里按回正文栏宽,超出改为表内横滚。
252
+ // 特异性 (0,2,2),压得住宿主的 .Pg4CGa_body .md-table-wide (0,2,0)。
253
+ TABLE + ".md-table-wide{width:100%;max-width:100%;margin-inline:0;padding-inline:0;overflow-x:auto}",
254
+ // 塞得下就撑满正文栏,塞不下保持自然宽度让外面滚,不把列挤成一列一个字
255
+ TABLE + ".md-table-wide > table{width:100%;max-width:none;min-width:max-content}",
256
+ // 宿主原本是「悬停才出滚动条」,所以悬停时会把预留的滚动条高度收掉;
257
+ // 现在滚动条常驻,再收掉就会跳 5px,固定住。
258
+ TABLE + ".md-table-wide:hover{padding-bottom:var(--dsh-scrollbar-width,5px)}"
181
259
  ].join("\n");
182
260
  var scripts = /* @__PURE__ */ new Map();
183
261
  var pending = /* @__PURE__ */ new Set();
@@ -213,6 +291,22 @@ window.__ModuleLoader__.load({
213
291
  var info = banner?.firstElementChild;
214
292
  return (info?.textContent ?? "").trim().toLowerCase();
215
293
  }
294
+ function sniffDiagramType(code) {
295
+ var text = code;
296
+ if (text.startsWith("---")) {
297
+ var blank = text.indexOf("\n");
298
+ var close = blank < 0 ? -1 : text.indexOf("\n---", blank);
299
+ text = close < 0 ? "" : text.slice(close + 4);
300
+ }
301
+ var body = text.split("\n").filter((line) => !line.trimStart().startsWith("%%")).join("\n");
302
+ return MMD_TYPE.exec(body)?.[1] ?? null;
303
+ }
304
+ function diagramKind(block) {
305
+ if (MMD_LANGS.has(readLang(block))) return "diagram";
306
+ var pre = block.querySelector(CONTENT + " pre");
307
+ if (pre === null) return null;
308
+ return sniffDiagramType(pre.textContent ?? "") === null ? null : "diagram";
309
+ }
216
310
  function isOurs(node) {
217
311
  var element = node?.nodeType === 1 ? node : node?.parentElement;
218
312
  return element?.closest?.("." + HOST_CLASS + ", ." + BTN_CLASS + ", ." + MMD_CLASS) != null;
@@ -292,7 +386,7 @@ window.__ModuleLoader__.load({
292
386
  var copy = banner?.querySelector("button:not(." + BTN_CLASS + ")");
293
387
  var action = copy?.parentElement;
294
388
  if (action === null || action === void 0) return;
295
- var specs = MMD_LANGS.has(readLang(block)) ? MMD_BUTTONS : PLAIN_BUTTONS;
389
+ var specs = readLang(block) !== "" && diagramKind(block) !== null ? MMD_BUTTONS : PLAIN_BUTTONS;
296
390
  for (const existing of action.querySelectorAll("." + BTN_CLASS)) {
297
391
  if (specs.some((spec) => existing.classList.contains(spec.cls))) continue;
298
392
  existing.remove();
@@ -310,16 +404,26 @@ window.__ModuleLoader__.load({
310
404
  injected.add(created);
311
405
  }
312
406
  }
407
+ function releaseTakenOver(block, content) {
408
+ var host = content.querySelector("." + HOST_CLASS);
409
+ if (host === null) return false;
410
+ if (content.querySelector("pre.shiki:not(.md-plus-pre)") === null) return false;
411
+ host.remove();
412
+ block.removeAttribute(KEY_ATTR);
413
+ block.removeAttribute(DONE_ATTR);
414
+ return true;
415
+ }
313
416
  async function enhance(block) {
314
417
  if (!block.isConnected) return;
315
418
  if (block.closest("[data-streaming]") !== null) return;
316
419
  var content = block.querySelector(CONTENT);
317
420
  if (content === null) return;
421
+ if (releaseTakenOver(block, content)) return;
318
422
  var pre = content.querySelector("pre");
319
423
  if (pre === null || pre.classList.contains("shiki")) return;
320
424
  var lang = readLang(block);
321
425
  if (lang === "") return;
322
- if (MMD_LANGS.has(lang)) {
426
+ if (diagramKind(block) !== null) {
323
427
  await renderDiagram(block, content, pre);
324
428
  return;
325
429
  }
@@ -373,6 +477,10 @@ window.__ModuleLoader__.load({
373
477
  return;
374
478
  }
375
479
  if (typeof svg !== "string" || svg === "") return;
480
+ if (svg.includes('aria-roledescription="error"')) {
481
+ failDiagram(block, content, pre, key, new Error("mermaid \u8FD4\u56DE\u7684\u662F\u9519\u8BEF\u5361\u7247"));
482
+ return;
483
+ }
376
484
  if (!pre.isConnected || (pre.textContent ?? "").trim() !== code) {
377
485
  schedule(block);
378
486
  return;
@@ -491,7 +599,8 @@ window.__ModuleLoader__.load({
491
599
  content.querySelector("." + MMD_CLASS)?.remove();
492
600
  var note = content.querySelector("." + MMD_CLASS + "-error") ?? document.createElement("p");
493
601
  note.className = MMD_CLASS + "-error";
494
- note.textContent = "mermaid \u6E32\u67D3\u5931\u8D25\uFF1A" + (error?.message ?? String(error));
602
+ var reason = (error?.message ?? String(error)).split("\n")[0] ?? "";
603
+ note.textContent = "mermaid \u6E32\u67D3\u5931\u8D25\uFF1A" + reason;
495
604
  content.appendChild(note);
496
605
  block.setAttribute(KEY_ATTR, key);
497
606
  block.setAttribute(MMD_ATTR, "failed");
@@ -525,6 +634,8 @@ window.__ModuleLoader__.load({
525
634
  script.remove();
526
635
  }
527
636
  scripts.clear();
637
+ delete window[HL_GLOBAL];
638
+ delete window[MMD_GLOBAL];
528
639
  };
529
640
  }, "dsh-md-plus: code block styling, highlighting and diagrams");
530
641
  }
package/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // dsh-md-plus 宿主半:只做一件事——把两个浏览器端运行时经同源路由发出去。
2
2
  // 皮肤与行为全在浏览器半(lib/client.js),这里不做别的。
3
- import { readFile } from "node:fs/promises";
3
+ import { readFile, stat } from "node:fs/promises";
4
4
 
5
5
  export const name = "dsh-md-plus";
6
6
  export const inject = ["webServer"];
@@ -26,10 +26,20 @@ export function apply(ctx) {
26
26
  return;
27
27
  }
28
28
  try {
29
+ // 文件名不带内容指纹,原来的一年 immutable 会让升级后的运行时发不出去:
30
+ // 改成每次回源校验,没变就 304。
31
+ const info = await stat(source);
32
+ const etag = `W/"${info.size.toString(16)}-${Math.floor(info.mtimeMs).toString(16)}"`;
33
+ if (req.headers?.["if-none-match"] === etag) {
34
+ res.writeHead(304, { etag, "cache-control": "no-cache" });
35
+ res.end();
36
+ return;
37
+ }
29
38
  const body = await readFile(source);
30
39
  res.writeHead(200, {
31
40
  "content-type": "text/javascript; charset=utf-8",
32
- "cache-control": "public, max-age=31536000, immutable",
41
+ "cache-control": "no-cache",
42
+ etag,
33
43
  });
34
44
  res.end(req.method === "HEAD" ? undefined : body);
35
45
  } catch {