@intranefr/superbackend 1.6.5 → 1.6.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intranefr/superbackend",
3
- "version": "1.6.5",
3
+ "version": "1.6.6",
4
4
  "description": "Node.js middleware that gives your project backend superpowers",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/src/middleware.js CHANGED
@@ -99,6 +99,12 @@ function createMiddleware(options = {}) {
99
99
 
100
100
  const bootstrapPluginsRuntime = async () => {
101
101
  try {
102
+ if (options.plugins?.extraRoots) {
103
+ const roots = Array.isArray(options.plugins.extraRoots) ? options.plugins.extraRoots : [];
104
+ for (const root of roots) {
105
+ await pluginsService.loadAllPluginsFromFolder(root, { context: {} });
106
+ }
107
+ }
102
108
  const superbackend = globalThis.superbackend || globalThis.saasbackend || {};
103
109
  await pluginsService.bootstrap({
104
110
  context: {
@@ -249,6 +249,7 @@ async function logAuditEntry({
249
249
  model,
250
250
  variables: variables || {},
251
251
  requestOptions: requestOptions || {},
252
+ auditContext: requestOptions?.auditContext || null,
252
253
  errorMessage,
253
254
  usage: usage || null,
254
255
  },
@@ -12,6 +12,8 @@ const DEFAULT_REGISTRY_ID = 'plugins';
12
12
  const exposedServices = {};
13
13
  const exposedHelpers = {};
14
14
 
15
+ const additionalPluginsRoots = new Set();
16
+
15
17
  function sha256(value) {
16
18
  return crypto.createHash('sha256').update(String(value || ''), 'utf8').digest('hex');
17
19
  }
@@ -24,6 +26,24 @@ function resolvePluginsRoot(customRoot) {
24
26
  return customRoot || path.join(process.cwd(), 'plugins');
25
27
  }
26
28
 
29
+ function listPluginsRoots({ pluginsRoot } = {}) {
30
+ const roots = [];
31
+ const primary = resolvePluginsRoot(pluginsRoot);
32
+ if (primary) roots.push(primary);
33
+ for (const extra of additionalPluginsRoots) {
34
+ if (extra && typeof extra === 'string') roots.push(extra);
35
+ }
36
+ // de-dup while preserving order
37
+ return Array.from(new Set(roots));
38
+ }
39
+
40
+ function registerPluginsRoot(absolutePath) {
41
+ const root = String(absolutePath || '').trim();
42
+ if (!root) return { ok: false, reason: 'empty_path' };
43
+ additionalPluginsRoots.add(root);
44
+ return { ok: true, root };
45
+ }
46
+
27
47
  function normalizePlugin(rawModule, pluginId, absoluteDir) {
28
48
  const candidate = rawModule && typeof rawModule === 'object' ? rawModule : {};
29
49
  const hooks = candidate.hooks && typeof candidate.hooks === 'object' ? candidate.hooks : {};
@@ -131,28 +151,40 @@ function readPluginModule(pluginDir) {
131
151
  }
132
152
 
133
153
  async function discoverPlugins({ pluginsRoot } = {}) {
134
- const root = resolvePluginsRoot(pluginsRoot);
135
- if (!fs.existsSync(root)) return [];
136
-
137
- const entries = fs.readdirSync(root, { withFileTypes: true });
138
- const plugins = [];
139
-
140
- for (const entry of entries) {
141
- if (!entry.isDirectory()) continue;
142
- const pluginId = entry.name;
143
- const absoluteDir = path.join(root, pluginId);
144
- const loaded = readPluginModule(absoluteDir);
145
- if (!loaded) continue;
146
-
147
- const plugin = normalizePlugin(loaded, pluginId, absoluteDir);
148
- if (!plugin.id) continue;
149
- plugins.push(plugin);
154
+ const roots = listPluginsRoots({ pluginsRoot });
155
+ const pluginById = new Map();
156
+
157
+ for (const root of roots) {
158
+ if (!fs.existsSync(root)) continue;
159
+ const entries = fs.readdirSync(root, { withFileTypes: true });
160
+
161
+ for (const entry of entries) {
162
+ if (!entry.isDirectory()) continue;
163
+ const pluginId = entry.name;
164
+ const absoluteDir = path.join(root, pluginId);
165
+ const loaded = readPluginModule(absoluteDir);
166
+ if (!loaded) continue;
167
+
168
+ const plugin = normalizePlugin(loaded, pluginId, absoluteDir);
169
+ if (!plugin.id) continue;
170
+
171
+ // First discovered wins (stable and avoids unexpected overrides)
172
+ if (!pluginById.has(plugin.id)) {
173
+ pluginById.set(plugin.id, plugin);
174
+ }
175
+ }
150
176
  }
151
177
 
178
+ const plugins = Array.from(pluginById.values());
152
179
  plugins.sort((a, b) => a.id.localeCompare(b.id));
153
180
  return plugins;
154
181
  }
155
182
 
183
+ async function loadAllPluginsFromFolder(absolutePath, { context } = {}) {
184
+ registerPluginsRoot(absolutePath);
185
+ return bootstrap({ context });
186
+ }
187
+
156
188
  async function ensurePluginsRegistry() {
157
189
  return registryService.ensureRegistry({
158
190
  id: DEFAULT_REGISTRY_ID,
@@ -337,6 +369,8 @@ module.exports = {
337
369
  PLUGINS_STATE_KEY,
338
370
  DEFAULT_REGISTRY_ID,
339
371
  ensurePluginsRegistry,
372
+ registerPluginsRoot,
373
+ loadAllPluginsFromFolder,
340
374
  discoverPlugins,
341
375
  bootstrap,
342
376
  listPlugins,