@rooode/dsh-plugin-preview 0.1.2 → 0.1.4
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/client.js +3277 -1241
- package/lib/index.js +172 -1
- package/package.json +2 -2
package/lib/index.js
CHANGED
|
@@ -130,6 +130,146 @@ export class PreviewService extends Service {
|
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
readWorkspaceTree(targetDir, maxDepth = 4, includeAll = false) {
|
|
134
|
+
try {
|
|
135
|
+
const normalizedRoot = path.resolve(targetDir);
|
|
136
|
+
if (!fs.existsSync(normalizedRoot)) {
|
|
137
|
+
return { ok: false, error: { code: 'DIR_NOT_FOUND', message: '工作区目录不存在: ' + normalizedRoot } };
|
|
138
|
+
}
|
|
139
|
+
const stat = fs.statSync(normalizedRoot);
|
|
140
|
+
if (!stat.isDirectory()) {
|
|
141
|
+
return { ok: false, error: { code: 'NOT_A_DIRECTORY', message: '目标路径不是目录: ' + normalizedRoot } };
|
|
142
|
+
}
|
|
143
|
+
const ignoreDirs = new Set([
|
|
144
|
+
'node_modules', '.git', '.dsh', '.gemini', '.tempmediastorage', '.idea', '.vscode',
|
|
145
|
+
'dist', 'build', 'coverage', '.next', '.nuxt', '.output', '.turbo', '.cache',
|
|
146
|
+
'__pycache__', '.pytest_cache', 'target', 'vendor'
|
|
147
|
+
]);
|
|
148
|
+
let totalFiles = 0;
|
|
149
|
+
let totalDirs = 0;
|
|
150
|
+
|
|
151
|
+
const buildTree = (dirPath, currentDepth) => {
|
|
152
|
+
if (currentDepth > maxDepth) return [];
|
|
153
|
+
let entries = [];
|
|
154
|
+
try {
|
|
155
|
+
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
const nodes = [];
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
const entryName = entry.name;
|
|
162
|
+
const lowerName = entryName.toLowerCase();
|
|
163
|
+
if (!includeAll) {
|
|
164
|
+
if (ignoreDirs.has(lowerName)) continue;
|
|
165
|
+
if (entryName.startsWith('.') && entryName !== '.env' && entryName !== '.gitignore') continue;
|
|
166
|
+
}
|
|
167
|
+
const fullPath = path.join(dirPath, entryName);
|
|
168
|
+
const relPath = path.relative(normalizedRoot, fullPath).replace(/\\/g, '/');
|
|
169
|
+
try {
|
|
170
|
+
if (entry.isDirectory()) {
|
|
171
|
+
totalDirs++;
|
|
172
|
+
const children = buildTree(fullPath, currentDepth + 1);
|
|
173
|
+
nodes.push({
|
|
174
|
+
name: entryName,
|
|
175
|
+
path: fullPath,
|
|
176
|
+
relativePath: relPath,
|
|
177
|
+
isDir: true,
|
|
178
|
+
children,
|
|
179
|
+
childCount: children.length,
|
|
180
|
+
});
|
|
181
|
+
} else if (entry.isFile()) {
|
|
182
|
+
totalFiles++;
|
|
183
|
+
const ext = path.extname(entryName).toLowerCase();
|
|
184
|
+
let size = 0;
|
|
185
|
+
let mtime = 0;
|
|
186
|
+
try {
|
|
187
|
+
const s = fs.statSync(fullPath);
|
|
188
|
+
size = s.size;
|
|
189
|
+
mtime = s.mtimeMs;
|
|
190
|
+
} catch (e) {}
|
|
191
|
+
nodes.push({
|
|
192
|
+
name: entryName,
|
|
193
|
+
path: fullPath,
|
|
194
|
+
relativePath: relPath,
|
|
195
|
+
isDir: false,
|
|
196
|
+
extension: ext,
|
|
197
|
+
size,
|
|
198
|
+
mtime,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {}
|
|
202
|
+
}
|
|
203
|
+
nodes.sort((a, b) => {
|
|
204
|
+
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
205
|
+
return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
|
|
206
|
+
});
|
|
207
|
+
return nodes;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const tree = buildTree(normalizedRoot, 1);
|
|
211
|
+
const workspaceName = path.basename(normalizedRoot) || normalizedRoot;
|
|
212
|
+
return {
|
|
213
|
+
ok: true,
|
|
214
|
+
workspaceRoot: normalizedRoot,
|
|
215
|
+
workspaceName,
|
|
216
|
+
tree,
|
|
217
|
+
totalFiles,
|
|
218
|
+
totalDirs,
|
|
219
|
+
};
|
|
220
|
+
} catch (err) {
|
|
221
|
+
return { ok: false, error: { code: 'SCAN_ERROR', message: err.message || '扫描工作区文件树失败' } };
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
listDirectoryEntries(targetDir, includeAll = false) {
|
|
226
|
+
try {
|
|
227
|
+
const normalized = path.resolve(targetDir);
|
|
228
|
+
if (!fs.existsSync(normalized) || !fs.statSync(normalized).isDirectory()) {
|
|
229
|
+
return { ok: false, error: { code: 'DIR_NOT_FOUND', message: '目录不存在' } };
|
|
230
|
+
}
|
|
231
|
+
const ignoreDirs = new Set(['node_modules', '.git', '.dsh', '.gemini', '.tempmediastorage', '.idea', '.vscode']);
|
|
232
|
+
const rawEntries = fs.readdirSync(normalized, { withFileTypes: true });
|
|
233
|
+
const nodes = [];
|
|
234
|
+
for (const entry of rawEntries) {
|
|
235
|
+
const name = entry.name;
|
|
236
|
+
if (!includeAll) {
|
|
237
|
+
if (ignoreDirs.has(name.toLowerCase())) continue;
|
|
238
|
+
if (name.startsWith('.') && name !== '.env' && name !== '.gitignore') continue;
|
|
239
|
+
}
|
|
240
|
+
const fullPath = path.join(normalized, name);
|
|
241
|
+
const isDir = entry.isDirectory();
|
|
242
|
+
let size = 0;
|
|
243
|
+
let mtime = 0;
|
|
244
|
+
let ext = '';
|
|
245
|
+
if (!isDir) {
|
|
246
|
+
ext = path.extname(name).toLowerCase();
|
|
247
|
+
try {
|
|
248
|
+
const s = fs.statSync(fullPath);
|
|
249
|
+
size = s.size;
|
|
250
|
+
mtime = s.mtimeMs;
|
|
251
|
+
} catch (e) {}
|
|
252
|
+
}
|
|
253
|
+
nodes.push({
|
|
254
|
+
name,
|
|
255
|
+
path: fullPath,
|
|
256
|
+
relativePath: name,
|
|
257
|
+
isDir,
|
|
258
|
+
extension: ext,
|
|
259
|
+
size,
|
|
260
|
+
mtime,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
nodes.sort((a, b) => {
|
|
264
|
+
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
265
|
+
return a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' });
|
|
266
|
+
});
|
|
267
|
+
return { ok: true, entries: nodes };
|
|
268
|
+
} catch (err) {
|
|
269
|
+
return { ok: false, error: { code: 'LIST_ERROR', message: err.message || '读取目录失败' } };
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
133
273
|
async handleHttpRequest(req, res) {
|
|
134
274
|
this.setCorsHeaders(res);
|
|
135
275
|
if (req.method === 'OPTIONS') {
|
|
@@ -144,7 +284,7 @@ export class PreviewService extends Service {
|
|
|
144
284
|
try {
|
|
145
285
|
if (pathname === '/api/preview/status') {
|
|
146
286
|
res.statusCode = 200;
|
|
147
|
-
res.end(JSON.stringify({ ok: true, version: '0.1.
|
|
287
|
+
res.end(JSON.stringify({ ok: true, version: '0.1.3', service: 'preview' }));
|
|
148
288
|
return;
|
|
149
289
|
}
|
|
150
290
|
if (pathname === '/api/preview/read' && req.method === 'GET') {
|
|
@@ -159,6 +299,37 @@ export class PreviewService extends Service {
|
|
|
159
299
|
res.end(JSON.stringify(result));
|
|
160
300
|
return;
|
|
161
301
|
}
|
|
302
|
+
if (pathname === '/api/preview/workspace-tree' && req.method === 'GET') {
|
|
303
|
+
const targetPath = url.searchParams.get('path') || process.cwd();
|
|
304
|
+
const depth = parseInt(url.searchParams.get('depth') || '4', 10);
|
|
305
|
+
const includeAll = url.searchParams.get('includeAll') === 'true';
|
|
306
|
+
const result = this.readWorkspaceTree(targetPath, isNaN(depth) ? 4 : depth, includeAll);
|
|
307
|
+
res.statusCode = result.ok ? 200 : 404;
|
|
308
|
+
res.end(JSON.stringify(result));
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (pathname === '/api/preview/list-dir' && req.method === 'GET') {
|
|
312
|
+
const targetPath = url.searchParams.get('path') || process.cwd();
|
|
313
|
+
const includeAll = url.searchParams.get('includeAll') === 'true';
|
|
314
|
+
const result = this.listDirectoryEntries(targetPath, includeAll);
|
|
315
|
+
res.statusCode = result.ok ? 200 : 404;
|
|
316
|
+
res.end(JSON.stringify(result));
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (pathname === '/api/preview/save' && req.method === 'POST') {
|
|
320
|
+
const body = await this.parseRequestBody(req);
|
|
321
|
+
if (!body.path || typeof body.content !== 'string') {
|
|
322
|
+
res.statusCode = 400;
|
|
323
|
+
res.end(JSON.stringify({ ok: false, error: { code: 'INVALID_PARAMS', message: '参数格式错误' } }));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
const normalized = path.resolve(body.path);
|
|
327
|
+
fs.writeFileSync(normalized, body.content, 'utf-8');
|
|
328
|
+
const stat = fs.statSync(normalized);
|
|
329
|
+
res.statusCode = 200;
|
|
330
|
+
res.end(JSON.stringify({ ok: true, size: stat.size, mtime: stat.mtimeMs }));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
162
333
|
if (pathname === '/api/preview/reveal' && req.method === 'POST') {
|
|
163
334
|
const body = await this.parseRequestBody(req);
|
|
164
335
|
if (!body.path) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rooode/dsh-plugin-preview",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "DeepSeek Harness Markdown
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "DeepSeek Harness Markdown 文档与工作空间文件浏览器右侧预览插件 (支持工作区文件树、JSON 交互结构树/格式化、YAML/JS/TS/Python 多语言语法高亮、代码符号大纲、自动换行与多标签 FileTabs)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|