@backstage/backend-dynamic-feature-service 0.4.2-next.1 → 0.4.2-next.2
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 +25 -0
- package/dist/index.cjs.js +19 -695
- package/dist/index.cjs.js.map +1 -1
- package/dist/loader/CommonJSModuleLoader.cjs.js +43 -0
- package/dist/loader/CommonJSModuleLoader.cjs.js.map +1 -0
- package/dist/manager/plugin-manager.cjs.js +257 -0
- package/dist/manager/plugin-manager.cjs.js.map +1 -0
- package/dist/manager/types.cjs.js +8 -0
- package/dist/manager/types.cjs.js.map +1 -0
- package/dist/scanner/plugin-scanner.cjs.js +281 -0
- package/dist/scanner/plugin-scanner.cjs.js.map +1 -0
- package/dist/schemas/appBackendModule.cjs.js +32 -0
- package/dist/schemas/appBackendModule.cjs.js.map +1 -0
- package/dist/schemas/rootLoggerServiceFactory.cjs.js +40 -0
- package/dist/schemas/rootLoggerServiceFactory.cjs.js.map +1 -0
- package/dist/schemas/schemas.cjs.js +131 -0
- package/dist/schemas/schemas.cjs.js.map +1 -0
- package/package.json +15 -15
package/dist/index.cjs.js
CHANGED
|
@@ -1,698 +1,22 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
23
|
-
|
|
24
|
-
function _interopNamespaceCompat(e) {
|
|
25
|
-
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
26
|
-
var n = Object.create(null);
|
|
27
|
-
if (e) {
|
|
28
|
-
Object.keys(e).forEach(function (k) {
|
|
29
|
-
if (k !== 'default') {
|
|
30
|
-
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
31
|
-
Object.defineProperty(n, k, d.get ? d : {
|
|
32
|
-
enumerable: true,
|
|
33
|
-
get: function () { return e[k]; }
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
n.default = e;
|
|
39
|
-
return Object.freeze(n);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs$1);
|
|
43
|
-
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceCompat(fs);
|
|
44
|
-
var chokidar__namespace = /*#__PURE__*/_interopNamespaceCompat(chokidar);
|
|
45
|
-
var path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
|
|
46
|
-
var url__namespace = /*#__PURE__*/_interopNamespaceCompat(url);
|
|
47
|
-
var debounce__default = /*#__PURE__*/_interopDefaultCompat(debounce);
|
|
48
|
-
var fs__default = /*#__PURE__*/_interopDefaultCompat(fs$2);
|
|
49
|
-
|
|
50
|
-
function isBackendDynamicPluginInstaller(obj) {
|
|
51
|
-
return obj !== void 0 && "kind" in obj && (obj.kind === "new" || obj.kind === "legacy");
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
class PluginScanner {
|
|
55
|
-
constructor(config, logger, backstageRoot, preferAlpha) {
|
|
56
|
-
this.config = config;
|
|
57
|
-
this.logger = logger;
|
|
58
|
-
this.backstageRoot = backstageRoot;
|
|
59
|
-
this.preferAlpha = preferAlpha;
|
|
60
|
-
}
|
|
61
|
-
_rootDirectory;
|
|
62
|
-
configUnsubscribe;
|
|
63
|
-
rootDirectoryWatcher;
|
|
64
|
-
subscribers = [];
|
|
65
|
-
static create(options) {
|
|
66
|
-
const scanner = new PluginScanner(
|
|
67
|
-
options.config,
|
|
68
|
-
options.logger,
|
|
69
|
-
options.backstageRoot,
|
|
70
|
-
options.preferAlpha || false
|
|
71
|
-
);
|
|
72
|
-
scanner.applyConfig();
|
|
73
|
-
return scanner;
|
|
74
|
-
}
|
|
75
|
-
subscribeToRootDirectoryChange(subscriber) {
|
|
76
|
-
this.subscribers.push(subscriber);
|
|
77
|
-
}
|
|
78
|
-
get rootDirectory() {
|
|
79
|
-
return this._rootDirectory;
|
|
80
|
-
}
|
|
81
|
-
applyConfig() {
|
|
82
|
-
const dynamicPlugins = this.config.getOptional("dynamicPlugins");
|
|
83
|
-
if (!dynamicPlugins) {
|
|
84
|
-
this.logger.info("'dynamicPlugins' config entry not found.");
|
|
85
|
-
this._rootDirectory = void 0;
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
if (typeof dynamicPlugins !== "object") {
|
|
89
|
-
this.logger.warn("'dynamicPlugins' config entry should be an object.");
|
|
90
|
-
this._rootDirectory = void 0;
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (!("rootDirectory" in dynamicPlugins)) {
|
|
94
|
-
this.logger.warn(
|
|
95
|
-
"'dynamicPlugins' config entry does not contain the 'rootDirectory' field."
|
|
96
|
-
);
|
|
97
|
-
this._rootDirectory = void 0;
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
if (typeof dynamicPlugins.rootDirectory !== "string") {
|
|
101
|
-
this.logger.warn(
|
|
102
|
-
"'dynamicPlugins.rootDirectory' config entry should be a string."
|
|
103
|
-
);
|
|
104
|
-
this._rootDirectory = void 0;
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const dynamicPluginsRootPath = path__namespace.isAbsolute(dynamicPlugins.rootDirectory) ? path__namespace.resolve(dynamicPlugins.rootDirectory) : path__namespace.resolve(this.backstageRoot, dynamicPlugins.rootDirectory);
|
|
108
|
-
if (!path__namespace.dirname(dynamicPluginsRootPath).startsWith(path__namespace.resolve(this.backstageRoot))) {
|
|
109
|
-
const nodePath = process.env.NODE_PATH;
|
|
110
|
-
const backstageNodeModules = path__namespace.resolve(
|
|
111
|
-
this.backstageRoot,
|
|
112
|
-
"node_modules"
|
|
113
|
-
);
|
|
114
|
-
if (!nodePath || !nodePath.split(path__namespace.delimiter).includes(backstageNodeModules)) {
|
|
115
|
-
throw new Error(
|
|
116
|
-
`Dynamic plugins under '${dynamicPluginsRootPath}' cannot access backstage modules in '${backstageNodeModules}'.
|
|
117
|
-
Please add '${backstageNodeModules}' to the 'NODE_PATH' when running the backstage backend.`
|
|
118
|
-
);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
if (!fs.lstatSync(dynamicPluginsRootPath).isDirectory()) {
|
|
122
|
-
throw new Error("Not a directory");
|
|
123
|
-
}
|
|
124
|
-
this._rootDirectory = dynamicPluginsRootPath;
|
|
125
|
-
}
|
|
126
|
-
async scanRoot() {
|
|
127
|
-
if (!this._rootDirectory) {
|
|
128
|
-
return { packages: [] };
|
|
129
|
-
}
|
|
130
|
-
const dynamicPluginsLocation = this._rootDirectory;
|
|
131
|
-
const scannedPlugins = [];
|
|
132
|
-
for (const dirEnt of await fs__namespace.readdir(dynamicPluginsLocation, {
|
|
133
|
-
withFileTypes: true
|
|
134
|
-
})) {
|
|
135
|
-
const pluginDir = dirEnt;
|
|
136
|
-
if (pluginDir.name === "lost+found") {
|
|
137
|
-
this.logger.debug(`skipping '${pluginDir.name}' system directory`);
|
|
138
|
-
continue;
|
|
139
|
-
}
|
|
140
|
-
const pluginHome = path__namespace.normalize(
|
|
141
|
-
path__namespace.resolve(dynamicPluginsLocation, pluginDir.name)
|
|
142
|
-
);
|
|
143
|
-
if (dirEnt.isSymbolicLink()) {
|
|
144
|
-
if (!(await fs__namespace.lstat(await fs__namespace.readlink(pluginHome))).isDirectory()) {
|
|
145
|
-
this.logger.info(
|
|
146
|
-
`skipping '${pluginHome}' since it is not a directory`
|
|
147
|
-
);
|
|
148
|
-
continue;
|
|
149
|
-
}
|
|
150
|
-
} else if (!dirEnt.isDirectory()) {
|
|
151
|
-
this.logger.info(
|
|
152
|
-
`skipping '${pluginHome}' since it is not a directory`
|
|
153
|
-
);
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
let scannedPlugin;
|
|
157
|
-
let platform;
|
|
158
|
-
try {
|
|
159
|
-
scannedPlugin = await this.scanDir(pluginHome);
|
|
160
|
-
if (!scannedPlugin.manifest.main) {
|
|
161
|
-
throw new Error("field 'main' not found in 'package.json'");
|
|
162
|
-
}
|
|
163
|
-
if (scannedPlugin.manifest.backstage?.role) {
|
|
164
|
-
platform = cliNode.PackageRoles.getRoleInfo(
|
|
165
|
-
scannedPlugin.manifest.backstage.role
|
|
166
|
-
).platform;
|
|
167
|
-
} else {
|
|
168
|
-
throw new Error("field 'backstage.role' not found in 'package.json'");
|
|
169
|
-
}
|
|
170
|
-
} catch (e) {
|
|
171
|
-
this.logger.error(
|
|
172
|
-
`failed to load dynamic plugin manifest from '${pluginHome}'`,
|
|
173
|
-
e
|
|
174
|
-
);
|
|
175
|
-
continue;
|
|
176
|
-
}
|
|
177
|
-
if (platform === "node") {
|
|
178
|
-
if (this.preferAlpha) {
|
|
179
|
-
const pluginHomeAlpha = path__namespace.resolve(pluginHome, "alpha");
|
|
180
|
-
if (fs.existsSync(pluginHomeAlpha)) {
|
|
181
|
-
if ((await fs__namespace.lstat(pluginHomeAlpha)).isDirectory()) {
|
|
182
|
-
const backstage = scannedPlugin.manifest.backstage;
|
|
183
|
-
try {
|
|
184
|
-
scannedPlugin = await this.scanDir(pluginHomeAlpha);
|
|
185
|
-
} catch (e) {
|
|
186
|
-
this.logger.error(
|
|
187
|
-
`failed to load dynamic plugin manifest from '${pluginHomeAlpha}'`,
|
|
188
|
-
e
|
|
189
|
-
);
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
scannedPlugin.manifest.backstage = backstage;
|
|
193
|
-
} else {
|
|
194
|
-
this.logger.warn(
|
|
195
|
-
`skipping '${pluginHomeAlpha}' since it is not a directory`
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
scannedPlugins.push(scannedPlugin);
|
|
202
|
-
}
|
|
203
|
-
return { packages: scannedPlugins };
|
|
204
|
-
}
|
|
205
|
-
async scanDir(pluginHome) {
|
|
206
|
-
const manifestFile = path__namespace.resolve(pluginHome, "package.json");
|
|
207
|
-
const content = await fs__namespace.readFile(manifestFile);
|
|
208
|
-
const manifest = JSON.parse(content.toString());
|
|
209
|
-
return {
|
|
210
|
-
location: url__namespace.pathToFileURL(pluginHome),
|
|
211
|
-
manifest
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
async trackChanges() {
|
|
215
|
-
const setupRootDirectoryWatcher = async () => {
|
|
216
|
-
return new Promise((resolve, reject) => {
|
|
217
|
-
if (!this._rootDirectory) {
|
|
218
|
-
resolve();
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
const callSubscribers = debounce__default.default(() => {
|
|
222
|
-
this.subscribers.forEach((s) => s());
|
|
223
|
-
}, 500);
|
|
224
|
-
let ready = false;
|
|
225
|
-
this.rootDirectoryWatcher = chokidar__namespace.watch(this._rootDirectory, {
|
|
226
|
-
ignoreInitial: true,
|
|
227
|
-
followSymlinks: true,
|
|
228
|
-
depth: 1,
|
|
229
|
-
disableGlobbing: true
|
|
230
|
-
}).on(
|
|
231
|
-
"all",
|
|
232
|
-
(event, eventPath, _) => {
|
|
233
|
-
if (["addDir", "unlinkDir"].includes(event) && path__namespace.dirname(eventPath) === this._rootDirectory || ["add", "unlink", "change"].includes(event) && path__namespace.dirname(path__namespace.dirname(eventPath)) === this._rootDirectory && path__namespace.basename(eventPath) === "package.json") {
|
|
234
|
-
this.logger.info(
|
|
235
|
-
`rootDirectory changed (${event} - ${eventPath}): scanning plugins again`
|
|
236
|
-
);
|
|
237
|
-
callSubscribers();
|
|
238
|
-
} else {
|
|
239
|
-
this.logger.debug(
|
|
240
|
-
`rootDirectory changed (${event} - ${eventPath}): no need to scan plugins again`
|
|
241
|
-
);
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
).on("error", (error) => {
|
|
245
|
-
this.logger.error(
|
|
246
|
-
`error while watching '${this.rootDirectory}'`,
|
|
247
|
-
error
|
|
248
|
-
);
|
|
249
|
-
if (!ready) {
|
|
250
|
-
reject(error);
|
|
251
|
-
}
|
|
252
|
-
}).on("ready", () => {
|
|
253
|
-
ready = true;
|
|
254
|
-
resolve();
|
|
255
|
-
});
|
|
256
|
-
});
|
|
257
|
-
};
|
|
258
|
-
await setupRootDirectoryWatcher();
|
|
259
|
-
if (this.config.subscribe) {
|
|
260
|
-
const { unsubscribe } = this.config.subscribe(async () => {
|
|
261
|
-
const oldRootDirectory = this._rootDirectory;
|
|
262
|
-
try {
|
|
263
|
-
this.applyConfig();
|
|
264
|
-
} catch (e) {
|
|
265
|
-
this.logger.error(
|
|
266
|
-
"failed to apply new config for dynamic plugins",
|
|
267
|
-
e
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
|
-
if (oldRootDirectory !== this._rootDirectory) {
|
|
271
|
-
this.logger.info(
|
|
272
|
-
`rootDirectory changed in Config from '${oldRootDirectory}' to '${this._rootDirectory}'`
|
|
273
|
-
);
|
|
274
|
-
this.subscribers.forEach((s) => s());
|
|
275
|
-
if (this.rootDirectoryWatcher) {
|
|
276
|
-
await this.rootDirectoryWatcher.close();
|
|
277
|
-
}
|
|
278
|
-
await setupRootDirectoryWatcher();
|
|
279
|
-
}
|
|
280
|
-
});
|
|
281
|
-
this.configUnsubscribe = unsubscribe;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
async untrackChanges() {
|
|
285
|
-
if (this.rootDirectoryWatcher) {
|
|
286
|
-
this.rootDirectoryWatcher.close();
|
|
287
|
-
}
|
|
288
|
-
if (this.configUnsubscribe) {
|
|
289
|
-
this.configUnsubscribe();
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
destructor() {
|
|
293
|
-
this.untrackChanges();
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
class CommonJSModuleLoader {
|
|
298
|
-
constructor(logger) {
|
|
299
|
-
this.logger = logger;
|
|
300
|
-
}
|
|
301
|
-
async bootstrap(backstageRoot, dynamicPluginsPaths) {
|
|
302
|
-
const backstageRootNodeModulesPath = `${backstageRoot}/node_modules`;
|
|
303
|
-
const dynamicNodeModulesPaths = [
|
|
304
|
-
...dynamicPluginsPaths.map((p) => path__namespace.default.resolve(p, "node_modules"))
|
|
305
|
-
];
|
|
306
|
-
const Module = require("module");
|
|
307
|
-
const oldNodeModulePaths = Module._nodeModulePaths;
|
|
308
|
-
Module._nodeModulePaths = (from) => {
|
|
309
|
-
const result = oldNodeModulePaths(from);
|
|
310
|
-
if (!dynamicPluginsPaths.some((p) => from.startsWith(p))) {
|
|
311
|
-
return result;
|
|
312
|
-
}
|
|
313
|
-
const filtered = result.filter((nodeModulePath) => {
|
|
314
|
-
return nodeModulePath === backstageRootNodeModulesPath || dynamicNodeModulesPaths.some((p) => nodeModulePath.startsWith(p));
|
|
315
|
-
});
|
|
316
|
-
this.logger.debug(
|
|
317
|
-
`Overriding node_modules search path for dynamic plugin ${from} to: ${filtered}`
|
|
318
|
-
);
|
|
319
|
-
return filtered;
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
async load(packagePath) {
|
|
323
|
-
return await require(
|
|
324
|
-
/* webpackIgnore: true */
|
|
325
|
-
packagePath
|
|
326
|
-
);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
class DynamicPluginManager {
|
|
331
|
-
constructor(logger, packages, moduleLoader) {
|
|
332
|
-
this.logger = logger;
|
|
333
|
-
this.packages = packages;
|
|
334
|
-
this.moduleLoader = moduleLoader;
|
|
335
|
-
this._plugins = [];
|
|
336
|
-
this._availablePackages = packages;
|
|
337
|
-
}
|
|
338
|
-
static async create(options) {
|
|
339
|
-
const backstageRoot = cliCommon.findPaths(__dirname).targetRoot;
|
|
340
|
-
const scanner = PluginScanner.create({
|
|
341
|
-
config: options.config,
|
|
342
|
-
logger: options.logger,
|
|
343
|
-
backstageRoot,
|
|
344
|
-
preferAlpha: options.preferAlpha
|
|
345
|
-
});
|
|
346
|
-
const scannedPlugins = (await scanner.scanRoot()).packages;
|
|
347
|
-
scanner.trackChanges();
|
|
348
|
-
const moduleLoader = options.moduleLoader || new CommonJSModuleLoader(options.logger);
|
|
349
|
-
const manager = new DynamicPluginManager(
|
|
350
|
-
options.logger,
|
|
351
|
-
scannedPlugins,
|
|
352
|
-
moduleLoader
|
|
353
|
-
);
|
|
354
|
-
const dynamicPluginsPaths = scannedPlugins.map(
|
|
355
|
-
(p) => fs__namespace$1.realpathSync(
|
|
356
|
-
path__namespace.default.dirname(
|
|
357
|
-
path__namespace.default.dirname(
|
|
358
|
-
path__namespace.default.resolve(url__namespace.fileURLToPath(p.location), p.manifest.main)
|
|
359
|
-
)
|
|
360
|
-
)
|
|
361
|
-
)
|
|
362
|
-
);
|
|
363
|
-
moduleLoader.bootstrap(backstageRoot, dynamicPluginsPaths);
|
|
364
|
-
scanner.subscribeToRootDirectoryChange(async () => {
|
|
365
|
-
manager._availablePackages = (await scanner.scanRoot()).packages;
|
|
366
|
-
});
|
|
367
|
-
manager._plugins.push(...await manager.loadPlugins());
|
|
368
|
-
return manager;
|
|
369
|
-
}
|
|
370
|
-
_plugins;
|
|
371
|
-
_availablePackages;
|
|
372
|
-
get availablePackages() {
|
|
373
|
-
return this._availablePackages;
|
|
374
|
-
}
|
|
375
|
-
addBackendPlugin(plugin) {
|
|
376
|
-
this._plugins.push(plugin);
|
|
377
|
-
}
|
|
378
|
-
async loadPlugins() {
|
|
379
|
-
const loadedPlugins = [];
|
|
380
|
-
for (const scannedPlugin of this.packages) {
|
|
381
|
-
const platform = cliNode.PackageRoles.getRoleInfo(
|
|
382
|
-
scannedPlugin.manifest.backstage.role
|
|
383
|
-
).platform;
|
|
384
|
-
if (platform === "node" && scannedPlugin.manifest.backstage.role.includes("-plugin")) {
|
|
385
|
-
const plugin = await this.loadBackendPlugin(scannedPlugin);
|
|
386
|
-
if (plugin !== void 0) {
|
|
387
|
-
loadedPlugins.push(plugin);
|
|
388
|
-
}
|
|
389
|
-
} else {
|
|
390
|
-
loadedPlugins.push({
|
|
391
|
-
name: scannedPlugin.manifest.name,
|
|
392
|
-
version: scannedPlugin.manifest.version,
|
|
393
|
-
role: scannedPlugin.manifest.backstage.role,
|
|
394
|
-
platform: "web"
|
|
395
|
-
// TODO(davidfestal): add required front-end plugin information here.
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return loadedPlugins;
|
|
400
|
-
}
|
|
401
|
-
async loadBackendPlugin(plugin) {
|
|
402
|
-
const packagePath = url__namespace.fileURLToPath(
|
|
403
|
-
`${plugin.location}/${plugin.manifest.main}`
|
|
404
|
-
);
|
|
405
|
-
try {
|
|
406
|
-
const pluginModule = await this.moduleLoader.load(packagePath);
|
|
407
|
-
let dynamicPluginInstaller;
|
|
408
|
-
if (isBackendFeature(pluginModule.default)) {
|
|
409
|
-
dynamicPluginInstaller = {
|
|
410
|
-
kind: "new",
|
|
411
|
-
install: () => pluginModule.default
|
|
412
|
-
};
|
|
413
|
-
} else if (isBackendFeatureFactory(pluginModule.default)) {
|
|
414
|
-
dynamicPluginInstaller = {
|
|
415
|
-
kind: "new",
|
|
416
|
-
install: pluginModule.default
|
|
417
|
-
};
|
|
418
|
-
} else {
|
|
419
|
-
dynamicPluginInstaller = pluginModule.dynamicPluginInstaller;
|
|
420
|
-
}
|
|
421
|
-
if (!isBackendDynamicPluginInstaller(dynamicPluginInstaller)) {
|
|
422
|
-
this.logger.error(
|
|
423
|
-
`dynamic backend plugin '${plugin.manifest.name}' could not be loaded from '${plugin.location}': the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`
|
|
424
|
-
);
|
|
425
|
-
return void 0;
|
|
426
|
-
}
|
|
427
|
-
this.logger.info(
|
|
428
|
-
`loaded dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`
|
|
429
|
-
);
|
|
430
|
-
return {
|
|
431
|
-
name: plugin.manifest.name,
|
|
432
|
-
version: plugin.manifest.version,
|
|
433
|
-
platform: "node",
|
|
434
|
-
role: plugin.manifest.backstage.role,
|
|
435
|
-
installer: dynamicPluginInstaller
|
|
436
|
-
};
|
|
437
|
-
} catch (error) {
|
|
438
|
-
this.logger.error(
|
|
439
|
-
`an error occurred while loading dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`,
|
|
440
|
-
error
|
|
441
|
-
);
|
|
442
|
-
return void 0;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
backendPlugins() {
|
|
446
|
-
return this._plugins.filter(
|
|
447
|
-
(p) => p.platform === "node"
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
frontendPlugins() {
|
|
451
|
-
return this._plugins.filter(
|
|
452
|
-
(p) => p.platform === "web"
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
plugins() {
|
|
456
|
-
return this._plugins;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
const dynamicPluginsServiceRef = backendPluginApi.createServiceRef(
|
|
460
|
-
{
|
|
461
|
-
id: "core.dynamicplugins",
|
|
462
|
-
scope: "root"
|
|
463
|
-
}
|
|
464
|
-
);
|
|
465
|
-
const dynamicPluginsServiceFactoryWithOptions = (options) => backendPluginApi.createServiceFactory({
|
|
466
|
-
service: dynamicPluginsServiceRef,
|
|
467
|
-
deps: {
|
|
468
|
-
config: backendPluginApi.coreServices.rootConfig,
|
|
469
|
-
logger: backendPluginApi.coreServices.rootLogger
|
|
470
|
-
},
|
|
471
|
-
async factory({ config, logger }) {
|
|
472
|
-
return await DynamicPluginManager.create({
|
|
473
|
-
config,
|
|
474
|
-
logger,
|
|
475
|
-
preferAlpha: true,
|
|
476
|
-
moduleLoader: options?.moduleLoader?.(logger)
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
});
|
|
480
|
-
const dynamicPluginsServiceFactory = dynamicPluginsServiceFactoryWithOptions();
|
|
481
|
-
class DynamicPluginsEnabledFeatureDiscoveryService {
|
|
482
|
-
constructor(dynamicPlugins, featureDiscoveryService) {
|
|
483
|
-
this.dynamicPlugins = dynamicPlugins;
|
|
484
|
-
this.featureDiscoveryService = featureDiscoveryService;
|
|
485
|
-
}
|
|
486
|
-
async getBackendFeatures() {
|
|
487
|
-
const staticFeatures = (await this.featureDiscoveryService?.getBackendFeatures())?.features ?? [];
|
|
488
|
-
return {
|
|
489
|
-
features: [
|
|
490
|
-
...this.dynamicPlugins.backendPlugins().flatMap((plugin) => {
|
|
491
|
-
if (plugin.installer.kind === "new") {
|
|
492
|
-
const installed = plugin.installer.install();
|
|
493
|
-
if (Array.isArray(installed)) {
|
|
494
|
-
return installed;
|
|
495
|
-
}
|
|
496
|
-
return [installed];
|
|
497
|
-
}
|
|
498
|
-
return [];
|
|
499
|
-
}),
|
|
500
|
-
...staticFeatures
|
|
501
|
-
]
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
const dynamicPluginsFeatureDiscoveryServiceFactory = backendPluginApi.createServiceFactory({
|
|
506
|
-
service: alpha.featureDiscoveryServiceRef,
|
|
507
|
-
deps: {
|
|
508
|
-
config: backendPluginApi.coreServices.rootConfig,
|
|
509
|
-
dynamicPlugins: dynamicPluginsServiceRef
|
|
510
|
-
},
|
|
511
|
-
factory({ dynamicPlugins }) {
|
|
512
|
-
return new DynamicPluginsEnabledFeatureDiscoveryService(dynamicPlugins);
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
const dynamicPluginsFeatureDiscoveryLoaderWithOptions = (options) => backendPluginApi.createBackendFeatureLoader({
|
|
516
|
-
deps: {
|
|
517
|
-
config: backendPluginApi.coreServices.rootConfig,
|
|
518
|
-
logger: backendPluginApi.coreServices.rootLogger
|
|
519
|
-
},
|
|
520
|
-
async loader({ config, logger }) {
|
|
521
|
-
const manager = await DynamicPluginManager.create({
|
|
522
|
-
config,
|
|
523
|
-
logger,
|
|
524
|
-
preferAlpha: true,
|
|
525
|
-
moduleLoader: options?.moduleLoader?.(logger)
|
|
526
|
-
});
|
|
527
|
-
const service = new DynamicPluginsEnabledFeatureDiscoveryService(manager);
|
|
528
|
-
const { features } = await service.getBackendFeatures();
|
|
529
|
-
return features;
|
|
530
|
-
}
|
|
531
|
-
});
|
|
532
|
-
const dynamicPluginsFeatureDiscoveryLoader = Object.assign(
|
|
533
|
-
dynamicPluginsFeatureDiscoveryLoaderWithOptions,
|
|
534
|
-
dynamicPluginsFeatureDiscoveryLoaderWithOptions()
|
|
535
|
-
);
|
|
536
|
-
function isBackendFeature(value) {
|
|
537
|
-
return !!value && (typeof value === "object" || typeof value === "function") && value.$$type === "@backstage/BackendFeature";
|
|
538
|
-
}
|
|
539
|
-
function isBackendFeatureFactory(value) {
|
|
540
|
-
return !!value && typeof value === "function" && value.$$type === "@backstage/BackendFeatureFactory";
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
const dynamicPluginsSchemasServiceRef = backendPluginApi.createServiceRef({
|
|
544
|
-
id: "core.dynamicplugins.schemas",
|
|
545
|
-
scope: "root"
|
|
546
|
-
});
|
|
547
|
-
const dynamicPluginsSchemasServiceFactoryWithOptions = (options) => backendPluginApi.createServiceFactory({
|
|
548
|
-
service: dynamicPluginsSchemasServiceRef,
|
|
549
|
-
deps: {
|
|
550
|
-
config: backendPluginApi.coreServices.rootConfig
|
|
551
|
-
},
|
|
552
|
-
factory({ config }) {
|
|
553
|
-
let additionalSchemas;
|
|
554
|
-
return {
|
|
555
|
-
async addDynamicPluginsSchemas(configSchema) {
|
|
556
|
-
if (!additionalSchemas) {
|
|
557
|
-
const logger = {
|
|
558
|
-
...console,
|
|
559
|
-
child() {
|
|
560
|
-
return this;
|
|
561
|
-
}
|
|
562
|
-
};
|
|
563
|
-
const scanner = PluginScanner.create({
|
|
564
|
-
config,
|
|
565
|
-
logger,
|
|
566
|
-
// eslint-disable-next-line no-restricted-syntax
|
|
567
|
-
backstageRoot: cliCommon.findPaths(__dirname).targetRoot,
|
|
568
|
-
preferAlpha: true
|
|
569
|
-
});
|
|
570
|
-
const { packages } = await scanner.scanRoot();
|
|
571
|
-
additionalSchemas = await gatherDynamicPluginsSchemas(
|
|
572
|
-
packages,
|
|
573
|
-
logger,
|
|
574
|
-
options?.schemaLocator
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
const serialized = configSchema.serialize();
|
|
578
|
-
if (serialized?.backstageConfigSchemaVersion !== 1) {
|
|
579
|
-
throw new Error(
|
|
580
|
-
"Serialized configuration schema is invalid or has an invalid version number"
|
|
581
|
-
);
|
|
582
|
-
}
|
|
583
|
-
const schemas = serialized.schemas;
|
|
584
|
-
schemas.push(
|
|
585
|
-
...Object.keys(additionalSchemas).map((context) => {
|
|
586
|
-
return {
|
|
587
|
-
path: context,
|
|
588
|
-
value: additionalSchemas[context]
|
|
589
|
-
};
|
|
590
|
-
})
|
|
591
|
-
);
|
|
592
|
-
serialized.schemas = schemas;
|
|
593
|
-
return {
|
|
594
|
-
schema: await configLoader.loadConfigSchema({
|
|
595
|
-
serialized
|
|
596
|
-
})
|
|
597
|
-
};
|
|
598
|
-
}
|
|
599
|
-
};
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
const dynamicPluginsSchemasServiceFactory = dynamicPluginsSchemasServiceFactoryWithOptions();
|
|
603
|
-
async function gatherDynamicPluginsSchemas(packages, logger, schemaLocator = () => path__namespace.join("dist", "configSchema.json")) {
|
|
604
|
-
const allSchemas = {};
|
|
605
|
-
for (const pluginPackage of packages) {
|
|
606
|
-
let schemaLocation = schemaLocator(pluginPackage);
|
|
607
|
-
if (!path__namespace.isAbsolute(schemaLocation)) {
|
|
608
|
-
let pluginLocation = url__namespace.fileURLToPath(pluginPackage.location);
|
|
609
|
-
if (path__namespace.basename(pluginLocation) === "alpha") {
|
|
610
|
-
pluginLocation = path__namespace.dirname(pluginLocation);
|
|
611
|
-
}
|
|
612
|
-
schemaLocation = path__namespace.resolve(pluginLocation, schemaLocation);
|
|
613
|
-
}
|
|
614
|
-
if (!await fs__default.default.pathExists(schemaLocation)) {
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
const serialized = await fs__default.default.readJson(schemaLocation);
|
|
618
|
-
if (!serialized) {
|
|
619
|
-
continue;
|
|
620
|
-
}
|
|
621
|
-
if (lodash.isEmpty(serialized)) {
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
if (!serialized?.$schema || serialized?.type !== "object") {
|
|
625
|
-
logger.error(
|
|
626
|
-
`Serialized configuration schema is invalid for plugin ${pluginPackage.manifest.name}`
|
|
627
|
-
);
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
allSchemas[schemaLocation] = serialized;
|
|
631
|
-
}
|
|
632
|
-
return allSchemas;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
const dynamicPluginsFrontendSchemas = backendPluginApi.createBackendModule({
|
|
636
|
-
pluginId: "app",
|
|
637
|
-
moduleId: "core.dynamicplugins.frontendSchemas",
|
|
638
|
-
register(reg) {
|
|
639
|
-
reg.registerInit({
|
|
640
|
-
deps: {
|
|
641
|
-
config: backendPluginApi.coreServices.rootConfig,
|
|
642
|
-
schemas: dynamicPluginsSchemasServiceRef,
|
|
643
|
-
configSchemaExtension: pluginAppNode.configSchemaExtensionPoint
|
|
644
|
-
},
|
|
645
|
-
async init({ config, schemas, configSchemaExtension }) {
|
|
646
|
-
const appPackageName = config.getOptionalString("app.packageName") ?? "app";
|
|
647
|
-
const appDistDir = backendPluginApi.resolvePackagePath(appPackageName, "dist");
|
|
648
|
-
const compiledConfigSchema = await pluginAppNode.loadCompiledConfigSchema(appDistDir);
|
|
649
|
-
if (compiledConfigSchema) {
|
|
650
|
-
configSchemaExtension.setConfigSchema(
|
|
651
|
-
(await schemas.addDynamicPluginsSchemas(compiledConfigSchema)).schema
|
|
652
|
-
);
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
});
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
const dynamicPluginsRootLoggerServiceFactory = backendPluginApi.createServiceFactory({
|
|
660
|
-
service: backendPluginApi.coreServices.rootLogger,
|
|
661
|
-
deps: {
|
|
662
|
-
config: backendPluginApi.coreServices.rootConfig,
|
|
663
|
-
schemas: dynamicPluginsSchemasServiceRef
|
|
664
|
-
},
|
|
665
|
-
async factory({ config, schemas }) {
|
|
666
|
-
const logger = rootLogger.WinstonLogger.create({
|
|
667
|
-
meta: {
|
|
668
|
-
service: "backstage"
|
|
669
|
-
},
|
|
670
|
-
level: process.env.LOG_LEVEL || "info",
|
|
671
|
-
format: process.env.NODE_ENV === "production" ? winston.format.json() : rootLogger.WinstonLogger.colorFormat(),
|
|
672
|
-
transports: [new winston.transports.Console()]
|
|
673
|
-
});
|
|
674
|
-
const configSchema = await configLoader.loadConfigSchema({
|
|
675
|
-
dependencies: (await getPackages.getPackages(process.cwd())).packages.map((p) => p.packageJson.name)
|
|
676
|
-
});
|
|
677
|
-
const secretEnumerator = await backendCommon.createConfigSecretEnumerator({
|
|
678
|
-
logger,
|
|
679
|
-
schema: (await schemas.addDynamicPluginsSchemas(configSchema)).schema
|
|
680
|
-
});
|
|
681
|
-
logger.addRedactions(secretEnumerator(config));
|
|
682
|
-
config.subscribe?.(() => logger.addRedactions(secretEnumerator(config)));
|
|
683
|
-
return logger;
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
exports.DynamicPluginManager = DynamicPluginManager;
|
|
688
|
-
exports.dynamicPluginsFeatureDiscoveryLoader = dynamicPluginsFeatureDiscoveryLoader;
|
|
689
|
-
exports.dynamicPluginsFeatureDiscoveryServiceFactory = dynamicPluginsFeatureDiscoveryServiceFactory;
|
|
690
|
-
exports.dynamicPluginsFrontendSchemas = dynamicPluginsFrontendSchemas;
|
|
691
|
-
exports.dynamicPluginsRootLoggerServiceFactory = dynamicPluginsRootLoggerServiceFactory;
|
|
692
|
-
exports.dynamicPluginsSchemasServiceFactory = dynamicPluginsSchemasServiceFactory;
|
|
693
|
-
exports.dynamicPluginsSchemasServiceFactoryWithOptions = dynamicPluginsSchemasServiceFactoryWithOptions;
|
|
694
|
-
exports.dynamicPluginsServiceFactory = dynamicPluginsServiceFactory;
|
|
695
|
-
exports.dynamicPluginsServiceFactoryWithOptions = dynamicPluginsServiceFactoryWithOptions;
|
|
696
|
-
exports.dynamicPluginsServiceRef = dynamicPluginsServiceRef;
|
|
697
|
-
exports.isBackendDynamicPluginInstaller = isBackendDynamicPluginInstaller;
|
|
3
|
+
var types = require('./manager/types.cjs.js');
|
|
4
|
+
var pluginManager = require('./manager/plugin-manager.cjs.js');
|
|
5
|
+
var schemas = require('./schemas/schemas.cjs.js');
|
|
6
|
+
var appBackendModule = require('./schemas/appBackendModule.cjs.js');
|
|
7
|
+
var rootLoggerServiceFactory = require('./schemas/rootLoggerServiceFactory.cjs.js');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
exports.isBackendDynamicPluginInstaller = types.isBackendDynamicPluginInstaller;
|
|
12
|
+
exports.DynamicPluginManager = pluginManager.DynamicPluginManager;
|
|
13
|
+
exports.dynamicPluginsFeatureDiscoveryLoader = pluginManager.dynamicPluginsFeatureDiscoveryLoader;
|
|
14
|
+
exports.dynamicPluginsFeatureDiscoveryServiceFactory = pluginManager.dynamicPluginsFeatureDiscoveryServiceFactory;
|
|
15
|
+
exports.dynamicPluginsServiceFactory = pluginManager.dynamicPluginsServiceFactory;
|
|
16
|
+
exports.dynamicPluginsServiceFactoryWithOptions = pluginManager.dynamicPluginsServiceFactoryWithOptions;
|
|
17
|
+
exports.dynamicPluginsServiceRef = pluginManager.dynamicPluginsServiceRef;
|
|
18
|
+
exports.dynamicPluginsSchemasServiceFactory = schemas.dynamicPluginsSchemasServiceFactory;
|
|
19
|
+
exports.dynamicPluginsSchemasServiceFactoryWithOptions = schemas.dynamicPluginsSchemasServiceFactoryWithOptions;
|
|
20
|
+
exports.dynamicPluginsFrontendSchemas = appBackendModule.dynamicPluginsFrontendSchemas;
|
|
21
|
+
exports.dynamicPluginsRootLoggerServiceFactory = rootLoggerServiceFactory.dynamicPluginsRootLoggerServiceFactory;
|
|
698
22
|
//# sourceMappingURL=index.cjs.js.map
|