l104tvHU!Ox(R
z1;xH?hO4UB^P~0ghi!wpXSs9Iiy7D+fURc{maNIQxEAE36dhEZvk_Zs`$gcis
z@sfee4uo^b<7com7IqVSKD)f*k^AB**qVH^UNG%xX*c@U!&r5d6}m^LG8A4-*lQN9
zCXx4*FQeX5C~4ZdcUFB;h6>oC_M@h#6z?UMF;wxD+c+o6qF?}7MN2ik2
zOr%dgkA-o?ulv7aj2lJvV;($J+&rGY-HZIvGlx`J5rs~oU1#EkK&f8x3?3QK%*+;j
z323OIJzy^6{Jd+I{i7*&Y$t^ANShq;Q%a|*INSab=o+s1D8PUxzOH?Ytlk;yXJJd8
zB%<*rs2Q#XbjV4J;KPhAucm3w&s2ahq@qS1Qb~y?z@meZHeda!ZWy(%fiWi6yI*I-
z$i~-9Qvshn^)LOa>nI>FkjxpguA5mb?|*iuZ@v$cf3MJGfL8n&hW|hr73UbJbDS1<
zwJ2&N5D|iWE}xC9R!QkK_7N0DpKTw%1^b$yxo?56>QYkM5;s&gItSY>b~!HfR0_N~
zv~x%0hM;bB1TuE=%}JUE%mPGHj9MpRRo{?@tJ`{gGE~;!b@8IDVON?JWnpw4FTq42
zpKB*lc34e^c-6kte!A|hz?PM;=4bXRxJ3Xm8me-z5loLAR69#K-*>0}cg2^42fNBW
zy_F4bXcK4L+Jj-EHJVGh+ER7tH
z)Za6-r?qf~0qpC{;CKM~by%gOHj<*8<}*L@7B5?x-GY@YqbqP7hr*0rzBw!Wel?{G
z-IQyU#Y*c}iAFs~jTWsL7lzO6vKBmZTIv{!7}P}8<_BW%gqT)xUdy?jkhmQxAWa1f
z=NgPDoE_Y|FY5xV@B7=vW6e=Cu5*MH2?qr
literal 0
HcmV?d00001
diff --git a/tests/fixtures/PackageIcon/Icons/small.jpg b/tests/fixtures/PackageIcon/Icons/small.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..c096e1a9bfaca9180abb8b7b9a5f4c11665718a6
GIT binary patch
literal 775
zcmex=U
zW@aX!`+*(+s;&jfGq4D<3Mm>ovIz$!vMUve7&T5@$f4}C@t|nX#SbdRNkvVZTw>x9
zl2WQ_>Kd9_CZ=ZQ7M51dF0O9w9-dyoA)#U65s^{JDXD4c8JStdC8cHM6_r)ZEv;?s
z9i3g1CQq3GGAU*RJ2V
zdF$b$$4{OPfBE|D`;VW$K>lK6V1{@LNJ2b@<}X2@znFm0!om*n7b88f2KE_o9%~}Y
zXK;@p{B?_ghnW!=dCYq@n|(yg=yiG2kx8}fo;_NVd0cC5dOt7o
zI1#YJ^0|e~XFK%^B{%2QMj!02u3yyf$zmE?_@)5&Q*SEr_#9b2Sgzh>-|^-4e(ua$
zvOlHj7A~>*lzFRB|LOi&MHhWHKVxp(I^E(J$H7OrWKvqK~KDvE625|NkZc?{pEy
literal 0
HcmV?d00001
diff --git a/tests/fixtures/PackageIcon/Own/Own.csproj b/tests/fixtures/PackageIcon/Own/Own.csproj
new file mode 100644
index 0000000..7975093
--- /dev/null
+++ b/tests/fixtures/PackageIcon/Own/Own.csproj
@@ -0,0 +1,13 @@
+
+
+
+
+ DragoAnt.Fixture.OwnIcon
+ logo.png
+
+
+
+
+
+
+
diff --git a/tests/fixtures/PackageIcon/Own/Placeholder.cs b/tests/fixtures/PackageIcon/Own/Placeholder.cs
new file mode 100644
index 0000000..28196b4
--- /dev/null
+++ b/tests/fixtures/PackageIcon/Own/Placeholder.cs
@@ -0,0 +1,4 @@
+namespace Own;
+
+/// Placeholder.
+public static class Placeholder;
diff --git a/tests/fixtures/PackageIcon/Own/logo.png b/tests/fixtures/PackageIcon/Own/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e890a2973d5ebd15b6604fcb3f48cba42a2d0c15
GIT binary patch
literal 10282
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
literal 0
HcmV?d00001
diff --git a/tests/package-icon.sh b/tests/package-icon.sh
new file mode 100644
index 0000000..0a42146
--- /dev/null
+++ b/tests/package-icon.sh
@@ -0,0 +1,60 @@
+# Package icon: packed under icon, and a project's own PackageIcon is kept.
+# Sourced by tests/run.sh: uses its pass, bad, $out, $here, $clean_env and $icon_fixture.
+
+rm -rf "$icon_fixture"/*/bin "$icon_fixture"/*/obj
+
+ic_pack() {
+ name="$1"; proj="$2"; shift 2
+ rm -rf "$out/icon-$name"
+ : > "$out/icon-$name.nuspec"; : > "$out/icon-$name.files"
+ if $clean_env dotnet pack "$icon_fixture/$proj/$proj.csproj" -c Release -nologo -o "$out/icon-$name" \
+ -p:MSKit_PackageChecksAsErrors=False "$@" > "$out/icon-$name.log" 2>&1; then
+ pass "icon $name: pack succeeds"
+ nupkg=$(ls "$out/icon-$name"/*.nupkg | grep -v '\.snupkg$' | head -n 1)
+ unzip -p "$nupkg" '*.nuspec' | tr -d '\r' > "$out/icon-$name.nuspec"
+ unzip -l "$nupkg" | awk 'NR>3 {print $4}' | grep -v '^$' > "$out/icon-$name.files"
+ else
+ bad "icon $name: pack failed (see $out/icon-$name.log)"; tail -n 20 "$out/icon-$name.log"
+ fi
+}
+ic_icon() {
+ actual=$(sed -n 's:.*\(.*\).*:\1:p' "$out/icon-$1.nuspec")
+ [ "$actual" = "$2" ] && pass "icon $1: nuspec $actual" || bad "icon $1: nuspec expected '$2', got '$actual'"
+}
+ic_entry() { grep -qx -- "$2" "$out/icon-$1.files" && pass "icon $1: nupkg contains $2" || bad "icon $1: nupkg lacks $2"; }
+ic_no_entry() { grep -qx -- "$2" "$out/icon-$1.files" && bad "icon $1: nupkg still contains $2" || pass "icon $1: nupkg has no $2"; }
+ic_warns() { grep -q "warning $2" "$out/icon-$1.log" && pass "icon $1 warns $2" || bad "icon $1 does not warn $2 (see $out/icon-$1.log)"; }
+ic_quiet() { grep -q "$2" "$out/icon-$1.log" && bad "icon $1 reports $2 (see $out/icon-$1.log)" || pass "icon $1 does not report $2"; }
+
+# The kit's PNG icon.
+ic_pack default Icons
+ic_icon default icon.png
+ic_entry default icon.png
+ic_quiet default MSKIT_PKG015
+
+# A JPEG PackageIconPath keeps its extension.
+ic_pack jpg Icons -p:FixtureIcon=icon.jpg
+ic_icon jpg icon.jpg
+ic_entry jpg icon.jpg
+ic_no_entry jpg icon.png
+ic_quiet jpg NU5046
+ic_quiet jpg MSKIT_PKG015
+
+# A JPEG of the wrong size is still reported.
+ic_pack jpg-small Icons -p:FixtureIcon=small.jpg
+ic_icon jpg-small icon.jpg
+ic_warns jpg-small MSKIT_PKG015
+
+# An upper-case extension is lowercased.
+ic_pack upper Icons -p:FixtureIcon=ICON.PNG
+ic_icon upper icon.png
+ic_entry upper icon.png
+ic_no_entry upper ICON.PNG
+ic_no_entry upper icon.PNG
+
+# A project that sets PackageIcon keeps it; the kit's icon is not added.
+ic_pack own Own
+ic_icon own logo.png
+ic_entry own logo.png
+ic_no_entry own icon.png
+ic_quiet own MSKIT_PKG015
diff --git a/tests/run.sh b/tests/run.sh
index cbae9c0..7347351 100644
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -10,6 +10,7 @@ sample="$here/samples/MinimalLibrary"
fixtures="$here/tests/fixtures/PackageChecks"
readme_fixture="$here/tests/fixtures/PackageReadme"
tfm_fixture="$here/tests/fixtures/TfmConstants"
+icon_fixture="$here/tests/fixtures/PackageIcon"
out="$here/dist/selftest"
run_tests=1
[ "${1:-}" = "--skip-tests" ] && run_tests=0
@@ -19,7 +20,7 @@ failures=0
pass() { echo "PASS $*"; }
bad() { echo "FAIL $*"; failures=$((failures+1)); }
-for root in "$sample" "$fixtures" "$readme_fixture" "$tfm_fixture"; do
+for root in "$sample" "$fixtures" "$readme_fixture" "$tfm_fixture" "$icon_fixture"; do
sh "$kit/.toolkit/update.sh" --source "$kit" --root "$root" > "$out/install.log" || { cat "$out/install.log"; exit 1; }
done
pass "update.sh installed the kit into the sample and the fixtures"
@@ -109,6 +110,7 @@ grep -q "MSKIT_PKG" "$out/checks-skip.log" && bad "MSKit_SkipPackageChecks=All d
. "$here/tests/package-readme.sh"
. "$here/tests/tfm-constants.sh"
+. "$here/tests/package-icon.sh"
echo
if [ "$failures" -eq 0 ]; then echo "self-test: all checks passed"; else echo "self-test: $failures failure(s)"; exit 1; fi
From a04f62c971390c3d2e6395ed890403e5c654a414 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Tue, 6 Oct 2026 06:55:04 +0200
Subject: [PATCH 2/4] Pack the icon under its own extension, accept JPEG in
MSKIT_PKG015
A JPEG PackageIconPath was packed as icon.png and failed the pack
with NU5046. The icon is now packed as icon., lowercased,
and the nuspec names that file. nuget.org accepts PNG and JPEG, so
MSKIT_PKG015 reads a JPEG's size too and reports the format it found.
---
.../audit/audit.package.targets | 8 ++--
.../package.targets | 6 +--
.../package.tasks.cs.txt | 43 +++++++++++++++----
3 files changed, 42 insertions(+), 15 deletions(-)
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
index 563bc1e..fa84441 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
@@ -29,7 +29,7 @@
-
+
@@ -60,7 +60,7 @@
Text="Package '$(PackageId)' has no PackageTags. Add a few search terms that are not already in the package id."
Rule="$(_MSKit_PkgRules)#tags" />
<_MSKit_PkgFinding Include="MSKIT_PKG005" Condition="'$(PackageIcon)'==''"
- Text="Package '$(PackageId)' has no icon. Set PackageIconPath to a 128x128 PNG (the company layer of the kit sets one for every package)."
+ Text="Package '$(PackageId)' has no icon. Set PackageIconPath to a 128x128 PNG or JPEG (the company layer of the kit sets one for every package)."
Rule="$(_MSKit_PkgRules)#icon" />
<_MSKit_PkgFinding Include="MSKIT_PKG006" Condition="'$(PackageLicenseExpression)'=='' AND '$(PackageLicenseFile)'==''"
Text="Package '$(PackageId)' has no licence. Without one nobody may use it. Set PackageLicenseExpression to an SPDX id (for example MIT)."
@@ -89,8 +89,8 @@
<_MSKit_PkgFinding Include="MSKIT_PKG014" Condition="'$(_MSKit_RepoUrlFinal)'=='' OR '$(PackageProjectUrl)'==''"
Text="Package '$(PackageId)' has no repository or project URL. Set RepositoryUrl, or build from a git clone whose origin remote Source Link can read."
Rule="$(_MSKit_PkgRules)#repository-type-and-url" />
- <_MSKit_PkgFinding Include="MSKIT_PKG015" Condition="'$(MSKit_PackageIconSourcePath)'!='' AND Exists('$(MSKit_PackageIconSourcePath)') AND ('$(_MSKit_IconIsPng)'!='True' OR '$(_MSKit_IconWidth)'!='$(MSKit_PackageIconSize)' OR '$(_MSKit_IconHeight)'!='$(MSKit_PackageIconSize)')"
- Text="Package '$(PackageId)' icon is not a $(MSKit_PackageIconSize)x$(MSKit_PackageIconSize) PNG (PNG: $(_MSKit_IconIsPng), size: $(_MSKit_IconWidth)x$(_MSKit_IconHeight))."
+ <_MSKit_PkgFinding Include="MSKIT_PKG015" Condition="'$(MSKit_PackageIconSourcePath)'!='' AND Exists('$(MSKit_PackageIconSourcePath)') AND (('$(_MSKit_IconFormat)'!='PNG' AND '$(_MSKit_IconFormat)'!='JPEG') OR '$(_MSKit_IconWidth)'!='$(MSKit_PackageIconSize)' OR '$(_MSKit_IconHeight)'!='$(MSKit_PackageIconSize)')"
+ Text="Package '$(PackageId)' icon is not a $(MSKit_PackageIconSize)x$(MSKit_PackageIconSize) PNG or JPEG (format: $(_MSKit_IconFormat), size: $(_MSKit_IconWidth)x$(_MSKit_IconHeight))."
Rule="$(_MSKit_PkgRules)#icon" />
<_MSKit_PkgFinding Include="MSKIT_PKG016" Condition="'$(PackageReleaseNotes)'==''"
Text="Package '$(PackageId)' has no PackageReleaseNotes. Set them, or a link to the changelog (the kit links the releases page by default on GitHub, and on any host the generated readme knows with MSKit_PackageReadmeFrom)."
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets
index 9b2c60d..b299d47 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets
@@ -51,9 +51,9 @@
+ packed as icon. (icon.png, icon.jpg) unless the project sets PackageIcon. -->
- icon.png
+ icon$([System.IO.Path]::GetExtension('$(PackageIconPath)').ToLowerInvariant())
<_MSKit_PacksIcon>True
@@ -63,7 +63,7 @@
-
+
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt
index d0d5ddf..0ef49d8 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.tasks.cs.txt
@@ -31,7 +31,7 @@ public sealed class MSKit_PackageAssets : Task
[Output] public string HtmlTags { get; set; }
[Output] public string Alerts { get; set; }
[Output] public bool HasMermaid { get; set; }
- [Output] public bool IconIsPng { get; set; }
+ [Output] public string IconFormat { get; set; }
[Output] public int IconWidth { get; set; }
[Output] public int IconHeight { get; set; }
@@ -96,16 +96,18 @@ public sealed class MSKit_PackageAssets : Task
if (!string.IsNullOrEmpty(IconPath) && File.Exists(IconPath))
{
- var header = new byte[24];
- using (var stream = File.OpenRead(IconPath))
+ var icon = File.ReadAllBytes(IconPath);
+ IconFormat = "unknown";
+ if (icon.Length >= 24 && icon[0] == 0x89 && icon[1] == 0x50 && icon[2] == 0x4E && icon[3] == 0x47)
{
- var read = stream.Read(header, 0, header.Length);
- IconIsPng = read == 24 && header[0] == 0x89 && header[1] == 0x50 && header[2] == 0x4E && header[3] == 0x47;
+ IconFormat = "PNG";
+ IconWidth = BigEndian(icon, 16, 4);
+ IconHeight = BigEndian(icon, 20, 4);
}
- if (IconIsPng)
+ else if (icon.Length >= 4 && icon[0] == 0xFF && icon[1] == 0xD8)
{
- IconWidth = (header[16] << 24) | (header[17] << 16) | (header[18] << 8) | header[19];
- IconHeight = (header[20] << 24) | (header[21] << 16) | (header[22] << 8) | header[23];
+ IconFormat = "JPEG";
+ ReadJpegSize(icon);
}
}
@@ -116,6 +118,31 @@ public sealed class MSKit_PackageAssets : Task
Alerts = string.Join(", ", alerts.Distinct());
}
+ private static int BigEndian(byte[] bytes, int offset, int count)
+ {
+ var value = 0;
+ for (var i = 0; i < count; i++) value = (value << 8) | bytes[offset + i];
+ return value;
+ }
+
+ // Walks the JPEG segments to the first start-of-frame marker (SOF0..SOF15 except DHT, JPG, DAC).
+ private void ReadJpegSize(byte[] jpeg)
+ {
+ var i = 2;
+ while (i + 8 < jpeg.Length && jpeg[i] == 0xFF)
+ {
+ var marker = jpeg[i + 1];
+ if (marker == 0xFF) { i++; continue; }
+ if (marker >= 0xC0 && marker <= 0xCF && marker != 0xC4 && marker != 0xC8 && marker != 0xCC)
+ {
+ IconHeight = BigEndian(jpeg, i + 5, 2);
+ IconWidth = BigEndian(jpeg, i + 7, 2);
+ return;
+ }
+ i += 2 + BigEndian(jpeg, i + 2, 2);
+ }
+ }
+
public string OutputPath { get; set; }
public string FindingsBase { get; set; }
public string ReleasesUrlPath { get; set; }
From 0b523157e925e463f67f14d34b191a9225638867 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Tue, 6 Oct 2026 06:56:03 +0200
Subject: [PATCH 3/4] Document the icon name and JPEG icons
---
CHANGELOG.md | 4 ++++
README.md | 4 ++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 788b188..018721b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project are documented here. The format follows [Kee
## [Unreleased]
+### Fixed
+
+- A JPEG `PackageIconPath` no longer fails `dotnet pack` with NU5046: the icon is packed as `icon.`, lowercased (`icon.jpg`), and the nuspec names that file. `MSKIT_PKG015` accepts a 128×128 JPEG as well as a PNG, as nuget.org does.
+
## [0.2.1] - 2026-10-05
### Added
diff --git a/README.md b/README.md
index da3035d..1946065 100644
--- a/README.md
+++ b/README.md
@@ -100,7 +100,7 @@ Defaults apply to projects with `IsPackable=True` and yield to any value you set
| --- | --- | --- |
| `PackageLicenseExpression` | `MIT` (owner layer) | [licensing](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#licensing) |
| `Authors`, `Copyright` | owner name; `Copyright (c) ` | [copyright](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#copyright) |
-| `PackageIcon` | `.toolkit/res/package.icon.png` (128×128), packed as `icon.png` | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) |
+| `PackageIcon` | `PackageIconPath`, by default `.toolkit/res/package.icon.png` (128×128), packed as `icon.` (`icon.png`, `icon.jpg`); a `PackageIcon` the project sets is kept | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) |
| `PackageReadmeFile` | generated from `MSKit_PackageReadmeFrom` ([package readme](./docs/package-readme.md)), else `package.readme.md` (else `README.md`) next to the csproj, packed as `readme.md` | [README](https://learn.microsoft.com/nuget/reference/msbuild-targets#packagereadmefile) |
| `RepositoryUrl`, `PackageProjectUrl` | from `GITHUB_REPOSITORY`, else the git remote via Source Link | [repository](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#repository-type-and-url) |
| `PackageReleaseNotes` | the GitHub release page of the tag, else the releases page; on other hosts, the releases page the generated readme links (`MSKit_PackageReadmeFrom`) | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) |
@@ -130,7 +130,7 @@ Defaults apply to projects with `IsPackable=True` and yield to any value you set
| `MSKIT_PKG012` | the package README loads images from hosts nuget.org blocks | [allowed images](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org#allowed-domains-for-images-and-badges) |
| `MSKIT_PKG013` | the package version is not SemVer 2.0 | [package version](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#package-version) |
| `MSKIT_PKG014` | no repository or project URL | [repository](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#repository-type-and-url) |
-| `MSKIT_PKG015` | the icon is not a 128×128 PNG (`MSKit_PackageIconSize`) | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) |
+| `MSKIT_PKG015` | the icon is not a 128×128 PNG or JPEG (`MSKit_PackageIconSize`) | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) |
| `MSKIT_PKG016` | no `PackageReleaseNotes` | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) |
| `MSKIT_PKG017` | the package README has relative links | [package README](https://learn.microsoft.com/nuget/nuget-org/package-readme-on-nuget-org) |
| `MSKIT_PKG018` | an open-source licence with an "All rights reserved" copyright | [copyright](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#copyright) |
From bc2464aaf95e66187b59961c530c3c821ed005e7 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Tue, 6 Oct 2026 08:07:07 +0200
Subject: [PATCH 4/4] Link the icon fix in the changelog
---
CHANGELOG.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 018721b..553f38a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,7 +6,7 @@ All notable changes to this project are documented here. The format follows [Kee
### Fixed
-- A JPEG `PackageIconPath` no longer fails `dotnet pack` with NU5046: the icon is packed as `icon.`, lowercased (`icon.jpg`), and the nuspec names that file. `MSKIT_PKG015` accepts a 128×128 JPEG as well as a PNG, as nuget.org does.
+- A JPEG `PackageIconPath` no longer fails `dotnet pack` with NU5046: the icon is packed as `icon.`, lowercased (`icon.jpg`), and the nuspec names that file. `MSKIT_PKG015` accepts a 128×128 JPEG as well as a PNG, as nuget.org does ([#11](https://github.com/DragoAnt/MSBuildKit/pull/11)).
## [0.2.1] - 2026-10-05