diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7b68ef6..352a51b 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,11 +35,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v6 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -53,7 +53,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v2 + uses: github/codeql-action/autobuild@v3 # â„šī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun @@ -66,6 +66,6 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 1d73735..fb564da 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -14,12 +14,13 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: 1.24 + go-version: 1.25 + go-version-file: 'go.mod' - name: Build run: go build -v ./... diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 15ccb5a..e98b15c 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -16,15 +16,15 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v6 with: - go-version: 1.24 - - uses: actions/checkout@v4 + go-version: 1.25 + - uses: actions/checkout@v6 - name: golangci-lint - uses: golangci/golangci-lint-action@v8 + uses: golangci/golangci-lint-action@v9 with: # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: v2.2.0 + version: v2.11.3 # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/.github/workflows/release-binary.yml b/.github/workflows/release-binary.yml index fbe3626..8f2b4c0 100644 --- a/.github/workflows/release-binary.yml +++ b/.github/workflows/release-binary.yml @@ -11,14 +11,14 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check out code" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: "Set up Go" - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: 1.24 + go-version: 1.25 - name: "Create release on GitHub" timeout-minutes: 10 diff --git a/.github/workflows/release-test.yml b/.github/workflows/release-test.yml index e510774..01dee6e 100644 --- a/.github/workflows/release-test.yml +++ b/.github/workflows/release-test.yml @@ -12,14 +12,14 @@ jobs: runs-on: ubuntu-latest steps: - name: "Check out code" - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: 1.24 + go-version: 1.25 - name: release test uses: goreleaser/goreleaser-action@v6 diff --git a/.golangci.yml b/.golangci.yml index ca1f06d..1385b7c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -44,6 +44,7 @@ linters: - third_party$ - builtin$ - examples$ + - pkg/depsdev/v3alpha/api_test.go formatters: exclusions: generated: lax diff --git a/cmd/findings.go b/cmd/findings.go new file mode 100644 index 0000000..58a30a1 --- /dev/null +++ b/cmd/findings.go @@ -0,0 +1,67 @@ +/* + +depsdev - CLI client for deps.dev API. +Free access to dependencies, licenses, advisories, and other critical health and security signals for open source package versions. + + +@author: edoardottt, https://edoardottt.com/ + +@repository: https://github.com/edoardottt/depsdev + +@license: https://github.com/edoardottt/depsdev/blob/main/LICENSE + +*/ + +package cmd + +/* + +Not supported for now. +Maybe will be shipped when stable. + +// findingsCmd represents the package command when called with findings subcommand. +var findingsCmd = &cobra.Command{ + Use: "findings package-manager package-name [version]", + Short: "Get info about a safe dependency management", + Long: `Findings evaluates a specified package or version and returns findings which are relevant to safe dependency management.`, + Args: func(cmd *cobra.Command, args []string) error { + if len(args) < minArgsTwo { + return fmt.Errorf("%s %w", "two", input.ErrArgumentsLeast) + } + + if !input.IsValidPackageManager(args[0], input.AllValidPackageManagers) { + return input.ErrInvalidPackageManager + } + + return nil + }, + Run: func(cmd *cobra.Command, args []string) { + if len(args) >= minArgsThree { + v, err := api.GetFindingsVersion(args[0], args[1], args[2]) + if err != nil { + log.Fatal(err) + } + + vJSON, err := output.IndentJSON(v) + if err != nil { + log.Fatal(err.Error()) + } + + fmt.Println(vJSON) + } else { + p, err := api.GetFindings(args[0], args[1]) + if err != nil { + log.Fatal(err) + } + + pJSON, err := output.IndentJSON(p) + if err != nil { + log.Fatal(err.Error()) + } + + fmt.Println(pJSON) + } + }, +} + +*/ diff --git a/cmd/root.go b/cmd/root.go index 9822bb0..ef0342f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -57,4 +57,5 @@ func init() { rootCmd.AddCommand(graphCmd) rootCmd.AddCommand(reqsCmd) rootCmd.AddCommand(packagesCmd) + // rootCmd.AddCommand(findingsCmd) not supported in v3 for now. } diff --git a/go.mod b/go.mod index 26c154a..1dc3d7b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/edoardottt/depsdev -go 1.24 +go 1.25 require ( github.com/avast/retry-go v3.0.0+incompatible diff --git a/pkg/depsdev/definitions/batch.go b/pkg/depsdev/definitions/batch.go index 7dc8e93..d6c9975 100644 --- a/pkg/depsdev/definitions/batch.go +++ b/pkg/depsdev/definitions/batch.go @@ -112,3 +112,37 @@ func (p *PurlBatchResponse) Items() []Purl { return l } + +type FindingBatchRequest struct { + VersionKey VersionKey `json:"versionKey,omitempty"` + PackageKey PackageKey `json:"packageKey,omitempty"` +} + +type FindingBatchBody struct { + Requests []FindingBatchRequest `json:"requests"` + PageToken string `json:"pageToken"` +} + +func (p *FindingBatchBody) SetNextPageToken(token string) { + p.PageToken = token +} + +type FindingBatchResponse struct { + Responses []struct { + Findings Findings `json:"findings"` + Request FindingBatchRequest `json:"request"` + } `json:"responses"` + NextPageToken string `json:"nextPageToken"` +} + +func (p *FindingBatchResponse) GetNextPageToken() string { + return p.NextPageToken +} +func (p *FindingBatchResponse) Items() []Findings { + l := make([]Findings, 0, len(p.Responses)) + for _, r := range p.Responses { + l = append(l, r.Findings) + } + + return l +} diff --git a/pkg/depsdev/definitions/finding.go b/pkg/depsdev/definitions/finding.go new file mode 100644 index 0000000..f99c425 --- /dev/null +++ b/pkg/depsdev/definitions/finding.go @@ -0,0 +1,57 @@ +/* + +depsdev - CLI client for deps.dev API. +Free access to dependencies, licenses, advisories, and other critical health and security signals for open source package versions. + + +@author: edoardottt, https://edoardottt.com/ + +@repository: https://github.com/edoardottt/depsdev + +@license: https://github.com/edoardottt/depsdev/blob/main/LICENSE + +*/ + +package depsdev + +// Findings represents the root JSON object. +type Findings struct { + PackageKey *PackageKey `json:"packageKey,omitempty"` + VersionKey *VersionKey `json:"versionKey,omitempty"` + RecommendedVersions []VersionDetails `json:"recommendedVersions,omitempty"` + RequestedVersion *VersionDetails `json:"requestedVersion,omitempty"` + DefaultVersion *VersionDetails `json:"defaultVersion,omitempty"` + PackageFindings []Finding `json:"packageFindings,omitempty"` +} + +// VersionDetails contains findings and metadata for a specific version. +type VersionDetails struct { + VersionKey *VersionKey `json:"versionKey,omitempty"` + IsDefault bool `json:"isDefault,omitempty"` + Findings []Finding `json:"findings,omitempty"` + CooldownEnd string `json:"cooldownEnd,omitempty"` +} + +// Finding indicates specific risks or statuses associated with a package or version. +type Finding struct { + Type string `json:"type,omitempty"` + Risk string `json:"risk,omitempty"` + DeprecatedContext *DeprecatedContext `json:"deprecatedContext,omitempty"` + CooldownContext *CooldownContext `json:"cooldownContext,omitempty"` + LowUsageContext *LowUsageContext `json:"lowUsageContext,omitempty"` +} + +// DeprecatedContext provides the reason for deprecation. +type DeprecatedContext struct { + Reason string `json:"reason,omitempty"` +} + +// CooldownContext indicates the cooldown period for a specific version. +type CooldownContext struct { + End string `json:"end,omitempty"` +} + +// LowUsageContext provides alternative packages with higher usage. +type LowUsageContext struct { + AlternativePackages []string `json:"alternativePackages,omitempty"` +} diff --git a/pkg/depsdev/v3/api.go b/pkg/depsdev/v3/api.go index a9b2305..cdb20ff 100644 --- a/pkg/depsdev/v3/api.go +++ b/pkg/depsdev/v3/api.go @@ -131,6 +131,55 @@ func getAdvisory(c *client.Client, advisory string) (def.Advisory, error) { return response, nil } +/* + +Not supported for now. +Maybe will be shipped when stable. + +// GetFindings returns information about safe dependency management on a package. +func (a *APIv3) GetFindings(packageManager, packageName string) (def.Findings, error) { + if !input.IsValidPackageManager(packageManager, input.AllValidPackageManagers) { + return def.Findings{}, input.ErrInvalidPackageManager + } + + return getFindings(a.client, packageManager, packageName) +} + +// getFindings returns a Version object. +func getFindings(c *client.Client, packageManager, packageName string) (def.Findings, error) { + var response def.Findings + + var path = fmt.Sprintf(GetFindingsPath, packageManager, url.PathEscape(packageName)) + if err := c.Get(path, &response); err != nil { + return def.Findings{}, err + } + + return response, nil +} + +// GetFindingsVersion returns information about safe dependency management on a specific version of a package. +func (a *APIv3) GetFindingsVersion(packageManager, packageName, version string) (def.Findings, error) { + if !input.IsValidPackageManager(packageManager, input.AllValidPackageManagers) { + return def.Findings{}, input.ErrInvalidPackageManager + } + + return getFindingsVersion(a.client, packageManager, packageName, version) +} + +// getFindingsVersion returns a Version object. +func getFindingsVersion(c *client.Client, packageManager, packageName, version string) (def.Findings, error) { + var response def.Findings + + var path = fmt.Sprintf(GetFindingsVersionPath, packageManager, url.PathEscape(packageName), version) + if err := c.Get(path, &response); err != nil { + return def.Findings{}, err + } + + return response, nil +} + +*/ + // Query returns information about multiple package versions, which can be specified by name, content hash, or both. // If a hash was specified in the request, it returns the artifacts that matched the hash. // Querying by content hash is currently supported for npm, Cargo, Maven, NuGet, PyPI and RubyGems. diff --git a/pkg/depsdev/v3/routes.go b/pkg/depsdev/v3/routes.go index 48af893..cf1fbb9 100644 --- a/pkg/depsdev/v3/routes.go +++ b/pkg/depsdev/v3/routes.go @@ -19,11 +19,13 @@ const ( V3BasePath = `https://api.deps.dev/v3` // API routes. - GetPackagePath = `/systems/%s/packages/%s` - GetVersionPath = `/systems/%s/packages/%s/versions/%s` - GetDependenciesPath = `/systems/%s/packages/%s/versions/%s:dependencies` - GetProjectPath = `/projects/%s` - GetAdvisoryPath = `/advisories/%s` + GetPackagePath = `/systems/%s/packages/%s` + GetVersionPath = `/systems/%s/packages/%s/versions/%s` + GetDependenciesPath = `/systems/%s/packages/%s/versions/%s:dependencies` + GetProjectPath = `/projects/%s` + GetAdvisoryPath = `/advisories/%s` + // GetFindingsPath = `/systems/%s/packages/%s:findings`. + // GetFindingsVersionPath = `/systems/%s/packages/%s/versions/%s:findings`. QueryPath = `/query` GetRequirementsPath = `/systems/%s/packages/%s/versions/%s:requirements` GetProjectPackageVersionsPath = `/projects/%s:packageversions` diff --git a/pkg/depsdev/v3alpha/api.go b/pkg/depsdev/v3alpha/api.go index d21f15a..0ccfba2 100644 --- a/pkg/depsdev/v3alpha/api.go +++ b/pkg/depsdev/v3alpha/api.go @@ -24,6 +24,10 @@ import ( "github.com/edoardottt/depsdev/pkg/input" ) +const ( + FirstMarker = "first" +) + type APIv3Alpha struct { client *client.Client } @@ -245,6 +249,73 @@ func (a *APIv3Alpha) GetSimilarlyNamedPackages(packageManager, packageName strin return response, nil } +// GetFindings returns information about safe dependency management on a package. +func (a *APIv3Alpha) GetFindings(packageManager, packageName string) (def.Findings, error) { + if !input.IsValidPackageManager(packageManager, input.AllValidPackageManagers) { + return def.Findings{}, input.ErrInvalidPackageManager + } + + return getFindings(a.client, packageManager, packageName) +} + +// getFindings returns a Findings object. +func getFindings(c *client.Client, packageManager, packageName string) (def.Findings, error) { + var response def.Findings + + var path = fmt.Sprintf(GetFindingsPath, packageManager, url.PathEscape(packageName)) + if err := c.Get(path, &response); err != nil { + return def.Findings{}, err + } + + return response, nil +} + +// GetFindingsVersion returns information about safe dependency management on a specific version of a package. +func (a *APIv3Alpha) GetFindingsVersion(packageManager, packageName, version string) (def.Findings, error) { + if !input.IsValidPackageManager(packageManager, input.AllValidPackageManagers) { + return def.Findings{}, input.ErrInvalidPackageManager + } + + return getFindingsVersion(a.client, packageManager, packageName, version) +} + +// getFindingsVersion returns a Findings object. +func getFindingsVersion(c *client.Client, packageManager, packageName, version string) (def.Findings, error) { + var response def.Findings + + var path = fmt.Sprintf(GetFindingsVersionPath, packageManager, url.PathEscape(packageName), version) + if err := c.Get(path, &response); err != nil { + return def.Findings{}, err + } + + return response, nil +} + +// GetFindingsBatch performs GetFindings requests for a batch of versions. +// Large result sets may be paginated. +func (a *APIv3Alpha) GetFindingsBatch(req def.FindingBatchBody) (*Iterator[def.Findings], error) { + for _, v := range req.Requests { + if !input.IsValidPackageManager(v.VersionKey.System, input.AllValidPackageManagers) { + return nil, input.ErrInvalidPackageManager + } + } + + response := &def.FindingBatchResponse{ + NextPageToken: FirstMarker, + } + + ctx, cancel := context.WithCancel(context.Background()) + cIn := getBatch(ctx, a.client, GetFindingsBatchPath, &req, response) + + iter := Iterator[def.Findings]{ + cIn: cIn, + hasNext: true, + cancel: cancel, + } + + return &iter, nil +} + // GetVersionBatch performs GetVersion requests for a batch of versions. // Large result sets may be paginated. func (a *APIv3Alpha) GetVersionBatch(req def.VersionBatchBody) (*Iterator[def.Version], error) { @@ -255,7 +326,7 @@ func (a *APIv3Alpha) GetVersionBatch(req def.VersionBatchBody) (*Iterator[def.Ve } response := &def.VersionBatchResponse{ - NextPageToken: "first", + NextPageToken: FirstMarker, } ctx, cancel := context.WithCancel(context.Background()) @@ -274,7 +345,7 @@ func (a *APIv3Alpha) GetVersionBatch(req def.VersionBatchBody) (*Iterator[def.Ve // Large result sets may be paginated. func (a *APIv3Alpha) GetProjectBatch(req def.ProjectBatchBody) (*Iterator[def.Project], error) { response := &def.ProjectBatchResponse{ - NextPageToken: "first", + NextPageToken: FirstMarker, } ctx, cancel := context.WithCancel(context.Background()) @@ -317,7 +388,7 @@ func (a *APIv3Alpha) PurlLookup(purl string) (def.Purl, error) { // In particular, there must be no subpath or qualifiers. Large result sets may be paginated. func (a *APIv3Alpha) PurlLookupBatch(req def.PurlBatchBody) (*Iterator[def.Purl], error) { response := &def.PurlBatchResponse{ - NextPageToken: "first", + NextPageToken: FirstMarker, } ctx, cancel := context.WithCancel(context.Background()) diff --git a/pkg/depsdev/v3alpha/routes.go b/pkg/depsdev/v3alpha/routes.go index fcfa570..eed0ad9 100644 --- a/pkg/depsdev/v3alpha/routes.go +++ b/pkg/depsdev/v3alpha/routes.go @@ -28,6 +28,9 @@ const ( GetDependentsPath = `/systems/%s/packages/%s/versions/%s:dependents` GetProjectPath = `/projects/%s` GetAdvisoryPath = `/advisories/%s` + GetFindingsPath = `/systems/%s/packages/%s:findings` + GetFindingsVersionPath = `/systems/%s/packages/%s/versions/%s:findings` + GetFindingsBatchPath = `/findingsbatch` QueryPath = `/query` GetProjectPackageVersionsPath = `/projects/%s:packageversions` GetVersionBatchPath = `/versionbatch`