@rooode/dsh-plugin-preview 0.1.10 → 0.1.12

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
@@ -29,7 +29,7 @@ export class PreviewService extends Service {
29
29
  handler: this.handleHttpRequest.bind(this),
30
30
  });
31
31
  });
32
- console.log('[PreviewService] Registered /api/preview route on webServer');
32
+ console.log('[PreviewService] Registered /api/preview route on webServer (v0.1.12)');
33
33
  } else {
34
34
  console.warn('[PreviewService] webServer not available yet in ctx');
35
35
  }
@@ -270,6 +270,128 @@ export class PreviewService extends Service {
270
270
  }
271
271
  }
272
272
 
273
+ createDirectory(targetPath, dirName) {
274
+ try {
275
+ if (!targetPath) {
276
+ return { ok: false, error: { code: 'INVALID_PARAMS', message: '目标路径不能为空' } };
277
+ }
278
+ let fullPath = path.resolve(targetPath);
279
+ if (dirName && dirName.trim()) {
280
+ fullPath = path.join(fullPath, dirName.trim());
281
+ }
282
+ if (fs.existsSync(fullPath)) {
283
+ return { ok: false, error: { code: 'ALREADY_EXISTS', message: '文件夹已存在: ' + path.basename(fullPath) } };
284
+ }
285
+ fs.mkdirSync(fullPath, { recursive: true });
286
+ return { ok: true, path: fullPath, name: path.basename(fullPath) };
287
+ } catch (err) {
288
+ return { ok: false, error: { code: 'CREATE_DIR_ERROR', message: err.message || '创建文件夹失败' } };
289
+ }
290
+ }
291
+
292
+ createFile(targetPath, fileName, content = '') {
293
+ try {
294
+ if (!targetPath) {
295
+ return { ok: false, error: { code: 'INVALID_PARAMS', message: '目标路径不能为空' } };
296
+ }
297
+ let fullPath = path.resolve(targetPath);
298
+ if (fileName && fileName.trim()) {
299
+ fullPath = path.join(fullPath, fileName.trim());
300
+ }
301
+ if (fs.existsSync(fullPath)) {
302
+ return { ok: false, error: { code: 'ALREADY_EXISTS', message: '文件已存在: ' + path.basename(fullPath) } };
303
+ }
304
+ const parentDir = path.dirname(fullPath);
305
+ if (!fs.existsSync(parentDir)) {
306
+ fs.mkdirSync(parentDir, { recursive: true });
307
+ }
308
+ fs.writeFileSync(fullPath, content, 'utf-8');
309
+ return { ok: true, path: fullPath, name: path.basename(fullPath) };
310
+ } catch (err) {
311
+ return { ok: false, error: { code: 'CREATE_FILE_ERROR', message: err.message || '创建文件失败' } };
312
+ }
313
+ }
314
+
315
+ movePath(sourcePath, targetPath, overwrite = false) {
316
+ try {
317
+ if (!sourcePath || !targetPath) {
318
+ return { ok: false, error: { code: 'INVALID_PARAMS', message: '源路径或目标路径不能为空' } };
319
+ }
320
+ const normalizedSrc = path.resolve(sourcePath);
321
+ let normalizedDest = path.resolve(targetPath);
322
+ if (!fs.existsSync(normalizedSrc)) {
323
+ return { ok: false, error: { code: 'SRC_NOT_FOUND', message: '源文件/文件夹不存在: ' + normalizedSrc } };
324
+ }
325
+ const srcStat = fs.statSync(normalizedSrc);
326
+ const isDir = srcStat.isDirectory();
327
+ if (fs.existsSync(normalizedDest) && fs.statSync(normalizedDest).isDirectory() && normalizedSrc !== normalizedDest) {
328
+ normalizedDest = path.join(normalizedDest, path.basename(normalizedSrc));
329
+ }
330
+ if (normalizedSrc.toLowerCase() === normalizedDest.toLowerCase()) {
331
+ return { ok: true, sourcePath: normalizedSrc, targetPath: normalizedDest, name: path.basename(normalizedDest), isDir, noop: true };
332
+ }
333
+ if (isDir) {
334
+ const srcPrefix = normalizedSrc.toLowerCase() + path.sep.toLowerCase();
335
+ if (normalizedDest.toLowerCase().startsWith(srcPrefix) || normalizedDest.toLowerCase() === normalizedSrc.toLowerCase()) {
336
+ return { ok: false, error: { code: 'CIRCULAR_MOVE', message: '不能将文件夹移动到其自身或其子文件夹内部' } };
337
+ }
338
+ }
339
+ if (fs.existsSync(normalizedDest) && !overwrite) {
340
+ return { ok: false, error: { code: 'TARGET_EXISTS', message: '目标位置已存在同名' + (fs.statSync(normalizedDest).isDirectory() ? '文件夹: ' : '文件: ') + path.basename(normalizedDest) } };
341
+ }
342
+ const destParent = path.dirname(normalizedDest);
343
+ if (!fs.existsSync(destParent)) {
344
+ fs.mkdirSync(destParent, { recursive: true });
345
+ }
346
+ try {
347
+ fs.renameSync(normalizedSrc, normalizedDest);
348
+ } catch (err) {
349
+ if (err.code === 'EXDEV') {
350
+ fs.cpSync(normalizedSrc, normalizedDest, { recursive: true });
351
+ fs.rmSync(normalizedSrc, { recursive: true, force: true });
352
+ } else {
353
+ throw err;
354
+ }
355
+ }
356
+ return { ok: true, sourcePath: normalizedSrc, targetPath: normalizedDest, name: path.basename(normalizedDest), isDir };
357
+ } catch (err) {
358
+ return { ok: false, error: { code: 'MOVE_ERROR', message: err.message || '移动失败' } };
359
+ }
360
+ }
361
+
362
+ renamePath(targetPath, newName) {
363
+ try {
364
+ if (!targetPath || !newName || !newName.trim()) {
365
+ return { ok: false, error: { code: 'INVALID_PARAMS', message: '路径与新名称不能为空' } };
366
+ }
367
+ const normalized = path.resolve(targetPath);
368
+ const cleanName = newName.trim();
369
+ const parentDir = path.dirname(normalized);
370
+ const destPath = path.join(parentDir, cleanName);
371
+ const result = this.movePath(normalized, destPath, false);
372
+ if (!result.ok) return result;
373
+ return { ok: true, sourcePath: normalized, targetPath: destPath, name: cleanName };
374
+ } catch (err) {
375
+ return { ok: false, error: { code: 'RENAME_ERROR', message: err.message || '重命名失败' } };
376
+ }
377
+ }
378
+
379
+ deletePath(targetPath) {
380
+ try {
381
+ if (!targetPath) {
382
+ return { ok: false, error: { code: 'INVALID_PARAMS', message: '目标路径不能为空' } };
383
+ }
384
+ const normalized = path.resolve(targetPath);
385
+ if (!fs.existsSync(normalized)) {
386
+ return { ok: false, error: { code: 'NOT_FOUND', message: '文件或目录不存在: ' + normalized } };
387
+ }
388
+ fs.rmSync(normalized, { recursive: true, force: true });
389
+ return { ok: true, path: normalized };
390
+ } catch (err) {
391
+ return { ok: false, error: { code: 'DELETE_ERROR', message: err.message || '删除失败' } };
392
+ }
393
+ }
394
+
273
395
  async handleHttpRequest(req, res) {
274
396
  this.setCorsHeaders(res);
275
397
  if (req.method === 'OPTIONS') {
@@ -284,7 +406,7 @@ export class PreviewService extends Service {
284
406
  try {
285
407
  if (pathname === '/api/preview/status') {
286
408
  res.statusCode = 200;
287
- res.end(JSON.stringify({ ok: true, version: '0.1.3', service: 'preview' }));
409
+ res.end(JSON.stringify({ ok: true, version: '0.1.11', service: 'preview' }));
288
410
  return;
289
411
  }
290
412
  if (pathname === '/api/preview/read' && req.method === 'GET') {
@@ -390,6 +512,52 @@ export class PreviewService extends Service {
390
512
  }
391
513
  return;
392
514
  }
515
+ if (pathname === '/api/preview/create-dir' && req.method === 'POST') {
516
+ const body = await this.parseRequestBody(req);
517
+ const targetPath = body.parentPath || body.path;
518
+ const dirName = body.name;
519
+ const result = this.createDirectory(targetPath, dirName);
520
+ res.statusCode = result.ok ? 200 : (result.error && result.error.code === 'ALREADY_EXISTS' ? 409 : 400);
521
+ res.end(JSON.stringify(result));
522
+ return;
523
+ }
524
+ if (pathname === '/api/preview/create-file' && req.method === 'POST') {
525
+ const body = await this.parseRequestBody(req);
526
+ const targetPath = body.parentPath || body.path;
527
+ const fileName = body.name;
528
+ const content = body.content || '';
529
+ const result = this.createFile(targetPath, fileName, content);
530
+ res.statusCode = result.ok ? 200 : (result.error && result.error.code === 'ALREADY_EXISTS' ? 409 : 400);
531
+ res.end(JSON.stringify(result));
532
+ return;
533
+ }
534
+ if (pathname === '/api/preview/move' && req.method === 'POST') {
535
+ const body = await this.parseRequestBody(req);
536
+ const src = body.sourcePath || body.src;
537
+ const dest = body.targetPath || body.dest;
538
+ const overwrite = Boolean(body.overwrite);
539
+ const result = this.movePath(src, dest, overwrite);
540
+ res.statusCode = result.ok ? 200 : (result.error && result.error.code === 'TARGET_EXISTS' ? 409 : 400);
541
+ res.end(JSON.stringify(result));
542
+ return;
543
+ }
544
+ if (pathname === '/api/preview/rename' && req.method === 'POST') {
545
+ const body = await this.parseRequestBody(req);
546
+ const targetPath = body.path;
547
+ const newName = body.newName || body.name;
548
+ const result = this.renamePath(targetPath, newName);
549
+ res.statusCode = result.ok ? 200 : 400;
550
+ res.end(JSON.stringify(result));
551
+ return;
552
+ }
553
+ if (pathname === '/api/preview/delete' && req.method === 'POST') {
554
+ const body = await this.parseRequestBody(req);
555
+ const targetPath = body.path;
556
+ const result = this.deletePath(targetPath);
557
+ res.statusCode = result.ok ? 200 : 400;
558
+ res.end(JSON.stringify(result));
559
+ return;
560
+ }
393
561
  res.statusCode = 404;
394
562
  res.end(JSON.stringify({ ok: false, error: { code: 'NOT_FOUND', message: '未找到路由: ' + pathname } }));
395
563
  } catch (e) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rooode/dsh-plugin-preview",
3
- "version": "0.1.10",
4
- "description": "DeepSeek Harness Markdown 文档与工作空间文件浏览器右侧预览插件 (支持工作区文件树、JSON 交互结构树/格式化、Java/C++/Python/JS/TS/YAML/Go/Rust 多语言语法高亮与符号大纲、自动换行与多标签 FileTabs)",
3
+ "version": "0.1.12",
4
+ "description": "DeepSeek Harness Markdown 文档与工作空间文件浏览器右侧预览插件 (支持工作区文件树、文件夹创建与文件移动、JSON 交互结构树/格式化、Java/C++/Python/JS/TS/YAML/Go/Rust 多语言语法高亮与符号大纲、自动换行与多标签 FileTabs)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -11,7 +11,8 @@
11
11
  "README.md"
12
12
  ],
13
13
  "publishConfig": {
14
- "access": "public"
14
+ "access": "public",
15
+ "registry": "https://registry.npmjs.org"
15
16
  },
16
17
  "scripts": {
17
18
  "build": "node scripts/build.js",