Spaces:
Running
Running
Download index.html from getreadystack/vsix-publish-lint: direct link, hf CLI and curl.
- Browser
- Download file 49 kB
-
https://huggingface.co/spaces/getreadystack/vsix-publish-lint/resolve/main/index.html
- Command line
-
hf download hf://spaces/getreadystack/vsix-publish-lint/index.html
-
curl -L -o index.html https://huggingface.co/spaces/getreadystack/vsix-publish-lint/resolve/main/index.html
49 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width,initial-scale=1"> | |
| <title>VSIX package json errors — check extension package blockers before</title> | |
| <meta name="description" content="This browser tool checks an extension package manifest for mistakes that can make packaging or publishing fail or produce a broken listing."> | |
| <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/vsix-publish-lint"></head> | |
| <body> | |
| <div class="wrap"> | |
| <h1>VSIX package json errors — check extension package blockers before</h1> | |
| <p class="lede">Lints a VS Code extension package.json for the manifest mistakes that make vsce package/publish fail or ship a broken listing. 14 rules, offline. Runs entirely in your browser — nothing is uploaded.</p> | |
| <textarea id="src" spellcheck="false">{ | |
| "name": "Release Notes Helper", | |
| "displayName": "Release Notes Helper", | |
| "description": "Generates release notes from your git history, groups them by conventional commit type, renders a CHANGELOG entry, copies it to the clipboard, opens a preview pane, and lets you publish it straight to a GitHub release without leaving the editor.", | |
| "version": "0.9.1-beta.2", | |
| "engines": { "vscode": "^1.74.0" }, | |
| "categories": ["Linter", "Other"], | |
| "badges": [ | |
| { "url": "https://my-jenkins.internal.example/badge.svg", "href": "https://my-jenkins.internal.example", "description": "build" } | |
| ], | |
| "main": "./out/extension.js", | |
| "activationEvents": [ | |
| "*", | |
| "onCommand:releaseNotesHelper.generate" | |
| ], | |
| "contributes": { | |
| "commands": [ | |
| { "command": "releaseNotesHelper.generate", "title": "Release Notes: Generate" } | |
| ] | |
| }, | |
| "scripts": { "compile": "tsc -p ./" }, | |
| "devDependencies": { "@types/vscode": "^1.96.0", "typescript": "^5.4.0" } | |
| }</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> Lints the extension manifest you have open and names every publish blocker with the exact field to change. <strong>In the editor:</strong> Lints every extension manifest in the workspace in one pass and writes the result out as a JSON/SARIF file you own for CI — 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 VSX_RULES = [ | |
| {"id":"manifest_unparseable","sev":"error","title":"package.json is not valid JSON", | |
| "msg":"package.json does not parse as JSON, so vsce reads nothing and every other check below is blind.", | |
| "fix":"Fix the JSON syntax first (trailing comma, unquoted key, stray comment)."}, | |
| {"id":"engines_vscode_missing","sev":"error","title":"engines.vscode is missing", | |
| "msg":"No engines.vscode field. vsce refuses to package a manifest without it, and the Marketplace has no idea which VS Code builds may install you.", | |
| "fix":"Add \"engines\": { \"vscode\": \"^1.90.0\" } using the oldest VS Code you actually support."}, | |
| {"id":"engines_vscode_star","sev":"error","title":"engines.vscode is \"*\"", | |
| "msg":"engines.vscode is a wildcard. vsce rejects \"*\" — it needs a concrete minimum such as ^1.90.0.", | |
| "fix":"Replace \"*\" with the caret range of the oldest VS Code API you call."}, | |
| {"id":"types_vscode_ahead","sev":"error","title":"@types/vscode is newer than engines.vscode", | |
| "msg":"devDependencies[\"@types/vscode\"] declares a newer VS Code than engines.vscode allows. You compile against API that your declared minimum build does not have, and vsce stops the package step.", | |
| "fix":"Raise engines.vscode to match @types/vscode, or pin @types/vscode down to engines.vscode."}, | |
| {"id":"publisher_missing","sev":"error","title":"publisher is missing", | |
| "msg":"No publisher field. The extension id is publisher.name — without it there is no id, so packaging and publishing both stop.", | |
| "fix":"Add \"publisher\": \"\u003cyour marketplace publisher id\u003e\" (the same id on Open VSX becomes your namespace)."}, | |
| {"id":"name_invalid","sev":"error","title":"name is not a valid extension id segment", | |
| "msg":"name must be lowercase with no spaces — only letters, digits, hyphen and underscore. Anything else cannot become half of a publisher.name id.", | |
| "fix":"Lowercase the name and replace spaces and dots with hyphens. displayName is where the pretty title goes."}, | |
| {"id":"version_invalid","sev":"error","title":"version is not plain x.y.z", | |
| "msg":"The Marketplace accepts only three numeric parts. A four-part version or a -beta suffix is refused at upload, after your build has already run.", | |
| "fix":"Use a plain 1.2.3. Ship pre-releases with the --pre-release flag instead of a version suffix."}, | |
| {"id":"invalid_category","sev":"error","title":"categories contains a name that is not on the list", | |
| "msg":"categories is a closed vocabulary. A category that is not on the list fails validation, and a near-miss like \"Linter\" or \"Theme\" is the usual cause.", | |
| "fix":"Use exact names from the allowed list, e.g. Linters, Themes, Programming Languages, Snippets, Formatters."}, | |
| {"id":"badge_host_unapproved","sev":"error","title":"badges points at a host that is not approved", | |
| "msg":"Badge images are restricted to an approved host list. A badge from anywhere else is rejected rather than silently dropped.", | |
| "fix":"Serve the badge through img.shields.io, or drop the badges field and put the image in README.md instead."}, | |
| {"id":"entrypoint_missing","sev":"error","title":"no main and no contributes", | |
| "msg":"Nothing declares any code or any contribution, so the extension installs and then does nothing at all. This ships without an error.", | |
| "fix":"Add \"main\": \"./extension.js\" for code, or a contributes block for a declarative extension."}, | |
| {"id":"repository_missing","sev":"warn","title":"repository is missing", | |
| "msg":"Without repository, relative image and link paths in README.md have no base to be rewritten against, and your Open VSX namespace cannot be tied back to a source.", | |
| "fix":"Add \"repository\": { \"type\": \"git\", \"url\": \"https://github.com/\u003cowner\u003e/\u003crepo\u003e.git\" } with an https URL, not an SSH one."}, | |
| {"id":"license_field_missing","sev":"warn","title":"license field is missing", | |
| "msg":"No license field, so the listing page shows no licence to anyone deciding whether their company may install this.", | |
| "fix":"Add \"license\": \"MIT\" (an SPDX id) and keep the matching LICENSE file in the package."}, | |
| {"id":"icon_missing","sev":"warn","title":"icon is missing", | |
| "msg":"No icon field, so your tile renders as the grey default next to every competitor that shipped one.", | |
| "fix":"Add \"icon\": \"images/icon.png\", 128x128 PNG, and make sure .vscodeignore does not exclude it."}, | |
| {"id":"wildcard_activation","sev":"warn","title":"activationEvents contains \"*\"", | |
| "msg":"\"*\" activates your extension during every window startup, whether or not the user is doing anything you handle. It is the single biggest startup-cost flag reviewers look for.", | |
| "fix":"Replace \"*\" with the events you really need: onLanguage:, onCommand:, workspaceContains:, onStartupFinished."}, | |
| {"id":"redundant_oncommand","sev":"warn","title":"onCommand entry duplicates a contributed command", | |
| "msg":"This onCommand activation event repeats a command already declared in contributes.commands. Modern VS Code generates that activation for you, so the line is dead weight kept in sync by hand.", | |
| "fix":"Delete the duplicated onCommand entry and let contributes.commands imply it."}, | |
| {"id":"description_too_long","sev":"warn","title":"description is longer than 200 characters", | |
| "msg":"The short description is cut off in search results and on the tile, so the end of your sentence is never read by anyone browsing.", | |
| "fix":"Cut the description to 200 characters or fewer and move the detail into README.md."} | |
| ] | |
| ;</script> | |
| <script>/* VSIX Publish Lint — one brain, used by the VS Code extension and by the web page. */ | |
| (function (root) { | |
| 'use strict'; | |
| var RULES = (typeof module !== 'undefined' && module.exports) | |
| ? require('./rules.json') | |
| : root.VSX_RULES; | |
| var BY_ID = {}; | |
| for (var i = 0; i < RULES.length; i++) BY_ID[RULES[i].id] = RULES[i]; | |
| var CATEGORIES = ['AI', 'Azure', 'Chat', 'Data Science', 'Debuggers', 'Education', | |
| 'Extension Packs', 'Formatters', 'Keymaps', 'Language Packs', 'Linters', | |
| 'Machine Learning', 'Notebooks', 'Other', 'Programming Languages', | |
| 'SCM Providers', 'Snippets', 'Testing', 'Themes', 'Visualization']; | |
| var BADGE_HOSTS = ['api.travis-ci.com', 'api.travis-ci.org', 'badge.fury.io', | |
| 'badgen.net', 'flat.badgen.net', 'badges.gitter.im', 'ci.appveyor.com', | |
| 'circleci.com', 'codacy.com', 'codeclimate.com', 'codecov.io', 'coveralls.io', | |
| 'deepscan.io', 'dev.azure.com', 'gitlab.com', 'goreportcard.com', | |
| 'img.shields.io', 'isitmaintained.com', 'marketplace.visualstudio.com', | |
| 'opencollective.com', 'shields.io', 'snyk.io', 'travis-ci.com', 'visualstudio.com']; | |
| // 1-based line of the first occurrence of a top-level-ish "key" | |
| function lineOf(text, key) { | |
| var lines = String(text).split(/\r?\n/); | |
| var needle = '"' + key + '"'; | |
| for (var i = 0; i < lines.length; i++) { | |
| if (lines[i].indexOf(needle) !== -1) return i + 1; | |
| } | |
| return 1; | |
| } | |
| function lineOfValue(text, value) { | |
| var lines = String(text).split(/\r?\n/); | |
| for (var i = 0; i < lines.length; i++) { | |
| if (lines[i].indexOf(value) !== -1) return i + 1; | |
| } | |
| return 1; | |
| } | |
| function minVersion(range) { | |
| var m = String(range || '').match(/(\d+)\.(\d+)(?:\.(\d+))?/); | |
| if (!m) return null; | |
| return [parseInt(m[1], 10), parseInt(m[2], 10), parseInt(m[3] || '0', 10)]; | |
| } | |
| function cmp(a, b) { | |
| for (var i = 0; i < 3; i++) { | |
| if (a[i] !== b[i]) return a[i] < b[i] ? -1 : 1; | |
| } | |
| return 0; | |
| } | |
| function hostOf(url) { | |
| var m = String(url || '').match(/^https?:\/\/([^\/?#]+)/i); | |
| return m ? m[1].toLowerCase() : ''; | |
| } | |
| function hit(out, id, line, detail) { | |
| var r = BY_ID[id]; | |
| if (!r) return; | |
| out.push({ | |
| check: id, | |
| sev: r.sev, | |
| msg: (detail ? detail + ' — ' : '') + r.msg + ' Fix: ' + r.fix, | |
| line: line || 1 | |
| }); | |
| } | |
| function check(text, opts) { | |
| opts = opts || {}; | |
| var findings = []; | |
| var src = String(text == null ? '' : text); | |
| if (!src.trim()) return { findings: findings }; | |
| var pkg; | |
| try { | |
| pkg = JSON.parse(src); | |
| } catch (e) { | |
| hit(findings, 'manifest_unparseable', 1, String(e.message).slice(0, 90)); | |
| return { findings: findings }; | |
| } | |
| if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg)) { | |
| hit(findings, 'manifest_unparseable', 1, 'top level is not an object'); | |
| return { findings: findings }; | |
| } | |
| var engines = pkg.engines || {}; | |
| var eng = engines.vscode; | |
| if (eng === undefined || eng === null || String(eng).trim() === '') { | |
| hit(findings, 'engines_vscode_missing', lineOf(src, 'engines')); | |
| } else if (String(eng).trim() === '*') { | |
| hit(findings, 'engines_vscode_star', lineOf(src, 'vscode')); | |
| } else { | |
| var dev = pkg.devDependencies || {}; | |
| var types = dev['@types/vscode']; | |
| var a = minVersion(types); | |
| var b = minVersion(eng); | |
| if (a && b && cmp(a, b) > 0) { | |
| hit(findings, 'types_vscode_ahead', lineOf(src, '@types/vscode'), | |
| '@types/vscode ' + types + ' vs engines.vscode ' + eng); | |
| } | |
| } | |
| if (!pkg.publisher || !String(pkg.publisher).trim()) { | |
| hit(findings, 'publisher_missing', lineOf(src, 'name')); | |
| } | |
| var nm = String(pkg.name == null ? '' : pkg.name); | |
| if (!nm || !/^[a-z0-9][a-z0-9_-]*$/.test(nm)) { | |
| hit(findings, 'name_invalid', lineOf(src, 'name'), 'name = ' + JSON.stringify(pkg.name)); | |
| } | |
| var ver = String(pkg.version == null ? '' : pkg.version); | |
| if (!/^\d+\.\d+\.\d+$/.test(ver)) { | |
| hit(findings, 'version_invalid', lineOf(src, 'version'), 'version = ' + JSON.stringify(pkg.version)); | |
| } | |
| var cats = pkg.categories; | |
| if (Array.isArray(cats)) { | |
| for (var c = 0; c < cats.length; c++) { | |
| if (CATEGORIES.indexOf(cats[c]) === -1) { | |
| hit(findings, 'invalid_category', lineOfValue(src, String(cats[c])), | |
| '"' + cats[c] + '" is not an allowed category'); | |
| } | |
| } | |
| } | |
| var badges = pkg.badges; | |
| if (Array.isArray(badges)) { | |
| for (var d = 0; d < badges.length; d++) { | |
| var url = badges[d] && badges[d].url; | |
| var h = hostOf(url); | |
| if (!h || BADGE_HOSTS.indexOf(h) === -1) { | |
| hit(findings, 'badge_host_unapproved', lineOfValue(src, String(url || 'badges')), | |
| (h || 'that URL') + ' is not an approved badge host'); | |
| } | |
| } | |
| } | |
| if (!pkg.main && !pkg.browser && !pkg.contributes) { | |
| hit(findings, 'entrypoint_missing', 1); | |
| } | |
| var repo = pkg.repository; | |
| var repoUrl = repo && (typeof repo === 'string' ? repo : repo.url); | |
| if (!repoUrl || !String(repoUrl).trim()) { | |
| hit(findings, 'repository_missing', lineOf(src, 'name')); | |
| } | |
| if (!pkg.license || !String(pkg.license).trim()) { | |
| hit(findings, 'license_field_missing', lineOf(src, 'version')); | |
| } | |
| if (!pkg.icon || !String(pkg.icon).trim()) { | |
| hit(findings, 'icon_missing', lineOf(src, 'displayName')); | |
| } | |
| var acts = Array.isArray(pkg.activationEvents) ? pkg.activationEvents : []; | |
| var declared = {}; | |
| var contributed = (pkg.contributes && Array.isArray(pkg.contributes.commands)) | |
| ? pkg.contributes.commands : []; | |
| for (var k = 0; k < contributed.length; k++) { | |
| if (contributed[k] && contributed[k].command) declared[contributed[k].command] = true; | |
| } | |
| for (var j = 0; j < acts.length; j++) { | |
| var ev = String(acts[j]); | |
| if (ev === '*') { | |
| hit(findings, 'wildcard_activation', lineOfValue(src, '"*"')); | |
| } else if (ev.indexOf('onCommand:') === 0 && declared[ev.slice(10)]) { | |
| hit(findings, 'redundant_oncommand', lineOfValue(src, ev), ev); | |
| } | |
| } | |
| var desc = String(pkg.description == null ? '' : pkg.description); | |
| if (desc.length > 200) { | |
| hit(findings, 'description_too_long', lineOf(src, 'description'), | |
| desc.length + ' characters, ' + (desc.length - 200) + ' over'); | |
| } | |
| findings.sort(function (x, y) { | |
| if (x.sev !== y.sev) return x.sev === 'error' ? -1 : 1; | |
| return x.line - y.line; | |
| }); | |
| return { findings: findings }; | |
| } | |
| var api = { | |
| engine: { check: check }, | |
| RULES: RULES, | |
| RULE_COUNT: RULES.length, | |
| CATEGORIES: CATEGORIES, | |
| BADGE_HOSTS: BADGE_HOSTS | |
| }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = api; | |
| root.VSXLINT = api; | |
| })(typeof window !== 'undefined' ? window : globalThis); | |
| </script> | |
| <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다. | |
| ; | |
| 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 VSXREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml }; | |
| if (typeof module !== 'undefined' && module.exports) module.exports = VSXREPORT; | |
| else window.VSXREPORT = VSXREPORT; | |
| </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.VSXLINT.engine.check(src.value,{today:today.value}); out.innerHTML=window.VSXREPORT.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.vsix-publish-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'vsix-publish-lint',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/vsix-publish-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'vsix-publish-lint',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/vsix-publish-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'vsix-publish-lint',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://www.npmjs.com/package/@readystack/vsix-publish-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'vsix-publish-lint',src:'mcp',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">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/vsix-publish-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'vsix-publish-lint',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.vsix-publish-lint | |
| npx @readystack/vsix-publish-lint <file> | |
| npx @readystack/vsix-publish-lint --mcp</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 16 rules VSIX Publish Lint 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:"vsix-publish-lint",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">Find the package.json lines that stop vsce before you run the release build</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">One hour of specialised freelance developer time runs $75-$150 on Upwork's own 2026 rate guide; a rejected publish usually costs more than one.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_0qIqdKq149OPf1mvGZKukzSvyH1nK9fHwub4l4P27pr" 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 {"&":"&","<":"<",">":">",'"':"""}[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-12">Updated 2026-09-12</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-12","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/sku41398_result_card.jpg" alt="VSIX Publish Lint" loading="lazy" style="max-width:100%;border-radius:6px"></p> | |
| <p>Six blockers, six build cycles: for the indie author shipping a VS Code extension to the Marketplace and Open VSX, the sample manifest bundled with VSIX Publish Lint returns 12 findings - 6 that stop <code>vsce</code> outright and 6 that ship a worse listing - while <code>vsce</code> itself reports exactly one of them, and only after the compile step has already run.</p> | |
| <p>That order is the problem. <code>vsce package</code> runs your <code>vscode:prepublish</code> script first, validates the manifest second, and stops at the first thing it dislikes. Fix that line, run again, wait for TypeScript again, meet the next one.</p> | |
| <p>The bundled <code>_fixtures/dirty.json</code>, scored by the 16 rules:</p> | |
| <pre><code>error name_invalid L2 "Release Notes Helper" | |
| error publisher_missing L2 no publisher key at all | |
| error version_invalid L5 "0.9.1-beta.2" | |
| error invalid_category L7 "Linter" | |
| error badge_host_unapproved L9 my-jenkins.internal.example | |
| error types_vscode_ahead L22 @types/vscode ^1.96.0 vs engines ^1.74.0 | |
| ...plus 6 warnings: repository, license and icon missing, "*" activation, | |
| a redundant onCommand:, and a description 45 characters over the 200 shown.</code></pre> | |
| <p>Four of those six errors are one-character-class mistakes. <code>categories</code> is a closed vocabulary and the plural matters: <code>Linters</code>, not <code>Linter</code>. <code>name</code> is half of the <code>publisher.name</code> id, so it must be lowercase with no spaces; <code>displayName</code> holds the pretty title. <code>version</code> must be three numeric parts - pre-releases go through <code>--pre-release</code>, not a <code>-beta.2</code> suffix. Badge images come only from an approved host list, so one served from an internal Jenkins is refused, not quietly dropped.</p> | |
| <p>The fifth catches people who did everything else right. If <code>devDependencies["@types/vscode"]</code> is newer than <code>engines.vscode</code>, you are compiling against API your declared minimum build does not have. Bumping the types package is a routine dependency update; bumping <code>engines.vscode</code> is a decision about who may install you. The two drift apart silently until a release day.</p> | |
| <p>The six warnings never fail an upload, which is why they survive. A missing <code>icon</code> is the grey default tile next to competitors who shipped one; a description over 200 characters is cut off in search results; <code>"*"</code> in <code>activationEvents</code> starts your extension at every window launch.</p> | |
| <p>The same manifest corrected, <code>_fixtures/clean.json</code>, is 0 findings. A third example - a theme extension with no <code>engines</code> block and a two-part <code>"1.0"</code> version - returns 2, both fatal, neither visible until upload.</p> | |
| <p>Why not simply ask a chat model? The two checks that cost the most are the ones it cannot do: it cannot see the version <code>@types/vscode</code> is pinned to against the <code>engines.vscode</code> in your manifest, and it will cheerfully produce category names that are not on the list - plausible-sounding names are what a language model invents best. Nor can it give you a line number.</p> | |
| <p>VSIX Publish Lint runs the 16 rules over the manifest you have open, offline, printing rule id, severity, line, what breaks and the field to change. That tier is free and finishes the job: one manifest, all 16 rules, as often as you like, nothing watermarked and nothing held back.</p> | |
| <p>The licence key covers a different job, on the ownership axis: lint every manifest in the workspace in one pass and write the result out as a JSON or SARIF file you keep and can gate CI on, so the check runs on the pull request, not on release day. $29 once, one key per person or team seat. For scale: specialised freelance developer work is listed at $75 to $150 an hour in Upwork's own 2026 rate guide.</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/sku41398_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku41398_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 VSIX Publish Lint actually do?</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 package.json and reports the manifest problems that stop vsce package or vsce publish, plus the ones that ship a degraded Marketplace listing. Sixteen rules cover engines.vscode, the @types/vscode pairing, publisher, name, version, categories, badge hosts, entry point, repository, license, icon, activation events and description length. Every finding names the field and the replacement.</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">Extension authors who publish to the VS Code Marketplace or Open VSX, and the people who maintain a monorepo of several extensions. It is aimed at the moment just before a release: you have a manifest, you are about to tag, and you would rather not learn from a failed upload that categories must say Linters and not Linter.</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 and read the error?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">vsce tells you about one blocker at a time and only after your compile step has run, so a manifest with six problems costs six build cycles. It also stays silent on the non-fatal ones, such as a missing icon or repository, which do not fail the upload but do shape what shoppers see. This checks all sixteen at once, before the build.</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 licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free covers one manifest completely: open a package.json, run the lint, get all sixteen rules with line numbers and fixes, unlimited times, offline, with no watermark and no countdown. The licence key adds a different job: linting every extension manifest in the workspace in one pass, and writing the result to a JSON or SARIF file you own and can gate CI on.</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 if a person did it?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Specialised freelance developer work is listed at $75 to $150 an hour on Upwork's 2026 rate guide. A publish that is rejected after the build costs at least that in rework and a delayed release, and the review of a manifest before a release is exactly the errand people bill an hour for. The extension is $29 once.</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. VSIX Publish Lint checks the file you open against 16 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. 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 VSIX Publish Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a VS Code extension package.json and reports the manifest problems that stop vsce package or vsce publish, plus the ones that ship a degraded Marketplace listing. Sixteen rules cover engines.vscode, the @types/vscode pairing, publisher, name, version, categories, badge hosts, entry point, repository, license, icon, activation events and description length. Every finding names the field and the replacement."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Extension authors who publish to the VS Code Marketplace or Open VSX, and the people who maintain a monorepo of several extensions. It is aimed at the moment just before a release: you have a manifest, you are about to tag, and you would rather not learn from a failed upload that categories must say Linters and not Linter."}}, {"@type": "Question", "name": "Why not just run vsce and read the error?", "acceptedAnswer": {"@type": "Answer", "text": "vsce tells you about one blocker at a time and only after your compile step has run, so a manifest with six problems costs six build cycles. It also stays silent on the non-fatal ones, such as a missing icon or repository, which do not fail the upload but do shape what shoppers see. This checks all sixteen at once, before the build."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free covers one manifest completely: open a package.json, run the lint, get all sixteen rules with line numbers and fixes, unlimited times, offline, with no watermark and no countdown. The licence key adds a different job: linting every extension manifest in the workspace in one pass, and writing the result to a JSON or SARIF file you own and can gate CI on."}}, {"@type": "Question", "name": "What would this cost if a person did it?", "acceptedAnswer": {"@type": "Answer", "text": "Specialised freelance developer work is listed at $75 to $150 an hour on Upwork's 2026 rate guide. A publish that is rejected after the build costs at least that in rework and a delayed release, and the review of a manifest before a release is exactly the errand people bill an hour for. The extension is $29 once."}}, {"@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. VSIX Publish Lint checks the file you open against 16 rules from a rule set dated 2026-09-20, and points at the exact line with the fix. 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:41398,slug:"vsix-publish-lint",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/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/webflow-restaurant-template"><span class="yj-rel-t">Webflow Restaurant Template — delivered as self-contained HTML you own: 26 pag</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/autohotkey-toml-devicetree-snippets"><span class="yj-rel-t">AutoHotkey v2, TOML and DeviceTree checks for VS Code</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/bulk-sender-lint"><span class="yj-rel-t">Bulk Sender Lint - the Gmail, Yahoo and Outlook.com rules, checked in your mai</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cran-policy-submission-lint"><span class="yj-rel-t">CRAN Policy Submission Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/csaf-advisory-check-cra-2026"><span class="yj-rel-t">CSAF Advisory Check for CRA 2026</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> | |