getreadystack commited on
Commit
fd58f25
·
verified ·
1 Parent(s): 439cc6a

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +303 -17
index.html CHANGED
@@ -1,19 +1,305 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
  <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <title>Node 18 end of life — check package metadata before publishing</title>
7
+ <meta name="description" content="This free browser tool checks package.json for Node engine floors, pins, licence values, and repository or provenance blockers.">
8
+ <style>
9
+ :root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
10
+ .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}
11
+ 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}
12
+ .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}
13
+ input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
14
+ 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}
15
+ .sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
16
+ .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}
17
+ </style>
18
+ <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>
19
+ <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/package-json-release-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>
20
+ </head>
21
+ <body>
22
+ <div class="wrap">
23
+ <h1>Node 18 end of life — check package metadata before publishing</h1>
24
+ <p class="lede">Checks package.json before npm publish: engines.node floors that still admit end-of-life Node 18/20, volta and @types/node pins, non-SPDX licence values, and repository/provenance blockers. Runs entirely in your browser — nothing is uploaded.</p>
25
+ <textarea id="src" spellcheck="false">{
26
+ "name": "@acme/tiny-queue",
27
+ "version": "3.1.0",
28
+ "description": "A small in-memory job queue.",
29
+ "license": "Apache 2.0",
30
+ "homepage": "https://github.com/Acme/tiny-queue#readme",
31
+ "bugs": { "url": "https://github.com/Acme/tiny-queue/issues" },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/acme/tiny-queue.git"
35
+ },
36
+ "type": "module",
37
+ "exports": "./dist/index.js",
38
+ "files": ["dist"],
39
+ "engines": {
40
+ "node": "&gt;=18"
41
+ },
42
+ "volta": {
43
+ "node": "20.11.1"
44
+ },
45
+ "publishConfig": {
46
+ "provenance": true
47
+ },
48
+ "scripts": {
49
+ "build": "tsc -p .",
50
+ "test": "node --test"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^20.11.0",
54
+ "typescript": "^5.9.2"
55
+ }
56
+ }</textarea>
57
+ <div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
58
+ <div id="out" aria-live="polite"></div>
59
+ <div id="paid"><strong>Free here:</strong> Lists every line in package.json that still pins an end-of-life Node line or blocks npm publish --provenance, with the exact replacement line for each.. <strong>In the editor:</strong> A dated release report (Markdown) of every finding and its fix line, saved for the release ticket or audit file. — that part asks for a licence key.</div>
60
+ <p class="note">Same engine as the VS Code extension, byte for byte.</p>
61
+ </div>
62
+ <script>var PKG_RULES = [
63
+ {"id": "engines-node-missing", "sev": "warning", "field": "engines.node", "msg": "No engines.node field: npm installs this package on any Node line, including end-of-life Node 18 and Node 20.", "fix": "\"engines\": { \"node\": \"\u003e=22\" }"},
64
+ {"id": "engines-node-eol-floor", "sev": "error", "field": "engines.node", "msg": "engines.node lower bound still admits an end-of-life Node line.", "fix": "\"node\": \"\u003e=22\""},
65
+ {"id": "engines-node-floor-expiring", "sev": "warning", "field": "engines.node", "msg": "engines.node lower bound is a Node line that reaches end of life within 180 days.", "fix": "\"node\": \"\u003e=24\""},
66
+ {"id": "volta-node-eol", "sev": "error", "field": "volta.node", "msg": "volta.node pins an end-of-life Node line for every contributor and CI job that uses Volta.", "fix": "\"node\": \"24\""},
67
+ {"id": "types-node-eol", "sev": "warning", "field": "devDependencies.@types/node", "msg": "@types/node targets an end-of-life Node line, so the type checker allows APIs your engines floor should not promise.", "fix": "\"@types/node\": \"^22\""},
68
+ {"id": "package-manager-no-provenance", "sev": "error", "field": "packageManager", "msg": "packageManager pins npm older than 9.5.0 while provenance is on; npm documents 9.5.0+ for provenance.", "fix": "\"packageManager\": \"npm@11\""},
69
+ {"id": "license-missing", "sev": "warning", "field": "license", "msg": "No license field: users cannot tell under what terms they may use the package.", "fix": "\"license\": \"MIT\""},
70
+ {"id": "license-object", "sev": "error", "field": "license", "msg": "license is an object or a licenses array; npm documents this style as deprecated, not valid metadata.", "fix": "\"license\": \"MIT\""},
71
+ {"id": "license-not-spdx", "sev": "error", "field": "license", "msg": "license is not an SPDX license identifier or SPDX expression.", "fix": "\"license\": \"Apache-2.0\""},
72
+ {"id": "license-deprecated-id", "sev": "warning", "field": "license", "msg": "license uses a deprecated SPDX identifier; the current list splits it into -only and -or-later.", "fix": "\"license\": \"GPL-3.0-only\""},
73
+ {"id": "unlicensed-not-private", "sev": "warning", "field": "private", "msg": "license is UNLICENSED but private is not true; npm suggests private: true to prevent accidental publication.", "fix": "\"private\": true"},
74
+ {"id": "repository-missing", "sev": "warning", "field": "repository", "msg": "No repository field: npm repo cannot find the source and provenance has nothing to match.", "fix": "\"repository\": { \"type\": \"git\", \"url\": \"git+https://github.com/OWNER/REPO.git\" }"},
75
+ {"id": "provenance-without-repository", "sev": "error", "field": "repository", "msg": "publishConfig.provenance is true but there is no repository; npm needs a public repository that matches where you publish from.", "fix": "\"repository\": { \"type\": \"git\", \"url\": \"git+https://github.com/OWNER/REPO.git\" }"},
76
+ {"id": "repository-shorthand", "sev": "warning", "field": "repository", "msg": "repository is shorthand; npm normalizes it to the object form and warns during npm publish.", "fix": "\"repository\": { \"type\": \"git\", \"url\": \"git+https://github.com/OWNER/REPO.git\" }"},
77
+ {"id": "repository-html-url", "sev": "error", "field": "repository.url", "msg": "repository.url is a browser page or plain http URL, not a URL a VCS program can use without modification.", "fix": "\"url\": \"git+https://github.com/OWNER/REPO.git\""},
78
+ {"id": "repository-case-mismatch", "sev": "error", "field": "repository.url", "msg": "repository.url and homepage/bugs name the same repo with different letter case; provenance matches the repository case-sensitively.", "fix": "\"url\": \"git+https://github.com/OWNER/REPO.git\""},
79
+ {"id": "scoped-provenance-no-access", "sev": "error", "field": "publishConfig.access", "msg": "Scoped package publishes with provenance but publishConfig.access is not public; npm documents --access public for a first provenance publish.", "fix": "\"access\": \"public\""}
80
+ ]
81
+ ;</script>
82
+ <script>/* package.json release gate: one brain for the VS Code extension and the web page. */
83
+ (function () {
84
+ var RULES = (typeof module !== 'undefined' && typeof require !== 'undefined') ? require('./rules.json') : window.PKG_RULES;
85
+ var BY = {};
86
+ RULES.forEach(function (r) { BY[r.id] = r; });
87
+
88
+ // Node release schedule end-of-life dates (github.com/nodejs/Release schedule.json)
89
+ var NODE_EOL = { 14: '2023-04-30', 16: '2023-09-11', 18: '2025-04-30', 20: '2026-04-30', 22: '2027-04-30', 24: '2028-04-30', 26: '2029-04-30' };
90
+ var SPDX = ('0BSD AFL-3.0 AGPL-3.0-only AGPL-3.0-or-later Apache-1.1 Apache-2.0 Artistic-2.0 BlueOak-1.0.0 BSD-2-Clause BSD-3-Clause ' +
91
+ 'BSD-3-Clause-Clear BSL-1.0 CC-BY-4.0 CC-BY-SA-4.0 CC0-1.0 CDDL-1.0 CECILL-2.1 EPL-1.0 EPL-2.0 EUPL-1.2 GPL-2.0-only GPL-2.0-or-later ' +
92
+ 'GPL-3.0-only GPL-3.0-or-later ISC LGPL-2.1-only LGPL-2.1-or-later LGPL-3.0-only LGPL-3.0-or-later MIT MIT-0 MPL-2.0 MS-PL MS-RL ' +
93
+ 'NCSA OFL-1.1 PostgreSQL Python-2.0 Unicode-3.0 Unlicense UPL-1.0 W3C WTFPL Zlib ' +
94
+ 'Classpath-exception-2.0 LLVM-exception GCC-exception-3.1').split(' ');
95
+ var SPDX_DEPRECATED = { 'GPL-2.0': 'GPL-2.0-only', 'GPL-2.0+': 'GPL-2.0-or-later', 'GPL-3.0': 'GPL-3.0-only', 'GPL-3.0+': 'GPL-3.0-or-later',
96
+ 'LGPL-2.1': 'LGPL-2.1-only', 'LGPL-2.1+': 'LGPL-2.1-or-later', 'LGPL-3.0': 'LGPL-3.0-only', 'LGPL-3.0+': 'LGPL-3.0-or-later',
97
+ 'AGPL-3.0': 'AGPL-3.0-only', 'AGPL-1.0': 'AGPL-1.0-only' };
98
+ var LOWER = {};
99
+ SPDX.forEach(function (s) { LOWER[s.toLowerCase()] = s; });
100
+
101
+ function parseDay(s) {
102
+ var m = String(s || '').match(/(\d{4})-(\d{2})-(\d{2})/);
103
+ return m ? Date.UTC(+m[1], +m[2] - 1, +m[3]) : null;
104
+ }
105
+ function days(a, b) { return Math.round((b - a) / 86400000); }
106
+
107
+ // Lowest major a semver range admits (">=18", "^20.11", "18 || 20", "20.x", "*").
108
+ function floorMajor(range) {
109
+ var s = String(range).trim();
110
+ if (s === '' || s === '*' || s === 'x' || s === 'latest') return 0;
111
+ var low = null;
112
+ s.split('||').forEach(function (part) {
113
+ var m = part.match(/(?:^|[\s>=^~v])(\d+)(?:\.[\dx*]+)*/) || part.match(/(\d+)/);
114
+ if (/^\s*<\s*=?/.test(part) && !/>/.test(part)) m = ['', '0'];
115
+ if (m) { var n = +m[1]; if (low === null || n < low) low = n; }
116
+ });
117
+ return low === null ? 0 : low;
118
+ }
119
+ function eolLines(floor, now) {
120
+ var out = [];
121
+ Object.keys(NODE_EOL).forEach(function (k) {
122
+ var d = parseDay(NODE_EOL[k]);
123
+ if (+k >= floor && now !== null && d <= now) out.push('Node ' + k + ' (EOL ' + NODE_EOL[k] + ')');
124
+ });
125
+ if (floor < 14 && now !== null) out.unshift('Node ' + floor + ' and older');
126
+ return out;
127
+ }
128
+
129
+ function spdxCheck(expr) {
130
+ var toks = String(expr).replace(/[()]/g, ' ').trim().split(/\s+/);
131
+ if (!toks[0]) return { bad: 'empty' };
132
+ for (var i = 0; i < toks.length; i++) {
133
+ var t = toks[i];
134
+ if (/^(AND|OR|WITH)$/.test(t)) continue;
135
+ if (/^(and|or|with)$/i.test(t)) return { bad: t };
136
+ if (SPDX_DEPRECATED[t]) return { deprecated: t, to: SPDX_DEPRECATED[t] };
137
+ if (/^LicenseRef-[A-Za-z0-9.-]+$/.test(t)) continue;
138
+ if (SPDX.indexOf(t) >= 0 || SPDX.indexOf(t.replace(/\+$/, '')) >= 0) continue;
139
+ return { bad: t, hint: LOWER[t.toLowerCase()] };
140
+ }
141
+ return {};
142
+ }
143
+
144
+ // Line of a (possibly nested) key, found in order on the original text.
145
+ function lineOf(lines, path) {
146
+ var keys = path.split('.'), at = 0;
147
+ if (path.indexOf('@types/node') >= 0) keys = ['devDependencies', '@types/node'];
148
+ for (var k = 0; k < keys.length; k++) {
149
+ var re = new RegExp('"' + keys[k].replace(/[.*+?^${}()|[\]\\\/]/g, '\\$&') + '"\\s*:');
150
+ var found = -1;
151
+ for (var i = at; i < lines.length; i++) if (re.test(lines[i])) { found = i; break; }
152
+ if (found < 0) return at + 1;
153
+ at = found;
154
+ }
155
+ return at + 1;
156
+ }
157
+ function repoKey(u) {
158
+ var m = String(u || '').match(/(?:github\.com|gitlab\.com|bitbucket\.org)[\/:]([^\/#?\s]+)\/([^\/#?\s]+)/i);
159
+ if (!m) { m = String(u || '').match(/^(?:github:|gitlab:|bitbucket:)?([\w.-]+)\/([\w.-]+)$/); }
160
+ return m ? (m[1] + '/' + m[2].replace(/\.git$/, '')) : null;
161
+ }
162
+
163
+ function check(text, opts) {
164
+ opts = opts || {};
165
+ var findings = [], lines = String(text || '').split(/\r?\n/);
166
+ var now = parseDay(opts.today);
167
+ var pkg;
168
+ try { pkg = JSON.parse(String(text || '').replace(/^/, '')); } catch (e) {
169
+ return { findings: [{ check: 'json-parse', sev: 'error', msg: 'package.json is not valid JSON: ' + e.message + ' — npm publish stops here.', line: 1 }] };
170
+ }
171
+ if (!pkg || typeof pkg !== 'object') return { findings: [] };
172
+ var eolTail = '';
173
+ if (now !== null) {
174
+ var d20 = days(parseDay(NODE_EOL[20]), now);
175
+ eolTail = d20 >= 0 ? ' As of ' + opts.today + ', Node 20 has been end-of-life for ' + d20 + ' days.'
176
+ : ' As of ' + opts.today + ', Node 20 reaches end of life in ' + (-d20) + ' days (2026-04-30).';
177
+ }
178
+ function add(id, field, extra) {
179
+ var r = BY[id];
180
+ findings.push({ check: id, sev: r.sev, msg: r.msg + (extra || '') + ' Fix: ' + r.fix, line: lineOf(lines, field || r.field) });
181
+ }
182
+
183
+ // Node line pins: the migration map
184
+ var eng = pkg.engines && typeof pkg.engines === 'object' ? pkg.engines : null;
185
+ if (!eng || eng.node === undefined) add('engines-node-missing', 'name', eolTail);
186
+ else {
187
+ var fl = floorMajor(eng.node), dead = eolLines(fl, now);
188
+ if (dead.length) add('engines-node-eol-floor', 'engines.node', ' "' + eng.node + '" admits ' + dead.join(', ') + '.' + eolTail);
189
+ else if (now !== null && NODE_EOL[fl]) {
190
+ var left = days(now, parseDay(NODE_EOL[fl]));
191
+ if (left >= 0 && left <= 180) add('engines-node-floor-expiring', 'engines.node', ' Node ' + fl + ' ends ' + NODE_EOL[fl] + ', ' + left + ' days after ' + opts.today + '.');
192
+ }
193
+ }
194
+ if (pkg.volta && pkg.volta.node) {
195
+ var vm = floorMajor(pkg.volta.node), vd = eolLines(vm, now);
196
+ if (vd.length && +vm === +(String(pkg.volta.node).match(/\d+/) || [0])[0]) add('volta-node-eol', 'volta.node', ' Pinned "' + pkg.volta.node + '" = ' + vd[0] + '.');
197
+ }
198
+ var tn = (pkg.devDependencies || {})['@types/node'] || (pkg.dependencies || {})['@types/node'];
199
+ if (tn) {
200
+ var tm = floorMajor(tn);
201
+ if (tm >= 14 && eolLines(tm, now).length && NODE_EOL[tm] && parseDay(NODE_EOL[tm]) <= now) add('types-node-eol', '@types/node', ' "' + tn + '" = Node ' + tm + ' (EOL ' + NODE_EOL[tm] + ').');
202
+ }
203
+
204
+ // Licence
205
+ var prov = !!(pkg.publishConfig && (pkg.publishConfig.provenance === true || pkg.publishConfig.provenance === 'true'));
206
+ if (pkg.licenses || (pkg.license && typeof pkg.license === 'object')) add('license-object', pkg.licenses ? 'licenses' : 'license');
207
+ else if (pkg.license === undefined || pkg.license === '') add('license-missing', 'name');
208
+ else if (typeof pkg.license === 'string') {
209
+ var L = pkg.license.trim();
210
+ if (L === 'UNLICENSED') { if (pkg.private !== true) add('unlicensed-not-private', 'license'); }
211
+ else if (!/^SEE LICENSE IN /.test(L)) {
212
+ var sc = spdxCheck(L);
213
+ if (sc.deprecated) add('license-deprecated-id', 'license', ' "' + sc.deprecated + '" → "' + sc.to + '".');
214
+ else if (sc.bad) add('license-not-spdx', 'license', ' "' + L + '" is not valid' + (sc.hint ? ' (did you mean "' + sc.hint + '"?)' : '') + '.');
215
+ }
216
+ }
217
+
218
+ // Repository and provenance
219
+ var repo = pkg.repository, url = repo && typeof repo === 'object' ? repo.url : repo;
220
+ if (!repo) add(prov ? 'provenance-without-repository' : 'repository-missing', prov ? 'publishConfig' : 'name');
221
+ else {
222
+ if (typeof repo === 'string' && !/^(git\+)?(https?|ssh|git):\/\//.test(repo)) add('repository-shorthand', 'repository', ' "' + repo + '".');
223
+ else if (url && (/^http:\/\//.test(url) || /\/(tree|blob)\/|#readme/.test(url))) add('repository-html-url', 'repository.url', ' "' + url + '".');
224
+ var rk = repoKey(url), others = [pkg.homepage, pkg.bugs && (pkg.bugs.url || pkg.bugs)];
225
+ for (var o = 0; o < others.length; o++) {
226
+ var ok = repoKey(others[o]);
227
+ if (rk && ok && rk !== ok && rk.toLowerCase() === ok.toLowerCase()) {
228
+ add('repository-case-mismatch', typeof repo === 'object' ? 'repository.url' : 'repository', ' "' + rk + '" vs "' + ok + '".');
229
+ break;
230
+ }
231
+ }
232
+ }
233
+ if (prov && typeof pkg.packageManager === 'string') {
234
+ var pm = pkg.packageManager.match(/^npm@(\d+)\.(\d+)/);
235
+ if (pm && (+pm[1] < 9 || (+pm[1] === 9 && +pm[2] < 5))) add('package-manager-no-provenance', 'packageManager', ' "' + pkg.packageManager + '".');
236
+ }
237
+ if (prov && /^@/.test(pkg.name || '') && (pkg.publishConfig.access !== 'public')) add('scoped-provenance-no-access', 'publishConfig', ' "' + pkg.name + '" is scoped.');
238
+
239
+ findings.sort(function (a, b) { return a.line - b.line; });
240
+ return { findings: findings };
241
+ }
242
+
243
+ var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, NODE_EOL: NODE_EOL };
244
+ if (typeof module !== 'undefined' && module.exports) module.exports = API;
245
+ if (typeof window !== 'undefined') window.PKGENGINE = API;
246
+ })();
247
+ </script>
248
+ <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
249
+ 'use strict';
250
+ function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
251
+ function summaryLine(res) {
252
+ const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
253
+ return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
254
+ }
255
+ function toText(res, label) {
256
+ const f = (res && res.findings) || [];
257
+ const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
258
+ 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 || '')); });
259
+ return lines.join('\n');
260
+ }
261
+ function toHtml(res) {
262
+ const f = (res && res.findings) || [];
263
+ if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
264
+ 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('');
265
+ 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>';
266
+ }
267
+ var PKGREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
268
+ if (typeof module !== 'undefined' && module.exports) module.exports = PKGREPORT;
269
+ else window.PKGREPORT = PKGREPORT;
270
+ </script>
271
+ <script>
272
+ (function(){
273
+ var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
274
+ today.value=new Date().toISOString().slice(0,10);
275
+ function run(){ try { var res=window.PKGENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.PKGREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
276
+ go.onclick=run; if (src.value.trim()) run();
277
+ })();
278
+ </script>
279
+
280
+
281
+ <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/package-json-release-gate" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'package-json-release-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>
282
+ <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 npm publish package.json gate: engines &amp; SPDX 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:"package-json-release-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>
283
+
284
+ <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">A dated release report (Markdown) of every finding and its fix line, saved for the release ticket or audit file.</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><a href="https://getreadystack.com/api/buy/cl/polar_cl_EsjcBBVH2mQznQpVViIdCYtIofa1raKOgXsIw2k5dWg" 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>
285
+ <!--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>
286
+ <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>
287
+
288
+ <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-10-01">Updated 2026-10-01</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-10-01","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/sku434058_result_card.jpg" alt="npm publish package.json gate: engines &amp; SPDX" loading="lazy" style="max-width:100%;border-radius:6px"></p>
289
+ <p>npm publish package.json: 6 findings on one library manifest, checked on 2026-09-30 — this is for npm package maintainers who publish from GitHub Actions with --provenance and still carry an engines field written in the Node 18 era.</p>
290
+ <h3>The file</h3>
291
+ <p>The manifest is a small scoped library, @acme/tiny-queue 3.1.0. It builds with TypeScript and publishes with provenance turned on in publishConfig. Nothing about it looks broken.</p>
292
+ <h3>What the gate found</h3>
293
+ <p>Six lines, each with the replacement line next to it:</p>
294
+ <ol><li><code>&quot;license&quot;: &quot;Apache 2.0&quot;</code> is not an SPDX identifier. The fix is <code>&quot;license&quot;: &quot;Apache-2.0&quot;</code>.</li><li>homepage and bugs say <code>Acme/tiny-queue</code>, repository.url says <code>acme/tiny-queue</code>. npm documents that provenance needs a public repository that matches, case-sensitive, where you publish from. Pick one letter case and use it everywhere.</li><li><code>&quot;node&quot;: &quot;&gt;=18&quot;</code> admits Node 18 (end of life 2025-04-30) and Node 20 (end of life 2026-04-30). On 2026-09-30, Node 20 has been end-of-life for 153 days. The fix is <code>&quot;node&quot;: &quot;&gt;=22&quot;</code>.</li><li>volta pins <code>&quot;node&quot;: &quot;20.11.1&quot;</code>, so every contributor and CI job that uses Volta runs an end-of-life line. The fix is <code>&quot;node&quot;: &quot;24&quot;</code>.</li><li>The package is scoped and publishes with provenance, but publishConfig has no access field. npm documents <code>--access public</code> for a first provenance publish. The fix is <code>&quot;access&quot;: &quot;public&quot;</code>.</li><li><code>&quot;@types/node&quot;: &quot;^20.11.0&quot;</code> gives the compiler Node 20 types. The fix is <code>&quot;@types/node&quot;: &quot;^22&quot;</code>.</li></ol>
295
+ <h3>The Node line map</h3>
296
+ <p>The first three Node findings are one migration seen from three places: engines.node, volta.node and @types/node. A maintainer who moves only engines still ships a Volta pin and types for the old line. The gate walks all of them, plus packageManager when provenance is on (npm documents 9.5.0+ for provenance), so the whole move to Node 22 fits in one list.</p>
297
+ <h3>The date changes the answer</h3>
298
+ <p>The gate takes 2 inputs: the package.json text and today&#x27;s date. The same dirty file checked on 2026-03-01 gives 4 findings, not 6. Node 20 was still supported that day, so the volta pin and the @types/node line were fine, and the engines finding names only Node 18. The clean manifest in the bundle, with engines &quot;&gt;=22&quot;, gives 0 findings on 2026-09-30 and 1 warning on 2026-12-01, because Node 22 ends on 2027-04-30 and that is 150 days away.</p>
299
+ <h3>Why a chatbot answer is not enough</h3>
300
+ <p>A chatbot does not know what day it is, so it cannot tell you whether Node 20 is already end of life for your release. It guesses SPDX identifiers (&quot;Apache 2.0&quot; looks right to most readers) and never sees that two URLs in your manifest differ only in letter case. npm publish --dry-run does not judge an engines floor at all. The gate runs 17 rules on your own file, offline, with the Node release schedule dates written into the rules.</p>
301
+ <h3>The 17 rules</h3>
302
+ <p>Node line: engines.node missing, engines.node floor end-of-life, engines.node floor ending within 180 days, volta.node end-of-life, @types/node end-of-life, packageManager npm below 9.5.0 with provenance. Licence: missing, object or licenses array, not SPDX, deprecated SPDX id, UNLICENSED without private. Repository: missing, missing with provenance, shorthand, browser or http URL, letter-case mismatch. Access: scoped package with provenance and no public access.</p>
303
+ <h3>Try it on your own file</h3>
304
+ <p>Paste your package.json into the free web page or open it in VS Code and run the command. Every finding and every fix line is free, with no key. The optional full version saves a dated Markdown release report of the same findings for the release ticket.</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/sku434058_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku434058_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 the npm publish package.json gate check?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads one package.json and applies 17 rules with 2 inputs, the file text and today&#x27;s date. It flags engines.node floors that still admit end-of-life Node 18 or Node 20, volta and @types/node pins, licence values that are not SPDX, and repository or provenance blockers, and prints the exact replacement line for each finding.</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 the package.json release gate for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It is for npm package maintainers who publish from GitHub Actions with npm publish --provenance, and for library authors whose engines field was written when Node 18 or Node 20 was current. Node 20 reached end of life on 2026-04-30, so a range like &gt;=18 now admits two end-of-life lines.</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 ask a chatbot or run npm publish --dry-run?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A chatbot does not know today&#x27;s date, guesses SPDX identifiers, and cannot see letter case in your repository URL. npm publish --dry-run does not judge whether your engines floor is end of life. The gate runs on your own package.json offline, with the Node release schedule end-of-life dates written into its 17 rules.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what does the full version add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free, with no key: every finding and every fix line, in VS Code and on the web page. On the bundled fixture that is 6 findings from 17 rules and 2 inputs. The full version, one payment and one licence key per person or team seat, saves a dated Markdown release report of every finding for the release ticket or audit file.</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 fixing a failed publish by hand?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The checks themselves are free. The cost it avoids is a release run that stops at the provenance step or ships an engines range admitting Node 20, which reached end of life on 2026-04-30. The full version is a one-time $29 licence for the dated report; there is no subscription.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does the npm publish package.json gate check?", "acceptedAnswer": {"@type": "Answer", "text": "It reads one package.json and applies 17 rules with 2 inputs, the file text and today's date. It flags engines.node floors that still admit end-of-life Node 18 or Node 20, volta and @types/node pins, licence values that are not SPDX, and repository or provenance blockers, and prints the exact replacement line for each finding."}}, {"@type": "Question", "name": "Who is the package.json release gate for?", "acceptedAnswer": {"@type": "Answer", "text": "It is for npm package maintainers who publish from GitHub Actions with npm publish --provenance, and for library authors whose engines field was written when Node 18 or Node 20 was current. Node 20 reached end of life on 2026-04-30, so a range like >=18 now admits two end-of-life lines."}}, {"@type": "Question", "name": "Why not ask a chatbot or run npm publish --dry-run?", "acceptedAnswer": {"@type": "Answer", "text": "A chatbot does not know today's date, guesses SPDX identifiers, and cannot see letter case in your repository URL. npm publish --dry-run does not judge whether your engines floor is end of life. The gate runs on your own package.json offline, with the Node release schedule end-of-life dates written into its 17 rules."}}, {"@type": "Question", "name": "What is free and what does the full version add?", "acceptedAnswer": {"@type": "Answer", "text": "Free, with no key: every finding and every fix line, in VS Code and on the web page. On the bundled fixture that is 6 findings from 17 rules and 2 inputs. The full version, one payment and one licence key per person or team seat, saves a dated Markdown release report of every finding for the release ticket or audit file."}}, {"@type": "Question", "name": "What does it cost compared with fixing a failed publish by hand?", "acceptedAnswer": {"@type": "Answer", "text": "The checks themselves are free. The cost it avoids is a release run that stops at the provenance step or ships an engines range admitting Node 20, which reached end of life on 2026-04-30. The full version is a one-time $29 licence for the dated report; there is no subscription."}}]}</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:434058,slug:"package-json-release-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/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/xcode-26-sdk-gate"><span class="yj-rel-t">Xcode 26 SDK Gate for GitHub Actions</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><a class="yj-rel-c" href="https://getreadystack.com/tools/base-image-eol-lint"><span class="yj-rel-t">Base Image EOL Lint - Dockerfile and CI end-of-life check</span><span class="yj-rel-p">$29</span></a><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/cinc-migration-check"><span class="yj-rel-t">Cinc Migration Check: Chef Infra licence gate lint</span><span class="yj-rel-p">$29</span></a></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>
305
  </html>