blockmine 1.3.2 → 1.3.22

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 CHANGED
@@ -5,24 +5,24 @@
5
5
  <strong>Мощная и удобная панель управления для ваших Minecraft-ботов на базе Mineflayer.</strong>
6
6
  </p>
7
7
  <p>
8
- <img src="https://img.shields.io/github/stars/blockmineJS/blockmine?style=for-the-badge&logo=github" alt="Stars">
9
- <img src="https://img.shields.io/github/last-commit/blockmineJS/blockmine?style=for-the-badge&logo=git" alt="Last Commit">
8
+ <a href="https://github.com/blockmineJS/blockmine/stargazers"><img src="https://img.shields.io/github/stars/blockmineJS/blockmine?style=for-the-badge&logo=github" alt="Stars"></a>
9
+ <a href="https://github.com/blockmineJS/blockmine/commits/main"><img src="https://img.shields.io/github/last-commit/blockmineJS/blockmine?style=for-the-badge&logo=git" alt="Last Commit"></a>
10
10
  <a href="http://185.65.200.184:3000/api/stats" target="_blank">
11
11
  <img src="https://img.shields.io/endpoint?url=https://blockmine-proxy.vercel.app/api/shield&style=for-the-badge&logo=minecraft&logoColor=white" alt="Ботов онлайн">
12
+ </a>
12
13
  </p>
13
14
  </div>
14
15
 
15
- BlockMine — это open-source решение для централизованного управления и автоматизации ботов Minecraft. Запускайте ботов, управляйте ими в реальном времени через удобный веб-интерфейс, расширяйте их возможности с помощью плагинов и настраивайте всё под свои нужды.
16
+ **BlockMine** — это open-source решение для централизованного управления и автоматизации ботов Minecraft. Запускайте ботов, управляйте ими в реальном времени через удобный веб-интерфейс, расширяйте их возможности с помощью плагинов и настраивайте всё под свои нужды.
16
17
 
17
18
  ---
18
19
 
19
-
20
20
  <table align="center">
21
21
  <tr>
22
22
  <td align="center">
23
- <p><strong>Консоль</strong></p>
23
+ <p><strong>Дашборд</strong></p>
24
24
  <img src="./image/1.png" alt="Скриншот дашборда" width="100%">
25
- <em>Консоль бота</em>
25
+ <em>Общая сводка и быстрое управление всеми ботами.</em>
26
26
  </td>
27
27
  <td align="center">
28
28
  <p><strong>Магазин плагинов</strong></p>
@@ -30,9 +30,9 @@ BlockMine — это open-source решение для централизова
30
30
  <em>Расширяйте возможности с помощью удобного браузера плагинов.</em>
31
31
  </td>
32
32
  <td align="center">
33
- <p><strong>Управление</strong></p>
33
+ <p><strong>Управление командами</strong></p>
34
34
  <img src="./image/3.png" alt="Скриншот страницы управления" width="100%">
35
- <em>Настраивайте роли, права и так далее</em>
35
+ <em>Настраивайте права, алиасы и кулдауны для каждой команды.</em>
36
36
  </td>
37
37
  </tr>
38
38
  </table>
@@ -67,7 +67,7 @@ BlockMine — это open-source решение для централизова
67
67
 
68
68
  ## ✨ Быстрый старт с `npx`
69
69
 
70
- Это самый простой способ запустить панель у себя на компьютере или сервере. Убедитесь, что у вас установлен **Node.js v18+**.
70
+ Это самый простой способ запустить панель. Убедитесь, что у вас установлен **Node.js v18+**.
71
71
 
72
72
  1. Откройте терминал (командную строку).
73
73
  2. Выполните одну команду:
@@ -77,8 +77,7 @@ BlockMine — это open-source решение для централизова
77
77
  ```
78
78
  3. Готово! Скрипт автоматически скачает все необходимое, настроит базу данных и запустит сервер.
79
79
 
80
-
81
- > ⚠️ **Важно**: Если выскакивает ошибка - Невозможно загрузить файл C:\Program Files\nodejs\npx.ps1, так как выполнение сценариев отключено в этой системе. - Выполните `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser`
80
+ > ⚠️ **Для пользователей Windows**: Если появляется ошибка `Невозможно загрузить файл ... npx.ps1, так как выполнение сценариев отключено`, откройте PowerShell от имени администратора и выполните `Set-ExecutionPolicy RemoteSigned -Scope CurrentUser`. Нажмите 'Y' для подтверждения.
82
81
 
83
82
  После успешного запуска вы увидите в консоли:
84
83
  ```
@@ -86,20 +85,45 @@ BlockMine — это open-source решение для централизова
86
85
  ```
87
86
  Откройте этот адрес в вашем браузере, чтобы начать работу.
88
87
 
88
+ ---
89
+
90
+ ## 💡 Основные концепции BlockMine
91
+
92
+ Чтобы максимально эффективно использовать панель, важно понимать её ключевые компоненты.
93
+
94
+ ### 🔌 Плагины
95
+ Плагины — это основа функциональности вашего бота. Они добавляют новые команды, автоматизируют действия и интегрируются с другими системами.
96
+
97
+ * **Магазин плагинов**: Встроенный браузер позволяет легко находить и устанавливать плагины в один клик.
98
+ * **Зависимости**: Система автоматически определит и предложит установить плагины, необходимые для работы выбранного вами.
99
+ * **Настройка**: Большинство плагинов можно настроить прямо в веб-интерфейсе, не редактируя файлы конфигурации вручную.
100
+
101
+ ### ⚙️ Команды
102
+ Каждый плагин может регистрировать в системе свои команды. BlockMine предоставляет мощный интерфейс для управления ими.
103
+
104
+ * **Алиасы**: Назначайте командам короткие псевдонимы (например, `@p` для `@ping`).
105
+ * **Кулдауны**: Устанавливайте задержку между использованиями команды для предотвращения спама.
106
+ * **Включение/выключение**: Временно отключайте команды, не удаляя плагин.
107
+ * **Аргументы**: Панель автоматически обрабатывает аргументы команд, делая их использование предсказуемым.
108
+
109
+ ### 🔐 Права и Группы (Permissions)
110
+ Это гибкая система, позволяющая точно контролировать, кто и что может делать с вашим ботом.
111
+
112
+ * **Права (Permissions)**: Каждое действие (например, использование команды `@fly`) защищено правом (`user.fly`). Права обычно создаются плагинами.
113
+ * **Группы (Groups)**: Группы объединяют несколько прав. Например, группа `Member` может иметь право `member.say`, а группа `Admin` — право `admin.*` (доступ ко всем командам у admin.).
114
+ * **Пользователи**: Каждый игрок, взаимодействующий с ботом, становится пользователем в системе. Вы можете добавлять пользователей в разные группы, выдавая им соответствующие права.
115
+
116
+ Эта система позволяет создавать сложные иерархии доступа, например: обычные игроки, участники клана, модераторы и администраторы, каждый со своим набором возможностей.
117
+
89
118
  ## 🧑‍💻 Для разработчиков и контрибьюторов
90
119
 
91
120
  Если вы хотите внести свой вклад в проект или запустить его в режиме разработки.
92
121
 
93
122
  ### 1. Установка
94
123
 
95
- Сначала клонируйте репозиторий и установите все зависимости.
96
-
97
124
  ```bash
98
- # 1. Клонировать репозиторий
99
125
  git clone https://github.com/blockmineJS/blockmine.git
100
126
  cd blockmine
101
-
102
- # 2. Установить все зависимости для всех частей проекта (backend, frontend)
103
127
  npm install
104
128
  ```
105
129
 
package/backend/cli.js CHANGED
@@ -11,10 +11,15 @@ if (!fs.existsSync(DATA_DIR)) {
11
11
  }
12
12
  process.env.DATABASE_URL = `file:${path.join(DATA_DIR, 'blockmine.db')}`;
13
13
 
14
+ if (os.platform() === 'android') {
15
+ process.env.PRISMA_CLI_BINARY_TARGETS = 'linux-arm64-openssl-1.1.x';
16
+ }
17
+
18
+
14
19
  function runCommand(command) {
15
20
  try {
16
21
  console.log(`> ${command}`);
17
- execSync(command, { stdio: 'inherit', cwd: __dirname });
22
+ execSync(command, { stdio: 'inherit', cwd: __dirname, env: process.env });
18
23
  } catch (e) {
19
24
  console.error(`Команда "${command}" не удалась:`, e);
20
25
  process.exit(1);
@@ -1,5 +1,6 @@
1
1
  generator client {
2
2
  provider = "prisma-client-js"
3
+ binaryTargets = ["native", "linux-arm64-openssl-1.1.x"]
3
4
  }
4
5
 
5
6
  datasource db {
@@ -8,6 +8,7 @@ const PluginManager = require('../../core/PluginManager');
8
8
  const UserService = require('../../core/UserService');
9
9
  const commandManager = require('../../core/system/CommandManager');
10
10
 
11
+
11
12
  const multer = require('multer');
12
13
  const archiver = require('archiver');
13
14
  const AdmZip = require('adm-zip');
@@ -536,6 +537,8 @@ router.put('/:botId/commands/:commandId', async (req, res) => {
536
537
  data: dataToUpdate,
537
538
  });
538
539
 
540
+ BotManager.invalidateConfigCache(botId);
541
+
539
542
  res.json(updatedCommand);
540
543
  } catch (error) {
541
544
  console.error(`[API Error] /commands/:commandId PUT:`, error);
@@ -557,6 +560,10 @@ router.post('/:botId/groups', async (req, res) => {
557
560
  permissions: { create: (permissionIds || []).map(id => ({ permissionId: id })) }
558
561
  }
559
562
  });
563
+
564
+ BotManager.invalidateConfigCache(botId);
565
+
566
+
560
567
  res.status(201).json(newGroup);
561
568
  } catch (error) {
562
569
  if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
@@ -590,6 +597,8 @@ router.put('/:botId/groups/:groupId', async (req, res) => {
590
597
  BotManager.invalidateUserCache(botId, user.username);
591
598
  }
592
599
 
600
+ BotManager.invalidateConfigCache(botId);
601
+
593
602
  res.status(200).send();
594
603
  } catch (error) {
595
604
  if (error.code === 'P2002') return res.status(409).json({ error: 'Группа с таким именем уже существует для этого бота.' });
@@ -599,12 +608,16 @@ router.put('/:botId/groups/:groupId', async (req, res) => {
599
608
 
600
609
  router.delete('/:botId/groups/:groupId', async (req, res) => {
601
610
  try {
611
+ const botId = parseInt(req.params.botId, 10);
602
612
  const groupId = parseInt(req.params.groupId);
603
613
  const group = await prisma.group.findUnique({ where: { id: groupId } });
604
614
  if (group && group.owner !== 'admin') {
605
615
  return res.status(403).json({ error: `Нельзя удалить группу с источником "${group.owner}".` });
606
616
  }
607
617
  await prisma.group.delete({ where: { id: groupId } });
618
+ BotManager.invalidateConfigCache(botId);
619
+
620
+
608
621
  res.status(204).send();
609
622
  } catch (error) { res.status(500).json({ error: 'Не удалось удалить группу.' }); }
610
623
  });
@@ -617,6 +630,9 @@ router.post('/:botId/permissions', async (req, res) => {
617
630
  const newPermission = await prisma.permission.create({
618
631
  data: { name, description, botId, owner: 'admin' }
619
632
  });
633
+
634
+ BotManager.invalidateConfigCache(botId);
635
+
620
636
  res.status(201).json(newPermission);
621
637
  } catch (error) {
622
638
  if (error.code === 'P2002') return res.status(409).json({ error: 'Право с таким именем уже существует для этого бота.' });
@@ -1,4 +1,3 @@
1
-
2
1
  const { fork } = require('child_process');
3
2
  const path = require('path');
4
3
  const { getIO } = require('../real-time/socketHandler');
@@ -43,7 +42,8 @@ class BotManager {
43
42
  this.bots = new Map();
44
43
  this.logCache = new Map();
45
44
  this.resourceUsage = new Map();
46
-
45
+ this.botConfigs = new Map();
46
+
47
47
  setInterval(() => this.updateAllResourceUsage(), 5000);
48
48
 
49
49
 
@@ -52,6 +52,42 @@ class BotManager {
52
52
  }
53
53
  }
54
54
 
55
+ async loadConfigForBot(botId) {
56
+ console.log(`[BotManager] Caching configuration for bot ID ${botId}...`);
57
+ try {
58
+ const [commands, permissions] = await Promise.all([
59
+ prisma.command.findMany({ where: { botId } }),
60
+ prisma.permission.findMany({ where: { botId } }),
61
+ ]);
62
+
63
+ const config = {
64
+ commands: new Map(commands.map(cmd => [cmd.name, cmd])),
65
+ permissionsById: new Map(permissions.map(p => [p.id, p])),
66
+ commandAliases: new Map()
67
+ };
68
+
69
+ for (const cmd of commands) {
70
+ const aliases = JSON.parse(cmd.aliases || '[]');
71
+ for (const alias of aliases) {
72
+ config.commandAliases.set(alias, cmd.name);
73
+ }
74
+ }
75
+
76
+ this.botConfigs.set(botId, config);
77
+ console.log(`[BotManager] Configuration for bot ID ${botId} cached successfully.`);
78
+ return config;
79
+ } catch (error) {
80
+ console.error(`[BotManager] Failed to cache configuration for bot ${botId}:`, error);
81
+ throw new Error(`Failed to load/cache bot configuration for botId ${botId}: ${error.message}`);
82
+ }
83
+ }
84
+
85
+ invalidateConfigCache(botId) {
86
+ if (this.botConfigs.has(botId)) {
87
+ this.botConfigs.delete(botId);
88
+ console.log(`[BotManager] Invalidated config cache for bot ID ${botId}. It will be reloaded on next command.`);
89
+ }
90
+ }
55
91
 
56
92
  triggerHeartbeat() {
57
93
  if (this.heartbeatDebounceTimer) {
@@ -260,6 +296,7 @@ class BotManager {
260
296
  }
261
297
 
262
298
  await this._syncSystemPermissions(botConfig.id);
299
+ await this.loadConfigForBot(botConfig.id);
263
300
 
264
301
  this.logCache.set(botConfig.id, []);
265
302
  this.emitStatusUpdate(botConfig.id, 'starting', '');
@@ -319,6 +356,7 @@ class BotManager {
319
356
  const botId = botConfig.id;
320
357
  this.bots.delete(botId);
321
358
  this.resourceUsage.delete(botId);
359
+ this.botConfigs.delete(botId);
322
360
  this.emitStatusUpdate(botId, 'stopped', `Процесс завершился с кодом ${code} (сигнал: ${signal || 'none'}).`);
323
361
  this.updateAllResourceUsage();
324
362
  });
@@ -334,80 +372,74 @@ class BotManager {
334
372
 
335
373
  async handleCommandValidation(botConfig, message) {
336
374
  const { commandName, username, args, typeChat } = message;
337
-
375
+ const botId = botConfig.id;
376
+
338
377
  try {
339
- const user = await RealUserService.getUser(username, botConfig.id, botConfig);
340
-
341
- if (user.isBlacklisted) return;
378
+ let botConfigCache = this.botConfigs.get(botId);
379
+ if (!botConfigCache) {
380
+ botConfigCache = await this.loadConfigForBot(botId);
381
+ }
342
382
 
343
- const dbCommand = await prisma.command.findFirst({
344
- where: {
345
- botId: botConfig.id,
346
- OR: [ { name: commandName }, { aliases: { contains: `"${commandName}"` } } ]
347
- }
348
- });
383
+ const user = await RealUserService.getUser(username, botId, botConfig);
384
+ const child = this.bots.get(botId);
349
385
 
350
- if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) return;
386
+ if (!child) {
387
+ console.warn(`[BotManager] No running bot process found for botId: ${botId} during command validation. Aborting.`);
388
+ return;
389
+ }
390
+
391
+ if (user.isBlacklisted) {
392
+ child.send({ type: 'handle_blacklist', commandName, username, typeChat });
393
+ return;
394
+ }
395
+
396
+ const mainCommandName = botConfigCache.commandAliases.get(commandName) || commandName;
397
+ const dbCommand = botConfigCache.commands.get(mainCommandName);
398
+
399
+ if (!dbCommand || (!dbCommand.isEnabled && !user.isOwner)) {
400
+ return;
401
+ }
351
402
 
352
403
  const allowedTypes = JSON.parse(dbCommand.allowedChatTypes || '[]');
353
404
  if (!allowedTypes.includes(typeChat) && !user.isOwner) {
354
-
355
405
  if (typeChat === 'global') {
356
406
  return;
357
407
  }
358
-
359
- this._sendThrottledWarning(
360
- botConfig.id,
361
- username,
362
- `wrong_chat:${dbCommand.name}:${typeChat}`,
363
- `Команду ${dbCommand.name} нельзя использовать в этом типе чата.`,
364
- typeChat
365
- );
408
+ child.send({ type: 'handle_wrong_chat', commandName: dbCommand.name, username, typeChat });
366
409
  return;
367
410
  }
368
411
 
369
- const permission = dbCommand.permissionId ? await prisma.permission.findUnique({ where: { id: dbCommand.permissionId } }) : null;
412
+ const permission = dbCommand.permissionId ? botConfigCache.permissionsById.get(dbCommand.permissionId) : null;
370
413
  if (permission && !user.hasPermission(permission.name)) {
371
- this._sendThrottledWarning(
372
- botConfig.id,
373
- username,
374
- `no_permission:${dbCommand.name}`,
375
- `У вас нет прав для выполнения команды ${dbCommand.name}.`,
376
- typeChat
377
- );
414
+ child.send({ type: 'handle_permission_error', commandName: dbCommand.name, username, typeChat });
378
415
  return;
379
416
  }
380
417
 
381
418
  const domain = (permission?.name || '').split('.')[0] || 'user';
382
419
  const bypassCooldownPermission = `${domain}.cooldown.bypass`;
383
420
  if (dbCommand.cooldown > 0 && !user.isOwner && !user.hasPermission(bypassCooldownPermission)) {
384
- const cooldownKey = `${botConfig.id}:${dbCommand.name}:${user.id}`;
421
+ const cooldownKey = `${botId}:${dbCommand.name}:${user.id}`;
385
422
  const now = Date.now();
386
423
  const lastUsed = cooldowns.get(cooldownKey);
387
424
 
388
425
  if (lastUsed && (now - lastUsed < dbCommand.cooldown * 1000)) {
389
426
  const timeLeft = Math.ceil((dbCommand.cooldown * 1000 - (now - lastUsed)) / 1000);
390
-
391
- this._sendThrottledWarning(
392
- botConfig.id,
393
- username,
394
- `cooldown:${dbCommand.name}`,
395
- `Команду ${dbCommand.name} можно будет использовать через ${timeLeft} сек.`,
396
- typeChat
397
- );
427
+ child.send({ type: 'handle_cooldown', commandName: dbCommand.name, username, typeChat, timeLeft });
398
428
  return;
399
429
  }
400
430
  cooldowns.set(cooldownKey, now);
401
431
  }
402
432
 
403
- const child = this.bots.get(botConfig.id);
404
- if (child) {
405
- child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
406
- }
433
+ child.send({ type: 'execute_handler', commandName: dbCommand.name, username, args, typeChat });
407
434
 
408
435
  } catch (error) {
409
- console.error(`[BotManager] Ошибка валидации команды ${commandName}:`, error);
410
- this.sendMessageToBot(botConfig.id, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
436
+ console.error(`[BotManager] Command validation error for botId: ${botId}`, {
437
+ command: commandName,
438
+ user: username,
439
+ error: error.message,
440
+ stack: error.stack
441
+ });
442
+ this.sendMessageToBot(botId, `Произошла внутренняя ошибка при выполнении команды.`, 'private', username);
411
443
  }
412
444
  }
413
445
 
@@ -455,6 +487,7 @@ class BotManager {
455
487
  create: createData,
456
488
  });
457
489
 
490
+ this.invalidateConfigCache(botId);
458
491
 
459
492
  } catch (error) {
460
493
  console.error(`[BotManager] Ошибка при регистрации команды '${commandConfig.name}':`, error);
@@ -465,6 +498,7 @@ class BotManager {
465
498
  const child = this.bots.get(botId);
466
499
  if (child) {
467
500
  child.send({ type: 'stop' });
501
+ this.botConfigs.delete(botId);
468
502
  return { success: true };
469
503
  }
470
504
  return { success: false, message: 'Бот не найден или уже остановлен' };
@@ -50,7 +50,7 @@ function handleIncomingCommand(type, username, message) {
50
50
  if (argDef.type === 'number') {
51
51
  const numValue = parseFloat(value);
52
52
  if (isNaN(numValue)) {
53
- bot.api.sendMessage(type, `Ошибка: Неверный тип аргумента "${argDef.description}". Ожидалось число.`, username);
53
+ bot.api.sendMessage(type, `Ошибка: Аргумент "${argDef.description}" должен быть числом.`, username);
54
54
  return;
55
55
  }
56
56
  value = numValue;
@@ -61,7 +61,12 @@ function handleIncomingCommand(type, username, message) {
61
61
 
62
62
  if (processedArgs[argDef.name] === undefined) {
63
63
  if (argDef.required) {
64
+ const usage = commandInstance.args.map(arg => {
65
+ return arg.required ? `<${arg.description}>` : `[${arg.description}]`;
66
+ }).join(' ');
67
+
64
68
  bot.api.sendMessage(type, `Ошибка: Необходимо указать: ${argDef.description}`, username);
69
+ bot.api.sendMessage(type, `Использование: ${bot.config.prefix}${commandInstance.name} ${usage}`, username);
65
70
  return;
66
71
  }
67
72
  if (argDef.default !== undefined) {
@@ -271,6 +276,34 @@ process.on('message', async (message) => {
271
276
  if (message.username && bot && bot.config) {
272
277
  UserService.clearCache(message.username, bot.config.id);
273
278
  }
279
+ } else if (message.type === 'handle_permission_error') {
280
+ const { commandName, username, typeChat } = message;
281
+ const commandInstance = bot.commands.get(commandName);
282
+ if (commandInstance) {
283
+ commandInstance.onInsufficientPermissions(bot, typeChat, { username });
284
+ }
285
+ } else if (message.type === 'handle_wrong_chat') {
286
+ const { commandName, username, typeChat } = message;
287
+ const commandInstance = bot.commands.get(commandName);
288
+ if (commandInstance) {
289
+ commandInstance.onWrongChatType(bot, typeChat, { username });
290
+ }
291
+ } else if (message.type === 'handle_cooldown') {
292
+ const { commandName, username, typeChat, timeLeft } = message;
293
+ const commandInstance = bot.commands.get(commandName);
294
+ if (commandInstance) {
295
+ commandInstance.onCooldown(bot, typeChat, { username }, timeLeft);
296
+ }
297
+ } else if (message.type === 'handle_blacklist') {
298
+ const { commandName, username, typeChat } = message;
299
+ const commandInstance = bot.commands.get(commandName);
300
+ if (commandInstance) {
301
+ commandInstance.onBlacklisted(bot, typeChat, { username });
302
+ }
303
+ } else if (message.type === 'invalidate_user_cache') {
304
+ if (message.username && bot && bot.config) {
305
+ UserService.clearCache(message.username, bot.config.id);
306
+ }
274
307
  }
275
308
  });
276
309
 
@@ -19,17 +19,6 @@ class Command {
19
19
  this.allowedChatTypes = allowedChatTypes;
20
20
  }
21
21
 
22
- onInvalidArguments(bot, typeChat, user, error) {
23
- bot.api.sendMessage(typeChat, `Ошибка: ${error.message}`, user.username);
24
-
25
- const usage = this.args.map(arg => {
26
- const part = arg.required ? `<${arg.description}>` : `[${arg.description}]`;
27
- return part;
28
- }).join(' ');
29
-
30
- bot.api.sendMessage(typeChat, `Использование: ${bot.config.prefix}${this.name} ${usage}`, user.username);
31
- }
32
-
33
22
  onInsufficientPermissions(bot, typeChat, user) {
34
23
  bot.api.sendMessage(typeChat, `У вас нет прав для выполнения команды ${this.name}.`, user.username);
35
24
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
 
2
2
  {
3
3
  "name": "blockmine",
4
- "version": "1.3.2",
4
+ "version": "1.3.22",
5
5
  "description": "Мощная панель управления ботами для Майнкрафта.",
6
6
  "author": "merka",
7
7
  "license": "MIT",
@@ -26,9 +26,9 @@
26
26
  ],
27
27
  "scripts": {
28
28
  "dev": "concurrently \"npm run dev --workspace=backend\" \"npm run dev --workspace=frontend\"",
29
- "postinstall": "npm install --workspaces --ignore-scripts && prisma generate --schema=./backend/prisma/schema.prisma",
29
+ "postinstall": "npm install --workspaces --ignore-scripts && npx prisma generate --schema=./backend/prisma/schema.prisma",
30
30
  "build": "npm run build --workspace=frontend",
31
- "prepublishOnly": "npm run build && prisma generate --schema=./backend/prisma/schema.prisma"
31
+ "prepublishOnly": "npm run build && npx prisma generate --schema=./backend/prisma/schema.prisma"
32
32
  },
33
33
  "dependencies": {
34
34
  "prisma": "^5.14.0"