blockmine 1.25.0 → 1.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +46 -3
  2. package/backend/cli.js +1 -1
  3. package/backend/package.json +2 -2
  4. package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
  5. package/backend/prisma/migrations/migration_lock.toml +2 -2
  6. package/backend/prisma/schema.prisma +2 -0
  7. package/backend/src/api/routes/apiKeys.js +8 -0
  8. package/backend/src/api/routes/bots.js +258 -9
  9. package/backend/src/api/routes/eventGraphs.js +151 -1
  10. package/backend/src/api/routes/health.js +38 -0
  11. package/backend/src/api/routes/nodeRegistry.js +63 -0
  12. package/backend/src/api/routes/plugins.js +254 -29
  13. package/backend/src/container.js +11 -8
  14. package/backend/src/core/BotCommandLoader.js +161 -0
  15. package/backend/src/core/BotConnection.js +125 -0
  16. package/backend/src/core/BotEventHandlers.js +234 -0
  17. package/backend/src/core/BotIPCHandler.js +445 -0
  18. package/backend/src/core/BotManager.js +15 -7
  19. package/backend/src/core/BotProcess.js +75 -142
  20. package/backend/src/core/EventGraphManager.js +7 -3
  21. package/backend/src/core/GraphDebugHandler.js +229 -0
  22. package/backend/src/core/GraphDebugIPC.js +117 -0
  23. package/backend/src/core/GraphExecutionEngine.js +545 -978
  24. package/backend/src/core/GraphTraversal.js +80 -0
  25. package/backend/src/core/GraphValidation.js +73 -0
  26. package/backend/src/core/NodeDefinition.js +138 -0
  27. package/backend/src/core/NodeRegistry.js +153 -141
  28. package/backend/src/core/PluginManager.js +272 -31
  29. package/backend/src/core/RewindSignal.js +9 -0
  30. package/backend/src/core/config/ConfigValidator.js +72 -0
  31. package/backend/src/core/config/FeatureFlags.js +52 -0
  32. package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
  33. package/backend/src/core/domain/entities/Bot.js +39 -0
  34. package/backend/src/core/domain/entities/Command.js +41 -0
  35. package/backend/src/core/domain/entities/EventGraph.js +39 -0
  36. package/backend/src/core/domain/entities/Plugin.js +45 -0
  37. package/backend/src/core/domain/entities/User.js +40 -0
  38. package/backend/src/core/domain/services/DependencyResolver.js +168 -0
  39. package/backend/src/core/domain/services/GraphValidator.js +117 -0
  40. package/backend/src/core/domain/services/PermissionChecker.js +34 -0
  41. package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
  42. package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
  43. package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
  44. package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
  45. package/backend/src/core/errors/BaseError.js +29 -0
  46. package/backend/src/core/errors/ErrorHandler.js +81 -0
  47. package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
  48. package/backend/src/core/errors/index.js +68 -0
  49. package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
  50. package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
  51. package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
  52. package/backend/src/core/infrastructure/RateLimiter.js +64 -0
  53. package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
  54. package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
  55. package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
  56. package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
  57. package/backend/src/core/ipc/botApiFactory.js +72 -0
  58. package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
  59. package/backend/src/core/logging/AuditLogger.js +61 -0
  60. package/backend/src/core/logging/StructuredLogger.js +80 -0
  61. package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
  62. package/backend/src/core/logging/index.js +7 -0
  63. package/backend/src/core/metrics/MetricsCollector.js +104 -0
  64. package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
  65. package/backend/src/core/node-registries/actionsNodes.js +191 -0
  66. package/backend/src/core/node-registries/arraysNodes.js +152 -0
  67. package/backend/src/core/node-registries/botNodes.js +48 -0
  68. package/backend/src/core/node-registries/containerNodes.js +141 -0
  69. package/backend/src/core/node-registries/dataNodes.js +284 -0
  70. package/backend/src/core/node-registries/debugNodes.js +23 -0
  71. package/backend/src/core/node-registries/eventsNodes.js +223 -0
  72. package/backend/src/core/node-registries/flowNodes.js +151 -0
  73. package/backend/src/core/node-registries/furnaceNodes.js +123 -0
  74. package/backend/src/core/node-registries/index.js +108 -0
  75. package/backend/src/core/node-registries/inventory.js +102 -106
  76. package/backend/src/core/node-registries/logicNodes.js +54 -0
  77. package/backend/src/core/node-registries/mathNodes.js +38 -0
  78. package/backend/src/core/node-registries/navigationNodes.js +109 -0
  79. package/backend/src/core/node-registries/objectsNodes.js +90 -0
  80. package/backend/src/core/node-registries/stringsNodes.js +165 -0
  81. package/backend/src/core/node-registries/timeNodes.js +105 -0
  82. package/backend/src/core/node-registries/typeNodes.js +22 -0
  83. package/backend/src/core/node-registries/usersNodes.js +126 -0
  84. package/backend/src/core/nodes/arrays/shuffle.js +14 -0
  85. package/backend/src/core/nodes/bot/get_name.js +8 -0
  86. package/backend/src/core/nodes/bot/stop_bot.js +5 -0
  87. package/backend/src/core/nodes/container/open.js +101 -111
  88. package/backend/src/core/nodes/data/store_read.js +26 -0
  89. package/backend/src/core/nodes/data/store_write.js +23 -0
  90. package/backend/src/core/nodes/event/call_event.js +31 -0
  91. package/backend/src/core/nodes/event/custom_event.js +8 -0
  92. package/backend/src/core/nodes/flow/timer.js +35 -0
  93. package/backend/src/core/nodes/inventory/drop.js +73 -65
  94. package/backend/src/core/nodes/inventory/equip.js +54 -45
  95. package/backend/src/core/nodes/inventory/select_slot.js +48 -46
  96. package/backend/src/core/nodes/navigation/follow.js +54 -51
  97. package/backend/src/core/nodes/navigation/go_to.js +41 -53
  98. package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
  99. package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
  100. package/backend/src/core/nodes/navigation/stop.js +17 -26
  101. package/backend/src/core/nodes/users/add_to_group.js +24 -0
  102. package/backend/src/core/nodes/users/check_permission.js +26 -0
  103. package/backend/src/core/nodes/users/remove_from_group.js +24 -0
  104. package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
  105. package/backend/src/core/services/BotLifecycleService.js +41 -632
  106. package/backend/src/core/services/CacheManager.js +83 -23
  107. package/backend/src/core/services/CrashRestartManager.js +42 -0
  108. package/backend/src/core/services/DebugSessionManager.js +114 -12
  109. package/backend/src/core/services/EventGraphService.js +69 -0
  110. package/backend/src/core/services/MinecraftBotManager.js +9 -1
  111. package/backend/src/core/services/PluginManagementService.js +84 -0
  112. package/backend/src/core/services/TestModeContext.js +65 -0
  113. package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
  114. package/backend/src/core/services.js +1 -11
  115. package/backend/src/core/validation/InputValidator.js +167 -0
  116. package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
  117. package/backend/src/real-time/botApi/index.js +1 -1
  118. package/backend/src/real-time/socketHandler.js +26 -0
  119. package/backend/src/server.js +10 -5
  120. package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
  121. package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
  122. package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
  123. package/frontend/dist/index.html +2 -2
  124. package/frontend/dist/locales/en/bots.json +4 -1
  125. package/frontend/dist/locales/en/common.json +7 -1
  126. package/frontend/dist/locales/en/login.json +2 -0
  127. package/frontend/dist/locales/en/management.json +79 -1
  128. package/frontend/dist/locales/en/nodes.json +59 -4
  129. package/frontend/dist/locales/en/plugin-detail.json +24 -4
  130. package/frontend/dist/locales/en/plugins.json +226 -7
  131. package/frontend/dist/locales/en/setup.json +2 -0
  132. package/frontend/dist/locales/en/sidebar.json +171 -3
  133. package/frontend/dist/locales/en/visual-editor.json +230 -31
  134. package/frontend/dist/locales/ru/bots.json +4 -1
  135. package/frontend/dist/locales/ru/login.json +2 -0
  136. package/frontend/dist/locales/ru/management.json +79 -1
  137. package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
  138. package/frontend/dist/locales/ru/nodes.json +105 -51
  139. package/frontend/dist/locales/ru/plugins.json +103 -4
  140. package/frontend/dist/locales/ru/setup.json +2 -0
  141. package/frontend/dist/locales/ru/sidebar.json +171 -3
  142. package/frontend/dist/locales/ru/visual-editor.json +232 -33
  143. package/frontend/package.json +2 -0
  144. package/nul +12 -0
  145. package/package.json +3 -3
  146. package/backend/package-lock.json +0 -6801
  147. package/backend/src/core/node-registries/actions.js +0 -202
  148. package/backend/src/core/node-registries/arrays.js +0 -155
  149. package/backend/src/core/node-registries/bot.js +0 -23
  150. package/backend/src/core/node-registries/container.js +0 -162
  151. package/backend/src/core/node-registries/data.js +0 -290
  152. package/backend/src/core/node-registries/debug.js +0 -26
  153. package/backend/src/core/node-registries/events.js +0 -201
  154. package/backend/src/core/node-registries/flow.js +0 -139
  155. package/backend/src/core/node-registries/furnace.js +0 -143
  156. package/backend/src/core/node-registries/logic.js +0 -62
  157. package/backend/src/core/node-registries/math.js +0 -42
  158. package/backend/src/core/node-registries/navigation.js +0 -111
  159. package/backend/src/core/node-registries/objects.js +0 -98
  160. package/backend/src/core/node-registries/strings.js +0 -187
  161. package/backend/src/core/node-registries/time.js +0 -113
  162. package/backend/src/core/node-registries/type.js +0 -25
  163. package/backend/src/core/node-registries/users.js +0 -79
  164. package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
@@ -0,0 +1,232 @@
1
+ const ConfigValidator = require('../ConfigValidator');
2
+ const { ConfigurationError } = require('../../errors');
3
+
4
+ const VALID_CONFIG = {
5
+ server: {
6
+ host: '127.0.0.1',
7
+ port: 3001,
8
+ },
9
+ security: {
10
+ jwtSecret: 'a'.repeat(32),
11
+ encryptionKey: 'a'.repeat(64),
12
+ },
13
+ };
14
+
15
+ describe('ConfigValidator', () => {
16
+ let validator;
17
+
18
+ beforeEach(() => {
19
+ validator = new ConfigValidator();
20
+ });
21
+
22
+ describe('validate()', () => {
23
+ it('returns valid for a correct config', () => {
24
+ const result = validator.validate(VALID_CONFIG);
25
+ expect(result.valid).toBe(true);
26
+ expect(result.errors).toHaveLength(0);
27
+ });
28
+
29
+ it('returns error when config is null', () => {
30
+ const result = validator.validate(null);
31
+ expect(result.valid).toBe(false);
32
+ expect(result.errors).toContain('config.errors.config_required');
33
+ });
34
+
35
+ it('returns error when server.host is missing', () => {
36
+ const config = { ...VALID_CONFIG, server: { ...VALID_CONFIG.server, host: undefined } };
37
+ const result = validator.validate(config);
38
+ expect(result.valid).toBe(false);
39
+ expect(result.errors).toContain('config.errors.server_host_required');
40
+ });
41
+
42
+ it('returns error when server.host is not a string', () => {
43
+ const config = { ...VALID_CONFIG, server: { ...VALID_CONFIG.server, host: 123 } };
44
+ const result = validator.validate(config);
45
+ expect(result.valid).toBe(false);
46
+ expect(result.errors).toContain('config.errors.server_host_required');
47
+ });
48
+
49
+ it('returns error when server.port is missing', () => {
50
+ const config = { ...VALID_CONFIG, server: { host: '127.0.0.1' } };
51
+ const result = validator.validate(config);
52
+ expect(result.valid).toBe(false);
53
+ expect(result.errors).toContain('config.errors.server_port_required');
54
+ });
55
+
56
+ it('returns error when server.port is 0', () => {
57
+ const config = { ...VALID_CONFIG, server: { ...VALID_CONFIG.server, port: 0 } };
58
+ const result = validator.validate(config);
59
+ expect(result.valid).toBe(false);
60
+ expect(result.errors).toContain('config.errors.server_port_invalid');
61
+ });
62
+
63
+ it('returns error when server.port is 65536', () => {
64
+ const config = { ...VALID_CONFIG, server: { ...VALID_CONFIG.server, port: 65536 } };
65
+ const result = validator.validate(config);
66
+ expect(result.valid).toBe(false);
67
+ expect(result.errors).toContain('config.errors.server_port_invalid');
68
+ });
69
+
70
+ it('returns error when server.port is a float', () => {
71
+ const config = { ...VALID_CONFIG, server: { ...VALID_CONFIG.server, port: 3000.5 } };
72
+ const result = validator.validate(config);
73
+ expect(result.valid).toBe(false);
74
+ expect(result.errors).toContain('config.errors.server_port_invalid');
75
+ });
76
+
77
+ it('accepts port 1 and port 65535 as valid boundaries', () => {
78
+ const config1 = { ...VALID_CONFIG, server: { ...VALID_CONFIG.server, port: 1 } };
79
+ const config2 = { ...VALID_CONFIG, server: { ...VALID_CONFIG.server, port: 65535 } };
80
+ expect(validator.validate(config1).valid).toBe(true);
81
+ expect(validator.validate(config2).valid).toBe(true);
82
+ });
83
+
84
+ it('returns error when jwtSecret is missing', () => {
85
+ const config = { ...VALID_CONFIG, security: { ...VALID_CONFIG.security, jwtSecret: undefined } };
86
+ const result = validator.validate(config);
87
+ expect(result.valid).toBe(false);
88
+ expect(result.errors).toContain('config.errors.jwt_secret_required');
89
+ });
90
+
91
+ it('returns error when jwtSecret is shorter than 32 chars', () => {
92
+ const config = { ...VALID_CONFIG, security: { ...VALID_CONFIG.security, jwtSecret: 'short' } };
93
+ const result = validator.validate(config);
94
+ expect(result.valid).toBe(false);
95
+ expect(result.errors).toContain('config.errors.jwt_secret_too_short');
96
+ });
97
+
98
+ it('accepts jwtSecret of exactly 32 chars', () => {
99
+ const config = { ...VALID_CONFIG, security: { ...VALID_CONFIG.security, jwtSecret: 'a'.repeat(32) } };
100
+ expect(validator.validate(config).valid).toBe(true);
101
+ });
102
+
103
+ it('returns error when encryptionKey is missing', () => {
104
+ const config = { ...VALID_CONFIG, security: { ...VALID_CONFIG.security, encryptionKey: undefined } };
105
+ const result = validator.validate(config);
106
+ expect(result.valid).toBe(false);
107
+ expect(result.errors).toContain('config.errors.encryption_key_required');
108
+ });
109
+
110
+ it('returns error when encryptionKey is not 64 hex chars', () => {
111
+ const config = { ...VALID_CONFIG, security: { ...VALID_CONFIG.security, encryptionKey: 'abc123' } };
112
+ const result = validator.validate(config);
113
+ expect(result.valid).toBe(false);
114
+ expect(result.errors).toContain('config.errors.encryption_key_invalid');
115
+ });
116
+
117
+ it('returns error when encryptionKey contains non-hex chars', () => {
118
+ const config = { ...VALID_CONFIG, security: { ...VALID_CONFIG.security, encryptionKey: 'z'.repeat(64) } };
119
+ const result = validator.validate(config);
120
+ expect(result.valid).toBe(false);
121
+ expect(result.errors).toContain('config.errors.encryption_key_invalid');
122
+ });
123
+
124
+ it('accepts valid 64-char lowercase hex encryptionKey', () => {
125
+ const config = { ...VALID_CONFIG, security: { ...VALID_CONFIG.security, encryptionKey: 'deadbeef'.repeat(8) } };
126
+ expect(validator.validate(config).valid).toBe(true);
127
+ });
128
+
129
+ it('collects multiple errors at once', () => {
130
+ const config = { server: {}, security: {} };
131
+ const result = validator.validate(config);
132
+ expect(result.valid).toBe(false);
133
+ expect(result.errors.length).toBeGreaterThan(1);
134
+ });
135
+ });
136
+
137
+ describe('validateEnv()', () => {
138
+ const originalEnv = process.env;
139
+
140
+ beforeEach(() => {
141
+ process.env = { ...originalEnv };
142
+ });
143
+
144
+ afterEach(() => {
145
+ process.env = originalEnv;
146
+ });
147
+
148
+ it('returns valid when no optional env vars are set', () => {
149
+ delete process.env.NODE_ENV;
150
+ delete process.env.LOG_LEVEL;
151
+ delete process.env.DATABASE_URL;
152
+ delete process.env.GITHUB_TOKEN;
153
+ const result = validator.validateEnv();
154
+ expect(result.valid).toBe(true);
155
+ expect(result.errors).toHaveLength(0);
156
+ expect(result.warnings).toHaveLength(0);
157
+ });
158
+
159
+ it('accepts valid NODE_ENV values', () => {
160
+ for (const env of ['development', 'production', 'test']) {
161
+ process.env.NODE_ENV = env;
162
+ expect(validator.validateEnv().valid).toBe(true);
163
+ }
164
+ });
165
+
166
+ it('returns error for invalid NODE_ENV', () => {
167
+ process.env.NODE_ENV = 'staging';
168
+ const result = validator.validateEnv();
169
+ expect(result.valid).toBe(false);
170
+ expect(result.errors).toContain('config.errors.node_env_invalid');
171
+ });
172
+
173
+ it('accepts valid LOG_LEVEL values', () => {
174
+ for (const level of ['debug', 'info', 'warn', 'error', 'fatal']) {
175
+ process.env.LOG_LEVEL = level;
176
+ expect(validator.validateEnv().valid).toBe(true);
177
+ }
178
+ });
179
+
180
+ it('returns error for invalid LOG_LEVEL', () => {
181
+ process.env.LOG_LEVEL = 'verbose';
182
+ const result = validator.validateEnv();
183
+ expect(result.valid).toBe(false);
184
+ expect(result.errors).toContain('config.errors.log_level_invalid');
185
+ });
186
+
187
+ it('accepts DATABASE_URL as optional string', () => {
188
+ process.env.DATABASE_URL = 'file:./dev.db';
189
+ const result = validator.validateEnv();
190
+ expect(result.valid).toBe(true);
191
+ });
192
+
193
+ it('accepts GITHUB_TOKEN as optional string', () => {
194
+ process.env.GITHUB_TOKEN = 'ghp_sometoken';
195
+ const result = validator.validateEnv();
196
+ expect(result.valid).toBe(true);
197
+ });
198
+
199
+ it('returns result with warnings array', () => {
200
+ const result = validator.validateEnv();
201
+ expect(Array.isArray(result.warnings)).toBe(true);
202
+ });
203
+ });
204
+
205
+ describe('validateAndThrow()', () => {
206
+ it('does not throw for valid config', () => {
207
+ expect(() => validator.validateAndThrow(VALID_CONFIG)).not.toThrow();
208
+ });
209
+
210
+ it('throws ConfigurationError for invalid config', () => {
211
+ expect(() => validator.validateAndThrow(null)).toThrow(ConfigurationError);
212
+ });
213
+
214
+ it('throws ConfigurationError with errors in context', () => {
215
+ try {
216
+ validator.validateAndThrow({ server: {}, security: {} });
217
+ } catch (err) {
218
+ expect(err).toBeInstanceOf(ConfigurationError);
219
+ expect(err.context.errors).toBeDefined();
220
+ expect(err.context.errors.length).toBeGreaterThan(0);
221
+ }
222
+ });
223
+
224
+ it('thrown error has correct messageKey', () => {
225
+ try {
226
+ validator.validateAndThrow(null);
227
+ } catch (err) {
228
+ expect(err.messageKey).toBe('config.errors.invalid_configuration');
229
+ }
230
+ });
231
+ });
232
+ });
@@ -0,0 +1,39 @@
1
+ class Bot {
2
+ constructor({ id, username, password, prefix, note, owners, serverId, proxyId, sortOrder, createdAt, updatedAt } = {}) {
3
+ this.id = id;
4
+ this.username = username;
5
+ this.password = password;
6
+ this.prefix = prefix || '@';
7
+ this.note = note || null;
8
+ this.owners = owners || '';
9
+ this.serverId = serverId;
10
+ this.proxyId = proxyId || null;
11
+ this.sortOrder = sortOrder || null;
12
+ this.createdAt = createdAt;
13
+ this.updatedAt = updatedAt;
14
+ }
15
+
16
+ getOwnerList() {
17
+ if (!this.owners) return [];
18
+ return this.owners.split(',').map(o => o.trim()).filter(Boolean);
19
+ }
20
+
21
+ isOwner(username) {
22
+ return this.getOwnerList().includes(username);
23
+ }
24
+
25
+ toJSON() {
26
+ return {
27
+ id: this.id,
28
+ username: this.username,
29
+ prefix: this.prefix,
30
+ note: this.note,
31
+ owners: this.owners,
32
+ serverId: this.serverId,
33
+ proxyId: this.proxyId,
34
+ sortOrder: this.sortOrder,
35
+ };
36
+ }
37
+ }
38
+
39
+ module.exports = Bot;
@@ -0,0 +1,41 @@
1
+ class Command {
2
+ constructor({ id, botId, name, isEnabled, cooldown, aliases, description, owner, permissionId, allowedChatTypes, isVisual, argumentsJson, graphJson, pluginOwnerId } = {}) {
3
+ this.id = id;
4
+ this.botId = botId;
5
+ this.name = name;
6
+ this.isEnabled = isEnabled !== false;
7
+ this.cooldown = cooldown || 0;
8
+ this.aliases = typeof aliases === 'string' ? JSON.parse(aliases || '[]') : (aliases || []);
9
+ this.description = description || null;
10
+ this.owner = owner || null;
11
+ this.permissionId = permissionId || null;
12
+ this.allowedChatTypes = typeof allowedChatTypes === 'string' ? JSON.parse(allowedChatTypes || '["chat","private"]') : (allowedChatTypes || ['chat', 'private']);
13
+ this.isVisual = isVisual || false;
14
+ this.arguments = typeof argumentsJson === 'string' ? JSON.parse(argumentsJson || '[]') : (argumentsJson || []);
15
+ this.graphJson = graphJson || null;
16
+ this.pluginOwnerId = pluginOwnerId || null;
17
+ }
18
+
19
+ isAllowedInChat(chatType) {
20
+ return this.allowedChatTypes.includes(chatType);
21
+ }
22
+
23
+ hasCooldown() {
24
+ return this.cooldown > 0;
25
+ }
26
+
27
+ toJSON() {
28
+ return {
29
+ id: this.id,
30
+ botId: this.botId,
31
+ name: this.name,
32
+ isEnabled: this.isEnabled,
33
+ cooldown: this.cooldown,
34
+ aliases: this.aliases,
35
+ description: this.description,
36
+ allowedChatTypes: this.allowedChatTypes,
37
+ };
38
+ }
39
+ }
40
+
41
+ module.exports = Command;
@@ -0,0 +1,39 @@
1
+ class EventGraph {
2
+ constructor({ id, botId, name, isEnabled, graphJson, variables, pluginOwnerId, createdAt, updatedAt } = {}) {
3
+ this.id = id;
4
+ this.botId = botId;
5
+ this.name = name;
6
+ this.isEnabled = isEnabled !== false;
7
+ this.graphJson = graphJson || null;
8
+ this.variables = typeof variables === 'string' ? JSON.parse(variables || '[]') : (variables || []);
9
+ this.pluginOwnerId = pluginOwnerId || null;
10
+ this.createdAt = createdAt;
11
+ this.updatedAt = updatedAt;
12
+ }
13
+
14
+ getParsedGraph() {
15
+ if (!this.graphJson || this.graphJson === 'null') return null;
16
+ if (typeof this.graphJson === 'object') return this.graphJson;
17
+ try {
18
+ return JSON.parse(this.graphJson);
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ isOwnedByPlugin() {
25
+ return this.pluginOwnerId !== null;
26
+ }
27
+
28
+ toJSON() {
29
+ return {
30
+ id: this.id,
31
+ botId: this.botId,
32
+ name: this.name,
33
+ isEnabled: this.isEnabled,
34
+ pluginOwnerId: this.pluginOwnerId,
35
+ };
36
+ }
37
+ }
38
+
39
+ module.exports = EventGraph;
@@ -0,0 +1,45 @@
1
+ class Plugin {
2
+ constructor({ id, botId, name, version, description, sourceType, sourceUri, sourceRefType, sourceRef, path, isEnabled, manifest, settings, createdAt } = {}) {
3
+ this.id = id;
4
+ this.botId = botId;
5
+ this.name = name;
6
+ this.version = version;
7
+ this.description = description || '';
8
+ this.sourceType = sourceType;
9
+ this.sourceUri = sourceUri || null;
10
+ this.sourceRefType = sourceRefType || null;
11
+ this.sourceRef = sourceRef || null;
12
+ this.path = path;
13
+ this.isEnabled = isEnabled !== false;
14
+ this.manifest = typeof manifest === 'string' ? JSON.parse(manifest || '{}') : (manifest || {});
15
+ this.settings = typeof settings === 'string' ? JSON.parse(settings || '{}') : (settings || {});
16
+ this.createdAt = createdAt;
17
+ }
18
+
19
+ getDependencies() {
20
+ return this.manifest.dependencies || {};
21
+ }
22
+
23
+ isLocal() {
24
+ return this.sourceType === 'LOCAL' || this.sourceType === 'LOCAL_IDE';
25
+ }
26
+
27
+ isGithub() {
28
+ return this.sourceType === 'GITHUB';
29
+ }
30
+
31
+ toJSON() {
32
+ return {
33
+ id: this.id,
34
+ botId: this.botId,
35
+ name: this.name,
36
+ version: this.version,
37
+ description: this.description,
38
+ sourceType: this.sourceType,
39
+ sourceUri: this.sourceUri,
40
+ isEnabled: this.isEnabled,
41
+ };
42
+ }
43
+ }
44
+
45
+ module.exports = Plugin;
@@ -0,0 +1,40 @@
1
+ class User {
2
+ constructor({ id, username, isBlacklisted, botId, groups } = {}) {
3
+ this.id = id;
4
+ this.username = username;
5
+ this.isBlacklisted = isBlacklisted || false;
6
+ this.botId = botId;
7
+ this.groups = groups || [];
8
+ }
9
+
10
+ hasPermission(permissionName) {
11
+ for (const group of this.groups) {
12
+ const perms = group.permissions || [];
13
+ for (const perm of perms) {
14
+ const name = perm.name || perm;
15
+ if (name === permissionName) return true;
16
+ if (name.endsWith('.*')) {
17
+ const prefix = name.slice(0, -2);
18
+ if (permissionName.startsWith(prefix)) return true;
19
+ }
20
+ }
21
+ }
22
+ return false;
23
+ }
24
+
25
+ getGroupNames() {
26
+ return this.groups.map(g => g.name || g);
27
+ }
28
+
29
+ toJSON() {
30
+ return {
31
+ id: this.id,
32
+ username: this.username,
33
+ isBlacklisted: this.isBlacklisted,
34
+ botId: this.botId,
35
+ groups: this.getGroupNames(),
36
+ };
37
+ }
38
+ }
39
+
40
+ module.exports = User;
@@ -0,0 +1,168 @@
1
+ const semver = require('semver');
2
+
3
+ const ISSUE_TYPES = {
4
+ MISSING: 'missing_dependency',
5
+ DISABLED: 'disabled_dependency',
6
+ VERSION_MISMATCH: 'version_mismatch',
7
+ CIRCULAR: 'circular_dependency',
8
+ };
9
+
10
+ class DependencyResolver {
11
+ resolve(enabledPlugins, allPlugins) {
12
+ const pluginMap = new Map(allPlugins.map(p => [p.name, p]));
13
+ const enabledSet = new Set(enabledPlugins.map(p => p.name));
14
+ const pluginInfo = {};
15
+ let hasCriticalIssues = false;
16
+
17
+ for (const plugin of allPlugins) {
18
+ const manifest = this._parseManifest(plugin.manifest);
19
+ pluginInfo[plugin.id] = {
20
+ ...plugin,
21
+ isEnabled: enabledSet.has(plugin.name),
22
+ issues: [],
23
+ dependencies: manifest.dependencies ? Object.entries(manifest.dependencies) : [],
24
+ optionalDependencies: manifest.optionalDependencies ? Object.entries(manifest.optionalDependencies) : [],
25
+ };
26
+ }
27
+
28
+ for (const plugin of enabledPlugins) {
29
+ const info = pluginInfo[plugin.id];
30
+
31
+ for (const [depName, depRange] of info.dependencies) {
32
+ const issue = this._checkDependency(depName, depRange, pluginMap, enabledSet, false);
33
+ if (issue) {
34
+ info.issues.push(issue);
35
+ if (issue.type !== ISSUE_TYPES.DISABLED) hasCriticalIssues = true;
36
+ }
37
+ }
38
+
39
+ for (const [depName, depRange] of info.optionalDependencies) {
40
+ const issue = this._checkDependency(depName, depRange, pluginMap, enabledSet, true);
41
+ if (issue) {
42
+ info.issues.push({ ...issue, optional: true });
43
+ }
44
+ }
45
+ }
46
+
47
+ const { sorted, cycles } = this._topologicalSort(enabledPlugins, pluginMap, enabledSet);
48
+
49
+ for (const cycle of cycles) {
50
+ hasCriticalIssues = true;
51
+ const cyclePath = cycle.join(' -> ');
52
+ for (const plugin of enabledPlugins) {
53
+ if (cycle.includes(plugin.name)) {
54
+ pluginInfo[plugin.id].issues.push({
55
+ type: ISSUE_TYPES.CIRCULAR,
56
+ messageKey: 'dependency.circular',
57
+ context: { cyclePath },
58
+ });
59
+ }
60
+ }
61
+ }
62
+
63
+ return { sortedPlugins: sorted, pluginInfo, hasCriticalIssues };
64
+ }
65
+
66
+ checkCompatibility(plugin, installedPlugins) {
67
+ const pluginMap = new Map(installedPlugins.map(p => [p.name, p]));
68
+ const manifest = this._parseManifest(plugin.manifest);
69
+ const deps = manifest.dependencies || {};
70
+ const issues = [];
71
+
72
+ for (const [depName, depRange] of Object.entries(deps)) {
73
+ const dep = pluginMap.get(depName);
74
+ if (!dep) {
75
+ issues.push({ type: ISSUE_TYPES.MISSING, messageKey: 'dependency.missing', context: { depName, depRange } });
76
+ } else if (!this._satisfies(dep.version, depRange)) {
77
+ const suggestion = this._suggestResolution(dep.version, depRange);
78
+ issues.push({ type: ISSUE_TYPES.VERSION_MISMATCH, messageKey: 'dependency.versionMismatch', context: { depName, depRange, installed: dep.version, suggestion } });
79
+ }
80
+ }
81
+
82
+ return { compatible: issues.length === 0, issues };
83
+ }
84
+
85
+ _checkDependency(depName, depRange, pluginMap, enabledSet, optional) {
86
+ const dep = pluginMap.get(depName);
87
+ if (!dep) {
88
+ return optional ? null : { type: ISSUE_TYPES.MISSING, messageKey: 'dependency.missing', context: { depName, depRange } };
89
+ }
90
+ if (!enabledSet.has(depName)) {
91
+ return { type: ISSUE_TYPES.DISABLED, messageKey: 'dependency.disabled', context: { depName } };
92
+ }
93
+ if (!this._satisfies(dep.version, depRange)) {
94
+ const suggestion = this._suggestResolution(dep.version, depRange);
95
+ return { type: ISSUE_TYPES.VERSION_MISMATCH, messageKey: 'dependency.versionMismatch', context: { depName, depRange, installed: dep.version, suggestion } };
96
+ }
97
+ return null;
98
+ }
99
+
100
+ _satisfies(version, range) {
101
+ try {
102
+ const coerced = semver.coerce(version);
103
+ if (!coerced) return false;
104
+ return semver.satisfies(coerced.version, range);
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ _suggestResolution(installedVersion, requiredRange) {
111
+ const coerced = semver.coerce(installedVersion);
112
+ if (!coerced) return null;
113
+ const minVersion = semver.minVersion(requiredRange);
114
+ if (minVersion && semver.gt(minVersion.version, coerced.version)) {
115
+ return `dependency.suggestion.upgrade`;
116
+ }
117
+ return `dependency.suggestion.downgrade`;
118
+ }
119
+
120
+ _topologicalSort(enabledPlugins, pluginMap, enabledSet) {
121
+ const sorted = [];
122
+ const visited = new Set();
123
+ const visiting = new Set();
124
+ const cycles = [];
125
+
126
+ const visit = (name, path) => {
127
+ if (visited.has(name)) return;
128
+ if (visiting.has(name)) {
129
+ const cycleStart = path.indexOf(name);
130
+ cycles.push(path.slice(cycleStart).concat(name));
131
+ return;
132
+ }
133
+
134
+ visiting.add(name);
135
+ const plugin = pluginMap.get(name);
136
+ if (plugin) {
137
+ const manifest = this._parseManifest(plugin.manifest);
138
+ const deps = { ...(manifest.dependencies || {}), ...(manifest.optionalDependencies || {}) };
139
+ for (const depName of Object.keys(deps)) {
140
+ if (enabledSet.has(depName)) {
141
+ visit(depName, [...path, name]);
142
+ }
143
+ }
144
+ }
145
+ visiting.delete(name);
146
+ visited.add(name);
147
+ if (plugin) sorted.push(plugin);
148
+ };
149
+
150
+ for (const plugin of enabledPlugins) {
151
+ visit(plugin.name, []);
152
+ }
153
+
154
+ return { sorted, cycles };
155
+ }
156
+
157
+ _parseManifest(manifest) {
158
+ if (!manifest) return {};
159
+ if (typeof manifest === 'object') return manifest;
160
+ try {
161
+ return JSON.parse(manifest);
162
+ } catch {
163
+ return {};
164
+ }
165
+ }
166
+ }
167
+
168
+ module.exports = DependencyResolver;