@backstage/plugin-scaffolder-backend 0.15.7 → 0.15.8

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # @backstage/plugin-scaffolder-backend
2
2
 
3
+ ## 0.15.8
4
+
5
+ ### Patch Changes
6
+
7
+ - 42c618abf6: Use `resolveSafeChildPath` in the `fetchContents` function to forbid reading files outside the base directory when a template is registered from a `file:` location.
8
+ - 18083d1821: Introduce the new `scaffolder.backstage.io/v1beta3` template kind with nunjucks support 🥋
9
+ - Updated dependencies
10
+ - @backstage/integration@0.6.8
11
+ - @backstage/plugin-catalog-backend@0.17.0
12
+
3
13
  ## 0.15.7
4
14
 
5
15
  ### Patch Changes
package/dist/index.cjs.js CHANGED
@@ -27,9 +27,11 @@ var Router = require('express-promise-router');
27
27
  var jsonschema = require('jsonschema');
28
28
  var uuid = require('uuid');
29
29
  var luxon = require('luxon');
30
+ var os = require('os');
30
31
  var Handlebars = require('handlebars');
31
32
  var winston = require('winston');
32
- var os = require('os');
33
+ var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
34
+ var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
33
35
 
34
36
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
35
37
 
@@ -54,6 +56,7 @@ function _interopNamespace(e) {
54
56
  }
55
57
 
56
58
  var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs);
59
+ var path__namespace = /*#__PURE__*/_interopNamespace(path);
57
60
  var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
58
61
  var yaml__namespace = /*#__PURE__*/_interopNamespace(yaml);
59
62
  var globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
@@ -61,9 +64,9 @@ var nunjucks__default = /*#__PURE__*/_interopDefaultLegacy(nunjucks);
61
64
  var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
62
65
  var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
63
66
  var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
67
+ var os__default = /*#__PURE__*/_interopDefaultLegacy(os);
64
68
  var Handlebars__namespace = /*#__PURE__*/_interopNamespace(Handlebars);
65
69
  var winston__namespace = /*#__PURE__*/_interopNamespace(winston);
66
- var os__default = /*#__PURE__*/_interopDefaultLegacy(os);
67
70
 
68
71
  const createTemplateAction = (templateAction) => {
69
72
  return templateAction;
@@ -183,12 +186,16 @@ function createDebugLogAction() {
183
186
  listWorkspace: {
184
187
  title: "List all files in the workspace, if true.",
185
188
  type: "boolean"
189
+ },
190
+ extra: {
191
+ title: "Extra info"
186
192
  }
187
193
  }
188
194
  }
189
195
  },
190
196
  async handler(ctx) {
191
197
  var _a, _b;
198
+ ctx.logger.info(JSON.stringify(ctx.input, null, 2));
192
199
  if ((_a = ctx.input) == null ? void 0 : _a.message) {
193
200
  ctx.logStream.write(ctx.input.message);
194
201
  }
@@ -227,10 +234,7 @@ async function fetchContents({
227
234
  }
228
235
  if (!fetchUrlIsAbsolute && (baseUrl == null ? void 0 : baseUrl.startsWith("file://"))) {
229
236
  const basePath = baseUrl.slice("file://".length);
230
- if (path.isAbsolute(fetchUrl)) {
231
- throw new errors.InputError(`Fetch URL may not be absolute for file locations, ${fetchUrl}`);
232
- }
233
- const srcDir = path.resolve(basePath, "..", fetchUrl);
237
+ const srcDir = backendCommon.resolveSafeChildPath(path__namespace.dirname(basePath), fetchUrl);
234
238
  await fs__default['default'].copy(srcDir, outputPath);
235
239
  } else {
236
240
  let readUrl;
@@ -2035,39 +2039,90 @@ class StorageTaskBroker {
2035
2039
  }
2036
2040
  }
2037
2041
 
2042
+ class TaskWorker {
2043
+ constructor(options) {
2044
+ this.options = options;
2045
+ }
2046
+ start() {
2047
+ (async () => {
2048
+ for (; ; ) {
2049
+ const task = await this.options.taskBroker.claim();
2050
+ await this.runOneTask(task);
2051
+ }
2052
+ })();
2053
+ }
2054
+ async runOneTask(task) {
2055
+ try {
2056
+ const {output} = task.spec.apiVersion === "scaffolder.backstage.io/v1beta3" ? await this.options.runners.workflowRunner.execute(task) : await this.options.runners.legacyWorkflowRunner.execute(task);
2057
+ await task.complete("completed", {output});
2058
+ } catch (error) {
2059
+ await task.complete("failed", {
2060
+ error: {name: error.name, message: error.message}
2061
+ });
2062
+ }
2063
+ }
2064
+ }
2065
+
2066
+ async function getWorkingDirectory(config, logger) {
2067
+ if (!config.has("backend.workingDirectory")) {
2068
+ return os__default['default'].tmpdir();
2069
+ }
2070
+ const workingDirectory = config.getString("backend.workingDirectory");
2071
+ try {
2072
+ await fs__default['default'].access(workingDirectory, fs__default['default'].constants.F_OK | fs__default['default'].constants.W_OK);
2073
+ logger.info(`using working directory: ${workingDirectory}`);
2074
+ } catch (err) {
2075
+ logger.error(`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`);
2076
+ throw err;
2077
+ }
2078
+ return workingDirectory;
2079
+ }
2080
+ function getEntityBaseUrl(entity) {
2081
+ var _a, _b;
2082
+ let location = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.SOURCE_LOCATION_ANNOTATION];
2083
+ if (!location) {
2084
+ location = (_b = entity.metadata.annotations) == null ? void 0 : _b[catalogModel.LOCATION_ANNOTATION];
2085
+ }
2086
+ if (!location) {
2087
+ return void 0;
2088
+ }
2089
+ const {type, target} = catalogModel.parseLocationReference(location);
2090
+ if (type === "url") {
2091
+ return target;
2092
+ } else if (type === "file") {
2093
+ return `file://${target}`;
2094
+ }
2095
+ return void 0;
2096
+ }
2097
+
2038
2098
  function isTruthy(value) {
2039
2099
  return lodash.isArray(value) ? value.length > 0 : !!value;
2040
2100
  }
2041
2101
 
2042
- class TaskWorker {
2102
+ const isValidTaskSpec$1 = (taskSpec) => taskSpec.apiVersion === "backstage.io/v1beta2";
2103
+ class LegacyWorkflowRunner {
2043
2104
  constructor(options) {
2044
2105
  this.options = options;
2045
2106
  this.handlebars = Handlebars__namespace.create();
2046
2107
  this.handlebars.registerHelper("parseRepoUrl", (repoUrl) => {
2047
- return JSON.stringify(parseRepoUrl(repoUrl, options.integrations));
2108
+ return JSON.stringify(parseRepoUrl(repoUrl, this.options.integrations));
2048
2109
  });
2049
2110
  this.handlebars.registerHelper("projectSlug", (repoUrl) => {
2050
- const {owner, repo} = parseRepoUrl(repoUrl, options.integrations);
2111
+ const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
2051
2112
  return `${owner}/${repo}`;
2052
2113
  });
2053
2114
  this.handlebars.registerHelper("json", (obj) => JSON.stringify(obj));
2054
2115
  this.handlebars.registerHelper("not", (value) => !isTruthy(value));
2055
2116
  this.handlebars.registerHelper("eq", (a, b) => a === b);
2056
2117
  }
2057
- start() {
2058
- (async () => {
2059
- for (; ; ) {
2060
- const task = await this.options.taskBroker.claim();
2061
- await this.runOneTask(task);
2062
- }
2063
- })();
2064
- }
2065
- async runOneTask(task) {
2118
+ async execute(task) {
2066
2119
  var _a, _b;
2067
- let workspacePath = void 0;
2120
+ if (!isValidTaskSpec$1(task.spec)) {
2121
+ throw new errors.InputError(`Task spec is not a valid v1beta2 task spec`);
2122
+ }
2123
+ const {actionRegistry} = this.options;
2124
+ const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
2068
2125
  try {
2069
- const {actionRegistry} = this.options;
2070
- workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
2071
2126
  await fs__default['default'].ensureDir(workspacePath);
2072
2127
  await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
2073
2128
  const templateCtx = {parameters: task.spec.values, steps: {}};
@@ -2209,11 +2264,7 @@ class TaskWorker {
2209
2264
  }
2210
2265
  return value;
2211
2266
  });
2212
- await task.complete("completed", {output});
2213
- } catch (error) {
2214
- await task.complete("failed", {
2215
- error: {name: error.name, message: error.message}
2216
- });
2267
+ return {output};
2217
2268
  } finally {
2218
2269
  if (workspacePath) {
2219
2270
  await fs__default['default'].remove(workspacePath);
@@ -2222,40 +2273,159 @@ class TaskWorker {
2222
2273
  }
2223
2274
  }
2224
2275
 
2225
- async function getWorkingDirectory(config, logger) {
2226
- if (!config.has("backend.workingDirectory")) {
2227
- return os__default['default'].tmpdir();
2228
- }
2229
- const workingDirectory = config.getString("backend.workingDirectory");
2230
- try {
2231
- await fs__default['default'].access(workingDirectory, fs__default['default'].constants.F_OK | fs__default['default'].constants.W_OK);
2232
- logger.info(`using working directory: ${workingDirectory}`);
2233
- } catch (err) {
2234
- logger.error(`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`);
2235
- throw err;
2276
+ const isValidTaskSpec = (taskSpec) => {
2277
+ return taskSpec.apiVersion === "scaffolder.backstage.io/v1beta3";
2278
+ };
2279
+ const createStepLogger = ({task, step}) => {
2280
+ const metadata = {stepId: step.id};
2281
+ const taskLogger = winston__namespace.createLogger({
2282
+ level: process.env.LOG_LEVEL || "info",
2283
+ format: winston__namespace.format.combine(winston__namespace.format.colorize(), winston__namespace.format.timestamp(), winston__namespace.format.simple()),
2284
+ defaultMeta: {}
2285
+ });
2286
+ const streamLogger = new stream.PassThrough();
2287
+ streamLogger.on("data", async (data) => {
2288
+ const message = data.toString().trim();
2289
+ if ((message == null ? void 0 : message.length) > 1) {
2290
+ await task.emitLog(message, metadata);
2291
+ }
2292
+ });
2293
+ taskLogger.add(new winston__namespace.transports.Stream({stream: streamLogger}));
2294
+ return {taskLogger, streamLogger};
2295
+ };
2296
+ class DefaultWorkflowRunner {
2297
+ constructor(options) {
2298
+ this.options = options;
2299
+ this.nunjucksOptions = {
2300
+ autoescape: false,
2301
+ tags: {
2302
+ variableStart: "${{",
2303
+ variableEnd: "}}"
2304
+ }
2305
+ };
2306
+ this.nunjucks = nunjucks__default['default'].configure(this.nunjucksOptions);
2307
+ this.nunjucks.addFilter("parseRepoUrl", (repoUrl) => {
2308
+ return parseRepoUrl(repoUrl, this.options.integrations);
2309
+ });
2310
+ this.nunjucks.addFilter("projectSlug", (repoUrl) => {
2311
+ const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
2312
+ return `${owner}/${repo}`;
2313
+ });
2236
2314
  }
2237
- return workingDirectory;
2238
- }
2239
- function getEntityBaseUrl(entity) {
2240
- var _a, _b;
2241
- let location = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.SOURCE_LOCATION_ANNOTATION];
2242
- if (!location) {
2243
- location = (_b = entity.metadata.annotations) == null ? void 0 : _b[catalogModel.LOCATION_ANNOTATION];
2315
+ isSingleTemplateString(input) {
2316
+ const {parser, nodes} = require("nunjucks");
2317
+ const parsed = parser.parse(input, {}, this.nunjucksOptions);
2318
+ return parsed.children.length === 1 && !(parsed.children[0] instanceof nodes.TemplateData);
2244
2319
  }
2245
- if (!location) {
2246
- return void 0;
2320
+ render(input, context) {
2321
+ return JSON.parse(JSON.stringify(input), (_key, value) => {
2322
+ try {
2323
+ if (typeof value === "string") {
2324
+ try {
2325
+ if (this.isSingleTemplateString(value)) {
2326
+ const wrappedDumped = value.replace(/\${{(.+)}}/g, "${{ ( $1 ) | dump }}");
2327
+ const templated2 = this.nunjucks.renderString(wrappedDumped, context);
2328
+ if (templated2 === "") {
2329
+ return void 0;
2330
+ }
2331
+ return JSON.parse(templated2);
2332
+ }
2333
+ } catch (ex) {
2334
+ this.options.logger.error(`Failed to parse template string: ${value} with error ${ex.message}`);
2335
+ }
2336
+ const templated = this.nunjucks.renderString(value, context);
2337
+ if (templated === "") {
2338
+ return void 0;
2339
+ }
2340
+ return templated;
2341
+ }
2342
+ } catch {
2343
+ return value;
2344
+ }
2345
+ return value;
2346
+ });
2247
2347
  }
2248
- const {type, target} = catalogModel.parseLocationReference(location);
2249
- if (type === "url") {
2250
- return target;
2251
- } else if (type === "file") {
2252
- return `file://${target}`;
2348
+ async execute(task) {
2349
+ var _a, _b;
2350
+ if (!isValidTaskSpec(task.spec)) {
2351
+ throw new errors.InputError("Wrong template version executed with the workflow engine");
2352
+ }
2353
+ const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
2354
+ try {
2355
+ await fs__default['default'].ensureDir(workspacePath);
2356
+ await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
2357
+ const context = {
2358
+ parameters: task.spec.parameters,
2359
+ steps: {}
2360
+ };
2361
+ for (const step of task.spec.steps) {
2362
+ try {
2363
+ if (step.if) {
2364
+ const ifResult = await this.render(step.if, context);
2365
+ if (!isTruthy(ifResult)) {
2366
+ await task.emitLog(`Skipping step ${step.id} because it's if condition was false`, {stepId: step.id, status: "skipped"});
2367
+ continue;
2368
+ }
2369
+ }
2370
+ await task.emitLog(`Beginning step ${step.name}`, {
2371
+ stepId: step.id,
2372
+ status: "processing"
2373
+ });
2374
+ const action = this.options.actionRegistry.get(step.action);
2375
+ const {taskLogger, streamLogger} = createStepLogger({task, step});
2376
+ const input = (_a = step.input && this.render(step.input, context)) != null ? _a : {};
2377
+ if ((_b = action.schema) == null ? void 0 : _b.input) {
2378
+ const validateResult = jsonschema.validate(input, action.schema.input);
2379
+ if (!validateResult.valid) {
2380
+ const errors$1 = validateResult.errors.join(", ");
2381
+ throw new errors.InputError(`Invalid input passed to action ${action.id}, ${errors$1}`);
2382
+ }
2383
+ }
2384
+ const tmpDirs = new Array();
2385
+ const stepOutput = {};
2386
+ await action.handler({
2387
+ baseUrl: task.spec.baseUrl,
2388
+ input,
2389
+ logger: taskLogger,
2390
+ logStream: streamLogger,
2391
+ workspacePath,
2392
+ createTemporaryDirectory: async () => {
2393
+ const tmpDir = await fs__default['default'].mkdtemp(`${workspacePath}_step-${step.id}-`);
2394
+ tmpDirs.push(tmpDir);
2395
+ return tmpDir;
2396
+ },
2397
+ output(name, value) {
2398
+ stepOutput[name] = value;
2399
+ }
2400
+ });
2401
+ for (const tmpDir of tmpDirs) {
2402
+ await fs__default['default'].remove(tmpDir);
2403
+ }
2404
+ context.steps[step.id] = {output: stepOutput};
2405
+ await task.emitLog(`Finished step ${step.name}`, {
2406
+ stepId: step.id,
2407
+ status: "completed"
2408
+ });
2409
+ } catch (err) {
2410
+ await task.emitLog(String(err.stack), {
2411
+ stepId: step.id,
2412
+ status: "failed"
2413
+ });
2414
+ throw err;
2415
+ }
2416
+ }
2417
+ const output = this.render(task.spec.output, context);
2418
+ return {output};
2419
+ } finally {
2420
+ if (workspacePath) {
2421
+ await fs__default['default'].remove(workspacePath);
2422
+ }
2423
+ }
2253
2424
  }
2254
- return void 0;
2255
2425
  }
2256
2426
 
2257
- function isBeta2Template(entity) {
2258
- return entity.apiVersion === "backstage.io/v1beta2";
2427
+ function isSupportedTemplate(entity) {
2428
+ return entity.apiVersion === "backstage.io/v1beta2" || entity.apiVersion === "scaffolder.backstage.io/v1beta3";
2259
2429
  }
2260
2430
  async function createRouter(options) {
2261
2431
  const router = Router__default['default']();
@@ -2277,14 +2447,26 @@ async function createRouter(options) {
2277
2447
  const databaseTaskStore = await DatabaseTaskStore.create(await database.getClient());
2278
2448
  const taskBroker = new StorageTaskBroker(databaseTaskStore, logger);
2279
2449
  const actionRegistry = new TemplateActionRegistry();
2450
+ const legacyWorkflowRunner = new LegacyWorkflowRunner({
2451
+ logger,
2452
+ actionRegistry,
2453
+ integrations,
2454
+ workingDirectory
2455
+ });
2456
+ const workflowRunner = new DefaultWorkflowRunner({
2457
+ actionRegistry,
2458
+ integrations,
2459
+ logger,
2460
+ workingDirectory
2461
+ });
2280
2462
  const workers = [];
2281
2463
  for (let i = 0; i < (taskWorkers || 1); i++) {
2282
2464
  const worker = new TaskWorker({
2283
- logger,
2284
2465
  taskBroker,
2285
- actionRegistry,
2286
- workingDirectory,
2287
- integrations
2466
+ runners: {
2467
+ legacyWorkflowRunner,
2468
+ workflowRunner
2469
+ }
2288
2470
  });
2289
2471
  workers.push(worker);
2290
2472
  }
@@ -2309,7 +2491,7 @@ async function createRouter(options) {
2309
2491
  const template = await entityClient.findTemplate(name, {
2310
2492
  token: getBearerToken(req.headers.authorization)
2311
2493
  });
2312
- if (isBeta2Template(template)) {
2494
+ if (isSupportedTemplate(template)) {
2313
2495
  const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
2314
2496
  res.json({
2315
2497
  title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
@@ -2334,7 +2516,7 @@ async function createRouter(options) {
2334
2516
  });
2335
2517
  res.json(actionsList);
2336
2518
  }).post("/v2/tasks", async (req, res) => {
2337
- var _a, _b;
2519
+ var _a, _b, _c;
2338
2520
  const templateName = req.body.templateName;
2339
2521
  const values = req.body.values;
2340
2522
  const token = getBearerToken(req.headers.authorization);
@@ -2342,7 +2524,7 @@ async function createRouter(options) {
2342
2524
  token
2343
2525
  });
2344
2526
  let taskSpec;
2345
- if (isBeta2Template(template)) {
2527
+ if (isSupportedTemplate(template)) {
2346
2528
  for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
2347
2529
  const result2 = jsonschema.validate(values, parameters);
2348
2530
  if (!result2.valid) {
@@ -2351,7 +2533,8 @@ async function createRouter(options) {
2351
2533
  }
2352
2534
  }
2353
2535
  const baseUrl = getEntityBaseUrl(template);
2354
- taskSpec = {
2536
+ taskSpec = template.apiVersion === "backstage.io/v1beta2" ? {
2537
+ apiVersion: template.apiVersion,
2355
2538
  baseUrl,
2356
2539
  values,
2357
2540
  steps: template.spec.steps.map((step, index) => {
@@ -2363,6 +2546,19 @@ async function createRouter(options) {
2363
2546
  };
2364
2547
  }),
2365
2548
  output: (_b = template.spec.output) != null ? _b : {}
2549
+ } : {
2550
+ apiVersion: template.apiVersion,
2551
+ baseUrl,
2552
+ parameters: values,
2553
+ steps: template.spec.steps.map((step, index) => {
2554
+ var _a2, _b2;
2555
+ return {
2556
+ ...step,
2557
+ id: (_a2 = step.id) != null ? _a2 : `step-${index + 1}`,
2558
+ name: (_b2 = step.name) != null ? _b2 : step.action
2559
+ };
2560
+ }),
2561
+ output: (_c = template.spec.output) != null ? _c : {}
2366
2562
  };
2367
2563
  } else {
2368
2564
  throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
@@ -2421,6 +2617,54 @@ function getBearerToken(header) {
2421
2617
  return (_a = header == null ? void 0 : header.match(/Bearer\s+(\S+)/i)) == null ? void 0 : _a[1];
2422
2618
  }
2423
2619
 
2620
+ class ScaffolderEntitiesProcessor {
2621
+ constructor() {
2622
+ this.validators = [
2623
+ catalogModel.entityKindSchemaValidator(pluginScaffolderCommon.templateEntityV1beta3Schema)
2624
+ ];
2625
+ }
2626
+ async validateEntityKind(entity) {
2627
+ for (const validator of this.validators) {
2628
+ if (validator(entity)) {
2629
+ return true;
2630
+ }
2631
+ }
2632
+ return false;
2633
+ }
2634
+ async postProcessEntity(entity, _location, emit) {
2635
+ const selfRef = catalogModel.getEntityName(entity);
2636
+ if (entity.apiVersion === "scaffolder.backstage.io/v1beta3" && entity.kind === "Template") {
2637
+ const template = entity;
2638
+ const target = template.spec.owner;
2639
+ if (target) {
2640
+ const targetRef = catalogModel.parseEntityRef(target, {
2641
+ defaultKind: "Group",
2642
+ defaultNamespace: selfRef.namespace
2643
+ });
2644
+ emit(pluginCatalogBackend.results.relation({
2645
+ source: selfRef,
2646
+ type: catalogModel.RELATION_OWNED_BY,
2647
+ target: {
2648
+ kind: targetRef.kind,
2649
+ namespace: targetRef.namespace,
2650
+ name: targetRef.name
2651
+ }
2652
+ }));
2653
+ emit(pluginCatalogBackend.results.relation({
2654
+ source: {
2655
+ kind: targetRef.kind,
2656
+ namespace: targetRef.namespace,
2657
+ name: targetRef.name
2658
+ },
2659
+ type: catalogModel.RELATION_OWNER_OF,
2660
+ target: selfRef
2661
+ }));
2662
+ }
2663
+ }
2664
+ return entity;
2665
+ }
2666
+ }
2667
+
2424
2668
  Object.defineProperty(exports, 'createFetchCookiecutterAction', {
2425
2669
  enumerable: true,
2426
2670
  get: function () {
@@ -2429,6 +2673,7 @@ Object.defineProperty(exports, 'createFetchCookiecutterAction', {
2429
2673
  });
2430
2674
  exports.CatalogEntityClient = CatalogEntityClient;
2431
2675
  exports.OctokitProvider = OctokitProvider;
2676
+ exports.ScaffolderEntitiesProcessor = ScaffolderEntitiesProcessor;
2432
2677
  exports.TemplateActionRegistry = TemplateActionRegistry;
2433
2678
  exports.createBuiltinActions = createBuiltinActions;
2434
2679
  exports.createCatalogRegisterAction = createCatalogRegisterAction;