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
|
@@ -13,7 +13,192 @@ const DATA_DIR = path.join(os.homedir(), '.blockmine');
|
|
|
13
13
|
const PLUGINS_BASE_DIR = path.join(DATA_DIR, 'storage', 'plugins');
|
|
14
14
|
|
|
15
15
|
const TELEMETRY_ENABLED = true;
|
|
16
|
-
const STATS_SERVER_URL = 'http://185.65.200.184:3000';
|
|
16
|
+
const STATS_SERVER_URL = process.env.STATS_SERVER_URL || 'http://185.65.200.184:3000';
|
|
17
|
+
|
|
18
|
+
function normalizeGithubRepoUrl(repoUrl) {
|
|
19
|
+
if (!repoUrl || typeof repoUrl !== 'string') return null;
|
|
20
|
+
|
|
21
|
+
let trimmed = repoUrl.trim();
|
|
22
|
+
if (!trimmed) return null;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
trimmed = trimmed.replace(/^git\+/i, '');
|
|
26
|
+
if (/^github\.com\//i.test(trimmed)) {
|
|
27
|
+
trimmed = `https://${trimmed}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const sshMatch = trimmed.match(/^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/i);
|
|
31
|
+
if (sshMatch) {
|
|
32
|
+
const owner = sshMatch[1].toLowerCase();
|
|
33
|
+
const repo = sshMatch[2].toLowerCase();
|
|
34
|
+
return `https://github.com/${owner}/${repo}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const parsed = new URL(trimmed);
|
|
38
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
39
|
+
if (hostname !== 'github.com' && hostname !== 'www.github.com') return null;
|
|
40
|
+
|
|
41
|
+
const pathParts = parsed.pathname.split('/').filter(Boolean);
|
|
42
|
+
if (pathParts.length < 2) return null;
|
|
43
|
+
|
|
44
|
+
const owner = pathParts[0].toLowerCase();
|
|
45
|
+
const repo = pathParts[1].replace(/\.git$/i, '').toLowerCase();
|
|
46
|
+
return `https://github.com/${owner}/${repo}`;
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function parseGithubOwnerRepo(repoUrl) {
|
|
53
|
+
const normalized = normalizeGithubRepoUrl(repoUrl);
|
|
54
|
+
if (!normalized) return null;
|
|
55
|
+
try {
|
|
56
|
+
const parsed = new URL(normalized);
|
|
57
|
+
const parts = parsed.pathname.split('/').filter(Boolean);
|
|
58
|
+
if (parts.length < 2) return null;
|
|
59
|
+
return { owner: parts[0], repo: parts[1] };
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function getGithubRepoName(repoUrl) {
|
|
66
|
+
const ownerRepo = parseGithubOwnerRepo(repoUrl);
|
|
67
|
+
return ownerRepo?.repo || null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function getGithubHeaders(extra = {}) {
|
|
71
|
+
const headers = {
|
|
72
|
+
Accept: 'application/vnd.github+json',
|
|
73
|
+
'User-Agent': 'BlockMine',
|
|
74
|
+
...extra
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if (process.env.GITHUB_TOKEN) {
|
|
78
|
+
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return headers;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function fetchJsonWithTimeout(url, timeoutMs = 7000) {
|
|
85
|
+
const controller = new AbortController();
|
|
86
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
87
|
+
try {
|
|
88
|
+
const response = await fetch(url, { headers: getGithubHeaders(), signal: controller.signal });
|
|
89
|
+
if (!response.ok) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return response.json();
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
} finally {
|
|
96
|
+
clearTimeout(timeoutId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function fetchGithubResponseWithTimeout(url, options = {}, timeoutMs = 10000) {
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
...options,
|
|
107
|
+
headers: getGithubHeaders(options.headers || {}),
|
|
108
|
+
signal: controller.signal
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
const error = new Error(`GitHub request failed with status ${response.status}.`);
|
|
113
|
+
error.status = response.status;
|
|
114
|
+
throw error;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return response;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error.name === 'AbortError') {
|
|
120
|
+
const timeoutError = new Error('GitHub request timed out.');
|
|
121
|
+
timeoutError.name = 'AbortError';
|
|
122
|
+
throw timeoutError;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
throw error;
|
|
126
|
+
} finally {
|
|
127
|
+
clearTimeout(timeoutId);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function fetchGithubRepoInfo(repoUrl) {
|
|
132
|
+
const ownerRepo = parseGithubOwnerRepo(repoUrl);
|
|
133
|
+
if (!ownerRepo) return null;
|
|
134
|
+
|
|
135
|
+
return fetchJsonWithTimeout(`https://api.github.com/repos/${ownerRepo.owner}/${ownerRepo.repo}`);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function downloadGithubArchive(repoUrl, ref) {
|
|
139
|
+
const ownerRepo = parseGithubOwnerRepo(repoUrl);
|
|
140
|
+
if (!ownerRepo) {
|
|
141
|
+
throw new Error('Invalid GitHub repository URL.');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const response = await fetchGithubResponseWithTimeout(
|
|
145
|
+
`https://api.github.com/repos/${ownerRepo.owner}/${ownerRepo.repo}/zipball/${encodeURIComponent(ref)}`,
|
|
146
|
+
{ headers: { Accept: 'application/vnd.github+json' } },
|
|
147
|
+
15000
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return response;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function fetchGithubPackageVersion(repoUrl, ref = null) {
|
|
154
|
+
const ownerRepo = parseGithubOwnerRepo(repoUrl);
|
|
155
|
+
if (!ownerRepo) return null;
|
|
156
|
+
|
|
157
|
+
const resolvedRef = ref || (await fetchGithubRepoInfo(repoUrl))?.default_branch;
|
|
158
|
+
if (!resolvedRef) return null;
|
|
159
|
+
|
|
160
|
+
const packageJsonData = await fetchJsonWithTimeout(
|
|
161
|
+
`https://api.github.com/repos/${ownerRepo.owner}/${ownerRepo.repo}/contents/package.json?ref=${encodeURIComponent(resolvedRef)}`
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
if (!packageJsonData?.content) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const packageJsonRaw = Buffer.from(packageJsonData.content, packageJsonData.encoding || 'base64').toString('utf8');
|
|
170
|
+
const packageJson = JSON.parse(packageJsonRaw);
|
|
171
|
+
return typeof packageJson?.version === 'string' ? packageJson.version : null;
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function fetchLatestGithubVersionTag(repoUrl) {
|
|
178
|
+
const ownerRepo = parseGithubOwnerRepo(repoUrl);
|
|
179
|
+
if (!ownerRepo) return null;
|
|
180
|
+
|
|
181
|
+
const releaseData = await fetchJsonWithTimeout(`https://api.github.com/repos/${ownerRepo.owner}/${ownerRepo.repo}/releases/latest`);
|
|
182
|
+
const releaseTag = typeof releaseData?.tag_name === 'string' ? releaseData.tag_name : null;
|
|
183
|
+
if (releaseTag) return releaseTag;
|
|
184
|
+
|
|
185
|
+
const tagsData = await fetchJsonWithTimeout(`https://api.github.com/repos/${ownerRepo.owner}/${ownerRepo.repo}/tags?per_page=20`);
|
|
186
|
+
if (!Array.isArray(tagsData) || tagsData.length === 0) return null;
|
|
187
|
+
|
|
188
|
+
const semverTags = tagsData
|
|
189
|
+
.map(item => item?.name)
|
|
190
|
+
.filter(Boolean)
|
|
191
|
+
.map(tag => ({ tag, normalized: semver.coerce(tag)?.version || null }))
|
|
192
|
+
.filter(item => item.normalized);
|
|
193
|
+
|
|
194
|
+
if (semverTags.length > 0) {
|
|
195
|
+
semverTags.sort((a, b) => semver.rcompare(a.normalized, b.normalized));
|
|
196
|
+
return semverTags[0].tag;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const firstTag = tagsData[0]?.name;
|
|
200
|
+
return typeof firstTag === 'string' ? firstTag : null;
|
|
201
|
+
}
|
|
17
202
|
|
|
18
203
|
function reportPluginDownload(pluginName) {
|
|
19
204
|
if (!TELEMETRY_ENABLED) return;
|
|
@@ -188,6 +373,8 @@ class PluginManager {
|
|
|
188
373
|
async installFromGithub(botId, repoUrl, prismaClient = prisma, isUpdate = false, tag = null) {
|
|
189
374
|
const botPluginsDir = path.join(PLUGINS_BASE_DIR, `bot_${botId}`);
|
|
190
375
|
await fse.mkdir(botPluginsDir, { recursive: true });
|
|
376
|
+
let sourceRefType = tag ? 'tag' : 'branch';
|
|
377
|
+
let sourceRef = tag || 'main';
|
|
191
378
|
|
|
192
379
|
if (!isUpdate) {
|
|
193
380
|
const existing = await prismaClient.installedPlugin.findFirst({ where: { botId, sourceUri: repoUrl } });
|
|
@@ -197,32 +384,30 @@ class PluginManager {
|
|
|
197
384
|
try {
|
|
198
385
|
const url = new URL(repoUrl);
|
|
199
386
|
const repoPath = url.pathname.replace(/^\/|\.git$/g, '');
|
|
387
|
+
const repoInfo = tag ? null : await fetchGithubRepoInfo(repoUrl);
|
|
388
|
+
if (repoInfo?.default_branch) {
|
|
389
|
+
sourceRef = repoInfo.default_branch;
|
|
390
|
+
}
|
|
200
391
|
|
|
201
392
|
let response;
|
|
202
393
|
|
|
203
|
-
// Если указан тег - скачиваем конкретный релиз
|
|
204
394
|
if (tag) {
|
|
205
|
-
const archiveUrlTag = `https://github.com/${repoPath}/archive/refs/tags/${encodeURIComponent(tag)}.zip`;
|
|
206
395
|
console.log(`[PluginManager] Скачиваем релиз ${tag} из ${repoUrl}...`);
|
|
207
396
|
try {
|
|
208
|
-
response = await
|
|
397
|
+
response = await downloadGithubArchive(repoUrl, tag);
|
|
209
398
|
} catch (err) {
|
|
210
399
|
throw new Error(`Ошибка сети при скачивании релиза ${tag}: ${err.message || err}`);
|
|
211
400
|
}
|
|
212
|
-
if (!response.ok) {
|
|
213
|
-
throw new Error(`Не удалось скачать релиз ${tag}. Статус: ${response.status}. Возможно, тег не существует.`);
|
|
214
|
-
}
|
|
215
401
|
} else {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
throw new Error(`Не удалось скачать архив плагина. Статус: ${response.status}`);
|
|
402
|
+
try {
|
|
403
|
+
response = await downloadGithubArchive(repoUrl, sourceRef);
|
|
404
|
+
} catch (err) {
|
|
405
|
+
if (!repoInfo && sourceRef !== 'master') {
|
|
406
|
+
console.log(`[PluginManager] Ветка '${sourceRef}' не найдена для ${repoUrl}, пробую 'master'...`);
|
|
407
|
+
sourceRef = 'master';
|
|
408
|
+
response = await downloadGithubArchive(repoUrl, sourceRef);
|
|
409
|
+
} else {
|
|
410
|
+
throw new Error(`Не удалось скачать архив плагина: ${err.message || err}`);
|
|
226
411
|
}
|
|
227
412
|
}
|
|
228
413
|
}
|
|
@@ -264,7 +449,10 @@ class PluginManager {
|
|
|
264
449
|
depCheck.warnings.forEach(w => console.warn(`[PluginManager] ⚠️ ${w}`));
|
|
265
450
|
}
|
|
266
451
|
|
|
267
|
-
const newPlugin = await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient
|
|
452
|
+
const newPlugin = await this.registerPlugin(botId, localPath, 'GITHUB', repoUrl, prismaClient, {
|
|
453
|
+
sourceRefType,
|
|
454
|
+
sourceRef
|
|
455
|
+
});
|
|
268
456
|
|
|
269
457
|
reportPluginDownload(packageJson.name);
|
|
270
458
|
|
|
@@ -285,7 +473,7 @@ class PluginManager {
|
|
|
285
473
|
}
|
|
286
474
|
}
|
|
287
475
|
|
|
288
|
-
async registerPlugin(botId, directoryPath, sourceType, sourceUri, prismaClient = prisma) {
|
|
476
|
+
async registerPlugin(botId, directoryPath, sourceType, sourceUri, prismaClient = prisma, extraData = {}) {
|
|
289
477
|
const packageJsonPath = path.join(directoryPath, 'package.json');
|
|
290
478
|
let packageJson;
|
|
291
479
|
try {
|
|
@@ -307,6 +495,7 @@ class PluginManager {
|
|
|
307
495
|
sourceType,
|
|
308
496
|
sourceUri: sourceUri || directoryPath,
|
|
309
497
|
manifest: JSON.stringify(packageJson.botpanel || {}),
|
|
498
|
+
...extraData,
|
|
310
499
|
};
|
|
311
500
|
|
|
312
501
|
return prismaClient.installedPlugin.upsert({
|
|
@@ -397,24 +586,76 @@ class PluginManager {
|
|
|
397
586
|
where: { botId, sourceType: 'GITHUB' }
|
|
398
587
|
});
|
|
399
588
|
const updatesAvailable = [];
|
|
400
|
-
const
|
|
589
|
+
const catalogMapByRepo = new Map();
|
|
590
|
+
const catalogMapByName = new Map();
|
|
591
|
+
const catalogMapByRepoName = new Map();
|
|
592
|
+
const latestTagCache = new Map();
|
|
593
|
+
|
|
594
|
+
for (const item of catalog) {
|
|
595
|
+
const normalizedRepoUrl = normalizeGithubRepoUrl(item.repoUrl);
|
|
596
|
+
if (normalizedRepoUrl) {
|
|
597
|
+
catalogMapByRepo.set(normalizedRepoUrl, item);
|
|
598
|
+
const repoName = getGithubRepoName(normalizedRepoUrl);
|
|
599
|
+
if (repoName && !catalogMapByRepoName.has(repoName)) {
|
|
600
|
+
catalogMapByRepoName.set(repoName, item);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (item?.name) {
|
|
604
|
+
catalogMapByName.set(String(item.name).toLowerCase(), item);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
401
607
|
|
|
402
608
|
for (const plugin of githubPlugins) {
|
|
403
609
|
try {
|
|
404
|
-
const
|
|
405
|
-
|
|
610
|
+
const normalizedSourceUri = normalizeGithubRepoUrl(plugin.sourceUri);
|
|
611
|
+
const repoName = getGithubRepoName(normalizedSourceUri || plugin.sourceUri);
|
|
612
|
+
const catalogInfo =
|
|
613
|
+
(normalizedSourceUri ? catalogMapByRepo.get(normalizedSourceUri) : null) ||
|
|
614
|
+
catalogMapByName.get(String(plugin.name).toLowerCase()) ||
|
|
615
|
+
(repoName ? catalogMapByRepoName.get(repoName) : null);
|
|
616
|
+
|
|
617
|
+
const targetRepoUrl = catalogInfo?.repoUrl || normalizedSourceUri || normalizeGithubRepoUrl(plugin.sourceUri) || plugin.sourceUri;
|
|
618
|
+
let latestTagRaw =
|
|
619
|
+
catalogInfo?.latestTag ||
|
|
620
|
+
catalogInfo?.recommendedVersion ||
|
|
621
|
+
catalogInfo?.version ||
|
|
622
|
+
catalogInfo?.latestVersion ||
|
|
623
|
+
catalogInfo?.tag;
|
|
624
|
+
|
|
625
|
+
if (!latestTagRaw) {
|
|
626
|
+
const cacheKey = targetRepoUrl || plugin.name;
|
|
627
|
+
if (latestTagCache.has(cacheKey)) {
|
|
628
|
+
latestTagRaw = latestTagCache.get(cacheKey);
|
|
629
|
+
} else {
|
|
630
|
+
const fetchedTag = targetRepoUrl ? await fetchLatestGithubVersionTag(targetRepoUrl) : null;
|
|
631
|
+
latestTagCache.set(cacheKey, fetchedTag);
|
|
632
|
+
latestTagRaw = fetchedTag;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
let latestVersionRaw = latestTagRaw;
|
|
637
|
+
if (!latestVersionRaw && targetRepoUrl) {
|
|
638
|
+
latestVersionRaw = await fetchGithubPackageVersion(
|
|
639
|
+
targetRepoUrl,
|
|
640
|
+
plugin.sourceRefType === 'branch' ? plugin.sourceRef : null
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
if (!latestVersionRaw) continue;
|
|
406
645
|
|
|
407
|
-
const
|
|
408
|
-
const
|
|
646
|
+
const localSemver = semver.coerce(plugin.version);
|
|
647
|
+
const remoteSemver = semver.coerce(latestVersionRaw);
|
|
648
|
+
if (!localSemver || !remoteSemver) continue;
|
|
409
649
|
|
|
410
|
-
if (semver.gt(
|
|
650
|
+
if (semver.gt(remoteSemver.version, localSemver.version)) {
|
|
411
651
|
updatesAvailable.push({
|
|
412
652
|
id: plugin.id,
|
|
413
653
|
name: plugin.name,
|
|
414
654
|
sourceUri: plugin.sourceUri,
|
|
415
|
-
currentVersion:
|
|
416
|
-
recommendedVersion:
|
|
417
|
-
latestTag: catalogInfo
|
|
655
|
+
currentVersion: localSemver.version,
|
|
656
|
+
recommendedVersion: remoteSemver.version,
|
|
657
|
+
latestTag: catalogInfo?.latestTag || latestTagRaw || null,
|
|
658
|
+
targetRepoUrl,
|
|
418
659
|
});
|
|
419
660
|
}
|
|
420
661
|
} catch (error) {
|
|
@@ -424,7 +665,7 @@ class PluginManager {
|
|
|
424
665
|
return updatesAvailable;
|
|
425
666
|
}
|
|
426
667
|
|
|
427
|
-
async updatePlugin(pluginId, targetTag = null) {
|
|
668
|
+
async updatePlugin(pluginId, targetTag = null, targetRepoUrl = null) {
|
|
428
669
|
const plugin = await prisma.installedPlugin.findUnique({ where: { id: pluginId } });
|
|
429
670
|
if (!plugin || plugin.sourceType !== 'GITHUB') {
|
|
430
671
|
throw new Error('Плагин не найден или не является GitHub-плагином.');
|
|
@@ -432,7 +673,7 @@ class PluginManager {
|
|
|
432
673
|
|
|
433
674
|
console.log(`[PluginManager] Начало обновления плагина ${plugin.name}${targetTag ? ` до версии ${targetTag}` : ''}...`);
|
|
434
675
|
|
|
435
|
-
const repoUrl = plugin.sourceUri;
|
|
676
|
+
const repoUrl = targetRepoUrl || plugin.sourceUri;
|
|
436
677
|
const botId = plugin.botId;
|
|
437
678
|
const oldVersion = plugin.version;
|
|
438
679
|
|
|
@@ -654,4 +895,4 @@ class PluginManager {
|
|
|
654
895
|
}
|
|
655
896
|
}
|
|
656
897
|
|
|
657
|
-
module.exports = PluginManager;
|
|
898
|
+
module.exports = PluginManager;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const { ConfigurationError } = require('../errors');
|
|
2
|
+
|
|
3
|
+
const HEX_64_REGEX = /^[0-9a-fA-F]{64}$/;
|
|
4
|
+
const VALID_NODE_ENVS = ['development', 'production', 'test'];
|
|
5
|
+
const VALID_LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'];
|
|
6
|
+
|
|
7
|
+
class ConfigValidator {
|
|
8
|
+
validate(config) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
|
|
11
|
+
if (!config || typeof config !== 'object') {
|
|
12
|
+
errors.push('config.errors.config_required');
|
|
13
|
+
return { valid: false, errors };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const server = config.server || {};
|
|
17
|
+
const security = config.security || {};
|
|
18
|
+
|
|
19
|
+
if (!server.host || typeof server.host !== 'string') {
|
|
20
|
+
errors.push('config.errors.server_host_required');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const port = server.port;
|
|
24
|
+
if (port === undefined || port === null) {
|
|
25
|
+
errors.push('config.errors.server_port_required');
|
|
26
|
+
} else if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
27
|
+
errors.push('config.errors.server_port_invalid');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!security.jwtSecret || typeof security.jwtSecret !== 'string') {
|
|
31
|
+
errors.push('config.errors.jwt_secret_required');
|
|
32
|
+
} else if (security.jwtSecret.length < 32) {
|
|
33
|
+
errors.push('config.errors.jwt_secret_too_short');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!security.encryptionKey || typeof security.encryptionKey !== 'string') {
|
|
37
|
+
errors.push('config.errors.encryption_key_required');
|
|
38
|
+
} else if (!HEX_64_REGEX.test(security.encryptionKey)) {
|
|
39
|
+
errors.push('config.errors.encryption_key_invalid');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { valid: errors.length === 0, errors };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
validateEnv() {
|
|
46
|
+
const errors = [];
|
|
47
|
+
const warnings = [];
|
|
48
|
+
|
|
49
|
+
const nodeEnv = process.env.NODE_ENV;
|
|
50
|
+
if (nodeEnv !== undefined && !VALID_NODE_ENVS.includes(nodeEnv)) {
|
|
51
|
+
errors.push('config.errors.node_env_invalid');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const logLevel = process.env.LOG_LEVEL;
|
|
55
|
+
if (logLevel !== undefined && !VALID_LOG_LEVELS.includes(logLevel)) {
|
|
56
|
+
errors.push('config.errors.log_level_invalid');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
validateAndThrow(config) {
|
|
63
|
+
const result = this.validate(config);
|
|
64
|
+
if (!result.valid) {
|
|
65
|
+
throw new ConfigurationError('config.errors.invalid_configuration', {
|
|
66
|
+
context: { errors: result.errors },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = ConfigValidator;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const DEFAULTS = {
|
|
2
|
+
useNewDependencyResolver: true,
|
|
3
|
+
useStructuredLogger: true,
|
|
4
|
+
useErrorHandler: true,
|
|
5
|
+
useInputValidation: true,
|
|
6
|
+
useRateLimiter: false,
|
|
7
|
+
useCircuitBreaker: false,
|
|
8
|
+
useMetrics: false,
|
|
9
|
+
useAuditLog: false,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
class FeatureFlags {
|
|
13
|
+
constructor(overrides = {}) {
|
|
14
|
+
this._flags = { ...DEFAULTS, ...this._loadFromEnv(), ...overrides };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
_loadFromEnv() {
|
|
18
|
+
const flags = {};
|
|
19
|
+
for (const key of Object.keys(DEFAULTS)) {
|
|
20
|
+
const envKey = `FEATURE_${key.replace(/([A-Z])/g, '_$1').toUpperCase()}`;
|
|
21
|
+
if (process.env[envKey] !== undefined) {
|
|
22
|
+
flags[key] = process.env[envKey] === 'true';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return flags;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
isEnabled(flag) {
|
|
29
|
+
return this._flags[flag] === true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
enable(flag) {
|
|
33
|
+
this._flags[flag] = true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
disable(flag) {
|
|
37
|
+
this._flags[flag] = false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
getAll() {
|
|
41
|
+
return { ...this._flags };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let instance = null;
|
|
46
|
+
|
|
47
|
+
function getFeatureFlags() {
|
|
48
|
+
if (!instance) instance = new FeatureFlags();
|
|
49
|
+
return instance;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { FeatureFlags, getFeatureFlags };
|