@backstage/integration 0.7.3 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/dist/index.cjs.js +13 -10
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +864 -0
- package/dist/index.esm.js +13 -10
- package/dist/index.esm.js.map +1 -1
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# @backstage/integration
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 34af86517c: ensure `apiBaseUrl` being set for Bitbucket integrations, replace hardcoded defaults
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 33d5e79822: Fix Bitbucket Cloud and Bitbucket Server line number reference.
|
|
12
|
+
|
|
13
|
+
## 0.7.5
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 4e1384884f: Fixed bug in integration package where Self Hosted GitLab instances with custom ports weren't supported (because of the lack of an option to add the port in the integration configs. Now users can add the port directly in the host)
|
|
18
|
+
|
|
19
|
+
## 0.7.4
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Fix for the previous release with missing type declarations.
|
|
24
|
+
- Updated dependencies
|
|
25
|
+
- @backstage/config@0.1.15
|
|
26
|
+
|
|
3
27
|
## 0.7.3
|
|
4
28
|
|
|
5
29
|
### Patch Changes
|
|
@@ -221,7 +245,7 @@
|
|
|
221
245
|
|
|
222
246
|
- 0fd4ea443: Updates the `GithubCredentialsProvider` to return the token type, it can either be `token` or `app` depending on the authentication method.
|
|
223
247
|
|
|
224
|
-
Update the `GithubOrgReaderProcessor` NOT to query for email addresses if GitHub Apps is used for authentication, this is due to inconsistencies in the GitHub API when using server to server communications and installation tokens. https://github.community/t/api-v4-unable-to-retrieve-email-resource-not-accessible-by-integration/13831/4 for more info.
|
|
248
|
+
Update the `GithubOrgReaderProcessor` NOT to query for email addresses if GitHub Apps is used for authentication, this is due to inconsistencies in the GitHub API when using server to server communications and installation tokens. See [this community discussion](https://github.community/t/api-v4-unable-to-retrieve-email-resource-not-accessible-by-integration/13831/4) for more info.
|
|
225
249
|
|
|
226
250
|
**Removes** deprecated GithubOrgReaderProcessor provider configuration(`catalog.processors.githubOrg`). If you're using the deprecated config section make sure to migrate to [integrations](https://backstage.io/docs/integrations/github/locations) instead.
|
|
227
251
|
|
package/dist/index.cjs.js
CHANGED
|
@@ -310,6 +310,8 @@ function readBitbucketIntegrationConfig(config) {
|
|
|
310
310
|
apiBaseUrl = lodash.trimEnd(apiBaseUrl, "/");
|
|
311
311
|
} else if (host === BITBUCKET_HOST) {
|
|
312
312
|
apiBaseUrl = BITBUCKET_API_BASE_URL;
|
|
313
|
+
} else {
|
|
314
|
+
apiBaseUrl = `https://${host}/rest/api/1.0`;
|
|
313
315
|
}
|
|
314
316
|
return {
|
|
315
317
|
host,
|
|
@@ -345,13 +347,16 @@ const _BitbucketIntegration = class {
|
|
|
345
347
|
}
|
|
346
348
|
resolveUrl(options) {
|
|
347
349
|
const resolved = defaultScmResolveUrl(options);
|
|
348
|
-
if (options.lineNumber) {
|
|
349
|
-
|
|
350
|
-
const filename = url.pathname.split("/").slice(-1)[0];
|
|
351
|
-
url.hash = `${filename}-${options.lineNumber}`;
|
|
352
|
-
return url.toString();
|
|
350
|
+
if (!options.lineNumber) {
|
|
351
|
+
return resolved;
|
|
353
352
|
}
|
|
354
|
-
|
|
353
|
+
const url = new URL(resolved);
|
|
354
|
+
if (this.integrationConfig.host === "bitbucket.org") {
|
|
355
|
+
url.hash = `lines-${options.lineNumber}`;
|
|
356
|
+
} else {
|
|
357
|
+
url.hash = `${options.lineNumber}`;
|
|
358
|
+
}
|
|
359
|
+
return url.toString();
|
|
355
360
|
}
|
|
356
361
|
resolveEditUrl(url) {
|
|
357
362
|
const urlData = parseGitUrl__default["default"](url);
|
|
@@ -739,9 +744,7 @@ function readGitLabIntegrationConfig(config) {
|
|
|
739
744
|
} else {
|
|
740
745
|
baseUrl = `https://${host}`;
|
|
741
746
|
}
|
|
742
|
-
if (host
|
|
743
|
-
throw new Error(`Invalid GitLab integration config, host '${host}' should just be the host name (e.g. "github.com"), not a URL`);
|
|
744
|
-
} else if (!isValidHost(host)) {
|
|
747
|
+
if (!isValidHost(host)) {
|
|
745
748
|
throw new Error(`Invalid GitLab integration config, '${host}' is not a valid host`);
|
|
746
749
|
} else if (!apiBaseUrl || !isValidUrl(apiBaseUrl)) {
|
|
747
750
|
throw new Error(`Invalid GitLab integration config, '${apiBaseUrl}' is not a valid apiBaseUrl`);
|
|
@@ -815,7 +818,7 @@ async function getProjectId(target, config) {
|
|
|
815
818
|
}
|
|
816
819
|
try {
|
|
817
820
|
const repo = url.pathname.split("/-/blob/")[0];
|
|
818
|
-
const repoIDLookup = new URL(`${url.
|
|
821
|
+
const repoIDLookup = new URL(`${url.origin}/api/v4/projects/${encodeURIComponent(repo.replace(/^\//, ""))}`);
|
|
819
822
|
const response = await fetch__default["default"](repoIDLookup.toString(), getGitLabRequestOptions(config));
|
|
820
823
|
const data = await response.json();
|
|
821
824
|
if (!response.ok) {
|
package/dist/index.cjs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs.js","sources":["../src/helpers.ts","../src/azure/AzureUrl.ts","../src/azure/config.ts","../src/azure/AzureIntegration.ts","../src/azure/core.ts","../src/bitbucket/config.ts","../src/bitbucket/BitbucketIntegration.ts","../src/bitbucket/core.ts","../src/github/config.ts","../src/github/core.ts","../src/github/SingleInstanceGithubCredentialsProvider.ts","../src/github/DefaultGithubCredentialsProvider.ts","../src/github/GitHubIntegration.ts","../src/gitlab/config.ts","../src/gitlab/core.ts","../src/gitlab/GitLabIntegration.ts","../src/googleGcs/config.ts","../src/awsS3/config.ts","../src/awsS3/AwsS3Integration.ts","../src/ScmIntegrations.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd } from 'lodash';\nimport { ScmIntegration, ScmIntegrationsGroup } from './types';\n\n/** Checks whether the given argument is a valid URL hostname */\nexport function isValidHost(host: string): boolean {\n const check = new URL('http://example.com');\n check.host = host;\n return check.host === host;\n}\n\n/** Checks whether the given argument is a valid URL */\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function basicIntegrations<T extends ScmIntegration>(\n integrations: T[],\n getHost: (integration: T) => string,\n): ScmIntegrationsGroup<T> {\n return {\n list(): T[] {\n return integrations;\n },\n byUrl(url: string | URL): T | undefined {\n try {\n const parsed = typeof url === 'string' ? new URL(url) : url;\n return integrations.find(i => getHost(i) === parsed.host);\n } catch {\n return undefined;\n }\n },\n byHost(host: string): T | undefined {\n return integrations.find(i => getHost(i) === host);\n },\n };\n}\n\n/**\n * Default implementation of {@link ScmIntegration} `resolveUrl`, that only\n * works with URL pathname based providers.\n *\n * @public\n */\nexport function defaultScmResolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n}): string {\n const { url, base, lineNumber } = options;\n\n // If it is a fully qualified URL - then return it verbatim\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return url;\n } catch {\n // ignore intentionally\n }\n\n let updated: URL;\n\n if (url.startsWith('/')) {\n // If it is an absolute path, move relative to the repo root\n const { filepath } = parseGitUrl(base);\n updated = new URL(base);\n const repoRootPath = trimEnd(\n updated.pathname.substring(0, updated.pathname.length - filepath.length),\n '/',\n );\n updated.pathname = `${repoRootPath}${url}`;\n } else {\n // For relative URLs, just let the default URL constructor handle the\n // resolving. Note that this essentially will treat the last segment of the\n // base as a file - NOT a folder - unless the url ends in a slash.\n updated = new URL(url, base);\n }\n\n updated.search = new URL(base).search;\n if (lineNumber) {\n updated.hash = `L${lineNumber}`;\n }\n return updated.toString();\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst VERSION_PREFIX_GIT_BRANCH = 'GB';\n\nexport class AzureUrl {\n /**\n * Parses an azure URL as copied from the browser address bar.\n *\n * Throws an error if the URL is not a valid azure repo URL.\n */\n static fromRepoUrl(repoUrl: string): AzureUrl {\n const url = new URL(repoUrl);\n\n let owner;\n let project;\n let repo;\n\n const parts = url.pathname.split('/').map(part => decodeURIComponent(part));\n if (parts[2] === '_git') {\n owner = parts[1];\n project = repo = parts[3];\n } else if (parts[3] === '_git') {\n owner = parts[1];\n project = parts[2];\n repo = parts[4];\n }\n\n if (!owner || !project || !repo) {\n throw new Error('Azure URL must point to a git repository');\n }\n\n const path = url.searchParams.get('path') ?? undefined;\n\n let ref;\n const version = url.searchParams.get('version');\n if (version) {\n const prefix = version.slice(0, 2);\n if (prefix !== 'GB') {\n throw new Error('Azure URL version must point to a git branch');\n }\n ref = version.slice(2);\n }\n\n return new AzureUrl(url.origin, owner, project, repo, path, ref);\n }\n\n #origin: string;\n #owner: string;\n #project: string;\n #repo: string;\n #path?: string;\n #ref?: string;\n\n private constructor(\n origin: string,\n owner: string,\n project: string,\n repo: string,\n path?: string,\n ref?: string,\n ) {\n this.#origin = origin;\n this.#owner = owner;\n this.#project = project;\n this.#repo = repo;\n this.#path = path;\n this.#ref = ref;\n }\n\n #baseUrl = (...parts: string[]): URL => {\n const url = new URL(this.#origin);\n url.pathname = parts.map(part => encodeURIComponent(part)).join('/');\n return url;\n };\n\n /**\n * Returns a repo URL that can be used to navigate to the resource in azure.\n *\n * Throws an error if the URL is not a valid azure repo URL.\n */\n toRepoUrl(): string {\n let url;\n if (this.#project === this.#repo) {\n url = this.#baseUrl(this.#owner, '_git', this.#repo);\n } else {\n url = this.#baseUrl(this.#owner, this.#project, '_git', this.#repo);\n }\n\n if (this.#path) {\n url.searchParams.set('path', this.#path);\n }\n if (this.#ref) {\n url.searchParams.set('version', VERSION_PREFIX_GIT_BRANCH + this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the file download URL for this azure resource.\n *\n * Throws an error if the URL does not point to a file.\n */\n toFileUrl(): string {\n if (!this.#path) {\n throw new Error(\n 'Azure URL must point to a specific path to be able to download a file',\n );\n }\n\n const url = this.#baseUrl(\n this.#owner,\n this.#project,\n '_apis',\n 'git',\n 'repositories',\n this.#repo,\n 'items',\n );\n url.searchParams.set('api-version', '6.0');\n url.searchParams.set('path', this.#path);\n\n if (this.#ref) {\n url.searchParams.set('version', this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the archive download URL for this azure resource.\n *\n * Throws an error if the URL does not point to a repo.\n */\n toArchiveUrl(): string {\n const url = this.#baseUrl(\n this.#owner,\n this.#project,\n '_apis',\n 'git',\n 'repositories',\n this.#repo,\n 'items',\n );\n url.searchParams.set('recursionLevel', 'full');\n url.searchParams.set('download', 'true');\n url.searchParams.set('api-version', '6.0');\n\n if (this.#path) {\n url.searchParams.set('scopePath', this.#path);\n }\n if (this.#ref) {\n url.searchParams.set('version', this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the API url for fetching commits from a branch for this azure resource.\n *\n * Throws an error if the URL does not point to a commit.\n */\n toCommitsUrl(): string {\n const url = this.#baseUrl(\n this.#owner,\n this.#project,\n '_apis',\n 'git',\n 'repositories',\n this.#repo,\n 'commits',\n );\n url.searchParams.set('api-version', '6.0');\n\n if (this.#ref) {\n url.searchParams.set('searchCriteria.itemVersion.version', this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the name of the owner, a user or an organization.\n */\n getOwner(): string {\n return this.#owner;\n }\n\n /**\n * Returns the name of the project.\n */\n getProject(): string {\n return this.#project;\n }\n\n /**\n * Returns the name of the repo.\n */\n getRepo(): string {\n return this.#repo;\n }\n\n /**\n * Returns the file path within the repo if the URL contains one.\n */\n getPath(): string | undefined {\n return this.#path;\n }\n\n /**\n * Returns the git ref in the repo if the URL contains one.\n */\n getRef(): string | undefined {\n return this.#ref;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { isValidHost } from '../helpers';\n\nconst AZURE_HOST = 'dev.azure.com';\n\n/**\n * The configuration parameters for a single Azure provider.\n *\n * @public\n */\nexport type AzureIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. \"dev.azure.com\".\n *\n * Currently only \"dev.azure.com\" is supported.\n */\n host: string;\n\n /**\n * The authorization token to use for requests.\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n};\n\n/**\n * Reads a single Azure integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readAzureIntegrationConfig(\n config: Config,\n): AzureIntegrationConfig {\n const host = config.getOptionalString('host') ?? AZURE_HOST;\n const token = config.getOptionalString('token');\n\n if (!isValidHost(host)) {\n throw new Error(\n `Invalid Azure integration config, '${host}' is not a valid host`,\n );\n }\n\n return { host, token };\n}\n\n/**\n * Reads a set of Azure integration configs, and inserts some defaults for\n * public Azure if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readAzureIntegrationConfigs(\n configs: Config[],\n): AzureIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readAzureIntegrationConfig);\n\n // If no explicit dev.azure.com integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === AZURE_HOST)) {\n result.push({ host: AZURE_HOST });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, isValidUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport { AzureUrl } from './AzureUrl';\nimport { AzureIntegrationConfig, readAzureIntegrationConfigs } from './config';\n\n/**\n * Microsoft Azure based integration.\n *\n * @public\n */\nexport class AzureIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<AzureIntegration> = ({ config }) => {\n const configs = readAzureIntegrationConfigs(\n config.getOptionalConfigArray('integrations.azure') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new AzureIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: AzureIntegrationConfig) {}\n\n get type(): string {\n return 'azure';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): AzureIntegrationConfig {\n return this.integrationConfig;\n }\n\n /*\n * Azure repo URLs on the form with a `path` query param are treated specially.\n *\n * Example base URL: https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml\n */\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n const { url, base } = options;\n\n // If we can parse the url, it is absolute - then return it verbatim\n if (isValidUrl(url)) {\n return url;\n }\n\n try {\n const azureUrl = AzureUrl.fromRepoUrl(base);\n const newUrl = new URL(base);\n\n // We lean on the URL path resolution logic to resolve the path param\n const mockBaseUrl = new URL(`https://a.com${azureUrl.getPath() ?? ''}`);\n const updatedPath = new URL(url, mockBaseUrl).pathname;\n newUrl.searchParams.set('path', updatedPath);\n\n if (options.lineNumber) {\n newUrl.searchParams.set('line', String(options.lineNumber));\n newUrl.searchParams.set('lineEnd', String(options.lineNumber + 1));\n newUrl.searchParams.set('lineStartColumn', '1');\n newUrl.searchParams.set('lineEndColumn', '1');\n }\n\n return newUrl.toString();\n } catch {\n // If not an actual file path within a repo, treat the URL as raw\n return new URL(url, base).toString();\n }\n }\n\n resolveEditUrl(url: string): string {\n // TODO: Implement edit URL for Azure, fallback to view url as I don't know\n // how azure works.\n return url;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AzureUrl } from './AzureUrl';\nimport { AzureIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * - from: `https://dev.azure.com/{organization}/{project}/_git/reponame?path={path}&version=GB{commitOrBranch}&_a=contents`\n * - to: `https://dev.azure.com/{organization}/{project}/_apis/git/repositories/reponame/items?path={path}&version={commitOrBranch}`\n *\n * @param url - A URL pointing to a file\n * @public\n */\nexport function getAzureFileFetchUrl(url: string): string {\n return AzureUrl.fromRepoUrl(url).toFileUrl();\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @public\n */\nexport function getAzureDownloadUrl(url: string): string {\n return AzureUrl.fromRepoUrl(url).toArchiveUrl();\n}\n\n/**\n * Given a URL, return the API URL to fetch commits on the branch.\n *\n * @param url - A URL pointing to a repository or a sub-path\n * @public\n */\nexport function getAzureCommitsUrl(url: string): string {\n return AzureUrl.fromRepoUrl(url).toCommitsUrl();\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getAzureRequestOptions(\n config: AzureIntegrationConfig,\n additionalHeaders?: Record<string, string>,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = additionalHeaders\n ? { ...additionalHeaders }\n : {};\n\n if (config.token) {\n const buffer = Buffer.from(`:${config.token}`, 'utf8');\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return { headers };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\nimport { isValidHost } from '../helpers';\n\nconst BITBUCKET_HOST = 'bitbucket.org';\nconst BITBUCKET_API_BASE_URL = 'https://api.bitbucket.org/2.0';\n\n/**\n * The configuration parameters for a single Bitbucket API provider.\n *\n * @public\n */\nexport type BitbucketIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. \"bitbucket.org\"\n */\n host: string;\n\n /**\n * The base URL of the API of this provider, e.g. \"https://api.bitbucket.org/2.0\",\n * with no trailing slash.\n *\n * May be omitted specifically for Bitbucket Cloud; then it will be deduced.\n *\n * The API will always be preferred if both its base URL and a token are\n * present.\n */\n apiBaseUrl?: string;\n\n /**\n * The authorization token to use for requests to a Bitbucket Server provider.\n *\n * See https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n\n /**\n * The username to use for requests to Bitbucket Cloud (bitbucket.org).\n */\n username?: string;\n\n /**\n * Authentication with Bitbucket Cloud (bitbucket.org) is done using app passwords.\n *\n * See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/\n */\n appPassword?: string;\n};\n\n/**\n * Reads a single Bitbucket integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readBitbucketIntegrationConfig(\n config: Config,\n): BitbucketIntegrationConfig {\n const host = config.getOptionalString('host') ?? BITBUCKET_HOST;\n let apiBaseUrl = config.getOptionalString('apiBaseUrl');\n const token = config.getOptionalString('token');\n const username = config.getOptionalString('username');\n const appPassword = config.getOptionalString('appPassword');\n\n if (!isValidHost(host)) {\n throw new Error(\n `Invalid Bitbucket integration config, '${host}' is not a valid host`,\n );\n }\n\n if (apiBaseUrl) {\n apiBaseUrl = trimEnd(apiBaseUrl, '/');\n } else if (host === BITBUCKET_HOST) {\n apiBaseUrl = BITBUCKET_API_BASE_URL;\n }\n\n return {\n host,\n apiBaseUrl,\n token,\n username,\n appPassword,\n };\n}\n\n/**\n * Reads a set of Bitbucket integration configs, and inserts some defaults for\n * public Bitbucket if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readBitbucketIntegrationConfigs(\n configs: Config[],\n): BitbucketIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readBitbucketIntegrationConfig);\n\n // If no explicit bitbucket.org integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === BITBUCKET_HOST)) {\n result.push({\n host: BITBUCKET_HOST,\n apiBaseUrl: BITBUCKET_API_BASE_URL,\n });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport {\n BitbucketIntegrationConfig,\n readBitbucketIntegrationConfigs,\n} from './config';\n\n/**\n * A Bitbucket based integration.\n *\n * @public\n */\nexport class BitbucketIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<BitbucketIntegration> = ({\n config,\n }) => {\n const configs = readBitbucketIntegrationConfigs(\n config.getOptionalConfigArray('integrations.bitbucket') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new BitbucketIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: BitbucketIntegrationConfig) {}\n\n get type(): string {\n return 'bitbucket';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): BitbucketIntegrationConfig {\n return this.integrationConfig;\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n const resolved = defaultScmResolveUrl(options);\n\n // Bitbucket line numbers use the syntax #example.txt-42, rather than #L42\n if (options.lineNumber) {\n const url = new URL(resolved);\n\n const filename = url.pathname.split('/').slice(-1)[0];\n url.hash = `${filename}-${options.lineNumber}`;\n return url.toString();\n }\n\n return resolved;\n }\n\n resolveEditUrl(url: string): string {\n const urlData = parseGitUrl(url);\n const editUrl = new URL(url);\n\n editUrl.searchParams.set('mode', 'edit');\n // TODO: Not sure what spa=0 does, at least bitbucket.org doesn't support it\n // but this is taken over from the initial implementation.\n editUrl.searchParams.set('spa', '0');\n editUrl.searchParams.set('at', urlData.ref);\n return editUrl.toString();\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fetch from 'cross-fetch';\nimport parseGitUrl from 'git-url-parse';\nimport { BitbucketIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a path on a provider, returns the default branch.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketDefaultBranch(\n url: string,\n config: BitbucketIntegrationConfig,\n): Promise<string> {\n const { name: repoName, owner: project, resource } = parseGitUrl(url);\n\n const isHosted = resource === 'bitbucket.org';\n // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184\n let branchUrl = isHosted\n ? `${config.apiBaseUrl}/repositories/${project}/${repoName}`\n : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;\n\n let response = await fetch(branchUrl, getBitbucketRequestOptions(config));\n\n if (response.status === 404 && !isHosted) {\n // First try the new format, and then if it gets specifically a 404 it should try the old format\n // (to support old Atlassian Bitbucket v5.11.1 format )\n branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;\n response = await fetch(branchUrl, getBitbucketRequestOptions(config));\n }\n\n if (!response.ok) {\n const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n\n let defaultBranch;\n if (isHosted) {\n const repoInfo = await response.json();\n defaultBranch = repoInfo.mainbranch.name;\n } else {\n const { displayId } = await response.json();\n defaultBranch = displayId;\n }\n if (!defaultBranch) {\n throw new Error(\n `Failed to read default branch from ${branchUrl}. ` +\n `Response ${response.status} ${response.json()}`,\n );\n }\n return defaultBranch;\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketDownloadUrl(\n url: string,\n config: BitbucketIntegrationConfig,\n): Promise<string> {\n const {\n name: repoName,\n owner: project,\n ref,\n protocol,\n resource,\n filepath,\n } = parseGitUrl(url);\n\n const isHosted = resource === 'bitbucket.org';\n\n let branch = ref;\n if (!branch) {\n branch = await getBitbucketDefaultBranch(url, config);\n }\n // path will limit the downloaded content\n // /docs will only download the docs folder and everything below it\n // /docs/index.md will download the docs folder and everything below it\n const path = filepath ? `&path=${encodeURIComponent(filepath)}` : '';\n const archiveUrl = isHosted\n ? `${protocol}://${resource}/${project}/${repoName}/get/${branch}.tar.gz`\n : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?format=tgz&at=${branch}&prefix=${project}-${repoName}${path}`;\n\n return archiveUrl;\n}\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://bitbucket.org/orgname/reponame/src/master/file.yaml\n * to: https://api.bitbucket.org/2.0/repositories/orgname/reponame/src/master/file.yaml\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketFileFetchUrl(\n url: string,\n config: BitbucketIntegrationConfig,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url);\n if (\n !owner ||\n !name ||\n (filepathtype !== 'browse' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'src')\n ) {\n throw new Error('Invalid Bitbucket URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n\n if (config.host === 'bitbucket.org') {\n if (!ref) {\n throw new Error('Invalid Bitbucket URL or file path');\n }\n return `${config.apiBaseUrl}/repositories/${owner}/${name}/src/${ref}/${pathWithoutSlash}`;\n }\n return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?at=${ref}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketRequestOptions(\n config: BitbucketIntegrationConfig,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.appPassword) {\n const buffer = Buffer.from(\n `${config.username}:${config.appPassword}`,\n 'utf8',\n );\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return {\n headers,\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\nimport { isValidHost } from '../helpers';\n\nconst GITHUB_HOST = 'github.com';\nconst GITHUB_API_BASE_URL = 'https://api.github.com';\nconst GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com';\n\n/**\n * The configuration parameters for a single GitHub integration.\n *\n * @public\n */\nexport type GitHubIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. \"github.com\"\n */\n host: string;\n\n /**\n * The base URL of the API of this provider, e.g. \"https://api.github.com\",\n * with no trailing slash.\n *\n * May be omitted specifically for GitHub; then it will be deduced.\n *\n * The API will always be preferred if both its base URL and a token are\n * present.\n */\n apiBaseUrl?: string;\n\n /**\n * The base URL of the raw fetch endpoint of this provider, e.g.\n * \"https://raw.githubusercontent.com\", with no trailing slash.\n *\n * May be omitted specifically for GitHub; then it will be deduced.\n *\n * The API will always be preferred if both its base URL and a token are\n * present.\n */\n rawBaseUrl?: string;\n\n /**\n * The authorization token to use for requests to this provider.\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n\n /**\n * The GitHub Apps configuration to use for requests to this provider.\n *\n * If no apps are specified, token or anonymous is used.\n */\n apps?: GithubAppConfig[];\n};\n\n/**\n * The configuration parameters for authenticating a GitHub Application.\n *\n * @remarks\n *\n * A GitHub Apps configuration can be generated using the `backstage-cli create-github-app` command.\n *\n * @public\n */\nexport type GithubAppConfig = {\n /**\n * Unique app identifier, found at https://github.com/organizations/$org/settings/apps/$AppName\n */\n appId: number;\n /**\n * The private key is used by the GitHub App integration to authenticate the app.\n * A private key can be generated from the app at https://github.com/organizations/$org/settings/apps/$AppName\n */\n privateKey: string;\n /**\n * Webhook secret can be configured at https://github.com/organizations/$org/settings/apps/$AppName\n */\n webhookSecret: string;\n /**\n * Found at https://github.com/organizations/$org/settings/apps/$AppName\n */\n clientId: string;\n /**\n * Client secrets can be generated at https://github.com/organizations/$org/settings/apps/$AppName\n */\n clientSecret: string;\n /**\n * List of installation owners allowed to be used by this GitHub app. The GitHub UI does not provide a way to list the installations.\n * However you can list the installations with the GitHub API. You can find the list of installations here:\n * https://api.github.com/app/installations\n * The relevant documentation for this is here.\n * https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app--code-samples\n */\n allowedInstallationOwners?: string[];\n};\n\n/**\n * Reads a single GitHub integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readGitHubIntegrationConfig(\n config: Config,\n): GitHubIntegrationConfig {\n const host = config.getOptionalString('host') ?? GITHUB_HOST;\n let apiBaseUrl = config.getOptionalString('apiBaseUrl');\n let rawBaseUrl = config.getOptionalString('rawBaseUrl');\n const token = config.getOptionalString('token');\n const apps = config.getOptionalConfigArray('apps')?.map(c => ({\n appId: c.getNumber('appId'),\n clientId: c.getString('clientId'),\n clientSecret: c.getString('clientSecret'),\n webhookSecret: c.getString('webhookSecret'),\n privateKey: c.getString('privateKey'),\n allowedInstallationOwners: c.getOptionalStringArray(\n 'allowedInstallationOwners',\n ),\n }));\n\n if (!isValidHost(host)) {\n throw new Error(\n `Invalid GitHub integration config, '${host}' is not a valid host`,\n );\n }\n\n if (apiBaseUrl) {\n apiBaseUrl = trimEnd(apiBaseUrl, '/');\n } else if (host === GITHUB_HOST) {\n apiBaseUrl = GITHUB_API_BASE_URL;\n }\n\n if (rawBaseUrl) {\n rawBaseUrl = trimEnd(rawBaseUrl, '/');\n } else if (host === GITHUB_HOST) {\n rawBaseUrl = GITHUB_RAW_BASE_URL;\n }\n\n return { host, apiBaseUrl, rawBaseUrl, token, apps };\n}\n\n/**\n * Reads a set of GitHub integration configs, and inserts some defaults for\n * public GitHub if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readGitHubIntegrationConfigs(\n configs: Config[],\n): GitHubIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readGitHubIntegrationConfig);\n\n // If no explicit github.com integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === GITHUB_HOST)) {\n result.push({\n host: GITHUB_HOST,\n apiBaseUrl: GITHUB_API_BASE_URL,\n rawBaseUrl: GITHUB_RAW_BASE_URL,\n });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GitHubIntegrationConfig } from './config';\nimport { GithubCredentials } from './types';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://github.com/a/b/blob/branchname/path/to/c.yaml\n * to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname\n * or: https://raw.githubusercontent.com/a/b/branchname/c.yaml\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getGitHubFileFetchUrl(\n url: string,\n config: GitHubIntegrationConfig,\n credentials: GithubCredentials,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url);\n if (\n !owner ||\n !name ||\n !ref ||\n // GitHub is automatically redirecting tree urls to blob urls so it's\n // fine to pass a tree url.\n (filepathtype !== 'blob' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'tree')\n ) {\n throw new Error('Invalid GitHub URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n if (chooseEndpoint(config, credentials) === 'api') {\n return `${config.apiBaseUrl}/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`;\n }\n return `${config.rawBaseUrl}/${owner}/${name}/${ref}/${pathWithoutSlash}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @deprecated This function is no longer used internally\n * @param config - The relevant provider config\n * @public\n */\nexport function getGitHubRequestOptions(\n config: GitHubIntegrationConfig,\n credentials: GithubCredentials,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (chooseEndpoint(config, credentials) === 'api') {\n headers.Accept = 'application/vnd.github.v3.raw';\n }\n\n if (credentials.token) {\n headers.Authorization = `token ${credentials.token}`;\n }\n\n return { headers };\n}\n\nexport function chooseEndpoint(\n config: GitHubIntegrationConfig,\n credentials: GithubCredentials,\n): 'api' | 'raw' {\n if (config.apiBaseUrl && (credentials.token || !config.rawBaseUrl)) {\n return 'api';\n }\n return 'raw';\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GithubAppConfig, GitHubIntegrationConfig } from './config';\nimport { createAppAuth } from '@octokit/auth-app';\nimport { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { DateTime } from 'luxon';\nimport {\n GithubCredentials,\n GithubCredentialsProvider,\n GithubCredentialType,\n} from './types';\n\ntype InstallationData = {\n installationId: number;\n suspended: boolean;\n};\n\nclass Cache {\n private readonly tokenCache = new Map<\n string,\n { token: string; expiresAt: DateTime }\n >();\n\n async getOrCreateToken(\n key: string,\n supplier: () => Promise<{ token: string; expiresAt: DateTime }>,\n ): Promise<{ accessToken: string }> {\n const item = this.tokenCache.get(key);\n if (item && this.isNotExpired(item.expiresAt)) {\n return { accessToken: item.token };\n }\n\n const result = await supplier();\n this.tokenCache.set(key, result);\n return { accessToken: result.token };\n }\n\n // consider timestamps older than 50 minutes to be expired.\n private isNotExpired = (date: DateTime) =>\n date.diff(DateTime.local(), 'minutes').minutes > 50;\n}\n\n/**\n * This accept header is required when calling App APIs in GitHub Enterprise.\n * It has no effect on calls to github.com and can probably be removed entirely\n * once GitHub Apps is out of preview.\n */\nconst HEADERS = {\n Accept: 'application/vnd.github.machine-man-preview+json',\n};\n\n/**\n * GithubAppManager issues and caches tokens for a specific GitHub App.\n */\nclass GithubAppManager {\n private readonly appClient: Octokit;\n private readonly baseUrl?: string;\n private readonly baseAuthConfig: { appId: number; privateKey: string };\n private readonly cache = new Cache();\n private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations\n\n constructor(config: GithubAppConfig, baseUrl?: string) {\n this.allowedInstallationOwners = config.allowedInstallationOwners;\n this.baseUrl = baseUrl;\n this.baseAuthConfig = {\n appId: config.appId,\n privateKey: config.privateKey.replace(/\\\\n/gm, '\\n'),\n };\n this.appClient = new Octokit({\n baseUrl,\n headers: HEADERS,\n authStrategy: createAppAuth,\n auth: this.baseAuthConfig,\n });\n }\n\n async getInstallationCredentials(\n owner: string,\n repo?: string,\n ): Promise<{ accessToken: string | undefined }> {\n const { installationId, suspended } = await this.getInstallationData(owner);\n if (this.allowedInstallationOwners) {\n if (!this.allowedInstallationOwners?.includes(owner)) {\n return { accessToken: undefined }; // An empty token allows anonymous access to public repos\n }\n }\n if (suspended) {\n throw new Error(`The GitHub application for ${owner} is suspended`);\n }\n\n const cacheKey = repo ? `${owner}/${repo}` : owner;\n\n // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.\n return this.cache.getOrCreateToken(cacheKey, async () => {\n const result = await this.appClient.apps.createInstallationAccessToken({\n installation_id: installationId,\n headers: HEADERS,\n });\n if (repo && result.data.repository_selection === 'selected') {\n const installationClient = new Octokit({\n baseUrl: this.baseUrl,\n auth: result.data.token,\n });\n const repos = await installationClient.paginate(\n installationClient.apps.listReposAccessibleToInstallation,\n );\n const hasRepo = repos.some(repository => {\n return repository.name === repo;\n });\n if (!hasRepo) {\n throw new Error(\n `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,\n );\n }\n }\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n };\n });\n }\n\n getInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n return this.appClient.paginate(this.appClient.apps.listInstallations);\n }\n\n private async getInstallationData(owner: string): Promise<InstallationData> {\n const allInstallations = await this.getInstallations();\n const installation = allInstallations.find(\n inst =>\n inst.account?.login?.toLocaleLowerCase('en-US') ===\n owner.toLocaleLowerCase('en-US'),\n );\n if (installation) {\n return {\n installationId: installation.id,\n suspended: Boolean(installation.suspended_by),\n };\n }\n const notFoundError = new Error(\n `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`,\n );\n notFoundError.name = 'NotFoundError';\n throw notFoundError;\n }\n}\n\n/**\n * Corresponds to a Github installation which internally could hold several GitHub Apps.\n *\n * @public\n */\nexport class GithubAppCredentialsMux {\n private readonly apps: GithubAppManager[];\n\n constructor(config: GitHubIntegrationConfig) {\n this.apps =\n config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? [];\n }\n\n async getAllInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n if (!this.apps.length) {\n return [];\n }\n\n const installs = await Promise.all(\n this.apps.map(app => app.getInstallations()),\n );\n\n return installs.flat();\n }\n\n async getAppToken(owner: string, repo?: string): Promise<string | undefined> {\n if (this.apps.length === 0) {\n return undefined;\n }\n\n const results = await Promise.all(\n this.apps.map(app =>\n app.getInstallationCredentials(owner, repo).then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n ),\n ),\n );\n\n const result = results.find(resultItem => resultItem.credentials);\n if (result) {\n return result.credentials!.accessToken;\n }\n\n const errors = results.map(r => r.error);\n const notNotFoundError = errors.find(err => err.name !== 'NotFoundError');\n if (notNotFoundError) {\n throw notNotFoundError;\n }\n\n return undefined;\n }\n}\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class SingleInstanceGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static create: (\n config: GitHubIntegrationConfig,\n ) => GithubCredentialsProvider = config => {\n return new SingleInstanceGithubCredentialsProvider(\n new GithubAppCredentialsMux(config),\n config.token,\n );\n };\n\n private constructor(\n private readonly githubAppCredentialsMux: GithubAppCredentialsMux,\n private readonly token?: string,\n ) {}\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'github.com/backstage/foobar'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = parseGitUrl(opts.url);\n\n const owner = parsed.owner || parsed.name;\n const repo = parsed.owner ? parsed.name : undefined;\n\n let type: GithubCredentialType = 'app';\n let token = await this.githubAppCredentialsMux.getAppToken(owner, repo);\n if (!token) {\n type = 'token';\n token = this.token;\n }\n\n return {\n headers: token ? { Authorization: `Bearer ${token}` } : undefined,\n token,\n type,\n };\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GithubCredentials, GithubCredentialsProvider } from './types';\nimport { ScmIntegrationRegistry } from '../registry';\nimport { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class DefaultGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static fromIntegrations(integrations: ScmIntegrationRegistry) {\n const credentialsProviders: Map<string, GithubCredentialsProvider> =\n new Map<string, GithubCredentialsProvider>();\n\n integrations.github.list().forEach(integration => {\n const credentialsProvider =\n SingleInstanceGithubCredentialsProvider.create(integration.config);\n credentialsProviders.set(integration.config.host, credentialsProvider);\n });\n return new DefaultGithubCredentialsProvider(credentialsProviders);\n }\n\n private constructor(\n private readonly providers: Map<string, GithubCredentialsProvider>,\n ) {}\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage/foobar'\n * })\n *\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = new URL(opts.url);\n const provider = this.providers.get(parsed.host);\n\n if (!provider) {\n throw new Error(\n `There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`,\n );\n }\n\n return provider.getCredentials(opts);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport {\n GitHubIntegrationConfig,\n readGitHubIntegrationConfigs,\n} from './config';\n\n/**\n * A GitHub based integration.\n *\n * @public\n */\nexport class GitHubIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<GitHubIntegration> = ({ config }) => {\n const configs = readGitHubIntegrationConfigs(\n config.getOptionalConfigArray('integrations.github') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new GitHubIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: GitHubIntegrationConfig) {}\n\n get type(): string {\n return 'github';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): GitHubIntegrationConfig {\n return this.integrationConfig;\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n // GitHub uses blob URLs for files and tree urls for directory listings. But\n // there is a redirect from tree to blob for files, so we can always return\n // tree urls here.\n return replaceGitHubUrlType(defaultScmResolveUrl(options), 'tree');\n }\n\n resolveEditUrl(url: string): string {\n return replaceGitHubUrlType(url, 'edit');\n }\n}\n\n/**\n * Takes a GitHub URL and replaces the type part (blob, tree etc).\n *\n * @param url - The original URL\n * @param type - The desired type, e.g. \"blob\"\n * @public\n */\nexport function replaceGitHubUrlType(\n url: string,\n type: 'blob' | 'tree' | 'edit',\n): string {\n return url.replace(\n /\\/\\/([^/]+)\\/([^/]+)\\/([^/]+)\\/(blob|tree|edit)\\//,\n (_, host, owner, repo) => {\n return `//${host}/${owner}/${repo}/${type}/`;\n },\n );\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\nimport { isValidHost, isValidUrl } from '../helpers';\n\nconst GITLAB_HOST = 'gitlab.com';\nconst GITLAB_API_BASE_URL = 'https://gitlab.com/api/v4';\n\n/**\n * The configuration parameters for a single GitLab integration.\n *\n * @public\n */\nexport type GitLabIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. `gitlab.com`.\n */\n host: string;\n\n /**\n * The base URL of the API of this provider, e.g.\n * `https://gitlab.com/api/v4`, with no trailing slash.\n *\n * May be omitted specifically for public GitLab; then it will be deduced.\n */\n apiBaseUrl: string;\n\n /**\n * The authorization token to use for requests to this provider.\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n\n /**\n * The baseUrl of this provider, e.g. `https://gitlab.com`, which is passed\n * into the GitLab client.\n *\n * If no baseUrl is provided, it will default to `https://${host}`\n */\n baseUrl: string;\n};\n\n/**\n * Reads a single GitLab integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readGitLabIntegrationConfig(\n config: Config,\n): GitLabIntegrationConfig {\n const host = config.getString('host');\n let apiBaseUrl = config.getOptionalString('apiBaseUrl');\n const token = config.getOptionalString('token');\n let baseUrl = config.getOptionalString('baseUrl');\n\n if (apiBaseUrl) {\n apiBaseUrl = trimEnd(apiBaseUrl, '/');\n } else if (host === GITLAB_HOST) {\n apiBaseUrl = GITLAB_API_BASE_URL;\n }\n\n if (baseUrl) {\n baseUrl = trimEnd(baseUrl, '/');\n } else {\n baseUrl = `https://${host}`;\n }\n\n if (host.includes(':')) {\n throw new Error(\n `Invalid GitLab integration config, host '${host}' should just be the host name (e.g. \"github.com\"), not a URL`,\n );\n } else if (!isValidHost(host)) {\n throw new Error(\n `Invalid GitLab integration config, '${host}' is not a valid host`,\n );\n } else if (!apiBaseUrl || !isValidUrl(apiBaseUrl)) {\n throw new Error(\n `Invalid GitLab integration config, '${apiBaseUrl}' is not a valid apiBaseUrl`,\n );\n } else if (!isValidUrl(baseUrl)) {\n throw new Error(\n `Invalid GitLab integration config, '${baseUrl}' is not a valid baseUrl`,\n );\n }\n\n return { host, token, apiBaseUrl, baseUrl };\n}\n\n/**\n * Reads a set of GitLab integration configs, and inserts some defaults for\n * public GitLab if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readGitLabIntegrationConfigs(\n configs: Config[],\n): GitLabIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readGitLabIntegrationConfig);\n\n // As a convenience we always make sure there's at least an unauthenticated\n // reader for public gitlab repos.\n if (!result.some(c => c.host === GITLAB_HOST)) {\n result.push({\n host: GITLAB_HOST,\n apiBaseUrl: GITLAB_API_BASE_URL,\n baseUrl: `https://${GITLAB_HOST}`,\n });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GitLabIntegrationConfig } from './config';\nimport fetch from 'cross-fetch';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://gitlab.example.com/a/b/blob/master/c.yaml\n * to: https://gitlab.example.com/a/b/raw/master/c.yaml\n * -or-\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport async function getGitLabFileFetchUrl(\n url: string,\n config: GitLabIntegrationConfig,\n): Promise<string> {\n // TODO(Rugvip): From the old GitlabReaderProcessor; used\n // the existence of /-/blob/ to switch the logic. Don't know if this\n // makes sense and it might require some more work.\n if (url.includes('/-/blob/')) {\n const projectID = await getProjectId(url, config);\n return buildProjectUrl(url, projectID).toString();\n }\n return buildRawUrl(url).toString();\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getGitLabRequestOptions(config: GitLabIntegrationConfig): {\n headers: Record<string, string>;\n} {\n const { token = '' } = config;\n return {\n headers: {\n 'PRIVATE-TOKEN': token,\n },\n };\n}\n\n// Converts\n// from: https://gitlab.example.com/a/b/blob/master/c.yaml\n// to: https://gitlab.example.com/a/b/raw/master/c.yaml\nexport function buildRawUrl(target: string): URL {\n try {\n const url = new URL(target);\n\n const [empty, userOrOrg, repoName, blobKeyword, ...restOfPath] =\n url.pathname.split('/');\n\n if (\n empty !== '' ||\n userOrOrg === '' ||\n repoName === '' ||\n blobKeyword !== 'blob' ||\n !restOfPath.join('/').match(/\\.(yaml|yml)$/)\n ) {\n throw new Error('Wrong GitLab URL');\n }\n\n // Replace 'blob' with 'raw'\n url.pathname = [empty, userOrOrg, repoName, 'raw', ...restOfPath].join('/');\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n// Converts\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch\nexport function buildProjectUrl(target: string, projectID: Number): URL {\n try {\n const url = new URL(target);\n\n const branchAndFilePath = url.pathname.split('/-/blob/')[1];\n const [branch, ...filePath] = branchAndFilePath.split('/');\n\n url.pathname = [\n '/api/v4/projects',\n projectID,\n 'repository/files',\n encodeURIComponent(decodeURIComponent(filePath.join('/'))),\n 'raw',\n ].join('/');\n url.search = `?ref=${branch}`;\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n// Convert\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: The project ID that corresponds to the URL\nexport async function getProjectId(\n target: string,\n config: GitLabIntegrationConfig,\n): Promise<number> {\n const url = new URL(target);\n\n if (!url.pathname.includes('/-/blob/')) {\n throw new Error('Please provide full path to yaml file from GitLab');\n }\n\n try {\n const repo = url.pathname.split('/-/blob/')[0];\n\n // Convert\n // to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FsubgroupA%2FteamA%2Frepo\n const repoIDLookup = new URL(\n `${url.protocol + url.hostname}/api/v4/projects/${encodeURIComponent(\n repo.replace(/^\\//, ''),\n )}`,\n );\n const response = await fetch(\n repoIDLookup.toString(),\n getGitLabRequestOptions(config),\n );\n const data = await response.json();\n\n if (!response.ok) {\n throw new Error(\n `GitLab Error '${data.error}', ${data.error_description}`,\n );\n }\n\n return Number(data.id);\n } catch (e) {\n throw new Error(`Could not get GitLab project ID for: ${target}, ${e}`);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport {\n GitLabIntegrationConfig,\n readGitLabIntegrationConfigs,\n} from './config';\n\n/**\n * A GitLab based integration.\n *\n * @public\n */\nexport class GitLabIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<GitLabIntegration> = ({ config }) => {\n const configs = readGitLabIntegrationConfigs(\n config.getOptionalConfigArray('integrations.gitlab') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new GitLabIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: GitLabIntegrationConfig) {}\n\n get type(): string {\n return 'gitlab';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): GitLabIntegrationConfig {\n return this.integrationConfig;\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n return defaultScmResolveUrl(options);\n }\n\n resolveEditUrl(url: string): string {\n return replaceUrlType(url, 'edit');\n }\n}\n\nexport function replaceUrlType(\n url: string,\n type: 'blob' | 'tree' | 'edit',\n): string {\n return url.replace(/\\/\\-\\/(blob|tree|edit)\\//, `/-/${type}/`);\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\n\n/**\n * The configuration parameters for a single Google Cloud Storage provider.\n *\n * @public\n */\nexport type GoogleGcsIntegrationConfig = {\n /**\n * Service account email used to authenticate requests.\n */\n clientEmail?: string;\n /**\n * Service account private key used to authenticate requests.\n */\n privateKey?: string;\n};\n\n/**\n * Reads a single Google GCS integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readGoogleGcsIntegrationConfig(\n config: Config,\n): GoogleGcsIntegrationConfig {\n if (!config) {\n return {};\n }\n\n if (!config.has('clientEmail') && !config.has('privateKey')) {\n return {};\n }\n\n const privateKey = config.getString('privateKey').split('\\\\n').join('\\n');\n\n const clientEmail = config.getString('clientEmail');\n return { clientEmail: clientEmail, privateKey: privateKey };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\n\nconst AMAZON_AWS_HOST = 'amazonaws.com';\n\n/**\n * The configuration parameters for a single AWS S3 provider.\n *\n * @public\n */\nexport type AwsS3IntegrationConfig = {\n /**\n * Host, derived from endpoint, and defaults to amazonaws.com\n */\n host: string;\n\n /**\n * (Optional) AWS Endpoint.\n * The endpoint URI to send requests to. The default endpoint is built from the configured region.\n * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property\n *\n * Supports non-AWS providers, e.g. for LocalStack, endpoint may look like http://localhost:4566\n */\n endpoint?: string;\n\n /**\n * (Optional) Whether to use path style URLs when communicating with S3.\n * Defaults to false.\n * This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used.\n */\n s3ForcePathStyle?: boolean;\n\n /**\n * (Optional) User access key id\n */\n accessKeyId?: string;\n\n /**\n * (Optional) User secret access key\n */\n secretAccessKey?: string;\n\n /**\n * (Optional) ARN of role to be assumed\n */\n roleArn?: string;\n};\n\n/**\n * Reads a single Aws S3 integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\n\nexport function readAwsS3IntegrationConfig(\n config: Config,\n): AwsS3IntegrationConfig {\n const endpoint = config.getOptionalString('endpoint');\n const s3ForcePathStyle =\n config.getOptionalBoolean('s3ForcePathStyle') ?? false;\n let host;\n let pathname;\n if (endpoint) {\n try {\n const url = new URL(endpoint);\n host = url.host;\n pathname = url.pathname;\n } catch {\n throw new Error(\n `invalid awsS3 integration config, endpoint '${endpoint}' is not a valid URL`,\n );\n }\n if (pathname !== '/') {\n throw new Error(\n `invalid awsS3 integration config, endpoints cannot contain path, got '${endpoint}'`,\n );\n }\n } else {\n host = AMAZON_AWS_HOST;\n }\n\n const accessKeyId = config.getOptionalString('accessKeyId');\n const secretAccessKey = config.getOptionalString('secretAccessKey');\n const roleArn = config.getOptionalString('roleArn');\n\n return {\n host,\n endpoint,\n s3ForcePathStyle,\n accessKeyId,\n secretAccessKey,\n roleArn,\n };\n}\n\n/**\n * Reads a set of AWS S3 integration configs, and inserts some defaults for\n * public Amazon AWS if not specified.\n *\n * @param configs - The config objects of the integrations\n * @public\n */\nexport function readAwsS3IntegrationConfigs(\n configs: Config[],\n): AwsS3IntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readAwsS3IntegrationConfig);\n\n // If no explicit amazonaws.com integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === AMAZON_AWS_HOST)) {\n result.push({\n host: AMAZON_AWS_HOST,\n });\n }\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport { AwsS3IntegrationConfig, readAwsS3IntegrationConfigs } from './config';\n\n/**\n * Integrates with AWS S3 or compatible solutions.\n *\n * @public\n */\nexport class AwsS3Integration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<AwsS3Integration> = ({ config }) => {\n const configs = readAwsS3IntegrationConfigs(\n config.getOptionalConfigArray('integrations.awsS3') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new AwsS3Integration(c)),\n i => i.config.host,\n );\n };\n\n get type(): string {\n return 'awsS3';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): AwsS3IntegrationConfig {\n return this.integrationConfig;\n }\n\n constructor(private readonly integrationConfig: AwsS3IntegrationConfig) {}\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number | undefined;\n }): string {\n const resolved = defaultScmResolveUrl(options);\n return resolved;\n }\n\n resolveEditUrl(url: string): string {\n // TODO: Implement edit URL for awsS3\n return url;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { AwsS3Integration } from './awsS3/AwsS3Integration';\nimport { AzureIntegration } from './azure/AzureIntegration';\nimport { BitbucketIntegration } from './bitbucket/BitbucketIntegration';\nimport { GitHubIntegration } from './github/GitHubIntegration';\nimport { GitLabIntegration } from './gitlab/GitLabIntegration';\nimport { defaultScmResolveUrl } from './helpers';\nimport { ScmIntegration, ScmIntegrationsGroup } from './types';\nimport { ScmIntegrationRegistry } from './registry';\n\n/**\n * The set of supported integrations.\n *\n * @public\n */\nexport interface IntegrationsByType {\n awsS3: ScmIntegrationsGroup<AwsS3Integration>;\n azure: ScmIntegrationsGroup<AzureIntegration>;\n bitbucket: ScmIntegrationsGroup<BitbucketIntegration>;\n github: ScmIntegrationsGroup<GitHubIntegration>;\n gitlab: ScmIntegrationsGroup<GitLabIntegration>;\n}\n\n/**\n * Exposes the set of supported integrations.\n *\n * @public\n */\nexport class ScmIntegrations implements ScmIntegrationRegistry {\n private readonly byType: IntegrationsByType;\n\n static fromConfig(config: Config): ScmIntegrations {\n return new ScmIntegrations({\n awsS3: AwsS3Integration.factory({ config }),\n azure: AzureIntegration.factory({ config }),\n bitbucket: BitbucketIntegration.factory({ config }),\n github: GitHubIntegration.factory({ config }),\n gitlab: GitLabIntegration.factory({ config }),\n });\n }\n\n constructor(integrationsByType: IntegrationsByType) {\n this.byType = integrationsByType;\n }\n\n get awsS3(): ScmIntegrationsGroup<AwsS3Integration> {\n return this.byType.awsS3;\n }\n\n get azure(): ScmIntegrationsGroup<AzureIntegration> {\n return this.byType.azure;\n }\n\n get bitbucket(): ScmIntegrationsGroup<BitbucketIntegration> {\n return this.byType.bitbucket;\n }\n\n get github(): ScmIntegrationsGroup<GitHubIntegration> {\n return this.byType.github;\n }\n\n get gitlab(): ScmIntegrationsGroup<GitLabIntegration> {\n return this.byType.gitlab;\n }\n\n list(): ScmIntegration[] {\n return Object.values(this.byType).flatMap(\n i => i.list() as ScmIntegration[],\n );\n }\n\n byUrl(url: string | URL): ScmIntegration | undefined {\n return Object.values(this.byType)\n .map(i => i.byUrl(url))\n .find(Boolean);\n }\n\n byHost(host: string): ScmIntegration | undefined {\n return Object.values(this.byType)\n .map(i => i.byHost(host))\n .find(Boolean);\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n const integration = this.byUrl(options.base);\n if (!integration) {\n return defaultScmResolveUrl(options);\n }\n\n return integration.resolveUrl(options);\n }\n\n resolveEditUrl(url: string): string {\n const integration = this.byUrl(url);\n if (!integration) {\n return url;\n }\n\n return integration.resolveEditUrl(url);\n }\n}\n"],"names":["parseGitUrl","trimEnd","fetch","DateTime","Octokit","createAppAuth"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs.js","sources":["../src/helpers.ts","../src/azure/AzureUrl.ts","../src/azure/config.ts","../src/azure/AzureIntegration.ts","../src/azure/core.ts","../src/bitbucket/config.ts","../src/bitbucket/BitbucketIntegration.ts","../src/bitbucket/core.ts","../src/github/config.ts","../src/github/core.ts","../src/github/SingleInstanceGithubCredentialsProvider.ts","../src/github/DefaultGithubCredentialsProvider.ts","../src/github/GitHubIntegration.ts","../src/gitlab/config.ts","../src/gitlab/core.ts","../src/gitlab/GitLabIntegration.ts","../src/googleGcs/config.ts","../src/awsS3/config.ts","../src/awsS3/AwsS3Integration.ts","../src/ScmIntegrations.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { trimEnd } from 'lodash';\nimport { ScmIntegration, ScmIntegrationsGroup } from './types';\n\n/** Checks whether the given argument is a valid URL hostname */\nexport function isValidHost(host: string): boolean {\n const check = new URL('http://example.com');\n check.host = host;\n return check.host === host;\n}\n\n/** Checks whether the given argument is a valid URL */\nexport function isValidUrl(url: string): boolean {\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function basicIntegrations<T extends ScmIntegration>(\n integrations: T[],\n getHost: (integration: T) => string,\n): ScmIntegrationsGroup<T> {\n return {\n list(): T[] {\n return integrations;\n },\n byUrl(url: string | URL): T | undefined {\n try {\n const parsed = typeof url === 'string' ? new URL(url) : url;\n return integrations.find(i => getHost(i) === parsed.host);\n } catch {\n return undefined;\n }\n },\n byHost(host: string): T | undefined {\n return integrations.find(i => getHost(i) === host);\n },\n };\n}\n\n/**\n * Default implementation of {@link ScmIntegration} `resolveUrl`, that only\n * works with URL pathname based providers.\n *\n * @public\n */\nexport function defaultScmResolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n}): string {\n const { url, base, lineNumber } = options;\n\n // If it is a fully qualified URL - then return it verbatim\n try {\n // eslint-disable-next-line no-new\n new URL(url);\n return url;\n } catch {\n // ignore intentionally\n }\n\n let updated: URL;\n\n if (url.startsWith('/')) {\n // If it is an absolute path, move relative to the repo root\n const { filepath } = parseGitUrl(base);\n updated = new URL(base);\n const repoRootPath = trimEnd(\n updated.pathname.substring(0, updated.pathname.length - filepath.length),\n '/',\n );\n updated.pathname = `${repoRootPath}${url}`;\n } else {\n // For relative URLs, just let the default URL constructor handle the\n // resolving. Note that this essentially will treat the last segment of the\n // base as a file - NOT a folder - unless the url ends in a slash.\n updated = new URL(url, base);\n }\n\n updated.search = new URL(base).search;\n if (lineNumber) {\n updated.hash = `L${lineNumber}`;\n }\n return updated.toString();\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nconst VERSION_PREFIX_GIT_BRANCH = 'GB';\n\nexport class AzureUrl {\n /**\n * Parses an azure URL as copied from the browser address bar.\n *\n * Throws an error if the URL is not a valid azure repo URL.\n */\n static fromRepoUrl(repoUrl: string): AzureUrl {\n const url = new URL(repoUrl);\n\n let owner;\n let project;\n let repo;\n\n const parts = url.pathname.split('/').map(part => decodeURIComponent(part));\n if (parts[2] === '_git') {\n owner = parts[1];\n project = repo = parts[3];\n } else if (parts[3] === '_git') {\n owner = parts[1];\n project = parts[2];\n repo = parts[4];\n }\n\n if (!owner || !project || !repo) {\n throw new Error('Azure URL must point to a git repository');\n }\n\n const path = url.searchParams.get('path') ?? undefined;\n\n let ref;\n const version = url.searchParams.get('version');\n if (version) {\n const prefix = version.slice(0, 2);\n if (prefix !== 'GB') {\n throw new Error('Azure URL version must point to a git branch');\n }\n ref = version.slice(2);\n }\n\n return new AzureUrl(url.origin, owner, project, repo, path, ref);\n }\n\n #origin: string;\n #owner: string;\n #project: string;\n #repo: string;\n #path?: string;\n #ref?: string;\n\n private constructor(\n origin: string,\n owner: string,\n project: string,\n repo: string,\n path?: string,\n ref?: string,\n ) {\n this.#origin = origin;\n this.#owner = owner;\n this.#project = project;\n this.#repo = repo;\n this.#path = path;\n this.#ref = ref;\n }\n\n #baseUrl = (...parts: string[]): URL => {\n const url = new URL(this.#origin);\n url.pathname = parts.map(part => encodeURIComponent(part)).join('/');\n return url;\n };\n\n /**\n * Returns a repo URL that can be used to navigate to the resource in azure.\n *\n * Throws an error if the URL is not a valid azure repo URL.\n */\n toRepoUrl(): string {\n let url;\n if (this.#project === this.#repo) {\n url = this.#baseUrl(this.#owner, '_git', this.#repo);\n } else {\n url = this.#baseUrl(this.#owner, this.#project, '_git', this.#repo);\n }\n\n if (this.#path) {\n url.searchParams.set('path', this.#path);\n }\n if (this.#ref) {\n url.searchParams.set('version', VERSION_PREFIX_GIT_BRANCH + this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the file download URL for this azure resource.\n *\n * Throws an error if the URL does not point to a file.\n */\n toFileUrl(): string {\n if (!this.#path) {\n throw new Error(\n 'Azure URL must point to a specific path to be able to download a file',\n );\n }\n\n const url = this.#baseUrl(\n this.#owner,\n this.#project,\n '_apis',\n 'git',\n 'repositories',\n this.#repo,\n 'items',\n );\n url.searchParams.set('api-version', '6.0');\n url.searchParams.set('path', this.#path);\n\n if (this.#ref) {\n url.searchParams.set('version', this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the archive download URL for this azure resource.\n *\n * Throws an error if the URL does not point to a repo.\n */\n toArchiveUrl(): string {\n const url = this.#baseUrl(\n this.#owner,\n this.#project,\n '_apis',\n 'git',\n 'repositories',\n this.#repo,\n 'items',\n );\n url.searchParams.set('recursionLevel', 'full');\n url.searchParams.set('download', 'true');\n url.searchParams.set('api-version', '6.0');\n\n if (this.#path) {\n url.searchParams.set('scopePath', this.#path);\n }\n if (this.#ref) {\n url.searchParams.set('version', this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the API url for fetching commits from a branch for this azure resource.\n *\n * Throws an error if the URL does not point to a commit.\n */\n toCommitsUrl(): string {\n const url = this.#baseUrl(\n this.#owner,\n this.#project,\n '_apis',\n 'git',\n 'repositories',\n this.#repo,\n 'commits',\n );\n url.searchParams.set('api-version', '6.0');\n\n if (this.#ref) {\n url.searchParams.set('searchCriteria.itemVersion.version', this.#ref);\n }\n\n return url.toString();\n }\n\n /**\n * Returns the name of the owner, a user or an organization.\n */\n getOwner(): string {\n return this.#owner;\n }\n\n /**\n * Returns the name of the project.\n */\n getProject(): string {\n return this.#project;\n }\n\n /**\n * Returns the name of the repo.\n */\n getRepo(): string {\n return this.#repo;\n }\n\n /**\n * Returns the file path within the repo if the URL contains one.\n */\n getPath(): string | undefined {\n return this.#path;\n }\n\n /**\n * Returns the git ref in the repo if the URL contains one.\n */\n getRef(): string | undefined {\n return this.#ref;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { isValidHost } from '../helpers';\n\nconst AZURE_HOST = 'dev.azure.com';\n\n/**\n * The configuration parameters for a single Azure provider.\n *\n * @public\n */\nexport type AzureIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. \"dev.azure.com\".\n *\n * Currently only \"dev.azure.com\" is supported.\n */\n host: string;\n\n /**\n * The authorization token to use for requests.\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n};\n\n/**\n * Reads a single Azure integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readAzureIntegrationConfig(\n config: Config,\n): AzureIntegrationConfig {\n const host = config.getOptionalString('host') ?? AZURE_HOST;\n const token = config.getOptionalString('token');\n\n if (!isValidHost(host)) {\n throw new Error(\n `Invalid Azure integration config, '${host}' is not a valid host`,\n );\n }\n\n return { host, token };\n}\n\n/**\n * Reads a set of Azure integration configs, and inserts some defaults for\n * public Azure if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readAzureIntegrationConfigs(\n configs: Config[],\n): AzureIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readAzureIntegrationConfig);\n\n // If no explicit dev.azure.com integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === AZURE_HOST)) {\n result.push({ host: AZURE_HOST });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, isValidUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport { AzureUrl } from './AzureUrl';\nimport { AzureIntegrationConfig, readAzureIntegrationConfigs } from './config';\n\n/**\n * Microsoft Azure based integration.\n *\n * @public\n */\nexport class AzureIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<AzureIntegration> = ({ config }) => {\n const configs = readAzureIntegrationConfigs(\n config.getOptionalConfigArray('integrations.azure') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new AzureIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: AzureIntegrationConfig) {}\n\n get type(): string {\n return 'azure';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): AzureIntegrationConfig {\n return this.integrationConfig;\n }\n\n /*\n * Azure repo URLs on the form with a `path` query param are treated specially.\n *\n * Example base URL: https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml\n */\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n const { url, base } = options;\n\n // If we can parse the url, it is absolute - then return it verbatim\n if (isValidUrl(url)) {\n return url;\n }\n\n try {\n const azureUrl = AzureUrl.fromRepoUrl(base);\n const newUrl = new URL(base);\n\n // We lean on the URL path resolution logic to resolve the path param\n const mockBaseUrl = new URL(`https://a.com${azureUrl.getPath() ?? ''}`);\n const updatedPath = new URL(url, mockBaseUrl).pathname;\n newUrl.searchParams.set('path', updatedPath);\n\n if (options.lineNumber) {\n newUrl.searchParams.set('line', String(options.lineNumber));\n newUrl.searchParams.set('lineEnd', String(options.lineNumber + 1));\n newUrl.searchParams.set('lineStartColumn', '1');\n newUrl.searchParams.set('lineEndColumn', '1');\n }\n\n return newUrl.toString();\n } catch {\n // If not an actual file path within a repo, treat the URL as raw\n return new URL(url, base).toString();\n }\n }\n\n resolveEditUrl(url: string): string {\n // TODO: Implement edit URL for Azure, fallback to view url as I don't know\n // how azure works.\n return url;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { AzureUrl } from './AzureUrl';\nimport { AzureIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * - from: `https://dev.azure.com/{organization}/{project}/_git/reponame?path={path}&version=GB{commitOrBranch}&_a=contents`\n * - to: `https://dev.azure.com/{organization}/{project}/_apis/git/repositories/reponame/items?path={path}&version={commitOrBranch}`\n *\n * @param url - A URL pointing to a file\n * @public\n */\nexport function getAzureFileFetchUrl(url: string): string {\n return AzureUrl.fromRepoUrl(url).toFileUrl();\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @public\n */\nexport function getAzureDownloadUrl(url: string): string {\n return AzureUrl.fromRepoUrl(url).toArchiveUrl();\n}\n\n/**\n * Given a URL, return the API URL to fetch commits on the branch.\n *\n * @param url - A URL pointing to a repository or a sub-path\n * @public\n */\nexport function getAzureCommitsUrl(url: string): string {\n return AzureUrl.fromRepoUrl(url).toCommitsUrl();\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getAzureRequestOptions(\n config: AzureIntegrationConfig,\n additionalHeaders?: Record<string, string>,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = additionalHeaders\n ? { ...additionalHeaders }\n : {};\n\n if (config.token) {\n const buffer = Buffer.from(`:${config.token}`, 'utf8');\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return { headers };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\nimport { isValidHost } from '../helpers';\n\nconst BITBUCKET_HOST = 'bitbucket.org';\nconst BITBUCKET_API_BASE_URL = 'https://api.bitbucket.org/2.0';\n\n/**\n * The configuration parameters for a single Bitbucket API provider.\n *\n * @public\n */\nexport type BitbucketIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. \"bitbucket.org\"\n */\n host: string;\n\n /**\n * The base URL of the API of this provider, e.g. \"https://api.bitbucket.org/2.0\",\n * with no trailing slash.\n *\n * Values omitted at the optional property at the app-config will be deduced\n * from the \"host\" value.\n */\n apiBaseUrl: string;\n\n /**\n * The authorization token to use for requests to a Bitbucket Server provider.\n *\n * See https://confluence.atlassian.com/bitbucketserver/personal-access-tokens-939515499.html\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n\n /**\n * The username to use for requests to Bitbucket Cloud (bitbucket.org).\n */\n username?: string;\n\n /**\n * Authentication with Bitbucket Cloud (bitbucket.org) is done using app passwords.\n *\n * See https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/\n */\n appPassword?: string;\n};\n\n/**\n * Reads a single Bitbucket integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readBitbucketIntegrationConfig(\n config: Config,\n): BitbucketIntegrationConfig {\n const host = config.getOptionalString('host') ?? BITBUCKET_HOST;\n let apiBaseUrl = config.getOptionalString('apiBaseUrl');\n const token = config.getOptionalString('token');\n const username = config.getOptionalString('username');\n const appPassword = config.getOptionalString('appPassword');\n\n if (!isValidHost(host)) {\n throw new Error(\n `Invalid Bitbucket integration config, '${host}' is not a valid host`,\n );\n }\n\n if (apiBaseUrl) {\n apiBaseUrl = trimEnd(apiBaseUrl, '/');\n } else if (host === BITBUCKET_HOST) {\n apiBaseUrl = BITBUCKET_API_BASE_URL;\n } else {\n apiBaseUrl = `https://${host}/rest/api/1.0`;\n }\n\n return {\n host,\n apiBaseUrl,\n token,\n username,\n appPassword,\n };\n}\n\n/**\n * Reads a set of Bitbucket integration configs, and inserts some defaults for\n * public Bitbucket if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readBitbucketIntegrationConfigs(\n configs: Config[],\n): BitbucketIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readBitbucketIntegrationConfig);\n\n // If no explicit bitbucket.org integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === BITBUCKET_HOST)) {\n result.push({\n host: BITBUCKET_HOST,\n apiBaseUrl: BITBUCKET_API_BASE_URL,\n });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport {\n BitbucketIntegrationConfig,\n readBitbucketIntegrationConfigs,\n} from './config';\n\n/**\n * A Bitbucket based integration.\n *\n * @public\n */\nexport class BitbucketIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<BitbucketIntegration> = ({\n config,\n }) => {\n const configs = readBitbucketIntegrationConfigs(\n config.getOptionalConfigArray('integrations.bitbucket') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new BitbucketIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: BitbucketIntegrationConfig) {}\n\n get type(): string {\n return 'bitbucket';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): BitbucketIntegrationConfig {\n return this.integrationConfig;\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n const resolved = defaultScmResolveUrl(options);\n if (!options.lineNumber) {\n return resolved;\n }\n\n const url = new URL(resolved);\n\n if (this.integrationConfig.host === 'bitbucket.org') {\n // Bitbucket Cloud uses the syntax #lines-{start}[:{end}][,...]\n url.hash = `lines-${options.lineNumber}`;\n } else {\n // Bitbucket Server uses the syntax #{start}[-{end}][,...]\n url.hash = `${options.lineNumber}`;\n }\n\n return url.toString();\n }\n\n resolveEditUrl(url: string): string {\n const urlData = parseGitUrl(url);\n const editUrl = new URL(url);\n\n editUrl.searchParams.set('mode', 'edit');\n // TODO: Not sure what spa=0 does, at least bitbucket.org doesn't support it\n // but this is taken over from the initial implementation.\n editUrl.searchParams.set('spa', '0');\n editUrl.searchParams.set('at', urlData.ref);\n return editUrl.toString();\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport fetch from 'cross-fetch';\nimport parseGitUrl from 'git-url-parse';\nimport { BitbucketIntegrationConfig } from './config';\n\n/**\n * Given a URL pointing to a path on a provider, returns the default branch.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketDefaultBranch(\n url: string,\n config: BitbucketIntegrationConfig,\n): Promise<string> {\n const { name: repoName, owner: project, resource } = parseGitUrl(url);\n\n const isHosted = resource === 'bitbucket.org';\n // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp184\n let branchUrl = isHosted\n ? `${config.apiBaseUrl}/repositories/${project}/${repoName}`\n : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;\n\n let response = await fetch(branchUrl, getBitbucketRequestOptions(config));\n\n if (response.status === 404 && !isHosted) {\n // First try the new format, and then if it gets specifically a 404 it should try the old format\n // (to support old Atlassian Bitbucket v5.11.1 format )\n branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;\n response = await fetch(branchUrl, getBitbucketRequestOptions(config));\n }\n\n if (!response.ok) {\n const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;\n throw new Error(message);\n }\n\n let defaultBranch;\n if (isHosted) {\n const repoInfo = await response.json();\n defaultBranch = repoInfo.mainbranch.name;\n } else {\n const { displayId } = await response.json();\n defaultBranch = displayId;\n }\n if (!defaultBranch) {\n throw new Error(\n `Failed to read default branch from ${branchUrl}. ` +\n `Response ${response.status} ${response.json()}`,\n );\n }\n return defaultBranch;\n}\n\n/**\n * Given a URL pointing to a path on a provider, returns a URL that is suitable\n * for downloading the subtree.\n *\n * @param url - A URL pointing to a path\n * @param config - The relevant provider config\n * @public\n */\nexport async function getBitbucketDownloadUrl(\n url: string,\n config: BitbucketIntegrationConfig,\n): Promise<string> {\n const {\n name: repoName,\n owner: project,\n ref,\n protocol,\n resource,\n filepath,\n } = parseGitUrl(url);\n\n const isHosted = resource === 'bitbucket.org';\n\n let branch = ref;\n if (!branch) {\n branch = await getBitbucketDefaultBranch(url, config);\n }\n // path will limit the downloaded content\n // /docs will only download the docs folder and everything below it\n // /docs/index.md will download the docs folder and everything below it\n const path = filepath ? `&path=${encodeURIComponent(filepath)}` : '';\n const archiveUrl = isHosted\n ? `${protocol}://${resource}/${project}/${repoName}/get/${branch}.tar.gz`\n : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/archive?format=tgz&at=${branch}&prefix=${project}-${repoName}${path}`;\n\n return archiveUrl;\n}\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://bitbucket.org/orgname/reponame/src/master/file.yaml\n * to: https://api.bitbucket.org/2.0/repositories/orgname/reponame/src/master/file.yaml\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketFileFetchUrl(\n url: string,\n config: BitbucketIntegrationConfig,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url);\n if (\n !owner ||\n !name ||\n (filepathtype !== 'browse' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'src')\n ) {\n throw new Error('Invalid Bitbucket URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n\n if (config.host === 'bitbucket.org') {\n if (!ref) {\n throw new Error('Invalid Bitbucket URL or file path');\n }\n return `${config.apiBaseUrl}/repositories/${owner}/${name}/src/${ref}/${pathWithoutSlash}`;\n }\n return `${config.apiBaseUrl}/projects/${owner}/repos/${name}/raw/${pathWithoutSlash}?at=${ref}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getBitbucketRequestOptions(\n config: BitbucketIntegrationConfig,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (config.token) {\n headers.Authorization = `Bearer ${config.token}`;\n } else if (config.username && config.appPassword) {\n const buffer = Buffer.from(\n `${config.username}:${config.appPassword}`,\n 'utf8',\n );\n headers.Authorization = `Basic ${buffer.toString('base64')}`;\n }\n\n return {\n headers,\n };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\nimport { isValidHost } from '../helpers';\n\nconst GITHUB_HOST = 'github.com';\nconst GITHUB_API_BASE_URL = 'https://api.github.com';\nconst GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com';\n\n/**\n * The configuration parameters for a single GitHub integration.\n *\n * @public\n */\nexport type GitHubIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. \"github.com\"\n */\n host: string;\n\n /**\n * The base URL of the API of this provider, e.g. \"https://api.github.com\",\n * with no trailing slash.\n *\n * May be omitted specifically for GitHub; then it will be deduced.\n *\n * The API will always be preferred if both its base URL and a token are\n * present.\n */\n apiBaseUrl?: string;\n\n /**\n * The base URL of the raw fetch endpoint of this provider, e.g.\n * \"https://raw.githubusercontent.com\", with no trailing slash.\n *\n * May be omitted specifically for GitHub; then it will be deduced.\n *\n * The API will always be preferred if both its base URL and a token are\n * present.\n */\n rawBaseUrl?: string;\n\n /**\n * The authorization token to use for requests to this provider.\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n\n /**\n * The GitHub Apps configuration to use for requests to this provider.\n *\n * If no apps are specified, token or anonymous is used.\n */\n apps?: GithubAppConfig[];\n};\n\n/**\n * The configuration parameters for authenticating a GitHub Application.\n *\n * @remarks\n *\n * A GitHub Apps configuration can be generated using the `backstage-cli create-github-app` command.\n *\n * @public\n */\nexport type GithubAppConfig = {\n /**\n * Unique app identifier, found at https://github.com/organizations/$org/settings/apps/$AppName\n */\n appId: number;\n /**\n * The private key is used by the GitHub App integration to authenticate the app.\n * A private key can be generated from the app at https://github.com/organizations/$org/settings/apps/$AppName\n */\n privateKey: string;\n /**\n * Webhook secret can be configured at https://github.com/organizations/$org/settings/apps/$AppName\n */\n webhookSecret: string;\n /**\n * Found at https://github.com/organizations/$org/settings/apps/$AppName\n */\n clientId: string;\n /**\n * Client secrets can be generated at https://github.com/organizations/$org/settings/apps/$AppName\n */\n clientSecret: string;\n /**\n * List of installation owners allowed to be used by this GitHub app. The GitHub UI does not provide a way to list the installations.\n * However you can list the installations with the GitHub API. You can find the list of installations here:\n * https://api.github.com/app/installations\n * The relevant documentation for this is here.\n * https://docs.github.com/en/rest/reference/apps#list-installations-for-the-authenticated-app--code-samples\n */\n allowedInstallationOwners?: string[];\n};\n\n/**\n * Reads a single GitHub integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readGitHubIntegrationConfig(\n config: Config,\n): GitHubIntegrationConfig {\n const host = config.getOptionalString('host') ?? GITHUB_HOST;\n let apiBaseUrl = config.getOptionalString('apiBaseUrl');\n let rawBaseUrl = config.getOptionalString('rawBaseUrl');\n const token = config.getOptionalString('token');\n const apps = config.getOptionalConfigArray('apps')?.map(c => ({\n appId: c.getNumber('appId'),\n clientId: c.getString('clientId'),\n clientSecret: c.getString('clientSecret'),\n webhookSecret: c.getString('webhookSecret'),\n privateKey: c.getString('privateKey'),\n allowedInstallationOwners: c.getOptionalStringArray(\n 'allowedInstallationOwners',\n ),\n }));\n\n if (!isValidHost(host)) {\n throw new Error(\n `Invalid GitHub integration config, '${host}' is not a valid host`,\n );\n }\n\n if (apiBaseUrl) {\n apiBaseUrl = trimEnd(apiBaseUrl, '/');\n } else if (host === GITHUB_HOST) {\n apiBaseUrl = GITHUB_API_BASE_URL;\n }\n\n if (rawBaseUrl) {\n rawBaseUrl = trimEnd(rawBaseUrl, '/');\n } else if (host === GITHUB_HOST) {\n rawBaseUrl = GITHUB_RAW_BASE_URL;\n }\n\n return { host, apiBaseUrl, rawBaseUrl, token, apps };\n}\n\n/**\n * Reads a set of GitHub integration configs, and inserts some defaults for\n * public GitHub if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readGitHubIntegrationConfigs(\n configs: Config[],\n): GitHubIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readGitHubIntegrationConfig);\n\n // If no explicit github.com integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === GITHUB_HOST)) {\n result.push({\n host: GITHUB_HOST,\n apiBaseUrl: GITHUB_API_BASE_URL,\n rawBaseUrl: GITHUB_RAW_BASE_URL,\n });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GitHubIntegrationConfig } from './config';\nimport { GithubCredentials } from './types';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://github.com/a/b/blob/branchname/path/to/c.yaml\n * to: https://api.github.com/repos/a/b/contents/path/to/c.yaml?ref=branchname\n * or: https://raw.githubusercontent.com/a/b/branchname/c.yaml\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport function getGitHubFileFetchUrl(\n url: string,\n config: GitHubIntegrationConfig,\n credentials: GithubCredentials,\n): string {\n try {\n const { owner, name, ref, filepathtype, filepath } = parseGitUrl(url);\n if (\n !owner ||\n !name ||\n !ref ||\n // GitHub is automatically redirecting tree urls to blob urls so it's\n // fine to pass a tree url.\n (filepathtype !== 'blob' &&\n filepathtype !== 'raw' &&\n filepathtype !== 'tree')\n ) {\n throw new Error('Invalid GitHub URL or file path');\n }\n\n const pathWithoutSlash = filepath.replace(/^\\//, '');\n if (chooseEndpoint(config, credentials) === 'api') {\n return `${config.apiBaseUrl}/repos/${owner}/${name}/contents/${pathWithoutSlash}?ref=${ref}`;\n }\n return `${config.rawBaseUrl}/${owner}/${name}/${ref}/${pathWithoutSlash}`;\n } catch (e) {\n throw new Error(`Incorrect URL: ${url}, ${e}`);\n }\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @deprecated This function is no longer used internally\n * @param config - The relevant provider config\n * @public\n */\nexport function getGitHubRequestOptions(\n config: GitHubIntegrationConfig,\n credentials: GithubCredentials,\n): { headers: Record<string, string> } {\n const headers: Record<string, string> = {};\n\n if (chooseEndpoint(config, credentials) === 'api') {\n headers.Accept = 'application/vnd.github.v3.raw';\n }\n\n if (credentials.token) {\n headers.Authorization = `token ${credentials.token}`;\n }\n\n return { headers };\n}\n\nexport function chooseEndpoint(\n config: GitHubIntegrationConfig,\n credentials: GithubCredentials,\n): 'api' | 'raw' {\n if (config.apiBaseUrl && (credentials.token || !config.rawBaseUrl)) {\n return 'api';\n }\n return 'raw';\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport parseGitUrl from 'git-url-parse';\nimport { GithubAppConfig, GitHubIntegrationConfig } from './config';\nimport { createAppAuth } from '@octokit/auth-app';\nimport { Octokit, RestEndpointMethodTypes } from '@octokit/rest';\nimport { DateTime } from 'luxon';\nimport {\n GithubCredentials,\n GithubCredentialsProvider,\n GithubCredentialType,\n} from './types';\n\ntype InstallationData = {\n installationId: number;\n suspended: boolean;\n};\n\nclass Cache {\n private readonly tokenCache = new Map<\n string,\n { token: string; expiresAt: DateTime }\n >();\n\n async getOrCreateToken(\n key: string,\n supplier: () => Promise<{ token: string; expiresAt: DateTime }>,\n ): Promise<{ accessToken: string }> {\n const item = this.tokenCache.get(key);\n if (item && this.isNotExpired(item.expiresAt)) {\n return { accessToken: item.token };\n }\n\n const result = await supplier();\n this.tokenCache.set(key, result);\n return { accessToken: result.token };\n }\n\n // consider timestamps older than 50 minutes to be expired.\n private isNotExpired = (date: DateTime) =>\n date.diff(DateTime.local(), 'minutes').minutes > 50;\n}\n\n/**\n * This accept header is required when calling App APIs in GitHub Enterprise.\n * It has no effect on calls to github.com and can probably be removed entirely\n * once GitHub Apps is out of preview.\n */\nconst HEADERS = {\n Accept: 'application/vnd.github.machine-man-preview+json',\n};\n\n/**\n * GithubAppManager issues and caches tokens for a specific GitHub App.\n */\nclass GithubAppManager {\n private readonly appClient: Octokit;\n private readonly baseUrl?: string;\n private readonly baseAuthConfig: { appId: number; privateKey: string };\n private readonly cache = new Cache();\n private readonly allowedInstallationOwners: string[] | undefined; // undefined allows all installations\n\n constructor(config: GithubAppConfig, baseUrl?: string) {\n this.allowedInstallationOwners = config.allowedInstallationOwners;\n this.baseUrl = baseUrl;\n this.baseAuthConfig = {\n appId: config.appId,\n privateKey: config.privateKey.replace(/\\\\n/gm, '\\n'),\n };\n this.appClient = new Octokit({\n baseUrl,\n headers: HEADERS,\n authStrategy: createAppAuth,\n auth: this.baseAuthConfig,\n });\n }\n\n async getInstallationCredentials(\n owner: string,\n repo?: string,\n ): Promise<{ accessToken: string | undefined }> {\n const { installationId, suspended } = await this.getInstallationData(owner);\n if (this.allowedInstallationOwners) {\n if (!this.allowedInstallationOwners?.includes(owner)) {\n return { accessToken: undefined }; // An empty token allows anonymous access to public repos\n }\n }\n if (suspended) {\n throw new Error(`The GitHub application for ${owner} is suspended`);\n }\n\n const cacheKey = repo ? `${owner}/${repo}` : owner;\n\n // Go and grab an access token for the app scoped to a repository if provided, if not use the organisation installation.\n return this.cache.getOrCreateToken(cacheKey, async () => {\n const result = await this.appClient.apps.createInstallationAccessToken({\n installation_id: installationId,\n headers: HEADERS,\n });\n if (repo && result.data.repository_selection === 'selected') {\n const installationClient = new Octokit({\n baseUrl: this.baseUrl,\n auth: result.data.token,\n });\n const repos = await installationClient.paginate(\n installationClient.apps.listReposAccessibleToInstallation,\n );\n const hasRepo = repos.some(repository => {\n return repository.name === repo;\n });\n if (!hasRepo) {\n throw new Error(\n `The Backstage GitHub application used in the ${owner} organization does not have access to a repository with the name ${repo}`,\n );\n }\n }\n return {\n token: result.data.token,\n expiresAt: DateTime.fromISO(result.data.expires_at),\n };\n });\n }\n\n getInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n return this.appClient.paginate(this.appClient.apps.listInstallations);\n }\n\n private async getInstallationData(owner: string): Promise<InstallationData> {\n const allInstallations = await this.getInstallations();\n const installation = allInstallations.find(\n inst =>\n inst.account?.login?.toLocaleLowerCase('en-US') ===\n owner.toLocaleLowerCase('en-US'),\n );\n if (installation) {\n return {\n installationId: installation.id,\n suspended: Boolean(installation.suspended_by),\n };\n }\n const notFoundError = new Error(\n `No app installation found for ${owner} in ${this.baseAuthConfig.appId}`,\n );\n notFoundError.name = 'NotFoundError';\n throw notFoundError;\n }\n}\n\n/**\n * Corresponds to a Github installation which internally could hold several GitHub Apps.\n *\n * @public\n */\nexport class GithubAppCredentialsMux {\n private readonly apps: GithubAppManager[];\n\n constructor(config: GitHubIntegrationConfig) {\n this.apps =\n config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? [];\n }\n\n async getAllInstallations(): Promise<\n RestEndpointMethodTypes['apps']['listInstallations']['response']['data']\n > {\n if (!this.apps.length) {\n return [];\n }\n\n const installs = await Promise.all(\n this.apps.map(app => app.getInstallations()),\n );\n\n return installs.flat();\n }\n\n async getAppToken(owner: string, repo?: string): Promise<string | undefined> {\n if (this.apps.length === 0) {\n return undefined;\n }\n\n const results = await Promise.all(\n this.apps.map(app =>\n app.getInstallationCredentials(owner, repo).then(\n credentials => ({ credentials, error: undefined }),\n error => ({ credentials: undefined, error }),\n ),\n ),\n );\n\n const result = results.find(resultItem => resultItem.credentials);\n if (result) {\n return result.credentials!.accessToken;\n }\n\n const errors = results.map(r => r.error);\n const notNotFoundError = errors.find(err => err.name !== 'NotFoundError');\n if (notNotFoundError) {\n throw notNotFoundError;\n }\n\n return undefined;\n }\n}\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class SingleInstanceGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static create: (\n config: GitHubIntegrationConfig,\n ) => GithubCredentialsProvider = config => {\n return new SingleInstanceGithubCredentialsProvider(\n new GithubAppCredentialsMux(config),\n config.token,\n );\n };\n\n private constructor(\n private readonly githubAppCredentialsMux: GithubAppCredentialsMux,\n private readonly token?: string,\n ) {}\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'github.com/backstage/foobar'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = parseGitUrl(opts.url);\n\n const owner = parsed.owner || parsed.name;\n const repo = parsed.owner ? parsed.name : undefined;\n\n let type: GithubCredentialType = 'app';\n let token = await this.githubAppCredentialsMux.getAppToken(owner, repo);\n if (!token) {\n type = 'token';\n token = this.token;\n }\n\n return {\n headers: token ? { Authorization: `Bearer ${token}` } : undefined,\n token,\n type,\n };\n }\n}\n","/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GithubCredentials, GithubCredentialsProvider } from './types';\nimport { ScmIntegrationRegistry } from '../registry';\nimport { SingleInstanceGithubCredentialsProvider } from './SingleInstanceGithubCredentialsProvider';\n\n/**\n * Handles the creation and caching of credentials for GitHub integrations.\n *\n * @public\n * @remarks\n *\n * TODO: Possibly move this to a backend only package so that it's not used in the frontend by mistake\n */\nexport class DefaultGithubCredentialsProvider\n implements GithubCredentialsProvider\n{\n static fromIntegrations(integrations: ScmIntegrationRegistry) {\n const credentialsProviders: Map<string, GithubCredentialsProvider> =\n new Map<string, GithubCredentialsProvider>();\n\n integrations.github.list().forEach(integration => {\n const credentialsProvider =\n SingleInstanceGithubCredentialsProvider.create(integration.config);\n credentialsProviders.set(integration.config.host, credentialsProvider);\n });\n return new DefaultGithubCredentialsProvider(credentialsProviders);\n }\n\n private constructor(\n private readonly providers: Map<string, GithubCredentialsProvider>,\n ) {}\n\n /**\n * Returns {@link GithubCredentials} for a given URL.\n *\n * @remarks\n *\n * Consecutive calls to this method with the same URL will return cached\n * credentials.\n *\n * The shortest lifetime for a token returned is 10 minutes.\n *\n * @example\n * ```ts\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage/foobar'\n * })\n *\n * const { token, headers } = await getCredentials({\n * url: 'https://github.com/backstage'\n * })\n * ```\n *\n * @param opts - The organization or repository URL\n * @returns A promise of {@link GithubCredentials}.\n */\n async getCredentials(opts: { url: string }): Promise<GithubCredentials> {\n const parsed = new URL(opts.url);\n const provider = this.providers.get(parsed.host);\n\n if (!provider) {\n throw new Error(\n `There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`,\n );\n }\n\n return provider.getCredentials(opts);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport {\n GitHubIntegrationConfig,\n readGitHubIntegrationConfigs,\n} from './config';\n\n/**\n * A GitHub based integration.\n *\n * @public\n */\nexport class GitHubIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<GitHubIntegration> = ({ config }) => {\n const configs = readGitHubIntegrationConfigs(\n config.getOptionalConfigArray('integrations.github') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new GitHubIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: GitHubIntegrationConfig) {}\n\n get type(): string {\n return 'github';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): GitHubIntegrationConfig {\n return this.integrationConfig;\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n // GitHub uses blob URLs for files and tree urls for directory listings. But\n // there is a redirect from tree to blob for files, so we can always return\n // tree urls here.\n return replaceGitHubUrlType(defaultScmResolveUrl(options), 'tree');\n }\n\n resolveEditUrl(url: string): string {\n return replaceGitHubUrlType(url, 'edit');\n }\n}\n\n/**\n * Takes a GitHub URL and replaces the type part (blob, tree etc).\n *\n * @param url - The original URL\n * @param type - The desired type, e.g. \"blob\"\n * @public\n */\nexport function replaceGitHubUrlType(\n url: string,\n type: 'blob' | 'tree' | 'edit',\n): string {\n return url.replace(\n /\\/\\/([^/]+)\\/([^/]+)\\/([^/]+)\\/(blob|tree|edit)\\//,\n (_, host, owner, repo) => {\n return `//${host}/${owner}/${repo}/${type}/`;\n },\n );\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { trimEnd } from 'lodash';\nimport { isValidHost, isValidUrl } from '../helpers';\n\nconst GITLAB_HOST = 'gitlab.com';\nconst GITLAB_API_BASE_URL = 'https://gitlab.com/api/v4';\n\n/**\n * The configuration parameters for a single GitLab integration.\n *\n * @public\n */\nexport type GitLabIntegrationConfig = {\n /**\n * The host of the target that this matches on, e.g. `gitlab.com`.\n */\n host: string;\n\n /**\n * The base URL of the API of this provider, e.g.\n * `https://gitlab.com/api/v4`, with no trailing slash.\n *\n * May be omitted specifically for public GitLab; then it will be deduced.\n */\n apiBaseUrl: string;\n\n /**\n * The authorization token to use for requests to this provider.\n *\n * If no token is specified, anonymous access is used.\n */\n token?: string;\n\n /**\n * The baseUrl of this provider, e.g. `https://gitlab.com`, which is passed\n * into the GitLab client.\n *\n * If no baseUrl is provided, it will default to `https://${host}`\n */\n baseUrl: string;\n};\n\n/**\n * Reads a single GitLab integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readGitLabIntegrationConfig(\n config: Config,\n): GitLabIntegrationConfig {\n const host = config.getString('host');\n let apiBaseUrl = config.getOptionalString('apiBaseUrl');\n const token = config.getOptionalString('token');\n let baseUrl = config.getOptionalString('baseUrl');\n\n if (apiBaseUrl) {\n apiBaseUrl = trimEnd(apiBaseUrl, '/');\n } else if (host === GITLAB_HOST) {\n apiBaseUrl = GITLAB_API_BASE_URL;\n }\n\n if (baseUrl) {\n baseUrl = trimEnd(baseUrl, '/');\n } else {\n baseUrl = `https://${host}`;\n }\n\n if (!isValidHost(host)) {\n throw new Error(\n `Invalid GitLab integration config, '${host}' is not a valid host`,\n );\n } else if (!apiBaseUrl || !isValidUrl(apiBaseUrl)) {\n throw new Error(\n `Invalid GitLab integration config, '${apiBaseUrl}' is not a valid apiBaseUrl`,\n );\n } else if (!isValidUrl(baseUrl)) {\n throw new Error(\n `Invalid GitLab integration config, '${baseUrl}' is not a valid baseUrl`,\n );\n }\n\n return { host, token, apiBaseUrl, baseUrl };\n}\n\n/**\n * Reads a set of GitLab integration configs, and inserts some defaults for\n * public GitLab if not specified.\n *\n * @param configs - All of the integration config objects\n * @public\n */\nexport function readGitLabIntegrationConfigs(\n configs: Config[],\n): GitLabIntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readGitLabIntegrationConfig);\n\n // As a convenience we always make sure there's at least an unauthenticated\n // reader for public gitlab repos.\n if (!result.some(c => c.host === GITLAB_HOST)) {\n result.push({\n host: GITLAB_HOST,\n apiBaseUrl: GITLAB_API_BASE_URL,\n baseUrl: `https://${GITLAB_HOST}`,\n });\n }\n\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { GitLabIntegrationConfig } from './config';\nimport fetch from 'cross-fetch';\n\n/**\n * Given a URL pointing to a file on a provider, returns a URL that is suitable\n * for fetching the contents of the data.\n *\n * @remarks\n *\n * Converts\n * from: https://gitlab.example.com/a/b/blob/master/c.yaml\n * to: https://gitlab.example.com/a/b/raw/master/c.yaml\n * -or-\n * from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n * to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch\n *\n * @param url - A URL pointing to a file\n * @param config - The relevant provider config\n * @public\n */\nexport async function getGitLabFileFetchUrl(\n url: string,\n config: GitLabIntegrationConfig,\n): Promise<string> {\n // TODO(Rugvip): From the old GitlabReaderProcessor; used\n // the existence of /-/blob/ to switch the logic. Don't know if this\n // makes sense and it might require some more work.\n if (url.includes('/-/blob/')) {\n const projectID = await getProjectId(url, config);\n return buildProjectUrl(url, projectID).toString();\n }\n return buildRawUrl(url).toString();\n}\n\n/**\n * Gets the request options necessary to make requests to a given provider.\n *\n * @param config - The relevant provider config\n * @public\n */\nexport function getGitLabRequestOptions(config: GitLabIntegrationConfig): {\n headers: Record<string, string>;\n} {\n const { token = '' } = config;\n return {\n headers: {\n 'PRIVATE-TOKEN': token,\n },\n };\n}\n\n// Converts\n// from: https://gitlab.example.com/a/b/blob/master/c.yaml\n// to: https://gitlab.example.com/a/b/raw/master/c.yaml\nexport function buildRawUrl(target: string): URL {\n try {\n const url = new URL(target);\n\n const [empty, userOrOrg, repoName, blobKeyword, ...restOfPath] =\n url.pathname.split('/');\n\n if (\n empty !== '' ||\n userOrOrg === '' ||\n repoName === '' ||\n blobKeyword !== 'blob' ||\n !restOfPath.join('/').match(/\\.(yaml|yml)$/)\n ) {\n throw new Error('Wrong GitLab URL');\n }\n\n // Replace 'blob' with 'raw'\n url.pathname = [empty, userOrOrg, repoName, 'raw', ...restOfPath].join('/');\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n// Converts\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: https://gitlab.com/api/v4/projects/projectId/repository/files/filepath?ref=branch\nexport function buildProjectUrl(target: string, projectID: Number): URL {\n try {\n const url = new URL(target);\n\n const branchAndFilePath = url.pathname.split('/-/blob/')[1];\n const [branch, ...filePath] = branchAndFilePath.split('/');\n\n url.pathname = [\n '/api/v4/projects',\n projectID,\n 'repository/files',\n encodeURIComponent(decodeURIComponent(filePath.join('/'))),\n 'raw',\n ].join('/');\n url.search = `?ref=${branch}`;\n\n return url;\n } catch (e) {\n throw new Error(`Incorrect url: ${target}, ${e}`);\n }\n}\n\n// Convert\n// from: https://gitlab.com/groupA/teams/teamA/subgroupA/repoA/-/blob/branch/filepath\n// to: The project ID that corresponds to the URL\nexport async function getProjectId(\n target: string,\n config: GitLabIntegrationConfig,\n): Promise<number> {\n const url = new URL(target);\n\n if (!url.pathname.includes('/-/blob/')) {\n throw new Error('Please provide full path to yaml file from GitLab');\n }\n\n try {\n const repo = url.pathname.split('/-/blob/')[0];\n\n // Convert\n // to: https://gitlab.com/api/v4/projects/groupA%2Fteams%2FsubgroupA%2FteamA%2Frepo\n const repoIDLookup = new URL(\n `${url.origin}/api/v4/projects/${encodeURIComponent(\n repo.replace(/^\\//, ''),\n )}`,\n );\n const response = await fetch(\n repoIDLookup.toString(),\n getGitLabRequestOptions(config),\n );\n const data = await response.json();\n\n if (!response.ok) {\n throw new Error(\n `GitLab Error '${data.error}', ${data.error_description}`,\n );\n }\n\n return Number(data.id);\n } catch (e) {\n throw new Error(`Could not get GitLab project ID for: ${target}, ${e}`);\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport {\n GitLabIntegrationConfig,\n readGitLabIntegrationConfigs,\n} from './config';\n\n/**\n * A GitLab based integration.\n *\n * @public\n */\nexport class GitLabIntegration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<GitLabIntegration> = ({ config }) => {\n const configs = readGitLabIntegrationConfigs(\n config.getOptionalConfigArray('integrations.gitlab') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new GitLabIntegration(c)),\n i => i.config.host,\n );\n };\n\n constructor(private readonly integrationConfig: GitLabIntegrationConfig) {}\n\n get type(): string {\n return 'gitlab';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): GitLabIntegrationConfig {\n return this.integrationConfig;\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n return defaultScmResolveUrl(options);\n }\n\n resolveEditUrl(url: string): string {\n return replaceUrlType(url, 'edit');\n }\n}\n\nexport function replaceUrlType(\n url: string,\n type: 'blob' | 'tree' | 'edit',\n): string {\n return url.replace(/\\/\\-\\/(blob|tree|edit)\\//, `/-/${type}/`);\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\n\n/**\n * The configuration parameters for a single Google Cloud Storage provider.\n *\n * @public\n */\nexport type GoogleGcsIntegrationConfig = {\n /**\n * Service account email used to authenticate requests.\n */\n clientEmail?: string;\n /**\n * Service account private key used to authenticate requests.\n */\n privateKey?: string;\n};\n\n/**\n * Reads a single Google GCS integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\nexport function readGoogleGcsIntegrationConfig(\n config: Config,\n): GoogleGcsIntegrationConfig {\n if (!config) {\n return {};\n }\n\n if (!config.has('clientEmail') && !config.has('privateKey')) {\n return {};\n }\n\n const privateKey = config.getString('privateKey').split('\\\\n').join('\\n');\n\n const clientEmail = config.getString('clientEmail');\n return { clientEmail: clientEmail, privateKey: privateKey };\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\n\nconst AMAZON_AWS_HOST = 'amazonaws.com';\n\n/**\n * The configuration parameters for a single AWS S3 provider.\n *\n * @public\n */\nexport type AwsS3IntegrationConfig = {\n /**\n * Host, derived from endpoint, and defaults to amazonaws.com\n */\n host: string;\n\n /**\n * (Optional) AWS Endpoint.\n * The endpoint URI to send requests to. The default endpoint is built from the configured region.\n * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property\n *\n * Supports non-AWS providers, e.g. for LocalStack, endpoint may look like http://localhost:4566\n */\n endpoint?: string;\n\n /**\n * (Optional) Whether to use path style URLs when communicating with S3.\n * Defaults to false.\n * This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used.\n */\n s3ForcePathStyle?: boolean;\n\n /**\n * (Optional) User access key id\n */\n accessKeyId?: string;\n\n /**\n * (Optional) User secret access key\n */\n secretAccessKey?: string;\n\n /**\n * (Optional) ARN of role to be assumed\n */\n roleArn?: string;\n};\n\n/**\n * Reads a single Aws S3 integration config.\n *\n * @param config - The config object of a single integration\n * @public\n */\n\nexport function readAwsS3IntegrationConfig(\n config: Config,\n): AwsS3IntegrationConfig {\n const endpoint = config.getOptionalString('endpoint');\n const s3ForcePathStyle =\n config.getOptionalBoolean('s3ForcePathStyle') ?? false;\n let host;\n let pathname;\n if (endpoint) {\n try {\n const url = new URL(endpoint);\n host = url.host;\n pathname = url.pathname;\n } catch {\n throw new Error(\n `invalid awsS3 integration config, endpoint '${endpoint}' is not a valid URL`,\n );\n }\n if (pathname !== '/') {\n throw new Error(\n `invalid awsS3 integration config, endpoints cannot contain path, got '${endpoint}'`,\n );\n }\n } else {\n host = AMAZON_AWS_HOST;\n }\n\n const accessKeyId = config.getOptionalString('accessKeyId');\n const secretAccessKey = config.getOptionalString('secretAccessKey');\n const roleArn = config.getOptionalString('roleArn');\n\n return {\n host,\n endpoint,\n s3ForcePathStyle,\n accessKeyId,\n secretAccessKey,\n roleArn,\n };\n}\n\n/**\n * Reads a set of AWS S3 integration configs, and inserts some defaults for\n * public Amazon AWS if not specified.\n *\n * @param configs - The config objects of the integrations\n * @public\n */\nexport function readAwsS3IntegrationConfigs(\n configs: Config[],\n): AwsS3IntegrationConfig[] {\n // First read all the explicit integrations\n const result = configs.map(readAwsS3IntegrationConfig);\n\n // If no explicit amazonaws.com integration was added, put one in the list as\n // a convenience\n if (!result.some(c => c.host === AMAZON_AWS_HOST)) {\n result.push({\n host: AMAZON_AWS_HOST,\n });\n }\n return result;\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { basicIntegrations, defaultScmResolveUrl } from '../helpers';\nimport { ScmIntegration, ScmIntegrationsFactory } from '../types';\nimport { AwsS3IntegrationConfig, readAwsS3IntegrationConfigs } from './config';\n\n/**\n * Integrates with AWS S3 or compatible solutions.\n *\n * @public\n */\nexport class AwsS3Integration implements ScmIntegration {\n static factory: ScmIntegrationsFactory<AwsS3Integration> = ({ config }) => {\n const configs = readAwsS3IntegrationConfigs(\n config.getOptionalConfigArray('integrations.awsS3') ?? [],\n );\n return basicIntegrations(\n configs.map(c => new AwsS3Integration(c)),\n i => i.config.host,\n );\n };\n\n get type(): string {\n return 'awsS3';\n }\n\n get title(): string {\n return this.integrationConfig.host;\n }\n\n get config(): AwsS3IntegrationConfig {\n return this.integrationConfig;\n }\n\n constructor(private readonly integrationConfig: AwsS3IntegrationConfig) {}\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number | undefined;\n }): string {\n const resolved = defaultScmResolveUrl(options);\n return resolved;\n }\n\n resolveEditUrl(url: string): string {\n // TODO: Implement edit URL for awsS3\n return url;\n }\n}\n","/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Config } from '@backstage/config';\nimport { AwsS3Integration } from './awsS3/AwsS3Integration';\nimport { AzureIntegration } from './azure/AzureIntegration';\nimport { BitbucketIntegration } from './bitbucket/BitbucketIntegration';\nimport { GitHubIntegration } from './github/GitHubIntegration';\nimport { GitLabIntegration } from './gitlab/GitLabIntegration';\nimport { defaultScmResolveUrl } from './helpers';\nimport { ScmIntegration, ScmIntegrationsGroup } from './types';\nimport { ScmIntegrationRegistry } from './registry';\n\n/**\n * The set of supported integrations.\n *\n * @public\n */\nexport interface IntegrationsByType {\n awsS3: ScmIntegrationsGroup<AwsS3Integration>;\n azure: ScmIntegrationsGroup<AzureIntegration>;\n bitbucket: ScmIntegrationsGroup<BitbucketIntegration>;\n github: ScmIntegrationsGroup<GitHubIntegration>;\n gitlab: ScmIntegrationsGroup<GitLabIntegration>;\n}\n\n/**\n * Exposes the set of supported integrations.\n *\n * @public\n */\nexport class ScmIntegrations implements ScmIntegrationRegistry {\n private readonly byType: IntegrationsByType;\n\n static fromConfig(config: Config): ScmIntegrations {\n return new ScmIntegrations({\n awsS3: AwsS3Integration.factory({ config }),\n azure: AzureIntegration.factory({ config }),\n bitbucket: BitbucketIntegration.factory({ config }),\n github: GitHubIntegration.factory({ config }),\n gitlab: GitLabIntegration.factory({ config }),\n });\n }\n\n constructor(integrationsByType: IntegrationsByType) {\n this.byType = integrationsByType;\n }\n\n get awsS3(): ScmIntegrationsGroup<AwsS3Integration> {\n return this.byType.awsS3;\n }\n\n get azure(): ScmIntegrationsGroup<AzureIntegration> {\n return this.byType.azure;\n }\n\n get bitbucket(): ScmIntegrationsGroup<BitbucketIntegration> {\n return this.byType.bitbucket;\n }\n\n get github(): ScmIntegrationsGroup<GitHubIntegration> {\n return this.byType.github;\n }\n\n get gitlab(): ScmIntegrationsGroup<GitLabIntegration> {\n return this.byType.gitlab;\n }\n\n list(): ScmIntegration[] {\n return Object.values(this.byType).flatMap(\n i => i.list() as ScmIntegration[],\n );\n }\n\n byUrl(url: string | URL): ScmIntegration | undefined {\n return Object.values(this.byType)\n .map(i => i.byUrl(url))\n .find(Boolean);\n }\n\n byHost(host: string): ScmIntegration | undefined {\n return Object.values(this.byType)\n .map(i => i.byHost(host))\n .find(Boolean);\n }\n\n resolveUrl(options: {\n url: string;\n base: string;\n lineNumber?: number;\n }): string {\n const integration = this.byUrl(options.base);\n if (!integration) {\n return defaultScmResolveUrl(options);\n }\n\n return integration.resolveUrl(options);\n }\n\n resolveEditUrl(url: string): string {\n const integration = this.byUrl(url);\n if (!integration) {\n return url;\n }\n\n return integration.resolveEditUrl(url);\n }\n}\n"],"names":["parseGitUrl","trimEnd","fetch","DateTime","Octokit","createAppAuth"],"mappings":"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"}
|