@backstage/plugin-scaffolder-backend 1.4.0-next.2 → 1.5.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs.js CHANGED
@@ -36,6 +36,8 @@ var zod = require('zod');
36
36
  var url = require('url');
37
37
  var os = require('os');
38
38
  var pluginCatalogBackend = require('@backstage/plugin-catalog-backend');
39
+ var backendPluginApi = require('@backstage/backend-plugin-api');
40
+ var pluginCatalogNode = require('@backstage/plugin-catalog-node');
39
41
 
40
42
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
41
43
 
@@ -145,7 +147,9 @@ function createCatalogRegisterAction(options) {
145
147
  const { repoContentsUrl, catalogInfoPath = "/catalog-info.yaml" } = input;
146
148
  const integration = integrations.byUrl(repoContentsUrl);
147
149
  if (!integration) {
148
- throw new errors.InputError(`No integration found for host ${repoContentsUrl}`);
150
+ throw new errors.InputError(
151
+ `No integration found for host ${repoContentsUrl}`
152
+ );
149
153
  }
150
154
  catalogInfoUrl = integration.resolveUrl({
151
155
  base: repoContentsUrl,
@@ -153,22 +157,32 @@ function createCatalogRegisterAction(options) {
153
157
  });
154
158
  }
155
159
  ctx.logger.info(`Registering ${catalogInfoUrl} in the catalog`);
156
- await catalogClient.addLocation({
157
- type: "url",
158
- target: catalogInfoUrl
159
- }, ((_a = ctx.secrets) == null ? void 0 : _a.backstageToken) ? { token: ctx.secrets.backstageToken } : {});
160
- try {
161
- const result = await catalogClient.addLocation({
162
- dryRun: true,
160
+ await catalogClient.addLocation(
161
+ {
163
162
  type: "url",
164
163
  target: catalogInfoUrl
165
- }, ((_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
+ );
166
176
  if (result.entities.length > 0) {
167
177
  const { entities } = result;
168
178
  let entity;
169
- 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
+ );
170
182
  if (!entity) {
171
- entity = entities.find((e) => !e.metadata.name.startsWith("generated-"));
183
+ entity = entities.find(
184
+ (e) => !e.metadata.name.startsWith("generated-")
185
+ );
172
186
  }
173
187
  if (!entity) {
174
188
  entity = entities[0];
@@ -211,7 +225,10 @@ function createCatalogWriteAction() {
211
225
  ctx.logStream.write(`Writing catalog-info.yaml`);
212
226
  const { filePath, entity } = ctx.input;
213
227
  const path = filePath != null ? filePath : "catalog-info.yaml";
214
- 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
+ );
215
232
  }
216
233
  });
217
234
  }
@@ -247,18 +264,22 @@ function createDebugLogAction() {
247
264
  }
248
265
  if ((_b = ctx.input) == null ? void 0 : _b.listWorkspace) {
249
266
  const files = await recursiveReadDir(ctx.workspacePath);
250
- ctx.logStream.write(`Workspace:
251
- ${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
+ );
252
271
  }
253
272
  }
254
273
  });
255
274
  }
256
275
  async function recursiveReadDir(dir) {
257
276
  const subdirs = await fs.readdir(dir);
258
- const files = await Promise.all(subdirs.map(async (subdir) => {
259
- const res = path.join(dir, subdir);
260
- return (await fs.stat(res)).isDirectory() ? recursiveReadDir(res) : [res];
261
- }));
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
+ );
262
283
  return files.reduce((a, f) => a.concat(f), []);
263
284
  }
264
285
 
@@ -293,7 +314,9 @@ async function fetchContents({
293
314
  base: baseUrl
294
315
  });
295
316
  } else {
296
- 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
+ );
297
320
  }
298
321
  const res = await reader.readTree(readUrl);
299
322
  await fs__default["default"].ensureDir(outputPath);
@@ -422,13 +445,21 @@ class SecureTemplater {
422
445
  sandbox.parseRepoUrl = (url) => JSON.stringify(parseRepoUrl(url));
423
446
  }
424
447
  if (additionalTemplateFilters) {
425
- sandbox.additionalTemplateFilters = Object.fromEntries(Object.entries(additionalTemplateFilters).filter(([_, filterFunction]) => !!filterFunction).map(([filterName, filterFunction]) => [
426
- filterName,
427
- (...args) => JSON.stringify(filterFunction(...args))
428
- ]));
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
+ );
429
454
  }
430
455
  const vm = new vm2.VM({ sandbox });
431
- 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
+ );
432
463
  vm.run(mkScript(nunjucksSource));
433
464
  const render = (template, values) => {
434
465
  if (!vm) {
@@ -471,13 +502,21 @@ function createFetchTemplateAction(options) {
471
502
  type: "object"
472
503
  },
473
504
  copyWithoutRender: {
474
- title: "Copy Without Render",
505
+ title: "[Deprecated] Copy Without Render",
475
506
  description: "An array of glob patterns. Any files or directories which match are copied without being processed as templates.",
476
507
  type: "array",
477
508
  items: {
478
509
  type: "string"
479
510
  }
480
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
+ },
481
520
  cookiecutterCompat: {
482
521
  title: "Cookiecutter compatibility mode",
483
522
  description: "Enable features to maximise compatibility with templates built for fetch:cookiecutter",
@@ -499,11 +538,32 @@ function createFetchTemplateAction(options) {
499
538
  const templateDir = backendCommon.resolveSafeChildPath(workDir, "template");
500
539
  const targetPath = (_a = ctx.input.targetPath) != null ? _a : "./";
501
540
  const outputDir = backendCommon.resolveSafeChildPath(ctx.workspacePath, targetPath);
502
- if (ctx.input.copyWithoutRender && !Array.isArray(ctx.input.copyWithoutRender)) {
503
- 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
+ );
545
+ }
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
+ );
504
562
  }
505
- if (ctx.input.templateFileExtension && (ctx.input.copyWithoutRender || ctx.input.cookiecutterCompat)) {
506
- throw new errors.InputError("Fetch action input extension incompatible with copyWithoutRender and cookiecutterCompat");
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
+ );
507
567
  }
508
568
  let extension = false;
509
569
  if (ctx.input.templateFileExtension) {
@@ -527,58 +587,84 @@ function createFetchTemplateAction(options) {
527
587
  markDirectories: true,
528
588
  followSymbolicLinks: false
529
589
  });
530
- const nonTemplatedEntries = new Set((await Promise.all((ctx.input.copyWithoutRender || []).map((pattern) => globby__default["default"](pattern, {
531
- cwd: templateDir,
532
- dot: true,
533
- onlyFiles: false,
534
- markDirectories: true,
535
- followSymbolicLinks: false
536
- })))).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
+ );
537
603
  const { cookiecutterCompat, values } = ctx.input;
538
604
  const context = {
539
605
  [cookiecutterCompat ? "cookiecutter" : "values"]: values
540
606
  };
541
- 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
+ );
542
611
  const renderTemplate = await SecureTemplater.loadRenderer({
543
612
  cookiecutterCompat: ctx.input.cookiecutterCompat,
544
613
  additionalTemplateFilters
545
614
  });
546
615
  for (const location of allEntriesInTemplate) {
547
- let renderFilename;
548
616
  let renderContents;
549
617
  let localOutputPath = location;
550
618
  if (extension) {
551
- renderFilename = true;
552
619
  renderContents = path.extname(localOutputPath) === extension;
553
620
  if (renderContents) {
554
621
  localOutputPath = localOutputPath.slice(0, -extension.length);
555
622
  }
623
+ localOutputPath = renderTemplate(localOutputPath, context);
556
624
  } else {
557
- 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
+ }
558
631
  }
559
- if (renderFilename) {
560
- localOutputPath = renderTemplate(localOutputPath, context);
632
+ if (containsSkippedContent(localOutputPath)) {
633
+ continue;
561
634
  }
562
635
  const outputPath = backendCommon.resolveSafeChildPath(outputDir, localOutputPath);
563
- if (outputDir === outputPath) {
636
+ if (fs__default["default"].existsSync(outputPath)) {
564
637
  continue;
565
638
  }
566
639
  if (!renderContents && !extension) {
567
- ctx.logger.info(`Copying file/directory ${location} without processing.`);
640
+ ctx.logger.info(
641
+ `Copying file/directory ${location} without processing.`
642
+ );
568
643
  }
569
644
  if (location.endsWith("/")) {
570
- ctx.logger.info(`Writing directory ${location} to template output path.`);
645
+ ctx.logger.info(
646
+ `Writing directory ${location} to template output path.`
647
+ );
571
648
  await fs__default["default"].ensureDir(outputPath);
572
649
  } else {
573
650
  const inputFilePath = backendCommon.resolveSafeChildPath(templateDir, location);
574
- if (await isbinaryfile.isBinaryFile(inputFilePath)) {
575
- ctx.logger.info(`Copying binary file ${location} to template output path.`);
651
+ const stats = await fs__default["default"].promises.lstat(inputFilePath);
652
+ if (stats.isSymbolicLink() || await isbinaryfile.isBinaryFile(inputFilePath)) {
653
+ ctx.logger.info(
654
+ `Copying file binary or symbolic link at ${location}, to template output path.`
655
+ );
576
656
  await fs__default["default"].copy(inputFilePath, outputPath);
577
657
  } else {
578
658
  const statsObj = await fs__default["default"].stat(inputFilePath);
579
- 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
+ );
580
662
  const inputFileContents = await fs__default["default"].readFile(inputFilePath, "utf-8");
581
- 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
+ );
582
668
  }
583
669
  }
584
670
  }
@@ -586,6 +672,9 @@ function createFetchTemplateAction(options) {
586
672
  }
587
673
  });
588
674
  }
675
+ function containsSkippedContent(localOutputPath) {
676
+ return localOutputPath === "" || path__default["default"].isAbsolute(localOutputPath) || localOutputPath.includes(`${path__default["default"].sep}${path__default["default"].sep}`);
677
+ }
589
678
 
590
679
  const createFilesystemDeleteAction = () => {
591
680
  return createTemplateAction({
@@ -672,15 +761,23 @@ const createFilesystemRenameAction = () => {
672
761
  if (!file.from || !file.to) {
673
762
  throw new errors.InputError("each file must have a from and to property");
674
763
  }
675
- const sourceFilepath = backendCommon.resolveSafeChildPath(ctx.workspacePath, file.from);
764
+ const sourceFilepath = backendCommon.resolveSafeChildPath(
765
+ ctx.workspacePath,
766
+ file.from
767
+ );
676
768
  const destFilepath = backendCommon.resolveSafeChildPath(ctx.workspacePath, file.to);
677
769
  try {
678
770
  await fs__default["default"].move(sourceFilepath, destFilepath, {
679
771
  overwrite: (_b = file.overwrite) != null ? _b : false
680
772
  });
681
- ctx.logger.info(`File ${sourceFilepath} renamed to ${destFilepath} successfully`);
773
+ ctx.logger.info(
774
+ `File ${sourceFilepath} renamed to ${destFilepath} successfully`
775
+ );
682
776
  } catch (err) {
683
- 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
+ );
684
781
  throw err;
685
782
  }
686
783
  }
@@ -690,7 +787,10 @@ const createFilesystemRenameAction = () => {
690
787
 
691
788
  const getRepoSourceDirectory = (workspacePath, sourcePath) => {
692
789
  if (sourcePath) {
693
- const safeSuffix = path.normalize(sourcePath).replace(/^(\.\.(\/|\\|$))+/, "");
790
+ const safeSuffix = path.normalize(sourcePath).replace(
791
+ /^(\.\.(\/|\\|$))+/,
792
+ ""
793
+ );
694
794
  const path$1 = path.join(workspacePath, safeSuffix);
695
795
  if (!backendCommon.isChildPath(workspacePath, path$1)) {
696
796
  throw new Error("Invalid source path");
@@ -705,7 +805,9 @@ const parseRepoUrl = (repoUrl, integrations) => {
705
805
  try {
706
806
  parsed = new URL(`https://${repoUrl}`);
707
807
  } catch (error) {
708
- 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
+ );
709
811
  }
710
812
  const host = parsed.host;
711
813
  const owner = (_a = parsed.searchParams.get("owner")) != null ? _a : void 0;
@@ -714,25 +816,35 @@ const parseRepoUrl = (repoUrl, integrations) => {
714
816
  const project = (_d = parsed.searchParams.get("project")) != null ? _d : void 0;
715
817
  const type = (_e = integrations.byHost(host)) == null ? void 0 : _e.type;
716
818
  if (!type) {
717
- 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
+ );
718
822
  }
719
823
  if (type === "bitbucket") {
720
824
  if (host === "bitbucket.org") {
721
825
  if (!workspace) {
722
- 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
+ );
723
829
  }
724
830
  }
725
831
  if (!project) {
726
- 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
+ );
727
835
  }
728
836
  } else {
729
- if (!owner) {
730
- 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
+ );
731
841
  }
732
842
  }
733
843
  const repo = parsed.searchParams.get("repo");
734
844
  if (!repo) {
735
- 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
+ );
736
848
  }
737
849
  return { host, owner, repo, organization, workspace, project };
738
850
  };
@@ -757,7 +869,9 @@ const executeShellCommand = async (options) => {
757
869
  });
758
870
  process.on("close", (code) => {
759
871
  if (code !== 0) {
760
- return reject(new Error(`Command ${command} failed, exit code: ${code}`));
872
+ return reject(
873
+ new Error(`Command ${command} failed, exit code: ${code}`)
874
+ );
761
875
  }
762
876
  return resolve();
763
877
  });
@@ -774,8 +888,7 @@ async function initRepoAndPush({
774
888
  }) {
775
889
  var _a, _b;
776
890
  const git = backendCommon.Git.fromAuth({
777
- username: auth.username,
778
- password: auth.password,
891
+ ...auth,
779
892
  logger
780
893
  });
781
894
  await git.init({
@@ -803,6 +916,39 @@ async function initRepoAndPush({
803
916
  remote: "origin"
804
917
  });
805
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
+ }
806
952
  const enableBranchProtectionOnDefaultRepoBranch = async ({
807
953
  repoName,
808
954
  client,
@@ -810,7 +956,8 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
810
956
  logger,
811
957
  requireCodeOwnerReviews,
812
958
  requiredStatusCheckContexts = [],
813
- defaultBranch = "master"
959
+ defaultBranch = "master",
960
+ enforceAdmins = true
814
961
  }) => {
815
962
  const tryOnce = async () => {
816
963
  try {
@@ -826,7 +973,7 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
826
973
  contexts: requiredStatusCheckContexts
827
974
  },
828
975
  restrictions: null,
829
- enforce_admins: true,
976
+ enforce_admins: enforceAdmins,
830
977
  required_pull_request_reviews: {
831
978
  required_approving_review_count: 1,
832
979
  require_code_owner_reviews: requireCodeOwnerReviews
@@ -834,8 +981,12 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
834
981
  });
835
982
  } catch (e) {
836
983
  errors.assertError(e);
837
- if (e.message.includes("Upgrade to GitHub Pro or make this repository public to enable this feature")) {
838
- 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
+ );
839
990
  } else {
840
991
  throw e;
841
992
  }
@@ -877,10 +1028,14 @@ async function getOctokitOptions(options) {
877
1028
  }
878
1029
  const githubCredentialsProvider = credentialsProvider != null ? credentialsProvider : integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
879
1030
  const { token: credentialProviderToken } = await githubCredentialsProvider.getCredentials({
880
- url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
1031
+ url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(
1032
+ repo
1033
+ )}`
881
1034
  });
882
1035
  if (!credentialProviderToken) {
883
- 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
+ );
884
1039
  }
885
1040
  return {
886
1041
  auth: credentialProviderToken,
@@ -917,9 +1072,13 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
917
1072
  } catch (e) {
918
1073
  errors.assertError(e);
919
1074
  if (e.message === "Resource not accessible by integration") {
920
- 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
+ );
921
1078
  }
922
- 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
+ );
923
1082
  }
924
1083
  if (access == null ? void 0 : access.startsWith(`${owner}/`)) {
925
1084
  const [, team] = access.split("/");
@@ -960,7 +1119,9 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
960
1119
  } catch (e) {
961
1120
  errors.assertError(e);
962
1121
  const name = extractCollaboratorName(collaborator);
963
- logger.warn(`Skipping ${collaborator.access} access for ${name}, ${e.message}`);
1122
+ logger.warn(
1123
+ `Skipping ${collaborator.access} access for ${name}, ${e.message}`
1124
+ );
964
1125
  }
965
1126
  }
966
1127
  }
@@ -978,7 +1139,7 @@ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, r
978
1139
  }
979
1140
  return newRepo;
980
1141
  }
981
- 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) {
982
1143
  const gitAuthorInfo = {
983
1144
  name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
984
1145
  email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
@@ -1005,11 +1166,14 @@ async function initRepoPushAndProtect(remoteUrl, password, workspacePath, source
1005
1166
  logger,
1006
1167
  defaultBranch,
1007
1168
  requireCodeOwnerReviews,
1008
- requiredStatusCheckContexts
1169
+ requiredStatusCheckContexts,
1170
+ enforceAdmins: protectEnforceAdmins
1009
1171
  });
1010
1172
  } catch (e) {
1011
1173
  errors.assertError(e);
1012
- logger.warn(`Skipping: default branch protection on '${repo}', ${e.message}`);
1174
+ logger.warn(
1175
+ `Skipping: default branch protection on '${repo}', ${e.message}`
1176
+ );
1013
1177
  }
1014
1178
  }
1015
1179
  }
@@ -1067,17 +1231,21 @@ function createGithubActionsDispatchAction(options) {
1067
1231
  workflowInputs,
1068
1232
  token: providedToken
1069
1233
  } = ctx.input;
1070
- 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
+ );
1071
1237
  const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1072
1238
  if (!owner) {
1073
1239
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1074
1240
  }
1075
- const client = new octokit.Octokit(await getOctokitOptions({
1076
- integrations,
1077
- repoUrl,
1078
- credentialsProvider: githubCredentialsProvider,
1079
- token: providedToken
1080
- }));
1241
+ const client = new octokit.Octokit(
1242
+ await getOctokitOptions({
1243
+ integrations,
1244
+ repoUrl,
1245
+ credentialsProvider: githubCredentialsProvider,
1246
+ token: providedToken
1247
+ })
1248
+ );
1081
1249
  await client.rest.actions.createWorkflowDispatch({
1082
1250
  owner,
1083
1251
  repo,
@@ -1133,12 +1301,14 @@ function createGithubIssuesLabelAction(options) {
1133
1301
  if (!owner) {
1134
1302
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1135
1303
  }
1136
- const client = new octokit.Octokit(await getOctokitOptions({
1137
- integrations,
1138
- credentialsProvider: githubCredentialsProvider,
1139
- repoUrl,
1140
- token: providedToken
1141
- }));
1304
+ const client = new octokit.Octokit(
1305
+ await getOctokitOptions({
1306
+ integrations,
1307
+ credentialsProvider: githubCredentialsProvider,
1308
+ repoUrl,
1309
+ token: providedToken
1310
+ })
1311
+ );
1142
1312
  try {
1143
1313
  await client.rest.issues.addLabels({
1144
1314
  owner,
@@ -1148,7 +1318,9 @@ function createGithubIssuesLabelAction(options) {
1148
1318
  });
1149
1319
  } catch (e) {
1150
1320
  errors.assertError(e);
1151
- 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
+ );
1152
1324
  }
1153
1325
  }
1154
1326
  });
@@ -1264,6 +1436,11 @@ const protectDefaultBranch = {
1264
1436
  type: "boolean",
1265
1437
  description: `Protect the default branch after creating the repository. The default value is 'true'`
1266
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
+ };
1267
1444
  const gitCommitMessage = {
1268
1445
  title: "Git Commit Message",
1269
1446
  type: "string",
@@ -1342,7 +1519,21 @@ function createGithubRepoCreateAction(options) {
1342
1519
  if (!owner) {
1343
1520
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1344
1521
  }
1345
- 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
+ );
1346
1537
  ctx.output("remoteUrl", newRepo.clone_url);
1347
1538
  }
1348
1539
  });
@@ -1363,6 +1554,7 @@ function createGithubRepoPushAction(options) {
1363
1554
  requiredStatusCheckContexts: requiredStatusCheckContexts,
1364
1555
  defaultBranch: defaultBranch,
1365
1556
  protectDefaultBranch: protectDefaultBranch,
1557
+ protectEnforceAdmins: protectEnforceAdmins,
1366
1558
  gitCommitMessage: gitCommitMessage,
1367
1559
  gitAuthorName: gitAuthorName,
1368
1560
  gitAuthorEmail: gitAuthorEmail,
@@ -1383,6 +1575,7 @@ function createGithubRepoPushAction(options) {
1383
1575
  repoUrl,
1384
1576
  defaultBranch = "master",
1385
1577
  protectDefaultBranch = true,
1578
+ protectEnforceAdmins = true,
1386
1579
  gitCommitMessage = "initial commit",
1387
1580
  gitAuthorName,
1388
1581
  gitAuthorEmail,
@@ -1404,7 +1597,25 @@ function createGithubRepoPushAction(options) {
1404
1597
  const targetRepo = await client.rest.repos.get({ owner, repo });
1405
1598
  const remoteUrl = targetRepo.data.clone_url;
1406
1599
  const repoContentsUrl = `${targetRepo.data.html_url}/blob/${defaultBranch}`;
1407
- 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
+ );
1408
1619
  ctx.output("remoteUrl", remoteUrl);
1409
1620
  ctx.output("repoContentsUrl", repoContentsUrl);
1410
1621
  }
@@ -1496,12 +1707,14 @@ function createGithubWebhookAction(options) {
1496
1707
  if (!owner) {
1497
1708
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
1498
1709
  }
1499
- const client = new octokit.Octokit(await getOctokitOptions({
1500
- integrations,
1501
- credentialsProvider: githubCredentialsProvider,
1502
- repoUrl,
1503
- token: providedToken
1504
- }));
1710
+ const client = new octokit.Octokit(
1711
+ await getOctokitOptions({
1712
+ integrations,
1713
+ credentialsProvider: githubCredentialsProvider,
1714
+ repoUrl,
1715
+ token: providedToken
1716
+ })
1717
+ );
1505
1718
  try {
1506
1719
  const insecure_ssl = insecureSsl ? "1" : "0";
1507
1720
  await client.rest.repos.createWebhook({
@@ -1519,7 +1732,9 @@ function createGithubWebhookAction(options) {
1519
1732
  ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
1520
1733
  } catch (e) {
1521
1734
  errors.assertError(e);
1522
- 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
+ );
1523
1738
  }
1524
1739
  }
1525
1740
  });
@@ -1598,13 +1813,20 @@ function createPublishAzureAction(options) {
1598
1813
  gitAuthorName,
1599
1814
  gitAuthorEmail
1600
1815
  } = ctx.input;
1601
- const { owner, repo, host, organization } = parseRepoUrl(repoUrl, integrations);
1816
+ const { owner, repo, host, organization } = parseRepoUrl(
1817
+ repoUrl,
1818
+ integrations
1819
+ );
1602
1820
  if (!organization) {
1603
- 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
+ );
1604
1824
  }
1605
1825
  const integrationConfig = integrations.azure.byHost(host);
1606
1826
  if (!integrationConfig) {
1607
- 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
+ );
1608
1830
  }
1609
1831
  if (!integrationConfig.config.token && !ctx.input.token) {
1610
1832
  throw new errors.InputError(`No token provided for Azure Integration ${host}`);
@@ -1616,12 +1838,16 @@ function createPublishAzureAction(options) {
1616
1838
  const createOptions = { name: repo };
1617
1839
  const returnedRepo = await client.createRepository(createOptions, owner);
1618
1840
  if (!returnedRepo) {
1619
- throw new errors.InputError(`Unable to create the repository with Organization ${organization}, Project ${owner} and Repo ${repo}.
1620
- 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
+ );
1621
1845
  }
1622
1846
  const remoteUrl = returnedRepo.remoteUrl;
1623
1847
  if (!remoteUrl) {
1624
- 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
+ );
1625
1851
  }
1626
1852
  const repoContentsUrl = remoteUrl;
1627
1853
  const gitAuthorInfo = {
@@ -1672,12 +1898,17 @@ const createBitbucketCloudRepository = async (opts) => {
1672
1898
  };
1673
1899
  let response;
1674
1900
  try {
1675
- response = await fetch__default["default"](`${apiBaseUrl}/repositories/${workspace}/${repo}`, options);
1901
+ response = await fetch__default["default"](
1902
+ `${apiBaseUrl}/repositories/${workspace}/${repo}`,
1903
+ options
1904
+ );
1676
1905
  } catch (e) {
1677
1906
  throw new Error(`Unable to create repository, ${e}`);
1678
1907
  }
1679
1908
  if (response.status !== 200) {
1680
- 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
+ );
1681
1912
  }
1682
1913
  const r = await response.json();
1683
1914
  let remoteUrl = "";
@@ -1717,7 +1948,9 @@ const createBitbucketServerRepository = async (opts) => {
1717
1948
  throw new Error(`Unable to create repository, ${e}`);
1718
1949
  }
1719
1950
  if (response.status !== 201) {
1720
- 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
+ );
1721
1954
  }
1722
1955
  const r = await response.json();
1723
1956
  let remoteUrl = "";
@@ -1729,15 +1962,20 @@ const createBitbucketServerRepository = async (opts) => {
1729
1962
  const repoContentsUrl = `${r.links.self[0].href}`;
1730
1963
  return { remoteUrl, repoContentsUrl };
1731
1964
  };
1732
- const getAuthorizationHeader$2 = (config) => {
1965
+ const getAuthorizationHeader$1 = (config) => {
1733
1966
  if (config.username && config.appPassword) {
1734
- const buffer = Buffer.from(`${config.username}:${config.appPassword}`, "utf8");
1967
+ const buffer = Buffer.from(
1968
+ `${config.username}:${config.appPassword}`,
1969
+ "utf8"
1970
+ );
1735
1971
  return `Basic ${buffer.toString("base64")}`;
1736
1972
  }
1737
1973
  if (config.token) {
1738
1974
  return `Bearer ${config.token}`;
1739
1975
  }
1740
- 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
+ );
1741
1979
  };
1742
1980
  const performEnableLFS$1 = async (opts) => {
1743
1981
  const { authorization, host, project, repo } = opts;
@@ -1747,9 +1985,14 @@ const performEnableLFS$1 = async (opts) => {
1747
1985
  Authorization: authorization
1748
1986
  }
1749
1987
  };
1750
- 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
+ );
1751
1992
  if (!ok)
1752
- 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
+ );
1753
1996
  };
1754
1997
  function createPublishBitbucketAction(options) {
1755
1998
  const { integrations, config } = options;
@@ -1827,7 +2070,9 @@ function createPublishBitbucketAction(options) {
1827
2070
  },
1828
2071
  async handler(ctx) {
1829
2072
  var _a;
1830
- 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
+ );
1831
2076
  const {
1832
2077
  repoUrl,
1833
2078
  description,
@@ -1838,24 +2083,35 @@ function createPublishBitbucketAction(options) {
1838
2083
  gitAuthorName,
1839
2084
  gitAuthorEmail
1840
2085
  } = ctx.input;
1841
- const { workspace, project, repo, host } = parseRepoUrl(repoUrl, integrations);
2086
+ const { workspace, project, repo, host } = parseRepoUrl(
2087
+ repoUrl,
2088
+ integrations
2089
+ );
1842
2090
  if (host === "bitbucket.org") {
1843
2091
  if (!workspace) {
1844
- 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
+ );
1845
2095
  }
1846
2096
  }
1847
2097
  if (!project) {
1848
- 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
+ );
1849
2101
  }
1850
2102
  const integrationConfig = integrations.bitbucket.byHost(host);
1851
2103
  if (!integrationConfig) {
1852
- 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
+ );
1853
2107
  }
1854
- const authorization = getAuthorizationHeader$2(ctx.input.token ? {
1855
- host: integrationConfig.config.host,
1856
- apiBaseUrl: integrationConfig.config.apiBaseUrl,
1857
- token: ctx.input.token
1858
- } : 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
+ );
1859
2115
  const apiBaseUrl = integrationConfig.config.apiBaseUrl;
1860
2116
  const createMethod = host === "bitbucket.org" ? createBitbucketCloudRepository : createBitbucketServerRepository;
1861
2117
  const { remoteUrl, repoContentsUrl } = await createMethod({
@@ -1928,12 +2184,17 @@ const createRepository$1 = async (opts) => {
1928
2184
  };
1929
2185
  let response;
1930
2186
  try {
1931
- response = await fetch__default["default"](`${apiBaseUrl}/repositories/${workspace}/${repo}`, options);
2187
+ response = await fetch__default["default"](
2188
+ `${apiBaseUrl}/repositories/${workspace}/${repo}`,
2189
+ options
2190
+ );
1932
2191
  } catch (e) {
1933
2192
  throw new Error(`Unable to create repository, ${e}`);
1934
2193
  }
1935
2194
  if (response.status !== 200) {
1936
- 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
+ );
1937
2198
  }
1938
2199
  const r = await response.json();
1939
2200
  let remoteUrl = "";
@@ -1945,15 +2206,20 @@ const createRepository$1 = async (opts) => {
1945
2206
  const repoContentsUrl = `${r.links.html.href}/src/${mainBranch}`;
1946
2207
  return { remoteUrl, repoContentsUrl };
1947
2208
  };
1948
- const getAuthorizationHeader$1 = (config) => {
2209
+ const getAuthorizationHeader = (config) => {
1949
2210
  if (config.username && config.appPassword) {
1950
- const buffer = Buffer.from(`${config.username}:${config.appPassword}`, "utf8");
2211
+ const buffer = Buffer.from(
2212
+ `${config.username}:${config.appPassword}`,
2213
+ "utf8"
2214
+ );
1951
2215
  return `Basic ${buffer.toString("base64")}`;
1952
2216
  }
1953
2217
  if (config.token) {
1954
2218
  return `Bearer ${config.token}`;
1955
2219
  }
1956
- 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
+ );
1957
2223
  };
1958
2224
  function createPublishBitbucketCloudAction(options) {
1959
2225
  const { integrations, config } = options;
@@ -2016,18 +2282,29 @@ function createPublishBitbucketCloudAction(options) {
2016
2282
  defaultBranch = "master",
2017
2283
  repoVisibility = "private"
2018
2284
  } = ctx.input;
2019
- const { workspace, project, repo, host } = parseRepoUrl(repoUrl, integrations);
2285
+ const { workspace, project, repo, host } = parseRepoUrl(
2286
+ repoUrl,
2287
+ integrations
2288
+ );
2020
2289
  if (!workspace) {
2021
- 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
+ );
2022
2293
  }
2023
2294
  if (!project) {
2024
- 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
+ );
2025
2298
  }
2026
2299
  const integrationConfig = integrations.bitbucketCloud.byHost(host);
2027
2300
  if (!integrationConfig) {
2028
- 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
+ );
2029
2304
  }
2030
- 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
+ );
2031
2308
  const apiBaseUrl = integrationConfig.config.apiBaseUrl;
2032
2309
  const { remoteUrl, repoContentsUrl } = await createRepository$1({
2033
2310
  authorization,
@@ -2051,7 +2328,9 @@ function createPublishBitbucketCloudAction(options) {
2051
2328
  };
2052
2329
  } else {
2053
2330
  if (!integrationConfig.config.username || !integrationConfig.config.appPassword) {
2054
- 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
+ );
2055
2334
  }
2056
2335
  auth = {
2057
2336
  username: integrationConfig.config.username,
@@ -2064,7 +2343,9 @@ function createPublishBitbucketCloudAction(options) {
2064
2343
  auth,
2065
2344
  defaultBranch,
2066
2345
  logger: ctx.logger,
2067
- commitMessage: config.getOptionalString("scaffolder.defaultCommitMessage"),
2346
+ commitMessage: config.getOptionalString(
2347
+ "scaffolder.defaultCommitMessage"
2348
+ ),
2068
2349
  gitAuthorInfo
2069
2350
  });
2070
2351
  ctx.output("remoteUrl", remoteUrl);
@@ -2101,7 +2382,9 @@ const createRepository = async (opts) => {
2101
2382
  throw new Error(`Unable to create repository, ${e}`);
2102
2383
  }
2103
2384
  if (response.status !== 201) {
2104
- 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
+ );
2105
2388
  }
2106
2389
  const r = await response.json();
2107
2390
  let remoteUrl = "";
@@ -2113,9 +2396,6 @@ const createRepository = async (opts) => {
2113
2396
  const repoContentsUrl = `${r.links.self[0].href}`;
2114
2397
  return { remoteUrl, repoContentsUrl };
2115
2398
  };
2116
- const getAuthorizationHeader = (config) => {
2117
- return `Bearer ${config.token}`;
2118
- };
2119
2399
  const performEnableLFS = async (opts) => {
2120
2400
  const { authorization, host, project, repo } = opts;
2121
2401
  const options = {
@@ -2124,9 +2404,14 @@ const performEnableLFS = async (opts) => {
2124
2404
  Authorization: authorization
2125
2405
  }
2126
2406
  };
2127
- 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
+ );
2128
2411
  if (!ok)
2129
- 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
+ );
2130
2415
  };
2131
2416
  function createPublishBitbucketServerAction(options) {
2132
2417
  const { integrations, config } = options;
@@ -2198,17 +2483,28 @@ function createPublishBitbucketServerAction(options) {
2198
2483
  } = ctx.input;
2199
2484
  const { project, repo, host } = parseRepoUrl(repoUrl, integrations);
2200
2485
  if (!project) {
2201
- 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
+ );
2202
2489
  }
2203
2490
  const integrationConfig = integrations.bitbucketServer.byHost(host);
2204
2491
  if (!integrationConfig) {
2205
- 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
+ );
2206
2495
  }
2207
2496
  const token = (_a = ctx.input.token) != null ? _a : integrationConfig.config.token;
2208
- if (!token) {
2209
- 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
+ );
2210
2507
  }
2211
- const authorization = getAuthorizationHeader({ token });
2212
2508
  const apiBaseUrl = integrationConfig.config.apiBaseUrl;
2213
2509
  const { remoteUrl, repoContentsUrl } = await createRepository({
2214
2510
  authorization,
@@ -2222,9 +2518,11 @@ function createPublishBitbucketServerAction(options) {
2222
2518
  name: config.getOptionalString("scaffolder.defaultAuthor.name"),
2223
2519
  email: config.getOptionalString("scaffolder.defaultAuthor.email")
2224
2520
  };
2225
- const auth = {
2226
- username: "x-token-auth",
2227
- password: token
2521
+ const auth = authConfig.token ? {
2522
+ token
2523
+ } : {
2524
+ username: authConfig.username,
2525
+ password: authConfig.password
2228
2526
  };
2229
2527
  await initRepoAndPush({
2230
2528
  dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
@@ -2232,7 +2530,9 @@ function createPublishBitbucketServerAction(options) {
2232
2530
  auth,
2233
2531
  defaultBranch,
2234
2532
  logger: ctx.logger,
2235
- commitMessage: config.getOptionalString("scaffolder.defaultCommitMessage"),
2533
+ commitMessage: config.getOptionalString(
2534
+ "scaffolder.defaultCommitMessage"
2535
+ ),
2236
2536
  gitAuthorInfo
2237
2537
  });
2238
2538
  if (enableLFS) {
@@ -2279,7 +2579,7 @@ const createGerritProject = async (config, options) => {
2279
2579
  body: JSON.stringify({
2280
2580
  parent,
2281
2581
  description,
2282
- owners: [owner],
2582
+ owners: owner ? [owner] : [],
2283
2583
  create_empty_commit: false
2284
2584
  }),
2285
2585
  headers: {
@@ -2287,9 +2587,14 @@ const createGerritProject = async (config, options) => {
2287
2587
  "Content-Type": "application/json"
2288
2588
  }
2289
2589
  };
2290
- 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
+ );
2291
2594
  if (response.status !== 201) {
2292
- 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
+ );
2293
2598
  }
2294
2599
  };
2295
2600
  const generateCommitMessage = (config, commitSubject) => {
@@ -2336,6 +2641,11 @@ function createPublishGerritAction(options) {
2336
2641
  title: "Default Author Email",
2337
2642
  type: "string",
2338
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.`
2339
2649
  }
2340
2650
  }
2341
2651
  },
@@ -2360,18 +2670,23 @@ function createPublishGerritAction(options) {
2360
2670
  defaultBranch = "master",
2361
2671
  gitAuthorName,
2362
2672
  gitAuthorEmail,
2363
- gitCommitMessage = "initial commit"
2673
+ gitCommitMessage = "initial commit",
2674
+ sourcePath
2364
2675
  } = ctx.input;
2365
- const { repo, host, owner, workspace } = parseRepoUrl(repoUrl, integrations);
2676
+ const { repo, host, owner, workspace } = parseRepoUrl(
2677
+ repoUrl,
2678
+ integrations
2679
+ );
2366
2680
  const integrationConfig = integrations.gerrit.byHost(host);
2367
2681
  if (!integrationConfig) {
2368
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
2369
- }
2370
- if (!owner) {
2371
- 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
+ );
2372
2685
  }
2373
2686
  if (!workspace) {
2374
- 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
+ );
2375
2690
  }
2376
2691
  await createGerritProject(integrationConfig.config, {
2377
2692
  description,
@@ -2389,7 +2704,7 @@ function createPublishGerritAction(options) {
2389
2704
  };
2390
2705
  const remoteUrl = `${integrationConfig.config.cloneUrl}/a/${repo}`;
2391
2706
  await initRepoAndPush({
2392
- dir: getRepoSourceDirectory(ctx.workspacePath, void 0),
2707
+ dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath),
2393
2708
  remoteUrl,
2394
2709
  auth,
2395
2710
  defaultBranch,
@@ -2404,6 +2719,115 @@ function createPublishGerritAction(options) {
2404
2719
  });
2405
2720
  }
2406
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
+
2407
2831
  function createPublishGithubAction(options) {
2408
2832
  const { integrations, config, githubCredentialsProvider } = options;
2409
2833
  return createTemplateAction({
@@ -2422,6 +2846,7 @@ function createPublishGithubAction(options) {
2422
2846
  repoVisibility: repoVisibility,
2423
2847
  defaultBranch: defaultBranch,
2424
2848
  protectDefaultBranch: protectDefaultBranch,
2849
+ protectEnforceAdmins: protectEnforceAdmins,
2425
2850
  deleteBranchOnMerge: deleteBranchOnMerge,
2426
2851
  gitCommitMessage: gitCommitMessage,
2427
2852
  gitAuthorName: gitAuthorName,
@@ -2453,6 +2878,7 @@ function createPublishGithubAction(options) {
2453
2878
  repoVisibility = "private",
2454
2879
  defaultBranch = "master",
2455
2880
  protectDefaultBranch = true,
2881
+ protectEnforceAdmins = true,
2456
2882
  deleteBranchOnMerge = false,
2457
2883
  gitCommitMessage = "initial commit",
2458
2884
  gitAuthorName,
@@ -2475,10 +2901,42 @@ function createPublishGithubAction(options) {
2475
2901
  if (!owner) {
2476
2902
  throw new errors.InputError("Invalid repository owner provided in repoUrl");
2477
2903
  }
2478
- 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
+ );
2479
2919
  const remoteUrl = newRepo.clone_url;
2480
2920
  const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`;
2481
- 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
+ );
2482
2940
  ctx.output("remoteUrl", remoteUrl);
2483
2941
  ctx.output("repoContentsUrl", repoContentsUrl);
2484
2942
  }
@@ -2505,11 +2963,15 @@ async function serializeDirectoryContents(sourcePath, options) {
2505
2963
  stats: true
2506
2964
  });
2507
2965
  const limiter = limiterFactory__default["default"](10);
2508
- return Promise.all(paths.map(async ({ path: path$1, stats }) => ({
2509
- path: path$1,
2510
- content: await limiter(async () => fs__default["default"].readFile(path.join(sourcePath, path$1))),
2511
- executable: isExecutable(stats == null ? void 0 : stats.mode)
2512
- })));
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
+ );
2513
2975
  }
2514
2976
 
2515
2977
  async function deserializeDirectoryContents(targetPath, files) {
@@ -2530,7 +2992,9 @@ const defaultClientFactory = async ({
2530
2992
  host = "github.com",
2531
2993
  token: providedToken
2532
2994
  }) => {
2533
- const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map(encodeURIComponent);
2995
+ const [encodedHost, encodedOwner, encodedRepo] = [host, owner, repo].map(
2996
+ encodeURIComponent
2997
+ );
2534
2998
  const octokitOptions = await getOctokitOptions({
2535
2999
  integrations,
2536
3000
  credentialsProvider: githubCredentialsProvider,
@@ -2624,7 +3088,9 @@ const createPublishGithubPullRequestAction = ({
2624
3088
  } = ctx.input;
2625
3089
  const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
2626
3090
  if (!owner) {
2627
- 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
+ );
2628
3094
  }
2629
3095
  const client = await clientFactory({
2630
3096
  integrations,
@@ -2638,14 +3104,16 @@ const createPublishGithubPullRequestAction = ({
2638
3104
  const directoryContents = await serializeDirectoryContents(fileRoot, {
2639
3105
  gitignore: true
2640
3106
  });
2641
- const files = Object.fromEntries(directoryContents.map((file) => [
2642
- targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path,
2643
- {
2644
- mode: file.executable ? "100755" : "100644",
2645
- encoding: "base64",
2646
- content: file.content.toString("base64")
2647
- }
2648
- ]));
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
+ );
2649
3117
  try {
2650
3118
  const response = await client.createPullRequest({
2651
3119
  owner,
@@ -2721,6 +3189,11 @@ function createPublishGitlabAction(options) {
2721
3189
  title: "Authentication Token",
2722
3190
  type: "string",
2723
3191
  description: "The token to use for authorization to GitLab"
3192
+ },
3193
+ setUserAsOwner: {
3194
+ title: "Set User As Owner",
3195
+ type: "boolean",
3196
+ description: "Set the token user as owner of the newly created repository. Requires a token authorized to do the edit in the integration configuration for the matching host"
2724
3197
  }
2725
3198
  }
2726
3199
  },
@@ -2745,15 +3218,20 @@ function createPublishGitlabAction(options) {
2745
3218
  defaultBranch = "master",
2746
3219
  gitCommitMessage = "initial commit",
2747
3220
  gitAuthorName,
2748
- gitAuthorEmail
3221
+ gitAuthorEmail,
3222
+ setUserAsOwner = false
2749
3223
  } = ctx.input;
2750
3224
  const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
2751
3225
  if (!owner) {
2752
- 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
+ );
2753
3229
  }
2754
3230
  const integrationConfig = integrations.gitlab.byHost(host);
2755
3231
  if (!integrationConfig) {
2756
- 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
+ );
2757
3235
  }
2758
3236
  if (!integrationConfig.config.token && !ctx.input.token) {
2759
3237
  throw new errors.InputError(`No token available for host ${host}`);
@@ -2765,15 +3243,22 @@ function createPublishGitlabAction(options) {
2765
3243
  [tokenType]: token
2766
3244
  });
2767
3245
  let { id: targetNamespace } = await client.Namespaces.show(owner);
3246
+ const { id: userId } = await client.Users.current();
2768
3247
  if (!targetNamespace) {
2769
- const { id } = await client.Users.current();
2770
- targetNamespace = id;
3248
+ targetNamespace = userId;
2771
3249
  }
2772
- const { http_url_to_repo } = await client.Projects.create({
3250
+ const { id: projectId, http_url_to_repo } = await client.Projects.create({
2773
3251
  namespace_id: targetNamespace,
2774
3252
  name: repo,
2775
3253
  visibility: repoVisibility
2776
3254
  });
3255
+ if (setUserAsOwner && integrationConfig.config.token) {
3256
+ const adminClient = new node.Gitlab({
3257
+ host: integrationConfig.config.baseUrl,
3258
+ token: integrationConfig.config.token
3259
+ });
3260
+ await adminClient.ProjectMembers.add(projectId, userId, 50);
3261
+ }
2777
3262
  const remoteUrl = http_url_to_repo.replace(/\.git$/, "");
2778
3263
  const repoContentsUrl = `${remoteUrl}/-/blob/${defaultBranch}`;
2779
3264
  const gitAuthorInfo = {
@@ -2842,10 +3327,21 @@ const createPublishGitlabMergeRequestAction = (options) => {
2842
3327
  type: "string",
2843
3328
  description: "The token to use for authorization to GitLab"
2844
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
+ },
2845
3336
  removeSourceBranch: {
2846
3337
  title: "Delete source branch",
2847
3338
  type: "boolean",
2848
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"
2849
3345
  }
2850
3346
  }
2851
3347
  },
@@ -2881,7 +3377,9 @@ const createPublishGitlabMergeRequestAction = (options) => {
2881
3377
  const integrationConfig = integrations.gitlab.byHost(host);
2882
3378
  const destinationBranch = ctx.input.branchName;
2883
3379
  if (!integrationConfig) {
2884
- 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
+ );
2885
3383
  }
2886
3384
  if (!integrationConfig.config.token && !ctx.input.token) {
2887
3385
  throw new errors.InputError(`No token available for host ${host}`);
@@ -2892,34 +3390,70 @@ const createPublishGitlabMergeRequestAction = (options) => {
2892
3390
  host: integrationConfig.config.baseUrl,
2893
3391
  [tokenType]: token
2894
3392
  });
2895
- 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
+ );
2896
3409
  const fileContents = await serializeDirectoryContents(targetPath, {
2897
3410
  gitignore: true
2898
3411
  });
2899
- const actions = fileContents.map((file) => ({
2900
- action: "create",
2901
- filePath: path__default["default"].posix.join(ctx.input.targetPath, file.path),
2902
- encoding: "base64",
2903
- content: file.content.toString("base64"),
2904
- execute_filemode: file.executable
2905
- }));
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
+ });
2906
3422
  const projects = await api.Projects.show(projectPath);
2907
3423
  const { default_branch: defaultBranch } = projects;
2908
3424
  try {
2909
- await api.Branches.create(projectPath, destinationBranch, String(defaultBranch));
3425
+ await api.Branches.create(
3426
+ projectPath,
3427
+ destinationBranch,
3428
+ String(defaultBranch)
3429
+ );
2910
3430
  } catch (e) {
2911
3431
  throw new errors.InputError(`The branch creation failed ${e}`);
2912
3432
  }
2913
3433
  try {
2914
- 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
+ );
2915
3440
  } catch (e) {
2916
- 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
+ );
2917
3444
  }
2918
3445
  try {
2919
- const mergeRequestUrl = await api.MergeRequests.create(projectPath, destinationBranch, String(defaultBranch), ctx.input.title, {
2920
- description: ctx.input.description,
2921
- removeSourceBranch: ctx.input.removeSourceBranch ? ctx.input.removeSourceBranch : false
2922
- }).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) => {
2923
3457
  return mergeRequest.web_url;
2924
3458
  });
2925
3459
  ctx.output("projectid", projectPath);
@@ -2955,6 +3489,10 @@ const createBuiltinActions = (options) => {
2955
3489
  integrations,
2956
3490
  config
2957
3491
  }),
3492
+ createPublishGerritReviewAction({
3493
+ integrations,
3494
+ config
3495
+ }),
2958
3496
  createPublishGithubAction({
2959
3497
  integrations,
2960
3498
  config,
@@ -3023,14 +3561,18 @@ class TemplateActionRegistry {
3023
3561
  }
3024
3562
  register(action) {
3025
3563
  if (this.actions.has(action.id)) {
3026
- 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
+ );
3027
3567
  }
3028
3568
  this.actions.set(action.id, action);
3029
3569
  }
3030
3570
  get(actionId) {
3031
3571
  const action = this.actions.get(actionId);
3032
3572
  if (!action) {
3033
- 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
+ );
3034
3576
  }
3035
3577
  return action;
3036
3578
  }
@@ -3039,7 +3581,10 @@ class TemplateActionRegistry {
3039
3581
  }
3040
3582
  }
3041
3583
 
3042
- const migrationsDir = backendCommon.resolvePackagePath("@backstage/plugin-scaffolder-backend", "migrations");
3584
+ const migrationsDir = backendCommon.resolvePackagePath(
3585
+ "@backstage/plugin-scaffolder-backend",
3586
+ "migrations"
3587
+ );
3043
3588
  const parseSqlDateToIsoString = (input) => {
3044
3589
  if (typeof input === "string") {
3045
3590
  return luxon.DateTime.fromSQL(input, { zone: "UTC" }).toISO();
@@ -3154,10 +3699,14 @@ class DatabaseTaskStore {
3154
3699
  }
3155
3700
  }
3156
3701
  async listStaleTasks({ timeoutS }) {
3157
- 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', ?, ?)`, [
3158
- `-${timeoutS}`,
3159
- this.db.fn.now()
3160
- ]));
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
+ );
3161
3710
  const tasks = rawRows.map((row) => ({
3162
3711
  taskId: row.id
3163
3712
  }));
@@ -3172,7 +3721,9 @@ class DatabaseTaskStore {
3172
3721
  if (status === "failed" || status === "completed") {
3173
3722
  oldStatus = "processing";
3174
3723
  } else {
3175
- 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
+ );
3176
3727
  }
3177
3728
  await this.db.transaction(async (tx) => {
3178
3729
  const [task] = await tx("tasks").where({
@@ -3182,7 +3733,9 @@ class DatabaseTaskStore {
3182
3733
  throw new Error(`No task with taskId ${taskId} found`);
3183
3734
  }
3184
3735
  if (task.status !== oldStatus) {
3185
- 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
+ );
3186
3739
  }
3187
3740
  const updateCount = await tx("tasks").where({
3188
3741
  id: taskId,
@@ -3191,7 +3744,9 @@ class DatabaseTaskStore {
3191
3744
  status
3192
3745
  });
3193
3746
  if (updateCount !== 1) {
3194
- 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
+ );
3195
3750
  }
3196
3751
  await tx("task_events").insert({
3197
3752
  task_id: taskId,
@@ -3231,7 +3786,9 @@ class DatabaseTaskStore {
3231
3786
  createdAt: parseSqlDateToIsoString(event.created_at)
3232
3787
  };
3233
3788
  } catch (error) {
3234
- 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
+ );
3235
3792
  }
3236
3793
  });
3237
3794
  return { events };
@@ -3292,7 +3849,10 @@ class TaskManager {
3292
3849
  this.startTimeout();
3293
3850
  } catch (error) {
3294
3851
  this.isDone = true;
3295
- 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
+ );
3296
3856
  }
3297
3857
  }, 1e3);
3298
3858
  }
@@ -3313,7 +3873,9 @@ class StorageTaskBroker {
3313
3873
  }
3314
3874
  async list(options) {
3315
3875
  if (!this.storage.list) {
3316
- 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
+ );
3317
3879
  }
3318
3880
  return await this.storage.list({ createdBy: options == null ? void 0 : options.createdBy });
3319
3881
  }
@@ -3321,12 +3883,16 @@ class StorageTaskBroker {
3321
3883
  for (; ; ) {
3322
3884
  const pendingTask = await this.storage.claimTask();
3323
3885
  if (pendingTask) {
3324
- return TaskManager.create({
3325
- taskId: pendingTask.id,
3326
- spec: pendingTask.spec,
3327
- secrets: pendingTask.secrets,
3328
- createdBy: pendingTask.createdBy
3329
- }, 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
+ );
3330
3896
  }
3331
3897
  await this.waitForDispatch();
3332
3898
  }
@@ -3364,19 +3930,21 @@ class StorageTaskBroker {
3364
3930
  }
3365
3931
  async vacuumTasks(options) {
3366
3932
  const { tasks } = await this.storage.listStaleTasks(options);
3367
- await Promise.all(tasks.map(async (task) => {
3368
- try {
3369
- await this.storage.completeTask({
3370
- taskId: task.taskId,
3371
- status: "failed",
3372
- eventBody: {
3373
- message: "The task was cancelled because the task worker lost connection to the task broker"
3374
- }
3375
- });
3376
- } catch (error) {
3377
- this.logger.warn(`Failed to cancel task '${task.taskId}', ${error}`);
3378
- }
3379
- }));
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
+ );
3380
3948
  }
3381
3949
  waitForDispatch() {
3382
3950
  return this.deferredDispatch.promise;
@@ -3397,10 +3965,12 @@ function generateExampleOutput(schema) {
3397
3965
  return examples[0];
3398
3966
  }
3399
3967
  if (schema.type === "object") {
3400
- return Object.fromEntries(Object.entries((_a = schema.properties) != null ? _a : {}).map(([key, value]) => [
3401
- key,
3402
- generateExampleOutput(value)
3403
- ]));
3968
+ return Object.fromEntries(
3969
+ Object.entries((_a = schema.properties) != null ? _a : {}).map(([key, value]) => [
3970
+ key,
3971
+ generateExampleOutput(value)
3972
+ ])
3973
+ );
3404
3974
  } else if (schema.type === "array") {
3405
3975
  const [firstSchema] = (_b = [schema.items]) == null ? void 0 : _b.flat();
3406
3976
  if (firstSchema) {
@@ -3427,7 +3997,11 @@ const createStepLogger = ({
3427
3997
  const metadata = { stepId: step.id };
3428
3998
  const taskLogger = winston__namespace.createLogger({
3429
3999
  level: process.env.LOG_LEVEL || "info",
3430
- 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
+ ),
3431
4005
  defaultMeta: {}
3432
4006
  });
3433
4007
  const streamLogger = new stream.PassThrough();
@@ -3447,13 +4021,17 @@ class NunjucksWorkflowRunner {
3447
4021
  isSingleTemplateString(input) {
3448
4022
  var _a, _b;
3449
4023
  const { parser, nodes } = nunjucks__default["default"];
3450
- const parsed = parser.parse(input, {}, {
3451
- autoescape: false,
3452
- tags: {
3453
- variableStart: "${{",
3454
- variableEnd: "}}"
4024
+ const parsed = parser.parse(
4025
+ input,
4026
+ {},
4027
+ {
4028
+ autoescape: false,
4029
+ tags: {
4030
+ variableStart: "${{",
4031
+ variableEnd: "}}"
4032
+ }
3455
4033
  }
3456
- });
4034
+ );
3457
4035
  return parsed.children.length === 1 && !(((_b = (_a = parsed.children[0]) == null ? void 0 : _a.children) == null ? void 0 : _b[0]) instanceof nodes.TemplateData);
3458
4036
  }
3459
4037
  render(input, context, renderTemplate) {
@@ -3462,7 +4040,10 @@ class NunjucksWorkflowRunner {
3462
4040
  if (typeof value === "string") {
3463
4041
  try {
3464
4042
  if (this.isSingleTemplateString(value)) {
3465
- const wrappedDumped = value.replace(/\${{(.+)}}/g, "${{ ( $1 ) | dump }}");
4043
+ const wrappedDumped = value.replace(
4044
+ /\${{(.+)}}/g,
4045
+ "${{ ( $1 ) | dump }}"
4046
+ );
3466
4047
  const templated2 = renderTemplate(wrappedDumped, context);
3467
4048
  if (templated2 === "") {
3468
4049
  return void 0;
@@ -3470,7 +4051,9 @@ class NunjucksWorkflowRunner {
3470
4051
  return JSON.parse(templated2);
3471
4052
  }
3472
4053
  } catch (ex) {
3473
- 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
+ );
3474
4057
  }
3475
4058
  const templated = renderTemplate(value, context);
3476
4059
  if (templated === "") {
@@ -3487,9 +4070,14 @@ class NunjucksWorkflowRunner {
3487
4070
  async execute(task) {
3488
4071
  var _a, _b, _c, _d, _e;
3489
4072
  if (!isValidTaskSpec(task.spec)) {
3490
- 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
+ );
3491
4076
  }
3492
- 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
+ );
3493
4081
  const { integrations } = this.options;
3494
4082
  const renderTemplate = await SecureTemplater.loadRenderer({
3495
4083
  parseRepoUrl(url) {
@@ -3499,7 +4087,9 @@ class NunjucksWorkflowRunner {
3499
4087
  });
3500
4088
  try {
3501
4089
  await fs__default["default"].ensureDir(workspacePath);
3502
- 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
+ );
3503
4093
  const context = {
3504
4094
  parameters: task.spec.parameters,
3505
4095
  steps: {},
@@ -3508,9 +4098,16 @@ class NunjucksWorkflowRunner {
3508
4098
  for (const step of task.spec.steps) {
3509
4099
  try {
3510
4100
  if (step.if) {
3511
- const ifResult = await this.render(step.if, context, renderTemplate);
4101
+ const ifResult = await this.render(
4102
+ step.if,
4103
+ context,
4104
+ renderTemplate
4105
+ );
3512
4106
  if (!isTruthy(ifResult)) {
3513
- 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
+ );
3514
4111
  continue;
3515
4112
  }
3516
4113
  }
@@ -3521,10 +4118,13 @@ class NunjucksWorkflowRunner {
3521
4118
  const action = this.options.actionRegistry.get(step.action);
3522
4119
  const { taskLogger, streamLogger } = createStepLogger({ task, step });
3523
4120
  if (task.isDryRun && !action.supportsDryRun) {
3524
- task.emitLog(`Skipping because ${action.id} does not support dry-run`, {
3525
- stepId: step.id,
3526
- status: "skipped"
3527
- });
4121
+ task.emitLog(
4122
+ `Skipping because ${action.id} does not support dry-run`,
4123
+ {
4124
+ stepId: step.id,
4125
+ status: "skipped"
4126
+ }
4127
+ );
3528
4128
  const outputSchema = (_a = action.schema) == null ? void 0 : _a.output;
3529
4129
  if (outputSchema) {
3530
4130
  context.steps[step.id] = {
@@ -3535,12 +4135,21 @@ class NunjucksWorkflowRunner {
3535
4135
  }
3536
4136
  continue;
3537
4137
  }
3538
- 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 : {};
3539
4143
  if ((_d = action.schema) == null ? void 0 : _d.input) {
3540
- const validateResult = jsonschema.validate(input, action.schema.input);
4144
+ const validateResult = jsonschema.validate(
4145
+ input,
4146
+ action.schema.input
4147
+ );
3541
4148
  if (!validateResult.valid) {
3542
4149
  const errors$1 = validateResult.errors.join(", ");
3543
- 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
+ );
3544
4153
  }
3545
4154
  }
3546
4155
  const tmpDirs = new Array();
@@ -3552,7 +4161,9 @@ class NunjucksWorkflowRunner {
3552
4161
  logStream: streamLogger,
3553
4162
  workspacePath,
3554
4163
  createTemporaryDirectory: async () => {
3555
- 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
+ );
3556
4167
  tmpDirs.push(tmpDir);
3557
4168
  return tmpDir;
3558
4169
  },
@@ -3623,9 +4234,13 @@ class TaskWorker {
3623
4234
  async runOneTask(task) {
3624
4235
  try {
3625
4236
  if (task.spec.apiVersion !== "scaffolder.backstage.io/v1beta3") {
3626
- throw new Error(`Unsupported Template apiVersion ${task.spec.apiVersion}`);
4237
+ throw new Error(
4238
+ `Unsupported Template apiVersion ${task.spec.apiVersion}`
4239
+ );
3627
4240
  }
3628
- const { output } = await this.options.runners.workflowRunner.execute(task);
4241
+ const { output } = await this.options.runners.workflowRunner.execute(
4242
+ task
4243
+ );
3629
4244
  await task.complete("completed", { output });
3630
4245
  } catch (error) {
3631
4246
  errors.assertError(error);
@@ -3672,7 +4287,10 @@ function createDryRunner(options) {
3672
4287
  });
3673
4288
  const dryRunId = uuid.v4();
3674
4289
  const log = new Array();
3675
- const contentsPath = backendCommon.resolveSafeChildPath(options.workingDirectory, `dry-run-content-${dryRunId}`);
4290
+ const contentsPath = backendCommon.resolveSafeChildPath(
4291
+ options.workingDirectory,
4292
+ `dry-run-content-${dryRunId}`
4293
+ );
3676
4294
  try {
3677
4295
  await deserializeDirectoryContents(contentsPath, input.directoryContents);
3678
4296
  const result = await workflowRunner.execute({
@@ -3688,7 +4306,9 @@ function createDryRunner(options) {
3688
4306
  ],
3689
4307
  templateInfo: {
3690
4308
  entityRef: "template:default/dry-run",
3691
- baseUrl: url.pathToFileURL(backendCommon.resolveSafeChildPath(contentsPath, "template.yaml")).toString()
4309
+ baseUrl: url.pathToFileURL(
4310
+ backendCommon.resolveSafeChildPath(contentsPath, "template.yaml")
4311
+ ).toString()
3692
4312
  }
3693
4313
  },
3694
4314
  secrets: input.secrets,
@@ -3735,7 +4355,9 @@ async function getWorkingDirectory(config, logger) {
3735
4355
  logger.info(`using working directory: ${workingDirectory}`);
3736
4356
  } catch (err) {
3737
4357
  errors.assertError(err);
3738
- 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
+ );
3739
4361
  throw err;
3740
4362
  }
3741
4363
  return workingDirectory;
@@ -3764,7 +4386,9 @@ async function findTemplate(options) {
3764
4386
  }
3765
4387
  const template = await catalogApi.getEntityByRef(entityRef, { token });
3766
4388
  if (!template) {
3767
- throw new errors.NotFoundError(`Template ${catalogModel.stringifyEntityRef(entityRef)} not found`);
4389
+ throw new errors.NotFoundError(
4390
+ `Template ${catalogModel.stringifyEntityRef(entityRef)} not found`
4391
+ );
3768
4392
  }
3769
4393
  return template;
3770
4394
  }
@@ -3826,31 +4450,36 @@ async function createRouter(options) {
3826
4450
  workingDirectory,
3827
4451
  additionalTemplateFilters
3828
4452
  });
3829
- router.get("/v2/templates/:namespace/:kind/:name/parameter-schema", async (req, res) => {
3830
- var _a, _b;
3831
- const { namespace, kind, name } = req.params;
3832
- const { token } = parseBearerToken(req.headers.authorization);
3833
- const template = await findTemplate({
3834
- catalogApi: catalogClient,
3835
- entityRef: { kind, namespace, name },
3836
- token
3837
- });
3838
- if (isSupportedTemplate(template)) {
3839
- const parameters = [(_a = template.spec.parameters) != null ? _a : []].flat();
3840
- res.json({
3841
- title: (_b = template.metadata.title) != null ? _b : template.metadata.name,
3842
- steps: parameters.map((schema) => {
3843
- var _a2;
3844
- return {
3845
- title: (_a2 = schema.title) != null ? _a2 : "Fill in template parameters",
3846
- schema
3847
- };
3848
- })
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
3849
4463
  });
3850
- } else {
3851
- 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
+ steps: parameters.map((schema) => {
4469
+ var _a2;
4470
+ return {
4471
+ title: (_a2 = schema.title) != null ? _a2 : "Fill in template parameters",
4472
+ schema
4473
+ };
4474
+ })
4475
+ });
4476
+ } else {
4477
+ throw new errors.InputError(
4478
+ `Unsupported apiVersion field in schema entity, ${template.apiVersion}`
4479
+ );
4480
+ }
3852
4481
  }
3853
- }).get("/v2/actions", async (_req, res) => {
4482
+ ).get("/v2/actions", async (_req, res) => {
3854
4483
  const actionsList = actionRegistry.list().map((action) => {
3855
4484
  return {
3856
4485
  id: action.id,
@@ -3865,8 +4494,15 @@ async function createRouter(options) {
3865
4494
  const { kind, namespace, name } = catalogModel.parseEntityRef(templateRef, {
3866
4495
  defaultKind: "template"
3867
4496
  });
3868
- const { token, entityRef: userEntityRef } = parseBearerToken(req.headers.authorization);
4497
+ const { token, entityRef: userEntityRef } = parseBearerToken(
4498
+ req.headers.authorization
4499
+ );
3869
4500
  const userEntity = userEntityRef ? await catalogClient.getEntityByRef(userEntityRef, { token }) : void 0;
4501
+ let auditLog = `Scaffolding task for ${templateRef}`;
4502
+ if (userEntityRef) {
4503
+ auditLog += ` created by ${userEntityRef}`;
4504
+ }
4505
+ logger.info(auditLog);
3870
4506
  const values = req.body.values;
3871
4507
  const template = await findTemplate({
3872
4508
  catalogApi: catalogClient,
@@ -3874,7 +4510,9 @@ async function createRouter(options) {
3874
4510
  token
3875
4511
  });
3876
4512
  if (!isSupportedTemplate(template)) {
3877
- throw new errors.InputError(`Unsupported apiVersion field in schema entity, ${template.apiVersion}`);
4513
+ throw new errors.InputError(
4514
+ `Unsupported apiVersion field in schema entity, ${template.apiVersion}`
4515
+ );
3878
4516
  }
3879
4517
  for (const parameters of [(_a = template.spec.parameters) != null ? _a : []].flat()) {
3880
4518
  const result2 = jsonschema.validate(values, parameters);
@@ -3924,7 +4562,9 @@ async function createRouter(options) {
3924
4562
  throw new errors.InputError("createdBy query parameter must be a string");
3925
4563
  }
3926
4564
  if (!taskBroker.list) {
3927
- throw new Error("TaskBroker does not support listing tasks, please implement the list method on the TaskBroker.");
4565
+ throw new Error(
4566
+ "TaskBroker does not support listing tasks, please implement the list method on the TaskBroker."
4567
+ );
3928
4568
  }
3929
4569
  const tasks = await taskBroker.list({
3930
4570
  createdBy: userEntityRef
@@ -3949,23 +4589,30 @@ async function createRouter(options) {
3949
4589
  });
3950
4590
  const subscription = taskBroker.event$({ taskId, after }).subscribe({
3951
4591
  error: (error) => {
3952
- logger.error(`Received error from event stream when observing taskId '${taskId}', ${error}`);
4592
+ logger.error(
4593
+ `Received error from event stream when observing taskId '${taskId}', ${error}`
4594
+ );
4595
+ res.end();
3953
4596
  },
3954
4597
  next: ({ events }) => {
3955
4598
  var _a;
3956
4599
  let shouldUnsubscribe = false;
3957
4600
  for (const event of events) {
3958
- res.write(`event: ${event.type}
4601
+ res.write(
4602
+ `event: ${event.type}
3959
4603
  data: ${JSON.stringify(event)}
3960
4604
 
3961
- `);
4605
+ `
4606
+ );
3962
4607
  if (event.type === "completion") {
3963
4608
  shouldUnsubscribe = true;
3964
4609
  }
3965
4610
  }
3966
4611
  (_a = res.flush) == null ? void 0 : _a.call(res);
3967
- if (shouldUnsubscribe)
4612
+ if (shouldUnsubscribe) {
3968
4613
  subscription.unsubscribe();
4614
+ res.end();
4615
+ }
3969
4616
  }
3970
4617
  });
3971
4618
  req.on("close", () => {
@@ -3980,7 +4627,9 @@ data: ${JSON.stringify(event)}
3980
4627
  }, 3e4);
3981
4628
  const subscription = taskBroker.event$({ taskId, after }).subscribe({
3982
4629
  error: (error) => {
3983
- logger.error(`Received error from event stream when observing taskId '${taskId}', ${error}`);
4630
+ logger.error(
4631
+ `Received error from event stream when observing taskId '${taskId}', ${error}`
4632
+ );
3984
4633
  },
3985
4634
  next: ({ events }) => {
3986
4635
  clearTimeout(timeout);
@@ -3998,7 +4647,9 @@ data: ${JSON.stringify(event)}
3998
4647
  template: zod.z.unknown(),
3999
4648
  values: zod.z.record(zod.z.unknown()),
4000
4649
  secrets: zod.z.record(zod.z.string()).optional(),
4001
- directoryContents: zod.z.array(zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() }))
4650
+ directoryContents: zod.z.array(
4651
+ zod.z.object({ path: zod.z.string(), base64Content: zod.z.string() })
4652
+ )
4002
4653
  });
4003
4654
  const body = await bodySchema.parseAsync(req.body).catch((e) => {
4004
4655
  throw new errors.InputError(`Malformed request: ${e}`);
@@ -4065,7 +4716,9 @@ function parseBearerToken(header) {
4065
4716
  throw new TypeError("Expected Bearer with JWT");
4066
4717
  }
4067
4718
  const [_header, rawPayload, _signature] = token.split(".");
4068
- const payload = JSON.parse(Buffer.from(rawPayload, "base64").toString());
4719
+ const payload = JSON.parse(
4720
+ Buffer.from(rawPayload, "base64").toString()
4721
+ );
4069
4722
  if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
4070
4723
  throw new TypeError("Malformed JWT payload");
4071
4724
  }
@@ -4104,30 +4757,51 @@ class ScaffolderEntitiesProcessor {
4104
4757
  defaultKind: "Group",
4105
4758
  defaultNamespace: selfRef.namespace
4106
4759
  });
4107
- emit(pluginCatalogBackend.processingResult.relation({
4108
- source: selfRef,
4109
- type: catalogModel.RELATION_OWNED_BY,
4110
- target: {
4111
- kind: targetRef.kind,
4112
- namespace: targetRef.namespace,
4113
- name: targetRef.name
4114
- }
4115
- }));
4116
- emit(pluginCatalogBackend.processingResult.relation({
4117
- source: {
4118
- kind: targetRef.kind,
4119
- namespace: targetRef.namespace,
4120
- name: targetRef.name
4121
- },
4122
- type: catalogModel.RELATION_OWNER_OF,
4123
- target: selfRef
4124
- }));
4760
+ emit(
4761
+ pluginCatalogBackend.processingResult.relation({
4762
+ source: selfRef,
4763
+ type: catalogModel.RELATION_OWNED_BY,
4764
+ target: {
4765
+ kind: targetRef.kind,
4766
+ namespace: targetRef.namespace,
4767
+ name: targetRef.name
4768
+ }
4769
+ })
4770
+ );
4771
+ emit(
4772
+ pluginCatalogBackend.processingResult.relation({
4773
+ source: {
4774
+ kind: targetRef.kind,
4775
+ namespace: targetRef.namespace,
4776
+ name: targetRef.name
4777
+ },
4778
+ type: catalogModel.RELATION_OWNER_OF,
4779
+ target: selfRef
4780
+ })
4781
+ );
4125
4782
  }
4126
4783
  }
4127
4784
  return entity;
4128
4785
  }
4129
4786
  }
4130
4787
 
4788
+ const scaffolderCatalogModule = backendPluginApi.createBackendModule({
4789
+ moduleId: "scaffolder.module",
4790
+ pluginId: "catalog",
4791
+ register(env) {
4792
+ env.registerInit({
4793
+ deps: {
4794
+ catalogProcessingExtensionPoint: pluginCatalogNode.catalogProcessingExtentionPoint
4795
+ },
4796
+ async init({ catalogProcessingExtensionPoint }) {
4797
+ catalogProcessingExtensionPoint.addProcessor(
4798
+ new ScaffolderEntitiesProcessor()
4799
+ );
4800
+ }
4801
+ });
4802
+ }
4803
+ });
4804
+
4131
4805
  exports.DatabaseTaskStore = DatabaseTaskStore;
4132
4806
  exports.ScaffolderEntitiesProcessor = ScaffolderEntitiesProcessor;
4133
4807
  exports.TaskManager = TaskManager;
@@ -4152,6 +4826,7 @@ exports.createPublishBitbucketCloudAction = createPublishBitbucketCloudAction;
4152
4826
  exports.createPublishBitbucketServerAction = createPublishBitbucketServerAction;
4153
4827
  exports.createPublishFileAction = createPublishFileAction;
4154
4828
  exports.createPublishGerritAction = createPublishGerritAction;
4829
+ exports.createPublishGerritReviewAction = createPublishGerritReviewAction;
4155
4830
  exports.createPublishGithubAction = createPublishGithubAction;
4156
4831
  exports.createPublishGithubPullRequestAction = createPublishGithubPullRequestAction;
4157
4832
  exports.createPublishGitlabAction = createPublishGitlabAction;
@@ -4160,4 +4835,5 @@ exports.createRouter = createRouter;
4160
4835
  exports.createTemplateAction = createTemplateAction;
4161
4836
  exports.executeShellCommand = executeShellCommand;
4162
4837
  exports.fetchContents = fetchContents;
4838
+ exports.scaffolderCatalogModule = scaffolderCatalogModule;
4163
4839
  //# sourceMappingURL=index.cjs.js.map