ghx 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54d0617ded0141f352b6e27b38245f8d269546093d328ffa8428cef0cc45ae0f
4
- data.tar.gz: 52bd1ef5a4c214203938a1dbbadf487ac49c5fc398ccc17045ec2c400425ff80
3
+ metadata.gz: 2b80c109f50e1c94cc2aa0bd727a2dbe547b66080380a2847132bc7cda0b34b8
4
+ data.tar.gz: 179db655ee4d499eb53912cc3a09ebc7ff5c9100d650b4ef6567ce2a8ff9acb7
5
5
  SHA512:
6
- metadata.gz: 14df558d839c2efb46d68b78482b30c7c6b26ba86be9c1c38a6e4d22202678f5e19f1a2f4a56f713060ba216523c75b2ee57945e590e10407059c5c9ce1a3382
7
- data.tar.gz: 14c21c92397ade112c51e2bef0253bcd5d61a28a5b28b776c699fb0b8c6af392d9e611c03bb0aaa1575bf1ec075e7b337f2e717fe9dd4a85cb0995280f3d7932
6
+ metadata.gz: 22bbefe3f68291efca16223b120575cf64997a9f2f07cc09963f0454314c423933d4cbbaf4b5c26b267d292e560114ca71e6f8faf08e7ad84e016c70229edd3e
7
+ data.tar.gz: 1082fc4c645cf94a660305ef1e0f7fdaff8c06ebd7bab58ac74d8acead82cb01cf12c8608681f8019b83ad79e479d602fab0f073270dc4d82017d997ae11f18e
@@ -115,9 +115,11 @@
115
115
 
116
116
  module GHX
117
117
  module Dependabot
118
+ # A Dependabot Alert
118
119
  class Alert
119
120
  attr_reader :number, :state, :dependency, :security_advisory, :security_vulnerability, :url, :html_url, :created_at, :updated_at
120
121
 
122
+ # @param json_data [Hash] The JSON data for the alert, from the API
121
123
  def initialize(json_data)
122
124
  @number = json_data["number"]
123
125
  @state = json_data["state"]
@@ -1,5 +1,6 @@
1
1
  module GHX
2
2
  module Dependabot
3
+ # A package is a dependency that is managed by a package manager. Referenced by a SecurityVulnerability.
3
4
  class Package
4
5
  attr_reader :ecosystem, :name
5
6
 
@@ -1,5 +1,6 @@
1
1
  module GHX
2
2
  module Dependabot
3
+ # A SecurityVulnerability is referenced by an Alert
3
4
  class SecurityVulnerability
4
5
  attr_reader :package, :severity, :vulnerable_version_range, :first_patched_version
5
6
 
@@ -1,6 +1,15 @@
1
1
  module GHX
2
+ # Module for Dependabot-related classes and methods
2
3
  module Dependabot
4
+ # Get Dependabot alerts for a given repository
5
+ #
6
+ # @note ONLY RETURNS THE FIRST 100 ALERTS
7
+ # @param owner [String] the owner of the repository
8
+ # @param repo [String] the repository name
9
+ # @return [Array<GHX::Dependabot::Alert>] the alerts
3
10
  def self.get_alerts(owner:, repo:)
11
+ # TODO: Add pagination to get all alerts in one go
12
+
4
13
  GHX.rest_client.get("repos/#{owner}/#{repo}/dependabot/alerts?state=open&per_page=100").map do |alert|
5
14
  GHX::Dependabot::Alert.new(alert)
6
15
  end
data/lib/ghx/errors.rb ADDED
@@ -0,0 +1,7 @@
1
+ module GHX
2
+ class GHXError < StandardError; end
3
+
4
+ class RateLimitExceededError < GHXError; end
5
+
6
+ class OtherApiError < GHXError; end
7
+ end
@@ -1,9 +1,14 @@
1
1
  module GHX
2
+ # Internal class to interact with the GitHub GraphQL API
2
3
  class GraphqlClient
4
+ # @param api_key [String]
3
5
  def initialize(api_key)
4
6
  @api_key = api_key
5
7
  end
6
8
 
9
+ # Perform a GraphQL Query and return the result
10
+ # @param query [String] GraphQL Query
11
+ # @return [Net::HTTPResponse]
7
12
  def query(query)
8
13
  uri = URI("https://api.github.com/graphql")
9
14
  req = Net::HTTP::Post.new(uri)
@@ -15,54 +20,5 @@ module GHX
15
20
  http.request(req)
16
21
  end
17
22
  end
18
-
19
- # @param [String] project_id
20
- # @param [GithubProjectItem] project_item
21
- # @param [DateTime] reported_at
22
- def update_project_item_reported_at(project_item_id:, reported_at:, project_id: GithubProject::MAIN_GH_PROJECT_ID)
23
- field_id = "PVTF_lADOALH_aM4Ac-_zzgSzAZs" # project_item.field_map["Reported At"]
24
-
25
- gql_query = <<~GQL
26
- mutation {
27
- updateProjectV2ItemFieldValue(input: {
28
- fieldId: "#{field_id}",
29
- itemId: "#{project_item_id}",
30
- projectId: "#{project_id}",
31
- value: {
32
- date: "#{reported_at.to_date}"
33
- }
34
- }) {
35
- projectV2Item {
36
- id
37
- }
38
- }
39
- }
40
- GQL
41
-
42
- query(gql_query)
43
- end
44
-
45
- def update_project_item_reported_by(project_item_id:, reported_by:, project_id: GithubProject::MAIN_GH_PROJECT_ID)
46
- field_id = "PVTF_lADOALH_aM4Ac-_zzgSzBcc" # project_item.field_map["Reporter"]
47
-
48
- gql_query = <<~GQL
49
- mutation {
50
- updateProjectV2ItemFieldValue(input: {
51
- fieldId: "#{field_id}",
52
- itemId: "#{project_item_id}",
53
- projectId: "#{project_id}",
54
- value: {
55
- text: "#{reported_by}"
56
- }
57
- }) {
58
- projectV2Item {
59
- id
60
- }
61
- }
62
- }
63
- GQL
64
-
65
- query(gql_query)
66
- end
67
23
  end
68
24
  end
data/lib/ghx/issue.rb CHANGED
@@ -1,29 +1,45 @@
1
1
  module GHX
2
+ # A GitHub Issue
2
3
  class Issue
3
4
  attr_accessor :owner, :repo, :number, :title, :body, :state, :state_reason, :author, :assignees, :labels, :milestone, :created_at, :updated_at, :closed_at
4
5
 
6
+ # Search for issues in a repository
7
+ # @param owner [String] the owner of the repository
8
+ # @param repo [String] the repository name
9
+ # @param query [String] the search query, using GitHub's search syntax
10
+ # @return [Array<Issue>] the issues found
5
11
  def self.search(owner:, repo:, query:)
6
12
  data = GHX.rest_client.get("search/issues?q=#{URI.encode_www_form_component(query)}+is:issue+repo:#{owner}/#{repo}")
7
13
  data.fetch("items").to_a.map do |issue_data|
8
14
  new(owner: owner, repo: repo, **issue_data)
9
15
  end
10
- rescue => e
11
- GHX.logger.error "Error searching for issues with query: #{e.message}"
12
- GHX.logger.error "Received data: #{data}"
13
- []
14
16
  end
15
17
 
18
+ # Find an issue by its number
19
+ # @param owner [String] the owner of the repository
20
+ # @param repo [String] the repository name
21
+ # @param number [Integer] the issue number
22
+ # @return [Issue] the issue found
16
23
  def self.find(owner:, repo:, number:)
17
24
  response_data = GHX.rest_client.get("repos/#{owner}/#{repo}/issues/#{number}")
18
25
  new(owner: owner, repo: repo, **response_data)
19
26
  end
20
27
 
28
+ # @param owner [String] the owner of the repository
29
+ # @param repo [String] the repository name
30
+ # @param **args [Hash] the attributes of the issue you wish to assign
31
+ # @return [Issue] the new issue
21
32
  def initialize(owner:, repo:, **args)
22
33
  @owner = owner
23
34
  @repo = repo
24
35
  update_attributes(args)
25
36
  end
26
37
 
38
+ # Save the issue to GitHub. Handles both creating and updating.
39
+ #
40
+ # If the issue has a number, it will be updated. Otherwise, it will be created.
41
+ #
42
+ # @return [Issue] the saved issue
27
43
  def save
28
44
  @number.nil? ? create : update
29
45
  end
@@ -1,7 +1,14 @@
1
1
  module GHX
2
+ # A GitHub Project Item. This is a single item in a GitHub Project Board.
3
+ #
4
+ # ProjectsV2 are only available via the GraphQL API. This class wraps access to the API and provides a more OO interface.
5
+ #
6
+ # @note Access to ProjectItems should be done largely (if not always) through the Project class.
2
7
  class ProjectItem
3
8
  attr_accessor :id, :project_id, :issue_number, :issue_title, :issue_url, :issue_state, :field_values, :field_map
4
9
 
10
+ # @param field_configuration [Array<Hash>] An array of field configurations. These are the fields that are available to the Project Item. These are provided via the Project itself. It's much easier to access ProjectItems through the project because of this.
11
+ # @param data [Hash] The data from the GraphQL API.
5
12
  def initialize(field_configuration:, data:)
6
13
  _setup_field_configuration(field_configuration)
7
14
 
@@ -33,7 +40,11 @@ module GHX
33
40
  end
34
41
  end
35
42
 
36
- # Updates the given fields to the given values. Makes a GraphQL call per field to do the update.
43
+ # Updates the given fields to the given values. Makes a GraphQL call *per field* to do the update.
44
+ #
45
+ # The implementation wraps access to the various types for each field. Since GraphQL requires us to type match on all
46
+ # requests, this gives us convenience, especially for things like a select field. We can pass in the value, and the
47
+ # method will find the ID of the option and update the field for us.
37
48
  #
38
49
  # @param fields [Hash] A hash of field names to values.
39
50
  def update(**fields)
@@ -138,23 +149,27 @@ module GHX
138
149
 
139
150
  private
140
151
 
152
+ # Parse the field_configuration and set up the instance variables and accessors.
153
+ #
154
+ # Example field_configuration:
155
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCno", :name=>"Title", :data_type=>"TITLE", :options=>nil}
156
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCns", :name=>"Assignees", :data_type=>"ASSIGNEES", :options=>nil}
157
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzAZs", :name=>"Reported At", :data_type=>"DATE", :options=>nil}
158
+ # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCnw", :name=>"Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"f971fb55", :name=>"To triage"}, {:id=>"856cdede", :name=>"Ready to Assign"}, {:id=>"f75ad846", :name=>"Assigned"}, {:id=>"47fc9ee4", :name=>"Fix In progress"}, {:id=>"5ef0dc97", :name=>"Additional Info Requested"}, {:id=>"98236657", :name=>"Done - Fixed"}, {:id=>"98aea6ad", :name=>"Done - Won't Fix"}, {:id=>"a3b4fc3a", :name=>"Duplicate"}, {:id=>"81377549", :name=>"Not a Vulnerability"}]}
159
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn0", :name=>"Labels", :data_type=>"LABELS", :options=>nil}
160
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn4", :name=>"Linked pull requests", :data_type=>"LINKED_PULL_REQUESTS", :options=>nil}
161
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn8", :name=>"Milestone", :data_type=>"MILESTONE", :options=>nil}
162
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoA", :name=>"Repository", :data_type=>"REPOSITORY", :options=>nil}
163
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoM", :name=>"Reviewers", :data_type=>"REVIEWERS", :options=>nil}
164
+ # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCuA", :name=>"Severity", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"79628723", :name=>"Informational"}, {:id=>"153889c6", :name=>"Low"}, {:id=>"093709ee", :name=>"Medium"}, {:id=>"5a00bbe7", :name=>"High"}, {:id=>"00e0bbaf", :name=>"Critical"}, {:id=>"fd986bd9", :name=>"Duplicate"}]}
165
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBcc", :name=>"Reporter", :data_type=>"TEXT", :options=>nil}
166
+ # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBho", :name=>"Resolve By", :data_type=>"DATE", :options=>nil}
167
+ # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgTKjOw", :name=>"Payout Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"53c47c02", :name=>"Ready for Invoice"}, {:id=>"0b8a4629", :name=>"Payout in Process"}, {:id=>"5f356a58", :name=>"Payout Complete"}, {:id=>"368048ac", :name=>"Ineligible for Payout"}]}
168
+ #
169
+ #
141
170
  def _setup_field_configuration(field_configuration)
142
171
  @field_configuration = field_configuration.map { |fc| fc.merge({normalized_name: normalized_field_value_name(fc[:name])}) }
143
172
 
144
- # Example field_configuration:
145
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCno", :name=>"Title", :data_type=>"TITLE", :options=>nil}
146
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCns", :name=>"Assignees", :data_type=>"ASSIGNEES", :options=>nil}
147
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzAZs", :name=>"Reported At", :data_type=>"DATE", :options=>nil}
148
- # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCnw", :name=>"Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"f971fb55", :name=>"To triage"}, {:id=>"856cdede", :name=>"Ready to Assign"}, {:id=>"f75ad846", :name=>"Assigned"}, {:id=>"47fc9ee4", :name=>"Fix In progress"}, {:id=>"5ef0dc97", :name=>"Additional Info Requested"}, {:id=>"98236657", :name=>"Done - Fixed"}, {:id=>"98aea6ad", :name=>"Done - Won't Fix"}, {:id=>"a3b4fc3a", :name=>"Duplicate"}, {:id=>"81377549", :name=>"Not a Vulnerability"}]}
149
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn0", :name=>"Labels", :data_type=>"LABELS", :options=>nil}
150
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn4", :name=>"Linked pull requests", :data_type=>"LINKED_PULL_REQUESTS", :options=>nil}
151
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCn8", :name=>"Milestone", :data_type=>"MILESTONE", :options=>nil}
152
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoA", :name=>"Repository", :data_type=>"REPOSITORY", :options=>nil}
153
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSxCoM", :name=>"Reviewers", :data_type=>"REVIEWERS", :options=>nil}
154
- # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgSxCuA", :name=>"Severity", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"79628723", :name=>"Informational"}, {:id=>"153889c6", :name=>"Low"}, {:id=>"093709ee", :name=>"Medium"}, {:id=>"5a00bbe7", :name=>"High"}, {:id=>"00e0bbaf", :name=>"Critical"}, {:id=>"fd986bd9", :name=>"Duplicate"}]}
155
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBcc", :name=>"Reporter", :data_type=>"TEXT", :options=>nil}
156
- # {:id=>"PVTF_lADOALH_aM4Ac-_zzgSzBho", :name=>"Resolve By", :data_type=>"DATE", :options=>nil}
157
- # {:id=>"PVTSSF_lADOALH_aM4Ac-_zzgTKjOw", :name=>"Payout Status", :data_type=>"SINGLE_SELECT", :options=>[{:id=>"53c47c02", :name=>"Ready for Invoice"}, {:id=>"0b8a4629", :name=>"Payout in Process"}, {:id=>"5f356a58", :name=>"Payout Complete"}, {:id=>"368048ac", :name=>"Ineligible for Payout"}]}
158
173
  @field_configuration.each do |field|
159
174
  next unless field[:name]
160
175
  next if field[:name].to_s.empty?
@@ -1,57 +1,62 @@
1
1
  require "net/http"
2
2
 
3
3
  module GHX
4
+ # RestClient is a simple wrapper around Net::HTTP to make it easier to make API calls to the GitHub REST API.
5
+ #
6
+ # This is necessary because not all GitHub API endpoints are covered by Octokit.
4
7
  class RestClient
5
8
  attr_reader :api_key
6
9
 
10
+ # @param api_key [String] the GitHub API key
7
11
  def initialize(api_key)
8
12
  @api_key = api_key
9
13
  end
10
14
 
11
- # @return [Hash] the JSON response
15
+ # Make a GET request to the given path
16
+ # @param path [String] the path to the API endpoint
17
+ # @return [Hash] the parsed JSON response
12
18
  def get(path)
13
19
  uri = URI.parse("https://api.github.com/#{path}")
14
20
  request = Net::HTTP::Get.new(uri)
15
- request["Accept"] = "application/vnd.github+json"
16
- request["Authorization"] = "Bearer #{@api_key}"
17
- request["X-Github-Api-Version"] = "2022-11-28"
18
-
19
- req_options = {
20
- use_ssl: uri.scheme == "https"
21
- }
22
21
 
23
- response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
24
- http.request(request)
25
- end
22
+ response = _http_request(uri: uri, request: request)
26
23
 
27
24
  JSON.parse(response.body)
28
25
  end
29
26
 
30
- # @return [Hash] the JSON response
27
+ # Make a POST request to the given path with the given params
28
+ # @param path [String] the path to the API endpoint
29
+ # @param params [Hash] the request body
30
+ # @return [Hash] the parsed JSON response
31
31
  def post(path, params)
32
32
  uri = URI.parse("https://api.github.com/#{path}")
33
33
  request = Net::HTTP::Post.new(uri)
34
- request["Accept"] = "application/vnd.github+json"
35
- request["Authorization"] = "Bearer #{@api_key}"
36
- request["X-Github-Api-Version"] = "2022-11-28"
37
-
38
- req_options = {
39
- use_ssl: uri.scheme == "https"
40
- }
41
34
 
42
35
  request.body = params.to_json
43
36
 
44
- response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
45
- http.request(request)
46
- end
37
+ response = _http_request(uri: uri, request: request)
47
38
 
48
39
  JSON.parse(response.body)
49
40
  end
50
41
 
51
- # @return [Hash] the JSON response
42
+ # Make a PATCH request to the given path with the given params
43
+ # @param path [String] the path to the API endpoint
44
+ # @param params [Hash] the request body
45
+ # @return [Hash] the parsed JSON response
52
46
  def patch(path, params)
53
47
  uri = URI.parse("https://api.github.com/#{path}")
54
48
  request = Net::HTTP::Patch.new(uri)
49
+
50
+ request.body = params.to_json
51
+
52
+ response = _http_request(uri: uri, request: request)
53
+
54
+ JSON.parse(response.body)
55
+ end
56
+
57
+ private
58
+
59
+ def _http_request(uri:, request:)
55
60
  request["Accept"] = "application/vnd.github+json"
56
61
  request["Authorization"] = "Bearer #{@api_key}"
57
62
  request["X-Github-Api-Version"] = "2022-11-28"
@@ -60,13 +65,22 @@ module GHX
60
65
  use_ssl: uri.scheme == "https"
61
66
  }
62
67
 
63
- request.body = params.to_json
64
-
65
68
  response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
66
69
  http.request(request)
67
70
  end
68
71
 
69
- JSON.parse(response.body)
72
+ if response.code.to_i < 400
73
+ response
74
+ elsif [403, 429].include?(response.code.to_i)
75
+ if response["X-RateLimit-Remaining"].to_i == 0
76
+ reset_time = Time.at(response["X-RateLimit-Reset"].to_i)
77
+ raise GHX::RateLimitExceededError, "GitHub API rate limit exceeded. Try again after #{reset_time}"
78
+ else
79
+ raise GHX::RateLimitExceededError, "GitHub API rate limit exceeded. Try again later."
80
+ end
81
+ else
82
+ raise GHX::OtherApiError, "GitHub API returned an error: #{response.code} #{response.body}"
83
+ end
70
84
  end
71
85
  end
72
86
  end
data/lib/ghx.rb CHANGED
@@ -13,6 +13,7 @@ class Hash
13
13
  end
14
14
 
15
15
  require_relative "version"
16
+ require_relative "ghx/errors"
16
17
  require_relative "ghx/graphql_client"
17
18
  require_relative "ghx/rest_client"
18
19
  require_relative "ghx/dependabot"
@@ -25,34 +26,52 @@ require_relative "ghx/project_item"
25
26
  # Extra classes to support more OO interfaces to the GitHub API. Wraps both the REST API and GraphQL API. Currently
26
27
  # incomplete. Functionality has been built for our existing use-cases, but nothing else.
27
28
  module GHX
29
+ # Defaults to $stdout
30
+ # @return [Logger]
28
31
  def self.logger
29
32
  @logger ||= Logger.new($stdout)
30
33
  end
31
34
 
35
+ # @param logger [Logger]
32
36
  def self.logger=(logger)
33
37
  @logger = logger
34
38
  end
35
39
 
40
+ # Internal octokit client.
41
+ # API Key defaults to ENV["GITHUB_TOKEN"]
42
+ # @return [Octokit::Client]
36
43
  def self.octokit
37
44
  @octokit ||= Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
38
45
  end
39
46
 
47
+ # @param octokit [Octokit::Client]
48
+ # @return [Octokit::Client]
40
49
  def self.octokit=(octokit)
41
50
  @octokit = octokit
42
51
  end
43
52
 
53
+ # Internal graphql client.
54
+ # API Key defaults to ENV["GITHUB_TOKEN"]
55
+ # @return [GHX::GraphqlClient]
44
56
  def self.graphql
45
57
  @graphql ||= GHX::GraphqlClient.new(ENV["GITHUB_GRAPHQL_TOKEN"])
46
58
  end
47
59
 
60
+ # @param graphql [GHX::GraphqlClient]
61
+ # @return [GHX::GraphqlClient]
48
62
  def self.graphql=(graphql)
49
63
  @graphql = graphql
50
64
  end
51
65
 
66
+ # Internal graphql client.
67
+ # API Key defaults to ENV["GITHUB_TOKEN"]
68
+ # @return [GHX::RestClient]
52
69
  def self.rest_client
53
70
  @rest_client ||= GHX::RestClient.new(ENV["GITHUB_TOKEN"])
54
71
  end
55
72
 
73
+ # @param rest_client [GHX::RestClient]
74
+ # @return [GHX::RestClient]
56
75
  def self.rest_client=(rest_client)
57
76
  @rest_client = rest_client
58
77
  end
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module GHX
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ghx
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - CompanyCam
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-21 00:00:00.000000000 Z
11
+ date: 2024-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: octokit
@@ -106,6 +106,7 @@ files:
106
106
  - lib/ghx/dependabot/alert.rb
107
107
  - lib/ghx/dependabot/package.rb
108
108
  - lib/ghx/dependabot/security_vulnerability.rb
109
+ - lib/ghx/errors.rb
109
110
  - lib/ghx/graphql_client.rb
110
111
  - lib/ghx/issue.rb
111
112
  - lib/ghx/project.rb