diff --git a/src/NuGetizer.Tasks/CreatePackage.cs b/src/NuGetizer.Tasks/CreatePackage.cs index 8fd17957..bf77a577 100644 --- a/src/NuGetizer.Tasks/CreatePackage.cs +++ b/src/NuGetizer.Tasks/CreatePackage.cs @@ -237,16 +237,30 @@ void GeneratePackage(Stream output = null) Uri.TryCreate(manifest.Metadata.Repository.Url, UriKind.Absolute, out var uri) && uri.Host.EndsWith("github.com")) { - // expr to match markdown links. use named groups to capture the link text and url. - linkExpr ??= new Regex(@"\[(?[^\]]+)\]\((?[^)]+)\)", RegexOptions.None); + // expr to match markdown links with optional title. use named groups to capture the link text, url and optional title. + linkExpr ??= new Regex(@"\[(?[^\]]+)\]\((?[^\s)]+)(?:\s+""(?[^""]*)"")?\)", RegexOptions.None); var repoUrl = manifest.Metadata.Repository.Url.TrimEnd('/'); + + // Extract owner and repo from URL for raw.githubusercontent.com format + var repoPath = uri.AbsolutePath.TrimStart('/'); + var rawBaseUrl = $"https://raw.githubusercontent.com/{repoPath}"; + replaced = linkExpr.Replace(replaced, match => { var url = match.Groups["url"].Value; + var title = match.Groups["title"].Value; + + // Check if the URL is already absolute if (Uri.IsWellFormedUriString(url, UriKind.Absolute)) return match.Value; - var newUrl = $"{repoUrl}/blob/{manifest.Metadata.Repository.Commit}/{url.TrimStart('/')}"; + // Use raw.githubusercontent.com format for proper image display on nuget.org + var newUrl = $"{rawBaseUrl}/{manifest.Metadata.Repository.Commit}/{url.TrimStart('/')}"; + + // Preserve the title if present + if (!string.IsNullOrEmpty(title)) + return $"[{match.Groups["text"].Value}]({newUrl} \"{title}\")"; + return $"[{match.Groups["text"].Value}]({newUrl})"; }); } diff --git a/src/NuGetizer.Tests/CreatePackageTests.cs b/src/NuGetizer.Tests/CreatePackageTests.cs index c9813088..9442ed7b 100644 --- a/src/NuGetizer.Tests/CreatePackageTests.cs +++ b/src/NuGetizer.Tests/CreatePackageTests.cs @@ -333,7 +333,114 @@ public void when_readme_has_relativeurl_then_expands_github_url() var readme = File.ReadAllText(file.Source); - Assert.Contains("[license](https://github.com/devlooped/nugetizer/blob/9dc2cb5de/license.txt)", readme); + Assert.Contains("[license](https://raw.githubusercontent.com/devlooped/nugetizer/9dc2cb5de/license.txt)", readme); + } + + [Fact] + public void when_readme_has_link_with_tooltip_then_preserves_tooltip() + { + var content = Path.GetTempFileName(); + File.WriteAllText(content, "See ![avatar](avatars/user.png \"User Avatar\")."); + task.Contents = new[] + { + new TaskItem(content, new Metadata + { + { MetadataName.PackageId, task.Manifest.GetMetadata("Id") }, + { MetadataName.PackFolder, PackFolderKind.None }, + { MetadataName.PackagePath, "readme.md" } + }), + }; + + task.Manifest.SetMetadata("Readme", "readme.md"); + task.Manifest.SetMetadata("RepositoryType", "git"); + task.Manifest.SetMetadata("RepositoryUrl", "https://github.com/devlooped/nugetizer"); + task.Manifest.SetMetadata("RepositorySha", "abc123def"); + + createPackage = true; + ExecuteTask(out var manifest); + + Assert.NotNull(manifest); + + var file = manifest.Files.FirstOrDefault(f => Path.GetFileName(f.Target) == manifest.Metadata.Readme); + Assert.NotNull(file); + Assert.True(File.Exists(file.Source)); + + var readme = File.ReadAllText(file.Source); + + Assert.Contains("[avatar](https://raw.githubusercontent.com/devlooped/nugetizer/abc123def/avatars/user.png \"User Avatar\")", readme); + } + + [Fact] + public void when_readme_has_absolute_url_then_does_not_replace() + { + var content = Path.GetTempFileName(); + File.WriteAllText(content, "[![badge](https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/user.png \"User\")](https://github.com/user)"); + task.Contents = new[] + { + new TaskItem(content, new Metadata + { + { MetadataName.PackageId, task.Manifest.GetMetadata("Id") }, + { MetadataName.PackFolder, PackFolderKind.None }, + { MetadataName.PackagePath, "readme.md" } + }), + }; + + task.Manifest.SetMetadata("Readme", "readme.md"); + task.Manifest.SetMetadata("RepositoryType", "git"); + task.Manifest.SetMetadata("RepositoryUrl", "https://github.com/devlooped/nugetizer"); + task.Manifest.SetMetadata("RepositorySha", "abc123def"); + + createPackage = true; + ExecuteTask(out var manifest); + + Assert.NotNull(manifest); + + var file = manifest.Files.FirstOrDefault(f => Path.GetFileName(f.Target) == manifest.Metadata.Readme); + Assert.NotNull(file); + Assert.True(File.Exists(file.Source)); + + var readme = File.ReadAllText(file.Source); + + // Should NOT prepend repository URL to absolute URLs + Assert.DoesNotContain("https://raw.githubusercontent.com/devlooped/nugetizer/abc123def/https://raw.githubusercontent.com", readme); + // Should preserve the original absolute URL + Assert.Contains("https://raw.githubusercontent.com/devlooped/sponsors/main/.github/avatars/user.png", readme); + } + + [Fact] + public void when_readme_has_image_link_then_uses_raw_url() + { + var content = Path.GetTempFileName(); + File.WriteAllText(content, "![Image](img/logo.png)"); + task.Contents = new[] + { + new TaskItem(content, new Metadata + { + { MetadataName.PackageId, task.Manifest.GetMetadata("Id") }, + { MetadataName.PackFolder, PackFolderKind.None }, + { MetadataName.PackagePath, "readme.md" } + }), + }; + + task.Manifest.SetMetadata("Readme", "readme.md"); + task.Manifest.SetMetadata("RepositoryType", "git"); + task.Manifest.SetMetadata("RepositoryUrl", "https://github.com/devlooped/nugetizer"); + task.Manifest.SetMetadata("RepositorySha", "abc123def"); + + createPackage = true; + ExecuteTask(out var manifest); + + Assert.NotNull(manifest); + + var file = manifest.Files.FirstOrDefault(f => Path.GetFileName(f.Target) == manifest.Metadata.Readme); + Assert.NotNull(file); + Assert.True(File.Exists(file.Source)); + + var readme = File.ReadAllText(file.Source); + + // Should use raw.githubusercontent.com format for proper image display + Assert.Contains("https://raw.githubusercontent.com/devlooped/nugetizer/abc123def/img/logo.png", readme); + Assert.DoesNotContain("/blob/", readme); } [Fact]