open-vsx-publish-gate / index.html
getreadystack's picture
Upload index.html with huggingface_hub
a11394f verified
Raw History Blame Contribute Delete
47.1 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Open VSX Publish requirements — check extension before publishing</title>
<meta name="description" content="This free browser tool checks extension publishing fields, icons, badges, dependencies, and VSCE_PAT publishing.">
<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/open-vsx-publish-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>Open VSX Publish requirements — check extension before publishing</h1>
<p class="lede">Open VSX and vsce preflight for extension authors: flags SVG icons, http or untrusted SVG badges, missing publisher/engines, Microsoft-only dependencies and VSCE_PAT publishing before 2026-12-01. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">{
"name": "py-lens",
"displayName": "Py Lens",
"version": "2.1.0-beta.3",
"publisher": "lensworks",
"icon": "images/icon.svg",
"engines": { "vscode": "^1.90.0" },
"badges": [
{ "url": "http://img.shields.io/badge/build-passing-green.png", "href": "https://github.com/lensworks/py-lens", "description": "build" },
{ "url": "https://badges.example-ci.dev/lensworks/py-lens/coverage.svg", "href": "https://github.com/lensworks/py-lens", "description": "coverage" }
],
"extensionDependencies": ["ms-python.python", "ms-python.vscode-pylance"],
"extensionPack": ["ms-vscode-remote.remote-ssh"],
"scripts": {
"compile": "tsc -p .",
"publish": "vsce publish -p $VSCE_PAT"
},
"devDependencies": { "@vscode/vsce": "^3.9.2" }
}</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>
<p class="paid"><a href="https://getreadystack.com/api/buy/cl/polar_cl_ORIhnEZxJdlIhV17Ylw83l6Kmaa1ZCUauJPvK43oifW">Full version: whole-workspace publish plan, dated migration report and CI gate</a> - $29 once, one licence key per person or team seat.</p>
<div id="paid"><strong>Free here:</strong> Checks the open package.json or publish workflow and names every vsce / Open VSX blocker with its line and the exact replacement line.. <strong>In the editor:</strong> Whole-workspace publish plan: every extension manifest and workflow in the repo, a dated migration report, and a CI gate that fails a build adding a new blocker. — 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 OVSX_RULES = [
{"id": "publisher-missing", "sev": "error", "target": "vsce + Open VSX", "msg": "No \"publisher\" field. vsce publish needs it, and on Open VSX the publisher is the namespace your extension lives in.", "fix": "\"publisher\": \"\u003cyour-id\u003e\", then once: npx ovsx create-namespace \u003cyour-id\u003e -p $OVSX_PAT"},
{"id": "publisher-vscode-samples", "sev": "error", "target": "vsce", "msg": "publisher \"vscode-samples\" is refused by vsce (\"It's not allowed to use the 'vscode-samples' publisher\").", "fix": "\"publisher\": \"\u003cyour-id\u003e\""},
{"id": "engines-missing", "sev": "error", "target": "vsce + Open VSX", "msg": "No engines.vscode. vsce stops with \"Manifest missing field: engines\" and ovsx packages through vsce.", "fix": "\"engines\": { \"vscode\": \"^1.90.0\" }"},
{"id": "icon-svg", "sev": "error", "target": "vsce + Open VSX", "msg": "The icon is an SVG. vsce stops with \"SVGs can't be used as icons\".", "fix": "\"icon\": \"images/icon.png\" (128x128 PNG)"},
{"id": "badge-http", "sev": "error", "target": "vsce + Open VSX", "msg": "A badge URL is not HTTPS. vsce stops with \"Badge URLs must come from an HTTPS source\".", "fix": "change http:// to https:// in badges[].url"},
{"id": "badge-svg-untrusted", "sev": "error", "target": "vsce + Open VSX", "msg": "An SVG badge comes from a host that is not on the vsce trusted list (img.shields.io, badgen.net, codecov.io, GitHub workflow badges and 29 more). vsce stops with \"Badge SVGs are restricted\".", "fix": "serve the badge from img.shields.io or use a PNG"},
{"id": "version-prerelease", "sev": "error", "target": "vsce", "msg": "The version has a semver pre-release tag. vsce stops with \"The VS Marketplace doesn't support prerelease versions\".", "fix": "\"version\": \"\u003cmajor.minor.patch\u003e\" and publish with --pre-release"},
{"id": "proposed-api", "sev": "warn", "target": "vsce", "msg": "enabledApiProposals is set. vsce refuses: \"Extensions using unallowed proposed API ... can't be published to the Marketplace\".", "fix": "remove enabledApiProposals from the published manifest, or ship proposed-API builds to Open VSX only"},
{"id": "dep-vscode", "sev": "error", "target": "vsce + Open VSX", "msg": "\"vscode\" is listed in dependencies. vsce stops with \"You should not depend on 'vscode' in your 'dependencies'\".", "fix": "move it to devDependencies as \"@types/vscode\""},
{"id": "repository-missing", "sev": "warn", "target": "vsce + Open VSX", "msg": "No \"repository\" field. vsce warns \"A 'repository' field is missing\" and relative README links cannot be rewritten.", "fix": "\"repository\": { \"type\": \"git\", \"url\": \"https://github.com/\u003cowner\u003e/\u003crepo\u003e\" }"},
{"id": "license-missing", "sev": "info", "target": "vsce + Open VSX", "msg": "No \"license\" field. The vsce publishing docs ask for a LICENSE file in the extension root.", "fix": "\"license\": \"MIT\" (or \"SEE LICENSE IN LICENSE.txt\")"},
{"id": "ms-only-dependency", "sev": "error", "target": "Open VSX", "msg": "This extension depends on a Microsoft-only ID that returns 404 on open-vsx.org (checked 2026-09-30). VSCodium, Cursor, Windsurf and Gitpod users cannot install your extension.", "fix": "replace with the Open VSX ID shown, or move it to a recommendation"},
{"id": "no-ovsx-script", "sev": "warn", "target": "Open VSX", "msg": "scripts publish with vsce but nothing publishes to Open VSX.", "fix": "\"publish:ovsx\": \"ovsx publish -p $OVSX_PAT\""},
{"id": "vsce-pat-script", "sev": "warn", "target": "vsce", "msg": "scripts pass a Personal Access Token to vsce. The vsce docs: \"On December 1, 2026, global Personal Access Tokens (PATs) in Azure DevOps are retired\" - and the same docs tell you to create the PAT for All accessible organizations, which is a global PAT.", "fix": "\"publish:vsce\": \"vsce publish --azure-credential\" (Microsoft Entra ID)"},
{"id": "wf-vsce-pat", "sev": "warn", "target": "vsce", "msg": "This workflow publishes to the VS Marketplace with a PAT secret. Global Azure DevOps PATs are retired on December 1, 2026.", "fix": "azure/login with workload identity federation, then: npx @vscode/vsce publish --azure-credential"},
{"id": "wf-no-ovsx", "sev": "warn", "target": "Open VSX", "msg": "This workflow publishes to the VS Marketplace only. Nothing reaches open-vsx.org.", "fix": "- run: npx ovsx publish -p ${{ secrets.OVSX_PAT }}"}
]
;</script>
<script>// Open VSX Publish Gate - checks a VS Code extension package.json or a GitHub publish workflow
// against the vsce packaging checks and the Open VSX (ovsx) publishing steps.
(function () {
const RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.OVSX_RULES;
const BY_ID = {};
RULES.forEach(r => { BY_ID[r.id] = r; });
// vsce TrustedSVGSources (package.js) - an SVG badge from any other host is refused
const TRUSTED = ['api.travis-ci.com', 'app.fossa.io', 'badge.buildkite.com', 'badge.fury.io', 'badgen.net',
'badges.frapsoft.com', 'badges.gitter.im', 'cdn.travis-ci.com', 'ci.appveyor.com', 'circleci.com',
'cla.opensource.microsoft.com', 'codacy.com', 'codeclimate.com', 'codecov.io', 'coveralls.io', 'david-dm.org',
'deepscan.io', 'dev.azure.com', 'docs.rs', 'flat.badgen.net', 'gitlab.com', 'godoc.org', 'goreportcard.com',
'img.shields.io', 'isitmaintained.com', 'marketplace.visualstudio.com', 'nodesecurity.io', 'opencollective.com',
'snyk.io', 'travis-ci.com', 'visualstudio.com', 'vsmarketplacebadges.dev'];
// IDs that return 404 on https://open-vsx.org/api/<ns>/<name> (checked 2026-09-30) -> Open VSX replacement
const MS_ONLY = {
'ms-python.vscode-pylance': 'detachhead.basedpyright',
'ms-vscode.cpptools': 'llvm-vs-code-extensions.vscode-clangd',
'ms-vscode-remote.remote-ssh': 'jeanp413.open-remote-ssh',
'ms-vscode-remote.remote-containers': '',
'ms-vsliveshare.vsliveshare': '',
'ms-dotnettools.csharp': 'muhammad-sammy.csharp',
'ms-dotnettools.csdevkit': 'muhammad-sammy.csharp',
'github.copilot': '',
'github.copilot-chat': ''
};
function lineOf(lines, re, from) {
for (let i = from || 0; i < lines.length; i++) if (re.test(lines[i])) return i + 1;
return 1;
}
function esc(s) { return String(s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
function add(out, id, line, extra, fix) {
const r = BY_ID[id];
out.push({ check: id, sev: r.sev, target: r.target, line: line,
msg: r.msg + (extra ? ' ' + extra : ''), fix: fix || r.fix });
}
function checkManifest(m, lines, out) {
const scripts = m.scripts && typeof m.scripts === 'object' ? m.scripts : {};
const scriptText = Object.keys(scripts).map(k => String(scripts[k])).join('\n');
const isExt = m.engines && m.engines.vscode || m.contributes || m.activationEvents ||
m.extensionDependencies || m.extensionPack || /\b(vsce|ovsx)\b/.test(scriptText) || m.publisher;
if (!isExt) return;
const L = k => lineOf(lines, new RegExp('"' + esc(k) + '"\\s*:'));
if (!m.publisher) add(out, 'publisher-missing', lineOf(lines, /"name"\s*:/));
else if (m.publisher === 'vscode-samples') add(out, 'publisher-vscode-samples', L('publisher'));
if (!m.engines || !m.engines.vscode) add(out, 'engines-missing', m.engines ? L('engines') : lineOf(lines, /"version"\s*:/));
if (/\.svg$/i.test(m.icon || '')) add(out, 'icon-svg', L('icon'), 'icon: ' + m.icon);
(Array.isArray(m.badges) ? m.badges : []).forEach(b => {
const u = String(b && b.url || '');
const ln = lineOf(lines, new RegExp(esc(u))) ;
let d = u;
try { d = decodeURI(u); } catch (e) { d = u; }
// plain parser: the browser build may run where the URL global is absent
const pm = /^([a-z][a-z0-9+.-]*:)\/\/([^\/?#]+)([^?#]*)/i.exec(d);
if (!pm) return;
const url = { protocol: pm[1], host: pm[2], pathname: pm[3] || '/', href: d };
if (!/^https:$/i.test(url.protocol)) { add(out, 'badge-http', ln, u, u.replace(/^http:/i, 'https:')); return; }
const gh = /^https:\/\/github\.com\/[^/]+\/[^/]+\/(actions\/)?workflows\/.*badge\.svg/.test(url.href);
if (/\.svg$/i.test(url.pathname) && TRUSTED.indexOf(url.host.toLowerCase()) < 0 && !gh)
add(out, 'badge-svg-untrusted', ln, 'host: ' + url.host);
});
const v = String(m.version || '');
const pre = /^\d+\.\d+\.\d+-(.+)$/.exec(v);
if (pre) add(out, 'version-prerelease', L('version'), 'version: ' + v,
'"version": "' + v.split('-')[0] + '" and publish with vsce publish --pre-release / ovsx publish --pre-release');
if (Array.isArray(m.enabledApiProposals) && m.enabledApiProposals.length)
add(out, 'proposed-api', L('enabledApiProposals'), 'proposals: ' + m.enabledApiProposals.join(', '));
if (m.dependencies && Object.prototype.hasOwnProperty.call(m.dependencies, 'vscode'))
add(out, 'dep-vscode', lineOf(lines, /"vscode"\s*:/, L('dependencies') - 1));
if (!m.repository) add(out, 'repository-missing', lineOf(lines, /"name"\s*:/));
if (!m.license) add(out, 'license-missing', lineOf(lines, /"name"\s*:/));
['extensionDependencies', 'extensionPack'].forEach(k => {
(Array.isArray(m[k]) ? m[k] : []).forEach(id => {
const key = String(id).toLowerCase();
if (!Object.prototype.hasOwnProperty.call(MS_ONLY, key)) return;
const rep = MS_ONLY[key];
add(out, 'ms-only-dependency', lineOf(lines, new RegExp('"' + esc(id) + '"', 'i'), L(k) - 1),
k + ': ' + id + '.', rep ? 'replace "' + id + '" with "' + rep + '" (on open-vsx.org)' :
'no Open VSX drop-in: remove "' + id + '" from ' + k + ' and list it under recommendations in your README');
});
});
const vscePub = Object.keys(scripts).filter(k => /\bvsce\s+publish\b/.test(String(scripts[k])));
const ovsxPub = Object.keys(scripts).some(k => /\bovsx\s+publish\b/.test(String(scripts[k])));
if (vscePub.length && !ovsxPub) {
const ns = m.publisher || '<your-id>';
add(out, 'no-ovsx-script', L(vscePub[0]), 'Open VSX namespace = publisher "' + ns + '".',
'"publish:ovsx": "ovsx publish -p $OVSX_PAT" (once: npx ovsx create-namespace ' + ns + ' -p $OVSX_PAT)');
}
vscePub.forEach(k => {
const s = String(scripts[k]);
if (/(\s-p\s|\s--pat\b|VSCE_PAT)/.test(' ' + s) && !/--azure-credential/.test(s))
add(out, 'vsce-pat-script', L(k), 'script "' + k + '": ' + s);
});
}
function checkWorkflow(text, lines, out) {
const vsceLine = lineOf(lines, /\bvsce\s+publish\b/);
const hasVsce = /\bvsce\s+publish\b/.test(text);
const mpRegistry = (text.match(/registryUrl:\s*['"]?https:\/\/marketplace\.visualstudio\.com/g) || []).length;
const haaleo = (text.match(/uses:\s*['"]?HaaLeo\/publish-vscode-extension/gi) || []).length;
const toMarketplace = hasVsce || mpRegistry > 0;
if (!toMarketplace) return;
const firstLine = hasVsce ? vsceLine : lineOf(lines, /registryUrl:\s*['"]?https:\/\/marketplace\.visualstudio\.com/);
const toOvsx = /\bovsx\s+publish\b/.test(text) || haaleo > mpRegistry;
const usesPat = !/--azure-credential/.test(text) &&
(/VSCE_PAT|VS_MARKETPLACE_TOKEN|AZURE_PAT|MARKETPLACE_PAT/.test(text) ||
lines.some(l => /\bvsce\s+publish\b.*(\s-p\s|--pat\b)/.test(l)) || (mpRegistry > 0 && /\bpat:/.test(text)));
if (usesPat) {
const pl = lineOf(lines, /VSCE_PAT|VS_MARKETPLACE_TOKEN|AZURE_PAT|MARKETPLACE_PAT/);
add(out, 'wf-vsce-pat', pl > 1 ? pl : firstLine);
}
if (!toOvsx) add(out, 'wf-no-ovsx', firstLine);
}
function check(text, opts) {
const out = [];
const src = String(text || '').replace(/^/, '');
const lines = src.split(/\r?\n/);
let m = null;
if (/^\s*\{/.test(src)) { try { m = JSON.parse(src); } catch (e) { m = null; } }
if (m && typeof m === 'object') checkManifest(m, lines, out);
else if (/^\s*jobs\s*:/m.test(src)) checkWorkflow(src, lines, out);
out.sort((a, b) => a.line - b.line);
return { findings: out, today: (opts && opts.today) || '' };
}
const api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, MS_ONLY: MS_ONLY };
if (typeof module !== 'undefined') module.exports = api;
if (typeof window !== 'undefined') window.OVSXENGINE = 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 OVSXREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = OVSXREPORT;
else window.OVSXREPORT = OVSXREPORT;
</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.OVSXENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.OVSXREPORT.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/open-vsx-publish-gate" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'open-vsx-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><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 Open VSX Publish Gate - vsce &amp; ovsx preflight 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:"open-vsx-publish-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">Whole-workspace publish plan: every extension manifest and workflow in the repo, a dated migration report, and a CI gate that fails a build adding a new blocker.</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">US software developer median wage: $65.38 an hour (BLS OEWS, occupation 15-1252, 2025 data)</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_ORIhnEZxJdlIhV17Ylw83l6Kmaa1ZCUauJPvK43oifW" 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 {"&":"&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><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/sku430348_result_card.jpg" alt="Open VSX Publish Gate - vsce &amp; ovsx preflight" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Six release errors in one sample package.json: that is what Open VSX Publish Gate reports for a VS Code extension author who publishes to the Visual Studio Marketplace and wants the same build on Open VSX, where VSCodium, Cursor, Windsurf and Gitpod users install from.</p>
<h3>Migrate from Marketplace-only publishing to Open VSX: the date first</h3>
<p>The official vsce publishing page now carries this line: &quot;On December 1, 2026, global Personal Access Tokens (PATs) in Azure DevOps are retired.&quot; The same page still tells you to create the publishing PAT with Organization set to All accessible organizations. That is a global PAT. If your release job runs vsce publish -p $VSCE_PAT, it stops working on December 1, 2026. The replacement is vsce publish --azure-credential, which signs in with Microsoft Entra ID.</p>
<p>Many teams use that deadline to move the release job anyway, and add Open VSX in the same pull request. That is where the second set of problems shows up.</p>
<h3>What the sample package.json contained</h3>
<p>The sample is an extension called py-lens. The gate reads the file once and reports 10 findings: 6 errors, 3 warnings and 1 info.</p>
<p>The 6 errors:</p>
<ul><li>&quot;version&quot;: &quot;2.1.0-beta.3&quot; - vsce refuses semver pre-release tags. Use &quot;2.1.0&quot; and publish with --pre-release.</li><li>&quot;icon&quot;: &quot;images/icon.svg&quot; - vsce stops with &quot;SVGs can&#x27;t be used as icons&quot;. Use a PNG.</li><li>a badge on http://img.shields.io - vsce wants HTTPS badge URLs.</li><li>an SVG badge from badges.example-ci.dev - that host is not on the vsce trusted list of 32 hosts.</li><li>ms-python.vscode-pylance in extensionDependencies - it returns 404 on open-vsx.org, so VSCodium users cannot install py-lens. The gate suggests detachhead.basedpyright.</li><li>ms-vscode-remote.remote-ssh in extensionPack - also 404 on open-vsx.org. The gate suggests jeanp413.open-remote-ssh.</li></ul>
<p>The 3 warnings: the publish script passes $VSCE_PAT, nothing runs ovsx publish, and there is no repository field. The info finding is the missing license field.</p>
<h3>Why vsce alone does not show this</h3>
<p>vsce package throws on the first error it meets. Six errors means six rounds of fix, re-run, read the next error. And vsce never checks Open VSX at all: a Pylance dependency packages without a word, then breaks installs in VSCodium. The gate prints every finding with its line and the replacement line in one pass, from 16 rules.</p>
<h3>The four-line move to Open VSX</h3>
<ol><li>Sign the Eclipse Publisher Agreement on open-vsx.org and create an access token. Store it as OVSX_PAT.</li><li>Run npx ovsx create-namespace &lt;publisher&gt; -p $OVSX_PAT. Your publisher field is the namespace.</li><li>Add &quot;publish:ovsx&quot;: &quot;ovsx publish -p $OVSX_PAT&quot; to scripts.</li><li>In GitHub Actions, HaaLeo/publish-vscode-extension publishes to Open VSX by default. Set registryUrl: https://marketplace.visualstudio.com only on the Marketplace step.</li></ol>
<p>The gate also reads the workflow file. If it sees vsce publish or a Marketplace registryUrl with a VSCE_PAT secret, it flags the PAT. If nothing reaches open-vsx.org, it prints the ovsx step to add.</p>
<h3>Free, and what comes next</h3>
<p>The free check covers the file you have open, in full, in VS Code or in the web version. Nothing is hidden. The full version adds the whole workspace: every extension manifest and workflow in the repo, a dated migration report, and a CI gate that fails a build adding a new blocker.</p>
<p>Sources: the vsce publishing page on code.visualstudio.com, the Open VSX publishing wiki, the vsce source (package.js), and open-vsx.org/api lookups made on 2026-09-30.</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/sku430348_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku430348_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 Open VSX Publish Gate check?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads a VS Code extension&#x27;s package.json or its GitHub publish workflow and lists every line that stops vsce or ovsx: an SVG icon, http or untrusted SVG badges, a semver pre-release version, a missing publisher or engines field, Microsoft-only dependencies that return 404 on open-vsx.org, and a VSCE_PAT that global PAT retirement on December 1, 2026 will break. Each finding carries 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 this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">VS Code extension authors and small tool teams who publish to the Visual Studio Marketplace and want the same release on Open VSX, where VSCodium, Cursor, Windsurf and Gitpod users install from. It also fits maintainers whose release job still runs vsce publish with a Personal Access Token and who have to switch to Microsoft Entra ID before December 1, 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 vsce package and see what fails?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">vsce throws on the first error it meets, so a manifest with 6 errors takes 6 rounds of fix and re-run. It never checks Open VSX: an extension that lists ms-python.vscode-pylance in extensionDependencies packages fine, yet VSCodium users cannot install it because that ID returns 404 on open-vsx.org. The gate lists all 16 rule hits in one pass.</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 a key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free, with no key: the open package.json or workflow file is checked in full and every finding shows its line and the fix; the web version does the same for a pasted file. The $29 key adds the whole workspace: every extension manifest and publish workflow in the repo, a dated migration report you can file, and a CI gate that fails a build adding a new blocker.</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 it cost compared with doing it by hand?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The free tier costs nothing and ends the job for one file. The full version is $29 once, one licence key per person or team seat, no subscription. The yardstick: a US software developer&#x27;s median wage is $65.38 an hour (BLS OEWS 2025), and doing this by hand means reading the vsce trusted-host list and querying open-vsx.org per dependency ID.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Open VSX Publish Gate check?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a VS Code extension's package.json or its GitHub publish workflow and lists every line that stops vsce or ovsx: an SVG icon, http or untrusted SVG badges, a semver pre-release version, a missing publisher or engines field, Microsoft-only dependencies that return 404 on open-vsx.org, and a VSCE_PAT that global PAT retirement on December 1, 2026 will break. Each finding carries the replacement line."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "VS Code extension authors and small tool teams who publish to the Visual Studio Marketplace and want the same release on Open VSX, where VSCodium, Cursor, Windsurf and Gitpod users install from. It also fits maintainers whose release job still runs vsce publish with a Personal Access Token and who have to switch to Microsoft Entra ID before December 1, 2026."}}, {"@type": "Question", "name": "Why not just run vsce package and see what fails?", "acceptedAnswer": {"@type": "Answer", "text": "vsce throws on the first error it meets, so a manifest with 6 errors takes 6 rounds of fix and re-run. It never checks Open VSX: an extension that lists ms-python.vscode-pylance in extensionDependencies packages fine, yet VSCodium users cannot install it because that ID returns 404 on open-vsx.org. The gate lists all 16 rule hits in one pass."}}, {"@type": "Question", "name": "What is free and what needs a key?", "acceptedAnswer": {"@type": "Answer", "text": "Free, with no key: the open package.json or workflow file is checked in full and every finding shows its line and the fix; the web version does the same for a pasted file. The $29 key adds the whole workspace: every extension manifest and publish workflow in the repo, a dated migration report you can file, and a CI gate that fails a build adding a new blocker."}}, {"@type": "Question", "name": "What does it cost compared with doing it by hand?", "acceptedAnswer": {"@type": "Answer", "text": "The free tier costs nothing and ends the job for one file. The full version is $29 once, one licence key per person or team seat, no subscription. The yardstick: a US software developer's median wage is $65.38 an hour (BLS OEWS 2025), and doing this by hand means reading the vsce trusted-host list and querying open-vsx.org per dependency ID."}}]}</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:430348,slug:"open-vsx-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/cargo-publish-gate"><span class="yj-rel-t">Cargo.toml Publish Gate - crates.io</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/coverage-gate-lint"><span class="yj-rel-t">Coverage Gate Lint</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/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><a class="yj-rel-c" href="https://getreadystack.com/tools/vscodium-extension-licence-lint"><span class="yj-rel-t">VSCodium Migration Lint: Microsoft-only Extensions</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/composer-release-gate"><span class="yj-rel-t">composer.json Validate - PHP EOL Release Gate</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>