@backstage/integration 0.6.10 → 0.7.2

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 CHANGED
@@ -1,5 +1,48 @@
1
1
  # @backstage/integration
2
2
 
3
+ ## 0.7.2
4
+
5
+ ### Patch Changes
6
+
7
+ - f45e99e5da: Do not return a token rather than fail where the owner is not in the allowed installation owners
8
+ for a GitHub app. This allows anonymous access to public files in the organisation.
9
+ - Updated dependencies
10
+ - @backstage/config@0.1.13
11
+
12
+ ## 0.7.2-next.0
13
+
14
+ ### Patch Changes
15
+
16
+ - Updated dependencies
17
+ - @backstage/config@0.1.13-next.0
18
+
19
+ ## 0.7.1
20
+
21
+ ### Patch Changes
22
+
23
+ - 3b4d8caff6: Adds a new GitHub credentials provider (DefaultGithubCredentialsProvider). It handles multiple app configurations. It looks up the app configuration based on the url.
24
+ - 5333451def: Cleaned up API exports
25
+ - Updated dependencies
26
+ - @backstage/config@0.1.12
27
+
28
+ ## 0.7.0
29
+
30
+ ### Minor Changes
31
+
32
+ - 7d4b4e937c: Create an interface for the GitHub credentials provider in order to support providing implementations.
33
+
34
+ We have changed the name of the `GithubCredentialsProvider` to `SingleInstanceGithubCredentialsProvider`.
35
+
36
+ `GithubCredentialsProvider` is now an interface that maybe implemented to provide a custom mechanism to retrieve GitHub credentials.
37
+
38
+ In a later release we will support configuring URL readers, scaffolder tasks, and processors with customer GitHub credentials providers.
39
+
40
+ If you want to uptake this release, you will need to replace all references to `GithubCredentialsProvider.create` with `SingleInstanceGithubCredentialsProvider.create`.
41
+
42
+ ### Patch Changes
43
+
44
+ - cf2e20a792: Added `endpoint` and `s3ForcePathStyle` as optional configuration for AWS S3 integrations.
45
+
3
46
  ## 0.6.10
4
47
 
5
48
  ### Patch Changes
package/config.d.ts CHANGED
@@ -167,10 +167,23 @@ export interface Config {
167
167
  /** Integration configuration for AWS S3 Service */
168
168
  awsS3?: Array<{
169
169
  /**
170
- * The host of the target that this matches on, e.g. "amazonaws.com".
170
+ * AWS Endpoint.
171
+ * The endpoint URI to send requests to. The default endpoint is built from the configured region.
172
+ * @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
173
+ *
174
+ * Supports non-AWS providers, e.g. for LocalStack, endpoint may look like http://localhost:4566
171
175
  * @visibility frontend
172
176
  */
173
- host: string;
177
+ endpoint?: string;
178
+
179
+ /**
180
+ * Whether to use path style URLs when communicating with S3.
181
+ * Defaults to false.
182
+ * This allows providers like LocalStack, Minio and Wasabi (and possibly others) to be used.
183
+ * @visibility frontend
184
+ */
185
+ s3ForcePathStyle?: boolean;
186
+
174
187
  /**
175
188
  * Account access key used to authenticate requests.
176
189
  * @visibility backend
package/dist/index.cjs.js CHANGED
@@ -46,7 +46,7 @@ function basicIntegrations(integrations, getHost) {
46
46
  };
47
47
  }
48
48
  function defaultScmResolveUrl(options) {
49
- const {url, base, lineNumber} = options;
49
+ const { url, base, lineNumber } = options;
50
50
  try {
51
51
  new URL(url);
52
52
  return url;
@@ -54,7 +54,7 @@ function defaultScmResolveUrl(options) {
54
54
  }
55
55
  let updated;
56
56
  if (url.startsWith("/")) {
57
- const {filepath} = parseGitUrl__default['default'](base);
57
+ const { filepath } = parseGitUrl__default["default"](base);
58
58
  updated = new URL(base);
59
59
  const repoRootPath = lodash.trimEnd(updated.pathname.substring(0, updated.pathname.length - filepath.length), "/");
60
60
  updated.pathname = `${repoRootPath}${url}`;
@@ -76,6 +76,11 @@ var __privateGet = (obj, member, getter) => {
76
76
  __accessCheck(obj, member, "read from private field");
77
77
  return getter ? getter.call(obj) : member.get(obj);
78
78
  };
79
+ var __privateAdd = (obj, member, value) => {
80
+ if (member.has(obj))
81
+ throw TypeError("Cannot add the same private member more than once");
82
+ member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
83
+ };
79
84
  var __privateSet = (obj, member, value, setter) => {
80
85
  __accessCheck(obj, member, "write to private field");
81
86
  setter ? setter.call(obj, value) : member.set(obj, value);
@@ -85,13 +90,13 @@ var _origin, _owner, _project, _repo, _path, _ref, _baseUrl;
85
90
  const VERSION_PREFIX_GIT_BRANCH = "GB";
86
91
  const _AzureUrl = class {
87
92
  constructor(origin, owner, project, repo, path, ref) {
88
- _origin.set(this, void 0);
89
- _owner.set(this, void 0);
90
- _project.set(this, void 0);
91
- _repo.set(this, void 0);
92
- _path.set(this, void 0);
93
- _ref.set(this, void 0);
94
- _baseUrl.set(this, (...parts) => {
93
+ __privateAdd(this, _origin, void 0);
94
+ __privateAdd(this, _owner, void 0);
95
+ __privateAdd(this, _project, void 0);
96
+ __privateAdd(this, _repo, void 0);
97
+ __privateAdd(this, _path, void 0);
98
+ __privateAdd(this, _ref, void 0);
99
+ __privateAdd(this, _baseUrl, (...parts) => {
95
100
  const url = new URL(__privateGet(this, _origin));
96
101
  url.pathname = parts.map((part) => encodeURIComponent(part)).join("/");
97
102
  return url;
@@ -214,12 +219,12 @@ function readAzureIntegrationConfig(config) {
214
219
  if (!isValidHost(host)) {
215
220
  throw new Error(`Invalid Azure integration config, '${host}' is not a valid host`);
216
221
  }
217
- return {host, token};
222
+ return { host, token };
218
223
  }
219
224
  function readAzureIntegrationConfigs(configs) {
220
225
  const result = configs.map(readAzureIntegrationConfig);
221
226
  if (!result.some((c) => c.host === AZURE_HOST)) {
222
- result.push({host: AZURE_HOST});
227
+ result.push({ host: AZURE_HOST });
223
228
  }
224
229
  return result;
225
230
  }
@@ -239,7 +244,7 @@ const _AzureIntegration = class {
239
244
  }
240
245
  resolveUrl(options) {
241
246
  var _a;
242
- const {url, base} = options;
247
+ const { url, base } = options;
243
248
  if (isValidUrl(url)) {
244
249
  return url;
245
250
  }
@@ -265,7 +270,7 @@ const _AzureIntegration = class {
265
270
  }
266
271
  };
267
272
  let AzureIntegration = _AzureIntegration;
268
- AzureIntegration.factory = ({config}) => {
273
+ AzureIntegration.factory = ({ config }) => {
269
274
  var _a;
270
275
  const configs = readAzureIntegrationConfigs((_a = config.getOptionalConfigArray("integrations.azure")) != null ? _a : []);
271
276
  return basicIntegrations(configs.map((c) => new _AzureIntegration(c)), (i) => i.config.host);
@@ -281,12 +286,12 @@ function getAzureCommitsUrl(url) {
281
286
  return AzureUrl.fromRepoUrl(url).toCommitsUrl();
282
287
  }
283
288
  function getAzureRequestOptions(config, additionalHeaders) {
284
- const headers = additionalHeaders ? {...additionalHeaders} : {};
289
+ const headers = additionalHeaders ? { ...additionalHeaders } : {};
285
290
  if (config.token) {
286
291
  const buffer = Buffer.from(`:${config.token}`, "utf8");
287
292
  headers.Authorization = `Basic ${buffer.toString("base64")}`;
288
293
  }
289
- return {headers};
294
+ return { headers };
290
295
  }
291
296
 
292
297
  const BITBUCKET_HOST = "bitbucket.org";
@@ -349,7 +354,7 @@ const _BitbucketIntegration = class {
349
354
  return resolved;
350
355
  }
351
356
  resolveEditUrl(url) {
352
- const urlData = parseGitUrl__default['default'](url);
357
+ const urlData = parseGitUrl__default["default"](url);
353
358
  const editUrl = new URL(url);
354
359
  editUrl.searchParams.set("mode", "edit");
355
360
  editUrl.searchParams.set("spa", "0");
@@ -367,13 +372,13 @@ BitbucketIntegration.factory = ({
367
372
  };
368
373
 
369
374
  async function getBitbucketDefaultBranch(url, config) {
370
- const {name: repoName, owner: project, resource} = parseGitUrl__default['default'](url);
375
+ const { name: repoName, owner: project, resource } = parseGitUrl__default["default"](url);
371
376
  const isHosted = resource === "bitbucket.org";
372
377
  let branchUrl = isHosted ? `${config.apiBaseUrl}/repositories/${project}/${repoName}` : `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/default-branch`;
373
- let response = await fetch__default['default'](branchUrl, getBitbucketRequestOptions(config));
378
+ let response = await fetch__default["default"](branchUrl, getBitbucketRequestOptions(config));
374
379
  if (response.status === 404 && !isHosted) {
375
380
  branchUrl = `${config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches/default`;
376
- response = await fetch__default['default'](branchUrl, getBitbucketRequestOptions(config));
381
+ response = await fetch__default["default"](branchUrl, getBitbucketRequestOptions(config));
377
382
  }
378
383
  if (!response.ok) {
379
384
  const message = `Failed to retrieve default branch from ${branchUrl}, ${response.status} ${response.statusText}`;
@@ -384,7 +389,7 @@ async function getBitbucketDefaultBranch(url, config) {
384
389
  const repoInfo = await response.json();
385
390
  defaultBranch = repoInfo.mainbranch.name;
386
391
  } else {
387
- const {displayId} = await response.json();
392
+ const { displayId } = await response.json();
388
393
  defaultBranch = displayId;
389
394
  }
390
395
  if (!defaultBranch) {
@@ -400,7 +405,7 @@ async function getBitbucketDownloadUrl(url, config) {
400
405
  protocol,
401
406
  resource,
402
407
  filepath
403
- } = parseGitUrl__default['default'](url);
408
+ } = parseGitUrl__default["default"](url);
404
409
  const isHosted = resource === "bitbucket.org";
405
410
  let branch = ref;
406
411
  if (!branch) {
@@ -412,7 +417,7 @@ async function getBitbucketDownloadUrl(url, config) {
412
417
  }
413
418
  function getBitbucketFileFetchUrl(url, config) {
414
419
  try {
415
- const {owner, name, ref, filepathtype, filepath} = parseGitUrl__default['default'](url);
420
+ const { owner, name, ref, filepathtype, filepath } = parseGitUrl__default["default"](url);
416
421
  if (!owner || !name || filepathtype !== "browse" && filepathtype !== "raw" && filepathtype !== "src") {
417
422
  throw new Error("Invalid Bitbucket URL or file path");
418
423
  }
@@ -471,7 +476,7 @@ function readGitHubIntegrationConfig(config) {
471
476
  } else if (host === GITHUB_HOST) {
472
477
  rawBaseUrl = GITHUB_RAW_BASE_URL;
473
478
  }
474
- return {host, apiBaseUrl, rawBaseUrl, token, apps};
479
+ return { host, apiBaseUrl, rawBaseUrl, token, apps };
475
480
  }
476
481
  function readGitHubIntegrationConfigs(configs) {
477
482
  const result = configs.map(readGitHubIntegrationConfig);
@@ -487,7 +492,7 @@ function readGitHubIntegrationConfigs(configs) {
487
492
 
488
493
  function getGitHubFileFetchUrl(url, config, credentials) {
489
494
  try {
490
- const {owner, name, ref, filepathtype, filepath} = parseGitUrl__default['default'](url);
495
+ const { owner, name, ref, filepathtype, filepath } = parseGitUrl__default["default"](url);
491
496
  if (!owner || !name || !ref || filepathtype !== "blob" && filepathtype !== "raw" && filepathtype !== "tree") {
492
497
  throw new Error("Invalid GitHub URL or file path");
493
498
  }
@@ -508,7 +513,7 @@ function getGitHubRequestOptions(config, credentials) {
508
513
  if (credentials.token) {
509
514
  headers.Authorization = `token ${credentials.token}`;
510
515
  }
511
- return {headers};
516
+ return { headers };
512
517
  }
513
518
  function chooseEndpoint(config, credentials) {
514
519
  if (config.apiBaseUrl && (credentials.token || !config.rawBaseUrl)) {
@@ -519,17 +524,17 @@ function chooseEndpoint(config, credentials) {
519
524
 
520
525
  class Cache {
521
526
  constructor() {
522
- this.tokenCache = new Map();
527
+ this.tokenCache = /* @__PURE__ */ new Map();
523
528
  this.isNotExpired = (date) => date.diff(luxon.DateTime.local(), "minutes").minutes > 50;
524
529
  }
525
530
  async getOrCreateToken(key, supplier) {
526
531
  const item = this.tokenCache.get(key);
527
532
  if (item && this.isNotExpired(item.expiresAt)) {
528
- return {accessToken: item.token};
533
+ return { accessToken: item.token };
529
534
  }
530
535
  const result = await supplier();
531
536
  this.tokenCache.set(key, result);
532
- return {accessToken: result.token};
537
+ return { accessToken: result.token };
533
538
  }
534
539
  }
535
540
  const HEADERS = {
@@ -553,10 +558,10 @@ class GithubAppManager {
553
558
  }
554
559
  async getInstallationCredentials(owner, repo) {
555
560
  var _a;
556
- const {installationId, suspended} = await this.getInstallationData(owner);
561
+ const { installationId, suspended } = await this.getInstallationData(owner);
557
562
  if (this.allowedInstallationOwners) {
558
563
  if (!((_a = this.allowedInstallationOwners) == null ? void 0 : _a.includes(owner))) {
559
- throw new Error(`The GitHub application for ${owner} is not included in the allowed installation list (${installationId}).`);
564
+ return { accessToken: void 0 };
560
565
  }
561
566
  }
562
567
  if (suspended) {
@@ -623,7 +628,7 @@ class GithubAppCredentialsMux {
623
628
  if (this.apps.length === 0) {
624
629
  return void 0;
625
630
  }
626
- const results = await Promise.all(this.apps.map((app) => app.getInstallationCredentials(owner, repo).then((credentials) => ({credentials, error: void 0}), (error) => ({credentials: void 0, error}))));
631
+ const results = await Promise.all(this.apps.map((app) => app.getInstallationCredentials(owner, repo).then((credentials) => ({ credentials, error: void 0 }), (error) => ({ credentials: void 0, error }))));
627
632
  const result = results.find((resultItem) => resultItem.credentials);
628
633
  if (result) {
629
634
  return result.credentials.accessToken;
@@ -636,16 +641,13 @@ class GithubAppCredentialsMux {
636
641
  return void 0;
637
642
  }
638
643
  }
639
- class GithubCredentialsProvider {
644
+ const _SingleInstanceGithubCredentialsProvider = class {
640
645
  constructor(githubAppCredentialsMux, token) {
641
646
  this.githubAppCredentialsMux = githubAppCredentialsMux;
642
647
  this.token = token;
643
648
  }
644
- static create(config) {
645
- return new GithubCredentialsProvider(new GithubAppCredentialsMux(config), config.token);
646
- }
647
649
  async getCredentials(opts) {
648
- const parsed = parseGitUrl__default['default'](opts.url);
650
+ const parsed = parseGitUrl__default["default"](opts.url);
649
651
  const owner = parsed.owner || parsed.name;
650
652
  const repo = parsed.owner ? parsed.name : void 0;
651
653
  let type = "app";
@@ -655,11 +657,37 @@ class GithubCredentialsProvider {
655
657
  token = this.token;
656
658
  }
657
659
  return {
658
- headers: token ? {Authorization: `Bearer ${token}`} : void 0,
660
+ headers: token ? { Authorization: `Bearer ${token}` } : void 0,
659
661
  token,
660
662
  type
661
663
  };
662
664
  }
665
+ };
666
+ let SingleInstanceGithubCredentialsProvider = _SingleInstanceGithubCredentialsProvider;
667
+ SingleInstanceGithubCredentialsProvider.create = (config) => {
668
+ return new _SingleInstanceGithubCredentialsProvider(new GithubAppCredentialsMux(config), config.token);
669
+ };
670
+
671
+ class DefaultGithubCredentialsProvider {
672
+ constructor(providers) {
673
+ this.providers = providers;
674
+ }
675
+ static fromIntegrations(integrations) {
676
+ const credentialsProviders = /* @__PURE__ */ new Map();
677
+ integrations.github.list().forEach((integration) => {
678
+ const credentialsProvider = SingleInstanceGithubCredentialsProvider.create(integration.config);
679
+ credentialsProviders.set(integration.config.host, credentialsProvider);
680
+ });
681
+ return new DefaultGithubCredentialsProvider(credentialsProviders);
682
+ }
683
+ async getCredentials(opts) {
684
+ const parsed = new URL(opts.url);
685
+ const provider = this.providers.get(parsed.host);
686
+ if (!provider) {
687
+ throw new Error(`There is no GitHub integration that matches ${opts.url}. Please add a configuration for an integration.`);
688
+ }
689
+ return provider.getCredentials(opts);
690
+ }
663
691
  }
664
692
 
665
693
  const _GitHubIntegration = class {
@@ -683,7 +711,7 @@ const _GitHubIntegration = class {
683
711
  }
684
712
  };
685
713
  let GitHubIntegration = _GitHubIntegration;
686
- GitHubIntegration.factory = ({config}) => {
714
+ GitHubIntegration.factory = ({ config }) => {
687
715
  var _a;
688
716
  const configs = readGitHubIntegrationConfigs((_a = config.getOptionalConfigArray("integrations.github")) != null ? _a : []);
689
717
  return basicIntegrations(configs.map((c) => new _GitHubIntegration(c)), (i) => i.config.host);
@@ -720,7 +748,7 @@ function readGitLabIntegrationConfig(config) {
720
748
  } else if (!isValidUrl(baseUrl)) {
721
749
  throw new Error(`Invalid GitLab integration config, '${baseUrl}' is not a valid baseUrl`);
722
750
  }
723
- return {host, token, apiBaseUrl, baseUrl};
751
+ return { host, token, apiBaseUrl, baseUrl };
724
752
  }
725
753
  function readGitLabIntegrationConfigs(configs) {
726
754
  const result = configs.map(readGitLabIntegrationConfig);
@@ -742,7 +770,7 @@ async function getGitLabFileFetchUrl(url, config) {
742
770
  return buildRawUrl(url).toString();
743
771
  }
744
772
  function getGitLabRequestOptions(config) {
745
- const {token = ""} = config;
773
+ const { token = "" } = config;
746
774
  return {
747
775
  headers: {
748
776
  "PRIVATE-TOKEN": token
@@ -788,7 +816,7 @@ async function getProjectId(target, config) {
788
816
  try {
789
817
  const repo = url.pathname.split("/-/blob/")[0];
790
818
  const repoIDLookup = new URL(`${url.protocol + url.hostname}/api/v4/projects/${encodeURIComponent(repo.replace(/^\//, ""))}`);
791
- const response = await fetch__default['default'](repoIDLookup.toString(), getGitLabRequestOptions(config));
819
+ const response = await fetch__default["default"](repoIDLookup.toString(), getGitLabRequestOptions(config));
792
820
  const data = await response.json();
793
821
  if (!response.ok) {
794
822
  throw new Error(`GitLab Error '${data.error}', ${data.error_description}`);
@@ -820,7 +848,7 @@ const _GitLabIntegration = class {
820
848
  }
821
849
  };
822
850
  let GitLabIntegration = _GitLabIntegration;
823
- GitLabIntegration.factory = ({config}) => {
851
+ GitLabIntegration.factory = ({ config }) => {
824
852
  var _a;
825
853
  const configs = readGitLabIntegrationConfigs((_a = config.getOptionalConfigArray("integrations.gitlab")) != null ? _a : []);
826
854
  return basicIntegrations(configs.map((c) => new _GitLabIntegration(c)), (i) => i.config.host);
@@ -838,20 +866,41 @@ function readGoogleGcsIntegrationConfig(config) {
838
866
  }
839
867
  const privateKey = config.getString("privateKey").split("\\n").join("\n");
840
868
  const clientEmail = config.getString("clientEmail");
841
- return {clientEmail, privateKey};
869
+ return { clientEmail, privateKey };
842
870
  }
843
871
 
844
872
  const AMAZON_AWS_HOST = "amazonaws.com";
845
873
  function readAwsS3IntegrationConfig(config) {
846
874
  var _a;
847
- const host = (_a = config.getOptionalString("host")) != null ? _a : AMAZON_AWS_HOST;
875
+ const endpoint = config.getOptionalString("endpoint");
876
+ const s3ForcePathStyle = (_a = config.getOptionalBoolean("s3ForcePathStyle")) != null ? _a : false;
877
+ let host;
878
+ let pathname;
879
+ if (endpoint) {
880
+ try {
881
+ const url = new URL(endpoint);
882
+ host = url.host;
883
+ pathname = url.pathname;
884
+ } catch {
885
+ throw new Error(`invalid awsS3 integration config, endpoint '${endpoint}' is not a valid URL`);
886
+ }
887
+ if (pathname !== "/") {
888
+ throw new Error(`invalid awsS3 integration config, endpoints cannot contain path, got '${endpoint}'`);
889
+ }
890
+ } else {
891
+ host = AMAZON_AWS_HOST;
892
+ }
848
893
  const accessKeyId = config.getOptionalString("accessKeyId");
849
894
  const secretAccessKey = config.getOptionalString("secretAccessKey");
850
895
  const roleArn = config.getOptionalString("roleArn");
851
- if (!isValidHost(host)) {
852
- throw new Error(`Invalid awsS3 integration config, '${host}' is not a valid host`);
853
- }
854
- return {host, accessKeyId, secretAccessKey, roleArn};
896
+ return {
897
+ host,
898
+ endpoint,
899
+ s3ForcePathStyle,
900
+ accessKeyId,
901
+ secretAccessKey,
902
+ roleArn
903
+ };
855
904
  }
856
905
  function readAwsS3IntegrationConfigs(configs) {
857
906
  const result = configs.map(readAwsS3IntegrationConfig);
@@ -885,7 +934,7 @@ const _AwsS3Integration = class {
885
934
  }
886
935
  };
887
936
  let AwsS3Integration = _AwsS3Integration;
888
- AwsS3Integration.factory = ({config}) => {
937
+ AwsS3Integration.factory = ({ config }) => {
889
938
  var _a;
890
939
  const configs = readAwsS3IntegrationConfigs((_a = config.getOptionalConfigArray("integrations.awsS3")) != null ? _a : []);
891
940
  return basicIntegrations(configs.map((c) => new _AwsS3Integration(c)), (i) => i.config.host);
@@ -894,11 +943,11 @@ AwsS3Integration.factory = ({config}) => {
894
943
  class ScmIntegrations {
895
944
  static fromConfig(config) {
896
945
  return new ScmIntegrations({
897
- awsS3: AwsS3Integration.factory({config}),
898
- azure: AzureIntegration.factory({config}),
899
- bitbucket: BitbucketIntegration.factory({config}),
900
- github: GitHubIntegration.factory({config}),
901
- gitlab: GitLabIntegration.factory({config})
946
+ awsS3: AwsS3Integration.factory({ config }),
947
+ azure: AzureIntegration.factory({ config }),
948
+ bitbucket: BitbucketIntegration.factory({ config }),
949
+ github: GitHubIntegration.factory({ config }),
950
+ gitlab: GitLabIntegration.factory({ config })
902
951
  });
903
952
  }
904
953
  constructor(integrationsByType) {
@@ -947,11 +996,12 @@ class ScmIntegrations {
947
996
  exports.AwsS3Integration = AwsS3Integration;
948
997
  exports.AzureIntegration = AzureIntegration;
949
998
  exports.BitbucketIntegration = BitbucketIntegration;
999
+ exports.DefaultGithubCredentialsProvider = DefaultGithubCredentialsProvider;
950
1000
  exports.GitHubIntegration = GitHubIntegration;
951
1001
  exports.GitLabIntegration = GitLabIntegration;
952
1002
  exports.GithubAppCredentialsMux = GithubAppCredentialsMux;
953
- exports.GithubCredentialsProvider = GithubCredentialsProvider;
954
1003
  exports.ScmIntegrations = ScmIntegrations;
1004
+ exports.SingleInstanceGithubCredentialsProvider = SingleInstanceGithubCredentialsProvider;
955
1005
  exports.defaultScmResolveUrl = defaultScmResolveUrl;
956
1006
  exports.getAzureCommitsUrl = getAzureCommitsUrl;
957
1007
  exports.getAzureDownloadUrl = getAzureDownloadUrl;