cargo-publish-gate / index.html
getreadystack's picture
Upload index.html with huggingface_hub
88f3add verified
Raw History Blame Contribute Delete
44.4 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Crates.io Publish checklist — check cargo package before publishing</title>
<meta name="description" content="This free browser tool reads Cargo.toml and lists crates.io publish blockers before cargo publish.">
<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/cargo-publish-gate"></head>
<body>
<div class="wrap">
<h1>Crates.io Publish checklist — check cargo package before publishing</h1>
<p class="lede">Reads Cargo.toml and lists every crates.io publish blocker before you run cargo publish - the version number you burn cannot be re-uploaded. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">[package]
name = "ubl-invoice"
version = "0.0.0"
publish = false
keywords = [
"ubl",
"invoice",
"xml",
"einvoice",
"parser",
"electronic-invoicing-en16931",
]
categories = [
"parser-implementations",
"encoding",
"text-processing",
"data-structures",
"development-tools",
"command-line-utilities",
]
[dependencies]
serde = "*"
quick-xml = { git = "https://github.com/tafia/quick-xml" }
ubl-core = { path = "../ubl-core" }
[dev-dependencies]
pretty_assertions = "1"</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> Opens the Cargo.toml you are looking at and names every crates.io publish blocker in it, with the line number and the fix.. <strong>In the editor:</strong> Gates every Cargo.toml in the workspace at once and writes a dated pre-publish report you keep and attach to the release PR. — that part asks for a licence key.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var CARGO_RULES = [
{"id": "missing_description", "sev": "error", "field": "description", "msg": "[package] has no description - crates.io refuses the upload. Add description = \"one sentence\"."},
{"id": "missing_license", "sev": "error", "field": "license", "msg": "[package] has neither license nor license-file - crates.io refuses the upload. Add license = \"MIT OR Apache-2.0\"."},
{"id": "wildcard_dep", "sev": "error", "msg": "Wildcard version \"*\" - crates.io rejects wildcard dependency constraints. Pin a caret range such as \"1\"."},
{"id": "git_dep", "sev": "error", "msg": "git = dependency - crates.io rejects git dependencies. Publish that crate first and depend on its registry version."},
{"id": "path_dep_no_version", "sev": "error", "msg": "path = dependency with no version - cargo publish stops: all path dependencies must also carry a version."},
{"id": "too_many_keywords", "sev": "error", "max": 5, "msg": "More than 5 keywords - crates.io accepts at most 5."},
{"id": "keyword_too_long", "sev": "error", "max_len": 20, "msg": "Keyword longer than 20 characters - crates.io rejects it."},
{"id": "too_many_categories", "sev": "error", "max": 5, "msg": "More than 5 categories - crates.io accepts at most 5."},
{"id": "publish_false", "sev": "warn", "msg": "publish = false - cargo publish refuses to upload this crate. Remove the key when the crate is meant to go out."},
{"id": "missing_repository", "sev": "warn", "field": "repository", "msg": "[package] has no repository - the crates.io page ships with no source link and the version is frozen that way."},
{"id": "missing_readme", "sev": "warn", "field": "readme", "msg": "[package] has no readme - the crates.io page body stays empty for this version forever."},
{"id": "edition_missing_or_2015", "sev": "warn", "msg": "edition is absent or 2015 - cargo falls back to the 2015 edition for this published version."},
{"id": "version_zero", "sev": "warn", "msg": "version = 0.0.0 - a placeholder version number gets burned on crates.io and can never be re-uploaded."}
]
;</script>
<script>/* Cargo.toml Publish Gate - crates.io
Same brain in VS Code and in the browser. No dependencies. */
var RULES = (typeof module !== 'undefined' && module.exports)
? require('./rules.json')
: window.CARGO_RULES;
var RULE_COUNT = RULES.length;
function rule(id) {
for (var i = 0; i < RULES.length; i++) if (RULES[i].id === id) return RULES[i];
return { id: id, sev: 'warn', msg: id };
}
/* --- tiny TOML reader: enough for a Cargo manifest ------------------ */
function stripComment(line) {
var out = '', q = null;
for (var i = 0; i < line.length; i++) {
var c = line[i];
if (q) { out += c; if (c === q) q = null; continue; }
if (c === '"' || c === "'") { q = c; out += c; continue; }
if (c === '#') break;
out += c;
}
return out;
}
function unquote(v) {
v = String(v == null ? '' : v).trim();
if (v.length > 1 && (v[0] === '"' || v[0] === "'") && v[v.length - 1] === v[0]) {
return v.slice(1, -1);
}
return v;
}
function parseArray(v) {
v = String(v).trim();
if (v[0] !== '[') return null;
var inner = v.slice(1, v.lastIndexOf(']'));
var items = [], cur = '', q = null;
for (var i = 0; i < inner.length; i++) {
var c = inner[i];
if (q) { cur += c; if (c === q) q = null; continue; }
if (c === '"' || c === "'") { q = c; cur += c; continue; }
if (c === ',') { items.push(cur); cur = ''; continue; }
cur += c;
}
items.push(cur);
var out = [];
for (var j = 0; j < items.length; j++) {
var s = unquote(items[j]);
if (s !== '') out.push(s);
}
return out;
}
/* inline table { version = "1", path = "../x" } -> {version:"1", path:"../x"} */
function parseInline(v) {
var spec = {};
var inner = String(v).trim();
inner = inner.slice(1, inner.lastIndexOf('}'));
var parts = [], cur = '', q = null, depth = 0;
for (var i = 0; i < inner.length; i++) {
var c = inner[i];
if (q) { cur += c; if (c === q) q = null; continue; }
if (c === '"' || c === "'") { q = c; cur += c; continue; }
if (c === '[' || c === '{') depth++;
if (c === ']' || c === '}') depth--;
if (c === ',' && depth === 0) { parts.push(cur); cur = ''; continue; }
cur += c;
}
parts.push(cur);
for (var j = 0; j < parts.length; j++) {
var eq = parts[j].indexOf('=');
if (eq < 0) continue;
spec[parts[j].slice(0, eq).trim()] = parts[j].slice(eq + 1).trim();
}
return spec;
}
function depKind(section) {
if (section.indexOf('dev-dependencies') >= 0) return 'dev';
if (section.indexOf('build-dependencies') >= 0) return 'build';
return 'normal';
}
function parse(text) {
var lines = String(text == null ? '' : text).split(/\r?\n/);
var section = '';
var pkg = {}; /* field -> {value, line} */
var pkgSeen = false;
var deps = []; /* {name, kind, line, spec} */
var subDep = null;
for (var i = 0; i < lines.length; i++) {
var raw = stripComment(lines[i]);
var line = i + 1;
var t = raw.trim();
if (t === '') continue;
var head = t.match(/^\[\[?([^\]]+)\]\]?$/);
if (head) {
section = head[1].trim().replace(/["']/g, '');
subDep = null;
if (section === 'package') pkgSeen = true;
var sub = section.match(/(^|\.)(dev-|build-)?dependencies\.(.+)$/);
if (sub) {
subDep = { name: sub[3], kind: depKind(section), line: line, spec: {} };
deps.push(subDep);
}
continue;
}
var kv = t.match(/^([A-Za-z0-9_.\-"']+)\s*=\s*(.*)$/);
if (!kv) continue;
var key = kv[1].replace(/["']/g, '');
var val = kv[2].trim();
/* keep multi-line arrays / inline tables together */
var open = (val.match(/[\[{]/g) || []).length;
var close = (val.match(/[\]}]/g) || []).length;
var j = i;
while (open > close && j + 1 < lines.length) {
j++;
var more = stripComment(lines[j]);
val += ' ' + more.trim();
open += (more.match(/[\[{]/g) || []).length;
close += (more.match(/[\]}]/g) || []).length;
}
i = j;
if (subDep) { subDep.spec[key] = val; continue; }
if (section === 'package') {
var field = key.split('.')[0];
if (!pkg[field]) pkg[field] = { value: val, line: line, key: key };
continue;
}
if (/(^|\.)(dev-|build-)?dependencies$/.test(section)) {
var spec = {};
if (val[0] === '{') spec = parseInline(val);
else spec.version = val;
deps.push({ name: key, kind: depKind(section), line: line, spec: spec });
}
}
return { pkg: pkg, pkgSeen: pkgSeen, deps: deps };
}
/* --- the gate ------------------------------------------------------- */
function check(text, opts) {
opts = opts || {};
var m = parse(text);
var f = [];
function add(id, line, extra) {
var r = rule(id);
f.push({ check: r.id, sev: r.sev, msg: extra ? r.msg + ' ' + extra : r.msg, line: line });
}
if (m.pkgSeen) {
var pkgLine = 1;
var lines = String(text == null ? '' : text).split(/\r?\n/);
for (var i = 0; i < lines.length; i++) {
if (lines[i].trim() === '[package]') { pkgLine = i + 1; break; }
}
var has = function (k) { return !!m.pkg[k]; };
if (!has('description')) add('missing_description', pkgLine);
if (!has('license') && !has('license-file')) add('missing_license', pkgLine);
if (!has('repository')) add('missing_repository', pkgLine);
if (!has('readme')) add('missing_readme', pkgLine);
var ed = m.pkg['edition'];
if (!ed) add('edition_missing_or_2015', pkgLine);
else if (unquote(ed.value) === '2015') add('edition_missing_or_2015', ed.line);
var ver = m.pkg['version'];
if (ver && unquote(ver.value) === '0.0.0') add('version_zero', ver.line);
var pub = m.pkg['publish'];
if (pub && /^false$/i.test(unquote(pub.value).trim())) add('publish_false', pub.line);
var kw = m.pkg['keywords'];
if (kw) {
var ks = parseArray(kw.value) || [];
var maxK = rule('too_many_keywords').max;
if (ks.length > maxK) add('too_many_keywords', kw.line, '(' + ks.length + ' listed)');
var maxLen = rule('keyword_too_long').max_len;
for (var k = 0; k < ks.length; k++) {
if (ks[k].length > maxLen) add('keyword_too_long', kw.line, '("' + ks[k] + '")');
}
}
var cat = m.pkg['categories'];
if (cat) {
var cs = parseArray(cat.value) || [];
if (cs.length > rule('too_many_categories').max) {
add('too_many_categories', cat.line, '(' + cs.length + ' listed)');
}
}
}
for (var d = 0; d < m.deps.length; d++) {
var dep = m.deps[d];
var s = dep.spec || {};
var v = s.version ? unquote(s.version) : '';
if (v === '*') add('wildcard_dep', dep.line, '[' + dep.name + ']');
if (s.git && dep.kind !== 'dev') add('git_dep', dep.line, '[' + dep.name + ']');
if (s.path && !s.version && dep.kind !== 'dev') {
add('path_dep_no_version', dep.line, '[' + dep.name + ']');
}
}
f.sort(function (a, b) { return a.line - b.line; });
return { findings: f };
}
var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULE_COUNT };
if (typeof module !== 'undefined' && module.exports) module.exports = API;
if (typeof window !== 'undefined') window.CARGOGATE = API;
</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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
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 CARGOREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = CARGOREPORT;
else window.CARGOREPORT = CARGOREPORT;
</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.CARGOGATE.engine.check(src.value,{today:today.value}); out.innerHTML=window.CARGOREPORT.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://marketplace.visualstudio.com/items?itemName=ReadyStack.cargo-publish-gate" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'cargo-publish-gate',src:'mp',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">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/cargo-publish-gate" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'cargo-publish-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><a href="https://www.npmjs.com/package/@readystack/cargo-publish-gate" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'cargo-publish-gate',src:'npm',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">npm</a><a href="https://hub.docker.com/r/getreadystack/cargo-publish-gate" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'cargo-publish-gate',src:'docker',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">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.cargo-publish-gate
npx @readystack/cargo-publish-gate &lt;file&gt;
docker run --rm -v &quot;$PWD:/w&quot; getreadystack/cargo-publish-gate /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" 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 the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 13 rules Cargo.toml Publish Gate - crates.io checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. 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">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</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:"cargo-publish-gate",lang:"en",source:"rule_table"+((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);})();</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 complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Names every crates.io publish blocker in a Cargo.toml before the upload burns a version number.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A contract Rust engineer on the open freelance market starts near $50 an hour, and the hand check of one release manifest against the crates.io rules takes most of that hour, at every release.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_712ED1or9Ov8WeL57O3UARKq30s2RtRcwPsHH2PaSpU" 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>
<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 {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[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>
<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>
<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-17">Updated 2026-09-17</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-17","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/sku101948_result_card.jpg" alt="Cargo.toml Publish Gate - crates.io" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>A Rust crate author&#x27;s release manifest went through this gate on 2026-09-17 and came back with 13 flagged lines - 8 of them hard refusals that stop the upload, 5 of them things that would have been frozen onto the crates.io page for good. The manifest compiled cleanly the whole time.</p>
<p>That is the shape of the problem. cargo build, cargo check and clippy do not care whether a Cargo.toml has a description, a licence, or a dependency pinned to the wildcard &quot;*&quot;. Those are registry rules, not compiler rules. They surface for the first time at cargo publish, which is exactly the moment they cost the most.</p>
<h3>The door only opens one way</h3>
<p>crates.io does not delete versions. Once a version number is uploaded it belongs to that upload forever, and yanking it does not return the number to you - a yanked version stays in the index, visible, and the next release has to carry a different number. So a manifest that was wrong is not a build failure you retry - it is a permanent row in the release history of the crate.</p>
<h3>What the gate reads</h3>
<p>Cargo.toml Publish Gate applies 13 rules to the manifest text, offline, in the editor. Eight of them are refusals. [package] with no description, and [package] with neither license nor license-file, are both refused outright by crates.io. A dependency written as &quot;*&quot; is refused - a wildcard cannot be resolved downstream. A dependency carrying git = is refused, because the registry will not follow a git source. A dependency carrying path = but no version = stops cargo publish, since the packaged copy has no directory to walk to. And three limits bite quietly: at most five keywords, at most five categories, and no keyword longer than 20 characters.</p>
<p>The other five rules do not stop the upload - they describe what the upload will freeze. publish = false means cargo publish refuses this crate outright. A missing repository means the page for that version carries no source link. A missing readme means the page body stays empty. An absent or 2015 edition means that published version compiles as 2015. And version = &quot;0.0.0&quot; means a placeholder number has just been burned like any other.</p>
<h3>Measured on the fixtures</h3>
<p>The extension ships two manifests. _fixtures/clean.toml returns 0 findings. _fixtures/dirty.toml returns 13 - one per rule - and the report names each line number and the fix beside it. The gate also follows cargo&#x27;s own packaging behaviour: a path dependency inside [dev-dependencies] with no version is stripped at package time, so it is not reported.</p>
<h3>Where AI-written manifests land</h3>
<p>An assistant asked for a Cargo.toml produces the parts it can infer from the code - the dependency list, the crate name, the edition - and leaves the parts it cannot: description, licence, repository, readme. Those four decide whether the upload happens at all. The list a generator leaves blank is almost exactly the list crates.io refuses on.</p>
<h3>Free and paid</h3>
<p>Free finishes one file: open any Cargo.toml, run the check, read every blocker with its line and its fix. Nothing is hidden, watermarked or counted down. The paid layer changes the scope and who owns the output - every Cargo.toml in the workspace in one pass, plus a dated report file you keep and attach to the release pull request. It is $29 once, one licence key per person or team seat, with a 7-day full refund.</p>
<p>The yardstick is simple. A contract Rust engineer on the open freelance market starts near $50 an hour, and reading the crates.io manifest rules against a release manifest by hand takes most of that hour - every release, every crate in the workspace. The gate does it before the door closes.</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/sku101948_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku101948_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 Cargo.toml Publish Gate actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads a Cargo.toml manifest and applies 13 rules that crates.io and cargo publish enforce at upload time: missing description or licence, wildcard and git dependencies, path dependencies without a version, the five-keyword and five-category limits, publish = false, a missing repository or readme, and a 2015 or absent edition. Each finding carries its line number and its fix.</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 this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Rust crate authors and release engineers who publish to crates.io: maintainers cutting a release from a workspace, teams whose first upload of a new crate has to land correctly, and anyone whose Cargo.toml was partly written by an AI assistant that filled in dependencies but left description, licence and version placeholders untouched.</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 is a free tool not enough here?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">cargo check, cargo build and clippy all compile fine with a wildcard dependency, no licence and no description, because those are registry rules rather than compiler rules. cargo publish is where they appear, and by then the run is live. This gate applies the registry rules offline, in the editor, before the upload.</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 needs the licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free covers one file completely: open any Cargo.toml, run the check, and read every blocker with its line and its fix. Nothing is hidden, watermarked or limited by a countdown. The licence key adds scope and ownership: every Cargo.toml in the workspace in one pass, plus a dated report file you keep and attach to the release pull request.</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 would this cost done by hand?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A contract Rust engineer on the open freelance market starts near $50 an hour, and re-reading the crates.io manifest rules against a release manifest takes most of that hour, once per release. The paid layer is $29 once, with one licence key per person or team seat and.</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 ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Cargo.toml Publish Gate - crates.io checks the file you open against 13 rules from a rule set dated 2026-09-17, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Cargo.toml Publish Gate actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a Cargo.toml manifest and applies 13 rules that crates.io and cargo publish enforce at upload time: missing description or licence, wildcard and git dependencies, path dependencies without a version, the five-keyword and five-category limits, publish = false, a missing repository or readme, and a 2015 or absent edition. Each finding carries its line number and its fix."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Rust crate authors and release engineers who publish to crates.io: maintainers cutting a release from a workspace, teams whose first upload of a new crate has to land correctly, and anyone whose Cargo.toml was partly written by an AI assistant that filled in dependencies but left description, licence and version placeholders untouched."}}, {"@type": "Question", "name": "Why is a free tool not enough here?", "acceptedAnswer": {"@type": "Answer", "text": "cargo check, cargo build and clippy all compile fine with a wildcard dependency, no licence and no description, because those are registry rules rather than compiler rules. cargo publish is where they appear, and by then the run is live. This gate applies the registry rules offline, in the editor, before the upload."}}, {"@type": "Question", "name": "What is free and what needs the licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free covers one file completely: open any Cargo.toml, run the check, and read every blocker with its line and its fix. Nothing is hidden, watermarked or limited by a countdown. The licence key adds scope and ownership: every Cargo.toml in the workspace in one pass, plus a dated report file you keep and attach to the release pull request."}}, {"@type": "Question", "name": "What would this cost done by hand?", "acceptedAnswer": {"@type": "Answer", "text": "A contract Rust engineer on the open freelance market starts near $50 an hour, and re-reading the crates.io manifest rules against a release manifest takes most of that hour, once per release. The paid layer is $29 once, with one licence key per person or team seat and."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Cargo.toml Publish Gate - crates.io checks the file you open against 13 rules from a rule set dated 2026-09-17, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files."}}]}</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:101948,slug:"cargo-publish-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/pyproject-release-gate"><span class="yj-rel-t">pyproject.toml Release Gate (PEP 639)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/firmware-release-gate"><span class="yj-rel-t">Firmware Release Gate for sdkconfig and prj.conf</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/play-release-blocker-lint"><span class="yj-rel-t">Google Play Release Blocker Lint - target API 36, Billing 8, 16 KB</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/maven-central-publish-gate"><span class="yj-rel-t">Maven Central Publish Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/renpy-release-gate"><span class="yj-rel-t">Ren&#x27;Py Release Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/spdx-license-field-lint"><span class="yj-rel-t">SPDX License Field Lint for package.json, pyproject.toml and Cargo.toml</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>