Skip to content

Commit 5721d3b

Browse files
czunkerclaude
andcommitted
✨ Add archived field to depsdev.project using GitHub API
The deps.dev API does not expose whether a project repository is archived, so the new `archived` field queries the GitHub API directly. It extracts owner/repo from the project ID (e.g. github.com/owner/repo/subpath) and calls GET /repos/{owner}/{repo}. Authentication via the GITHUB_TOKEN env var is supported to avoid GitHub's 60 req/hour unauthenticated rate limit. Also fixes nil pointer dereference when project() returns nil by properly setting the null state on the resource field. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent f660d38 commit 5721d3b

File tree

7 files changed

+98
-18
lines changed

7 files changed

+98
-18
lines changed

providers/depsdev/config/config.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ var Config = plugin.Provider{
1515
ConnectionTypes: []string{provider.DefaultConnectionType},
1616
Connectors: []plugin.Connector{
1717
{
18-
Name: "depsdev",
19-
Use: "depsdev [path-to-go.mod]",
20-
Short: "deps.dev dependency analysis",
18+
Name: "depsdev",
19+
Use: "depsdev [path-to-go.mod]",
20+
Short: "deps.dev dependency analysis",
2121
Long: `Use the depsdev provider to query Go module dependency information from the deps.dev API.
2222
2323
Point it at a go.mod file to check all direct dependencies:
@@ -29,8 +29,8 @@ Or use it without arguments and query individual packages:
2929
mql shell depsdev
3030
> depsdev.package("github.com/rs/zerolog") { latestVersion latestPublished }
3131
`,
32-
MinArgs: 0,
33-
MaxArgs: 1,
32+
MinArgs: 0,
33+
MaxArgs: 1,
3434
Discovery: []string{},
3535
Flags: []plugin.Flag{
3636
{

providers/depsdev/resources/api.go

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import (
99
"io"
1010
"net/http"
1111
"net/url"
12+
"os"
13+
"strings"
1214
"time"
1315
)
1416

1517
const depsDevBaseURL = "https://api.deps.dev/v3"
18+
const githubAPIBaseURL = "https://api.github.com"
1619

1720
// API response structs for deps.dev v3
1821

@@ -40,14 +43,14 @@ type depsDevVersionResponse struct {
4043
Name string `json:"name"`
4144
Version string `json:"version"`
4245
} `json:"versionKey"`
43-
IsDefault bool `json:"isDefault"`
44-
PublishedAt time.Time `json:"publishedAt"`
45-
Licenses []string `json:"licenses"`
46+
IsDefault bool `json:"isDefault"`
47+
PublishedAt time.Time `json:"publishedAt"`
48+
Licenses []string `json:"licenses"`
4649
RelatedProjects []struct {
4750
ProjectKey struct {
4851
ID string `json:"id"`
4952
} `json:"projectKey"`
50-
RelationType string `json:"relationType"`
53+
RelationType string `json:"relationType"`
5154
RelationProvenance string `json:"relationProvenance"`
5255
} `json:"relatedProjects"`
5356
}
@@ -56,19 +59,19 @@ type depsDevProjectResponse struct {
5659
ProjectKey struct {
5760
ID string `json:"id"`
5861
} `json:"projectKey"`
59-
OpenIssuesCount int `json:"openIssuesCount"`
60-
StarsCount int `json:"starsCount"`
61-
ForksCount int `json:"forksCount"`
62-
License string `json:"license"`
63-
Description string `json:"description"`
64-
Homepage string `json:"homepage"`
62+
OpenIssuesCount int `json:"openIssuesCount"`
63+
StarsCount int `json:"starsCount"`
64+
ForksCount int `json:"forksCount"`
65+
License string `json:"license"`
66+
Description string `json:"description"`
67+
Homepage string `json:"homepage"`
6568
Scorecard *depsDevScorecardResponse `json:"scorecard"`
6669
}
6770

6871
type depsDevScorecardResponse struct {
69-
Date time.Time `json:"date"`
70-
OverallScore float64 `json:"overallScore"`
71-
Checks []depsDevScorecardCheck `json:"checks"`
72+
Date time.Time `json:"date"`
73+
OverallScore float64 `json:"overallScore"`
74+
Checks []depsDevScorecardCheck `json:"checks"`
7275
}
7376

7477
type depsDevScorecardCheck struct {
@@ -153,3 +156,48 @@ func fetchProject(client *http.Client, projectID string) (*depsDevProjectRespons
153156

154157
return &result, nil
155158
}
159+
160+
type githubRepoResponse struct {
161+
Archived bool `json:"archived"`
162+
}
163+
164+
// fetchGitHubRepo retrieves repository info from the GitHub API.
165+
// The projectID is expected to be in the format "github.com/owner/repo" or
166+
// "github.com/owner/repo/subpath" (only owner/repo is used).
167+
func fetchGitHubRepo(client *http.Client, projectID string) (*githubRepoResponse, error) {
168+
// Extract owner/repo from "github.com/owner/repo[/...]"
169+
parts := strings.Split(projectID, "/")
170+
if len(parts) < 3 || parts[0] != "github.com" {
171+
return nil, fmt.Errorf("project %q is not a GitHub repository", projectID)
172+
}
173+
ownerRepo := parts[1] + "/" + parts[2]
174+
175+
u := fmt.Sprintf("%s/repos/%s", githubAPIBaseURL, ownerRepo)
176+
req, err := http.NewRequest("GET", u, nil)
177+
if err != nil {
178+
return nil, fmt.Errorf("failed to create GitHub API request: %w", err)
179+
}
180+
181+
// Use GITHUB_TOKEN for authentication if available to avoid rate limiting
182+
if token := os.Getenv("GITHUB_TOKEN"); token != "" {
183+
req.Header.Set("Authorization", "Bearer "+token)
184+
}
185+
186+
resp, err := client.Do(req)
187+
if err != nil {
188+
return nil, fmt.Errorf("GitHub API request failed: %w", err)
189+
}
190+
defer resp.Body.Close()
191+
192+
if resp.StatusCode != http.StatusOK {
193+
body, _ := io.ReadAll(resp.Body)
194+
return nil, fmt.Errorf("GitHub API returned %d for %s: %s", resp.StatusCode, ownerRepo, string(body))
195+
}
196+
197+
var result githubRepoResponse
198+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
199+
return nil, fmt.Errorf("failed to decode GitHub API response: %w", err)
200+
}
201+
202+
return &result, nil
203+
}

providers/depsdev/resources/depsdev.lr

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ depsdev.project @defaults("id starsCount forksCount") {
5656
description() string
5757
// Project homepage URL
5858
homepage() string
59+
// Whether the project repository is archived
60+
archived() bool
5961
// OpenSSF Scorecard data
6062
scorecard() depsdev.scorecard
6163
}

providers/depsdev/resources/depsdev.lr.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

providers/depsdev/resources/depsdev.lr.versions

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ depsdev.packageVersion.publishedAt 11.0.1
1616
depsdev.packageVersion.version 11.0.1
1717
depsdev.packages 11.0.1
1818
depsdev.project 11.0.1
19+
depsdev.project.archived 11.0.1
1920
depsdev.project.description 11.0.1
2021
depsdev.project.forksCount 11.0.1
2122
depsdev.project.homepage 11.0.1

providers/depsdev/resources/package.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ func (r *mqlDepsdevPackage) project() (*mqlDepsdevProject, error) {
111111
}
112112

113113
if version == "" {
114+
r.Project.State = plugin.StateIsNull | plugin.StateIsSet
114115
return nil, nil
115116
}
116117

@@ -135,6 +136,7 @@ func (r *mqlDepsdevPackage) project() (*mqlDepsdevProject, error) {
135136
return res.(*mqlDepsdevProject), nil
136137
}
137138

139+
r.Project.State = plugin.StateIsNull | plugin.StateIsSet
138140
return nil, nil
139141
}
140142

providers/depsdev/resources/project.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,19 @@ func (r *mqlDepsdevProject) scorecard() (*mqlDepsdevScorecard, error) {
121121
return nil, r.fetchProjectInfo()
122122
}
123123

124+
func (r *mqlDepsdevProject) archived() (bool, error) {
125+
conn := r.MqlRuntime.Connection.(*connection.DepsDevConnection)
126+
127+
repo, err := fetchGitHubRepo(conn.HttpClient, r.Id.Data)
128+
if err != nil {
129+
// If it's not a GitHub project, we can't determine archived status
130+
r.Archived = plugin.TValue[bool]{Data: false, State: plugin.StateIsSet | plugin.StateIsNull}
131+
return false, nil
132+
}
133+
134+
return repo.Archived, nil
135+
}
136+
124137
// depsdev.scorecard
125138

126139
func (r *mqlDepsdevScorecard) id() (string, error) {

0 commit comments

Comments
 (0)