@backstage/plugin-scaffolder-backend 1.25.0-next.2 → 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
@@ -1453,7 +1585,8 @@ const createBuiltinActions = (options) => {
1453
1585
  githubCredentialsProvider
1454
1586
  }),
1455
1587
  github.createGithubEnvironmentAction({
1456
- integrations
1588
+ integrations,
1589
+ catalogClient
1457
1590
  }),
1458
1591
  github.createGithubDeployKeyAction({
1459
1592
  integrations
@@ -1523,6 +1656,79 @@ const intervalFromNowTill = (timeoutS, knex) => {
1523
1656
  return heartbeatInterval;
1524
1657
  };
1525
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
+
1526
1732
  const migrationsDir = backendPluginApi.resolvePackagePath(
1527
1733
  "@backstage/plugin-scaffolder-backend",
1528
1734
  "migrations"
@@ -1594,16 +1800,41 @@ class DatabaseTaskStore {
1594
1800
  this.db = client;
1595
1801
  }
1596
1802
  async list(options) {
1803
+ const { createdBy, status, pagination, order, filters } = options ?? {};
1597
1804
  const queryBuilder = this.db("tasks");
1598
- if (options.createdBy) {
1599
- queryBuilder.where({
1600
- 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);
1601
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);
1602
1830
  }
1603
- if (options.status) {
1604
- queryBuilder.where({ status: options.status });
1831
+ if (pagination?.offset !== void 0) {
1832
+ queryBuilder.offset(pagination.offset);
1605
1833
  }
1606
- const results = await queryBuilder.orderBy("created_at", "desc").select();
1834
+ const [results, [{ count }]] = await Promise.all([
1835
+ queryBuilder.select(),
1836
+ countQuery
1837
+ ]);
1607
1838
  const tasks = results.map((result) => ({
1608
1839
  id: result.id,
1609
1840
  spec: JSON.parse(result.spec),
@@ -1612,7 +1843,7 @@ class DatabaseTaskStore {
1612
1843
  lastHeartbeatAt: parseSqlDateToIsoString(result.last_heartbeat_at),
1613
1844
  createdAt: parseSqlDateToIsoString(result.created_at)
1614
1845
  }));
1615
- return { tasks };
1846
+ return { tasks, totalTasks: count };
1616
1847
  }
1617
1848
  async getTask(taskId) {
1618
1849
  const [result] = await this.db("tasks").where({ id: taskId }).select();
@@ -1781,7 +2012,7 @@ class DatabaseTaskStore {
1781
2012
  }
1782
2013
  }
1783
2014
  async listEvents(options) {
1784
- const { taskId, after } = options;
2015
+ const { isTaskRecoverable, taskId, after } = options;
1785
2016
  const rawEvents = await this.db("task_events").where({
1786
2017
  task_id: taskId
1787
2018
  }).andWhere((builder) => {
@@ -1794,6 +2025,7 @@ class DatabaseTaskStore {
1794
2025
  const body = JSON.parse(event.body);
1795
2026
  return {
1796
2027
  id: Number(event.id),
2028
+ isTaskRecoverable,
1797
2029
  taskId,
1798
2030
  body,
1799
2031
  type: event.event_type,
@@ -1864,6 +2096,28 @@ class DatabaseTaskStore {
1864
2096
  body: serializedBody
1865
2097
  });
1866
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
+ }
1867
2121
  async recoverTasks(options) {
1868
2122
  const taskIdsToRecover = [];
1869
2123
  const timeoutS = luxon.Duration.fromObject(options.timeout).as("seconds");
@@ -1975,6 +2229,7 @@ class DefaultWorkspaceService {
1975
2229
  }
1976
2230
  async rehydrateWorkspace(options) {
1977
2231
  if (this.isWorkspaceSerializationEnabled()) {
2232
+ await fs__default.default.mkdirp(options.targetPath);
1978
2233
  await this.workspaceProvider.rehydrateWorkspace(options);
1979
2234
  }
1980
2235
  }
@@ -2126,10 +2381,7 @@ class StorageTaskBroker {
2126
2381
  "TaskStore does not implement the list method. Please implement the list method to be able to list tasks"
2127
2382
  );
2128
2383
  }
2129
- return await this.storage.list({
2130
- createdBy: options?.createdBy,
2131
- status: options?.status
2132
- });
2384
+ return await this.storage.list(options ?? {});
2133
2385
  }
2134
2386
  deferredDispatch = defer();
2135
2387
  async registerCancellable(taskId, abortController) {
@@ -2144,7 +2396,7 @@ class StorageTaskBroker {
2144
2396
  abortController.abort();
2145
2397
  shouldUnsubscribe = true;
2146
2398
  }
2147
- if (event.type === "completion") {
2399
+ if (event.type === "completion" && !event.isTaskRecoverable) {
2148
2400
  shouldUnsubscribe = true;
2149
2401
  }
2150
2402
  }
@@ -2226,8 +2478,14 @@ class StorageTaskBroker {
2226
2478
  let after = options.after;
2227
2479
  let cancelled = false;
2228
2480
  (async () => {
2481
+ const task = await this.storage.getTask(taskId);
2482
+ const isTaskRecoverable = task.spec.EXPERIMENTAL_recovery?.EXPERIMENTAL_strategy === "startOver";
2229
2483
  while (!cancelled) {
2230
- const result = await this.storage.listEvents({ taskId, after });
2484
+ const result = await this.storage.listEvents({
2485
+ isTaskRecoverable,
2486
+ taskId,
2487
+ after
2488
+ });
2231
2489
  const { events } = result;
2232
2490
  if (events.length) {
2233
2491
  after = events[events.length - 1].id;
@@ -2281,6 +2539,10 @@ class StorageTaskBroker {
2281
2539
  }
2282
2540
  });
2283
2541
  }
2542
+ async retry(taskId) {
2543
+ await this.storage.retryTask?.({ taskId });
2544
+ this.signalDispatch();
2545
+ }
2284
2546
  }
2285
2547
 
2286
2548
  function createCounterMetric(config) {
@@ -3219,53 +3481,6 @@ function createDryRunner(options) {
3219
3481
  };
3220
3482
  }
3221
3483
 
3222
- async function getWorkingDirectory(config, logger) {
3223
- if (!config.has("backend.workingDirectory")) {
3224
- return os__default.default.tmpdir();
3225
- }
3226
- const workingDirectory = config.getString("backend.workingDirectory");
3227
- try {
3228
- await fs__default.default.access(workingDirectory, fs__default.default.constants.F_OK | fs__default.default.constants.W_OK);
3229
- logger.info(`using working directory: ${workingDirectory}`);
3230
- } catch (err) {
3231
- errors.assertError(err);
3232
- logger.error(
3233
- `working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`
3234
- );
3235
- throw err;
3236
- }
3237
- return workingDirectory;
3238
- }
3239
- function getEntityBaseUrl(entity) {
3240
- let location = entity.metadata.annotations?.[catalogModel.ANNOTATION_SOURCE_LOCATION];
3241
- if (!location) {
3242
- location = entity.metadata.annotations?.[catalogModel.ANNOTATION_LOCATION];
3243
- }
3244
- if (!location) {
3245
- return void 0;
3246
- }
3247
- const { type, target } = catalogModel.parseLocationRef(location);
3248
- if (type === "url") {
3249
- return target;
3250
- } else if (type === "file") {
3251
- return `file://${target}`;
3252
- }
3253
- return void 0;
3254
- }
3255
- async function findTemplate(options) {
3256
- const { entityRef, token, catalogApi } = options;
3257
- if (entityRef.kind.toLocaleLowerCase("en-US") !== "template") {
3258
- throw new errors.InputError(`Invalid kind, only 'Template' kind is supported`);
3259
- }
3260
- const template = await catalogApi.getEntityByRef(entityRef, { token });
3261
- if (!template) {
3262
- throw new errors.NotFoundError(
3263
- `Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
3264
- );
3265
- }
3266
- return template;
3267
- }
3268
-
3269
3484
  async function checkPermission(options) {
3270
3485
  const { permissions, permissionService, credentials } = options;
3271
3486
  if (permissionService) {
@@ -3600,22 +3815,37 @@ async function createRouter(options) {
3600
3815
  permissions: [alpha$1.taskReadPermission],
3601
3816
  permissionService: permissions
3602
3817
  });
3603
- const [userEntityRef] = [req.query.createdBy].flat();
3604
- if (typeof userEntityRef !== "string" && typeof userEntityRef !== "undefined") {
3605
- throw new errors.InputError("createdBy query parameter must be a string");
3606
- }
3607
3818
  if (!taskBroker.list) {
3608
3819
  throw new Error(
3609
3820
  "TaskBroker does not support listing tasks, please implement the list method on the TaskBroker."
3610
3821
  );
3611
3822
  }
3612
- const [statusQuery] = [req.query.status].flat();
3613
- if (typeof statusQuery !== "string" && typeof statusQuery !== "undefined") {
3614
- throw new errors.InputError("status query parameter must be a string");
3615
- }
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");
3616
3839
  const tasks = await taskBroker.list({
3617
- createdBy: userEntityRef,
3618
- 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
+ }
3619
3849
  });
3620
3850
  res.status(200).json(tasks);
3621
3851
  }).get("/v2/tasks/:taskId", async (req, res) => {
@@ -3642,6 +3872,16 @@ async function createRouter(options) {
3642
3872
  const { taskId } = req.params;
3643
3873
  await taskBroker.cancel?.(taskId);
3644
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 });
3645
3885
  }).get("/v2/tasks/:taskId/eventstream", async (req, res) => {
3646
3886
  const credentials = await httpAuth.credentials(req);
3647
3887
  await checkPermission({
@@ -3673,7 +3913,7 @@ data: ${JSON.stringify(event)}
3673
3913
 
3674
3914
  `
3675
3915
  );
3676
- if (event.type === "completion") {
3916
+ if (event.type === "completion" && !event.isTaskRecoverable) {
3677
3917
  shouldUnsubscribe = true;
3678
3918
  }
3679
3919
  }
@@ -3851,10 +4091,11 @@ exports.createFetchCatalogEntityAction = createFetchCatalogEntityAction;
3851
4091
  exports.createFetchPlainAction = createFetchPlainAction;
3852
4092
  exports.createFetchPlainFileAction = createFetchPlainFileAction;
3853
4093
  exports.createFetchTemplateAction = createFetchTemplateAction;
4094
+ exports.createFetchTemplateFileAction = createFetchTemplateFileAction;
3854
4095
  exports.createFilesystemDeleteAction = createFilesystemDeleteAction;
3855
4096
  exports.createFilesystemRenameAction = createFilesystemRenameAction;
3856
4097
  exports.createRouter = createRouter;
3857
4098
  exports.createWaitAction = createWaitAction;
3858
4099
  exports.scaffolderActionRules = scaffolderActionRules;
3859
4100
  exports.scaffolderTemplateRules = scaffolderTemplateRules;
3860
- //# sourceMappingURL=router-B1lzg7oz.cjs.js.map
4101
+ //# sourceMappingURL=router-CC-UhVkG.cjs.js.map