fix(build): compiled binary missing @modelcontextprotocol/sdk modules

shadcn (web devDependency) pulled a second peer copy of
@modelcontextprotocol/[email protected].1 (zod@3), so the server's zod@4 copy
lived in the npm cache as sdk/1.30.1_1. `deno compile --exclude-unused-npm`
embeds that _1 folder but the pruned snapshot resolves it as sdk/1.30.1,
so every SDK import failed at startup with ERR_MODULE_NOT_FOUND.

- drop the shadcn CLI dependency; vendor its tailwind.css (the only
  runtime use) as apps/web/app/shadcn-tailwind.css, removing the duplicate
- scripts/check-compiled-binary.sh: run the compiled binary with an empty
  env and `--role preflight`; it must load the full module graph and exit 2
  on config validation. Wired into `deno task ci` (check:binary) and the
  Dockerfile build stage.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-28 23:10:08 +08:00
1 parent 266d66ddbb
commit f05d6195a3
8 files changed
+158 -1148

No files matched your search

+2 -1
View File
@@ -16,7 +16,8 @@ RUN deno task check:tokens && cd apps/web && deno task build
# 权限在编译时固定:读写不限路径(库根/下载目录由用户挂载,位置不固定;写保护靠 /app root 只读 + 非 root 运行);
# --allow-run 只放 ffprobe(入库探测)、sha256sum(强制全量哈希走子进程,coreutils 自带)与 /app/umt-server 自身
# (fsproxy 把单个文件操作放进 `umt-server --role fsworker` 一次性子进程,挂死可 SIGKILL 回收;二进制必须装在 /app/umt-server)
RUN cd apps/server && deno task compile
# 编译后立即自检:空环境跑 --role preflight,模块图缺件(ERR_MODULE_NOT_FOUND)在构建期就失败,不留到容器启动
RUN cd apps/server && deno task compile && cd /src && sh scripts/check-compiled-binary.sh
FROM debian:trixie-slim
# gosu:root 修完 DATA_DIR 属主后降权;postgresql-client:备份/恢复脚本用 pg_dump/pg_restore/psql
+1 -1
View File
@@ -1,6 +1,6 @@
@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
@import "./shadcn-tailwind.css";
/* ───────────────────────── Tailwind configuration ───────────────────────── */
+100
View File
@@ -0,0 +1,100 @@
/* 取自 npm:[email protected] dist/tailwind.css(MIT),原样内置。
不再把 shadcn CLI 列为依赖:它经 zod@3 拉出第二份 @modelcontextprotocol/sdk(peer 变体),
deno compile --exclude-unused-npm 会按裁剪后的快照找 sdk/1.30.1、实际嵌入的是 sdk/1.30.1_1,编译产物启动即缺模块。
加组件用 deno run -A --no-lock npm:[email protected] add <name>,不要写进锁文件。 */
@theme inline {
@keyframes accordion-down {
from {
height: 0;
}
to {
height: var(
--radix-accordion-content-height,
var(--accordion-panel-height, auto)
);
}
}
@keyframes accordion-up {
from {
height: var(
--radix-accordion-content-height,
var(--accordion-panel-height, auto)
);
}
to {
height: 0;
}
}
}
/* Custom variants */
@custom-variant data-open {
&:where([data-state="open"]),
&:where([data-open]:not([data-open="false"])) {
@slot;
}
}
@custom-variant data-closed {
&:where([data-state="closed"]),
&:where([data-closed]:not([data-closed="false"])) {
@slot;
}
}
@custom-variant data-checked {
&:where([data-state="checked"]),
&:where([data-checked]:not([data-checked="false"])) {
@slot;
}
}
@custom-variant data-unchecked {
&:where([data-state="unchecked"]),
&:where([data-unchecked]:not([data-unchecked="false"])) {
@slot;
}
}
@custom-variant data-selected {
&:where([data-selected="true"]) {
@slot;
}
}
@custom-variant data-disabled {
&:where([data-disabled="true"]),
&:where([data-disabled]:not([data-disabled="false"])) {
@slot;
}
}
@custom-variant data-active {
&:where([data-state="active"]),
&:where([data-active]:not([data-active="false"])) {
@slot;
}
}
@custom-variant data-horizontal {
&:where([data-orientation="horizontal"]) {
@slot;
}
}
@custom-variant data-vertical {
&:where([data-orientation="vertical"]) {
@slot;
}
}
@utility no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
&::-webkit-scrollbar {
display: none;
}
}
-1
View File
@@ -41,7 +41,6 @@
"@tailwindcss/vite": "^4.1.13",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"shadcn": "^3.8.5",
"tailwindcss": "^4.1.13",
"tw-animate-css": "^1.4.0",
"vite": "^7.1.7",
+1
View File
@@ -16,6 +16,7 @@
"!spikes",
"!docs",
"!apps/web/app/app.css",
"!apps/web/app/shadcn-tailwind.css",
"!apps/web/app/lib/design-tokens.generated.ts"
]
},
+5 -1
View File
@@ -86,7 +86,11 @@
"command": "deno run -A scripts/check-copy-punctuation.mjs --strict"
},
"build": "cd apps/web && deno task build && cd ../server && deno task compile",
"ci": "deno task typecheck && deno task lint && deno task test && deno task check:tokens && deno task check:copy && deno task build",
"check:binary": {
"description": "编译产物自检:空环境跑 dist/umt-server --role preflight,确认完整模块图可加载(防 deno compile 漏嵌 npm 模块)",
"command": "sh scripts/check-compiled-binary.sh"
},
"ci": "deno task typecheck && deno task lint && deno task test && deno task check:tokens && deno task check:copy && deno task build && deno task check:binary",
"gen:tokens": "cd apps/web && deno task gen:tokens"
}
}
Generated
+31 -1144
View File
File diff suppressed because it is too large. Load diff
+18
View File
@@ -0,0 +1,18 @@
#!/bin/sh
# 编译产物自检(deno task ci 的最后一步):deno compile 的 --exclude-unused-npm 可能漏嵌 npm 包/子路径,
# 开发态与测试全绿但二进制启动即 ERR_MODULE_NOT_FOUND。这里用空环境跑 `--role preflight`:
# 它会先加载完整的服务端模块图(db/任务引擎/全部模块),然后才因缺配置以退出码 2 结束;
# 模块缺失会以退出码 1 + ERR_MODULE_NOT_FOUND 结束。
set -u
bin="${1:-dist/umt-server}"
case "$bin" in /*) ;; *) bin="$PWD/$bin" ;; esac
[ -x "$bin" ] || { echo "check-compiled-binary: 找不到编译产物 $bin(先跑 deno task build)" >&2; exit 1; }
out=$(cd /tmp && env -i PATH="$PATH" "$bin" --role preflight 2>&1)
code=$?
if [ "$code" -eq 2 ] && printf '%s' "$out" | grep -q '配置无效'; then
echo "check-compiled-binary: ok(模块图加载完整)"
exit 0
fi
echo "check-compiled-binary: 编译产物自检失败(退出码 $code):" >&2
printf '%s\n' "$out" >&2
exit 1