'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var pluginScaffolderNode = require('@backstage/plugin-scaffolder-node'); var node = require('@gitbeaker/node'); var zod = require('zod'); var errors = require('@backstage/errors'); var rest = require('@gitbeaker/rest'); var yaml = require('yaml'); var path = require('path'); var backendCommon = require('@backstage/backend-common'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var yaml__default = /*#__PURE__*/_interopDefaultLegacy(yaml); var path__default = /*#__PURE__*/_interopDefaultLegacy(path); const commonGitlabConfig = zod.z.object({ repoUrl: zod.z.string({ description: "Repository Location" }), token: zod.z.string({ description: "The token to use for authorization to GitLab" }).optional() }); const commonGitlabConfigExample = { repoUrl: "gitlab.com?owner=namespace-or-owner&repo=project-name", token: "${{ secrets.USER_OAUTH_TOKEN }}" }; const parseRepoHost = (repoUrl) => { let parsed; try { parsed = new URL(`https://${repoUrl}`); } catch (error) { throw new errors.InputError( `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}` ); } return parsed.host; }; const getToken = (config, integrations) => { const host = parseRepoHost(config.repoUrl); const integrationConfig = integrations.gitlab.byHost(host); if (!integrationConfig) { throw new errors.InputError( `No matching integration configuration for host ${host}, please check your integrations config` ); } const token = config.token || integrationConfig.config.token; const tokenType = config.token ? "oauthToken" : "token"; if (tokenType === "oauthToken") { throw new errors.InputError(`OAuth Token is currently not supported`); } return { token, integrationConfig }; }; const parseRepoUrl = (repoUrl, integrations) => { var _a, _b; let parsed; try { parsed = new URL(`https://${repoUrl}`); } catch (error) { throw new errors.InputError( `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}` ); } const host = parsed.host; const owner = (_a = parsed.searchParams.get("owner")) != null ? _a : void 0; const repo = parsed.searchParams.get("repo"); const type = (_b = integrations.byHost(host)) == null ? void 0 : _b.type; if (!type) { throw new errors.InputError( `No matching integration configuration for host ${host}, please check your integrations config` ); } return { host, owner, repo }; }; function getClient(props) { const { host, token, integrations } = props; const integrationConfig = integrations.gitlab.byHost(host); if (!integrationConfig) { throw new errors.InputError( `No matching integration configuration for host ${host}, please check your integrations config` ); } const { config } = integrationConfig; if (!config.token && !token) { throw new errors.InputError(`No token available for host ${host}`); } const requestToken = token || config.token; const tokenType = token ? "oauthToken" : "token"; const gitlabOptions = { host: config.baseUrl }; gitlabOptions[tokenType] = requestToken; return new rest.Gitlab(gitlabOptions); } function convertDate(inputDate, defaultDate) { try { return inputDate ? new Date(inputDate).toISOString() : new Date(defaultDate).toISOString(); } catch (error) { throw new errors.InputError(`Error converting input date - ${error}`); } } async function getTopLevelParentGroup(client, groupId) { try { const topParentGroup = await client.Groups.show(groupId); if (topParentGroup.parent_id) { return getTopLevelParentGroup( client, topParentGroup.parent_id ); } return topParentGroup; } catch (error) { throw new errors.InputError( `Error finding top-level parent group ID: ${error.message}` ); } } async function checkEpicScope(client, projectId, epicId) { try { const project = await client.Projects.show(projectId); if (!project) { throw new errors.InputError( `Project with id ${projectId} not found. Check your GitLab instance.` ); } const topParentGroup = await getTopLevelParentGroup( client, project.namespace.id ); if (!topParentGroup) { throw new errors.InputError(`Couldn't find a suitable top-level parent group.`); } const epic = (await client.Epics.all(topParentGroup.id)).find( (x) => x.id === epicId ); if (!epic) { throw new errors.InputError( `Epic with id ${epicId} not found in the top-level parent group ${topParentGroup.name}.` ); } const epicGroup = await client.Groups.show(epic.group_id); const projectNamespace = project.path_with_namespace; return projectNamespace.startsWith(epicGroup.full_path); } catch (error) { throw new errors.InputError(`Could not find epic scope: ${error.message}`); } } const createGitlabGroupEnsureExistsAction = (options) => { const { integrations } = options; return pluginScaffolderNode.createTemplateAction({ id: "gitlab:group:ensureExists", description: "Ensures a Gitlab group exists", supportsDryRun: true, schema: { input: commonGitlabConfig.merge( zod.z.object({ path: zod.z.array(zod.z.string(), { description: "A path of group names that is ensured to exist" }).min(1) }) ), output: zod.z.object({ groupId: zod.z.number({ description: "The id of the innermost sub-group" }).optional() }) }, async handler(ctx) { if (ctx.isDryRun) { ctx.output("groupId", 42); return; } const { path } = ctx.input; const { token, integrationConfig } = getToken(ctx.input, integrations); const api = new node.Gitlab({ host: integrationConfig.config.baseUrl, token }); let currentPath = null; let parent = null; for (const pathElement of path) { const fullPath = currentPath ? `${currentPath}/${pathElement}` : pathElement; const result = await api.Groups.search( fullPath ); const subGroup = result.find( (searchPathElem) => searchPathElem.full_path === fullPath ); if (!subGroup) { ctx.logger.info(`creating missing group ${fullPath}`); parent = await api.Groups.create( pathElement, pathElement, parent ? { parent_id: parent.id } : {} ); } else { parent = subGroup; } currentPath = fullPath; } if (parent !== null) { ctx.output("groupId", parent == null ? void 0 : parent.id); } } }); }; const createGitlabProjectDeployTokenAction = (options) => { const { integrations } = options; return pluginScaffolderNode.createTemplateAction({ id: "gitlab:projectDeployToken:create", schema: { input: commonGitlabConfig.merge( zod.z.object({ projectId: zod.z.union([zod.z.number(), zod.z.string()], { description: "Project ID" }), name: zod.z.string({ description: "Deploy Token Name" }), username: zod.z.string({ description: "Deploy Token Username" }).optional(), scopes: zod.z.array(zod.z.string(), { description: "Scopes" }).optional() }) ), output: zod.z.object({ deploy_token: zod.z.string({ description: "Deploy Token" }), user: zod.z.string({ description: "User" }) }) }, async handler(ctx) { ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`); const { projectId, name, username, scopes } = ctx.input; const { token, integrationConfig } = getToken(ctx.input, integrations); const api = new node.Gitlab({ host: integrationConfig.config.baseUrl, token }); const deployToken = await api.ProjectDeployTokens.add( projectId, name, scopes, { username } ); if (!deployToken.hasOwnProperty("token")) { throw new errors.InputError(`No deploy_token given from gitlab instance`); } ctx.output("deploy_token", deployToken.token); ctx.output("user", deployToken.username); } }); }; const examples$2 = [ { description: "Create a GitLab project access token with minimal options.", example: yaml__default["default"].stringify({ steps: [ { id: "createAccessToken", action: "gitlab:projectAccessToken:create", name: "Create GitLab Project Access Token", input: { repoUrl: "gitlab.com?repo=repo&owner=owner", projectId: "456" } } ] }) }, { description: "Create a GitLab project access token with custom scopes.", example: yaml__default["default"].stringify({ steps: [ { id: "createAccessToken", action: "gitlab:projectAccessToken:create", name: "Create GitLab Project Access Token", input: { repoUrl: "gitlab.com?repo=repo&owner=owner", projectId: "789", scopes: ["read_registry", "write_repository"] } } ] }) }, { description: "Create a GitLab project access token with a specified name.", example: yaml__default["default"].stringify({ steps: [ { id: "createAccessToken", action: "gitlab:projectAccessToken:create", name: "Create GitLab Project Access Token", input: { repoUrl: "gitlab.com?repo=repo&owner=owner", projectId: "101112", name: "my-custom-token" } } ] }) }, { description: "Create a GitLab project access token with a numeric project ID.", example: yaml__default["default"].stringify({ steps: [ { id: "createAccessToken", action: "gitlab:projectAccessToken:create", name: "Create GitLab Project Access Token", input: { repoUrl: "gitlab.com?repo=repo&owner=owner", projectId: 42 } } ] }) }, { description: "Create a GitLab project access token using specific GitLab integrations.", example: yaml__default["default"].stringify({ steps: [ { id: "createAccessToken", action: "gitlab:projectAccessToken:create", name: "Create GitLab Project Access Token", input: { repoUrl: "gitlab.com?repo=repo&owner=owner", projectId: "123" } } ] }) } ]; const createGitlabProjectAccessTokenAction = (options) => { const { integrations } = options; return pluginScaffolderNode.createTemplateAction({ id: "gitlab:projectAccessToken:create", examples: examples$2, schema: { input: commonGitlabConfig.merge( zod.z.object({ projectId: zod.z.union([zod.z.number(), zod.z.string()], { description: "Project ID" }), name: zod.z.string({ description: "Deploy Token Name" }).optional(), accessLevel: zod.z.number({ description: "Access Level of the Token" }).optional(), scopes: zod.z.array(zod.z.string(), { description: "Scopes" }).optional() }) ), output: zod.z.object({ access_token: zod.z.string({ description: "Access Token" }) }) }, async handler(ctx) { ctx.logger.info(`Creating Token for Project "${ctx.input.projectId}"`); const { projectId, name, accessLevel, scopes } = ctx.input; const { token, integrationConfig } = getToken(ctx.input, integrations); const response = await fetch( `${integrationConfig.config.baseUrl}/api/v4/projects/${projectId}/access_tokens`, { method: "POST", // *GET, POST, PUT, DELETE, etc. headers: { "PRIVATE-TOKEN": token, "Content-Type": "application/json" }, body: JSON.stringify({ name, scopes, access_level: accessLevel }) } ); const result = await response.json(); ctx.output("access_token", result.token); } }); }; const createGitlabProjectVariableAction = (options) => { const { integrations } = options; return pluginScaffolderNode.createTemplateAction({ id: "gitlab:projectVariable:create", schema: { input: commonGitlabConfig.merge( zod.z.object({ projectId: zod.z.union([zod.z.number(), zod.z.string()], { description: "Project ID" }), key: zod.z.string({ description: "The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed" }).regex(/^[A-Za-z0-9_]{1,255}$/), value: zod.z.string({ description: "The value of a variable" }), variableType: zod.z.string({ description: "Variable Type (env_var or file)" }), variableProtected: zod.z.boolean({ description: "Whether the variable is protected" }).default(false).optional(), masked: zod.z.boolean({ description: "Whether the variable is masked" }).default(false).optional(), raw: zod.z.boolean({ description: "Whether the variable is expandable" }).default(false).optional(), environmentScope: zod.z.string({ description: "The environment_scope of the variable" }).default("*").optional() }) ) }, async handler(ctx) { const { projectId, key, value, variableType, variableProtected = false, masked = false, raw = false, environmentScope = "*" } = ctx.input; const { token, integrationConfig } = getToken(ctx.input, integrations); const api = new node.Gitlab({ host: integrationConfig.config.baseUrl, token }); await api.ProjectVariables.create(projectId, { key, value, variable_type: variableType, protected: variableProtected, masked, raw, environment_scope: environmentScope }); } }); }; const examples$1 = [ { description: "Create a GitLab issue with minimal options", example: yaml__default["default"].stringify({ steps: [ { id: "gitlabIssue", name: "Issues", action: "gitlab:issues:create", input: { ...commonGitlabConfigExample, projectId: 12, title: "Test Issue", description: "This is the description of the issue" } } ] }) }, { description: "Create a GitLab issue with assignees and date options", example: yaml__default["default"].stringify({ steps: [ { id: "gitlabIssue", name: "Issues", action: "gitlab:issues:create", input: { ...commonGitlabConfigExample, projectId: 12, title: "Test Issue", assignees: [18], description: "This is the description of the issue", createdAt: "2022-09-27 18:00:00.000", dueDate: "2022-09-28 12:00:00.000" } } ] }) }, { description: "Create a GitLab Issue with several options", example: yaml__default["default"].stringify({ steps: [ { id: "gitlabIssue", name: "Issues", action: "gitlab:issues:create", input: { ...commonGitlabConfigExample, projectId: 12, title: "Test Issue", assignees: [18, 15], description: "This is the description of the issue", confidential: false, createdAt: "2022-09-27 18:00:00.000", dueDate: "2022-09-28 12:00:00.000", discussionToResolve: 1, epicId: 1, labels: "phase1:label1,phase2:label2" } } ] }) } ]; var IssueType = /* @__PURE__ */ ((IssueType2) => { IssueType2["ISSUE"] = "issue"; IssueType2["INCIDENT"] = "incident"; IssueType2["TEST"] = "test_case"; return IssueType2; })(IssueType || {}); const issueInputProperties = zod.z.object({ projectId: zod.z.number().describe("Project Id"), title: zod.z.string({ description: "Title of the issue" }), assignees: zod.z.array(zod.z.number(), { description: "IDs of the users to assign the issue to." }).optional(), confidential: zod.z.boolean({ description: "Issue Confidentiality" }).optional(), description: zod.z.string().describe("Issue description").max(1048576).optional(), createdAt: zod.z.string().describe("Creation date/time").regex( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, "Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ" ).optional(), dueDate: zod.z.string().describe("Due date/time").regex( /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/, "Invalid date format. Use YYYY-MM-DDTHH:mm:ssZ or YYYY-MM-DDTHH:mm:ss.SSSZ" ).optional(), discussionToResolve: zod.z.string({ description: 'Id of a discussion to resolve. Use in combination with "merge_request_to_resolve_discussions_of"' }).optional(), epicId: zod.z.number({ description: "Id of the linked Epic" }).min(0, "Valid values should be equal or greater than zero").optional(), labels: zod.z.string({ description: "Labels to apply" }).optional(), issueType: zod.z.nativeEnum(IssueType, { description: "Type of the issue" }).optional(), mergeRequestToResolveDiscussionsOf: zod.z.number({ description: "IID of a merge request in which to resolve all issues" }).optional(), milestoneId: zod.z.number({ description: "Global ID of a milestone to assign the issue" }).optional(), weight: zod.z.number({ description: "The issue weight" }).min(0).refine((value) => { const isValid = value >= 0; if (!isValid) { return { message: "Valid values should be equal or greater than zero" }; } return isValid; }).optional() }); const issueOutputProperties = zod.z.object({ issueUrl: zod.z.string({ description: "Issue Url" }), issueId: zod.z.number({ description: "Issue Id" }) }); const createGitlabIssueAction = (options) => { const { integrations } = options; return pluginScaffolderNode.createTemplateAction({ id: "gitlab:issues:create", description: "Creates a Gitlab issue.", examples: examples$1, schema: { input: commonGitlabConfig.merge(issueInputProperties), output: issueOutputProperties }, async handler(ctx) { try { const { repoUrl, projectId, title, description = "", confidential = false, assignees = [], createdAt = "", dueDate, discussionToResolve = "", epicId, labels = "", issueType, mergeRequestToResolveDiscussionsOf, milestoneId, weight, token } = commonGitlabConfig.merge(issueInputProperties).parse(ctx.input); const { host } = parseRepoUrl(repoUrl, integrations); const api = getClient({ host, integrations, token }); let isEpicScoped = false; if (epicId) { isEpicScoped = await checkEpicScope( api, projectId, epicId ); if (isEpicScoped) { ctx.logger.info("Epic is within Project Scope"); } else { ctx.logger.warn( "Chosen epic is not within the Project Scope. The issue will be created without an associated epic." ); } } const mappedCreatedAt = convertDate( String(createdAt), (/* @__PURE__ */ new Date()).toISOString() ); const mappedDueDate = dueDate ? convertDate(String(dueDate), (/* @__PURE__ */ new Date()).toISOString()) : void 0; const issueOptions = { description, assigneeIds: assignees, confidential, epicId: isEpicScoped ? epicId : void 0, labels, createdAt: mappedCreatedAt, dueDate: mappedDueDate, discussionToResolve, issueType, mergeRequestToResolveDiscussionsOf, milestoneId, weight }; const response = await api.Issues.create( projectId, title, issueOptions ); ctx.output("issueId", response.id); ctx.output("issueUrl", response.web_url); } catch (error) { if (error instanceof zod.z.ZodError) { throw new errors.InputError(`Validation error: ${error.message}`, { validationErrors: error.errors }); } throw new errors.InputError(`Failed to create GitLab issue: ${error.message}`); } } }); }; const examples = [ { description: "Initializes a git repository of the content in the workspace, and publishes it to GitLab.", example: yaml__default["default"].stringify({ steps: [ { id: "publish", action: "publish:gitlab", name: "Publish to GitLab", input: { repoUrl: "gitlab.com?repo=project_name&owner=group_name" } } ] }) }, { description: "Add a description.", example: yaml__default["default"].stringify({ steps: [ { id: "publish", action: "publish:gitlab", name: "Publish to GitLab", input: { repoUrl: "gitlab.com?repo=project_name&owner=group_name", description: "Initialize a git repository" } } ] }) }, { description: "Sets the commit message on the repository. The default value is `initial commit`.", example: yaml__default["default"].stringify({ steps: [ { id: "publish", action: "publish:gitlab", name: "Publish to GitLab", input: { repoUrl: "gitlab.com?repo=project_name&owner=group_name", description: "Initialize a git repository", gitCommitMessage: "Started a project." } } ] }) }, { description: "Initializes a git repository with additional settings.", example: yaml__default["default"].stringify({ steps: [ { id: "publish", action: "publish:gitlab", name: "Publish to GitLab", input: { repoUrl: "gitlab.com?repo=project_name&owner=group_name", settings: { ci_config_path: ".gitlab-ci.yml", visibility: "public" } } } ] }) }, { description: "Initializes a git repository with branches settings", example: yaml__default["default"].stringify({ steps: [ { id: "publish", action: "publish:gitlab", name: "Publish to GitLab", input: { repoUrl: "gitlab.com?repo=project_name&owner=group_name", branches: [ { name: "dev", create: true, protected: true, ref: "master" }, { name: "master", protected: true } ] } } ] }) }, { description: "Initializes a git repository with environment variables", example: yaml__default["default"].stringify({ steps: [ { id: "publish", action: "publish:gitlab", name: "Publish to GitLab", input: { repoUrl: "gitlab.com?repo=project_name&owner=group_name", projectVariables: [ { key: "key1", value: "value1", protected: true, masked: false }, { key: "key2", value: "value2", protected: true, masked: false } ] } } ] }) } ]; function createPublishGitlabAction(options) { const { integrations, config } = options; return pluginScaffolderNode.createTemplateAction({ id: "publish:gitlab", description: "Initializes a git repository of the content in the workspace, and publishes it to GitLab.", examples, schema: { input: { type: "object", required: ["repoUrl"], properties: { repoUrl: { title: "Repository Location", type: "string", description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username` }, repoVisibility: { title: "Repository Visibility", description: `Sets the visibility of the repository. The default value is 'private'. (deprecated, use settings.visibility instead)`, type: "string", enum: ["private", "public", "internal"] }, defaultBranch: { title: "Default Branch", type: "string", description: `Sets the default branch on the repository. The default value is 'master'` }, gitCommitMessage: { title: "Git Commit Message", type: "string", description: `Sets the commit message on the repository. The default value is 'initial commit'` }, gitAuthorName: { title: "Default Author Name", type: "string", description: `Sets the default author name for the commit. The default value is 'Scaffolder'` }, gitAuthorEmail: { title: "Default Author Email", type: "string", description: `Sets the default author email for the commit.` }, sourcePath: { title: "Source Path", description: "Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.", type: "string" }, token: { title: "Authentication Token", type: "string", description: "The token to use for authorization to GitLab" }, setUserAsOwner: { title: "Set User As Owner", type: "boolean", 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" }, topics: { title: "Topic labels", description: "Topic labels to apply on the repository. (deprecated, use settings.topics instead)", type: "array", items: { type: "string" } }, settings: { title: "Project settings", description: "Additional project settings, based on https://docs.gitlab.com/ee/api/projects.html#create-project attributes", type: "object", properties: { path: { title: "Project path", description: "Repository name for new project. Generated based on name if not provided (generated as lowercase with dashes).", type: "string" }, auto_devops_enabled: { title: "Auto DevOps enabled", description: "Enable Auto DevOps for this project", type: "boolean" }, ci_config_path: { title: "CI config path", description: "Custom CI config path for this project", type: "string" }, description: { title: "Project description", description: "Short project description", type: "string" }, topics: { title: "Topic labels", description: "Topic labels to apply on the repository", type: "array", items: { type: "string" } }, visibility: { title: "Project visibility", description: "The visibility of the project. Can be private, internal, or public. The default value is private.", type: "string", enum: ["private", "public", "internal"] } } }, branches: { title: "Project branches settings", type: "array", items: { type: "object", required: ["name"], properties: { name: { title: "Branch name", type: "string" }, protect: { title: "Should branch be protected", description: `Will mark branch as protected. The default value is 'false'`, type: "boolean" }, create: { title: "Should branch be created", description: `If branch does not exist, it will be created from provided ref. The default value is 'false'`, type: "boolean" }, ref: { title: "Branch reference", description: `Branch reference to create branch from. The default value is 'master'`, type: "string" } } } }, projectVariables: { title: "Project variables", description: "Project variables settings based on Gitlab Project Environments API - https://docs.gitlab.com/ee/api/project_level_variables.html#create-a-variable", type: "array", items: { type: "object", required: ["key", "value"], properties: { key: { title: "Variable key", description: "The key of a variable; must have no more than 255 characters; only A-Z, a-z, 0-9, and _ are allowed", type: "string" }, value: { title: "Variable value", description: "The value of a variable", type: "string" }, description: { title: "Variable description", description: `The description of the variable. The default value is 'null'`, type: "string" }, variable_type: { title: "Variable type", description: `The type of a variable. The default value is 'env_var'`, type: "string", enum: ["env_var", "file"] }, protected: { title: "Variable protection", description: `Whether the variable is protected. The default value is 'false'`, type: "boolean" }, raw: { title: "Variable raw", description: `Whether the variable is in raw format. The default value is 'false'`, type: "boolean" }, environment_scope: { title: "Variable environment scope", description: `The environment_scope of the variable. The default value is '*'`, type: "string" } } } } } }, output: { type: "object", properties: { remoteUrl: { title: "A URL to the repository with the provider", type: "string" }, repoContentsUrl: { title: "A URL to the root of the repository", type: "string" }, projectId: { title: "The ID of the project", type: "string" }, commitHash: { title: "The git commit hash of the initial commit", type: "string" } } } }, async handler(ctx) { var _a, _b, _c, _d, _e; const { repoUrl, repoVisibility = "private", defaultBranch = "master", gitCommitMessage = "initial commit", gitAuthorName, gitAuthorEmail, setUserAsOwner = false, topics = [], settings = {}, branches = [], projectVariables = [] } = ctx.input; const { owner, repo, host } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations); if (!owner) { throw new errors.InputError( `No owner provided for host: ${host}, and repo ${repo}` ); } const integrationConfig = integrations.gitlab.byHost(host); if (!integrationConfig) { throw new errors.InputError( `No matching integration configuration for host ${host}, please check your integrations config` ); } if (!integrationConfig.config.token && !ctx.input.token) { throw new errors.InputError(`No token available for host ${host}`); } const token = ctx.input.token || integrationConfig.config.token; const tokenType = ctx.input.token ? "oauthToken" : "token"; const client = new node.Gitlab({ host: integrationConfig.config.baseUrl, [tokenType]: token }); let targetNamespaceId; try { const namespaceResponse = await client.Namespaces.show(owner); targetNamespaceId = namespaceResponse.id; } catch (e) { if (e.response && e.response.statusCode === 404) { throw new errors.InputError( `The namespace ${owner} is not found or the user doesn't have permissions to access it` ); } throw e; } const { id: userId } = await client.Users.current(); if (!targetNamespaceId) { targetNamespaceId = userId; } const { id: projectId, http_url_to_repo } = await client.Projects.create({ namespace_id: targetNamespaceId, name: repo, visibility: repoVisibility, ...topics.length ? { topics } : {}, ...Object.keys(settings).length ? { ...settings } : {} }); if (setUserAsOwner && integrationConfig.config.token) { const adminClient = new node.Gitlab({ host: integrationConfig.config.baseUrl, token: integrationConfig.config.token }); await adminClient.ProjectMembers.add(projectId, userId, 50); } const remoteUrl = http_url_to_repo.replace(/\.git$/, ""); const repoContentsUrl = `${remoteUrl}/-/blob/${defaultBranch}`; const gitAuthorInfo = { name: gitAuthorName ? gitAuthorName : config.getOptionalString("scaffolder.defaultAuthor.name"), email: gitAuthorEmail ? gitAuthorEmail : config.getOptionalString("scaffolder.defaultAuthor.email") }; const commitResult = await pluginScaffolderNode.initRepoAndPush({ dir: pluginScaffolderNode.getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl: http_url_to_repo, defaultBranch, auth: { username: "oauth2", password: token }, logger: ctx.logger, commitMessage: gitCommitMessage ? gitCommitMessage : config.getOptionalString("scaffolder.defaultCommitMessage"), gitAuthorInfo }); if (branches) { for (const branch of branches) { const { name, protect = false, create = false, ref = "master" } = branch; if (create) { try { await client.Branches.create(projectId, name, ref); } catch (e) { throw new errors.InputError( `Branch creation failed for ${name}. ${printGitlabError(e)}` ); } ctx.logger.info( `Branch ${name} created for ${projectId} with ref ${ref}` ); } if (protect) { try { await client.ProtectedBranches.protect(projectId, name); } catch (e) { throw new errors.InputError( `Branch protection failed for ${name}. ${printGitlabError(e)}` ); } ctx.logger.info(`Branch ${name} protected for ${projectId}`); } } } if (projectVariables) { for (const variable of projectVariables) { const variableWithDefaults = Object.assign(variable, { variable_type: (_a = variable.variable_type) != null ? _a : "env_var", protected: (_b = variable.protected) != null ? _b : false, masked: (_c = variable.masked) != null ? _c : false, raw: (_d = variable.raw) != null ? _d : false, environment_scope: (_e = variable.environment_scope) != null ? _e : "*" }); try { await client.ProjectVariables.create( projectId, variableWithDefaults ); } catch (e) { throw new errors.InputError( `Environment variable creation failed for ${variableWithDefaults.key}. ${printGitlabError(e)}` ); } } } ctx.output("commitHash", commitResult == null ? void 0 : commitResult.commitHash); ctx.output("remoteUrl", remoteUrl); ctx.output("repoContentsUrl", repoContentsUrl); ctx.output("projectId", projectId); } }); } function printGitlabError(error) { return JSON.stringify({ code: error.code, message: error.description }); } function createGitlabApi(options) { const { integrations, token: providedToken, repoUrl } = options; const { host } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations); const integrationConfig = integrations.gitlab.byHost(host); if (!integrationConfig) { throw new errors.InputError( `No matching integration configuration for host ${host}, please check your integrations config` ); } if (!integrationConfig.config.token && !providedToken) { throw new errors.InputError(`No token available for host ${host}`); } const token = providedToken != null ? providedToken : integrationConfig.config.token; const tokenType = providedToken ? "oauthToken" : "token"; return new node.Gitlab({ host: integrationConfig.config.baseUrl, [tokenType]: token }); } const createPublishGitlabMergeRequestAction = (options) => { const { integrations } = options; return pluginScaffolderNode.createTemplateAction({ id: "publish:gitlab:merge-request", schema: { input: { required: ["repoUrl", "branchName"], type: "object", properties: { repoUrl: { type: "string", title: "Repository Location", description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username` }, /** @deprecated projectID is passed as query parameters in the repoUrl */ projectid: { type: "string", title: "projectid", description: "Project ID/Name(slug) of the Gitlab Project" }, title: { type: "string", title: "Merge Request Name", description: "The name for the merge request" }, description: { type: "string", title: "Merge Request Description", description: "The description of the merge request" }, branchName: { type: "string", title: "Source Branch Name", description: "The source branch name of the merge request" }, targetBranchName: { type: "string", title: "Target Branch Name", description: "The target branch name of the merge request" }, sourcePath: { type: "string", title: "Working Subdirectory", description: "Subdirectory of working directory to copy changes from" }, targetPath: { type: "string", title: "Repository Subdirectory", description: "Subdirectory of repository to apply changes to" }, token: { title: "Authentication Token", type: "string", description: "The token to use for authorization to GitLab" }, commitAction: { title: "Commit action", type: "string", enum: ["create", "update", "delete"], description: "The action to be used for git commit. Defaults to create." }, removeSourceBranch: { title: "Delete source branch", type: "boolean", description: "Option to delete source branch once the MR has been merged. Default: false" }, assignee: { title: "Merge Request Assignee", type: "string", description: "User this merge request will be assigned to" } } }, output: { type: "object", properties: { targetBranchName: { title: "Target branch name of the merge request", type: "string" }, projectid: { title: "Gitlab Project id/Name(slug)", type: "string" }, projectPath: { title: "Gitlab Project path", type: "string" }, mergeRequestUrl: { title: "MergeRequest(MR) URL", type: "string", description: "Link to the merge request in GitLab" } } } }, async handler(ctx) { const { assignee, branchName, targetBranchName, description, repoUrl, removeSourceBranch, targetPath, sourcePath, title, token } = ctx.input; const { owner, repo, project } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations); const repoID = project ? project : `${owner}/${repo}`; const api = createGitlabApi({ integrations, token, repoUrl }); let assigneeId = void 0; if (assignee !== void 0) { try { const assigneeUser = await api.Users.username(assignee); assigneeId = assigneeUser[0].id; } catch (e) { ctx.logger.warn( `Failed to find gitlab user id for ${assignee}: ${e}. Proceeding with MR creation without an assignee.` ); } } let fileRoot; if (sourcePath) { fileRoot = backendCommon.resolveSafeChildPath(ctx.workspacePath, sourcePath); } else if (targetPath) { fileRoot = backendCommon.resolveSafeChildPath(ctx.workspacePath, targetPath); } else { fileRoot = ctx.workspacePath; } const fileContents = await pluginScaffolderNode.serializeDirectoryContents(fileRoot, { gitignore: true }); const actions = fileContents.map((file) => { var _a; return { action: (_a = ctx.input.commitAction) != null ? _a : "create", filePath: targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path, encoding: "base64", content: file.content.toString("base64"), execute_filemode: file.executable }; }); let targetBranch = targetBranchName; if (!targetBranch) { const projects = await api.Projects.show(repoID); const { default_branch: defaultBranch } = projects; targetBranch = defaultBranch; } try { await api.Branches.create(repoID, branchName, String(targetBranch)); } catch (e) { throw new errors.InputError( `The branch creation failed. Please check that your repo does not already contain a branch named '${branchName}'. ${e}` ); } try { await api.Commits.create(repoID, branchName, ctx.input.title, actions); } catch (e) { throw new errors.InputError( `Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${e}` ); } try { const mergeRequestUrl = await api.MergeRequests.create( repoID, branchName, String(targetBranch), title, { description, removeSourceBranch: removeSourceBranch ? removeSourceBranch : false, assigneeId } ).then((mergeRequest) => { return mergeRequest.web_url; }); ctx.output("projectid", repoID); ctx.output("targetBranchName", targetBranch); ctx.output("projectPath", repoID); ctx.output("mergeRequestUrl", mergeRequestUrl); } catch (e) { throw new errors.InputError(`Merge request creation failed${e}`); } } }); }; const createGitlabRepoPushAction = (options) => { const { integrations } = options; return pluginScaffolderNode.createTemplateAction({ id: "gitlab:repo:push", schema: { input: { required: ["repoUrl", "branchName", "commitMessage"], type: "object", properties: { repoUrl: { type: "string", title: "Repository Location", description: `Accepts the format 'gitlab.com?repo=project_name&owner=group_name' where 'project_name' is the repository name and 'group_name' is a group or username` }, branchName: { type: "string", title: "Source Branch Name", description: "The branch name for the commit" }, commitMessage: { type: "string", title: "Commit Message", description: `The commit message` }, sourcePath: { type: "string", title: "Working Subdirectory", description: "Subdirectory of working directory to copy changes from" }, targetPath: { type: "string", title: "Repository Subdirectory", description: "Subdirectory of repository to apply changes to" }, token: { title: "Authentication Token", type: "string", description: "The token to use for authorization to GitLab" }, commitAction: { title: "Commit action", type: "string", enum: ["create", "update", "delete"], description: "The action to be used for git commit. Defaults to create." } } }, output: { type: "object", properties: { projectid: { title: "Gitlab Project id/Name(slug)", type: "string" }, projectPath: { title: "Gitlab Project path", type: "string" }, commitHash: { title: "The git commit hash of the commit", type: "string" } } } }, async handler(ctx) { var _a; const { branchName, repoUrl, targetPath, sourcePath, token, commitAction } = ctx.input; const { owner, repo, project } = pluginScaffolderNode.parseRepoUrl(repoUrl, integrations); const repoID = project ? project : `${owner}/${repo}`; const api = createGitlabApi({ integrations, token, repoUrl }); let fileRoot; if (sourcePath) { fileRoot = backendCommon.resolveSafeChildPath(ctx.workspacePath, sourcePath); } else { fileRoot = ctx.workspacePath; } const fileContents = await pluginScaffolderNode.serializeDirectoryContents(fileRoot, { gitignore: true }); const actions = fileContents.map((file) => ({ action: commitAction != null ? commitAction : "create", filePath: targetPath ? path__default["default"].posix.join(targetPath, file.path) : file.path, encoding: "base64", content: file.content.toString("base64"), execute_filemode: file.executable })); let branchExists = false; try { await api.Branches.show(repoID, branchName); branchExists = true; } catch (e) { if (((_a = e.response) == null ? void 0 : _a.statusCode) !== 404) { throw new errors.InputError( `Failed to check status of branch '${branchName}'. Please make sure that branch already exists or Backstage has permissions to create one. ${e}` ); } } if (!branchExists) { try { const projects = await api.Projects.show(repoID); const { default_branch: defaultBranch } = projects; await api.Branches.create(repoID, branchName, String(defaultBranch)); } catch (e) { throw new errors.InputError( `The branch '${branchName}' was not found and creation failed with error. Please make sure that branch already exists or Backstage has permissions to create one. ${e}` ); } } try { const commit = await api.Commits.create( repoID, branchName, ctx.input.commitMessage, actions ); ctx.output("projectid", repoID); ctx.output("projectPath", repoID); ctx.output("commitHash", commit.id); } catch (e) { throw new errors.InputError( `Committing the changes to ${branchName} failed. Please check that none of the files created by the template already exists. ${e}` ); } } }); }; exports.IssueType = IssueType; exports.createGitlabGroupEnsureExistsAction = createGitlabGroupEnsureExistsAction; exports.createGitlabIssueAction = createGitlabIssueAction; exports.createGitlabProjectAccessTokenAction = createGitlabProjectAccessTokenAction; exports.createGitlabProjectDeployTokenAction = createGitlabProjectDeployTokenAction; exports.createGitlabProjectVariableAction = createGitlabProjectVariableAction; exports.createGitlabRepoPushAction = createGitlabRepoPushAction; exports.createPublishGitlabAction = createPublishGitlabAction; exports.createPublishGitlabMergeRequestAction = createPublishGitlabMergeRequestAction; //# sourceMappingURL=index.cjs.js.map