mrmd-server 0.1.0 → 0.1.2
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/LICENSE +21 -0
- package/bin/cli.js +4 -20
- package/package.json +20 -3
- package/src/api/bash.js +72 -189
- package/src/api/file.js +26 -20
- package/src/api/index.js +5 -0
- package/src/api/notebook.js +290 -0
- package/src/api/project.js +178 -12
- package/src/api/pty.js +73 -293
- package/src/api/r.js +337 -0
- package/src/api/session.js +96 -251
- package/src/api/settings.js +782 -0
- package/src/api/system.js +199 -1
- package/src/server.js +133 -8
- package/src/services.js +42 -0
- package/src/sync-manager.js +223 -0
- package/static/favicon.png +0 -0
- package/static/http-shim.js +172 -3
- package/static/index.html +1 -0
package/src/api/system.js
CHANGED
|
@@ -8,7 +8,8 @@ import { Router } from 'express';
|
|
|
8
8
|
import os from 'os';
|
|
9
9
|
import path from 'path';
|
|
10
10
|
import fs from 'fs/promises';
|
|
11
|
-
import {
|
|
11
|
+
import { existsSync } from 'fs';
|
|
12
|
+
import { spawn, execSync } from 'child_process';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Create system routes
|
|
@@ -26,6 +27,105 @@ export function createSystemRoutes(ctx) {
|
|
|
26
27
|
res.json({ homeDir: os.homedir() });
|
|
27
28
|
});
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* GET /api/system/info
|
|
32
|
+
* Get system and app info including uv status
|
|
33
|
+
* Mirrors: electronAPI.system.info()
|
|
34
|
+
*/
|
|
35
|
+
router.get('/info', async (req, res) => {
|
|
36
|
+
try {
|
|
37
|
+
// Check uv availability
|
|
38
|
+
let uvInfo = { installed: false };
|
|
39
|
+
try {
|
|
40
|
+
const uvVersion = execSync('uv --version', { encoding: 'utf-8' }).trim();
|
|
41
|
+
const uvPath = execSync('which uv', { encoding: 'utf-8' }).trim();
|
|
42
|
+
uvInfo = {
|
|
43
|
+
installed: true,
|
|
44
|
+
version: uvVersion.replace('uv ', ''),
|
|
45
|
+
path: uvPath,
|
|
46
|
+
};
|
|
47
|
+
} catch {}
|
|
48
|
+
|
|
49
|
+
// Get Node.js version
|
|
50
|
+
const nodeVersion = process.version;
|
|
51
|
+
|
|
52
|
+
res.json({
|
|
53
|
+
appVersion: '0.1.0',
|
|
54
|
+
platform: os.platform(),
|
|
55
|
+
arch: os.arch(),
|
|
56
|
+
nodeVersion,
|
|
57
|
+
pythonDeps: ['ipython', 'starlette', 'uvicorn', 'sse-starlette'],
|
|
58
|
+
uv: uvInfo,
|
|
59
|
+
serverMode: true, // Indicates this is running in server mode, not Electron
|
|
60
|
+
});
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('[system:info]', err);
|
|
63
|
+
res.status(500).json({ error: err.message });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* POST /api/system/ensure-uv
|
|
69
|
+
* Ensure uv is installed (auto-install if missing)
|
|
70
|
+
* Mirrors: electronAPI.system.ensureUv()
|
|
71
|
+
*/
|
|
72
|
+
router.post('/ensure-uv', async (req, res) => {
|
|
73
|
+
try {
|
|
74
|
+
// Check if uv is already installed
|
|
75
|
+
try {
|
|
76
|
+
const uvVersion = execSync('uv --version', { encoding: 'utf-8' }).trim();
|
|
77
|
+
const uvPath = execSync('which uv', { encoding: 'utf-8' }).trim();
|
|
78
|
+
return res.json({
|
|
79
|
+
success: true,
|
|
80
|
+
path: uvPath,
|
|
81
|
+
version: uvVersion.replace('uv ', ''),
|
|
82
|
+
alreadyInstalled: true,
|
|
83
|
+
});
|
|
84
|
+
} catch {}
|
|
85
|
+
|
|
86
|
+
// Try to install uv using the official installer
|
|
87
|
+
const installScript = 'curl -LsSf https://astral.sh/uv/install.sh | sh';
|
|
88
|
+
|
|
89
|
+
const proc = spawn('sh', ['-c', installScript], {
|
|
90
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
let stdout = '';
|
|
94
|
+
let stderr = '';
|
|
95
|
+
|
|
96
|
+
proc.stdout.on('data', (data) => { stdout += data; });
|
|
97
|
+
proc.stderr.on('data', (data) => { stderr += data; });
|
|
98
|
+
|
|
99
|
+
await new Promise((resolve, reject) => {
|
|
100
|
+
proc.on('close', (code) => {
|
|
101
|
+
if (code === 0) resolve();
|
|
102
|
+
else reject(new Error(`Install failed with code ${code}: ${stderr}`));
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Verify installation
|
|
107
|
+
const uvPath = path.join(os.homedir(), '.local', 'bin', 'uv');
|
|
108
|
+
if (existsSync(uvPath)) {
|
|
109
|
+
try {
|
|
110
|
+
const uvVersion = execSync(`${uvPath} --version`, { encoding: 'utf-8' }).trim();
|
|
111
|
+
return res.json({
|
|
112
|
+
success: true,
|
|
113
|
+
path: uvPath,
|
|
114
|
+
version: uvVersion.replace('uv ', ''),
|
|
115
|
+
});
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
res.json({
|
|
120
|
+
success: false,
|
|
121
|
+
error: 'Installation completed but uv not found',
|
|
122
|
+
});
|
|
123
|
+
} catch (err) {
|
|
124
|
+
console.error('[system:ensureUv]', err);
|
|
125
|
+
res.status(500).json({ success: false, error: err.message });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
29
129
|
/**
|
|
30
130
|
* GET /api/system/recent
|
|
31
131
|
* Get recent files and venvs
|
|
@@ -117,6 +217,104 @@ export function createSystemRoutes(ctx) {
|
|
|
117
217
|
}
|
|
118
218
|
});
|
|
119
219
|
|
|
220
|
+
/**
|
|
221
|
+
* POST /api/system/create-venv
|
|
222
|
+
* Create a new Python virtual environment
|
|
223
|
+
* Mirrors: electronAPI.createVenv(venvPath)
|
|
224
|
+
*/
|
|
225
|
+
router.post('/create-venv', async (req, res) => {
|
|
226
|
+
try {
|
|
227
|
+
const { venvPath } = req.body;
|
|
228
|
+
if (!venvPath) {
|
|
229
|
+
return res.status(400).json({ error: 'venvPath required' });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const resolvedPath = path.resolve(venvPath);
|
|
233
|
+
|
|
234
|
+
// Check if venv already exists
|
|
235
|
+
if (existsSync(path.join(resolvedPath, 'bin', 'activate'))) {
|
|
236
|
+
return res.json({
|
|
237
|
+
success: true,
|
|
238
|
+
path: resolvedPath,
|
|
239
|
+
message: 'Virtual environment already exists',
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Create parent directory if needed
|
|
244
|
+
await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
|
|
245
|
+
|
|
246
|
+
// Try uv first (faster)
|
|
247
|
+
let uvPath = null;
|
|
248
|
+
try {
|
|
249
|
+
uvPath = execSync('which uv', { encoding: 'utf-8' }).trim();
|
|
250
|
+
} catch {
|
|
251
|
+
// Check common locations
|
|
252
|
+
const uvLocations = [
|
|
253
|
+
path.join(os.homedir(), '.local', 'bin', 'uv'),
|
|
254
|
+
'/usr/local/bin/uv',
|
|
255
|
+
'/usr/bin/uv',
|
|
256
|
+
];
|
|
257
|
+
for (const loc of uvLocations) {
|
|
258
|
+
if (existsSync(loc)) {
|
|
259
|
+
uvPath = loc;
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (uvPath) {
|
|
266
|
+
// Use uv to create venv
|
|
267
|
+
const proc = spawn(uvPath, ['venv', resolvedPath], {
|
|
268
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
let stderr = '';
|
|
272
|
+
proc.stderr.on('data', (data) => { stderr += data; });
|
|
273
|
+
|
|
274
|
+
await new Promise((resolve, reject) => {
|
|
275
|
+
proc.on('close', (code) => {
|
|
276
|
+
if (code === 0) resolve();
|
|
277
|
+
else reject(new Error(`uv venv failed: ${stderr}`));
|
|
278
|
+
});
|
|
279
|
+
proc.on('error', reject);
|
|
280
|
+
});
|
|
281
|
+
} else {
|
|
282
|
+
// Fallback to python3 -m venv
|
|
283
|
+
const proc = spawn('python3', ['-m', 'venv', resolvedPath], {
|
|
284
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
let stderr = '';
|
|
288
|
+
proc.stderr.on('data', (data) => { stderr += data; });
|
|
289
|
+
|
|
290
|
+
await new Promise((resolve, reject) => {
|
|
291
|
+
proc.on('close', (code) => {
|
|
292
|
+
if (code === 0) resolve();
|
|
293
|
+
else reject(new Error(`python3 -m venv failed (code ${code}): ${stderr}`));
|
|
294
|
+
});
|
|
295
|
+
proc.on('error', reject);
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Verify creation
|
|
300
|
+
if (existsSync(path.join(resolvedPath, 'bin', 'activate'))) {
|
|
301
|
+
res.json({
|
|
302
|
+
success: true,
|
|
303
|
+
path: resolvedPath,
|
|
304
|
+
method: uvPath ? 'uv' : 'python3',
|
|
305
|
+
});
|
|
306
|
+
} else {
|
|
307
|
+
res.status(500).json({
|
|
308
|
+
success: false,
|
|
309
|
+
error: 'Virtual environment creation completed but activation script not found',
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error('[system:create-venv]', err);
|
|
314
|
+
res.status(500).json({ success: false, error: err.message });
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
120
318
|
/**
|
|
121
319
|
* POST /api/system/install-mrmd-python
|
|
122
320
|
* Install mrmd-python in a venv
|
package/src/server.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Express server that mirrors Electron's electronAPI
|
|
3
|
+
*
|
|
4
|
+
* Uses services from mrmd-electron for full feature parity.
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import express from 'express';
|
|
@@ -9,7 +11,10 @@ import { WebSocketServer } from 'ws';
|
|
|
9
11
|
import path from 'path';
|
|
10
12
|
import fs from 'fs/promises';
|
|
11
13
|
import { existsSync } from 'fs';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
14
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
15
|
+
import { createRequire } from 'module';
|
|
16
|
+
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
13
18
|
|
|
14
19
|
import { createAuthMiddleware, generateToken } from './auth.js';
|
|
15
20
|
import { EventBus } from './events.js';
|
|
@@ -23,8 +28,33 @@ import { createRuntimeRoutes } from './api/runtime.js';
|
|
|
23
28
|
import { createJuliaRoutes } from './api/julia.js';
|
|
24
29
|
import { createPtyRoutes } from './api/pty.js';
|
|
25
30
|
import { createNotebookRoutes } from './api/notebook.js';
|
|
31
|
+
import { createSettingsRoutes } from './api/settings.js';
|
|
32
|
+
import { createRRoutes } from './api/r.js';
|
|
26
33
|
import { setupWebSocket } from './websocket.js';
|
|
27
34
|
|
|
35
|
+
// Import services from mrmd-electron (pure Node.js, no Electron deps)
|
|
36
|
+
import {
|
|
37
|
+
ProjectService,
|
|
38
|
+
SessionService,
|
|
39
|
+
BashSessionService,
|
|
40
|
+
RSessionService,
|
|
41
|
+
JuliaSessionService,
|
|
42
|
+
PtySessionService,
|
|
43
|
+
FileService,
|
|
44
|
+
AssetService,
|
|
45
|
+
SettingsService,
|
|
46
|
+
} from './services.js';
|
|
47
|
+
|
|
48
|
+
// Import sync manager for dynamic project handling
|
|
49
|
+
import {
|
|
50
|
+
acquireSyncServer,
|
|
51
|
+
releaseSyncServer,
|
|
52
|
+
getSyncServer,
|
|
53
|
+
listSyncServers,
|
|
54
|
+
stopAllSyncServers,
|
|
55
|
+
onSyncDeath,
|
|
56
|
+
} from './sync-manager.js';
|
|
57
|
+
|
|
28
58
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
29
59
|
|
|
30
60
|
/**
|
|
@@ -59,28 +89,65 @@ export async function createServer(config) {
|
|
|
59
89
|
aiPort = 51790,
|
|
60
90
|
} = config;
|
|
61
91
|
|
|
92
|
+
// projectDir is optional now - dynamic project detection is supported
|
|
62
93
|
if (!projectDir) {
|
|
63
|
-
|
|
94
|
+
console.log('[server] No projectDir specified - dynamic project detection enabled');
|
|
64
95
|
}
|
|
65
96
|
|
|
66
97
|
const app = express();
|
|
67
98
|
const server = createHttpServer(app);
|
|
68
99
|
const eventBus = new EventBus();
|
|
69
100
|
|
|
101
|
+
// Instantiate services from mrmd-electron
|
|
102
|
+
const projectService = new ProjectService();
|
|
103
|
+
const sessionService = new SessionService();
|
|
104
|
+
const bashSessionService = new BashSessionService();
|
|
105
|
+
const rSessionService = new RSessionService();
|
|
106
|
+
const juliaSessionService = new JuliaSessionService();
|
|
107
|
+
const ptySessionService = new PtySessionService();
|
|
108
|
+
const fileService = new FileService();
|
|
109
|
+
const assetService = new AssetService();
|
|
110
|
+
const settingsService = new SettingsService();
|
|
111
|
+
|
|
70
112
|
// Service context passed to all route handlers
|
|
71
113
|
const context = {
|
|
72
|
-
|
|
114
|
+
// Legacy: fixed project dir (for backwards compat, may be null)
|
|
115
|
+
projectDir: projectDir ? path.resolve(projectDir) : null,
|
|
73
116
|
syncPort,
|
|
74
117
|
pythonPort,
|
|
75
118
|
aiPort,
|
|
76
119
|
eventBus,
|
|
77
|
-
|
|
120
|
+
|
|
121
|
+
// Services from mrmd-electron
|
|
122
|
+
projectService,
|
|
123
|
+
sessionService,
|
|
124
|
+
bashSessionService,
|
|
125
|
+
rSessionService,
|
|
126
|
+
juliaSessionService,
|
|
127
|
+
ptySessionService,
|
|
128
|
+
fileService,
|
|
129
|
+
assetService,
|
|
130
|
+
settingsService,
|
|
131
|
+
|
|
132
|
+
// Sync server management (dynamic per-project)
|
|
133
|
+
acquireSyncServer,
|
|
134
|
+
releaseSyncServer,
|
|
135
|
+
getSyncServer,
|
|
136
|
+
listSyncServers,
|
|
137
|
+
|
|
138
|
+
// Legacy: process tracking (kept for backwards compat)
|
|
78
139
|
syncProcess: null,
|
|
79
140
|
pythonProcess: null,
|
|
80
141
|
monitorProcesses: new Map(),
|
|
81
142
|
watchers: new Map(),
|
|
143
|
+
pythonReady: false,
|
|
82
144
|
};
|
|
83
145
|
|
|
146
|
+
// Register for sync death notifications and broadcast via WebSocket
|
|
147
|
+
onSyncDeath((message) => {
|
|
148
|
+
eventBus.emit('sync-server-died', message);
|
|
149
|
+
});
|
|
150
|
+
|
|
84
151
|
// Middleware
|
|
85
152
|
app.use(cors({
|
|
86
153
|
origin: true,
|
|
@@ -118,6 +185,8 @@ export async function createServer(config) {
|
|
|
118
185
|
app.use('/api/julia', createJuliaRoutes(context));
|
|
119
186
|
app.use('/api/pty', createPtyRoutes(context));
|
|
120
187
|
app.use('/api/notebook', createNotebookRoutes(context));
|
|
188
|
+
app.use('/api/settings', createSettingsRoutes(context));
|
|
189
|
+
app.use('/api/r', createRRoutes(context));
|
|
121
190
|
|
|
122
191
|
// Serve http-shim.js
|
|
123
192
|
app.get('/http-shim.js', (req, res) => {
|
|
@@ -131,9 +200,13 @@ export async function createServer(config) {
|
|
|
131
200
|
// Serve mrmd-electron assets (fonts, icons)
|
|
132
201
|
app.use('/assets', express.static(path.join(electronPath, 'assets')));
|
|
133
202
|
|
|
134
|
-
// Serve mrmd-editor dist
|
|
203
|
+
// Serve mrmd-editor dist (referenced as ../mrmd-editor/dist/ in index.html)
|
|
135
204
|
const editorDistPath = path.join(electronPath, '../mrmd-editor/dist');
|
|
136
|
-
app.use('/dist', express.static(editorDistPath));
|
|
205
|
+
app.use('/mrmd-editor/dist', express.static(editorDistPath));
|
|
206
|
+
app.use('/dist', express.static(editorDistPath)); // Also at /dist for compatibility
|
|
207
|
+
|
|
208
|
+
// Serve node_modules from mrmd-electron (for xterm, etc.)
|
|
209
|
+
app.use('/node_modules', express.static(path.join(electronPath, 'node_modules')));
|
|
137
210
|
|
|
138
211
|
// Serve transformed index.html at root
|
|
139
212
|
app.get('/', async (req, res) => {
|
|
@@ -144,6 +217,7 @@ export async function createServer(config) {
|
|
|
144
217
|
// Transform for browser mode:
|
|
145
218
|
// 1. Inject http-shim.js as first script in head
|
|
146
219
|
// 2. Update CSP to allow HTTP connections to this server
|
|
220
|
+
// 3. Fix relative paths for HTTP serving
|
|
147
221
|
html = transformIndexHtml(html, host, port);
|
|
148
222
|
|
|
149
223
|
res.type('html').send(html);
|
|
@@ -211,7 +285,33 @@ export async function createServer(config) {
|
|
|
211
285
|
await watcher.close();
|
|
212
286
|
}
|
|
213
287
|
|
|
214
|
-
//
|
|
288
|
+
// Stop all sync servers
|
|
289
|
+
stopAllSyncServers();
|
|
290
|
+
|
|
291
|
+
// Stop all sessions via services (if they have shutdown methods)
|
|
292
|
+
try {
|
|
293
|
+
if (typeof sessionService.shutdown === 'function') {
|
|
294
|
+
await sessionService.shutdown();
|
|
295
|
+
}
|
|
296
|
+
} catch (e) {
|
|
297
|
+
console.warn('[server] Error stopping sessions:', e.message);
|
|
298
|
+
}
|
|
299
|
+
try {
|
|
300
|
+
if (typeof bashSessionService.shutdown === 'function') {
|
|
301
|
+
await bashSessionService.shutdown();
|
|
302
|
+
}
|
|
303
|
+
} catch (e) {
|
|
304
|
+
console.warn('[server] Error stopping bash sessions:', e.message);
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
if (typeof ptySessionService.shutdown === 'function') {
|
|
308
|
+
await ptySessionService.shutdown();
|
|
309
|
+
}
|
|
310
|
+
} catch (e) {
|
|
311
|
+
console.warn('[server] Error stopping pty sessions:', e.message);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Legacy: kill child processes
|
|
215
315
|
if (context.syncProcess) {
|
|
216
316
|
context.syncProcess.kill();
|
|
217
317
|
}
|
|
@@ -238,15 +338,26 @@ export async function createServer(config) {
|
|
|
238
338
|
*/
|
|
239
339
|
function findElectronDir(fromDir) {
|
|
240
340
|
const candidates = [
|
|
341
|
+
// Development: sibling directories
|
|
241
342
|
path.join(fromDir, '../../mrmd-electron'),
|
|
242
343
|
path.join(fromDir, '../../../mrmd-electron'),
|
|
243
344
|
path.join(process.cwd(), '../mrmd-electron'),
|
|
244
345
|
path.join(process.cwd(), 'mrmd-electron'),
|
|
245
|
-
//
|
|
346
|
+
// npm/npx: node_modules relative to mrmd-server package
|
|
347
|
+
path.join(fromDir, '../node_modules/mrmd-electron'),
|
|
246
348
|
path.join(fromDir, '../../node_modules/mrmd-electron'),
|
|
349
|
+
// npm/npx: node_modules in cwd
|
|
247
350
|
path.join(process.cwd(), 'node_modules/mrmd-electron'),
|
|
248
351
|
];
|
|
249
352
|
|
|
353
|
+
// Also try require.resolve to find the package
|
|
354
|
+
try {
|
|
355
|
+
const electronPkg = path.dirname(require.resolve('mrmd-electron/package.json'));
|
|
356
|
+
candidates.unshift(electronPkg);
|
|
357
|
+
} catch (e) {
|
|
358
|
+
// mrmd-electron not found via require, continue with path search
|
|
359
|
+
}
|
|
360
|
+
|
|
250
361
|
for (const candidate of candidates) {
|
|
251
362
|
const indexPath = path.join(candidate, 'index.html');
|
|
252
363
|
if (existsSync(indexPath)) {
|
|
@@ -261,6 +372,7 @@ function findElectronDir(fromDir) {
|
|
|
261
372
|
* Transform index.html for browser mode
|
|
262
373
|
* - Inject http-shim.js as first script
|
|
263
374
|
* - Update CSP to allow HTTP connections
|
|
375
|
+
* - Fix relative paths for HTTP serving
|
|
264
376
|
*/
|
|
265
377
|
function transformIndexHtml(html, host, port) {
|
|
266
378
|
// 1. Inject http-shim.js right after <head>
|
|
@@ -283,6 +395,19 @@ function transformIndexHtml(html, host, port) {
|
|
|
283
395
|
html = html.replace(/-webkit-app-region:\s*drag;/g, '/* -webkit-app-region: drag; */');
|
|
284
396
|
html = html.replace(/-webkit-app-region:\s*no-drag;/g, '/* -webkit-app-region: no-drag; */');
|
|
285
397
|
|
|
398
|
+
// 4. Fix relative paths for HTTP serving
|
|
399
|
+
// ../mrmd-editor/dist/ -> /mrmd-editor/dist/
|
|
400
|
+
html = html.replace(/src=["']\.\.\/mrmd-editor\//g, 'src="/mrmd-editor/');
|
|
401
|
+
html = html.replace(/href=["']\.\.\/mrmd-editor\//g, 'href="/mrmd-editor/');
|
|
402
|
+
|
|
403
|
+
// ./node_modules/ -> /node_modules/
|
|
404
|
+
html = html.replace(/src=["']\.\/node_modules\//g, 'src="/node_modules/');
|
|
405
|
+
html = html.replace(/href=["']\.\/node_modules\//g, 'href="/node_modules/');
|
|
406
|
+
|
|
407
|
+
// ./assets/ -> /assets/
|
|
408
|
+
html = html.replace(/src=["']\.\/assets\//g, 'src="/assets/');
|
|
409
|
+
html = html.replace(/href=["']\.\/assets\//g, 'href="/assets/');
|
|
410
|
+
|
|
286
411
|
return html;
|
|
287
412
|
}
|
|
288
413
|
|
package/src/services.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export services from mrmd-electron
|
|
3
|
+
*
|
|
4
|
+
* These services are pure Node.js (no Electron dependencies)
|
|
5
|
+
* and can be used directly by mrmd-server.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
default as ProjectService,
|
|
10
|
+
} from 'mrmd-electron/src/services/project-service.js';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
default as SessionService,
|
|
14
|
+
} from 'mrmd-electron/src/services/session-service.js';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
default as BashSessionService,
|
|
18
|
+
} from 'mrmd-electron/src/services/bash-session-service.js';
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
default as RSessionService,
|
|
22
|
+
} from 'mrmd-electron/src/services/r-session-service.js';
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
default as JuliaSessionService,
|
|
26
|
+
} from 'mrmd-electron/src/services/julia-session-service.js';
|
|
27
|
+
|
|
28
|
+
export {
|
|
29
|
+
default as PtySessionService,
|
|
30
|
+
} from 'mrmd-electron/src/services/pty-session-service.js';
|
|
31
|
+
|
|
32
|
+
export {
|
|
33
|
+
default as FileService,
|
|
34
|
+
} from 'mrmd-electron/src/services/file-service.js';
|
|
35
|
+
|
|
36
|
+
export {
|
|
37
|
+
default as AssetService,
|
|
38
|
+
} from 'mrmd-electron/src/services/asset-service.js';
|
|
39
|
+
|
|
40
|
+
export {
|
|
41
|
+
default as SettingsService,
|
|
42
|
+
} from 'mrmd-electron/src/services/settings-service.js';
|