UNPKG

@backstage/backend-defaults

Version:

Backend defaults used by Backstage backend apps

210 lines (204 loc) • 7.67 kB
'use strict'; var errors = require('@backstage/errors'); var integration = require('@backstage/integration'); var parseGitUrl = require('git-url-parse'); var lodash = require('lodash'); var minimatch = require('minimatch'); var ReadUrlResponseFactory = require('./ReadUrlResponseFactory.cjs.js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; } var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl); class BitbucketUrlReader { static factory = ({ config, logger, treeResponseFactory }) => { const integrations = integration.ScmIntegrations.fromConfig(config); return integrations.bitbucket.list().filter( (item) => !integrations.bitbucketCloud.byHost(item.config.host) && !integrations.bitbucketServer.byHost(item.config.host) ).map((integration) => { const reader = new BitbucketUrlReader(integration, logger, { treeResponseFactory }); const predicate = (url) => url.host === integration.config.host; return { reader, predicate }; }); }; integration; deps; constructor(integration, logger, deps) { this.integration = integration; this.deps = deps; const { host, token, username, appPassword } = integration.config; const replacement = host === "bitbucket.org" ? "bitbucketCloud" : "bitbucketServer"; logger.warn( `[Deprecated] Please migrate from "integrations.bitbucket" to "integrations.${replacement}".` ); if (!token && username && !appPassword) { throw new Error( `Bitbucket integration for '${host}' has configured a username but is missing a required appPassword.` ); } } async read(url) { const response = await this.readUrl(url); return response.buffer(); } getCredentials = async (options) => { if (options?.token) { return { headers: { Authorization: `Bearer ${options.token}` } }; } return await integration.getBitbucketRequestOptions(this.integration.config); }; async readUrl(url, options) { const { etag, lastModifiedAfter, signal } = options ?? {}; const bitbucketUrl = integration.getBitbucketFileFetchUrl(url, this.integration.config); const requestOptions = await this.getCredentials(options); let response; try { response = await fetch(bitbucketUrl.toString(), { headers: { ...requestOptions.headers, ...etag && { "If-None-Match": etag }, ...lastModifiedAfter && { "If-Modified-Since": lastModifiedAfter.toUTCString() } }, // TODO(freben): The signal cast is there because pre-3.x versions of // node-fetch have a very slightly deviating AbortSignal type signature. // The difference does not affect us in practice however. The cast can be // removed after we support ESM for CLI dependencies and migrate to // version 3 of node-fetch. // https://github.com/backstage/backstage/issues/8242 ...signal && { signal } }); } catch (e) { throw new Error(`Unable to read ${url}, ${e}`); } if (response.status === 304) { throw new errors.NotModifiedError(); } if (response.ok) { return ReadUrlResponseFactory.ReadUrlResponseFactory.fromResponse(response); } const message = `${url} could not be read as ${bitbucketUrl}, ${response.status} ${response.statusText}`; if (response.status === 404) { throw new errors.NotFoundError(message); } throw new Error(message); } async readTree(url, options) { const { filepath } = parseGitUrl__default.default(url); const lastCommitShortHash = await this.getLastCommitShortHash(url); if (options?.etag && options.etag === lastCommitShortHash) { throw new errors.NotModifiedError(); } const downloadUrl = await integration.getBitbucketDownloadUrl( url, this.integration.config ); const archiveBitbucketResponse = await fetch( downloadUrl, integration.getBitbucketRequestOptions(this.integration.config) ); if (!archiveBitbucketResponse.ok) { const message = `Failed to read tree from ${url}, ${archiveBitbucketResponse.status} ${archiveBitbucketResponse.statusText}`; if (archiveBitbucketResponse.status === 404) { throw new errors.NotFoundError(message); } throw new Error(message); } return await this.deps.treeResponseFactory.fromTarArchive({ response: archiveBitbucketResponse, subpath: filepath, etag: lastCommitShortHash, filter: options?.filter }); } async search(url, options) { const { filepath } = parseGitUrl__default.default(url); if (!filepath?.match(/[*?]/)) { try { const data = await this.readUrl(url, options); return { files: [ { url, content: data.buffer, lastModifiedAt: data.lastModifiedAt } ], etag: data.etag ?? "" }; } catch (error) { errors.assertError(error); if (error.name === "NotFoundError") { return { files: [], etag: "" }; } throw error; } } const matcher = new minimatch.Minimatch(filepath); const treeUrl = lodash.trimEnd(url.replace(filepath, ""), "/"); const tree = await this.readTree(treeUrl, { etag: options?.etag, filter: (path) => matcher.match(path) }); const files = await tree.files(); return { etag: tree.etag, files: files.map((file) => ({ url: this.integration.resolveUrl({ url: `/${file.path}`, base: url }), content: file.content, lastModifiedAt: file.lastModifiedAt })) }; } toString() { const { host, token, username, appPassword } = this.integration.config; let authed = Boolean(token); if (!authed) { authed = Boolean(username && appPassword); } return `bitbucket{host=${host},authed=${authed}}`; } async getLastCommitShortHash(url) { const { resource, name: repoName, owner: project, ref } = parseGitUrl__default.default(url); let branch = ref; if (!branch) { branch = await integration.getBitbucketDefaultBranch(url, this.integration.config); } const isHosted = resource === "bitbucket.org"; const commitsApiUrl = isHosted ? `${this.integration.config.apiBaseUrl}/repositories/${project}/${repoName}/commits/${branch}` : `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits`; const commitsResponse = await fetch( commitsApiUrl, integration.getBitbucketRequestOptions(this.integration.config) ); if (!commitsResponse.ok) { const message = `Failed to retrieve commits from ${commitsApiUrl}, ${commitsResponse.status} ${commitsResponse.statusText}`; if (commitsResponse.status === 404) { throw new errors.NotFoundError(message); } throw new Error(message); } const commits = await commitsResponse.json(); if (isHosted) { if (commits && commits.values && commits.values.length > 0 && commits.values[0].hash) { return commits.values[0].hash.substring(0, 12); } } else { if (commits && commits.values && commits.values.length > 0 && commits.values[0].id) { return commits.values[0].id.substring(0, 12); } } throw new Error(`Failed to read response from ${commitsApiUrl}`); } } exports.BitbucketUrlReader = BitbucketUrlReader; //# sourceMappingURL=BitbucketUrlReader.cjs.js.map