@backstage/plugin-scaffolder-backend 1.4.0-next.3 → 1.5.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/dist/index.cjs.js CHANGED
@@ -147,7 +147,9 @@ function createCatalogRegisterAction(options) {
147
147
  const { repoContentsUrl, catalogInfoPath = "/catalog-info.yaml" } = input;
148
148
  const integration = integrations.byUrl(repoContentsUrl);
149
149
  if (!integration) {
150
- throw new errors.InputError(`No integration found for host ${repoContentsUrl}`);
150
+ throw new errors.InputError(
151
+ `No integration found for host ${repoContentsUrl}`
152
+ );
151
153
  }
152
154
  catalogInfoUrl = integration.resolveUrl({
153
155
  base: repoContentsUrl,
@@ -155,22 +157,32 @@ function createCatalogRegisterAction(options) {
155
157
  });
156
158
  }
157
159
  ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
158
- await catalogClient.addLocation({
159
- type: "url",
160
- target: catalogInfoUrl
161
- }, ((_a = ctx.secrets) == null ? void 0 : _a.backstageToken) ? { token: ctx.secrets.backstageToken } : {});
162
- try {
163
- const result = await catalogClient.addLocation({
164
- dryRun: true,
160
+ await catalogClient.addLocation(
161
+ {
165
162
  type: "url",
166
163
  target: catalogInfoUrl
167
- }, ((_b = ctx.secrets) == null ? void 0 : _b.backstageToken) ? { token: ctx.secrets.backstageToken } : {});
164
+ },
165
+ ((_a = ctx.secrets) == null ? void 0 : _a.backstageToken) ? { token: ctx.secrets.backstageToken } : {}
166
+ );
167
+ try {
168
+ const result = await catalogClient.addLocation(
169
+ {
170
+ dryRun: true,
171
+ type: "url",
172
+ target: catalogInfoUrl
173
+ },
174
+ ((_b = ctx.secrets) == null ? void 0 : _b.backstageToken) ? { token: ctx.secrets.backstageToken } : {}
175
+ );
168
176
  if (result.entities.length > 0) {
169
177
  const { entities } = result;
170
178
  let entity;
171
- entity = entities.find((e) => !e.metadata.name.startsWith("generated-") && e.kind === "Component");
179
+ entity = entities.find(
180
+ (e) => !e.metadata.name.startsWith("generated-") && e.kind === "Component"
181
+ );
172
182
  if (!entity) {
173
- entity = entities.find((e) => !e.metadata.name.startsWith("generated-"));
183
+ entity = entities.find(
184
+ (e) => !e.metadata.name.startsWith("generated-")
185
+ );
174
186
  }
175
187
  if (!entity) {
176
188
  entity = entities[0];
@@ -213,7 +225,10 @@ function createCatalogWriteAction() {
213
225
  ctx.logStream.write(`Writing catalog-info.yaml`);
214
226
  const { filePath, entity } = ctx.input;
215
227
  const path = filePath != null ? filePath : "catalog-info.yaml";
216
- await fs__default["default"].writeFile(backendCommon.resolveSafeChildPath(ctx.workspacePath, path), yaml__namespace.stringify(entity));
228
+ await fs__default["default"].writeFile(
229
+ backendCommon.resolveSafeChildPath(ctx.workspacePath, path),
230
+ yaml__namespace.stringify(entity)
231
+ );
217
232
  }
218
233
  });
219
234
  }
@@ -249,18 +264,22 @@ function createDebugLogAction() {
249
264
  }
250
265
  if ((_b = ctx.input) == null ? void 0 : _b.listWorkspace) {
251
266
  const files = await recursiveReadDir(ctx.workspacePath);
252
- ctx.logStream.write(`Workspace:
253
- ${files.map((f) => ` - ${path.relative(ctx.workspacePath, f)}`).join("\n")}`);
267
+ ctx.logStream.write(
268
+ `Workspace:
269
+ ${files.map((f) => ` - ${path.relative(ctx.workspacePath, f)}`).join("\n")}`
270
+ );
254
271
  }
255
272
  }
256
273
  });
257
274
  }
258
275
  async function recursiveReadDir(dir) {
259
276
  const subdirs = await fs.readdir(dir);
260
- const files = await Promise.all(subdirs.map(async (subdir) => {
261
- const res = path.join(dir, subdir);
262
- return (await fs.stat(res)).isDirectory() ? recursiveReadDir(res) : [res];
263
- }));
277
+ const files = await Promise.all(
278
+ subdirs.map(async (subdir) => {
279
+ const res = path.join(dir, subdir);
280
+ return (await fs.stat(res)).isDirectory() ? recursiveReadDir(res) : [res];
281
+ })
282
+ );
264
283
  return files.reduce((a, f) => a.concat(f), []);
265
284
  }
266
285
 
@@ -295,7 +314,9 @@ async function fetchContents({
295
314
  base: baseUrl
296
315
  });
297
316
  } else {
298
- throw new errors.InputError(`Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`);
317
+ throw new errors.InputError(
318
+ `Failed to fetch, template location could not be determined and the fetch URL is relative, ${fetchUrl}`
319
+ );
299
320
  }
300
321
  const res = await reader.readTree(readUrl);
301
322
  await fs__default["default"].ensureDir(outputPath);
@@ -424,13 +445,21 @@ class SecureTemplater {
424
445
  sandbox.parseRepoUrl = (url) => JSON.stringify(parseRepoUrl(url));
425
446
  }
426
447
  if (additionalTemplateFilters) {
427
- sandbox.additionalTemplateFilters = Object.fromEntries(Object.entries(additionalTemplateFilters).filter(([_, filterFunction]) => !!filterFunction).map(([filterName, filterFunction]) => [
428
- filterName,
429
- (...args) => JSON.stringify(filterFunction(...args))
430
- ]));
448
+ sandbox.additionalTemplateFilters = Object.fromEntries(
449
+ Object.entries(additionalTemplateFilters).filter(([_, filterFunction]) => !!filterFunction).map(([filterName, filterFunction]) => [
450
+ filterName,
451
+ (...args) => JSON.stringify(filterFunction(...args))
452
+ ])
453
+ );
431
454
  }
432
455
  const vm = new vm2.VM({ sandbox });
433
- const nunjucksSource = await fs__default["default"].readFile(backendCommon.resolvePackagePath("@backstage/plugin-scaffolder-backend", "assets/nunjucks.js.txt"), "utf-8");
456
+ const nunjucksSource = await fs__default["default"].readFile(
457
+ backendCommon.resolvePackagePath(
458
+ "@backstage/plugin-scaffolder-backend",
459
+ "assets/nunjucks.js.txt"
460
+ ),
461
+ "utf-8"
462
+ );
434
463
  vm.run(mkScript(nunjucksSource));
435
464
  const render = (template, values) => {
436
465
  if (!vm) {
@@ -473,13 +502,21 @@ function createFetchTemplateAction(options) {
473
502
  type: "object"
474
503
  },
475
504
  copyWithoutRender: {
476
- title: "Copy Without Render",
505
+ title: "[Deprecated] Copy Without Render",
477
506
  description: "An array of glob patterns. Any files or directories which match are copied without being processed as templates.",
478
507
  type: "array",
479
508
  items: {
480
509
  type: "string"
481
510
  }
482
511
  },
512
+ copyWithoutTemplating: {
513
+ title: "Copy Without Templating",
514
+ description: "An array of glob patterns. Contents of matched files or directories are copied without being processed, but paths are subject to rendering.",
515
+ type: "array",
516
+ items: {
517
+ type: "string"
518
+ }
519
+ },
483
520
  cookiecutterCompat: {
484
521
  title: "Cookiecutter compatibility mode",
485
522
  description: "Enable features to maximise compatibility with templates built for fetch:cookiecutter",
@@ -501,11 +538,32 @@ function createFetchTemplateAction(options) {
501
538
  const templateDir = backendCommon.resolveSafeChildPath(workDir, "template");
502
539
  const targetPath = (_a = ctx.input.targetPath) != null ? _a : "./";
503
540
  const outputDir = backendCommon.resolveSafeChildPath(ctx.workspacePath, targetPath);
504
- if (ctx.input.copyWithoutRender && !Array.isArray(ctx.input.copyWithoutRender)) {
505
- throw new errors.InputError("Fetch action input copyWithoutRender must be an Array");
541
+ if (ctx.input.copyWithoutRender && ctx.input.copyWithoutTemplating) {
542
+ throw new errors.InputError(
543
+ "Fetch action input copyWithoutRender and copyWithoutTemplating can not be used at the same time"
544
+ );
506
545
  }
507
- if (ctx.input.templateFileExtension && (ctx.input.copyWithoutRender || ctx.input.cookiecutterCompat)) {
508
- throw new errors.InputError("Fetch action input extension incompatible with copyWithoutRender and cookiecutterCompat");
546
+ let copyOnlyPatterns;
547
+ let renderFilename;
548
+ if (ctx.input.copyWithoutRender) {
549
+ ctx.logger.warn(
550
+ "[Deprecated] Please use copyWithoutTemplating instead."
551
+ );
552
+ copyOnlyPatterns = ctx.input.copyWithoutRender;
553
+ renderFilename = false;
554
+ } else {
555
+ copyOnlyPatterns = ctx.input.copyWithoutTemplating;
556
+ renderFilename = true;
557
+ }
558
+ if (copyOnlyPatterns && !Array.isArray(copyOnlyPatterns)) {
559
+ throw new errors.InputError(
560
+ "Fetch action input copyWithoutRender/copyWithoutTemplating must be an Array"
561
+ );
562
+ }
563
+ if (ctx.input.templateFileExtension && (copyOnlyPatterns || ctx.input.cookiecutterCompat)) {
564
+ throw new errors.InputError(
565
+ "Fetch action input extension incompatible with copyWithoutRender/copyWithoutTemplating and cookiecutterCompat"
566
+ );
509
567
  }
510
568
  let extension = false;
511
569
  if (ctx.input.templateFileExtension) {
@@ -529,59 +587,84 @@ function createFetchTemplateAction(options) {
529
587
  markDirectories: true,
530
588
  followSymbolicLinks: false
531
589
  });
532
- const nonTemplatedEntries = new Set((await Promise.all((ctx.input.copyWithoutRender || []).map((pattern) => globby__default["default"](pattern, {
533
- cwd: templateDir,
534
- dot: true,
535
- onlyFiles: false,
536
- markDirectories: true,
537
- followSymbolicLinks: false
538
- })))).flat());
590
+ const nonTemplatedEntries = new Set(
591
+ (await Promise.all(
592
+ (copyOnlyPatterns || []).map(
593
+ (pattern) => globby__default["default"](pattern, {
594
+ cwd: templateDir,
595
+ dot: true,
596
+ onlyFiles: false,
597
+ markDirectories: true,
598
+ followSymbolicLinks: false
599
+ })
600
+ )
601
+ )).flat()
602
+ );
539
603
  const { cookiecutterCompat, values } = ctx.input;
540
604
  const context = {
541
605
  [cookiecutterCompat ? "cookiecutter" : "values"]: values
542
606
  };
543
- ctx.logger.info(`Processing ${allEntriesInTemplate.length} template files/directories with input values`, ctx.input.values);
607
+ ctx.logger.info(
608
+ `Processing ${allEntriesInTemplate.length} template files/directories with input values`,
609
+ ctx.input.values
610
+ );
544
611
  const renderTemplate = await SecureTemplater.loadRenderer({
545
612
  cookiecutterCompat: ctx.input.cookiecutterCompat,
546
613
  additionalTemplateFilters
547
614
  });
548
615
  for (const location of allEntriesInTemplate) {
549
- let renderFilename;
550
616
  let renderContents;
551
617
  let localOutputPath = location;
552
618
  if (extension) {
553
- renderFilename = true;
554
619
  renderContents = path.extname(localOutputPath) === extension;
555
620
  if (renderContents) {
556
621
  localOutputPath = localOutputPath.slice(0, -extension.length);
557
622
  }
623
+ localOutputPath = renderTemplate(localOutputPath, context);
558
624
  } else {
559
- renderFilename = renderContents = !nonTemplatedEntries.has(location);
625
+ renderContents = !nonTemplatedEntries.has(location);
626
+ if (renderFilename) {
627
+ localOutputPath = renderTemplate(localOutputPath, context);
628
+ } else {
629
+ localOutputPath = renderContents ? renderTemplate(localOutputPath, context) : localOutputPath;
630
+ }
560
631
  }
561
- if (renderFilename) {
562
- localOutputPath = renderTemplate(localOutputPath, context);
632
+ if (containsSkippedContent(localOutputPath)) {
633
+ continue;
563
634
  }
564
635
  const outputPath = backendCommon.resolveSafeChildPath(outputDir, localOutputPath);
565
- if (outputDir === outputPath) {
636
+ if (fs__default["default"].existsSync(outputPath)) {
566
637
  continue;
567
638
  }
568
639
  if (!renderContents && !extension) {
569
- ctx.logger.info(`Copying file/directory ${location} without processing.`);
640
+ ctx.logger.info(
641
+ `Copying file/directory ${location} without processing.`
642
+ );
570
643
  }
571
644
  if (location.endsWith("/")) {
572
- ctx.logger.info(`Writing directory ${location} to template output path.`);
645
+ ctx.logger.info(
646
+ `Writing directory ${location} to template output path.`
647
+ );
573
648
  await fs__default["default"].ensureDir(outputPath);
574
649
  } else {
575
650
  const inputFilePath = backendCommon.resolveSafeChildPath(templateDir, location);
576
651
  const stats = await fs__default["default"].promises.lstat(inputFilePath);
577
652
  if (stats.isSymbolicLink() || await isbinaryfile.isBinaryFile(inputFilePath)) {
578
- ctx.logger.info(`Copying file binary or symbolic link at ${location}, to template output path.`);
653
+ ctx.logger.info(
654
+ `Copying file binary or symbolic link at ${location}, to template output path.`
655
+ );
579
656
  await fs__default["default"].copy(inputFilePath, outputPath);
580
657
  } else {
581
658
  const statsObj = await fs__default["default"].stat(inputFilePath);
582
- ctx.logger.info(`Writing file ${location} to template output path with mode ${statsObj.mode}.`);
659
+ ctx.logger.info(
660
+ `Writing file ${location} to template output path with mode ${statsObj.mode}.`
661
+ );
583
662
  const inputFileContents = await fs__default["default"].readFile(inputFilePath, "utf-8");
584
- await fs__default["default"].outputFile(outputPath, renderContents ? renderTemplate(inputFileContents, context) : inputFileContents, { mode: statsObj.mode });
663
+ await fs__default["default"].outputFile(
664
+ outputPath,
665
+ renderContents ? renderTemplate(inputFileContents, context) : inputFileContents,
666
+ { mode: statsObj.mode }
667
+ );
585
668
  }
586
669
  }
587
670
  }
@@ -589,6 +672,9 @@ function createFetchTemplateAction(options) {
589
672
  }
590
673
  });
591
674
  }
675
+ function containsSkippedContent(localOutputPath) {
676
+ return localOutputPath === "" || path__default["default"].isAbsolute(localOutputPath) || localOutputPath.includes(`${path__default["default"].sep}${path__default["default"].sep}`);
677
+ }
592
678
 
593
679
  const createFilesystemDeleteAction = () => {
594
680
  return createTemplateAction({
@@ -675,15 +761,23 @@ const createFilesystemRenameAction = () => {
675
761
  if (!file.from || !file.to) {
676
762
  throw new errors.InputError("each file must have a from and to property");
677
763
  }
678
- const sourceFilepath = backendCommon.resolveSafeChildPath(ctx.workspacePath, file.from);
764
+ const sourceFilepath = backendCommon.resolveSafeChildPath(
765
+ ctx.workspacePath,
766
+ file.from
767
+ );
679
768
  const destFilepath = backendCommon.resolveSafeChildPath(ctx.workspacePath, file.to);
680
769
  try {
681
770
  await fs__default["default"].move(sourceFilepath, destFilepath, {
682
771
  overwrite: (_b = file.overwrite) != null ? _b : false
683
772
  });
684
- ctx.logger.info(`File ${sourceFilepath} renamed to ${destFilepath} successfully`);
773
+ ctx.logger.info(
774
+ `File ${sourceFilepath} renamed to ${destFilepath} successfully`
775
+ );
685
776
  } catch (err) {
686
- ctx.logger.error(`Failed to rename file ${sourceFilepath} to ${destFilepath}:`, err);
777
+ ctx.logger.error(
778
+ `Failed to rename file ${sourceFilepath} to ${destFilepath}:`,
779
+ err
780
+ );
687
781
  throw err;
688
782
  }
689
783
  }
@@ -693,7 +787,10 @@ const createFilesystemRenameAction = () => {
693
787
 
694
788
  const getRepoSourceDirectory = (workspacePath, sourcePath) => {
695
789
  if (sourcePath) {
696
- const safeSuffix = path.normalize(sourcePath).replace(/^(\.\.(\/|\\|$))+/, "");
790
+ const safeSuffix = path.normalize(sourcePath).replace(
791
+ /^(\.\.(\/|\\|$))+/,
792
+ ""
793
+ );
697
794
  const path$1 = path.join(workspacePath, safeSuffix);
698
795
  if (!backendCommon.isChildPath(workspacePath, path$1)) {
699
796
  throw new Error("Invalid source path");
@@ -708,7 +805,9 @@ const parseRepoUrl = (repoUrl, integrations) => {
708
805
  try {
709
806
  parsed = new URL(`https://${repoUrl}`);
710
807
  } catch (error) {
711
- throw new errors.InputError(`Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`);
808
+ throw new errors.InputError(
809
+ `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`
810
+ );
712
811
  }
713
812
  const host = parsed.host;
714
813
  const owner = (_a = parsed.searchParams.get("owner")) != null ? _a : void 0;
@@ -717,25 +816,35 @@ const parseRepoUrl = (repoUrl, integrations) => {
717
816
  const project = (_d = parsed.searchParams.get("project")) != null ? _d : void 0;
718
817
  const type = (_e = integrations.byHost(host)) == null ? void 0 : _e.type;
719
818
  if (!type) {
720
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
819
+ throw new errors.InputError(
820
+ `No matching integration configuration for host ${host}, please check your integrations config`
821
+ );
721
822
  }
722
823
  if (type === "bitbucket") {
723
824
  if (host === "bitbucket.org") {
724
825
  if (!workspace) {
725
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`);
826
+ throw new errors.InputError(
827
+ `Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`
828
+ );
726
829
  }
727
830
  }
728
831
  if (!project) {
729
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing project`);
832
+ throw new errors.InputError(
833
+ `Invalid repo URL passed to publisher: ${repoUrl}, missing project`
834
+ );
730
835
  }
731
836
  } else {
732
- if (!owner) {
733
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing owner`);
837
+ if (!owner && type !== "gerrit") {
838
+ throw new errors.InputError(
839
+ `Invalid repo URL passed to publisher: ${repoUrl}, missing owner`
840
+ );
734
841
  }
735
842
  }
736
843
  const repo = parsed.searchParams.get("repo");
737
844
  if (!repo) {
738
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing repo`);
845
+ throw new errors.InputError(
846
+ `Invalid repo URL passed to publisher: ${repoUrl}, missing repo`
847
+ );
739
848
  }
740
849
  return { host, owner, repo, organization, workspace, project };
741
850
  };
@@ -760,7 +869,9 @@ const executeShellCommand = async (options) => {
760
869
  });
761
870
  process.on("close", (code) => {
762
871
  if (code !== 0) {
763
- return reject(new Error(`Command ${command} failed, exit code: ${code}`));
872
+ return reject(
873
+ new Error(`Command ${command} failed, exit code: ${code}`)
874
+ );
764
875
  }
765
876
  return resolve();
766
877
  });
@@ -777,8 +888,7 @@ async function initRepoAndPush({
777
888
  }) {
778
889
  var _a, _b;
779
890
  const git = backendCommon.Git.fromAuth({
780
- username: auth.username,
781
- password: auth.password,
891
+ ...auth,
782
892
  logger
783
893
  });
784
894
  await git.init({
@@ -806,6 +916,39 @@ async function initRepoAndPush({
806
916
  remote: "origin"
807
917
  });
808
918
  }
919
+ async function commitAndPushRepo({
920
+ dir,
921
+ auth,
922
+ logger,
923
+ commitMessage,
924
+ gitAuthorInfo,
925
+ branch = "master",
926
+ remoteRef
927
+ }) {
928
+ var _a, _b;
929
+ const git = backendCommon.Git.fromAuth({
930
+ ...auth,
931
+ logger
932
+ });
933
+ await git.fetch({ dir });
934
+ await git.checkout({ dir, ref: branch });
935
+ await git.add({ dir, filepath: "." });
936
+ const authorInfo = {
937
+ name: (_a = gitAuthorInfo == null ? void 0 : gitAuthorInfo.name) != null ? _a : "Scaffolder",
938
+ email: (_b = gitAuthorInfo == null ? void 0 : gitAuthorInfo.email) != null ? _b : "scaffolder@backstage.io"
939
+ };
940
+ await git.commit({
941
+ dir,
942
+ message: commitMessage,
943
+ author: authorInfo,
944
+ committer: authorInfo
945
+ });
946
+ await git.push({
947
+ dir,
948
+ remote: "origin",
949
+ remoteRef: remoteRef != null ? remoteRef : `refs/heads/${branch}`
950
+ });
951
+ }
809
952
  const enableBranchProtectionOnDefaultRepoBranch = async ({
810
953
  repoName,
811
954
  client,
@@ -813,7 +956,8 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
813
956
  logger,
814
957
  requireCodeOwnerReviews,
815
958
  requiredStatusCheckContexts = [],
816
- defaultBranch = "master"
959
+ defaultBranch = "master",
960
+ enforceAdmins = true
817
961
  }) => {
818
962
  const tryOnce = async () => {
819
963
  try {
@@ -829,7 +973,7 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
829
973
  contexts: requiredStatusCheckContexts
830
974
  },
831
975
  restrictions: null,
832
- enforce_admins: true,
976
+ enforce_admins: enforceAdmins,
833
977
  required_pull_request_reviews: {
834
978
  required_approving_review_count: 1,
835
979
  require_code_owner_reviews: requireCodeOwnerReviews
@@ -837,8 +981,12 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
837
981
  });
838
982
  } catch (e) {
839
983
  errors.assertError(e);
840
- if (e.message.includes("Upgrade to GitHub Pro or make this repository public to enable this feature")) {
841
- logger.warn("Branch protection was not enabled as it requires GitHub Pro for private repositories");
984
+ if (e.message.includes(
985
+ "Upgrade to GitHub Pro or make this repository public to enable this feature"
986
+ )) {
987
+ logger.warn(
988
+ "Branch protection was not enabled as it requires GitHub Pro for private repositories"
989
+ );
842
990
  } else {
843
991
  throw e;
844
992
  }
@@ -880,10 +1028,14 @@ async function getOctokitOptions(options) {
880
1028
  }
881
1029
  const githubCredentialsProvider = credentialsProvider != null ? credentialsProvider : integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
882
1030
  const { token: credentialProviderToken } = await githubCredentialsProvider.getCredentials({
883
- url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
1031
+ url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
1032
+ repo
1033
+ )}`
884
1034
  });
885
1035
  if (!credentialProviderToken) {
886
- throw new errors.InputError(`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`);
1036
+ throw new errors.InputError(
1037
+ `No token available for host: ${host}, with owner ${owner}, and repo ${repo}`
1038
+ );
887
1039
  }
888
1040
  return {
889
1041
  auth: credentialProviderToken,
@@ -920,9 +1072,13 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
920
1072
  } catch (e) {
921
1073
  errors.assertError(e);
922
1074
  if (e.message === "Resource not accessible by integration") {
923
- logger.warn(`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`);
1075
+ logger.warn(
1076
+ `The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`
1077
+ );
924
1078
  }
925
- throw new Error(`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`);
1079
+ throw new Error(
1080
+ `Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`
1081
+ );
926
1082
  }
927
1083
  if (access == null ? void 0 : access.startsWith(`${owner}/`)) {
928
1084
  const [, team] = access.split("/");
@@ -963,7 +1119,9 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
963
1119
  } catch (e) {
964
1120
  errors.assertError(e);
965
1121
  const name = extractCollaboratorName(collaborator);
966
- logger.warn(`Skipping ${collaborator.access} access for ${name}, ${e.message}`);
1122
+ logger.warn(
1123
+ `Skipping ${collaborator.access} access for ${name}, ${e.message}`
1124
+ );
967
1125
  }
968
1126
  }
969
1127
  }
@@ -981,7 +1139,7 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
981
1139
  }
982
1140
  return newRepo;
983
1141
  }
984
- async function initRepoPushAndProtect(remoteUrl, password, workspacePath, sourcePath, defaultBranch, protectDefaultBranch, owner, client, repo, requireCodeOwnerReviews, requiredStatusCheckContexts, config, logger, gitCommitMessage, gitAuthorName, gitAuthorEmail) {
1142
+ async function initRepoPushAndProtect(remoteUrl, password, workspacePath, sourcePath, defaultBranch, protectDefaultBranch, protectEnforceAdmins, owner, client, repo, requireCodeOwnerReviews, requiredStatusCheckContexts, config, logger, gitCommitMessage, gitAuthorName, gitAuthorEmail) {
985
1143
  const gitAuthorInfo = {
986
1144
  name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
987
1145
  email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
@@ -1008,11 +1166,14 @@ async function initRepoPushAndProtect(remoteUrl, password, workspacePath, source
1008
1166
  logger,
1009
1167
  defaultBranch,
1010
1168
  requireCodeOwnerReviews,
1011
- requiredStatusCheckContexts
1169
+ requiredStatusCheckContexts,
1170
+ enforceAdmins: protectEnforceAdmins
1012
1171
  });
1013
1172
  } catch (e) {
1014
1173
  errors.assertError(e);
1015
- logger.warn(`Skipping: default branch protection on '${repo}', ${e.message}`);
1174
+ logger.warn(
1175
+ `Skipping: default branch protection on '${repo}', ${e.message}`
1176
+ );
1016
1177
  }
1017
1178
  }
1018
1179
  }
@@ -1070,17 +1231,21 @@ function createGithubActionsDispatchAction(options) {
1070
1231
  workflowInputs,
1071
1232
  token: providedToken
1072
1233
  } = ctx.input;
1073
- ctx.logger.info(`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`);
1234
+ ctx.logger.info(
1235
+ `Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`
1236
+ );
1074
1237
  const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1075
1238
  if (!owner) {
1076
1239
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1077
1240
  }
1078
- const client = new octokit.Octokit(await getOctokitOptions({
1079
- integrations,
1080
- repoUrl,
1081
- credentialsProvider: githubCredentialsProvider,
1082
- token: providedToken
1083
- }));
1241
+ const client = new octokit.Octokit(
1242
+ await getOctokitOptions({
1243
+ integrations,
1244
+ repoUrl,
1245
+ credentialsProvider: githubCredentialsProvider,
1246
+ token: providedToken
1247
+ })
1248
+ );
1084
1249
  await client.rest.actions.createWorkflowDispatch({
1085
1250
  owner,
1086
1251
  repo,
@@ -1136,12 +1301,14 @@ function createGithubIssuesLabelAction(options) {
1136
1301
  if (!owner) {
1137
1302
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1138
1303
  }
1139
- const client = new octokit.Octokit(await getOctokitOptions({
1140
- integrations,
1141
- credentialsProvider: githubCredentialsProvider,
1142
- repoUrl,
1143
- token: providedToken
1144
- }));
1304
+ const client = new octokit.Octokit(
1305
+ await getOctokitOptions({
1306
+ integrations,
1307
+ credentialsProvider: githubCredentialsProvider,
1308
+ repoUrl,
1309
+ token: providedToken
1310
+ })
1311
+ );
1145
1312
  try {
1146
1313
  await client.rest.issues.addLabels({
1147
1314
  owner,
@@ -1151,7 +1318,9 @@ function createGithubIssuesLabelAction(options) {
1151
1318
  });
1152
1319
  } catch (e) {
1153
1320
  errors.assertError(e);
1154
- ctx.logger.warn(`Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`);
1321
+ ctx.logger.warn(
1322
+ `Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`
1323
+ );
1155
1324
  }
1156
1325
  }
1157
1326
  });
@@ -1267,6 +1436,11 @@ const protectDefaultBranch = {
1267
1436
  type: "boolean",
1268
1437
  description: `Protect the default branch after creating the repository. The default value is 'true'`
1269
1438
  };
1439
+ const protectEnforceAdmins = {
1440
+ title: "Enforce Admins On Protected Branches",
1441
+ type: "boolean",
1442
+ description: `Enforce admins to adhere to default branch protection. The default value is 'true'`
1443
+ };
1270
1444
  const gitCommitMessage = {
1271
1445
  title: "Git Commit Message",
1272
1446
  type: "string",
@@ -1345,7 +1519,21 @@ function createGithubRepoCreateAction(options) {
1345
1519
  if (!owner) {
1346
1520
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1347
1521
  }
1348
- const newRepo = await createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, repoVisibility, description, deleteBranchOnMerge, allowMergeCommit, allowSquashMerge, allowRebaseMerge, access, collaborators, topics, ctx.logger);
1522
+ const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
1523
+ client,
1524
+ repo,
1525
+ owner,
1526
+ repoVisibility,
1527
+ description,
1528
+ deleteBranchOnMerge,
1529
+ allowMergeCommit,
1530
+ allowSquashMerge,
1531
+ allowRebaseMerge,
1532
+ access,
1533
+ collaborators,
1534
+ topics,
1535
+ ctx.logger
1536
+ );
1349
1537
  ctx.output("remoteUrl", newRepo.clone_url);
1350
1538
  }
1351
1539
  });
@@ -1366,6 +1554,7 @@ function createGithubRepoPushAction(options) {
1366
1554
  requiredStatusCheckContexts: requiredStatusCheckContexts,
1367
1555
  defaultBranch: defaultBranch,
1368
1556
  protectDefaultBranch: protectDefaultBranch,
1557
+ protectEnforceAdmins: protectEnforceAdmins,
1369
1558
  gitCommitMessage: gitCommitMessage,
1370
1559
  gitAuthorName: gitAuthorName,
1371
1560
  gitAuthorEmail: gitAuthorEmail,
@@ -1386,6 +1575,7 @@ function createGithubRepoPushAction(options) {
1386
1575
  repoUrl,
1387
1576
  defaultBranch = "master",
1388
1577
  protectDefaultBranch = true,
1578
+ protectEnforceAdmins = true,
1389
1579
  gitCommitMessage = "initial commit",
1390
1580
  gitAuthorName,
1391
1581
  gitAuthorEmail,
@@ -1407,7 +1597,25 @@ function createGithubRepoPushAction(options) {
1407
1597
  const targetRepo = await client.rest.repos.get({ owner, repo });
1408
1598
  const remoteUrl = targetRepo.data.clone_url;
1409
1599
  const repoContentsUrl = `${targetRepo.data.html_url}/blob/${defaultBranch}`;
1410
- await initRepoPushAndProtect(remoteUrl, octokitOptions.auth, ctx.workspacePath, ctx.input.sourcePath, defaultBranch, protectDefaultBranch, owner, client, repo, requireCodeOwnerReviews, requiredStatusCheckContexts, config, ctx.logger, gitCommitMessage, gitAuthorName, gitAuthorEmail);
1600
+ await initRepoPushAndProtect(
1601
+ remoteUrl,
1602
+ octokitOptions.auth,
1603
+ ctx.workspacePath,
1604
+ ctx.input.sourcePath,
1605
+ defaultBranch,
1606
+ protectDefaultBranch,
1607
+ protectEnforceAdmins,
1608
+ owner,
1609
+ client,
1610
+ repo,
1611
+ requireCodeOwnerReviews,
1612
+ requiredStatusCheckContexts,
1613
+ config,
1614
+ ctx.logger,
1615
+ gitCommitMessage,
1616
+ gitAuthorName,
1617
+ gitAuthorEmail
1618
+ );
1411
1619
  ctx.output("remoteUrl", remoteUrl);
1412
1620
  ctx.output("repoContentsUrl", repoContentsUrl);
1413
1621
  }
@@ -1499,12 +1707,14 @@ function createGithubWebhookAction(options) {
1499
1707
  if (!owner) {
1500
1708
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1501
1709
  }
1502
- const client = new octokit.Octokit(await getOctokitOptions({
1503
- integrations,
1504
- credentialsProvider: githubCredentialsProvider,
1505
- repoUrl,
1506
- token: providedToken
1507
- }));
1710
+ const client = new octokit.Octokit(
1711
+ await getOctokitOptions({
1712
+ integrations,
1713
+ credentialsProvider: githubCredentialsProvider,
1714
+ repoUrl,
1715
+ token: providedToken
1716
+ })
1717
+ );
1508
1718
  try {
1509
1719
  const insecure_ssl = insecureSsl ? "1" : "0";
1510
1720
  await client.rest.repos.createWebhook({
@@ -1522,7 +1732,9 @@ function createGithubWebhookAction(options) {
1522
1732
  ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
1523
1733
  } catch (e) {
1524
1734
  errors.assertError(e);
1525
- ctx.logger.warn(`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`);
1735
+ ctx.logger.warn(
1736
+ `Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`
1737
+ );
1526
1738
  }
1527
1739
  }
1528
1740
  });
@@ -1601,13 +1813,20 @@ function createPublishAzureAction(options) {
1601
1813
  gitAuthorName,
1602
1814
  gitAuthorEmail
1603
1815
  } = ctx.input;
1604
- const { owner, repo, host, organization } = parseRepoUrl(repoUrl, integrations);
1816
+ const { owner, repo, host, organization } = parseRepoUrl(
1817
+ repoUrl,
1818
+ integrations
1819
+ );
1605
1820
  if (!organization) {
1606
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing organization`);
1821
+ throw new errors.InputError(
1822
+ `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing organization`
1823
+ );
1607
1824
  }
1608
1825
  const integrationConfig = integrations.azure.byHost(host);
1609
1826
  if (!integrationConfig) {
1610
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
1827
+ throw new errors.InputError(
1828
+ `No matching integration configuration for host ${host}, please check your integrations config`
1829
+ );
1611
1830
  }
1612
1831
  if (!integrationConfig.config.token && !ctx.input.token) {
1613
1832
  throw new errors.InputError(`No token provided for Azure Integration ${host}`);
@@ -1619,12 +1838,16 @@ function createPublishAzureAction(options) {
1619
1838
  const createOptions = { name: repo };
1620
1839
  const returnedRepo = await client.createRepository(createOptions, owner);
1621
1840
  if (!returnedRepo) {
1622
- throw new errors.InputError(`Unable to create the repository with Organization ${organization}, Project ${owner} and Repo ${repo}.
1623
- Please make sure that both the Org and Project are typed corrected and exist.`);
1841
+ throw new errors.InputError(
1842
+ `Unable to create the repository with Organization ${organization}, Project ${owner} and Repo ${repo}.
1843
+ Please make sure that both the Org and Project are typed corrected and exist.`
1844
+ );
1624
1845
  }
1625
1846
  const remoteUrl = returnedRepo.remoteUrl;
1626
1847
  if (!remoteUrl) {
1627
- throw new errors.InputError("No remote URL returned from create repository for Azure");
1848
+ throw new errors.InputError(
1849
+ "No remote URL returned from create repository for Azure"
1850
+ );
1628
1851
  }
1629
1852
  const repoContentsUrl = remoteUrl;
1630
1853
  const gitAuthorInfo = {
@@ -1675,12 +1898,17 @@ const createBitbucketCloudRepository = async (opts) => {
1675
1898
  };
1676
1899
  let response;
1677
1900
  try {
1678
- response = await fetch__default["default"](`${apiBaseUrl}/repositories/${workspace}/${repo}`, options);
1901
+ response = await fetch__default["default"](
1902
+ `${apiBaseUrl}/repositories/${workspace}/${repo}`,
1903
+ options
1904
+ );
1679
1905
  } catch (e) {
1680
1906
  throw new Error(`Unable to create repository, ${e}`);
1681
1907
  }
1682
1908
  if (response.status !== 200) {
1683
- throw new Error(`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`);
1909
+ throw new Error(
1910
+ `Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
1911
+ );
1684
1912
  }
1685
1913
  const r = await response.json();
1686
1914
  let remoteUrl = "";
@@ -1720,7 +1948,9 @@ const createBitbucketServerRepository = async (opts) => {
1720
1948
  throw new Error(`Unable to create repository, ${e}`);
1721
1949
  }
1722
1950
  if (response.status !== 201) {
1723
- throw new Error(`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`);
1951
+ throw new Error(
1952
+ `Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
1953
+ );
1724
1954
  }
1725
1955
  const r = await response.json();
1726
1956
  let remoteUrl = "";
@@ -1732,15 +1962,20 @@ const createBitbucketServerRepository = async (opts) => {
1732
1962
  const repoContentsUrl = `${r.links.self[0].href}`;
1733
1963
  return { remoteUrl, repoContentsUrl };
1734
1964
  };
1735
- const getAuthorizationHeader$2 = (config) => {
1965
+ const getAuthorizationHeader$1 = (config) => {
1736
1966
  if (config.username && config.appPassword) {
1737
- const buffer = Buffer.from(`${config.username}:${config.appPassword}`, "utf8");
1967
+ const buffer = Buffer.from(
1968
+ `${config.username}:${config.appPassword}`,
1969
+ "utf8"
1970
+ );
1738
1971
  return `Basic ${buffer.toString("base64")}`;
1739
1972
  }
1740
1973
  if (config.token) {
1741
1974
  return `Bearer ${config.token}`;
1742
1975
  }
1743
- throw new Error(`Authorization has not been provided for Bitbucket. Please add either username + appPassword or token to the Integrations config`);
1976
+ throw new Error(
1977
+ `Authorization has not been provided for Bitbucket. Please add either username + appPassword or token to the Integrations config`
1978
+ );
1744
1979
  };
1745
1980
  const performEnableLFS$1 = async (opts) => {
1746
1981
  const { authorization, host, project, repo } = opts;
@@ -1750,9 +1985,14 @@ const performEnableLFS$1 = async (opts) => {
1750
1985
  Authorization: authorization
1751
1986
  }
1752
1987
  };
1753
- const { ok, status, statusText } = await fetch__default["default"](`https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`, options);
1988
+ const { ok, status, statusText } = await fetch__default["default"](
1989
+ `https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`,
1990
+ options
1991
+ );
1754
1992
  if (!ok)
1755
- throw new Error(`Failed to enable LFS in the repository, ${status}: ${statusText}`);
1993
+ throw new Error(
1994
+ `Failed to enable LFS in the repository, ${status}: ${statusText}`
1995
+ );
1756
1996
  };
1757
1997
  function createPublishBitbucketAction(options) {
1758
1998
  const { integrations, config } = options;
@@ -1830,7 +2070,9 @@ function createPublishBitbucketAction(options) {
1830
2070
  },
1831
2071
  async handler(ctx) {
1832
2072
  var _a;
1833
- ctx.logger.warn(`[Deprecated] Please migrate the use of action "publish:bitbucket" to "publish:bitbucketCloud" or "publish:bitbucketServer".`);
2073
+ ctx.logger.warn(
2074
+ `[Deprecated] Please migrate the use of action "publish:bitbucket" to "publish:bitbucketCloud" or "publish:bitbucketServer".`
2075
+ );
1834
2076
  const {
1835
2077
  repoUrl,
1836
2078
  description,
@@ -1841,24 +2083,35 @@ function createPublishBitbucketAction(options) {
1841
2083
  gitAuthorName,
1842
2084
  gitAuthorEmail
1843
2085
  } = ctx.input;
1844
- const { workspace, project, repo, host } = parseRepoUrl(repoUrl, integrations);
2086
+ const { workspace, project, repo, host } = parseRepoUrl(
2087
+ repoUrl,
2088
+ integrations
2089
+ );
1845
2090
  if (host === "bitbucket.org") {
1846
2091
  if (!workspace) {
1847
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`);
2092
+ throw new errors.InputError(
2093
+ `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`
2094
+ );
1848
2095
  }
1849
2096
  }
1850
2097
  if (!project) {
1851
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`);
2098
+ throw new errors.InputError(
2099
+ `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`
2100
+ );
1852
2101
  }
1853
2102
  const integrationConfig = integrations.bitbucket.byHost(host);
1854
2103
  if (!integrationConfig) {
1855
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
2104
+ throw new errors.InputError(
2105
+ `No matching integration configuration for host ${host}, please check your integrations config`
2106
+ );
1856
2107
  }
1857
- const authorization = getAuthorizationHeader$2(ctx.input.token ? {
1858
- host: integrationConfig.config.host,
1859
- apiBaseUrl: integrationConfig.config.apiBaseUrl,
1860
- token: ctx.input.token
1861
- } : integrationConfig.config);
2108
+ const authorization = getAuthorizationHeader$1(
2109
+ ctx.input.token ? {
2110
+ host: integrationConfig.config.host,
2111
+ apiBaseUrl: integrationConfig.config.apiBaseUrl,
2112
+ token: ctx.input.token
2113
+ } : integrationConfig.config
2114
+ );
1862
2115
  const apiBaseUrl = integrationConfig.config.apiBaseUrl;
1863
2116
  const createMethod = host === "bitbucket.org" ? createBitbucketCloudRepository : createBitbucketServerRepository;
1864
2117
  const { remoteUrl, repoContentsUrl } = await createMethod({
@@ -1931,12 +2184,17 @@ const createRepository$1 = async (opts) => {
1931
2184
  };
1932
2185
  let response;
1933
2186
  try {
1934
- response = await fetch__default["default"](`${apiBaseUrl}/repositories/${workspace}/${repo}`, options);
2187
+ response = await fetch__default["default"](
2188
+ `${apiBaseUrl}/repositories/${workspace}/${repo}`,
2189
+ options
2190
+ );
1935
2191
  } catch (e) {
1936
2192
  throw new Error(`Unable to create repository, ${e}`);
1937
2193
  }
1938
2194
  if (response.status !== 200) {
1939
- throw new Error(`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`);
2195
+ throw new Error(
2196
+ `Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
2197
+ );
1940
2198
  }
1941
2199
  const r = await response.json();
1942
2200
  let remoteUrl = "";
@@ -1948,15 +2206,20 @@ const createRepository$1 = async (opts) => {
1948
2206
  const repoContentsUrl = `${r.links.html.href}/src/${mainBranch}`;
1949
2207
  return { remoteUrl, repoContentsUrl };
1950
2208
  };
1951
- const getAuthorizationHeader$1 = (config) => {
2209
+ const getAuthorizationHeader = (config) => {
1952
2210
  if (config.username && config.appPassword) {
1953
- const buffer = Buffer.from(`${config.username}:${config.appPassword}`, "utf8");
2211
+ const buffer = Buffer.from(
2212
+ `${config.username}:${config.appPassword}`,
2213
+ "utf8"
2214
+ );
1954
2215
  return `Basic ${buffer.toString("base64")}`;
1955
2216
  }
1956
2217
  if (config.token) {
1957
2218
  return `Bearer ${config.token}`;
1958
2219
  }
1959
- throw new Error(`Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`);
2220
+ throw new Error(
2221
+ `Authorization has not been provided for Bitbucket Cloud. Please add either username + appPassword to the Integrations config or a user login auth token`
2222
+ );
1960
2223
  };
1961
2224
  function createPublishBitbucketCloudAction(options) {
1962
2225
  const { integrations, config } = options;
@@ -2019,18 +2282,29 @@ function createPublishBitbucketCloudAction(options) {
2019
2282
  defaultBranch = "master",
2020
2283
  repoVisibility = "private"
2021
2284
  } = ctx.input;
2022
- const { workspace, project, repo, host } = parseRepoUrl(repoUrl, integrations);
2285
+ const { workspace, project, repo, host } = parseRepoUrl(
2286
+ repoUrl,
2287
+ integrations
2288
+ );
2023
2289
  if (!workspace) {
2024
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`);
2290
+ throw new errors.InputError(
2291
+ `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`
2292
+ );
2025
2293
  }
2026
2294
  if (!project) {
2027
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`);
2295
+ throw new errors.InputError(
2296
+ `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`
2297
+ );
2028
2298
  }
2029
2299
  const integrationConfig = integrations.bitbucketCloud.byHost(host);
2030
2300
  if (!integrationConfig) {
2031
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
2301
+ throw new errors.InputError(
2302
+ `No matching integration configuration for host ${host}, please check your integrations config`
2303
+ );
2032
2304
  }
2033
- const authorization = getAuthorizationHeader$1(ctx.input.token ? { token: ctx.input.token } : integrationConfig.config);
2305
+ const authorization = getAuthorizationHeader(
2306
+ ctx.input.token ? { token: ctx.input.token } : integrationConfig.config
2307
+ );
2034
2308
  const apiBaseUrl = integrationConfig.config.apiBaseUrl;
2035
2309
  const { remoteUrl, repoContentsUrl } = await createRepository$1({
2036
2310
  authorization,
@@ -2054,7 +2328,9 @@ function createPublishBitbucketCloudAction(options) {
2054
2328
  };
2055
2329
  } else {
2056
2330
  if (!integrationConfig.config.username || !integrationConfig.config.appPassword) {
2057
- throw new Error("Credentials for Bitbucket Cloud integration required for this action.");
2331
+ throw new Error(
2332
+ "Credentials for Bitbucket Cloud integration required for this action."
2333
+ );
2058
2334
  }
2059
2335
  auth = {
2060
2336
  username: integrationConfig.config.username,
@@ -2067,7 +2343,9 @@ function createPublishBitbucketCloudAction(options) {
2067
2343
  auth,
2068
2344
  defaultBranch,
2069
2345
  logger: ctx.logger,
2070
- commitMessage: config.getOptionalString("scaffolder.defaultCommitMessage"),
2346
+ commitMessage: config.getOptionalString(
2347
+ "scaffolder.defaultCommitMessage"
2348
+ ),
2071
2349
  gitAuthorInfo
2072
2350
  });
2073
2351
  ctx.output("remoteUrl", remoteUrl);
@@ -2104,7 +2382,9 @@ const createRepository = async (opts) => {
2104
2382
  throw new Error(`Unable to create repository, ${e}`);
2105
2383
  }
2106
2384
  if (response.status !== 201) {
2107
- throw new Error(`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`);
2385
+ throw new Error(
2386
+ `Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
2387
+ );
2108
2388
  }
2109
2389
  const r = await response.json();
2110
2390
  let remoteUrl = "";
@@ -2116,9 +2396,6 @@ const createRepository = async (opts) => {
2116
2396
  const repoContentsUrl = `${r.links.self[0].href}`;
2117
2397
  return { remoteUrl, repoContentsUrl };
2118
2398
  };
2119
- const getAuthorizationHeader = (config) => {
2120
- return `Bearer ${config.token}`;
2121
- };
2122
2399
  const performEnableLFS = async (opts) => {
2123
2400
  const { authorization, host, project, repo } = opts;
2124
2401
  const options = {
@@ -2127,9 +2404,14 @@ const performEnableLFS = async (opts) => {
2127
2404
  Authorization: authorization
2128
2405
  }
2129
2406
  };
2130
- const { ok, status, statusText } = await fetch__default["default"](`https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`, options);
2407
+ const { ok, status, statusText } = await fetch__default["default"](
2408
+ `https://${host}/rest/git-lfs/admin/projects/${project}/repos/${repo}/enabled`,
2409
+ options
2410
+ );
2131
2411
  if (!ok)
2132
- throw new Error(`Failed to enable LFS in the repository, ${status}: ${statusText}`);
2412
+ throw new Error(
2413
+ `Failed to enable LFS in the repository, ${status}: ${statusText}`
2414
+ );
2133
2415
  };
2134
2416
  function createPublishBitbucketServerAction(options) {
2135
2417
  const { integrations, config } = options;
@@ -2201,17 +2483,28 @@ function createPublishBitbucketServerAction(options) {
2201
2483
  } = ctx.input;
2202
2484
  const { project, repo, host } = parseRepoUrl(repoUrl, integrations);
2203
2485
  if (!project) {
2204
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`);
2486
+ throw new errors.InputError(
2487
+ `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing project`
2488
+ );
2205
2489
  }
2206
2490
  const integrationConfig = integrations.bitbucketServer.byHost(host);
2207
2491
  if (!integrationConfig) {
2208
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
2492
+ throw new errors.InputError(
2493
+ `No matching integration configuration for host ${host}, please check your integrations config`
2494
+ );
2209
2495
  }
2210
2496
  const token = (_a = ctx.input.token) != null ? _a : integrationConfig.config.token;
2211
- if (!token) {
2212
- throw new Error(`Authorization has not been provided for ${integrationConfig.config.host}. Please add either token to the Integrations config or a user login auth token`);
2497
+ const authConfig = {
2498
+ ...integrationConfig.config,
2499
+ ...{ token }
2500
+ };
2501
+ const reqOpts = integration.getBitbucketServerRequestOptions(authConfig);
2502
+ const authorization = reqOpts.headers.Authorization;
2503
+ if (!authorization) {
2504
+ throw new Error(
2505
+ `Authorization has not been provided for ${integrationConfig.config.host}. Please add either (a) a user login auth token, or (b) a token or (c) username + password to the integration config.`
2506
+ );
2213
2507
  }
2214
- const authorization = getAuthorizationHeader({ token });
2215
2508
  const apiBaseUrl = integrationConfig.config.apiBaseUrl;
2216
2509
  const { remoteUrl, repoContentsUrl } = await createRepository({
2217
2510
  authorization,
@@ -2225,9 +2518,11 @@ function createPublishBitbucketServerAction(options) {
2225
2518
  name: config.getOptionalString("scaffolder.defaultAuthor.name"),
2226
2519
  email: config.getOptionalString("scaffolder.defaultAuthor.email")
2227
2520
  };
2228
- const auth = {
2229
- username: "x-token-auth",
2230
- password: token
2521
+ const auth = authConfig.token ? {
2522
+ token
2523
+ } : {
2524
+ username: authConfig.username,
2525
+ password: authConfig.password
2231
2526
  };
2232
2527
  await initRepoAndPush({
2233
2528
  dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
@@ -2235,7 +2530,9 @@ function createPublishBitbucketServerAction(options) {
2235
2530
  auth,
2236
2531
  defaultBranch,
2237
2532
  logger: ctx.logger,
2238
- commitMessage: config.getOptionalString("scaffolder.defaultCommitMessage"),
2533
+ commitMessage: config.getOptionalString(
2534
+ "scaffolder.defaultCommitMessage"
2535
+ ),
2239
2536
  gitAuthorInfo
2240
2537
  });
2241
2538
  if (enableLFS) {
@@ -2282,7 +2579,7 @@ const createGerritProject = async (config, options) => {
2282
2579
  body: JSON.stringify({
2283
2580
  parent,
2284
2581
  description,
2285
- owners: [owner],
2582
+ owners: owner ? [owner] : [],
2286
2583
  create_empty_commit: false
2287
2584
  }),
2288
2585
  headers: {
@@ -2290,9 +2587,14 @@ const createGerritProject = async (config, options) => {
2290
2587
  "Content-Type": "application/json"
2291
2588
  }
2292
2589
  };
2293
- const response = await fetch__default["default"](`${config.baseUrl}/a/projects/${encodeURIComponent(projectName)}`, fetchOptions);
2590
+ const response = await fetch__default["default"](
2591
+ `${config.baseUrl}/a/projects/${encodeURIComponent(projectName)}`,
2592
+ fetchOptions
2593
+ );
2294
2594
  if (response.status !== 201) {
2295
- throw new Error(`Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`);
2595
+ throw new Error(
2596
+ `Unable to create repository, ${response.status} ${response.statusText}, ${await response.text()}`
2597
+ );
2296
2598
  }
2297
2599
  };
2298
2600
  const generateCommitMessage = (config, commitSubject) => {
@@ -2339,6 +2641,11 @@ function createPublishGerritAction(options) {
2339
2641
  title: "Default Author Email",
2340
2642
  type: "string",
2341
2643
  description: `Sets the default author email for the commit.`
2644
+ },
2645
+ sourcePath: {
2646
+ title: "Source Path",
2647
+ type: "string",
2648
+ description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`
2342
2649
  }
2343
2650
  }
2344
2651
  },
@@ -2363,18 +2670,23 @@ function createPublishGerritAction(options) {
2363
2670
  defaultBranch = "master",
2364
2671
  gitAuthorName,
2365
2672
  gitAuthorEmail,
2366
- gitCommitMessage = "initial commit"
2673
+ gitCommitMessage = "initial commit",
2674
+ sourcePath
2367
2675
  } = ctx.input;
2368
- const { repo, host, owner, workspace } = parseRepoUrl(repoUrl, integrations);
2676
+ const { repo, host, owner, workspace } = parseRepoUrl(
2677
+ repoUrl,
2678
+ integrations
2679
+ );
2369
2680
  const integrationConfig = integrations.gerrit.byHost(host);
2370
2681
  if (!integrationConfig) {
2371
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
2372
- }
2373
- if (!owner) {
2374
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing owner`);
2682
+ throw new errors.InputError(
2683
+ `No matching integration configuration for host ${host}, please check your integrations config`
2684
+ );
2375
2685
  }
2376
2686
  if (!workspace) {
2377
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`);
2687
+ throw new errors.InputError(
2688
+ `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`
2689
+ );
2378
2690
  }
2379
2691
  await createGerritProject(integrationConfig.config, {
2380
2692
  description,
@@ -2392,7 +2704,7 @@ function createPublishGerritAction(options) {
2392
2704
  };
2393
2705
  const remoteUrl = `${integrationConfig.config.cloneUrl}/a/${repo}`;
2394
2706
  await initRepoAndPush({
2395
- dir: getRepoSourceDirectory(ctx.workspacePath, void 0),
2707
+ dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath),
2396
2708
  remoteUrl,
2397
2709
  auth,
2398
2710
  defaultBranch,
@@ -2407,6 +2719,115 @@ function createPublishGerritAction(options) {
2407
2719
  });
2408
2720
  }
2409
2721
 
2722
+ const generateGerritChangeId = () => {
2723
+ const changeId = crypto__default["default"].randomBytes(20).toString("hex");
2724
+ return `I${changeId}`;
2725
+ };
2726
+ function createPublishGerritReviewAction(options) {
2727
+ const { integrations, config } = options;
2728
+ return createTemplateAction({
2729
+ id: "publish:gerrit:review",
2730
+ description: "Creates a new Gerrit review.",
2731
+ schema: {
2732
+ input: {
2733
+ type: "object",
2734
+ required: ["repoUrl", "gitCommitMessage"],
2735
+ properties: {
2736
+ repoUrl: {
2737
+ title: "Repository Location",
2738
+ type: "string"
2739
+ },
2740
+ branch: {
2741
+ title: "Repository branch",
2742
+ type: "string",
2743
+ description: "Branch of the repository the review will be created on"
2744
+ },
2745
+ sourcePath: {
2746
+ type: "string",
2747
+ title: "Working Subdirectory",
2748
+ description: "Subdirectory of working directory containing the repository"
2749
+ },
2750
+ gitCommitMessage: {
2751
+ title: "Git Commit Message",
2752
+ type: "string",
2753
+ description: `Sets the commit message on the repository.`
2754
+ },
2755
+ gitAuthorName: {
2756
+ title: "Default Author Name",
2757
+ type: "string",
2758
+ description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
2759
+ },
2760
+ gitAuthorEmail: {
2761
+ title: "Default Author Email",
2762
+ type: "string",
2763
+ description: `Sets the default author email for the commit.`
2764
+ }
2765
+ }
2766
+ },
2767
+ output: {
2768
+ type: "object",
2769
+ properties: {
2770
+ reviewUrl: {
2771
+ title: "A URL to the review",
2772
+ type: "string"
2773
+ },
2774
+ repoContentsUrl: {
2775
+ title: "A URL to the root of the repository",
2776
+ type: "string"
2777
+ }
2778
+ }
2779
+ }
2780
+ },
2781
+ async handler(ctx) {
2782
+ var _a;
2783
+ const {
2784
+ repoUrl,
2785
+ branch = "master",
2786
+ sourcePath,
2787
+ gitAuthorName,
2788
+ gitAuthorEmail,
2789
+ gitCommitMessage
2790
+ } = ctx.input;
2791
+ const { host, repo } = parseRepoUrl(repoUrl, integrations);
2792
+ if (!gitCommitMessage) {
2793
+ throw new errors.InputError(`Missing gitCommitMessage input`);
2794
+ }
2795
+ const integrationConfig = integrations.gerrit.byHost(host);
2796
+ if (!integrationConfig) {
2797
+ throw new errors.InputError(
2798
+ `No matching integration configuration for host ${host}, please check your integrations config`
2799
+ );
2800
+ }
2801
+ const auth = {
2802
+ username: integrationConfig.config.username,
2803
+ password: integrationConfig.config.password
2804
+ };
2805
+ const gitAuthorInfo = {
2806
+ name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
2807
+ email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
2808
+ };
2809
+ const changeId = generateGerritChangeId();
2810
+ const commitMessage = `${gitCommitMessage}
2811
+
2812
+ Change-Id: ${changeId}`;
2813
+ await commitAndPushRepo({
2814
+ dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath),
2815
+ auth,
2816
+ logger: ctx.logger,
2817
+ commitMessage,
2818
+ gitAuthorInfo,
2819
+ branch,
2820
+ remoteRef: `refs/for/${branch}`
2821
+ });
2822
+ const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${branch}`;
2823
+ const reviewUrl = `${integrationConfig.config.baseUrl}/#/q/${changeId}`;
2824
+ (_a = ctx.logger) == null ? void 0 : _a.info(`Review available on ${reviewUrl}`);
2825
+ ctx.output("repoContentsUrl", repoContentsUrl);
2826
+ ctx.output("reviewUrl", reviewUrl);
2827
+ }
2828
+ });
2829
+ }
2830
+
2410
2831
  function createPublishGithubAction(options) {
2411
2832
  const { integrations, config, githubCredentialsProvider } = options;
2412
2833
  return createTemplateAction({
@@ -2425,6 +2846,7 @@ function createPublishGithubAction(options) {
2425
2846
  repoVisibility: repoVisibility,
2426
2847
  defaultBranch: defaultBranch,
2427
2848
  protectDefaultBranch: protectDefaultBranch,
2849
+ protectEnforceAdmins: protectEnforceAdmins,
2428
2850
  deleteBranchOnMerge: deleteBranchOnMerge,
2429
2851
  gitCommitMessage: gitCommitMessage,
2430
2852
  gitAuthorName: gitAuthorName,
@@ -2456,6 +2878,7 @@ function createPublishGithubAction(options) {
2456
2878
  repoVisibility = "private",
2457
2879
  defaultBranch = "master",
2458
2880
  protectDefaultBranch = true,
2881
+ protectEnforceAdmins = true,
2459
2882
  deleteBranchOnMerge = false,
2460
2883
  gitCommitMessage = "initial commit",
2461
2884
  gitAuthorName,
@@ -2478,10 +2901,42 @@ function createPublishGithubAction(options) {
2478
2901
  if (!owner) {
2479
2902
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
2480
2903
  }
2481
- const newRepo = await createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, repoVisibility, description, deleteBranchOnMerge, allowMergeCommit, allowSquashMerge, allowRebaseMerge, access, collaborators, topics, ctx.logger);
2904
+ const newRepo = await createGithubRepoWithCollaboratorsAndTopics(
2905
+ client,
2906
+ repo,
2907
+ owner,
2908
+ repoVisibility,
2909
+ description,
2910
+ deleteBranchOnMerge,
2911
+ allowMergeCommit,
2912
+ allowSquashMerge,
2913
+ allowRebaseMerge,
2914
+ access,
2915
+ collaborators,
2916
+ topics,
2917
+ ctx.logger
2918
+ );
2482
2919
  const remoteUrl = newRepo.clone_url;
2483
2920
  const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`;
2484
- await initRepoPushAndProtect(remoteUrl, octokitOptions.auth, ctx.workspacePath, ctx.input.sourcePath, defaultBranch, protectDefaultBranch, owner, client, repo, requireCodeOwnerReviews, requiredStatusCheckContexts, config, ctx.logger, gitCommitMessage, gitAuthorName, gitAuthorEmail);
2921
+ await initRepoPushAndProtect(
2922
+ remoteUrl,
2923
+ octokitOptions.auth,
2924
+ ctx.workspacePath,
2925
+ ctx.input.sourcePath,
2926
+ defaultBranch,
2927
+ protectDefaultBranch,
2928
+ protectEnforceAdmins,
2929
+ owner,
2930
+ client,
2931
+ repo,
2932
+ requireCodeOwnerReviews,
2933
+ requiredStatusCheckContexts,
2934
+ config,
2935
+ ctx.logger,
2936
+ gitCommitMessage,
2937
+ gitAuthorName,
2938
+ gitAuthorEmail
2939
+ );
2485
2940
  ctx.output("remoteUrl", remoteUrl);
2486
2941
  ctx.output("repoContentsUrl", repoContentsUrl);
2487
2942
  }
@@ -2508,11 +2963,15 @@ async function serializeDirectoryContents(sourcePath, options) {
2508
2963
  stats: true
2509
2964
  });
2510
2965
  const limiter = limiterFactory__default["default"](10);
2511
- return Promise.all(paths.map(async ({ path: path$1, stats }) => ({
2512
- path: path$1,
2513
- content: await limiter(async () => fs__default["default"].readFile(path.join(sourcePath, path$1))),
2514
- executable: isExecutable(stats == null ? void 0 : stats.mode)
2515
- })));
2966
+ return Promise.all(
2967
+ paths.map(async ({ path: path$1, stats }) => ({
2968
+ path: path$1,
2969
+ content: await limiter(
2970
+ async () => fs__default["default"].readFile(path.join(sourcePath, path$1))
2971
+ ),
2972
+ executable: isExecutable(stats == null ? void 0 : stats.mode)
2973
+ }))
2974
+ );
2516
2975
  }
2517
2976
 
2518
2977
  async function deserializeDirectoryContents(targetPath, files) {
@@ -2533,7 +2992,9 @@ const defaultClientFactory = async ({
2533
2992
  host = "github.com",
2534
2993
  token: providedToken
2535
2994
  }) => {
2536
- const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map(encodeURIComponent);
2995
+ const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map(
2996
+ encodeURIComponent
2997
+ );
2537
2998
  const octokitOptions = await getOctokitOptions({
2538
2999
  integrations,
2539
3000
  credentialsProvider: githubCredentialsProvider,
@@ -2627,7 +3088,9 @@ const createPublishGithubPullRequestAction = ({
2627
3088
  } = ctx.input;
2628
3089
  const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
2629
3090
  if (!owner) {
2630
- throw new errors.InputError(`No owner provided for host: ${host}, and repo ${repo}`);
3091
+ throw new errors.InputError(
3092
+ `No owner provided for host: ${host}, and repo ${repo}`
3093
+ );
2631
3094
  }
2632
3095
  const client = await clientFactory({
2633
3096
  integrations,
@@ -2641,14 +3104,16 @@ const createPublishGithubPullRequestAction = ({
2641
3104
  const directoryContents = await serializeDirectoryContents(fileRoot, {
2642
3105
  gitignore: true
2643
3106
  });
2644
- const files = Object.fromEntries(directoryContents.map((file) => [
2645
- targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path,
2646
- {
2647
- mode: file.executable ? "100755" : "100644",
2648
- encoding: "base64",
2649
- content: file.content.toString("base64")
2650
- }
2651
- ]));
3107
+ const files = Object.fromEntries(
3108
+ directoryContents.map((file) => [
3109
+ targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path,
3110
+ {
3111
+ mode: file.executable ? "100755" : "100644",
3112
+ encoding: "base64",
3113
+ content: file.content.toString("base64")
3114
+ }
3115
+ ])
3116
+ );
2652
3117
  try {
2653
3118
  const response = await client.createPullRequest({
2654
3119
  owner,
@@ -2758,11 +3223,15 @@ function createPublishGitlabAction(options) {
2758
3223
  } = ctx.input;
2759
3224
  const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
2760
3225
  if (!owner) {
2761
- throw new errors.InputError(`No owner provided for host: ${host}, and repo ${repo}`);
3226
+ throw new errors.InputError(
3227
+ `No owner provided for host: ${host}, and repo ${repo}`
3228
+ );
2762
3229
  }
2763
3230
  const integrationConfig = integrations.gitlab.byHost(host);
2764
3231
  if (!integrationConfig) {
2765
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
3232
+ throw new errors.InputError(
3233
+ `No matching integration configuration for host ${host}, please check your integrations config`
3234
+ );
2766
3235
  }
2767
3236
  if (!integrationConfig.config.token && !ctx.input.token) {
2768
3237
  throw new errors.InputError(`No token available for host ${host}`);
@@ -2858,10 +3327,21 @@ const createPublishGitlabMergeRequestAction = (options) => {
2858
3327
  type: "string",
2859
3328
  description: "The token to use for authorization to GitLab"
2860
3329
  },
3330
+ commitAction: {
3331
+ title: "Commit action",
3332
+ type: "string",
3333
+ enum: ["create", "update", "delete"],
3334
+ description: "The action to be used for git commit. Defaults to create."
3335
+ },
2861
3336
  removeSourceBranch: {
2862
3337
  title: "Delete source branch",
2863
3338
  type: "boolean",
2864
3339
  description: "Option to delete source branch once the MR has been merged. Default: false"
3340
+ },
3341
+ assignee: {
3342
+ title: "Merge Request Assignee",
3343
+ type: "string",
3344
+ description: "User this merge request will be assigned to"
2865
3345
  }
2866
3346
  }
2867
3347
  },
@@ -2897,7 +3377,9 @@ const createPublishGitlabMergeRequestAction = (options) => {
2897
3377
  const integrationConfig = integrations.gitlab.byHost(host);
2898
3378
  const destinationBranch = ctx.input.branchName;
2899
3379
  if (!integrationConfig) {
2900
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
3380
+ throw new errors.InputError(
3381
+ `No matching integration configuration for host ${host}, please check your integrations config`
3382
+ );
2901
3383
  }
2902
3384
  if (!integrationConfig.config.token && !ctx.input.token) {
2903
3385
  throw new errors.InputError(`No token available for host ${host}`);
@@ -2908,34 +3390,70 @@ const createPublishGitlabMergeRequestAction = (options) => {
2908
3390
  host: integrationConfig.config.baseUrl,
2909
3391
  [tokenType]: token
2910
3392
  });
2911
- const targetPath = backendCommon.resolveSafeChildPath(ctx.workspacePath, ctx.input.targetPath);
3393
+ const assignee = ctx.input.assignee;
3394
+ let assigneeId = void 0;
3395
+ if (assignee !== void 0) {
3396
+ try {
3397
+ const assigneeUser = await api.Users.username(assignee);
3398
+ assigneeId = assigneeUser[0].id;
3399
+ } catch (e) {
3400
+ ctx.logger.warn(
3401
+ `Failed to find gitlab user id for ${assignee}: ${e}. Proceeding with MR creation without an assignee.`
3402
+ );
3403
+ }
3404
+ }
3405
+ const targetPath = backendCommon.resolveSafeChildPath(
3406
+ ctx.workspacePath,
3407
+ ctx.input.targetPath
3408
+ );
2912
3409
  const fileContents = await serializeDirectoryContents(targetPath, {
2913
3410
  gitignore: true
2914
3411
  });
2915
- const actions = fileContents.map((file) => ({
2916
- action: "create",
2917
- filePath: path__default["default"].posix.join(ctx.input.targetPath, file.path),
2918
- encoding: "base64",
2919
- content: file.content.toString("base64"),
2920
- execute_filemode: file.executable
2921
- }));
3412
+ const actions = fileContents.map((file) => {
3413
+ var _a2;
3414
+ return {
3415
+ action: (_a2 = ctx.input.commitAction) != null ? _a2 : "create",
3416
+ filePath: path__default["default"].posix.join(ctx.input.targetPath, file.path),
3417
+ encoding: "base64",
3418
+ content: file.content.toString("base64"),
3419
+ execute_filemode: file.executable
3420
+ };
3421
+ });
2922
3422
  const projects = await api.Projects.show(projectPath);
2923
3423
  const { default_branch: defaultBranch } = projects;
2924
3424
  try {
2925
- await api.Branches.create(projectPath, destinationBranch, String(defaultBranch));
3425
+ await api.Branches.create(
3426
+ projectPath,
3427
+ destinationBranch,
3428
+ String(defaultBranch)
3429
+ );
2926
3430
  } catch (e) {
2927
3431
  throw new errors.InputError(`The branch creation failed ${e}`);
2928
3432
  }
2929
3433
  try {
2930
- await api.Commits.create(projectPath, destinationBranch, ctx.input.title, actions);
3434
+ await api.Commits.create(
3435
+ projectPath,
3436
+ destinationBranch,
3437
+ ctx.input.title,
3438
+ actions
3439
+ );
2931
3440
  } catch (e) {
2932
- throw new errors.InputError(`Committing the changes to ${destinationBranch} failed ${e}`);
3441
+ throw new errors.InputError(
3442
+ `Committing the changes to ${destinationBranch} failed ${e}`
3443
+ );
2933
3444
  }
2934
3445
  try {
2935
- const mergeRequestUrl = await api.MergeRequests.create(projectPath, destinationBranch, String(defaultBranch), ctx.input.title, {
2936
- description: ctx.input.description,
2937
- removeSourceBranch: ctx.input.removeSourceBranch ? ctx.input.removeSourceBranch : false
2938
- }).then((mergeRequest) => {
3446
+ const mergeRequestUrl = await api.MergeRequests.create(
3447
+ projectPath,
3448
+ destinationBranch,
3449
+ String(defaultBranch),
3450
+ ctx.input.title,
3451
+ {
3452
+ description: ctx.input.description,
3453
+ removeSourceBranch: ctx.input.removeSourceBranch ? ctx.input.removeSourceBranch : false,
3454
+ assigneeId
3455
+ }
3456
+ ).then((mergeRequest) => {
2939
3457
  return mergeRequest.web_url;
2940
3458
  });
2941
3459
  ctx.output("projectid", projectPath);
@@ -2971,6 +3489,10 @@ const createBuiltinActions = (options) => {
2971
3489
  integrations,
2972
3490
  config
2973
3491
  }),
3492
+ createPublishGerritReviewAction({
3493
+ integrations,
3494
+ config
3495
+ }),
2974
3496
  createPublishGithubAction({
2975
3497
  integrations,
2976
3498
  config,
@@ -3039,14 +3561,18 @@ class TemplateActionRegistry {
3039
3561
  }
3040
3562
  register(action) {
3041
3563
  if (this.actions.has(action.id)) {
3042
- throw new errors.ConflictError(`Template action with ID '${action.id}' has already been registered`);
3564
+ throw new errors.ConflictError(
3565
+ `Template action with ID '${action.id}' has already been registered`
3566
+ );
3043
3567
  }
3044
3568
  this.actions.set(action.id, action);
3045
3569
  }
3046
3570
  get(actionId) {
3047
3571
  const action = this.actions.get(actionId);
3048
3572
  if (!action) {
3049
- throw new errors.NotFoundError(`Template action with ID '${actionId}' is not registered.`);
3573
+ throw new errors.NotFoundError(
3574
+ `Template action with ID '${actionId}' is not registered.`
3575
+ );
3050
3576
  }
3051
3577
  return action;
3052
3578
  }
@@ -3055,7 +3581,10 @@ class TemplateActionRegistry {
3055
3581
  }
3056
3582
  }
3057
3583
 
3058
- const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-scaffolder-backend", "migrations");
3584
+ const migrationsDir = backendCommon.resolvePackagePath(
3585
+ "@backstage/plugin-scaffolder-backend",
3586
+ "migrations"
3587
+ );
3059
3588
  const parseSqlDateToIsoString = (input) => {
3060
3589
  if (typeof input === "string") {
3061
3590
  return luxon.DateTime.fromSQL(input, { zone: "UTC" }).toISO();
@@ -3170,10 +3699,14 @@ class DatabaseTaskStore {
3170
3699
  }
3171
3700
  }
3172
3701
  async listStaleTasks({ timeoutS }) {
3173
- const rawRows = await this.db("tasks").where("status", "processing").andWhere("last_heartbeat_at", "<=", this.db.client.config.client.includes("sqlite3") ? this.db.raw(`datetime('now', ?)`, [`-${timeoutS} seconds`]) : this.db.raw(`dateadd('second', ?, ?)`, [
3174
- `-${timeoutS}`,
3175
- this.db.fn.now()
3176
- ]));
3702
+ const rawRows = await this.db("tasks").where("status", "processing").andWhere(
3703
+ "last_heartbeat_at",
3704
+ "<=",
3705
+ this.db.client.config.client.includes("sqlite3") ? this.db.raw(`datetime('now', ?)`, [`-${timeoutS} seconds`]) : this.db.raw(`dateadd('second', ?, ?)`, [
3706
+ `-${timeoutS}`,
3707
+ this.db.fn.now()
3708
+ ])
3709
+ );
3177
3710
  const tasks = rawRows.map((row) => ({
3178
3711
  taskId: row.id
3179
3712
  }));
@@ -3188,7 +3721,9 @@ class DatabaseTaskStore {
3188
3721
  if (status === "failed" || status === "completed") {
3189
3722
  oldStatus = "processing";
3190
3723
  } else {
3191
- throw new Error(`Invalid status update of run '${taskId}' to status '${status}'`);
3724
+ throw new Error(
3725
+ `Invalid status update of run '${taskId}' to status '${status}'`
3726
+ );
3192
3727
  }
3193
3728
  await this.db.transaction(async (tx) => {
3194
3729
  const [task] = await tx("tasks").where({
@@ -3198,7 +3733,9 @@ class DatabaseTaskStore {
3198
3733
  throw new Error(`No task with taskId ${taskId} found`);
3199
3734
  }
3200
3735
  if (task.status !== oldStatus) {
3201
- throw new errors.ConflictError(`Refusing to update status of run '${taskId}' to status '${status}' as it is currently '${task.status}', expected '${oldStatus}'`);
3736
+ throw new errors.ConflictError(
3737
+ `Refusing to update status of run '${taskId}' to status '${status}' as it is currently '${task.status}', expected '${oldStatus}'`
3738
+ );
3202
3739
  }
3203
3740
  const updateCount = await tx("tasks").where({
3204
3741
  id: taskId,
@@ -3207,7 +3744,9 @@ class DatabaseTaskStore {
3207
3744
  status
3208
3745
  });
3209
3746
  if (updateCount !== 1) {
3210
- throw new errors.ConflictError(`Failed to update status to '${status}' for taskId ${taskId}`);
3747
+ throw new errors.ConflictError(
3748
+ `Failed to update status to '${status}' for taskId ${taskId}`
3749
+ );
3211
3750
  }
3212
3751
  await tx("task_events").insert({
3213
3752
  task_id: taskId,
@@ -3247,7 +3786,9 @@ class DatabaseTaskStore {
3247
3786
  createdAt: parseSqlDateToIsoString(event.created_at)
3248
3787
  };
3249
3788
  } catch (error) {
3250
- throw new Error(`Failed to parse event body from event taskId=${taskId} id=${event.id}, ${error}`);
3789
+ throw new Error(
3790
+ `Failed to parse event body from event taskId=${taskId} id=${event.id}, ${error}`
3791
+ );
3251
3792
  }
3252
3793
  });
3253
3794
  return { events };
@@ -3308,7 +3849,10 @@ class TaskManager {
3308
3849
  this.startTimeout();
3309
3850
  } catch (error) {
3310
3851
  this.isDone = true;
3311
- this.logger.error(`Heartbeat for task ${this.task.taskId} failed`, error);
3852
+ this.logger.error(
3853
+ `Heartbeat for task ${this.task.taskId} failed`,
3854
+ error
3855
+ );
3312
3856
  }
3313
3857
  }, 1e3);
3314
3858
  }
@@ -3329,7 +3873,9 @@ class StorageTaskBroker {
3329
3873
  }
3330
3874
  async list(options) {
3331
3875
  if (!this.storage.list) {
3332
- throw new Error("TaskStore does not implement the list method. Please implement the list method to be able to list tasks");
3876
+ throw new Error(
3877
+ "TaskStore does not implement the list method. Please implement the list method to be able to list tasks"
3878
+ );
3333
3879
  }
3334
3880
  return await this.storage.list({ createdBy: options == null ? void 0 : options.createdBy });
3335
3881
  }
@@ -3337,12 +3883,16 @@ class StorageTaskBroker {
3337
3883
  for (; ; ) {
3338
3884
  const pendingTask = await this.storage.claimTask();
3339
3885
  if (pendingTask) {
3340
- return TaskManager.create({
3341
- taskId: pendingTask.id,
3342
- spec: pendingTask.spec,
3343
- secrets: pendingTask.secrets,
3344
- createdBy: pendingTask.createdBy
3345
- }, this.storage, this.logger);
3886
+ return TaskManager.create(
3887
+ {
3888
+ taskId: pendingTask.id,
3889
+ spec: pendingTask.spec,
3890
+ secrets: pendingTask.secrets,
3891
+ createdBy: pendingTask.createdBy
3892
+ },
3893
+ this.storage,
3894
+ this.logger
3895
+ );
3346
3896
  }
3347
3897
  await this.waitForDispatch();
3348
3898
  }
@@ -3380,19 +3930,21 @@ class StorageTaskBroker {
3380
3930
  }
3381
3931
  async vacuumTasks(options) {
3382
3932
  const { tasks } = await this.storage.listStaleTasks(options);
3383
- await Promise.all(tasks.map(async (task) => {
3384
- try {
3385
- await this.storage.completeTask({
3386
- taskId: task.taskId,
3387
- status: "failed",
3388
- eventBody: {
3389
- message: "The task was cancelled because the task worker lost connection to the task broker"
3390
- }
3391
- });
3392
- } catch (error) {
3393
- this.logger.warn(`Failed to cancel task '${task.taskId}', ${error}`);
3394
- }
3395
- }));
3933
+ await Promise.all(
3934
+ tasks.map(async (task) => {
3935
+ try {
3936
+ await this.storage.completeTask({
3937
+ taskId: task.taskId,
3938
+ status: "failed",
3939
+ eventBody: {
3940
+ message: "The task was cancelled because the task worker lost connection to the task broker"
3941
+ }
3942
+ });
3943
+ } catch (error) {
3944
+ this.logger.warn(`Failed to cancel task '${task.taskId}', ${error}`);
3945
+ }
3946
+ })
3947
+ );
3396
3948
  }
3397
3949
  waitForDispatch() {
3398
3950
  return this.deferredDispatch.promise;
@@ -3413,10 +3965,12 @@ function generateExampleOutput(schema) {
3413
3965
  return examples[0];
3414
3966
  }
3415
3967
  if (schema.type === "object") {
3416
- return Object.fromEntries(Object.entries((_a = schema.properties) != null ? _a : {}).map(([key, value]) => [
3417
- key,
3418
- generateExampleOutput(value)
3419
- ]));
3968
+ return Object.fromEntries(
3969
+ Object.entries((_a = schema.properties) != null ? _a : {}).map(([key, value]) => [
3970
+ key,
3971
+ generateExampleOutput(value)
3972
+ ])
3973
+ );
3420
3974
  } else if (schema.type === "array") {
3421
3975
  const [firstSchema] = (_b = [schema.items]) == null ? void 0 : _b.flat();
3422
3976
  if (firstSchema) {
@@ -3443,7 +3997,11 @@ const createStepLogger = ({
3443
3997
  const metadata = { stepId: step.id };
3444
3998
  const taskLogger = winston__namespace.createLogger({
3445
3999
  level: process.env.LOG_LEVEL || "info",
3446
- format: winston__namespace.format.combine(winston__namespace.format.colorize(), winston__namespace.format.timestamp(), winston__namespace.format.simple()),
4000
+ format: winston__namespace.format.combine(
4001
+ winston__namespace.format.colorize(),
4002
+ winston__namespace.format.timestamp(),
4003
+ winston__namespace.format.simple()
4004
+ ),
3447
4005
  defaultMeta: {}
3448
4006
  });
3449
4007
  const streamLogger = new stream.PassThrough();
@@ -3463,13 +4021,17 @@ class NunjucksWorkflowRunner {
3463
4021
  isSingleTemplateString(input) {
3464
4022
  var _a, _b;
3465
4023
  const { parser, nodes } = nunjucks__default["default"];
3466
- const parsed = parser.parse(input, {}, {
3467
- autoescape: false,
3468
- tags: {
3469
- variableStart: "${{",
3470
- variableEnd: "}}"
4024
+ const parsed = parser.parse(
4025
+ input,
4026
+ {},
4027
+ {
4028
+ autoescape: false,
4029
+ tags: {
4030
+ variableStart: "${{",
4031
+ variableEnd: "}}"
4032
+ }
3471
4033
  }
3472
- });
4034
+ );
3473
4035
  return parsed.children.length === 1 && !(((_b = (_a = parsed.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b[0]) instanceof nodes.TemplateData);
3474
4036
  }
3475
4037
  render(input, context, renderTemplate) {
@@ -3478,7 +4040,10 @@ class NunjucksWorkflowRunner {
3478
4040
  if (typeof value === "string") {
3479
4041
  try {
3480
4042
  if (this.isSingleTemplateString(value)) {
3481
- const wrappedDumped = value.replace(/\${{(.+)}}/g, "${{ ( $1 ) | dump }}");
4043
+ const wrappedDumped = value.replace(
4044
+ /\${{(.+)}}/g,
4045
+ "${{ ( $1 ) | dump }}"
4046
+ );
3482
4047
  const templated2 = renderTemplate(wrappedDumped, context);
3483
4048
  if (templated2 === "") {
3484
4049
  return void 0;
@@ -3486,7 +4051,9 @@ class NunjucksWorkflowRunner {
3486
4051
  return JSON.parse(templated2);
3487
4052
  }
3488
4053
  } catch (ex) {
3489
- this.options.logger.error(`Failed to parse template string: ${value} with error ${ex.message}`);
4054
+ this.options.logger.error(
4055
+ `Failed to parse template string: ${value} with error ${ex.message}`
4056
+ );
3490
4057
  }
3491
4058
  const templated = renderTemplate(value, context);
3492
4059
  if (templated === "") {
@@ -3503,9 +4070,14 @@ class NunjucksWorkflowRunner {
3503
4070
  async execute(task) {
3504
4071
  var _a, _b, _c, _d, _e;
3505
4072
  if (!isValidTaskSpec(task.spec)) {
3506
- throw new errors.InputError("Wrong template version executed with the workflow engine");
4073
+ throw new errors.InputError(
4074
+ "Wrong template version executed with the workflow engine"
4075
+ );
3507
4076
  }
3508
- const workspacePath = path__default["default"].join(this.options.workingDirectory, await task.getWorkspaceName());
4077
+ const workspacePath = path__default["default"].join(
4078
+ this.options.workingDirectory,
4079
+ await task.getWorkspaceName()
4080
+ );
3509
4081
  const { integrations } = this.options;
3510
4082
  const renderTemplate = await SecureTemplater.loadRenderer({
3511
4083
  parseRepoUrl(url) {
@@ -3515,7 +4087,9 @@ class NunjucksWorkflowRunner {
3515
4087
  });
3516
4088
  try {
3517
4089
  await fs__default["default"].ensureDir(workspacePath);
3518
- await task.emitLog(`Starting up task with ${task.spec.steps.length} steps`);
4090
+ await task.emitLog(
4091
+ `Starting up task with ${task.spec.steps.length} steps`
4092
+ );
3519
4093
  const context = {
3520
4094
  parameters: task.spec.parameters,
3521
4095
  steps: {},
@@ -3524,9 +4098,16 @@ class NunjucksWorkflowRunner {
3524
4098
  for (const step of task.spec.steps) {
3525
4099
  try {
3526
4100
  if (step.if) {
3527
- const ifResult = await this.render(step.if, context, renderTemplate);
4101
+ const ifResult = await this.render(
4102
+ step.if,
4103
+ context,
4104
+ renderTemplate
4105
+ );
3528
4106
  if (!isTruthy(ifResult)) {
3529
- await task.emitLog(`Skipping step ${step.id} because it's if condition was false`, { stepId: step.id, status: "skipped" });
4107
+ await task.emitLog(
4108
+ `Skipping step ${step.id} because it's if condition was false`,
4109
+ { stepId: step.id, status: "skipped" }
4110
+ );
3530
4111
  continue;
3531
4112
  }
3532
4113
  }
@@ -3537,10 +4118,13 @@ class NunjucksWorkflowRunner {
3537
4118
  const action = this.options.actionRegistry.get(step.action);
3538
4119
  const { taskLogger, streamLogger } = createStepLogger({ task, step });
3539
4120
  if (task.isDryRun && !action.supportsDryRun) {
3540
- task.emitLog(`Skipping because ${action.id} does not support dry-run`, {
3541
- stepId: step.id,
3542
- status: "skipped"
3543
- });
4121
+ task.emitLog(
4122
+ `Skipping because ${action.id} does not support dry-run`,
4123
+ {
4124
+ stepId: step.id,
4125
+ status: "skipped"
4126
+ }
4127
+ );
3544
4128
  const outputSchema = (_a = action.schema) == null ? void 0 : _a.output;
3545
4129
  if (outputSchema) {
3546
4130
  context.steps[step.id] = {
@@ -3551,12 +4135,21 @@ class NunjucksWorkflowRunner {
3551
4135
  }
3552
4136
  continue;
3553
4137
  }
3554
- const input = (_c = step.input && this.render(step.input, { ...context, secrets: (_b = task.secrets) != null ? _b : {} }, renderTemplate)) != null ? _c : {};
4138
+ const input = (_c = step.input && this.render(
4139
+ step.input,
4140
+ { ...context, secrets: (_b = task.secrets) != null ? _b : {} },
4141
+ renderTemplate
4142
+ )) != null ? _c : {};
3555
4143
  if ((_d = action.schema) == null ? void 0 : _d.input) {
3556
- const validateResult = jsonschema.validate(input, action.schema.input);
4144
+ const validateResult = jsonschema.validate(
4145
+ input,
4146
+ action.schema.input
4147
+ );
3557
4148
  if (!validateResult.valid) {
3558
4149
  const errors$1 = validateResult.errors.join(", ");
3559
- throw new errors.InputError(`Invalid input passed to action ${action.id}, ${errors$1}`);
4150
+ throw new errors.InputError(
4151
+ `Invalid input passed to action ${action.id}, ${errors$1}`
4152
+ );
3560
4153
  }
3561
4154
  }
3562
4155
  const tmpDirs = new Array();
@@ -3568,7 +4161,9 @@ class NunjucksWorkflowRunner {
3568
4161
  logStream: streamLogger,
3569
4162
  workspacePath,
3570
4163
  createTemporaryDirectory: async () => {
3571
- const tmpDir = await fs__default["default"].mkdtemp(`${workspacePath}_step-${step.id}-`);
4164
+ const tmpDir = await fs__default["default"].mkdtemp(
4165
+ `${workspacePath}_step-${step.id}-`
4166
+ );
3572
4167
  tmpDirs.push(tmpDir);
3573
4168
  return tmpDir;
3574
4169
  },
@@ -3639,9 +4234,13 @@ class TaskWorker {
3639
4234
  async runOneTask(task) {
3640
4235
  try {
3641
4236
  if (task.spec.apiVersion !== "scaffolder.backstage.io/v1beta3") {
3642
- throw new Error(`Unsupported Template apiVersion ${task.spec.apiVersion}`);
4237
+ throw new Error(
4238
+ `Unsupported Template apiVersion ${task.spec.apiVersion}`
4239
+ );
3643
4240
  }
3644
- const { output } = await this.options.runners.workflowRunner.execute(task);
4241
+ const { output } = await this.options.runners.workflowRunner.execute(
4242
+ task
4243
+ );
3645
4244
  await task.complete("completed", { output });
3646
4245
  } catch (error) {
3647
4246
  errors.assertError(error);
@@ -3688,7 +4287,10 @@ function createDryRunner(options) {
3688
4287
  });
3689
4288
  const dryRunId = uuid.v4();
3690
4289
  const log = new Array();
3691
- const contentsPath = backendCommon.resolveSafeChildPath(options.workingDirectory, `dry-run-content-${dryRunId}`);
4290
+ const contentsPath = backendCommon.resolveSafeChildPath(
4291
+ options.workingDirectory,
4292
+ `dry-run-content-${dryRunId}`
4293
+ );
3692
4294
  try {
3693
4295
  await deserializeDirectoryContents(contentsPath, input.directoryContents);
3694
4296
  const result = await workflowRunner.execute({
@@ -3704,7 +4306,9 @@ function createDryRunner(options) {
3704
4306
  ],
3705
4307
  templateInfo: {
3706
4308
  entityRef: "template:default/dry-run",
3707
- baseUrl: url.pathToFileURL(backendCommon.resolveSafeChildPath(contentsPath, "template.yaml")).toString()
4309
+ baseUrl: url.pathToFileURL(
4310
+ backendCommon.resolveSafeChildPath(contentsPath, "template.yaml")
4311
+ ).toString()
3708
4312
  }
3709
4313
  },
3710
4314
  secrets: input.secrets,
@@ -3751,7 +4355,9 @@ async function getWorkingDirectory(config, logger) {
3751
4355
  logger.info(`using working directory: ${workingDirectory}`);
3752
4356
  } catch (err) {
3753
4357
  errors.assertError(err);
3754
- logger.error(`working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`);
4358
+ logger.error(
4359
+ `working directory ${workingDirectory} ${err.code === "ENOENT" ? "does not exist" : "is not writable"}`
4360
+ );
3755
4361
  throw err;
3756
4362
  }
3757
4363
  return workingDirectory;
@@ -3780,7 +4386,9 @@ async function findTemplate(options) {
3780
4386
  }
3781
4387
  const template = await catalogApi.getEntityByRef(entityRef, { token });
3782
4388
  if (!template) {
3783
- throw new errors.NotFoundError(`Template ${catalogModel.stringifyEntityRef(entityRef)} not found`);
4389
+ throw new errors.NotFoundError(
4390
+ `Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
4391
+ );
3784
4392
  }
3785
4393
  return template;
3786
4394
  }
@@ -3842,31 +4450,38 @@ async function createRouter(options) {
3842
4450
  workingDirectory,
3843
4451
  additionalTemplateFilters
3844
4452
  });
3845
- router.get("/v2/templates/:namespace/:kind/:name/parameter-schema", async (req, res) => {
3846
- var _a, _b;
3847
- const { namespace, kind, name } = req.params;
3848
- const { token } = parseBearerToken(req.headers.authorization);
3849
- const template = await findTemplate({
3850
- catalogApi: catalogClient,
3851
- entityRef: { kind, namespace, name },
3852
- token
3853
- });
3854
- if (isSupportedTemplate(template)) {
3855
- const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
3856
- res.json({
3857
- title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
3858
- steps: parameters.map((schema) => {
3859
- var _a2;
3860
- return {
3861
- title: (_a2 = schema.title) != null ? _a2 : "Fill in template parameters",
3862
- schema
3863
- };
3864
- })
4453
+ router.get(
4454
+ "/v2/templates/:namespace/:kind/:name/parameter-schema",
4455
+ async (req, res) => {
4456
+ var _a, _b;
4457
+ const { namespace, kind, name } = req.params;
4458
+ const { token } = parseBearerToken(req.headers.authorization);
4459
+ const template = await findTemplate({
4460
+ catalogApi: catalogClient,
4461
+ entityRef: { kind, namespace, name },
4462
+ token
3865
4463
  });
3866
- } else {
3867
- throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
4464
+ if (isSupportedTemplate(template)) {
4465
+ const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
4466
+ res.json({
4467
+ title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
4468
+ description: template.metadata.description,
4469
+ steps: parameters.map((schema) => {
4470
+ var _a2;
4471
+ return {
4472
+ title: (_a2 = schema.title) != null ? _a2 : "Please enter the following information",
4473
+ description: schema.description,
4474
+ schema
4475
+ };
4476
+ })
4477
+ });
4478
+ } else {
4479
+ throw new errors.InputError(
4480
+ `Unsupported apiVersion field in schema entity, ${template.apiVersion}`
4481
+ );
4482
+ }
3868
4483
  }
3869
- }).get("/v2/actions", async (_req, res) => {
4484
+ ).get("/v2/actions", async (_req, res) => {
3870
4485
  const actionsList = actionRegistry.list().map((action) => {
3871
4486
  return {
3872
4487
  id: action.id,
@@ -3881,8 +4496,15 @@ async function createRouter(options) {
3881
4496
  const { kind, namespace, name } = catalogModel.parseEntityRef(templateRef, {
3882
4497
  defaultKind: "template"
3883
4498
  });
3884
- const { token, entityRef: userEntityRef } = parseBearerToken(req.headers.authorization);
4499
+ const { token, entityRef: userEntityRef } = parseBearerToken(
4500
+ req.headers.authorization
4501
+ );
3885
4502
  const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
4503
+ let auditLog = `Scaffolding task for ${templateRef}`;
4504
+ if (userEntityRef) {
4505
+ auditLog += ` created by ${userEntityRef}`;
4506
+ }
4507
+ logger.info(auditLog);
3886
4508
  const values = req.body.values;
3887
4509
  const template = await findTemplate({
3888
4510
  catalogApi: catalogClient,
@@ -3890,7 +4512,9 @@ async function createRouter(options) {
3890
4512
  token
3891
4513
  });
3892
4514
  if (!isSupportedTemplate(template)) {
3893
- throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
4515
+ throw new errors.InputError(
4516
+ `Unsupported apiVersion field in schema entity, ${template.apiVersion}`
4517
+ );
3894
4518
  }
3895
4519
  for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
3896
4520
  const result2 = jsonschema.validate(values, parameters);
@@ -3940,7 +4564,9 @@ async function createRouter(options) {
3940
4564
  throw new errors.InputError("createdBy query parameter must be a string");
3941
4565
  }
3942
4566
  if (!taskBroker.list) {
3943
- throw new Error("TaskBroker does not support listing tasks, please implement the list method on the TaskBroker.");
4567
+ throw new Error(
4568
+ "TaskBroker does not support listing tasks, please implement the list method on the TaskBroker."
4569
+ );
3944
4570
  }
3945
4571
  const tasks = await taskBroker.list({
3946
4572
  createdBy: userEntityRef
@@ -3965,23 +4591,30 @@ async function createRouter(options) {
3965
4591
  });
3966
4592
  const subscription = taskBroker.event$({ taskId, after }).subscribe({
3967
4593
  error: (error) => {
3968
- logger.error(`Received error from event stream when observing taskId '${taskId}', ${error}`);
4594
+ logger.error(
4595
+ `Received error from event stream when observing taskId '${taskId}', ${error}`
4596
+ );
4597
+ res.end();
3969
4598
  },
3970
4599
  next: ({ events }) => {
3971
4600
  var _a;
3972
4601
  let shouldUnsubscribe = false;
3973
4602
  for (const event of events) {
3974
- res.write(`event: ${event.type}
4603
+ res.write(
4604
+ `event: ${event.type}
3975
4605
  data: ${JSON.stringify(event)}
3976
4606
 
3977
- `);
4607
+ `
4608
+ );
3978
4609
  if (event.type === "completion") {
3979
4610
  shouldUnsubscribe = true;
3980
4611
  }
3981
4612
  }
3982
4613
  (_a = res.flush) == null ? void 0 : _a.call(res);
3983
- if (shouldUnsubscribe)
4614
+ if (shouldUnsubscribe) {
3984
4615
  subscription.unsubscribe();
4616
+ res.end();
4617
+ }
3985
4618
  }
3986
4619
  });
3987
4620
  req.on("close", () => {
@@ -3996,7 +4629,9 @@ data: ${JSON.stringify(event)}
3996
4629
  }, 3e4);
3997
4630
  const subscription = taskBroker.event$({ taskId, after }).subscribe({
3998
4631
  error: (error) => {
3999
- logger.error(`Received error from event stream when observing taskId '${taskId}', ${error}`);
4632
+ logger.error(
4633
+ `Received error from event stream when observing taskId '${taskId}', ${error}`
4634
+ );
4000
4635
  },
4001
4636
  next: ({ events }) => {
4002
4637
  clearTimeout(timeout);
@@ -4014,7 +4649,9 @@ data: ${JSON.stringify(event)}
4014
4649
  template: zod.z.unknown(),
4015
4650
  values: zod.z.record(zod.z.unknown()),
4016
4651
  secrets: zod.z.record(zod.z.string()).optional(),
4017
- directoryContents: zod.z.array(zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() }))
4652
+ directoryContents: zod.z.array(
4653
+ zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() })
4654
+ )
4018
4655
  });
4019
4656
  const body = await bodySchema.parseAsync(req.body).catch((e) => {
4020
4657
  throw new errors.InputError(`Malformed request: ${e}`);
@@ -4081,7 +4718,9 @@ function parseBearerToken(header) {
4081
4718
  throw new TypeError("Expected Bearer with JWT");
4082
4719
  }
4083
4720
  const [_header, rawPayload, _signature] = token.split(".");
4084
- const payload = JSON.parse(Buffer.from(rawPayload, "base64").toString());
4721
+ const payload = JSON.parse(
4722
+ Buffer.from(rawPayload, "base64").toString()
4723
+ );
4085
4724
  if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
4086
4725
  throw new TypeError("Malformed JWT payload");
4087
4726
  }
@@ -4120,24 +4759,28 @@ class ScaffolderEntitiesProcessor {
4120
4759
  defaultKind: "Group",
4121
4760
  defaultNamespace: selfRef.namespace
4122
4761
  });
4123
- emit(pluginCatalogBackend.processingResult.relation({
4124
- source: selfRef,
4125
- type: catalogModel.RELATION_OWNED_BY,
4126
- target: {
4127
- kind: targetRef.kind,
4128
- namespace: targetRef.namespace,
4129
- name: targetRef.name
4130
- }
4131
- }));
4132
- emit(pluginCatalogBackend.processingResult.relation({
4133
- source: {
4134
- kind: targetRef.kind,
4135
- namespace: targetRef.namespace,
4136
- name: targetRef.name
4137
- },
4138
- type: catalogModel.RELATION_OWNER_OF,
4139
- target: selfRef
4140
- }));
4762
+ emit(
4763
+ pluginCatalogBackend.processingResult.relation({
4764
+ source: selfRef,
4765
+ type: catalogModel.RELATION_OWNED_BY,
4766
+ target: {
4767
+ kind: targetRef.kind,
4768
+ namespace: targetRef.namespace,
4769
+ name: targetRef.name
4770
+ }
4771
+ })
4772
+ );
4773
+ emit(
4774
+ pluginCatalogBackend.processingResult.relation({
4775
+ source: {
4776
+ kind: targetRef.kind,
4777
+ namespace: targetRef.namespace,
4778
+ name: targetRef.name
4779
+ },
4780
+ type: catalogModel.RELATION_OWNER_OF,
4781
+ target: selfRef
4782
+ })
4783
+ );
4141
4784
  }
4142
4785
  }
4143
4786
  return entity;
@@ -4153,7 +4796,9 @@ const scaffolderCatalogModule = backendPluginApi.createBackendModule({
4153
4796
  catalogProcessingExtensionPoint: pluginCatalogNode.catalogProcessingExtentionPoint
4154
4797
  },
4155
4798
  async init({ catalogProcessingExtensionPoint }) {
4156
- catalogProcessingExtensionPoint.addProcessor(new ScaffolderEntitiesProcessor());
4799
+ catalogProcessingExtensionPoint.addProcessor(
4800
+ new ScaffolderEntitiesProcessor()
4801
+ );
4157
4802
  }
4158
4803
  });
4159
4804
  }
@@ -4183,6 +4828,7 @@ exports.createPublishBitbucketCloudAction = createPublishBitbucketCloudAction;
4183
4828
  exports.createPublishBitbucketServerAction = createPublishBitbucketServerAction;
4184
4829
  exports.createPublishFileAction = createPublishFileAction;
4185
4830
  exports.createPublishGerritAction = createPublishGerritAction;
4831
+ exports.createPublishGerritReviewAction = createPublishGerritReviewAction;
4186
4832
  exports.createPublishGithubAction = createPublishGithubAction;
4187
4833
  exports.createPublishGithubPullRequestAction = createPublishGithubPullRequestAction;
4188
4834
  exports.createPublishGitlabAction = createPublishGitlabAction;