mrmd-server 0.1.11 → 0.1.13
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/package.json +1 -1
- package/src/api/asset.js +10 -129
- package/src/api/file.js +27 -148
- package/src/api/project.js +7 -17
package/package.json
CHANGED
package/src/api/asset.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Asset API routes
|
|
3
3
|
*
|
|
4
|
-
* Mirrors electronAPI.asset.*
|
|
4
|
+
* Mirrors electronAPI.asset.* using AssetService from mrmd-electron
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Router } from 'express';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
import fs from 'fs/promises';
|
|
10
|
-
import crypto from 'crypto';
|
|
11
9
|
import multer from 'multer';
|
|
12
10
|
|
|
13
11
|
// Configure multer for file uploads
|
|
@@ -22,6 +20,7 @@ const upload = multer({
|
|
|
22
20
|
*/
|
|
23
21
|
export function createAssetRoutes(ctx) {
|
|
24
22
|
const router = Router();
|
|
23
|
+
const { assetService } = ctx;
|
|
25
24
|
|
|
26
25
|
/**
|
|
27
26
|
* GET /api/asset?projectRoot=...
|
|
@@ -31,33 +30,8 @@ export function createAssetRoutes(ctx) {
|
|
|
31
30
|
router.get('/', async (req, res) => {
|
|
32
31
|
try {
|
|
33
32
|
const projectRoot = req.query.projectRoot || ctx.projectDir;
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const files = await fs.readdir(assetsDir);
|
|
38
|
-
const assets = [];
|
|
39
|
-
|
|
40
|
-
for (const file of files) {
|
|
41
|
-
if (file.startsWith('.')) continue;
|
|
42
|
-
|
|
43
|
-
const filePath = path.join(assetsDir, file);
|
|
44
|
-
const stat = await fs.stat(filePath);
|
|
45
|
-
|
|
46
|
-
assets.push({
|
|
47
|
-
name: file,
|
|
48
|
-
path: `_assets/${file}`,
|
|
49
|
-
size: stat.size,
|
|
50
|
-
modified: stat.mtime.toISOString(),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
res.json(assets);
|
|
55
|
-
} catch (err) {
|
|
56
|
-
if (err.code === 'ENOENT') {
|
|
57
|
-
return res.json([]);
|
|
58
|
-
}
|
|
59
|
-
throw err;
|
|
60
|
-
}
|
|
33
|
+
const assets = await assetService.list(projectRoot);
|
|
34
|
+
res.json(assets);
|
|
61
35
|
} catch (err) {
|
|
62
36
|
console.error('[asset:list]', err);
|
|
63
37
|
res.status(500).json({ error: err.message });
|
|
@@ -94,36 +68,9 @@ export function createAssetRoutes(ctx) {
|
|
|
94
68
|
return res.status(400).json({ error: 'No file provided' });
|
|
95
69
|
}
|
|
96
70
|
|
|
97
|
-
|
|
98
|
-
await
|
|
99
|
-
|
|
100
|
-
// Compute hash for deduplication
|
|
101
|
-
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex').slice(0, 8);
|
|
102
|
-
const ext = path.extname(filename);
|
|
103
|
-
const base = path.basename(filename, ext);
|
|
104
|
-
|
|
105
|
-
// Check if identical file already exists
|
|
106
|
-
const existingFiles = await fs.readdir(assetsDir).catch(() => []);
|
|
107
|
-
for (const existing of existingFiles) {
|
|
108
|
-
if (existing.startsWith(hash + '-')) {
|
|
109
|
-
// Found duplicate
|
|
110
|
-
return res.json({
|
|
111
|
-
path: `_assets/${existing}`,
|
|
112
|
-
deduplicated: true,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Save with hash prefix
|
|
118
|
-
const finalName = `${hash}-${base}${ext}`;
|
|
119
|
-
const finalPath = path.join(assetsDir, finalName);
|
|
120
|
-
|
|
121
|
-
await fs.writeFile(finalPath, fileBuffer);
|
|
122
|
-
|
|
123
|
-
res.json({
|
|
124
|
-
path: `_assets/${finalName}`,
|
|
125
|
-
deduplicated: false,
|
|
126
|
-
});
|
|
71
|
+
// AssetService.save expects a file-like object
|
|
72
|
+
const result = await assetService.save(projectRoot, fileBuffer, filename);
|
|
73
|
+
res.json(result);
|
|
127
74
|
} catch (err) {
|
|
128
75
|
console.error('[asset:save]', err);
|
|
129
76
|
res.status(500).json({ error: err.message });
|
|
@@ -162,40 +109,8 @@ export function createAssetRoutes(ctx) {
|
|
|
162
109
|
router.get('/orphans', async (req, res) => {
|
|
163
110
|
try {
|
|
164
111
|
const projectRoot = req.query.projectRoot || ctx.projectDir;
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
// Get all assets
|
|
168
|
-
let assetFiles;
|
|
169
|
-
try {
|
|
170
|
-
assetFiles = await fs.readdir(assetsDir);
|
|
171
|
-
} catch {
|
|
172
|
-
return res.json([]);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Get all markdown files
|
|
176
|
-
const mdFiles = await scanMarkdownFiles(projectRoot);
|
|
177
|
-
|
|
178
|
-
// Read all markdown content and find referenced assets
|
|
179
|
-
const referencedAssets = new Set();
|
|
180
|
-
for (const mdFile of mdFiles) {
|
|
181
|
-
try {
|
|
182
|
-
const content = await fs.readFile(mdFile, 'utf-8');
|
|
183
|
-
// Find asset references (images, links)
|
|
184
|
-
const matches = content.matchAll(/!\[.*?\]\(([^)]+)\)|href="([^"]+)"/g);
|
|
185
|
-
for (const match of matches) {
|
|
186
|
-
const ref = match[1] || match[2];
|
|
187
|
-
if (ref && ref.includes('_assets/')) {
|
|
188
|
-
const assetName = path.basename(ref);
|
|
189
|
-
referencedAssets.add(assetName);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
} catch {}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Find orphans
|
|
196
|
-
const orphans = assetFiles.filter(f => !f.startsWith('.') && !referencedAssets.has(f));
|
|
197
|
-
|
|
198
|
-
res.json(orphans.map(f => `_assets/${f}`));
|
|
112
|
+
const orphans = await assetService.findOrphans(projectRoot);
|
|
113
|
+
res.json(orphans);
|
|
199
114
|
} catch (err) {
|
|
200
115
|
console.error('[asset:orphans]', err);
|
|
201
116
|
res.status(500).json({ error: err.message });
|
|
@@ -216,14 +131,7 @@ export function createAssetRoutes(ctx) {
|
|
|
216
131
|
return res.status(400).json({ error: 'assetPath required' });
|
|
217
132
|
}
|
|
218
133
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
// Security check
|
|
222
|
-
if (!fullPath.startsWith(path.resolve(projectRoot))) {
|
|
223
|
-
return res.status(400).json({ error: 'Invalid path' });
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
await fs.unlink(fullPath);
|
|
134
|
+
await assetService.delete(projectRoot, assetPath);
|
|
227
135
|
res.json({ success: true });
|
|
228
136
|
} catch (err) {
|
|
229
137
|
console.error('[asset:delete]', err);
|
|
@@ -254,30 +162,3 @@ export function createAssetRoutes(ctx) {
|
|
|
254
162
|
|
|
255
163
|
return router;
|
|
256
164
|
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Recursively scan for markdown files
|
|
260
|
-
*/
|
|
261
|
-
async function scanMarkdownFiles(dir, maxDepth = 6, currentDepth = 0) {
|
|
262
|
-
if (currentDepth > maxDepth) return [];
|
|
263
|
-
|
|
264
|
-
const files = [];
|
|
265
|
-
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
266
|
-
|
|
267
|
-
for (const entry of entries) {
|
|
268
|
-
if (entry.name.startsWith('.')) continue;
|
|
269
|
-
if (entry.name === 'node_modules') continue;
|
|
270
|
-
if (entry.name === '_assets') continue;
|
|
271
|
-
|
|
272
|
-
const fullPath = path.join(dir, entry.name);
|
|
273
|
-
|
|
274
|
-
if (entry.isDirectory()) {
|
|
275
|
-
const subFiles = await scanMarkdownFiles(fullPath, maxDepth, currentDepth + 1);
|
|
276
|
-
files.push(...subFiles);
|
|
277
|
-
} else if (entry.name.endsWith('.md')) {
|
|
278
|
-
files.push(fullPath);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
return files;
|
|
283
|
-
}
|
package/src/api/file.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* File API routes
|
|
3
3
|
*
|
|
4
|
-
* Mirrors electronAPI.file.*
|
|
4
|
+
* Mirrors electronAPI.file.* using FileService from mrmd-electron
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { Router } from 'express';
|
|
8
8
|
import path from 'path';
|
|
9
|
-
import fs from 'fs/promises';
|
|
10
|
-
import { constants as fsConstants } from 'fs';
|
|
11
9
|
|
|
12
10
|
/**
|
|
13
11
|
* Create file routes
|
|
@@ -15,6 +13,7 @@ import { constants as fsConstants } from 'fs';
|
|
|
15
13
|
*/
|
|
16
14
|
export function createFileRoutes(ctx) {
|
|
17
15
|
const router = Router();
|
|
16
|
+
const { fileService } = ctx;
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
19
|
* GET /api/file/scan?root=...&extensions=...&maxDepth=...
|
|
@@ -24,11 +23,13 @@ export function createFileRoutes(ctx) {
|
|
|
24
23
|
router.get('/scan', async (req, res) => {
|
|
25
24
|
try {
|
|
26
25
|
const root = req.query.root || ctx.projectDir;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const options = {
|
|
27
|
+
extensions: req.query.extensions?.split(',') || ['.md'],
|
|
28
|
+
maxDepth: parseInt(req.query.maxDepth) || 6,
|
|
29
|
+
includeHidden: req.query.includeHidden === 'true',
|
|
30
|
+
};
|
|
30
31
|
|
|
31
|
-
const files = await
|
|
32
|
+
const files = await fileService.scan(root, options);
|
|
32
33
|
res.json(files);
|
|
33
34
|
} catch (err) {
|
|
34
35
|
console.error('[file:scan]', err);
|
|
@@ -49,23 +50,14 @@ export function createFileRoutes(ctx) {
|
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
const fullPath = resolvePath(ctx.projectDir, filePath);
|
|
52
|
-
|
|
53
|
-
// Create directory if needed
|
|
54
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
55
|
-
|
|
56
|
-
// Check if file exists
|
|
57
|
-
try {
|
|
58
|
-
await fs.access(fullPath, fsConstants.F_OK);
|
|
59
|
-
return res.status(409).json({ error: 'File already exists' });
|
|
60
|
-
} catch {
|
|
61
|
-
// File doesn't exist, good to create
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
await fs.writeFile(fullPath, content, 'utf-8');
|
|
53
|
+
const result = await fileService.createFile(fullPath, content);
|
|
65
54
|
|
|
66
55
|
ctx.eventBus.projectChanged(ctx.projectDir);
|
|
67
|
-
res.json({ success: true, path:
|
|
56
|
+
res.json({ success: true, path: result });
|
|
68
57
|
} catch (err) {
|
|
58
|
+
if (err.message?.includes('already exists')) {
|
|
59
|
+
return res.status(409).json({ error: 'File already exists' });
|
|
60
|
+
}
|
|
69
61
|
console.error('[file:create]', err);
|
|
70
62
|
res.status(500).json({ error: err.message });
|
|
71
63
|
}
|
|
@@ -84,40 +76,12 @@ export function createFileRoutes(ctx) {
|
|
|
84
76
|
}
|
|
85
77
|
|
|
86
78
|
const root = projectRoot || ctx.projectDir;
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
// Create directory if needed
|
|
90
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
91
|
-
|
|
92
|
-
// If file exists, find next available FSML name
|
|
93
|
-
let finalPath = fullPath;
|
|
94
|
-
try {
|
|
95
|
-
await fs.access(fullPath, fsConstants.F_OK);
|
|
96
|
-
// File exists, generate unique name
|
|
97
|
-
const dir = path.dirname(fullPath);
|
|
98
|
-
const ext = path.extname(fullPath);
|
|
99
|
-
const base = path.basename(fullPath, ext);
|
|
100
|
-
|
|
101
|
-
let counter = 1;
|
|
102
|
-
while (true) {
|
|
103
|
-
finalPath = path.join(dir, `${base}-${counter}${ext}`);
|
|
104
|
-
try {
|
|
105
|
-
await fs.access(finalPath, fsConstants.F_OK);
|
|
106
|
-
counter++;
|
|
107
|
-
} catch {
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
} catch {
|
|
112
|
-
// File doesn't exist, use original path
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
await fs.writeFile(finalPath, content, 'utf-8');
|
|
79
|
+
const result = await fileService.createInProject(root, relativePath, content);
|
|
116
80
|
|
|
117
81
|
ctx.eventBus.projectChanged(root);
|
|
118
82
|
res.json({
|
|
119
83
|
success: true,
|
|
120
|
-
path:
|
|
84
|
+
path: result.relativePath,
|
|
121
85
|
});
|
|
122
86
|
} catch (err) {
|
|
123
87
|
console.error('[file:createInProject]', err);
|
|
@@ -138,27 +102,13 @@ export function createFileRoutes(ctx) {
|
|
|
138
102
|
}
|
|
139
103
|
|
|
140
104
|
const root = projectRoot || ctx.projectDir;
|
|
141
|
-
const
|
|
142
|
-
const fullToPath = resolvePath(root, toPath);
|
|
143
|
-
|
|
144
|
-
// Create destination directory if needed
|
|
145
|
-
await fs.mkdir(path.dirname(fullToPath), { recursive: true });
|
|
146
|
-
|
|
147
|
-
// Move the file
|
|
148
|
-
await fs.rename(fullFromPath, fullToPath);
|
|
149
|
-
|
|
150
|
-
// TODO: Update internal links in other files (refactoring)
|
|
151
|
-
// This would require parsing all .md files and updating links
|
|
152
|
-
// For now, just return the moved file
|
|
105
|
+
const result = await fileService.move(root, fromPath, toPath);
|
|
153
106
|
|
|
154
107
|
ctx.eventBus.projectChanged(root);
|
|
155
108
|
res.json({
|
|
156
109
|
success: true,
|
|
157
|
-
movedFile:
|
|
158
|
-
|
|
159
|
-
to: toPath,
|
|
160
|
-
},
|
|
161
|
-
updatedFiles: [], // TODO: implement link refactoring
|
|
110
|
+
movedFile: result.movedFile,
|
|
111
|
+
updatedFiles: result.updatedFiles || [],
|
|
162
112
|
});
|
|
163
113
|
} catch (err) {
|
|
164
114
|
console.error('[file:move]', err);
|
|
@@ -179,36 +129,13 @@ export function createFileRoutes(ctx) {
|
|
|
179
129
|
}
|
|
180
130
|
|
|
181
131
|
const root = projectRoot || ctx.projectDir;
|
|
182
|
-
|
|
183
|
-
// TODO: Implement FSML reordering
|
|
184
|
-
// This involves:
|
|
185
|
-
// 1. Reading the source and target directories
|
|
186
|
-
// 2. Calculating new FSML prefixes
|
|
187
|
-
// 3. Renaming files with new prefixes
|
|
188
|
-
|
|
189
|
-
// For now, just do a simple move
|
|
190
|
-
const fullSourcePath = resolvePath(root, sourcePath);
|
|
191
|
-
let fullTargetPath;
|
|
192
|
-
|
|
193
|
-
if (position === 'inside') {
|
|
194
|
-
// Move into target directory
|
|
195
|
-
fullTargetPath = resolvePath(root, path.join(targetPath, path.basename(sourcePath)));
|
|
196
|
-
} else {
|
|
197
|
-
// Move to same directory as target
|
|
198
|
-
fullTargetPath = resolvePath(root, path.join(path.dirname(targetPath), path.basename(sourcePath)));
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
await fs.mkdir(path.dirname(fullTargetPath), { recursive: true });
|
|
202
|
-
await fs.rename(fullSourcePath, fullTargetPath);
|
|
132
|
+
const result = await fileService.reorder(root, sourcePath, targetPath, position);
|
|
203
133
|
|
|
204
134
|
ctx.eventBus.projectChanged(root);
|
|
205
135
|
res.json({
|
|
206
136
|
success: true,
|
|
207
|
-
movedFile:
|
|
208
|
-
|
|
209
|
-
to: path.relative(root, fullTargetPath),
|
|
210
|
-
},
|
|
211
|
-
updatedFiles: [],
|
|
137
|
+
movedFile: result.movedFile,
|
|
138
|
+
updatedFiles: result.updatedFiles || [],
|
|
212
139
|
});
|
|
213
140
|
} catch (err) {
|
|
214
141
|
console.error('[file:reorder]', err);
|
|
@@ -229,13 +156,7 @@ export function createFileRoutes(ctx) {
|
|
|
229
156
|
}
|
|
230
157
|
|
|
231
158
|
const fullPath = resolvePath(ctx.projectDir, filePath);
|
|
232
|
-
|
|
233
|
-
const stat = await fs.stat(fullPath);
|
|
234
|
-
if (stat.isDirectory()) {
|
|
235
|
-
await fs.rm(fullPath, { recursive: true });
|
|
236
|
-
} else {
|
|
237
|
-
await fs.unlink(fullPath);
|
|
238
|
-
}
|
|
159
|
+
await fileService.delete(fullPath);
|
|
239
160
|
|
|
240
161
|
ctx.eventBus.projectChanged(ctx.projectDir);
|
|
241
162
|
res.json({ success: true });
|
|
@@ -258,7 +179,7 @@ export function createFileRoutes(ctx) {
|
|
|
258
179
|
}
|
|
259
180
|
|
|
260
181
|
const fullPath = resolvePath(ctx.projectDir, filePath);
|
|
261
|
-
const content = await
|
|
182
|
+
const content = await fileService.read(fullPath);
|
|
262
183
|
|
|
263
184
|
res.json({ success: true, content });
|
|
264
185
|
} catch (err) {
|
|
@@ -283,11 +204,7 @@ export function createFileRoutes(ctx) {
|
|
|
283
204
|
}
|
|
284
205
|
|
|
285
206
|
const fullPath = resolvePath(ctx.projectDir, filePath);
|
|
286
|
-
|
|
287
|
-
// Create directory if needed
|
|
288
|
-
await fs.mkdir(path.dirname(fullPath), { recursive: true });
|
|
289
|
-
|
|
290
|
-
await fs.writeFile(fullPath, content ?? '', 'utf-8');
|
|
207
|
+
await fileService.write(fullPath, content ?? '');
|
|
291
208
|
|
|
292
209
|
res.json({ success: true });
|
|
293
210
|
} catch (err) {
|
|
@@ -311,7 +228,7 @@ export function createFileRoutes(ctx) {
|
|
|
311
228
|
}
|
|
312
229
|
|
|
313
230
|
const fullPath = resolvePath(ctx.projectDir, filePath);
|
|
314
|
-
const content = await
|
|
231
|
+
const content = await fileService.read(fullPath);
|
|
315
232
|
const previewLines = content.split('\n').slice(0, lines).join('\n');
|
|
316
233
|
|
|
317
234
|
res.json({ success: true, content: previewLines });
|
|
@@ -337,6 +254,8 @@ export function createFileRoutes(ctx) {
|
|
|
337
254
|
}
|
|
338
255
|
|
|
339
256
|
const fullPath = resolvePath(ctx.projectDir, filePath);
|
|
257
|
+
// FileService doesn't have getInfo, use fs directly for this simple operation
|
|
258
|
+
const fs = await import('fs/promises');
|
|
340
259
|
const stat = await fs.stat(fullPath);
|
|
341
260
|
|
|
342
261
|
res.json({
|
|
@@ -361,53 +280,13 @@ export function createFileRoutes(ctx) {
|
|
|
361
280
|
/**
|
|
362
281
|
* Resolve path - allows full filesystem access
|
|
363
282
|
*
|
|
364
|
-
* Security model: The server runs on the user's machine with their permissions.
|
|
365
|
-
* Access control is handled via auth token, not path restrictions.
|
|
366
|
-
*
|
|
367
283
|
* @param {string} basePath - Base path for relative paths (ignored for absolute)
|
|
368
284
|
* @param {string} inputPath - Path to resolve (absolute or relative)
|
|
369
285
|
* @returns {string} Resolved absolute path
|
|
370
286
|
*/
|
|
371
287
|
function resolvePath(basePath, inputPath) {
|
|
372
|
-
// If it's already absolute, use it directly
|
|
373
288
|
if (path.isAbsolute(inputPath)) {
|
|
374
289
|
return inputPath;
|
|
375
290
|
}
|
|
376
|
-
|
|
377
|
-
// Otherwise resolve relative to basePath
|
|
378
291
|
return path.resolve(basePath, inputPath);
|
|
379
292
|
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Scan directory for files
|
|
383
|
-
*/
|
|
384
|
-
async function scanDirectory(root, extensions, maxDepth, includeHidden, currentDepth = 0) {
|
|
385
|
-
if (currentDepth > maxDepth) return [];
|
|
386
|
-
|
|
387
|
-
const files = [];
|
|
388
|
-
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
389
|
-
|
|
390
|
-
for (const entry of entries) {
|
|
391
|
-
// Skip hidden files unless requested
|
|
392
|
-
if (!includeHidden && entry.name.startsWith('.')) continue;
|
|
393
|
-
|
|
394
|
-
// Skip common non-content directories
|
|
395
|
-
if (entry.name === 'node_modules') continue;
|
|
396
|
-
if (entry.name === '__pycache__') continue;
|
|
397
|
-
if (entry.name === '.git') continue;
|
|
398
|
-
|
|
399
|
-
const fullPath = path.join(root, entry.name);
|
|
400
|
-
|
|
401
|
-
if (entry.isDirectory()) {
|
|
402
|
-
const subFiles = await scanDirectory(fullPath, extensions, maxDepth, includeHidden, currentDepth + 1);
|
|
403
|
-
files.push(...subFiles);
|
|
404
|
-
} else {
|
|
405
|
-
const ext = path.extname(entry.name);
|
|
406
|
-
if (extensions.includes(ext)) {
|
|
407
|
-
files.push(fullPath);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return files;
|
|
413
|
-
}
|
package/src/api/project.js
CHANGED
|
@@ -145,6 +145,11 @@ export function createProjectRoutes(ctx) {
|
|
|
145
145
|
* POST /api/project
|
|
146
146
|
* Create a new mrmd project
|
|
147
147
|
* Mirrors: electronAPI.project.create(targetPath)
|
|
148
|
+
*
|
|
149
|
+
* Uses ProjectService.createProject() which:
|
|
150
|
+
* 1. Creates scaffold files (mrmd.md, index files)
|
|
151
|
+
* 2. Creates venv
|
|
152
|
+
* 3. Installs mrmd-python
|
|
148
153
|
*/
|
|
149
154
|
router.post('/', async (req, res) => {
|
|
150
155
|
try {
|
|
@@ -155,23 +160,8 @@ export function createProjectRoutes(ctx) {
|
|
|
155
160
|
|
|
156
161
|
const resolvedPath = path.resolve(ctx.projectDir, targetPath);
|
|
157
162
|
|
|
158
|
-
//
|
|
159
|
-
await
|
|
160
|
-
|
|
161
|
-
// Create mrmd.md config file
|
|
162
|
-
const mrmdPath = path.join(resolvedPath, 'mrmd.md');
|
|
163
|
-
const mrmdContent = `# ${path.basename(resolvedPath)}
|
|
164
|
-
|
|
165
|
-
A new mrmd project.
|
|
166
|
-
|
|
167
|
-
---
|
|
168
|
-
venv: .venv
|
|
169
|
-
---
|
|
170
|
-
`;
|
|
171
|
-
await fs.writeFile(mrmdPath, mrmdContent);
|
|
172
|
-
|
|
173
|
-
// Get and return project info
|
|
174
|
-
const projectInfo = await getProjectInfo(mrmdPath, ctx.projectDir);
|
|
163
|
+
// Use ProjectService to create full project (same as Electron)
|
|
164
|
+
const projectInfo = await projectService.createProject(resolvedPath);
|
|
175
165
|
res.json(projectInfo);
|
|
176
166
|
} catch (err) {
|
|
177
167
|
console.error('[project:create]', err);
|