blockmine 1.18.4 → 1.19.1
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/.kiro/steering/product.md +27 -0
- package/.kiro/steering/structure.md +89 -0
- package/.kiro/steering/tech.md +94 -0
- package/CHANGELOG.md +147 -112
- package/backend/cli.js +59 -57
- package/backend/prisma/migrations/20250701190321_add/migration.sql +2 -2
- package/backend/prisma/migrations/20250709150611_add_run_on_startup_to_tasks/migration.sql +21 -21
- package/backend/prisma/migrations/20250709151124_make_cron_pattern_optional/migration.sql +21 -21
- package/backend/prisma/migrations/20250718181335_add_plugin_data_store/migration.sql +14 -14
- package/backend/prisma/migrations/20250719115906_add_plugin_owner_to_graphs/migration.sql +45 -45
- package/backend/prisma/migrations/20250723160648_add_bot_sort_order/migration.sql +2 -2
- package/backend/prisma/migrations/20250816083216_add_panel_user_bot_access/migration.sql +30 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +244 -229
- package/backend/src/api/middleware/botAccess.js +35 -0
- package/backend/src/api/routes/auth.js +633 -595
- package/backend/src/api/routes/bots.js +294 -179
- package/backend/src/api/routes/eventGraphs.js +459 -459
- package/backend/src/api/routes/pluginIde.js +6 -2
- package/backend/src/api/routes/servers.js +27 -0
- package/backend/src/core/BotManager.js +0 -1
- package/backend/src/core/BotProcess.js +1 -2
- package/backend/src/core/GraphExecutionEngine.js +917 -917
- package/backend/src/core/PluginLoader.js +208 -86
- package/backend/src/core/PluginManager.js +465 -427
- package/backend/src/core/commands/dev.js +6 -1
- package/backend/src/real-time/presence.js +74 -0
- package/backend/src/real-time/socketHandler.js +2 -0
- package/backend/src/server.js +193 -186
- package/frontend/dist/assets/index-BFd7YoAj.css +1 -0
- package/frontend/dist/assets/index-DxdxTe6I.js +8352 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/frontend/dist/assets/index-CA3XrPPP.css +0 -1
- package/frontend/dist/assets/index-CM9ljR30.js +0 -8352
|
@@ -1,87 +1,209 @@
|
|
|
1
|
-
|
|
2
|
-
const path = require('path');
|
|
3
|
-
const fs = require('fs/promises');
|
|
4
|
-
const { execSync } = require('child_process');
|
|
5
|
-
const fssync = require('fs');
|
|
6
|
-
const PluginStore = require('../plugins/PluginStore');
|
|
7
|
-
const { deepMergeSettings } = require('./utils/settingsMerger');
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
defaultSettings[key] = config.default;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
1
|
+
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs/promises');
|
|
4
|
+
const { execSync } = require('child_process');
|
|
5
|
+
const fssync = require('fs');
|
|
6
|
+
const PluginStore = require('../plugins/PluginStore');
|
|
7
|
+
const { deepMergeSettings } = require('./utils/settingsMerger');
|
|
8
|
+
const { createRequire } = require('module');
|
|
9
|
+
const { execSync: execSyncRaw } = require('child_process');
|
|
10
|
+
|
|
11
|
+
const projectRoot = path.resolve(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
async function initializePlugins(bot, installedPlugins = [], prisma) {
|
|
16
|
+
if (!installedPlugins || installedPlugins.length === 0) return;
|
|
17
|
+
|
|
18
|
+
const sendLog = bot.sendLog || console.log;
|
|
19
|
+
sendLog(`[PluginLoader] Загрузка ${installedPlugins.length} плагинов...`);
|
|
20
|
+
|
|
21
|
+
for (const plugin of installedPlugins) {
|
|
22
|
+
if (plugin && plugin.path) {
|
|
23
|
+
try {
|
|
24
|
+
const manifest = plugin.manifest ? JSON.parse(plugin.manifest) : {};
|
|
25
|
+
const savedSettings = plugin.settings ? JSON.parse(plugin.settings) : {};
|
|
26
|
+
const defaultSettings = {};
|
|
27
|
+
|
|
28
|
+
if (manifest.settings) {
|
|
29
|
+
for (const key in manifest.settings) {
|
|
30
|
+
const config = manifest.settings[key];
|
|
31
|
+
if (config.type === 'json_file' && config.defaultPath) {
|
|
32
|
+
const configFilePath = path.join(plugin.path, config.defaultPath);
|
|
33
|
+
try {
|
|
34
|
+
const fileContent = await fs.readFile(configFilePath, 'utf-8');
|
|
35
|
+
defaultSettings[key] = JSON.parse(fileContent);
|
|
36
|
+
} catch (e) {
|
|
37
|
+
sendLog(`[PluginLoader] WARN: Не удалось прочитать defaultPath '${config.defaultPath}' для плагина ${plugin.name}.`);
|
|
38
|
+
defaultSettings[key] = (config.type === 'string[]' || config.type === 'json_file') ? [] : {};
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
try {
|
|
42
|
+
defaultSettings[key] = JSON.parse(config.default || 'null');
|
|
43
|
+
} catch {
|
|
44
|
+
defaultSettings[key] = config.default;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const finalSettings = deepMergeSettings(defaultSettings, savedSettings);
|
|
51
|
+
const store = new PluginStore(prisma, bot.config.id, plugin.name);
|
|
52
|
+
|
|
53
|
+
const mainFile = manifest.main || 'index.js';
|
|
54
|
+
const entryPointPath = path.join(plugin.path, mainFile);
|
|
55
|
+
const normalizedPath = entryPointPath.replace(/\\/g, '/');
|
|
56
|
+
const pluginRequire = createRequire(entryPointPath);
|
|
57
|
+
const { pathToFileURL } = require('url');
|
|
58
|
+
|
|
59
|
+
const loadAndInit = async () => {
|
|
60
|
+
let pluginModule;
|
|
61
|
+
try {
|
|
62
|
+
pluginModule = pluginRequire(normalizedPath);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
if (e.code === 'ERR_REQUIRE_ESM' || /Cannot use import statement|Unexpected token 'export'|Unexpected identifier 'import'/.test(e.message)) {
|
|
65
|
+
const moduleUrl = pathToFileURL(entryPointPath).href;
|
|
66
|
+
const esmModule = await import(moduleUrl);
|
|
67
|
+
pluginModule = esmModule && esmModule.default ? esmModule.default : esmModule;
|
|
68
|
+
} else {
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof pluginModule === 'function') {
|
|
74
|
+
pluginModule(bot, { settings: finalSettings, store });
|
|
75
|
+
} else if (pluginModule && typeof pluginModule.onLoad === 'function') {
|
|
76
|
+
pluginModule.onLoad(bot, { settings: finalSettings, store });
|
|
77
|
+
} else if (pluginModule && pluginModule.default && typeof pluginModule.default === 'function') {
|
|
78
|
+
pluginModule.default(bot, { settings: finalSettings, store });
|
|
79
|
+
} else if (pluginModule && pluginModule.default && typeof pluginModule.default.onLoad === 'function') {
|
|
80
|
+
pluginModule.default.onLoad(bot, { settings: finalSettings, store });
|
|
81
|
+
} else {
|
|
82
|
+
sendLog(`[PluginLoader] [ERROR] ${plugin.name} не экспортирует функцию или объект с методом onLoad.`);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
sendLog(`[PluginLoader] Загрузка: ${plugin.name} (v${plugin.version}) из ${normalizedPath}`);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await loadAndInit();
|
|
90
|
+
} catch (error) {
|
|
91
|
+
let handled = false;
|
|
92
|
+
let lastError = error;
|
|
93
|
+
if (error.message.includes('Cannot find module')) {
|
|
94
|
+
const moduleMatch = error.message.match(/Cannot find module '([^']+)'/);
|
|
95
|
+
const missingModule = moduleMatch ? moduleMatch[1] : null;
|
|
96
|
+
if (missingModule) {
|
|
97
|
+
try {
|
|
98
|
+
// Диагностика текущего состояния резолва
|
|
99
|
+
pluginRequire.resolve(missingModule);
|
|
100
|
+
sendLog(`[PluginLoader] [DEBUG] ${missingModule} уже резолвится до установки? Это неожиданно.`);
|
|
101
|
+
} catch {
|
|
102
|
+
sendLog(`[PluginLoader] [DEBUG] ${missingModule} не резолвится до установки.`);
|
|
103
|
+
}
|
|
104
|
+
sendLog(`[PluginLoader] [WARN] Модуль ${missingModule} не найден для плагина ${plugin.name}. Пытаюсь установить автоматически...`);
|
|
105
|
+
try {
|
|
106
|
+
// Сначала пробуем установить все зависимости из package.json плагина
|
|
107
|
+
try {
|
|
108
|
+
execSync('npm install --omit=dev --no-audit --no-fund', { cwd: plugin.path, stdio: 'inherit' });
|
|
109
|
+
} catch (e1) {
|
|
110
|
+
sendLog(`[PluginLoader] [WARN] npm install завершился с ошибкой, пробую с --legacy-peer-deps...`);
|
|
111
|
+
execSync('npm install --omit=dev --legacy-peer-deps --no-audit --no-fund', { cwd: plugin.path, stdio: 'inherit' });
|
|
112
|
+
}
|
|
113
|
+
sendLog(`[PluginLoader] [INFO] Зависимости установлены. Повторная загрузка плагина ${plugin.name}...`);
|
|
114
|
+
// Очистим кэш require для файлов плагина перед повторной загрузкой
|
|
115
|
+
for (const key of Object.keys(require.cache)) {
|
|
116
|
+
if (key.startsWith(plugin.path)) {
|
|
117
|
+
delete require.cache[key];
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Если конкретный модуль всё ещё не резолвится, попробуем точечно
|
|
121
|
+
let needTargetedInstall = false;
|
|
122
|
+
try {
|
|
123
|
+
pluginRequire.resolve(missingModule);
|
|
124
|
+
sendLog(`[PluginLoader] [DEBUG] ${missingModule} резолвится после общего npm install.`);
|
|
125
|
+
} catch {
|
|
126
|
+
sendLog(`[PluginLoader] [DEBUG] ${missingModule} всё ещё не резолвится после общего npm install.`);
|
|
127
|
+
needTargetedInstall = true;
|
|
128
|
+
}
|
|
129
|
+
if (needTargetedInstall) {
|
|
130
|
+
throw new Error(`Cannot find module '${missingModule}' after npm install`);
|
|
131
|
+
}
|
|
132
|
+
await loadAndInit();
|
|
133
|
+
handled = true;
|
|
134
|
+
} catch (retryErr) {
|
|
135
|
+
lastError = retryErr;
|
|
136
|
+
// Если после общей установки модуль всё ещё отсутствует, пробуем поставить точечно
|
|
137
|
+
if (retryErr.message && retryErr.message.includes('Cannot find module') && missingModule) {
|
|
138
|
+
try {
|
|
139
|
+
sendLog(`[PluginLoader] [WARN] Повторная попытка: устанавливаю ${missingModule} адресно...`);
|
|
140
|
+
// Простая валидация имени пакета для безопасности
|
|
141
|
+
const safeName = /^[a-zA-Z0-9@_/\\.\-]+$/.test(missingModule) ? missingModule : null;
|
|
142
|
+
if (!safeName) {
|
|
143
|
+
throw new Error(`Некорректное имя пакета: ${missingModule}`);
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
execSync(`npm install ${safeName} --omit=dev --no-audit --no-fund`, { cwd: plugin.path, stdio: 'inherit' });
|
|
147
|
+
} catch (e2) {
|
|
148
|
+
sendLog(`[PluginLoader] [WARN] Адресная установка ${missingModule} завершилась с ошибкой, пробую с --legacy-peer-deps...`);
|
|
149
|
+
execSync(`npm install ${safeName} --omit=dev --legacy-peer-deps --no-audit --no-fund`, { cwd: plugin.path, stdio: 'inherit' });
|
|
150
|
+
}
|
|
151
|
+
sendLog(`[PluginLoader] [INFO] Модуль ${missingModule} установлен. Повторная загрузка ${plugin.name}...`);
|
|
152
|
+
for (const key of Object.keys(require.cache)) {
|
|
153
|
+
if (key.startsWith(plugin.path)) {
|
|
154
|
+
delete require.cache[key];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Если у пакета есть peerDependencies, установим их
|
|
158
|
+
try {
|
|
159
|
+
const out = execSyncRaw(`npm view ${safeName} peerDependencies --json`, { cwd: plugin.path });
|
|
160
|
+
const text = String(out || '').trim();
|
|
161
|
+
if (text && text !== 'null' && text !== 'undefined') {
|
|
162
|
+
const peersObj = JSON.parse(text);
|
|
163
|
+
const peers = Object.keys(peersObj || {});
|
|
164
|
+
if (peers.length > 0) {
|
|
165
|
+
sendLog(`[PluginLoader] [INFO] Установка peerDependencies для ${missingModule}: ${peers.join(', ')}`);
|
|
166
|
+
const installLine = `npm install ${peers.join(' ')} --omit=dev --legacy-peer-deps --no-audit --no-fund`;
|
|
167
|
+
execSync(installLine, { cwd: plugin.path, stdio: 'inherit' });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
} catch (peerErr) {
|
|
171
|
+
sendLog(`[PluginLoader] [WARN] Не удалось получить/установить peerDependencies для ${missingModule}: ${peerErr.message}`);
|
|
172
|
+
}
|
|
173
|
+
// Проверим, что модуль теперь доступен
|
|
174
|
+
try {
|
|
175
|
+
const paths = pluginRequire.resolve.paths ? pluginRequire.resolve.paths(missingModule) : [];
|
|
176
|
+
sendLog(`[PluginLoader] [DEBUG] Пути резолва для ${missingModule}: ${JSON.stringify(paths)}`);
|
|
177
|
+
pluginRequire.resolve(missingModule);
|
|
178
|
+
} catch (rErr) {
|
|
179
|
+
sendLog(`[PluginLoader] [ERROR] ${missingModule} всё ещё не резолвится после адресной установки: ${rErr.message}`);
|
|
180
|
+
throw rErr;
|
|
181
|
+
}
|
|
182
|
+
sendLog(`[PluginLoader] [DEBUG] ${missingModule} резолвится после адресной установки.`);
|
|
183
|
+
await loadAndInit();
|
|
184
|
+
handled = true;
|
|
185
|
+
} catch (installErr) {
|
|
186
|
+
lastError = installErr;
|
|
187
|
+
sendLog(`[PluginLoader] [ERROR] Автоустановка модуля ${missingModule} для ${plugin.name} не удалась: ${installErr.message}`);
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
sendLog(`[PluginLoader] [ERROR] Не удалось автоматически установить зависимости для ${plugin.name}: ${retryErr.message}`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
sendLog(`[PluginLoader] [ERROR] Не удалось определить отсутствующий модуль для плагина ${plugin.name}.`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (!handled) {
|
|
198
|
+
throw lastError;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
} catch (error) {
|
|
203
|
+
sendLog(`[PluginLoader] [FATAL] Не удалось загрузить плагин ${plugin.name}: ${error.stack}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
87
209
|
module.exports = { initializePlugins };
|