From 1722712e90ae19f89087127b93fe3a260161d230 Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Wed, 5 Feb 2025 13:18:30 +0100 Subject: [PATCH 01/13] Add matrix --- .github/workflows/pr.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e09833356..9dea980f0 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -19,7 +19,11 @@ env: jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] steps: - uses: actions/checkout@v4 @@ -37,6 +41,7 @@ jobs: run: ./build.sh publishbinaries - uses: actions/upload-artifact@v4 + if: matrix.os == 'ubuntu-latest' with: name: docs-builder-binary path: .artifacts/publish/docs-builder/release/docs-builder From a09e2df484de769834e0245f069c4a4379745ba1 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 12 Feb 2025 11:49:26 +0100 Subject: [PATCH 02/13] Split windows out of matrix and set shell explicitly --- .github/workflows/pr.yml | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 9dea980f0..e46f65316 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest, windows-latest, macos-latest ] + os: [ ubuntu-latest, macos-latest ] steps: - uses: actions/checkout@v4 @@ -47,3 +47,24 @@ jobs: path: .artifacts/publish/docs-builder/release/docs-builder if-no-files-found: error retention-days: 1 + + build-windows: + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, windows-latest, macos-latest ] + steps: + - uses: actions/checkout@v4 + + - name: Bootstrap Action Workspace + id: bootstrap + uses: ./.github/actions/bootstrap + + - name: Test + shell: cmd + run: build.bat test + + - name: Publish AOT + shell: cmd + run: build.bat publishbinaries From 09d8505e46ca2d61ea1d4020345cd78a51b7ade9 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Wed, 12 Feb 2025 11:53:20 +0100 Subject: [PATCH 03/13] skip compile of build in release mode --- docs-builder.sln | 1 - 1 file changed, 1 deletion(-) diff --git a/docs-builder.sln b/docs-builder.sln index 5a621c48f..957279704 100644 --- a/docs-builder.sln +++ b/docs-builder.sln @@ -77,7 +77,6 @@ Global {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10857974-6CF1-42B5-B793-AAA988BD7348}.Debug|Any CPU.Build.0 = Debug|Any CPU {10857974-6CF1-42B5-B793-AAA988BD7348}.Release|Any CPU.ActiveCfg = Release|Any CPU - {10857974-6CF1-42B5-B793-AAA988BD7348}.Release|Any CPU.Build.0 = Release|Any CPU {28350800-B44B-479B-86E2-1D39E321C0B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {28350800-B44B-479B-86E2-1D39E321C0B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {28350800-B44B-479B-86E2-1D39E321C0B4}.Release|Any CPU.ActiveCfg = Release|Any CPU From 61d7e599063d0ad481be56cf89d2e6231177377c Mon Sep 17 00:00:00 2001 From: Jan Calanog Date: Fri, 14 Mar 2025 16:48:11 +0100 Subject: [PATCH 04/13] Refactor --- .github/workflows/pr.yml | 44 +++++++--------------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e46f65316..7b5ca0578 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -5,10 +5,6 @@ on: permissions: contents: read - packages: read - id-token: write - pull-requests: write - deployments: write concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -23,7 +19,10 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest, macos-latest ] + os: + - ubuntu-latest + - macos-latest + - windows-latest steps: - uses: actions/checkout@v4 @@ -32,39 +31,10 @@ jobs: uses: ./.github/actions/bootstrap - name: Build - run: ./build.sh + run: dotnet run --project build -c release - name: Test - run: ./build.sh test + run: dotnet run --project build -c release -- test - name: Publish AOT - run: ./build.sh publishbinaries - - - uses: actions/upload-artifact@v4 - if: matrix.os == 'ubuntu-latest' - with: - name: docs-builder-binary - path: .artifacts/publish/docs-builder/release/docs-builder - if-no-files-found: error - retention-days: 1 - - build-windows: - runs-on: windows-latest - strategy: - fail-fast: false - matrix: - os: [ ubuntu-latest, windows-latest, macos-latest ] - steps: - - uses: actions/checkout@v4 - - - name: Bootstrap Action Workspace - id: bootstrap - uses: ./.github/actions/bootstrap - - - name: Test - shell: cmd - run: build.bat test - - - name: Publish AOT - shell: cmd - run: build.bat publishbinaries + run: dotnet run --project build -c release -- publishbinaries From 43bc9c172f3347d5824ab1bb65f99b655cae91ff Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 14 Mar 2025 18:15:36 -0300 Subject: [PATCH 05/13] Fix Elastic.Markdown.Tests on Windows * DiagnosticLinkInlineParser.UpdateLinkUrl()'s Path traversal is adjusted before processing into the HTML payload * Mover received Windows-specific handling --- src/Elastic.Markdown.Refactor/Move.cs | 16 ++++++++++ .../DiagnosticLinkInlineParser.cs | 5 ++++ .../DocSet/BreadCrumbTests.cs | 2 +- .../DocSet/NavigationTests.cs | 30 +++++++++---------- .../DocSet/NestedTocTests.cs | 2 +- .../Inline/InlneBaseTests.cs | 7 +++-- .../Mover/MoverTests.cs | 18 +++++------ 7 files changed, 52 insertions(+), 28 deletions(-) diff --git a/src/Elastic.Markdown.Refactor/Move.cs b/src/Elastic.Markdown.Refactor/Move.cs index a20b031e5..71af0dff3 100644 --- a/src/Elastic.Markdown.Refactor/Move.cs +++ b/src/Elastic.Markdown.Refactor/Move.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using Elastic.Markdown.IO; using Microsoft.Extensions.Logging; @@ -64,6 +65,9 @@ private async Task SetupChanges(ChangeSet changeSet, Cancel ctx) var fullPath = Path.GetFullPath(Path.Combine(sourceDirectory, originalPath)); var relativePath = Path.GetRelativePath(targetDirectory, fullPath); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + relativePath = relativePath.Replace('\\', '/'); + newPath = originalPath.StartsWith("./", OrdinalIgnoreCase) && !relativePath.StartsWith("./", OrdinalIgnoreCase) ? "./" + relativePath : relativePath; @@ -258,6 +262,15 @@ string targetPath var absolutStyleSource = $"/{relativeToDocsFolder}"; var relativeToDocsFolderTarget = Path.GetRelativePath(documentationSet.SourceDirectory.FullName, targetPath); var absoluteStyleTarget = $"/{relativeToDocsFolderTarget}"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + relativeSource = relativeSource.Replace('\\', '/'); + relativeSourceWithDotSlash = relativeSourceWithDotSlash.Replace('\\', '/'); + absolutStyleSource = absolutStyleSource.Replace('\\', '/'); + absoluteStyleTarget = absoluteStyleTarget.Replace('\\', '/'); + } + return ( relativeSource, relativeSourceWithDotSlash, @@ -298,6 +311,9 @@ private string ReplaceLinks( : $"[{match.Groups[1].Value}]({relativeTarget}{anchor})"; } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + newLink = newLink.Replace('\\', '/'); + var lineNumber = content[..match.Index].Count(c => c == '\n') + 1; var columnNumber = match.Index - content.LastIndexOf('\n', match.Index); if (!_linkModifications.ContainsKey(changeSet)) diff --git a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs index e298d1ca3..e0cacf619 100644 --- a/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs +++ b/src/Elastic.Markdown/Myst/InlineParsers/DiagnosticLinkInlineParser.cs @@ -5,6 +5,7 @@ using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.IO.Abstractions; +using System.Runtime.InteropServices; using System.Text.RegularExpressions; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Helpers; @@ -271,6 +272,10 @@ private static void UpdateLinkUrl(LinkInline link, string url, ParserContext con if (!string.IsNullOrWhiteSpace(url) && !string.IsNullOrWhiteSpace(urlPathPrefix)) url = $"{urlPathPrefix.TrimEnd('/')}{url}"; + // When running on Windows, path traversal results must be normalized prior to being used in a URL + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + url = url.Replace('\\', '/'); + link.Url = string.IsNullOrEmpty(anchor) ? url : $"{url}#{anchor}"; } diff --git a/tests/Elastic.Markdown.Tests/DocSet/BreadCrumbTests.cs b/tests/Elastic.Markdown.Tests/DocSet/BreadCrumbTests.cs index 1316c5d81..0b4279034 100644 --- a/tests/Elastic.Markdown.Tests/DocSet/BreadCrumbTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/BreadCrumbTests.cs @@ -12,7 +12,7 @@ public class BreadCrumbTests(ITestOutputHelper output) : NavigationTestsBase(out [Fact] public void ParsesATableOfContents() { - var doc = Generator.DocumentationSet.Files.FirstOrDefault(f => f.RelativePath == "testing/nested/index.md") as MarkdownFile; + var doc = Generator.DocumentationSet.Files.FirstOrDefault(f => f.RelativePath == Path.Combine("testing", "nested", "index.md")) as MarkdownFile; doc.Should().NotBeNull(); diff --git a/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs b/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs index a01991ec4..43025cb5c 100644 --- a/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs @@ -17,38 +17,38 @@ public void ParsesNestedFoldersAndPrefixesPaths() { Configuration.ImplicitFolders.Should().NotBeNullOrEmpty(); Configuration.ImplicitFolders.Should() - .Contain("testing/nested"); + .Contain(Path.Combine("testing", "nested")); } [Fact] public void ParsesFilesAndPrefixesPaths() => Configuration.Files.Should() .Contain("index.md") - .And.Contain("syntax/index.md"); + .And.Contain(Path.Combine("syntax", "index.md")); [Fact] public void ParsesRedirects() { Configuration.Redirects.Should() .NotBeNullOrEmpty() - .And.ContainKey("testing/redirects/first-page-old.md") - .And.ContainKey("testing/redirects/second-page-old.md") - .And.ContainKey("testing/redirects/4th-page.md") - .And.ContainKey("testing/redirects/third-page.md"); + .And.ContainKey(Path.Combine("testing", "redirects", "first-page-old.md")) + .And.ContainKey(Path.Combine("testing", "redirects", "second-page-old.md")) + .And.ContainKey(Path.Combine("testing", "redirects", "4th-page.md")) + .And.ContainKey(Path.Combine("testing", "redirects", "third-page.md")); - var redirect1 = Configuration.Redirects!["testing/redirects/first-page-old.md"]; - redirect1.To.Should().Be("testing/redirects/second-page.md"); + var redirect1 = Configuration.Redirects![Path.Combine("testing", "redirects", "first-page-old.md")]; + redirect1.To.Should().Be(Path.Combine("testing", "redirects", "second-page.md")); - var redirect2 = Configuration.Redirects!["testing/redirects/second-page-old.md"]; + var redirect2 = Configuration.Redirects![Path.Combine("testing", "redirects", "second-page-old.md")]; redirect2.Many.Should().NotBeNullOrEmpty().And.HaveCount(2); - redirect2.Many![0].To.Should().Be("testing/redirects/second-page.md"); - redirect2.Many![1].To.Should().Be("testing/redirects/third-page.md"); + redirect2.Many![0].To.Should().Be(Path.Combine("testing", "redirects", "second-page.md")); + redirect2.Many![1].To.Should().Be(Path.Combine("testing", "redirects", "third-page.md")); redirect2.To.Should().BeNullOrEmpty(); - var redirect3 = Configuration.Redirects!["testing/redirects/third-page.md"]; - redirect3.To.Should().Be("testing/redirects/third-page.md"); + var redirect3 = Configuration.Redirects![Path.Combine("testing", "redirects", "third-page.md")]; + redirect3.To.Should().Be(Path.Combine("testing", "redirects", "third-page.md")); - var redirect4 = Configuration.Redirects!["testing/redirects/4th-page.md"]; - redirect4.To.Should().Be("testing/redirects/5th-page.md"); + var redirect4 = Configuration.Redirects![Path.Combine("testing", "redirects", "4th-page.md")]; + redirect4.To.Should().Be(Path.Combine("testing", "redirects", "5th-page.md")); } } diff --git a/tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs b/tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs index d89f5934d..cc8855b6c 100644 --- a/tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/NestedTocTests.cs @@ -12,7 +12,7 @@ public class NestedTocTests(ITestOutputHelper output) : NavigationTestsBase(outp [Fact] public void InjectsNestedTocsIntoDocumentationSet() { - var doc = Generator.DocumentationSet.Files.FirstOrDefault(f => f.RelativePath == "development/index.md") as MarkdownFile; + var doc = Generator.DocumentationSet.Files.FirstOrDefault(f => f.RelativePath == Path.Combine("development", "index.md")) as MarkdownFile; doc.Should().NotBeNull(); diff --git a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs index c8d1f4807..e4f0cfd63 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlneBaseTests.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information using System.IO.Abstractions.TestingHelpers; +using System.Runtime.InteropServices; using Elastic.Markdown.IO; using FluentAssertions; using JetBrains.Annotations; @@ -103,8 +104,10 @@ protected InlineTest( // ReSharper disable once VirtualMemberCallInConstructor // nasty but sub implementations won't use class state. AddToFileSystem(FileSystem); - - var root = FileSystem.DirectoryInfo.New(Path.Combine(Paths.Root.FullName, "docs/")); + var baseRootPath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Paths.Root.FullName.Replace('\\', '/') + : Paths.Root.FullName; + var root = FileSystem.DirectoryInfo.New($"{baseRootPath}/docs/"); FileSystem.GenerateDocSetYaml(root, globalVariables); Collector = new TestDiagnosticsCollector(output); diff --git a/tests/Elastic.Markdown.Tests/Mover/MoverTests.cs b/tests/Elastic.Markdown.Tests/Mover/MoverTests.cs index ee41e4770..a2af9f44c 100644 --- a/tests/Elastic.Markdown.Tests/Mover/MoverTests.cs +++ b/tests/Elastic.Markdown.Tests/Mover/MoverTests.cs @@ -28,15 +28,15 @@ public async Task RelativeLinks() linkModifications.Should().HaveCount(3); - Path.GetRelativePath(".", linkModifications[0].SourceFile).Should().Be("testing/mover/first-page.md"); + Path.GetRelativePath(".", linkModifications[0].SourceFile).Should().Be(Path.Combine("testing", "mover", "first-page.md")); linkModifications[0].OldLink.Should().Be("[Link to second page](second-page.md)"); linkModifications[0].NewLink.Should().Be("[Link to second page](../testing/mover/second-page.md)"); - Path.GetRelativePath(".", linkModifications[1].SourceFile).Should().Be("testing/mover/second-page.md"); + Path.GetRelativePath(".", linkModifications[1].SourceFile).Should().Be(Path.Combine("testing", "mover", "second-page.md")); linkModifications[1].OldLink.Should().Be("[Link to first page](first-page.md)"); linkModifications[1].NewLink.Should().Be("[Link to first page](../../new-folder/hello-world.md)"); - Path.GetRelativePath(".", linkModifications[2].SourceFile).Should().Be("testing/mover/second-page.md"); + Path.GetRelativePath(".", linkModifications[2].SourceFile).Should().Be(Path.Combine("testing", "mover", "second-page.md")); linkModifications[2].OldLink.Should().Be("[Absolut link to first page](/testing/mover/first-page.md)"); linkModifications[2].NewLink.Should().Be("[Absolut link to first page](/new-folder/hello-world.md)"); } @@ -56,15 +56,15 @@ public async Task MoveToFolder() var linkModifications = mover.LinkModifications[changeSet]; linkModifications.Should().HaveCount(3); - Path.GetRelativePath(".", linkModifications[0].SourceFile).Should().Be("testing/mover/first-page.md"); + Path.GetRelativePath(".", linkModifications[0].SourceFile).Should().Be(Path.Combine("testing", "mover", "first-page.md")); linkModifications[0].OldLink.Should().Be("[Link to second page](second-page.md)"); linkModifications[0].NewLink.Should().Be("[Link to second page](../testing/mover/second-page.md)"); - Path.GetRelativePath(".", linkModifications[1].SourceFile).Should().Be("testing/mover/second-page.md"); + Path.GetRelativePath(".", linkModifications[1].SourceFile).Should().Be(Path.Combine("testing", "mover", "second-page.md")); linkModifications[1].OldLink.Should().Be("[Link to first page](first-page.md)"); linkModifications[1].NewLink.Should().Be("[Link to first page](../../new-folder/first-page.md)"); - Path.GetRelativePath(".", linkModifications[2].SourceFile).Should().Be("testing/mover/second-page.md"); + Path.GetRelativePath(".", linkModifications[2].SourceFile).Should().Be(Path.Combine("testing", "mover", "second-page.md")); linkModifications[2].OldLink.Should().Be("[Absolut link to first page](/testing/mover/first-page.md)"); linkModifications[2].NewLink.Should().Be("[Absolut link to first page](/new-folder/first-page.md)"); } @@ -84,15 +84,15 @@ public async Task MoveFolderToFolder() var linkModifications = mover.LinkModifications[changeSet]; linkModifications.Should().HaveCount(3); - Path.GetRelativePath(".", linkModifications[0].SourceFile).Should().Be("testing/mover/first-page.md"); + Path.GetRelativePath(".", linkModifications[0].SourceFile).Should().Be(Path.Combine("testing", "mover", "first-page.md")); linkModifications[0].OldLink.Should().Be("[Link to second page](second-page.md)"); linkModifications[0].NewLink.Should().Be("[Link to second page](../testing/mover/second-page.md)"); - Path.GetRelativePath(".", linkModifications[1].SourceFile).Should().Be("testing/mover/second-page.md"); + Path.GetRelativePath(".", linkModifications[1].SourceFile).Should().Be(Path.Combine("testing", "mover", "second-page.md")); linkModifications[1].OldLink.Should().Be("[Link to first page](first-page.md)"); linkModifications[1].NewLink.Should().Be("[Link to first page](../../new-folder/first-page.md)"); - Path.GetRelativePath(".", linkModifications[2].SourceFile).Should().Be("testing/mover/second-page.md"); + Path.GetRelativePath(".", linkModifications[2].SourceFile).Should().Be(Path.Combine("testing", "mover", "second-page.md")); linkModifications[2].OldLink.Should().Be("[Absolut link to first page](/testing/mover/first-page.md)"); linkModifications[2].NewLink.Should().Be("[Absolut link to first page](/new-folder/first-page.md)"); } From 28c742a08fa201a43d2c58630563770b3ce13e39 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 14 Mar 2025 18:28:40 -0300 Subject: [PATCH 06/13] Undo NavigationTests as they were working as intended. --- .../DocSet/NavigationTests.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs b/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs index 43025cb5c..64e52f786 100644 --- a/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs +++ b/tests/Elastic.Markdown.Tests/DocSet/NavigationTests.cs @@ -31,24 +31,24 @@ public void ParsesRedirects() { Configuration.Redirects.Should() .NotBeNullOrEmpty() - .And.ContainKey(Path.Combine("testing", "redirects", "first-page-old.md")) - .And.ContainKey(Path.Combine("testing", "redirects", "second-page-old.md")) - .And.ContainKey(Path.Combine("testing", "redirects", "4th-page.md")) - .And.ContainKey(Path.Combine("testing", "redirects", "third-page.md")); + .And.ContainKey("testing/redirects/first-page-old.md") + .And.ContainKey("testing/redirects/second-page-old.md") + .And.ContainKey("testing/redirects/4th-page.md") + .And.ContainKey("testing/redirects/third-page.md"); - var redirect1 = Configuration.Redirects![Path.Combine("testing", "redirects", "first-page-old.md")]; - redirect1.To.Should().Be(Path.Combine("testing", "redirects", "second-page.md")); + var redirect1 = Configuration.Redirects!["testing/redirects/first-page-old.md"]; + redirect1.To.Should().Be("testing/redirects/second-page.md"); - var redirect2 = Configuration.Redirects![Path.Combine("testing", "redirects", "second-page-old.md")]; + var redirect2 = Configuration.Redirects!["testing/redirects/second-page-old.md"]; redirect2.Many.Should().NotBeNullOrEmpty().And.HaveCount(2); - redirect2.Many![0].To.Should().Be(Path.Combine("testing", "redirects", "second-page.md")); - redirect2.Many![1].To.Should().Be(Path.Combine("testing", "redirects", "third-page.md")); + redirect2.Many![0].To.Should().Be("testing/redirects/second-page.md"); + redirect2.Many![1].To.Should().Be("testing/redirects/third-page.md"); redirect2.To.Should().BeNullOrEmpty(); - var redirect3 = Configuration.Redirects![Path.Combine("testing", "redirects", "third-page.md")]; - redirect3.To.Should().Be(Path.Combine("testing", "redirects", "third-page.md")); + var redirect3 = Configuration.Redirects!["testing/redirects/third-page.md"]; + redirect3.To.Should().Be("testing/redirects/third-page.md"); - var redirect4 = Configuration.Redirects![Path.Combine("testing", "redirects", "4th-page.md")]; - redirect4.To.Should().Be(Path.Combine("testing", "redirects", "5th-page.md")); + var redirect4 = Configuration.Redirects!["testing/redirects/4th-page.md"]; + redirect4.To.Should().Be("testing/redirects/5th-page.md"); } } From b92cfe4f5e5b7a8116c46e2286234f42a955a671 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 14 Mar 2025 19:35:36 -0300 Subject: [PATCH 07/13] Add prerelease step for Windows to configure line feed behavior. --- .github/workflows/pr.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 7b5ca0578..1d1ab68b7 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -30,6 +30,12 @@ jobs: id: bootstrap uses: ./.github/actions/bootstrap + - name: Configure Git for Windows + if: matrix.os == 'windows-latest' + run: | + git config --global core.autocrlf input + git config --global core.eol lf + - name: Build run: dotnet run --project build -c release From 6042a10d7bafa98c0426f90bc8449d0bf852cb41 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 14 Mar 2025 19:41:53 -0300 Subject: [PATCH 08/13] Configure before checkout --- .github/workflows/pr.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 1d1ab68b7..df3a0446e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -24,17 +24,17 @@ jobs: - macos-latest - windows-latest steps: - - uses: actions/checkout@v4 - - - name: Bootstrap Action Workspace - id: bootstrap - uses: ./.github/actions/bootstrap - - name: Configure Git for Windows if: matrix.os == 'windows-latest' run: | git config --global core.autocrlf input git config --global core.eol lf + + - uses: actions/checkout@v4 + + - name: Bootstrap Action Workspace + id: bootstrap + uses: ./.github/actions/bootstrap - name: Build run: dotnet run --project build -c release From 241e4bd08abdad8fb94b515c2578324b1099d708 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 14 Mar 2025 19:44:57 -0300 Subject: [PATCH 09/13] Remove eol directive from Git Configuration --- .github/workflows/pr.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index df3a0446e..b07c70e2b 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -28,7 +28,6 @@ jobs: if: matrix.os == 'windows-latest' run: | git config --global core.autocrlf input - git config --global core.eol lf - uses: actions/checkout@v4 From 41d1a911a6db59a435cec4ccf6379978f914d116 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 14 Mar 2025 19:51:10 -0300 Subject: [PATCH 10/13] Remove PR as it breaks linting rules. Changing approach to individually handling tests. --- .github/workflows/pr.yml | 7 +------ tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index b07c70e2b..fc3c8a992 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -23,12 +23,7 @@ jobs: - ubuntu-latest - macos-latest - windows-latest - steps: - - name: Configure Git for Windows - if: matrix.os == 'windows-latest' - run: | - git config --global core.autocrlf input - + steps: - uses: actions/checkout@v4 - name: Bootstrap Action Workspace diff --git a/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs b/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs index bba7ed365..93ed7dece 100644 --- a/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs +++ b/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs @@ -118,7 +118,7 @@ public void ParsesMagicCallOuts() => Block!.CallOuts [Fact] public void RendersExpectedHtml() => - Html.Should().Contain(""" + Html.ReplaceLineEndings().Should().Contain("""
var x = 1; 1
@@ -132,7 +132,7 @@ public void RendersExpectedHtml() =>
 		                      
  • Marking the first callout
  • Marking the second callout
  • - """); + """.ReplaceLineEndings()); [Fact] From 167e81782b39c8532d2ff9d1f1d73c94f4a5f864 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Fri, 14 Mar 2025 20:00:14 -0300 Subject: [PATCH 11/13] Handle more tests. --- tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs | 8 ++++---- tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs | 8 ++++---- tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs b/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs index 93ed7dece..af2f3c0b2 100644 --- a/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs +++ b/tests/Elastic.Markdown.Tests/CodeBlocks/CallOutTests.cs @@ -367,12 +367,12 @@ public void ParsesCallouts() => Block!.CallOuts [Fact] public void RenderedHtmlContainsCallouts() => - Html.Should().Contain(""" + Html.ReplaceLineEndings().Should().Contain("""
    1. First callout
    2. Second callout
    - """); + """.ReplaceLineEndings()); } public class CodeBlockWithMultipleCommentTypesThenList(ITestOutputHelper output) : CodeBlockCallOutTests(output, "csharp", @@ -426,10 +426,10 @@ public void ParsesCallouts() => Block!.CallOuts [Fact] public void RendersIntermediateParagraph() => - Html.Should().Contain(""" + Html.ReplaceLineEndings().Should().Contain("""

    This is an intermediate paragraph

      - """); + """.ReplaceLineEndings()); } public class CodeBlockWithCommentBlocksTwoParagraphsThenList(ITestOutputHelper output) : CodeBlockCallOutTests(output, "csharp", diff --git a/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs index 351981ba8..b344f372e 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlineAnchorTests.cs @@ -111,11 +111,11 @@ public class InlineAnchorInHeading(ITestOutputHelper output) : BlockTest // language=html - Html.Should().Be( + Html.ReplaceLineEndings().TrimEnd().Should().Be( """ - """.TrimEnd() + """.ReplaceLineEndings().TrimEnd() ); } @@ -128,11 +128,11 @@ public class ExplicitSlugInHeader(ITestOutputHelper output) : BlockTest // language=html - Html.Should().Be( + Html.ReplaceLineEndings().TrimEnd().Should().Be( """ - """.TrimEnd() + """.ReplaceLineEndings().TrimEnd() ); } diff --git a/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs b/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs index d2409987f..7f4c6c9e1 100644 --- a/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs +++ b/tests/Elastic.Markdown.Tests/Inline/InlineLinkTests.cs @@ -267,7 +267,7 @@ public class CommentedNonExistingLinks2(ITestOutputHelper output) : LinkTestBase [Fact] public void GeneratesHtml() => // language=html - Html.TrimEnd().Should().Be(""" + Html.ReplaceLineEndings().TrimEnd().Should().Be("""

      Links:

      • Special Requirements
      • @@ -275,7 +275,7 @@ public void GeneratesHtml() => - """); + """.ReplaceLineEndings()); [Fact] public void HasErrors() => Collector.Diagnostics.Should().HaveCount(0); From a35adb97d621f6c43854829e2afa96170c083744 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Sat, 15 Mar 2025 15:42:04 -0300 Subject: [PATCH 12/13] Fix path inference on Windows for link references and MarkdownFile Url property. --- src/Elastic.Markdown/IO/MarkdownFile.cs | 3 +++ src/Elastic.Markdown/IO/State/LinkReference.cs | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Elastic.Markdown/IO/MarkdownFile.cs b/src/Elastic.Markdown/IO/MarkdownFile.cs index 869b99b18..548e0e297 100644 --- a/src/Elastic.Markdown/IO/MarkdownFile.cs +++ b/src/Elastic.Markdown/IO/MarkdownFile.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information using System.IO.Abstractions; +using System.Runtime.InteropServices; using Elastic.Markdown.Diagnostics; using Elastic.Markdown.Helpers; using Elastic.Markdown.IO.Navigation; @@ -95,6 +96,8 @@ public string Url get { var relativePath = RelativePathUrl; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + relativePath = relativePath.Replace('\\', '/'); return Path.GetFileName(relativePath) == "index.md" ? $"{UrlPathPrefix}/{relativePath.Remove(relativePath.LastIndexOf("index.md", StringComparison.Ordinal), "index.md".Length)}" : $"{UrlPathPrefix}/{relativePath.Remove(relativePath.LastIndexOf(SourceFile.Extension, StringComparison.Ordinal), SourceFile.Extension.Length)}"; diff --git a/src/Elastic.Markdown/IO/State/LinkReference.cs b/src/Elastic.Markdown/IO/State/LinkReference.cs index c5a0a6b14..96c52e4c8 100644 --- a/src/Elastic.Markdown/IO/State/LinkReference.cs +++ b/src/Elastic.Markdown/IO/State/LinkReference.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Serialization; using Elastic.Markdown.IO.Discovery; @@ -70,7 +71,9 @@ public static LinkReference Create(DocumentationSet set) var crossLinks = set.Build.Collector.CrossLinks.ToHashSet().ToArray(); var links = set.MarkdownFiles.Values .Select(m => (m.RelativePath, File: m)) - .ToDictionary(k => k.RelativePath, v => + .ToDictionary(k => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? k.RelativePath.Replace('\\', '/') + : k.RelativePath, v => { var anchors = v.File.Anchors.Count == 0 ? null : v.File.Anchors.ToArray(); return new LinkMetadata { Anchors = anchors, Hidden = v.File.Hidden }; From 86ead9cc864859cb3481e28365ae90c201906d5f Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Sat, 15 Mar 2025 15:42:44 -0300 Subject: [PATCH 13/13] Fix MarkdownResultsAssertions on Windows --- tests/authoring/Framework/MarkdownResultsAssertions.fs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/authoring/Framework/MarkdownResultsAssertions.fs b/tests/authoring/Framework/MarkdownResultsAssertions.fs index 4749d1a68..52a745a75 100644 --- a/tests/authoring/Framework/MarkdownResultsAssertions.fs +++ b/tests/authoring/Framework/MarkdownResultsAssertions.fs @@ -11,6 +11,7 @@ open DiffPlex.DiffBuilder.Model open FsUnit.Xunit open JetBrains.Annotations open Xunit.Sdk +open System.IO [] module ResultsAssertions = @@ -50,7 +51,7 @@ module ResultsAssertions = let result = results.MarkdownResults - |> Seq.tryFind (fun m -> m.File.RelativePath = file) + |> Seq.tryFind (fun m -> m.File.RelativePath = (string file).Replace('/', Path.DirectorySeparatorChar)) match result with | None ->