@backstage/plugin-scaffolder-backend 1.25.0 → 1.26.0-next.0

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.
@@ -32,6 +32,7 @@ var gitlab = require('@backstage/plugin-scaffolder-backend-module-gitlab');
32
32
  var pluginScaffolderBackendModuleGitea = require('@backstage/plugin-scaffolder-backend-module-gitea');
33
33
  var uuid = require('uuid');
34
34
  var alpha = require('@backstage/plugin-scaffolder-node/alpha');
35
+ var os = require('os');
35
36
  var ObservableImpl = require('zen-observable');
36
37
  var lodash = require('lodash');
37
38
  var PQueue = require('p-queue');
@@ -45,7 +46,6 @@ var pluginPermissionCommon = require('@backstage/plugin-permission-common');
45
46
  var Transport = require('winston-transport');
46
47
  var tripleBeam = require('triple-beam');
47
48
  var url = require('url');
48
- var os = require('os');
49
49
 
50
50
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
51
51
 
@@ -75,14 +75,14 @@ var path__default = /*#__PURE__*/_interopDefaultCompat(path);
75
75
  var fs__default$1 = /*#__PURE__*/_interopDefaultCompat(fs$1);
76
76
  var globby__default = /*#__PURE__*/_interopDefaultCompat(globby);
77
77
  var get__default = /*#__PURE__*/_interopDefaultCompat(get);
78
+ var os__default = /*#__PURE__*/_interopDefaultCompat(os);
78
79
  var ObservableImpl__default = /*#__PURE__*/_interopDefaultCompat(ObservableImpl);
79
80
  var PQueue__default = /*#__PURE__*/_interopDefaultCompat(PQueue);
80
81
  var winston__namespace = /*#__PURE__*/_interopNamespaceCompat(winston);
81
82
  var nunjucks__default = /*#__PURE__*/_interopDefaultCompat(nunjucks);
82
83
  var Transport__default = /*#__PURE__*/_interopDefaultCompat(Transport);
83
- var os__default = /*#__PURE__*/_interopDefaultCompat(os);
84
84
 
85
- const examples$9 = [
85
+ const examples$a = [
86
86
  {
87
87
  description: "Register with the catalog",
88
88
  example: yaml__namespace.default.stringify({
@@ -106,7 +106,7 @@ function createCatalogRegisterAction(options) {
106
106
  return pluginScaffolderNode.createTemplateAction({
107
107
  id: id$4,
108
108
  description: "Registers entities from a catalog descriptor file in the workspace into the software catalog.",
109
- examples: examples$9,
109
+ examples: examples$a,
110
110
  schema: {
111
111
  input: {
112
112
  oneOf: [
@@ -233,7 +233,7 @@ function createCatalogRegisterAction(options) {
233
233
  });
234
234
  }
235
235
 
236
- const examples$8 = [
236
+ const examples$9 = [
237
237
  {
238
238
  description: "Write a catalog yaml file",
239
239
  example: yaml__namespace.stringify({
@@ -277,7 +277,7 @@ function createCatalogWriteAction() {
277
277
  )
278
278
  })
279
279
  },
280
- examples: examples$8,
280
+ examples: examples$9,
281
281
  supportsDryRun: true,
282
282
  async handler(ctx) {
283
283
  const { filePath, entity } = ctx.input;
@@ -303,7 +303,7 @@ function createCatalogWriteAction() {
303
303
  });
304
304
  }
305
305
 
306
- const examples$7 = [
306
+ const examples$8 = [
307
307
  {
308
308
  description: "Fetch entity by reference",
309
309
  example: yaml__namespace.default.stringify({
@@ -342,7 +342,7 @@ function createFetchCatalogEntityAction(options) {
342
342
  return pluginScaffolderNode.createTemplateAction({
343
343
  id: id$2,
344
344
  description: "Returns entity or entities from the catalog by entity reference(s)",
345
- examples: examples$7,
345
+ examples: examples$8,
346
346
  supportsDryRun: true,
347
347
  schema: {
348
348
  input: zod.z.object({
@@ -420,7 +420,7 @@ function createFetchCatalogEntityAction(options) {
420
420
  });
421
421
  }
422
422
 
423
- const examples$6 = [
423
+ const examples$7 = [
424
424
  {
425
425
  description: "Write a debug message",
426
426
  example: yaml__namespace.default.stringify({
@@ -473,7 +473,7 @@ function createDebugLogAction() {
473
473
  return pluginScaffolderNode.createTemplateAction({
474
474
  id: id$1,
475
475
  description: "Writes a message into the log and/or lists all files in the workspace.",
476
- examples: examples$6,
476
+ examples: examples$7,
477
477
  schema: {
478
478
  input: zod.z.object({
479
479
  message: zod.z.string({ description: "Message to output." }).optional(),
@@ -518,7 +518,7 @@ async function recursiveReadDir(dir) {
518
518
  return files.reduce((a, f) => a.concat(f), []);
519
519
  }
520
520
 
521
- const examples$5 = [
521
+ const examples$6 = [
522
522
  {
523
523
  description: "Waiting for 50 milliseconds",
524
524
  example: yaml__namespace.default.stringify({
@@ -581,7 +581,7 @@ function createWaitAction(options) {
581
581
  return pluginScaffolderNode.createTemplateAction({
582
582
  id,
583
583
  description: "Waits for a certain period of time.",
584
- examples: examples$5,
584
+ examples: examples$6,
585
585
  schema: {
586
586
  input: {
587
587
  type: "object",
@@ -624,7 +624,7 @@ function createWaitAction(options) {
624
624
  });
625
625
  }
626
626
 
627
- const examples$4 = [
627
+ const examples$5 = [
628
628
  {
629
629
  description: "Downloads content and places it in the workspace.",
630
630
  example: yaml__namespace.default.stringify({
@@ -663,7 +663,7 @@ function createFetchPlainAction(options) {
663
663
  const { reader, integrations } = options;
664
664
  return pluginScaffolderNode.createTemplateAction({
665
665
  id: ACTION_ID,
666
- examples: examples$4,
666
+ examples: examples$5,
667
667
  description: "Downloads content and places it in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.",
668
668
  schema: {
669
669
  input: {
@@ -705,7 +705,7 @@ function createFetchPlainAction(options) {
705
705
  });
706
706
  }
707
707
 
708
- const examples$3 = [
708
+ const examples$4 = [
709
709
  {
710
710
  description: "Downloads a file and places it in the workspace.",
711
711
  example: yaml__namespace.default.stringify({
@@ -729,7 +729,7 @@ function createFetchPlainFileAction(options) {
729
729
  return pluginScaffolderNode.createTemplateAction({
730
730
  id: "fetch:plain:file",
731
731
  description: "Downloads single file and places it in the workspace.",
732
- examples: examples$3,
732
+ examples: examples$4,
733
733
  schema: {
734
734
  input: {
735
735
  type: "object",
@@ -950,7 +950,7 @@ const createDefaultFilters = ({
950
950
  };
951
951
  };
952
952
 
953
- const examples$2 = [
953
+ const examples$3 = [
954
954
  {
955
955
  description: "Downloads a skeleton directory that lives alongside the template file and fill it out with values.",
956
956
  example: yaml__namespace.default.stringify({
@@ -986,7 +986,7 @@ function createFetchTemplateAction(options) {
986
986
  return pluginScaffolderNode.createTemplateAction({
987
987
  id: "fetch:template",
988
988
  description: "Downloads a skeleton, templates variables into file and directory names and content, and places the result in the workspace, or optionally in a subdirectory specified by the `targetPath` input option.",
989
- examples: examples$2,
989
+ examples: examples$3,
990
990
  schema: {
991
991
  input: {
992
992
  type: "object",
@@ -1196,6 +1196,132 @@ function containsSkippedContent(localOutputPath) {
1196
1196
  return localOutputPath === "" || localOutputPath.startsWith("/") || localOutputPath.includes("//");
1197
1197
  }
1198
1198
 
1199
+ const examples$2 = [
1200
+ {
1201
+ description: "Downloads a template file and fill it out with values.",
1202
+ example: yaml__namespace.default.stringify({
1203
+ steps: [
1204
+ {
1205
+ action: "fetch:template:file",
1206
+ id: "fetch-template-file",
1207
+ name: "Fetch template file",
1208
+ input: {
1209
+ url: "./skeleton.txt",
1210
+ targetPath: "./target/skeleton.txt",
1211
+ values: {
1212
+ name: "test-project",
1213
+ count: 1234,
1214
+ itemList: ["first", "second", "third"]
1215
+ }
1216
+ }
1217
+ }
1218
+ ]
1219
+ })
1220
+ }
1221
+ ];
1222
+
1223
+ function createFetchTemplateFileAction(options) {
1224
+ const {
1225
+ reader,
1226
+ integrations,
1227
+ additionalTemplateFilters,
1228
+ additionalTemplateGlobals
1229
+ } = options;
1230
+ const defaultTemplateFilters = createDefaultFilters({ integrations });
1231
+ return pluginScaffolderNode.createTemplateAction({
1232
+ id: "fetch:template:file",
1233
+ description: "Downloads single file and places it in the workspace.",
1234
+ examples: examples$2,
1235
+ schema: {
1236
+ input: {
1237
+ type: "object",
1238
+ required: ["url", "targetPath"],
1239
+ properties: {
1240
+ url: {
1241
+ title: "Fetch URL",
1242
+ description: "Relative path or absolute URL pointing to the single file to fetch.",
1243
+ type: "string"
1244
+ },
1245
+ targetPath: {
1246
+ title: "Target Path",
1247
+ description: "Target path within the working directory to download the file as.",
1248
+ type: "string"
1249
+ },
1250
+ values: {
1251
+ title: "Template Values",
1252
+ description: "Values to pass on to the templating engine",
1253
+ type: "object"
1254
+ },
1255
+ cookiecutterCompat: {
1256
+ title: "Cookiecutter compatibility mode",
1257
+ description: "Enable features to maximise compatibility with templates built for fetch:cookiecutter",
1258
+ type: "boolean"
1259
+ },
1260
+ replace: {
1261
+ title: "Replace file",
1262
+ description: "If set, replace file in targetPath instead of overwriting existing one.",
1263
+ type: "boolean"
1264
+ },
1265
+ token: {
1266
+ title: "Token",
1267
+ description: "An optional token to use for authentication when reading the resources.",
1268
+ type: "string"
1269
+ }
1270
+ }
1271
+ }
1272
+ },
1273
+ supportsDryRun: true,
1274
+ async handler(ctx) {
1275
+ ctx.logger.info("Fetching template file content from remote URL");
1276
+ const workDir = await ctx.createTemporaryDirectory();
1277
+ const tmpFilePath = path__default.default.join(workDir, "tmp");
1278
+ const outputPath = backendPluginApi.resolveSafeChildPath(
1279
+ ctx.workspacePath,
1280
+ ctx.input.targetPath
1281
+ );
1282
+ if (fs__default.default.existsSync(outputPath) && !ctx.input.replace) {
1283
+ ctx.logger.info(
1284
+ `File ${ctx.input.targetPath} already exists in workspace, not replacing.`
1285
+ );
1286
+ return;
1287
+ }
1288
+ await pluginScaffolderNode.fetchFile({
1289
+ reader,
1290
+ integrations,
1291
+ baseUrl: ctx.templateInfo?.baseUrl,
1292
+ fetchUrl: ctx.input.url,
1293
+ outputPath: tmpFilePath,
1294
+ token: ctx.input.token
1295
+ });
1296
+ const { cookiecutterCompat, values } = ctx.input;
1297
+ const context = {
1298
+ [cookiecutterCompat ? "cookiecutter" : "values"]: values
1299
+ };
1300
+ ctx.logger.info(
1301
+ `Processing template file with input values`,
1302
+ ctx.input.values
1303
+ );
1304
+ const renderTemplate = await SecureTemplater.loadRenderer({
1305
+ cookiecutterCompat,
1306
+ templateFilters: {
1307
+ ...defaultTemplateFilters,
1308
+ ...additionalTemplateFilters
1309
+ },
1310
+ templateGlobals: additionalTemplateGlobals,
1311
+ nunjucksConfigs: {
1312
+ trimBlocks: ctx.input.trimBlocks,
1313
+ lstripBlocks: ctx.input.lstripBlocks
1314
+ }
1315
+ });
1316
+ const contents = await fs__default.default.readFile(tmpFilePath, "utf-8");
1317
+ const result = renderTemplate(contents, context);
1318
+ await fs__default.default.ensureDir(path__default.default.dirname(outputPath));
1319
+ await fs__default.default.outputFile(outputPath, result);
1320
+ ctx.logger.info(`Template file has been written to ${outputPath}`);
1321
+ }
1322
+ });
1323
+ }
1324
+
1199
1325
  const examples$1 = [
1200
1326
  {
1201
1327
  description: "Delete specified files",
@@ -1371,6 +1497,12 @@ const createBuiltinActions = (options) => {
1371
1497
  additionalTemplateFilters,
1372
1498
  additionalTemplateGlobals
1373
1499
  }),
1500
+ createFetchTemplateFileAction({
1501
+ integrations,
1502
+ reader,
1503
+ additionalTemplateFilters,
1504
+ additionalTemplateGlobals
1505
+ }),
1374
1506
  gerrit.createPublishGerritAction({
1375
1507
  integrations,
1376
1508
  config
@@ -1524,6 +1656,79 @@ const intervalFromNowTill = (timeoutS, knex) => {
1524
1656
  return heartbeatInterval;
1525
1657
  };
1526
1658
 
1659
+ async function getWorkingDirectory(config, logger) {
1660
+ if (!config.has("backend.workingDirectory")) {
1661
+ return os__default.default.tmpdir();
1662
+ }
1663
+ const workingDirectory = config.getString("backend.workingDirectory");
1664
+ try {
1665
+ await fs__default.default.access(workingDirectory, fs__default.default.constants.F_OK | fs__default.default.constants.W_OK);
1666
+ logger.info(`using working directory: ${workingDirectory}`);
1667
+ } catch (err) {
1668
+ errors.assertError(err);
1669
+ logger.error(
1670
+ `working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`
1671
+ );
1672
+ throw err;
1673
+ }
1674
+ return workingDirectory;
1675
+ }
1676
+ function getEntityBaseUrl(entity) {
1677
+ let location = entity.metadata.annotations?.[catalogModel.ANNOTATION_SOURCE_LOCATION];
1678
+ if (!location) {
1679
+ location = entity.metadata.annotations?.[catalogModel.ANNOTATION_LOCATION];
1680
+ }
1681
+ if (!location) {
1682
+ return void 0;
1683
+ }
1684
+ const { type, target } = catalogModel.parseLocationRef(location);
1685
+ if (type === "url") {
1686
+ return target;
1687
+ } else if (type === "file") {
1688
+ return `file://${target}`;
1689
+ }
1690
+ return void 0;
1691
+ }
1692
+ async function findTemplate(options) {
1693
+ const { entityRef, token, catalogApi } = options;
1694
+ if (entityRef.kind.toLocaleLowerCase("en-US") !== "template") {
1695
+ throw new errors.InputError(`Invalid kind, only 'Template' kind is supported`);
1696
+ }
1697
+ const template = await catalogApi.getEntityByRef(entityRef, { token });
1698
+ if (!template) {
1699
+ throw new errors.NotFoundError(
1700
+ `Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
1701
+ );
1702
+ }
1703
+ return template;
1704
+ }
1705
+ function parseStringsParam(param, paramName) {
1706
+ if (param === void 0) {
1707
+ return void 0;
1708
+ }
1709
+ const array = [param].flat();
1710
+ if (array.some((p) => typeof p !== "string")) {
1711
+ throw new errors.InputError(
1712
+ `Invalid ${paramName}, not a string or array of strings`
1713
+ );
1714
+ }
1715
+ return array;
1716
+ }
1717
+ function parseNumberParam(param, paramName) {
1718
+ return parseStringsParam(param, paramName)?.map((val) => {
1719
+ const ret = Number.parseInt(val, 10);
1720
+ if (isNaN(ret)) {
1721
+ throw new errors.InputError(
1722
+ `Invalid ${paramName} parameter "${val}", expected a number or array of numbers`
1723
+ );
1724
+ }
1725
+ return ret;
1726
+ });
1727
+ }
1728
+ function flattenParams(...params) {
1729
+ return [...params].flat().filter(Boolean);
1730
+ }
1731
+
1527
1732
  const migrationsDir = backendPluginApi.resolvePackagePath(
1528
1733
  "@backstage/plugin-scaffolder-backend",
1529
1734
  "migrations"
@@ -1595,16 +1800,41 @@ class DatabaseTaskStore {
1595
1800
  this.db = client;
1596
1801
  }
1597
1802
  async list(options) {
1803
+ const { createdBy, status, pagination, order, filters } = options ?? {};
1598
1804
  const queryBuilder = this.db("tasks");
1599
- if (options.createdBy) {
1600
- queryBuilder.where({
1601
- created_by: options.createdBy
1805
+ if (createdBy || filters?.createdBy) {
1806
+ const arr = flattenParams(
1807
+ createdBy,
1808
+ filters?.createdBy
1809
+ );
1810
+ queryBuilder.whereIn("created_by", [...new Set(arr)]);
1811
+ }
1812
+ if (status || filters?.status) {
1813
+ const arr = flattenParams(
1814
+ status,
1815
+ filters?.status
1816
+ );
1817
+ queryBuilder.whereIn("status", [...new Set(arr)]);
1818
+ }
1819
+ if (order) {
1820
+ order.forEach((f) => {
1821
+ queryBuilder.orderBy(f.field, f.order);
1602
1822
  });
1823
+ } else {
1824
+ queryBuilder.orderBy("created_at", "desc");
1825
+ }
1826
+ const countQuery = queryBuilder.clone();
1827
+ countQuery.count("tasks.id", { as: "count" });
1828
+ if (pagination?.limit !== void 0) {
1829
+ queryBuilder.limit(pagination.limit);
1603
1830
  }
1604
- if (options.status) {
1605
- queryBuilder.where({ status: options.status });
1831
+ if (pagination?.offset !== void 0) {
1832
+ queryBuilder.offset(pagination.offset);
1606
1833
  }
1607
- const results = await queryBuilder.orderBy("created_at", "desc").select();
1834
+ const [results, [{ count }]] = await Promise.all([
1835
+ queryBuilder.select(),
1836
+ countQuery
1837
+ ]);
1608
1838
  const tasks = results.map((result) => ({
1609
1839
  id: result.id,
1610
1840
  spec: JSON.parse(result.spec),
@@ -1613,7 +1843,7 @@ class DatabaseTaskStore {
1613
1843
  lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at),
1614
1844
  createdAt: parseSqlDateToIsoString(result.created_at)
1615
1845
  }));
1616
- return { tasks };
1846
+ return { tasks, totalTasks: count };
1617
1847
  }
1618
1848
  async getTask(taskId) {
1619
1849
  const [result] = await this.db("tasks").where({ id: taskId }).select();
@@ -1782,7 +2012,7 @@ class DatabaseTaskStore {
1782
2012
  }
1783
2013
  }
1784
2014
  async listEvents(options) {
1785
- const { taskId, after } = options;
2015
+ const { isTaskRecoverable, taskId, after } = options;
1786
2016
  const rawEvents = await this.db("task_events").where({
1787
2017
  task_id: taskId
1788
2018
  }).andWhere((builder) => {
@@ -1795,6 +2025,7 @@ class DatabaseTaskStore {
1795
2025
  const body = JSON.parse(event.body);
1796
2026
  return {
1797
2027
  id: Number(event.id),
2028
+ isTaskRecoverable,
1798
2029
  taskId,
1799
2030
  body,
1800
2031
  type: event.event_type,
@@ -1865,6 +2096,28 @@ class DatabaseTaskStore {
1865
2096
  body: serializedBody
1866
2097
  });
1867
2098
  }
2099
+ async retryTask(options) {
2100
+ await this.db.transaction(async (tx) => {
2101
+ const result = await tx("tasks").where("id", options.taskId).update(
2102
+ {
2103
+ status: "open",
2104
+ last_heartbeat_at: this.db.fn.now()
2105
+ },
2106
+ ["id", "spec"]
2107
+ );
2108
+ for (const { id, spec } of result) {
2109
+ const taskSpec = JSON.parse(spec);
2110
+ await tx("task_events").where("task_id", id).andWhere((q) => q.whereIn("event_type", ["cancelled", "completion"])).del();
2111
+ await tx("task_events").insert({
2112
+ task_id: id,
2113
+ event_type: "recovered",
2114
+ body: JSON.stringify({
2115
+ recoverStrategy: taskSpec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy ?? "none"
2116
+ })
2117
+ });
2118
+ }
2119
+ });
2120
+ }
1868
2121
  async recoverTasks(options) {
1869
2122
  const taskIdsToRecover = [];
1870
2123
  const timeoutS = luxon.Duration.fromObject(options.timeout).as("seconds");
@@ -1976,6 +2229,7 @@ class DefaultWorkspaceService {
1976
2229
  }
1977
2230
  async rehydrateWorkspace(options) {
1978
2231
  if (this.isWorkspaceSerializationEnabled()) {
2232
+ await fs__default.default.mkdirp(options.targetPath);
1979
2233
  await this.workspaceProvider.rehydrateWorkspace(options);
1980
2234
  }
1981
2235
  }
@@ -2127,10 +2381,7 @@ class StorageTaskBroker {
2127
2381
  "TaskStore does not implement the list method. Please implement the list method to be able to list tasks"
2128
2382
  );
2129
2383
  }
2130
- return await this.storage.list({
2131
- createdBy: options?.createdBy,
2132
- status: options?.status
2133
- });
2384
+ return await this.storage.list(options ?? {});
2134
2385
  }
2135
2386
  deferredDispatch = defer();
2136
2387
  async registerCancellable(taskId, abortController) {
@@ -2145,7 +2396,7 @@ class StorageTaskBroker {
2145
2396
  abortController.abort();
2146
2397
  shouldUnsubscribe = true;
2147
2398
  }
2148
- if (event.type === "completion") {
2399
+ if (event.type === "completion" && !event.isTaskRecoverable) {
2149
2400
  shouldUnsubscribe = true;
2150
2401
  }
2151
2402
  }
@@ -2227,8 +2478,14 @@ class StorageTaskBroker {
2227
2478
  let after = options.after;
2228
2479
  let cancelled = false;
2229
2480
  (async () => {
2481
+ const task = await this.storage.getTask(taskId);
2482
+ const isTaskRecoverable = task.spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy === "startOver";
2230
2483
  while (!cancelled) {
2231
- const result = await this.storage.listEvents({ taskId, after });
2484
+ const result = await this.storage.listEvents({
2485
+ isTaskRecoverable,
2486
+ taskId,
2487
+ after
2488
+ });
2232
2489
  const { events } = result;
2233
2490
  if (events.length) {
2234
2491
  after = events[events.length - 1].id;
@@ -2282,6 +2539,10 @@ class StorageTaskBroker {
2282
2539
  }
2283
2540
  });
2284
2541
  }
2542
+ async retry(taskId) {
2543
+ await this.storage.retryTask?.({ taskId });
2544
+ this.signalDispatch();
2545
+ }
2285
2546
  }
2286
2547
 
2287
2548
  function createCounterMetric(config) {
@@ -3220,53 +3481,6 @@ function createDryRunner(options) {
3220
3481
  };
3221
3482
  }
3222
3483
 
3223
- async function getWorkingDirectory(config, logger) {
3224
- if (!config.has("backend.workingDirectory")) {
3225
- return os__default.default.tmpdir();
3226
- }
3227
- const workingDirectory = config.getString("backend.workingDirectory");
3228
- try {
3229
- await fs__default.default.access(workingDirectory, fs__default.default.constants.F_OK | fs__default.default.constants.W_OK);
3230
- logger.info(`using working directory: ${workingDirectory}`);
3231
- } catch (err) {
3232
- errors.assertError(err);
3233
- logger.error(
3234
- `working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`
3235
- );
3236
- throw err;
3237
- }
3238
- return workingDirectory;
3239
- }
3240
- function getEntityBaseUrl(entity) {
3241
- let location = entity.metadata.annotations?.[catalogModel.ANNOTATION_SOURCE_LOCATION];
3242
- if (!location) {
3243
- location = entity.metadata.annotations?.[catalogModel.ANNOTATION_LOCATION];
3244
- }
3245
- if (!location) {
3246
- return void 0;
3247
- }
3248
- const { type, target } = catalogModel.parseLocationRef(location);
3249
- if (type === "url") {
3250
- return target;
3251
- } else if (type === "file") {
3252
- return `file://${target}`;
3253
- }
3254
- return void 0;
3255
- }
3256
- async function findTemplate(options) {
3257
- const { entityRef, token, catalogApi } = options;
3258
- if (entityRef.kind.toLocaleLowerCase("en-US") !== "template") {
3259
- throw new errors.InputError(`Invalid kind, only 'Template' kind is supported`);
3260
- }
3261
- const template = await catalogApi.getEntityByRef(entityRef, { token });
3262
- if (!template) {
3263
- throw new errors.NotFoundError(
3264
- `Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
3265
- );
3266
- }
3267
- return template;
3268
- }
3269
-
3270
3484
  async function checkPermission(options) {
3271
3485
  const { permissions, permissionService, credentials } = options;
3272
3486
  if (permissionService) {
@@ -3601,22 +3815,37 @@ async function createRouter(options) {
3601
3815
  permissions: [alpha$1.taskReadPermission],
3602
3816
  permissionService: permissions
3603
3817
  });
3604
- const [userEntityRef] = [req.query.createdBy].flat();
3605
- if (typeof userEntityRef !== "string" && typeof userEntityRef !== "undefined") {
3606
- throw new errors.InputError("createdBy query parameter must be a string");
3607
- }
3608
3818
  if (!taskBroker.list) {
3609
3819
  throw new Error(
3610
3820
  "TaskBroker does not support listing tasks, please implement the list method on the TaskBroker."
3611
3821
  );
3612
3822
  }
3613
- const [statusQuery] = [req.query.status].flat();
3614
- if (typeof statusQuery !== "string" && typeof statusQuery !== "undefined") {
3615
- throw new errors.InputError("status query parameter must be a string");
3616
- }
3823
+ const createdBy = parseStringsParam(req.query.createdBy, "createdBy");
3824
+ const status = parseStringsParam(req.query.status, "status");
3825
+ const order = parseStringsParam(req.query.order, "order")?.map((item) => {
3826
+ const match = item.match(/^(asc|desc):(.+)$/);
3827
+ if (!match) {
3828
+ throw new errors.InputError(
3829
+ `Invalid order parameter "${item}", expected "<asc or desc>:<field name>"`
3830
+ );
3831
+ }
3832
+ return {
3833
+ order: match[1],
3834
+ field: match[2]
3835
+ };
3836
+ });
3837
+ const limit = parseNumberParam(req.query.limit, "limit");
3838
+ const offset = parseNumberParam(req.query.offset, "offset");
3617
3839
  const tasks = await taskBroker.list({
3618
- createdBy: userEntityRef,
3619
- status: statusQuery ? statusQuery : void 0
3840
+ filters: {
3841
+ createdBy,
3842
+ status: status ? status : void 0
3843
+ },
3844
+ order,
3845
+ pagination: {
3846
+ limit: limit ? limit[0] : void 0,
3847
+ offset: offset ? offset[0] : void 0
3848
+ }
3620
3849
  });
3621
3850
  res.status(200).json(tasks);
3622
3851
  }).get("/v2/tasks/:taskId", async (req, res) => {
@@ -3643,6 +3872,16 @@ async function createRouter(options) {
3643
3872
  const { taskId } = req.params;
3644
3873
  await taskBroker.cancel?.(taskId);
3645
3874
  res.status(200).json({ status: "cancelled" });
3875
+ }).post("/v2/tasks/:taskId/retry", async (req, res) => {
3876
+ const credentials = await httpAuth.credentials(req);
3877
+ await checkPermission({
3878
+ credentials,
3879
+ permissions: [alpha$1.taskCreatePermission, alpha$1.taskReadPermission],
3880
+ permissionService: permissions
3881
+ });
3882
+ const { taskId } = req.params;
3883
+ await taskBroker.retry?.(taskId);
3884
+ res.status(201).json({ id: taskId });
3646
3885
  }).get("/v2/tasks/:taskId/eventstream", async (req, res) => {
3647
3886
  const credentials = await httpAuth.credentials(req);
3648
3887
  await checkPermission({
@@ -3674,7 +3913,7 @@ data: ${JSON.stringify(event)}
3674
3913
 
3675
3914
  `
3676
3915
  );
3677
- if (event.type === "completion") {
3916
+ if (event.type === "completion" && !event.isTaskRecoverable) {
3678
3917
  shouldUnsubscribe = true;
3679
3918
  }
3680
3919
  }
@@ -3852,10 +4091,11 @@ exports.createFetchCatalogEntityAction = createFetchCatalogEntityAction;
3852
4091
  exports.createFetchPlainAction = createFetchPlainAction;
3853
4092
  exports.createFetchPlainFileAction = createFetchPlainFileAction;
3854
4093
  exports.createFetchTemplateAction = createFetchTemplateAction;
4094
+ exports.createFetchTemplateFileAction = createFetchTemplateFileAction;
3855
4095
  exports.createFilesystemDeleteAction = createFilesystemDeleteAction;
3856
4096
  exports.createFilesystemRenameAction = createFilesystemRenameAction;
3857
4097
  exports.createRouter = createRouter;
3858
4098
  exports.createWaitAction = createWaitAction;
3859
4099
  exports.scaffolderActionRules = scaffolderActionRules;
3860
4100
  exports.scaffolderTemplateRules = scaffolderTemplateRules;
3861
- //# sourceMappingURL=router-L9rspgmo.cjs.js.map
4101
+ //# sourceMappingURL=router-CC-UhVkG.cjs.js.map