type Metadata struct {
Description string `json:"description,omitempty"`
ReleaseNotes string `json:"release_notes,omitempty"`
+ Readme string `json:"readme,omitempty"`
Authors string `json:"authors,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
Version string `json:"version"`
}
+// https://learn.microsoft.com/en-us/nuget/reference/nuspec
type nuspecPackage struct {
Metadata struct {
ID string `xml:"id"`
ProjectURL string `xml:"projectUrl"`
Description string `xml:"description"`
ReleaseNotes string `xml:"releaseNotes"`
+ Readme string `xml:"readme"`
PackageTypes struct {
PackageType []struct {
Name string `xml:"name,attr"`
URL string `xml:"url,attr"`
} `xml:"repository"`
Dependencies struct {
+ Dependency []struct {
+ ID string `xml:"id,attr"`
+ Version string `xml:"version,attr"`
+ Exclude string `xml:"exclude,attr"`
+ } `xml:"dependency"`
Group []struct {
TargetFramework string `xml:"targetFramework,attr"`
Dependency []struct {
}
defer f.Close()
- return ParseNuspecMetaData(f)
+ return ParseNuspecMetaData(archive, f)
}
}
return nil, ErrMissingNuspecFile
}
// ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package
-func ParseNuspecMetaData(r io.Reader) (*Package, error) {
+func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
var p nuspecPackage
if err := xml.NewDecoder(r).Decode(&p); err != nil {
return nil, err
Dependencies: make(map[string][]Dependency),
}
+ if p.Metadata.Readme != "" {
+ f, err := archive.Open(p.Metadata.Readme)
+ if err == nil {
+ buf, _ := io.ReadAll(f)
+ m.Readme = string(buf)
+ _ = f.Close()
+ }
+ }
+
+ if len(p.Metadata.Dependencies.Dependency) > 0 {
+ deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency))
+ for _, dep := range p.Metadata.Dependencies.Dependency {
+ if dep.ID == "" || dep.Version == "" {
+ continue
+ }
+ deps = append(deps, Dependency{
+ ID: dep.ID,
+ Version: dep.Version,
+ })
+ }
+ m.Dependencies[""] = deps
+ }
for _, group := range p.Metadata.Dependencies.Group {
deps := make([]Dependency, 0, len(group.Dependency))
for _, dep := range group.Dependency {
import (
"archive/zip"
"bytes"
- "strings"
"testing"
"github.com/stretchr/testify/assert"
projectURL = "https://gitea.io"
description = "Package Description"
releaseNotes = "Package Release Notes"
+ readme = "Readme"
repositoryURL = "https://gitea.io/gitea/gitea"
targetFramework = ".NETStandard2.1"
dependencyID = "System.Text.Json"
<description>` + description + `</description>
<releaseNotes>` + releaseNotes + `</releaseNotes>
<repository url="` + repositoryURL + `" />
+ <readme>README.md</readme>
<dependencies>
<group targetFramework="` + targetFramework + `">
<dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
</package>`
func TestParsePackageMetaData(t *testing.T) {
- createArchive := func(name, content string) []byte {
+ createArchive := func(files map[string]string) []byte {
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
- w, _ := archive.Create(name)
- w.Write([]byte(content))
+ for name, content := range files {
+ w, _ := archive.Create(name)
+ w.Write([]byte(content))
+ }
archive.Close()
return buf.Bytes()
}
t.Run("MissingNuspecFile", func(t *testing.T) {
- data := createArchive("dummy.txt", "")
+ data := createArchive(map[string]string{"dummy.txt": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
})
t.Run("MissingNuspecFileInRoot", func(t *testing.T) {
- data := createArchive("sub/package.nuspec", "")
+ data := createArchive(map[string]string{"sub/package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
})
t.Run("InvalidNuspecFile", func(t *testing.T) {
- data := createArchive("package.nuspec", "")
+ data := createArchive(map[string]string{"package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
})
t.Run("InvalidPackageId", func(t *testing.T) {
- data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?>
+ data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata></metadata>
- </package>`)
+ </package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
})
t.Run("InvalidPackageVersion", func(t *testing.T) {
- data := createArchive("package.nuspec", `<?xml version="1.0" encoding="utf-8"?>
+ data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
- <id>`+id+`</id>
+ <id>` + id + `</id>
</metadata>
- </package>`)
+ </package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.ErrorIs(t, err, ErrNuspecInvalidVersion)
})
- t.Run("Valid", func(t *testing.T) {
- data := createArchive("package.nuspec", nuspecContent)
+ t.Run("MissingReadme", func(t *testing.T) {
+ data := createArchive(map[string]string{"package.nuspec": nuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
+ assert.Empty(t, np.Metadata.Readme)
})
-}
-func TestParseNuspecMetaData(t *testing.T) {
t.Run("Dependency Package", func(t *testing.T) {
- np, err := ParseNuspecMetaData(strings.NewReader(nuspecContent))
+ data := createArchive(map[string]string{
+ "package.nuspec": nuspecContent,
+ "README.md": readme,
+ })
+
+ np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, DependencyPackage, np.PackageType)
assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, description, np.Metadata.Description)
assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
+ assert.Equal(t, readme, np.Metadata.Readme)
assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL)
assert.Len(t, np.Metadata.Dependencies, 1)
assert.Contains(t, np.Metadata.Dependencies, targetFramework)
assert.Equal(t, dependencyVersion, deps[0].Version)
t.Run("NormalizedVersion", func(t *testing.T) {
- np, err := ParseNuspecMetaData(strings.NewReader(`<?xml version="1.0" encoding="utf-8"?>
-<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
- <metadata>
- <id>test</id>
- <version>1.04.5.2.5-rc.1+metadata</version>
- </metadata>
-</package>`))
+ data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
+ <package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
+ <metadata>
+ <id>test</id>
+ <version>1.04.5.2.5-rc.1+metadata</version>
+ </metadata>
+ </package>`})
+
+ np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, "1.4.5.2-rc.1", np.Version)
})
t.Run("Symbols Package", func(t *testing.T) {
- np, err := ParseNuspecMetaData(strings.NewReader(symbolsNuspecContent))
+ data := createArchive(map[string]string{"package.nuspec": symbolsNuspecContent})
+
+ np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, SymbolsPackage, np.PackageType)