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/README.en.md +219 -0
- package/README.md +47 -79
- package/cordis.patch.yml +15 -0
- package/lib/client.js +421 -259
- package/lib/index.js +82 -66
- package/package.json +6 -3
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
|
-
?
|
|
927
|
+
? tr("超过 GitHub 100MB 单文件限制(${}),已被 .gitignore 的 \"${}\" 排除", [fmtMB(e.bytes), e.path])
|
|
914
928
|
: e.kind === 'dir'
|
|
915
|
-
?
|
|
916
|
-
:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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.
|
|
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":
|
|
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
|
}
|