lex-github 0.2.5 → 0.3.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/CLAUDE.md +45 -19
  4. data/README.md +155 -83
  5. data/lex-github.gemspec +2 -0
  6. data/lib/legion/extensions/github/app/actor/token_refresh.rb +68 -0
  7. data/lib/legion/extensions/github/app/actor/webhook_poller.rb +65 -0
  8. data/lib/legion/extensions/github/app/hooks/setup.rb +19 -0
  9. data/lib/legion/extensions/github/app/hooks/webhook.rb +19 -0
  10. data/lib/legion/extensions/github/app/runners/auth.rb +48 -0
  11. data/lib/legion/extensions/github/app/runners/credential_store.rb +46 -0
  12. data/lib/legion/extensions/github/app/runners/installations.rb +56 -0
  13. data/lib/legion/extensions/github/app/runners/manifest.rb +65 -0
  14. data/lib/legion/extensions/github/app/runners/webhooks.rb +118 -0
  15. data/lib/legion/extensions/github/app/transport/exchanges/app.rb +17 -0
  16. data/lib/legion/extensions/github/app/transport/messages/event.rb +18 -0
  17. data/lib/legion/extensions/github/app/transport/queues/auth.rb +18 -0
  18. data/lib/legion/extensions/github/app/transport/queues/webhooks.rb +18 -0
  19. data/lib/legion/extensions/github/cli/app.rb +57 -0
  20. data/lib/legion/extensions/github/cli/auth.rb +99 -0
  21. data/lib/legion/extensions/github/client.rb +24 -0
  22. data/lib/legion/extensions/github/errors.rb +44 -0
  23. data/lib/legion/extensions/github/helpers/browser_auth.rb +106 -0
  24. data/lib/legion/extensions/github/helpers/cache.rb +99 -0
  25. data/lib/legion/extensions/github/helpers/callback_server.rb +89 -0
  26. data/lib/legion/extensions/github/helpers/client.rb +292 -2
  27. data/lib/legion/extensions/github/helpers/scope_registry.rb +91 -0
  28. data/lib/legion/extensions/github/helpers/token_cache.rb +86 -0
  29. data/lib/legion/extensions/github/middleware/credential_fallback.rb +76 -0
  30. data/lib/legion/extensions/github/middleware/rate_limit.rb +40 -0
  31. data/lib/legion/extensions/github/middleware/scope_probe.rb +37 -0
  32. data/lib/legion/extensions/github/oauth/actor/token_refresh.rb +76 -0
  33. data/lib/legion/extensions/github/oauth/hooks/callback.rb +19 -0
  34. data/lib/legion/extensions/github/oauth/runners/auth.rb +111 -0
  35. data/lib/legion/extensions/github/oauth/transport/exchanges/oauth.rb +17 -0
  36. data/lib/legion/extensions/github/oauth/transport/queues/auth.rb +18 -0
  37. data/lib/legion/extensions/github/runners/actions.rb +100 -0
  38. data/lib/legion/extensions/github/runners/branches.rb +5 -3
  39. data/lib/legion/extensions/github/runners/checks.rb +84 -0
  40. data/lib/legion/extensions/github/runners/comments.rb +13 -7
  41. data/lib/legion/extensions/github/runners/commits.rb +11 -6
  42. data/lib/legion/extensions/github/runners/contents.rb +3 -1
  43. data/lib/legion/extensions/github/runners/deployments.rb +76 -0
  44. data/lib/legion/extensions/github/runners/gists.rb +9 -4
  45. data/lib/legion/extensions/github/runners/issues.rb +16 -9
  46. data/lib/legion/extensions/github/runners/labels.rb +16 -9
  47. data/lib/legion/extensions/github/runners/organizations.rb +10 -8
  48. data/lib/legion/extensions/github/runners/pull_requests.rb +24 -14
  49. data/lib/legion/extensions/github/runners/releases.rb +89 -0
  50. data/lib/legion/extensions/github/runners/repositories.rb +17 -10
  51. data/lib/legion/extensions/github/runners/repository_webhooks.rb +76 -0
  52. data/lib/legion/extensions/github/runners/search.rb +11 -8
  53. data/lib/legion/extensions/github/runners/users.rb +12 -8
  54. data/lib/legion/extensions/github/version.rb +1 -1
  55. data/lib/legion/extensions/github.rb +22 -0
  56. metadata +63 -1
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'openssl'
5
+ require 'legion/extensions/github/helpers/client'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Github
10
+ module App
11
+ module Runners
12
+ module Auth
13
+ include Legion::Extensions::Github::Helpers::Client
14
+
15
+ def generate_jwt(app_id:, private_key:, **)
16
+ key = OpenSSL::PKey::RSA.new(private_key)
17
+ now = Time.now.to_i
18
+ payload = { iat: now - 60, exp: now + (10 * 60), iss: app_id.to_s }
19
+ token = JWT.encode(payload, key, 'RS256')
20
+ { result: token }
21
+ end
22
+
23
+ def create_installation_token(jwt:, installation_id:, **)
24
+ conn = connection(token: jwt, **)
25
+ response = conn.post("/app/installations/#{installation_id}/access_tokens")
26
+ { result: response.body }
27
+ end
28
+
29
+ def list_installations(jwt:, per_page: 30, page: 1, **)
30
+ conn = connection(token: jwt, **)
31
+ response = conn.get('/app/installations', per_page: per_page, page: page)
32
+ { result: response.body }
33
+ end
34
+
35
+ def get_installation(jwt:, installation_id:, **)
36
+ conn = connection(token: jwt, **)
37
+ response = conn.get("/app/installations/#{installation_id}")
38
+ { result: response.body }
39
+ end
40
+
41
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
42
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Github
8
+ module App
9
+ module Runners
10
+ module CredentialStore
11
+ def store_app_credentials(app_id:, private_key:, client_id:, client_secret:, webhook_secret:, **)
12
+ vault_set('github/app/app_id', app_id)
13
+ vault_set('github/app/private_key', private_key)
14
+ vault_set('github/app/client_id', client_id)
15
+ vault_set('github/app/client_secret', client_secret)
16
+ vault_set('github/app/webhook_secret', webhook_secret)
17
+ { result: true }
18
+ end
19
+
20
+ def store_oauth_token(user:, access_token:, refresh_token:, expires_in: nil, scope: nil, **)
21
+ data = { 'access_token' => access_token, 'refresh_token' => refresh_token,
22
+ 'expires_in' => expires_in, 'scope' => scope,
23
+ 'stored_at' => Time.now.iso8601 }.compact
24
+ vault_set("github/oauth/#{user}/token", data)
25
+ # Also write to canonical delegated path so resolve_vault_delegated can discover the token
26
+ vault_set('github/oauth/delegated/token', data)
27
+ { result: true }
28
+ end
29
+
30
+ def load_oauth_token(user:, **)
31
+ data = begin
32
+ vault_get("github/oauth/#{user}/token")
33
+ rescue StandardError => _e
34
+ nil
35
+ end
36
+ { result: data }
37
+ end
38
+
39
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
40
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/github/helpers/client'
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Github
8
+ module App
9
+ module Runners
10
+ module Installations
11
+ include Legion::Extensions::Github::Helpers::Client
12
+
13
+ def list_installations(jwt:, per_page: 30, page: 1, **)
14
+ conn = connection(token: jwt, **)
15
+ response = conn.get('/app/installations', per_page: per_page, page: page)
16
+ { result: response.body }
17
+ end
18
+
19
+ def get_installation(jwt:, installation_id:, **)
20
+ conn = connection(token: jwt, **)
21
+ response = conn.get("/app/installations/#{installation_id}")
22
+ { result: response.body }
23
+ end
24
+
25
+ def list_installation_repos(per_page: 30, page: 1, **)
26
+ response = connection(**).get('/installation/repositories',
27
+ per_page: per_page, page: page)
28
+ { result: response.body }
29
+ end
30
+
31
+ def suspend_installation(jwt:, installation_id:, **)
32
+ conn = connection(token: jwt, **)
33
+ response = conn.put("/app/installations/#{installation_id}/suspended")
34
+ { result: response.status == 204 }
35
+ end
36
+
37
+ def unsuspend_installation(jwt:, installation_id:, **)
38
+ conn = connection(token: jwt, **)
39
+ response = conn.delete("/app/installations/#{installation_id}/suspended")
40
+ { result: response.status == 204 }
41
+ end
42
+
43
+ def delete_installation(jwt:, installation_id:, **)
44
+ conn = connection(token: jwt, **)
45
+ response = conn.delete("/app/installations/#{installation_id}")
46
+ { result: response.status == 204 }
47
+ end
48
+
49
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
50
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'uri'
5
+ require 'legion/extensions/github/helpers/client'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Github
10
+ module App
11
+ module Runners
12
+ module Manifest
13
+ include Legion::Extensions::Github::Helpers::Client
14
+
15
+ DEFAULT_PERMISSIONS = {
16
+ contents: 'write', issues: 'write', pull_requests: 'write',
17
+ metadata: 'read', administration: 'write', members: 'read',
18
+ checks: 'write', statuses: 'write', actions: 'read',
19
+ workflows: 'write', webhooks: 'write', repository_hooks: 'write'
20
+ }.freeze
21
+
22
+ DEFAULT_EVENTS = %w[
23
+ push pull_request pull_request_review issues issue_comment
24
+ create delete check_run check_suite status workflow_run
25
+ repository installation
26
+ ].freeze
27
+
28
+ def generate_manifest(name:, url:, webhook_url:, callback_url:,
29
+ permissions: DEFAULT_PERMISSIONS, events: DEFAULT_EVENTS,
30
+ public: true, **)
31
+ manifest = {
32
+ name: name, url: url, public: public,
33
+ hook_attributes: { url: webhook_url, active: true },
34
+ setup_url: callback_url,
35
+ redirect_url: callback_url,
36
+ default_permissions: permissions,
37
+ default_events: events
38
+ }
39
+ { result: manifest }
40
+ end
41
+
42
+ def exchange_manifest_code(code:, **)
43
+ conn = connection(**)
44
+ response = conn.post("/app-manifests/#{code}/conversions")
45
+ { result: response.body }
46
+ end
47
+
48
+ def manifest_url(manifest:, org: nil, **)
49
+ base = if org
50
+ "https://github.com/organizations/#{org}/settings/apps/new"
51
+ else
52
+ 'https://github.com/settings/apps/new'
53
+ end
54
+ json_str = ::JSON.generate(manifest)
55
+ { result: "#{base}?manifest=#{URI.encode_www_form_component(json_str)}" }
56
+ end
57
+
58
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
59
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'openssl'
5
+ require 'legion/extensions/github/helpers/client'
6
+
7
+ module Legion
8
+ module Extensions
9
+ module Github
10
+ module App
11
+ module Runners
12
+ module Webhooks
13
+ include Legion::Extensions::Github::Helpers::Client
14
+
15
+ def verify_signature(payload:, signature:, secret:, **)
16
+ return { result: false } if signature.nil? || signature.empty?
17
+
18
+ expected = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', secret, payload)}"
19
+ # Use constant-time comparison to prevent timing side-channel attacks.
20
+ # Pad to equal length so fixed_length_secure_compare can be used safely.
21
+ result = expected.length == signature.length &&
22
+ OpenSSL.fixed_length_secure_compare(expected, signature)
23
+ { result: result }
24
+ end
25
+
26
+ def parse_event(payload:, event_type:, delivery_id:, **)
27
+ parsed = payload.is_a?(String) ? ::JSON.parse(payload) : payload
28
+ { result: { event_type: event_type, delivery_id: delivery_id, payload: parsed } }
29
+ end
30
+
31
+ def receive_event(payload:, signature:, secret:, event_type:, delivery_id:, **)
32
+ verified = verify_signature(payload: payload, signature: signature, secret: secret)[:result]
33
+ unless verified
34
+ return { result: { verified: false, event_type: event_type, delivery_id: delivery_id,
35
+ payload: nil } }
36
+ end
37
+
38
+ parsed = parse_event(payload: payload, event_type: event_type, delivery_id: delivery_id)[:result]
39
+ invalidate_scopes_for_event(event_type: event_type, payload: parsed[:payload])
40
+ { result: parsed.merge(verified: true) }
41
+ end
42
+
43
+ SCOPE_INVALIDATION_EVENTS = %w[installation installation_repositories].freeze
44
+
45
+ def invalidate_scopes_for_event(event_type:, payload:, **)
46
+ return unless SCOPE_INVALIDATION_EVENTS.include?(event_type.to_s)
47
+
48
+ owner = payload&.dig('installation', 'account', 'login')
49
+ return unless owner
50
+
51
+ invalidate_all_scopes_for_owner(owner: owner)
52
+ end
53
+
54
+ def invalidate_all_scopes_for_owner(owner:)
55
+ known_fingerprints = resolve_known_fingerprints
56
+ known_fingerprints.each do |fp|
57
+ invalidate_scope(fingerprint: fp, owner: owner)
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def resolve_known_fingerprints
64
+ fingerprints = []
65
+
66
+ # Delegated (OAuth user) — check Vault and settings without resolving tokens
67
+ fingerprints << credential_fingerprint(auth_type: :oauth_user, identifier: 'vault_delegated') if vault_delegated_configured?
68
+ fingerprints << credential_fingerprint(auth_type: :oauth_user, identifier: 'settings_delegated') if settings_delegated_configured?
69
+
70
+ # App installation — derive from app_id without generating installation tokens
71
+ if (vault_app_id = safe_vault_get('github/app/app_id'))
72
+ fingerprints << credential_fingerprint(auth_type: :app_installation, identifier: "vault_app_#{vault_app_id}")
73
+ end
74
+ if (settings_app_id = safe_settings_dig(:github, :app, :app_id))
75
+ fingerprints << credential_fingerprint(auth_type: :app_installation, identifier: "settings_app_#{settings_app_id}")
76
+ end
77
+
78
+ # PAT
79
+ fingerprints << credential_fingerprint(auth_type: :pat, identifier: 'vault_pat') if safe_vault_get('github/token')
80
+ fingerprints << credential_fingerprint(auth_type: :pat, identifier: 'settings_pat') if safe_settings_dig(:github, :token)
81
+
82
+ # CLI and ENV
83
+ fingerprints << credential_fingerprint(auth_type: :cli, identifier: 'gh_cli')
84
+ fingerprints << credential_fingerprint(auth_type: :env, identifier: 'env')
85
+
86
+ fingerprints.uniq
87
+ rescue StandardError => _e
88
+ []
89
+ end
90
+
91
+ def vault_delegated_configured?
92
+ defined?(Legion::Crypt) && safe_vault_get('github/oauth/delegated/token')
93
+ end
94
+
95
+ def settings_delegated_configured?
96
+ defined?(Legion::Settings) && safe_settings_dig(:github, :oauth, :access_token)
97
+ end
98
+
99
+ def safe_vault_get(path)
100
+ vault_get(path) if defined?(Legion::Crypt)
101
+ rescue StandardError => _e
102
+ nil
103
+ end
104
+
105
+ def safe_settings_dig(*keys)
106
+ Legion::Settings.dig(*keys) if defined?(Legion::Settings)
107
+ rescue StandardError => _e
108
+ nil
109
+ end
110
+
111
+ include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers, false) &&
112
+ Legion::Extensions::Helpers.const_defined?(:Lex, false)
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Github
6
+ module App
7
+ module Transport
8
+ module Exchanges
9
+ class App < Legion::Transport::Exchange
10
+ def exchange_name = 'lex.github.app'
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Github
6
+ module App
7
+ module Transport
8
+ module Messages
9
+ class Event < Legion::Transport::Message
10
+ def routing_key = 'lex.github.app.runners.webhooks'
11
+ def exchange = Legion::Extensions::Github::App::Transport::Exchanges::App
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Github
6
+ module App
7
+ module Transport
8
+ module Queues
9
+ class Auth < Legion::Transport::Queue
10
+ def queue_name = 'lex.github.app.runners.auth'
11
+ def queue_options = { auto_delete: false }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Github
6
+ module App
7
+ module Transport
8
+ module Queues
9
+ class Webhooks < Legion::Transport::Queue
10
+ def queue_name = 'lex.github.app.runners.webhooks'
11
+ def queue_options = { auto_delete: false }
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/github/helpers/client'
4
+ require 'legion/extensions/github/helpers/callback_server'
5
+ require 'legion/extensions/github/app/runners/manifest'
6
+ require 'legion/extensions/github/app/runners/credential_store'
7
+
8
+ module Legion
9
+ module Extensions
10
+ module Github
11
+ module CLI
12
+ module App
13
+ include Helpers::Client
14
+ include Github::App::Runners::Manifest
15
+ include Github::App::Runners::CredentialStore
16
+
17
+ def setup(name:, url:, webhook_url:, org: nil, callback_timeout: 300, **)
18
+ server = Helpers::CallbackServer.new
19
+ server.start
20
+ callback_url = server.redirect_uri
21
+
22
+ manifest = generate_manifest(
23
+ name: name, url: url,
24
+ webhook_url: webhook_url,
25
+ callback_url: callback_url
26
+ )[:result]
27
+
28
+ url_result = manifest_url(manifest: manifest, org: org)[:result]
29
+
30
+ { result: { manifest_url: url_result, callback_port: server.port,
31
+ message: 'Open the manifest URL in your browser to create the GitHub App',
32
+ callback: server.wait_for_callback(timeout: callback_timeout) } }
33
+ ensure
34
+ server&.shutdown
35
+ end
36
+
37
+ def complete_setup(code:, **)
38
+ result = exchange_manifest_code(code: code)[:result]
39
+ return { error: 'exchange_failed' } unless result&.dig('id')
40
+
41
+ if respond_to?(:store_app_credentials, true)
42
+ store_app_credentials(
43
+ app_id: result['id'].to_s,
44
+ private_key: result['pem'],
45
+ client_id: result['client_id'],
46
+ client_secret: result['client_secret'],
47
+ webhook_secret: result['webhook_secret']
48
+ )
49
+ end
50
+
51
+ { result: result }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/github/helpers/client'
4
+ require 'legion/extensions/github/helpers/browser_auth'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Github
9
+ module CLI
10
+ module Auth
11
+ include Helpers::Client
12
+
13
+ def login(client_id: nil, client_secret: nil, scopes: nil, **)
14
+ cid = client_id || settings_client_id
15
+ csec = client_secret || settings_client_secret
16
+ sc = scopes || settings_scopes
17
+
18
+ unless cid && csec
19
+ return { error: 'missing_config',
20
+ description: 'Set github.oauth.client_id or github.app.client_id and github.app.client_secret in settings or pass as arguments' }
21
+ end
22
+
23
+ browser = Helpers::BrowserAuth.new(client_id: cid, client_secret: csec, scopes: sc)
24
+ result = browser.authenticate
25
+
26
+ if result[:result]&.dig('access_token') && respond_to?(:store_oauth_token, true)
27
+ user = begin
28
+ current_user(token: result[:result]['access_token'])
29
+ rescue StandardError => _e
30
+ 'default'
31
+ end
32
+ store_oauth_token(
33
+ user: user,
34
+ access_token: result[:result]['access_token'],
35
+ refresh_token: result[:result]['refresh_token'],
36
+ expires_in: result[:result]['expires_in']
37
+ )
38
+ end
39
+
40
+ result
41
+ end
42
+
43
+ def status(**)
44
+ cred = resolve_credential
45
+ return { result: { authenticated: false } } unless cred
46
+
47
+ user_info = {}
48
+ scopes = nil
49
+
50
+ begin
51
+ response = connection(token: cred[:token]).get('/user')
52
+ user_info = response.body || {}
53
+ headers = response.respond_to?(:headers) ? response.headers : {}
54
+ scopes_header = headers['X-OAuth-Scopes'] || headers['x-oauth-scopes']
55
+ scopes = scopes_header&.split(',')&.map(&:strip)
56
+ rescue StandardError => _e
57
+ user_info = {}
58
+ scopes = nil
59
+ end
60
+
61
+ { result: { authenticated: true, auth_type: cred[:auth_type],
62
+ user: user_info['login'], scopes: scopes } }
63
+ end
64
+
65
+ private
66
+
67
+ def current_user(token:)
68
+ connection(token: token).get('/user').body['login']
69
+ end
70
+
71
+ def settings_client_id
72
+ return nil unless defined?(Legion::Settings)
73
+
74
+ Legion::Settings.dig(:github, :oauth, :client_id) ||
75
+ Legion::Settings.dig(:github, :app, :client_id)
76
+ rescue StandardError => _e
77
+ nil
78
+ end
79
+
80
+ def settings_client_secret
81
+ return nil unless defined?(Legion::Settings)
82
+
83
+ Legion::Settings.dig(:github, :app, :client_secret)
84
+ rescue StandardError => _e
85
+ nil
86
+ end
87
+
88
+ def settings_scopes
89
+ return nil unless defined?(Legion::Settings)
90
+
91
+ Legion::Settings.dig(:github, :oauth, :scopes)
92
+ rescue StandardError => _e
93
+ nil
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'legion/extensions/github/helpers/client'
4
+ require 'legion/extensions/github/helpers/cache'
4
5
  require 'legion/extensions/github/runners/repositories'
5
6
  require 'legion/extensions/github/runners/issues'
6
7
  require 'legion/extensions/github/runners/pull_requests'
@@ -13,12 +14,24 @@ require 'legion/extensions/github/runners/labels'
13
14
  require 'legion/extensions/github/runners/comments'
14
15
  require 'legion/extensions/github/runners/branches'
15
16
  require 'legion/extensions/github/runners/contents'
17
+ require 'legion/extensions/github/runners/actions'
18
+ require 'legion/extensions/github/runners/checks'
19
+ require 'legion/extensions/github/runners/releases'
20
+ require 'legion/extensions/github/runners/deployments'
21
+ require 'legion/extensions/github/runners/repository_webhooks'
22
+ require 'legion/extensions/github/app/runners/auth'
23
+ require 'legion/extensions/github/app/runners/webhooks'
24
+ require 'legion/extensions/github/app/runners/manifest'
25
+ require 'legion/extensions/github/app/runners/installations'
26
+ require 'legion/extensions/github/app/runners/credential_store'
27
+ require 'legion/extensions/github/oauth/runners/auth'
16
28
 
17
29
  module Legion
18
30
  module Extensions
19
31
  module Github
20
32
  class Client
21
33
  include Helpers::Client
34
+ include Helpers::Cache
22
35
  include Runners::Repositories
23
36
  include Runners::Issues
24
37
  include Runners::PullRequests
@@ -31,6 +44,17 @@ module Legion
31
44
  include Runners::Comments
32
45
  include Runners::Branches
33
46
  include Runners::Contents
47
+ include Runners::Actions
48
+ include Runners::Checks
49
+ include Runners::Releases
50
+ include Runners::Deployments
51
+ include Runners::RepositoryWebhooks
52
+ include App::Runners::Auth
53
+ include App::Runners::Webhooks
54
+ include App::Runners::Manifest
55
+ include App::Runners::Installations
56
+ include App::Runners::CredentialStore
57
+ include OAuth::Runners::Auth
34
58
 
35
59
  attr_reader :opts
36
60
 
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Github
6
+ class Error < StandardError; end
7
+
8
+ class RateLimitError < Error
9
+ attr_reader :reset_at, :credential_fingerprint
10
+
11
+ def initialize(message = 'GitHub API rate limit exceeded', reset_at: nil, credential_fingerprint: nil)
12
+ @reset_at = reset_at
13
+ @credential_fingerprint = credential_fingerprint
14
+ super(message)
15
+ end
16
+ end
17
+
18
+ class AuthorizationError < Error
19
+ attr_reader :owner, :repo, :attempted_sources
20
+
21
+ def initialize(message = 'No authorized credential available', owner: nil, repo: nil,
22
+ attempted_sources: [])
23
+ @owner = owner
24
+ @repo = repo
25
+ @attempted_sources = attempted_sources
26
+ super(message)
27
+ end
28
+ end
29
+
30
+ class ScopeDeniedError < Error
31
+ attr_reader :owner, :repo, :credential_fingerprint, :auth_type
32
+
33
+ def initialize(message = 'Credential not authorized for this scope',
34
+ owner: nil, repo: nil, credential_fingerprint: nil, auth_type: nil)
35
+ @owner = owner
36
+ @repo = repo
37
+ @credential_fingerprint = credential_fingerprint
38
+ @auth_type = auth_type
39
+ super(message)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end