mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	Some NuGet package enhancements (#30280)
Fixes #30265 1. Read second type of dependencies 2. Render `Description` and `ReleaseNotes` old:  new:  The NuGet spec does not specify what kind of text can be stored in the description but we can best guess markdown. The official NuGet registry just [converts the newlines to html lines](https://www.nuget.org/packages/rb.Firefox#readme-body-tab). 3. Extract and render the readme. This is the new and better place to store larger text than in the description. The content is markdown.  --------- Co-authored-by: Benjamin Heemann <benjamin.heemann@raith.de>
This commit is contained in:
		| @@ -58,6 +58,7 @@ type Package struct { | ||||
| 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"` | ||||
| @@ -71,6 +72,7 @@ type Dependency struct { | ||||
| 	Version string `json:"version"` | ||||
| } | ||||
|  | ||||
| // https://learn.microsoft.com/en-us/nuget/reference/nuspec | ||||
| type nuspecPackage struct { | ||||
| 	Metadata struct { | ||||
| 		ID                       string `xml:"id"` | ||||
| @@ -80,6 +82,7 @@ type nuspecPackage struct { | ||||
| 		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"` | ||||
| @@ -89,6 +92,11 @@ type nuspecPackage struct { | ||||
| 			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 { | ||||
| @@ -122,14 +130,14 @@ func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) { | ||||
| 			} | ||||
| 			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 | ||||
| @@ -166,6 +174,28 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) { | ||||
| 		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 { | ||||
|   | ||||
| @@ -6,7 +6,6 @@ package nuget | ||||
| import ( | ||||
| 	"archive/zip" | ||||
| 	"bytes" | ||||
| 	"strings" | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| @@ -19,6 +18,7 @@ const ( | ||||
| 	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" | ||||
| @@ -36,6 +36,7 @@ const nuspecContent = `<?xml version="1.0" encoding="utf-8"?> | ||||
|     <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" /> | ||||
| @@ -60,17 +61,19 @@ const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?> | ||||
| </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) | ||||
| @@ -78,7 +81,7 @@ func TestParsePackageMetaData(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	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) | ||||
| @@ -86,7 +89,7 @@ func TestParsePackageMetaData(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	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) | ||||
| @@ -94,10 +97,10 @@ func TestParsePackageMetaData(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	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) | ||||
| @@ -105,30 +108,34 @@ func TestParsePackageMetaData(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	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) | ||||
| @@ -139,6 +146,7 @@ func TestParseNuspecMetaData(t *testing.T) { | ||||
| 		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) | ||||
| @@ -148,13 +156,15 @@ func TestParseNuspecMetaData(t *testing.T) { | ||||
| 		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) | ||||
| @@ -162,7 +172,9 @@ func TestParseNuspecMetaData(t *testing.T) { | ||||
| 	}) | ||||
|  | ||||
| 	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) | ||||
|   | ||||
| @@ -16,12 +16,11 @@ | ||||
| 		</div> | ||||
| 	</div> | ||||
|  | ||||
| 	{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.ReleaseNotes}} | ||||
| 	{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.ReleaseNotes .PackageDescriptor.Metadata.Readme}} | ||||
| 		<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4> | ||||
| 		<div class="ui attached segment"> | ||||
| 			{{if .PackageDescriptor.Metadata.Description}}{{.PackageDescriptor.Metadata.Description}}{{end}} | ||||
| 			{{if .PackageDescriptor.Metadata.ReleaseNotes}}{{.PackageDescriptor.Metadata.ReleaseNotes}}{{end}} | ||||
| 		</div> | ||||
| 		{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Description}}</div>{{end}} | ||||
| 		{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment markup markdown">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}} | ||||
| 		{{if .PackageDescriptor.Metadata.ReleaseNotes}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.ReleaseNotes}}</div>{{end}} | ||||
| 	{{end}} | ||||
|  | ||||
| 	{{if .PackageDescriptor.Metadata.Dependencies}} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user