Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,8 @@ private async Task WriteBundleFileAsync(Bundle bundledData, string outputPath, C
_logger.LogInformation("Output file already exists, using unique filename: {OutputPath}", outputPath);
}

// Write bundled file
await _fileSystem.File.WriteAllTextAsync(outputPath, bundledYaml, ctx);
// Write bundled file with explicit UTF-8 encoding to ensure proper character handling
await _fileSystem.File.WriteAllTextAsync(outputPath, bundledYaml, Encoding.UTF8, ctx);
_logger.LogInformation("Created bundled changelog: {OutputPath}", outputPath);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using System.IO.Abstractions;
using System.Text;
using Elastic.Changelog.Configuration;
using Elastic.Changelog.Serialization;
using Elastic.Documentation;
Expand Down Expand Up @@ -43,8 +44,8 @@ public async Task<bool> WriteChangelogAsync(
var filename = GenerateFilename(collector, input, prUrl);
var filePath = fileSystem.Path.Combine(outputDir, filename);

// Write file
await fileSystem.File.WriteAllTextAsync(filePath, yamlContent, ctx);
// Write file with explicit UTF-8 encoding to ensure proper character handling
await fileSystem.File.WriteAllTextAsync(filePath, yamlContent, Encoding.UTF8, ctx);
logger.LogInformation("Created changelog fragment: {FilePath}", filePath);

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ private async Task<bool> ProcessPrReference(
var slug = ChangelogTextUtilities.GenerateSlug(title);
var filename = $"{prRef.PrNumber}-{finalType.ToStringFast(true)}-{slug}.yaml";
var filePath = _fileSystem.Path.Combine(outputDir, filename);
await _fileSystem.File.WriteAllTextAsync(filePath, yamlContent, ctx);
await _fileSystem.File.WriteAllTextAsync(filePath, yamlContent, Encoding.UTF8, ctx);

createdFiles.Add(filename);
_logger.LogDebug("Created changelog: {FilePath}", filePath);
Expand Down Expand Up @@ -324,7 +324,7 @@ private async Task<string> CreateBundleFile(
// Name format: <version>-<product>-bundle.yml
var bundleFilename = $"{productInfo.Target}-{productInfo.Product}-bundle.yml";
var bundlePath = _fileSystem.Path.Combine(bundlesDir, bundleFilename);
await _fileSystem.File.WriteAllTextAsync(bundlePath, yamlContent, ctx);
await _fileSystem.File.WriteAllTextAsync(bundlePath, yamlContent, Encoding.UTF8, ctx);

return bundlePath;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ public async Task RenderAsciidoc(ChangelogRenderContext context, Cancel ctx)
if (!string.IsNullOrWhiteSpace(asciidocDir) && !fileSystem.Directory.Exists(asciidocDir))
_ = fileSystem.Directory.CreateDirectory(asciidocDir);

await fileSystem.File.WriteAllTextAsync(asciidocPath, sb.ToString(), ctx);
await fileSystem.File.WriteAllTextAsync(asciidocPath, sb.ToString(), Encoding.UTF8, ctx);
}

private static void RenderSectionHeader(StringBuilder sb, string anchorPrefix, string titleSlug, string title)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ protected async Task WriteOutputFileAsync(string outputDir, string titleSlug, st
if (!string.IsNullOrWhiteSpace(outputDirectory) && !FileSystem.Directory.Exists(outputDirectory))
_ = FileSystem.Directory.CreateDirectory(outputDirectory);

await FileSystem.File.WriteAllTextAsync(outputPath, content, ctx);
await FileSystem.File.WriteAllTextAsync(outputPath, content, Encoding.UTF8, ctx);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ public static class ChangelogYamlSerialization
new StaticSerializerBuilder(new ChangelogYamlStaticContext())
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitEmptyCollections)
.WithQuotingNecessaryStrings()
.DisableAliases()
.Build();

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,93 @@ public async Task BundleChangelogs_WithResolve_CopiesChangelogContents()
bundleContent.Should().Contain("description: This is a test feature");
}

[Fact]
public async Task BundleChangelogs_WithResolve_PreservesSpecialCharactersInUtf8()
{
// Arrange - Create changelog with special characters that could be corrupted
// These characters were reported as being corrupted to "&o0" and "*o0" in the original issue

// language=yaml
var changelog1 =
"""
title: Feature with special characters & symbols
type: feature
products:
- product: elasticsearch
target: 9.3.0
lifecycle: ga
pr: https://github.com/elastic/elasticsearch/pull/100
description: |
This feature includes special characters:
- Ampersand: & symbol
- Asterisk: * symbol
- Other special chars: < > " ' / \
- Unicode: © ® ™ € £ ¥
""";

var file1 = FileSystem.Path.Combine(_changelogDir, "1755268130-special-chars.yaml");
await FileSystem.File.WriteAllTextAsync(file1, changelog1, System.Text.Encoding.UTF8, TestContext.Current.CancellationToken);

var outputPath = FileSystem.Path.Combine(FileSystem.Path.GetTempPath(), Guid.NewGuid().ToString(), "bundle.yaml");
var input = new BundleChangelogsArguments
{
Directory = _changelogDir,
All = true,
Resolve = true,
Output = outputPath
};

// Act
var result = await Service.BundleChangelogs(Collector, input, TestContext.Current.CancellationToken);

// Assert
result.Should().BeTrue();
Collector.Errors.Should().Be(0);

// Read the bundle file with explicit UTF-8 encoding
var bundleContent = await FileSystem.File.ReadAllTextAsync(input.Output, System.Text.Encoding.UTF8, TestContext.Current.CancellationToken);

// Verify special characters are preserved correctly (not corrupted)
// The original issue reported "&o0" and "*o0" corruption, so we verify the characters are correct
bundleContent.Should().Contain("&"); // Ampersand should be preserved
bundleContent.Should().Contain("Feature with special characters & symbols"); // Ampersand in title
bundleContent.Should().Contain("Ampersand: & symbol"); // Ampersand in description

// Check that asterisk appears correctly (not corrupted to "*o0")
bundleContent.Should().Contain("*"); // Asterisk should be preserved
bundleContent.Should().Contain("Asterisk: * symbol"); // Asterisk in description

// Verify the ampersand and asterisk are not corrupted
// The corruption pattern would be "&o0" or "*o0" appearing where we expect "&" or "*"
// We check that the title contains the correct pattern, not the corrupted one
var titleLine = bundleContent.Split('\n').FirstOrDefault(l => l.Contains("title:"));
titleLine.Should().NotBeNull();
titleLine.Should().Contain("&");
titleLine.Should().NotContain("&o0"); // Should not be corrupted in title

// Verify no corruption patterns exist (these would indicate encoding issues)
bundleContent.Should().NotContain("&o0"); // Should not contain corrupted ampersand
bundleContent.Should().NotContain("*o0"); // Should not contain corrupted asterisk

// Verify other special characters are preserved
bundleContent.Should().Contain("<");
bundleContent.Should().Contain(">");
bundleContent.Should().Contain("\"");

// Verify Unicode characters are preserved
bundleContent.Should().Contain("©");
bundleContent.Should().Contain("®");
bundleContent.Should().Contain("™");
bundleContent.Should().Contain("€");

// Verify the content structure is correct
bundleContent.Should().Contain("title: Feature with special characters & symbols");
bundleContent.Should().Contain("type: feature");
bundleContent.Should().Contain("product: elasticsearch");
bundleContent.Should().Contain("target: 9.3.0");
bundleContent.Should().Contain("lifecycle: ga");
}

[Fact]
public async Task BundleChangelogs_WithDirectoryOutputPath_CreatesDefaultFilename()
{
Expand Down
Loading