@backstage/backend-dynamic-feature-service 0.4.2-next.1 → 0.4.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 +63 -0
- package/README.md +1 -2
- package/dist/features/features.cjs.js +35 -0
- package/dist/features/features.cjs.js.map +1 -0
- package/dist/index.cjs.js +20 -695
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +93 -53
- package/dist/loader/CommonJSModuleLoader.cjs.js +41 -0
- package/dist/loader/CommonJSModuleLoader.cjs.js.map +1 -0
- package/dist/manager/plugin-manager.cjs.js +274 -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 +283 -0
- package/dist/scanner/plugin-scanner.cjs.js.map +1 -0
- package/dist/schemas/frontend.cjs.js +32 -0
- package/dist/schemas/frontend.cjs.js.map +1 -0
- package/dist/schemas/rootLogger.cjs.js +45 -0
- package/dist/schemas/rootLogger.cjs.js.map +1 -0
- package/dist/schemas/schemas.cjs.js +133 -0
- package/dist/schemas/schemas.cjs.js.map +1 -0
- package/package.json +24 -22
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var types = require('./types.cjs.js');
|
|
4
|
+
var pluginScanner = require('../scanner/plugin-scanner.cjs.js');
|
|
5
|
+
var CommonJSModuleLoader = require('../loader/CommonJSModuleLoader.cjs.js');
|
|
6
|
+
var url = require('url');
|
|
7
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
8
|
+
var cliNode = require('@backstage/cli-node');
|
|
9
|
+
var cliCommon = require('@backstage/cli-common');
|
|
10
|
+
var path = require('path');
|
|
11
|
+
var fs = require('fs');
|
|
12
|
+
var alpha = require('@backstage/backend-plugin-api/alpha');
|
|
13
|
+
|
|
14
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
15
|
+
|
|
16
|
+
function _interopNamespaceCompat(e) {
|
|
17
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
18
|
+
var n = Object.create(null);
|
|
19
|
+
if (e) {
|
|
20
|
+
Object.keys(e).forEach(function (k) {
|
|
21
|
+
if (k !== 'default') {
|
|
22
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
23
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
get: function () { return e[k]; }
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
n.default = e;
|
|
31
|
+
return Object.freeze(n);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
var url__namespace = /*#__PURE__*/_interopNamespaceCompat(url);
|
|
35
|
+
var path__default = /*#__PURE__*/_interopDefaultCompat(path);
|
|
36
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs);
|
|
37
|
+
|
|
38
|
+
class DynamicPluginManager {
|
|
39
|
+
constructor(logger, packages, moduleLoader) {
|
|
40
|
+
this.logger = logger;
|
|
41
|
+
this.packages = packages;
|
|
42
|
+
this.moduleLoader = moduleLoader;
|
|
43
|
+
this._plugins = [];
|
|
44
|
+
this._availablePackages = packages;
|
|
45
|
+
}
|
|
46
|
+
static async create(options) {
|
|
47
|
+
const backstageRoot = cliCommon.findPaths(__dirname).targetRoot;
|
|
48
|
+
const scanner = pluginScanner.PluginScanner.create({
|
|
49
|
+
config: options.config,
|
|
50
|
+
logger: options.logger,
|
|
51
|
+
backstageRoot,
|
|
52
|
+
preferAlpha: options.preferAlpha
|
|
53
|
+
});
|
|
54
|
+
const scannedPlugins = (await scanner.scanRoot()).packages;
|
|
55
|
+
scanner.trackChanges();
|
|
56
|
+
const moduleLoader = options.moduleLoader || new CommonJSModuleLoader.CommonJSModuleLoader(options.logger);
|
|
57
|
+
const manager = new DynamicPluginManager(
|
|
58
|
+
options.logger,
|
|
59
|
+
scannedPlugins,
|
|
60
|
+
moduleLoader
|
|
61
|
+
);
|
|
62
|
+
const dynamicPluginsPaths = scannedPlugins.map(
|
|
63
|
+
(p) => fs__namespace.realpathSync(
|
|
64
|
+
path__default.default.dirname(
|
|
65
|
+
path__default.default.dirname(
|
|
66
|
+
path__default.default.resolve(url__namespace.fileURLToPath(p.location), p.manifest.main)
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
);
|
|
71
|
+
await moduleLoader.bootstrap(backstageRoot, dynamicPluginsPaths);
|
|
72
|
+
scanner.subscribeToRootDirectoryChange(async () => {
|
|
73
|
+
manager._availablePackages = (await scanner.scanRoot()).packages;
|
|
74
|
+
});
|
|
75
|
+
manager._plugins.push(...await manager.loadPlugins());
|
|
76
|
+
return manager;
|
|
77
|
+
}
|
|
78
|
+
_plugins;
|
|
79
|
+
_availablePackages;
|
|
80
|
+
get availablePackages() {
|
|
81
|
+
return this._availablePackages;
|
|
82
|
+
}
|
|
83
|
+
addBackendPlugin(plugin) {
|
|
84
|
+
this._plugins.push(plugin);
|
|
85
|
+
}
|
|
86
|
+
async loadPlugins() {
|
|
87
|
+
const loadedPlugins = [];
|
|
88
|
+
for (const scannedPlugin of this.packages) {
|
|
89
|
+
const role = scannedPlugin.manifest.backstage.role;
|
|
90
|
+
const platform = cliNode.PackageRoles.getRoleInfo(role).platform;
|
|
91
|
+
const isPlugin = role.endsWith("-plugin") || role.endsWith("-plugin-module") || role === "frontend-dynamic-container";
|
|
92
|
+
if (!isPlugin) {
|
|
93
|
+
this.logger.info(
|
|
94
|
+
`skipping dynamic plugin package '${scannedPlugin.manifest.name}' from '${scannedPlugin.location}': incompatible role '${role}'`
|
|
95
|
+
);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
switch (platform) {
|
|
99
|
+
case "node":
|
|
100
|
+
loadedPlugins.push(await this.loadBackendPlugin(scannedPlugin));
|
|
101
|
+
break;
|
|
102
|
+
case "web":
|
|
103
|
+
loadedPlugins.push({
|
|
104
|
+
name: scannedPlugin.manifest.name,
|
|
105
|
+
version: scannedPlugin.manifest.version,
|
|
106
|
+
role: scannedPlugin.manifest.backstage.role,
|
|
107
|
+
platform: "web"
|
|
108
|
+
// TODO(davidfestal): add required front-end plugin information here.
|
|
109
|
+
});
|
|
110
|
+
break;
|
|
111
|
+
default:
|
|
112
|
+
this.logger.info(
|
|
113
|
+
`skipping dynamic plugin package '${scannedPlugin.manifest.name}' from '${scannedPlugin.location}': unrelated platform '${platform}'`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return loadedPlugins;
|
|
118
|
+
}
|
|
119
|
+
async loadBackendPlugin(plugin) {
|
|
120
|
+
const packagePath = url__namespace.fileURLToPath(
|
|
121
|
+
`${plugin.location}/${plugin.manifest.main}`
|
|
122
|
+
);
|
|
123
|
+
const dynamicPlugin = {
|
|
124
|
+
name: plugin.manifest.name,
|
|
125
|
+
version: plugin.manifest.version,
|
|
126
|
+
platform: "node",
|
|
127
|
+
role: plugin.manifest.backstage.role
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
const pluginModule = await this.moduleLoader.load(packagePath);
|
|
131
|
+
if (isBackendFeature(pluginModule.default)) {
|
|
132
|
+
dynamicPlugin.installer = {
|
|
133
|
+
kind: "new",
|
|
134
|
+
install: () => pluginModule.default
|
|
135
|
+
};
|
|
136
|
+
} else if (isBackendFeatureFactory(pluginModule.default)) {
|
|
137
|
+
dynamicPlugin.installer = {
|
|
138
|
+
kind: "new",
|
|
139
|
+
install: pluginModule.default
|
|
140
|
+
};
|
|
141
|
+
} else if (types.isBackendDynamicPluginInstaller(pluginModule.dynamicPluginInstaller)) {
|
|
142
|
+
dynamicPlugin.installer = pluginModule.dynamicPluginInstaller;
|
|
143
|
+
}
|
|
144
|
+
if (dynamicPlugin.installer) {
|
|
145
|
+
this.logger.info(
|
|
146
|
+
`loaded dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`
|
|
147
|
+
);
|
|
148
|
+
} else {
|
|
149
|
+
dynamicPlugin.failure = `the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`;
|
|
150
|
+
this.logger.error(
|
|
151
|
+
`dynamic backend plugin '${plugin.manifest.name}' could not be loaded from '${plugin.location}': ${dynamicPlugin.failure}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return dynamicPlugin;
|
|
155
|
+
} catch (error) {
|
|
156
|
+
const typedError = typeof error === "object" && "message" in error && "name" in error ? error : new Error(error);
|
|
157
|
+
dynamicPlugin.failure = `${typedError.name}: ${typedError.message}`;
|
|
158
|
+
this.logger.error(
|
|
159
|
+
`an error occurred while loading dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`,
|
|
160
|
+
typedError
|
|
161
|
+
);
|
|
162
|
+
return dynamicPlugin;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
backendPlugins(options) {
|
|
166
|
+
return this.plugins(options).filter(
|
|
167
|
+
(p) => p.platform === "node"
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
frontendPlugins(options) {
|
|
171
|
+
return this.plugins(options).filter(
|
|
172
|
+
(p) => p.platform === "web"
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
plugins(options) {
|
|
176
|
+
return this._plugins.filter((p) => options?.includeFailed || !p.failure);
|
|
177
|
+
}
|
|
178
|
+
getScannedPackage(plugin) {
|
|
179
|
+
const pkg = this.packages.find(
|
|
180
|
+
(p) => p.manifest.name === plugin.name && p.manifest.version === plugin.version
|
|
181
|
+
);
|
|
182
|
+
if (pkg === void 0) {
|
|
183
|
+
throw new Error(
|
|
184
|
+
`The scanned package of a dynamic plugin should always be available: ${plugin.name}/${plugin.version}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
return pkg;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const dynamicPluginsServiceRef = backendPluginApi.createServiceRef(
|
|
191
|
+
{
|
|
192
|
+
id: "core.dynamicplugins",
|
|
193
|
+
scope: "root"
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
const dynamicPluginsServiceFactoryWithOptions = (options) => backendPluginApi.createServiceFactory({
|
|
197
|
+
service: dynamicPluginsServiceRef,
|
|
198
|
+
deps: {
|
|
199
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
200
|
+
logger: backendPluginApi.coreServices.rootLogger
|
|
201
|
+
},
|
|
202
|
+
async factory({ config, logger }) {
|
|
203
|
+
return await DynamicPluginManager.create({
|
|
204
|
+
config,
|
|
205
|
+
logger,
|
|
206
|
+
preferAlpha: true,
|
|
207
|
+
moduleLoader: await options?.moduleLoader?.(logger)
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
const dynamicPluginsServiceFactory = Object.assign(
|
|
212
|
+
dynamicPluginsServiceFactoryWithOptions,
|
|
213
|
+
dynamicPluginsServiceFactoryWithOptions()
|
|
214
|
+
);
|
|
215
|
+
class DynamicPluginsEnabledFeatureDiscoveryService {
|
|
216
|
+
constructor(dynamicPlugins, featureDiscoveryService) {
|
|
217
|
+
this.dynamicPlugins = dynamicPlugins;
|
|
218
|
+
this.featureDiscoveryService = featureDiscoveryService;
|
|
219
|
+
}
|
|
220
|
+
async getBackendFeatures() {
|
|
221
|
+
const staticFeatures = (await this.featureDiscoveryService?.getBackendFeatures())?.features ?? [];
|
|
222
|
+
return {
|
|
223
|
+
features: [
|
|
224
|
+
...this.dynamicPlugins.backendPlugins().flatMap((plugin) => {
|
|
225
|
+
if (plugin.installer?.kind === "new") {
|
|
226
|
+
const installed = plugin.installer.install();
|
|
227
|
+
if (Array.isArray(installed)) {
|
|
228
|
+
return installed;
|
|
229
|
+
}
|
|
230
|
+
return [installed];
|
|
231
|
+
}
|
|
232
|
+
return [];
|
|
233
|
+
}),
|
|
234
|
+
...staticFeatures
|
|
235
|
+
]
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const dynamicPluginsFeatureDiscoveryServiceFactory = backendPluginApi.createServiceFactory({
|
|
240
|
+
service: alpha.featureDiscoveryServiceRef,
|
|
241
|
+
deps: {
|
|
242
|
+
config: backendPluginApi.coreServices.rootConfig,
|
|
243
|
+
dynamicPlugins: dynamicPluginsServiceRef
|
|
244
|
+
},
|
|
245
|
+
factory({ dynamicPlugins }) {
|
|
246
|
+
return new DynamicPluginsEnabledFeatureDiscoveryService(dynamicPlugins);
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
const dynamicPluginsFeatureDiscoveryLoader = backendPluginApi.createBackendFeatureLoader({
|
|
250
|
+
deps: {
|
|
251
|
+
dynamicPlugins: dynamicPluginsServiceRef
|
|
252
|
+
},
|
|
253
|
+
async loader({ dynamicPlugins }) {
|
|
254
|
+
const service = new DynamicPluginsEnabledFeatureDiscoveryService(
|
|
255
|
+
dynamicPlugins
|
|
256
|
+
);
|
|
257
|
+
const { features } = await service.getBackendFeatures();
|
|
258
|
+
return features;
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
function isBackendFeature(value) {
|
|
262
|
+
return !!value && (typeof value === "object" || typeof value === "function") && value.$$type === "@backstage/BackendFeature";
|
|
263
|
+
}
|
|
264
|
+
function isBackendFeatureFactory(value) {
|
|
265
|
+
return !!value && typeof value === "function" && value.$$type === "@backstage/BackendFeatureFactory";
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
exports.DynamicPluginManager = DynamicPluginManager;
|
|
269
|
+
exports.dynamicPluginsFeatureDiscoveryLoader = dynamicPluginsFeatureDiscoveryLoader;
|
|
270
|
+
exports.dynamicPluginsFeatureDiscoveryServiceFactory = dynamicPluginsFeatureDiscoveryServiceFactory;
|
|
271
|
+
exports.dynamicPluginsServiceFactory = dynamicPluginsServiceFactory;
|
|
272
|
+
exports.dynamicPluginsServiceFactoryWithOptions = dynamicPluginsServiceFactoryWithOptions;
|
|
273
|
+
exports.dynamicPluginsServiceRef = dynamicPluginsServiceRef;
|
|
274
|
+
//# sourceMappingURL=plugin-manager.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-manager.cjs.js","sources":["../../src/manager/plugin-manager.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Config } from '@backstage/config';\nimport {\n DynamicPluginProvider,\n BackendDynamicPlugin,\n isBackendDynamicPluginInstaller,\n DynamicPlugin,\n FrontendDynamicPlugin,\n} from './types';\nimport { ScannedPluginPackage } from '../scanner';\nimport { PluginScanner } from '../scanner/plugin-scanner';\nimport { ModuleLoader } from '../loader';\nimport { CommonJSModuleLoader } from '../loader/CommonJSModuleLoader';\nimport * as url from 'url';\nimport {\n BackendFeature,\n LoggerService,\n coreServices,\n createBackendFeatureLoader,\n createServiceFactory,\n createServiceRef,\n} from '@backstage/backend-plugin-api';\nimport { PackageRole, PackageRoles } from '@backstage/cli-node';\nimport { findPaths } from '@backstage/cli-common';\nimport path from 'path';\nimport * as fs from 'fs';\nimport {\n FeatureDiscoveryService,\n featureDiscoveryServiceRef,\n} from '@backstage/backend-plugin-api/alpha';\n\n/**\n * @public\n */\nexport interface DynamicPluginManagerOptions {\n config: Config;\n logger: LoggerService;\n preferAlpha?: boolean;\n moduleLoader?: ModuleLoader;\n}\n\n/**\n * @public\n */\nexport class DynamicPluginManager implements DynamicPluginProvider {\n static async create(\n options: DynamicPluginManagerOptions,\n ): Promise<DynamicPluginManager> {\n /* eslint-disable-next-line no-restricted-syntax */\n const backstageRoot = findPaths(__dirname).targetRoot;\n const scanner = PluginScanner.create({\n config: options.config,\n logger: options.logger,\n backstageRoot,\n preferAlpha: options.preferAlpha,\n });\n const scannedPlugins = (await scanner.scanRoot()).packages;\n scanner.trackChanges();\n const moduleLoader =\n options.moduleLoader || new CommonJSModuleLoader(options.logger);\n const manager = new DynamicPluginManager(\n options.logger,\n scannedPlugins,\n moduleLoader,\n );\n\n const dynamicPluginsPaths = scannedPlugins.map(p =>\n fs.realpathSync(\n path.dirname(\n path.dirname(\n path.resolve(url.fileURLToPath(p.location), p.manifest.main),\n ),\n ),\n ),\n );\n\n await moduleLoader.bootstrap(backstageRoot, dynamicPluginsPaths);\n\n scanner.subscribeToRootDirectoryChange(async () => {\n manager._availablePackages = (await scanner.scanRoot()).packages;\n // TODO: do not store _scannedPlugins again, but instead store a diff of the changes\n });\n manager._plugins.push(...(await manager.loadPlugins()));\n\n return manager;\n }\n\n private readonly _plugins: DynamicPlugin[];\n private _availablePackages: ScannedPluginPackage[];\n\n private constructor(\n private readonly logger: LoggerService,\n private readonly packages: ScannedPluginPackage[],\n private readonly moduleLoader: ModuleLoader,\n ) {\n this._plugins = [];\n this._availablePackages = packages;\n }\n\n get availablePackages(): ScannedPluginPackage[] {\n return this._availablePackages;\n }\n\n addBackendPlugin(plugin: BackendDynamicPlugin): void {\n this._plugins.push(plugin);\n }\n\n private async loadPlugins(): Promise<DynamicPlugin[]> {\n const loadedPlugins: DynamicPlugin[] = [];\n\n for (const scannedPlugin of this.packages) {\n const role = scannedPlugin.manifest.backstage.role;\n const platform = PackageRoles.getRoleInfo(role).platform;\n const isPlugin =\n role.endsWith('-plugin') ||\n role.endsWith('-plugin-module') ||\n role === ('frontend-dynamic-container' as PackageRole);\n\n if (!isPlugin) {\n this.logger.info(\n `skipping dynamic plugin package '${scannedPlugin.manifest.name}' from '${scannedPlugin.location}': incompatible role '${role}'`,\n );\n continue;\n }\n\n switch (platform) {\n case 'node':\n loadedPlugins.push(await this.loadBackendPlugin(scannedPlugin));\n break;\n\n case 'web':\n loadedPlugins.push({\n name: scannedPlugin.manifest.name,\n version: scannedPlugin.manifest.version,\n role: scannedPlugin.manifest.backstage.role,\n platform: 'web',\n // TODO(davidfestal): add required front-end plugin information here.\n });\n break;\n\n default:\n this.logger.info(\n `skipping dynamic plugin package '${scannedPlugin.manifest.name}' from '${scannedPlugin.location}': unrelated platform '${platform}'`,\n );\n }\n }\n return loadedPlugins;\n }\n\n private async loadBackendPlugin(\n plugin: ScannedPluginPackage,\n ): Promise<BackendDynamicPlugin> {\n const packagePath = url.fileURLToPath(\n `${plugin.location}/${plugin.manifest.main}`,\n );\n const dynamicPlugin: BackendDynamicPlugin = {\n name: plugin.manifest.name,\n version: plugin.manifest.version,\n platform: 'node',\n role: plugin.manifest.backstage.role,\n };\n\n try {\n const pluginModule = await this.moduleLoader.load(packagePath);\n\n if (isBackendFeature(pluginModule.default)) {\n dynamicPlugin.installer = {\n kind: 'new',\n install: () => pluginModule.default,\n };\n } else if (isBackendFeatureFactory(pluginModule.default)) {\n dynamicPlugin.installer = {\n kind: 'new',\n install: pluginModule.default,\n };\n } else if (\n isBackendDynamicPluginInstaller(pluginModule.dynamicPluginInstaller)\n ) {\n dynamicPlugin.installer = pluginModule.dynamicPluginInstaller;\n }\n if (dynamicPlugin.installer) {\n this.logger.info(\n `loaded dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`,\n );\n } else {\n dynamicPlugin.failure = `the module should either export a 'BackendFeature' or 'BackendFeatureFactory' as default export, or export a 'const dynamicPluginInstaller: BackendDynamicPluginInstaller' field as dynamic loading entrypoint.`;\n this.logger.error(\n `dynamic backend plugin '${plugin.manifest.name}' could not be loaded from '${plugin.location}': ${dynamicPlugin.failure}`,\n );\n }\n return dynamicPlugin;\n } catch (error) {\n const typedError =\n typeof error === 'object' && 'message' in error && 'name' in error\n ? error\n : new Error(error);\n dynamicPlugin.failure = `${typedError.name}: ${typedError.message}`;\n this.logger.error(\n `an error occurred while loading dynamic backend plugin '${plugin.manifest.name}' from '${plugin.location}'`,\n typedError,\n );\n return dynamicPlugin;\n }\n }\n\n backendPlugins(options?: {\n includeFailed?: boolean;\n }): BackendDynamicPlugin[] {\n return this.plugins(options).filter(\n (p): p is BackendDynamicPlugin => p.platform === 'node',\n );\n }\n\n frontendPlugins(options?: {\n includeFailed?: boolean;\n }): FrontendDynamicPlugin[] {\n return this.plugins(options).filter(\n (p): p is FrontendDynamicPlugin => p.platform === 'web',\n );\n }\n\n plugins(options?: { includeFailed?: boolean }): DynamicPlugin[] {\n return this._plugins.filter(p => options?.includeFailed || !p.failure);\n }\n\n getScannedPackage(plugin: DynamicPlugin): ScannedPluginPackage {\n const pkg = this.packages.find(\n p =>\n p.manifest.name === plugin.name &&\n p.manifest.version === plugin.version,\n );\n if (pkg === undefined) {\n throw new Error(\n `The scanned package of a dynamic plugin should always be available: ${plugin.name}/${plugin.version}`,\n );\n }\n return pkg;\n }\n}\n\n/**\n * @public\n */\nexport const dynamicPluginsServiceRef = createServiceRef<DynamicPluginProvider>(\n {\n id: 'core.dynamicplugins',\n scope: 'root',\n },\n);\n\n/**\n * @public\n */\nexport interface DynamicPluginsFactoryOptions {\n moduleLoader?(logger: LoggerService): ModuleLoader | Promise<ModuleLoader>;\n}\n\n/**\n * @public\n * @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.\n */\nexport const dynamicPluginsServiceFactoryWithOptions = (\n options?: DynamicPluginsFactoryOptions,\n) =>\n createServiceFactory({\n service: dynamicPluginsServiceRef,\n deps: {\n config: coreServices.rootConfig,\n logger: coreServices.rootLogger,\n },\n async factory({ config, logger }) {\n return await DynamicPluginManager.create({\n config,\n logger,\n preferAlpha: true,\n moduleLoader: await options?.moduleLoader?.(logger),\n });\n },\n });\n\n/**\n * @public\n * @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.\n */\nexport const dynamicPluginsServiceFactory = Object.assign(\n dynamicPluginsServiceFactoryWithOptions,\n dynamicPluginsServiceFactoryWithOptions(),\n);\n\nclass DynamicPluginsEnabledFeatureDiscoveryService\n implements FeatureDiscoveryService\n{\n constructor(\n private readonly dynamicPlugins: DynamicPluginProvider,\n private readonly featureDiscoveryService?: FeatureDiscoveryService,\n ) {}\n\n async getBackendFeatures(): Promise<{ features: Array<BackendFeature> }> {\n const staticFeatures =\n (await this.featureDiscoveryService?.getBackendFeatures())?.features ??\n [];\n\n return {\n features: [\n ...this.dynamicPlugins\n .backendPlugins()\n .flatMap((plugin): BackendFeature[] => {\n if (plugin.installer?.kind === 'new') {\n const installed = plugin.installer.install();\n if (Array.isArray(installed)) {\n return installed;\n }\n return [installed];\n }\n return [];\n }),\n ...staticFeatures,\n ],\n };\n }\n}\n\n/**\n * @public\n * @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.\n */\nexport const dynamicPluginsFeatureDiscoveryServiceFactory =\n createServiceFactory({\n service: featureDiscoveryServiceRef,\n deps: {\n config: coreServices.rootConfig,\n dynamicPlugins: dynamicPluginsServiceRef,\n },\n factory({ dynamicPlugins }) {\n return new DynamicPluginsEnabledFeatureDiscoveryService(dynamicPlugins);\n },\n });\n\n/**\n * @public\n * @deprecated Use {@link dynamicPluginsFeatureLoader} instead, which gathers all services and features required for dynamic plugins.\n */\nexport const dynamicPluginsFeatureDiscoveryLoader = createBackendFeatureLoader({\n deps: {\n dynamicPlugins: dynamicPluginsServiceRef,\n },\n async loader({ dynamicPlugins }) {\n const service = new DynamicPluginsEnabledFeatureDiscoveryService(\n dynamicPlugins,\n );\n const { features } = await service.getBackendFeatures();\n return features;\n },\n});\n\nfunction isBackendFeature(value: unknown): value is BackendFeature {\n return (\n !!value &&\n (typeof value === 'object' || typeof value === 'function') &&\n (value as BackendFeature).$$type === '@backstage/BackendFeature'\n );\n}\n\nfunction isBackendFeatureFactory(\n value: unknown,\n): value is () => BackendFeature {\n return (\n !!value &&\n typeof value === 'function' &&\n (value as any).$$type === '@backstage/BackendFeatureFactory'\n );\n}\n"],"names":["findPaths","PluginScanner","CommonJSModuleLoader","fs","path","url","PackageRoles","isBackendDynamicPluginInstaller","createServiceRef","createServiceFactory","coreServices","featureDiscoveryServiceRef","createBackendFeatureLoader"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function isBackendDynamicPluginInstaller(obj) {
|
|
4
|
+
return obj !== void 0 && "kind" in obj && (obj.kind === "new" || obj.kind === "legacy");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
exports.isBackendDynamicPluginInstaller = isBackendDynamicPluginInstaller;
|
|
8
|
+
//# sourceMappingURL=types.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.cjs.js","sources":["../../src/manager/types.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Logger } from 'winston';\nimport { Config } from '@backstage/config';\nimport {\n PluginCacheManager,\n PluginDatabaseManager,\n PluginEndpointDiscovery,\n TokenManager,\n} from '@backstage/backend-common';\nimport { Router } from 'express';\nimport { IdentityApi } from '@backstage/plugin-auth-node';\nimport { PermissionEvaluator } from '@backstage/plugin-permission-common';\nimport {\n EventBroker,\n EventsService,\n HttpPostIngressOptions,\n} from '@backstage/plugin-events-node';\n\nimport {\n BackendFeature,\n UrlReaderService,\n SchedulerService,\n SchedulerServiceTaskRunner,\n} from '@backstage/backend-plugin-api';\nimport { PackagePlatform, PackageRole } from '@backstage/cli-node';\nimport { CatalogBuilder } from '@backstage/plugin-catalog-backend';\nimport { TemplateAction } from '@backstage/plugin-scaffolder-node';\nimport { IndexBuilder } from '@backstage/plugin-search-backend-node';\nimport { EventsBackend } from '@backstage/plugin-events-backend';\nimport { PermissionPolicy } from '@backstage/plugin-permission-node';\nimport { ScannedPluginPackage } from '../scanner';\n\n/**\n * @public\n *\n * @deprecated\n *\n * Support for the legacy backend system will be removed in the future.\n *\n * When adding a legacy plugin installer entrypoint in your plugin,\n * you should always take the opportunity to also implement support\n * for the new backend system if not already done.\n *\n */\nexport type LegacyPluginEnvironment = {\n logger: Logger;\n cache: PluginCacheManager;\n database: PluginDatabaseManager;\n config: Config;\n reader: UrlReaderService;\n discovery: PluginEndpointDiscovery;\n tokenManager: TokenManager;\n permissions: PermissionEvaluator;\n scheduler: SchedulerService;\n identity: IdentityApi;\n eventBroker: EventBroker;\n events: EventsService;\n pluginProvider: BackendPluginProvider;\n};\n\n/**\n * @public\n */\nexport interface DynamicPluginProvider\n extends FrontendPluginProvider,\n BackendPluginProvider {\n plugins(options?: { includeFailed?: boolean }): DynamicPlugin[];\n getScannedPackage(plugin: DynamicPlugin): ScannedPluginPackage;\n}\n\n/**\n * @public\n */\nexport interface BackendPluginProvider {\n backendPlugins(options?: { includeFailed?: boolean }): BackendDynamicPlugin[];\n}\n\n/**\n * @public\n */\nexport interface FrontendPluginProvider {\n frontendPlugins(options?: {\n includeFailed?: boolean;\n }): FrontendDynamicPlugin[];\n}\n\n/**\n * @public\n */\nexport interface BaseDynamicPlugin {\n name: string;\n version: string;\n role: PackageRole;\n platform: PackagePlatform;\n failure?: string;\n}\n\n/**\n * @public\n */\nexport type DynamicPlugin = FrontendDynamicPlugin | BackendDynamicPlugin;\n\n/**\n * @public\n */\nexport interface FrontendDynamicPlugin extends BaseDynamicPlugin {\n platform: 'web';\n}\n\n/**\n * @public\n */\nexport interface BackendDynamicPlugin extends BaseDynamicPlugin {\n platform: 'node';\n installer?: BackendDynamicPluginInstaller;\n}\n\n/**\n * @public\n */\nexport type BackendDynamicPluginInstaller =\n | LegacyBackendPluginInstaller\n | NewBackendPluginInstaller;\n\n/**\n * @public\n */\nexport interface NewBackendPluginInstaller {\n kind: 'new';\n\n install(): BackendFeature | BackendFeature[];\n}\n\n/**\n * @public\n * @deprecated\n *\n * Support for the legacy backend system will be removed in the future.\n *\n * When adding a legacy plugin installer entrypoint in your plugin,\n * you should always take the opportunity to also implement support\n * for the new backend system if not already done.\n *\n */\nexport interface LegacyBackendPluginInstaller {\n kind: 'legacy';\n\n router?: {\n pluginID: string;\n createPlugin(env: LegacyPluginEnvironment): Promise<Router>;\n };\n\n catalog?(builder: CatalogBuilder, env: LegacyPluginEnvironment): void;\n scaffolder?(env: LegacyPluginEnvironment): TemplateAction<any>[];\n search?(\n indexBuilder: IndexBuilder,\n schedule: SchedulerServiceTaskRunner,\n env: LegacyPluginEnvironment,\n ): void;\n events?(\n eventsBackend: EventsBackend,\n env: LegacyPluginEnvironment,\n ): HttpPostIngressOptions[];\n permissions?: {\n policy?: PermissionPolicy;\n };\n}\n\n/**\n * @public\n */\nexport function isBackendDynamicPluginInstaller(\n obj: any,\n): obj is BackendDynamicPluginInstaller {\n return (\n obj !== undefined &&\n 'kind' in obj &&\n (obj.kind === 'new' || obj.kind === 'legacy')\n );\n}\n"],"names":[],"mappings":";;AA0LO,SAAS,gCACd,GACsC,EAAA;AACtC,EACE,OAAA,GAAA,KAAQ,UACR,MAAU,IAAA,GAAA,KACT,IAAI,IAAS,KAAA,KAAA,IAAS,IAAI,IAAS,KAAA,QAAA,CAAA,CAAA;AAExC;;;;"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs$1 = require('fs/promises');
|
|
4
|
+
var fs = require('fs');
|
|
5
|
+
var chokidar = require('chokidar');
|
|
6
|
+
var path = require('path');
|
|
7
|
+
var url = require('url');
|
|
8
|
+
var debounce = require('lodash/debounce');
|
|
9
|
+
var cliNode = require('@backstage/cli-node');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
function _interopNamespaceCompat(e) {
|
|
14
|
+
if (e && typeof e === 'object' && 'default' in e) return e;
|
|
15
|
+
var n = Object.create(null);
|
|
16
|
+
if (e) {
|
|
17
|
+
Object.keys(e).forEach(function (k) {
|
|
18
|
+
if (k !== 'default') {
|
|
19
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
20
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
21
|
+
enumerable: true,
|
|
22
|
+
get: function () { return e[k]; }
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
n.default = e;
|
|
28
|
+
return Object.freeze(n);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceCompat(fs$1);
|
|
32
|
+
var chokidar__namespace = /*#__PURE__*/_interopNamespaceCompat(chokidar);
|
|
33
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceCompat(path);
|
|
34
|
+
var url__namespace = /*#__PURE__*/_interopNamespaceCompat(url);
|
|
35
|
+
var debounce__default = /*#__PURE__*/_interopDefaultCompat(debounce);
|
|
36
|
+
|
|
37
|
+
const configKey = "dynamicPlugins";
|
|
38
|
+
class PluginScanner {
|
|
39
|
+
constructor(config, logger, backstageRoot, preferAlpha) {
|
|
40
|
+
this.config = config;
|
|
41
|
+
this.logger = logger;
|
|
42
|
+
this.backstageRoot = backstageRoot;
|
|
43
|
+
this.preferAlpha = preferAlpha;
|
|
44
|
+
}
|
|
45
|
+
_rootDirectory;
|
|
46
|
+
configUnsubscribe;
|
|
47
|
+
rootDirectoryWatcher;
|
|
48
|
+
subscribers = [];
|
|
49
|
+
static create(options) {
|
|
50
|
+
const scanner = new PluginScanner(
|
|
51
|
+
options.config,
|
|
52
|
+
options.logger,
|
|
53
|
+
options.backstageRoot,
|
|
54
|
+
options.preferAlpha || false
|
|
55
|
+
);
|
|
56
|
+
scanner.applyConfig();
|
|
57
|
+
return scanner;
|
|
58
|
+
}
|
|
59
|
+
subscribeToRootDirectoryChange(subscriber) {
|
|
60
|
+
this.subscribers.push(subscriber);
|
|
61
|
+
}
|
|
62
|
+
get rootDirectory() {
|
|
63
|
+
return this._rootDirectory;
|
|
64
|
+
}
|
|
65
|
+
applyConfig() {
|
|
66
|
+
const dynamicPlugins = this.config.getOptional(configKey);
|
|
67
|
+
if (!dynamicPlugins) {
|
|
68
|
+
this.logger.info(`'${configKey}' config entry not found.`);
|
|
69
|
+
this._rootDirectory = void 0;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (typeof dynamicPlugins !== "object") {
|
|
73
|
+
this.logger.warn(`'${configKey}' config entry should be an object.`);
|
|
74
|
+
this._rootDirectory = void 0;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!("rootDirectory" in dynamicPlugins)) {
|
|
78
|
+
this.logger.warn(
|
|
79
|
+
`'${configKey}' config entry does not contain the 'rootDirectory' field.`
|
|
80
|
+
);
|
|
81
|
+
this._rootDirectory = void 0;
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (typeof dynamicPlugins.rootDirectory !== "string") {
|
|
85
|
+
this.logger.warn(
|
|
86
|
+
`'${configKey}.rootDirectory' config entry should be a string.`
|
|
87
|
+
);
|
|
88
|
+
this._rootDirectory = void 0;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const dynamicPluginsRootPath = path__namespace.isAbsolute(dynamicPlugins.rootDirectory) ? path__namespace.resolve(dynamicPlugins.rootDirectory) : path__namespace.resolve(this.backstageRoot, dynamicPlugins.rootDirectory);
|
|
92
|
+
if (!path__namespace.dirname(dynamicPluginsRootPath).startsWith(path__namespace.resolve(this.backstageRoot))) {
|
|
93
|
+
const nodePath = process.env.NODE_PATH;
|
|
94
|
+
const backstageNodeModules = path__namespace.resolve(
|
|
95
|
+
this.backstageRoot,
|
|
96
|
+
"node_modules"
|
|
97
|
+
);
|
|
98
|
+
if (!nodePath || !nodePath.split(path__namespace.delimiter).includes(backstageNodeModules)) {
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Dynamic plugins under '${dynamicPluginsRootPath}' cannot access backstage modules in '${backstageNodeModules}'.
|
|
101
|
+
Please add '${backstageNodeModules}' to the 'NODE_PATH' when running the backstage backend.`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (!fs.lstatSync(dynamicPluginsRootPath).isDirectory()) {
|
|
106
|
+
throw new Error("Not a directory");
|
|
107
|
+
}
|
|
108
|
+
this._rootDirectory = dynamicPluginsRootPath;
|
|
109
|
+
}
|
|
110
|
+
async scanRoot() {
|
|
111
|
+
if (!this._rootDirectory) {
|
|
112
|
+
return { packages: [] };
|
|
113
|
+
}
|
|
114
|
+
const dynamicPluginsLocation = this._rootDirectory;
|
|
115
|
+
const scannedPlugins = [];
|
|
116
|
+
for (const dirEnt of await fs__namespace.readdir(dynamicPluginsLocation, {
|
|
117
|
+
withFileTypes: true
|
|
118
|
+
})) {
|
|
119
|
+
const pluginDir = dirEnt;
|
|
120
|
+
if (pluginDir.name === "lost+found") {
|
|
121
|
+
this.logger.debug(`skipping '${pluginDir.name}' system directory`);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const pluginHome = path__namespace.normalize(
|
|
125
|
+
path__namespace.resolve(dynamicPluginsLocation, pluginDir.name)
|
|
126
|
+
);
|
|
127
|
+
if (dirEnt.isSymbolicLink()) {
|
|
128
|
+
if (!(await fs__namespace.lstat(await fs__namespace.readlink(pluginHome))).isDirectory()) {
|
|
129
|
+
this.logger.info(
|
|
130
|
+
`skipping '${pluginHome}' since it is not a directory`
|
|
131
|
+
);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
} else if (!dirEnt.isDirectory()) {
|
|
135
|
+
this.logger.info(
|
|
136
|
+
`skipping '${pluginHome}' since it is not a directory`
|
|
137
|
+
);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let scannedPlugin;
|
|
141
|
+
let platform;
|
|
142
|
+
try {
|
|
143
|
+
scannedPlugin = await this.scanDir(pluginHome);
|
|
144
|
+
if (!scannedPlugin.manifest.main) {
|
|
145
|
+
throw new Error("field 'main' not found in 'package.json'");
|
|
146
|
+
}
|
|
147
|
+
if (scannedPlugin.manifest.backstage?.role) {
|
|
148
|
+
platform = cliNode.PackageRoles.getRoleInfo(
|
|
149
|
+
scannedPlugin.manifest.backstage.role
|
|
150
|
+
).platform;
|
|
151
|
+
} else {
|
|
152
|
+
throw new Error("field 'backstage.role' not found in 'package.json'");
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
this.logger.error(
|
|
156
|
+
`failed to load dynamic plugin manifest from '${pluginHome}'`,
|
|
157
|
+
e
|
|
158
|
+
);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (platform === "node") {
|
|
162
|
+
if (this.preferAlpha) {
|
|
163
|
+
const pluginHomeAlpha = path__namespace.resolve(pluginHome, "alpha");
|
|
164
|
+
if (fs.existsSync(pluginHomeAlpha)) {
|
|
165
|
+
if ((await fs__namespace.lstat(pluginHomeAlpha)).isDirectory()) {
|
|
166
|
+
const backstage = scannedPlugin.manifest.backstage;
|
|
167
|
+
try {
|
|
168
|
+
scannedPlugin = await this.scanDir(pluginHomeAlpha);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
this.logger.error(
|
|
171
|
+
`failed to load dynamic plugin manifest from '${pluginHomeAlpha}'`,
|
|
172
|
+
e
|
|
173
|
+
);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
scannedPlugin.manifest.backstage = backstage;
|
|
177
|
+
} else {
|
|
178
|
+
this.logger.warn(
|
|
179
|
+
`skipping '${pluginHomeAlpha}' since it is not a directory`
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
scannedPlugins.push(scannedPlugin);
|
|
186
|
+
}
|
|
187
|
+
return { packages: scannedPlugins };
|
|
188
|
+
}
|
|
189
|
+
async scanDir(pluginHome) {
|
|
190
|
+
const manifestFile = path__namespace.resolve(pluginHome, "package.json");
|
|
191
|
+
const content = await fs__namespace.readFile(manifestFile);
|
|
192
|
+
const manifest = JSON.parse(content.toString());
|
|
193
|
+
return {
|
|
194
|
+
location: url__namespace.pathToFileURL(pluginHome),
|
|
195
|
+
manifest
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
async trackChanges() {
|
|
199
|
+
const setupRootDirectoryWatcher = async () => {
|
|
200
|
+
return new Promise((resolve, reject) => {
|
|
201
|
+
if (!this._rootDirectory) {
|
|
202
|
+
resolve();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const callSubscribers = debounce__default.default(() => {
|
|
206
|
+
this.subscribers.forEach((s) => s());
|
|
207
|
+
}, 500);
|
|
208
|
+
let ready = false;
|
|
209
|
+
this.rootDirectoryWatcher = chokidar__namespace.watch(this._rootDirectory, {
|
|
210
|
+
ignoreInitial: true,
|
|
211
|
+
followSymlinks: true,
|
|
212
|
+
depth: 1,
|
|
213
|
+
disableGlobbing: true
|
|
214
|
+
}).on(
|
|
215
|
+
"all",
|
|
216
|
+
(event, eventPath, _) => {
|
|
217
|
+
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") {
|
|
218
|
+
this.logger.info(
|
|
219
|
+
`rootDirectory changed (${event} - ${eventPath}): scanning plugins again`
|
|
220
|
+
);
|
|
221
|
+
callSubscribers();
|
|
222
|
+
} else {
|
|
223
|
+
this.logger.debug(
|
|
224
|
+
`rootDirectory changed (${event} - ${eventPath}): no need to scan plugins again`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
).on("error", (error) => {
|
|
229
|
+
this.logger.error(
|
|
230
|
+
`error while watching '${this.rootDirectory}'`,
|
|
231
|
+
error
|
|
232
|
+
);
|
|
233
|
+
if (!ready) {
|
|
234
|
+
reject(error);
|
|
235
|
+
}
|
|
236
|
+
}).on("ready", () => {
|
|
237
|
+
ready = true;
|
|
238
|
+
resolve();
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
};
|
|
242
|
+
await setupRootDirectoryWatcher();
|
|
243
|
+
if (this.config.subscribe) {
|
|
244
|
+
const { unsubscribe } = this.config.subscribe(async () => {
|
|
245
|
+
const oldRootDirectory = this._rootDirectory;
|
|
246
|
+
try {
|
|
247
|
+
this.applyConfig();
|
|
248
|
+
} catch (e) {
|
|
249
|
+
this.logger.error(
|
|
250
|
+
"failed to apply new config for dynamic plugins",
|
|
251
|
+
e
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
if (oldRootDirectory !== this._rootDirectory) {
|
|
255
|
+
this.logger.info(
|
|
256
|
+
`rootDirectory changed in Config from '${oldRootDirectory}' to '${this._rootDirectory}'`
|
|
257
|
+
);
|
|
258
|
+
this.subscribers.forEach((s) => s());
|
|
259
|
+
if (this.rootDirectoryWatcher) {
|
|
260
|
+
await this.rootDirectoryWatcher.close();
|
|
261
|
+
}
|
|
262
|
+
await setupRootDirectoryWatcher();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
this.configUnsubscribe = unsubscribe;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
async untrackChanges() {
|
|
269
|
+
if (this.rootDirectoryWatcher) {
|
|
270
|
+
this.rootDirectoryWatcher.close();
|
|
271
|
+
}
|
|
272
|
+
if (this.configUnsubscribe) {
|
|
273
|
+
this.configUnsubscribe();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
destructor() {
|
|
277
|
+
this.untrackChanges();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
exports.PluginScanner = PluginScanner;
|
|
282
|
+
exports.configKey = configKey;
|
|
283
|
+
//# sourceMappingURL=plugin-scanner.cjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-scanner.cjs.js","sources":["../../src/scanner/plugin-scanner.ts"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport { Config } from '@backstage/config';\nimport { ScannedPluginPackage, ScannedPluginManifest } from './types';\nimport * as fs from 'fs/promises';\nimport { Stats, lstatSync, existsSync } from 'fs';\nimport * as chokidar from 'chokidar';\nimport * as path from 'path';\nimport * as url from 'url';\nimport debounce from 'lodash/debounce';\nimport { PackagePlatform, PackageRoles } from '@backstage/cli-node';\nimport { LoggerService } from '@backstage/backend-plugin-api';\n\nexport interface DynamicPluginScannerOptions {\n config: Config;\n backstageRoot: string;\n logger: LoggerService;\n preferAlpha?: boolean;\n}\n\nexport interface ScanRootResponse {\n packages: ScannedPluginPackage[];\n}\n\nexport const configKey = 'dynamicPlugins';\n\nexport class PluginScanner {\n private _rootDirectory?: string;\n private configUnsubscribe?: () => void;\n private rootDirectoryWatcher?: chokidar.FSWatcher;\n private subscribers: (() => void)[] = [];\n\n private constructor(\n private readonly config: Config,\n private readonly logger: LoggerService,\n private readonly backstageRoot: string,\n private readonly preferAlpha: boolean,\n ) {}\n\n static create(options: DynamicPluginScannerOptions): PluginScanner {\n const scanner = new PluginScanner(\n options.config,\n options.logger,\n options.backstageRoot,\n options.preferAlpha || false,\n );\n scanner.applyConfig();\n return scanner;\n }\n\n subscribeToRootDirectoryChange(subscriber: () => void) {\n this.subscribers.push(subscriber);\n }\n\n get rootDirectory(): string | undefined {\n return this._rootDirectory;\n }\n\n private applyConfig(): void | never {\n const dynamicPlugins = this.config.getOptional(configKey);\n if (!dynamicPlugins) {\n this.logger.info(`'${configKey}' config entry not found.`);\n this._rootDirectory = undefined;\n return;\n }\n if (typeof dynamicPlugins !== 'object') {\n this.logger.warn(`'${configKey}' config entry should be an object.`);\n this._rootDirectory = undefined;\n return;\n }\n if (!('rootDirectory' in dynamicPlugins)) {\n this.logger.warn(\n `'${configKey}' config entry does not contain the 'rootDirectory' field.`,\n );\n this._rootDirectory = undefined;\n return;\n }\n if (typeof dynamicPlugins.rootDirectory !== 'string') {\n this.logger.warn(\n `'${configKey}.rootDirectory' config entry should be a string.`,\n );\n this._rootDirectory = undefined;\n return;\n }\n\n const dynamicPluginsRootPath = path.isAbsolute(dynamicPlugins.rootDirectory)\n ? path.resolve(dynamicPlugins.rootDirectory)\n : path.resolve(this.backstageRoot, dynamicPlugins.rootDirectory);\n\n if (\n !path\n .dirname(dynamicPluginsRootPath)\n .startsWith(path.resolve(this.backstageRoot))\n ) {\n const nodePath = process.env.NODE_PATH;\n const backstageNodeModules = path.resolve(\n this.backstageRoot,\n 'node_modules',\n );\n if (\n !nodePath ||\n !nodePath.split(path.delimiter).includes(backstageNodeModules)\n ) {\n throw new Error(\n `Dynamic plugins under '${dynamicPluginsRootPath}' cannot access backstage modules in '${backstageNodeModules}'.\\n` +\n `Please add '${backstageNodeModules}' to the 'NODE_PATH' when running the backstage backend.`,\n );\n }\n }\n if (!lstatSync(dynamicPluginsRootPath).isDirectory()) {\n throw new Error('Not a directory');\n }\n\n this._rootDirectory = dynamicPluginsRootPath;\n }\n\n async scanRoot(): Promise<ScanRootResponse> {\n if (!this._rootDirectory) {\n return { packages: [] };\n }\n\n const dynamicPluginsLocation = this._rootDirectory;\n const scannedPlugins: ScannedPluginPackage[] = [];\n for (const dirEnt of await fs.readdir(dynamicPluginsLocation, {\n withFileTypes: true,\n })) {\n const pluginDir = dirEnt;\n\n if (pluginDir.name === 'lost+found') {\n this.logger.debug(`skipping '${pluginDir.name}' system directory`);\n continue;\n }\n const pluginHome = path.normalize(\n path.resolve(dynamicPluginsLocation, pluginDir.name),\n );\n if (dirEnt.isSymbolicLink()) {\n if (!(await fs.lstat(await fs.readlink(pluginHome))).isDirectory()) {\n this.logger.info(\n `skipping '${pluginHome}' since it is not a directory`,\n );\n continue;\n }\n } else if (!dirEnt.isDirectory()) {\n this.logger.info(\n `skipping '${pluginHome}' since it is not a directory`,\n );\n continue;\n }\n\n let scannedPlugin: ScannedPluginPackage;\n let platform: PackagePlatform;\n try {\n scannedPlugin = await this.scanDir(pluginHome);\n if (!scannedPlugin.manifest.main) {\n throw new Error(\"field 'main' not found in 'package.json'\");\n }\n if (scannedPlugin.manifest.backstage?.role) {\n platform = PackageRoles.getRoleInfo(\n scannedPlugin.manifest.backstage.role,\n ).platform;\n } else {\n throw new Error(\"field 'backstage.role' not found in 'package.json'\");\n }\n } catch (e) {\n this.logger.error(\n `failed to load dynamic plugin manifest from '${pluginHome}'`,\n e,\n );\n continue;\n }\n\n if (platform === 'node') {\n if (this.preferAlpha) {\n const pluginHomeAlpha = path.resolve(pluginHome, 'alpha');\n if (existsSync(pluginHomeAlpha)) {\n if ((await fs.lstat(pluginHomeAlpha)).isDirectory()) {\n const backstage = scannedPlugin.manifest.backstage;\n try {\n scannedPlugin = await this.scanDir(pluginHomeAlpha);\n } catch (e) {\n this.logger.error(\n `failed to load dynamic plugin manifest from '${pluginHomeAlpha}'`,\n e,\n );\n continue;\n }\n scannedPlugin.manifest.backstage = backstage;\n } else {\n this.logger.warn(\n `skipping '${pluginHomeAlpha}' since it is not a directory`,\n );\n }\n }\n }\n }\n\n scannedPlugins.push(scannedPlugin);\n }\n return { packages: scannedPlugins };\n }\n\n private async scanDir(pluginHome: string): Promise<ScannedPluginPackage> {\n const manifestFile = path.resolve(pluginHome, 'package.json');\n const content = await fs.readFile(manifestFile);\n const manifest: ScannedPluginManifest = JSON.parse(content.toString());\n return {\n location: url.pathToFileURL(pluginHome),\n manifest: manifest,\n };\n }\n\n async trackChanges(): Promise<void> {\n const setupRootDirectoryWatcher = async (): Promise<void> => {\n return new Promise((resolve, reject) => {\n if (!this._rootDirectory) {\n resolve();\n return;\n }\n const callSubscribers = debounce(() => {\n this.subscribers.forEach(s => s());\n }, 500);\n let ready = false;\n this.rootDirectoryWatcher = chokidar\n .watch(this._rootDirectory, {\n ignoreInitial: true,\n followSymlinks: true,\n depth: 1,\n disableGlobbing: true,\n })\n .on(\n 'all',\n (\n event: 'add' | 'addDir' | 'change' | 'unlink' | 'unlinkDir',\n eventPath: string,\n _: Stats | undefined,\n ): void => {\n if (\n (['addDir', 'unlinkDir'].includes(event) &&\n path.dirname(eventPath) === this._rootDirectory) ||\n (['add', 'unlink', 'change'].includes(event) &&\n path.dirname(path.dirname(eventPath)) ===\n this._rootDirectory &&\n path.basename(eventPath) === 'package.json')\n ) {\n this.logger.info(\n `rootDirectory changed (${event} - ${eventPath}): scanning plugins again`,\n );\n callSubscribers();\n } else {\n this.logger.debug(\n `rootDirectory changed (${event} - ${eventPath}): no need to scan plugins again`,\n );\n }\n },\n )\n .on('error', (error: Error) => {\n this.logger.error(\n `error while watching '${this.rootDirectory}'`,\n error,\n );\n if (!ready) {\n reject(error);\n }\n })\n .on('ready', () => {\n ready = true;\n resolve();\n });\n });\n };\n\n await setupRootDirectoryWatcher();\n if (this.config.subscribe) {\n const { unsubscribe } = this.config.subscribe(async (): Promise<void> => {\n const oldRootDirectory = this._rootDirectory;\n try {\n this.applyConfig();\n } catch (e) {\n this.logger.error(\n 'failed to apply new config for dynamic plugins',\n e,\n );\n }\n if (oldRootDirectory !== this._rootDirectory) {\n this.logger.info(\n `rootDirectory changed in Config from '${oldRootDirectory}' to '${this._rootDirectory}'`,\n );\n this.subscribers.forEach(s => s());\n if (this.rootDirectoryWatcher) {\n await this.rootDirectoryWatcher.close();\n }\n await setupRootDirectoryWatcher();\n }\n });\n this.configUnsubscribe = unsubscribe;\n }\n }\n\n async untrackChanges() {\n if (this.rootDirectoryWatcher) {\n this.rootDirectoryWatcher.close();\n }\n if (this.configUnsubscribe) {\n this.configUnsubscribe();\n }\n }\n\n destructor() {\n this.untrackChanges();\n }\n}\n"],"names":["path","lstatSync","fs","PackageRoles","existsSync","url","debounce","chokidar"],"mappings":"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"}
|