feat: 支持官方 CLI 一行安装(dsh plugin add dsh-better-sidebar)

- package.json 声明 dsh.bundle.patch(./cordis.patch.yml),随包发布挂载
  patch;dsh plugin --profile <name> add dsh-better-sidebar@<v> 自动完成
  安装 + 注册到 dsh.profile.bundles + 下次启动挂载(无需手写挂载行)
- cordis.patch.yml:bundle 挂载层(insert better-sidebar)
- scripts/install.sh:改走官方 CLI;预写 allowBuilds(node-pty/protobufjs)
  规避 pnpm 11 strict-dep-builds 非零退出;幂等移除旧手动挂载行防双挂载
- README(EN):安装置顶官方 CLI 一行命令

v0.10.2
This commit is contained in:
Menghuan1918
2026-08-13 23:19:05 +08:00
parent e515d74f77
commit 9fdb44d9f9
5 changed files with 160 additions and 106 deletions
+89 -54
View File
@@ -1,15 +1,20 @@
#!/usr/bin/env bash
# =============================================================================
# dsh-better-sidebar 一键安装脚本(npm 包方式)
# dsh-better-sidebar 一键安装脚本(官方 CLI 方式)
#
# 把插件以 npm 包形式装进 DSH 的 web profile(挂载仍走 cordis.patch.yml,
# 符合仓库硬约束:不修改 DSH 源码,插件永远作为独立包被 profile 引用)。
# 通过 DSH 官方插件命令安装 npm 包并自动挂载:
# dsh plugin --profile web add dsh-better-sidebar@<version>
#
# 包内声明了 dsh.bundle.patch(cordis.patch.yml):CLI 的 bundle 协调会把它
# 自动加进 profile 的 dsh.profile.bundles,下次启动即挂载——无需手动写
# cordis.patch.yml 挂载行。符合仓库硬约束:不修改 DSH 源码,插件永远作为
# 独立包被 profile 引用。
#
# 用法:
# bash scripts/install.sh [版本] [--restart] [--dry-run]
#
# 版本 npm 版本号/范围,缺省为 latest(自动解析为 ^<最新>)。
# 示例:0.10.1、^0.10.1、~0.10.1、latest
# 示例:0.10.2、^0.10.2、~0.10.2、latest
# --restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅打印提示)。
# 注意:重启会断开当前 DSH 页面会话,默认不自动重启。
# --dry-run 只打印将要执行的操作,不写任何文件。
@@ -17,18 +22,26 @@
# 环境:
# DSH_HOME 默认 ~/.dsh
# REGISTRY 默认 https://registry.npmjs.org(发布源;装依赖仍走 pnpm 配置)
# DSH_CMD 默认优先用 PATH 上的 `dsh`,缺省回退 npx -y --package @deepseek-ai/dsh
#
# 幂等:依赖已存在且版本满足 → 跳过写入;cordis.patch.yml 已含挂载行 → 不重复追加。
# 回滚:把 profile package.json 的依赖改回 "dsh-better-sidebar": "link:<路径>" 再 pnpm install。
# 说明:
# - pnpm 11 的 strict-dep-builds 会拦截 node-pty/protobufjs 的构建脚本并使
# `dsh plugin add` 非零退出(bundle 协调因此被跳过)。脚本会先把这两个
# 构建许可写进 profile 的 pnpm-workspace.yaml(幂等),保证 CLI 一步成功。
# - 老版本(<0.10.2)用手动挂载行,bundle 通道激活后需移除,否则双挂载
# (Node 半挂两次、页面两个侧边栏)。脚本会幂等移除 better-sidebar 挂载行。
# - 回滚:dsh plugin --profile web remove dsh-better-sidebar,或把 profile 依赖
# 改回 "dsh-better-sidebar": "link:<路径>" 再 pnpm install。
# =============================================================================
set -euo pipefail
DSH_HOME="${DSH_HOME:-$HOME/.dsh}"
PROFILE_DIR="$DSH_HOME/profiles/web"
PKG_JSON="$PROFILE_DIR/package.json"
WS_YML="$PROFILE_DIR/pnpm-workspace.yaml"
PATCH_YML="$PROFILE_DIR/cordis.patch.yml"
REGISTRY="${REGISTRY:-https://registry.npmjs.org}"
PKG="dsh-better-sidebar"
DSH_CMD="${DSH_CMD:-dsh}"
RESTART=false
DRY_RUN=false
@@ -46,77 +59,99 @@ say() { printf '\033[32m[install]\033[0m %s\n' "$*"; }
warn() { printf '\033[33m[warn]\033[0m %s\n' "$*" >&2; }
die() { printf '\033[31m[error]\033[0m %s\n' "$*" >&2; exit 1; }
# 解析用户给的版本 -> package.json 里要写的 spec("^x.y.z")
# 解析用户给的版本 -> CLI 要用的 npm spec("x.y.z" / "^x.y.z")
resolve_spec() {
local given="${1:-latest}"
case "$given" in
latest)
local v
v="$(npm view "$PKG" version --registry="$REGISTRY" 2>/dev/null)" \
|| die "无法从 $REGISTRY 解析 $PKG 的最新版本(请检查网络/登录)。可显式传版本号:bash scripts/install.sh 0.10.1"
printf '^%s' "$v"
|| die "无法从 $REGISTRY 解析 $PKG 的最新版本(请检查网络/登录)。可显式传版本号:bash scripts/install.sh 0.10.2"
printf '%s' "$v"
;;
^*|~*) printf '%s' "$given" ;;
*) printf '^%s' "$given" ;;
*) printf '%s' "$given" ;;
esac
}
# 组装 dsh CLI 调用:优先 PATH 上的 dsh,缺省 npx 拉官方包
dsh_cli() {
if command -v "$DSH_CMD" >/dev/null 2>&1; then
printf '%s' "$DSH_CMD"
else
printf 'npx -y --package @deepseek-ai/dsh dsh'
fi
}
[ -d "$PROFILE_DIR" ] || die "找不到 profile 目录:${PROFILE_DIR}(请先安装并运行过一次 dsh web)"
[ -f "$PKG_JSON" ] || die "找不到 $PKG_JSON"
[ -f "$PATCH_YML" ] || die "找不到 ${PATCH_YML}(请先初始化 web profile)"
[ -f "$WS_YML" ] || die "找不到 ${WS_YML}(请先初始化 web profile)"
SPEC="$(resolve_spec "$VERSION_SPEC")"
say "目标依赖:\"$PKG\": \"$SPEC\"(profile: ${PROFILE_DIR})"
CLI="$(dsh_cli)"
say "目标:$CLI plugin --profile web add $PKG@${SPEC}(profile: ${PROFILE_DIR})"
if [ "$DRY_RUN" = true ]; then
say "[dry-run] 步骤 1:更新 $PKG_JSON 的 dependencies[\"$PKG\"] = \"$SPEC\""
say "[dry-run] 步骤 2:确保 $PATCH_YML 含挂载行(- insert: - id: better-sidebar)"
say "[dry-run] 步骤 3:在 $PROFILE_DIR 执行 pnpm install"
say "[dry-run] 步骤 1:确保 $WS_YML 含 allowBuilds(node-pty/protobufjs: true)"
say "[dry-run] 步骤 2:执行 $CLI plugin --profile web add $PKG@${SPEC}(安装 + bundle 自动注册)"
say "[dry-run] 步骤 3:幂等移除 $PATCH_YML 里旧的 better-sidebar 手动挂载行(避免双挂载)"
if [ "$RESTART" = true ]; then say "[dry-run] 步骤 4:pm2 restart dsh-web"; else say "[dry-run] 步骤 4:提示用户手动重启 DSH"; fi
exit 0
fi
# 步骤 1:写依赖(用 node 安全改 JSON,保留其余字段;替换已存在的 link:/旧版本条目)
node -e '
const fs = require("fs");
const pkgJson = process.argv[1];
const name = process.argv[2];
const spec = process.argv[3];
const p = JSON.parse(fs.readFileSync(pkgJson, "utf8"));
p.dependencies = p.dependencies || {};
const old = p.dependencies[name];
if (old === spec) {
console.log("unchanged");
} else {
p.dependencies[name] = spec;
fs.writeFileSync(pkgJson, JSON.stringify(p, null, 2) + "\n");
console.log(old === undefined ? `added ${name}@${spec}` : `replaced ${name}@${old} -> ${name}@${spec}`);
}
' "$PKG_JSON" "$PKG" "$SPEC"
# 步骤 2:确保 cordis.patch.yml 挂载行存在(幂等追加)
if grep -qE '^\s*- id: better-sidebar\b' "$PATCH_YML"; then
say "挂载行已存在,跳过 cordis.patch.yml"
# 步骤 1:预写 allowBuilds(幂等),保证 pnpm 不为 node-pty/protobufjs 拦截构建
if ! grep -qE '^\s*node-pty: true\s*$' "$WS_YML"; then
# 清除 pnpm 自动写入的占位值("set this to true or false")后写入 true
node -e '
const fs = require("fs");
const p = process.argv[1];
let t = fs.readFileSync(p, "utf8");
t = t.replace(/node-pty:.*/g, "node-pty: true");
t = t.replace(/protobufjs:.*/g, "protobufjs: true");
if (!t.includes("allowBuilds:")) t += "\nallowBuilds:\n node-pty: true\n protobufjs: true\n";
fs.writeFileSync(p, t);
' "$WS_YML"
say "已确保 $WS_YML 的 allowBuilds(node-pty/protobufjs: true)"
else
# 确保文件末尾有换行
[ -n "$(tail -c1 "$PATCH_YML" 2>/dev/null)" ] && printf '\n' >> "$PATCH_YML"
cat >> "$PATCH_YML" <<'YAML'
# dsh-better-sidebar mount (added by scripts/install.sh)
- insert:
- id: better-sidebar
name: 'dsh-better-sidebar'
YAML
say "已追加挂载行到 cordis.patch.yml"
say "allowBuilds 已就绪,跳过"
fi
# 步骤 3:安装
say "执行 pnpm install(${PROFILE_DIR})..."
if ! (cd "$PROFILE_DIR" && pnpm install); then
warn "pnpm install 失败。若提示 minimumReleaseAge(新版本发布 <24h),pnpm 通常会自动写入 minimumReleaseAgeExclude 并重试;"
warn "仍失败可尝试:cd $PROFILE_DIR && pnpm clean --lockfile && pnpm install"
# 步骤 2:官方 CLI 安装 + bundle 自动注册(含挂载)
say "执行 $CLI plugin --profile web add $PKG@$SPEC ..."
if ! $CLI plugin --profile web add "$PKG@$SPEC" 2>&1 | tail -n +1; then
warn "dsh plugin add 失败。可能原因:minimumReleaseAge(发布 <24h,pnpm 通常自动写入排除并重试)"
warn "或 allowBuilds 未生效。可手动重试:cd $PROFILE_DIR && pnpm install"
exit 1
fi
# 校验 bundle 已注册(挂载生效的判据)
if ! node -e '
const fs = require("fs");
const p = JSON.parse(fs.readFileSync(process.argv[1], "utf8"));
const bundles = p.dsh?.profile?.bundles ?? [];
process.exit(bundles.includes(process.argv[2]) ? 0 : 1);
' "$PROFILE_DIR/package.json" "$PKG"; then
warn "dsh-better-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。"
warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。"
exit 1
fi
say "bundle 已注册:dsh.profile.bundles 包含 $PKG(下次启动自动挂载)"
# 步骤 3:幂等移除旧的 manual 挂载行(避免与 bundle 双挂载)
if grep -qE '^\s*- id: better-sidebar\b' "$PATCH_YML"; then
node -e '
const fs = require("fs");
const p = process.argv[1];
let t = fs.readFileSync(p, "utf8");
// 移除 better-sidebar 的 insert 块(含其前的注释行)
t = t.replace(/(?:^[ \t]*#[^\n]*\n)*[ \t]*- insert:\n[ \t]+- id: better-sidebar\n[ \t]+name: '"'"'dsh-better-sidebar'"'"'\n?/g, "");
// 清理可能留下的空行堆积
t = t.replace(/\n{3,}/g, "\n\n");
fs.writeFileSync(p, t);
' "$PATCH_YML"
say "已从 $PATCH_YML 移除旧的 better-sidebar 手动挂载行(bundle 通道接管挂载)"
else
say "无旧手动挂载行,跳过"
fi
say "安装完成:$PKG@$SPEC"
# 步骤 4:重启提示