From e128c303bc882cb07e7174081d6d556d744b8424 Mon Sep 17 00:00:00 2001 From: Menghuan1918 Date: Fri, 14 Aug 2026 00:48:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20Windows=20PowerShe?= =?UTF-8?q?ll=20=E4=B8=80=E9=94=AE=E5=AE=89=E8=A3=85=E8=84=9A=E6=9C=AC=20i?= =?UTF-8?q?nstall.ps1=EF=BC=8CREADME=20=E5=AE=89=E8=A3=85=E7=AB=A0?= =?UTF-8?q?=E8=8A=82=E4=B8=A4=E5=B9=B3=E5=8F=B0=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - scripts/install.ps1:官方 CLI 方式一键安装(PowerShell 5.1+/pwsh), 与 install.sh 对齐:预写 allowBuilds + minimumReleaseAgeExclude(幂等)、 执行 dsh plugin add、幂等清理旧手动挂载行;支持 -Version/-Restart/-DryRun。 - scripts/install.sh:加固 — -h/--help、Windows Git Bash 下 DSH_HOME 回退 $USERPROFILE、npm/pnpm view 双通道解析最新版本、失败提示细化、挂载行 清理改为逐块扫描(删除注释块跟随)。 - README.md:安装章节改为「① 前置 → ② 对应平台一键命令 → ③ 重启硬刷新」, macOS/Linux 与 Windows PowerShell 双命令示例。 - package.json:files 列表纳入 scripts/install.ps1。 --- README.md | 99 ++++++++++++++---- package.json | 1 + scripts/install.ps1 | 242 ++++++++++++++++++++++++++++++++++++++++++++ scripts/install.sh | 176 +++++++++++++++++++++++--------- 4 files changed, 451 insertions(+), 67 deletions(-) create mode 100644 scripts/install.ps1 diff --git a/README.md b/README.md index f211723..83186d0 100644 --- a/README.md +++ b/README.md @@ -35,35 +35,86 @@ https://github.com/user-attachments/assets/23187822-047e-45cc-b480-fe997bd55b86 ## 🚀 安装 -前置:已安装 DSH(`dsh web` 可运行),Node.js ≥ 20、pnpm ≥ 10。插件已发布到 npm:**`dsh-better-sidebar@0.10.2`**(`@deepseek-ai/*` peer 依赖对齐宿主实际版本 `^0.1.0-rc.6` / `@deepseek-ai/cordis@^4.0.1`,同版本单实例)。包内声明了 `dsh.bundle.patch`(随包发布的 `cordis.patch.yml`),安装后由官方 CLI 自动挂载,不修改 DSH 源码。 +装好只需三步:**① 满足前置 → ② 跑一条对应平台的一键命令 → ③ 重启 DSH 并硬刷新**。 + +### 前置要求 + +- 已安装 **DSH**,且 `dsh web` 能正常启动(首次运行会初始化 `~/.dsh/profiles/web` 这个 profile)。 +- **Node.js ≥ 20**(DSH 运行本身就需要)。 +- **pnpm ≥ 10**(装依赖用;pnpm 11 也可,脚本会自动处理它的新策略)。 +- 插件已发布到 npm:**`dsh-better-sidebar@0.10.2`**。包内声明了 `dsh.bundle.patch`(随包发布的 `cordis.patch.yml`),官方 CLI 装完即自动挂载,**不修改 DSH 源码**。 ### 一键安装(推荐) +**macOS / Linux**(Windows 装了 Git Bash 或 WSL 也可用): + ```sh curl -fsSL https://raw.githubusercontent.com/omdsh-dev/DSH-better-sidebar/main/scripts/install.sh | bash -# 指定版本: curl -fsSL <同上> | bash -s 0.10.2 -# 装完自动重启 DSH: curl -fsSL <同上> | bash -s -- --restart ``` -脚本自动完成:`pnpm add` 登记依赖到 `~/.dsh/profiles/web/package.json` → 预写 `allowBuilds`(node-pty/protobufjs,规避 pnpm 11 的构建脚本拦截)→ 识别包内 `dsh.bundle.patch`,把插件自动加进 `dsh.profile.bundles` 挂载 → 幂等清理旧的手动挂载行(防双挂载)。版本默认最新(`latest`),可传参;`--dry-run` 只预览不落盘。 +**Windows(PowerShell 5.1+ / pwsh)**: -### 手动安装(npx dsh + 命令链) +```powershell +irm https://raw.githubusercontent.com/omdsh-dev/DSH-better-sidebar/main/scripts/install.ps1 | iex +``` -等价于上面脚本的一连串 bash 命令(全新 profile 首次装可能因构建脚本被拦,链里 `||` 分支会自动批准并重试一次): +**指定版本 / 装完自动重启**(两平台参数一致): ```sh -cd ~/.dsh/profiles/web \ - && npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar \ - || (pnpm approve-builds --all \ - && npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar) -# 固定版本:把上面的 dsh-better-sidebar 换成 dsh-better-sidebar@0.10.2 +# macOS / Linux +curl -fsSL https://raw.githubusercontent.com/omdsh-dev/DSH-better-sidebar/main/scripts/install.sh | bash -s 0.10.2 --restart + +# Windows PowerShell +& ([scriptblock]::Create((irm 'https://raw.githubusercontent.com/omdsh-dev/DSH-better-sidebar/main/scripts/install.ps1'))) -Version 0.10.2 -Restart ``` -不带 `@<版本>` 时,pnpm 解析 npm 的 `latest` 标签(最新发布版)。`dsh plugin add` 完成:`pnpm add` 登记依赖 → 识别 `dsh.bundle.patch` 自动注册到 `dsh.profile.bundles` → 下次启动自动挂载(无需手写 `cordis.patch.yml`)。 +脚本会自动完成 4 件事(全部幂等,可安全重复执行): -> ⚠️ pnpm 11 的两道供应链策略,命令链无法完全规避: -> - `strict-dep-builds` 拦截 node-pty/protobufjs 的构建脚本:首次装报 `Ignored build scripts`(包其实已装上、node-pty 预编译产物可直接用),`pnpm approve-builds --all` 一次即可。 -> - `minimumReleaseAge` 拒绝发布 <24h 的新版本:这是 pnpm 的策略、命令链无法完全规避,仅因插件刚发布不久才会触发,**24 小时后自动消失**(触发时重跑一次,pnpm 会自动补 `minimumReleaseAgeExclude`)。 +1. 预写 `allowBuilds`(node-pty / protobufjs),规避 pnpm 11 的构建脚本拦截; +2. 预写 `minimumReleaseAgeExclude`,放行「发布不足 24 小时」的新版本; +3. 执行 `dsh plugin --profile web add dsh-better-sidebar`:登记依赖 → 识别 `dsh.bundle.patch` → 自动注册进 `dsh.profile.bundles` 挂载; +4. 清理旧版残留的手动挂载行,避免「双挂载」(页面出现两个侧边栏)。 + +不确定的话,可先加 `--dry-run`(PowerShell 用 `-DryRun`)预览步骤再真正执行。`curl | bash` / `irm | iex` 会执行远程代码——脚本已随仓库开源(`scripts/install.sh` / `scripts/install.ps1`),可先下载审阅。 + +### 手动安装(逐步命令) + +适合想看清每一步的用户,与一键脚本等价。**第 ③ 步可重复执行;①② 只需做一次。** + +**macOS / Linux(bash)**: + +```sh +cd ~/.dsh/profiles/web + +# ① 放行 node-pty / protobufjs 的构建脚本(pnpm 11 默认拦截;pnpm 10 可跳过) +pnpm approve-builds --all + +# ② 放行「发布不足 24h」的新版本(装老版本可跳过;若已有该键,把下面那行并入其下即可) +cat >> pnpm-workspace.yaml <<'EOF' +minimumReleaseAgeExclude: + - dsh-better-sidebar +EOF + +# ③ 安装并自动挂载(不带 @版本 = npm 的 latest;固定版本写 dsh-better-sidebar@0.10.2) +npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar +``` + +**Windows(PowerShell)**: + +```powershell +cd ~\.dsh\profiles\web + +# ① 放行构建脚本 +pnpm approve-builds --all + +# ② 放行新版本(一次性;若已有该键,把 - dsh-better-sidebar 并入其下即可) +Add-Content -Path pnpm-workspace.yaml -Value "`nminimumReleaseAgeExclude:`n - dsh-better-sidebar" + +# ③ 安装并自动挂载 +npx -y --package @deepseek-ai/dsh dsh plugin --profile web add dsh-better-sidebar +``` + +`dsh plugin add` 会:`pnpm add` 登记依赖 → 识别包内 `dsh.bundle.patch` → 自动注册到 `dsh.profile.bundles` → 下次启动自动挂载(**无需手写 `cordis.patch.yml`**)。 ### 更新 @@ -71,7 +122,18 @@ cd ~/.dsh/profiles/web \ dsh plugin --profile web add dsh-better-sidebar ``` -或重跑一次一键脚本;也可手动把 `~/.dsh/profiles/web/package.json` 版本号改新版后 `pnpm install`。随后重启 DSH 并硬刷新(Cmd/Ctrl+Shift+R)。 +或重跑一次一键脚本;也可把 `~/.dsh/profiles/web/package.json` 里的版本号改高后 `pnpm install`。改完**重启 DSH 并硬刷新**(Cmd/Ctrl+Shift+R)。 + +### 常见问题 + +| 现象 | 原因与解决 | +|---|---| +| 报 `Ignored build scripts` | pnpm 11 拦截构建脚本。跑 `pnpm approve-builds --all`(一键脚本已自动处理)。 | +| 报 `minimum release age` / 版本不足 24h | 装的版本发布不足 24 小时。等 24h 或重跑一次(pnpm 会自动补 `minimumReleaseAgeExclude`);一键脚本已自动处理。 | +| 报「找不到 profile 目录」 | 先跑一次 `dsh web`,让它初始化 `~/.dsh/profiles/web`。 | +| 页面出现**两个侧边栏** | 双挂载:`~/.dsh/profiles/web/cordis.patch.yml` 还留着旧的手动挂载行,删掉那段 `- insert: ... better-sidebar ...`(一键脚本会自动清)。 | +| Windows 下终端无法使用 | `node-pty` 依赖预编译二进制;若当前 Node 版本没有对应产物,需装编译工具链(VS Build Tools)。主流 Node 版本一般已有预编译。 | +| Windows 没有 bash / curl | 直接用 PowerShell 一键命令;或安装 Git Bash / WSL 再跑 bash 命令。 |
从源码安装 / 开发(可选,替代 npm 方式) @@ -82,7 +144,10 @@ dsh plugin --profile web add dsh-better-sidebar 1. git clone https://github.com/omdsh-dev/DSH-better-sidebar.git ~/Code/DSH-better-sidebar cd ~/Code/DSH-better-sidebar && pnpm install && pnpm build 2. ~/.dsh/profiles/web/package.json 的 dependencies 写 "dsh-better-sidebar": "link:<克隆目录绝对路径>" -3. ~/.dsh/profiles/web/cordis.patch.yml 追加挂载行(同上) +3. ~/.dsh/profiles/web/cordis.patch.yml 追加挂载行: + - insert: + - id: better-sidebar + name: 'dsh-better-sidebar' 4. 在 ~/.dsh/profiles/web 执行 pnpm install 5. 重启 DSH 并硬刷新 ``` diff --git a/package.json b/package.json index 1f3b9d1..7e76dca 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "lib/types/**/*.d.ts", "src", "scripts/install.sh", + "scripts/install.ps1", "cordis.patch.yml", "README.md", "README_EN.md", diff --git a/scripts/install.ps1 b/scripts/install.ps1 new file mode 100644 index 0000000..ab083c0 --- /dev/null +++ b/scripts/install.ps1 @@ -0,0 +1,242 @@ +# ============================================================================= +# dsh-better-sidebar 一键安装脚本(官方 CLI 方式,Windows PowerShell 5.1+ / pwsh) +# +# 通过 DSH 官方插件命令安装 npm 包并自动挂载: +# dsh plugin --profile web add dsh-better-sidebar@ +# +# 包内声明了 dsh.bundle.patch(cordis.patch.yml):CLI 的 bundle 协调会把它 +# 自动加进 profile 的 dsh.profile.bundles,下次启动即挂载——无需手动写 +# cordis.patch.yml 挂载行。符合仓库硬约束:不修改 DSH 源码,插件永远作为 +# 独立包被 profile 引用。 +# +# 用法(任选其一): +# # 默认最新版 +# irm https://raw.githubusercontent.com/omdsh-dev/DSH-better-sidebar/main/scripts/install.ps1 | iex +# # 指定版本 / 装完重启 +# & ([scriptblock]::Create((irm 'https://raw.githubusercontent.com/omdsh-dev/DSH-better-sidebar/main/scripts/install.ps1'))) -Version 0.10.2 -Restart +# # 本地保存后运行 +# powershell -ExecutionPolicy Bypass -File install.ps1 -Version 0.10.2 -DryRun +# +# 参数: +# -Version npm 版本号/范围,缺省 latest(自动解析为最新)。 +# -Restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅提示)。会断开当前页面会话。 +# -DryRun 只打印将要执行的操作,不写任何文件。 +# +# 环境变量(均可省略): +# DSH_HOME 默认 %USERPROFILE%\.dsh +# REGISTRY 默认 https://registry.npmjs.org +# DSH_CMD 默认优先 PATH 上的 dsh,缺省回退 npx -y --package @deepseek-ai/dsh +# +# 说明: +# - pnpm 11 的 strict-dep-builds 会拦截 node-pty/protobufjs 的构建脚本并使 +# `dsh plugin add` 非零退出。脚本会先把这两个构建许可写进 profile 的 +# pnpm-workspace.yaml(幂等),保证 CLI 一步成功。 +# - pnpm 11 的 minimumReleaseAge 会拒绝发布 <24h 的新版本。脚本会预写 +# minimumReleaseAgeExclude(幂等),放行本插件,避免"重跑一次才成功"。 +# - 老版本(<0.10.2)用手动挂载行,bundle 通道激活后需移除,否则双挂载。 +# 脚本会幂等移除 better-sidebar 挂载行。 +# ============================================================================= +param( + [string]$Version = '', + [switch]$Restart, + [switch]$DryRun +) + +$PKG = 'dsh-better-sidebar' +$REGISTRY = if ($env:REGISTRY) { $env:REGISTRY } else { 'https://registry.npmjs.org' } + +# DSH_HOME:DSH_HOME 环境变量 > %USERPROFILE% > $HOME +if ($env:DSH_HOME) { + $DSH_HOME = $env:DSH_HOME +} elseif ($env:USERPROFILE) { + $DSH_HOME = Join-Path $env:USERPROFILE '.dsh' +} else { + $DSH_HOME = Join-Path $HOME '.dsh' +} +$PROFILE_DIR = Join-Path $DSH_HOME 'profiles\web' +$WS_YML = Join-Path $PROFILE_DIR 'pnpm-workspace.yaml' +$PATCH_YML = Join-Path $PROFILE_DIR 'cordis.patch.yml' + +function Say([string]$m) { Write-Host "[install] $m" -ForegroundColor Green } +function Warn([string]$m) { Write-Host "[warn] $m" -ForegroundColor Yellow } +function Die([string]$m) { Write-Host "[error] $m" -ForegroundColor Red; exit 1 } + +# 解析版本 -> npm spec("x.y.z" / "^x.y.z" / latest) +function Resolve-Spec { + param([string]$Given) + if ([string]::IsNullOrWhiteSpace($Given) -or $Given -eq 'latest') { + $v = $null + foreach ($tool in @('npm', 'pnpm')) { + if (Get-Command $tool -ErrorAction SilentlyContinue) { + $v = (& $tool view $PKG version "--registry=$REGISTRY" 2>$null | Select-Object -Last 1) + if ($v) { break } + } + } + if ($v) { return ([string]$v).Trim() } + Warn '无法联网解析最新版本(npm/pnpm 查询失败),回退为 latest,由 pnpm 直接解析。' + Warn '若已知版本号,可显式传入:-Version 0.10.2' + return 'latest' + } + return $Given +} + +# 组装 dsh CLI:优先 PATH 上的 dsh,缺省 npx 拉官方包 +function Get-DshCli { + if ($env:DSH_CMD) { return $env:DSH_CMD } + if (Get-Command dsh -ErrorAction SilentlyContinue) { return 'dsh' } + if (Get-Command npx -ErrorAction SilentlyContinue) { return 'npx' } + return $null +} + +# 前置校验 +if (-not (Get-Command node -ErrorAction SilentlyContinue)) { + Die '未找到 node(DSH 运行需要 Node.js >= 20),请先安装 Node.js 并加入 PATH。' +} +if (-not (Test-Path $PROFILE_DIR)) { + Die "找不到 profile 目录:$PROFILE_DIR(请先安装并运行过一次 dsh web)" +} +if (-not (Test-Path $WS_YML)) { + Die "找不到 $WS_YML(请先初始化 web profile)" +} + +$SPEC = Resolve-Spec $Version +$CLI = Get-DshCli +if (-not $CLI) { + Die '未找到 dsh 或 npx。请先安装 DSH(并确保 Node/npm 可用),或用 DSH_CMD 指定。' +} +Say "目标:$CLI plugin --profile web add $PKG@$SPEC(profile: $PROFILE_DIR)" + +if ($DryRun) { + Say "[dry-run] 步骤 1:确保 $WS_YML 含 allowBuilds(node-pty/protobufjs: true)与 minimumReleaseAgeExclude($PKG)" + Say "[dry-run] 步骤 2:执行 $CLI plugin --profile web add $PKG@$SPEC(安装 + bundle 自动注册)" + Say "[dry-run] 步骤 3:校验 dsh.profile.bundles 含 $PKG" + Say "[dry-run] 步骤 4:幂等移除 $PATCH_YML 里旧的 better-sidebar 手动挂载行(避免双挂载)" + if ($Restart) { Say '[dry-run] 步骤 5:pm2 restart dsh-web' } else { Say '[dry-run] 步骤 5:提示用户手动重启 DSH' } + exit 0 +} + +# 步骤 1:预写 workspace 设置(幂等),保证 pnpm 不拦截构建、放行本插件新版本 +$wsScript = @' +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 (!/^\s*-\s+dsh-better-sidebar\s*$/m.test(t)) { + if (/^\s*minimumReleaseAgeExclude:\s*$/m.test(t)) { + t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-better-sidebar"); + } else { + t += "\nminimumReleaseAgeExclude:\n - dsh-better-sidebar\n"; + } +} +if (t !== before) fs.writeFileSync(p, t); +console.log(t === before ? "unchanged" : "updated"); +'@ +$wsOut = node -e $wsScript "$WS_YML" 2>&1 +$wsCode = $LASTEXITCODE +$wsResult = (($wsOut | Out-String)).Trim() +if ($wsCode -ne 0) { Die "处理 $WS_YML 失败(node 退出码 $wsCode):$wsResult" } +if ($wsResult -eq 'updated') { + Say "已确保 $WS_YML:allowBuilds(node-pty/protobufjs: true)+ minimumReleaseAgeExclude($PKG)" +} else { + Say 'workspace 设置已就绪,跳过' +} + +# 步骤 2:官方 CLI 安装 + bundle 自动注册(含挂载) +if ($CLI -eq 'dsh') { + $cliArgs = @('plugin', '--profile', 'web', 'add', "$PKG@$SPEC") +} else { + $cliArgs = @('-y', '--package', '@deepseek-ai/dsh', 'dsh', 'plugin', '--profile', 'web', 'add', "$PKG@$SPEC") +} +Say "执行 $CLI plugin --profile web add $PKG@$SPEC ..." +$addOut = & $CLI @cliArgs 2>&1 +$addCode = $LASTEXITCODE +$addOut | ForEach-Object { $_ } +if ($addCode -ne 0) { + Warn 'dsh plugin add 失败。已预写 allowBuilds 与 minimumReleaseAgeExclude,仍失败的可能原因:' + Warn ' - 网络/登录问题:npm registry 不可达或需要登录。' + Warn " - 依赖安装冲突:可手动重试 cd $PROFILE_DIR; pnpm install。" + exit 1 +} + +# 步骤 3:校验 bundle 已注册(挂载生效的判据) +$pkgJson = Get-Content -Raw (Join-Path $PROFILE_DIR 'package.json') | ConvertFrom-Json +$bundles = $pkgJson.dsh.profile.bundles +if ($bundles -notcontains $PKG) { + Warn 'dsh-better-sidebar 未出现在 dsh.profile.bundles 中——挂载未注册。' + Warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。" + exit 1 +} +Say "bundle 已注册:dsh.profile.bundles 包含 $PKG(下次启动自动挂载)" + +# 步骤 4:幂等移除旧的 manual 挂载行(避免与 bundle 双挂载) +$mountScript = @' +const fs = require("fs"); +const p = process.argv[1]; +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) => /id:\s*better-sidebar\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"); +} +'@ +$mountOut = node -e $mountScript "$PATCH_YML" 2>&1 +$mountCode = $LASTEXITCODE +$mountResult = (($mountOut | Out-String)).Trim() +if ($mountCode -ne 0) { Die "处理 $PATCH_YML 失败(node 退出码 $mountCode):$mountResult" } +if ($mountResult -eq 'removed') { + Say "已从 $PATCH_YML 移除旧的 better-sidebar 手动挂载行(bundle 通道接管挂载)" +} else { + Say '无旧手动挂载行,跳过' +} + +Say "安装完成:$PKG@$SPEC" + +# 步骤 5:重启提示 +if ($Restart) { + if (Get-Command pm2 -ErrorAction SilentlyContinue) { + Say '重启 dsh-web(pm2)...' + pm2 restart dsh-web + if ($LASTEXITCODE -ne 0) { Warn 'pm2 restart 失败,请手动重启 DSH' } + } else { + Warn '未找到 pm2,请手动重启 DSH(如:pm2 restart dsh-web 或 dsh web)' + } +} else { + Say '下一步:重启 DSH 并硬刷新(Ctrl+Shift+R / Cmd+Shift+R)使新副本生效。' + if (Get-Command pm2 -ErrorAction SilentlyContinue) { + Say '本机可用:pm2 restart dsh-web(会短暂断开当前页面会话)' + } +} diff --git a/scripts/install.sh b/scripts/install.sh index ec27d9a..979c7df 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ============================================================================= -# dsh-better-sidebar 一键安装脚本(官方 CLI 方式) +# dsh-better-sidebar 一键安装脚本(官方 CLI 方式,macOS / Linux / Windows Git Bash) # # 通过 DSH 官方插件命令安装 npm 包并自动挂载: # dsh plugin --profile web add dsh-better-sidebar@ @@ -13,14 +13,15 @@ # 用法: # bash scripts/install.sh [版本] [--restart] [--dry-run] # -# 版本 npm 版本号/范围,缺省为 latest(自动解析为 ^<最新>)。 -# 示例:0.10.2、^0.10.2、~0.10.2、latest +# 版本 npm 版本号/范围,缺省为 latest(自动解析为 ^<最新>)。 +# 示例:0.10.2、^0.10.2、~0.10.2、latest # --restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅打印提示)。 # 注意:重启会断开当前 DSH 页面会话,默认不自动重启。 # --dry-run 只打印将要执行的操作,不写任何文件。 +# -h/--help 打印本帮助。 # -# 环境: -# DSH_HOME 默认 ~/.dsh +# 环境(均可省略,脚本会自动探测): +# DSH_HOME 默认 ~/.dsh(Windows Git Bash 下回退 $USERPROFILE/.dsh) # REGISTRY 默认 https://registry.npmjs.org(发布源;装依赖仍走 pnpm 配置) # DSH_CMD 默认优先用 PATH 上的 `dsh`,缺省回退 npx -y --package @deepseek-ai/dsh # @@ -28,6 +29,8 @@ # - pnpm 11 的 strict-dep-builds 会拦截 node-pty/protobufjs 的构建脚本并使 # `dsh plugin add` 非零退出(bundle 协调因此被跳过)。脚本会先把这两个 # 构建许可写进 profile 的 pnpm-workspace.yaml(幂等),保证 CLI 一步成功。 +# - pnpm 11 的 minimumReleaseAge 会拒绝发布 <24h 的新版本。脚本会预写 +# minimumReleaseAgeExclude(幂等),放行本插件,避免"重跑一次才成功"。 # - 老版本(<0.10.2)用手动挂载行,bundle 通道激活后需移除,否则双挂载 # (Node 半挂两次、页面两个侧边栏)。脚本会幂等移除 better-sidebar 挂载行。 # - 回滚:dsh plugin --profile web remove dsh-better-sidebar,或把 profile 依赖 @@ -35,7 +38,25 @@ # ============================================================================= set -euo pipefail -DSH_HOME="${DSH_HOME:-$HOME/.dsh}" +# 帮助请求优先处理 +for arg in "$@"; do + if [ "$arg" = "-h" ] || [ "$arg" = "--help" ]; then + cat <<'EOF' +dsh-better-sidebar 一键安装脚本 + +用法:bash scripts/install.sh [版本] [--restart] [--dry-run] + + 版本 npm 版本号/范围,缺省 latest(自动解析为最新)。示例:0.10.2、^0.10.2、latest + --restart 装完后尝试 `pm2 restart dsh-web`(无 pm2 时仅提示) + --dry-run 只打印将要执行的操作,不写任何文件 + +环境变量(可省略):DSH_HOME(默认 ~/.dsh)、REGISTRY(npm 源)、DSH_CMD(dsh 命令) +EOF + exit 0 + fi +done + +DSH_HOME="${DSH_HOME:-${HOME:-${USERPROFILE:-}}/.dsh}" PROFILE_DIR="$DSH_HOME/profiles/web" WS_YML="$PROFILE_DIR/pnpm-workspace.yaml" PATCH_YML="$PROFILE_DIR/cordis.patch.yml" @@ -50,7 +71,8 @@ for arg in "$@"; do case "$arg" in --restart) RESTART=true ;; --dry-run) DRY_RUN=true ;; - -*) echo "未知参数: $arg" >&2; exit 2 ;; + -h|--help) : ;; # 已在上面处理 + -*) echo "未知参数: ${arg}(用 -h 查看用法)" >&2; exit 2 ;; *) VERSION_SPEC="$arg" ;; esac done @@ -59,15 +81,25 @@ 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; } -# 解析用户给的版本 -> CLI 要用的 npm spec("x.y.z" / "^x.y.z") +# 解析用户给的版本 -> CLI 要用的 npm spec("x.y.z" / "^x.y.z" / latest) 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.2" - printf '%s' "$v" + local v="" + if command -v npm >/dev/null 2>&1; then + v="$(npm view "$PKG" version --registry="$REGISTRY" 2>/dev/null)" || v="" + fi + if [ -z "$v" ] && command -v pnpm >/dev/null 2>&1; then + v="$(pnpm view "$PKG" version --registry="$REGISTRY" 2>/dev/null)" || v="" + fi + if [ -n "$v" ]; then + printf '%s' "$v" + else + warn "无法联网解析最新版本(npm/pnpm 查询失败),回退为 latest,由 pnpm 直接解析。" + warn "若已知版本号,可显式传入:bash scripts/install.sh 0.10.2" + printf 'latest' + fi ;; *) printf '%s' "$given" ;; esac @@ -77,11 +109,16 @@ resolve_spec() { dsh_cli() { if command -v "$DSH_CMD" >/dev/null 2>&1; then printf '%s' "$DSH_CMD" - else + elif command -v npx >/dev/null 2>&1; then printf 'npx -y --package @deepseek-ai/dsh dsh' + else + die "未找到 dsh 或 npx。请先安装 DSH(并确保 Node/npm 可用),或用 DSH_CMD 指定 dsh 路径。" fi } +# 前置校验 +command -v node >/dev/null 2>&1 || die "未找到 node(DSH 运行需要 Node.js ≥ 20),请先安装 Node.js 并加入 PATH。" + [ -d "$PROFILE_DIR" ] || die "找不到 profile 目录:${PROFILE_DIR}(请先安装并运行过一次 dsh web)" [ -f "$WS_YML" ] || die "找不到 ${WS_YML}(请先初始化 web profile)" @@ -90,35 +127,52 @@ CLI="$(dsh_cli)" say "目标:$CLI plugin --profile web add $PKG@${SPEC}(profile: ${PROFILE_DIR})" if [ "$DRY_RUN" = true ]; then - say "[dry-run] 步骤 1:确保 $WS_YML 含 allowBuilds(node-pty/protobufjs: true)" + say "[dry-run] 步骤 1:确保 $WS_YML 含 allowBuilds(node-pty/protobufjs: true)与 minimumReleaseAgeExclude(${PKG})" 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 + say "[dry-run] 步骤 3:校验 dsh.profile.bundles 含 $PKG" + say "[dry-run] 步骤 4:幂等移除 $PATCH_YML 里旧的 better-sidebar 手动挂载行(避免双挂载)" + if [ "$RESTART" = true ]; then say "[dry-run] 步骤 5:pm2 restart dsh-web"; else say "[dry-run] 步骤 5:提示用户手动重启 DSH"; fi exit 0 fi -# 步骤 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 - say "allowBuilds 已就绪,跳过" -fi +# 步骤 1:预写 workspace 设置(幂等),保证 pnpm 不拦截构建、不放行旧版本策略 +WS_RESULT="$(node -e ' +const fs = require("fs"); +const p = process.argv[1]; +let t = fs.readFileSync(p, "utf8"); +const before = t; +// allowBuilds:把 node-pty/protobufjs 归位为 true +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"); + } + } +} +// minimumReleaseAgeExclude:放行本插件(版本无关),避免 <24h 新版本被拒 +if (!/^\s*-\s+dsh-better-sidebar\s*$/m.test(t)) { + if (/^\s*minimumReleaseAgeExclude:\s*$/m.test(t)) { + t = t.replace(/^(\s*minimumReleaseAgeExclude:\s*)$/m, "$1\n - dsh-better-sidebar"); + } else { + t += "\nminimumReleaseAgeExclude:\n - dsh-better-sidebar\n"; + } +} +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)+ minimumReleaseAgeExclude(${PKG})" \ + || say "workspace 设置已就绪,跳过" # 步骤 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" + warn "dsh plugin add 失败。已预写 allowBuilds 与 minimumReleaseAgeExclude,仍失败的可能原因:" + warn " - 网络/登录问题:npm registry 不可达或需要登录。" + warn " - 依赖安装冲突:可手动重试 cd $PROFILE_DIR && pnpm install。" exit 1 fi @@ -133,24 +187,46 @@ if ! node -e ' warn "若上面的 pnpm 输出提示 ignored build scripts,请确认 $WS_YML 的 allowBuilds 后重跑本脚本。" exit 1 fi -say "bundle 已注册:dsh.profile.bundles 包含 $PKG(下次启动自动挂载)" +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 +MOUNT_RESULT="$(node -e ' +const fs = require("fs"); +const p = process.argv[1]; +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) => /id:\s*better-sidebar\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")" +[ "$MOUNT_RESULT" = "removed" ] \ + && say "已从 $PATCH_YML 移除旧的 better-sidebar 手动挂载行(bundle 通道接管挂载)" \ + || say "无旧手动挂载行,跳过" say "安装完成:$PKG@$SPEC"