readystack-angular-removed-api-lint 1.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- readystack_angular_removed_api_lint/__init__.py +1 -0
- readystack_angular_removed_api_lint/__main__.py +12 -0
- readystack_angular_removed_api_lint/js/cli.js +161 -0
- readystack_angular_removed_api_lint/js/engine.js +106 -0
- readystack_angular_removed_api_lint/js/license.js +103 -0
- readystack_angular_removed_api_lint/js/package.json +41 -0
- readystack_angular_removed_api_lint/js/rules.json +311 -0
- readystack_angular_removed_api_lint/js/strings.json +10 -0
- readystack_angular_removed_api_lint-1.0.7.dist-info/METADATA +84 -0
- readystack_angular_removed_api_lint-1.0.7.dist-info/RECORD +14 -0
- readystack_angular_removed_api_lint-1.0.7.dist-info/WHEEL +5 -0
- readystack_angular_removed_api_lint-1.0.7.dist-info/entry_points.txt +2 -0
- readystack_angular_removed_api_lint-1.0.7.dist-info/licenses/LICENSE.txt +15 -0
- readystack_angular_removed_api_lint-1.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.0.7'
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import os, sys
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def main():
|
|
5
|
+
# Runs the same checker as the npm package on the Node.js runtime installed with this package (nodejs-wheel-binaries).
|
|
6
|
+
from nodejs_wheel import node
|
|
7
|
+
here = os.path.dirname(os.path.abspath(__file__))
|
|
8
|
+
return node([os.path.join(here, "js", "cli.js")] + sys.argv[1:])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
if __name__ == "__main__":
|
|
12
|
+
sys.exit(main())
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// cli.js — the same rules as the VS Code extension, run from a terminal or a container. Generated by adapters.py.
|
|
3
|
+
'use strict';
|
|
4
|
+
const fs = require('fs'), path = require('path');
|
|
5
|
+
const RULES = require('./rules.json');
|
|
6
|
+
const S = require('./strings.json');
|
|
7
|
+
const lic = require('./license.js');
|
|
8
|
+
const ENGINE = require('./engine.js'); // s144 — scaffold products: the same engine the VS Code extension runs
|
|
9
|
+
const RULE_LIST = Array.isArray(ENGINE.RULES) ? ENGINE.RULES : (Array.isArray(RULES) ? RULES : []);
|
|
10
|
+
const RULE_N = ENGINE.RULE_COUNT || RULE_LIST.length;
|
|
11
|
+
function scan(text, file) {
|
|
12
|
+
const r = ENGINE.engine.check(String(text), { today: new Date().toISOString().slice(0, 10), path: file || '' });
|
|
13
|
+
return (r && r.findings || []).map((f) => ({ line: parseInt(f.line, 10) || 1, msg: String(f.msg || f.message || f.check || ''), fix: f.fix || null, sev: (String(f.sev || 'error').toLowerCase().startsWith('err') ? 'error' : 'warn') }));
|
|
14
|
+
}
|
|
15
|
+
function isText(p) { try { const b = fs.readFileSync(p); if (b.length > 2 * 1024 * 1024) return false; const s = b.subarray(0, 4096); for (const x of s) if (x === 0) return false; return true; } catch (e) { return false; } }
|
|
16
|
+
function walk(dir, exts, out) {
|
|
17
|
+
let ents = []; try { ents = fs.readdirSync(dir, { withFileTypes: true }); } catch (e) { return out; }
|
|
18
|
+
for (const e of ents) {
|
|
19
|
+
if (['node_modules', '.git', '.hg', '.svn', '.venv', 'venv', '.tox', '.cache', '.next', '.nuxt', '.terraform', '__pycache__'].includes(e.name)) continue; // s158 — .github · .gitlab-ci.yml · .env · .well-known 은 본다 (옛 줄은 점으로 시작하는 것을 전부 건너뛰어 워크플로 검사기가 .github/workflows 를 못 봤다)
|
|
20
|
+
const p = path.join(dir, e.name);
|
|
21
|
+
if (e.isDirectory()) walk(p, exts, out);
|
|
22
|
+
else if ((!exts.length || exts.includes(path.extname(e.name).toLowerCase())) && isText(p)) out.push(p);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); }
|
|
27
|
+
function csvq(s) { return '"' + String(s == null ? '' : s).replace(/"/g, '""') + '"'; }
|
|
28
|
+
function render(rows, fmt) {
|
|
29
|
+
if (fmt === 'json') return JSON.stringify({ tool: S.name, rules: RULE_N, files: rows }, null, 1);
|
|
30
|
+
if (fmt === 'csv') { const o = ['file,line,severity,message,fix']; for (const r of rows) for (const h of r.hits) o.push([csvq(r.file), h.line, h.sev, csvq(h.msg), csvq(h.fix)].join(',')); return o.join('\n') + '\n'; }
|
|
31
|
+
if (fmt === 'html') {
|
|
32
|
+
const n = rows.reduce((a, r) => a + r.hits.length, 0);
|
|
33
|
+
let o = '<!doctype html><meta charset="utf-8"><title>' + esc(S.name) + ' report</title><style>body{font:14px system-ui;margin:24px}table{border-collapse:collapse}td,th{border:1px solid #ddd;padding:4px 8px;font-size:13px}code{font-family:ui-monospace,monospace}</style>';
|
|
34
|
+
o += '<h1>' + esc(S.name) + '</h1><p>' + rows.length + ' files · ' + n + ' findings · ' + RULE_N + ' rules</p><table><tr><th>file</th><th>line</th><th>sev</th><th>message</th><th>fix</th></tr>';
|
|
35
|
+
for (const r of rows) for (const h of r.hits) o += '<tr><td><code>' + esc(r.file) + '</code></td><td>' + h.line + '</td><td>' + esc(h.sev) + '</td><td>' + esc(h.msg) + '</td><td>' + esc(h.fix || '') + '</td></tr>';
|
|
36
|
+
return o + '</table>';
|
|
37
|
+
}
|
|
38
|
+
let o = ''; for (const r of rows) { o += r.file + '\n'; for (const h of r.hits) o += ' ' + String(h.line).padStart(5) + ' ' + h.sev.padEnd(5) + ' ' + h.msg + (h.fix ? '\n -> ' + h.fix : '') + '\n'; if (!r.hits.length) o += ' (no findings)\n'; }
|
|
39
|
+
return o;
|
|
40
|
+
}
|
|
41
|
+
function trialState() {
|
|
42
|
+
// s158 — ⚑7일 무료 없음: 새 체험은 ⛔열지 않는다 · 이미 시작된 체험(trial.json)만 끝까지 지킨다 (읽기 전용)
|
|
43
|
+
if (process.env.READYSTACK_NO_TRIAL) return { active: false };
|
|
44
|
+
try {
|
|
45
|
+
const p = path.join(path.dirname(lic.storePath()), S.bin + '.trial.json');
|
|
46
|
+
const t = JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
47
|
+
return { active: !!(t && t.until) && new Date(t.until + 'T23:59:59Z').getTime() > Date.now(), until: t && t.until };
|
|
48
|
+
} catch (e) { return { active: false }; }
|
|
49
|
+
}
|
|
50
|
+
// ★s158 2026-09-23 — the free run SPEAKS about the rest of the folder (vsix: auto.js · crx: welcome tab).
|
|
51
|
+
// The answer for the named files is complete and free; the next step is shown with the user's OWN numbers:
|
|
52
|
+
// "this folder has N more matching files - M issues in K of them" + the one command that sweeps them.
|
|
53
|
+
// Never blocks, never starts a trial (s158: no free trial - the free answer on the named files is the "try it"), human seat only, 400 files / 1.5 s cap.
|
|
54
|
+
function folderHint(done) {
|
|
55
|
+
if (!process.stderr.isTTY || process.env.CI || process.env.READYSTACK_NO_HINT || !done.length) return;
|
|
56
|
+
const base = path.dirname(path.resolve(done[0]));
|
|
57
|
+
const seen = new Set(done.map((f) => path.resolve(f)));
|
|
58
|
+
const all = walk(base, S.exts || [], []).filter((f) => !seen.has(path.resolve(f)));
|
|
59
|
+
if (!all.length) return;
|
|
60
|
+
const t0 = Date.now(); let issues = 0, hitFiles = 0, scanned = 0;
|
|
61
|
+
for (const f of all.slice(0, 400)) { if (Date.now() - t0 > 1500) break; let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { continue; } scanned++; const h = scan(t, f); if (h.length) { issues += h.length; hitFiles++; } }
|
|
62
|
+
const rel = path.relative(process.cwd(), base) || '.';
|
|
63
|
+
const tail = trialState().active ? 'free during your trial' : '$' + S.price + ' once';
|
|
64
|
+
const more = all.length + ' more matching file' + (all.length > 1 ? 's' : '');
|
|
65
|
+
const found = issues ? ' - ' + (scanned < all.length ? 'at least ' : '') + issues + ' issue' + (issues > 1 ? 's' : '') + ' in ' + hitFiles + ' of them' : ' - no issues found in them';
|
|
66
|
+
process.stderr.write('\n' + (rel === '.' ? 'This folder' : rel) + ' has ' + more + found + '.\n' + (issues ? 'Sweep them all: ' + S.bin + ' --dir ' + (/\s/.test(rel) ? JSON.stringify(rel) : rel) + ' (' + tail + ')\n' : ''));
|
|
67
|
+
}
|
|
68
|
+
let _usePinged = false;
|
|
69
|
+
function pingUse() {
|
|
70
|
+
try {
|
|
71
|
+
if (_usePinged) return; _usePinged = true;
|
|
72
|
+
if (process.env.DO_NOT_TRACK === '1' || process.env.READYSTACK_NO_TELEMETRY || process.env.CI) return;
|
|
73
|
+
if (!(process.stdout.isTTY || process.stdin.isTTY)) return; // human seat only (s152)
|
|
74
|
+
const https = require('https');
|
|
75
|
+
const body = JSON.stringify({ t: 'use', slug: S.bin, src: 'cli', why: 'free' });
|
|
76
|
+
const req = https.request({ hostname: 'getreadystack.com', path: '/api/ev', method: 'POST', timeout: 3000,
|
|
77
|
+
headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'user-agent': 'readystack-cli/' + S.bin } }, function (res) { res.resume(); });
|
|
78
|
+
req.on('timeout', function () { req.destroy(); }); req.on('error', function () {});
|
|
79
|
+
req.write(body); req.end();
|
|
80
|
+
} catch (e) {}
|
|
81
|
+
}
|
|
82
|
+
function mcpServe() {
|
|
83
|
+
process.env.READYSTACK_MCP = '1'; // s152 - 에이전트(Claude Code·Cursor)가 부른 세션은 사람 자리다 · 키 판 핑 src=mcp
|
|
84
|
+
// s144 — MCP server over stdio (newline-delimited JSON-RPC · no dependencies). Free: check_text · check_file. Licence (s158: no free trial): check_dir.
|
|
85
|
+
const rl = require('readline').createInterface({ input: process.stdin });
|
|
86
|
+
const send = (o) => process.stdout.write(JSON.stringify(o) + '\n');
|
|
87
|
+
const tools = [
|
|
88
|
+
{ name: 'check_text', description: S.name + ' - run all ' + RULE_N + ' checks on a text (free)', inputSchema: { type: 'object', properties: { text: { type: 'string', description: 'file contents' }, path: { type: 'string', description: 'optional file name for context' } }, required: ['text'] } },
|
|
89
|
+
{ name: 'check_file', description: S.name + ' - run all checks on one file by path (free)', inputSchema: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } },
|
|
90
|
+
{ name: 'check_dir', description: S.name + ' - sweep a folder and return every finding (licence; $' + S.price + ' once)', inputSchema: { type: 'object', properties: { dir: { type: 'string' }, ext: { type: 'string', description: 'optional extension filter, e.g. .html' } }, required: ['dir'] } }
|
|
91
|
+
];
|
|
92
|
+
const result = (id, rows) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: render(rows, 'text') }], structuredContent: { tool: S.name, rules: RULE_N, files: rows } } });
|
|
93
|
+
const fail = (id, text) => send({ jsonrpc: '2.0', id, result: { content: [{ type: 'text', text }], isError: true } });
|
|
94
|
+
rl.on('line', async (line) => {
|
|
95
|
+
let m; try { m = JSON.parse(line); } catch (e) { return; }
|
|
96
|
+
const id = m.id, method = m.method;
|
|
97
|
+
if (method === 'initialize') return send({ jsonrpc: '2.0', id, result: { protocolVersion: '2025-06-18', capabilities: { tools: {} }, serverInfo: { name: '@readystack/' + S.bin, version: '1.0.0' } } });
|
|
98
|
+
if (method === 'notifications/initialized' || method === 'ping') { if (id !== undefined) send({ jsonrpc: '2.0', id, result: {} }); return; }
|
|
99
|
+
if (method === 'tools/list') return send({ jsonrpc: '2.0', id, result: { tools } });
|
|
100
|
+
if (method === 'tools/call') {
|
|
101
|
+
const name = (m.params || {}).name, args = (m.params || {}).arguments || {};
|
|
102
|
+
try {
|
|
103
|
+
if (name === 'check_text') return result(id, [{ file: args.path || '(text)', hits: scan(String(args.text || ''), args.path || '') }]);
|
|
104
|
+
if (name === 'check_file') return result(id, [{ file: args.path, hits: scan(fs.readFileSync(args.path, 'utf8'), args.path) }]);
|
|
105
|
+
if (name === 'check_dir') {
|
|
106
|
+
const r = await lic.ensure();
|
|
107
|
+
if (!r.ok && !trialState().active) return fail(id, S.need_key + ' Get a licence ($' + S.price + ', once): ' + lic.BUY_URL);
|
|
108
|
+
const files = walk(args.dir, args.ext ? [args.ext] : (S.exts || []), []);
|
|
109
|
+
return result(id, files.map((f) => { let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { return { file: f, hits: [], error: String(e.message) }; } return { file: f, hits: scan(t, f) }; }));
|
|
110
|
+
}
|
|
111
|
+
return send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'unknown tool ' + name } });
|
|
112
|
+
} catch (e) { return fail(id, String(e && e.message || e)); }
|
|
113
|
+
}
|
|
114
|
+
if (id !== undefined) send({ jsonrpc: '2.0', id, error: { code: -32601, message: 'method not found: ' + method } });
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function help() {
|
|
118
|
+
return [S.name + ' - ' + S.subtitle, '', 'Usage: ' + S.bin + ' <file> [more files] check the files you name (free, every rule)',
|
|
119
|
+
' ' + S.bin + ' --dir <folder> [--ext .html] scan a whole folder (licence)',
|
|
120
|
+
' ' + S.bin + ' ... --report csv|json|html [--out file] export a report (licence)',
|
|
121
|
+
' ' + S.bin + ' ... --ci exit 1 when an error-level finding exists (licence)',
|
|
122
|
+
' ' + S.bin + ' --license <key> store your licence key (or set READYSTACK_LICENSE)',
|
|
123
|
+
' ' + S.bin + ' --rules list the ' + RULE_N + ' rules',
|
|
124
|
+
' ' + S.bin + ' --mcp run as an MCP server (stdio) for Claude Code / Cursor / Windsurf - free checks, folder sweep needs a licence', '',
|
|
125
|
+
'Free: ' + S.free, 'Licence ($' + S.price + ', once): ' + S.paid, 'Get a licence: ' + lic.BUY_URL, ''].join('\n');
|
|
126
|
+
}
|
|
127
|
+
(async function main() {
|
|
128
|
+
try { const _feed = await lic.pullFeed(); if (_feed && Array.isArray(_feed.rules)) { if (Array.isArray(ENGINE.RULES)) for (const r of _feed.rules) ENGINE.RULES.push(r); } } catch (e) {} // ★s134 구독 피드 병합 (키 있는 손님만)
|
|
129
|
+
const a = process.argv.slice(2);
|
|
130
|
+
const get = (k) => { const i = a.indexOf(k); return i >= 0 ? a[i + 1] : null; };
|
|
131
|
+
if (a.includes('--mcp')) { mcpServe(); return; }
|
|
132
|
+
if (!a.length || a.includes('--help') || a.includes('-h')) { process.stdout.write(help()); return; }
|
|
133
|
+
if (a.includes('--rules')) { process.stdout.write((RULE_LIST.length ? RULE_LIST.map((r, i) => String(i + 1).padStart(3) + ' [' + (r.sev || 'warn') + '] ' + (r.message || r.msg || r.id || '')) : Array.from({ length: RULE_N }, (_, i) => String(i + 1).padStart(3) + ' [engine] check ' + (i + 1))).join('\n') + '\n'); return; }
|
|
134
|
+
if (a.includes('--license')) { const r = await lic.ensure(get('--license')); process.stdout.write(r.ok ? 'Licence stored: ' + lic.storePath() + '\n' : 'Licence not accepted (' + r.why + '). Get one: ' + lic.BUY_URL + '\n'); process.exit(r.ok ? 0 : 2); }
|
|
135
|
+
const dir = get('--dir'), fmt = get('--report'), out = get('--out'), ci = a.includes('--ci');
|
|
136
|
+
const exts = a.includes('--ext') ? [get('--ext')] : (S.exts || []);
|
|
137
|
+
const paid = !!(dir || fmt || ci);
|
|
138
|
+
if (paid) {
|
|
139
|
+
const r = await lic.ensure();
|
|
140
|
+
if (!r.ok) {
|
|
141
|
+
const t = trialState(); // s158 — ⚑7일 무료 없음 · 이미 시작된 체험만 지킨다
|
|
142
|
+
if (t.active) process.stderr.write('Trial: the full run is free until ' + t.until + ' — after that $' + S.price + ' once. Get a licence: ' + lic.BUY_URL + '\n');
|
|
143
|
+
else {
|
|
144
|
+
// s158 — the key is asked WITH the customer's own count (endowment · open loop): how many issues this folder holds.
|
|
145
|
+
let own = '';
|
|
146
|
+
if (dir) { try { const all = walk(dir, exts, []); let n = 0, k = 0, sc = 0; const t0 = Date.now();
|
|
147
|
+
for (const f of all.slice(0, 2000)) { if (Date.now() - t0 > 4000) break; let tx = ''; try { tx = fs.readFileSync(f, 'utf8'); } catch (e) { continue; } sc++; const h = scan(tx, f); if (h.length) { n += h.length; k++; } }
|
|
148
|
+
if (n) own = dir + ': ' + (sc < all.length ? 'at least ' : '') + n + ' issue' + (n > 1 ? 's' : '') + ' in ' + k + ' of ' + all.length + ' files.\n'; } catch (e) {} }
|
|
149
|
+
process.stderr.write(own + S.need_key + '\n set READYSTACK_LICENSE=<key> or ' + S.bin + ' --license <key>\n Get a licence ($' + S.price + ' once): ' + lic.BUY_URL + '\n'); process.exit(2);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const files = dir ? walk(dir, exts, []) : a.filter((x, i) => !x.startsWith('--') && !['--dir', '--report', '--out', '--ext', '--license'].includes(a[i - 1]));
|
|
154
|
+
if (!files.length) { process.stderr.write('No files. ' + S.bin + ' --help\n'); process.exit(2); }
|
|
155
|
+
const rows = files.map((f) => { let t = ''; try { t = fs.readFileSync(f, 'utf8'); } catch (e) { return { file: f, hits: [], error: String(e.message) }; } return { file: f, hits: scan(t) }; });
|
|
156
|
+
const text = render(rows, fmt || 'text');
|
|
157
|
+
if (out) fs.writeFileSync(out, text); else process.stdout.write(text.endsWith('\n') ? text : text + '\n');
|
|
158
|
+
if (!paid) { try { folderHint(files); } catch (e) {} pingUse(); } // s158 — a free run ends with the user's own folder count + one anonymous 'used' count (human seat only)
|
|
159
|
+
const errors = rows.reduce((n, r) => n + r.hits.filter((h) => h.sev === 'error').length, 0);
|
|
160
|
+
if (ci && errors) process.exit(1);
|
|
161
|
+
})().catch((e) => { process.stderr.write(String(e && e.stack || e) + '\n'); process.exit(3); });
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// Angular Removed-API Lint — the whole brain. Same file runs in Node (the extension) and in the browser (the free web check).
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var DATA = (typeof module !== 'undefined' && module.exports) ? require('./rules.json') : window.ANG_RULES;
|
|
5
|
+
var RULES = (DATA && DATA.rules) || [];
|
|
6
|
+
var SUPPORT = (DATA && DATA.support && DATA.support.majors) || {};
|
|
7
|
+
var POLICY = (DATA && DATA.policy) || '';
|
|
8
|
+
|
|
9
|
+
var SEV = { removed: 'error', support: 'error', deprecated: 'warning', advisory: 'info' };
|
|
10
|
+
|
|
11
|
+
// Blank out // and /* */ comments so a commented-out import is not a finding.
|
|
12
|
+
// Line breaks are preserved so reported line numbers stay true to the original file.
|
|
13
|
+
function stripComments(text) {
|
|
14
|
+
var out = '', i = 0, n = text.length, ch, nx, q;
|
|
15
|
+
while (i < n) {
|
|
16
|
+
ch = text[i]; nx = text[i + 1];
|
|
17
|
+
if (ch === '/' && nx === '/') {
|
|
18
|
+
while (i < n && text[i] !== '\n') { out += ' '; i++; }
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (ch === '/' && nx === '*') {
|
|
22
|
+
out += ' '; i += 2;
|
|
23
|
+
while (i < n && !(text[i] === '*' && text[i + 1] === '/')) { out += (text[i] === '\n' ? '\n' : ' '); i++; }
|
|
24
|
+
if (i < n) { out += ' '; i += 2; }
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (ch === '"' || ch === "'" || ch === '`') {
|
|
28
|
+
q = ch; out += ch; i++;
|
|
29
|
+
while (i < n) {
|
|
30
|
+
if (text[i] === '\\') { out += text[i] + (text[i + 1] || ''); i += 2; continue; }
|
|
31
|
+
out += text[i];
|
|
32
|
+
if (text[i] === q) { i++; break; }
|
|
33
|
+
i++;
|
|
34
|
+
}
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
out += ch; i++;
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function toDay(s) {
|
|
43
|
+
var m = /^(\d{4})-(\d{2})-(\d{2})/.exec(String(s || ''));
|
|
44
|
+
if (!m) return null;
|
|
45
|
+
return Date.UTC(+m[1], +m[2] - 1, +m[3]) / 86400000;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function plural(n, word) { return n + ' ' + word + (n === 1 ? '' : 's'); }
|
|
49
|
+
|
|
50
|
+
// The one date-sensitive check: an Angular major pinned in package.json is measured
|
|
51
|
+
// against its own 18-month window, as of the day you run the check.
|
|
52
|
+
function supportFinding(rule, line, major, today) {
|
|
53
|
+
var info = SUPPORT[String(major)];
|
|
54
|
+
var head = 'Angular ' + major + ' is pinned here. ';
|
|
55
|
+
if (!info) {
|
|
56
|
+
if (major < 12) {
|
|
57
|
+
return { check: rule.id, sev: 'error', line: line,
|
|
58
|
+
msg: head + 'Every major below 12 fell out of Angular’s 18-month support window before 2022-11-12: no patches, no security fixes. ' + rule.fix };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
var end = toDay(info.lts_end), now = toDay(today);
|
|
63
|
+
if (now === null || end === null) return null;
|
|
64
|
+
var tail = 'Angular 18-month window: v' + major + ' shipped ' + info.released + ', support ended ' + info.lts_end + '. ';
|
|
65
|
+
if (now > end) {
|
|
66
|
+
return { check: rule.id, sev: 'error', line: line,
|
|
67
|
+
msg: head + tail + 'That was ' + plural(Math.round(now - end), 'day') + ' before ' + today + ' — no patches and no security fixes are being published for this major. ' + rule.fix };
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function check(text, opts) {
|
|
73
|
+
text = String(text == null ? '' : text);
|
|
74
|
+
opts = opts || {};
|
|
75
|
+
var today = opts.today || new Date().toISOString().slice(0, 10);
|
|
76
|
+
var lines = stripComments(text).split(/\r?\n/);
|
|
77
|
+
var findings = [];
|
|
78
|
+
|
|
79
|
+
for (var r = 0; r < RULES.length; r++) {
|
|
80
|
+
var rule = RULES[r];
|
|
81
|
+
if (!rule.re) continue;
|
|
82
|
+
for (var i = 0; i < lines.length; i++) {
|
|
83
|
+
var re = new RegExp(rule.re, rule.flags || '');
|
|
84
|
+
var m = re.exec(lines[i]);
|
|
85
|
+
if (!m) continue;
|
|
86
|
+
if (rule.kind === 'support') {
|
|
87
|
+
var f = supportFinding(rule, i + 1, parseInt(m[1], 10), today);
|
|
88
|
+
if (f) findings.push(f);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
findings.push({
|
|
92
|
+
check: rule.id,
|
|
93
|
+
sev: SEV[rule.kind] || 'warning',
|
|
94
|
+
line: i + 1,
|
|
95
|
+
msg: rule.msg + ' ' + rule.fix
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
findings.sort(function (a, b) { return (a.line - b.line) || (a.check < b.check ? -1 : 1); });
|
|
101
|
+
return { findings: findings, today: today, rule_count: RULES.length, policy: POLICY };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
var ANGENGINE = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, stripComments: stripComments };
|
|
105
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = ANGENGINE;
|
|
106
|
+
else window.ANGENGINE = ANGENGINE;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// license.js — Polar licence check for the CLI / container. Generated by adapters.py; do not edit by hand.
|
|
2
|
+
'use strict';
|
|
3
|
+
const https = require('https'), fs = require('fs'), path = require('path'), os = require('os');
|
|
4
|
+
const ORG_ID = 'a5cdf664-d8e7-4f87-8895-056717aaba17';
|
|
5
|
+
const BENEFIT_ID = '33b198e6-828b-4be2-8384-2a49ebc92c6d'; // s142 — this product's Polar benefit (s140 law: ask with benefit_id, or one key opens every product)
|
|
6
|
+
const BUY_URL = 'https://getreadystack.com/api/buy/cl/polar_cl_2wSKxziqGHTfu7JassC9u93PIE4YL8ThOikL64MVZVc';
|
|
7
|
+
const SLUG = 'angular-removed-api-lint';
|
|
8
|
+
const GRACE_MS = 30 * 24 * 3600 * 1000; // after a successful check, 30 days work offline
|
|
9
|
+
const RECHECK_MS = 7 * 24 * 3600 * 1000; // re-ask Polar every 7 days (refunds / cancellations)
|
|
10
|
+
function storePath() { return path.join(process.env.READYSTACK_HOME || path.join(os.homedir(), '.config', 'readystack'), SLUG + '.json'); }
|
|
11
|
+
function load() { try { return JSON.parse(fs.readFileSync(storePath(), 'utf8')); } catch (e) { return {}; } }
|
|
12
|
+
function save(o) { try { fs.mkdirSync(path.dirname(storePath()), { recursive: true }); fs.writeFileSync(storePath(), JSON.stringify(o)); } catch (e) { /* read-only home: still works for this run */ } }
|
|
13
|
+
const ALL_BENEFIT_ID = '22692551-5203-4467-b1a3-e33cdba6589d'; // s149 2026-09-17 — 팀 키(전 린터 한 키 · Polar benefit) · 상품 benefit 다음에 한 번 더 묻는다
|
|
14
|
+
// s170 2026-09-29 — ask OUR worker first (/api/lic): keys bought on Whop open here (a price-tier key binds to the first tool it opens · the team key opens every tool). Then the old Polar check.
|
|
15
|
+
function validate(key) {
|
|
16
|
+
return validateHub(key).then(function (h) {
|
|
17
|
+
if (h.ok) return h;
|
|
18
|
+
return validate1(key, BENEFIT_ID).then(function (r) { return (r.ok || r.offline || !/^[0-9a-f-]{36}$/.test(ALL_BENEFIT_ID)) ? r : validate1(key, ALL_BENEFIT_ID); })
|
|
19
|
+
.then(function (r) { return r.ok ? r : { ok: false, offline: !!(h.offline || r.offline) }; });
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function validateHub(key) {
|
|
23
|
+
return new Promise(function (resolve) {
|
|
24
|
+
const req = https.request({ hostname: 'getreadystack.com', path: '/api/lic?key=' + encodeURIComponent(key) + '&slug=' + encodeURIComponent(SLUG),
|
|
25
|
+
method: 'GET', timeout: 8000, headers: { 'accept': 'application/json', 'user-agent': 'readystack-cli/' + SLUG } }, function (res) {
|
|
26
|
+
let buf = ''; res.on('data', function (d) { buf += d; });
|
|
27
|
+
res.on('end', function () {
|
|
28
|
+
if (res.statusCode !== 200) return resolve({ ok: false, offline: res.statusCode >= 500 });
|
|
29
|
+
try { const j = JSON.parse(buf); resolve({ ok: !!(j && j.ok), offline: false }); } catch (e) { resolve({ ok: false, offline: false }); }
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
req.on('timeout', function () { req.destroy(); resolve({ ok: false, offline: true }); });
|
|
33
|
+
req.on('error', function () { resolve({ ok: false, offline: true }); });
|
|
34
|
+
req.end();
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function validate1(key, ben) {
|
|
38
|
+
return new Promise(function (resolve) {
|
|
39
|
+
if (!ORG_ID) return resolve({ ok: false, offline: false });
|
|
40
|
+
const body = JSON.stringify(/^[0-9a-f-]{36}$/.test(ben) ? { key: key, organization_id: ORG_ID, benefit_id: ben } : { key: key, organization_id: ORG_ID });
|
|
41
|
+
const req = https.request({ hostname: 'api.polar.sh', path: '/v1/customer-portal/license-keys/validate', method: 'POST', timeout: 8000,
|
|
42
|
+
headers: { 'content-type': 'application/json', 'polar-version': '2026-04', 'content-length': Buffer.byteLength(body) } }, function (res) {
|
|
43
|
+
let buf = ''; res.on('data', function (d) { buf += d; });
|
|
44
|
+
res.on('end', function () {
|
|
45
|
+
if (res.statusCode !== 200) return resolve({ ok: false, offline: false });
|
|
46
|
+
try { const j = JSON.parse(buf); resolve({ ok: j && (j.status === 'granted' || j.valid === true || !!j.id), offline: false }); }
|
|
47
|
+
catch (e) { resolve({ ok: false, offline: false }); }
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
req.on('timeout', function () { req.destroy(); resolve({ ok: false, offline: true }); });
|
|
51
|
+
req.on('error', function () { resolve({ ok: false, offline: true }); });
|
|
52
|
+
req.write(body); req.end();
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// ★s151 2026-09-19 — 키 판(키가 없거나 거절된 순간)을 익명으로 센다 (슬러그·출처·이유만) · DO_NOT_TRACK=1 · READYSTACK_NO_TELEMETRY 면 안 보낸다 · 실패는 조용히 · 프로세스당 한 번.
|
|
56
|
+
let _pinged = false;
|
|
57
|
+
function pingPaywall(why) {
|
|
58
|
+
try {
|
|
59
|
+
if (_pinged) return; _pinged = true;
|
|
60
|
+
if (process.env.DO_NOT_TRACK === '1' || process.env.READYSTACK_NO_TELEMETRY || process.env.CI) return; // s151: CI(깃허브 액션 등)와 우리 빌드는 손님이 아니다
|
|
61
|
+
if (!(process.stdout.isTTY || process.stdin.isTTY || process.env.READYSTACK_MCP === '1')) return; // s152: 사람 자리(터미널·MCP 세션)에서만 센다 - 발행 1분 뒤 남의 실행기(JP · 우리 3대는 US)가 돌린 no_key 13건은 손님이 아니다
|
|
62
|
+
const body = JSON.stringify({ t: 'paywall', slug: SLUG, src: process.env.READYSTACK_MCP === '1' ? 'mcp' : 'cli', why: why || 'no_key' });
|
|
63
|
+
const req = https.request({ hostname: 'getreadystack.com', path: '/api/ev', method: 'POST', timeout: 3000,
|
|
64
|
+
headers: { 'content-type': 'application/json', 'content-length': Buffer.byteLength(body), 'user-agent': 'readystack-cli/' + SLUG } }, function (res) { res.resume(); });
|
|
65
|
+
req.on('timeout', function () { req.destroy(); }); req.on('error', function () {});
|
|
66
|
+
req.write(body); req.end();
|
|
67
|
+
} catch (e) { /* 세는 것이 실패해도 상품은 돈다 */ }
|
|
68
|
+
}
|
|
69
|
+
async function ensure(explicitKey) {
|
|
70
|
+
const st = load();
|
|
71
|
+
const key = explicitKey || process.env.READYSTACK_LICENSE || st.key;
|
|
72
|
+
if (!key) { pingPaywall('no_key'); return { ok: false, why: 'no_key' }; } // s151
|
|
73
|
+
const age = Date.now() - (st.okAt || 0);
|
|
74
|
+
if (!explicitKey && st.key === key && age < RECHECK_MS) return { ok: true, cached: true };
|
|
75
|
+
const r = await validate(String(key).trim());
|
|
76
|
+
if (r.ok) { save({ key: String(key).trim(), okAt: Date.now() }); return { ok: true }; }
|
|
77
|
+
if (r.offline && st.key === key && age < GRACE_MS) return { ok: true, offline: true };
|
|
78
|
+
if (!r.offline) pingPaywall('invalid'); // s151
|
|
79
|
+
return { ok: false, why: r.offline ? 'offline' : 'invalid' };
|
|
80
|
+
}
|
|
81
|
+
// ★s134 — 구독 규칙 피드(층3 "바뀌면 업데이트"): 키 있는 손님만 · 7일마다 · 오프라인은 캐시. 워커 GET /api/rules/<slug>?key=
|
|
82
|
+
const FEED_URL = 'https://getreadystack.com/api/rules/';
|
|
83
|
+
function pullFeed() {
|
|
84
|
+
const st = load(); const key = process.env.READYSTACK_LICENSE || st.key; const cached = st.feed || null;
|
|
85
|
+
if (!key) return Promise.resolve(cached);
|
|
86
|
+
if (cached && (Date.now() - (st.feedAt || 0)) < RECHECK_MS) return Promise.resolve(cached);
|
|
87
|
+
return new Promise(function (resolve) {
|
|
88
|
+
let req;
|
|
89
|
+
try {
|
|
90
|
+
req = https.get(FEED_URL + encodeURIComponent(SLUG) + '?key=' + encodeURIComponent(key), { timeout: 8000, headers: { 'user-agent': 'readystack-cli' } }, function (res) {
|
|
91
|
+
let buf = ''; res.on('data', function (d) { buf += d; });
|
|
92
|
+
res.on('end', function () {
|
|
93
|
+
if (res.statusCode !== 200) return resolve(cached);
|
|
94
|
+
try { const j = JSON.parse(buf); if (!j || !Array.isArray(j.rules)) return resolve(cached); save(Object.assign(load(), { feed: j, feedAt: Date.now() })); resolve(j); }
|
|
95
|
+
catch (e) { resolve(cached); }
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
} catch (e) { return resolve(cached); }
|
|
99
|
+
req.on('timeout', function () { req.destroy(); resolve(cached); });
|
|
100
|
+
req.on('error', function () { resolve(cached); });
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
module.exports = { ensure, BUY_URL, storePath, pullFeed };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@readystack/angular-removed-api-lint",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "32 checks for Angular APIs that were removed or deprecated, plus the support window on your version pin. The bundled legacy fixture — one NgModule file and its spec — returns 34 findings, 9 of them er",
|
|
5
|
+
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"angular",
|
|
11
|
+
"deprecated",
|
|
12
|
+
"migration",
|
|
13
|
+
"upgrade",
|
|
14
|
+
"lint",
|
|
15
|
+
"typescript",
|
|
16
|
+
"ai-generated"
|
|
17
|
+
],
|
|
18
|
+
"homepage": "https://getreadystack.com",
|
|
19
|
+
"funding": "https://getreadystack.com/api/buy/cl/polar_cl_2wSKxziqGHTfu7JassC9u93PIE4YL8ThOikL64MVZVc",
|
|
20
|
+
"bin": {
|
|
21
|
+
"angular-removed-api-lint": "cli.js"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18"
|
|
25
|
+
},
|
|
26
|
+
"mcpName": "io.github.jmshinhwa/angular-removed-api-lint",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/jmshinhwa/readystack-themes.git",
|
|
30
|
+
"directory": "angular-removed-api-lint"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"cli.js",
|
|
34
|
+
"license.js",
|
|
35
|
+
"rules.json",
|
|
36
|
+
"strings.json",
|
|
37
|
+
"README.md",
|
|
38
|
+
"LICENSE.txt",
|
|
39
|
+
"engine.js"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "angular-removed-api-lint",
|
|
3
|
+
"name": "Angular Removed-API Lint rule set",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"updated": "2026-09-11",
|
|
6
|
+
"levels": {
|
|
7
|
+
"error": "Removed from Angular — does not compile on a current major.",
|
|
8
|
+
"warning": "Deprecated — still works, scheduled to stop working.",
|
|
9
|
+
"info": "Supported, but the NgModule-era form the current schematics no longer emit."
|
|
10
|
+
},
|
|
11
|
+
"policy": "Angular supports each major release for 18 months: 6 months active support, then 12 months long-term support (LTS). Dates below are the release date and release + 18 months.",
|
|
12
|
+
"support": {
|
|
13
|
+
"majors": {
|
|
14
|
+
"12": {
|
|
15
|
+
"released": "2021-05-12",
|
|
16
|
+
"lts_end": "2022-11-12"
|
|
17
|
+
},
|
|
18
|
+
"13": {
|
|
19
|
+
"released": "2021-11-03",
|
|
20
|
+
"lts_end": "2023-05-04"
|
|
21
|
+
},
|
|
22
|
+
"14": {
|
|
23
|
+
"released": "2022-06-02",
|
|
24
|
+
"lts_end": "2023-11-18"
|
|
25
|
+
},
|
|
26
|
+
"15": {
|
|
27
|
+
"released": "2022-11-16",
|
|
28
|
+
"lts_end": "2024-05-18"
|
|
29
|
+
},
|
|
30
|
+
"16": {
|
|
31
|
+
"released": "2023-05-03",
|
|
32
|
+
"lts_end": "2024-11-08"
|
|
33
|
+
},
|
|
34
|
+
"17": {
|
|
35
|
+
"released": "2023-11-08",
|
|
36
|
+
"lts_end": "2025-05-15"
|
|
37
|
+
},
|
|
38
|
+
"18": {
|
|
39
|
+
"released": "2024-05-22",
|
|
40
|
+
"lts_end": "2025-11-19"
|
|
41
|
+
},
|
|
42
|
+
"19": {
|
|
43
|
+
"released": "2024-11-19",
|
|
44
|
+
"lts_end": "2026-05-19"
|
|
45
|
+
},
|
|
46
|
+
"20": {
|
|
47
|
+
"released": "2025-05-28",
|
|
48
|
+
"lts_end": "2026-11-21"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"rules": [
|
|
53
|
+
{
|
|
54
|
+
"id": "angular-major-out-of-support",
|
|
55
|
+
"kind": "support",
|
|
56
|
+
"since": 12,
|
|
57
|
+
"re": "[\"']@angular/core[\"']\\s*:\\s*[\"'][~^>= ]*([0-9]+)\\.",
|
|
58
|
+
"msg": "Angular version pin found.",
|
|
59
|
+
"fix": "Upgrade one major at a time with `ng update @angular/core@N @angular/cli@N`."
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "angular-http-package",
|
|
63
|
+
"kind": "removed",
|
|
64
|
+
"since": 8,
|
|
65
|
+
"re": "from\\s*['\"]@angular/http['\"]",
|
|
66
|
+
"flags": "",
|
|
67
|
+
"msg": "The `@angular/http` package was removed in Angular 8.",
|
|
68
|
+
"fix": "Import `HttpClient` from `@angular/common/http` and register it with `provideHttpClient()`."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"id": "renderer-v1",
|
|
72
|
+
"kind": "removed",
|
|
73
|
+
"since": 9,
|
|
74
|
+
"re": "import\\s*\\{[^}]*\\bRenderer\\b[^}]*\\}\\s*from\\s*['\"]@angular/core['\"]",
|
|
75
|
+
"msg": "`Renderer` (the v1 renderer) was removed in Angular 9.",
|
|
76
|
+
"fix": "Inject `Renderer2` instead; `setProperty`/`setAttribute`/`listen` replace the old methods."
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"id": "private-ivy-import",
|
|
80
|
+
"kind": "advisory",
|
|
81
|
+
"since": 9,
|
|
82
|
+
"re": "import\\s*\\{[^}]*\\u0275[^}]*\\}",
|
|
83
|
+
"msg": "This imports a private Angular symbol (the `ɵ` prefix marks compiler internals).",
|
|
84
|
+
"fix": "Private symbols carry no compatibility guarantee and change without notice — use the public API or vendor the behaviour."
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "module-with-providers-no-generic",
|
|
88
|
+
"kind": "removed",
|
|
89
|
+
"since": 10,
|
|
90
|
+
"re": "ModuleWithProviders(?!\\s*<)",
|
|
91
|
+
"msg": "`ModuleWithProviders` without a type argument stopped compiling in Angular 10.",
|
|
92
|
+
"fix": "Write `ModuleWithProviders<YourModule>`."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"id": "waitforasync-rename",
|
|
96
|
+
"kind": "removed",
|
|
97
|
+
"since": 12,
|
|
98
|
+
"re": "import\\s*\\{[^}]*\\basync\\b[^}]*\\}\\s*from\\s*['\"]@angular/core/testing['\"]",
|
|
99
|
+
"msg": "The `async()` test helper was removed in Angular 12 (it collided with the `async` keyword).",
|
|
100
|
+
"fix": "Import and call `waitForAsync()` instead — same behaviour, new name."
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "entry-components",
|
|
104
|
+
"kind": "removed",
|
|
105
|
+
"since": 13,
|
|
106
|
+
"re": "\\bentryComponents\\s*:",
|
|
107
|
+
"msg": "`entryComponents` was removed in Angular 13.",
|
|
108
|
+
"fix": "Delete the property — Ivy resolves dynamically created components without it."
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"id": "testbed-get",
|
|
112
|
+
"kind": "removed",
|
|
113
|
+
"since": 13,
|
|
114
|
+
"re": "TestBed\\s*\\.\\s*get\\s*\\(",
|
|
115
|
+
"msg": "`TestBed.get()` was removed in Angular 13 (deprecated in 9).",
|
|
116
|
+
"fix": "Use `TestBed.inject(Token)` — it is typed, so the cast you probably wrote can go too."
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"id": "inject-flags",
|
|
120
|
+
"kind": "deprecated",
|
|
121
|
+
"since": 14,
|
|
122
|
+
"re": "\\bInjectFlags\\b",
|
|
123
|
+
"msg": "`InjectFlags` has been deprecated since Angular 14.",
|
|
124
|
+
"fix": "Pass an options object instead: `inject(TOKEN, { optional: true, skipSelf: true })`."
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"id": "class-based-route-guard",
|
|
128
|
+
"kind": "deprecated",
|
|
129
|
+
"since": 15,
|
|
130
|
+
"re": "implements\\s+(CanActivate|CanActivateChild|CanDeactivate|CanLoad|Resolve)\\b",
|
|
131
|
+
"msg": "Class-based router guards and resolvers have been deprecated since Angular 15.2.",
|
|
132
|
+
"fix": "Export a function instead: `export const canActivateFoo: CanActivateFn = () => inject(Auth).ok();`."
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"id": "can-load-guard",
|
|
136
|
+
"kind": "deprecated",
|
|
137
|
+
"since": 15,
|
|
138
|
+
"re": "\\bcanLoad\\s*:",
|
|
139
|
+
"msg": "The `canLoad` route property has been deprecated since Angular 15.1.",
|
|
140
|
+
"fix": "Use `canMatch` — it also runs for already-loaded routes, which is what most `canLoad` guards meant."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "router-link-with-href",
|
|
144
|
+
"kind": "deprecated",
|
|
145
|
+
"since": 15,
|
|
146
|
+
"re": "\\bRouterLinkWithHref\\b",
|
|
147
|
+
"msg": "`RouterLinkWithHref` was folded into `RouterLink` in Angular 15.",
|
|
148
|
+
"fix": "Import `RouterLink`; it handles anchors and non-anchor elements alike."
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"id": "flex-layout",
|
|
152
|
+
"kind": "deprecated",
|
|
153
|
+
"since": 16,
|
|
154
|
+
"re": "@angular/flex-layout",
|
|
155
|
+
"msg": "`@angular/flex-layout` was retired by the Angular team and receives no further releases.",
|
|
156
|
+
"fix": "Replace the directives with CSS grid or flexbox, or with the community fork you vendor yourself."
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"id": "browser-module-server-transition",
|
|
160
|
+
"kind": "deprecated",
|
|
161
|
+
"since": 16,
|
|
162
|
+
"re": "withServerTransition\\s*\\(",
|
|
163
|
+
"msg": "`BrowserModule.withServerTransition()` has been deprecated since Angular 16.",
|
|
164
|
+
"fix": "Set `APP_ID` as a provider instead; the transition id is read from it."
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
"id": "enable-ivy-flag",
|
|
168
|
+
"kind": "removed",
|
|
169
|
+
"since": 16,
|
|
170
|
+
"re": "\"enableIvy\"",
|
|
171
|
+
"msg": "The `enableIvy` compiler flag was removed in Angular 16 — View Engine no longer exists.",
|
|
172
|
+
"fix": "Delete the flag from `angularCompilerOptions` in tsconfig.json."
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"id": "ngcc-script",
|
|
176
|
+
"kind": "removed",
|
|
177
|
+
"since": 16,
|
|
178
|
+
"re": "\\bngcc\\b",
|
|
179
|
+
"msg": "`ngcc` (the View Engine compatibility compiler) was removed in Angular 16.",
|
|
180
|
+
"fix": "Delete the ngcc postinstall script; every published library is Ivy now."
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
"id": "relative-link-resolution",
|
|
184
|
+
"kind": "removed",
|
|
185
|
+
"since": 16,
|
|
186
|
+
"re": "relativeLinkResolution",
|
|
187
|
+
"msg": "The router option `relativeLinkResolution` was removed in Angular 16.",
|
|
188
|
+
"fix": "Delete it — the `corrected` behaviour it selected is now the only behaviour."
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
"id": "rxjs-topromise",
|
|
192
|
+
"kind": "deprecated",
|
|
193
|
+
"since": 16,
|
|
194
|
+
"re": "\\.toPromise\\s*\\(\\s*\\)",
|
|
195
|
+
"msg": "`toPromise()` is deprecated in RxJS 7, the version Angular has shipped since 16, and is gone in RxJS 8.",
|
|
196
|
+
"fix": "Use `firstValueFrom(obs$)` or `lastValueFrom(obs$)` — they throw on empty instead of resolving `undefined`."
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"id": "ngmodule-router-for-root",
|
|
200
|
+
"kind": "advisory",
|
|
201
|
+
"since": 17,
|
|
202
|
+
"re": "RouterModule\\s*\\.\\s*forRoot\\s*\\(",
|
|
203
|
+
"msg": "`RouterModule.forRoot()` is the NgModule route setup; standalone bootstrap has been the documented default since Angular 17.",
|
|
204
|
+
"fix": "Move the routes to `provideRouter(routes)` inside `bootstrapApplication(App, { providers: [...] })`."
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
"id": "structural-directive-control-flow",
|
|
208
|
+
"kind": "advisory",
|
|
209
|
+
"since": 17,
|
|
210
|
+
"re": "\\*ng(If|For|Switch)\\b",
|
|
211
|
+
"msg": "Built-in control flow has been stable since Angular 17 and is what the current docs and schematics emit.",
|
|
212
|
+
"fix": "Run `ng generate @angular/core:control-flow` to rewrite `*ngIf`/`*ngFor`/`*ngSwitch` as `@if`/`@for`/`@switch`."
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
"id": "browser-animations-module",
|
|
216
|
+
"kind": "advisory",
|
|
217
|
+
"since": 17,
|
|
218
|
+
"re": "\\bBrowserAnimationsModule\\b",
|
|
219
|
+
"msg": "`BrowserAnimationsModule` is the NgModule form of the animations setup.",
|
|
220
|
+
"fix": "Use `provideAnimationsAsync()` (or `provideAnimations()`) in the bootstrap providers."
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
"id": "platform-browser-dynamic-bootstrap",
|
|
224
|
+
"kind": "advisory",
|
|
225
|
+
"since": 17,
|
|
226
|
+
"re": "platformBrowserDynamic\\s*\\(",
|
|
227
|
+
"msg": "`platformBrowserDynamic().bootstrapModule()` is the NgModule bootstrap path.",
|
|
228
|
+
"fix": "Use `bootstrapApplication(AppComponent, appConfig)` from `@angular/platform-browser`."
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
"id": "http-client-module",
|
|
232
|
+
"kind": "deprecated",
|
|
233
|
+
"since": 18,
|
|
234
|
+
"re": "\\bHttpClientModule\\b",
|
|
235
|
+
"msg": "`HttpClientModule` has been deprecated since Angular 18.",
|
|
236
|
+
"fix": "Call `provideHttpClient()` in your bootstrap providers; add `withInterceptors([...])` for interceptors."
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
"id": "http-client-testing-module",
|
|
240
|
+
"kind": "deprecated",
|
|
241
|
+
"since": 18,
|
|
242
|
+
"re": "\\bHttpClientTestingModule\\b",
|
|
243
|
+
"msg": "`HttpClientTestingModule` has been deprecated since Angular 18.",
|
|
244
|
+
"fix": "Use `provideHttpClient()` plus `provideHttpClientTesting()` in the testing providers."
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
"id": "app-initializer-token",
|
|
248
|
+
"kind": "deprecated",
|
|
249
|
+
"since": 19,
|
|
250
|
+
"re": "\\bAPP_INITIALIZER\\b",
|
|
251
|
+
"msg": "The `APP_INITIALIZER` token has been deprecated since Angular 19.",
|
|
252
|
+
"fix": "Use `provideAppInitializer(() => inject(Config).load())` — no multi-provider boilerplate."
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
"id": "environment-initializer-token",
|
|
256
|
+
"kind": "deprecated",
|
|
257
|
+
"since": 19,
|
|
258
|
+
"re": "\\bENVIRONMENT_INITIALIZER\\b",
|
|
259
|
+
"msg": "The `ENVIRONMENT_INITIALIZER` token has been deprecated since Angular 19.",
|
|
260
|
+
"fix": "Use `provideEnvironmentInitializer(() => { ... })`."
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
"id": "after-render-phase",
|
|
264
|
+
"kind": "deprecated",
|
|
265
|
+
"since": 19,
|
|
266
|
+
"re": "\\bAfterRenderPhase\\b",
|
|
267
|
+
"msg": "`AfterRenderPhase` has been deprecated since Angular 19.",
|
|
268
|
+
"fix": "Pass a phase-keyed object instead: `afterRender({ write: () => { ... } })`."
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"id": "experimental-pending-tasks",
|
|
272
|
+
"kind": "deprecated",
|
|
273
|
+
"since": 19,
|
|
274
|
+
"re": "\\bExperimentalPendingTasks\\b",
|
|
275
|
+
"msg": "`ExperimentalPendingTasks` was renamed in Angular 19.",
|
|
276
|
+
"fix": "Import `PendingTasks` from `@angular/core`; the API is unchanged."
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"id": "allow-signal-writes",
|
|
280
|
+
"kind": "deprecated",
|
|
281
|
+
"since": 19,
|
|
282
|
+
"re": "\\ballowSignalWrites\\b",
|
|
283
|
+
"msg": "The `allowSignalWrites` option on `effect()` was deprecated in Angular 19 and does nothing.",
|
|
284
|
+
"fix": "Delete the option — effects may write to signals without it."
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
"id": "redundant-standalone-true",
|
|
288
|
+
"kind": "advisory",
|
|
289
|
+
"since": 19,
|
|
290
|
+
"re": "standalone\\s*:\\s*true",
|
|
291
|
+
"msg": "`standalone: true` has been the default since Angular 19, so writing it is redundant.",
|
|
292
|
+
"fix": "Delete the property. Only `standalone: false` carries meaning now (a component still declared in an NgModule)."
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
"id": "experimental-zoneless",
|
|
296
|
+
"kind": "deprecated",
|
|
297
|
+
"since": 20,
|
|
298
|
+
"re": "provideExperimentalZonelessChangeDetection",
|
|
299
|
+
"msg": "`provideExperimentalZonelessChangeDetection()` was renamed when zoneless went stable in Angular 20.",
|
|
300
|
+
"fix": "Call `provideZonelessChangeDetection()`."
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
"id": "testbed-flush-effects",
|
|
304
|
+
"kind": "deprecated",
|
|
305
|
+
"since": 20,
|
|
306
|
+
"re": "\\.flushEffects\\s*\\(",
|
|
307
|
+
"msg": "`TestBed.flushEffects()` has been deprecated since Angular 20.",
|
|
308
|
+
"fix": "Call `TestBed.tick()` — it flushes effects and runs change detection."
|
|
309
|
+
}
|
|
310
|
+
]
|
|
311
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Angular Removed-API Lint",
|
|
3
|
+
"subtitle": "32 checks for Angular APIs that were removed or deprecated, plus the support window on your version pin. The bundled legacy fixture — one NgModule file and its spec — returns 34 findings, 9 of them errors for removed APIs, 18 warnings and 7 advisories.",
|
|
4
|
+
"bin": "angular-removed-api-lint",
|
|
5
|
+
"price": 29,
|
|
6
|
+
"free": "Check the file open in the editor, or a paste in the browser, against all 32 checks and get the replacement line for every hit.",
|
|
7
|
+
"paid": "Sweep every .ts, .html and .json in the workspace and write a dated angularRemovedApi-report.md you own and can hand to a client.",
|
|
8
|
+
"need_key": "This option needs a licence (Angular Removed-API Lint).",
|
|
9
|
+
"exts": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: readystack-angular-removed-api-lint
|
|
3
|
+
Version: 1.0.7
|
|
4
|
+
Summary: 32 checks for Angular APIs that were removed or deprecated, plus the support window on your version pin. The bundled legacy fixture — one NgModule file and its spec — returns 34 findings, 9 of them er (bundled Node.js runtime)
|
|
5
|
+
Author-email: ReadyStack <support@getreadystack.com>
|
|
6
|
+
License: Proprietary - see LICENSE.txt
|
|
7
|
+
Project-URL: Homepage, https://getreadystack.com/tools/angular-removed-api-lint?ref=pypi
|
|
8
|
+
Project-URL: Documentation, https://getreadystack.com/tools/angular-removed-api-lint?ref=pypi
|
|
9
|
+
Project-URL: Support, https://getreadystack.com/support
|
|
10
|
+
Keywords: angular,deprecated,migration,upgrade,lint,typescript,ai-generated
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE.txt
|
|
18
|
+
Requires-Dist: nodejs-wheel-binaries>=20
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# Angular Removed-API Lint
|
|
22
|
+
|
|
23
|
+
> Runs the same checker as the npm package `@readystack/angular-removed-api-lint` on a Node.js runtime that pip installs for you (`nodejs-wheel-binaries`) - no system Node.js needed. Your files are checked locally.
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
32 checks for Angular APIs that were removed or deprecated, plus the support window on your version pin. The bundled legacy fixture — one NgModule file and its spec — returns 34 findings, 9 of them errors for removed APIs, 18 warnings and 7 advisories.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
angular-removed-api-lint file
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Node 18+. The same 32 rules as the VS Code extension, from a terminal or CI.
|
|
36
|
+
|
|
37
|
+
## Free
|
|
38
|
+
|
|
39
|
+
- Check the file open in the editor, or a paste in the browser, against all 32 checks and get the replacement line for every hit.
|
|
40
|
+
- `--rules` lists every rule
|
|
41
|
+
|
|
42
|
+
## With a licence ($29 once)
|
|
43
|
+
|
|
44
|
+
- Sweep every .ts, .html and .json in the workspace and write a dated angularRemovedApi-report.md you own and can hand to a client.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
@readystack/angular-removed-api-lint --dir ./templates --report html --out report.html
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A freelance Angular upgrade consultant bills $80-$150/hour to read the same files by hand.
|
|
51
|
+
|
|
52
|
+
## Use from an AI agent (MCP)
|
|
53
|
+
|
|
54
|
+
Claude Code · Cursor · Windsurf · any MCP client - add to your MCP config:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{ "mcpServers": { "angular-removed-api-lint": { "command": "npx", "args": ["-y", "@readystack/angular-removed-api-lint", "--mcp"] } } }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Tools: `check_text` and `check_file` (free) · `check_dir` (licence). The agent gets every finding with the line number.
|
|
61
|
+
|
|
62
|
+
## Use in CI
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
- name: Angular Removed-API Lint
|
|
66
|
+
run: npx -y @readystack/angular-removed-api-lint --dir . --ci
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
(container: `docker run --rm -v "$PWD:/work" getreadystack/angular-removed-api-lint --dir /work --ci`)
|
|
70
|
+
|
|
71
|
+
The folder sweep, reports and CI mode need one licence — one payment, no subscription. Set `READYSTACK_LICENSE=<key>` or run `--license <key>` once.
|
|
72
|
+
|
|
73
|
+
[Get a licence](https://getreadystack.com/api/buy/cl/polar_cl_2wSKxziqGHTfu7JassC9u93PIE4YL8ThOikL64MVZVc)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
<!-- angular removed api lint -->
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
## Install (PyPI)
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
pip install readystack-angular-removed-api-lint
|
|
83
|
+
angular-removed-api-lint --help
|
|
84
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
readystack_angular_removed_api_lint/__init__.py,sha256=Th6FPUfmw3s94879QiaD_OgzmQyEFPOnYrMVoQqee8Y,22
|
|
2
|
+
readystack_angular_removed_api_lint/__main__.py,sha256=3DzZxi-7VBAiEeEUw4wbFJcFLoWDkdhCPmwyaTT-DkY,359
|
|
3
|
+
readystack_angular_removed_api_lint/js/cli.js,sha256=ib60vyNQbZ7meRCc-ACffzcJ04JFn1nbF-HjqGMWM_U,14665
|
|
4
|
+
readystack_angular_removed_api_lint/js/engine.js,sha256=U6aePEcoijysCJdwyXVLU1oHA2g9i1LW9YN-kWUPUX0,4062
|
|
5
|
+
readystack_angular_removed_api_lint/js/license.js,sha256=41GqnhIII0XWRqaIJgHx-8oImPyL2zMxQNPdXiDDQUc,7564
|
|
6
|
+
readystack_angular_removed_api_lint/js/package.json,sha256=lZPK0Tj83x3jg4aPKu5JMCeQQ9GlAgHGG6M5qc2guAQ,1124
|
|
7
|
+
readystack_angular_removed_api_lint/js/rules.json,sha256=hjw9l7V8ZvGUd-mHsWWOFp7oH6s4CIe6BcYND0eBvnU,11178
|
|
8
|
+
readystack_angular_removed_api_lint/js/strings.json,sha256=ib_4wAa1RUXZ5K4jO0mNTlWxPy9WFUkfspFxXlTly9Q,730
|
|
9
|
+
readystack_angular_removed_api_lint-1.0.7.dist-info/licenses/LICENSE.txt,sha256=sCqEEba7y6UOYypxm0yDbzHMnwOmh4BcceXUbgDKIXU,901
|
|
10
|
+
readystack_angular_removed_api_lint-1.0.7.dist-info/METADATA,sha256=zjoqDEkUPhyaN6_IgBhl34qlieTskRTOpFuEEHIJUzU,3381
|
|
11
|
+
readystack_angular_removed_api_lint-1.0.7.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
12
|
+
readystack_angular_removed_api_lint-1.0.7.dist-info/entry_points.txt,sha256=9_b2eB0vGllqotfk5N04jtNJ3U8AVLgFxB3mzf1reJM,95
|
|
13
|
+
readystack_angular_removed_api_lint-1.0.7.dist-info/top_level.txt,sha256=yxPl58Rge3x_K1y9vkzVOIPkUyjpGi31U9QnFH-nGB4,36
|
|
14
|
+
readystack_angular_removed_api_lint-1.0.7.dist-info/RECORD,,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Angular Removed-API Lint — Licence
|
|
2
|
+
|
|
3
|
+
Free scope
|
|
4
|
+
Check the file open in the editor against all 32 checks and see the replacement line for every hit is free for any use, personal or commercial, for as long as you keep the
|
|
5
|
+
extension installed, and the online check at the product page is free without registration.
|
|
6
|
+
|
|
7
|
+
Paid scope
|
|
8
|
+
Sweep every .ts, .html and .json in the workspace and write a dated angularRemovedApi-report.md you own and can hand to a client is the paid part. It asks for a licence key issued at purchase (one seat, one key). The key is checked against the payment provider and
|
|
9
|
+
cached locally for 30 days so it keeps working offline.
|
|
10
|
+
|
|
11
|
+
Redistribution
|
|
12
|
+
You may not resell, re-host or bundle this extension or its rule set. The rule set and the
|
|
13
|
+
engine are provided as-is; check results are advisory and do not constitute legal advice.
|
|
14
|
+
|
|
15
|
+
(c) ReadyStack — https://getreadystack.com
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
readystack_angular_removed_api_lint
|