@backstage/plugin-scaffolder-backend 0.15.7 → 0.15.11

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/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;
@@ -85,6 +88,11 @@ function createCatalogRegisterAction(options) {
85
88
  title: "Catalog Info URL",
86
89
  description: "An absolute URL pointing to the catalog info file location",
87
90
  type: "string"
91
+ },
92
+ optional: {
93
+ title: "Optional",
94
+ description: "Permit the registered location to optionally exist. Default: false",
95
+ type: "boolean"
88
96
  }
89
97
  }
90
98
  },
@@ -101,6 +109,11 @@ function createCatalogRegisterAction(options) {
101
109
  title: "Fetch URL",
102
110
  description: "A relative path from the repo root pointing to the catalog info file, defaults to /catalog-info.yaml",
103
111
  type: "string"
112
+ },
113
+ optional: {
114
+ title: "Optional",
115
+ description: "Permit the registered location to optionally exist. Default: false",
116
+ type: "boolean"
104
117
  }
105
118
  }
106
119
  }
@@ -129,15 +142,21 @@ function createCatalogRegisterAction(options) {
129
142
  type: "url",
130
143
  target: catalogInfoUrl
131
144
  }, ctx.token ? {token: ctx.token} : {});
132
- const result = await catalogClient.addLocation({
133
- dryRun: true,
134
- type: "url",
135
- target: catalogInfoUrl
136
- }, ctx.token ? {token: ctx.token} : {});
137
- if (result.entities.length > 0) {
138
- const {entities} = result;
139
- const entity = (_a = entities.find((e) => !e.metadata.name.startsWith("generated-"))) != null ? _a : entities[0];
140
- ctx.output("entityRef", catalogModel.stringifyEntityRef(entity));
145
+ try {
146
+ const result = await catalogClient.addLocation({
147
+ dryRun: true,
148
+ type: "url",
149
+ target: catalogInfoUrl
150
+ }, ctx.token ? {token: ctx.token} : {});
151
+ if (result.entities.length > 0) {
152
+ const {entities} = result;
153
+ const entity = (_a = entities.find((e) => !e.metadata.name.startsWith("generated-"))) != null ? _a : entities[0];
154
+ ctx.output("entityRef", catalogModel.stringifyEntityRef(entity));
155
+ }
156
+ } catch (e) {
157
+ if (!input.optional) {
158
+ throw e;
159
+ }
141
160
  }
142
161
  ctx.output("catalogInfoUrl", catalogInfoUrl);
143
162
  }
@@ -183,12 +202,16 @@ function createDebugLogAction() {
183
202
  listWorkspace: {
184
203
  title: "List all files in the workspace, if true.",
185
204
  type: "boolean"
205
+ },
206
+ extra: {
207
+ title: "Extra info"
186
208
  }
187
209
  }
188
210
  }
189
211
  },
190
212
  async handler(ctx) {
191
213
  var _a, _b;
214
+ ctx.logger.info(JSON.stringify(ctx.input, null, 2));
192
215
  if ((_a = ctx.input) == null ? void 0 : _a.message) {
193
216
  ctx.logStream.write(ctx.input.message);
194
217
  }
@@ -227,10 +250,7 @@ async function fetchContents({
227
250
  }
228
251
  if (!fetchUrlIsAbsolute && (baseUrl == null ? void 0 : baseUrl.startsWith("file://"))) {
229
252
  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);
253
+ const srcDir = backendCommon.resolveSafeChildPath(path__namespace.dirname(basePath), fetchUrl);
234
254
  await fs__default['default'].copy(srcDir, outputPath);
235
255
  } else {
236
256
  let readUrl;
@@ -627,6 +647,7 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
627
647
  }
628
648
  });
629
649
  } catch (e) {
650
+ errors.assertError(e);
630
651
  if (e.message.includes("Upgrade to GitHub Pro or make this repository public to enable this feature")) {
631
652
  logger.warn("Branch protection was not enabled as it requires GitHub Pro for private repositories");
632
653
  } else {
@@ -648,7 +669,11 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
648
669
  const getRepoSourceDirectory = (workspacePath, sourcePath) => {
649
670
  if (sourcePath) {
650
671
  const safeSuffix = path.normalize(sourcePath).replace(/^(\.\.(\/|\\|$))+/, "");
651
- return path.join(workspacePath, safeSuffix);
672
+ const path$1 = path.join(workspacePath, safeSuffix);
673
+ if (!backendCommon.isChildPath(workspacePath, path$1)) {
674
+ throw new Error("Invalid source path");
675
+ }
676
+ return path$1;
652
677
  }
653
678
  return workspacePath;
654
679
  };
@@ -1208,6 +1233,7 @@ function createPublishGithubAction(options) {
1208
1233
  permission
1209
1234
  });
1210
1235
  } catch (e) {
1236
+ errors.assertError(e);
1211
1237
  ctx.logger.warn(`Skipping ${permission} access for ${team_slug}, ${e.message}`);
1212
1238
  }
1213
1239
  }
@@ -1220,6 +1246,7 @@ function createPublishGithubAction(options) {
1220
1246
  names: topics.map((t) => t.toLowerCase())
1221
1247
  });
1222
1248
  } catch (e) {
1249
+ errors.assertError(e);
1223
1250
  ctx.logger.warn(`Skipping topics ${topics.join(" ")}, ${e.message}`);
1224
1251
  }
1225
1252
  }
@@ -1251,6 +1278,7 @@ function createPublishGithubAction(options) {
1251
1278
  requireCodeOwnerReviews
1252
1279
  });
1253
1280
  } catch (e) {
1281
+ errors.assertError(e);
1254
1282
  ctx.logger.warn(`Skipping: default branch protection on '${newRepo.name}', ${e.message}`);
1255
1283
  }
1256
1284
  ctx.output("remoteUrl", remoteUrl);
@@ -1357,19 +1385,31 @@ const createPublishGithubPullRequestAction = ({
1357
1385
  throw new errors.InputError(`No owner provided for host: ${host}, and repo ${repo}`);
1358
1386
  }
1359
1387
  const client = await clientFactory({integrations, host, owner, repo});
1360
- const fileRoot = sourcePath ? path__default['default'].resolve(ctx.workspacePath, sourcePath) : ctx.workspacePath;
1388
+ const fileRoot = sourcePath ? backendCommon.resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath;
1361
1389
  const localFilePaths = await globby__default['default'](["./**", "./**/.*", "!.git"], {
1362
1390
  cwd: fileRoot,
1363
1391
  gitignore: true,
1364
1392
  dot: true
1365
1393
  });
1366
- const fileContents = await Promise.all(localFilePaths.map((p) => fs.readFile(path__default['default'].resolve(fileRoot, p))));
1394
+ const fileContents = await Promise.all(localFilePaths.map((filePath) => {
1395
+ const absPath = path__default['default'].resolve(fileRoot, filePath);
1396
+ const base64EncodedContent = fs__default['default'].readFileSync(absPath).toString("base64");
1397
+ const fileStat = fs__default['default'].statSync(absPath);
1398
+ const isExecutable = fileStat.mode === 33277;
1399
+ const githubTreeItemMode = isExecutable ? "100755" : "100644";
1400
+ const encoding = "base64";
1401
+ return {
1402
+ encoding,
1403
+ content: base64EncodedContent,
1404
+ mode: githubTreeItemMode
1405
+ };
1406
+ }));
1367
1407
  const repoFilePaths = localFilePaths.map((repoFilePath) => {
1368
1408
  return targetPath ? `${targetPath}/${repoFilePath}` : repoFilePath;
1369
1409
  });
1370
1410
  const changes = [
1371
1411
  {
1372
- files: lodash.zipObject(repoFilePaths, fileContents.map((buf) => buf.toString())),
1412
+ files: lodash.zipObject(repoFilePaths, fileContents),
1373
1413
  commit: title
1374
1414
  }
1375
1415
  ];
@@ -1629,6 +1669,7 @@ function createGithubWebhookAction(options) {
1629
1669
  });
1630
1670
  ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
1631
1671
  } catch (e) {
1672
+ errors.assertError(e);
1632
1673
  ctx.logger.warn(`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`);
1633
1674
  }
1634
1675
  }
@@ -2002,6 +2043,7 @@ class StorageTaskBroker {
2002
2043
  try {
2003
2044
  callback(void 0, result);
2004
2045
  } catch (error) {
2046
+ errors.assertError(error);
2005
2047
  callback(error, {events: []});
2006
2048
  }
2007
2049
  }
@@ -2035,39 +2077,92 @@ class StorageTaskBroker {
2035
2077
  }
2036
2078
  }
2037
2079
 
2080
+ class TaskWorker {
2081
+ constructor(options) {
2082
+ this.options = options;
2083
+ }
2084
+ start() {
2085
+ (async () => {
2086
+ for (; ; ) {
2087
+ const task = await this.options.taskBroker.claim();
2088
+ await this.runOneTask(task);
2089
+ }
2090
+ })();
2091
+ }
2092
+ async runOneTask(task) {
2093
+ try {
2094
+ const {output} = task.spec.apiVersion === "scaffolder.backstage.io/v1beta3" ? await this.options.runners.workflowRunner.execute(task) : await this.options.runners.legacyWorkflowRunner.execute(task);
2095
+ await task.complete("completed", {output});
2096
+ } catch (error) {
2097
+ errors.assertError(error);
2098
+ await task.complete("failed", {
2099
+ error: {name: error.name, message: error.message}
2100
+ });
2101
+ }
2102
+ }
2103
+ }
2104
+
2105
+ async function getWorkingDirectory(config, logger) {
2106
+ if (!config.has("backend.workingDirectory")) {
2107
+ return os__default['default'].tmpdir();
2108
+ }
2109
+ const workingDirectory = config.getString("backend.workingDirectory");
2110
+ try {
2111
+ await fs__default['default'].access(workingDirectory, fs__default['default'].constants.F_OK | fs__default['default'].constants.W_OK);
2112
+ logger.info(`using working directory: ${workingDirectory}`);
2113
+ } catch (err) {
2114
+ errors.assertError(err);
2115
+ logger.error(`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`);
2116
+ throw err;
2117
+ }
2118
+ return workingDirectory;
2119
+ }
2120
+ function getEntityBaseUrl(entity) {
2121
+ var _a, _b;
2122
+ let location = (_a = entity.metadata.annotations) == null ? void 0 : _a[catalogModel.SOURCE_LOCATION_ANNOTATION];
2123
+ if (!location) {
2124
+ location = (_b = entity.metadata.annotations) == null ? void 0 : _b[catalogModel.LOCATION_ANNOTATION];
2125
+ }
2126
+ if (!location) {
2127
+ return void 0;
2128
+ }
2129
+ const {type, target} = catalogModel.parseLocationReference(location);
2130
+ if (type === "url") {
2131
+ return target;
2132
+ } else if (type === "file") {
2133
+ return `file://${target}`;
2134
+ }
2135
+ return void 0;
2136
+ }
2137
+
2038
2138
  function isTruthy(value) {
2039
2139
  return lodash.isArray(value) ? value.length > 0 : !!value;
2040
2140
  }
2041
2141
 
2042
- class TaskWorker {
2142
+ const isValidTaskSpec$1 = (taskSpec) => taskSpec.apiVersion === "backstage.io/v1beta2";
2143
+ class LegacyWorkflowRunner {
2043
2144
  constructor(options) {
2044
2145
  this.options = options;
2045
2146
  this.handlebars = Handlebars__namespace.create();
2046
2147
  this.handlebars.registerHelper("parseRepoUrl", (repoUrl) => {
2047
- return JSON.stringify(parseRepoUrl(repoUrl, options.integrations));
2148
+ return JSON.stringify(parseRepoUrl(repoUrl, this.options.integrations));
2048
2149
  });
2049
2150
  this.handlebars.registerHelper("projectSlug", (repoUrl) => {
2050
- const {owner, repo} = parseRepoUrl(repoUrl, options.integrations);
2151
+ const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
2051
2152
  return `${owner}/${repo}`;
2052
2153
  });
2053
2154
  this.handlebars.registerHelper("json", (obj) => JSON.stringify(obj));
2054
2155
  this.handlebars.registerHelper("not", (value) => !isTruthy(value));
2055
2156
  this.handlebars.registerHelper("eq", (a, b) => a === b);
2056
2157
  }
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) {
2158
+ async execute(task) {
2066
2159
  var _a, _b;
2067
- let workspacePath = void 0;
2160
+ if (!isValidTaskSpec$1(task.spec)) {
2161
+ throw new errors.InputError(`Task spec is not a valid v1beta2 task spec`);
2162
+ }
2163
+ const {actionRegistry} = this.options;
2164
+ const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
2068
2165
  try {
2069
- const {actionRegistry} = this.options;
2070
- workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
2071
2166
  await fs__default['default'].ensureDir(workspacePath);
2072
2167
  await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
2073
2168
  const templateCtx = {parameters: task.spec.values, steps: {}};
@@ -2209,11 +2304,7 @@ class TaskWorker {
2209
2304
  }
2210
2305
  return value;
2211
2306
  });
2212
- await task.complete("completed", {output});
2213
- } catch (error) {
2214
- await task.complete("failed", {
2215
- error: {name: error.name, message: error.message}
2216
- });
2307
+ return {output};
2217
2308
  } finally {
2218
2309
  if (workspacePath) {
2219
2310
  await fs__default['default'].remove(workspacePath);
@@ -2222,40 +2313,159 @@ class TaskWorker {
2222
2313
  }
2223
2314
  }
2224
2315
 
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;
2316
+ const isValidTaskSpec = (taskSpec) => {
2317
+ return taskSpec.apiVersion === "scaffolder.backstage.io/v1beta3";
2318
+ };
2319
+ const createStepLogger = ({task, step}) => {
2320
+ const metadata = {stepId: step.id};
2321
+ const taskLogger = winston__namespace.createLogger({
2322
+ level: process.env.LOG_LEVEL || "info",
2323
+ format: winston__namespace.format.combine(winston__namespace.format.colorize(), winston__namespace.format.timestamp(), winston__namespace.format.simple()),
2324
+ defaultMeta: {}
2325
+ });
2326
+ const streamLogger = new stream.PassThrough();
2327
+ streamLogger.on("data", async (data) => {
2328
+ const message = data.toString().trim();
2329
+ if ((message == null ? void 0 : message.length) > 1) {
2330
+ await task.emitLog(message, metadata);
2331
+ }
2332
+ });
2333
+ taskLogger.add(new winston__namespace.transports.Stream({stream: streamLogger}));
2334
+ return {taskLogger, streamLogger};
2335
+ };
2336
+ class DefaultWorkflowRunner {
2337
+ constructor(options) {
2338
+ this.options = options;
2339
+ this.nunjucksOptions = {
2340
+ autoescape: false,
2341
+ tags: {
2342
+ variableStart: "${{",
2343
+ variableEnd: "}}"
2344
+ }
2345
+ };
2346
+ this.nunjucks = nunjucks__default['default'].configure(this.nunjucksOptions);
2347
+ this.nunjucks.addFilter("parseRepoUrl", (repoUrl) => {
2348
+ return parseRepoUrl(repoUrl, this.options.integrations);
2349
+ });
2350
+ this.nunjucks.addFilter("projectSlug", (repoUrl) => {
2351
+ const {owner, repo} = parseRepoUrl(repoUrl, this.options.integrations);
2352
+ return `${owner}/${repo}`;
2353
+ });
2236
2354
  }
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];
2355
+ isSingleTemplateString(input) {
2356
+ const {parser, nodes} = require("nunjucks");
2357
+ const parsed = parser.parse(input, {}, this.nunjucksOptions);
2358
+ return parsed.children.length === 1 && !(parsed.children[0] instanceof nodes.TemplateData);
2244
2359
  }
2245
- if (!location) {
2246
- return void 0;
2360
+ render(input, context) {
2361
+ return JSON.parse(JSON.stringify(input), (_key, value) => {
2362
+ try {
2363
+ if (typeof value === "string") {
2364
+ try {
2365
+ if (this.isSingleTemplateString(value)) {
2366
+ const wrappedDumped = value.replace(/\${{(.+)}}/g, "${{ ( $1 ) | dump }}");
2367
+ const templated2 = this.nunjucks.renderString(wrappedDumped, context);
2368
+ if (templated2 === "") {
2369
+ return void 0;
2370
+ }
2371
+ return JSON.parse(templated2);
2372
+ }
2373
+ } catch (ex) {
2374
+ this.options.logger.error(`Failed to parse template string: ${value} with error ${ex.message}`);
2375
+ }
2376
+ const templated = this.nunjucks.renderString(value, context);
2377
+ if (templated === "") {
2378
+ return void 0;
2379
+ }
2380
+ return templated;
2381
+ }
2382
+ } catch {
2383
+ return value;
2384
+ }
2385
+ return value;
2386
+ });
2247
2387
  }
2248
- const {type, target} = catalogModel.parseLocationReference(location);
2249
- if (type === "url") {
2250
- return target;
2251
- } else if (type === "file") {
2252
- return `file://${target}`;
2388
+ async execute(task) {
2389
+ var _a, _b;
2390
+ if (!isValidTaskSpec(task.spec)) {
2391
+ throw new errors.InputError("Wrong template version executed with the workflow engine");
2392
+ }
2393
+ const workspacePath = path__default['default'].join(this.options.workingDirectory, await task.getWorkspaceName());
2394
+ try {
2395
+ await fs__default['default'].ensureDir(workspacePath);
2396
+ await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
2397
+ const context = {
2398
+ parameters: task.spec.parameters,
2399
+ steps: {}
2400
+ };
2401
+ for (const step of task.spec.steps) {
2402
+ try {
2403
+ if (step.if) {
2404
+ const ifResult = await this.render(step.if, context);
2405
+ if (!isTruthy(ifResult)) {
2406
+ await task.emitLog(`Skipping step ${step.id} because it's if condition was false`, {stepId: step.id, status: "skipped"});
2407
+ continue;
2408
+ }
2409
+ }
2410
+ await task.emitLog(`Beginning step ${step.name}`, {
2411
+ stepId: step.id,
2412
+ status: "processing"
2413
+ });
2414
+ const action = this.options.actionRegistry.get(step.action);
2415
+ const {taskLogger, streamLogger} = createStepLogger({task, step});
2416
+ const input = (_a = step.input && this.render(step.input, context)) != null ? _a : {};
2417
+ if ((_b = action.schema) == null ? void 0 : _b.input) {
2418
+ const validateResult = jsonschema.validate(input, action.schema.input);
2419
+ if (!validateResult.valid) {
2420
+ const errors$1 = validateResult.errors.join(", ");
2421
+ throw new errors.InputError(`Invalid input passed to action ${action.id}, ${errors$1}`);
2422
+ }
2423
+ }
2424
+ const tmpDirs = new Array();
2425
+ const stepOutput = {};
2426
+ await action.handler({
2427
+ baseUrl: task.spec.baseUrl,
2428
+ input,
2429
+ logger: taskLogger,
2430
+ logStream: streamLogger,
2431
+ workspacePath,
2432
+ createTemporaryDirectory: async () => {
2433
+ const tmpDir = await fs__default['default'].mkdtemp(`${workspacePath}_step-${step.id}-`);
2434
+ tmpDirs.push(tmpDir);
2435
+ return tmpDir;
2436
+ },
2437
+ output(name, value) {
2438
+ stepOutput[name] = value;
2439
+ }
2440
+ });
2441
+ for (const tmpDir of tmpDirs) {
2442
+ await fs__default['default'].remove(tmpDir);
2443
+ }
2444
+ context.steps[step.id] = {output: stepOutput};
2445
+ await task.emitLog(`Finished step ${step.name}`, {
2446
+ stepId: step.id,
2447
+ status: "completed"
2448
+ });
2449
+ } catch (err) {
2450
+ await task.emitLog(String(err.stack), {
2451
+ stepId: step.id,
2452
+ status: "failed"
2453
+ });
2454
+ throw err;
2455
+ }
2456
+ }
2457
+ const output = this.render(task.spec.output, context);
2458
+ return {output};
2459
+ } finally {
2460
+ if (workspacePath) {
2461
+ await fs__default['default'].remove(workspacePath);
2462
+ }
2463
+ }
2253
2464
  }
2254
- return void 0;
2255
2465
  }
2256
2466
 
2257
- function isBeta2Template(entity) {
2258
- return entity.apiVersion === "backstage.io/v1beta2";
2467
+ function isSupportedTemplate(entity) {
2468
+ return entity.apiVersion === "backstage.io/v1beta2" || entity.apiVersion === "scaffolder.backstage.io/v1beta3";
2259
2469
  }
2260
2470
  async function createRouter(options) {
2261
2471
  const router = Router__default['default']();
@@ -2277,14 +2487,26 @@ async function createRouter(options) {
2277
2487
  const databaseTaskStore = await DatabaseTaskStore.create(await database.getClient());
2278
2488
  const taskBroker = new StorageTaskBroker(databaseTaskStore, logger);
2279
2489
  const actionRegistry = new TemplateActionRegistry();
2490
+ const legacyWorkflowRunner = new LegacyWorkflowRunner({
2491
+ logger,
2492
+ actionRegistry,
2493
+ integrations,
2494
+ workingDirectory
2495
+ });
2496
+ const workflowRunner = new DefaultWorkflowRunner({
2497
+ actionRegistry,
2498
+ integrations,
2499
+ logger,
2500
+ workingDirectory
2501
+ });
2280
2502
  const workers = [];
2281
2503
  for (let i = 0; i < (taskWorkers || 1); i++) {
2282
2504
  const worker = new TaskWorker({
2283
- logger,
2284
2505
  taskBroker,
2285
- actionRegistry,
2286
- workingDirectory,
2287
- integrations
2506
+ runners: {
2507
+ legacyWorkflowRunner,
2508
+ workflowRunner
2509
+ }
2288
2510
  });
2289
2511
  workers.push(worker);
2290
2512
  }
@@ -2309,7 +2531,7 @@ async function createRouter(options) {
2309
2531
  const template = await entityClient.findTemplate(name, {
2310
2532
  token: getBearerToken(req.headers.authorization)
2311
2533
  });
2312
- if (isBeta2Template(template)) {
2534
+ if (isSupportedTemplate(template)) {
2313
2535
  const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
2314
2536
  res.json({
2315
2537
  title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
@@ -2334,7 +2556,7 @@ async function createRouter(options) {
2334
2556
  });
2335
2557
  res.json(actionsList);
2336
2558
  }).post("/v2/tasks", async (req, res) => {
2337
- var _a, _b;
2559
+ var _a, _b, _c;
2338
2560
  const templateName = req.body.templateName;
2339
2561
  const values = req.body.values;
2340
2562
  const token = getBearerToken(req.headers.authorization);
@@ -2342,7 +2564,7 @@ async function createRouter(options) {
2342
2564
  token
2343
2565
  });
2344
2566
  let taskSpec;
2345
- if (isBeta2Template(template)) {
2567
+ if (isSupportedTemplate(template)) {
2346
2568
  for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
2347
2569
  const result2 = jsonschema.validate(values, parameters);
2348
2570
  if (!result2.valid) {
@@ -2351,7 +2573,8 @@ async function createRouter(options) {
2351
2573
  }
2352
2574
  }
2353
2575
  const baseUrl = getEntityBaseUrl(template);
2354
- taskSpec = {
2576
+ taskSpec = template.apiVersion === "backstage.io/v1beta2" ? {
2577
+ apiVersion: template.apiVersion,
2355
2578
  baseUrl,
2356
2579
  values,
2357
2580
  steps: template.spec.steps.map((step, index) => {
@@ -2363,6 +2586,19 @@ async function createRouter(options) {
2363
2586
  };
2364
2587
  }),
2365
2588
  output: (_b = template.spec.output) != null ? _b : {}
2589
+ } : {
2590
+ apiVersion: template.apiVersion,
2591
+ baseUrl,
2592
+ parameters: values,
2593
+ steps: template.spec.steps.map((step, index) => {
2594
+ var _a2, _b2;
2595
+ return {
2596
+ ...step,
2597
+ id: (_a2 = step.id) != null ? _a2 : `step-${index + 1}`,
2598
+ name: (_b2 = step.name) != null ? _b2 : step.action
2599
+ };
2600
+ }),
2601
+ output: (_c = template.spec.output) != null ? _c : {}
2366
2602
  };
2367
2603
  } else {
2368
2604
  throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
@@ -2421,6 +2657,54 @@ function getBearerToken(header) {
2421
2657
  return (_a = header == null ? void 0 : header.match(/Bearer\s+(\S+)/i)) == null ? void 0 : _a[1];
2422
2658
  }
2423
2659
 
2660
+ class ScaffolderEntitiesProcessor {
2661
+ constructor() {
2662
+ this.validators = [
2663
+ catalogModel.entityKindSchemaValidator(pluginScaffolderCommon.templateEntityV1beta3Schema)
2664
+ ];
2665
+ }
2666
+ async validateEntityKind(entity) {
2667
+ for (const validator of this.validators) {
2668
+ if (validator(entity)) {
2669
+ return true;
2670
+ }
2671
+ }
2672
+ return false;
2673
+ }
2674
+ async postProcessEntity(entity, _location, emit) {
2675
+ const selfRef = catalogModel.getEntityName(entity);
2676
+ if (entity.apiVersion === "scaffolder.backstage.io/v1beta3" && entity.kind === "Template") {
2677
+ const template = entity;
2678
+ const target = template.spec.owner;
2679
+ if (target) {
2680
+ const targetRef = catalogModel.parseEntityRef(target, {
2681
+ defaultKind: "Group",
2682
+ defaultNamespace: selfRef.namespace
2683
+ });
2684
+ emit(pluginCatalogBackend.results.relation({
2685
+ source: selfRef,
2686
+ type: catalogModel.RELATION_OWNED_BY,
2687
+ target: {
2688
+ kind: targetRef.kind,
2689
+ namespace: targetRef.namespace,
2690
+ name: targetRef.name
2691
+ }
2692
+ }));
2693
+ emit(pluginCatalogBackend.results.relation({
2694
+ source: {
2695
+ kind: targetRef.kind,
2696
+ namespace: targetRef.namespace,
2697
+ name: targetRef.name
2698
+ },
2699
+ type: catalogModel.RELATION_OWNER_OF,
2700
+ target: selfRef
2701
+ }));
2702
+ }
2703
+ }
2704
+ return entity;
2705
+ }
2706
+ }
2707
+
2424
2708
  Object.defineProperty(exports, 'createFetchCookiecutterAction', {
2425
2709
  enumerable: true,
2426
2710
  get: function () {
@@ -2429,6 +2713,7 @@ Object.defineProperty(exports, 'createFetchCookiecutterAction', {
2429
2713
  });
2430
2714
  exports.CatalogEntityClient = CatalogEntityClient;
2431
2715
  exports.OctokitProvider = OctokitProvider;
2716
+ exports.ScaffolderEntitiesProcessor = ScaffolderEntitiesProcessor;
2432
2717
  exports.TemplateActionRegistry = TemplateActionRegistry;
2433
2718
  exports.createBuiltinActions = createBuiltinActions;
2434
2719
  exports.createCatalogRegisterAction = createCatalogRegisterAction;