source-code-mgmt 1.8.0 → 1.10.1

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 CHANGED
@@ -24,6 +24,20 @@ import {
24
24
  import { join, dirname } from 'node:path'
25
25
  import { homedir } from 'node:os'
26
26
 
27
+ import { AsyncLocalStorage } from 'node:async_hooks'
28
+
29
+ // ---------- i18n: host messages follow the client’s language (?lang= query param, per request) ----------
30
+ const HOST_EN = {"尚未配置 Gitee 私人令牌(请打开 ③ 代码管理输入令牌)":"No Gitee personal token configured (enter one in ③ Code Management)","Gitee API 请求失败(需要 curl)":"Gitee API request failed (curl required)","not a git repository(尚未 git init,可用「新建仓库并推送」初始化为 git 仓库并上传)":"not a git repository (run \"Create repo & push\" to init one and upload)","超过 GitHub 100MB 单文件限制(${}),已被 .gitignore 的 \"${}\" 排除":"Exceeds GitHub's 100MB file limit (${}) — already excluded by .gitignore entry \"${}\"","超过 GitHub 100MB 单文件限制(${}),${} 所在的一级目录「${}」整体忽略(该文件夹为一整体)":"Exceeds GitHub's 100MB file limit (${}) — the whole first-level folder \"${}\" of ${} is ignored (the folder is one unit)","超过 GitHub 100MB 单文件限制(${}),已忽略单个文件「${}」":"Exceeds GitHub's 100MB file limit (${}) — the file \"${}\" was ignored individually","尚未配置远程仓库 origin,请使用「新建仓库」创建远程仓库。":"No origin remote configured — use \"Create repo\" to create a remote repo.","not a git repository(尚未 git init,无法拉取)":"not a git repository (no git init yet — cannot pull)","尚未配置远程仓库 origin,无法拉取。":"No origin remote configured — cannot pull.","拉取存在冲突:本地有未合并改动或与远程冲突,请手动 git pull 处理合并。":"Pull conflicts: you have unmerged local changes or conflicts with the remote — resolve them manually with git pull.","git pull 失败":"git pull failed","not a git repository(尚未 git init)":"not a git repository (run git init first)","拉取并推送失败:合并存在冲突,请手动解决后重试。":"Pull-and-push failed: merge conflicts — resolve them manually and retry.","pull --rebase 失败":"pull --rebase failed","push 失败":"push failed","push --force 失败":"push --force failed","强制拉取存在冲突,请手动处理。":"Force-pull conflicts — handle them manually.","git pull --force 失败":"git pull --force failed","缺少仓库名称":"Repo name required","仓库名称包含非法字符(仅允许字母、数字、点、下划线、横线)":"Repo name contains invalid characters (only letters, digits, dots, underscores, dashes)","git add 失败:${}":"git add failed: ${}","需要先配置 Gitee 私人令牌":"Configure the Gitee personal token first","Gitee 创建仓库失败":"Failed to create the Gitee repo","缺少文件路径":"File path required","无法识别当前分支":"Cannot determine the current branch","尚未配置远程仓库 origin,无法对齐。":"No origin remote configured — cannot align.","git fetch 失败":"git fetch failed","git reset --hard 失败":"git reset --hard failed","git init 失败":"git init failed","git add 失败":"git add failed","git reset 失败":"git reset failed","提交信息不能为空":"Commit message cannot be empty","没有已暂存(staged)的改动可提交":"Nothing staged to commit","git commit 失败":"git commit failed","缺少分支名":"Branch name required","切换分支失败":"Failed to switch branch","git log 失败":"git log failed","缺少 commit hash":"Commit hash required","revert 失败(可能有冲突,请手动处理)":"revert failed (possible conflicts — handle manually)","cherry-pick 失败(可能有冲突,请手动处理)":"cherry-pick failed (possible conflicts — handle manually)","读取提交 diff 失败":"Failed to read the commit diff","未知工具:":"Unknown tool: ","未找到可用的包管理器(":"No usable package manager found (",")。请手动安装。":"). Please install manually.","安装成功":"Installed successfully","安装失败":"Installation failed","$dlg.Description = '选择本地目录';":"$dlg.Description = 'Choose a local folder';","Gitee 修改可见性失败":"Failed to change Gitee visibility","需要先登录 GitHub CLI(gh)":"Log in to the GitHub CLI (gh) first","修改可见性失败:${}":"Failed to change visibility: ${}","gh repo edit 失败":"gh repo edit failed","缺少目录路径":"Folder path required","未选择目录":"No folder selected","目录不存在或不是文件夹:":"Folder does not exist or is not a directory: ","令牌不能为空":"Token cannot be empty","Gitee 令牌无效,请检查(需有 projects 权限)":"Invalid Gitee token — check it (needs projects permission)"};
31
+ const requestLangStore = new AsyncLocalStorage();
32
+ function langOf(req) { try { const l = new URL(req.url ?? "", "http://localhost").searchParams.get("lang"); return l === "en" ? "en" : "zh" } catch { return "zh" } }
33
+ function lang() { return requestLangStore.getStore() ?? "zh" }
34
+ function tr(key, params) {
35
+ let s = lang() === "en" ? (HOST_EN[key] ?? key) : key;
36
+ if (params && params.length) { let i = 0; s = String(s).replace(/\$\{\}/g, () => String(params[i++] ?? "")) }
37
+ return s
38
+ }
39
+
40
+
27
41
  /** Stable cordis plugin name (also the browser bundle id). */
28
42
  export const name = 'source-code-mgmt'
29
43
 
@@ -525,7 +539,7 @@ function saveGiteeToken(token) {
525
539
  */
526
540
  function giteeApi(method, path, body, token) {
527
541
  const tok = token || readGiteeToken()
528
- if (!tok) return { ok: false, error: '尚未配置 Gitee 私人令牌(请打开 ③ 代码管理输入令牌)' }
542
+ if (!tok) return { ok: false, error: tr('尚未配置 Gitee 私人令牌(请打开 ③ 代码管理输入令牌)') }
529
543
  const args = [
530
544
  '-sS', '-L',
531
545
  '-X', String(method || 'GET').toUpperCase(),
@@ -539,7 +553,7 @@ function giteeApi(method, path, body, token) {
539
553
  args.push('https://gitee.com/api/v5/' + String(path).replace(/^\/+/, ''))
540
554
  const r = run(CURL, args, { timeout: 70_000 })
541
555
  if (!r.ok) {
542
- return { ok: false, error: (r.stderr || r.stdout || '').trim() || 'Gitee API 请求失败(需要 curl)' }
556
+ return { ok: false, error: (r.stderr || r.stdout || '').trim() || tr('Gitee API 请求失败(需要 curl)') }
543
557
  }
544
558
  let data
545
559
  try { data = JSON.parse(r.stdout || '{}') } catch { data = null }
@@ -733,7 +747,7 @@ function repoStatus(dir, provider = 'github', full = false) {
733
747
  repoOwner: ev.owner,
734
748
  visibility: ev.checked && ev.exists ? ev.visibility : undefined,
735
749
  provider,
736
- error: 'not a git repository(尚未 git init,可用「新建仓库并推送」初始化为 git 仓库并上传)',
750
+ error: tr('not a git repository(尚未 git init,可用「新建仓库并推送」初始化为 git 仓库并上传)'),
737
751
  }
738
752
  }
739
753
 
@@ -910,10 +924,10 @@ function pushFlow(dir) {
910
924
  const plan = ignorePlan(dir)
911
925
  const skipped = plan.entries.map((e) => {
912
926
  const reason = e.ignored
913
- ? `超过 GitHub 100MB 单文件限制(${fmtMB(e.bytes)}),已被 .gitignore 的 "${e.path}" 排除`
927
+ ? tr("超过 GitHub 100MB 单文件限制(${}),已被 .gitignore 的 \"${}\" 排除", [fmtMB(e.bytes), e.path])
914
928
  : e.kind === 'dir'
915
- ? `超过 GitHub 100MB 单文件限制(${fmtMB(e.bytes)}),${e.source} 所在的一级目录「${e.path}」整体忽略(该文件夹为一整体)`
916
- : `超过 GitHub 100MB 单文件限制(${fmtMB(e.bytes)}),已忽略单个文件「${e.path}」`
929
+ ? tr("超过 GitHub 100MB 单文件限制(${}),${} 所在的一级目录「${}」整体忽略(该文件夹为一整体)", [fmtMB(e.bytes), e.source, e.path])
930
+ : tr("超过 GitHub 100MB 单文件限制(${}),已忽略单个文件「${}」", [fmtMB(e.bytes), e.path])
917
931
  return { path: e.kind === 'dir' ? e.path + '/' : e.path, reason }
918
932
  })
919
933
 
@@ -965,7 +979,7 @@ function pushFlow(dir) {
965
979
  return {
966
980
  ok: false, needsRemote: true, branch: branchName,
967
981
  committed, commitHash, skipped,
968
- error: '尚未配置远程仓库 origin,请使用「新建仓库」创建远程仓库。',
982
+ error: tr('尚未配置远程仓库 origin,请使用「新建仓库」创建远程仓库。'),
969
983
  }
970
984
  }
971
985
 
@@ -994,11 +1008,11 @@ function pushFlow(dir) {
994
1008
  function pullFlow(dir) {
995
1009
  if (!dir || !existsSync(dir)) return { ok: false, error: 'folder does not exist' }
996
1010
  if (!existsSync(join(dir, '.git'))) {
997
- return { ok: false, error: 'not a git repository(尚未 git init,无法拉取)' }
1011
+ return { ok: false, error: tr('not a git repository(尚未 git init,无法拉取)') }
998
1012
  }
999
1013
  const hasRemote = run(GIT, ['-C', dir, 'remote', 'get-url', 'origin']).ok
1000
1014
  if (!hasRemote) {
1001
- return { ok: false, error: '尚未配置远程仓库 origin,无法拉取。' }
1015
+ return { ok: false, error: tr('尚未配置远程仓库 origin,无法拉取。') }
1002
1016
  }
1003
1017
 
1004
1018
  // Was already up to date? First cheap check via ls-remote comparison.
@@ -1028,8 +1042,8 @@ function pullFlow(dir) {
1028
1042
  conflict,
1029
1043
  branch: branchName,
1030
1044
  error: conflict
1031
- ? '拉取存在冲突:本地有未合并改动或与远程冲突,请手动 git pull 处理合并。'
1032
- : (pull.stderr || pull.stdout).trim() || 'git pull 失败',
1045
+ ? tr('拉取存在冲突:本地有未合并改动或与远程冲突,请手动 git pull 处理合并。')
1046
+ : (pull.stderr || pull.stdout).trim() || tr('git pull 失败'),
1033
1047
  detail: blob,
1034
1048
  }
1035
1049
  }
@@ -1043,7 +1057,7 @@ function pullFlow(dir) {
1043
1057
  function mergePushFlow(dir) {
1044
1058
  if (!dir || !existsSync(dir)) return { ok: false, error: 'folder does not exist' }
1045
1059
  if (!existsSync(join(dir, '.git'))) {
1046
- return { ok: false, error: 'not a git repository(尚未 git init)' }
1060
+ return { ok: false, error: tr('not a git repository(尚未 git init)') }
1047
1061
  }
1048
1062
  const branch = run(GIT, ['-C', dir, 'rev-parse', '--abbrev-ref', 'HEAD'])
1049
1063
  const branchName = branch.ok ? branch.stdout.trim() : 'main'
@@ -1057,8 +1071,8 @@ function mergePushFlow(dir) {
1057
1071
  return {
1058
1072
  ok: false, conflict, branch: branchName,
1059
1073
  error: conflict
1060
- ? '拉取并推送失败:合并存在冲突,请手动解决后重试。'
1061
- : (rebase.stderr || rebase.stdout).trim() || 'pull --rebase 失败',
1074
+ ? tr('拉取并推送失败:合并存在冲突,请手动解决后重试。')
1075
+ : (rebase.stderr || rebase.stdout).trim() || tr('pull --rebase 失败'),
1062
1076
  detail: blob,
1063
1077
  }
1064
1078
  }
@@ -1069,7 +1083,7 @@ function mergePushFlow(dir) {
1069
1083
  if (!push.ok) {
1070
1084
  return {
1071
1085
  ok: false, branch: branchName, detail: (push.stdout + '\n' + push.stderr).trim(),
1072
- error: (push.stderr || push.stdout).trim() || 'push 失败',
1086
+ error: (push.stderr || push.stdout).trim() || tr('push 失败'),
1073
1087
  }
1074
1088
  }
1075
1089
  return { ok: true, branch: branchName, rebased: true, pushed: true }
@@ -1084,7 +1098,7 @@ function mergePushFlow(dir) {
1084
1098
  function forcePushFlow(dir) {
1085
1099
  if (!dir || !existsSync(dir)) return { ok: false, error: 'folder does not exist' }
1086
1100
  if (!existsSync(join(dir, '.git'))) {
1087
- return { ok: false, error: 'not a git repository(尚未 git init)' }
1101
+ return { ok: false, error: tr('not a git repository(尚未 git init)') }
1088
1102
  }
1089
1103
  const branch = run(GIT, ['-C', dir, 'rev-parse', '--abbrev-ref', 'HEAD'])
1090
1104
  const branchName = branch.ok ? branch.stdout.trim() : 'main'
@@ -1094,7 +1108,7 @@ function forcePushFlow(dir) {
1094
1108
  const blob = (push.stdout + '\n' + push.stderr).trim()
1095
1109
  return {
1096
1110
  ok: push.ok, branch: branchName, forced: push.ok,
1097
- error: push.ok ? undefined : (push.stderr || push.stdout).trim() || 'push --force 失败',
1111
+ error: push.ok ? undefined : (push.stderr || push.stdout).trim() || tr('push --force 失败'),
1098
1112
  detail: blob,
1099
1113
  }
1100
1114
  }
@@ -1109,7 +1123,7 @@ function forcePushFlow(dir) {
1109
1123
  function forcePullFlow(dir) {
1110
1124
  if (!dir || !existsSync(dir)) return { ok: false, error: 'folder does not exist' }
1111
1125
  if (!existsSync(join(dir, '.git'))) {
1112
- return { ok: false, error: 'not a git repository(尚未 git init)' }
1126
+ return { ok: false, error: tr('not a git repository(尚未 git init)') }
1113
1127
  }
1114
1128
  const branch = run(GIT, ['-C', dir, 'rev-parse', '--abbrev-ref', 'HEAD'])
1115
1129
  const branchName = branch.ok ? branch.stdout.trim() : 'main'
@@ -1122,8 +1136,8 @@ function forcePullFlow(dir) {
1122
1136
  ok: pull.ok, conflict, branch: branchName,
1123
1137
  error: !pull.ok
1124
1138
  ? (conflict
1125
- ? '强制拉取存在冲突,请手动处理。'
1126
- : (pull.stderr || pull.stdout).trim() || 'git pull --force 失败')
1139
+ ? tr('强制拉取存在冲突,请手动处理。')
1140
+ : (pull.stderr || pull.stdout).trim() || tr('git pull --force 失败'))
1127
1141
  : undefined,
1128
1142
  detail: blob,
1129
1143
  }
@@ -1144,9 +1158,9 @@ function forcePullFlow(dir) {
1144
1158
  function createRepoFlow(dir, name, visibility, provider = 'github') {
1145
1159
  if (!dir || !existsSync(dir)) return { ok: false, error: 'folder does not exist' }
1146
1160
  const repoName = String(name || '').trim()
1147
- if (!repoName) return { ok: false, error: '缺少仓库名称' }
1161
+ if (!repoName) return { ok: false, error: tr('缺少仓库名称') }
1148
1162
  if (!/^[A-Za-z0-9._-]+$/.test(repoName) || repoName === '.') {
1149
- return { ok: false, error: '仓库名称包含非法字符(仅允许字母、数字、点、下划线、横线)' }
1163
+ return { ok: false, error: tr('仓库名称包含非法字符(仅允许字母、数字、点、下划线、横线)') }
1150
1164
  }
1151
1165
  if (!existsSync(join(dir, '.git'))) {
1152
1166
  const init = run(GIT, ['-C', dir, 'init'])
@@ -1167,7 +1181,7 @@ function createRepoFlow(dir, name, visibility, provider = 'github') {
1167
1181
  if (!head.ok) {
1168
1182
  const stagedCount = run(GIT, ['-C', dir, 'add', '-A'])
1169
1183
  if (!stagedCount.ok) {
1170
- return { ok: false, error: `git add 失败:${(stagedCount.stderr || '')}` }
1184
+ return { ok: false, error: tr("git add 失败:${}", [(stagedCount.stderr || '')]) }
1171
1185
  }
1172
1186
  const commit = run(GIT, ['-C', dir, 'commit', '-m', 'init: initial commit from DSH source-code-mgmt'])
1173
1187
  if (!commit.ok) {
@@ -1182,10 +1196,10 @@ function createRepoFlow(dir, name, visibility, provider = 'github') {
1182
1196
  // 1) Create the empty remote repo via the Gitee OpenAPI.
1183
1197
  const owner = giteeOwner()
1184
1198
  const token = readGiteeToken()
1185
- if (!owner || !token) return { ok: false, error: '需要先配置 Gitee 私人令牌' }
1199
+ if (!owner || !token) return { ok: false, error: tr('需要先配置 Gitee 私人令牌') }
1186
1200
  const api = giteeApi('POST', 'user/repos', { name: repoName, private: vis === 'private' }, token)
1187
1201
  if (!api.ok || giteeApiFailed(api.data)) {
1188
- return { ok: false, error: (api.error || (api.data && api.data.message) || 'Gitee 创建仓库失败'), detail: api.data }
1202
+ return { ok: false, error: (api.error || (api.data && api.data.message) || tr('Gitee 创建仓库失败')), detail: api.data }
1189
1203
  }
1190
1204
  // 2) Point origin at the SSH remote and push (SSH per user's choice).
1191
1205
  const sshUrl = 'git@gitee.com:' + owner + '/' + repoName + '.git'
@@ -1244,7 +1258,7 @@ function repoDiffFlow(dir, path) {
1244
1258
  return { ok: false, error: 'not a git repository' }
1245
1259
  }
1246
1260
  const p = String(path || '').trim()
1247
- if (!p) return { ok: false, error: '缺少文件路径' }
1261
+ if (!p) return { ok: false, error: tr('缺少文件路径') }
1248
1262
  // Untracked files have no tracked diff.
1249
1263
  const status = run(GIT, ['-C', dir, 'status', '--porcelain', '--', p])
1250
1264
  const isUntracked = status.ok && /^\?\?/.test(status.stdout.trim().slice(0, 2))
@@ -1266,23 +1280,23 @@ function repoDiffFlow(dir, path) {
1266
1280
  function alignFlow(dir) {
1267
1281
  if (!dir || !existsSync(dir)) return { ok: false, error: 'folder does not exist' }
1268
1282
  if (!existsSync(join(dir, '.git'))) {
1269
- return { ok: false, error: 'not a git repository(尚未 git init)' }
1283
+ return { ok: false, error: tr('not a git repository(尚未 git init)') }
1270
1284
  }
1271
1285
  const branch = run(GIT, ['-C', dir, 'rev-parse', '--abbrev-ref', 'HEAD'])
1272
1286
  const branchName = branch.ok ? branch.stdout.trim() : undefined
1273
- if (!branchName) return { ok: false, error: '无法识别当前分支' }
1287
+ if (!branchName) return { ok: false, error: tr('无法识别当前分支') }
1274
1288
  const hasRemote = run(GIT, ['-C', dir, 'remote', 'get-url', 'origin']).ok
1275
- if (!hasRemote) return { ok: false, error: '尚未配置远程仓库 origin,无法对齐。' }
1289
+ if (!hasRemote) return { ok: false, error: tr('尚未配置远程仓库 origin,无法对齐。') }
1276
1290
 
1277
1291
  const fetch = run(GIT, ['-C', dir, 'fetch', 'origin'], { timeout: 120_000 })
1278
1292
  if (!fetch.ok) {
1279
- return { ok: false, error: (fetch.stderr || fetch.stdout).trim() || 'git fetch 失败', detail: (fetch.stdout + '\n' + fetch.stderr).trim() }
1293
+ return { ok: false, error: (fetch.stderr || fetch.stdout).trim() || tr('git fetch 失败'), detail: (fetch.stdout + '\n' + fetch.stderr).trim() }
1280
1294
  }
1281
1295
  const reset = run(GIT, ['-C', dir, 'reset', '--hard', 'origin/' + branchName], { timeout: 60_000 })
1282
1296
  return {
1283
1297
  ok: reset.ok,
1284
1298
  branch: branchName,
1285
- error: reset.ok ? undefined : (reset.stderr || reset.stdout).trim() || 'git reset --hard 失败',
1299
+ error: reset.ok ? undefined : (reset.stderr || reset.stdout).trim() || tr('git reset --hard 失败'),
1286
1300
  detail: (fetch.stdout + '\n' + reset.stdout).trim(),
1287
1301
  }
1288
1302
  }
@@ -1299,7 +1313,7 @@ function initGitFlow(dir) {
1299
1313
  return { ok: true, alreadyRepo: true, dir }
1300
1314
  }
1301
1315
  const init = run(GIT, ['-C', dir, 'init'])
1302
- if (!init.ok) return { ok: false, error: (init.stderr || init.stdout).trim() || 'git init 失败' }
1316
+ if (!init.ok) return { ok: false, error: (init.stderr || init.stdout).trim() || tr('git init 失败') }
1303
1317
  const ident = run(GIT, ['-C', dir, 'config', 'user.email'])
1304
1318
  if (!ident.ok) {
1305
1319
  run(GIT, ['-C', dir, 'config', 'user.name', 'DSH User'])
@@ -1316,7 +1330,7 @@ function initGitFlow(dir) {
1316
1330
  /** Basic guard: the folder must exist and be a git repo; returns an error object or null. */
1317
1331
  function requireGitRepo(dir) {
1318
1332
  if (!dir || !existsSync(dir)) return { error: 'folder does not exist' }
1319
- if (!existsSync(join(dir, '.git'))) return { error: 'not a git repository(尚未 git init)' }
1333
+ if (!existsSync(join(dir, '.git'))) return { error: tr('not a git repository(尚未 git init)') }
1320
1334
  return null
1321
1335
  }
1322
1336
 
@@ -1331,7 +1345,7 @@ function stageFlow(dir, path) {
1331
1345
  const args = ['-C', dir, 'add', '-A']
1332
1346
  if (path && String(path).trim() !== '') args.push('--', String(path).trim())
1333
1347
  const r = run(GIT, args)
1334
- if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || 'git add 失败' }
1348
+ if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || tr('git add 失败') }
1335
1349
  return { ok: true, path: path || null }
1336
1350
  }
1337
1351
 
@@ -1346,7 +1360,7 @@ function unstageFlow(dir, path) {
1346
1360
  const args = ['-C', dir, 'reset', '-q']
1347
1361
  if (path && String(path).trim() !== '') args.push('--', String(path).trim())
1348
1362
  const r = run(GIT, args)
1349
- if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || 'git reset 失败' }
1363
+ if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || tr('git reset 失败') }
1350
1364
  return { ok: true, path: path || null }
1351
1365
  }
1352
1366
 
@@ -1364,7 +1378,7 @@ function commitFlow(dir, message, paths) {
1364
1378
  const guard = requireGitRepo(dir)
1365
1379
  if (guard) return { ok: false, error: guard.error }
1366
1380
  const msg = String(message || '').trim()
1367
- if (msg === '') return { ok: false, error: '提交信息不能为空' }
1381
+ if (msg === '') return { ok: false, error: tr('提交信息不能为空') }
1368
1382
 
1369
1383
  const ident = run(GIT, ['-C', dir, 'config', 'user.email'])
1370
1384
  if (!ident.ok) {
@@ -1377,7 +1391,7 @@ function commitFlow(dir, message, paths) {
1377
1391
  if (list.length > 0) {
1378
1392
  // 只提交指定文件:先把这些文件 add 进暂存区。
1379
1393
  const add = run(GIT, ['-C', dir, 'add', '--', ...list])
1380
- if (!add.ok) return { ok: false, error: (add.stderr || add.stdout).trim() || 'git add 失败' }
1394
+ if (!add.ok) return { ok: false, error: (add.stderr || add.stdout).trim() || tr('git add 失败') }
1381
1395
  stagedPaths = list
1382
1396
  } else {
1383
1397
  // 提交整个暂存区:列出已暂存文件用于提示。
@@ -1387,10 +1401,10 @@ function commitFlow(dir, message, paths) {
1387
1401
 
1388
1402
  // 无可提交内容时避免报错。
1389
1403
  const hasChanges = run(GIT, ['-C', dir, 'diff', '--cached', '--quiet']).code !== 0
1390
- if (!hasChanges) return { ok: false, error: '没有已暂存(staged)的改动可提交', staged: 0 }
1404
+ if (!hasChanges) return { ok: false, error: tr('没有已暂存(staged)的改动可提交'), staged: 0 }
1391
1405
 
1392
1406
  const commit = run(GIT, ['-C', dir, 'commit', '-m', msg])
1393
- if (!commit.ok) return { ok: false, error: (commit.stderr || commit.stdout).trim() || 'git commit 失败', staged: stagedPaths.length }
1407
+ if (!commit.ok) return { ok: false, error: (commit.stderr || commit.stdout).trim() || tr('git commit 失败'), staged: stagedPaths.length }
1394
1408
 
1395
1409
  const rev = run(GIT, ['-C', dir, 'rev-parse', '--short', 'HEAD'])
1396
1410
  return {
@@ -1420,7 +1434,7 @@ function pushStagedFlow(dir, message) {
1420
1434
  // Remote must exist before we can push.
1421
1435
  const hasRemote = run(GIT, ['-C', dir, 'remote', 'get-url', 'origin']).ok
1422
1436
  if (!hasRemote) {
1423
- return { ok: false, needsRemote: true, error: '尚未配置远程仓库 origin,请使用「新建仓库」创建远程仓库。' }
1437
+ return { ok: false, needsRemote: true, error: tr('尚未配置远程仓库 origin,请使用「新建仓库」创建远程仓库。') }
1424
1438
  }
1425
1439
 
1426
1440
  const msg = String(message || '').trim()
@@ -1436,7 +1450,7 @@ function pushStagedFlow(dir, message) {
1436
1450
  }
1437
1451
  const useMsg = msg || ('chore: update ' + baseName(dir))
1438
1452
  const commit = run(GIT, ['-C', dir, 'commit', '-m', useMsg])
1439
- if (!commit.ok) return { ok: false, error: (commit.stderr || commit.stdout).trim() || 'git commit 失败' }
1453
+ if (!commit.ok) return { ok: false, error: (commit.stderr || commit.stdout).trim() || tr('git commit 失败') }
1440
1454
  committed = true
1441
1455
  const rev = run(GIT, ['-C', dir, 'rev-parse', '--short', 'HEAD'])
1442
1456
  commitHash = rev.ok ? rev.stdout.trim() : undefined
@@ -1482,9 +1496,9 @@ function checkoutFlow(dir, branch) {
1482
1496
  const guard = requireGitRepo(dir)
1483
1497
  if (guard) return { ok: false, error: guard.error }
1484
1498
  const b = String(branch || '').trim()
1485
- if (b === '') return { ok: false, error: '缺少分支名' }
1499
+ if (b === '') return { ok: false, error: tr('缺少分支名') }
1486
1500
  const r = run(GIT, ['-C', dir, 'checkout', b])
1487
- if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || '切换分支失败' }
1501
+ if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || tr('切换分支失败') }
1488
1502
  return { ok: true, branch: b }
1489
1503
  }
1490
1504
 
@@ -1505,7 +1519,7 @@ function logFlow(dir, count) {
1505
1519
  if (/does not have any commits|bad revision|your current branch/i.test((r.stderr || r.stdout))) {
1506
1520
  return { ok: true, commits: [] }
1507
1521
  }
1508
- return { ok: false, error: (r.stderr || r.stdout).trim() || 'git log 失败' }
1522
+ return { ok: false, error: (r.stderr || r.stdout).trim() || tr('git log 失败') }
1509
1523
  }
1510
1524
  const commits = r.stdout.split(/\r?\n/).filter((l) => l.trim() !== '').map((line) => {
1511
1525
  const [short, subject, author, date, full, refs] = line.split('\x1f')
@@ -1530,9 +1544,9 @@ function revertFlow(dir, hash) {
1530
1544
  const guard = requireGitRepo(dir)
1531
1545
  if (guard) return { ok: false, error: guard.error }
1532
1546
  const h = String(hash || '').trim()
1533
- if (h === '') return { ok: false, error: '缺少 commit hash' }
1547
+ if (h === '') return { ok: false, error: tr('缺少 commit hash') }
1534
1548
  const r = run(GIT, ['-C', dir, 'revert', '--no-edit', h], { timeout: 60_000 })
1535
- if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || 'revert 失败(可能有冲突,请手动处理)' }
1549
+ if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || tr('revert 失败(可能有冲突,请手动处理)') }
1536
1550
  return { ok: true, hash: h }
1537
1551
  }
1538
1552
 
@@ -1545,9 +1559,9 @@ function cherryPickFlow(dir, hash) {
1545
1559
  const guard = requireGitRepo(dir)
1546
1560
  if (guard) return { ok: false, error: guard.error }
1547
1561
  const h = String(hash || '').trim()
1548
- if (h === '') return { ok: false, error: '缺少 commit hash' }
1562
+ if (h === '') return { ok: false, error: tr('缺少 commit hash') }
1549
1563
  const r = run(GIT, ['-C', dir, 'cherry-pick', h], { timeout: 60_000 })
1550
- if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || 'cherry-pick 失败(可能有冲突,请手动处理)' }
1564
+ if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || tr('cherry-pick 失败(可能有冲突,请手动处理)') }
1551
1565
  return { ok: true, hash: h }
1552
1566
  }
1553
1567
 
@@ -1561,9 +1575,9 @@ function commitDiffFlow(dir, hash) {
1561
1575
  const guard = requireGitRepo(dir)
1562
1576
  if (guard) return { ok: false, error: guard.error }
1563
1577
  const h = String(hash || '').trim()
1564
- if (h === '') return { ok: false, error: '缺少 commit hash' }
1578
+ if (h === '') return { ok: false, error: tr('缺少 commit hash') }
1565
1579
  const r = run(GIT, ['-C', dir, 'show', '--no-ext-diff', '--no-color', '--format=', '-m', '--first-parent', h])
1566
- if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || '读取提交 diff 失败' }
1580
+ if (!r.ok) return { ok: false, error: (r.stderr || r.stdout).trim() || tr('读取提交 diff 失败') }
1567
1581
  return { ok: true, hash: h, diff: r.stdout }
1568
1582
  }
1569
1583
 
@@ -1633,7 +1647,7 @@ function installCommand(tool) {
1633
1647
  */
1634
1648
  function installToolFlow(tool) {
1635
1649
  const t = String(tool || '').trim().toLowerCase()
1636
- if (t !== 'git' && t !== 'gh' && t !== 'ssh') return { ok: false, error: '未知工具:' + tool }
1650
+ if (t !== 'git' && t !== 'gh' && t !== 'ssh') return { ok: false, error: tr('未知工具:') + tool }
1637
1651
  // 已安装就直接返回,不重复安装。
1638
1652
  const present = t === 'git' ? (GIT && GIT !== 'git')
1639
1653
  : t === 'gh' ? (GH && GH !== 'gh')
@@ -1642,7 +1656,7 @@ function installToolFlow(tool) {
1642
1656
 
1643
1657
  const cmd = installCommand(t)
1644
1658
  if (!cmd) {
1645
- return { ok: false, error: '未找到可用的包管理器(' + (IS_WIN ? 'winget / choco / scoop' : process.platform === 'darwin' ? 'brew' : 'apt-get / dnf / pacman') + ')。请手动安装。', tool: t }
1659
+ return { ok: false, error: tr('未找到可用的包管理器(') + (IS_WIN ? 'winget / choco / scoop' : process.platform === 'darwin' ? 'brew' : 'apt-get / dnf / pacman') + tr(')。请手动安装。'), tool: t }
1646
1660
  }
1647
1661
  const r = run(cmd.bin, cmd.args, { timeout: 300_000 })
1648
1662
  const output = (r.stdout + '\n' + r.stderr).trim()
@@ -1652,7 +1666,7 @@ function installToolFlow(tool) {
1652
1666
  ok,
1653
1667
  tool: t,
1654
1668
  command: cmd.label,
1655
- output: output || (ok ? '安装成功' : '安装失败'),
1669
+ output: output || (ok ? tr('安装成功') : tr('安装失败')),
1656
1670
  needElevation: IS_WIN && t === 'ssh',
1657
1671
  }
1658
1672
  }
@@ -1811,7 +1825,7 @@ function pickDirDialog(initialDir) {
1811
1825
  const script =
1812
1826
  'Add-Type -AssemblyName System.Windows.Forms;' +
1813
1827
  '$dlg = New-Object System.Windows.Forms.FolderBrowserDialog;' +
1814
- '$dlg.Description = \'选择本地目录\';' +
1828
+ tr('$dlg.Description = \'选择本地目录\';') +
1815
1829
  (initial ? `$dlg.SelectedPath = '${initial}';` : '') +
1816
1830
  'if ($dlg.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) { Write-Output $dlg.SelectedPath }'
1817
1831
  const r = run('powershell', ['-NoProfile', '-STA', '-Command', script], { timeout: 180_000 })
@@ -1938,15 +1952,15 @@ function setVisibilityFlow(name, target, provider = 'github') {
1938
1952
  const vis = target === 'public' ? 'public' : 'private'
1939
1953
  if (provider === 'gitee') {
1940
1954
  const owner = giteeOwner()
1941
- if (!owner || !name) return { ok: false, error: '需要先配置 Gitee 私人令牌' }
1955
+ if (!owner || !name) return { ok: false, error: tr('需要先配置 Gitee 私人令牌') }
1942
1956
  const api = giteeApi('PATCH', 'repos/' + owner + '/' + name, { private: vis === 'private' })
1943
1957
  if (!api.ok || giteeApiFailed(api.data)) {
1944
- return { ok: false, error: (api.error || (api.data && api.data.message) || 'Gitee 修改可见性失败') }
1958
+ return { ok: false, error: (api.error || (api.data && api.data.message) || tr('Gitee 修改可见性失败')) }
1945
1959
  }
1946
1960
  return { ok: true, name, visibility: vis, provider: 'gitee' }
1947
1961
  }
1948
1962
  const owner = ghOwner()
1949
- if (!owner || !name) return { ok: false, error: '需要先登录 GitHub CLI(gh)' }
1963
+ if (!owner || !name) return { ok: false, error: tr('需要先登录 GitHub CLI(gh)') }
1950
1964
  const r = run(GH, [
1951
1965
  'repo', 'edit', owner + '/' + name,
1952
1966
  '--visibility', vis,
@@ -1955,7 +1969,7 @@ function setVisibilityFlow(name, target, provider = 'github') {
1955
1969
  if (!r.ok) {
1956
1970
  return {
1957
1971
  ok: false,
1958
- error: `修改可见性失败:${(r.stderr || r.stdout || '').trim() || 'gh repo edit 失败'}`,
1972
+ error: tr("修改可见性失败:${}", [(r.stderr || r.stdout || '').trim() || tr('gh repo edit 失败')]),
1959
1973
  }
1960
1974
  }
1961
1975
  return { ok: true, name, visibility: vis, provider: 'github' }
@@ -1970,11 +1984,13 @@ function baseName(p) {
1970
1984
 
1971
1985
  export function apply(ctx) {
1972
1986
  const fallbackDir = resolveDefaultDir(process.cwd())
1973
- const handle = async (req, res, fn) => {
1987
+ // 每个请求按 ?lang= 参数跑在独立语言上下文里(AsyncLocalStorage),
1988
+ // 所有 flow 函数内的 tr() 都能读到当前请求的语言,并发请求互不干扰。
1989
+ const handle = async (req, res, fn) => requestLangStore.run(langOf(req), async () => {
1974
1990
  if (!isLoopbackRequest(req)) return forbidden(res)
1975
1991
  const payload = await fn(req)
1976
1992
  json(res, 200, payload)
1977
- }
1993
+ })
1978
1994
 
1979
1995
  const routes = {
1980
1996
  '/env': (req, res) => handle(req, res, async () => ({ ok: true, ...checkEnv() })),
@@ -2022,7 +2038,7 @@ export function apply(ctx) {
2022
2038
  await handle(req, res, async (req) => {
2023
2039
  const body = JSON.parse((await readBody(req)) || '{}')
2024
2040
  const dir = String(body.dir || '').trim()
2025
- if (!dir) return { ok: false, error: '缺少目录路径' }
2041
+ if (!dir) return { ok: false, error: tr('缺少目录路径') }
2026
2042
  removeCustomDir(dir)
2027
2043
  return { ok: true, removed: dir, customDirs: readCustomDirs(), workspaces: listWorkspaces() }
2028
2044
  })
@@ -2046,7 +2062,7 @@ export function apply(ctx) {
2046
2062
  await handle(req, res, async (req) => {
2047
2063
  const body = JSON.parse((await readBody(req)) || '{}')
2048
2064
  const picked = pickDirDialog(body.initial || fallbackDir)
2049
- if (!picked) return { ok: false, cancelled: true, error: '未选择目录' }
2065
+ if (!picked) return { ok: false, cancelled: true, error: tr('未选择目录') }
2050
2066
  return { ok: true, dir: picked, name: baseName(picked) }
2051
2067
  })
2052
2068
  },
@@ -2055,9 +2071,9 @@ export function apply(ctx) {
2055
2071
  await handle(req, res, async (req) => {
2056
2072
  const body = JSON.parse((await readBody(req)) || '{}')
2057
2073
  const dir = String(body.dir || '').trim()
2058
- if (!dir) return { ok: false, error: '缺少目录路径' }
2074
+ if (!dir) return { ok: false, error: tr('缺少目录路径') }
2059
2075
  if (!existsSync(dir) || !statSync(dir).isDirectory()) {
2060
- return { ok: false, error: '目录不存在或不是文件夹:' + dir }
2076
+ return { ok: false, error: tr('目录不存在或不是文件夹:') + dir }
2061
2077
  }
2062
2078
  saveCustomDir(dir)
2063
2079
  return { ok: true, dir, name: baseName(dir), workspaces: listWorkspaces(), customDirs: readCustomDirs() }
@@ -2221,13 +2237,13 @@ export function apply(ctx) {
2221
2237
  return { ok: true, configured: false, cleared: true }
2222
2238
  }
2223
2239
  const token = String(body.token || '').trim()
2224
- if (!token) return { ok: false, error: '令牌不能为空' }
2240
+ if (!token) return { ok: false, error: tr('令牌不能为空') }
2225
2241
  saveGiteeToken(token)
2226
2242
  const owner = giteeOwner()
2227
2243
  if (!owner) {
2228
2244
  // 令牌无效则清掉,避免留有坏令牌。
2229
2245
  saveGiteeToken('')
2230
- return { ok: false, error: 'Gitee 令牌无效,请检查(需有 projects 权限)' }
2246
+ return { ok: false, error: tr('Gitee 令牌无效,请检查(需有 projects 权限)') }
2231
2247
  }
2232
2248
  return { ok: true, configured: true, owner }
2233
2249
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "source-code-mgmt",
3
- "version": "1.8.0",
3
+ "version": "1.10.1",
4
4
  "description": "DSH plugin: source code management. Registers「代码管理」as a dsh-better-sidebar sidebar Tab when installed, otherwise a button in the DSH session header's right-aligned utilities (beside \"Session log\") opening a right-side integrated panel that pushes the main content — env checks (git/gh), SSH ed25519 key setup, GitHub/Gitee dual-platform (SSH config 443 + Gitee OpenAPI), connectivity test, push / new repo, and auto-ignore files over 100MB with per-file skipped reasons. Cross-platform (Windows / Linux / macOS).",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -14,7 +14,9 @@
14
14
  "./package.json": "./package.json"
15
15
  },
16
16
  "dsh": {
17
- "bundle": "./lib/index.js",
17
+ "bundle": {
18
+ "patch": "./cordis.patch.yml"
19
+ },
18
20
  "client": {
19
21
  "inject": [
20
22
  "@deepseek-ai/dsh-client-runtime"
@@ -32,7 +34,8 @@
32
34
  }
33
35
  },
34
36
  "files": [
35
- "lib/**/*.js"
37
+ "lib/**/*.js",
38
+ "cordis.patch.yml"
36
39
  ],
37
40
  "license": "MIT"
38
41
  }