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.
@@ -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' // 'empty' or 'command'
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 // Forked plugins are disabled by default
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 taskData = { ...req.body, cronPattern: normalizeCronPattern(req.body.cronPattern) };
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
- TaskScheduler.scheduleTask(newTask);
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
  }