@backstage/plugin-scaffolder-backend 1.3.0-next.0 → 1.3.0-next.1

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,22 @@
1
1
  # @backstage/plugin-scaffolder-backend
2
2
 
3
+ ## 1.3.0-next.1
4
+
5
+ ### Minor Changes
6
+
7
+ - c042c5eaff: Add an option to not protect the default branch.
8
+
9
+ ### Patch Changes
10
+
11
+ - 8f7b1835df: Updated dependency `msw` to `^0.41.0`.
12
+ - Updated dependencies
13
+ - @backstage/backend-common@0.13.6-next.1
14
+ - @backstage/catalog-client@1.0.3-next.0
15
+ - @backstage/integration@1.2.1-next.1
16
+ - @backstage/plugin-catalog-backend@1.2.0-next.1
17
+ - @backstage/catalog-model@1.0.3-next.0
18
+ - @backstage/plugin-scaffolder-common@1.1.1-next.0
19
+
3
20
  ## 1.3.0-next.0
4
21
 
5
22
  ### Minor Changes
package/dist/index.cjs.js CHANGED
@@ -18,8 +18,8 @@ var azureDevopsNodeApi = require('azure-devops-node-api');
18
18
  var fetch = require('node-fetch');
19
19
  var crypto = require('crypto');
20
20
  var octokit = require('octokit');
21
- var lodash = require('lodash');
22
21
  var octokitPluginCreatePullRequest = require('octokit-plugin-create-pull-request');
22
+ var limiterFactory = require('p-limit');
23
23
  var node = require('@gitbeaker/node');
24
24
  var webhooks = require('@octokit/webhooks');
25
25
  var uuid = require('uuid');
@@ -27,12 +27,15 @@ var luxon = require('luxon');
27
27
  var ObservableImpl = require('zen-observable');
28
28
  var winston = require('winston');
29
29
  var nunjucks = require('nunjucks');
30
+ var lodash = require('lodash');
30
31
  var jsonschema = require('jsonschema');
32
+ var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
31
33
  var express = require('express');
32
34
  var Router = require('express-promise-router');
35
+ var zod = require('zod');
36
+ var url = require('url');
33
37
  var os = require('os');
34
38
  var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
35
- var pluginScaffolderCommon = require('@backstage/plugin-scaffolder-common');
36
39
 
37
40
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
38
41
 
@@ -60,6 +63,7 @@ var path__default = /*#__PURE__*/_interopDefaultLegacy(path);
60
63
  var globby__default = /*#__PURE__*/_interopDefaultLegacy(globby);
61
64
  var fetch__default = /*#__PURE__*/_interopDefaultLegacy(fetch);
62
65
  var crypto__default = /*#__PURE__*/_interopDefaultLegacy(crypto);
66
+ var limiterFactory__default = /*#__PURE__*/_interopDefaultLegacy(limiterFactory);
63
67
  var ObservableImpl__default = /*#__PURE__*/_interopDefaultLegacy(ObservableImpl);
64
68
  var winston__namespace = /*#__PURE__*/_interopNamespace(winston);
65
69
  var nunjucks__default = /*#__PURE__*/_interopDefaultLegacy(nunjucks);
@@ -117,6 +121,18 @@ function createCatalogRegisterAction(options) {
117
121
  }
118
122
  }
119
123
  ]
124
+ },
125
+ output: {
126
+ type: "object",
127
+ required: ["catalogInfoUrl"],
128
+ properties: {
129
+ entityRef: {
130
+ type: "string"
131
+ },
132
+ catalogInfoUrl: {
133
+ type: "string"
134
+ }
135
+ }
120
136
  }
121
137
  },
122
138
  async handler(ctx) {
@@ -190,6 +206,7 @@ function createCatalogWriteAction() {
190
206
  }
191
207
  }
192
208
  },
209
+ supportsDryRun: true,
193
210
  async handler(ctx) {
194
211
  ctx.logStream.write(`Writing catalog-info.yaml`);
195
212
  const { filePath, entity } = ctx.input;
@@ -221,6 +238,7 @@ function createDebugLogAction() {
221
238
  }
222
239
  }
223
240
  },
241
+ supportsDryRun: true,
224
242
  async handler(ctx) {
225
243
  var _a, _b;
226
244
  ctx.logger.info(JSON.stringify(ctx.input, null, 2));
@@ -306,6 +324,7 @@ function createFetchPlainAction(options) {
306
324
  }
307
325
  }
308
326
  },
327
+ supportsDryRun: true,
309
328
  async handler(ctx) {
310
329
  var _a, _b;
311
330
  ctx.logger.info("Fetching plain content from remote URL");
@@ -472,6 +491,7 @@ function createFetchTemplateAction(options) {
472
491
  }
473
492
  }
474
493
  },
494
+ supportsDryRun: true,
475
495
  async handler(ctx) {
476
496
  var _a, _b;
477
497
  ctx.logger.info("Fetching template content from remote URL");
@@ -504,13 +524,15 @@ function createFetchTemplateAction(options) {
504
524
  cwd: templateDir,
505
525
  dot: true,
506
526
  onlyFiles: false,
507
- markDirectories: true
527
+ markDirectories: true,
528
+ followSymbolicLinks: false
508
529
  });
509
530
  const nonTemplatedEntries = new Set((await Promise.all((ctx.input.copyWithoutRender || []).map((pattern) => globby__default["default"](pattern, {
510
531
  cwd: templateDir,
511
532
  dot: true,
512
533
  onlyFiles: false,
513
- markDirectories: true
534
+ markDirectories: true,
535
+ followSymbolicLinks: false
514
536
  })))).flat());
515
537
  const { cookiecutterCompat, values } = ctx.input;
516
538
  const context = {
@@ -585,6 +607,7 @@ const createFilesystemDeleteAction = () => {
585
607
  }
586
608
  }
587
609
  },
610
+ supportsDryRun: true,
588
611
  async handler(ctx) {
589
612
  var _a;
590
613
  if (!Array.isArray((_a = ctx.input) == null ? void 0 : _a.files)) {
@@ -639,6 +662,7 @@ const createFilesystemRenameAction = () => {
639
662
  }
640
663
  }
641
664
  },
665
+ supportsDryRun: true,
642
666
  async handler(ctx) {
643
667
  var _a, _b;
644
668
  if (!Array.isArray((_a = ctx.input) == null ? void 0 : _a.files)) {
@@ -827,11 +851,6 @@ const parseRepoUrl = (repoUrl, integrations) => {
827
851
  }
828
852
  return { host, owner, repo, organization, workspace, project };
829
853
  };
830
- const isExecutable = (fileMode) => {
831
- const executeBitMask = 73;
832
- const res = fileMode & executeBitMask;
833
- return res > 0;
834
- };
835
854
 
836
855
  function createPublishAzureAction(options) {
837
856
  const { integrations, config } = options;
@@ -1796,6 +1815,11 @@ function createPublishGithubAction(options) {
1796
1815
  type: "string",
1797
1816
  description: `Sets the default branch on the repository. The default value is 'master'`
1798
1817
  },
1818
+ protectDefaultBranch: {
1819
+ title: "Protect Default Branch",
1820
+ type: "boolean",
1821
+ description: `Protect the default branch after creating the repository. The default value is 'true'`
1822
+ },
1799
1823
  deleteBranchOnMerge: {
1800
1824
  title: "Delete Branch On Merge",
1801
1825
  type: "boolean",
@@ -1893,6 +1917,7 @@ function createPublishGithubAction(options) {
1893
1917
  requiredStatusCheckContexts = [],
1894
1918
  repoVisibility = "private",
1895
1919
  defaultBranch = "master",
1920
+ protectDefaultBranch = true,
1896
1921
  deleteBranchOnMerge = false,
1897
1922
  gitCommitMessage = "initial commit",
1898
1923
  gitAuthorName,
@@ -2013,19 +2038,21 @@ function createPublishGithubAction(options) {
2013
2038
  commitMessage: gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage"),
2014
2039
  gitAuthorInfo
2015
2040
  });
2016
- try {
2017
- await enableBranchProtectionOnDefaultRepoBranch({
2018
- owner,
2019
- client,
2020
- repoName: newRepo.name,
2021
- logger: ctx.logger,
2022
- defaultBranch,
2023
- requireCodeOwnerReviews,
2024
- requiredStatusCheckContexts
2025
- });
2026
- } catch (e) {
2027
- errors.assertError(e);
2028
- ctx.logger.warn(`Skipping: default branch protection on '${newRepo.name}', ${e.message}`);
2041
+ if (protectDefaultBranch) {
2042
+ try {
2043
+ await enableBranchProtectionOnDefaultRepoBranch({
2044
+ owner,
2045
+ client,
2046
+ repoName: newRepo.name,
2047
+ logger: ctx.logger,
2048
+ defaultBranch,
2049
+ requireCodeOwnerReviews,
2050
+ requiredStatusCheckContexts
2051
+ });
2052
+ } catch (e) {
2053
+ errors.assertError(e);
2054
+ ctx.logger.warn(`Skipping: default branch protection on '${newRepo.name}', ${e.message}`);
2055
+ }
2029
2056
  }
2030
2057
  ctx.output("remoteUrl", remoteUrl);
2031
2058
  ctx.output("repoContentsUrl", repoContentsUrl);
@@ -2033,6 +2060,41 @@ function createPublishGithubAction(options) {
2033
2060
  });
2034
2061
  }
2035
2062
 
2063
+ const DEFAULT_GLOB_PATTERNS = ["./**", "!.git"];
2064
+ const isExecutable = (fileMode) => {
2065
+ if (!fileMode) {
2066
+ return false;
2067
+ }
2068
+ const executeBitMask = 73;
2069
+ const res = fileMode & executeBitMask;
2070
+ return res > 0;
2071
+ };
2072
+ async function serializeDirectoryContents(sourcePath, options) {
2073
+ var _a;
2074
+ const paths = await globby__default["default"]((_a = options == null ? void 0 : options.globPatterns) != null ? _a : DEFAULT_GLOB_PATTERNS, {
2075
+ cwd: sourcePath,
2076
+ dot: true,
2077
+ gitignore: options == null ? void 0 : options.gitignore,
2078
+ followSymbolicLinks: false,
2079
+ objectMode: true,
2080
+ stats: true
2081
+ });
2082
+ const limiter = limiterFactory__default["default"](10);
2083
+ return Promise.all(paths.map(async ({ path: path$1, stats }) => ({
2084
+ path: path$1,
2085
+ content: await limiter(async () => fs__default["default"].readFile(path.join(sourcePath, path$1))),
2086
+ executable: isExecutable(stats == null ? void 0 : stats.mode)
2087
+ })));
2088
+ }
2089
+
2090
+ async function deserializeDirectoryContents(targetPath, files) {
2091
+ for (const file of files) {
2092
+ const filePath = backendCommon.resolveSafeChildPath(targetPath, file.path);
2093
+ await fs__default["default"].ensureDir(path.dirname(filePath));
2094
+ await fs__default["default"].writeFile(filePath, file.content);
2095
+ }
2096
+ }
2097
+
2036
2098
  class GithubResponseError extends errors.CustomErrorBase {
2037
2099
  }
2038
2100
  const defaultClientFactory = async ({
@@ -2148,38 +2210,28 @@ const createPublishGithubPullRequestAction = ({
2148
2210
  token: providedToken
2149
2211
  });
2150
2212
  const fileRoot = sourcePath ? backendCommon.resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath;
2151
- const localFilePaths = await globby__default["default"](["./**", "./**/.*", "!.git"], {
2152
- cwd: fileRoot,
2153
- gitignore: true,
2154
- dot: true
2213
+ const directoryContents = await serializeDirectoryContents(fileRoot, {
2214
+ gitignore: true
2155
2215
  });
2156
- const fileContents = await Promise.all(localFilePaths.map((filePath) => {
2157
- const absPath = backendCommon.resolveSafeChildPath(fileRoot, filePath);
2158
- const base64EncodedContent = fs__default["default"].readFileSync(absPath).toString("base64");
2159
- const fileStat = fs__default["default"].statSync(absPath);
2160
- const githubTreeItemMode = isExecutable(fileStat.mode) ? "100755" : "100644";
2161
- const encoding = "base64";
2162
- return {
2163
- encoding,
2164
- content: base64EncodedContent,
2165
- mode: githubTreeItemMode
2166
- };
2167
- }));
2168
- const repoFilePaths = localFilePaths.map((repoFilePath) => {
2169
- return targetPath ? `${targetPath}/${repoFilePath}` : repoFilePath;
2170
- });
2171
- const changes = [
2216
+ const files = Object.fromEntries(directoryContents.map((file) => [
2217
+ targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path,
2172
2218
  {
2173
- files: lodash.zipObject(repoFilePaths, fileContents),
2174
- commit: title
2219
+ mode: file.executable ? "100755" : "100644",
2220
+ encoding: "base64",
2221
+ content: file.content.toString("base64")
2175
2222
  }
2176
- ];
2223
+ ]));
2177
2224
  try {
2178
2225
  const response = await client.createPullRequest({
2179
2226
  owner,
2180
2227
  repo,
2181
2228
  title,
2182
- changes,
2229
+ changes: [
2230
+ {
2231
+ files,
2232
+ commit: title
2233
+ }
2234
+ ],
2183
2235
  body: description,
2184
2236
  head: branchName,
2185
2237
  draft
@@ -2387,7 +2439,6 @@ const createPublishGitlabMergeRequestAction = (options) => {
2387
2439
  const repoUrl = ctx.input.repoUrl;
2388
2440
  const { host } = parseRepoUrl(repoUrl, integrations);
2389
2441
  const integrationConfig = integrations.gitlab.byHost(host);
2390
- const actions = [];
2391
2442
  const destinationBranch = ctx.input.branchName;
2392
2443
  if (!integrationConfig) {
2393
2444
  throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
@@ -2401,23 +2452,17 @@ const createPublishGitlabMergeRequestAction = (options) => {
2401
2452
  host: integrationConfig.config.baseUrl,
2402
2453
  [tokenType]: token
2403
2454
  });
2404
- const fileRoot = ctx.workspacePath;
2405
- const localFilePaths = await globby__default["default"]([`${ctx.input.targetPath}/**`], {
2406
- cwd: fileRoot,
2407
- gitignore: true,
2408
- dot: true
2409
- });
2410
- const fileContents = await Promise.all(localFilePaths.map((p) => fs.readFile(backendCommon.resolveSafeChildPath(fileRoot, p))));
2411
- const repoFilePaths = localFilePaths.map((repoFilePath) => {
2412
- return repoFilePath;
2455
+ const targetPath = backendCommon.resolveSafeChildPath(ctx.workspacePath, ctx.input.targetPath);
2456
+ const fileContents = await serializeDirectoryContents(targetPath, {
2457
+ gitignore: true
2413
2458
  });
2414
- for (let i = 0; i < repoFilePaths.length; i++) {
2415
- actions.push({
2416
- action: "create",
2417
- filePath: repoFilePaths[i],
2418
- content: fileContents[i].toString()
2419
- });
2420
- }
2459
+ const actions = fileContents.map((file) => ({
2460
+ action: "create",
2461
+ filePath: path__default["default"].posix.join(ctx.input.targetPath, file.path),
2462
+ encoding: "base64",
2463
+ content: file.content.toString("base64"),
2464
+ execute_filemode: file.executable
2465
+ }));
2421
2466
  const projects = await api.Projects.show(ctx.input.projectid);
2422
2467
  const { default_branch: defaultBranch } = projects;
2423
2468
  try {
@@ -3106,6 +3151,32 @@ class StorageTaskBroker {
3106
3151
  function isTruthy(value) {
3107
3152
  return lodash.isArray(value) ? value.length > 0 : !!value;
3108
3153
  }
3154
+ function generateExampleOutput(schema) {
3155
+ var _a, _b;
3156
+ const { examples } = schema;
3157
+ if (examples && Array.isArray(examples)) {
3158
+ return examples[0];
3159
+ }
3160
+ if (schema.type === "object") {
3161
+ return Object.fromEntries(Object.entries((_a = schema.properties) != null ? _a : {}).map(([key, value]) => [
3162
+ key,
3163
+ generateExampleOutput(value)
3164
+ ]));
3165
+ } else if (schema.type === "array") {
3166
+ const [firstSchema] = (_b = [schema.items]) == null ? void 0 : _b.flat();
3167
+ if (firstSchema) {
3168
+ return [generateExampleOutput(firstSchema)];
3169
+ }
3170
+ return [];
3171
+ } else if (schema.type === "string") {
3172
+ return "<example>";
3173
+ } else if (schema.type === "number") {
3174
+ return 0;
3175
+ } else if (schema.type === "boolean") {
3176
+ return false;
3177
+ }
3178
+ return "<unknown>";
3179
+ }
3109
3180
 
3110
3181
  const isValidTaskSpec = (taskSpec) => {
3111
3182
  return taskSpec.apiVersion === "scaffolder.backstage.io/v1beta3";
@@ -3175,7 +3246,7 @@ class NunjucksWorkflowRunner {
3175
3246
  });
3176
3247
  }
3177
3248
  async execute(task) {
3178
- var _a, _b, _c, _d;
3249
+ var _a, _b, _c, _d, _e;
3179
3250
  if (!isValidTaskSpec(task.spec)) {
3180
3251
  throw new errors.InputError("Wrong template version executed with the workflow engine");
3181
3252
  }
@@ -3210,8 +3281,23 @@ class NunjucksWorkflowRunner {
3210
3281
  });
3211
3282
  const action = this.options.actionRegistry.get(step.action);
3212
3283
  const { taskLogger, streamLogger } = createStepLogger({ task, step });
3213
- const input = (_b = step.input && this.render(step.input, { ...context, secrets: (_a = task.secrets) != null ? _a : {} }, renderTemplate)) != null ? _b : {};
3214
- if ((_c = action.schema) == null ? void 0 : _c.input) {
3284
+ if (task.isDryRun && !action.supportsDryRun) {
3285
+ task.emitLog(`Skipping because ${action.id} does not support dry-run`, {
3286
+ stepId: step.id,
3287
+ status: "skipped"
3288
+ });
3289
+ const outputSchema = (_a = action.schema) == null ? void 0 : _a.output;
3290
+ if (outputSchema) {
3291
+ context.steps[step.id] = {
3292
+ output: generateExampleOutput(outputSchema)
3293
+ };
3294
+ } else {
3295
+ context.steps[step.id] = { output: {} };
3296
+ }
3297
+ continue;
3298
+ }
3299
+ const input = (_c = step.input && this.render(step.input, { ...context, secrets: (_b = task.secrets) != null ? _b : {} }, renderTemplate)) != null ? _c : {};
3300
+ if ((_d = action.schema) == null ? void 0 : _d.input) {
3215
3301
  const validateResult = jsonschema.validate(input, action.schema.input);
3216
3302
  if (!validateResult.valid) {
3217
3303
  const errors$1 = validateResult.errors.join(", ");
@@ -3222,7 +3308,7 @@ class NunjucksWorkflowRunner {
3222
3308
  const stepOutput = {};
3223
3309
  await action.handler({
3224
3310
  input,
3225
- secrets: (_d = task.secrets) != null ? _d : {},
3311
+ secrets: (_e = task.secrets) != null ? _e : {},
3226
3312
  logger: taskLogger,
3227
3313
  logStream: streamLogger,
3228
3314
  workspacePath,
@@ -3311,6 +3397,95 @@ class TaskWorker {
3311
3397
  }
3312
3398
  }
3313
3399
 
3400
+ class DecoratedActionsRegistry extends TemplateActionRegistry {
3401
+ constructor(innerRegistry, extraActions) {
3402
+ super();
3403
+ this.innerRegistry = innerRegistry;
3404
+ for (const action of extraActions) {
3405
+ this.register(action);
3406
+ }
3407
+ }
3408
+ get(actionId) {
3409
+ try {
3410
+ return super.get(actionId);
3411
+ } catch {
3412
+ return this.innerRegistry.get(actionId);
3413
+ }
3414
+ }
3415
+ }
3416
+
3417
+ function createDryRunner(options) {
3418
+ return async function dryRun(input) {
3419
+ let contentPromise;
3420
+ const workflowRunner = new NunjucksWorkflowRunner({
3421
+ ...options,
3422
+ actionRegistry: new DecoratedActionsRegistry(options.actionRegistry, [
3423
+ createTemplateAction({
3424
+ id: "dry-run:extract",
3425
+ supportsDryRun: true,
3426
+ async handler(ctx) {
3427
+ contentPromise = serializeDirectoryContents(ctx.workspacePath);
3428
+ await contentPromise.catch(() => {
3429
+ });
3430
+ }
3431
+ })
3432
+ ])
3433
+ });
3434
+ const dryRunId = uuid.v4();
3435
+ const log = new Array();
3436
+ const contentsPath = backendCommon.resolveSafeChildPath(options.workingDirectory, `dry-run-content-${dryRunId}`);
3437
+ try {
3438
+ await deserializeDirectoryContents(contentsPath, input.directoryContents);
3439
+ const result = await workflowRunner.execute({
3440
+ spec: {
3441
+ ...input.spec,
3442
+ steps: [
3443
+ ...input.spec.steps,
3444
+ {
3445
+ id: dryRunId,
3446
+ name: "dry-run:extract",
3447
+ action: "dry-run:extract"
3448
+ }
3449
+ ],
3450
+ templateInfo: {
3451
+ entityRef: "template:default/dry-run",
3452
+ baseUrl: url.pathToFileURL(backendCommon.resolveSafeChildPath(contentsPath, "template.yaml")).toString()
3453
+ }
3454
+ },
3455
+ secrets: input.secrets,
3456
+ done: false,
3457
+ isDryRun: true,
3458
+ getWorkspaceName: async () => `dry-run-${dryRunId}`,
3459
+ async emitLog(message, logMetadata) {
3460
+ if ((logMetadata == null ? void 0 : logMetadata.stepId) === dryRunId) {
3461
+ return;
3462
+ }
3463
+ log.push({
3464
+ body: {
3465
+ ...logMetadata,
3466
+ message
3467
+ }
3468
+ });
3469
+ },
3470
+ async complete() {
3471
+ throw new Error("Not implemented");
3472
+ }
3473
+ });
3474
+ if (!contentPromise) {
3475
+ throw new Error("Content extraction step was skipped");
3476
+ }
3477
+ const directoryContents = await contentPromise;
3478
+ return {
3479
+ log,
3480
+ directoryContents,
3481
+ output: result.output
3482
+ };
3483
+ } finally {
3484
+ await fs__default["default"].remove(contentsPath);
3485
+ }
3486
+ };
3487
+ }
3488
+
3314
3489
  async function getWorkingDirectory(config, logger) {
3315
3490
  if (!config.has("backend.workingDirectory")) {
3316
3491
  return os__default["default"].tmpdir();
@@ -3408,6 +3583,13 @@ async function createRouter(options) {
3408
3583
  });
3409
3584
  actionsToRegister.forEach((action) => actionRegistry.register(action));
3410
3585
  workers.forEach((worker) => worker.start());
3586
+ const dryRunner = createDryRunner({
3587
+ actionRegistry,
3588
+ integrations,
3589
+ logger,
3590
+ workingDirectory,
3591
+ additionalTemplateFilters
3592
+ });
3411
3593
  router.get("/v2/templates/:namespace/:kind/:name/parameter-schema", async (req, res) => {
3412
3594
  var _a, _b;
3413
3595
  const { namespace, kind, name } = req.params;
@@ -3562,6 +3744,62 @@ data: ${JSON.stringify(event)}
3562
3744
  subscription.unsubscribe();
3563
3745
  clearTimeout(timeout);
3564
3746
  });
3747
+ }).post("/v2/dry-run", async (req, res) => {
3748
+ var _a, _b, _c;
3749
+ const bodySchema = zod.z.object({
3750
+ template: zod.z.unknown(),
3751
+ values: zod.z.record(zod.z.unknown()),
3752
+ secrets: zod.z.record(zod.z.string()).optional(),
3753
+ directoryContents: zod.z.array(zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() }))
3754
+ });
3755
+ const body = await bodySchema.parseAsync(req.body).catch((e) => {
3756
+ throw new errors.InputError(`Malformed request: ${e}`);
3757
+ });
3758
+ const template = body.template;
3759
+ if (!await pluginScaffolderCommon.templateEntityV1beta3Validator.check(template)) {
3760
+ throw new errors.InputError("Input template is not a template");
3761
+ }
3762
+ const { token } = parseBearerToken(req.headers.authorization);
3763
+ for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
3764
+ const result2 = jsonschema.validate(body.values, parameters);
3765
+ if (!result2.valid) {
3766
+ res.status(400).json({ errors: result2.errors });
3767
+ return;
3768
+ }
3769
+ }
3770
+ const steps = template.spec.steps.map((step, index) => {
3771
+ var _a2, _b2;
3772
+ return {
3773
+ ...step,
3774
+ id: (_a2 = step.id) != null ? _a2 : `step-${index + 1}`,
3775
+ name: (_b2 = step.name) != null ? _b2 : step.action
3776
+ };
3777
+ });
3778
+ const result = await dryRunner({
3779
+ spec: {
3780
+ apiVersion: template.apiVersion,
3781
+ steps,
3782
+ output: (_b = template.spec.output) != null ? _b : {},
3783
+ parameters: body.values
3784
+ },
3785
+ directoryContents: ((_c = body.directoryContents) != null ? _c : []).map((file) => ({
3786
+ path: file.path,
3787
+ content: Buffer.from(file.base64Content, "base64")
3788
+ })),
3789
+ secrets: {
3790
+ ...body.secrets,
3791
+ ...token && { backstageToken: token }
3792
+ }
3793
+ });
3794
+ res.status(200).json({
3795
+ ...result,
3796
+ steps,
3797
+ directoryContents: result.directoryContents.map((file) => ({
3798
+ path: file.path,
3799
+ executable: file.executable,
3800
+ base64Content: file.content.toString("base64")
3801
+ }))
3802
+ });
3565
3803
  });
3566
3804
  const app = express__default["default"]();
3567
3805
  app.set("logger", logger);