Files
apes-sidebar/scripts/switch.sh
T
figmar e5bbef1907 feat(sync): re-base the fork onto upstream dsh-better-sidebar v0.24.1 (fork 1.3.0)
Branch for hardware verification on a host that meets v0.24.1's peer range.

Base: upstream `dsh-better-sidebar` v0.24.1 (tag v0.24.1 = a2751cf), MIT.
Measured delta carried over from the v0.19.1 line: 78 rename-only files, 11 real
edits, 10 additions. Ported with the same measurement method as the v0.19.1
re-base: build a normalized base (upstream tree + the fork rename, excluding
README.md / package.json / docs/plans/* which deliberately keep the upstream
name), extract the delta against it, then three-way apply onto the renamed
v0.24.1 tree (`git apply -3`). 13 conflicts resolved by hand.

Identity:
- version 1.3.0, SIDEBAR_SERVICE_VERSION 1.3.0 (package.json ↔ service.ts pair
  is asserted by tests/service.spec.ts), forkedFrom -> v0.24.1/a2751cf.
- package.json keeps the union of both sides' metadata: upstream's new
  `zustand` + `icon`/`locale` files entries, plus the fork's `scripts/switch.sh`
  entry and its `forkedFrom` block.
- README.md takes upstream v0.24.1 wholesale (its own v0.24.1 changelog and all
  16 sections) and keeps the fork's two deltas: the fork-notice block at the top
  (now documenting the 0.2.0-rc.1+ requirement) and the fork package name.
- peerDependencies stay upstream's `^0.2.0-rc.1` — this line does NOT load on
  DSH 0.1.x: the host's semver pre-check rejects a caret range across a minor
  and silently disables the row (no error, no load).

Carried features, all re-verified against the new base:
- apes:* information-layer tabs (src/client/apes/) — the subtree's session feed
  changed shape in 0.1.7 and the port follows the host's own migration notes in
  context-types.ts: the per-session catalog is now
  `projectionsBySession[id].values.subagentCatalog` (a flat entry list) instead
  of 0.1.6's `subagentsByParent` catalog with `entries`/`kind`/`activity`, and
  the removed `list.current` is answered by `props.scope.sessionId`. Rows are
  normalized to { id, label, mode } with activity read from the durable
  summaries, so catalog and byId sources render alike.
- HTML preview delivery through srcdoc (sandbox kept intact).
- Panel host mounted inside the shell's framed content viewport.

Gate: typecheck clean; build clean; 1515 passed / 9 skipped (141 files).

NOT applied to ~/.dsh/profiles/desktop — that profile runs DSH 2.0.10 with
@deepseek-ai/dsh 0.1.5-rc.2, where this line is silently disabled by design.
2026-10-04 00:58:28 +08:00

295 lines
12 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# =============================================================================
# dsh-apes-sidebar 两线切换脚本(apes fork 线 ↔ upstream better-sidebar 线)
#
# 同一个 DSH profile 内**二选一**切换侧边栏插件——双装不可行(两条线都注册
# 进 `ctx.betterSidebar`,cordis entry id 冲突,第二个被丢弃)。切换 =
# 换 profile 依赖 + 换 dsh.profile.bundles + pnpm install + 提示重启。
#
# aps 本 fork 线(dsh-apes-sidebar,含 4 个 apes:* 信息层 tab)
# 依赖:dsh-apes-sidebar: <file: 本地路径>(本地开发)或
# dsh-apes-sidebar@<版本>(npm,发布后)
# upstream upstream 线(dsh-better-sidebar,官方 8 tab + 生态)
# 依赖:dsh-better-sidebar@<版本>(registry,默认 latest)
#
# 用法:
# bash scripts/switch.sh aps # 切到本 fork 线(本地 file: 安装)
# bash scripts/switch.sh upstream # 切到 upstream 线(registry 安装)
# bash scripts/switch.sh upstream 0.18.0 # 指定 upstream 版本
# bash scripts/switch.sh --status # 只看当前线,不改任何东西
#
# 通用 flag(两线均适用):
# --profile <名> 目标 profile(缺省 desktop)
# --dry-run 只打印将执行的操作,不写任何文件
# -h/--help 打印本帮助
#
# 环境(均可省略,脚本自动探测):
# DSH_HOME 默认 ~/.dsh
# REGISTRY 默认 https://registry.npmjs.org
# DSH_CMD 默认 PATH 上的 dsh,缺省 npx -y --package @deepseek-ai/dsh
# APS_SRC aps 线本地源码路径(缺省 = 本仓库根,即 file: 指向自己)
#
# 机制(幂等,可重复跑):
# 1. 探测当前线(依赖里哪个包在)
# 2. 与目标线相同 → 提示后直接退出(不重装)
# 3. 移除另一线的依赖 + 从 bundles 摘除另一线的 bundle
# 4. 写入目标线依赖 + 确保 workspace 放行 node-pty/protobufjs 构建
# 5. cd profile && pnpm install(reconcileProfilePnpmWorkspace 会重建
# node_modules——这是预期行为,两线不共存)
# 6. 确保 bundles 含目标线包名(bundle 通道挂载生效的判据)
# 7. 幂等移除 cordis.patch.yml 里对手线的手动挂载行(避免双挂载)
# 8. 提示重启 DSH
#
# 回滚:跑 `bash scripts/switch.sh <另一线>` 即可切回去。
# =============================================================================
set -euo pipefail
for arg in "$@"; do
if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then
cat <<'EOF'
dsh-apes-sidebar 两线切换(同一 profile 内二选一)
用法:
bash scripts/switch.sh <aps|upstream> [版本]
bash scripts/switch.sh --status
aps 切到本 fork 线(dsh-apes-sidebar,4 个 apes:* tab)
upstream 切到 upstream 线(dsh-better-sidebar,官方)
<版本> 仅对 upstream 生效(缺省 latest)
--status 只看当前线,不改动
--profile 目标 profile(缺省 desktop)
--dry-run 只打印操作,不写文件
-h/--help 本帮助
环境变量:DSH_HOME、REGISTRY、DSH_CMD、APS_SRC(aps 本地源码路径)
EOF
exit 0
fi
done
DSH_HOME="${DSH_HOME:-${HOME:-${USERPROFILE:-}}/.dsh}"
REGISTRY="${REGISTRY:-https://registry.npmjs.org}"
DSH_CMD="${DSH_CMD:-dsh}"
# aps 线本地源码:缺省 = 本仓库根(脚本在 scripts/ 下,根 = 上一级)
APS_SRC="${APS_SRC:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
APS_PKG="dsh-apes-sidebar"
UP_PKG="dsh-better-sidebar"
DRY_RUN=false
STATUS_ONLY=false
TARGET=""
UP_VERSION=""
PROFILE_NAME="desktop"
while [ $# -gt 0 ]; do
case "$1" in
--status) STATUS_ONLY=true ;;
--dry-run) DRY_RUN=true ;;
--profile)
if [ $# -lt 2 ]; then echo "--profile 需要一个 profile 名(如 desktop)" >&2; exit 2; fi
PROFILE_NAME="$2"; shift ;;
aps|upstream) TARGET="$1" ;;
-h|--help) : ;;
-*) echo "未知参数: ${1}(用 -h 查看用法)" >&2; exit 2 ;;
*) UP_VERSION="$1" ;; # 版本参数(仅 upstream 用)
esac
shift
done
PROFILE_DIR="$DSH_HOME/profiles/$PROFILE_NAME"
PKG_JSON="$PROFILE_DIR/package.json"
WS_YML="$PROFILE_DIR/pnpm-workspace.yaml"
PATCH_YML="$PROFILE_DIR/cordis.patch.yml"
say() { printf '\033[32m[switch]\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; }
command -v node >/dev/null 2>&1 || die "未找到 node(DSH 运行需要 Node.js ≥ 20)。"
[ -d "$PROFILE_DIR" ] || die "找不到 profile 目录:${PROFILE_DIR}(请先初始化 ${PROFILE_NAME} profile)"
[ -f "$PKG_JSON" ] || die "找不到 ${PKG_JSON}(profile 未初始化)"
# ── 探测当前线(依赖里哪个包在)─────────────────────────────────────────
current_line() {
node -e '
const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
const deps = p.dependencies || {};
if (deps["dsh-apes-sidebar"]) console.log("aps");
else if (deps["dsh-better-sidebar"]) console.log("upstream");
else console.log("none");
' "$PKG_JSON"
}
if [ "$STATUS_ONLY" = true ]; then
CUR="$(current_line)"
say "profile: ${PROFILE_NAME} (${PROFILE_DIR})"
case "$CUR" in
aps) say "当前线:aps(${APS_PKG})" ;;
upstream) say "当前线:upstream(${UP_PKG})" ;;
none) say "当前线:无(两条线都未装)" ;;
esac
# 附带显示 bundles 里的 sidebar 条目
node -e '
const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
const b = (p.dsh && p.dsh.profile && p.dsh.profile.bundles) || [];
const side = b.filter(x => /sidebar/.test(x));
console.log("bundles 里的 sidebar 条目: " + (side.length ? side.join(", ") : "(无)"));
' "$PKG_JSON"
exit 0
fi
[ -n "$TARGET" ] || die "缺少目标线(aps | upstream)。用 --status 查看当前线。"
CUR="$(current_line)"
case "$TARGET" in
aps) T_PKG="$APS_PKG"; T_DEP="file:${APS_SRC}" ;;
upstream)
T_PKG="$UP_PKG"
# pnpm 从依赖 key 取包名,值只给版本 spec(不含包名,否则双 @)
case "${UP_VERSION:-latest}" in
latest) T_DEP="latest" ;;
*) T_DEP="$UP_VERSION" ;;
esac
;;
esac
[ "$CUR" = "$TARGET" ] && {
say "当前已是 ${TARGET} 线(${T_PKG}),无需切换。"
say "如需强制重装:先跑 --status 确认,再手动移除依赖后重跑。"
exit 0
}
OTHER_PKG=""
case "$TARGET" in
aps) OTHER_PKG="$UP_PKG" ;;
upstream) OTHER_PKG="$APS_PKG" ;;
esac
say "切换:${CUR} → ${TARGET}(profile: ${PROFILE_NAME})"
say "目标依赖:${T_DEP}"
if [ "$DRY_RUN" = true ]; then
say "[dry-run] 步骤 1:移除 ${OTHER_PKG:-(无)} 依赖 + 从 bundles 摘除 ${OTHER_PKG:-(无)}"
say "[dry-run] 步骤 2:写入 ${T_PKG} 依赖(${T_DEP})"
say "[dry-run] 步骤 3:确保 ${WS_YML} 放行 node-pty/protobufjs 构建"
say "[dry-run] 步骤 4:cd ${PROFILE_DIR} && pnpm install(重建 node_modules)"
say "[dry-run] 步骤 5:确保 bundles 含 ${T_PKG}"
say "[dry-run] 步骤 6:幂等移除 ${PATCH_YML} 里 ${OTHER_PKG:-对手线} 的手动挂载行"
say "[dry-run] 步骤 7:提示重启 DSH"
exit 0
fi
# ── 步骤 1+2+5 合并:一次 node 调用完成依赖 + bundles 的对称改写 ─────────
# 移除对手线依赖 + bundle,写入目标线依赖 + 确保目标 bundle 在列。幂等。
node -e '
const fs = require("fs");
const p = process.argv[1];
const keep = process.argv[2]; // 目标包名
const depSpec = process.argv[3]; // 目标依赖 spec(file:... 或 pkg@ver)
const remove = process.argv[4]; // 对手包名(可能为空串)
const t = JSON.parse(fs.readFileSync(p, "utf8"));
t.dependencies = t.dependencies || {};
// 移除对手线依赖
if (remove) { const had = t.dependencies[remove]; delete t.dependencies[remove]; if (had) console.log("removed dep: " + remove + "=" + had); }
// 写入目标线依赖
t.dependencies[keep] = depSpec;
// bundles:摘除对手线,确保目标线在列
const prof = (t.dsh && t.dsh.profile) || {};
const b = prof.bundles || [];
const nb = b.filter(x => x !== remove);
if (!nb.includes(keep)) nb.push(keep);
t.dsh = t.dsh || {}; t.dsh.profile = prof; prof.bundles = nb;
fs.writeFileSync(p, JSON.stringify(t, null, 2) + "\n");
console.log("deps now: " + JSON.stringify(t.dependencies));
console.log("bundles now: " + nb.join(", "));
' "$PKG_JSON" "$T_PKG" "$T_DEP" "$OTHER_PKG"
say "依赖 + bundles 已改写"
# ── 步骤 3:确保 workspace 放行 node-pty/protobufjs 构建(幂等)─────────
if [ -f "$WS_YML" ]; then
WS_RESULT="$(node -e '
const fs = require("fs");
const p = process.argv[1];
let t = fs.readFileSync(p, "utf8");
const before = t;
t = t.replace(/^(\s*)(node-pty|protobufjs):.*$/gm, "$1$2: true");
if (!/^\s*allowBuilds:\s*$/m.test(t)) {
t += "\nallowBuilds:\n node-pty: true\n protobufjs: true\n";
} else {
for (const k of ["node-pty", "protobufjs"]) {
if (!new RegExp("^\\s*" + k + ":\\s*true\\s*$", "m").test(t)) {
t = t.replace(/^(\s*allowBuilds:\s*)$/m, "$1\n " + k + ": true");
}
}
}
if (t !== before) fs.writeFileSync(p, t);
console.log(t === before ? "unchanged" : "updated");
' "$WS_YML")"
[ "$WS_RESULT" = "updated" ] && say "已确保 ${WS_YML}:allowBuilds(node-pty/protobufjs: true)" \
|| say "workspace 设置已就绪,跳过"
else
warn "找不到 ${WS_YML},跳过 workspace 预写(若 pnpm install 报 ignored build scripts 需手动补)"
fi
# ── 步骤 4:pnpm install(重建 node_modules——两线不共存,这是预期)──────
command -v pnpm >/dev/null 2>&1 || die "未找到 pnpm。请安装 pnpm 9+ 并加入 PATH。"
say "执行 cd ${PROFILE_DIR} && pnpm install(reconcileProfilePnpmWorkspace 会重建 node_modules)..."
if ! (cd "$PROFILE_DIR" && pnpm install); then
die "pnpm install 失败。请确认网络可用、依赖 spec 正确,然后重试。"
fi
# ── 步骤 5 校验:bundles 含目标包(挂载生效判据)────────────────────────
if ! node -e '
const p = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
const b = (p.dsh && p.dsh.profile && p.dsh.profile.bundles) || [];
process.exit(b.includes(process.argv[2]) ? 0 : 1);
' "$PKG_JSON" "$T_PKG"; then
warn "${T_PKG} 未出现在 dsh.profile.bundles——挂载未注册,请检查 ${PKG_JSON}。"
exit 1
fi
say "bundle 已注册:dsh.profile.bundles 含 ${T_PKG}(下次启动自动挂载)"
# ── 步骤 6:幂等移除对手线在 cordis.patch.yml 里的手动挂载行 ─────────────
if [ -f "$PATCH_YML" ]; then
MOUNT_RESULT="$(node -e '
const fs = require("fs");
const p = process.argv[1];
const removePkg = process.argv[2];
if (!removePkg) { console.log("none"); process.exit(0); }
const lines = fs.readFileSync(p, "utf8").split("\n");
const out = [];
let i = 0;
let removed = false;
while (i < lines.length) {
const line = lines[i];
if (/^[ \t]*- insert:\s*$/.test(line)) {
const block = [line];
let j = i + 1;
while (j < lines.length && lines[j].trim() !== "" && !/^- \s/.test(lines[j])) {
block.push(lines[j]);
j++;
}
if (block.some((l) => new RegExp("id:\\s*" + removePkg.replace(/[-/]/g, "\\$&") + "\\b").test(l))) {
while (out.length && /^[ \t]*#/.test(out[out.length - 1])) out.pop();
i = j;
removed = true;
continue;
}
}
out.push(line);
i++;
}
if (!removed) console.log("none");
else { const t = out.join("\n").replace(/\n{3,}/g, "\n\n"); fs.writeFileSync(p, t); console.log("removed"); }
' "$PATCH_YML" "$OTHER_PKG")"
[ "$MOUNT_RESULT" = "removed" ] && say "已从 ${PATCH_YML} 移除 ${OTHER_PKG} 的手动挂载行" \
|| say "无 ${OTHER_PKG} 手动挂载行,跳过"
else
say "无 ${PATCH_YML},跳过手动挂载行清理"
fi
# ── 步骤 7:提示重启 ────────────────────────────────────────────────────
say "切换完成:${CUR} → ${TARGET}(${T_PKG})"
say "下一步:重启 DSH 并硬刷新(Cmd/Ctrl+Shift+R)使新副本生效。"