dsh-vscode-mode 0.14.13 → 0.15.2

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
@@ -18681,6 +18681,46 @@ var SvnUpdateJobs = class {
18681
18681
  }
18682
18682
  };
18683
18683
  //#endregion
18684
+ //#region src/svnResolve.ts
18685
+ /** @public @author ddj 2026年10月14号 @param value Requested strategy. @returns Whether SVN accepts this explicit strategy. */
18686
+ function validAccept(value) {
18687
+ return value === "mine-full" || value === "theirs-full" || value === "working";
18688
+ }
18689
+ /** @public @author ddj 2026年10月14号 @param value Untrusted relative target. @returns Whether the path names exactly one non-root workspace target. */
18690
+ function validTarget(value) {
18691
+ if (typeof value !== "string" || !value || /[\u0000-\u001f]/.test(value) || /^[A-Za-z]:|^[\\/]/.test(value)) return false;
18692
+ return value.replace(/\\/g, "/").split("/").every((part) => Boolean(part) && part !== "." && part !== ".." && part.toLowerCase() !== ".svn");
18693
+ }
18694
+ /** @public @author ddj 2026年10月14号 @param path Exact relative target. @returns Peg-safe SVN argument. */
18695
+ function pegTarget(path) {
18696
+ return path.includes("@") ? path + "@" : path;
18697
+ }
18698
+ /** @public @author ddj 2026年10月14号 @param xml Verbose depth-empty status. @param path Client target identity. @returns Confirmed conflict evidence. @throws Missing or incomplete status evidence. */
18699
+ function conflictOf(xml, path) {
18700
+ if (!/^\s*(?:<\?xml[^>]*>\s*)?<status\b/.test(xml) || !/<\/status>\s*$/.test(xml)) throw new Error("SVN 冲突状态输出为空或不完整,状态未确认");
18701
+ const statuses = [...scanXmlTags(xml, ["wc-status"])].filter((hit) => !hit.closing);
18702
+ if (statuses.length !== 1) throw new Error("SVN 未返回唯一目标状态,状态未确认");
18703
+ const attrs = statuses[0].attrs;
18704
+ const item = attrOf(attrs, "item");
18705
+ if (!item) throw new Error("SVN 目标状态缺失,状态未确认");
18706
+ return {
18707
+ path,
18708
+ text: item === "conflicted",
18709
+ props: attrOf(attrs, "props") === "conflicted",
18710
+ tree: attrOf(attrs, "tree-conflicted") === "true",
18711
+ versioned: ![
18712
+ "unversioned",
18713
+ "ignored",
18714
+ "external",
18715
+ "none"
18716
+ ].includes(item)
18717
+ };
18718
+ }
18719
+ /** @public @author ddj 2026年10月14号 @param state Confirmed target evidence. @returns Whether any conflict remains. */
18720
+ function hasConflict(state) {
18721
+ return state.text || state.props || state.tree;
18722
+ }
18723
+ //#endregion
18684
18724
  //#region src/shared/svnExplorer.ts
18685
18725
  const ICON_KIND = {
18686
18726
  normal: "clean",
@@ -19694,6 +19734,18 @@ function createSvnRpc(deps) {
19694
19734
  const now = deps.now ?? (() => Date.now());
19695
19735
  const findRoot = deps.findRoot ?? ((cwd) => findSvnRoot(cwd));
19696
19736
  const findTortoise = deps.findTortoise ?? ((dir) => findTortoiseProc(dir));
19737
+ const conflictWrites = /* @__PURE__ */ new Set();
19738
+ /** @private @author ddj 2026年10月14号 @param root WC root. @returns Canonical lock identity across sessions. */
19739
+ const writeKey = (root) => (deps.platform ?? process.platform) === "win32" ? root.toLowerCase() : root;
19740
+ /** @private @author ddj 2026年10月14号 @param root WC root. @returns Whether a live update or conflict write owns the WC. */
19741
+ const writeBusy = (root) => {
19742
+ const job = updateJobs.active(root);
19743
+ return conflictWrites.has(writeKey(root)) || Boolean(job && [
19744
+ "starting",
19745
+ "running",
19746
+ "cancelling"
19747
+ ].includes(job.phase));
19748
+ };
19697
19749
  const statusCache = /* @__PURE__ */ new Map();
19698
19750
  /** 运行中更新按会话目录复用工作副本根;TTL 到期才重新扫描 .svn。 */
19699
19751
  const updateRootCache = /* @__PURE__ */ new Map();
@@ -19833,7 +19885,7 @@ function createSvnRpc(deps) {
19833
19885
  * @param stdoutCap stdout 收集上限(缺省 1MB;大载荷场景如日志拉取需显式放大)
19834
19886
  * @returns 退出码与输出
19835
19887
  */
19836
- const runSvn = async (cwd, argv, graceMs, stdoutCap = OUTPUT_CAP) => {
19888
+ const runSvn = async (cwd, argv, graceMs, stdoutCap = OUTPUT_CAP, allowFallback = true) => {
19837
19889
  const spawn = subprocess();
19838
19890
  if (!spawn) throw new Error("缺少 subprocess 服务");
19839
19891
  const fs = ctx.get("fs");
@@ -19868,7 +19920,8 @@ function createSvnRpc(deps) {
19868
19920
  stdout,
19869
19921
  stderr
19870
19922
  };
19871
- } catch {
19923
+ } catch (error) {
19924
+ if (!allowFallback) throw error;
19872
19925
  const outcome = await attempt("inherit").done;
19873
19926
  return {
19874
19927
  code: outcome?.exitCode ?? outcome?.code ?? null,
@@ -19877,6 +19930,22 @@ function createSvnRpc(deps) {
19877
19930
  };
19878
19931
  }
19879
19932
  };
19933
+ /** @private @author ddj 2026年10月14号 @param cwd Session workspace. @param root WC root. @param path Validated target. @returns Exact conflict evidence without recursive scanning. */
19934
+ const readConflict = async (cwd, root, path) => {
19935
+ const target = svnPathOf(cwd, root, path);
19936
+ if (!target) throw new Error("目标不在 SVN 工作副本内");
19937
+ const result = await runSvn(root, [
19938
+ "status",
19939
+ "--xml",
19940
+ "--verbose",
19941
+ "--depth",
19942
+ "empty",
19943
+ "--",
19944
+ pegTarget(target)
19945
+ ], READ_GRACE_MS, OUTPUT_CAP, false);
19946
+ if (result.code !== 0) throw new Error(result.stderr || result.stdout || "SVN 状态读取失败");
19947
+ return conflictOf(result.stdout, path);
19948
+ };
19880
19949
  /** 发射 TortoiseProc(GUI 长驻:不 await done,no-op catch 防未处理 rejection)。 */
19881
19950
  const launchTortoise = (exe, action, absPath) => {
19882
19951
  const spawn = subprocess();
@@ -19976,11 +20045,16 @@ function createSvnRpc(deps) {
19976
20045
  error: "svn 命令不可用(可在设置页配置 svn 路径)"
19977
20046
  };
19978
20047
  try {
20048
+ const targets = rels.map((path) => svnPathOf(sc.cwd, status.wcRoot, path));
20049
+ if (targets.some((path) => path === null)) return {
20050
+ ok: false,
20051
+ error: "目标不在 SVN 工作副本内"
20052
+ };
19979
20053
  const outcome = await runSvn(status.wcRoot, [
19980
20054
  ...verbArgs,
19981
20055
  "--",
19982
- ...rels
19983
- ], MUTATE_GRACE_MS);
20056
+ ...targets.map((path) => pegTarget(path))
20057
+ ], MUTATE_GRACE_MS, OUTPUT_CAP, !verbArgs.includes("--depth"));
19984
20058
  const result = batchResultOf(outcome.stdout, outcome.stderr, outcome.code, verb);
19985
20059
  changesCache.delete(status.wcRoot);
19986
20060
  if (outcome.code !== 0) return {
@@ -20498,8 +20572,142 @@ function createSvnRpc(deps) {
20498
20572
  };
20499
20573
  }
20500
20574
  },
20575
+ "svn.conflictStatus": async (args) => {
20576
+ const scope = await updateScope(args.sessionId, true);
20577
+ if ("err" in scope) return {
20578
+ ok: false,
20579
+ error: scope.err
20580
+ };
20581
+ if (!scope.cli) return {
20582
+ ok: false,
20583
+ error: "svn 命令不可用"
20584
+ };
20585
+ if (!Array.isArray(args.paths) || args.paths.length > 64) return {
20586
+ ok: false,
20587
+ error: "冲突路径列表不合法或超限"
20588
+ };
20589
+ if (!args.paths.every(validTarget)) return {
20590
+ ok: false,
20591
+ error: "路径不合法"
20592
+ };
20593
+ const paths = [...new Set(args.paths.map(safeRel))];
20594
+ if (paths.some((path) => !path)) return {
20595
+ ok: false,
20596
+ error: "路径不合法"
20597
+ };
20598
+ try {
20599
+ const states = [];
20600
+ for (const path of paths) states.push(await readConflict(scope.cwd, scope.root, path));
20601
+ return {
20602
+ ok: true,
20603
+ states
20604
+ };
20605
+ } catch (error) {
20606
+ return {
20607
+ ok: false,
20608
+ error: String(error)
20609
+ };
20610
+ }
20611
+ },
20612
+ "svn.resolve": async (args) => {
20613
+ if (!validAccept(args.accept)) return {
20614
+ ok: false,
20615
+ error: "解决策略不合法"
20616
+ };
20617
+ if (!validTarget(args.path)) return {
20618
+ ok: false,
20619
+ error: "路径不合法"
20620
+ };
20621
+ const path = safeRel(args.path);
20622
+ if (!path) return {
20623
+ ok: false,
20624
+ error: "路径不合法"
20625
+ };
20626
+ const scope = await updateScope(args.sessionId, true);
20627
+ if ("err" in scope) return {
20628
+ ok: false,
20629
+ error: scope.err
20630
+ };
20631
+ if (!scope.cli) return {
20632
+ ok: false,
20633
+ error: "svn 命令不可用"
20634
+ };
20635
+ if (writeBusy(scope.root)) return {
20636
+ ok: false,
20637
+ error: "工作副本正在更新或解决冲突,请等待完成"
20638
+ };
20639
+ conflictWrites.add(writeKey(scope.root));
20640
+ try {
20641
+ const evidence = await readConflict(scope.cwd, scope.root, path);
20642
+ if (!hasConflict(evidence)) return {
20643
+ ok: false,
20644
+ error: "目标已无冲突,请刷新状态"
20645
+ };
20646
+ if (evidence.tree && args.accept !== "working") return {
20647
+ ok: false,
20648
+ error: "树冲突不支持通用完整版本选择,请针对性处理"
20649
+ };
20650
+ const target = svnPathOf(scope.cwd, scope.root, path);
20651
+ const outcome = await runSvn(scope.root, [
20652
+ "resolve",
20653
+ "--accept",
20654
+ args.accept,
20655
+ "--depth",
20656
+ "empty",
20657
+ "--",
20658
+ pegTarget(target)
20659
+ ], MUTATE_GRACE_MS, OUTPUT_CAP, false);
20660
+ if (outcome.code !== 0) return {
20661
+ ok: false,
20662
+ error: outcome.stderr || outcome.stdout || "SVN 解决冲突失败"
20663
+ };
20664
+ return {
20665
+ ok: true,
20666
+ summary: "SVN 解决命令已执行,请核对剩余冲突",
20667
+ output: outcome.stdout
20668
+ };
20669
+ } catch (error) {
20670
+ return {
20671
+ ok: false,
20672
+ error: String(error)
20673
+ };
20674
+ } finally {
20675
+ conflictWrites.delete(writeKey(scope.root));
20676
+ changesCache.delete(scope.root);
20677
+ explorerCache.clear();
20678
+ }
20679
+ },
20501
20680
  "svn.revert": async (args) => {
20502
- return mutate("还原", ["revert", "-R"], args.sessionId, args.paths);
20681
+ if (args.depth === void 0) return mutate("还原", ["revert", "-R"], args.sessionId, args.paths);
20682
+ if (args.depth !== "empty") return {
20683
+ ok: false,
20684
+ error: "还原深度不合法"
20685
+ };
20686
+ if (!Array.isArray(args.paths) || !args.paths.length || !args.paths.every(validTarget)) return {
20687
+ ok: false,
20688
+ error: "路径不合法"
20689
+ };
20690
+ const scope = await updateScope(args.sessionId, true);
20691
+ if ("err" in scope) return {
20692
+ ok: false,
20693
+ error: scope.err
20694
+ };
20695
+ if (writeBusy(scope.root)) return {
20696
+ ok: false,
20697
+ error: "工作副本正在更新或解决冲突,请等待完成"
20698
+ };
20699
+ conflictWrites.add(writeKey(scope.root));
20700
+ try {
20701
+ return await mutate("还原", [
20702
+ "revert",
20703
+ "--depth",
20704
+ "empty"
20705
+ ], args.sessionId, args.paths);
20706
+ } finally {
20707
+ conflictWrites.delete(writeKey(scope.root));
20708
+ changesCache.delete(scope.root);
20709
+ explorerCache.clear();
20710
+ }
20503
20711
  },
20504
20712
  "svn.add": async (args) => {
20505
20713
  return mutate("加入版本控制", ["add"], args.sessionId, args.paths);
@@ -21354,6 +21562,10 @@ function createSvnRpc(deps) {
21354
21562
  try {
21355
21563
  const fs = ctx.get("fs");
21356
21564
  const cwd = fs?.resolve && fs?.processPath ? fs.processPath(await fs.resolve(scope.cwd, {})) : scope.cwd;
21565
+ if (conflictWrites.has(writeKey(scope.root))) return {
21566
+ ok: false,
21567
+ error: "工作副本正在解决冲突,请等待完成"
21568
+ };
21357
21569
  return {
21358
21570
  ok: true,
21359
21571
  ...updateJobs.start({