File size: 49,524 Bytes
44229d2 f33ff0a 44229d2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 | <!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>NuGet package license check — check csproj before packing</title>
<meta name="description" content="This free browser tool checks a csproj for NuGet package metadata, file, and target framework issues and shows fix lines.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/nuget-pack-csproj-gate"><style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:anywhere}#out pre,#out code{white-space:pre-wrap;overflow-wrap:anywhere}@media (max-width:640px){table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static!important;justify-content:flex-end;margin:0 0 10px}h1,h2,h3,h4,p,li,dd,dt,blockquote,figcaption,summary,label,.res,.lead{overflow-wrap:anywhere}label{max-width:100%;box-sizing:border-box}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style>
</head>
<body>
<div class="wrap">
<h1>NuGet package license check — check csproj before packing</h1>
<p class="lede">nuget pack csproj check: flags PackageLicenseUrl (NU5125), PackageIconUrl (NU5048), non-SPDX licence expressions, unpacked icon/readme/licence files and net8.0/net9.0 (end of support 2026-11-10), with the fix line. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false"><Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0;net9.0</TargetFrameworks>
<PackageId>Acme.Invoicing</PackageId>
<Version>3.4.0</Version>
<Authors>Acme Software</Authors>
<Description>UBL and Factur-X invoice builder for .NET.</Description>
<PackageLicenseUrl>https://github.com/acme/Acme.Invoicing/blob/main/LICENSE</PackageLicenseUrl>
<PackageLicenseExpression>Apache 2.0</PackageLicenseExpression>
<PackageIconUrl>https://raw.githubusercontent.com/acme/Acme.Invoicing/main/icon.png</PackageIconUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/acme/Acme.Invoicing.git</RepositoryUrl>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project></textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Check the open .csproj, .fsproj, .vbproj or Directory.Build.props against all 10 pack rules, with the exact replacement line for each finding. <strong>In the editor:</strong> Scan every project in the workspace and export a dated pack-readiness report per package; a team key adds a CI gate that fails a release on any error — that part asks for a licence key. <a href="https://getreadystack.com/api/buy/cl/polar_cl_HRrltuicrvAeILT9iCgeVosowxectMtosdopx4GRhRw">Full version — workspace pack-readiness report + CI gate, $29 once</a></div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var NP_RULES = [
{"id": "NP01", "check": "license-url-deprecated", "sev": "warn",
"msg": "PackageLicenseUrl is deprecated (NU5125: \"The 'licenseUrl' element will be deprecated. Consider using the 'license' element instead.\"). Use PackageLicenseExpression or PackageLicenseFile instead.",
"fix": "\u003cPackageLicenseExpression\u003eMIT\u003c/PackageLicenseExpression\u003e",
"source": "https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu5125"},
{"id": "NP02", "check": "icon-url-deprecated", "sev": "warn",
"msg": "PackageIconUrl without PackageIcon raises NU5048: \"The 'PackageIconUrl'/'iconUrl' element is deprecated. Consider using the 'PackageIcon'/'icon' element instead.\" Embed the icon (PNG or JPEG, 1 MB max, 128x128 recommended). Keeping PackageIconUrl next to PackageIcon is fine for older clients.",
"fix": "\u003cPackageIcon\u003eicon.png\u003c/PackageIcon\u003e + \u003cNone Include=\"icon.png\" Pack=\"true\" PackagePath=\"\\\"/\u003e",
"source": "https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu5048"},
{"id": "NP03", "check": "license-expression-invalid", "sev": "error",
"msg": "PackageLicenseExpression must be an SPDX license identifier or expression (NuGet's ABNF: identifiers joined by AND, OR, WITH, optional +). This value is not a valid SPDX expression.",
"fix": "\u003cPackageLicenseExpression\u003eApache-2.0\u003c/PackageLicenseExpression\u003e",
"source": "https://learn.microsoft.com/en-us/nuget/reference/nuspec#license",
"ids": ["MIT", "MIT-0", "Apache-2.0", "BSD-2-Clause", "BSD-3-Clause", "0BSD", "ISC", "Zlib", "BSL-1.0", "Unlicense", "CC0-1.0", "MPL-2.0", "MS-PL", "MS-RL", "EPL-1.0", "EPL-2.0", "EUPL-1.2", "GPL-2.0-only", "GPL-2.0-or-later", "GPL-3.0-only", "GPL-3.0-or-later", "LGPL-2.1-only", "LGPL-2.1-or-later", "LGPL-3.0-only", "LGPL-3.0-or-later", "AGPL-3.0-only", "AGPL-3.0-or-later", "GPL-2.0", "GPL-3.0", "LGPL-2.1", "LGPL-3.0", "AGPL-3.0", "Artistic-2.0", "PostgreSQL", "OFL-1.1", "UPL-1.0", "Python-2.0", "X11", "NCSA", "AFL-3.0", "OSL-3.0", "CDDL-1.0", "CECILL-2.1", "WTFPL", "Unicode-DFS-2016", "BlueOak-1.0.0"],
"exceptions": ["LLVM-exception", "Classpath-exception-2.0", "GCC-exception-3.1", "Autoconf-exception-3.0", "Bison-exception-2.2", "Font-exception-2.0"],
"aliases": {"apache 2.0": "Apache-2.0", "apache2": "Apache-2.0", "apache 2": "Apache-2.0", "apache-2": "Apache-2.0", "apache license 2.0": "Apache-2.0", "apache license, version 2.0": "Apache-2.0", "mit license": "MIT", "the mit license": "MIT", "gplv3": "GPL-3.0-only", "gpl3": "GPL-3.0-only", "gpl v3": "GPL-3.0-only", "gplv2": "GPL-2.0-only", "lgplv3": "LGPL-3.0-only", "lgplv2.1": "LGPL-2.1-only", "lgpl 2.1": "LGPL-2.1-only", "mpl 2.0": "MPL-2.0", "mpl2": "MPL-2.0", "bsd 3-clause": "BSD-3-Clause", "bsd-3": "BSD-3-Clause", "bsd 2-clause": "BSD-2-Clause", "bsd-2": "BSD-2-Clause", "boost": "BSL-1.0", "cc0": "CC0-1.0"}},
{"id": "NP04", "check": "license-not-osi-fsf", "sev": "error",
"msg": "nuget.org \"only accepts license expressions that are approved by the Open Source Initiative or the Free Software Foundation\". This identifier is neither, so use a packed licence file instead.",
"fix": "\u003cPackageLicenseFile\u003eLICENSE.txt\u003c/PackageLicenseFile\u003e + \u003cNone Include=\"LICENSE.txt\" Pack=\"true\" PackagePath=\"\"/\u003e",
"source": "https://learn.microsoft.com/en-us/nuget/reference/nuspec#license",
"ids": ["BUSL-1.1", "SSPL-1.0", "Elastic-2.0", "PolyForm-Noncommercial-1.0.0", "PolyForm-Small-Business-1.0.0", "CC-BY-NC-4.0", "CC-BY-NC-SA-4.0", "CC-BY-ND-4.0", "CC-BY-NC-ND-4.0", "Commons-Clause"]},
{"id": "NP05", "check": "license-conflict", "sev": "error",
"msg": "\"Only one of PackageLicenseExpression, PackageLicenseFile, and PackageLicenseUrl can be specified at a time.\" This project sets PackageLicenseExpression and PackageLicenseFile.",
"fix": "keep one: PackageLicenseExpression (SPDX licence) or PackageLicenseFile (custom licence)",
"source": "https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#packing-a-license-expression-or-a-license-file"},
{"id": "NP06", "check": "license-file-not-packed", "sev": "error",
"msg": "PackageLicenseFile points to a file that no item in this project packs. \"You need to explicitly pack the referenced license file.\"",
"fix": "\u003cNone Include=\"LICENSE.txt\" Pack=\"true\" PackagePath=\"\"/\u003e",
"source": "https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#packing-a-license-expression-or-a-license-file"},
{"id": "NP07", "check": "icon-not-packed", "sev": "error",
"msg": "PackageIcon points to a file that no item in this project packs, or to a format NuGet does not take. \"You need to explicitly pack the referenced icon image file\"; supported formats are JPEG and PNG, 1 MB max.",
"fix": "\u003cNone Include=\"icon.png\" Pack=\"true\" PackagePath=\"\\\"/\u003e",
"source": "https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#packing-an-icon-image-file"},
{"id": "NP08", "check": "readme-not-packed", "sev": "error",
"msg": "PackageReadmeFile points to a file that no item in this project packs, or that is not Markdown. \"You need to explicitly pack the referenced readme file\"; \"Supported file formats include only Markdown (.md)\".",
"fix": "\u003cNone Include=\"README.md\" Pack=\"true\" PackagePath=\"\\\"/\u003e",
"source": "https://learn.microsoft.com/en-us/nuget/reference/msbuild-targets#packagereadmefile"},
{"id": "NP09", "check": "tfm-end-of-support-2026", "sev": "warn",
"msg": "Microsoft's .NET support policy lists .NET 8 (LTS) and .NET 9 (STS) with end of support on November 10, 2026. After that date they get no more patches.",
"fix": "net10.0 (LTS, end of support November 14, 2028)",
"source": "https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core",
"eos": {"net8.0": "2026-11-10", "net9.0": "2026-11-10"}},
{"id": "NP10", "check": "tfm-out-of-support", "sev": "warn",
"msg": "This target framework is no longer on Microsoft's list of supported .NET versions (only .NET 8, 9 and 10 are). A new package version that targets it ships on an unpatched runtime.",
"fix": "net10.0 (LTS, end of support November 14, 2028)",
"source": "https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core",
"tfms": ["net5.0", "net6.0", "net7.0", "netcoreapp1.0", "netcoreapp1.1", "netcoreapp2.0", "netcoreapp2.1", "netcoreapp2.2", "netcoreapp3.0", "netcoreapp3.1"]}
]
;</script>
<script>// NuGet Pack Gate engine: checks .csproj / .fsproj / .vbproj / Directory.Build.props pack metadata before dotnet pack.
// Same file runs in Node (VS Code extension, verify) and in the browser (free web page).
(function (root) {
const RULES = (typeof module !== 'undefined') ? require('./rules.json') : root.NP_RULES;
const R = {};
RULES.forEach(r => { R[r.check] = r; });
const low = a => (a || []).map(s => s.toLowerCase());
const SPDX = low(R['license-expression-invalid'].ids);
const EXC = low(R['license-expression-invalid'].exceptions);
const ALIAS = R['license-expression-invalid'].aliases || {};
const NONOSI = low(R['license-not-osi-fsf'].ids);
const EOS = R['tfm-end-of-support-2026'].eos || {};
const OLD = low(R['tfm-out-of-support'].tfms);
// XML comments become blanks so line numbers stay put
const stripComments = s => s.replace(/<!--[\s\S]*?-->/g, m => m.replace(/[^\n]/g, ' '));
const base = p => String(p || '').replace(/\\/g, '/').split('/').pop().toLowerCase();
function props(lines, name) {
const rx = new RegExp('<' + name + '(?:\\s[^>]*)?>([^<]*)</' + name + '>', 'i');
const out = [];
lines.forEach((l, i) => { const m = l.match(rx); if (m) out.push({ value: m[1].trim(), line: i + 1, text: l.trim() }); });
return out;
}
function packedFiles(src) {
const out = [];
const rx = /<(None|Content|EmbeddedResource|Compile|Resource)\b([^>]*?)(\/>|>([\s\S]*?)<\/\1>)/gi;
let m;
while ((m = rx.exec(src))) {
const attrs = m[2] || '', body = m[4] || '';
const inc = (attrs.match(/\b(?:Include|Update)\s*=\s*"([^"]+)"/i) || [])[1];
const pack = /\bPack\s*=\s*"true"/i.test(attrs) || /<Pack>\s*true\s*<\/Pack>/i.test(body);
if (inc && pack) inc.split(';').forEach(p => out.push(base(p.trim())));
}
return out;
}
const globMatch = (pat, name) => new RegExp('^' + pat.replace(/[.+^${}()|[\]]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + '$', 'i').test(name);
const isPacked = (packed, file) => packed.some(p => globMatch(p, base(file)));
function parseExpr(expr) {
const toks = expr.replace(/([()])/g, ' $1 ').trim().split(/\s+/).filter(Boolean);
let expectId = true, afterWith = false, depth = 0, ok = toks.length > 0;
const unknown = [], nonOsi = [];
if (toks.length === 1 && toks[0] === 'UNLICENSED') return { ok: true, unknown, nonOsi };
for (const t of toks) {
if (t === '(') { if (!expectId) ok = false; depth++; continue; }
if (t === ')') { if (expectId) ok = false; depth--; if (depth < 0) ok = false; continue; }
if (t === 'AND' || t === 'OR' || t === 'WITH') { if (expectId) ok = false; expectId = true; afterWith = t === 'WITH'; continue; }
if (/^(and|or|with)$/i.test(t)) { ok = false; continue; }
if (!expectId) ok = false;
expectId = false;
const id = t.replace(/\+$/, '').toLowerCase();
if (afterWith) { afterWith = false; if (!EXC.includes(id)) unknown.push(t); continue; }
if (NONOSI.includes(id)) nonOsi.push(t);
else if (!SPDX.includes(id)) { if (/^[A-Za-z0-9.\-]+$/.test(id)) unknown.push(t); else ok = false; }
}
if (expectId || depth !== 0) ok = false;
return { ok, unknown, nonOsi };
}
function check(text, opts) {
const today = String((opts && opts.today) || new Date().toISOString().slice(0, 10)).slice(0, 10);
const src = stripComments(String(text || ''));
const lines = src.split(/\r?\n/);
const packed = packedFiles(src);
const findings = [];
const add = (check, at, extra) => {
const r = R[check];
findings.push(Object.assign({ check, id: r.id, sev: r.sev, msg: r.msg, fix: r.fix, source: r.source, line: at.line, text: at.text }, extra || {}));
};
const dyn = v => /\$\(/.test(v); // MSBuild property we cannot resolve from one file
const expr = props(lines, 'PackageLicenseExpression');
const lfile = props(lines, 'PackageLicenseFile');
const lurl = props(lines, 'PackageLicenseUrl');
const icon = props(lines, 'PackageIcon');
const iconUrl = props(lines, 'PackageIconUrl');
const readme = props(lines, 'PackageReadmeFile');
lurl.forEach(p => {
let fix;
if (expr.length || lfile.length) fix = 'delete this line — only one licence property may be set, keep ' + (expr.length ? 'PackageLicenseExpression' : 'PackageLicenseFile');
else if (/apache/i.test(p.value)) fix = '<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>';
else if (/\bmit\b/i.test(p.value)) fix = '<PackageLicenseExpression>MIT</PackageLicenseExpression>';
else fix = '<PackageLicenseExpression>SPDX id</PackageLicenseExpression>, or <PackageLicenseFile>LICENSE.txt</PackageLicenseFile> + <None Include="LICENSE.txt" Pack="true" PackagePath=""/>';
add('license-url-deprecated', p, { fix });
});
if (!icon.length) iconUrl.forEach(p => add('icon-url-deprecated', p));
expr.forEach(p => {
if (!p.value || dyn(p.value)) return;
const res = parseExpr(p.value);
if (!res.ok) {
const alias = ALIAS[p.value.toLowerCase().replace(/\s+/g, ' ')];
add('license-expression-invalid', p, { msg: R['license-expression-invalid'].msg + ' Found: "' + p.value + '".',
fix: '<PackageLicenseExpression>' + (alias || 'SPDX id, e.g. MIT or Apache-2.0') + '</PackageLicenseExpression>' });
} else if (res.nonOsi.length) {
add('license-not-osi-fsf', p, { msg: R['license-not-osi-fsf'].msg + ' Found: ' + res.nonOsi.join(', ') + '.' });
} else if (res.unknown.length) {
add('license-expression-invalid', p, { sev: 'warn', msg: 'Identifier ' + res.unknown.join(', ') + ' is not in this checker\'s list of common OSI/FSF licences; confirm the exact id on spdx.org/licenses before pack.', fix: 'check the id at https://spdx.org/licenses/' });
}
});
if (expr.length && lfile.length) add('license-conflict', lfile[0]);
lfile.forEach(p => { if (p.value && !dyn(p.value) && !isPacked(packed, p.value)) add('license-file-not-packed', p, { fix: '<None Include="' + p.value + '" Pack="true" PackagePath=""/>' }); });
icon.forEach(p => {
if (!p.value || dyn(p.value)) return;
const badFmt = !/\.(png|jpe?g)$/i.test(p.value);
if (badFmt || !isPacked(packed, p.value)) add('icon-not-packed', p, { fix: badFmt ? 'a PNG or JPEG icon, 128x128, under 1 MB' : '<None Include="' + p.value + '" Pack="true" PackagePath="\\"/>' });
});
readme.forEach(p => {
if (!p.value || dyn(p.value)) return;
const badFmt = !/\.md$/i.test(p.value);
if (badFmt || !isPacked(packed, p.value)) add('readme-not-packed', p, { fix: badFmt ? 'a Markdown (.md) readme' : '<None Include="' + p.value + '" Pack="true" PackagePath="\\"/>' });
});
props(lines, 'TargetFrameworks?').forEach(p => {
p.value.split(';').map(s => s.trim()).filter(Boolean).forEach(tfm => {
const b = tfm.toLowerCase().replace(/-.*$/, '');
if (EOS[b]) {
const ended = today >= EOS[b];
add('tfm-end-of-support-2026', p, { sev: ended ? 'error' : 'warn', tfm,
msg: (ended ? tfm + ' ended support on ' : tfm + ' reaches end of support on ') + EOS[b] + ' (Microsoft .NET support policy: .NET 8 LTS and .NET 9 STS both end November 10, 2026). ' + (ended ? 'It gets no more security patches.' : 'After that date it gets no more patches.') });
} else if (OLD.includes(b)) {
add('tfm-out-of-support', p, { tfm, msg: tfm + ': ' + R['tfm-out-of-support'].msg });
}
});
});
findings.sort((a, b) => a.line - b.line);
return {
findings,
errors: findings.filter(f => f.sev === 'error').length,
warnings: findings.filter(f => f.sev === 'warn').length,
as_of: today
};
}
const api = { engine: { check }, RULES, RULE_COUNT: RULES.length };
if (typeof module !== 'undefined') module.exports = api;
root.NPENGINE = api;
})(typeof window !== 'undefined' ? window : globalThis);
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
function summaryLine(res) {
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
const f = (res && res.findings) || [];
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
f.forEach(function (x) { lines.push(' ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + ' ' + (x.line ? 'L' + x.line + ' ' : '') + (x.check || x.id || '') + ' ' + (x.msg || x.message || '')); });
return lines.join('\n');
}
function toHtml(res) {
const f = (res && res.findings) || [];
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var NPREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = NPREPORT;
else window.NPREPORT = NPREPORT;
</script>
<script>
(function(){
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
today.value=new Date().toISOString().slice(0,10);
function run(){ try { var res=window.NPENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.NPREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
go.onclick=run; if (src.value.trim()) run();
})();
</script>
<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://open-vsx.org/extension/readystack/nuget-pack-csproj-gate" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'nuget-pack-csproj-gate',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind NuGet Pack Gate — csproj licence, icon, TFM every day. When a rule changes, you get a single email with what changed and the updated check. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"nuget-pack-csproj-gate",lang:"en",source:"rule_alert"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},true);})();</script></section>
<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">Scan every project in the workspace and export a dated pack-readiness report per package; a team key adds a CI gate that fails a release on any error</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><p class="yj-cta-anchor" style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">nuget.org does not support permanent deletion of packages: a version pushed with a bad licence or a missing readme can only be unlisted, and it still restores by exact version.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_HRrltuicrvAeILT9iCgeVosowxectMtosdopx4GRhRw" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>
<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&","<":"<",">":">",'"':"""}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div><section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-30">Updated 2026-09-30</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-30","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku433599_result_card.jpg" alt="nuget pack csproj check: six issues in one library's project file before November 10, 2026" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Six NuGet pack issues turned up in Acme.Invoicing.csproj, the project file of a .NET library author publishing an invoice builder to nuget.org: two errors and four warnings, checked on September 30, 2026.</p>
<h3>What Microsoft's own pages say</h3>
<p>Microsoft's .NET support policy lists .NET 8 (LTS) and .NET 9 (STS) with end of support on November 10, 2026. .NET 10 is LTS with end of support on November 14, 2028.</p>
<p>On the metadata side, NU5125 says: "The 'licenseUrl' element will be deprecated. Consider using the 'license' element instead." NU5048 says the same about PackageIconUrl and PackageIcon. The nuspec reference adds that nuget.org "only accepts license expressions that are approved by the Open Source Initiative or the Free Software Foundation". The MSBuild pack reference says the icon, readme and licence file must each be packed explicitly.</p>
<h3>The six lines</h3>
<table><thead><tr><th>Line</th><th>csproj line</th><th>Fix</th><th>Severity</th></tr></thead><tbody><tr><td>4</td><td><code><TargetFrameworks> …net8.0</code></td><td><code>net10.0, LTS to November 14, 2028</code></td><td>warning</td></tr><tr><td>4</td><td><code><TargetFrameworks> …net9.0</code></td><td><code>net10.0, LTS to November 14, 2028</code></td><td>warning</td></tr><tr><td>9</td><td><code><PackageLicenseUrl>…/LICENSE</code></td><td><code>delete it, keep one licence property</code></td><td>warning</td></tr><tr><td>10</td><td><code><PackageLicenseExpression>Apache 2.0</code></td><td><code><PackageLicenseExpression>Apache-2.0</code></td><td>error</td></tr><tr><td>11</td><td><code><PackageIconUrl>…/icon.png</code></td><td><code><PackageIcon>icon.png + Pack="true"</code></td><td>warning</td></tr><tr><td>12</td><td><code><PackageReadmeFile>README.md, not packed</code></td><td><code><None Include="README.md" Pack="true"></code></td><td>error</td></tr></tbody></table>
<p>"Apache 2.0" is not an SPDX identifier; Apache-2.0 is. The readme is named in PackageReadmeFile but no item packs it. The four warnings are the two target frameworks, the deprecated licence URL left next to the new expression, and an icon URL with no embedded icon.</p>
<p>Change the check date to November 10, 2026 or later and the same file returns four errors and two warnings, because net8.0 and net9.0 have then ended support.</p>
<h3>Why dotnet pack is not enough</h3>
<p>dotnet pack does print NU5125 and NU5048, but as warnings, after a full build, one project at a time. A default build still passes, so in a CI log they scroll past. It is silent on the November 10 date. And nuget.org does not support permanent deletion of packages: a version pushed with the wrong licence can only be unlisted, and it still restores for anyone who pins it. The fix costs a version number.</p>
<h3>Checking every line at once</h3>
<p>NuGet Pack Gate reads .csproj, .fsproj, .vbproj and Directory.Build.props against 10 rules: PackageLicenseUrl, PackageIconUrl without PackageIcon, invalid SPDX expressions, licences nuget.org does not accept (BUSL-1.1, SSPL-1.0, Elastic-2.0, PolyForm, CC-BY-NC), licence expression and licence file both set, unpacked licence, icon and readme files, net8.0 and net9.0, and older targets such as net6.0. Each finding names the line, the fix and its Microsoft source page. The fixed project returns 0 findings.</p>
<p>The file changes the answer. A licence URL pointing at opensource.org/licenses/MIT gets the MIT expression as its fix. A Directory.Build.props that sets both an expression and a file gets a conflict error. An SVG icon is rejected because NuGet takes only PNG and JPEG.</p>
<h3>What to do next</h3>
<p>The single-file check is free in VS Code and on the web page, with every rule and every fix. The paid version scans every project in a workspace and exports a dated pack-readiness report per package; a team key adds a CI gate that fails a release on any error.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku433599_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku433599_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does NuGet Pack Gate do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">NuGet Pack Gate reads a .csproj, .fsproj, .vbproj or Directory.Build.props and flags pack metadata that breaks or degrades a nuget.org release: PackageLicenseUrl (NU5125), PackageIconUrl without PackageIcon (NU5048), licence expressions that are not SPDX or not OSI/FSF approved, icon, readme and licence files that are never packed, and net8.0 or net9.0 targets. Each finding shows the replacement line.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is NuGet Pack Gate for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It is for .NET library authors and maintainers who publish packages to nuget.org or an internal feed, and for teams moving older csproj files off PackageLicenseUrl and PackageIconUrl. It matters most this quarter for anyone still targeting net8.0 or net9.0, which Microsoft's .NET support policy lists with end of support on November 10, 2026.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just run dotnet pack and read the warnings?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">dotnet pack prints NU5125 and NU5048 as warnings after a full build, one project at a time, so a default build still passes and the warnings scroll past in CI logs. It says nothing today about net8.0 and net9.0 ending support on November 10, 2026, and a pushed nuget.org version cannot be deleted, only unlisted.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what does the paid version add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The free version runs all 10 rules on the open project file in VS Code and on the web page, with the fix line on every finding and no cap. The paid version, $29 once with one licence key per person or team seat, scans every project in the workspace and exports a dated pack-readiness report per package; a team key adds a CI gate.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does a bad package version cost?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">nuget.org does not support permanent deletion of packages. A version pushed with an invalid licence, a missing readme or no icon can only be unlisted, and it still restores for anyone who pins that exact version, so the fix costs a new version number. The single-file check here is free and takes seconds.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does NuGet Pack Gate do?", "acceptedAnswer": {"@type": "Answer", "text": "NuGet Pack Gate reads a .csproj, .fsproj, .vbproj or Directory.Build.props and flags pack metadata that breaks or degrades a nuget.org release: PackageLicenseUrl (NU5125), PackageIconUrl without PackageIcon (NU5048), licence expressions that are not SPDX or not OSI/FSF approved, icon, readme and licence files that are never packed, and net8.0 or net9.0 targets. Each finding shows the replacement line."}}, {"@type": "Question", "name": "Who is NuGet Pack Gate for?", "acceptedAnswer": {"@type": "Answer", "text": "It is for .NET library authors and maintainers who publish packages to nuget.org or an internal feed, and for teams moving older csproj files off PackageLicenseUrl and PackageIconUrl. It matters most this quarter for anyone still targeting net8.0 or net9.0, which Microsoft's .NET support policy lists with end of support on November 10, 2026."}}, {"@type": "Question", "name": "Why not just run dotnet pack and read the warnings?", "acceptedAnswer": {"@type": "Answer", "text": "dotnet pack prints NU5125 and NU5048 as warnings after a full build, one project at a time, so a default build still passes and the warnings scroll past in CI logs. It says nothing today about net8.0 and net9.0 ending support on November 10, 2026, and a pushed nuget.org version cannot be deleted, only unlisted."}}, {"@type": "Question", "name": "What is free and what does the paid version add?", "acceptedAnswer": {"@type": "Answer", "text": "The free version runs all 10 rules on the open project file in VS Code and on the web page, with the fix line on every finding and no cap. The paid version, $29 once with one licence key per person or team seat, scans every project in the workspace and exports a dated pack-readiness report per package; a team key adds a CI gate."}}, {"@type": "Question", "name": "What does a bad package version cost?", "acceptedAnswer": {"@type": "Answer", "text": "nuget.org does not support permanent deletion of packages. A version pushed with an invalid licence, a missing readme or no icon can only be unlisted, and it still restores for anyone who pins that exact version, so the fix costs a new version number. The single-file check here is free and takes seconds."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:433599,slug:"nuget-pack-csproj-gate",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/dotnet-eol-support-gate"><span class="yj-rel-t">.NET EOL & CRA Support Window Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cinc-migration-check"><span class="yj-rel-t">Cinc Migration Check: Chef Infra licence gate lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/js-licence-key-gate"><span class="yj-rel-t">JS Licence Key Gate: GPL & Eval Keys</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/maui-ios-submission-gate"><span class="yj-rel-t">MAUI iOS Submission Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/nuget-commercial-license-gate"><span class="yj-rel-t">NuGet License Gate: AutoMapper, MediatR</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/pekko-migration-gate"><span class="yj-rel-t">Pekko Migration Gate — Akka BSL licence check</span><span class="yj-rel-p">$29</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>
|