blockmine 1.5.7 → 1.6.0
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/README.md +2 -3
- package/backend/prisma/migrations/20250709150611_add_run_on_startup_to_tasks/migration.sql +21 -0
- package/backend/prisma/migrations/20250709151124_make_cron_pattern_optional/migration.sql +21 -0
- package/backend/prisma/schema.prisma +2 -1
- package/backend/src/api/routes/bots.js +1088 -1081
- package/backend/src/api/routes/pluginIde.js +133 -8
- package/backend/src/api/routes/tasks.js +19 -2
- package/backend/src/core/BotManager.js +789 -715
- package/backend/src/core/BotProcess.js +41 -2
- package/backend/src/core/EventGraphManager.js +3 -0
- package/backend/src/core/GraphExecutionEngine.js +796 -705
- package/backend/src/core/NodeRegistry.js +1059 -888
- package/backend/src/core/PluginManager.js +12 -5
- package/backend/src/core/TaskScheduler.js +35 -18
- package/backend/src/core/UserService.js +1 -1
- package/backend/src/core/services.js +1 -2
- package/backend/src/lib/prisma.js +5 -0
- package/backend/src/real-time/socketHandler.js +9 -11
- package/backend/src/server.js +26 -23
- package/frontend/dist/assets/index-4mfeN9LH.css +1 -0
- package/frontend/dist/assets/index-CVKjf72r.js +8203 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-BRG5IJlS.js +0 -8203
- package/frontend/dist/assets/index-CxAe5KlR.css +0 -1
|
@@ -12,10 +12,8 @@ const router = express.Router({ mergeParams: true });
|
|
|
12
12
|
const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
13
13
|
const PLUGINS_BASE_DIR = path.join(DATA_DIR, 'storage', 'plugins');
|
|
14
14
|
|
|
15
|
-
// All routes in this file require plugin development permission
|
|
16
15
|
router.use(authenticate, authorize('plugin:develop'));
|
|
17
16
|
|
|
18
|
-
// Middleware to resolve plugin path and ensure it's safe
|
|
19
17
|
const resolvePluginPath = async (req, res, next) => {
|
|
20
18
|
try {
|
|
21
19
|
const { botId, pluginName } = req.params;
|
|
@@ -26,7 +24,6 @@ const resolvePluginPath = async (req, res, next) => {
|
|
|
26
24
|
const botPluginsDir = path.join(PLUGINS_BASE_DIR, `bot_${botId}`);
|
|
27
25
|
const pluginPath = path.resolve(botPluginsDir, pluginName);
|
|
28
26
|
|
|
29
|
-
// Security check: ensure the resolved path is still within the bot's plugins directory
|
|
30
27
|
if (!pluginPath.startsWith(botPluginsDir)) {
|
|
31
28
|
return res.status(403).json({ error: 'Доступ запрещен: попытка доступа за пределы директории плагина.' });
|
|
32
29
|
}
|
|
@@ -35,7 +32,6 @@ const resolvePluginPath = async (req, res, next) => {
|
|
|
35
32
|
return res.status(404).json({ error: 'Директория плагина не найдена.' });
|
|
36
33
|
}
|
|
37
34
|
|
|
38
|
-
// Attach the safe path to the request object
|
|
39
35
|
req.pluginPath = pluginPath;
|
|
40
36
|
next();
|
|
41
37
|
} catch (error) {
|
|
@@ -52,7 +48,7 @@ router.post('/create', async (req, res) => {
|
|
|
52
48
|
version = '1.0.0',
|
|
53
49
|
description = '',
|
|
54
50
|
author = '',
|
|
55
|
-
template = 'empty'
|
|
51
|
+
template = 'empty'
|
|
56
52
|
} = req.body;
|
|
57
53
|
|
|
58
54
|
if (!name) {
|
|
@@ -297,6 +293,28 @@ router.post('/:pluginName/file', resolvePluginPath, async (req, res) => {
|
|
|
297
293
|
}
|
|
298
294
|
|
|
299
295
|
await fse.writeFile(safePath, content, 'utf-8');
|
|
296
|
+
|
|
297
|
+
if (relativePath === 'package.json' || relativePath.endsWith('/package.json')) {
|
|
298
|
+
try {
|
|
299
|
+
const packageJson = JSON.parse(content);
|
|
300
|
+
await prisma.installedPlugin.updateMany({
|
|
301
|
+
where: {
|
|
302
|
+
botId: parseInt(req.params.botId),
|
|
303
|
+
path: req.pluginPath,
|
|
304
|
+
},
|
|
305
|
+
data: {
|
|
306
|
+
name: packageJson.name || req.params.pluginName,
|
|
307
|
+
version: packageJson.version || '1.0.0',
|
|
308
|
+
description: packageJson.description || '',
|
|
309
|
+
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
console.log(`[Plugin IDE] Manifest обновлен для плагина ${req.params.pluginName} после сохранения package.json`);
|
|
313
|
+
} catch (manifestError) {
|
|
314
|
+
console.error(`[Plugin IDE] Ошибка обновления manifest для ${req.params.pluginName}:`, manifestError);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
300
318
|
res.status(200).json({ message: 'Файл успешно сохранен.' });
|
|
301
319
|
|
|
302
320
|
} catch (error) {
|
|
@@ -375,7 +393,7 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
375
393
|
}
|
|
376
394
|
|
|
377
395
|
const currentManifest = await fse.readJson(manifestPath);
|
|
378
|
-
const { name, version, description, author } = req.body;
|
|
396
|
+
const { name, version, description, author, repositoryUrl } = req.body;
|
|
379
397
|
|
|
380
398
|
const newManifest = {
|
|
381
399
|
...currentManifest,
|
|
@@ -384,10 +402,16 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
384
402
|
description: description,
|
|
385
403
|
author: author,
|
|
386
404
|
};
|
|
405
|
+
|
|
406
|
+
if (repositoryUrl) {
|
|
407
|
+
newManifest.repository = {
|
|
408
|
+
type: 'git',
|
|
409
|
+
url: repositoryUrl
|
|
410
|
+
};
|
|
411
|
+
}
|
|
387
412
|
|
|
388
413
|
await fse.writeJson(manifestPath, newManifest, { spaces: 2 });
|
|
389
414
|
|
|
390
|
-
// Also update the DB record
|
|
391
415
|
await prisma.installedPlugin.updateMany({
|
|
392
416
|
where: {
|
|
393
417
|
botId: parseInt(req.params.botId),
|
|
@@ -397,6 +421,7 @@ router.post('/:pluginName/manifest', resolvePluginPath, async (req, res) => {
|
|
|
397
421
|
name: newManifest.name,
|
|
398
422
|
version: newManifest.version,
|
|
399
423
|
description: newManifest.description,
|
|
424
|
+
manifest: JSON.stringify(newManifest.botpanel || {}),
|
|
400
425
|
}
|
|
401
426
|
});
|
|
402
427
|
|
|
@@ -436,6 +461,12 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
436
461
|
|
|
437
462
|
packageJson.name = newName;
|
|
438
463
|
packageJson.description = `(Forked from ${pluginName}) ${packageJson.description || ''}`;
|
|
464
|
+
|
|
465
|
+
packageJson.repository = {
|
|
466
|
+
type: 'git',
|
|
467
|
+
url: currentPlugin.sourceUri
|
|
468
|
+
};
|
|
469
|
+
|
|
439
470
|
await fse.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
|
440
471
|
|
|
441
472
|
const forkedPlugin = await prisma.installedPlugin.create({
|
|
@@ -448,7 +479,7 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
448
479
|
sourceType: 'LOCAL_IDE',
|
|
449
480
|
sourceUri: newPath,
|
|
450
481
|
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
451
|
-
isEnabled: false
|
|
482
|
+
isEnabled: false
|
|
452
483
|
}
|
|
453
484
|
});
|
|
454
485
|
|
|
@@ -460,4 +491,98 @@ router.post('/:pluginName/fork', resolvePluginPath, async (req, res) => {
|
|
|
460
491
|
}
|
|
461
492
|
});
|
|
462
493
|
|
|
494
|
+
router.post('/:pluginName/create-pr', resolvePluginPath, async (req, res) => {
|
|
495
|
+
const cp = require('child_process');
|
|
496
|
+
const { branch = 'main', commitMessage = 'Changes from local edit', repositoryUrl } = req.body;
|
|
497
|
+
|
|
498
|
+
if (!branch) {
|
|
499
|
+
return res.status(400).json({ error: 'Название ветки обязательно.' });
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
cp.execSync('git --version');
|
|
504
|
+
} catch (e) {
|
|
505
|
+
return res.status(400).json({ error: 'Git не установлен на этой системе. Пожалуйста, установите Git для создания PR.' });
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
const manifestPath = path.join(req.pluginPath, 'package.json');
|
|
510
|
+
const packageJson = await fse.readJson(manifestPath);
|
|
511
|
+
let originalRepo = packageJson.repository?.url;
|
|
512
|
+
|
|
513
|
+
if (repositoryUrl) {
|
|
514
|
+
originalRepo = repositoryUrl;
|
|
515
|
+
|
|
516
|
+
packageJson.repository = {
|
|
517
|
+
type: 'git',
|
|
518
|
+
url: repositoryUrl
|
|
519
|
+
};
|
|
520
|
+
await fse.writeJson(manifestPath, packageJson, { spaces: 2 });
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (!originalRepo) {
|
|
524
|
+
return res.status(400).json({ error: 'URL репозитория не указан.' });
|
|
525
|
+
}
|
|
526
|
+
const cleanRepoUrl = originalRepo.replace(/^git\+/, '');
|
|
527
|
+
|
|
528
|
+
const parseRepo = (url) => {
|
|
529
|
+
const match = url.match(/(?:git\+)?https?:\/\/github\.com\/([^\/]+)\/([^\/]+)(?:\.git)?/);
|
|
530
|
+
return match ? { owner: match[1], repo: match[2].replace(/\.git$/, '') } : null;
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const repoInfo = parseRepo(cleanRepoUrl);
|
|
534
|
+
if (!repoInfo) {
|
|
535
|
+
return res.status(400).json({ error: 'Неверный формат URL репозитория.' });
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const cwd = req.pluginPath;
|
|
539
|
+
const tempDir = path.join(cwd, '..', `temp-${Date.now()}`);
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
cp.execSync(`git clone ${cleanRepoUrl} "${tempDir}"`, { stdio: 'inherit' });
|
|
543
|
+
|
|
544
|
+
process.chdir(tempDir);
|
|
545
|
+
|
|
546
|
+
cp.execSync(`git checkout -b ${branch}`);
|
|
547
|
+
|
|
548
|
+
const files = await fse.readdir(req.pluginPath);
|
|
549
|
+
for (const file of files) {
|
|
550
|
+
if (file !== '.git') {
|
|
551
|
+
const sourcePath = path.join(req.pluginPath, file);
|
|
552
|
+
const destPath = path.join(tempDir, file);
|
|
553
|
+
await fse.copy(sourcePath, destPath, { overwrite: true });
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
cp.execSync('git add .');
|
|
558
|
+
try {
|
|
559
|
+
cp.execSync(`git commit -m "${commitMessage}"`);
|
|
560
|
+
} catch (e) {
|
|
561
|
+
if (e.message.includes('nothing to commit')) {
|
|
562
|
+
return res.status(400).json({ error: 'Нет изменений для коммита.' });
|
|
563
|
+
}
|
|
564
|
+
throw e;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
cp.execSync(`git push -u origin ${branch}`);
|
|
568
|
+
|
|
569
|
+
const prUrl = `https://github.com/${repoInfo.owner}/${repoInfo.repo}/pull/new/${branch}`;
|
|
570
|
+
|
|
571
|
+
res.json({ success: true, prUrl });
|
|
572
|
+
|
|
573
|
+
} finally {
|
|
574
|
+
try {
|
|
575
|
+
process.chdir(req.pluginPath);
|
|
576
|
+
await fse.remove(tempDir);
|
|
577
|
+
} catch (cleanupError) {
|
|
578
|
+
console.error('Cleanup error:', cleanupError);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error('[Plugin IDE Error] /create-pr:', error);
|
|
584
|
+
res.status(500).json({ error: 'Не удалось создать PR: ' + error.message });
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
463
588
|
module.exports = router;
|
|
@@ -31,9 +31,19 @@ router.get('/', authorize('task:list'), async (req, res) => {
|
|
|
31
31
|
|
|
32
32
|
router.post('/', authorize('task:create'), async (req, res) => {
|
|
33
33
|
try {
|
|
34
|
-
const
|
|
34
|
+
const { runOnStartup, cronPattern, ...restOfBody } = req.body;
|
|
35
|
+
const taskData = { ...restOfBody, runOnStartup: !!runOnStartup };
|
|
36
|
+
|
|
37
|
+
if (runOnStartup) {
|
|
38
|
+
taskData.cronPattern = null;
|
|
39
|
+
} else {
|
|
40
|
+
taskData.cronPattern = normalizeCronPattern(cronPattern);
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
const newTask = await prisma.scheduledTask.create({ data: taskData });
|
|
36
|
-
|
|
44
|
+
if (!runOnStartup) {
|
|
45
|
+
TaskScheduler.scheduleTask(newTask);
|
|
46
|
+
}
|
|
37
47
|
res.status(201).json(newTask);
|
|
38
48
|
} catch (error) {
|
|
39
49
|
console.error("[API /tasks] Ошибка создания задачи:", error);
|
|
@@ -46,6 +56,13 @@ router.put('/:id', authorize('task:edit'), async (req, res) => {
|
|
|
46
56
|
try {
|
|
47
57
|
const { id, createdAt, updatedAt, lastRun, ...dataToUpdate } = req.body;
|
|
48
58
|
|
|
59
|
+
if (typeof dataToUpdate.runOnStartup !== 'undefined') {
|
|
60
|
+
dataToUpdate.runOnStartup = !!dataToUpdate.runOnStartup;
|
|
61
|
+
if (dataToUpdate.runOnStartup) {
|
|
62
|
+
dataToUpdate.cronPattern = null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
49
66
|
if (dataToUpdate.cronPattern) {
|
|
50
67
|
dataToUpdate.cronPattern = normalizeCronPattern(dataToUpdate.cronPattern);
|
|
51
68
|
}
|