mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +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 { | type Metadata struct { | ||||||
| 	Description              string                  `json:"description,omitempty"` | 	Description              string                  `json:"description,omitempty"` | ||||||
| 	ReleaseNotes             string                  `json:"release_notes,omitempty"` | 	ReleaseNotes             string                  `json:"release_notes,omitempty"` | ||||||
|  | 	Readme                   string                  `json:"readme,omitempty"` | ||||||
| 	Authors                  string                  `json:"authors,omitempty"` | 	Authors                  string                  `json:"authors,omitempty"` | ||||||
| 	ProjectURL               string                  `json:"project_url,omitempty"` | 	ProjectURL               string                  `json:"project_url,omitempty"` | ||||||
| 	RepositoryURL            string                  `json:"repository_url,omitempty"` | 	RepositoryURL            string                  `json:"repository_url,omitempty"` | ||||||
| @@ -71,6 +72,7 @@ type Dependency struct { | |||||||
| 	Version string `json:"version"` | 	Version string `json:"version"` | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // https://learn.microsoft.com/en-us/nuget/reference/nuspec | ||||||
| type nuspecPackage struct { | type nuspecPackage struct { | ||||||
| 	Metadata struct { | 	Metadata struct { | ||||||
| 		ID                       string `xml:"id"` | 		ID                       string `xml:"id"` | ||||||
| @@ -80,6 +82,7 @@ type nuspecPackage struct { | |||||||
| 		ProjectURL               string `xml:"projectUrl"` | 		ProjectURL               string `xml:"projectUrl"` | ||||||
| 		Description              string `xml:"description"` | 		Description              string `xml:"description"` | ||||||
| 		ReleaseNotes             string `xml:"releaseNotes"` | 		ReleaseNotes             string `xml:"releaseNotes"` | ||||||
|  | 		Readme                   string `xml:"readme"` | ||||||
| 		PackageTypes             struct { | 		PackageTypes             struct { | ||||||
| 			PackageType []struct { | 			PackageType []struct { | ||||||
| 				Name string `xml:"name,attr"` | 				Name string `xml:"name,attr"` | ||||||
| @@ -89,6 +92,11 @@ type nuspecPackage struct { | |||||||
| 			URL string `xml:"url,attr"` | 			URL string `xml:"url,attr"` | ||||||
| 		} `xml:"repository"` | 		} `xml:"repository"` | ||||||
| 		Dependencies struct { | 		Dependencies struct { | ||||||
|  | 			Dependency []struct { | ||||||
|  | 				ID      string `xml:"id,attr"` | ||||||
|  | 				Version string `xml:"version,attr"` | ||||||
|  | 				Exclude string `xml:"exclude,attr"` | ||||||
|  | 			} `xml:"dependency"` | ||||||
| 			Group []struct { | 			Group []struct { | ||||||
| 				TargetFramework string `xml:"targetFramework,attr"` | 				TargetFramework string `xml:"targetFramework,attr"` | ||||||
| 				Dependency      []struct { | 				Dependency      []struct { | ||||||
| @@ -122,14 +130,14 @@ func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) { | |||||||
| 			} | 			} | ||||||
| 			defer f.Close() | 			defer f.Close() | ||||||
|  |  | ||||||
| 			return ParseNuspecMetaData(f) | 			return ParseNuspecMetaData(archive, f) | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return nil, ErrMissingNuspecFile | 	return nil, ErrMissingNuspecFile | ||||||
| } | } | ||||||
|  |  | ||||||
| // ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package | // 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 | 	var p nuspecPackage | ||||||
| 	if err := xml.NewDecoder(r).Decode(&p); err != nil { | 	if err := xml.NewDecoder(r).Decode(&p); err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @@ -166,6 +174,28 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) { | |||||||
| 		Dependencies:             make(map[string][]Dependency), | 		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 { | 	for _, group := range p.Metadata.Dependencies.Group { | ||||||
| 		deps := make([]Dependency, 0, len(group.Dependency)) | 		deps := make([]Dependency, 0, len(group.Dependency)) | ||||||
| 		for _, dep := range group.Dependency { | 		for _, dep := range group.Dependency { | ||||||
|   | |||||||
| @@ -6,7 +6,6 @@ package nuget | |||||||
| import ( | import ( | ||||||
| 	"archive/zip" | 	"archive/zip" | ||||||
| 	"bytes" | 	"bytes" | ||||||
| 	"strings" |  | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| @@ -19,6 +18,7 @@ const ( | |||||||
| 	projectURL        = "https://gitea.io" | 	projectURL        = "https://gitea.io" | ||||||
| 	description       = "Package Description" | 	description       = "Package Description" | ||||||
| 	releaseNotes      = "Package Release Notes" | 	releaseNotes      = "Package Release Notes" | ||||||
|  | 	readme            = "Readme" | ||||||
| 	repositoryURL     = "https://gitea.io/gitea/gitea" | 	repositoryURL     = "https://gitea.io/gitea/gitea" | ||||||
| 	targetFramework   = ".NETStandard2.1" | 	targetFramework   = ".NETStandard2.1" | ||||||
| 	dependencyID      = "System.Text.Json" | 	dependencyID      = "System.Text.Json" | ||||||
| @@ -36,6 +36,7 @@ const nuspecContent = `<?xml version="1.0" encoding="utf-8"?> | |||||||
|     <description>` + description + `</description> |     <description>` + description + `</description> | ||||||
|     <releaseNotes>` + releaseNotes + `</releaseNotes> |     <releaseNotes>` + releaseNotes + `</releaseNotes> | ||||||
|     <repository url="` + repositoryURL + `" /> |     <repository url="` + repositoryURL + `" /> | ||||||
|  |     <readme>README.md</readme> | ||||||
|     <dependencies> |     <dependencies> | ||||||
|       <group targetFramework="` + targetFramework + `"> |       <group targetFramework="` + targetFramework + `"> | ||||||
|         <dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" /> |         <dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" /> | ||||||
| @@ -60,17 +61,19 @@ const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?> | |||||||
| </package>` | </package>` | ||||||
|  |  | ||||||
| func TestParsePackageMetaData(t *testing.T) { | func TestParsePackageMetaData(t *testing.T) { | ||||||
| 	createArchive := func(name, content string) []byte { | 	createArchive := func(files map[string]string) []byte { | ||||||
| 		var buf bytes.Buffer | 		var buf bytes.Buffer | ||||||
| 		archive := zip.NewWriter(&buf) | 		archive := zip.NewWriter(&buf) | ||||||
| 		w, _ := archive.Create(name) | 		for name, content := range files { | ||||||
| 		w.Write([]byte(content)) | 			w, _ := archive.Create(name) | ||||||
|  | 			w.Write([]byte(content)) | ||||||
|  | 		} | ||||||
| 		archive.Close() | 		archive.Close() | ||||||
| 		return buf.Bytes() | 		return buf.Bytes() | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	t.Run("MissingNuspecFile", func(t *testing.T) { | 	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))) | 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | ||||||
| 		assert.Nil(t, np) | 		assert.Nil(t, np) | ||||||
| @@ -78,7 +81,7 @@ func TestParsePackageMetaData(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("MissingNuspecFileInRoot", func(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))) | 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | ||||||
| 		assert.Nil(t, np) | 		assert.Nil(t, np) | ||||||
| @@ -86,7 +89,7 @@ func TestParsePackageMetaData(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("InvalidNuspecFile", func(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))) | 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | ||||||
| 		assert.Nil(t, np) | 		assert.Nil(t, np) | ||||||
| @@ -94,10 +97,10 @@ func TestParsePackageMetaData(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("InvalidPackageId", func(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"> | 		<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> | ||||||
| 		  <metadata></metadata> | 		  <metadata></metadata> | ||||||
| 		</package>`) | 		</package>`}) | ||||||
|  |  | ||||||
| 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | ||||||
| 		assert.Nil(t, np) | 		assert.Nil(t, np) | ||||||
| @@ -105,30 +108,34 @@ func TestParsePackageMetaData(t *testing.T) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("InvalidPackageVersion", func(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"> | 		<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> | ||||||
| 		  <metadata> | 		  <metadata> | ||||||
| 			<id>`+id+`</id> | 			<id>` + id + `</id> | ||||||
| 		  </metadata> | 		  </metadata> | ||||||
| 		</package>`) | 		</package>`}) | ||||||
|  |  | ||||||
| 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | ||||||
| 		assert.Nil(t, np) | 		assert.Nil(t, np) | ||||||
| 		assert.ErrorIs(t, err, ErrNuspecInvalidVersion) | 		assert.ErrorIs(t, err, ErrNuspecInvalidVersion) | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("Valid", func(t *testing.T) { | 	t.Run("MissingReadme", func(t *testing.T) { | ||||||
| 		data := createArchive("package.nuspec", nuspecContent) | 		data := createArchive(map[string]string{"package.nuspec": nuspecContent}) | ||||||
|  |  | ||||||
| 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | 		np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | ||||||
| 		assert.NoError(t, err) | 		assert.NoError(t, err) | ||||||
| 		assert.NotNil(t, np) | 		assert.NotNil(t, np) | ||||||
|  | 		assert.Empty(t, np.Metadata.Readme) | ||||||
| 	}) | 	}) | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestParseNuspecMetaData(t *testing.T) { |  | ||||||
| 	t.Run("Dependency Package", func(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.NoError(t, err) | ||||||
| 		assert.NotNil(t, np) | 		assert.NotNil(t, np) | ||||||
| 		assert.Equal(t, DependencyPackage, np.PackageType) | 		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, projectURL, np.Metadata.ProjectURL) | ||||||
| 		assert.Equal(t, description, np.Metadata.Description) | 		assert.Equal(t, description, np.Metadata.Description) | ||||||
| 		assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) | 		assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) | ||||||
|  | 		assert.Equal(t, readme, np.Metadata.Readme) | ||||||
| 		assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) | 		assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) | ||||||
| 		assert.Len(t, np.Metadata.Dependencies, 1) | 		assert.Len(t, np.Metadata.Dependencies, 1) | ||||||
| 		assert.Contains(t, np.Metadata.Dependencies, targetFramework) | 		assert.Contains(t, np.Metadata.Dependencies, targetFramework) | ||||||
| @@ -148,13 +156,15 @@ func TestParseNuspecMetaData(t *testing.T) { | |||||||
| 		assert.Equal(t, dependencyVersion, deps[0].Version) | 		assert.Equal(t, dependencyVersion, deps[0].Version) | ||||||
|  |  | ||||||
| 		t.Run("NormalizedVersion", func(t *testing.T) { | 		t.Run("NormalizedVersion", func(t *testing.T) { | ||||||
| 			np, err := ParseNuspecMetaData(strings.NewReader(`<?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"> | 				<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> | ||||||
|   <metadata> | 				  <metadata> | ||||||
| 	<id>test</id> | 					<id>test</id> | ||||||
| 	<version>1.04.5.2.5-rc.1+metadata</version> | 					<version>1.04.5.2.5-rc.1+metadata</version> | ||||||
|   </metadata> | 				  </metadata> | ||||||
| </package>`)) | 				</package>`}) | ||||||
|  |  | ||||||
|  | 			np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) | ||||||
| 			assert.NoError(t, err) | 			assert.NoError(t, err) | ||||||
| 			assert.NotNil(t, np) | 			assert.NotNil(t, np) | ||||||
| 			assert.Equal(t, "1.4.5.2-rc.1", np.Version) | 			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) { | 	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.NoError(t, err) | ||||||
| 		assert.NotNil(t, np) | 		assert.NotNil(t, np) | ||||||
| 		assert.Equal(t, SymbolsPackage, np.PackageType) | 		assert.Equal(t, SymbolsPackage, np.PackageType) | ||||||
|   | |||||||
| @@ -16,12 +16,11 @@ | |||||||
| 		</div> | 		</div> | ||||||
| 	</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> | 		<h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4> | ||||||
| 		<div class="ui attached segment"> | 		{{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Description}}</div>{{end}} | ||||||
| 			{{if .PackageDescriptor.Metadata.Description}}{{.PackageDescriptor.Metadata.Description}}{{end}} | 		{{if .PackageDescriptor.Metadata.Readme}}<div class="ui attached segment markup markdown">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}</div>{{end}} | ||||||
| 			{{if .PackageDescriptor.Metadata.ReleaseNotes}}{{.PackageDescriptor.Metadata.ReleaseNotes}}{{end}} | 		{{if .PackageDescriptor.Metadata.ReleaseNotes}}<div class="ui attached segment">{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.ReleaseNotes}}</div>{{end}} | ||||||
| 		</div> |  | ||||||
| 	{{end}} | 	{{end}} | ||||||
|  |  | ||||||
| 	{{if .PackageDescriptor.Metadata.Dependencies}} | 	{{if .PackageDescriptor.Metadata.Dependencies}} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user