From c78563cbb1f5fb3ed3a0c28878d2ff333031a397 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Tue, 6 Oct 2026 11:28:21 +0200
Subject: [PATCH 1/3] Test MSKit_DefaultPackageIconUrl next to the kit's icon
- iconUrl only with the kit's embedded icon, never with the project's
own PackageIcon or an empty property; MSKITPKG008 only for a
project-set PackageIconUrl
---
tests/package-icon.sh | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/tests/package-icon.sh b/tests/package-icon.sh
index 6e18a21..35b0666 100644
--- a/tests/package-icon.sh
+++ b/tests/package-icon.sh
@@ -21,6 +21,10 @@ 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_icon_url() {
+ 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)"; }
@@ -58,3 +62,21 @@ ic_icon own logo.png
ic_entry own logo.png
ic_no_entry own icon.png
ic_quiet own MSKITPKG015
+
+# MSKit_DefaultPackageIconUrl goes into the nuspec next to the kit's own embedded icon only.
+icon_url=https://example.com/fixture-icon.png
+ic_icon_url default ""
+ic_pack url Icons -p:MSKit_DefaultPackageIconUrl=$icon_url
+ic_icon url icon.png
+ic_icon_url url "$icon_url"
+ic_quiet url MSKITPKG008
+ic_quiet url NU5048
+
+ic_pack own-url Own -p:MSKit_DefaultPackageIconUrl=$icon_url
+ic_icon own-url logo.png
+ic_icon_url own-url ""
+
+# A PackageIconUrl the project sets itself is still reported, and the kit's default does not replace it.
+ic_pack project-url Icons -p:PackageIconUrl=https://example.com/own.png -p:MSKit_DefaultPackageIconUrl=$icon_url
+ic_icon_url project-url https://example.com/own.png
+ic_warns project-url MSKITPKG008
From 1eeea98883e880dc41d4d7785114bdd66aacc025 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Tue, 6 Oct 2026 11:36:01 +0200
Subject: [PATCH 2/3] Add MSKit_DefaultPackageIconUrl next to the kit's icon
- package.targets writes it as PackageIconUrl only when the kit's own
icon is packed and the project set no PackageIconUrl
- MSKITPKG008 skips the kit-set URL, still reports a project-set one
---
.../audit/audit.package.targets | 4 ++--
.../msbuild/DragoAnt.MSBuildKit.Packaging/package.targets | 6 ++++++
2 files changed, 8 insertions(+), 2 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 bf15e43..1bad3e6 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/audit/audit.package.targets
@@ -68,8 +68,8 @@
<_MSKit_PkgFinding Include="MSKITPKG007" Condition="'$(PackageLicenseUrl)'!=''"
Text="Package '$(PackageId)' uses the deprecated PackageLicenseUrl. Replace it with PackageLicenseExpression (an SPDX id) or PackageLicenseFile."
Rule="$(_MSKit_PkgRules)#licensing" />
- <_MSKit_PkgFinding Include="MSKITPKG008" Condition="'$(PackageIconUrl)'!=''"
- Text="Package '$(PackageId)' uses the deprecated PackageIconUrl. Pack the image and point PackageIcon (or the kit's PackageIconPath) at it."
+ <_MSKit_PkgFinding Include="MSKITPKG008" Condition="'$(PackageIconUrl)'!='' AND '$(_MSKit_SetsIconUrl)'!='True'"
+ Text="Package '$(PackageId)' uses the deprecated PackageIconUrl. Pack the image and point PackageIcon (or the kit's PackageIconPath) at it; for older clients, set MSKit_DefaultPackageIconUrl to write the URL next to the kit's icon."
Rule="$(_MSKit_PkgRules)#icon" />
<_MSKit_PkgFinding Include="MSKITPKG009" Condition="'$(_MSKit_RelativeImages)'!=''"
Text="The package README of '$(PackageId)' has relative images, which nuget.org does not render: $(_MSKit_RelativeImages). Use absolute https URLs from an allowed host."
diff --git a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets
index b299d47..2c9767e 100644
--- a/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets
+++ b/kit/.toolkit/msbuild/DragoAnt.MSBuildKit.Packaging/package.targets
@@ -56,6 +56,12 @@
icon$([System.IO.Path]::GetExtension('$(PackageIconPath)').ToLowerInvariant())
<_MSKit_PacksIcon>True
+
+
+ $(MSKit_DefaultPackageIconUrl)
+ <_MSKit_SetsIconUrl>True
+
$(PackageIconPath)
$(MSBuildProjectDirectory)/$(PackageIcon)
From 614513ade93457a88381dc31f3fab15298233265 Mon Sep 17 00:00:00 2001
From: VasiliyF <5789590+vfofanov@users.noreply.github.com>
Date: Tue, 6 Oct 2026 11:39:26 +0200
Subject: [PATCH 3/3] Document MSKit_DefaultPackageIconUrl
- property row, the icon line in packaging, the MSKITPKG008 section,
the owner-layer note and an Unreleased changelog entry
---
CHANGELOG.md | 1 +
docs/customizing.md | 1 +
docs/packaging.md | 4 ++--
docs/reference/codes.md | 2 +-
docs/reference/properties.md | 1 +
5 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f4ca31..2218b93 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,7 @@ All notable changes to this project are documented here. The format follows [Kee
### Added
- Every warning and error links to its section of the [code reference](./docs/reference/codes.md) (`HelpLink`, shown by the terminal logger and IDEs). `MSKit_CodesHelpBaseUrl` points the links at another copy of the page.
+- `MSKit_DefaultPackageIconUrl`: when the kit packs its own icon, this URL is also written as `PackageIconUrl`, so clients that predate embedded icons show it; nuget.org keeps showing the embedded one. Empty by default; an owner sets it in its owner layer. `MSKITPKG008` now reports only a `PackageIconUrl` the project sets itself. See [docs/packaging.md](./docs/packaging.md#package-metadata).
- `manager/`: the first build of `mskit-manager`, the `DragoAnt.MSBuildKit.Manager` .NET tool (`net8.0`, `net10.0`, `RollForward=Major`) that will install, update and migrate the kit. This build has one command, `status [--json]`, which prints the tool version; the logo goes to stderr, only on a terminal and never with `--no-logo`, so `--json` output always parses. Each run writes a log under `/mskit-manager/logs/`, named after the command, newest 20 kept. Not published yet.
### Changed
diff --git a/docs/customizing.md b/docs/customizing.md
index 351030a..839e619 100644
--- a/docs/customizing.md
+++ b/docs/customizing.md
@@ -22,6 +22,7 @@ Almost every default is written as ``, so the first va
| `ManufacturerName`, `FullManufacturerName` | `DragoAnt` | **unconditional**: set them below the kit import to change them; they feed `Authors`, `Company`, `Copyright` |
| `PackageLicenseExpression` | `MIT`, unless `PackageLicenseFile` is set | |
| `PackageIconPath` | `.toolkit/res/package.icon.png` | |
+| `MSKit_DefaultPackageIconUrl` | not set | another owner sets it here to a hosted copy of its icon, so older clients show it too ([Packaging](./packaging.md#package-metadata)) |
| `MSKit_PrereleasePackagePrefix` | `DragoAnt.` | [prerelease check](./build.md#reference-checks) |
| `MSKit_IsStableBranchRegex` | `^(main\|release/.+)$` | |
| `MSKit_VersionStrategy` | `ReleaseTag` | [Versioning](./versioning.md) |
diff --git a/docs/packaging.md b/docs/packaging.md
index 1c86cfa..50e1735 100644
--- a/docs/packaging.md
+++ b/docs/packaging.md
@@ -23,7 +23,7 @@ For projects with `IsPackable=True` (the owner layer makes that the default; tes
| `PackageLicenseExpression` | `MIT` (owner layer), unless `PackageLicenseFile` is set | [licensing](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#licensing) |
| `PackageRequireLicenseAcceptance` | `false` | |
| `Authors`, `Copyright` | the owner; `Copyright (c) ` ([Build](./build.md#language-and-product-defaults)) | [copyright](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#copyright) |
-| `PackageIcon` | `PackageIconPath` (owner layer: `.toolkit/res/package.icon.png`, 128×128; PNG or JPEG) 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) |
+| `PackageIcon` | `PackageIconPath` (owner layer: `.toolkit/res/package.icon.png`, 128×128; PNG or JPEG) packed as `icon` (`icon.png`, `icon.jpg`); a `PackageIcon` the project sets is kept. With the kit's icon, `MSKit_DefaultPackageIconUrl` also writes `PackageIconUrl` (nuget.org shows the embedded icon, older clients the URL) | [icon](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#icon) |
| `PackageReadmeFile` | generated from `MSKit_PackageReadmeFrom` ([Package readme](./package-readme.md)), else `package.readme.md` next to the csproj, else `README.md` next to it; packed as `readme.md`. `MSKit_PackageReadmeSourcePath` names another file | [README](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#readme) |
| `RepositoryType`, `RepositoryUrl`, `PackageProjectUrl` | `git`; `GITHUB_SERVER_URL/GITHUB_REPOSITORY` on GitHub Actions, else the git remote [Source Link](https://learn.microsoft.com/dotnet/standard/library-guidance/sourcelink) reads, without `.git` | [repository](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#repository-type-and-url) |
| `PackageReleaseNotes` | on a `https://github.com/` repository the tag's release page on a tag build, else its releases page; on other hosts the releases page the generated readme links (needs `MSKit_PackageReadmeFrom`). `MSKit_DefaultReleaseNotes=False` turns the default off | [release notes](https://learn.microsoft.com/nuget/create-packages/package-authoring-best-practices#release-notes) |
@@ -47,7 +47,7 @@ Set `MSKit_PackageValidationBaselineVersion` to the last published version once
| [`MSKITPKG005`](./reference/codes.md#mskitpkg005) | no icon |
| [`MSKITPKG006`](./reference/codes.md#mskitpkg006) | no licence expression or file |
| [`MSKITPKG007`](./reference/codes.md#mskitpkg007) | the deprecated `PackageLicenseUrl` is set |
-| [`MSKITPKG008`](./reference/codes.md#mskitpkg008) | the deprecated `PackageIconUrl` is set |
+| [`MSKITPKG008`](./reference/codes.md#mskitpkg008) | the project sets the deprecated `PackageIconUrl` (the one `MSKit_DefaultPackageIconUrl` writes is not reported) |
| [`MSKITPKG009`](./reference/codes.md#mskitpkg009) | the README has relative images |
| [`MSKITPKG010`](./reference/codes.md#mskitpkg010) | the README contains HTML |
| [`MSKITPKG011`](./reference/codes.md#mskitpkg011) | the README uses GitHub alerts (`> [!NOTE]`) |
diff --git a/docs/reference/codes.md b/docs/reference/codes.md
index bea7a21..e5add78 100644
--- a/docs/reference/codes.md
+++ b/docs/reference/codes.md
@@ -48,7 +48,7 @@ Earlier releases put an underscore between the prefix and the family; each secti
### MSKITPKG008
-`MSKITPKG008` (formerly `MSKIT_PKG008`) — the deprecated `PackageIconUrl` is set. Pack the image and use `PackageIcon` or `PackageIconPath`.
+`MSKITPKG008` (formerly `MSKIT_PKG008`) — the project sets the deprecated `PackageIconUrl`. Pack the image and use `PackageIcon` or `PackageIconPath`. To keep a URL for older clients, set `MSKit_DefaultPackageIconUrl` instead: the kit writes it only next to its own embedded icon, and that one is not reported.
### MSKITPKG009
diff --git a/docs/reference/properties.md b/docs/reference/properties.md
index 868d637..5ccdde9 100644
--- a/docs/reference/properties.md
+++ b/docs/reference/properties.md
@@ -95,6 +95,7 @@ Every property and item the kit sets or reads, grouped by topic. **Set** = a val
| --- | --- | --- | --- |
| `IsPackable` | set | `True` (owner layer); `False` for test projects and code fixes | |
| `PackageIconPath` | set | `.toolkit/res/package.icon.png` (owner layer) | The icon (PNG or JPEG), packed as `icon` |
+| `MSKit_DefaultPackageIconUrl` | set | empty (off) | A URL of the same icon, written as `PackageIconUrl` next to the kit's embedded icon for older clients; ignored when the project sets `PackageIcon` or `PackageIconUrl` ([Packaging](../packaging.md#package-metadata)) |
| `MSKit_PackageIconSourcePath` | out | | The icon file the checks read |
| `MSKit_PackageReadmeSourcePath` | set | `package.readme.md`, else `README.md` next to the csproj | The README packed as `readme.md` |
| `MSKit_PackageValidationBaselineVersion` | set | empty | The release package validation compares against |