From 81f57ff279193aca741dfcb0c8822f7401e64218 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 6 Dec 2023 12:45:46 +0100 Subject: [PATCH] chore: move some other things around a little bit Signed-off-by: blam --- plugins/scaffolder-backend/package.json | 11 +- .../actions/builtin/publish/util.ts | 119 +--------------- plugins/scaffolder-node/src/actions/index.ts | 2 + .../src/actions/repoHelpers.ts | 127 ++++++++++++++++++ plugins/scaffolder-node/src/actions/util.ts | 112 +++++++++++++++ .../deserializeDirectoryContents.test.ts | 0 .../files/deserializeDirectoryContents.ts | 0 .../src}/files/index.ts | 0 .../files/serializeDirectoryContents.test.ts | 0 .../src}/files/serializeDirectoryContents.ts | 0 .../src}/files/types.ts | 0 plugins/scaffolder-node/src/index.ts | 1 + .../scaffolder-react/src/components/types.ts | 1 + 13 files changed, 249 insertions(+), 124 deletions(-) create mode 100644 plugins/scaffolder-node/src/actions/repoHelpers.ts create mode 100644 plugins/scaffolder-node/src/actions/util.ts rename plugins/{scaffolder-backend/src/lib => scaffolder-node/src}/files/deserializeDirectoryContents.test.ts (100%) rename plugins/{scaffolder-backend/src/lib => scaffolder-node/src}/files/deserializeDirectoryContents.ts (100%) rename plugins/{scaffolder-backend/src/lib => scaffolder-node/src}/files/index.ts (100%) rename plugins/{scaffolder-backend/src/lib => scaffolder-node/src}/files/serializeDirectoryContents.test.ts (100%) rename plugins/{scaffolder-backend/src/lib => scaffolder-node/src}/files/serializeDirectoryContents.ts (100%) rename plugins/{scaffolder-backend/src/lib => scaffolder-node/src}/files/types.ts (100%) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f1ea2a3517..d89fc2f3ca 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -64,9 +64,6 @@ "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/plugin-scaffolder-node": "workspace:^", "@backstage/types": "workspace:^", - "@gitbeaker/core": "^35.6.0", - "@gitbeaker/node": "^35.1.0", - "@octokit/webhooks": "^10.0.0", "@types/express": "^4.17.6", "@types/luxon": "^3.0.0", "azure-devops-node-api": "^11.0.1", @@ -83,14 +80,11 @@ "isomorphic-git": "^1.23.0", "jsonschema": "^1.2.6", "knex": "^3.0.0", - "libsodium-wrappers": "^0.7.11", "lodash": "^4.17.21", "luxon": "^3.0.0", "morgan": "^1.10.0", "node-fetch": "^2.6.7", "nunjucks": "^3.2.3", - "octokit": "^2.0.0", - "octokit-plugin-create-pull-request": "^3.10.0", "p-limit": "^3.1.0", "p-queue": "^6.6.2", "prom-client": "^14.0.1", @@ -101,12 +95,10 @@ "zod": "^3.21.4" }, "devDependencies": { - "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", - "@types/libsodium-wrappers": "^0.7.10", "@types/nunjucks": "^3.1.4", "@types/supertest": "^2.0.8", "@types/zen-observable": "^0.8.0", @@ -115,8 +107,7 @@ "msw": "^1.0.0", "strip-ansi": "^7.1.0", "supertest": "^6.1.3", - "wait-for-expect": "^3.0.2", - "yaml": "^2.0.0" + "wait-for-expect": "^3.0.2" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts index 61bd704a0b..7ad21e34ba 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/util.ts @@ -1,5 +1,5 @@ /* - * Copyright 2021 The Backstage Authors + * Copyright 2023 The Backstage Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,116 +14,7 @@ * limitations under the License. */ -import { InputError } from '@backstage/errors'; -import { isChildPath } from '@backstage/backend-common'; -import { join as joinPath, normalize as normalizePath } from 'path'; -import { ScmIntegrationRegistry } from '@backstage/integration'; - -export const getRepoSourceDirectory = ( - workspacePath: string, - sourcePath: string | undefined, -) => { - if (sourcePath) { - const safeSuffix = normalizePath(sourcePath).replace( - /^(\.\.(\/|\\|$))+/, - '', - ); - const path = joinPath(workspacePath, safeSuffix); - if (!isChildPath(workspacePath, path)) { - throw new Error('Invalid source path'); - } - return path; - } - return workspacePath; -}; -export type RepoSpec = { - repo: string; - host: string; - owner?: string; - organization?: string; - workspace?: string; - project?: string; -}; - -export const parseRepoUrl = ( - repoUrl: string, - integrations: ScmIntegrationRegistry, -): RepoSpec => { - let parsed; - try { - parsed = new URL(`https://${repoUrl}`); - } catch (error) { - throw new InputError( - `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`, - ); - } - const host = parsed.host; - const owner = parsed.searchParams.get('owner') ?? undefined; - const organization = parsed.searchParams.get('organization') ?? undefined; - const workspace = parsed.searchParams.get('workspace') ?? undefined; - const project = parsed.searchParams.get('project') ?? undefined; - - const type = integrations.byHost(host)?.type; - - if (!type) { - throw new InputError( - `No matching integration configuration for host ${host}, please check your integrations config`, - ); - } - - const repo: string = parsed.searchParams.get('repo')!; - switch (type) { - case 'bitbucket': { - if (host === 'www.bitbucket.org') { - checkRequiredParams(parsed, 'workspace'); - } - checkRequiredParams(parsed, 'project', 'repo'); - break; - } - case 'gitlab': { - // project is the projectID, and if defined, owner and repo won't be needed. - if (!project) { - checkRequiredParams(parsed, 'owner', 'repo'); - } - break; - } - case 'gerrit': { - checkRequiredParams(parsed, 'repo'); - break; - } - default: { - checkRequiredParams(parsed, 'repo', 'owner'); - break; - } - } - - return { host, owner, repo, organization, workspace, project }; -}; -export const isExecutable = (fileMode: number) => { - const executeBitMask = 0o000111; - const res = fileMode & executeBitMask; - return res > 0; -}; - -/** - * This function checks if the required parameters (given as the `params` argument) are present in the URL - * - * @param repoUrl - the URL for the repository - * @param params - a variadic list of URL query parameter names - * - * @throws - * An InputError exception is thrown if any of the required parameters is not in the URL. - * - * @public - */ -function checkRequiredParams(repoUrl: URL, ...params: string[]) { - for (let i = 0; i < params.length; i++) { - if (!repoUrl.searchParams.get(params[i])) { - throw new InputError( - `Invalid repo URL passed to publisher: ${repoUrl.toString()}, missing ${ - params[i] - }`, - ); - } - } -} +// const executeBitMask = 0o000111; +// const res = fileMode & executeBitMask; +// return res > 0; +// }; diff --git a/plugins/scaffolder-node/src/actions/index.ts b/plugins/scaffolder-node/src/actions/index.ts index 47af320ccb..27046bebaf 100644 --- a/plugins/scaffolder-node/src/actions/index.ts +++ b/plugins/scaffolder-node/src/actions/index.ts @@ -25,3 +25,5 @@ export { } from './executeShellCommand'; export { fetchContents, fetchFile } from './fetch'; export { type ActionContext, type TemplateAction } from './types'; +export { initRepoAndPush, commitAndPushRepo } from './repoHelpers'; +export { parseRepoUrl, getRepoSourceDirectory } from './util'; diff --git a/plugins/scaffolder-node/src/actions/repoHelpers.ts b/plugins/scaffolder-node/src/actions/repoHelpers.ts new file mode 100644 index 0000000000..a814ca38ed --- /dev/null +++ b/plugins/scaffolder-node/src/actions/repoHelpers.ts @@ -0,0 +1,127 @@ +/* + * Copyright 2023 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Git } from '@backstage/backend-common'; +import { Logger } from 'winston'; + +export async function initRepoAndPush({ + dir, + remoteUrl, + auth, + logger, + defaultBranch = 'master', + commitMessage = 'Initial commit', + gitAuthorInfo, +}: { + dir: string; + remoteUrl: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger: Logger; + defaultBranch?: string; + commitMessage?: string; + gitAuthorInfo?: { name?: string; email?: string }; +}): Promise<{ commitHash: string }> { + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.init({ + dir, + defaultBranch, + }); + + await git.add({ dir, filepath: '.' }); + + // use provided info if possible, otherwise use fallbacks + const authorInfo = { + name: gitAuthorInfo?.name ?? 'Scaffolder', + email: gitAuthorInfo?.email ?? 'scaffolder@backstage.io', + }; + + const commitHash = await git.commit({ + dir, + message: commitMessage, + author: authorInfo, + committer: authorInfo, + }); + await git.addRemote({ + dir, + url: remoteUrl, + remote: 'origin', + }); + + await git.push({ + dir, + remote: 'origin', + }); + + return { commitHash }; +} + +export async function commitAndPushRepo({ + dir, + auth, + logger, + commitMessage, + gitAuthorInfo, + branch = 'master', + remoteRef, +}: { + dir: string; + // For use cases where token has to be used with Basic Auth + // it has to be provided as password together with a username + // which may be a fixed value defined by the provider. + auth: { username: string; password: string } | { token: string }; + logger: Logger; + commitMessage: string; + gitAuthorInfo?: { name?: string; email?: string }; + branch?: string; + remoteRef?: string; +}): Promise<{ commitHash: string }> { + const git = Git.fromAuth({ + ...auth, + logger, + }); + + await git.fetch({ dir }); + await git.checkout({ dir, ref: branch }); + await git.add({ dir, filepath: '.' }); + + // use provided info if possible, otherwise use fallbacks + const authorInfo = { + name: gitAuthorInfo?.name ?? 'Scaffolder', + email: gitAuthorInfo?.email ?? 'scaffolder@backstage.io', + }; + + const commitHash = await git.commit({ + dir, + message: commitMessage, + author: authorInfo, + committer: authorInfo, + }); + + await git.push({ + dir, + remote: 'origin', + remoteRef: remoteRef ?? `refs/heads/${branch}`, + }); + + return { commitHash }; +} diff --git a/plugins/scaffolder-node/src/actions/util.ts b/plugins/scaffolder-node/src/actions/util.ts new file mode 100644 index 0000000000..cdd8512e63 --- /dev/null +++ b/plugins/scaffolder-node/src/actions/util.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { InputError } from '@backstage/errors'; +import { isChildPath } from '@backstage/backend-common'; +import { join as joinPath, normalize as normalizePath } from 'path'; +import { ScmIntegrationRegistry } from '@backstage/integration'; + +export const getRepoSourceDirectory = ( + workspacePath: string, + sourcePath: string | undefined, +) => { + if (sourcePath) { + const safeSuffix = normalizePath(sourcePath).replace( + /^(\.\.(\/|\\|$))+/, + '', + ); + const path = joinPath(workspacePath, safeSuffix); + if (!isChildPath(workspacePath, path)) { + throw new Error('Invalid source path'); + } + return path; + } + return workspacePath; +}; + +export const parseRepoUrl = ( + repoUrl: string, + integrations: ScmIntegrationRegistry, +): { + repo: string; + host: string; + owner?: string; + organization?: string; + workspace?: string; + project?: string; +} => { + let parsed; + try { + parsed = new URL(`https://${repoUrl}`); + } catch (error) { + throw new InputError( + `Invalid repo URL passed to publisher, got ${repoUrl}, ${error}`, + ); + } + const host = parsed.host; + const owner = parsed.searchParams.get('owner') ?? undefined; + const organization = parsed.searchParams.get('organization') ?? undefined; + const workspace = parsed.searchParams.get('workspace') ?? undefined; + const project = parsed.searchParams.get('project') ?? undefined; + + const type = integrations.byHost(host)?.type; + + if (!type) { + throw new InputError( + `No matching integration configuration for host ${host}, please check your integrations config`, + ); + } + + const repo: string = parsed.searchParams.get('repo')!; + switch (type) { + case 'bitbucket': { + if (host === 'www.bitbucket.org') { + checkRequiredParams(parsed, 'workspace'); + } + checkRequiredParams(parsed, 'project', 'repo'); + break; + } + case 'gitlab': { + // project is the projectID, and if defined, owner and repo won't be needed. + if (!project) { + checkRequiredParams(parsed, 'owner', 'repo'); + } + break; + } + case 'gerrit': { + checkRequiredParams(parsed, 'repo'); + break; + } + default: { + checkRequiredParams(parsed, 'repo', 'owner'); + break; + } + } + + return { host, owner, repo, organization, workspace, project }; +}; + +function checkRequiredParams(repoUrl: URL, ...params: string[]) { + for (let i = 0; i < params.length; i++) { + if (!repoUrl.searchParams.get(params[i])) { + throw new InputError( + `Invalid repo URL passed to publisher: ${repoUrl.toString()}, missing ${ + params[i] + }`, + ); + } + } +} diff --git a/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts rename to plugins/scaffolder-node/src/files/deserializeDirectoryContents.test.ts diff --git a/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts rename to plugins/scaffolder-node/src/files/deserializeDirectoryContents.ts diff --git a/plugins/scaffolder-backend/src/lib/files/index.ts b/plugins/scaffolder-node/src/files/index.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/index.ts rename to plugins/scaffolder-node/src/files/index.ts diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts b/plugins/scaffolder-node/src/files/serializeDirectoryContents.test.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts rename to plugins/scaffolder-node/src/files/serializeDirectoryContents.test.ts diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts b/plugins/scaffolder-node/src/files/serializeDirectoryContents.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts rename to plugins/scaffolder-node/src/files/serializeDirectoryContents.ts diff --git a/plugins/scaffolder-backend/src/lib/files/types.ts b/plugins/scaffolder-node/src/files/types.ts similarity index 100% rename from plugins/scaffolder-backend/src/lib/files/types.ts rename to plugins/scaffolder-node/src/files/types.ts diff --git a/plugins/scaffolder-node/src/index.ts b/plugins/scaffolder-node/src/index.ts index 98691c2afa..0ec492dd32 100644 --- a/plugins/scaffolder-node/src/index.ts +++ b/plugins/scaffolder-node/src/index.ts @@ -22,4 +22,5 @@ export * from './actions'; export * from './tasks'; +export * from './files'; export type { TemplateFilter, TemplateGlobal } from './types'; diff --git a/plugins/scaffolder-react/src/components/types.ts b/plugins/scaffolder-react/src/components/types.ts index 597998bac4..e9ec3bf711 100644 --- a/plugins/scaffolder-react/src/components/types.ts +++ b/plugins/scaffolder-react/src/components/types.ts @@ -23,6 +23,7 @@ import { JsonObject } from '@backstage/types'; export type TemplateGroupFilter = { title?: React.ReactNode; filter: (entity: TemplateEntityV1beta3) => boolean; + sort?: (a: TemplateEntityV1beta3, b: TemplateEntityV1beta3) => number; }; /**