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

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
@@ -12,16 +12,16 @@ var path = require('path');
12
12
  var globby = require('globby');
13
13
  var isbinaryfile = require('isbinaryfile');
14
14
  var vm2 = require('vm2');
15
+ var octokit = require('octokit');
15
16
  var child_process = require('child_process');
16
17
  var stream = require('stream');
18
+ var webhooks = require('@octokit/webhooks');
17
19
  var azureDevopsNodeApi = require('azure-devops-node-api');
18
20
  var fetch = require('node-fetch');
19
21
  var crypto = require('crypto');
20
- var octokit = require('octokit');
21
22
  var octokitPluginCreatePullRequest = require('octokit-plugin-create-pull-request');
22
23
  var limiterFactory = require('p-limit');
23
24
  var node = require('@gitbeaker/node');
24
- var webhooks = require('@octokit/webhooks');
25
25
  var uuid = require('uuid');
26
26
  var luxon = require('luxon');
27
27
  var ObservableImpl = require('zen-observable');
@@ -688,6 +688,55 @@ const createFilesystemRenameAction = () => {
688
688
  });
689
689
  };
690
690
 
691
+ const getRepoSourceDirectory = (workspacePath, sourcePath) => {
692
+ if (sourcePath) {
693
+ const safeSuffix = path.normalize(sourcePath).replace(/^(\.\.(\/|\\|$))+/, "");
694
+ const path$1 = path.join(workspacePath, safeSuffix);
695
+ if (!backendCommon.isChildPath(workspacePath, path$1)) {
696
+ throw new Error("Invalid source path");
697
+ }
698
+ return path$1;
699
+ }
700
+ return workspacePath;
701
+ };
702
+ const parseRepoUrl = (repoUrl, integrations) => {
703
+ var _a, _b, _c, _d, _e;
704
+ let parsed;
705
+ try {
706
+ parsed = new URL(`https://${repoUrl}`);
707
+ } catch (error) {
708
+ throw new errors.InputError(`Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`);
709
+ }
710
+ const host = parsed.host;
711
+ const owner = (_a = parsed.searchParams.get("owner")) != null ? _a : void 0;
712
+ const organization = (_b = parsed.searchParams.get("organization")) != null ? _b : void 0;
713
+ const workspace = (_c = parsed.searchParams.get("workspace")) != null ? _c : void 0;
714
+ const project = (_d = parsed.searchParams.get("project")) != null ? _d : void 0;
715
+ const type = (_e = integrations.byHost(host)) == null ? void 0 : _e.type;
716
+ if (!type) {
717
+ throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
718
+ }
719
+ if (type === "bitbucket") {
720
+ if (host === "bitbucket.org") {
721
+ if (!workspace) {
722
+ throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`);
723
+ }
724
+ }
725
+ if (!project) {
726
+ throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing project`);
727
+ }
728
+ } else {
729
+ if (!owner) {
730
+ throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing owner`);
731
+ }
732
+ }
733
+ const repo = parsed.searchParams.get("repo");
734
+ if (!repo) {
735
+ throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing repo`);
736
+ }
737
+ return { host, owner, repo, organization, workspace, project };
738
+ };
739
+
691
740
  const executeShellCommand = async (options) => {
692
741
  const {
693
742
  command,
@@ -803,138 +852,762 @@ const enableBranchProtectionOnDefaultRepoBranch = async ({
803
852
  }
804
853
  };
805
854
 
806
- const getRepoSourceDirectory = (workspacePath, sourcePath) => {
807
- if (sourcePath) {
808
- const safeSuffix = path.normalize(sourcePath).replace(/^(\.\.(\/|\\|$))+/, "");
809
- const path$1 = path.join(workspacePath, safeSuffix);
810
- if (!backendCommon.isChildPath(workspacePath, path$1)) {
811
- throw new Error("Invalid source path");
812
- }
813
- return path$1;
855
+ const DEFAULT_TIMEOUT_MS = 6e4;
856
+ async function getOctokitOptions(options) {
857
+ var _a;
858
+ const { integrations, credentialsProvider, repoUrl, token } = options;
859
+ const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
860
+ const requestOptions = {
861
+ timeout: DEFAULT_TIMEOUT_MS
862
+ };
863
+ if (!owner) {
864
+ throw new errors.InputError(`No owner provided for repo ${repoUrl}`);
814
865
  }
815
- return workspacePath;
816
- };
817
- const parseRepoUrl = (repoUrl, integrations) => {
818
- var _a, _b, _c, _d, _e;
819
- let parsed;
866
+ const integrationConfig = (_a = integrations.github.byHost(host)) == null ? void 0 : _a.config;
867
+ if (!integrationConfig) {
868
+ throw new errors.InputError(`No integration for host ${host}`);
869
+ }
870
+ if (token) {
871
+ return {
872
+ auth: token,
873
+ baseUrl: integrationConfig.apiBaseUrl,
874
+ previews: ["nebula-preview"],
875
+ request: requestOptions
876
+ };
877
+ }
878
+ const githubCredentialsProvider = credentialsProvider != null ? credentialsProvider : integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
879
+ const { token: credentialProviderToken } = await githubCredentialsProvider.getCredentials({
880
+ url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
881
+ });
882
+ if (!credentialProviderToken) {
883
+ throw new errors.InputError(`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`);
884
+ }
885
+ return {
886
+ auth: credentialProviderToken,
887
+ baseUrl: integrationConfig.apiBaseUrl,
888
+ previews: ["nebula-preview"]
889
+ };
890
+ }
891
+ async function createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, repoVisibility, description, deleteBranchOnMerge, allowMergeCommit, allowSquashMerge, allowRebaseMerge, access, collaborators, topics, logger) {
892
+ const user = await client.rest.users.getByUsername({
893
+ username: owner
894
+ });
895
+ const repoCreationPromise = user.data.type === "Organization" ? client.rest.repos.createInOrg({
896
+ name: repo,
897
+ org: owner,
898
+ private: repoVisibility === "private",
899
+ visibility: repoVisibility,
900
+ description,
901
+ delete_branch_on_merge: deleteBranchOnMerge,
902
+ allow_merge_commit: allowMergeCommit,
903
+ allow_squash_merge: allowSquashMerge,
904
+ allow_rebase_merge: allowRebaseMerge
905
+ }) : client.rest.repos.createForAuthenticatedUser({
906
+ name: repo,
907
+ private: repoVisibility === "private",
908
+ description,
909
+ delete_branch_on_merge: deleteBranchOnMerge,
910
+ allow_merge_commit: allowMergeCommit,
911
+ allow_squash_merge: allowSquashMerge,
912
+ allow_rebase_merge: allowRebaseMerge
913
+ });
914
+ let newRepo;
820
915
  try {
821
- parsed = new URL(`https://${repoUrl}`);
822
- } catch (error) {
823
- throw new errors.InputError(`Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`);
916
+ newRepo = (await repoCreationPromise).data;
917
+ } catch (e) {
918
+ errors.assertError(e);
919
+ 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}.`);
921
+ }
922
+ throw new Error(`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`);
824
923
  }
825
- const host = parsed.host;
826
- const owner = (_a = parsed.searchParams.get("owner")) != null ? _a : void 0;
827
- const organization = (_b = parsed.searchParams.get("organization")) != null ? _b : void 0;
828
- const workspace = (_c = parsed.searchParams.get("workspace")) != null ? _c : void 0;
829
- const project = (_d = parsed.searchParams.get("project")) != null ? _d : void 0;
830
- const type = (_e = integrations.byHost(host)) == null ? void 0 : _e.type;
831
- if (!type) {
832
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
924
+ if (access == null ? void 0 : access.startsWith(`${owner}/`)) {
925
+ const [, team] = access.split("/");
926
+ await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
927
+ org: owner,
928
+ team_slug: team,
929
+ owner,
930
+ repo,
931
+ permission: "admin"
932
+ });
933
+ } else if (access && access !== owner) {
934
+ await client.rest.repos.addCollaborator({
935
+ owner,
936
+ repo,
937
+ username: access,
938
+ permission: "admin"
939
+ });
833
940
  }
834
- if (type === "bitbucket") {
835
- if (host === "bitbucket.org") {
836
- if (!workspace) {
837
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing workspace`);
941
+ if (collaborators) {
942
+ for (const collaborator of collaborators) {
943
+ try {
944
+ if ("user" in collaborator) {
945
+ await client.rest.repos.addCollaborator({
946
+ owner,
947
+ repo,
948
+ username: collaborator.user,
949
+ permission: collaborator.access
950
+ });
951
+ } else if ("team" in collaborator) {
952
+ await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
953
+ org: owner,
954
+ team_slug: collaborator.team,
955
+ owner,
956
+ repo,
957
+ permission: collaborator.access
958
+ });
959
+ }
960
+ } catch (e) {
961
+ errors.assertError(e);
962
+ const name = extractCollaboratorName(collaborator);
963
+ logger.warn(`Skipping ${collaborator.access} access for ${name}, ${e.message}`);
838
964
  }
839
965
  }
840
- if (!project) {
841
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing project`);
842
- }
843
- } else {
844
- if (!owner) {
845
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing owner`);
966
+ }
967
+ if (topics) {
968
+ try {
969
+ await client.rest.repos.replaceAllTopics({
970
+ owner,
971
+ repo,
972
+ names: topics.map((t) => t.toLowerCase())
973
+ });
974
+ } catch (e) {
975
+ errors.assertError(e);
976
+ logger.warn(`Skipping topics ${topics.join(" ")}, ${e.message}`);
846
977
  }
847
978
  }
848
- const repo = parsed.searchParams.get("repo");
849
- if (!repo) {
850
- throw new errors.InputError(`Invalid repo URL passed to publisher: ${repoUrl}, missing repo`);
979
+ return newRepo;
980
+ }
981
+ async function initRepoPushAndProtect(remoteUrl, password, workspacePath, sourcePath, defaultBranch, protectDefaultBranch, owner, client, repo, requireCodeOwnerReviews, requiredStatusCheckContexts, config, logger, gitCommitMessage, gitAuthorName, gitAuthorEmail) {
982
+ const gitAuthorInfo = {
983
+ name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
984
+ email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
985
+ };
986
+ const commitMessage = gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage");
987
+ await initRepoAndPush({
988
+ dir: getRepoSourceDirectory(workspacePath, sourcePath),
989
+ remoteUrl,
990
+ defaultBranch,
991
+ auth: {
992
+ username: "x-access-token",
993
+ password
994
+ },
995
+ logger,
996
+ commitMessage,
997
+ gitAuthorInfo
998
+ });
999
+ if (protectDefaultBranch) {
1000
+ try {
1001
+ await enableBranchProtectionOnDefaultRepoBranch({
1002
+ owner,
1003
+ client,
1004
+ repoName: repo,
1005
+ logger,
1006
+ defaultBranch,
1007
+ requireCodeOwnerReviews,
1008
+ requiredStatusCheckContexts
1009
+ });
1010
+ } catch (e) {
1011
+ errors.assertError(e);
1012
+ logger.warn(`Skipping: default branch protection on '${repo}', ${e.message}`);
1013
+ }
851
1014
  }
852
- return { host, owner, repo, organization, workspace, project };
853
- };
1015
+ }
1016
+ function extractCollaboratorName(collaborator) {
1017
+ if ("username" in collaborator)
1018
+ return collaborator.username;
1019
+ if ("user" in collaborator)
1020
+ return collaborator.user;
1021
+ return collaborator.team;
1022
+ }
854
1023
 
855
- function createPublishAzureAction(options) {
856
- const { integrations, config } = options;
1024
+ function createGithubActionsDispatchAction(options) {
1025
+ const { integrations, githubCredentialsProvider } = options;
857
1026
  return createTemplateAction({
858
- id: "publish:azure",
859
- description: "Initializes a git repository of the content in the workspace, and publishes it to Azure.",
1027
+ id: "github:actions:dispatch",
1028
+ description: "Dispatches a GitHub Action workflow for a given branch or tag",
860
1029
  schema: {
861
1030
  input: {
862
1031
  type: "object",
863
- required: ["repoUrl"],
1032
+ required: ["repoUrl", "workflowId", "branchOrTagName"],
864
1033
  properties: {
865
1034
  repoUrl: {
866
1035
  title: "Repository Location",
1036
+ description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
867
1037
  type: "string"
868
1038
  },
869
- description: {
870
- title: "Repository Description",
1039
+ workflowId: {
1040
+ title: "Workflow ID",
1041
+ description: "The GitHub Action Workflow filename",
871
1042
  type: "string"
872
1043
  },
873
- defaultBranch: {
874
- title: "Default Branch",
875
- type: "string",
876
- description: `Sets the default branch on the repository. The default value is 'master'`
877
- },
878
- gitCommitMessage: {
879
- title: "Git Commit Message",
880
- type: "string",
881
- description: `Sets the commit message on the repository. The default value is 'initial commit'`
882
- },
883
- gitAuthorName: {
884
- title: "Default Author Name",
885
- type: "string",
886
- description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
887
- },
888
- gitAuthorEmail: {
889
- title: "Default Author Email",
890
- type: "string",
891
- description: `Sets the default author email for the commit.`
892
- },
893
- sourcePath: {
894
- title: "Source Path",
895
- description: "Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.",
1044
+ branchOrTagName: {
1045
+ title: "Branch or Tag name",
1046
+ description: "The git branch or tag name used to dispatch the workflow",
896
1047
  type: "string"
897
1048
  },
1049
+ workflowInputs: {
1050
+ title: "Workflow Inputs",
1051
+ description: "Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10. ",
1052
+ type: "object"
1053
+ },
898
1054
  token: {
899
1055
  title: "Authentication Token",
900
1056
  type: "string",
901
- description: "The token to use for authorization to Azure"
902
- }
903
- }
904
- },
905
- output: {
906
- type: "object",
907
- properties: {
908
- remoteUrl: {
909
- title: "A URL to the repository with the provider",
910
- type: "string"
911
- },
912
- repoContentsUrl: {
913
- title: "A URL to the root of the repository",
914
- type: "string"
1057
+ description: "The GITHUB_TOKEN to use for authorization to GitHub"
915
1058
  }
916
1059
  }
917
1060
  }
918
1061
  },
919
1062
  async handler(ctx) {
920
- var _a;
921
1063
  const {
922
1064
  repoUrl,
923
- defaultBranch = "master",
924
- gitCommitMessage = "initial commit",
925
- gitAuthorName,
926
- gitAuthorEmail
1065
+ workflowId,
1066
+ branchOrTagName,
1067
+ workflowInputs,
1068
+ token: providedToken
927
1069
  } = ctx.input;
928
- const { owner, repo, host, organization } = parseRepoUrl(repoUrl, integrations);
929
- if (!organization) {
930
- throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing organization`);
931
- }
932
- const integrationConfig = integrations.azure.byHost(host);
933
- if (!integrationConfig) {
934
- throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
1070
+ ctx.logger.info(`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`);
1071
+ const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1072
+ if (!owner) {
1073
+ throw new errors.InputError("Invalid repository owner provided in repoUrl");
935
1074
  }
936
- if (!integrationConfig.config.token && !ctx.input.token) {
937
- throw new errors.InputError(`No token provided for Azure Integration ${host}`);
1075
+ const client = new octokit.Octokit(await getOctokitOptions({
1076
+ integrations,
1077
+ repoUrl,
1078
+ credentialsProvider: githubCredentialsProvider,
1079
+ token: providedToken
1080
+ }));
1081
+ await client.rest.actions.createWorkflowDispatch({
1082
+ owner,
1083
+ repo,
1084
+ workflow_id: workflowId,
1085
+ ref: branchOrTagName,
1086
+ inputs: workflowInputs
1087
+ });
1088
+ ctx.logger.info(`Workflow ${workflowId} dispatched successfully`);
1089
+ }
1090
+ });
1091
+ }
1092
+
1093
+ function createGithubIssuesLabelAction(options) {
1094
+ const { integrations, githubCredentialsProvider } = options;
1095
+ return createTemplateAction({
1096
+ id: "github:issues:label",
1097
+ description: "Adds labels to a pull request or issue on GitHub.",
1098
+ schema: {
1099
+ input: {
1100
+ type: "object",
1101
+ required: ["repoUrl", "number", "labels"],
1102
+ properties: {
1103
+ repoUrl: {
1104
+ title: "Repository Location",
1105
+ description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the repository name and 'owner' is an organization or username`,
1106
+ type: "string"
1107
+ },
1108
+ number: {
1109
+ title: "Pull Request or issue number",
1110
+ description: "The pull request or issue number to add labels to",
1111
+ type: "number"
1112
+ },
1113
+ labels: {
1114
+ title: "Labels",
1115
+ description: "The labels to add to the pull request or issue",
1116
+ type: "array",
1117
+ items: {
1118
+ type: "string"
1119
+ }
1120
+ },
1121
+ token: {
1122
+ title: "Authentication Token",
1123
+ type: "string",
1124
+ description: "The GITHUB_TOKEN to use for authorization to GitHub"
1125
+ }
1126
+ }
1127
+ }
1128
+ },
1129
+ async handler(ctx) {
1130
+ const { repoUrl, number, labels, token: providedToken } = ctx.input;
1131
+ const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1132
+ ctx.logger.info(`Adding labels to ${number} issue on repo ${repo}`);
1133
+ if (!owner) {
1134
+ throw new errors.InputError("Invalid repository owner provided in repoUrl");
1135
+ }
1136
+ const client = new octokit.Octokit(await getOctokitOptions({
1137
+ integrations,
1138
+ credentialsProvider: githubCredentialsProvider,
1139
+ repoUrl,
1140
+ token: providedToken
1141
+ }));
1142
+ try {
1143
+ await client.rest.issues.addLabels({
1144
+ owner,
1145
+ repo,
1146
+ issue_number: number,
1147
+ labels
1148
+ });
1149
+ } catch (e) {
1150
+ errors.assertError(e);
1151
+ ctx.logger.warn(`Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`);
1152
+ }
1153
+ }
1154
+ });
1155
+ }
1156
+
1157
+ const repoUrl = {
1158
+ title: "Repository Location",
1159
+ description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
1160
+ type: "string"
1161
+ };
1162
+ const description = {
1163
+ title: "Repository Description",
1164
+ type: "string"
1165
+ };
1166
+ const access = {
1167
+ title: "Repository Access",
1168
+ description: `Sets an admin collaborator on the repository. Can either be a user reference different from 'owner' in 'repoUrl' or team reference, eg. 'org/team-name'`,
1169
+ type: "string"
1170
+ };
1171
+ const requireCodeOwnerReviews = {
1172
+ title: "Require CODEOWNER Reviews?",
1173
+ description: "Require an approved review in PR including files with a designated Code Owner",
1174
+ type: "boolean"
1175
+ };
1176
+ const requiredStatusCheckContexts = {
1177
+ title: "Required Status Check Contexts",
1178
+ description: "The list of status checks to require in order to merge into this branch",
1179
+ type: "array",
1180
+ items: {
1181
+ type: "string"
1182
+ }
1183
+ };
1184
+ const repoVisibility = {
1185
+ title: "Repository Visibility",
1186
+ type: "string",
1187
+ enum: ["private", "public", "internal"]
1188
+ };
1189
+ const deleteBranchOnMerge = {
1190
+ title: "Delete Branch On Merge",
1191
+ type: "boolean",
1192
+ description: `Delete the branch after merging the PR. The default value is 'false'`
1193
+ };
1194
+ const gitAuthorName = {
1195
+ title: "Default Author Name",
1196
+ type: "string",
1197
+ description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
1198
+ };
1199
+ const gitAuthorEmail = {
1200
+ title: "Default Author Email",
1201
+ type: "string",
1202
+ description: `Sets the default author email for the commit.`
1203
+ };
1204
+ const allowMergeCommit = {
1205
+ title: "Allow Merge Commits",
1206
+ type: "boolean",
1207
+ description: `Allow merge commits. The default value is 'true'`
1208
+ };
1209
+ const allowSquashMerge = {
1210
+ title: "Allow Squash Merges",
1211
+ type: "boolean",
1212
+ description: `Allow squash merges. The default value is 'true'`
1213
+ };
1214
+ const allowRebaseMerge = {
1215
+ title: "Allow Rebase Merges",
1216
+ type: "boolean",
1217
+ description: `Allow rebase merges. The default value is 'true'`
1218
+ };
1219
+ const collaborators = {
1220
+ title: "Collaborators",
1221
+ description: "Provide additional users or teams with permissions",
1222
+ type: "array",
1223
+ items: {
1224
+ type: "object",
1225
+ additionalProperties: false,
1226
+ required: ["access"],
1227
+ properties: {
1228
+ access: {
1229
+ type: "string",
1230
+ description: "The type of access for the user",
1231
+ enum: ["push", "pull", "admin", "maintain", "triage"]
1232
+ },
1233
+ user: {
1234
+ type: "string",
1235
+ description: "The name of the user that will be added as a collaborator"
1236
+ },
1237
+ team: {
1238
+ type: "string",
1239
+ description: "The name of the team that will be added as a collaborator"
1240
+ }
1241
+ },
1242
+ oneOf: [{ required: ["user"] }, { required: ["team"] }]
1243
+ }
1244
+ };
1245
+ const token = {
1246
+ title: "Authentication Token",
1247
+ type: "string",
1248
+ description: "The token to use for authorization to GitHub"
1249
+ };
1250
+ const topics = {
1251
+ title: "Topics",
1252
+ type: "array",
1253
+ items: {
1254
+ type: "string"
1255
+ }
1256
+ };
1257
+ const defaultBranch = {
1258
+ title: "Default Branch",
1259
+ type: "string",
1260
+ description: `Sets the default branch on the repository. The default value is 'master'`
1261
+ };
1262
+ const protectDefaultBranch = {
1263
+ title: "Protect Default Branch",
1264
+ type: "boolean",
1265
+ description: `Protect the default branch after creating the repository. The default value is 'true'`
1266
+ };
1267
+ const gitCommitMessage = {
1268
+ title: "Git Commit Message",
1269
+ type: "string",
1270
+ description: `Sets the commit message on the repository. The default value is 'initial commit'`
1271
+ };
1272
+ const sourcePath = {
1273
+ title: "Source Path",
1274
+ description: "Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.",
1275
+ type: "string"
1276
+ };
1277
+
1278
+ const remoteUrl = {
1279
+ title: "A URL to the repository with the provider",
1280
+ type: "string"
1281
+ };
1282
+ const repoContentsUrl = {
1283
+ title: "A URL to the root of the repository",
1284
+ type: "string"
1285
+ };
1286
+
1287
+ function createGithubRepoCreateAction(options) {
1288
+ const { integrations, githubCredentialsProvider } = options;
1289
+ return createTemplateAction({
1290
+ id: "github:repo:create",
1291
+ description: "Creates a GitHub repository.",
1292
+ schema: {
1293
+ input: {
1294
+ type: "object",
1295
+ required: ["repoUrl"],
1296
+ properties: {
1297
+ repoUrl: repoUrl,
1298
+ description: description,
1299
+ access: access,
1300
+ requireCodeOwnerReviews: requireCodeOwnerReviews,
1301
+ requiredStatusCheckContexts: requiredStatusCheckContexts,
1302
+ repoVisibility: repoVisibility,
1303
+ deleteBranchOnMerge: deleteBranchOnMerge,
1304
+ allowMergeCommit: allowMergeCommit,
1305
+ allowSquashMerge: allowSquashMerge,
1306
+ allowRebaseMerge: allowRebaseMerge,
1307
+ collaborators: collaborators,
1308
+ token: token,
1309
+ topics: topics
1310
+ }
1311
+ },
1312
+ output: {
1313
+ type: "object",
1314
+ properties: {
1315
+ remoteUrl: remoteUrl,
1316
+ repoContentsUrl: repoContentsUrl
1317
+ }
1318
+ }
1319
+ },
1320
+ async handler(ctx) {
1321
+ const {
1322
+ repoUrl,
1323
+ description,
1324
+ access,
1325
+ repoVisibility = "private",
1326
+ deleteBranchOnMerge = false,
1327
+ allowMergeCommit = true,
1328
+ allowSquashMerge = true,
1329
+ allowRebaseMerge = true,
1330
+ collaborators,
1331
+ topics,
1332
+ token: providedToken
1333
+ } = ctx.input;
1334
+ const octokitOptions = await getOctokitOptions({
1335
+ integrations,
1336
+ credentialsProvider: githubCredentialsProvider,
1337
+ token: providedToken,
1338
+ repoUrl
1339
+ });
1340
+ const client = new octokit.Octokit(octokitOptions);
1341
+ const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1342
+ if (!owner) {
1343
+ throw new errors.InputError("Invalid repository owner provided in repoUrl");
1344
+ }
1345
+ const newRepo = await createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, repoVisibility, description, deleteBranchOnMerge, allowMergeCommit, allowSquashMerge, allowRebaseMerge, access, collaborators, topics, ctx.logger);
1346
+ ctx.output("remoteUrl", newRepo.clone_url);
1347
+ }
1348
+ });
1349
+ }
1350
+
1351
+ function createGithubRepoPushAction(options) {
1352
+ const { integrations, config, githubCredentialsProvider } = options;
1353
+ return createTemplateAction({
1354
+ id: "github:repo:push",
1355
+ description: "Initializes a git repository of contents in workspace and publishes it to GitHub.",
1356
+ schema: {
1357
+ input: {
1358
+ type: "object",
1359
+ required: ["repoUrl"],
1360
+ properties: {
1361
+ repoUrl: repoUrl,
1362
+ requireCodeOwnerReviews: requireCodeOwnerReviews,
1363
+ requiredStatusCheckContexts: requiredStatusCheckContexts,
1364
+ defaultBranch: defaultBranch,
1365
+ protectDefaultBranch: protectDefaultBranch,
1366
+ gitCommitMessage: gitCommitMessage,
1367
+ gitAuthorName: gitAuthorName,
1368
+ gitAuthorEmail: gitAuthorEmail,
1369
+ sourcePath: sourcePath,
1370
+ token: token
1371
+ }
1372
+ },
1373
+ output: {
1374
+ type: "object",
1375
+ properties: {
1376
+ remoteUrl: remoteUrl,
1377
+ repoContentsUrl: repoContentsUrl
1378
+ }
1379
+ }
1380
+ },
1381
+ async handler(ctx) {
1382
+ const {
1383
+ repoUrl,
1384
+ defaultBranch = "master",
1385
+ protectDefaultBranch = true,
1386
+ gitCommitMessage = "initial commit",
1387
+ gitAuthorName,
1388
+ gitAuthorEmail,
1389
+ requireCodeOwnerReviews = false,
1390
+ requiredStatusCheckContexts = [],
1391
+ token: providedToken
1392
+ } = ctx.input;
1393
+ const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1394
+ if (!owner) {
1395
+ throw new errors.InputError("Invalid repository owner provided in repoUrl");
1396
+ }
1397
+ const octokitOptions = await getOctokitOptions({
1398
+ integrations,
1399
+ credentialsProvider: githubCredentialsProvider,
1400
+ token: providedToken,
1401
+ repoUrl
1402
+ });
1403
+ const client = new octokit.Octokit(octokitOptions);
1404
+ const targetRepo = await client.rest.repos.get({ owner, repo });
1405
+ const remoteUrl = targetRepo.data.clone_url;
1406
+ 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);
1408
+ ctx.output("remoteUrl", remoteUrl);
1409
+ ctx.output("repoContentsUrl", repoContentsUrl);
1410
+ }
1411
+ });
1412
+ }
1413
+
1414
+ function createGithubWebhookAction(options) {
1415
+ const { integrations, defaultWebhookSecret, githubCredentialsProvider } = options;
1416
+ const eventNames = webhooks.emitterEventNames.filter((event) => !event.includes("."));
1417
+ return createTemplateAction({
1418
+ id: "github:webhook",
1419
+ description: "Creates webhook for a repository on GitHub.",
1420
+ schema: {
1421
+ input: {
1422
+ type: "object",
1423
+ required: ["repoUrl", "webhookUrl"],
1424
+ properties: {
1425
+ repoUrl: {
1426
+ title: "Repository Location",
1427
+ description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
1428
+ type: "string"
1429
+ },
1430
+ webhookUrl: {
1431
+ title: "Webhook URL",
1432
+ description: "The URL to which the payloads will be delivered",
1433
+ type: "string"
1434
+ },
1435
+ webhookSecret: {
1436
+ title: "Webhook Secret",
1437
+ description: "Webhook secret value. The default can be provided internally in action creation",
1438
+ type: "string"
1439
+ },
1440
+ events: {
1441
+ title: "Triggering Events",
1442
+ description: "Determines what events the hook is triggered for. Default: push",
1443
+ type: "array",
1444
+ oneOf: [
1445
+ {
1446
+ items: {
1447
+ type: "string",
1448
+ enum: eventNames
1449
+ }
1450
+ },
1451
+ {
1452
+ items: {
1453
+ type: "string",
1454
+ const: "*"
1455
+ }
1456
+ }
1457
+ ]
1458
+ },
1459
+ active: {
1460
+ title: "Active",
1461
+ type: "boolean",
1462
+ description: `Determines if notifications are sent when the webhook is triggered. Default: true`
1463
+ },
1464
+ contentType: {
1465
+ title: "Content Type",
1466
+ type: "string",
1467
+ enum: ["form", "json"],
1468
+ description: `The media type used to serialize the payloads. The default is 'form'`
1469
+ },
1470
+ insecureSsl: {
1471
+ title: "Insecure SSL",
1472
+ type: "boolean",
1473
+ description: `Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default 'false'`
1474
+ },
1475
+ token: {
1476
+ title: "Authentication Token",
1477
+ type: "string",
1478
+ description: "The GITHUB_TOKEN to use for authorization to GitHub"
1479
+ }
1480
+ }
1481
+ }
1482
+ },
1483
+ async handler(ctx) {
1484
+ const {
1485
+ repoUrl,
1486
+ webhookUrl,
1487
+ webhookSecret = defaultWebhookSecret,
1488
+ events = ["push"],
1489
+ active = true,
1490
+ contentType = "form",
1491
+ insecureSsl = false,
1492
+ token: providedToken
1493
+ } = ctx.input;
1494
+ ctx.logger.info(`Creating webhook ${webhookUrl} for repo ${repoUrl}`);
1495
+ const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1496
+ if (!owner) {
1497
+ throw new errors.InputError("Invalid repository owner provided in repoUrl");
1498
+ }
1499
+ const client = new octokit.Octokit(await getOctokitOptions({
1500
+ integrations,
1501
+ credentialsProvider: githubCredentialsProvider,
1502
+ repoUrl,
1503
+ token: providedToken
1504
+ }));
1505
+ try {
1506
+ const insecure_ssl = insecureSsl ? "1" : "0";
1507
+ await client.rest.repos.createWebhook({
1508
+ owner,
1509
+ repo,
1510
+ config: {
1511
+ url: webhookUrl,
1512
+ content_type: contentType,
1513
+ secret: webhookSecret,
1514
+ insecure_ssl
1515
+ },
1516
+ events,
1517
+ active
1518
+ });
1519
+ ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
1520
+ } catch (e) {
1521
+ errors.assertError(e);
1522
+ ctx.logger.warn(`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`);
1523
+ }
1524
+ }
1525
+ });
1526
+ }
1527
+
1528
+ function createPublishAzureAction(options) {
1529
+ const { integrations, config } = options;
1530
+ return createTemplateAction({
1531
+ id: "publish:azure",
1532
+ description: "Initializes a git repository of the content in the workspace, and publishes it to Azure.",
1533
+ schema: {
1534
+ input: {
1535
+ type: "object",
1536
+ required: ["repoUrl"],
1537
+ properties: {
1538
+ repoUrl: {
1539
+ title: "Repository Location",
1540
+ type: "string"
1541
+ },
1542
+ description: {
1543
+ title: "Repository Description",
1544
+ type: "string"
1545
+ },
1546
+ defaultBranch: {
1547
+ title: "Default Branch",
1548
+ type: "string",
1549
+ description: `Sets the default branch on the repository. The default value is 'master'`
1550
+ },
1551
+ gitCommitMessage: {
1552
+ title: "Git Commit Message",
1553
+ type: "string",
1554
+ description: `Sets the commit message on the repository. The default value is 'initial commit'`
1555
+ },
1556
+ gitAuthorName: {
1557
+ title: "Default Author Name",
1558
+ type: "string",
1559
+ description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
1560
+ },
1561
+ gitAuthorEmail: {
1562
+ title: "Default Author Email",
1563
+ type: "string",
1564
+ description: `Sets the default author email for the commit.`
1565
+ },
1566
+ sourcePath: {
1567
+ title: "Source Path",
1568
+ description: "Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.",
1569
+ type: "string"
1570
+ },
1571
+ token: {
1572
+ title: "Authentication Token",
1573
+ type: "string",
1574
+ description: "The token to use for authorization to Azure"
1575
+ }
1576
+ }
1577
+ },
1578
+ output: {
1579
+ type: "object",
1580
+ properties: {
1581
+ remoteUrl: {
1582
+ title: "A URL to the repository with the provider",
1583
+ type: "string"
1584
+ },
1585
+ repoContentsUrl: {
1586
+ title: "A URL to the root of the repository",
1587
+ type: "string"
1588
+ }
1589
+ }
1590
+ }
1591
+ },
1592
+ async handler(ctx) {
1593
+ var _a;
1594
+ const {
1595
+ repoUrl,
1596
+ defaultBranch = "master",
1597
+ gitCommitMessage = "initial commit",
1598
+ gitAuthorName,
1599
+ gitAuthorEmail
1600
+ } = ctx.input;
1601
+ const { owner, repo, host, organization } = parseRepoUrl(repoUrl, integrations);
1602
+ if (!organization) {
1603
+ throw new errors.InputError(`Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing organization`);
1604
+ }
1605
+ const integrationConfig = integrations.azure.byHost(host);
1606
+ if (!integrationConfig) {
1607
+ throw new errors.InputError(`No matching integration configuration for host ${host}, please check your integrations config`);
1608
+ }
1609
+ if (!integrationConfig.config.token && !ctx.input.token) {
1610
+ throw new errors.InputError(`No token provided for Azure Integration ${host}`);
938
1611
  }
939
1612
  const token = (_a = ctx.input.token) != null ? _a : integrationConfig.config.token;
940
1613
  const authHandler = azureDevopsNodeApi.getPersonalAccessTokenHandler(token);
@@ -1731,43 +2404,6 @@ function createPublishGerritAction(options) {
1731
2404
  });
1732
2405
  }
1733
2406
 
1734
- const DEFAULT_TIMEOUT_MS = 6e4;
1735
- async function getOctokitOptions(options) {
1736
- var _a;
1737
- const { integrations, credentialsProvider, repoUrl, token } = options;
1738
- const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
1739
- const requestOptions = {
1740
- timeout: DEFAULT_TIMEOUT_MS
1741
- };
1742
- if (!owner) {
1743
- throw new errors.InputError(`No owner provided for repo ${repoUrl}`);
1744
- }
1745
- const integrationConfig = (_a = integrations.github.byHost(host)) == null ? void 0 : _a.config;
1746
- if (!integrationConfig) {
1747
- throw new errors.InputError(`No integration for host ${host}`);
1748
- }
1749
- if (token) {
1750
- return {
1751
- auth: token,
1752
- baseUrl: integrationConfig.apiBaseUrl,
1753
- previews: ["nebula-preview"],
1754
- request: requestOptions
1755
- };
1756
- }
1757
- const githubCredentialsProvider = credentialsProvider != null ? credentialsProvider : integration.DefaultGithubCredentialsProvider.fromIntegrations(integrations);
1758
- const { token: credentialProviderToken } = await githubCredentialsProvider.getCredentials({
1759
- url: `https://${host}/${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`
1760
- });
1761
- if (!credentialProviderToken) {
1762
- throw new errors.InputError(`No token available for host: ${host}, with owner ${owner}, and repo ${repo}`);
1763
- }
1764
- return {
1765
- auth: credentialProviderToken,
1766
- baseUrl: integrationConfig.apiBaseUrl,
1767
- previews: ["nebula-preview"]
1768
- };
1769
- }
1770
-
1771
2407
  function createPublishGithubAction(options) {
1772
2408
  const { integrations, config, githubCredentialsProvider } = options;
1773
2409
  return createTemplateAction({
@@ -1778,147 +2414,32 @@ function createPublishGithubAction(options) {
1778
2414
  type: "object",
1779
2415
  required: ["repoUrl"],
1780
2416
  properties: {
1781
- repoUrl: {
1782
- title: "Repository Location",
1783
- description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
1784
- type: "string"
1785
- },
1786
- description: {
1787
- title: "Repository Description",
1788
- type: "string"
1789
- },
1790
- access: {
1791
- title: "Repository Access",
1792
- description: `Sets an admin collaborator on the repository. Can either be a user reference different from 'owner' in 'repoUrl' or team reference, eg. 'org/team-name'`,
1793
- type: "string"
1794
- },
1795
- requireCodeOwnerReviews: {
1796
- title: "Require CODEOWNER Reviews?",
1797
- description: "Require an approved review in PR including files with a designated Code Owner",
1798
- type: "boolean"
1799
- },
1800
- requiredStatusCheckContexts: {
1801
- title: "Required Status Check Contexts",
1802
- description: "The list of status checks to require in order to merge into this branch",
1803
- type: "array",
1804
- items: {
1805
- type: "string"
1806
- }
1807
- },
1808
- repoVisibility: {
1809
- title: "Repository Visibility",
1810
- type: "string",
1811
- enum: ["private", "public", "internal"]
1812
- },
1813
- defaultBranch: {
1814
- title: "Default Branch",
1815
- type: "string",
1816
- description: `Sets the default branch on the repository. The default value is 'master'`
1817
- },
1818
- protectDefaultBranch: {
1819
- title: "Protect Default Branch",
1820
- type: "boolean",
1821
- description: `Protect the default branch after creating the repository. The default value is 'true'`
1822
- },
1823
- deleteBranchOnMerge: {
1824
- title: "Delete Branch On Merge",
1825
- type: "boolean",
1826
- description: `Delete the branch after merging the PR. The default value is 'false'`
1827
- },
1828
- gitCommitMessage: {
1829
- title: "Git Commit Message",
1830
- type: "string",
1831
- description: `Sets the commit message on the repository. The default value is 'initial commit'`
1832
- },
1833
- gitAuthorName: {
1834
- title: "Default Author Name",
1835
- type: "string",
1836
- description: `Sets the default author name for the commit. The default value is 'Scaffolder'`
1837
- },
1838
- gitAuthorEmail: {
1839
- title: "Default Author Email",
1840
- type: "string",
1841
- description: `Sets the default author email for the commit.`
1842
- },
1843
- allowMergeCommit: {
1844
- title: "Allow Merge Commits",
1845
- type: "boolean",
1846
- description: `Allow merge commits. The default value is 'true'`
1847
- },
1848
- allowSquashMerge: {
1849
- title: "Allow Squash Merges",
1850
- type: "boolean",
1851
- description: `Allow squash merges. The default value is 'true'`
1852
- },
1853
- allowRebaseMerge: {
1854
- title: "Allow Rebase Merges",
1855
- type: "boolean",
1856
- description: `Allow rebase merges. The default value is 'true'`
1857
- },
1858
- sourcePath: {
1859
- title: "Source Path",
1860
- description: "Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.",
1861
- type: "string"
1862
- },
1863
- collaborators: {
1864
- title: "Collaborators",
1865
- description: "Provide additional users or teams with permissions",
1866
- type: "array",
1867
- items: {
1868
- type: "object",
1869
- additionalProperties: false,
1870
- required: ["access"],
1871
- properties: {
1872
- access: {
1873
- type: "string",
1874
- description: "The type of access for the user",
1875
- enum: ["push", "pull", "admin", "maintain", "triage"]
1876
- },
1877
- user: {
1878
- type: "string",
1879
- description: "The name of the user that will be added as a collaborator"
1880
- },
1881
- username: {
1882
- type: "string",
1883
- description: "Deprecated. Use the `team` or `user` field instead."
1884
- },
1885
- team: {
1886
- type: "string",
1887
- description: "The name of the team that will be added as a collaborator"
1888
- }
1889
- },
1890
- oneOf: [
1891
- { required: ["user"] },
1892
- { required: ["username"] },
1893
- { required: ["team"] }
1894
- ]
1895
- }
1896
- },
1897
- token: {
1898
- title: "Authentication Token",
1899
- type: "string",
1900
- description: "The token to use for authorization to GitHub"
1901
- },
1902
- topics: {
1903
- title: "Topics",
1904
- type: "array",
1905
- items: {
1906
- type: "string"
1907
- }
1908
- }
2417
+ repoUrl: repoUrl,
2418
+ description: description,
2419
+ access: access,
2420
+ requireCodeOwnerReviews: requireCodeOwnerReviews,
2421
+ requiredStatusCheckContexts: requiredStatusCheckContexts,
2422
+ repoVisibility: repoVisibility,
2423
+ defaultBranch: defaultBranch,
2424
+ protectDefaultBranch: protectDefaultBranch,
2425
+ deleteBranchOnMerge: deleteBranchOnMerge,
2426
+ gitCommitMessage: gitCommitMessage,
2427
+ gitAuthorName: gitAuthorName,
2428
+ gitAuthorEmail: gitAuthorEmail,
2429
+ allowMergeCommit: allowMergeCommit,
2430
+ allowSquashMerge: allowSquashMerge,
2431
+ allowRebaseMerge: allowRebaseMerge,
2432
+ sourcePath: sourcePath,
2433
+ collaborators: collaborators,
2434
+ token: token,
2435
+ topics: topics
1909
2436
  }
1910
2437
  },
1911
2438
  output: {
1912
2439
  type: "object",
1913
2440
  properties: {
1914
- remoteUrl: {
1915
- title: "A URL to the repository with the provider",
1916
- type: "string"
1917
- },
1918
- repoContentsUrl: {
1919
- title: "A URL to the root of the repository",
1920
- type: "string"
1921
- }
2441
+ remoteUrl: remoteUrl,
2442
+ repoContentsUrl: repoContentsUrl
1922
2443
  }
1923
2444
  }
1924
2445
  },
@@ -1943,10 +2464,6 @@ function createPublishGithubAction(options) {
1943
2464
  topics,
1944
2465
  token: providedToken
1945
2466
  } = ctx.input;
1946
- const { owner, repo } = parseRepoUrl(repoUrl, integrations);
1947
- if (!owner) {
1948
- throw new errors.InputError("Invalid repository owner provided in repoUrl");
1949
- }
1950
2467
  const octokitOptions = await getOctokitOptions({
1951
2468
  integrations,
1952
2469
  credentialsProvider: githubCredentialsProvider,
@@ -1954,148 +2471,19 @@ function createPublishGithubAction(options) {
1954
2471
  repoUrl
1955
2472
  });
1956
2473
  const client = new octokit.Octokit(octokitOptions);
1957
- const user = await client.rest.users.getByUsername({
1958
- username: owner
1959
- });
1960
- const repoCreationPromise = user.data.type === "Organization" ? client.rest.repos.createInOrg({
1961
- name: repo,
1962
- org: owner,
1963
- private: repoVisibility === "private",
1964
- visibility: repoVisibility,
1965
- description,
1966
- delete_branch_on_merge: deleteBranchOnMerge,
1967
- allow_merge_commit: allowMergeCommit,
1968
- allow_squash_merge: allowSquashMerge,
1969
- allow_rebase_merge: allowRebaseMerge
1970
- }) : client.rest.repos.createForAuthenticatedUser({
1971
- name: repo,
1972
- private: repoVisibility === "private",
1973
- description,
1974
- delete_branch_on_merge: deleteBranchOnMerge,
1975
- allow_merge_commit: allowMergeCommit,
1976
- allow_squash_merge: allowSquashMerge,
1977
- allow_rebase_merge: allowRebaseMerge
1978
- });
1979
- let newRepo;
1980
- try {
1981
- newRepo = (await repoCreationPromise).data;
1982
- } catch (e) {
1983
- errors.assertError(e);
1984
- if (e.message === "Resource not accessible by integration") {
1985
- ctx.logger.warn(`The GitHub app or token provided may not have the required permissions to create the ${user.data.type} repository ${owner}/${repo}.`);
1986
- }
1987
- throw new Error(`Failed to create the ${user.data.type} repository ${owner}/${repo}, ${e.message}`);
1988
- }
1989
- if (access == null ? void 0 : access.startsWith(`${owner}/`)) {
1990
- const [, team] = access.split("/");
1991
- await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
1992
- org: owner,
1993
- team_slug: team,
1994
- owner,
1995
- repo,
1996
- permission: "admin"
1997
- });
1998
- } else if (access && access !== owner) {
1999
- await client.rest.repos.addCollaborator({
2000
- owner,
2001
- repo,
2002
- username: access,
2003
- permission: "admin"
2004
- });
2005
- }
2006
- if (collaborators) {
2007
- for (const collaborator of collaborators) {
2008
- try {
2009
- if ("user" in collaborator) {
2010
- await client.rest.repos.addCollaborator({
2011
- owner,
2012
- repo,
2013
- username: collaborator.user,
2014
- permission: collaborator.access
2015
- });
2016
- } else if ("username" in collaborator) {
2017
- ctx.logger.warn("The field `username` is deprecated in favor of `team` and will be removed in the future.");
2018
- await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
2019
- org: owner,
2020
- team_slug: collaborator.username,
2021
- owner,
2022
- repo,
2023
- permission: collaborator.access
2024
- });
2025
- } else if ("team" in collaborator) {
2026
- await client.rest.teams.addOrUpdateRepoPermissionsInOrg({
2027
- org: owner,
2028
- team_slug: collaborator.team,
2029
- owner,
2030
- repo,
2031
- permission: collaborator.access
2032
- });
2033
- }
2034
- } catch (e) {
2035
- errors.assertError(e);
2036
- const name = extractCollaboratorName(collaborator);
2037
- ctx.logger.warn(`Skipping ${collaborator.access} access for ${name}, ${e.message}`);
2038
- }
2039
- }
2040
- }
2041
- if (topics) {
2042
- try {
2043
- await client.rest.repos.replaceAllTopics({
2044
- owner,
2045
- repo,
2046
- names: topics.map((t) => t.toLowerCase())
2047
- });
2048
- } catch (e) {
2049
- errors.assertError(e);
2050
- ctx.logger.warn(`Skipping topics ${topics.join(" ")}, ${e.message}`);
2051
- }
2474
+ const { owner, repo } = parseRepoUrl(repoUrl, integrations);
2475
+ if (!owner) {
2476
+ throw new errors.InputError("Invalid repository owner provided in repoUrl");
2052
2477
  }
2478
+ const newRepo = await createGithubRepoWithCollaboratorsAndTopics(client, repo, owner, repoVisibility, description, deleteBranchOnMerge, allowMergeCommit, allowSquashMerge, allowRebaseMerge, access, collaborators, topics, ctx.logger);
2053
2479
  const remoteUrl = newRepo.clone_url;
2054
2480
  const repoContentsUrl = `${newRepo.html_url}/blob/${defaultBranch}`;
2055
- const gitAuthorInfo = {
2056
- name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"),
2057
- email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email")
2058
- };
2059
- await initRepoAndPush({
2060
- dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath),
2061
- remoteUrl,
2062
- defaultBranch,
2063
- auth: {
2064
- username: "x-access-token",
2065
- password: octokitOptions.auth
2066
- },
2067
- logger: ctx.logger,
2068
- commitMessage: gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage"),
2069
- gitAuthorInfo
2070
- });
2071
- if (protectDefaultBranch) {
2072
- try {
2073
- await enableBranchProtectionOnDefaultRepoBranch({
2074
- owner,
2075
- client,
2076
- repoName: newRepo.name,
2077
- logger: ctx.logger,
2078
- defaultBranch,
2079
- requireCodeOwnerReviews,
2080
- requiredStatusCheckContexts
2081
- });
2082
- } catch (e) {
2083
- errors.assertError(e);
2084
- ctx.logger.warn(`Skipping: default branch protection on '${newRepo.name}', ${e.message}`);
2085
- }
2086
- }
2481
+ await initRepoPushAndProtect(remoteUrl, octokitOptions.auth, ctx.workspacePath, ctx.input.sourcePath, defaultBranch, protectDefaultBranch, owner, client, repo, requireCodeOwnerReviews, requiredStatusCheckContexts, config, ctx.logger, gitCommitMessage, gitAuthorName, gitAuthorEmail);
2087
2482
  ctx.output("remoteUrl", remoteUrl);
2088
2483
  ctx.output("repoContentsUrl", repoContentsUrl);
2089
2484
  }
2090
2485
  });
2091
2486
  }
2092
- function extractCollaboratorName(collaborator) {
2093
- if ("username" in collaborator)
2094
- return collaborator.username;
2095
- if ("user" in collaborator)
2096
- return collaborator.user;
2097
- return collaborator.team;
2098
- }
2099
2487
 
2100
2488
  const DEFAULT_GLOB_PATTERNS = ["./**", "!.git"];
2101
2489
  const isExecutable = (fileMode) => {
@@ -2453,6 +2841,11 @@ const createPublishGitlabMergeRequestAction = (options) => {
2453
2841
  title: "Authentication Token",
2454
2842
  type: "string",
2455
2843
  description: "The token to use for authorization to GitLab"
2844
+ },
2845
+ removeSourceBranch: {
2846
+ title: "Delete source branch",
2847
+ type: "boolean",
2848
+ description: "Option to delete source branch once the MR has been merged. Default: false"
2456
2849
  }
2457
2850
  }
2458
2851
  },
@@ -2523,7 +2916,10 @@ const createPublishGitlabMergeRequestAction = (options) => {
2523
2916
  throw new errors.InputError(`Committing the changes to ${destinationBranch} failed ${e}`);
2524
2917
  }
2525
2918
  try {
2526
- const mergeRequestUrl = await api.MergeRequests.create(projectPath, destinationBranch, String(defaultBranch), ctx.input.title, { description: ctx.input.description }).then((mergeRequest) => {
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) => {
2527
2923
  return mergeRequest.web_url;
2528
2924
  });
2529
2925
  ctx.output("projectid", projectPath);
@@ -2536,253 +2932,6 @@ const createPublishGitlabMergeRequestAction = (options) => {
2536
2932
  });
2537
2933
  };
2538
2934
 
2539
- function createGithubActionsDispatchAction(options) {
2540
- const { integrations, githubCredentialsProvider } = options;
2541
- return createTemplateAction({
2542
- id: "github:actions:dispatch",
2543
- description: "Dispatches a GitHub Action workflow for a given branch or tag",
2544
- schema: {
2545
- input: {
2546
- type: "object",
2547
- required: ["repoUrl", "workflowId", "branchOrTagName"],
2548
- properties: {
2549
- repoUrl: {
2550
- title: "Repository Location",
2551
- description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
2552
- type: "string"
2553
- },
2554
- workflowId: {
2555
- title: "Workflow ID",
2556
- description: "The GitHub Action Workflow filename",
2557
- type: "string"
2558
- },
2559
- branchOrTagName: {
2560
- title: "Branch or Tag name",
2561
- description: "The git branch or tag name used to dispatch the workflow",
2562
- type: "string"
2563
- },
2564
- workflowInputs: {
2565
- title: "Workflow Inputs",
2566
- description: "Inputs keys and values to send to GitHub Action configured on the workflow file. The maximum number of properties is 10. ",
2567
- type: "object"
2568
- },
2569
- token: {
2570
- title: "Authentication Token",
2571
- type: "string",
2572
- description: "The GITHUB_TOKEN to use for authorization to GitHub"
2573
- }
2574
- }
2575
- }
2576
- },
2577
- async handler(ctx) {
2578
- const {
2579
- repoUrl,
2580
- workflowId,
2581
- branchOrTagName,
2582
- workflowInputs,
2583
- token: providedToken
2584
- } = ctx.input;
2585
- ctx.logger.info(`Dispatching workflow ${workflowId} for repo ${repoUrl} on ${branchOrTagName}`);
2586
- const { owner, repo } = parseRepoUrl(repoUrl, integrations);
2587
- if (!owner) {
2588
- throw new errors.InputError("Invalid repository owner provided in repoUrl");
2589
- }
2590
- const client = new octokit.Octokit(await getOctokitOptions({
2591
- integrations,
2592
- repoUrl,
2593
- credentialsProvider: githubCredentialsProvider,
2594
- token: providedToken
2595
- }));
2596
- await client.rest.actions.createWorkflowDispatch({
2597
- owner,
2598
- repo,
2599
- workflow_id: workflowId,
2600
- ref: branchOrTagName,
2601
- inputs: workflowInputs
2602
- });
2603
- ctx.logger.info(`Workflow ${workflowId} dispatched successfully`);
2604
- }
2605
- });
2606
- }
2607
-
2608
- function createGithubWebhookAction(options) {
2609
- const { integrations, defaultWebhookSecret, githubCredentialsProvider } = options;
2610
- const eventNames = webhooks.emitterEventNames.filter((event) => !event.includes("."));
2611
- return createTemplateAction({
2612
- id: "github:webhook",
2613
- description: "Creates webhook for a repository on GitHub.",
2614
- schema: {
2615
- input: {
2616
- type: "object",
2617
- required: ["repoUrl", "webhookUrl"],
2618
- properties: {
2619
- repoUrl: {
2620
- title: "Repository Location",
2621
- description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the new repository name and 'owner' is an organization or username`,
2622
- type: "string"
2623
- },
2624
- webhookUrl: {
2625
- title: "Webhook URL",
2626
- description: "The URL to which the payloads will be delivered",
2627
- type: "string"
2628
- },
2629
- webhookSecret: {
2630
- title: "Webhook Secret",
2631
- description: "Webhook secret value. The default can be provided internally in action creation",
2632
- type: "string"
2633
- },
2634
- events: {
2635
- title: "Triggering Events",
2636
- description: "Determines what events the hook is triggered for. Default: push",
2637
- type: "array",
2638
- oneOf: [
2639
- {
2640
- items: {
2641
- type: "string",
2642
- enum: eventNames
2643
- }
2644
- },
2645
- {
2646
- items: {
2647
- type: "string",
2648
- const: "*"
2649
- }
2650
- }
2651
- ]
2652
- },
2653
- active: {
2654
- title: "Active",
2655
- type: "boolean",
2656
- description: `Determines if notifications are sent when the webhook is triggered. Default: true`
2657
- },
2658
- contentType: {
2659
- title: "Content Type",
2660
- type: "string",
2661
- enum: ["form", "json"],
2662
- description: `The media type used to serialize the payloads. The default is 'form'`
2663
- },
2664
- insecureSsl: {
2665
- title: "Insecure SSL",
2666
- type: "boolean",
2667
- description: `Determines whether the SSL certificate of the host for url will be verified when delivering payloads. Default 'false'`
2668
- },
2669
- token: {
2670
- title: "Authentication Token",
2671
- type: "string",
2672
- description: "The GITHUB_TOKEN to use for authorization to GitHub"
2673
- }
2674
- }
2675
- }
2676
- },
2677
- async handler(ctx) {
2678
- const {
2679
- repoUrl,
2680
- webhookUrl,
2681
- webhookSecret = defaultWebhookSecret,
2682
- events = ["push"],
2683
- active = true,
2684
- contentType = "form",
2685
- insecureSsl = false,
2686
- token: providedToken
2687
- } = ctx.input;
2688
- ctx.logger.info(`Creating webhook ${webhookUrl} for repo ${repoUrl}`);
2689
- const { owner, repo } = parseRepoUrl(repoUrl, integrations);
2690
- if (!owner) {
2691
- throw new errors.InputError("Invalid repository owner provided in repoUrl");
2692
- }
2693
- const client = new octokit.Octokit(await getOctokitOptions({
2694
- integrations,
2695
- credentialsProvider: githubCredentialsProvider,
2696
- repoUrl,
2697
- token: providedToken
2698
- }));
2699
- try {
2700
- const insecure_ssl = insecureSsl ? "1" : "0";
2701
- await client.rest.repos.createWebhook({
2702
- owner,
2703
- repo,
2704
- config: {
2705
- url: webhookUrl,
2706
- content_type: contentType,
2707
- secret: webhookSecret,
2708
- insecure_ssl
2709
- },
2710
- events,
2711
- active
2712
- });
2713
- ctx.logger.info(`Webhook '${webhookUrl}' created successfully`);
2714
- } catch (e) {
2715
- errors.assertError(e);
2716
- ctx.logger.warn(`Failed: create webhook '${webhookUrl}' on repo: '${repo}', ${e.message}`);
2717
- }
2718
- }
2719
- });
2720
- }
2721
-
2722
- function createGithubIssuesLabelAction(options) {
2723
- const { integrations, githubCredentialsProvider } = options;
2724
- return createTemplateAction({
2725
- id: "github:issues:label",
2726
- description: "Adds labels to a pull request or issue on GitHub.",
2727
- schema: {
2728
- input: {
2729
- type: "object",
2730
- required: ["repoUrl", "number", "labels"],
2731
- properties: {
2732
- repoUrl: {
2733
- title: "Repository Location",
2734
- description: `Accepts the format 'github.com?repo=reponame&owner=owner' where 'reponame' is the repository name and 'owner' is an organization or username`,
2735
- type: "string"
2736
- },
2737
- number: {
2738
- title: "Pull Request or issue number",
2739
- description: "The pull request or issue number to add labels to",
2740
- type: "number"
2741
- },
2742
- labels: {
2743
- title: "Labels",
2744
- description: "The labels to add to the pull request or issue",
2745
- type: "array",
2746
- items: {
2747
- type: "string"
2748
- }
2749
- },
2750
- token: {
2751
- title: "Authentication Token",
2752
- type: "string",
2753
- description: "The GITHUB_TOKEN to use for authorization to GitHub"
2754
- }
2755
- }
2756
- }
2757
- },
2758
- async handler(ctx) {
2759
- const { repoUrl, number, labels, token: providedToken } = ctx.input;
2760
- const { owner, repo } = parseRepoUrl(repoUrl, integrations);
2761
- ctx.logger.info(`Adding labels to ${number} issue on repo ${repo}`);
2762
- if (!owner) {
2763
- throw new errors.InputError("Invalid repository owner provided in repoUrl");
2764
- }
2765
- const client = new octokit.Octokit(await getOctokitOptions({
2766
- integrations,
2767
- credentialsProvider: githubCredentialsProvider,
2768
- repoUrl,
2769
- token: providedToken
2770
- }));
2771
- try {
2772
- await client.rest.issues.addLabels({
2773
- owner,
2774
- repo,
2775
- issue_number: number,
2776
- labels
2777
- });
2778
- } catch (e) {
2779
- errors.assertError(e);
2780
- ctx.logger.warn(`Failed: adding labels to issue: '${number}' on repo: '${repo}', ${e.message}`);
2781
- }
2782
- }
2783
- });
2784
- }
2785
-
2786
2935
  const createBuiltinActions = (options) => {
2787
2936
  const {
2788
2937
  reader,
@@ -2854,6 +3003,15 @@ const createBuiltinActions = (options) => {
2854
3003
  createGithubIssuesLabelAction({
2855
3004
  integrations,
2856
3005
  githubCredentialsProvider
3006
+ }),
3007
+ createGithubRepoCreateAction({
3008
+ integrations,
3009
+ githubCredentialsProvider
3010
+ }),
3011
+ createGithubRepoPushAction({
3012
+ integrations,
3013
+ config,
3014
+ githubCredentialsProvider
2857
3015
  })
2858
3016
  ];
2859
3017
  return actions;
@@ -3985,6 +4143,8 @@ exports.createFilesystemDeleteAction = createFilesystemDeleteAction;
3985
4143
  exports.createFilesystemRenameAction = createFilesystemRenameAction;
3986
4144
  exports.createGithubActionsDispatchAction = createGithubActionsDispatchAction;
3987
4145
  exports.createGithubIssuesLabelAction = createGithubIssuesLabelAction;
4146
+ exports.createGithubRepoCreateAction = createGithubRepoCreateAction;
4147
+ exports.createGithubRepoPushAction = createGithubRepoPushAction;
3988
4148
  exports.createGithubWebhookAction = createGithubWebhookAction;
3989
4149
  exports.createPublishAzureAction = createPublishAzureAction;
3990
4150
  exports.createPublishBitbucketAction = createPublishBitbucketAction;