blockmine 1.25.0 → 1.27.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/CHANGELOG.md +46 -1
- package/backend/cli.js +1 -1
- package/backend/package.json +2 -2
- package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +2 -0
- package/backend/src/api/routes/apiKeys.js +8 -0
- package/backend/src/api/routes/bots.js +258 -9
- package/backend/src/api/routes/eventGraphs.js +151 -1
- package/backend/src/api/routes/health.js +38 -0
- package/backend/src/api/routes/nodeRegistry.js +63 -0
- package/backend/src/api/routes/plugins.js +254 -29
- package/backend/src/container.js +11 -8
- package/backend/src/core/BotCommandLoader.js +161 -0
- package/backend/src/core/BotConnection.js +125 -0
- package/backend/src/core/BotEventHandlers.js +234 -0
- package/backend/src/core/BotIPCHandler.js +445 -0
- package/backend/src/core/BotManager.js +15 -7
- package/backend/src/core/BotProcess.js +75 -142
- package/backend/src/core/EventGraphManager.js +7 -3
- package/backend/src/core/GraphDebugHandler.js +229 -0
- package/backend/src/core/GraphDebugIPC.js +117 -0
- package/backend/src/core/GraphExecutionEngine.js +545 -978
- package/backend/src/core/GraphTraversal.js +80 -0
- package/backend/src/core/GraphValidation.js +73 -0
- package/backend/src/core/NodeDefinition.js +138 -0
- package/backend/src/core/NodeRegistry.js +153 -141
- package/backend/src/core/PluginManager.js +272 -31
- package/backend/src/core/RewindSignal.js +9 -0
- package/backend/src/core/config/ConfigValidator.js +72 -0
- package/backend/src/core/config/FeatureFlags.js +52 -0
- package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
- package/backend/src/core/domain/entities/Bot.js +39 -0
- package/backend/src/core/domain/entities/Command.js +41 -0
- package/backend/src/core/domain/entities/EventGraph.js +39 -0
- package/backend/src/core/domain/entities/Plugin.js +45 -0
- package/backend/src/core/domain/entities/User.js +40 -0
- package/backend/src/core/domain/services/DependencyResolver.js +168 -0
- package/backend/src/core/domain/services/GraphValidator.js +117 -0
- package/backend/src/core/domain/services/PermissionChecker.js +34 -0
- package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
- package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
- package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
- package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
- package/backend/src/core/errors/BaseError.js +29 -0
- package/backend/src/core/errors/ErrorHandler.js +81 -0
- package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
- package/backend/src/core/errors/index.js +68 -0
- package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
- package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
- package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
- package/backend/src/core/infrastructure/RateLimiter.js +64 -0
- package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
- package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
- package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
- package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
- package/backend/src/core/ipc/botApiFactory.js +72 -0
- package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
- package/backend/src/core/logging/AuditLogger.js +61 -0
- package/backend/src/core/logging/StructuredLogger.js +80 -0
- package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
- package/backend/src/core/logging/index.js +7 -0
- package/backend/src/core/metrics/MetricsCollector.js +104 -0
- package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
- package/backend/src/core/node-registries/actionsNodes.js +191 -0
- package/backend/src/core/node-registries/arraysNodes.js +152 -0
- package/backend/src/core/node-registries/botNodes.js +48 -0
- package/backend/src/core/node-registries/containerNodes.js +141 -0
- package/backend/src/core/node-registries/dataNodes.js +284 -0
- package/backend/src/core/node-registries/debugNodes.js +23 -0
- package/backend/src/core/node-registries/eventsNodes.js +223 -0
- package/backend/src/core/node-registries/flowNodes.js +151 -0
- package/backend/src/core/node-registries/furnaceNodes.js +123 -0
- package/backend/src/core/node-registries/index.js +108 -0
- package/backend/src/core/node-registries/inventory.js +102 -106
- package/backend/src/core/node-registries/logicNodes.js +54 -0
- package/backend/src/core/node-registries/mathNodes.js +38 -0
- package/backend/src/core/node-registries/navigationNodes.js +109 -0
- package/backend/src/core/node-registries/objectsNodes.js +90 -0
- package/backend/src/core/node-registries/stringsNodes.js +165 -0
- package/backend/src/core/node-registries/timeNodes.js +105 -0
- package/backend/src/core/node-registries/typeNodes.js +22 -0
- package/backend/src/core/node-registries/usersNodes.js +126 -0
- package/backend/src/core/nodes/arrays/shuffle.js +14 -0
- package/backend/src/core/nodes/bot/get_name.js +8 -0
- package/backend/src/core/nodes/bot/stop_bot.js +5 -0
- package/backend/src/core/nodes/container/open.js +101 -111
- package/backend/src/core/nodes/data/store_read.js +26 -0
- package/backend/src/core/nodes/data/store_write.js +23 -0
- package/backend/src/core/nodes/event/call_event.js +31 -0
- package/backend/src/core/nodes/event/custom_event.js +8 -0
- package/backend/src/core/nodes/flow/timer.js +35 -0
- package/backend/src/core/nodes/inventory/drop.js +73 -65
- package/backend/src/core/nodes/inventory/equip.js +54 -45
- package/backend/src/core/nodes/inventory/select_slot.js +48 -46
- package/backend/src/core/nodes/navigation/follow.js +54 -51
- package/backend/src/core/nodes/navigation/go_to.js +41 -53
- package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
- package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
- package/backend/src/core/nodes/navigation/stop.js +17 -26
- package/backend/src/core/nodes/users/add_to_group.js +24 -0
- package/backend/src/core/nodes/users/check_permission.js +26 -0
- package/backend/src/core/nodes/users/remove_from_group.js +24 -0
- package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
- package/backend/src/core/services/BotLifecycleService.js +41 -632
- package/backend/src/core/services/CacheManager.js +83 -23
- package/backend/src/core/services/CrashRestartManager.js +42 -0
- package/backend/src/core/services/DebugSessionManager.js +114 -12
- package/backend/src/core/services/EventGraphService.js +69 -0
- package/backend/src/core/services/MinecraftBotManager.js +9 -1
- package/backend/src/core/services/PluginManagementService.js +84 -0
- package/backend/src/core/services/TestModeContext.js +65 -0
- package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
- package/backend/src/core/services.js +1 -11
- package/backend/src/core/validation/InputValidator.js +167 -0
- package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
- package/backend/src/real-time/botApi/index.js +1 -1
- package/backend/src/real-time/socketHandler.js +26 -0
- package/backend/src/server.js +10 -5
- package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
- package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
- package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/locales/en/bots.json +4 -1
- package/frontend/dist/locales/en/common.json +7 -1
- package/frontend/dist/locales/en/login.json +2 -0
- package/frontend/dist/locales/en/management.json +79 -1
- package/frontend/dist/locales/en/nodes.json +59 -4
- package/frontend/dist/locales/en/plugin-detail.json +24 -4
- package/frontend/dist/locales/en/plugins.json +226 -7
- package/frontend/dist/locales/en/setup.json +2 -0
- package/frontend/dist/locales/en/sidebar.json +171 -3
- package/frontend/dist/locales/en/visual-editor.json +230 -31
- package/frontend/dist/locales/ru/bots.json +4 -1
- package/frontend/dist/locales/ru/login.json +2 -0
- package/frontend/dist/locales/ru/management.json +79 -1
- package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
- package/frontend/dist/locales/ru/nodes.json +105 -51
- package/frontend/dist/locales/ru/plugins.json +103 -4
- package/frontend/dist/locales/ru/setup.json +2 -0
- package/frontend/dist/locales/ru/sidebar.json +171 -3
- package/frontend/dist/locales/ru/visual-editor.json +232 -33
- package/frontend/package.json +2 -0
- package/nul +12 -0
- package/package.json +3 -3
- package/scripts/postinstall.js +38 -0
- package/backend/package-lock.json +0 -6801
- package/backend/src/core/node-registries/actions.js +0 -202
- package/backend/src/core/node-registries/arrays.js +0 -155
- package/backend/src/core/node-registries/bot.js +0 -23
- package/backend/src/core/node-registries/container.js +0 -162
- package/backend/src/core/node-registries/data.js +0 -290
- package/backend/src/core/node-registries/debug.js +0 -26
- package/backend/src/core/node-registries/events.js +0 -201
- package/backend/src/core/node-registries/flow.js +0 -139
- package/backend/src/core/node-registries/furnace.js +0 -143
- package/backend/src/core/node-registries/logic.js +0 -62
- package/backend/src/core/node-registries/math.js +0 -42
- package/backend/src/core/node-registries/navigation.js +0 -111
- package/backend/src/core/node-registries/objects.js +0 -98
- package/backend/src/core/node-registries/strings.js +0 -187
- package/backend/src/core/node-registries/time.js +0 -113
- package/backend/src/core/node-registries/type.js +0 -25
- package/backend/src/core/node-registries/users.js +0 -79
- package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
|
-
# История версий
|
|
1
|
+
# История версий
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### [1.27.1](https://github.com/blockmineJS/blockmine/compare/v1.27.0...v1.27.1) (2026-05-15)
|
|
5
|
+
|
|
6
|
+
## [1.27.0](https://github.com/blockmineJS/blockmine/compare/v1.25.0...v1.27.0) (2026-05-12)
|
|
2
7
|
|
|
3
8
|
|
|
9
|
+
### 🛠 Рефакторинг
|
|
10
|
+
|
|
11
|
+
* внутренний большой рефактор бэкенда ([eb27f26](https://github.com/blockmineJS/blockmine/commit/eb27f2600218f05a8b8707adfed3a5a2298531f6))
|
|
12
|
+
* полноценный внутренний рефактор нод ([28387a9](https://github.com/blockmineJS/blockmine/commit/28387a917bf89e33e9715cbd03483accc136b9af))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### 🐛 Исправления
|
|
16
|
+
|
|
17
|
+
* нода "сообщение в чате" работает в локал мире ([17bbde8](https://github.com/blockmineJS/blockmine/commit/17bbde82115cecee646a10bc26f21ec23856e33e))
|
|
18
|
+
* пост инсталл скрипт теперь есть... ([c5248cb](https://github.com/blockmineJS/blockmine/commit/c5248cbb805057b52efaea67209bea325b2e0b31))
|
|
19
|
+
* причина кика теперь корректно пишется ([08de739](https://github.com/blockmineJS/blockmine/commit/08de739fc03ce35ae136b3bd5cb5b822145139cf))
|
|
20
|
+
* роут install local теперь может работать и через апи ключ ([03f1176](https://github.com/blockmineJS/blockmine/commit/03f117690cda3d2966a928be217ad78154d4ec87))
|
|
21
|
+
* address PR79 review feedback by САХАРОК ([5dceb17](https://github.com/blockmineJS/blockmine/commit/5dceb174ef3009f47cb5a94f48f5e90c4c790feb))
|
|
22
|
+
* address remaining PR79 review feedback by САХАРОК ([dde6905](https://github.com/blockmineJS/blockmine/commit/dde690593320344f181f9a13fc4dc4905872e2cf))
|
|
23
|
+
* close active connection on API key deletion by САХАРОК ([2f7db12](https://github.com/blockmineJS/blockmine/commit/2f7db123b0b46c3aedf806d7d543316d7aace1f5))
|
|
24
|
+
* close remaining plugin workflow review issues by САХАРОК ([9f2dd7f](https://github.com/blockmineJS/blockmine/commit/9f2dd7fa422b5b276df4da96f7b9302d489b5e9f))
|
|
25
|
+
* polish language selector modal by САХАРОК ([2cebe65](https://github.com/blockmineJS/blockmine/commit/2cebe6554b21191e7f6ce8a532e243832f2a2ded))
|
|
26
|
+
* polish plugin list alignment and text clarity by САХАРОК ([8c27d60](https://github.com/blockmineJS/blockmine/commit/8c27d6062625e5a55e76a193ed1d3500a88f8246))
|
|
27
|
+
* remove remaining emoji from english visual editor by САХАРОК ([5a4f356](https://github.com/blockmineJS/blockmine/commit/5a4f3563fad29a4054bc98b31b5bc79c0d0b8572))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
### ✨ Новые возможности
|
|
31
|
+
|
|
32
|
+
* большая переработка интерфейса и прочее ([d28e0fa](https://github.com/blockmineJS/blockmine/commit/d28e0fa4af1815476ad90ddd0fb5799db95707ea))
|
|
33
|
+
* в лайв дебаге появилась функция которая дает опробовать ноды без запуска бота ([bbd669f](https://github.com/blockmineJS/blockmine/commit/bbd669f463d62bffacb65b8d6066d5345a0e237f))
|
|
34
|
+
* новая нода - имя бота ([574eada](https://github.com/blockmineJS/blockmine/commit/574eada6af5e89b2b67159b82ca435a2f8446004))
|
|
35
|
+
* новая нода - прочитать/записать в стор ([eeb588e](https://github.com/blockmineJS/blockmine/commit/eeb588e1305d8bfb68cf7e388abf8b092a7865c2))
|
|
36
|
+
* новая нода - стоп бот ([0639ed4](https://github.com/blockmineJS/blockmine/commit/0639ed439d0263fda229f261f317d54df3876d8c))
|
|
37
|
+
* новая нода - таймер ([1d7899d](https://github.com/blockmineJS/blockmine/commit/1d7899d243232ae3062a84d6bc46f0f05897b40d))
|
|
38
|
+
* новая нода - шаффл. перемешать массив ([67e785a](https://github.com/blockmineJS/blockmine/commit/67e785aa7706eb19a489042fcf2aec20643a0b79))
|
|
39
|
+
* новые ноды - события ([ae4db8e](https://github.com/blockmineJS/blockmine/commit/ae4db8e8a11dcdf9c72536a850d65032fa528661))
|
|
40
|
+
* новые ноды. проверить право у юзера, добавить/убрать из группы ([3249483](https://github.com/blockmineJS/blockmine/commit/32494831636432013aa72978daa41bc4668a51af))
|
|
41
|
+
* обновлен сайдбар ([8d77a48](https://github.com/blockmineJS/blockmine/commit/8d77a4834bc60c76de6010a794b4b79926d3fed1))
|
|
42
|
+
* обновление mineflayer. 4.33 -> 4.37.1 . Поддерживает новые версии майнкрафта ([b7f2317](https://github.com/blockmineJS/blockmine/commit/b7f23175ba983fda2a2108d31e54d291d9d17212))
|
|
43
|
+
* improve plugin workflows and panel UX by САХАРОК ([09f111a](https://github.com/blockmineJS/blockmine/commit/09f111a9c47e7fd639b345be3d95de4c203213a9))
|
|
44
|
+
* polish management, viewer, and toast ux by САХАРОК ([d52e9b3](https://github.com/blockmineJS/blockmine/commit/d52e9b3d5eb56bc45c0417e10f397ad87441809e))
|
|
45
|
+
* polish panel ux, theming, and transitions by САХАРОК ([1f347b4](https://github.com/blockmineJS/blockmine/commit/1f347b442cef70f579cdc74c207554f95e68d15a))
|
|
46
|
+
* polish plugin UX and localize panel states by САХАРОК ([ef32fc0](https://github.com/blockmineJS/blockmine/commit/ef32fc08494c41e7a15312a2d0ed251255f3bd2b))
|
|
47
|
+
* refine visual editor and panel polish by САХАРОК ([0118ec6](https://github.com/blockmineJS/blockmine/commit/0118ec6de81078845551b443e5129b41e6e064f9))
|
|
48
|
+
|
|
4
49
|
## [1.25.0](https://github.com/blockmineJS/blockmine/compare/v1.24.0...v1.25.0) (2025-12-22)
|
|
5
50
|
|
|
6
51
|
|
package/backend/cli.js
CHANGED
package/backend/package.json
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"ts-jest": "^29.4.5"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@octokit/rest": "^
|
|
30
|
+
"@octokit/rest": "^20.1.1",
|
|
31
31
|
"awilix": "^12.0.5",
|
|
32
32
|
"date-fns": "^4.1.0",
|
|
33
33
|
"diff": "^8.0.2",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"express-validator": "^7.2.1",
|
|
36
36
|
"google-ai-kit": "^1.1.3",
|
|
37
37
|
"lru-cache": "^10.4.3",
|
|
38
|
-
"mineflayer": "^4.
|
|
38
|
+
"mineflayer": "^4.37.1",
|
|
39
39
|
"mineflayer-pathfinder": "^2.4.5",
|
|
40
40
|
"openrouter-kit": "^0.1.81",
|
|
41
41
|
"pino": "^9.7.0",
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
# Please do not edit this file manually
|
|
2
|
-
# It should be added in your version-control system (i.e. Git)
|
|
1
|
+
# Please do not edit this file manually
|
|
2
|
+
# It should be added in your version-control system (i.e. Git)
|
|
3
3
|
provider = "sqlite"
|
|
@@ -163,6 +163,14 @@ router.delete('/:keyId', authorize('bot:update'), async (req, res) => {
|
|
|
163
163
|
const botId = parseInt(req.params.botId, 10);
|
|
164
164
|
const keyId = parseInt(req.params.keyId, 10);
|
|
165
165
|
|
|
166
|
+
const { getIO } = require('../../real-time/socketHandler');
|
|
167
|
+
|
|
168
|
+
const io = getIO();
|
|
169
|
+
|
|
170
|
+
io.of("/bot-api")
|
|
171
|
+
.in(`key_${keyId}`)
|
|
172
|
+
.disconnectSockets(true)
|
|
173
|
+
|
|
166
174
|
const result = await prisma.botApiKey.deleteMany({
|
|
167
175
|
where: { id: keyId, botId },
|
|
168
176
|
});
|
|
@@ -17,6 +17,7 @@ const { deepMergeSettings } = require('../../core/utils/settingsMerger');
|
|
|
17
17
|
const { checkBotAccess } = require('../middleware/botAccess');
|
|
18
18
|
const { filterSecretSettings, prepareSettingsForSave, isGroupedSettings } = require('../../core/utils/secretsFilter');
|
|
19
19
|
const PluginHooks = require('../../core/PluginHooks');
|
|
20
|
+
const rateLimit = require('express-rate-limit');
|
|
20
21
|
|
|
21
22
|
const multer = require('multer');
|
|
22
23
|
const archiver = require('archiver');
|
|
@@ -26,6 +27,160 @@ const os = require('os');
|
|
|
26
27
|
const upload = multer({ storage: multer.memoryStorage() });
|
|
27
28
|
|
|
28
29
|
const router = express.Router();
|
|
30
|
+
const GITHUB_REQUEST_TIMEOUT_MS = 10000;
|
|
31
|
+
const GITHUB_OWNER_REPO_PATTERN = /^[A-Za-z0-9_.-]+$/;
|
|
32
|
+
|
|
33
|
+
const githubPreviewLimiter = rateLimit({
|
|
34
|
+
windowMs: 60 * 1000,
|
|
35
|
+
max: 30,
|
|
36
|
+
standardHeaders: true,
|
|
37
|
+
legacyHeaders: false,
|
|
38
|
+
message: { message: 'Too many GitHub preview requests. Try again later.' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const githubInstallLimiter = rateLimit({
|
|
42
|
+
windowMs: 60 * 1000,
|
|
43
|
+
max: 20,
|
|
44
|
+
standardHeaders: true,
|
|
45
|
+
legacyHeaders: false,
|
|
46
|
+
message: { message: 'Too many GitHub install requests. Try again later.' },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function getGithubHeaders(extra = {}) {
|
|
50
|
+
const headers = {
|
|
51
|
+
'Accept': 'application/vnd.github+json',
|
|
52
|
+
'User-Agent': 'BlockMine',
|
|
53
|
+
...extra
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
if (process.env.GITHUB_TOKEN) {
|
|
57
|
+
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return headers;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseGithubRepoUrl(repoUrl) {
|
|
64
|
+
if (typeof repoUrl !== 'string') {
|
|
65
|
+
throw new Error('Repository URL is required.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const trimmed = repoUrl.trim();
|
|
69
|
+
if (!trimmed) {
|
|
70
|
+
throw new Error('Repository URL is required.');
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
let parsedUrl;
|
|
74
|
+
try {
|
|
75
|
+
parsedUrl = new URL(trimmed);
|
|
76
|
+
} catch (error) {
|
|
77
|
+
throw new Error('Invalid GitHub repository URL.');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!['github.com', 'www.github.com'].includes(parsedUrl.hostname)) {
|
|
81
|
+
throw new Error('Only GitHub repository links are supported.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const pathParts = parsedUrl.pathname.replace(/\/+$/, '').split('/').filter(Boolean);
|
|
85
|
+
if (pathParts.length < 2) {
|
|
86
|
+
throw new Error('GitHub repository URL must include owner and repository name.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const owner = pathParts[0];
|
|
90
|
+
const repo = pathParts[1].replace(/\.git$/i, '');
|
|
91
|
+
|
|
92
|
+
if (!owner || !repo) {
|
|
93
|
+
throw new Error('GitHub repository URL must include owner and repository name.');
|
|
94
|
+
}
|
|
95
|
+
if (!GITHUB_OWNER_REPO_PATTERN.test(owner) || !GITHUB_OWNER_REPO_PATTERN.test(repo)) {
|
|
96
|
+
throw new Error('GitHub repository URL contains unsupported owner or repository characters.');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
owner,
|
|
101
|
+
repo,
|
|
102
|
+
normalizedUrl: `https://github.com/${owner}/${repo}`
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function normalizeGithubRepoUrl(repoUrl) {
|
|
107
|
+
return parseGithubRepoUrl(repoUrl).normalizedUrl;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function fetchGithubJson(url) {
|
|
111
|
+
const controller = new AbortController();
|
|
112
|
+
const timeoutId = setTimeout(() => controller.abort(), GITHUB_REQUEST_TIMEOUT_MS);
|
|
113
|
+
let response;
|
|
114
|
+
try {
|
|
115
|
+
response = await fetch(url, {
|
|
116
|
+
headers: getGithubHeaders(),
|
|
117
|
+
signal: controller.signal
|
|
118
|
+
});
|
|
119
|
+
} finally {
|
|
120
|
+
clearTimeout(timeoutId);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const error = new Error(`GitHub API request failed with status ${response.status}.`);
|
|
125
|
+
error.status = response.status;
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return response.json();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function fetchGithubReadme(owner, repo) {
|
|
133
|
+
const controller = new AbortController();
|
|
134
|
+
const timeoutId = setTimeout(() => controller.abort(), GITHUB_REQUEST_TIMEOUT_MS);
|
|
135
|
+
let response;
|
|
136
|
+
try {
|
|
137
|
+
response = await fetch(`https://api.github.com/repos/${owner}/${repo}/readme`, {
|
|
138
|
+
headers: getGithubHeaders({ 'Accept': 'application/vnd.github.raw+json' }),
|
|
139
|
+
signal: controller.signal
|
|
140
|
+
});
|
|
141
|
+
} finally {
|
|
142
|
+
clearTimeout(timeoutId);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return response.text();
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function renderGithubMarkdown(markdown, owner, repo) {
|
|
153
|
+
if (!markdown) {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const controller = new AbortController();
|
|
158
|
+
const timeoutId = setTimeout(() => controller.abort(), GITHUB_REQUEST_TIMEOUT_MS);
|
|
159
|
+
let response;
|
|
160
|
+
try {
|
|
161
|
+
response = await fetch('https://api.github.com/markdown', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: getGithubHeaders({
|
|
164
|
+
'Accept': 'text/html',
|
|
165
|
+
'Content-Type': 'application/json',
|
|
166
|
+
}),
|
|
167
|
+
signal: controller.signal,
|
|
168
|
+
body: JSON.stringify({
|
|
169
|
+
text: markdown,
|
|
170
|
+
mode: 'gfm',
|
|
171
|
+
context: `${owner}/${repo}`
|
|
172
|
+
})
|
|
173
|
+
});
|
|
174
|
+
} finally {
|
|
175
|
+
clearTimeout(timeoutId);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!response.ok) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return response.text();
|
|
183
|
+
}
|
|
29
184
|
|
|
30
185
|
const conditionalRestartAuth = (req, res, next) => {
|
|
31
186
|
if (process.env.DEBUG === 'true' || process.env.NODE_ENV === 'development') {
|
|
@@ -352,6 +507,17 @@ router.get('/:id/logs', conditionalListAuth, authenticateUniversal, checkBotAcce
|
|
|
352
507
|
}
|
|
353
508
|
});
|
|
354
509
|
|
|
510
|
+
router.post('/:botId/plugins/install/local', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
511
|
+
const { botId } = req.params;
|
|
512
|
+
const { path } = req.body;
|
|
513
|
+
try {
|
|
514
|
+
const newPlugin = await pluginManager.installFromLocalPath(parseInt(botId), path);
|
|
515
|
+
res.status(201).json(newPlugin);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
res.status(500).json({ message: error.message });
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
|
|
355
521
|
router.use(authenticate);
|
|
356
522
|
router.use('/:botId/event-graphs', eventGraphsRouter);
|
|
357
523
|
router.use('/:botId/plugins/ide', pluginIdeRouter);
|
|
@@ -622,25 +788,108 @@ router.get('/:botId/plugins', authenticateUniversal, checkBotAccess, authorize('
|
|
|
622
788
|
} catch (error) { res.status(500).json({ error: 'Не удалось получить плагины бота' }); }
|
|
623
789
|
});
|
|
624
790
|
|
|
625
|
-
router.post('/:botId/plugins/install/github', authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
626
|
-
const { botId } = req.params;
|
|
791
|
+
router.post('/:botId/plugins/install/github/preview', githubPreviewLimiter, authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
627
792
|
const { repoUrl } = req.body;
|
|
793
|
+
|
|
628
794
|
try {
|
|
629
|
-
const
|
|
630
|
-
|
|
795
|
+
const { owner, repo, normalizedUrl } = parseGithubRepoUrl(repoUrl);
|
|
796
|
+
const repoInfo = await fetchGithubJson(`https://api.github.com/repos/${owner}/${repo}`);
|
|
797
|
+
|
|
798
|
+
let tags = [];
|
|
799
|
+
let latestRelease = null;
|
|
800
|
+
let readme = null;
|
|
801
|
+
let readmeHtml = null;
|
|
802
|
+
|
|
803
|
+
try {
|
|
804
|
+
const tagsData = await fetchGithubJson(`https://api.github.com/repos/${owner}/${repo}/tags?per_page=20`);
|
|
805
|
+
tags = Array.isArray(tagsData) ? tagsData.map(tag => ({
|
|
806
|
+
name: tag.name,
|
|
807
|
+
sha: tag.commit?.sha || null
|
|
808
|
+
})) : [];
|
|
809
|
+
} catch (error) {
|
|
810
|
+
console.warn(`[GitHub Preview] Failed to load tags for ${owner}/${repo}:`, error.message);
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
try {
|
|
814
|
+
latestRelease = await fetchGithubJson(`https://api.github.com/repos/${owner}/${repo}/releases/latest`);
|
|
815
|
+
} catch (error) {
|
|
816
|
+
console.warn(`[GitHub Preview] Failed to load latest release for ${owner}/${repo}:`, error.message);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
readme = await fetchGithubReadme(owner, repo);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
console.warn(`[GitHub Preview] Failed to load README for ${owner}/${repo}:`, error.message);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
try {
|
|
826
|
+
readmeHtml = await renderGithubMarkdown(readme, owner, repo);
|
|
827
|
+
} catch (error) {
|
|
828
|
+
console.warn(`[GitHub Preview] Failed to render README for ${owner}/${repo}:`, error.message);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
res.json({
|
|
832
|
+
repo: {
|
|
833
|
+
name: repoInfo.name,
|
|
834
|
+
fullName: repoInfo.full_name,
|
|
835
|
+
description: repoInfo.description || '',
|
|
836
|
+
defaultBranch: repoInfo.default_branch,
|
|
837
|
+
stars: repoInfo.stargazers_count || 0,
|
|
838
|
+
htmlUrl: repoInfo.html_url || normalizedUrl,
|
|
839
|
+
visibility: repoInfo.private ? 'private' : 'public'
|
|
840
|
+
},
|
|
841
|
+
latestReleaseTag: latestRelease?.tag_name || null,
|
|
842
|
+
tags,
|
|
843
|
+
readme,
|
|
844
|
+
readmeHtml
|
|
845
|
+
});
|
|
631
846
|
} catch (error) {
|
|
632
|
-
|
|
847
|
+
if (error.name === 'AbortError') {
|
|
848
|
+
return res.status(504).json({ message: 'GitHub request timed out. Please try again.' });
|
|
849
|
+
}
|
|
850
|
+
if (error.status === 404) {
|
|
851
|
+
return res.status(404).json({ message: 'GitHub repository not found or it is private.' });
|
|
852
|
+
}
|
|
853
|
+
if (error.status === 403) {
|
|
854
|
+
return res.status(503).json({ message: 'GitHub API rate limit exceeded. Try again a bit later.' });
|
|
855
|
+
}
|
|
856
|
+
const status = error.status || (/required|invalid|only github|must include|unsupported/i.test(error.message) ? 400 : 500);
|
|
857
|
+
res.status(status).json({ message: error.message });
|
|
633
858
|
}
|
|
634
859
|
});
|
|
635
860
|
|
|
636
|
-
router.post('/:botId/plugins/install/
|
|
861
|
+
router.post('/:botId/plugins/install/github', githubInstallLimiter, authenticateUniversal, checkBotAccess, authorize('plugin:install'), async (req, res) => {
|
|
637
862
|
const { botId } = req.params;
|
|
638
|
-
const {
|
|
863
|
+
const { repoUrl, tag } = req.body;
|
|
864
|
+
let normalizedRepoUrl = repoUrl;
|
|
865
|
+
const normalizedTag = typeof tag === 'string' && tag.trim() ? tag.trim() : null;
|
|
639
866
|
try {
|
|
640
|
-
|
|
867
|
+
normalizedRepoUrl = normalizeGithubRepoUrl(repoUrl);
|
|
868
|
+
const newPlugin = await pluginManager.installFromGithub(parseInt(botId), normalizedRepoUrl, prisma, false, normalizedTag);
|
|
641
869
|
res.status(201).json(newPlugin);
|
|
642
870
|
} catch (error) {
|
|
643
|
-
|
|
871
|
+
let status = /required|invalid|only github|must include|unsupported/i.test(error.message) ? 400 : 500;
|
|
872
|
+
let message = error.message;
|
|
873
|
+
|
|
874
|
+
if (/status:\s*404|статус:\s*404/i.test(message)) {
|
|
875
|
+
status = 404;
|
|
876
|
+
message = normalizedTag
|
|
877
|
+
? `GitHub tag "${normalizedTag}" was not found in ${normalizedRepoUrl}.`
|
|
878
|
+
: `GitHub repository was not found or is private: ${normalizedRepoUrl}.`;
|
|
879
|
+
} else if (/status:\s*403|статус:\s*403/i.test(message)) {
|
|
880
|
+
status = 503;
|
|
881
|
+
message = 'GitHub temporarily refused the request or rate limit was exceeded. Try again later.';
|
|
882
|
+
} else if (/package\.json/i.test(message)) {
|
|
883
|
+
status = 400;
|
|
884
|
+
message = 'The GitHub repository does not contain a valid plugin package.json.';
|
|
885
|
+
} else if (/fetch/i.test(message)) {
|
|
886
|
+
status = 502;
|
|
887
|
+
message = normalizedTag
|
|
888
|
+
? `Failed to connect to GitHub while downloading tag "${normalizedTag}".`
|
|
889
|
+
: 'Failed to connect to GitHub while downloading the repository.';
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
res.status(status).json({ message });
|
|
644
893
|
}
|
|
645
894
|
});
|
|
646
895
|
|
|
@@ -178,7 +178,11 @@ router.put('/:graphId',
|
|
|
178
178
|
dataToUpdate.graphJson = graphJson;
|
|
179
179
|
|
|
180
180
|
const parsedGraph = JSON.parse(graphJson);
|
|
181
|
-
const
|
|
181
|
+
const NON_TRIGGER_TYPES = ['custom_event', 'call_event'];
|
|
182
|
+
const eventNodes = parsedGraph.nodes.filter(node =>
|
|
183
|
+
node.type.startsWith('event:') &&
|
|
184
|
+
!NON_TRIGGER_TYPES.includes(node.type.split(':')[1])
|
|
185
|
+
);
|
|
182
186
|
const eventTypes = [...new Set(eventNodes.map(node => node.type.split(':')[1]))];
|
|
183
187
|
|
|
184
188
|
const existingGraph = await prisma.eventGraph.findUnique({
|
|
@@ -458,5 +462,151 @@ router.post('/:graphId/duplicate',
|
|
|
458
462
|
}
|
|
459
463
|
);
|
|
460
464
|
|
|
465
|
+
router.post('/test-run/:graphId',
|
|
466
|
+
authorize('management:edit'),
|
|
467
|
+
async (req, res) => {
|
|
468
|
+
const { botId, graphId } = req.params;
|
|
469
|
+
const { eventType = 'chat', eventArgs = {} } = req.body || {};
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
const eventGraph = await prisma.eventGraph.findFirst({
|
|
473
|
+
where: { id: parseInt(graphId), botId: parseInt(botId) }
|
|
474
|
+
});
|
|
475
|
+
if (!eventGraph) {
|
|
476
|
+
return res.status(404).json({ error: 'Event graph not found' });
|
|
477
|
+
}
|
|
478
|
+
if (!eventGraph.graphJson) {
|
|
479
|
+
return res.status(400).json({ error: 'Graph has no data' });
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const parsed = JSON.parse(eventGraph.graphJson);
|
|
483
|
+
const graph = {
|
|
484
|
+
id: eventGraph.id,
|
|
485
|
+
name: eventGraph.name,
|
|
486
|
+
nodes: parsed.nodes || [],
|
|
487
|
+
connections: parsed.connections || [],
|
|
488
|
+
variables: parsed.variables || []
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
const nodeRegistry = require('../../core/NodeRegistry');
|
|
492
|
+
const GraphExecutionEngine = require('../../core/GraphExecutionEngine');
|
|
493
|
+
const { getGlobalDebugManager } = require('../../core/services/DebugSessionManager');
|
|
494
|
+
const { buildTestContext } = require('../../core/services/TestModeContext');
|
|
495
|
+
|
|
496
|
+
const debugManager = getGlobalDebugManager();
|
|
497
|
+
const debugState = debugManager.getOrCreate(parseInt(botId), parseInt(graphId));
|
|
498
|
+
debugState.enableTestMode({ botId: parseInt(botId), graphId: parseInt(graphId), eventType, eventArgs });
|
|
499
|
+
|
|
500
|
+
const engine = new GraphExecutionEngine(nodeRegistry, null);
|
|
501
|
+
|
|
502
|
+
const context = buildTestContext({
|
|
503
|
+
botId: parseInt(botId),
|
|
504
|
+
graphId: parseInt(graphId),
|
|
505
|
+
eventArgs
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
engine.execute(graph, context, eventType).catch(err => {
|
|
509
|
+
if (!err) return;
|
|
510
|
+
if (err.name === 'BreakLoopSignal') return;
|
|
511
|
+
if (err.message === 'Execution stopped by debugger') return;
|
|
512
|
+
logger.error('[Test Run] Execution error:', err.message);
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
res.json({ success: true, message: 'Test run started in step mode' });
|
|
516
|
+
} catch (error) {
|
|
517
|
+
logger.error('[Test Run] Failed:', error);
|
|
518
|
+
res.status(500).json({ error: error.message || 'Test run failed' });
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
router.post('/run-node/:graphId/:nodeId',
|
|
524
|
+
authorize('management:edit'),
|
|
525
|
+
async (req, res) => {
|
|
526
|
+
const { botId, graphId, nodeId } = req.params;
|
|
527
|
+
const { inputs = {}, variables = {} } = req.body || {};
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
const eventGraph = await prisma.eventGraph.findFirst({
|
|
531
|
+
where: { id: parseInt(graphId), botId: parseInt(botId) }
|
|
532
|
+
});
|
|
533
|
+
if (!eventGraph) {
|
|
534
|
+
return res.status(404).json({ error: 'Event graph not found' });
|
|
535
|
+
}
|
|
536
|
+
const parsed = JSON.parse(eventGraph.graphJson || '{}');
|
|
537
|
+
const node = (parsed.nodes || []).find(n => n.id === nodeId);
|
|
538
|
+
if (!node) {
|
|
539
|
+
return res.status(404).json({ error: 'Node not found in graph' });
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const nodeRegistry = require('../../core/NodeRegistry');
|
|
543
|
+
const nodeConfig = nodeRegistry.getNodeConfig(node.type);
|
|
544
|
+
if (!nodeConfig) {
|
|
545
|
+
return res.status(400).json({ error: `Unknown node type: ${node.type}` });
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const GraphExecutionEngine = require('../../core/GraphExecutionEngine');
|
|
549
|
+
const { buildTestContext } = require('../../core/services/TestModeContext');
|
|
550
|
+
const engine = new GraphExecutionEngine(nodeRegistry, null);
|
|
551
|
+
|
|
552
|
+
engine.activeGraph = {
|
|
553
|
+
id: parseInt(graphId),
|
|
554
|
+
nodes: parsed.nodes || [],
|
|
555
|
+
connections: parsed.connections || [],
|
|
556
|
+
variables: parsed.variables || []
|
|
557
|
+
};
|
|
558
|
+
engine.context = {
|
|
559
|
+
...buildTestContext({
|
|
560
|
+
botId: parseInt(botId),
|
|
561
|
+
graphId: parseInt(graphId),
|
|
562
|
+
eventArgs: inputs
|
|
563
|
+
}),
|
|
564
|
+
variables
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
for (const [pin, value] of Object.entries(inputs)) {
|
|
568
|
+
engine.memo.set(`__forced:${node.id}:${pin}`, value);
|
|
569
|
+
}
|
|
570
|
+
const originalResolve = engine.resolvePinValue.bind(engine);
|
|
571
|
+
engine.resolvePinValue = async function(targetNode, pinName) {
|
|
572
|
+
if (targetNode.id === node.id && engine.memo.has(`__forced:${node.id}:${pinName}`)) {
|
|
573
|
+
return engine.memo.get(`__forced:${node.id}:${pinName}`);
|
|
574
|
+
}
|
|
575
|
+
return originalResolve(targetNode, pinName);
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const startedAt = Date.now();
|
|
579
|
+
const helpers = {
|
|
580
|
+
resolvePinValue: engine.resolvePinValue.bind(engine),
|
|
581
|
+
traverse: async () => {},
|
|
582
|
+
memo: engine.memo,
|
|
583
|
+
clearLoopBodyMemo: () => {}
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
let outputs = {};
|
|
587
|
+
let errorMsg = null;
|
|
588
|
+
try {
|
|
589
|
+
await nodeConfig.executor.call(engine, node, engine.context, helpers);
|
|
590
|
+
outputs = await engine._captureNodeOutputs(node);
|
|
591
|
+
} catch (e) {
|
|
592
|
+
errorMsg = e.message;
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
res.json({
|
|
596
|
+
success: !errorMsg,
|
|
597
|
+
nodeId: node.id,
|
|
598
|
+
nodeType: node.type,
|
|
599
|
+
executionTime: Date.now() - startedAt,
|
|
600
|
+
outputs,
|
|
601
|
+
error: errorMsg,
|
|
602
|
+
variables: engine.context.variables
|
|
603
|
+
});
|
|
604
|
+
} catch (error) {
|
|
605
|
+
logger.error('[Run Node] Failed:', error);
|
|
606
|
+
res.status(500).json({ error: error.message || 'Run node failed' });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
);
|
|
610
|
+
|
|
461
611
|
module.exports = router;
|
|
462
612
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const express = require('express');
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
|
|
4
|
+
router.get('/', async (req, res) => {
|
|
5
|
+
const startTime = Date.now();
|
|
6
|
+
const checks = {};
|
|
7
|
+
let overallHealthy = true;
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
const prisma = require('../../lib/prisma');
|
|
11
|
+
await prisma.$queryRaw`SELECT 1`;
|
|
12
|
+
checks.database = { status: 'healthy' };
|
|
13
|
+
} catch (err) {
|
|
14
|
+
checks.database = { status: 'unhealthy', error: err.message };
|
|
15
|
+
overallHealthy = false;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const botManager = req.app.get('botManager');
|
|
20
|
+
checks.botManager = { status: botManager ? 'healthy' : 'unhealthy' };
|
|
21
|
+
if (!botManager) overallHealthy = false;
|
|
22
|
+
} catch (err) {
|
|
23
|
+
checks.botManager = { status: 'unhealthy', error: err.message };
|
|
24
|
+
overallHealthy = false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const duration = Date.now() - startTime;
|
|
28
|
+
const status = overallHealthy ? 200 : 503;
|
|
29
|
+
|
|
30
|
+
res.status(status).json({
|
|
31
|
+
status: overallHealthy ? 'healthy' : 'unhealthy',
|
|
32
|
+
checks,
|
|
33
|
+
duration,
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
module.exports = router;
|