Move the support line from 0.1.7-alpha.1 to 0.1.7-rc.1 (peer floor `^0.1.7-rc.1`). 0.21.0-alpha.1 never reached npm, and once the code-card labels below are fixed the same build crashes on alpha.1 (its DiffBlock still reads the `labels.files` upstream deleted), so declaring only the host we verify against is the honest floor — and from rc.1 on the host enforces peers at boot anyway: a row whose `@deepseek-ai/dsh-*` peers do not satisfy the runtime is silently disabled. Dependencies: 28 devDependency pins and 14 peers move to 0.1.7-rc.1. rc.1 also raised the vendored runtime (cordis 4.0.4, schemastery 3.18.4, cordis-plugin-group 1.0.4, cordis-plugin-include 1.0.9) and publishes DSH cross-references as exact versions, so bumping schemastery/cordis alone left four unmet transitive peers — the three vendor packages are hoisted into devDependencies, which is what makes `pnpm peers check` clean again. One compile break: rc.1 added `CodeToolbarLabels` (codeLabel / wrapLabel / unwrapLabel, all required), made DiffBlockLabels / ReadBlockLabels extend it and deleted `DiffBlockLabels.files`. Fixing SideChatView then leaving markdown on the legacy banner would render two different code-card styles inside one side-chat view, so the plugin adopts the card everywhere: `markdownTextProps()` now passes `labels.code.toolbarLabels` with the host's own wording (codeBlock.title|wrap|unwrap, added to all 20 dictionaries; `sideChatBlockFiles` retires with the deleted footer). Adopting the card surfaced a silent regression the suite caught: the new toolbar shows `codeLabel` for languages the highlighter does not know, and mermaid has no shiki grammar, so the info string no longer reaches the DOM and the banner-text probe in mermaid.tsx matched nothing — mermaid fences stopped being swapped for diagrams. Detection now matches the FENCE BODY from the source that produced the render. `tests/market-manifest.spec.ts` pins every DSH peer as a floating caret range: an exact pin (the devDependency style) or a comparator without a prerelease would fail rc.1's preflight and silently stop the plugin from loading, and `pnpm peers check` cannot see that — it validates the resolved dev tree, not the range shape the host reads.
200 lines
9.6 KiB
Bash
200 lines
9.6 KiB
Bash
# =============================================================================
|
||
# scripts/e2e-common.sh — e2e-mount.sh / e2e-aggregate-mount.sh 的共享骨架:
|
||
# 日志三件套、前置校验、DSH_CMD / tarball 解析、scratch home 与清理 trap、
|
||
# scratch profile 三件套、dsh web 后台启动与就绪轮询。
|
||
#
|
||
# 纯开发侧库文件,不进 npm 包(package.json files 白名单无它);
|
||
# install.sh / install.ps1 随 npm 包独立分发,必须自包含,绝不 source 本文件。
|
||
#
|
||
# 用法:调用方先设 E2E_TAG(say/warn/die 的日志前缀)再 source:
|
||
# E2E_TAG=e2e-mount
|
||
# source "$SCRIPT_DIR/e2e-common.sh"
|
||
#
|
||
# 依赖调用方已 `set -euo pipefail` 并定义 ROOT(仓库根绝对路径)。
|
||
# 两侧脚本有意保留的差异(行为等价,靠参数 / 调用点区分):
|
||
# - DSH_HOME 子路径:mount 用 $SCRATCH/home,aggregate 直接用 $SCRATCH;
|
||
# - 就绪行 grep 正则:mount 要带 token 的完整 URL,aggregate 只取 origin
|
||
# (两侧的理由见各自脚本的就绪行注释);
|
||
# - 额外清理项:aggregate 的 fixture 打包 TMP_DIR(mount 不设即跳过)。
|
||
# =============================================================================
|
||
|
||
: "${E2E_TAG:?必须先设置 E2E_TAG(日志前缀)再 source e2e-common.sh}"
|
||
|
||
# ── 日志三件套 ───────────────────────────────────────────────────────────────
|
||
say() { printf '\033[32m[%s]\033[0m %s\n' "$E2E_TAG" "$*"; }
|
||
warn() { printf '\033[33m[%s]\033[0m %s\n' "$E2E_TAG" "$*" >&2; }
|
||
die() { printf '\033[31m[%s]\033[0m %s\n' "$E2E_TAG" "$*" >&2; exit 1; }
|
||
|
||
# ── 前置校验 ─────────────────────────────────────────────────────────────────
|
||
# e2e_require_cmd <cmd> [用途]:不在 PATH 上即 die(沿用两脚本原有报错文案)。
|
||
e2e_require_cmd() {
|
||
if ! command -v "$1" >/dev/null 2>&1; then
|
||
if [ $# -ge 2 ]; then
|
||
die "未找到 $1($2)"
|
||
else
|
||
die "未找到 $1"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# ── DSH_CMD 解析 ─────────────────────────────────────────────────────────────
|
||
# PATH 上的 dsh 优先,否则 npx 拉官方包(同 scripts/install.sh)。DSH_CMD
|
||
# 缺省值(`dsh`)由调用方从环境变量取好传入。
|
||
#
|
||
# 回退的 npx 版本是显式钉死的:不钉就会解析 npm 的 `latest` dist-tag,而
|
||
# 0.1.7 线目前只有预发布(`latest` 仍是 0.1.5-rc.3),冒烟会静静地挂到一
|
||
# 个插件已不支持的宿主上。**必须写精确版本,不能写 `@alpha`**:0.1.7-rc.1
|
||
# 上线时 `alpha` 指的是 0.1.7-alpha.2,rc.1 走的是 `next`。钉版必须与
|
||
# package.json 的 peer 下限同步。
|
||
DSH_NPX_SPEC="${DSH_NPX_SPEC:-@deepseek-ai/dsh@0.1.7-rc.1}"
|
||
e2e_resolve_dsh_cmd() {
|
||
local explicit="${DSH_CMD}"
|
||
if ! command -v "$DSH_CMD" >/dev/null 2>&1; then
|
||
if command -v npx >/dev/null 2>&1; then
|
||
say "PATH 上无 ${DSH_CMD},回退 npx -y --package ${DSH_NPX_SPEC}"
|
||
DSH_CMD="npx -y --package ${DSH_NPX_SPEC} dsh"
|
||
else
|
||
die "未找到 $DSH_CMD 或 npx;请先安装 DSH CLI(npm i -g @deepseek-ai/dsh)或用 DSH_CMD 指定"
|
||
fi
|
||
fi
|
||
e2e_check_dsh_version "$explicit"
|
||
}
|
||
|
||
# 解析出的 CLI 版本必须与 peer 下限同一条支持线。这不是洁癖:宿主只比
|
||
# 基线早一个预发布时,`dsh plugin add` 的 bundle 协调会静默不写
|
||
# `dsh.profile.bundles`——CLI 退出码仍是 0,lane 却挂在「挂载未注册」上,
|
||
# 排查方向完全被带偏(本机 PATH 上的 dsh 一直是上一版,0.1.6-alpha.1 与
|
||
# 0.1.6-alpha.2 各实测过一次)。DSH_CMD 显式给出时不拦(调用方明确知道自
|
||
# 己在挂什么),只告警。
|
||
e2e_check_dsh_version() {
|
||
local explicit="$1" actual expected
|
||
expected="${DSH_EXPECT_VERSION:-0.1.7-rc.1}"
|
||
# `dsh --version` 冷启动要走 npx,给足预算;拿不到版本就只告警。
|
||
actual="$($DSH_CMD --version 2>/dev/null | tr -d '[:space:]' | head -c 64 || true)"
|
||
case "$actual" in
|
||
"$expected"|"$expected"*) return 0 ;;
|
||
esac
|
||
if [ "$explicit" = "dsh" ] && [ -n "$actual" ]; then
|
||
die "PATH 上的 dsh 是 ${actual},本仓库的基线是 ${expected}(peer 下限同一支持线)。请改用 DSH_CMD='npx -y --package @deepseek-ai/dsh@${expected} dsh',或安装该版本。"
|
||
fi
|
||
warn "DSH_CMD 版本为 ${actual:-未知},基线是 ${expected}——真机证据落在非基线宿主上。"
|
||
}
|
||
|
||
# ── tarball 解析 ─────────────────────────────────────────────────────────────
|
||
# TARBALL 已显式给出时原样使用;否则从仓库根 glob 里选。多个候选时取 mtime
|
||
# 最新——`ls | head -1` 的字典序会拿到旧版本号的历史 tarball,把冒烟挂到
|
||
# 过期产物上。未找到候选时返回非零,由调用方按各自文案 die。
|
||
e2e_resolve_tarball() {
|
||
if [ -z "${TARBALL:-}" ]; then
|
||
TARBALL="$(ls -t "$ROOT"/dsh-better-sidebar-*.tgz 2>/dev/null | head -1 || true)"
|
||
local count
|
||
count="$(ls "$ROOT"/dsh-better-sidebar-*.tgz 2>/dev/null | wc -l | tr -d ' ' || true)"
|
||
[ "$count" -le 1 ] || warn "发现 $count 个 tarball,按 mtime 选用最新:$(basename "$TARBALL")(建议清理其余)"
|
||
fi
|
||
[ -n "$TARBALL" ] && [ -f "$TARBALL" ]
|
||
}
|
||
|
||
# ── scratch home 与清理 ──────────────────────────────────────────────────────
|
||
# 始终在本调用拥有的全新目录里运行:调用方给了 DSH_HOME_BASE(可能是真实
|
||
# ~/.dsh)时,只在其下新建子目录并只删除该子目录;缺省时直接用系统临时
|
||
# 目录。调用方提供的目录本身绝不写入或删除。
|
||
# $1 = mktemp 模板短名(dsh-e2e-mount / dsh-e2e-agg,便于区分两 lane 的残留)。
|
||
# DSH_HOME 用 $SCRATCH 还是其子路径由调用方自行 export(两 lane 的既有差异)。
|
||
e2e_make_scratch() {
|
||
if [ -n "${DSH_HOME_BASE:-}" ]; then
|
||
SCRATCH="$(mktemp -d "$DSH_HOME_BASE/$1.XXXXXX")"
|
||
else
|
||
SCRATCH="$(mktemp -d /tmp/$1.XXXXXX)"
|
||
fi
|
||
}
|
||
|
||
# EXIT trap 兜底清理:杀 dsh web 后台进程;清 aggregate lane 的 fixture 打包
|
||
# 目录(TMP_DIR——mount lane 不设该变量即整段跳过);最后按 KEEP_HOME 决定
|
||
# 是否删除 scratch。结尾显式 exit 回传 trap 触发时的退出码。
|
||
e2e_cleanup() {
|
||
local code=$?
|
||
if [ -n "${SERVER_PID:-}" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
|
||
kill "$SERVER_PID" 2>/dev/null || true
|
||
wait "$SERVER_PID" 2>/dev/null || true
|
||
fi
|
||
if [ -n "${TMP_DIR:-}" ] && [ -d "${TMP_DIR:-}" ]; then
|
||
rm -rf "$TMP_DIR"
|
||
fi
|
||
if [ -z "${KEEP_HOME:-}" ]; then
|
||
if [ -d "${SCRATCH:-}" ]; then
|
||
rm -rf "$SCRATCH"
|
||
fi
|
||
else
|
||
warn "KEEP_HOME 已设置,保留 $SCRATCH"
|
||
fi
|
||
exit "$code"
|
||
}
|
||
|
||
# ── scratch profile 三件套 ───────────────────────────────────────────────────
|
||
# 引导 scratch profile(web 模板,镜像 dsh initProfile);先写
|
||
# pnpm-workspace.yaml 的 minimumReleaseAgeExclude,避免 pnpm 11 拒绝 <24h 新版本
|
||
# ——同 install.sh。@deepseek-ai/* 通配与仓库根 pnpm-workspace.yaml 同策:钉的
|
||
# DSH alpha 常在发布后 24h 内跑 lane,没有豁免会被 minimumReleaseAge 直接拒装。
|
||
e2e_write_profile() {
|
||
mkdir -p "$1"
|
||
cat > "$1/package.json" <<EOF
|
||
{
|
||
"name": "dsh-profile-web",
|
||
"private": true,
|
||
"dependencies": {},
|
||
"dsh": {
|
||
"profile": {
|
||
"bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app"]
|
||
}
|
||
}
|
||
}
|
||
EOF
|
||
printf '[]\n' > "$1/cordis.patch.yml"
|
||
cat > "$1/pnpm-workspace.yaml" <<'EOF'
|
||
packages:
|
||
- .
|
||
|
||
nodeLinker: hoisted
|
||
autoInstallPeers: false
|
||
|
||
minimumReleaseAgeExclude:
|
||
- dsh-better-sidebar
|
||
- '@deepseek-ai/*'
|
||
EOF
|
||
}
|
||
|
||
# ── dsh web 启动 + 就绪轮询 ──────────────────────────────────────────────────
|
||
# 后台启动 dsh web(--port 0 = OS 分配,避免端口冲突;keyless 可起)。
|
||
# $1 = stdout 日志;$2 = stderr 日志(缺省合并进 $1:mount lane 单文件,
|
||
# aggregate lane 分 out/err 两个文件)。写入全局 SERVER_PID。
|
||
e2e_start_dsh_web() {
|
||
if [ $# -ge 2 ]; then
|
||
$DSH_CMD web --port "$PORT" >"$1" 2>"$2" &
|
||
else
|
||
$DSH_CMD web --port "$PORT" >"$1" 2>&1 &
|
||
fi
|
||
SERVER_PID=$!
|
||
}
|
||
|
||
# 轮询日志等待就绪行(最多 120s)。$1 = 参与轮询的日志文件;就绪行 ERE 与
|
||
# 多条命中的取舍(head/tail)由 E2E_READY_RE / E2E_READY_PICK 传入——这是
|
||
# 两条 lane 有意保留的差异(token URL vs 裸 origin,理由见各自脚本的就绪
|
||
# 行注释)。URL 提取统一为「命中串按空白分段取末段」:mount 的模式带
|
||
# `dsh web: ` 前缀(两段)故末段即完整 URL;aggregate 的模式不含空白故末段
|
||
# 即 origin——与原先两侧的 awk '{print $3}' / 直接取整行等价。
|
||
# 返回:0 = URL 已写入全局 URL;1 = 120s 超时;2 = 服务进程提前退出。
|
||
e2e_wait_dsh_web_ready() {
|
||
URL=""
|
||
local _
|
||
for _ in $(seq 1 120); do
|
||
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
|
||
return 2
|
||
fi
|
||
if URL="$(grep -oE "$E2E_READY_RE" "$1" | "${E2E_READY_PICK:-head}" -1 | awk '{print $NF}')" && [ -n "$URL" ]; then
|
||
return 0
|
||
fi
|
||
sleep 1
|
||
done
|
||
return 1
|
||
}
|