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/CHANGELOG.md +10 -0
- package/README.md +13 -3
- package/lib/client.js +1 -4
- package/lib/client.js.map +1 -1
- package/lib/index.js +6 -6
- package/package.json +3 -2
- package/src/client/FileExplorer.tsx +5 -4
- package/src/client/index.ts +1 -1
- package/src/index.ts +19 -6
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
|
-
|
|
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:
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
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.
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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)
|
package/src/client/index.ts
CHANGED
|
@@ -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` (
|
|
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
|
|
663
|
-
//
|
|
664
|
-
// `ctx.fs.resolve` →
|
|
665
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|