dsh-plugin-workbench 0.0.6 → 0.0.7

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
@@ -525,13 +525,14 @@ async function copyEntry(ctx, payload, signal) {
525
525
  const overwrite = typeof payload === "object" && payload !== null && payload.overwrite === true;
526
526
  if (typeof from !== "string" || from.trim().length === 0) return fail("copy: payload.from must be a non-empty string");
527
527
  if (typeof to !== "string" || to.trim().length === 0) return fail("copy: payload.to must be a non-empty string");
528
+ let toOs = "";
528
529
  try {
529
530
  const fromTarget = await ctx.fs.resolve(from, { signal });
530
531
  const fromOs = ctx.fs.processPath(fromTarget);
531
532
  const info = await ctx.fs.stat(fromTarget, signal);
532
533
  if (info === void 0) return fail(`path not found: ${from}`);
533
534
  const toTarget = await ctx.fs.resolve(to, { signal });
534
- const toOs = ctx.fs.processPath(toTarget);
535
+ toOs = ctx.fs.processPath(toTarget);
535
536
  if (fromOs.toLowerCase() === toOs.toLowerCase()) return fail("copy: source and destination are the same path");
536
537
  if (info.type === "directory") {
537
538
  const sep = fromOs.includes("\\") ? "\\" : "/";
@@ -549,11 +550,10 @@ async function copyEntry(ctx, payload, signal) {
549
550
  };
550
551
  } catch (error) {
551
552
  if (isFsErrorCode(error, "ERR_FS_CP_EEXIST")) return {
552
- ok: false,
553
- error: {
554
- code: "exists",
555
- message: "destination already exists",
556
- details: {}
553
+ ok: true,
554
+ value: {
555
+ path: toOs,
556
+ exists: true
557
557
  }
558
558
  };
559
559
  return fail(mapError(error));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-workbench",
3
3
  "description": "VS Code-style workspace file explorer + editable preview for the dsh web GUI",
4
- "version": "0.0.6",
4
+ "version": "0.0.7",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@11.21.0",
7
7
  "engines": {
@@ -68,6 +68,7 @@
68
68
  "cordis.patch.yml",
69
69
  "src",
70
70
  "README.md",
71
- "CHANGELOG.md"
71
+ "CHANGELOG.md",
72
+ "LICENSE"
72
73
  ]
73
74
  }
@@ -34,6 +34,8 @@ export interface FsReadResult {
34
34
  /** Result of a context-menu mutation (create/rename/delete). */
35
35
  export interface FsMutationResult {
36
36
  path: string
37
+ /** Copy-only: `true` when the destination existed and the copy was skipped (client may ask about overwrite). */
38
+ exists?: boolean
37
39
  }
38
40
 
39
41
  interface SessionSummary {
@@ -588,10 +590,9 @@ export function FileExplorer({
588
590
  if (sameDest) dest = joinPath(targetDir, copyName(item.name))
589
591
  let overwritten = false
590
592
  try {
591
- try {
592
- await copyPath(item.path, dest, false)
593
- } catch (error) {
594
- if ((error as { code?: string }).code !== 'exists') throw error
593
+ const copied = await copyPath(item.path, dest, false)
594
+ if (copied.exists === true) {
595
+ // Collision: ask before overwriting, like the OS file manager.
595
596
  if (!window.confirm(t('confirm.overwrite', { name: item.name }))) continue
596
597
  overwritten = true
597
598
  await copyPath(item.path, dest, true)
@@ -52,7 +52,7 @@ interface RpcFailure {
52
52
  error: { code: string; message: string }
53
53
  }
54
54
 
55
- /** Error carrying the host's machine-readable `code` (e.g. 'exists' on copy collision). */
55
+ /** Error carrying the host's machine-readable `code` (always a core union code; copy collisions are reported in the value instead). */
56
56
  export interface RpcError extends Error {
57
57
  code?: string
58
58
  }
package/src/index.ts CHANGED
@@ -114,6 +114,13 @@ export interface FsWriteResult {
114
114
  /** Result of a context-menu mutation (create/rename/delete). */
115
115
  export interface FsMutationResult {
116
116
  path: string
117
+ /**
118
+ * Copy-only: `true` when the destination already existed and the copy was
119
+ * skipped (overwrite was false). Reported in the SUCCESS value — the generic
120
+ * RPC channel validates error bodies against the core's closed error-code
121
+ * union, so a custom error code like 'exists' would fail client-side parsing.
122
+ */
123
+ exists?: boolean
117
124
  }
118
125
 
119
126
  export type FilesRpcOk = { ok: true; value: FsListResult | FsReadResult | FsWriteResult | FsMutationResult }
@@ -659,10 +666,13 @@ async function deleteEntry(ctx: Context, payload: unknown, signal: AbortSignal):
659
666
  // Copy (the explorer's Copy / Cut + Paste)
660
667
  //
661
668
  // `fs.cp` handles files and (recursively) folders; `errorOnExist` turns a
662
- // colliding destination into a distinct `exists` error so the client can ask
663
- // the user whether to overwrite, exactly like the OS file manager. The same
664
- // `ctx.fs.resolve` → `processPath` resolution as every other endpoint applies,
665
- // so sandbox containment and error mapping stay uniform.
669
+ // colliding destination into an `ERR_FS_CP_EEXIST` throw, which is reported as
670
+ // a SUCCESS with `exists: true` so the client can ask the user whether to
671
+ // overwrite, exactly like the OS file manager. The same `ctx.fs.resolve` →
672
+ // `processPath` resolution as every other endpoint applies, so sandbox
673
+ // containment and error mapping stay uniform. (Reported in the value, never as
674
+ // an error body: generic-channel errors must use the core's closed error-code
675
+ // union, and a custom code there would fail the client-side response parse.)
666
676
  // ---------------------------------------------------------------------------
667
677
 
668
678
  async function copyEntry(ctx: Context, payload: unknown, signal: AbortSignal): Promise<FilesRpcResult> {
@@ -672,13 +682,14 @@ async function copyEntry(ctx: Context, payload: unknown, signal: AbortSignal): P
672
682
  const overwrite = typeof payload === 'object' && payload !== null && (payload as { overwrite?: unknown }).overwrite === true
673
683
  if (typeof from !== 'string' || from.trim().length === 0) return fail('copy: payload.from must be a non-empty string')
674
684
  if (typeof to !== 'string' || to.trim().length === 0) return fail('copy: payload.to must be a non-empty string')
685
+ let toOs = ''
675
686
  try {
676
687
  const fromTarget = await ctx.fs.resolve(from, { signal })
677
688
  const fromOs = ctx.fs.processPath(fromTarget)
678
689
  const info = await ctx.fs.stat(fromTarget, signal)
679
690
  if (info === undefined) return fail(`path not found: ${from}`)
680
691
  const toTarget = await ctx.fs.resolve(to, { signal })
681
- const toOs = ctx.fs.processPath(toTarget)
692
+ toOs = ctx.fs.processPath(toTarget)
682
693
  // Refuse trivial self-copies and copying a folder into itself or one of
683
694
  // its own descendants (comparisons case-insensitive — Windows).
684
695
  const samePath = fromOs.toLowerCase() === toOs.toLowerCase()
@@ -692,7 +703,9 @@ async function copyEntry(ctx: Context, payload: unknown, signal: AbortSignal): P
692
703
  return { ok: true, value: { path: toOs } }
693
704
  } catch (error) {
694
705
  if (isFsErrorCode(error, 'ERR_FS_CP_EEXIST')) {
695
- return { ok: false, error: { code: 'exists', message: 'destination already exists', details: {} } }
706
+ // Destination already exists and overwrite was not requested: report the
707
+ // collision as a successful no-op so the client can ask about overwrite.
708
+ return { ok: true, value: { path: toOs, exists: true } }
696
709
  }
697
710
  return fail(mapError(error))
698
711
  }