dsh-bottom-info-bar 1.14.2 → 1.14.3
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.
- package/lib/index.js +121 -5
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeSync } from 'node:fs'
|
|
11
11
|
import { createHash, createHmac, randomUUID } from 'node:crypto'
|
|
12
12
|
import { homedir } from 'node:os'
|
|
13
|
-
import { basename, dirname, join } from 'node:path'
|
|
13
|
+
import { basename, dirname, isAbsolute, join, resolve } from 'node:path'
|
|
14
14
|
// v1.9.0 PR2:字段注册表/预设色名单一来源(ESM 直接 import,构建时把 constants.js 一并复制进 lib/)
|
|
15
15
|
import { FIELD_REGISTRY, PRESET_COLOR_NAMES } from './constants.js'
|
|
16
16
|
import * as hostLocale from './host-locale.js'
|
|
@@ -98,9 +98,120 @@ function dshHomeDir() {
|
|
|
98
98
|
|
|
99
99
|
// 「更新命令」取决于本插件是怎么装上的,两者不能混用:
|
|
100
100
|
// - npm 安装 → dsh plugin add …@latest;
|
|
101
|
-
// - link:(本地代码 / 一键脚本安装)→ git
|
|
101
|
+
// - link:(本地代码 / 一键脚本安装)→ git 快进到远端默认分支。这类安装若改用 npm 命令,会把符号
|
|
102
102
|
// 链接换成 registry 版本,用户本地那份代码从此不再生效(本仓库 install.sh 即此类)。
|
|
103
103
|
// 读不到 profile 配置时按 npm 处理:那是最常见、也是唯一能从 npm 自动更新的形态。
|
|
104
|
+
//
|
|
105
|
+
// link: 形态的两条硬约束(2026-09-22 用户实测「点了标签复制命令、也执行了,但没有更新」后修正):
|
|
106
|
+
// ① 不能依赖「当前分支有可用的上游」:`git pull --ff-only` 在分支没有推到远端时直接失败
|
|
107
|
+
// (Your configuration specifies to merge with the ref … but no such ref was fetched),
|
|
108
|
+
// 而开发分支本来就不该被推到远端——用户遇到的正是这条;
|
|
109
|
+
// ② 要装的是「已发布版本」=远端默认分支:当前不在默认分支时先 checkout 过去再快进,否则即使
|
|
110
|
+
// fetch 成功,装的仍是分支上的旧代码。
|
|
111
|
+
// 全部探测只用只读文件读取完成(host 绝不执行任何命令,见 test-update-check 的守卫);读不出
|
|
112
|
+
// 默认分支时退回保守命令。真正改动用户副本的只有用户自己粘贴的那条命令,且始终是 --ff-only。
|
|
113
|
+
const GIT_LAYOUT_WALK_UP_LIMIT = 40
|
|
114
|
+
|
|
115
|
+
function readTextFileOrNull(file) {
|
|
116
|
+
try { return readFileSync(file, 'utf8') } catch { return null }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 从 link 目标向上找 git 工作副本,返回 { root, gitDir, commonDir };不是 git 副本时返回 null。
|
|
120
|
+
function findGitLayout(target) {
|
|
121
|
+
let dir = target
|
|
122
|
+
for (let step = 0; step < GIT_LAYOUT_WALK_UP_LIMIT; step++) {
|
|
123
|
+
const marker = join(dir, '.git')
|
|
124
|
+
if (existsSync(marker)) {
|
|
125
|
+
let isDirectory = false
|
|
126
|
+
try { isDirectory = statSync(marker).isDirectory() } catch { return null }
|
|
127
|
+
let gitDir = marker
|
|
128
|
+
if (!isDirectory) {
|
|
129
|
+
// 链接工作树 / 子模块:.git 是一个指向真实 gitdir 的文件
|
|
130
|
+
const text = readTextFileOrNull(marker)
|
|
131
|
+
const match = text ? text.match(/^gitdir:\s*(.+?)\s*$/m) : null
|
|
132
|
+
if (!match) return null
|
|
133
|
+
gitDir = isAbsolute(match[1]) ? match[1] : resolve(dir, match[1])
|
|
134
|
+
}
|
|
135
|
+
// 链接工作树:config 与远端 refs 都在 commondir 里(HEAD 仍在 gitDir)
|
|
136
|
+
let commonDir = gitDir
|
|
137
|
+
const common = readTextFileOrNull(join(gitDir, 'commondir'))
|
|
138
|
+
if (common && common.trim().length > 0) {
|
|
139
|
+
commonDir = isAbsolute(common.trim()) ? common.trim() : resolve(gitDir, common.trim())
|
|
140
|
+
}
|
|
141
|
+
return { root: dir, gitDir: gitDir, commonDir: commonDir }
|
|
142
|
+
}
|
|
143
|
+
const parent = dirname(dir)
|
|
144
|
+
if (parent === dir) break
|
|
145
|
+
dir = parent
|
|
146
|
+
}
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function currentBranchOf(layout) {
|
|
151
|
+
const text = readTextFileOrNull(join(layout.gitDir, 'HEAD'))
|
|
152
|
+
const match = text ? text.match(/^ref:\s*refs\/heads\/(.+?)\s*$/m) : null
|
|
153
|
+
return match ? match[1] : null // detached HEAD → null
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function remotesOf(layout) {
|
|
157
|
+
const text = readTextFileOrNull(join(layout.commonDir, 'config'))
|
|
158
|
+
if (!text) return []
|
|
159
|
+
const names = []
|
|
160
|
+
for (const match of text.matchAll(/^\[remote\s+"([^"]+)"\]/gm)) names.push(match[1])
|
|
161
|
+
return names
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// 远端默认分支:优先 refs/remotes/<r>/HEAD(clone 时写好),其次 main / master(松散 ref 或 packed-refs)。
|
|
165
|
+
function defaultBranchOf(layout, remote) {
|
|
166
|
+
const head = readTextFileOrNull(join(layout.commonDir, 'refs', 'remotes', remote, 'HEAD'))
|
|
167
|
+
const symbolic = head ? head.match(/^ref:\s*refs\/remotes\/[^/]+\/(.+?)\s*$/m) : null
|
|
168
|
+
if (symbolic) return symbolic[1]
|
|
169
|
+
const packed = readTextFileOrNull(join(layout.commonDir, 'packed-refs')) || ''
|
|
170
|
+
const escapedRemote = remote.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
171
|
+
for (const candidate of ['main', 'master']) {
|
|
172
|
+
if (existsSync(join(layout.commonDir, 'refs', 'remotes', remote, candidate))) return candidate
|
|
173
|
+
if (new RegExp(' refs/remotes/' + escapedRemote + '/' + candidate + '$', 'm').test(packed)) return candidate
|
|
174
|
+
}
|
|
175
|
+
return null
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// link: 安装的 git 同步命令:目标常是仓库内的子目录(…/dsh-bottom-info-bar/plugin),
|
|
179
|
+
// 一律以探测到的仓库根为准;路径可能含空格,必须做 shell 转义。
|
|
180
|
+
function linkGitSyncCommandFor(target) {
|
|
181
|
+
const fallback = 'git -C ' + shellQuote(target) + ' pull --ff-only'
|
|
182
|
+
const layout = findGitLayout(target)
|
|
183
|
+
if (!layout) return fallback
|
|
184
|
+
const quotedRoot = shellQuote(layout.root)
|
|
185
|
+
const remotes = remotesOf(layout)
|
|
186
|
+
const remote = remotes.indexOf('origin') >= 0 ? 'origin' : remotes[0]
|
|
187
|
+
if (!remote) return 'git -C ' + quotedRoot + ' pull --ff-only'
|
|
188
|
+
const branch = defaultBranchOf(layout, remote)
|
|
189
|
+
if (!branch) return 'git -C ' + quotedRoot + ' pull --ff-only'
|
|
190
|
+
const steps = ['git -C ' + quotedRoot + ' fetch ' + remote]
|
|
191
|
+
if (currentBranchOf(layout) !== branch) steps.push('git -C ' + quotedRoot + ' checkout ' + shellQuote(branch))
|
|
192
|
+
steps.push('git -C ' + quotedRoot + ' merge --ff-only ' + remote + '/' + shellQuote(branch))
|
|
193
|
+
return steps.join(' && ')
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// link: 的完整更新命令 = git 同步 + 重建 lib。
|
|
197
|
+
// plugin/package.json 的 main 指向 lib/index.js,而 lib/ 是构建产物、不入 git;只拉代码不重建,
|
|
198
|
+
// 用户重启后加载的仍是旧 lib —— 等于「更新了但没生效」(install.sh 里同样是先 build 再 add)。
|
|
199
|
+
function linkUpdateCommandFor(target) {
|
|
200
|
+
const gitCommand = linkGitSyncCommandFor(target)
|
|
201
|
+
const buildScript = join(target, 'scripts', 'build.mjs')
|
|
202
|
+
if (!existsSync(buildScript)) return gitCommand
|
|
203
|
+
return gitCommand + ' && node ' + shellQuote(buildScript)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// 命令行里的路径必须转义:克隆目录可能带空格(/Users/John Doe/dev/…)。
|
|
207
|
+
// POSIX 用单引号(内部单引号按 '\'' 拼接);Windows 用双引号(PowerShell / cmd 都认)。
|
|
208
|
+
function shellQuote(value) {
|
|
209
|
+
const text = String(value)
|
|
210
|
+
if (/^[A-Za-z0-9@%_+=:,./-]+$/.test(text)) return text
|
|
211
|
+
if (process.platform === 'win32') return '"' + text.replace(/"/g, '""') + '"'
|
|
212
|
+
return "'" + text.replace(/'/g, "'\\''") + "'"
|
|
213
|
+
}
|
|
214
|
+
|
|
104
215
|
function updateCommandForInstall() {
|
|
105
216
|
const profile = runningProfileName()
|
|
106
217
|
const npmCommand = 'dsh plugin --profile ' + profile + ' add dsh-bottom-info-bar@latest'
|
|
@@ -110,7 +221,7 @@ function updateCommandForInstall() {
|
|
|
110
221
|
const spec = pkg && pkg.dependencies && pkg.dependencies['dsh-bottom-info-bar']
|
|
111
222
|
if (typeof spec === 'string' && spec.startsWith('link:')) {
|
|
112
223
|
const target = spec.slice('link:'.length).trim()
|
|
113
|
-
if (target.length > 0) return { installMode: 'link', updateCommand:
|
|
224
|
+
if (target.length > 0) return { installMode: 'link', updateCommand: linkUpdateCommandFor(target) }
|
|
114
225
|
}
|
|
115
226
|
} catch (err) { /* 读不到就按 npm 处理 */ }
|
|
116
227
|
return { installMode: 'npm', updateCommand: npmCommand }
|
|
@@ -4191,10 +4302,15 @@ export default {
|
|
|
4191
4302
|
const ROUTE_PREFIX = '/_dsh/dsh-bottom-info-bar';
|
|
4192
4303
|
const ROUTES = {
|
|
4193
4304
|
getUpdateInfo: async function () {
|
|
4194
|
-
//
|
|
4305
|
+
// npm 的最新版本只在进程启动时查一次;但「已安装版本」每次都从磁盘重读——
|
|
4306
|
+
// 用户更新完本地副本后刷新页面就能看到新版本、提醒随之消失,不必等宿主重启。
|
|
4195
4307
|
// 命令按安装形态区分,避免把 link: 安装的用户引导到 npm 命令而丢掉本地代码。
|
|
4196
4308
|
const info = await updateInfoPromise
|
|
4197
|
-
|
|
4309
|
+
const current = packageVersion()
|
|
4310
|
+
return Object.assign({}, info, updateCommandForInstall(), {
|
|
4311
|
+
current: current,
|
|
4312
|
+
available: !!info.latest && compareVersions(info.latest, current) > 0,
|
|
4313
|
+
})
|
|
4198
4314
|
},
|
|
4199
4315
|
getBalanceSnapshot: async function (args) {
|
|
4200
4316
|
const sel = selectionFromArgs(args);
|
package/package.json
CHANGED