@spotify/backstage-plugin-soundcheck-backend-module-github 0.1.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 ADDED
@@ -0,0 +1,14 @@
1
+ # @spotify/backstage-plugin-soundcheck-backend-module-github
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 07770382: Upgrade to Backstage 1.12.0
8
+ - 7dc0e65c: Adds GitHub integration for fact extraction from GitHub repositories. See the modules README.md for additional details, including configuration details.
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [3e368dc9]
13
+ - @spotify/backstage-plugin-soundcheck-common@0.4.0
14
+ - @spotify/backstage-plugin-soundcheck-node@0.2.0
package/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ # Commercial License
2
+
3
+ Copyright (c) 2023 Spotify. All rights reserved.
4
+
5
+ This software is part of the "Spotify Plugins for Backstage" bundle. "Spotify
6
+ Plugins for Backstage" is commercial software. To use it, you must obtain a
7
+ license and agree to the [License
8
+ Terms](https://backstage.spotify.com/spotify-plugins-for-backstage-terms).
9
+ Commercial licenses can be obtained at https://backstage.spotify.com/.
package/README.md ADDED
@@ -0,0 +1,429 @@
1
+ # Spotify Plugins for Backstage: Soundcheck - Github Integration
2
+
3
+ Github integration plugin for [Soundcheck](https://backstage.spotify.com/plugins/soundcheck/).
4
+ Similarly to [SCM Integration plugin](https://www.npmjs.com/package/@spotify/backstage-plugin-soundcheck-backend-module-scm), it provides out-of-box integration with Github by leveraging Backstage's Github integration and implements extraction and collection of facts from Github repositories.
5
+ The purpose of Github Integration plugin is to provide Github-specific fact collection (like branch protections), while the SCM Integration plugin provides the collection of facts based on repository content.
6
+
7
+ Github integration plugin supports the extraction of the following facts:
8
+
9
+ - [BranchProtections](#collecting-branchprotections-fact)
10
+ - [RepositoryDetails](#collecting-repositorydetails-fact)
11
+
12
+ <!-- TOC -->
13
+
14
+ - [Spotify Plugins for Backstage: Soundcheck - Github Integration](#spotify-plugins-for-backstage-soundcheck---github-integration)
15
+ - [Prerequisites](#prerequisites)
16
+ - [Configure Github integration in Backstage](#configure-github-integration-in-backstage)
17
+ - [Plugin Configuration](#plugin-configuration)
18
+ - [Defining Github Fact Collectors](#defining-github-fact-collectors)
19
+ - [Overall Shape Of A Github Fact Collector](#overall-shape-of-a-github-fact-collector)
20
+ - [`frequency` [optional]](#frequency-optional)
21
+ - [`filter` [optional]](#filter-optional)
22
+ - [`cache` [optional]](#cache-optional)
23
+ - [`collects` [required]](#collects-required)
24
+ - [Overall Shape Of A Fact Extractor](#overall-shape-of-a-fact-extractor)
25
+ - [`factName` [required]](#factname-required)
26
+ - [`type` [required]](#type-required)
27
+ - [`frequency` [optional]](#frequency-optional)
28
+ - [`branch` [optional]](#branch-optional)
29
+ - [`filter` [optional]](#filter-optional)
30
+ - [`cache` [optional]](#cache-optional)
31
+ - [Collecting BranchProtections Fact](#collecting-branchprotections-fact)
32
+ - [Shape of A BranchProtections Fact Collector](#shape-of-a-branchprotections-fact-collector)
33
+ - [Shape of A BranchProtections Fact](#shape-of-a-branchprotections-fact)
34
+ - [Shape of A BranchProtections Fact Check](#shape-of-a-branchprotections-fact-check)
35
+ - [Collecting RepositoryDetails Fact](#collecting-repositorydetails-fact)
36
+ - [Shape of A RepositoryDetails Fact Collector](#shape-of-a-repositorydetails-fact-collector)
37
+ - [Shape of A RepositoryDetails Fact](#shape-of-a-repositorydetails-fact)
38
+ - [Shape of A RepositoryDetails Fact Check](#shape-of-a-repositorydetails-fact-check)
39
+ <!-- TOC -->
40
+
41
+ ## Prerequisites
42
+
43
+ ### Configure Github integration in Backstage
44
+
45
+ Integrations are configured at the root level of `app-config.yaml`, here's an example configuration for Github:
46
+
47
+ ```yaml
48
+ integrations:
49
+ github:
50
+ - host: github.com
51
+ token: ${GITHUB_TOKEN}
52
+ ```
53
+
54
+ Follow the [instructions](https://backstage.io/docs/integrations/github/locations) for full details on configuration.
55
+
56
+ ## Plugin Configuration
57
+
58
+ Collection of facts is driven by config. To learn more about the config, see the [Defining Github Fact Collectors](#defining-github-fact-collectors).
59
+
60
+ 1. Create `github-facts-collectors.yaml` in the root of your Backstage repository and fill in all your Github fact collectors.
61
+ A simple example Github fact collector is listed below.
62
+
63
+ **Note:** this file will be loaded at runtime along with the rest of your Backstage configuration files, so make sure it's available in deployed environments in the same way as your `app-config.yaml` files.
64
+
65
+ ```yaml
66
+ ---
67
+ frequency:
68
+ cron: '0 * * * *'
69
+ collects:
70
+ - factName: branch_protections
71
+ type: BranchProtections
72
+ branch: master
73
+ - factName: repo_details
74
+ type: RepositoryDetails
75
+ ```
76
+
77
+ 2. Add a soundcheck collectors field to `app-config.yaml` and reference the newly created `github-facts-collectors.yaml`
78
+
79
+ ```yaml
80
+ # app-config.yaml
81
+ soundcheck:
82
+ collectors:
83
+ github:
84
+ $include: ./github-facts-collectors.yaml
85
+ ```
86
+
87
+ ## Defining Github Fact Collectors
88
+
89
+ This section describes the data shape and semantics of Github Fact Collectors.
90
+
91
+ ### Overall Shape Of A Github Fact Collector
92
+
93
+ The following is an example of a descriptor file for a Github Fact Collector:
94
+
95
+ ```yaml
96
+ ---
97
+ frequency:
98
+ cron: '0 * * * *'
99
+ filter:
100
+ kind: 'Component'
101
+ cache:
102
+ duration:
103
+ hours: 2
104
+ collects:
105
+ - factName: branch_protections
106
+ type: BranchProtections
107
+ branch: master
108
+ filter:
109
+ - spec.lifecycle: 'production'
110
+ spec.type: 'website'
111
+ cache: false
112
+ - factName: repo_details
113
+ type: RepositoryDetails
114
+ cache: true
115
+ ```
116
+
117
+ See below for details about these fields.
118
+
119
+ #### `frequency` [optional]
120
+
121
+ The frequency at which the collector should be executed. Possible values are either a cron expression `{ cron: ... }` or [HumanDuration](https://backstage.io/docs/reference/backend-tasks.humanduration).
122
+ This is the default frequency for each extractor.
123
+
124
+ #### `filter` [optional]
125
+
126
+ A filter specifying which entities to collect the specified facts for. Matches the [filter format](https://backstage.io/docs/reference/catalog-client.entityfilterquery) used by the Catalog API.
127
+ This is the default filter for each extractor.
128
+
129
+ #### `cache` [optional]
130
+
131
+ If the collected facts should be cached, and if so for how long. Possible values are either `true` or `false` or a nested `{ duration:` [HumanDuration](https://backstage.io/docs/reference/types.humanduration) `}` field.
132
+ This is the default cache config for each extractor.
133
+
134
+ #### `collects` [required]
135
+
136
+ An array describing which facts to collect and how to extract them. See below for details about the overall shape of a fact extractor.
137
+
138
+ ### Overall Shape Of A Fact Extractor
139
+
140
+ Each extractor supports the fields described below.
141
+
142
+ #### `factName` [required]
143
+
144
+ The name of the fact to be extracted.
145
+
146
+ - Minimum length of 1
147
+ - Maximum length of 100
148
+ - Alphanumeric with single separator instances of periods, dashes, underscores, or forward slashes
149
+
150
+ #### `type` [required]
151
+
152
+ The type of the extractor (e.g. BranchProtections, RepositoryDetails).
153
+
154
+ #### `frequency` [optional]
155
+
156
+ The frequency at which the fact extraction should be executed. Possible values are either a cron expression `{ cron: ... }` or [HumanDuration](https://backstage.io/docs/reference/backend-tasks.humanduration).
157
+ If provided it overrides the default frequency provided at the top level. If not provided it defaults to the frequency provided at the top level. If neither extractor's frequency nor default frequency is provided the fact will only be collected on demand.
158
+ Example:
159
+
160
+ ```yaml
161
+ frequency:
162
+ minutes: 10
163
+ ```
164
+
165
+ #### `branch` [optional]
166
+
167
+ The branch to extract the fact from. If not provided, defaults to the repository's default branch.
168
+
169
+ #### `filter` [optional]
170
+
171
+ A filter specifying which entities to collect the specified facts for. Matches the [filter format](https://backstage.io/docs/reference/catalog-client.entityfilterquery) used by the Catalog API.
172
+ If provided it overrides the default filter provided at the top level. If not provided it defaults to the filter provided at the top level. If neither extractor's filter nor default filter is provided the fact will be collected for all entities.
173
+
174
+ #### `cache` [optional]
175
+
176
+ If the collected facts should be cached, and if so for how long. Possible values are either `true` or `false` or a nested `{ duration:` [HumanDuration](https://backstage.io/docs/reference/types.humanduration) `}` field.
177
+ If provided it overrides the default cache config provided at the top level. If not provided it defaults to the cache config provided at the top level. If neither extractor's cache nor default cache config is provided the fact will not be cached.
178
+ Example:
179
+
180
+ ```yaml
181
+ cache:
182
+ duration:
183
+ hours: 24
184
+ ```
185
+
186
+ ## Collecting BranchProtections Fact
187
+
188
+ BranchProtections fact contains information about configured branch protections for a given branch in Github repository.
189
+
190
+ ### Shape of A BranchProtections Fact Collector
191
+
192
+ The shape of a BranchProtections Fact Collector matches the [Overall Shape Of A Github Fact Collector](#overall-shape-of-a-github-fact-collector) (restriction: `type: BranchProtections`).
193
+
194
+ The following is an example of the BranchProtections Fact Collector config:
195
+
196
+ ```yaml
197
+ collects:
198
+ - factName: branch_protections
199
+ type: BranchProtections
200
+ frequency:
201
+ cron: '0 * * * *'
202
+ branch: master
203
+ filter:
204
+ - spec.lifecycle: 'production'
205
+ spec.type: 'website'
206
+ cache: false
207
+ ```
208
+
209
+ ### Shape of A BranchProtections Fact
210
+
211
+ The shape of a BranchProtections Fact is based on the [Fact Schema](https://www.npmjs.com/package/@spotify/backstage-plugin-soundcheck-backend#fact-schema).
212
+
213
+ For a description of the data collected regarding branch protection, refer to the [Github API documentation](https://docs.github.com/en/rest/branches/branch-protection).
214
+
215
+ The following is an example of the collected BranchProtections Fact:
216
+
217
+ ```yaml
218
+ factRef: github:master/branch_protections
219
+ entityRef: component:default/queue-proxy
220
+ scope: master
221
+ timestamp: 2023-02-24T15:50+00Z
222
+ data:
223
+ url: 'https://api.github.com/repos/backstage/backstage/branches/master/protection'
224
+ required_pull_request_reviews:
225
+ url: 'https://api.github.com/repos/backstage/backstage/branches/master/protection/required_pull_request_reviews',
226
+ dismiss_stale_reviews: false
227
+ require_code_owner_reviews: true
228
+ required_approving_review_count: 2
229
+ require_last_push_approval: false
230
+ required_signatures:
231
+ url: 'https://api.github.com/repos/backstage/backstage/branches/master/protection/required_signatures'
232
+ enabled: false
233
+ enforce_admins:
234
+ url: 'https://api.github.com/repos/backstage/backstage/branches/master/protection/enforce_admins'
235
+ enabled: false
236
+ required_linear_history:
237
+ enabled: false
238
+ allow_force_pushes:
239
+ enabled: true
240
+ allow_deletions:
241
+ enabled: true
242
+ block_creations:
243
+ enabled: true
244
+ required_conversation_resolution:
245
+ enabled: false
246
+ lock_branch:
247
+ enabled: false
248
+ allow_fork_syncing:
249
+ enabled: true
250
+ ```
251
+
252
+ ### Shape of A BranchProtections Fact Check
253
+
254
+ The shape of a BranchProtections Fact Check matches the [Shape of a Fact Check](https://www.npmjs.com/package/@spotify/backstage-plugin-soundcheck-backend#shape-of-a-fact-check).
255
+
256
+ The following is an example of the BranchProtections fact checks:
257
+
258
+ ```yaml
259
+ soundcheck:
260
+ checks:
261
+ - id: requires_code_owner_reviews
262
+ rule:
263
+ factRef: github:master/branch_protections
264
+ path: $.required_pull_request_reviews.require_code_owner_reviews
265
+ operator: equal
266
+ value: true
267
+ - id: requires_at_least_two_approving_reviews
268
+ rule:
269
+ factRef: github:master/branch_protections
270
+ path: $.required_pull_request_reviews.required_approving_review_count
271
+ operator: greaterThanInclusive
272
+ value: 2
273
+ ```
274
+
275
+ The following is an example of the Soundcheck program that utilizes these checks:
276
+
277
+ ```yaml
278
+ - id: demo
279
+ name: Demo
280
+ ownerEntityRef: group:default/owning_group
281
+ description: Demonstration of Soundcheck BranchProtections Fact Extractor
282
+ levels:
283
+ - ordinal: 1
284
+ name: First level
285
+ description: Checks leveraging Soundcheck's Github BranchProtections Fact Extractor
286
+ checks:
287
+ - id: requires_code_owner_reviews
288
+ name: Requires code owner reviews
289
+ description: PR requires code owner reviews
290
+ - id: requires_at_least_two_approving_reviews
291
+ name: Requires at least two approving reviews
292
+ description: PR requires at least two approving reviews
293
+ ```
294
+
295
+ ## Collecting RepositoryDetails Fact
296
+
297
+ RepositoryDetails fact contains information about a Github repository.
298
+
299
+ ### Shape of A RepositoryDetails Fact Collector
300
+
301
+ The shape of a RepositoryDetails Fact Collector matches the [Overall Shape Of A Github Fact Collector](#overall-shape-of-a-github-fact-collector) (restriction: `type: RepositoryDetails`).
302
+
303
+ The following is an example of the RepositoryDetails Fact Collector config:
304
+
305
+ ```yaml
306
+ collects:
307
+ - factName: repo_details
308
+ type: RepositoryDetails
309
+ frequency:
310
+ cron: '0 * * * *'
311
+ filter:
312
+ - spec.lifecycle: 'production'
313
+ cache: true
314
+ ```
315
+
316
+ ### Shape of A RepositoryDetails Fact
317
+
318
+ The shape of a RepositoryDetails Fact is based on the [Fact Schema](https://www.npmjs.com/package/@spotify/backstage-plugin-soundcheck-backend#fact-schema).
319
+
320
+ For a description of the data collected about repository, refer to the [Github API documentation](https://docs.github.com/en/rest/repos/repos#get-a-repository).
321
+
322
+ The following is an example of the collected RepositoryDetails Fact:
323
+
324
+ ```yaml
325
+ factRef: github:default/repo_details
326
+ entityRef: component:default/queue-proxy
327
+ scope: default
328
+ timestamp: 2023-02-24T15:50+00Z
329
+ data:
330
+ name: backstage
331
+ full_name: backstage/backstage
332
+ private: true
333
+ html_url: 'https://github.com/backstage/backstage'
334
+ description: null
335
+ fork: false
336
+ url: 'https://api.github.com/repos/backstage/backstage'
337
+ homepage: null
338
+ size: 3
339
+ stargazers_count: 0
340
+ watchers_count: 0
341
+ language: null
342
+ has_issues: true
343
+ has_projects: true
344
+ has_downloads: true
345
+ has_wiki: true
346
+ has_pages: false
347
+ has_discussions: false
348
+ forks_count: 0
349
+ mirror_url: null
350
+ archived: false
351
+ disabled: false
352
+ open_issues_count: 0
353
+ license: null
354
+ allow_forking: true
355
+ is_template: false
356
+ web_commit_signoff_required: false
357
+ visibility: 'private'
358
+ forks: 0
359
+ open_issues: 0
360
+ watchers: 0
361
+ default_branch: master
362
+ permissions:
363
+ admin: true
364
+ maintain: true
365
+ push: true
366
+ triage: true
367
+ pull: true
368
+ allow_squash_merge: true
369
+ allow_merge_commit: true
370
+ allow_rebase_merge: true
371
+ allow_auto_merge: false
372
+ delete_branch_on_merge: false
373
+ allow_update_branch: false
374
+ use_squash_pr_title_as_default: false
375
+ squash_merge_commit_message: 'COMMIT_MESSAGES'
376
+ squash_merge_commit_title: 'COMMIT_OR_PR_TITLE'
377
+ merge_commit_message: 'PR_TITLE'
378
+ merge_commit_title: 'MERGE_MESSAGE'
379
+ security_and_analysis:
380
+ secret_scanning:
381
+ status: 'disabled'
382
+ secret_scanning_push_protection:
383
+ status: 'disabled'
384
+ network_count: 0
385
+ subscribers_count: 1
386
+ ```
387
+
388
+ ### Shape of A RepositoryDetails Fact Check
389
+
390
+ The shape of a RepositoryDetails Fact Check matches the [Shape of a Fact Check](https://www.npmjs.com/package/@spotify/backstage-plugin-soundcheck-backend#shape-of-a-fact-check).
391
+
392
+ The following is an example of the RepositoryDetails fact checks:
393
+
394
+ ```yaml
395
+ soundcheck:
396
+ checks:
397
+ - id: allows_rebase_merge
398
+ rule:
399
+ factRef: github:default/repo_details
400
+ path: $.allow_rebase_merge
401
+ operator: equal
402
+ value: true
403
+ - id: has_less_than_ten_open_issues
404
+ rule:
405
+ factRef: github:default/repo_details
406
+ path: $.open_issues
407
+ operator: lessThan
408
+ value: 10
409
+ ```
410
+
411
+ The following is an example of the Soundcheck program that utilizes these checks:
412
+
413
+ ```yaml
414
+ - id: demo
415
+ name: Demo
416
+ ownerEntityRef: group:default/owning_group
417
+ description: Demonstration of Soundcheck RepositoryDetails Fact Extractor
418
+ levels:
419
+ - ordinal: 1
420
+ name: First level
421
+ description: Checks leveraging Soundcheck's Github RepositoryDetails Fact Extractor
422
+ checks:
423
+ - id: allows_rebase_merge
424
+ name: Allows Rebase Merge
425
+ description: Repository allows rebase merge
426
+ - id: has_less_than_ten_open_issues
427
+ name: Has Less Than 10 Open Issues
428
+ description: Github Repository Has Less Than 10 Open Issues
429
+ ```
@@ -0,0 +1,2 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var s=require("@spotify/backstage-plugin-soundcheck-common"),nt=require("@backstage/catalog-model"),ct=require("@octokit/request-error"),lt=require("@octokit/rest"),N=require("@backstage/integration"),v=require("zod"),ht=require("@backstage/errors"),ft=require("git-url-parse");function dt(t){return t&&typeof t=="object"&&"default"in t?t:{default:t}}var ut=dt(ft),pt=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)},W=(t,e,r)=>(pt(t,e,"read from private field"),r?r.call(t):e.get(t)),gt=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},d;function vt(t){return`Extractor with type '${t}' is not allowed. You may need to update collectors configuration at 'soundcheck.collectors.github'`}class mt{constructor(){gt(this,d,[])}register(e){W(this,d).push(e)}async extract(e,r,a){for(const{predicate:i,extractor:n}of W(this,d))if(i(e))return n.extract(e,r,a);throw new Error(vt(e.type))}toString(){return`predicateMux{extractors=${W(this,d).map(e=>e.extractor).join(",")}`}}d=new WeakMap;class wt{static create(e){const{logger:r,branchHelper:a,factories:i}=e,n=new mt;for(const k of i!=null?i:[]){const S=k({logger:r,branchHelper:a});n.register(S)}return n}}var F=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)},U=(t,e,r)=>(F(t,e,"read from private field"),r?r.call(t):e.get(t)),M=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},J=(t,e,r,a)=>(F(t,e,"write to private field"),a?a.call(t,r):e.set(t,r),r),yt=(t,e,r)=>(F(t,e,"access private method"),r),m,w,R,A;const L=class{constructor(t,e){M(this,R),M(this,m,void 0),M(this,w,void 0),J(this,m,t),J(this,w,e)}async extract(t,e,r){const a={owner:e.owner,repo:e.name,branch:await yt(this,R,A).call(this,t,e,r)};try{return(await r.rest.repos.getBranchProtection(a)).data}catch(i){return U(this,m).error(`[BranchProtections] fact extraction failed with: ${i}. Request parameters: ${JSON.stringify(a)}`),i instanceof ct.RequestError&&i.status===404?{}:void 0}}};let Y=L;m=new WeakMap,w=new WeakMap,R=new WeakSet,A=async function(t,e,r){return t.branch?t.branch:U(this,w).getDefaultBranch(e,r)},Y.factory=({logger:t,branchHelper:e})=>{const r=new L(t,e);return{predicate:a=>a.type==="BranchProtections",extractor:r}};var K=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)},$t=(t,e,r)=>(K(t,e,"read from private field"),r?r.call(t):e.get(t)),bt=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},Et=(t,e,r,a)=>(K(t,e,"write to private field"),a?a.call(t,r):e.set(t,r),r),y;const Q=class{constructor(t){bt(this,y,void 0),Et(this,y,t)}async extract(t,e,r){const a={owner:e.owner,repo:e.name};try{return(await r.rest.repos.get(a)).data}catch(i){$t(this,y).error(`[RepositoryDetails] fact extraction failed with: ${i}. Request parameters: ${JSON.stringify(a)}`);return}}};let V=Q;y=new WeakMap,V.factory=({logger:t})=>{const e=new Q(t);return{predicate:r=>r.type==="RepositoryDetails",extractor:e}};const xt=s.BaseFactExtractorSchema.and(v.z.object({type:v.z.string().regex(new RegExp("BranchProtections"))})),Ct=s.BaseFactExtractorSchema.and(v.z.object({type:v.z.string().regex(new RegExp("RepositoryDetails"))})),kt=xt.or(Ct),St=s.buildFactCollectorSchema(kt);var q=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)},P=(t,e,r)=>(q(t,e,"read from private field"),r?r.call(t):e.get(t)),T=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},B=(t,e,r,a)=>(q(t,e,"write to private field"),a?a.call(t,r):e.set(t,r),r),X=(t,e,r)=>(q(t,e,"access private method"),r),h,$,b,D;const Z=class{constructor(t,e){T(this,b),T(this,h,void 0),T(this,$,void 0);var r;B(this,$,t),B(this,h,X(this,b,D).call(this,e)),(r=e.subscribe)==null||r.call(e,()=>{B(this,h,X(this,b,D).call(this,e))})}static create(t,e){return new Z(t,e)}getExtractorConfig(t){return P(this,h)[t]}getExtractorConfigs(){return Object.values(P(this,h))}};let Wt=Z;h=new WeakMap,$=new WeakMap,b=new WeakSet,D=function(t){const e=t.getOptional("soundcheck.collectors.github");if(!e)return{};const r=St.safeParse(e);if(!r.success)throw P(this,$).error("Failed to parse GithubFactCollector from schema."),new ht.InputError(r.error.message);return s.buildExtractorConfigMap(r.data)};var tt=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)},O=(t,e,r)=>(tt(t,e,"read from private field"),r?r.call(t):e.get(t)),et=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},rt=(t,e,r,a)=>(tt(t,e,"write to private field"),a?a.call(t,r):e.set(t,r),r),E,u;const at=class{constructor(t,e){et(this,E,void 0),et(this,u,void 0),rt(this,E,t),rt(this,u,e)}static create(t,e){return new at(t,e)}async getDefaultBranch(t,e){let r=await O(this,u).get(t.full_name);if(r!==void 0)return r;const a={owner:t.owner,repo:t.name};try{r=(await e.rest.repos.get(a)).data.default_branch,await O(this,u).set(t.full_name,r)}catch(i){t.ref?r=t.ref:r="main",O(this,E).error(`Failed to get default branch name with: ${i}. Request parameters: ${JSON.stringify(a)}. Falling back to using [${r}] as default branch`)}return r}};let Ft=at;E=new WeakMap,u=new WeakMap;var _=(t,e,r)=>{if(!e.has(t))throw TypeError("Cannot "+r)},o=(t,e,r)=>(_(t,e,"read from private field"),r?r.call(t):e.get(t)),f=(t,e,r)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,r)},p=(t,e,r,a)=>(_(t,e,"write to private field"),a?a.call(t,r):e.set(t,r),r),Mt=(t,e,r)=>(_(t,e,"access private method"),r),c,g,x,C,l,j,it;const G=class{constructor(t,e,r){f(this,j),f(this,c,void 0),f(this,g,void 0),f(this,x,void 0),f(this,C,void 0),f(this,l,void 0),this.id=G.ID,p(this,c,e.child({target:this.id})),p(this,g,N.ScmIntegrations.fromConfig(t)),p(this,x,N.DefaultGithubCredentialsProvider.fromIntegrations(o(this,g))),p(this,C,wt.create({logger:o(this,c),branchHelper:Ft.create(o(this,c),r.getClient({defaultTtl:60*60*1e3})),factories:[Y.factory,V.factory]})),p(this,l,Wt.create(o(this,c),t))}static create(t,e,r){return new G(t,e,r)}async collect(t,e){const r=Mt(this,j,it).call(this,e);return Promise.all(t.filter(a=>s.isScmEntity(a)).map(async a=>{const i=nt.stringifyEntityRef(a),n=s.getEntityScmUrl(a),k=ut.default(n),S=await this.getOctokit(n);return Promise.all(r.map(async z=>{const H=s.getFactRef(this.id,z),I=await o(this,C).extract(z,k,S).catch(st=>{o(this,c).error(`Failed to collect ${H} fact data for ${i} entity: ${st}`)});if(I!==void 0)return s.buildFact(i,H,I)}))})).then(a=>a.flat().filter(i=>!!i))}async getOctokit(t){const{token:e}=await o(this,x).getCredentials({url:t}),r=o(this,g).github.byUrl(t);return new lt.Octokit({auth:e,baseUrl:r==null?void 0:r.config.apiBaseUrl})}async getCollectionConfigs(){return s.buildCollectionConfigs(this.id,o(this,l).getExtractorConfigs())}async getFactNames(){return o(this,l).getExtractorConfigs().map(t=>t.factName)}async getDataSchema(t){}};let ot=G;c=new WeakMap,g=new WeakMap,x=new WeakMap,C=new WeakMap,l=new WeakMap,j=new WeakSet,it=function(t){return t!=null&&t.factRefs?o(this,l).getExtractorConfigs().filter(e=>{var r,a;return(a=(r=t==null?void 0:t.factRefs)==null?void 0:r.map(s.stringifyFactRef))==null?void 0:a.includes(s.getFactRef(this.id,e))}):o(this,l).getExtractorConfigs()},ot.ID="github",exports.GithubFactCollector=ot;
2
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1,36 @@
1
+ import { FactRef, Fact, CollectionConfig } from '@spotify/backstage-plugin-soundcheck-common';
2
+ import { Entity } from '@backstage/catalog-model';
3
+ import { Logger } from 'winston';
4
+ import { Config } from '@backstage/config';
5
+ import { PluginCacheManager } from '@backstage/backend-common';
6
+ import { FactCollector } from '@spotify/backstage-plugin-soundcheck-node';
7
+
8
+ /**
9
+ * Soundcheck 3rd party integration - Github.
10
+ *
11
+ * @public
12
+ */
13
+ declare class GithubFactCollector implements FactCollector {
14
+ #private;
15
+ /**
16
+ * Source identifier used by the {@link GithubFactCollector}.
17
+ */
18
+ static ID: string;
19
+ static create(config: Config, logger: Logger, cache: PluginCacheManager): GithubFactCollector;
20
+ /** {@inheritDoc @spotify/backstage-plugin-soundcheck-node#FactCollector.id} */
21
+ id: string;
22
+ private constructor();
23
+ /** {@inheritDoc @spotify/backstage-plugin-soundcheck-node#FactCollector.collect} */
24
+ collect(entities: Entity[], params?: {
25
+ factRefs?: FactRef[];
26
+ refresh?: FactRef[];
27
+ }): Promise<Fact[]>;
28
+ /** {@inheritDoc @spotify/backstage-plugin-soundcheck-node#FactCollector.getCollectionConfigs} */
29
+ getCollectionConfigs(): Promise<CollectionConfig[]>;
30
+ /** {@inheritDoc @spotify/backstage-plugin-soundcheck-node#FactCollector.getFactNames} */
31
+ getFactNames(): Promise<string[]>;
32
+ /** {@inheritDoc @spotify/backstage-plugin-soundcheck-node#FactCollector.getDataSchema} */
33
+ getDataSchema(_factRef: FactRef): Promise<string | undefined>;
34
+ }
35
+
36
+ export { GithubFactCollector };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@spotify/backstage-plugin-soundcheck-backend-module-github",
3
+ "description": "Soundcheck 3rd party integration with Github",
4
+ "version": "0.1.0",
5
+ "license": "SEE LICENSE IN LICENSE.md",
6
+ "homepage": "https://backstage.spotify.com/",
7
+ "main": "dist/index.cjs.js",
8
+ "types": "dist/index.d.ts",
9
+ "publishConfig": {
10
+ "main": "dist/index.cjs.js",
11
+ "types": "dist/index.d.ts"
12
+ },
13
+ "backstage": {
14
+ "role": "node-library"
15
+ },
16
+ "scripts": {
17
+ "build": "backstage-cli package build --minify",
18
+ "lint": "backstage-cli package lint",
19
+ "test": "backstage-cli package test",
20
+ "clean": "backstage-cli package clean",
21
+ "prepack": "backstage-cli package prepack",
22
+ "postpack": "backstage-cli package postpack"
23
+ },
24
+ "devDependencies": {
25
+ "@backstage/cli": "^0.22.4",
26
+ "@types/git-url-parse": "^9.0.0"
27
+ },
28
+ "dependencies": {
29
+ "@backstage/backend-common": "^0.18.3",
30
+ "@backstage/catalog-model": "^1.2.1",
31
+ "@backstage/config": "^1.0.7",
32
+ "@backstage/errors": "^1.1.5",
33
+ "@backstage/integration": "^1.4.3",
34
+ "@backstage/types": "^1.0.2",
35
+ "@octokit/request-error": "^3.0.3",
36
+ "@octokit/rest": "^19.0.3",
37
+ "@spotify/backstage-plugin-soundcheck-common": "^0.4.0",
38
+ "@spotify/backstage-plugin-soundcheck-node": "^0.2.0",
39
+ "git-url-parse": "^13.0.0",
40
+ "lodash": "^4.17.21",
41
+ "winston": "^3.2.1",
42
+ "zod": "^3.20.0"
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "!dist/**/*.map"
47
+ ]
48
+ }