diff --git a/.changeset/silver-camels-brake.md b/.changeset/silver-camels-brake.md new file mode 100644 index 0000000000..f044bc9441 --- /dev/null +++ b/.changeset/silver-camels-brake.md @@ -0,0 +1,14 @@ +--- +'@backstage/plugin-scaffolder-backend-module-bitbucket-server': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket-cloud': patch +'@backstage/plugin-scaffolder-backend-module-bitbucket': patch +'@backstage/plugin-scaffolder-backend-module-gerrit': patch +'@backstage/plugin-scaffolder-backend-module-gitlab': patch +'@backstage/plugin-scaffolder-backend-module-azure': patch +'@backstage/plugin-scaffolder-backend-module-gitea': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder-node': patch +'@backstage/integration': patch +--- + +Allow signing git commits using configured private PGP key in scaffolder diff --git a/packages/integration/config.d.ts b/packages/integration/config.d.ts index 1e4c8d373d..bd5267ab3d 100644 --- a/packages/integration/config.d.ts +++ b/packages/integration/config.d.ts @@ -62,6 +62,11 @@ export interface Config { tenantId?: string; personalAccessToken?: string; }[]; + /** + * PGP signing key for signing commits. + * @visibility secret + */ + commitSigningKey?: string; }>; /** @@ -94,6 +99,11 @@ export interface Config { * @visibility secret */ appPassword?: string; + /** + * PGP signing key for signing commits. + * @visibility secret + */ + commitSigningKey?: string; }>; /** Integration configuration for Bitbucket Cloud */ @@ -108,6 +118,11 @@ export interface Config { * @visibility secret */ appPassword: string; + /** + * PGP signing key for signing commits. + * @visibility secret + */ + commitSigningKey?: string; }>; /** Integration configuration for Bitbucket Server */ @@ -137,6 +152,11 @@ export interface Config { * @visibility frontend */ apiBaseUrl?: string; + /** + * PGP signing key for signing commits. + * @visibility secret + */ + commitSigningKey?: string; }>; /** Integration configuration for Gerrit */ @@ -172,6 +192,11 @@ export interface Config { * @visibility secret */ password?: string; + /** + * PGP signing key for signing commits. + * @visibility secret + */ + commitSigningKey?: string; }>; /** Integration configuration for GitHub */ @@ -269,6 +294,11 @@ export interface Config { * @visibility frontend */ baseUrl?: string; + /** + * PGP signing key for signing commits. + * @visibility secret + */ + commitSigningKey?: string; }>; /** Integration configuration for Google Cloud Storage */ @@ -349,6 +379,11 @@ export interface Config { * @visibility secret */ password?: string; + /** + * PGP signing key for signing commits. + * @visibility secret + */ + commitSigningKey?: string; }>; /** Integration configuration for Harness Code */ harness?: Array<{ diff --git a/packages/integration/report.api.md b/packages/integration/report.api.md index 6bb48b8185..e44b02851c 100644 --- a/packages/integration/report.api.md +++ b/packages/integration/report.api.md @@ -201,6 +201,7 @@ export type AzureIntegrationConfig = { token?: string; credential?: AzureDevOpsCredential; credentials?: AzureDevOpsCredential[]; + commitSigningKey?: string; }; // @public @@ -237,6 +238,7 @@ export type BitbucketCloudIntegrationConfig = { username?: string; appPassword?: string; token?: string; + commitSigningKey?: string; }; // @public @deprecated @@ -267,6 +269,7 @@ export type BitbucketIntegrationConfig = { token?: string; username?: string; appPassword?: string; + commitSigningKey?: string; }; // @public @@ -297,6 +300,7 @@ export type BitbucketServerIntegrationConfig = { token?: string; username?: string; password?: string; + commitSigningKey?: string; }; // @public @deprecated @@ -395,6 +399,7 @@ export type GerritIntegrationConfig = { gitilesBaseUrl: string; username?: string; password?: string; + commitSigningKey?: string; }; // @public @@ -630,6 +635,7 @@ export type GiteaIntegrationConfig = { baseUrl?: string; username?: string; password?: string; + commitSigningKey?: string; }; // @public @@ -744,6 +750,7 @@ export type GitLabIntegrationConfig = { apiBaseUrl: string; token?: string; baseUrl: string; + commitSigningKey?: string; }; // @public diff --git a/packages/integration/src/azure/config.ts b/packages/integration/src/azure/config.ts index 5ed1914ea2..2910387829 100644 --- a/packages/integration/src/azure/config.ts +++ b/packages/integration/src/azure/config.ts @@ -57,6 +57,11 @@ export type AzureIntegrationConfig = { * If no credentials are specified at all, either a default credential (for Azure DevOps) or anonymous access (for Azure DevOps Server) is used. */ credentials?: AzureDevOpsCredential[]; + + /** + * Signing key for commits + */ + commitSigningKey?: string; }; /** @@ -349,6 +354,7 @@ export function readAzureIntegrationConfig( return { host, credentials, + commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/bitbucket/config.ts b/packages/integration/src/bitbucket/config.ts index 4b6296b0e9..6939034e7d 100644 --- a/packages/integration/src/bitbucket/config.ts +++ b/packages/integration/src/bitbucket/config.ts @@ -62,6 +62,11 @@ export type BitbucketIntegrationConfig = { * See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/ */ appPassword?: string; + + /** + * Signing key for commits + */ + commitSigningKey?: string; }; /** @@ -100,6 +105,7 @@ export function readBitbucketIntegrationConfig( token, username, appPassword, + commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/bitbucketCloud/config.ts b/packages/integration/src/bitbucketCloud/config.ts index 32a2af9313..eb4b5c4904 100644 --- a/packages/integration/src/bitbucketCloud/config.ts +++ b/packages/integration/src/bitbucketCloud/config.ts @@ -51,6 +51,9 @@ export type BitbucketCloudIntegrationConfig = { * The access token to use for requests to Bitbucket Cloud (bitbucket.org). */ token?: string; + + /** PGP private key for signing commits. */ + commitSigningKey?: string; }; /** @@ -74,6 +77,7 @@ export function readBitbucketCloudIntegrationConfig( apiBaseUrl, username, appPassword, + commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/bitbucketServer/config.ts b/packages/integration/src/bitbucketServer/config.ts index 1412b7ab17..b2a243b3b3 100644 --- a/packages/integration/src/bitbucketServer/config.ts +++ b/packages/integration/src/bitbucketServer/config.ts @@ -64,6 +64,11 @@ export type BitbucketServerIntegrationConfig = { * See https://developer.atlassian.com/server/bitbucket/how-tos/command-line-rest/#authentication */ password?: string; + + /** + * Signing key for commits + */ + commitSigningKey?: string; }; /** @@ -99,6 +104,7 @@ export function readBitbucketServerIntegrationConfig( token, username, password, + commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/gerrit/config.ts b/packages/integration/src/gerrit/config.ts index a5fdc8dabf..fe88750687 100644 --- a/packages/integration/src/gerrit/config.ts +++ b/packages/integration/src/gerrit/config.ts @@ -60,6 +60,11 @@ export type GerritIntegrationConfig = { * The password or http token to use for authentication. */ password?: string; + + /** + * The signing key to use for signing commits. + */ + commitSigningKey?: string; }; /** @@ -119,6 +124,7 @@ export function readGerritIntegrationConfig( gitilesBaseUrl, username, password, + commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/gitea/config.ts b/packages/integration/src/gitea/config.ts index ae1fe5ee87..461ee60715 100644 --- a/packages/integration/src/gitea/config.ts +++ b/packages/integration/src/gitea/config.ts @@ -45,6 +45,11 @@ export type GiteaIntegrationConfig = { * The password or http token to use for authentication. */ password?: string; + + /** + * Signing key to to sign commits + */ + commitSigningKey?: string; }; /** @@ -79,5 +84,6 @@ export function readGiteaConfig(config: Config): GiteaIntegrationConfig { baseUrl, username, password, + commitSigningKey: config.getOptionalString('commitSigningKey'), }; } diff --git a/packages/integration/src/gitlab/config.ts b/packages/integration/src/gitlab/config.ts index 79161561f3..636919291c 100644 --- a/packages/integration/src/gitlab/config.ts +++ b/packages/integration/src/gitlab/config.ts @@ -54,6 +54,11 @@ export type GitLabIntegrationConfig = { * If no baseUrl is provided, it will default to `https://${host}` */ baseUrl: string; + + /** + * Signing key to sign commits + */ + commitSigningKey?: string; }; /** @@ -95,7 +100,13 @@ export function readGitLabIntegrationConfig( ); } - return { host, token, apiBaseUrl, baseUrl }; + return { + host, + token, + apiBaseUrl, + baseUrl, + commitSigningKey: config.getOptionalString('commitSigningKey'), + }; } /** diff --git a/plugins/scaffolder-backend-module-azure/report.api.md b/plugins/scaffolder-backend-module-azure/report.api.md index e15aba0fa4..9e7592e145 100644 --- a/plugins/scaffolder-backend-module-azure/report.api.md +++ b/plugins/scaffolder-backend-module-azure/report.api.md @@ -27,6 +27,7 @@ export function createPublishAzureAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts index 518d2acbf7..acb4754520 100644 --- a/plugins/scaffolder-backend-module-azure/src/actions/azure.ts +++ b/plugins/scaffolder-backend-module-azure/src/actions/azure.ts @@ -20,10 +20,10 @@ import { ScmIntegrationRegistry, } from '@backstage/integration'; import { - initRepoAndPush, - getRepoSourceDirectory, - parseRepoUrl, createTemplateAction, + getRepoSourceDirectory, + initRepoAndPush, + parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { GitRepositoryCreateOptions } from 'azure-devops-node-api/interfaces/GitInterfaces'; import { @@ -54,6 +54,7 @@ export function createPublishAzureAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }>({ id: 'publish:azure', examples, @@ -103,6 +104,11 @@ export function createPublishAzureAction(options: { type: 'string', description: 'The token to use for authorization to Azure', }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, }, }, output: { @@ -134,6 +140,7 @@ export function createPublishAzureAction(options: { gitCommitMessage = 'initial commit', gitAuthorName, gitAuthorEmail, + signCommit, } = ctx.input; const { project, repo, host, organization } = parseRepoUrl( @@ -151,6 +158,7 @@ export function createPublishAzureAction(options: { const credentialProvider = DefaultAzureDevOpsCredentialsProvider.fromIntegrations(integrations); const credentials = await credentialProvider.getCredentials({ url: url }); + const integrationConfig = integrations.azure.byHost(host); if (credentials === undefined && ctx.input.token === undefined) { throw new InputError( @@ -212,6 +220,15 @@ export function createPublishAzureAction(options: { password: ctx.input.token ?? credentials!.token, }; + const signingKey = + integrationConfig?.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } + const commitResult = await initRepoAndPush({ dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, @@ -222,6 +239,7 @@ export function createPublishAzureAction(options: { ? gitCommitMessage : config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, }); ctx.output('commitHash', commitResult?.commitHash); diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md index ad74479b6e..428e21c027 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/report.api.md @@ -39,6 +39,7 @@ export function createPublishBitbucketCloudAction(options: { gitCommitMessage?: string; sourcePath?: string; token?: string; + signCommit?: boolean; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts index 00c27f6a54..945aab1216 100644 --- a/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts +++ b/plugins/scaffolder-backend-module-bitbucket-cloud/src/actions/bitbucketCloud.ts @@ -18,8 +18,8 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { createTemplateAction, - initRepoAndPush, getRepoSourceDirectory, + initRepoAndPush, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; @@ -113,6 +113,7 @@ export function createPublishBitbucketCloudAction(options: { gitCommitMessage?: string; sourcePath?: string; token?: string; + signCommit?: boolean; }>({ id: 'publish:bitbucketCloud', examples, @@ -158,6 +159,11 @@ export function createPublishBitbucketCloudAction(options: { description: 'The token to use for authorization to BitBucket Cloud', }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, }, }, output: { @@ -185,6 +191,7 @@ export function createPublishBitbucketCloudAction(options: { defaultBranch = 'master', gitCommitMessage, repoVisibility = 'private', + signCommit, } = ctx.input; const { workspace, project, repo, host } = parseRepoUrl( @@ -256,6 +263,15 @@ export function createPublishBitbucketCloudAction(options: { }; } + const signingKey = + integrationConfig.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } + const commitResult = await initRepoAndPush({ dir: getRepoSourceDirectory(ctx.workspacePath, ctx.input.sourcePath), remoteUrl, @@ -266,6 +282,7 @@ export function createPublishBitbucketCloudAction(options: { gitCommitMessage || config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, }); ctx.output('commitHash', commitResult?.commitHash); diff --git a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md index e92c85b284..21eee65d1c 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket-server/report.api.md @@ -29,6 +29,7 @@ export function createPublishBitbucketServerAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts index 612d090547..3f50d5f274 100644 --- a/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts +++ b/plugins/scaffolder-backend-module-bitbucket-server/src/actions/bitbucketServer.ts @@ -21,8 +21,8 @@ import { } from '@backstage/integration'; import { createTemplateAction, - initRepoAndPush, getRepoSourceDirectory, + initRepoAndPush, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; @@ -137,6 +137,7 @@ export function createPublishBitbucketServerAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }>({ id: 'publish:bitbucketServer', description: @@ -197,6 +198,11 @@ export function createPublishBitbucketServerAction(options: { type: 'string', description: `Sets the author email for the commit.`, }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, }, }, output: { @@ -227,6 +233,7 @@ export function createPublishBitbucketServerAction(options: { gitCommitMessage = 'initial commit', gitAuthorName, gitAuthorEmail, + signCommit, } = ctx.input; const { project, repo, host } = parseRepoUrl(repoUrl, integrations); @@ -279,6 +286,15 @@ export function createPublishBitbucketServerAction(options: { : config.getOptionalString('scaffolder.defaultAuthor.email'), }; + const signingKey = + integrationConfig.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } + const auth = authConfig.token ? { token: token!, @@ -298,6 +314,7 @@ export function createPublishBitbucketServerAction(options: { ? gitCommitMessage : config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, }); if (enableLFS) { diff --git a/plugins/scaffolder-backend-module-bitbucket/report.api.md b/plugins/scaffolder-backend-module-bitbucket/report.api.md index b90f2f8483..0b19806b51 100644 --- a/plugins/scaffolder-backend-module-bitbucket/report.api.md +++ b/plugins/scaffolder-backend-module-bitbucket/report.api.md @@ -44,6 +44,7 @@ export function createPublishBitbucketAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts index 7a58efa10a..e52eb50976 100644 --- a/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts +++ b/plugins/scaffolder-backend-module-bitbucket/src/actions/bitbucket.ts @@ -21,8 +21,8 @@ import { } from '@backstage/integration'; import { createTemplateAction, - initRepoAndPush, getRepoSourceDirectory, + initRepoAndPush, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { Config } from '@backstage/config'; @@ -220,6 +220,7 @@ export function createPublishBitbucketAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }>({ id: 'publish:bitbucket', description: @@ -280,6 +281,11 @@ export function createPublishBitbucketAction(options: { type: 'string', description: `Sets the default author email for the commit.`, }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, }, }, output: { @@ -313,6 +319,7 @@ export function createPublishBitbucketAction(options: { gitCommitMessage = 'initial commit', gitAuthorName, gitAuthorEmail, + signCommit, } = ctx.input; const { workspace, project, repo, host } = parseRepoUrl( @@ -380,6 +387,14 @@ export function createPublishBitbucketAction(options: { ? gitAuthorEmail : config.getOptionalString('scaffolder.defaultAuthor.email'), }; + const signingKey = + integrationConfig.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } let auth; @@ -409,6 +424,7 @@ export function createPublishBitbucketAction(options: { ? gitCommitMessage : config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, }); if (enableLFS && host !== 'bitbucket.org') { diff --git a/plugins/scaffolder-backend-module-gerrit/report.api.md b/plugins/scaffolder-backend-module-gerrit/report.api.md index a3f23d7aa7..b5af8f0acf 100644 --- a/plugins/scaffolder-backend-module-gerrit/report.api.md +++ b/plugins/scaffolder-backend-module-gerrit/report.api.md @@ -22,6 +22,7 @@ export function createPublishGerritAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; sourcePath?: string; + signCommit?: boolean; }, JsonObject >; @@ -38,6 +39,7 @@ export function createPublishGerritReviewAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts index db0ae2ba70..23f28ae934 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerrit.ts @@ -24,8 +24,8 @@ import { } from '@backstage/integration'; import { createTemplateAction, - initRepoAndPush, getRepoSourceDirectory, + initRepoAndPush, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; import { examples } from './gerrit.examples'; @@ -99,6 +99,7 @@ export function createPublishGerritAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; sourcePath?: string; + signCommit?: boolean; }>({ id: 'publish:gerrit', supportsDryRun: true, @@ -143,6 +144,11 @@ export function createPublishGerritAction(options: { type: 'string', description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, }, }, output: { @@ -172,6 +178,7 @@ export function createPublishGerritAction(options: { gitAuthorEmail, gitCommitMessage = 'initial commit', sourcePath, + signCommit, } = ctx.input; const { repo, host, owner, workspace } = parseRepoUrl( repoUrl, @@ -233,6 +240,15 @@ export function createPublishGerritAction(options: { email: gitEmail, }; + const signingKey = + integrationConfig.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } + const commitResult = await initRepoAndPush({ dir: getRepoSourceDirectory(ctx.workspacePath, sourcePath), remoteUrl, @@ -241,6 +257,7 @@ export function createPublishGerritAction(options: { logger: ctx.logger, commitMessage: generateCommitMessage(config, gitCommitMessage), gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, }); ctx.output('remoteUrl', remoteUrl); diff --git a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts index b4f844b08f..b4ab3afef7 100644 --- a/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts +++ b/plugins/scaffolder-backend-module-gerrit/src/actions/gerritReview.ts @@ -19,8 +19,8 @@ import { InputError } from '@backstage/errors'; import { Config } from '@backstage/config'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { - createTemplateAction, commitAndPushRepo, + createTemplateAction, getRepoSourceDirectory, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; @@ -48,6 +48,7 @@ export function createPublishGerritReviewAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; }>({ id: 'publish:gerrit:review', description: 'Creates a new Gerrit review.', @@ -88,6 +89,11 @@ export function createPublishGerritReviewAction(options: { type: 'string', description: `Sets the default author email for the commit.`, }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, }, }, output: { @@ -112,6 +118,7 @@ export function createPublishGerritReviewAction(options: { gitAuthorName, gitAuthorEmail, gitCommitMessage, + signCommit, } = ctx.input; const { host, repo } = parseRepoUrl(repoUrl, integrations); @@ -139,6 +146,14 @@ export function createPublishGerritReviewAction(options: { ? gitAuthorEmail : config.getOptionalString('scaffolder.defaultAuthor.email'), }; + const signingKey = + integrationConfig.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } const changeId = generateGerritChangeId(); const commitMessage = `${gitCommitMessage}\n\nChange-Id: ${changeId}`; @@ -150,6 +165,7 @@ export function createPublishGerritReviewAction(options: { gitAuthorInfo, branch, remoteRef: `refs/for/${branch}`, + signingKey: signCommit ? signingKey : undefined, }); const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${branch}`; diff --git a/plugins/scaffolder-backend-module-gitea/report.api.md b/plugins/scaffolder-backend-module-gitea/report.api.md index 8456b7183c..9a057dfb45 100644 --- a/plugins/scaffolder-backend-module-gitea/report.api.md +++ b/plugins/scaffolder-backend-module-gitea/report.api.md @@ -23,6 +23,7 @@ export function createPublishGiteaAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; sourcePath?: string; + signCommit?: boolean; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts index 6b29ff2a4c..4321de2167 100644 --- a/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts +++ b/plugins/scaffolder-backend-module-gitea/src/actions/gitea.ts @@ -219,6 +219,7 @@ export function createPublishGiteaAction(options: { gitAuthorName?: string; gitAuthorEmail?: string; sourcePath?: string; + signCommit?: boolean; }>({ id: 'publish:gitea', description: @@ -268,6 +269,11 @@ export function createPublishGiteaAction(options: { type: 'string', description: `Path within the workspace that will be used as the repository root. If omitted, the entire workspace will be published as the repository.`, }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, }, }, output: { @@ -298,6 +304,7 @@ export function createPublishGiteaAction(options: { gitAuthorEmail, gitCommitMessage = 'initial commit', sourcePath, + signCommit, } = ctx.input; const { repo, host, owner } = parseRepoUrl(repoUrl, integrations); @@ -338,6 +345,16 @@ export function createPublishGiteaAction(options: { ? gitAuthorEmail : config.getOptionalString('scaffolder.defaultAuthor.email'), }; + + const signingKey = + integrationConfig.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } + // The owner to be used should be either the org name or user authenticated with the gitea server const remoteUrl = `${integrationConfig.config.baseUrl}/${owner}/${repo}.git`; const commitResult = await initRepoAndPush({ diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index d50104e98e..1ba70a7ba6 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -144,6 +144,7 @@ export function createPublishGitlabAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; setUserAsOwner?: boolean; topics?: string[]; settings?: { diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts index e7ceaa3ed7..1e3ac40d35 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.test.ts @@ -75,6 +75,7 @@ describe('publish:gitlab', () => { host: 'gitlab.com', token: 'tokenlols', apiBaseUrl: 'https://api.gitlab.com', + commitSigningKey: 'test-signing-key', }, { host: 'hosted.gitlab.com', @@ -470,6 +471,31 @@ describe('publish:gitlab', () => { }); }); + it('should call initRepoAndPush with the signing key', async () => { + mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 }); + mockGitlabClient.Namespaces.show.mockResolvedValue({ id: 1234 }); + mockGitlabClient.Groups.allProjects.mockResolvedValue([]); + mockGitlabClient.Projects.create.mockResolvedValue({ + http_url_to_repo: 'http://mockurl.git', + }); + + await action.handler({ + ...mockContext, + input: { ...mockContext.input, signCommit: true }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + defaultBranch: 'master', + remoteUrl: 'http://mockurl.git', + auth: { username: 'oauth2', password: 'tokenlols' }, + logger: mockContext.logger, + commitMessage: 'initial commit', + gitAuthorInfo: {}, + signingKey: 'test-signing-key', + }); + }); + it('should not call initRepoAndPush when sourcePath is false', async () => { mockGitlabClient.Users.showCurrentUser.mockResolvedValue({ id: 12345 }); mockGitlabClient.Namespaces.show.mockResolvedValue({ diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts index 582716a979..753da2e253 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlab.ts @@ -16,13 +16,13 @@ import { InputError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { createTemplateAction } from '@backstage/plugin-scaffolder-node'; -import { Gitlab, VariableType } from '@gitbeaker/rest'; import { - initRepoAndPush, + createTemplateAction, getRepoSourceDirectory, + initRepoAndPush, parseRepoUrl, } from '@backstage/plugin-scaffolder-node'; +import { Gitlab, VariableType } from '@gitbeaker/rest'; import { Config } from '@backstage/config'; import { examples } from './gitlab.examples'; @@ -49,6 +49,7 @@ export function createPublishGitlabAction(options: { gitCommitMessage?: string; gitAuthorName?: string; gitAuthorEmail?: string; + signCommit?: boolean; setUserAsOwner?: boolean; /** @deprecated in favour of settings.topics field */ topics?: string[]; @@ -122,6 +123,11 @@ export function createPublishGitlabAction(options: { type: 'string', description: `Sets the default author email for the commit.`, }, + signCommit: { + title: 'Sign commit', + type: 'boolean', + description: 'Sign commit with configured PGP private key', + }, sourcePath: { title: 'Source Path', description: @@ -349,6 +355,7 @@ export function createPublishGitlabAction(options: { branches = [], projectVariables = [], skipExisting = false, + signCommit, } = ctx.input; const { owner, repo, host } = parseRepoUrl(repoUrl, integrations); @@ -452,6 +459,15 @@ export function createPublishGitlabAction(options: { ? gitAuthorEmail : config.getOptionalString('scaffolder.defaultAuthor.email'), }; + const signingKey = + integrationConfig.config.commitSigningKey ?? + config.getOptionalString('scaffolder.defaultCommitSigningKey'); + if (signCommit && !signingKey) { + throw new Error( + 'Signing commits is enabled but no signing key is provided in the configuration', + ); + } + const shouldSkipPublish = typeof ctx.input.sourcePath === 'boolean' && !ctx.input.sourcePath; if (!shouldSkipPublish) { @@ -474,6 +490,7 @@ export function createPublishGitlabAction(options: { ? gitCommitMessage : config.getOptionalString('scaffolder.defaultCommitMessage'), gitAuthorInfo, + signingKey: signCommit ? signingKey : undefined, }); if (branches) { diff --git a/plugins/scaffolder-backend/config.d.ts b/plugins/scaffolder-backend/config.d.ts index a391f97264..ae91c905b3 100644 --- a/plugins/scaffolder-backend/config.d.ts +++ b/plugins/scaffolder-backend/config.d.ts @@ -27,6 +27,11 @@ export interface Config { email?: string; }; + /** + * Default PGP signing key for signing commits. + * @visibility secret + */ + defaultCommitSigningKey?: string; /** * The commit message used when new components are created. */ diff --git a/plugins/scaffolder-node/package.json b/plugins/scaffolder-node/package.json index d7f8fc042b..770e66cf62 100644 --- a/plugins/scaffolder-node/package.json +++ b/plugins/scaffolder-node/package.json @@ -60,6 +60,7 @@ "@backstage/integration": "workspace:^", "@backstage/plugin-scaffolder-common": "workspace:^", "@backstage/types": "workspace:^", + "@isomorphic-git/pgp-plugin": "^0.0.7", "concat-stream": "^2.0.0", "fs-extra": "^11.2.0", "globby": "^11.0.0", diff --git a/plugins/scaffolder-node/report.api.md b/plugins/scaffolder-node/report.api.md index 333b47a6d1..afa611804d 100644 --- a/plugins/scaffolder-node/report.api.md +++ b/plugins/scaffolder-node/report.api.md @@ -106,6 +106,7 @@ export function commitAndPushBranch(options: { branch?: string; remoteRef?: string; remote?: string; + signingKey?: string; }): Promise<{ commitHash: string; }>; @@ -129,6 +130,7 @@ export function commitAndPushRepo(input: { }; branch?: string; remoteRef?: string; + signingKey?: string; }): Promise<{ commitHash: string; }>; @@ -242,6 +244,7 @@ export function initRepoAndPush(input: { name?: string; email?: string; }; + signingKey?: string; }): Promise<{ commitHash: string; }>; diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts index 2e8eb9366f..d70ddf3bc0 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.test.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.test.ts @@ -16,12 +16,12 @@ import { Git } from '../scm'; import { - commitAndPushRepo, - initRepoAndPush, - commitAndPushBranch, addFiles, - createBranch, cloneRepo, + commitAndPushBranch, + commitAndPushRepo, + createBranch, + initRepoAndPush, } from './gitHelpers'; import { mockServices } from '@backstage/backend-test-utils'; import { loggerToWinstonLogger } from './loggerToWinstonLogger'; @@ -224,6 +224,32 @@ describe('commitAndPushRepo', () => { }); }); + it('creates commit with signing key', async () => { + await commitAndPushRepo({ + dir: '/test/repo/dir/', + auth: { + username: 'test-user', + password: 'test-password', + }, + logger: loggerToWinstonLogger(mockServices.logger.mock()), + commitMessage: 'commit message', + signingKey: 'test-signing-key', + }); + expect(mockedGit.commit).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + message: 'commit message', + author: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + committer: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + signingKey: 'test-signing-key', + }); + }); + it('pushes to the remote', () => { expect(mockedGit.push).toHaveBeenCalledWith({ dir: '/test/repo/dir/', diff --git a/plugins/scaffolder-node/src/actions/gitHelpers.ts b/plugins/scaffolder-node/src/actions/gitHelpers.ts index e24c0cf025..8922975274 100644 --- a/plugins/scaffolder-node/src/actions/gitHelpers.ts +++ b/plugins/scaffolder-node/src/actions/gitHelpers.ts @@ -31,6 +31,7 @@ export async function initRepoAndPush(input: { defaultBranch?: string; commitMessage?: string; gitAuthorInfo?: { name?: string; email?: string }; + signingKey?: string; }): Promise<{ commitHash: string }> { const { dir, @@ -40,6 +41,7 @@ export async function initRepoAndPush(input: { defaultBranch = 'master', commitMessage = 'Initial commit', gitAuthorInfo, + signingKey, } = input; const git = Git.fromAuth({ ...auth, @@ -64,6 +66,7 @@ export async function initRepoAndPush(input: { message: commitMessage, author: authorInfo, committer: authorInfo, + signingKey, }); await git.push({ @@ -89,6 +92,7 @@ export async function commitAndPushRepo(input: { gitAuthorInfo?: { name?: string; email?: string }; branch?: string; remoteRef?: string; + signingKey?: string; }): Promise<{ commitHash: string }> { const { dir, @@ -98,6 +102,7 @@ export async function commitAndPushRepo(input: { gitAuthorInfo, branch = 'master', remoteRef, + signingKey, } = input; const git = Git.fromAuth({ @@ -120,6 +125,7 @@ export async function commitAndPushRepo(input: { message: commitMessage, author: authorInfo, committer: authorInfo, + signingKey, }); await git.push({ @@ -213,6 +219,7 @@ export async function commitAndPushBranch(options: { branch?: string; remoteRef?: string; remote?: string; + signingKey?: string; }): Promise<{ commitHash: string }> { const { dir, @@ -223,6 +230,7 @@ export async function commitAndPushBranch(options: { branch = 'master', remoteRef, remote = 'origin', + signingKey, } = options; const git = Git.fromAuth({ ...auth, @@ -240,6 +248,7 @@ export async function commitAndPushBranch(options: { message: commitMessage, author: authorInfo, committer: authorInfo, + signingKey, }); await git.push({ diff --git a/plugins/scaffolder-node/src/scm/git.test.ts b/plugins/scaffolder-node/src/scm/git.test.ts index 4a472a675a..dfbdd9cd16 100644 --- a/plugins/scaffolder-node/src/scm/git.test.ts +++ b/plugins/scaffolder-node/src/scm/git.test.ts @@ -16,6 +16,12 @@ jest.mock('isomorphic-git'); jest.mock('isomorphic-git/http/node'); jest.mock('fs-extra'); +jest.mock('@isomorphic-git/pgp-plugin', () => ({ + ...jest.requireActual('@isomorphic-git/pgp-plugin'), + pgp: { + sign: jest.fn().mockResolvedValue({ signature: 'sign' }), + }, +})); import * as isomorphic from 'isomorphic-git'; import { Git } from './git'; @@ -140,8 +146,9 @@ describe('Git', () => { name: 'comitter', email: 'test@backstage.io', }; + const signingKey = 'test-signing-key'; - await git.commit({ dir, message, author, committer }); + await git.commit({ dir, message, author, committer, signingKey }); expect(isomorphic.commit).toHaveBeenCalledWith({ fs, @@ -149,6 +156,8 @@ describe('Git', () => { message, author, committer, + signingKey, + onSign: expect.any(Function), }); }); }); diff --git a/plugins/scaffolder-node/src/scm/git.ts b/plugins/scaffolder-node/src/scm/git.ts index 760be73114..42926fd901 100644 --- a/plugins/scaffolder-node/src/scm/git.ts +++ b/plugins/scaffolder-node/src/scm/git.ts @@ -15,14 +15,16 @@ */ import git, { - ProgressCallback, - MergeResult, - ReadCommitResult, AuthCallback, + MergeResult, + ProgressCallback, + ReadCommitResult, } from 'isomorphic-git'; import http from 'isomorphic-git/http/node'; import fs from 'fs-extra'; import { LoggerService } from '@backstage/backend-plugin-api'; +// @ts-ignore +import { pgp } from '@isomorphic-git/pgp-plugin'; function isAuthCallbackOptions( options: StaticAuthOptions | AuthCallbackOptions, @@ -137,12 +139,21 @@ export class Git { message: string; author: { name: string; email: string }; committer: { name: string; email: string }; + signingKey?: string; }): Promise { - const { dir, message, author, committer } = options; + const { dir, message, author, committer, signingKey } = options; this.config.logger?.info( `Committing file to repo {dir=${dir},message=${message}}`, ); - return git.commit({ fs, dir, message, author, committer }); + return git.commit({ + fs, + dir, + message, + author, + committer, + signingKey, + onSign: signingKey ? pgp.sign : undefined, + }); } /** https://isomorphic-git.org/docs/en/clone */ @@ -241,8 +252,9 @@ export class Git { ours?: string; author: { name: string; email: string }; committer: { name: string; email: string }; + signingKey?: string; }): Promise { - const { dir, theirs, ours, author, committer } = options; + const { dir, theirs, ours, author, committer, signingKey } = options; this.config.logger?.info( `Merging branch '${theirs}' into '${ours}' for repository {dir=${dir}}`, ); @@ -255,6 +267,8 @@ export class Git { theirs, author, committer, + signingKey, + onSign: signingKey ? pgp.sign : undefined, }); } diff --git a/yarn.lock b/yarn.lock index b2cd9699a1..6b9b477b0f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7794,6 +7794,7 @@ __metadata: "@backstage/integration": "workspace:^" "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/types": "workspace:^" + "@isomorphic-git/pgp-plugin": ^0.0.7 concat-stream: ^2.0.0 fs-extra: ^11.2.0 globby: ^11.0.0 @@ -10933,6 +10934,59 @@ __metadata: languageName: node linkType: hard +"@isomorphic-git/pgp-plugin@npm:^0.0.7": + version: 0.0.7 + resolution: "@isomorphic-git/pgp-plugin@npm:0.0.7" + dependencies: + "@isomorphic-pgp/sign-and-verify": ^0.0.10 + "@isomorphic-pgp/util": ^0.0.6 + checksum: 5361b4e58933f87747607b4bcfdf7ce2e4d75f591d5839ecd313fba55196020ee0e867c307fbfa2cd0aa85442942a5efca40653dfac0acabe8043e80f1dbebc1 + languageName: node + linkType: hard + +"@isomorphic-pgp/parser@npm:^0.0.3": + version: 0.0.3 + resolution: "@isomorphic-pgp/parser@npm:0.0.3" + dependencies: + array-buffer-to-hex: ^1.0.0 + base64-js: ^1.3.0 + bn.js: ^4.11.8 + clz-buffer: ^1.0.0 + concat-buffers: ^1.0.0 + crc: ^3.8.0 + isomorphic-textencoder: ^1.0.1 + select-case: ^1.0.0 + checksum: 3ddfb1d39e7a9c0da98940c95bf2a9b7a59819a1ee0a4839fcbf3152c386aa974a16854043e6cd5f987db2f9475b480c10f3ef5686f0da1495675cb399cdd22f + languageName: node + linkType: hard + +"@isomorphic-pgp/sign-and-verify@npm:^0.0.10": + version: 0.0.10 + resolution: "@isomorphic-pgp/sign-and-verify@npm:0.0.10" + dependencies: + "@isomorphic-pgp/parser": ^0.0.3 + "@isomorphic-pgp/util": ^0.0.6 + "@wmhilton/crypto-hash": ^1.0.2 + array-buffer-to-hex: ^1.0.0 + isomorphic-textencoder: ^1.0.1 + jsbn: ^1.1.0 + sha.js: ^2.4.11 + checksum: 3d43408abdb0d53c4514797ef47ec1d8f634e4a6b4a80f0df9ac826b9ba51df1a9cedcb26f7ddbee4594827de9676d7cda57549d321ea87bc95facfaa7127146 + languageName: node + linkType: hard + +"@isomorphic-pgp/util@npm:^0.0.6": + version: 0.0.6 + resolution: "@isomorphic-pgp/util@npm:0.0.6" + dependencies: + "@isomorphic-pgp/parser": ^0.0.3 + array-buffer-to-hex: ^1.0.0 + concat-buffers: ^1.0.0 + sha.js: ^2.4.11 + checksum: 721a15ddb4597f90b7b897b2d18a5819e86988ac44510f9aee9dc6a7823a62c3f1ba5b12eb35792c803b3b1c6d4af924ae0bc31123f21fbc6f5a8e8e7962a412 + languageName: node + linkType: hard + "@istanbuljs/load-nyc-config@npm:^1.0.0": version: 1.0.0 resolution: "@istanbuljs/load-nyc-config@npm:1.0.0" @@ -21814,6 +21868,13 @@ __metadata: languageName: node linkType: hard +"@wmhilton/crypto-hash@npm:^1.0.2": + version: 1.0.2 + resolution: "@wmhilton/crypto-hash@npm:1.0.2" + checksum: 99c7e46856d75dbd828922ed8088dcd85ac405d58976f785e3d3062e3e4044b11073ef43a6c74976d42abd2c320820993afcd6a1d41db22f19bf7a9fe5557140 + languageName: node + linkType: hard + "@xmldom/is-dom-node@npm:^1.0.1": version: 1.0.1 resolution: "@xmldom/is-dom-node@npm:1.0.1" @@ -23063,6 +23124,13 @@ __metadata: languageName: node linkType: hard +"array-buffer-to-hex@npm:^1.0.0": + version: 1.0.0 + resolution: "array-buffer-to-hex@npm:1.0.0" + checksum: 8de16fbb42fc79c839e5579e3e7dbf9a36dfa52cfcb4c0576a0e9cc6eeb5bf3f066b671858a292c97fcf9bd117c6eebc121085325e836dfb62b2737babd29264 + languageName: node + linkType: hard + "array-differ@npm:^3.0.0": version: 3.0.0 resolution: "array-differ@npm:3.0.0" @@ -23989,7 +24057,7 @@ __metadata: languageName: node linkType: hard -"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.9": +"bn.js@npm:^4.0.0, bn.js@npm:^4.1.0, bn.js@npm:^4.11.8, bn.js@npm:^4.11.9": version: 4.12.0 resolution: "bn.js@npm:4.12.0" checksum: 39afb4f15f4ea537b55eaf1446c896af28ac948fdcf47171961475724d1bb65118cca49fa6e3d67706e4790955ec0e74de584e45c8f1ef89f46c812bee5b5a12 @@ -24314,7 +24382,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.5.0, buffer@npm:^5.7.1": +"buffer@npm:^5.1.0, buffer@npm:^5.5.0, buffer@npm:^5.7.1": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -25153,6 +25221,13 @@ __metadata: languageName: node linkType: hard +"clz-buffer@npm:^1.0.0": + version: 1.0.0 + resolution: "clz-buffer@npm:1.0.0" + checksum: 00a177ec2e7732e2c6393e3825de5f9d4a530d77fefb1797cdf1782a3d72407c43941e0c30d4511dd6c0536ed0652cf17d356ce8408e4c3234e80e1d5c88e980 + languageName: node + linkType: hard + "cmd-extension@npm:^1.0.2": version: 1.0.2 resolution: "cmd-extension@npm:1.0.2" @@ -25541,6 +25616,13 @@ __metadata: languageName: node linkType: hard +"concat-buffers@npm:^1.0.0": + version: 1.0.0 + resolution: "concat-buffers@npm:1.0.0" + checksum: 5b1d0215687a1ab27d9181d9a5036664e0f86f7da5bda5a9e446962572736e5c345eb3ca40b7d276198efad703754f8870fd216e70a36990d231b0b27c5fe9d7 + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -25942,6 +26024,15 @@ __metadata: languageName: node linkType: hard +"crc@npm:^3.8.0": + version: 3.8.0 + resolution: "crc@npm:3.8.0" + dependencies: + buffer: ^5.1.0 + checksum: dabbc4eba223b206068b92ca82bb471d583eb6be2384a87f5c3712730cfd6ba4b13a45e8ba3ef62174d5a781a2c5ac5c20bf36cf37bba73926899bd0aa19186f + languageName: node + linkType: hard + "create-ecdh@npm:^4.0.4": version: 4.0.4 resolution: "create-ecdh@npm:4.0.4" @@ -29389,6 +29480,13 @@ __metadata: languageName: node linkType: hard +"fast-text-encoding@npm:^1.0.0": + version: 1.0.6 + resolution: "fast-text-encoding@npm:1.0.6" + checksum: 9d58f694314b3283e785bf61954902536da228607ad246905e30256f9ab8331f780ac987e7222c9f5eafd04168d07e12b8054c85cedb76a2c05af0e82387a903 + languageName: node + linkType: hard + "fast-uri@npm:^3.0.1": version: 3.0.1 resolution: "fast-uri@npm:3.0.1" @@ -33010,6 +33108,15 @@ __metadata: languageName: node linkType: hard +"isomorphic-textencoder@npm:^1.0.1": + version: 1.0.1 + resolution: "isomorphic-textencoder@npm:1.0.1" + dependencies: + fast-text-encoding: ^1.0.0 + checksum: 9bd0c04bff7b1dc9e410d2f9a2705ca4cc288872929d88fbb7758f06b5147de8dbaea5f95ce6cf4d314f064643f3d42d69a07bbb68df0261e81153d2d56f77f3 + languageName: node + linkType: hard + "isomorphic-timers-promises@npm:^1.0.1": version: 1.0.1 resolution: "isomorphic-timers-promises@npm:1.0.1" @@ -33807,7 +33914,7 @@ __metadata: languageName: node linkType: hard -"jsbn@npm:1.1.0": +"jsbn@npm:1.1.0, jsbn@npm:^1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" checksum: 944f924f2bd67ad533b3850eee47603eed0f6ae425fd1ee8c760f477e8c34a05f144c1bd4f5a5dd1963141dc79a2c55f89ccc5ab77d039e7077f3ad196b64965 @@ -43006,6 +43113,13 @@ __metadata: languageName: node linkType: hard +"select-case@npm:^1.0.0": + version: 1.0.0 + resolution: "select-case@npm:1.0.0" + checksum: 19cf1c8fd16ae4b811dea9fc4a11860dc764b9f7d2e6f7c8910814d91617e0d0d508ce2186aa9f99a3708834caf261e963769fed3a313552e1dae3d28d64042a + languageName: node + linkType: hard + "select-hose@npm:^2.0.0": version: 2.0.0 resolution: "select-hose@npm:2.0.0"