dsh-github-flow 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.en.md +287 -69
  2. package/README.md +290 -68
  3. package/docs/architecture.md +71 -14
  4. package/docs/images/1.png +0 -0
  5. package/docs/images/2.png +0 -0
  6. package/docs/images/3.png +0 -0
  7. package/docs/images/4.png +0 -0
  8. package/docs/npm-publish-guide.md +12 -0
  9. package/lib/api/route-helpers.d.ts +47 -0
  10. package/lib/api/route-helpers.js +134 -0
  11. package/lib/api/routes-actions.d.ts +9 -0
  12. package/lib/api/routes-actions.js +115 -0
  13. package/lib/api/routes-lifecycle.d.ts +9 -0
  14. package/lib/api/routes-lifecycle.js +75 -0
  15. package/lib/api/routes-overview.d.ts +11 -0
  16. package/lib/api/routes-overview.js +227 -0
  17. package/lib/api/routes-proxy.d.ts +18 -0
  18. package/lib/api/routes-proxy.js +63 -0
  19. package/lib/api/routes.d.ts +17 -5
  20. package/lib/api/routes.js +18 -417
  21. package/lib/client.js +1179 -40
  22. package/lib/commands/gh.d.ts +2 -1
  23. package/lib/commands/gh.js +40 -3
  24. package/lib/executor.d.ts +17 -0
  25. package/lib/executor.js +40 -2
  26. package/lib/main.d.ts +12 -0
  27. package/lib/main.js +73 -4
  28. package/lib/proxy.d.ts +87 -0
  29. package/lib/proxy.js +275 -0
  30. package/lib/renderers/markdown.d.ts +24 -0
  31. package/lib/renderers/markdown.js +493 -0
  32. package/lib/tools/api.d.ts +4 -0
  33. package/lib/tools/api.js +14 -4
  34. package/lib/tools/help.d.ts +31 -0
  35. package/lib/tools/help.js +212 -0
  36. package/lib/tools/issue.d.ts +32 -1
  37. package/lib/tools/issue.js +94 -17
  38. package/lib/tools/pr.d.ts +22 -1
  39. package/lib/tools/pr.js +67 -15
  40. package/lib/tools/release.d.ts +89 -0
  41. package/lib/tools/release.js +219 -0
  42. package/lib/tools/repo.d.ts +13 -1
  43. package/lib/tools/repo.js +46 -14
  44. package/lib/tools/run.d.ts +22 -1
  45. package/lib/tools/run.js +74 -14
  46. package/lib/tools/secret.d.ts +60 -0
  47. package/lib/tools/secret.js +175 -0
  48. package/lib/tools/skill.d.ts +63 -0
  49. package/lib/tools/skill.js +146 -0
  50. package/lib/types.d.ts +57 -0
  51. package/lib/updater.d.ts +49 -0
  52. package/lib/updater.js +169 -0
  53. package/lib/upgrade-manager.d.ts +58 -0
  54. package/lib/upgrade-manager.js +309 -0
  55. package/lib/version.d.ts +13 -0
  56. package/lib/version.js +13 -0
  57. package/package.json +3 -2
  58. package/lib/client/icons.d.ts +0 -13
  59. package/lib/client/icons.js +0 -41
  60. package/lib/client/index.d.ts +0 -3
  61. package/lib/client/index.js +0 -36
  62. package/lib/client/panel.d.ts +0 -1
  63. package/lib/client/panel.js +0 -102
  64. package/lib/client/style.d.ts +0 -1
  65. package/lib/client/style.js +0 -555
package/lib/updater.js ADDED
@@ -0,0 +1,169 @@
1
+ import { CURRENT_PLUGIN_VERSION, PLUGIN_PACKAGE_NAME, NPM_PACKAGE_URL, NPM_REGISTRY_MIRROR, NPM_REGISTRY_OFFICIAL, } from './version.js';
2
+ /**
3
+ * 极简语义化版本大小比较函数 (latest > current 时返回 true)
4
+ *
5
+ * 为什么自研而不引入 semver npm 依赖:
6
+ * 避免为了简单的 x.y.z 比对而引入数十个传递子依赖,保持 DSH 插件轻量纯净与零运行期依赖负担。
7
+ * 约束条件:标准处理 x.y.z 形式,自动过滤前导 'v' 或 '^' 等非数字字符,忽略 pre-release 标签后续对比。
8
+ */
9
+ export function isNewerVersion(current, latest) {
10
+ if (!current || !latest)
11
+ return false;
12
+ const parseParts = (v) => {
13
+ return v
14
+ .replace(/^[^\d]*/, '')
15
+ .split('-')[0]
16
+ .split('.')
17
+ .map((part) => parseInt(part, 10) || 0);
18
+ };
19
+ const cParts = parseParts(current);
20
+ const lParts = parseParts(latest);
21
+ const len = Math.max(cParts.length, lParts.length, 3);
22
+ for (let i = 0; i < len; i++) {
23
+ const c = cParts[i] ?? 0;
24
+ const l = lParts[i] ?? 0;
25
+ if (l > c)
26
+ return true;
27
+ if (l < c)
28
+ return false;
29
+ }
30
+ return false;
31
+ }
32
+ /**
33
+ * 插件版本检测引擎
34
+ *
35
+ * 架构约束与为什么这样设计:
36
+ * 1. 业务解耦:getQuickStatus 同步读取内存缓存,耗时 0ms,供主业务路由无感调用;
37
+ * 2. 闲时触发:check() 异步外网探测,首选淘宝镜像加速 (npmmirror.com),官方 npmjs 兜底;
38
+ * 3. 避免击穿:通过 inFlightPromise 合并并发调用,防止多处同时触发重复请求;
39
+ * 4. 彻底容灾:任何网络或解析异常 100% 内部吸收,返回 hasUpdate: false,绝不影响主流程。
40
+ */
41
+ export class UpdateChecker {
42
+ cachedInfo = null;
43
+ inFlightPromise = null;
44
+ intervalMs;
45
+ customRegistry;
46
+ constructor(options = {}) {
47
+ // 默认缓存 24 小时,杜绝高频击穿 npm
48
+ this.intervalMs = typeof options.intervalMs === 'number' && options.intervalMs > 0
49
+ ? options.intervalMs
50
+ : 86_400_000;
51
+ this.customRegistry = options.customRegistry;
52
+ }
53
+ /**
54
+ * 应用代理到 npm 探测的 fetch 链路。
55
+ * 由 main.ts 在 ProxyManager 初始化/变更后调用:undici dispatcher 是进程级全局,
56
+ * 这里只做一次转发,真正的缓存/重建逻辑在 ProxyManager.applyFetchDispatcher 内。
57
+ */
58
+ applyProxy(manager) {
59
+ if (manager)
60
+ manager.applyFetchDispatcher();
61
+ }
62
+ /**
63
+ * 同步获取当前已知的版本状态(0ms 纯内存读取,绝不发外网请求)
64
+ */
65
+ getQuickStatus() {
66
+ if (this.cachedInfo) {
67
+ return this.cachedInfo;
68
+ }
69
+ return {
70
+ currentVersion: CURRENT_PLUGIN_VERSION,
71
+ hasUpdate: false,
72
+ upgradeCommand: `pnpm dsh plugin update ${PLUGIN_PACKAGE_NAME}`,
73
+ allCommands: {
74
+ dshCli: `pnpm dsh plugin update ${PLUGIN_PACKAGE_NAME}`,
75
+ profile: `dsh plugin --profile default add ${PLUGIN_PACKAGE_NAME}@latest`,
76
+ pnpm: `pnpm add ${PLUGIN_PACKAGE_NAME}@latest`,
77
+ npm: `npm install -g ${PLUGIN_PACKAGE_NAME}@latest`,
78
+ },
79
+ releaseNotesUrl: `${NPM_PACKAGE_URL}?activeTab=versions`,
80
+ checkedAt: 0,
81
+ };
82
+ }
83
+ /**
84
+ * 异步触发版本检测
85
+ * @param force 是否忽略 24h 缓存强制穿透探测
86
+ */
87
+ async check(force = false) {
88
+ const now = Date.now();
89
+ // 1. 命中有效缓存直接返回
90
+ if (!force && this.cachedInfo && now - this.cachedInfo.checkedAt < this.intervalMs) {
91
+ return this.cachedInfo;
92
+ }
93
+ // 2. 复用正在途中的网络请求,避免并发重复打 npm
94
+ if (this.inFlightPromise) {
95
+ return this.inFlightPromise;
96
+ }
97
+ this.inFlightPromise = this.performCheck(now).finally(() => {
98
+ this.inFlightPromise = null;
99
+ });
100
+ return this.inFlightPromise;
101
+ }
102
+ /**
103
+ * 内部双 Registry 竞速/降级探测
104
+ */
105
+ async performCheck(now) {
106
+ const fallback = this.getQuickStatus();
107
+ // 双通道策略:用户自定义源 > 国内淘宝镜像 (2.5s极速) > 官方 npmjs 源 (3.5s兜底)
108
+ const endpoints = [];
109
+ if (this.customRegistry) {
110
+ endpoints.push(`${this.customRegistry.replace(/\/$/, '')}/${PLUGIN_PACKAGE_NAME}/latest`);
111
+ }
112
+ else {
113
+ endpoints.push(`${NPM_REGISTRY_MIRROR}/${PLUGIN_PACKAGE_NAME}/latest`);
114
+ endpoints.push(`${NPM_REGISTRY_OFFICIAL}/${PLUGIN_PACKAGE_NAME}/latest`);
115
+ }
116
+ let latestVersion = null;
117
+ for (const url of endpoints) {
118
+ try {
119
+ const isMirror = url.includes('npmmirror');
120
+ const timeoutMs = isMirror ? 2_500 : 3_500;
121
+ const controller = new AbortController();
122
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
123
+ const res = await fetch(url, {
124
+ signal: controller.signal,
125
+ headers: {
126
+ 'Accept': 'application/json',
127
+ 'User-Agent': `dsh-plugin-updater/${CURRENT_PLUGIN_VERSION}`,
128
+ },
129
+ });
130
+ clearTimeout(timer);
131
+ if (res.ok) {
132
+ const json = (await res.json());
133
+ if (json && typeof json.version === 'string' && json.version.trim()) {
134
+ latestVersion = json.version.trim();
135
+ break;
136
+ }
137
+ }
138
+ }
139
+ catch {
140
+ // 单个源故障静默尝试下一个备用源,确保高可用
141
+ }
142
+ }
143
+ // 若双源均未拉取到版本,保留保底数据并记录时间戳,防止密集重试
144
+ if (!latestVersion) {
145
+ this.cachedInfo = {
146
+ ...fallback,
147
+ checkedAt: now,
148
+ };
149
+ return this.cachedInfo;
150
+ }
151
+ const hasUpdate = isNewerVersion(CURRENT_PLUGIN_VERSION, latestVersion);
152
+ const targetPkg = `${PLUGIN_PACKAGE_NAME}@${latestVersion}`;
153
+ this.cachedInfo = {
154
+ currentVersion: CURRENT_PLUGIN_VERSION,
155
+ latestVersion,
156
+ hasUpdate,
157
+ upgradeCommand: `pnpm dsh plugin add ${targetPkg}`,
158
+ allCommands: {
159
+ dshCli: `pnpm dsh plugin add ${targetPkg}`,
160
+ profile: `dsh plugin --profile default add ${targetPkg}`,
161
+ pnpm: `pnpm add ${targetPkg}`,
162
+ npm: `npm install -g ${targetPkg}`,
163
+ },
164
+ releaseNotesUrl: `${NPM_PACKAGE_URL}?activeTab=versions`,
165
+ checkedAt: now,
166
+ };
167
+ return this.cachedInfo;
168
+ }
169
+ }
@@ -0,0 +1,58 @@
1
+ import type { UpgradeExecutionResult, UpgradeStatusResult } from './types.js';
2
+ import type { ProxyManager } from './proxy.js';
3
+ /**
4
+ * 构建跨平台环境自愈增强的 ProcessEnv (重点解决 macOS GUI 应用丢失 PATH 的痛点)
5
+ *
6
+ * 为什么必须这样做:
7
+ * 1. 在 macOS 系统上,若 DSH 桌面客户端由 Finder / Dock / Spotlight (GUI) 启动,进程默认只继承
8
+ * 极简系统 PATH (/usr/bin:/bin:/usr/sbin:/sbin),不会自动读取用户 shell 配置文件 (~/.zshrc, ~/.bash_profile);
9
+ * 2. 用户通过 Homebrew (/opt/homebrew/bin 或 /usr/local/bin) 或 nvm 安装的 node、npm、pnpm 默认在 GUI 下不可见;
10
+ * 3. 动态将当前 node 运行时所在目录 (path.dirname(process.execPath)) 及 Mac 常用包管理器路径注入 PATH 首端,
11
+ * 彻底实现 macOS 与 Linux 环境下子进程对 npm / pnpm 命令的 100% 自愈发现。
12
+ */
13
+ export declare function buildCrossPlatformEnv(): NodeJS.ProcessEnv;
14
+ /**
15
+ * 自动升级执行引擎
16
+ *
17
+ * 核心设计考量与约束条件:
18
+ * 1. 互斥保护 (Mutex):同一时刻仅允许一个升级任务执行,防止用户连击或并发请求击垮包管理器 lockfile;
19
+ * 2. 环境自省 (Dev-Link Safe):检测是否在本地源码工作区调试,若是则阻断自动覆盖,保护源码不被破坏;
20
+ * 3. 跨平台兼容 (macOS / Windows / Linux):自动适配 cmd.exe 与 sh/zsh,自愈补全 macOS PATH 变量;
21
+ * 4. 稳态超时:设置 60s 进程上限,杜绝子进程因网络悬挂死锁宿主 WebServer;
22
+ * 5. 重启指引:Node.js 运行时内存不可变,升级成功后明确要求重启 DSH 彻底加载最新后端能力。
23
+ */
24
+ export declare class UpgradeManager {
25
+ private isUpgrading;
26
+ private lastResult;
27
+ private logBuffer;
28
+ /** 代理管理器(可选):注入后一键升级的 npm/pnpm 子进程走用户代理 */
29
+ private proxyManager?;
30
+ attachProxyManager(manager: ProxyManager): void;
31
+ /**
32
+ * 探测当前插件是否是以本地 Git 源码软链接方式运行
33
+ */
34
+ isDevLinked(): boolean;
35
+ /**
36
+ * 智能识别当前 DSH 运行环境 (桌面版 vs Web/CLI 版)
37
+ *
38
+ * 依据事实源:
39
+ * 1. 桌面版启动时会固定注入 DSH_PROFILE=desktop 与 DSH_PROFILE_DIR (~/.dsh/profiles/desktop);
40
+ * 2. Web版/CLI版通常 DSH_PROFILE 为 default 或自定义工作区 profile;
41
+ * 3. 跨平台支持:通过 os.homedir() 安全解析 ~/.dsh 目录。
42
+ */
43
+ detectEnvironment(): {
44
+ isDesktop: boolean;
45
+ profile: string;
46
+ profileDir?: string;
47
+ isDevLinked: boolean;
48
+ platform: NodeJS.Platform;
49
+ };
50
+ /**
51
+ * 获取当前升级状态
52
+ */
53
+ getStatus(): UpgradeStatusResult;
54
+ /**
55
+ * 执行一键升级核心流程
56
+ */
57
+ executeUpgrade(targetVersion?: string): Promise<UpgradeExecutionResult>;
58
+ }
@@ -0,0 +1,309 @@
1
+ import { spawn } from 'node:child_process';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import os from 'node:os';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { PLUGIN_PACKAGE_NAME } from './version.js';
7
+ /**
8
+ * 构建跨平台环境自愈增强的 ProcessEnv (重点解决 macOS GUI 应用丢失 PATH 的痛点)
9
+ *
10
+ * 为什么必须这样做:
11
+ * 1. 在 macOS 系统上,若 DSH 桌面客户端由 Finder / Dock / Spotlight (GUI) 启动,进程默认只继承
12
+ * 极简系统 PATH (/usr/bin:/bin:/usr/sbin:/sbin),不会自动读取用户 shell 配置文件 (~/.zshrc, ~/.bash_profile);
13
+ * 2. 用户通过 Homebrew (/opt/homebrew/bin 或 /usr/local/bin) 或 nvm 安装的 node、npm、pnpm 默认在 GUI 下不可见;
14
+ * 3. 动态将当前 node 运行时所在目录 (path.dirname(process.execPath)) 及 Mac 常用包管理器路径注入 PATH 首端,
15
+ * 彻底实现 macOS 与 Linux 环境下子进程对 npm / pnpm 命令的 100% 自愈发现。
16
+ */
17
+ export function buildCrossPlatformEnv() {
18
+ const currentPath = process.env.PATH || '';
19
+ const extraPaths = [];
20
+ const isMac = process.platform === 'darwin';
21
+ const isWin = process.platform === 'win32';
22
+ // 1. 当前正在运行的 node 自身二进制文件所在目录(最优先,必然有配套 npm)
23
+ if (process.execPath) {
24
+ try {
25
+ extraPaths.push(path.dirname(process.execPath));
26
+ }
27
+ catch { }
28
+ }
29
+ // 2. macOS 专有软件与包管理器安装路径
30
+ if (isMac) {
31
+ extraPaths.push('/opt/homebrew/bin'); // Apple Silicon Homebrew
32
+ extraPaths.push('/opt/homebrew/sbin');
33
+ extraPaths.push('/usr/local/bin'); // Intel Mac Homebrew
34
+ extraPaths.push('/usr/local/sbin');
35
+ extraPaths.push(path.join(os.homedir(), '.local/bin'));
36
+ extraPaths.push(path.join(os.homedir(), 'Library/pnpm'));
37
+ extraPaths.push(path.join(os.homedir(), '.nvm/current/bin'));
38
+ }
39
+ // 3. Linux 专有常见路径
40
+ if (process.platform === 'linux') {
41
+ extraPaths.push('/usr/local/bin');
42
+ extraPaths.push('/home/linuxbrew/.linuxbrew/bin');
43
+ extraPaths.push(path.join(os.homedir(), '.local/bin'));
44
+ }
45
+ const separator = isWin ? ';' : ':';
46
+ const uniquePaths = Array.from(new Set([...extraPaths, ...currentPath.split(separator)])).filter(Boolean);
47
+ return {
48
+ ...process.env,
49
+ PATH: uniquePaths.join(separator),
50
+ NPM_CONFIG_REGISTRY: 'https://registry.npmmirror.com', // 优先国内源加速
51
+ LANG: 'zh_CN.UTF-8',
52
+ LC_ALL: 'zh_CN.UTF-8',
53
+ };
54
+ }
55
+ /**
56
+ * 自动升级执行引擎
57
+ *
58
+ * 核心设计考量与约束条件:
59
+ * 1. 互斥保护 (Mutex):同一时刻仅允许一个升级任务执行,防止用户连击或并发请求击垮包管理器 lockfile;
60
+ * 2. 环境自省 (Dev-Link Safe):检测是否在本地源码工作区调试,若是则阻断自动覆盖,保护源码不被破坏;
61
+ * 3. 跨平台兼容 (macOS / Windows / Linux):自动适配 cmd.exe 与 sh/zsh,自愈补全 macOS PATH 变量;
62
+ * 4. 稳态超时:设置 60s 进程上限,杜绝子进程因网络悬挂死锁宿主 WebServer;
63
+ * 5. 重启指引:Node.js 运行时内存不可变,升级成功后明确要求重启 DSH 彻底加载最新后端能力。
64
+ */
65
+ export class UpgradeManager {
66
+ isUpgrading = false;
67
+ lastResult = {
68
+ state: 'idle',
69
+ };
70
+ logBuffer = [];
71
+ /** 代理管理器(可选):注入后一键升级的 npm/pnpm 子进程走用户代理 */
72
+ proxyManager;
73
+ attachProxyManager(manager) {
74
+ this.proxyManager = manager;
75
+ }
76
+ /**
77
+ * 探测当前插件是否是以本地 Git 源码软链接方式运行
78
+ */
79
+ isDevLinked() {
80
+ try {
81
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
82
+ const isInsideNodeModules = currentDir.includes('node_modules');
83
+ // 检查上级或同级是否存在 .git 目录
84
+ const gitDirParent = path.resolve(currentDir, '../.git');
85
+ const gitDirRoot = path.resolve(currentDir, '../../.git');
86
+ const hasGit = fs.existsSync(gitDirParent) || fs.existsSync(gitDirRoot);
87
+ return !isInsideNodeModules && hasGit;
88
+ }
89
+ catch {
90
+ return false;
91
+ }
92
+ }
93
+ /**
94
+ * 智能识别当前 DSH 运行环境 (桌面版 vs Web/CLI 版)
95
+ *
96
+ * 依据事实源:
97
+ * 1. 桌面版启动时会固定注入 DSH_PROFILE=desktop 与 DSH_PROFILE_DIR (~/.dsh/profiles/desktop);
98
+ * 2. Web版/CLI版通常 DSH_PROFILE 为 default 或自定义工作区 profile;
99
+ * 3. 跨平台支持:通过 os.homedir() 安全解析 ~/.dsh 目录。
100
+ */
101
+ detectEnvironment() {
102
+ const profile = (process.env.DSH_PROFILE || '').trim();
103
+ const profileDir = (process.env.DSH_PROFILE_DIR || '').trim();
104
+ const isDesktop = profile === 'desktop' || profileDir.toLowerCase().includes('desktop');
105
+ const isDevLinked = this.isDevLinked();
106
+ const platform = process.platform;
107
+ return {
108
+ isDesktop,
109
+ profile: profile || 'default',
110
+ profileDir: profileDir || undefined,
111
+ isDevLinked,
112
+ platform,
113
+ };
114
+ }
115
+ /**
116
+ * 获取当前升级状态
117
+ */
118
+ getStatus() {
119
+ const env = this.detectEnvironment();
120
+ return {
121
+ ...this.lastResult,
122
+ environment: env.isDesktop ? 'desktop' : 'web',
123
+ log: this.logBuffer.join('\n'),
124
+ };
125
+ }
126
+ /**
127
+ * 执行一键升级核心流程
128
+ */
129
+ async executeUpgrade(targetVersion) {
130
+ const env = this.detectEnvironment();
131
+ if (this.isUpgrading) {
132
+ return {
133
+ ok: false,
134
+ message: '升级任务已在进行中,请勿重复点击。',
135
+ needRestart: false,
136
+ environment: env.isDesktop ? 'desktop' : 'web',
137
+ log: this.logBuffer.join('\n'),
138
+ };
139
+ }
140
+ if (env.isDevLinked) {
141
+ const devMsg = '检测到当前插件正在以本地 Git 源码软链接模式 (link:) 运行,请在源码目录执行 git pull 更新,已自动阻止 npm 覆盖以保护工作区代码。';
142
+ this.lastResult = {
143
+ state: 'failed',
144
+ targetVersion,
145
+ message: devMsg,
146
+ completedAt: Date.now(),
147
+ environment: env.isDesktop ? 'desktop' : 'web',
148
+ };
149
+ return {
150
+ ok: false,
151
+ message: devMsg,
152
+ needRestart: false,
153
+ environment: env.isDesktop ? 'desktop' : 'web',
154
+ log: 'Operation blocked by dev-linked protection.',
155
+ };
156
+ }
157
+ this.isUpgrading = true;
158
+ this.logBuffer = [];
159
+ const startedAt = Date.now();
160
+ const pkgWithVer = targetVersion ? `${PLUGIN_PACKAGE_NAME}@${targetVersion}` : `${PLUGIN_PACKAGE_NAME}@latest`;
161
+ this.lastResult = {
162
+ state: 'upgrading',
163
+ targetVersion,
164
+ message: `正在执行命令拉取 ${pkgWithVer}...`,
165
+ startedAt,
166
+ environment: env.isDesktop ? 'desktop' : 'web',
167
+ };
168
+ return new Promise((resolve) => {
169
+ const isWin = process.platform === 'win32';
170
+ let command = '';
171
+ let executionCwd = process.cwd();
172
+ // 根据环境类型分流升级指令(跨平台安全路径):
173
+ if (env.isDesktop) {
174
+ // 场景 A:DSH 桌面版环境(macOS / Windows / Linux 桌面端通用)
175
+ const dshHome = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
176
+ const fallbackDesktopCwd = env.profileDir || path.join(dshHome, 'profiles', 'desktop');
177
+ if (fs.existsSync(fallbackDesktopCwd)) {
178
+ executionCwd = fallbackDesktopCwd;
179
+ command = `dsh plugin --profile desktop add ${pkgWithVer} || npm install ${pkgWithVer} --save`;
180
+ }
181
+ else {
182
+ command = `dsh plugin --profile desktop add ${pkgWithVer} || npm install -g ${pkgWithVer}`;
183
+ }
184
+ }
185
+ else {
186
+ // 场景 B:Web 版 / 开发者终端 CLI 版环境
187
+ const profile = env.profile || 'default';
188
+ command = `pnpm dsh plugin add ${pkgWithVer} || dsh plugin --profile ${profile} add ${pkgWithVer} || npm install -g ${pkgWithVer}`;
189
+ }
190
+ // 跨平台 Shell 选型与参数调度:
191
+ // - Windows: 采用 cmd.exe (/d /s /c),完美原生支持 '||' 且冷启动仅 ~10ms,规避 powershell 5.1 语法报错;
192
+ // - macOS / Linux: 优先采用用户的 SHELL (/bin/zsh 或 /bin/bash),兜底 /bin/sh,参数为 -c。
193
+ const shell = isWin
194
+ ? (process.env.ComSpec || 'cmd.exe')
195
+ : (process.env.SHELL || '/bin/sh');
196
+ const args = isWin
197
+ ? ['/d', '/s', '/c', command]
198
+ : ['-c', command];
199
+ let settled = false;
200
+ const safeSettle = (result) => {
201
+ if (settled)
202
+ return;
203
+ settled = true;
204
+ this.isUpgrading = false;
205
+ resolve(result);
206
+ };
207
+ // 注入跨平台增强环境(尤其治愈 macOS GUI 启动丢失 PATH 的痛点)
208
+ // 代理环境变量按 applyToPackageManager 开关合并:npm/pnpm 遵循 HTTPS_PROXY,
209
+ // 与 gh 链路同一管理器,保证「设置代理后升级也走代理」的体验一致性。
210
+ const childEnv = {
211
+ ...buildCrossPlatformEnv(),
212
+ ...(this.proxyManager?.getEnv('package-manager') || {}),
213
+ };
214
+ const child = spawn(shell, args, {
215
+ cwd: executionCwd,
216
+ windowsHide: true,
217
+ env: childEnv,
218
+ });
219
+ const timeoutTimer = setTimeout(() => {
220
+ child.kill();
221
+ const timeoutMsg = '升级命令执行超时(60 秒),可能是网络阻塞或包管理器锁占用,建议在终端手动执行升级。';
222
+ this.lastResult = {
223
+ state: 'failed',
224
+ targetVersion,
225
+ message: timeoutMsg,
226
+ completedAt: Date.now(),
227
+ environment: env.isDesktop ? 'desktop' : 'web',
228
+ };
229
+ safeSettle({
230
+ ok: false,
231
+ message: timeoutMsg,
232
+ needRestart: false,
233
+ environment: env.isDesktop ? 'desktop' : 'web',
234
+ log: this.logBuffer.join('\n'),
235
+ });
236
+ }, 60_000);
237
+ child.stdout?.on('data', (chunk) => {
238
+ const text = chunk.toString();
239
+ this.logBuffer.push(text);
240
+ if (this.logBuffer.length > 200)
241
+ this.logBuffer.shift();
242
+ });
243
+ child.stderr?.on('data', (chunk) => {
244
+ const text = chunk.toString();
245
+ this.logBuffer.push(text);
246
+ if (this.logBuffer.length > 200)
247
+ this.logBuffer.shift();
248
+ });
249
+ child.on('close', (code) => {
250
+ clearTimeout(timeoutTimer);
251
+ const completedAt = Date.now();
252
+ if (code === 0) {
253
+ const successMsg = env.isDesktop
254
+ ? `升级成功!已安装最新依赖 ${pkgWithVer} 到桌面版 Profile。需重启 DSH 桌面客户端彻底生效后端工具,或刷新当前界面。`
255
+ : `升级成功!已安装最新依赖 ${pkgWithVer}。需重启 DSH 服务彻底生效后端工具,或刷新网页先体验新版界面。`;
256
+ this.lastResult = {
257
+ state: 'success',
258
+ targetVersion,
259
+ message: successMsg,
260
+ completedAt,
261
+ environment: env.isDesktop ? 'desktop' : 'web',
262
+ };
263
+ safeSettle({
264
+ ok: true,
265
+ message: successMsg,
266
+ needRestart: true,
267
+ environment: env.isDesktop ? 'desktop' : 'web',
268
+ log: this.logBuffer.join('\n'),
269
+ });
270
+ }
271
+ else {
272
+ const failedMsg = `升级退出码异常 (${code}),可能需要管理员权限或在宿主环境终端手动执行。`;
273
+ this.lastResult = {
274
+ state: 'failed',
275
+ targetVersion,
276
+ message: failedMsg,
277
+ completedAt,
278
+ environment: env.isDesktop ? 'desktop' : 'web',
279
+ };
280
+ safeSettle({
281
+ ok: false,
282
+ message: failedMsg,
283
+ needRestart: false,
284
+ environment: env.isDesktop ? 'desktop' : 'web',
285
+ log: this.logBuffer.join('\n'),
286
+ });
287
+ }
288
+ });
289
+ child.on('error', (err) => {
290
+ clearTimeout(timeoutTimer);
291
+ const errMsg = `无法启动安装子进程: ${err.message}`;
292
+ this.lastResult = {
293
+ state: 'failed',
294
+ targetVersion,
295
+ message: errMsg,
296
+ completedAt: Date.now(),
297
+ environment: env.isDesktop ? 'desktop' : 'web',
298
+ };
299
+ safeSettle({
300
+ ok: false,
301
+ message: errMsg,
302
+ needRestart: false,
303
+ environment: env.isDesktop ? 'desktop' : 'web',
304
+ log: this.logBuffer.join('\n'),
305
+ });
306
+ });
307
+ });
308
+ }
309
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 插件版本与元数据模块
3
+ *
4
+ * 为什么独立为此文件:
5
+ * 1. 运行时需要精确获知自身当前安装版本与包标识,避免硬编码分散在各业务模块;
6
+ * 2. 避免在 ESM 运行时动态读取 package.json 引入 fs 权限和路径解析边界问题;
7
+ * 3. 方便在 CI 发布流水线或 build 脚本中自动化校准。
8
+ */
9
+ export declare const CURRENT_PLUGIN_VERSION = "0.1.8";
10
+ export declare const PLUGIN_PACKAGE_NAME = "dsh-github-flow";
11
+ export declare const NPM_PACKAGE_URL = "https://www.npmjs.com/package/dsh-github-flow";
12
+ export declare const NPM_REGISTRY_MIRROR = "https://registry.npmmirror.com";
13
+ export declare const NPM_REGISTRY_OFFICIAL = "https://registry.npmjs.org";
package/lib/version.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * 插件版本与元数据模块
3
+ *
4
+ * 为什么独立为此文件:
5
+ * 1. 运行时需要精确获知自身当前安装版本与包标识,避免硬编码分散在各业务模块;
6
+ * 2. 避免在 ESM 运行时动态读取 package.json 引入 fs 权限和路径解析边界问题;
7
+ * 3. 方便在 CI 发布流水线或 build 脚本中自动化校准。
8
+ */
9
+ export const CURRENT_PLUGIN_VERSION = '0.1.8';
10
+ export const PLUGIN_PACKAGE_NAME = 'dsh-github-flow';
11
+ export const NPM_PACKAGE_URL = 'https://www.npmjs.com/package/dsh-github-flow';
12
+ export const NPM_REGISTRY_MIRROR = 'https://registry.npmmirror.com';
13
+ export const NPM_REGISTRY_OFFICIAL = 'https://registry.npmjs.org';
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "dsh-github-flow",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "icon": "icon.svg",
5
- "description": "GitHub Flow: Native GitHub CLI (gh) integration for DeepSeek Harness — Agent Tools, Slash Commands, and Web UI Workspace",
5
+ "description": "GitHub 工作流引擎:为 DeepSeek Harness 提供原生 GitHub CLI (gh) 集成——9 大 Agent 领域工具(PR 审查合并、Issue 任务流转、CI 失败诊断、发版交付、密钥管理、Agent Skills 生态)、/gh 人类快捷指令、Web UI 全景驾驶舱与一键跨平台自动升级;零凭证侵入复用宿主机 gh 登录态,内置 Token 防溢出截断、高危操作二次确认与密文安全输入通道。",
6
6
  "type": "module",
7
7
  "main": "./lib/main.js",
8
8
  "repository": {
@@ -30,6 +30,7 @@
30
30
  ],
31
31
  "scripts": {
32
32
  "build": "tsc && node scripts/build.mjs",
33
+ "test": "node test/run-all.mjs",
33
34
  "prepare": "pnpm run build"
34
35
  },
35
36
  "dsh": {
@@ -1,13 +0,0 @@
1
- /**
2
- * GitHub 官方高精度的 Invertocat 矢量路径
3
- */
4
- export declare const GITHUB_OCTOCAT_PATH = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.72 6.56 11.52C4.78 11.32 2.92 10.63 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.32 9.47 11.52C9.76 11.77 10.01 12.25 10.01 13C10.01 14.08 10 14.95 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z";
5
- /**
6
- * 基础矢量 Logo
7
- */
8
- export declare function createOctocatSvg(size?: number, color?: string): any;
9
- /**
10
- * 现代暗黑科技感徽标 Artwork(用于卡片与主要入口)
11
- * 带有暗夜流光微渐变与精致白描
12
- */
13
- export declare function createOctocatBadge(size?: number): any;
@@ -1,41 +0,0 @@
1
- /**
2
- * GitHub 官方高精度的 Invertocat 矢量路径
3
- */
4
- export const GITHUB_OCTOCAT_PATH = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.72 6.56 11.52C4.78 11.32 2.92 10.63 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.32 9.47 11.52C9.76 11.77 10.01 12.25 10.01 13C10.01 14.08 10 14.95 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z";
5
- /**
6
- * 基础矢量 Logo
7
- */
8
- export function createOctocatSvg(size = 18, color = "currentColor") {
9
- return React.createElement("svg", {
10
- viewBox: "0 0 16 16",
11
- width: size,
12
- height: size,
13
- fill: color,
14
- style: { flexShrink: 0, display: "inline-block", verticalAlign: "middle" }
15
- }, React.createElement("path", {
16
- fillRule: "evenodd",
17
- clipRule: "evenodd",
18
- d: GITHUB_OCTOCAT_PATH
19
- }));
20
- }
21
- /**
22
- * 现代暗黑科技感徽标 Artwork(用于卡片与主要入口)
23
- * 带有暗夜流光微渐变与精致白描
24
- */
25
- export function createOctocatBadge(size = 36) {
26
- var iconSize = Math.round(size * 0.58);
27
- return React.createElement("div", {
28
- style: {
29
- display: "flex",
30
- alignItems: "center",
31
- justifyContent: "center",
32
- width: `${size}px`,
33
- height: `${size}px`,
34
- borderRadius: "10px",
35
- background: "linear-gradient(135deg, #2ea043 0%, #1f6feb 100%)", // GitHub 科技绿蓝微渐变
36
- boxShadow: "0 2px 8px rgba(31, 111, 235, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.2)",
37
- flexShrink: 0,
38
- position: "relative"
39
- }
40
- }, createOctocatSvg(iconSize, "#ffffff"));
41
- }
@@ -1,3 +0,0 @@
1
- export declare const name = "dsh-github-flow";
2
- export declare const inject: readonly ['slots'];
3
- export declare function apply(ctx: any): void;