@veap/plugins 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +132 -2
- package/dist/actions/index.d.ts +1 -0
- package/dist/actions/index.js +1 -0
- package/dist/actions/plugin.actions.d.ts +6 -0
- package/dist/actions/plugin.actions.js +60 -0
- package/dist/actions.d.ts +1 -0
- package/dist/actions.js +1 -0
- package/dist/cli/add.d.ts +4 -0
- package/dist/cli/add.js +191 -0
- package/dist/cli/eject.d.ts +2 -0
- package/dist/cli/eject.js +136 -0
- package/dist/cli/generate-plugin.d.ts +3 -0
- package/dist/cli/generate-plugin.js +48 -0
- package/dist/cli/index.d.ts +6 -0
- package/dist/cli/index.js +6 -0
- package/dist/cli/package-manager.d.ts +14 -0
- package/dist/cli/package-manager.js +62 -0
- package/dist/cli/stubs.d.ts +16 -0
- package/dist/cli/stubs.js +42 -0
- package/dist/cli/utils.d.ts +3 -0
- package/dist/cli/utils.js +164 -0
- package/dist/client.d.ts +6 -0
- package/dist/client.js +8 -0
- package/dist/common/context.d.ts +26 -0
- package/dist/common/context.js +26 -0
- package/dist/common/events.d.ts +19 -0
- package/dist/common/events.js +26 -0
- package/dist/common/index.d.ts +4 -0
- package/dist/common/index.js +4 -0
- package/dist/common/tokens.d.ts +14 -0
- package/dist/common/tokens.js +12 -0
- package/dist/common/types.d.ts +202 -0
- package/dist/common/types.js +39 -0
- package/dist/composition.d.ts +15 -0
- package/dist/composition.js +20 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/migrations/0001_create_plugins_tables.d.ts +4 -0
- package/dist/migrations/0001_create_plugins_tables.js +29 -0
- package/dist/migrations/index.d.ts +4 -0
- package/dist/migrations/index.js +7 -0
- package/dist/models/SystemPlugin.d.ts +19 -0
- package/dist/models/SystemPlugin.js +14 -0
- package/dist/models/SystemUserWidget.d.ts +19 -0
- package/dist/models/SystemUserWidget.js +17 -0
- package/dist/models/index.d.ts +2 -0
- package/dist/models/index.js +2 -0
- package/dist/models.d.ts +1 -0
- package/dist/models.js +1 -0
- package/dist/navigation/breadcrumbs.d.ts +2 -0
- package/dist/navigation/breadcrumbs.js +84 -0
- package/dist/navigation/index.d.ts +2 -0
- package/dist/navigation/index.js +2 -0
- package/dist/navigation/navigation.service.d.ts +27 -0
- package/dist/navigation/navigation.service.js +183 -0
- package/dist/providers/index.d.ts +1 -0
- package/dist/providers/index.js +1 -0
- package/dist/providers/plugins.provider.d.ts +20 -0
- package/dist/providers/plugins.provider.js +143 -0
- package/dist/registry/contracts.d.ts +72 -0
- package/dist/registry/contracts.js +4 -0
- package/dist/registry/facade.d.ts +45 -0
- package/dist/registry/facade.js +49 -0
- package/dist/registry/index.d.ts +5 -0
- package/dist/registry/index.js +5 -0
- package/dist/registry/migration-runner.d.ts +8 -0
- package/dist/registry/migration-runner.js +12 -0
- package/dist/registry/plugin.repository.d.ts +19 -0
- package/dist/registry/plugin.repository.js +74 -0
- package/dist/registry/registry.service.d.ts +69 -0
- package/dist/registry/registry.service.js +535 -0
- package/dist/router/discovery.d.ts +27 -0
- package/dist/router/discovery.js +37 -0
- package/dist/router/handler.d.ts +6 -0
- package/dist/router/handler.js +40 -0
- package/dist/router/index.d.ts +14 -0
- package/dist/router/index.js +14 -0
- package/dist/router/matcher.d.ts +5 -0
- package/dist/router/matcher.js +32 -0
- package/dist/router/middlewares.d.ts +25 -0
- package/dist/router/middlewares.js +119 -0
- package/dist/router/react/error-boundary.d.ts +30 -0
- package/dist/router/react/error-boundary.js +68 -0
- package/dist/router/react/index.d.ts +4 -0
- package/dist/router/react/index.js +4 -0
- package/dist/router/react/renderer.d.ts +13 -0
- package/dist/router/react/renderer.js +159 -0
- package/dist/router/react/soft-navigation.d.ts +4 -0
- package/dist/router/react/soft-navigation.js +34 -0
- package/dist/router/react/with-router.d.ts +22 -0
- package/dist/router/react/with-router.js +19 -0
- package/dist/router/route-tree.d.ts +177 -0
- package/dist/router/route-tree.js +639 -0
- package/dist/router/router.service.d.ts +25 -0
- package/dist/router/router.service.js +127 -0
- package/dist/router/scanner.d.ts +4 -0
- package/dist/router/scanner.js +337 -0
- package/dist/router/utils.d.ts +10 -0
- package/dist/router/utils.js +48 -0
- package/dist/router.d.ts +6 -0
- package/dist/router.js +6 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +17 -0
- package/dist/widgets/index.d.ts +8 -0
- package/dist/widgets/index.js +8 -0
- package/dist/widgets/path-prefix-context.d.ts +5 -0
- package/dist/widgets/path-prefix-context.js +9 -0
- package/dist/widgets/plugin-extension-point-client.d.ts +15 -0
- package/dist/widgets/plugin-extension-point-client.js +44 -0
- package/dist/widgets/plugin-extension-point.d.ts +14 -0
- package/dist/widgets/plugin-extension-point.js +51 -0
- package/dist/widgets/plugin-widget-area-client.d.ts +11 -0
- package/dist/widgets/plugin-widget-area-client.js +31 -0
- package/dist/widgets/plugin-widget-area.d.ts +10 -0
- package/dist/widgets/plugin-widget-area.js +19 -0
- package/dist/widgets/use-confirm-action.d.ts +19 -0
- package/dist/widgets/use-confirm-action.js +68 -0
- package/dist/widgets/widget-composer-client.d.ts +21 -0
- package/dist/widgets/widget-composer-client.js +281 -0
- package/dist/widgets/widget-composer.d.ts +8 -0
- package/dist/widgets/widget-composer.js +28 -0
- package/package.json +115 -4
|
@@ -0,0 +1,535 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { AppError, EVENT_BUS, Inject, Injectable, LOGGER, } from "@veap/kernel";
|
|
14
|
+
import { MIGRATION_RUNNER, PLUGIN_REPOSITORY, } from "./contracts";
|
|
15
|
+
/**
|
|
16
|
+
* Normalizes an npm dependency string (e.g. "@veap/media-plugin") to a registered plugin id (e.g. "media-plugin").
|
|
17
|
+
*/
|
|
18
|
+
const resolveDependencyId = (depId) => depId.replace(/^@[^/]+\//, "");
|
|
19
|
+
let PluginRegistry = class PluginRegistry {
|
|
20
|
+
repository;
|
|
21
|
+
migrationRunner;
|
|
22
|
+
logger;
|
|
23
|
+
eventBus;
|
|
24
|
+
plugins = new Map();
|
|
25
|
+
pluginStatus = new Map();
|
|
26
|
+
processingPlugins = new Set();
|
|
27
|
+
initPromise = null;
|
|
28
|
+
initialized = false;
|
|
29
|
+
constructor(repository, migrationRunner, logger, eventBus) {
|
|
30
|
+
this.repository = repository;
|
|
31
|
+
this.migrationRunner = migrationRunner;
|
|
32
|
+
this.logger = logger;
|
|
33
|
+
this.eventBus = eventBus;
|
|
34
|
+
}
|
|
35
|
+
/** Lookup a plugin by id, falling back to the npm-name form. */
|
|
36
|
+
findPlugin(id) {
|
|
37
|
+
return this.plugins.get(id) ?? this.plugins.get(resolveDependencyId(id));
|
|
38
|
+
}
|
|
39
|
+
register(plugin) {
|
|
40
|
+
if (this.plugins.has(plugin.manifest.id)) {
|
|
41
|
+
// return;
|
|
42
|
+
}
|
|
43
|
+
this.plugins.set(plugin.manifest.id, plugin);
|
|
44
|
+
// Rekurencyjna rejestracja wtyczek zależnych
|
|
45
|
+
if (plugin.plugins && Array.isArray(plugin.plugins)) {
|
|
46
|
+
for (const nested of plugin.plugins) {
|
|
47
|
+
this.register(nested);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (process.env.NODE_ENV === "development") {
|
|
51
|
+
// In development, we want to allow re-registration to pick up HMR changes
|
|
52
|
+
this.plugins.set(plugin.manifest.id, plugin);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async updateStatus(id, update) {
|
|
56
|
+
const current = this.pluginStatus.get(id) || {
|
|
57
|
+
enabled: false,
|
|
58
|
+
installed: false,
|
|
59
|
+
lastStep: null,
|
|
60
|
+
};
|
|
61
|
+
const next = { ...current, ...update };
|
|
62
|
+
this.pluginStatus.set(id, next);
|
|
63
|
+
if (update.lastStep !== undefined && update.lastStep !== null) {
|
|
64
|
+
this.logger.info("PluginRegistry", `"${id}" step: ${update.lastStep}`);
|
|
65
|
+
}
|
|
66
|
+
const isStateTransition = update.enabled !== undefined || update.installed !== undefined;
|
|
67
|
+
try {
|
|
68
|
+
await this.repository.upsertStatus(id, update, {
|
|
69
|
+
system: this.plugins.get(id)?.manifest.system ?? false,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (e) {
|
|
73
|
+
if (isStateTransition) {
|
|
74
|
+
// Rollback in-memory state on DB failure to avoid split-brain
|
|
75
|
+
this.pluginStatus.set(id, current);
|
|
76
|
+
this.logger.error("PluginRegistry", `Critical: DB status sync failed for plugin "${id}". Rolling back in-memory state:`, e);
|
|
77
|
+
throw e;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Ephemeral progress steps (lastStep) can safely fail without aborting
|
|
81
|
+
this.logger.warn("PluginRegistry", `DB step sync failed for "${id}":`, e);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async updateStep(id, step) {
|
|
86
|
+
await this.updateStatus(id, { lastStep: step });
|
|
87
|
+
}
|
|
88
|
+
sortPlugins(plugins) {
|
|
89
|
+
const sorted = [];
|
|
90
|
+
const visited = new Set();
|
|
91
|
+
const processing = new Set();
|
|
92
|
+
const visit = (plugin) => {
|
|
93
|
+
if (visited.has(plugin.manifest.id))
|
|
94
|
+
return;
|
|
95
|
+
if (processing.has(plugin.manifest.id)) {
|
|
96
|
+
throw AppError.Internal(`Circular dependency detected: ${plugin.manifest.id}`);
|
|
97
|
+
}
|
|
98
|
+
processing.add(plugin.manifest.id);
|
|
99
|
+
const dependencies = plugin.manifest.dependencies || [];
|
|
100
|
+
for (const depId of dependencies) {
|
|
101
|
+
const depPlugin = this.findPlugin(depId);
|
|
102
|
+
if (depPlugin) {
|
|
103
|
+
visit(depPlugin);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.logger.warn("PluginRegistry", `Missing dependency: ${depId} for plugin ${plugin.manifest.id}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
processing.delete(plugin.manifest.id);
|
|
110
|
+
visited.add(plugin.manifest.id);
|
|
111
|
+
sorted.push(plugin);
|
|
112
|
+
};
|
|
113
|
+
// First, visit system plugins
|
|
114
|
+
for (const plugin of plugins) {
|
|
115
|
+
if (plugin.manifest.system) {
|
|
116
|
+
visit(plugin);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Then visit the rest
|
|
120
|
+
for (const plugin of plugins) {
|
|
121
|
+
visit(plugin);
|
|
122
|
+
}
|
|
123
|
+
return sorted;
|
|
124
|
+
}
|
|
125
|
+
async init() {
|
|
126
|
+
// if (this.initialized && process.env.NODE_ENV !== "development") return;
|
|
127
|
+
if (this.initPromise) {
|
|
128
|
+
return this.initPromise;
|
|
129
|
+
}
|
|
130
|
+
this.initPromise = (async () => {
|
|
131
|
+
try {
|
|
132
|
+
await this.eventBus.publish("system:plugins:init:start", {
|
|
133
|
+
timestamp: Date.now(),
|
|
134
|
+
});
|
|
135
|
+
// 1. Sync with database
|
|
136
|
+
const pluginValues = Array.from(this.plugins.values()).map((plugin) => ({
|
|
137
|
+
id: plugin.manifest.id,
|
|
138
|
+
enabled: plugin.manifest.system ?? false,
|
|
139
|
+
installed: plugin.manifest.system ?? false,
|
|
140
|
+
system: plugin.manifest.system ?? false,
|
|
141
|
+
}));
|
|
142
|
+
if (pluginValues.length > 0) {
|
|
143
|
+
try {
|
|
144
|
+
await this.repository.seed(pluginValues);
|
|
145
|
+
}
|
|
146
|
+
catch (_e) {
|
|
147
|
+
// Ignore errors if table doesn't exist yet during bootstrap
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// 2. Fetch current status from DB
|
|
151
|
+
try {
|
|
152
|
+
const dbPlugins = await this.repository.findAll();
|
|
153
|
+
for (const dbp of dbPlugins) {
|
|
154
|
+
this.pluginStatus.set(dbp.id, {
|
|
155
|
+
enabled: dbp.enabled || dbp.system,
|
|
156
|
+
installed: dbp.installed || dbp.system,
|
|
157
|
+
lastStep: dbp.lastStep,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
catch (_e) {
|
|
162
|
+
for (const plugin of this.plugins.values()) {
|
|
163
|
+
this.pluginStatus.set(plugin.manifest.id, {
|
|
164
|
+
enabled: plugin.manifest.system ?? false,
|
|
165
|
+
installed: true,
|
|
166
|
+
lastStep: null,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
// 3. Sort plugins by dependencies
|
|
171
|
+
const sortedPlugins = this.sortPlugins(Array.from(this.plugins.values()));
|
|
172
|
+
// 4. Initialize enabled plugins
|
|
173
|
+
for (const plugin of sortedPlugins) {
|
|
174
|
+
const status = this.pluginStatus.get(plugin.manifest.id);
|
|
175
|
+
if (status?.enabled) {
|
|
176
|
+
if (plugin.migrations && plugin.migrations.length > 0) {
|
|
177
|
+
await this.migrationRunner.run(plugin.manifest.id, plugin.migrations);
|
|
178
|
+
}
|
|
179
|
+
if (!status.installed) {
|
|
180
|
+
await this.updateStep(plugin.manifest.id, "Installing...");
|
|
181
|
+
if (plugin.onMigrate)
|
|
182
|
+
await plugin.onMigrate();
|
|
183
|
+
if (plugin.onEnable)
|
|
184
|
+
await plugin.onEnable();
|
|
185
|
+
await this.updateStatus(plugin.manifest.id, {
|
|
186
|
+
installed: true,
|
|
187
|
+
lastStep: null,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
if (plugin.init)
|
|
191
|
+
await plugin.init();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
this.initialized = true;
|
|
195
|
+
this.logger.info("veap:plugins", `Initialized with ${this.plugins.size} plugins`);
|
|
196
|
+
await this.eventBus.publish("system:plugins:init:end", {
|
|
197
|
+
timestamp: Date.now(),
|
|
198
|
+
pluginCount: this.plugins.size,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
this.initPromise = null;
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
})();
|
|
206
|
+
return this.initPromise;
|
|
207
|
+
}
|
|
208
|
+
getPlugins() {
|
|
209
|
+
return Array.from(this.plugins.values());
|
|
210
|
+
}
|
|
211
|
+
getEnabledPlugins() {
|
|
212
|
+
return this.getPlugins().filter((p) => {
|
|
213
|
+
const status = this.pluginStatus.get(p.manifest.id);
|
|
214
|
+
return status?.enabled ?? false;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
getPlugin(id) {
|
|
218
|
+
return this.plugins.get(id);
|
|
219
|
+
}
|
|
220
|
+
async getPluginsStatus() {
|
|
221
|
+
try {
|
|
222
|
+
const dbPlugins = await this.repository.findAll();
|
|
223
|
+
const dbStatusMap = new Map(dbPlugins.map((p) => [p.id, p]));
|
|
224
|
+
return this.getPlugins().map((p) => {
|
|
225
|
+
const dbp = dbStatusMap.get(p.manifest.id);
|
|
226
|
+
const memStatus = this.pluginStatus.get(p.manifest.id);
|
|
227
|
+
return {
|
|
228
|
+
...p.manifest,
|
|
229
|
+
enabled: dbp?.enabled ?? memStatus?.enabled ?? false,
|
|
230
|
+
installed: dbp?.installed ?? memStatus?.installed ?? false,
|
|
231
|
+
lastStep: memStatus?.lastStep ?? dbp?.lastStep ?? null,
|
|
232
|
+
};
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
catch (_e) {
|
|
236
|
+
// Fallback for bootstrap phase
|
|
237
|
+
return this.getPlugins().map((p) => {
|
|
238
|
+
const status = this.pluginStatus.get(p.manifest.id);
|
|
239
|
+
return {
|
|
240
|
+
...p.manifest,
|
|
241
|
+
enabled: status?.enabled ?? false,
|
|
242
|
+
installed: status?.installed ?? false,
|
|
243
|
+
lastStep: status?.lastStep ?? null,
|
|
244
|
+
};
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async getPluginStatus(id) {
|
|
249
|
+
const plugin = this.plugins.get(id);
|
|
250
|
+
if (!plugin)
|
|
251
|
+
return null;
|
|
252
|
+
const memStatus = this.pluginStatus.get(id);
|
|
253
|
+
const isProcessing = this.processingPlugins.has(id);
|
|
254
|
+
try {
|
|
255
|
+
const dbp = await this.repository.findById(id);
|
|
256
|
+
return {
|
|
257
|
+
...plugin.manifest,
|
|
258
|
+
enabled: dbp?.enabled ?? memStatus?.enabled ?? false,
|
|
259
|
+
installed: dbp?.installed ?? memStatus?.installed ?? false,
|
|
260
|
+
// In production, the DB is the only shared truth between processes.
|
|
261
|
+
// We use DB's lastStep first, then memory, then "Processing..." indicator.
|
|
262
|
+
lastStep: dbp?.lastStep ??
|
|
263
|
+
memStatus?.lastStep ??
|
|
264
|
+
(isProcessing ? "Processing..." : null),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
catch (_e) {
|
|
268
|
+
// Fallback to in-memory if DB fails
|
|
269
|
+
return {
|
|
270
|
+
...plugin.manifest,
|
|
271
|
+
enabled: memStatus?.enabled ?? false,
|
|
272
|
+
installed: memStatus?.installed ?? false,
|
|
273
|
+
lastStep: memStatus?.lastStep ?? (isProcessing ? "Processing..." : null),
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
async getHooks(point) {
|
|
278
|
+
const hooks = [];
|
|
279
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
280
|
+
if (plugin.hooks) {
|
|
281
|
+
hooks.push(...plugin.hooks.filter((h) => h.point === point));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
// Items without priority default to 100 to appear later
|
|
285
|
+
return hooks.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
|
|
286
|
+
}
|
|
287
|
+
async getExtensions(target, point, includeDisabled = false, context) {
|
|
288
|
+
const extensions = [];
|
|
289
|
+
const pluginsToScan = includeDisabled
|
|
290
|
+
? this.getPlugins()
|
|
291
|
+
: this.getEnabledPlugins();
|
|
292
|
+
for (const plugin of pluginsToScan) {
|
|
293
|
+
if (plugin.extensions) {
|
|
294
|
+
extensions.push(...plugin.extensions.filter((e) => {
|
|
295
|
+
if (e.target !== target || e.point !== point)
|
|
296
|
+
return false;
|
|
297
|
+
if (context) {
|
|
298
|
+
const userRoles = context.roles || [];
|
|
299
|
+
const userPermissions = context.permissions || [];
|
|
300
|
+
if (e.roles && e.roles.length > 0) {
|
|
301
|
+
if (!e.roles.some((role) => userRoles.includes(role))) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (e.permissions && e.permissions.length > 0) {
|
|
306
|
+
if (!e.permissions.every((perm) => userPermissions.includes(perm))) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return true;
|
|
312
|
+
}));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Items without priority default to 100 to appear later
|
|
316
|
+
return extensions.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
|
|
317
|
+
}
|
|
318
|
+
async getWidgets(area, context) {
|
|
319
|
+
const widgets = [];
|
|
320
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
321
|
+
if (plugin.widgets) {
|
|
322
|
+
widgets.push(...plugin.widgets.filter((w) => {
|
|
323
|
+
if (w.area !== area)
|
|
324
|
+
return false;
|
|
325
|
+
if (context) {
|
|
326
|
+
const userRoles = context.roles || [];
|
|
327
|
+
const userPermissions = context.permissions || [];
|
|
328
|
+
if (w.roles && w.roles.length > 0) {
|
|
329
|
+
if (!w.roles.some((role) => userRoles.includes(role))) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
if (w.permissions && w.permissions.length > 0) {
|
|
334
|
+
if (!w.permissions.every((perm) => userPermissions.includes(perm))) {
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return true;
|
|
340
|
+
}));
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Items without priority default to 100 to appear later
|
|
344
|
+
return widgets.sort((a, b) => (a.priority ?? 100) - (b.priority ?? 100));
|
|
345
|
+
}
|
|
346
|
+
async delay(ms = 500) {
|
|
347
|
+
if (process.env.NODE_ENV === "production" ||
|
|
348
|
+
process.env.VERCEL ||
|
|
349
|
+
process.env.NEXT_RUNTIME === "edge")
|
|
350
|
+
return;
|
|
351
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
352
|
+
}
|
|
353
|
+
async togglePlugin(id, enabled, context) {
|
|
354
|
+
const plugin = this.findPlugin(id);
|
|
355
|
+
if (!plugin)
|
|
356
|
+
throw AppError.Internal(`Plugin ${id} not found`);
|
|
357
|
+
// Canonicalize to the registered id so status lookups match
|
|
358
|
+
id = plugin.manifest.id;
|
|
359
|
+
const currentStatus = this.pluginStatus.get(id);
|
|
360
|
+
if (currentStatus?.enabled === enabled)
|
|
361
|
+
return;
|
|
362
|
+
// Zapobieganie nieskończonej rekurencji (cyklom zależności)
|
|
363
|
+
if (this.processingPlugins.has(id)) {
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
this.processingPlugins.add(id);
|
|
367
|
+
try {
|
|
368
|
+
if (enabled) {
|
|
369
|
+
// activation process
|
|
370
|
+
// 1. Check if the plugin requires any plugins
|
|
371
|
+
await this.updateStep(id, "Checking dependencies...");
|
|
372
|
+
await this.delay();
|
|
373
|
+
const dependencies = plugin.manifest.dependencies || [];
|
|
374
|
+
// 2. Aktywacja pluginów wymaganych
|
|
375
|
+
for (const depId of dependencies) {
|
|
376
|
+
const depPlugin = this.findPlugin(depId);
|
|
377
|
+
const canonicalDepId = depPlugin?.manifest.id ?? resolveDependencyId(depId);
|
|
378
|
+
const depStatus = this.pluginStatus.get(canonicalDepId);
|
|
379
|
+
if (!depStatus?.enabled) {
|
|
380
|
+
const depName = depPlugin?.manifest.name || canonicalDepId;
|
|
381
|
+
await this.updateStep(id, `Waiting for dependency activation: ${depName}`);
|
|
382
|
+
// Recursive call for dependencies
|
|
383
|
+
await this.togglePlugin(canonicalDepId, true, context);
|
|
384
|
+
await this.delay();
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
// 3. Before running the onEnable hook, we perform migrations
|
|
388
|
+
if (plugin.migrations?.length) {
|
|
389
|
+
await this.updateStep(id, "Migrating the database...");
|
|
390
|
+
await this.delay();
|
|
391
|
+
await this.migrationRunner.run(id, plugin.migrations);
|
|
392
|
+
}
|
|
393
|
+
// 4. Once migrations are complete, run the onEnable hook
|
|
394
|
+
if (plugin.onEnable) {
|
|
395
|
+
await this.updateStep(id, "Running the onEnable hook...");
|
|
396
|
+
await this.delay();
|
|
397
|
+
await plugin.onEnable(context);
|
|
398
|
+
}
|
|
399
|
+
// 5. Finally, we turn on the plugin
|
|
400
|
+
await this.updateStatus(id, {
|
|
401
|
+
enabled: true,
|
|
402
|
+
installed: true,
|
|
403
|
+
lastStep: null,
|
|
404
|
+
});
|
|
405
|
+
await this.delay();
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
// deactivation process
|
|
409
|
+
// 1. Check for plugins that depend on us (automatic deactivation "upwards")
|
|
410
|
+
// We MUST deactivate dependents FIRST, otherwise they would be broken when we disable ourselves
|
|
411
|
+
// Match both the canonical id and the scoped npm-name form
|
|
412
|
+
const dependents = Array.from(this.plugins.values()).filter((p) => p.manifest.dependencies?.some((dep) => resolveDependencyId(dep) === resolveDependencyId(id)));
|
|
413
|
+
for (const dependent of dependents) {
|
|
414
|
+
const depStatus = this.pluginStatus.get(dependent.manifest.id);
|
|
415
|
+
if (depStatus?.enabled &&
|
|
416
|
+
!this.processingPlugins.has(dependent.manifest.id)) {
|
|
417
|
+
await this.updateStep(id, `Deactivating dependent plugin: ${dependent.manifest.name}`);
|
|
418
|
+
await this.togglePlugin(dependent.manifest.id, false, context);
|
|
419
|
+
await this.delay();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
// 2. Run the onDisable hook
|
|
423
|
+
if (plugin.onDisable) {
|
|
424
|
+
await this.updateStep(id, "Running onDisable hook...");
|
|
425
|
+
await this.delay();
|
|
426
|
+
await plugin.onDisable();
|
|
427
|
+
}
|
|
428
|
+
// 3. Run the rollback migrations
|
|
429
|
+
if (plugin.migrations?.length) {
|
|
430
|
+
await this.updateStep(id, "Rolling back database migrations...");
|
|
431
|
+
await this.delay();
|
|
432
|
+
try {
|
|
433
|
+
await this.migrationRunner.rollback(id, plugin.migrations);
|
|
434
|
+
}
|
|
435
|
+
catch (e) {
|
|
436
|
+
this.logger.error("PluginRegistry", `Rollback failed for ${id}:`, e);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
// 4. Disable the plugin itself
|
|
440
|
+
await this.updateStatus(id, { enabled: false, lastStep: null });
|
|
441
|
+
await this.delay();
|
|
442
|
+
// 5. Finally, proceed to deactivate unused dependencies
|
|
443
|
+
const dependencies = plugin.manifest.dependencies || [];
|
|
444
|
+
for (const depId of dependencies) {
|
|
445
|
+
const depPlugin = this.findPlugin(depId);
|
|
446
|
+
const canonicalDepId = depPlugin?.manifest.id ?? resolveDependencyId(depId);
|
|
447
|
+
const depStatus = this.pluginStatus.get(canonicalDepId);
|
|
448
|
+
if (depStatus?.enabled) {
|
|
449
|
+
// Check if anyone else is using this dependency (excluding those in the middle of deactivation)
|
|
450
|
+
const otherDependents = Array.from(this.plugins.values()).some((p) => p.manifest.id !== id &&
|
|
451
|
+
this.pluginStatus.get(p.manifest.id)?.enabled &&
|
|
452
|
+
!this.processingPlugins.has(p.manifest.id) &&
|
|
453
|
+
p.manifest.dependencies?.some((dep) => resolveDependencyId(dep) ===
|
|
454
|
+
resolveDependencyId(canonicalDepId)));
|
|
455
|
+
if (!otherDependents) {
|
|
456
|
+
const depName = depPlugin?.manifest.name || canonicalDepId;
|
|
457
|
+
await this.updateStep(id, `Cleaning up unused dependency: ${depName}`);
|
|
458
|
+
await this.togglePlugin(canonicalDepId, false, context);
|
|
459
|
+
await this.delay();
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Important: init() is only called if we are ENABLING
|
|
465
|
+
if (enabled && plugin.init)
|
|
466
|
+
await plugin.init();
|
|
467
|
+
// Ensure lastStep is cleared at the end of a SUCCESSFUL toggle
|
|
468
|
+
await this.updateStep(id, null);
|
|
469
|
+
}
|
|
470
|
+
catch (error) {
|
|
471
|
+
this.logger.warn("veap:plugins", `Error toggling plugin ${id}: ${error.message}`);
|
|
472
|
+
await this.updateStep(id, `Error: ${error.message}`);
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
finally {
|
|
476
|
+
this.processingPlugins.delete(id);
|
|
477
|
+
// Final safety clear: if we're not in an error state, make sure lastStep is null
|
|
478
|
+
const finalStatus = this.pluginStatus.get(id);
|
|
479
|
+
if (finalStatus?.lastStep &&
|
|
480
|
+
!finalStatus.lastStep.startsWith("Error:") &&
|
|
481
|
+
!finalStatus.lastStep.includes("...")) {
|
|
482
|
+
await this.updateStep(id, null);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
await this.eventBus.publish("system:plugin:toggle", {
|
|
486
|
+
pluginId: id,
|
|
487
|
+
isEnabled: enabled,
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
async hasHooks(point) {
|
|
491
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
492
|
+
if (plugin.hooks?.some((h) => h.point === point))
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
async hasExtension(target, point) {
|
|
498
|
+
for (const plugin of this.getEnabledPlugins()) {
|
|
499
|
+
if (plugin.extensions) {
|
|
500
|
+
if (plugin.extensions.some((e) => e.target === target && (point ? e.point === point : true)))
|
|
501
|
+
return true;
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
async getConfig(id) {
|
|
507
|
+
try {
|
|
508
|
+
const record = await this.repository.findById(id);
|
|
509
|
+
return record?.config ? JSON.parse(record.config) : null;
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
return null;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
async updateConfig(id, config) {
|
|
516
|
+
try {
|
|
517
|
+
await this.repository.updateConfig(id, JSON.stringify(config));
|
|
518
|
+
}
|
|
519
|
+
catch (_e) { }
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
PluginRegistry = __decorate([
|
|
523
|
+
Injectable(),
|
|
524
|
+
__param(0, Inject(PLUGIN_REPOSITORY)),
|
|
525
|
+
__param(1, Inject(MIGRATION_RUNNER)),
|
|
526
|
+
__param(2, Inject(LOGGER)),
|
|
527
|
+
__param(3, Inject(EVENT_BUS)),
|
|
528
|
+
__metadata("design:paramtypes", [Object, Object, Object, Object])
|
|
529
|
+
], PluginRegistry);
|
|
530
|
+
export { PluginRegistry };
|
|
531
|
+
// ─── Backwards-compatible re-exports ─────────────────────────────────────────
|
|
532
|
+
// The implementations live in `facade.ts`; this keeps
|
|
533
|
+
// `export * from "../application/plugins/registry"` in the entry barrels
|
|
534
|
+
// working for all module-level helpers.
|
|
535
|
+
export { applyPluginFilters, ensurePluginsInitialized, getPluginConfig, getPluginStatus, getPluginsStatus, hasPluginExtension, hasPluginHooks, registerPlugins, togglePluginState, updatePluginConfig, } from "./facade";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { matchRoute } from "./matcher";
|
|
2
|
+
export { matchRoute };
|
|
3
|
+
export declare function getPluginsWithHomepage(): Promise<{
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}[]>;
|
|
7
|
+
export { getPluginsWithHomepage as getModulesWithHomepage };
|
|
8
|
+
/**
|
|
9
|
+
* Builds a unified `RouteTree` from all enabled plugins.
|
|
10
|
+
*
|
|
11
|
+
* All trees are merged into a single `RouteTree` instance that can be
|
|
12
|
+
* passed to the `VeapRouter` component.
|
|
13
|
+
*
|
|
14
|
+
* @param includePrivate - If true, includes private routes prefixed with the configured privatePath.\n * @returns A fully merged `RouteTree` instance.
|
|
15
|
+
*/
|
|
16
|
+
export declare const buildRouteTree: (includePrivate?: boolean) => Promise<import("./route-tree").RouteTree>;
|
|
17
|
+
/**
|
|
18
|
+
* Registers an internal route rewrite rule in RouterService.
|
|
19
|
+
*
|
|
20
|
+
* @param from - Incoming public path (e.g. "/logowanie")
|
|
21
|
+
* @param to - Target virtual path (e.g. "/signin")
|
|
22
|
+
*/
|
|
23
|
+
export declare function addRouteRewrite(from: string, to: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Returns all registered route rewrites.
|
|
26
|
+
*/
|
|
27
|
+
export declare function getRouteRewrites(): Promise<Map<string, string>>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { app } from "@veap/kernel";
|
|
2
|
+
import { cache } from "react";
|
|
3
|
+
import { matchRoute } from "./matcher";
|
|
4
|
+
import { RouterService } from "./router.service";
|
|
5
|
+
export { matchRoute };
|
|
6
|
+
export async function getPluginsWithHomepage() {
|
|
7
|
+
return (await app(RouterService)).getPluginsWithHomepage();
|
|
8
|
+
}
|
|
9
|
+
export { getPluginsWithHomepage as getModulesWithHomepage };
|
|
10
|
+
/**
|
|
11
|
+
* Builds a unified `RouteTree` from all enabled plugins.
|
|
12
|
+
*
|
|
13
|
+
* All trees are merged into a single `RouteTree` instance that can be
|
|
14
|
+
* passed to the `VeapRouter` component.
|
|
15
|
+
*
|
|
16
|
+
* @param includePrivate - If true, includes private routes prefixed with the configured privatePath.\n * @returns A fully merged `RouteTree` instance.
|
|
17
|
+
*/
|
|
18
|
+
export const buildRouteTree = cache(async (includePrivate = false) => {
|
|
19
|
+
return (await app(RouterService)).buildRouteTree(includePrivate);
|
|
20
|
+
});
|
|
21
|
+
/**
|
|
22
|
+
* Registers an internal route rewrite rule in RouterService.
|
|
23
|
+
*
|
|
24
|
+
* @param from - Incoming public path (e.g. "/logowanie")
|
|
25
|
+
* @param to - Target virtual path (e.g. "/signin")
|
|
26
|
+
*/
|
|
27
|
+
export async function addRouteRewrite(from, to) {
|
|
28
|
+
const router = await app(RouterService);
|
|
29
|
+
router.addRewrite(from, to);
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Returns all registered route rewrites.
|
|
33
|
+
*/
|
|
34
|
+
export async function getRouteRewrites() {
|
|
35
|
+
const router = await app(RouterService);
|
|
36
|
+
return router.getRewrites();
|
|
37
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ApiEnsuredAuth, runApiPipeline } from "./middlewares";
|
|
2
|
+
import { collectApiMiddlewares, collectAuthRequirements } from "./utils";
|
|
3
|
+
export async function handleVeapApiRequest(request, { tree, path, searchParams, }) {
|
|
4
|
+
const match = tree.match(path);
|
|
5
|
+
if (!match) {
|
|
6
|
+
return new Response(JSON.stringify({ error: "Not Found" }), {
|
|
7
|
+
status: 404,
|
|
8
|
+
headers: { "Content-Type": "application/json" },
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
if (!match.isExact || !match.node.route) {
|
|
12
|
+
return new Response(JSON.stringify({ error: "Method Not Allowed or Not Found" }), {
|
|
13
|
+
status: 404,
|
|
14
|
+
headers: { "Content-Type": "application/json" },
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const method = request.method;
|
|
18
|
+
const handler = match.node.route[method] || match.node.route[method.toLowerCase()];
|
|
19
|
+
if (!handler) {
|
|
20
|
+
return new Response(JSON.stringify({ error: "Method Not Allowed" }), {
|
|
21
|
+
status: 405,
|
|
22
|
+
headers: { "Content-Type": "application/json" },
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
const allMiddlewares = collectApiMiddlewares(match.layoutChain, match.node);
|
|
26
|
+
const auth = collectAuthRequirements(match.layoutChain, match.node);
|
|
27
|
+
if (auth.needsAuth) {
|
|
28
|
+
allMiddlewares.unshift(ApiEnsuredAuth);
|
|
29
|
+
}
|
|
30
|
+
const context = {
|
|
31
|
+
params: match.params,
|
|
32
|
+
searchParams,
|
|
33
|
+
path,
|
|
34
|
+
roles: auth.roles.length > 0 ? auth.roles : undefined,
|
|
35
|
+
permissions: auth.permissions.length > 0 ? auth.permissions : undefined,
|
|
36
|
+
};
|
|
37
|
+
return runApiPipeline(allMiddlewares, request, context, async (req, ctx) => {
|
|
38
|
+
return await handler(req, ctx);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module router
|
|
3
|
+
* @description Virtual Router API for Veap Plugin system.
|
|
4
|
+
*/
|
|
5
|
+
export { ROUTER_SERVICE, SITE_LAYOUT } from "../common/tokens.js";
|
|
6
|
+
export { addRouteRewrite, buildRouteTree, getModulesWithHomepage, getPluginsWithHomepage, getRouteRewrites, matchRoute, } from "./discovery.js";
|
|
7
|
+
export { handleVeapApiRequest } from "./handler.js";
|
|
8
|
+
export { matchRoute as matchVirtualRoute } from "./matcher.js";
|
|
9
|
+
export { ApiEnsuredAuth, ApiSameOrigin, EnsuredAuth, EnsuredGuest, EnsuredUser, runApiPipeline, runPipeline, SameOrigin, SkipSecurity, } from "./middlewares.js";
|
|
10
|
+
export { RouterErrorBoundary, SoftNavigationInterceptor, VeapRouter, type WithRouterConfig, withRouter, } from "./react/index.js";
|
|
11
|
+
export { extractParamName, isCatchAllSegment, isDynamicSegment, isGroupSegment, isOptionalCatchAllSegment, isParallelSlot, type LayoutChainEntry, type MatchResult, type RouteNode, RouteTree, resolveMagicPrefix, segmentMatchesUrlPart, } from "./route-tree.js";
|
|
12
|
+
export { RouterService } from "./router.service.js";
|
|
13
|
+
export { discoverRoutes, generateRouteManifest, globToTree, } from "./scanner.js";
|
|
14
|
+
export { collectApiMiddlewares, collectAuthRequirements, collectMiddlewares, } from "./utils.js";
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module router
|
|
3
|
+
* @description Virtual Router API for Veap Plugin system.
|
|
4
|
+
*/
|
|
5
|
+
export { ROUTER_SERVICE, SITE_LAYOUT } from "../common/tokens.js";
|
|
6
|
+
export { addRouteRewrite, buildRouteTree, getModulesWithHomepage, getPluginsWithHomepage, getRouteRewrites, matchRoute, } from "./discovery.js";
|
|
7
|
+
export { handleVeapApiRequest } from "./handler.js";
|
|
8
|
+
export { matchRoute as matchVirtualRoute } from "./matcher.js";
|
|
9
|
+
export { ApiEnsuredAuth, ApiSameOrigin, EnsuredAuth, EnsuredGuest, EnsuredUser, runApiPipeline, runPipeline, SameOrigin, SkipSecurity, } from "./middlewares.js";
|
|
10
|
+
export { RouterErrorBoundary, SoftNavigationInterceptor, VeapRouter, withRouter, } from "./react/index.js";
|
|
11
|
+
export { extractParamName, isCatchAllSegment, isDynamicSegment, isGroupSegment, isOptionalCatchAllSegment, isParallelSlot, RouteTree, resolveMagicPrefix, segmentMatchesUrlPart, } from "./route-tree.js";
|
|
12
|
+
export { RouterService } from "./router.service.js";
|
|
13
|
+
export { discoverRoutes, generateRouteManifest, globToTree, } from "./scanner.js";
|
|
14
|
+
export { collectApiMiddlewares, collectAuthRequirements, collectMiddlewares, } from "./utils.js";
|