tavern-harness 0.1.15 → 0.1.16
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
CHANGED
|
@@ -3,6 +3,28 @@
|
|
|
3
3
|
> 本文件由 `scripts/changelog.mjs` 从 `changelog/v*.md` 生成,请勿手动编辑。
|
|
4
4
|
> 版本对比链接见文末;源文件位于仓库 `changelog/` 目录。
|
|
5
5
|
|
|
6
|
+
## [0.1.16] - 2026-10-25
|
|
7
|
+
|
|
8
|
+
### 中文
|
|
9
|
+
|
|
10
|
+
#### 修复
|
|
11
|
+
|
|
12
|
+
- 修复工作区文件管理器在触屏设备上删除按钮不可见、文件夹无法删除的问题:删除按钮现在始终显示,文件夹及其内容可以一并删除,`public` 共享链接仍不可删除
|
|
13
|
+
|
|
14
|
+
#### 内部
|
|
15
|
+
|
|
16
|
+
- 工作区删除接口支持递归删除目录并拒绝删除 `public` 软链接,补充对应测试
|
|
17
|
+
|
|
18
|
+
### English
|
|
19
|
+
|
|
20
|
+
#### Fixed
|
|
21
|
+
|
|
22
|
+
- Fixed the delete button staying hidden on touch devices and folders not being deletable in the workspace file manager: the button is now always visible, folders and their contents can be removed together, and the `public` shared link remains protected
|
|
23
|
+
|
|
24
|
+
#### Internal
|
|
25
|
+
|
|
26
|
+
- The workspace delete endpoint now removes directories recursively and refuses to delete the `public` symlink, with tests
|
|
27
|
+
|
|
6
28
|
## [0.1.15] - 2026-10-25
|
|
7
29
|
|
|
8
30
|
### 中文
|
|
@@ -444,6 +466,7 @@
|
|
|
444
466
|
- Vite + React + Zustand + Dexie stack with browser-side local persistence
|
|
445
467
|
|
|
446
468
|
<!-- 链接定义 -->
|
|
469
|
+
[0.1.16]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.15...v0.1.16
|
|
447
470
|
[0.1.15]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.14...v0.1.15
|
|
448
471
|
[0.1.14]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.13...v0.1.14
|
|
449
472
|
[0.1.13]: https://github.com/Rannichan/Tavern-Harness/compare/v0.1.12...v0.1.13
|
package/package.json
CHANGED
package/sandbox-server.mjs
CHANGED
|
@@ -670,11 +670,20 @@ const server = createServer(async (req, res) => {
|
|
|
670
670
|
throw new Error('Path is outside the workspace');
|
|
671
671
|
}
|
|
672
672
|
const fp = resolve(base, rel);
|
|
673
|
-
if (!existsSync(fp)
|
|
673
|
+
if (!existsSync(fp)) throw new Error('File or directory not found');
|
|
674
|
+
const entry = lstatSync(fp);
|
|
675
|
+
if (entry.isSymbolicLink() && rel === 'public') {
|
|
676
|
+
throw new Error('The public workspace link cannot be deleted');
|
|
677
|
+
}
|
|
678
|
+
if (!entry.isFile() && !entry.isDirectory()) throw new Error('Path is not a file or directory');
|
|
674
679
|
if (!deletingPublicLink) assertInside(base, realpathSync(fp));
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
}
|
|
680
|
+
if (entry.isDirectory()) {
|
|
681
|
+
rmSync(fp, { recursive: true, force: false });
|
|
682
|
+
} else {
|
|
683
|
+
await new Promise((resolveDelete, rejectDelete) => {
|
|
684
|
+
unlink(fp, (err) => (err ? rejectDelete(err) : resolveDelete()));
|
|
685
|
+
});
|
|
686
|
+
}
|
|
678
687
|
res.end(JSON.stringify({ ok: true, path: rel }));
|
|
679
688
|
} else {
|
|
680
689
|
// file_list:返回会话工作目录内的相对路径(分组目录)
|
|
@@ -11,7 +11,7 @@ import { useStore } from '../store/store';
|
|
|
11
11
|
// - 会话头部的工作目录标签点击打开
|
|
12
12
|
// - 只展示当前会话专属磁盘工作区(sandbox_workspace/)
|
|
13
13
|
// - 目录点击进入、支持返回上级与面包屑;文件点击复用 file_display 弹窗预览
|
|
14
|
-
// -
|
|
14
|
+
// - 可在确认后删除文件或目录(public 软链接除外)
|
|
15
15
|
// ============================================================
|
|
16
16
|
|
|
17
17
|
function extOf(path: string): string {
|
|
@@ -121,7 +121,9 @@ export function WorkspaceFileManagerModal({ sessionId, onClose }: { sessionId: n
|
|
|
121
121
|
const workspaceDir = await applySessionWorkspace(sessionId);
|
|
122
122
|
await deleteWorkspaceFile(pendingDelete.path, workspaceDir);
|
|
123
123
|
setFiles((current) => {
|
|
124
|
-
const next = (current ?? []).filter((path) =>
|
|
124
|
+
const next = (current ?? []).filter((path) => (
|
|
125
|
+
pendingDelete.isDir ? !path.startsWith(pendingDelete.path) : path !== pendingDelete.path
|
|
126
|
+
));
|
|
125
127
|
setLoadState(next.length === 0 ? 'empty' : 'ok');
|
|
126
128
|
return next;
|
|
127
129
|
});
|
|
@@ -197,33 +199,35 @@ export function WorkspaceFileManagerModal({ sessionId, onClose }: { sessionId: n
|
|
|
197
199
|
<span className="ws-fm-name">..</span>
|
|
198
200
|
</div>
|
|
199
201
|
)}
|
|
200
|
-
{visible.map((n) =>
|
|
201
|
-
n.isDir
|
|
202
|
-
|
|
202
|
+
{visible.map((n) => {
|
|
203
|
+
const isPublicLink = n.isDir && n.path === 'public/';
|
|
204
|
+
return (
|
|
205
|
+
<div
|
|
206
|
+
key={n.path}
|
|
207
|
+
className="ws-fm-row"
|
|
208
|
+
role="button"
|
|
209
|
+
onClick={() => n.isDir ? setDir(n.path) : openFile(n)}
|
|
210
|
+
title={n.path}
|
|
211
|
+
>
|
|
203
212
|
<span className="ws-fm-arrow" />
|
|
204
|
-
<Icon name=
|
|
205
|
-
<span className=
|
|
213
|
+
<Icon name={n.isDir ? 'folder' : 'file'} size={14} />
|
|
214
|
+
<span className={`ws-fm-name${n.isDir ? '' : ' mono'}`}>{n.name}{n.isDir ? '/' : ''}</span>
|
|
215
|
+
{!isPublicLink && (
|
|
216
|
+
<span className="ws-fm-actions">
|
|
217
|
+
<button
|
|
218
|
+
className="icon-btn ws-fm-delete"
|
|
219
|
+
title={t('common.delete')}
|
|
220
|
+
aria-label={t('common.delete')}
|
|
221
|
+
onClick={(event) => {
|
|
222
|
+
event.stopPropagation();
|
|
223
|
+
setPendingDelete(n);
|
|
224
|
+
}}
|
|
225
|
+
><Icon name="trash" size={13} /></button>
|
|
226
|
+
</span>
|
|
227
|
+
)}
|
|
206
228
|
</div>
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
<span className="ws-fm-arrow" />
|
|
210
|
-
<Icon name="file" size={14} />
|
|
211
|
-
<span className="ws-fm-name mono">{n.name}</span>
|
|
212
|
-
<span className="ws-fm-actions">
|
|
213
|
-
<span className="ws-fm-open"><Icon name="eye" size={12} /></span>
|
|
214
|
-
<button
|
|
215
|
-
className="icon-btn ws-fm-delete"
|
|
216
|
-
title={t('common.delete')}
|
|
217
|
-
aria-label={t('common.delete')}
|
|
218
|
-
onClick={(event) => {
|
|
219
|
-
event.stopPropagation();
|
|
220
|
-
setPendingDelete(n);
|
|
221
|
-
}}
|
|
222
|
-
><Icon name="trash" size={13} /></button>
|
|
223
|
-
</span>
|
|
224
|
-
</div>
|
|
225
|
-
)
|
|
226
|
-
)}
|
|
229
|
+
);
|
|
230
|
+
})}
|
|
227
231
|
{visible.length === 0 && <div className="ws-fm-empty">{t('header.fileManagerEmpty')}</div>}
|
|
228
232
|
</div>
|
|
229
233
|
)}
|
|
@@ -462,7 +462,7 @@ export async function writeWorkspaceFileTextFor(
|
|
|
462
462
|
return diskFileWrite(normalizedPath, content, false, workspaceDir);
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
-
/**
|
|
465
|
+
/** 删除工作区中的文件或目录。 */
|
|
466
466
|
export async function deleteWorkspaceFile(path: string, workspaceDir: string): Promise<void> {
|
|
467
467
|
const normalizedPath = normalizePath(path);
|
|
468
468
|
if (!normalizedPath) throw new Error('Invalid path');
|
|
@@ -1,7 +1,43 @@
|
|
|
1
1
|
{
|
|
2
|
-
"latest": "0.1.
|
|
2
|
+
"latest": "0.1.16",
|
|
3
3
|
"repo": "https://github.com/Rannichan/Tavern-Harness",
|
|
4
4
|
"entries": [
|
|
5
|
+
{
|
|
6
|
+
"version": "0.1.16",
|
|
7
|
+
"date": "2026-10-25",
|
|
8
|
+
"breaking": false,
|
|
9
|
+
"compare": "v0.1.15...v0.1.16",
|
|
10
|
+
"groups": {
|
|
11
|
+
"zh-CN": [
|
|
12
|
+
{
|
|
13
|
+
"kind": "fixed",
|
|
14
|
+
"items": [
|
|
15
|
+
"修复工作区文件管理器在触屏设备上删除按钮不可见、文件夹无法删除的问题:删除按钮现在始终显示,文件夹及其内容可以一并删除,`public` 共享链接仍不可删除"
|
|
16
|
+
]
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"kind": "internal",
|
|
20
|
+
"items": [
|
|
21
|
+
"工作区删除接口支持递归删除目录并拒绝删除 `public` 软链接,补充对应测试"
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"en": [
|
|
26
|
+
{
|
|
27
|
+
"kind": "fixed",
|
|
28
|
+
"items": [
|
|
29
|
+
"Fixed the delete button staying hidden on touch devices and folders not being deletable in the workspace file manager: the button is now always visible, folders and their contents can be removed together, and the `public` shared link remains protected"
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"kind": "internal",
|
|
34
|
+
"items": [
|
|
35
|
+
"The workspace delete endpoint now removes directories recursively and refuses to delete the `public` symlink, with tests"
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
5
41
|
{
|
|
6
42
|
"version": "0.1.15",
|
|
7
43
|
"date": "2026-10-25",
|
package/src/theme/views.css
CHANGED
|
@@ -1769,21 +1769,10 @@
|
|
|
1769
1769
|
align-items: center;
|
|
1770
1770
|
gap: 4px;
|
|
1771
1771
|
}
|
|
1772
|
-
.ws-fm-open {
|
|
1773
|
-
display: inline-flex;
|
|
1774
|
-
align-items: center;
|
|
1775
|
-
opacity: 0.55;
|
|
1776
|
-
}
|
|
1777
1772
|
.ws-fm-delete {
|
|
1778
1773
|
width: 26px;
|
|
1779
1774
|
height: 26px;
|
|
1780
1775
|
color: var(--text-faint);
|
|
1781
|
-
opacity: 0;
|
|
1782
|
-
}
|
|
1783
|
-
.ws-fm-row:hover .ws-fm-open,
|
|
1784
|
-
.ws-fm-row:hover .ws-fm-delete,
|
|
1785
|
-
.ws-fm-delete:focus-visible {
|
|
1786
|
-
opacity: 1;
|
|
1787
1776
|
}
|
|
1788
1777
|
.ws-fm-delete:hover {
|
|
1789
1778
|
color: var(--danger);
|