fchek 1.0.0
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.
- package/README.md +64 -0
- package/bin/fchek.js +107 -0
- package/lib/api.js +110 -0
- package/lib/audit.js +211 -0
- package/lib/bench.js +248 -0
- package/lib/config.js +191 -0
- package/lib/context.js +356 -0
- package/lib/convention.js +526 -0
- package/lib/coverage.js +604 -0
- package/lib/db.js +135 -0
- package/lib/deps-check.js +264 -0
- package/lib/deps.js +374 -0
- package/lib/docker.js +84 -0
- package/lib/doctor.js +149 -0
- package/lib/dom.js +226 -0
- package/lib/fuzz.js +470 -0
- package/lib/git.js +290 -0
- package/lib/goto.js +544 -0
- package/lib/launch.js +182 -0
- package/lib/lint.js +624 -0
- package/lib/new_features.test.js +181 -0
- package/lib/output.js +46 -0
- package/lib/port.js +173 -0
- package/lib/process.js +228 -0
- package/lib/profile.js +453 -0
- package/lib/python.js +41 -0
- package/lib/race.js +186 -0
- package/lib/registry.js +179 -0
- package/lib/repl.js +135 -0
- package/lib/run.js +403 -0
- package/lib/screenshot.js +152 -0
- package/lib/secrets.js +257 -0
- package/lib/state.js +219 -0
- package/lib/test.js +471 -0
- package/lib/vuln.js +253 -0
- package/lib/watch.js +240 -0
- package/lib/winlog.js +123 -0
- package/package.json +27 -0
- package/skills/ACTIVATE.md +274 -0
- package/skills/README.md +163 -0
- package/skills/agent.md +444 -0
- package/skills/api.md +47 -0
- package/skills/bench.md +117 -0
- package/skills/context.md +116 -0
- package/skills/convention.md +143 -0
- package/skills/coverage.md +99 -0
- package/skills/csharp.md +97 -0
- package/skills/db.md +66 -0
- package/skills/deps-check.md +135 -0
- package/skills/deps.md +143 -0
- package/skills/docker.md +61 -0
- package/skills/dom.md +56 -0
- package/skills/fuzz.md +167 -0
- package/skills/goto.md +111 -0
- package/skills/lint.md +123 -0
- package/skills/port.md +57 -0
- package/skills/profile.md +91 -0
- package/skills/race.md +117 -0
- package/skills/repl.md +81 -0
- package/skills/rules.md +318 -0
- package/skills/run.md +135 -0
- package/skills/secrets.md +170 -0
- package/skills/security.md +360 -0
- package/skills/state.md +261 -0
- package/skills/vuln.md +57 -0
- package/skills/windows.md +320 -0
package/lib/db.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { output, ok, fail } = require('./output');
|
|
6
|
+
|
|
7
|
+
const HELP = `
|
|
8
|
+
fchek db schema <db_path>
|
|
9
|
+
fchek db query <db_path> <sql_query>
|
|
10
|
+
|
|
11
|
+
Inspect and query SQLite databases.
|
|
12
|
+
schema: List all tables, columns, types, primary keys, and indexes.
|
|
13
|
+
query: Execute a SELECT or action query and return results.
|
|
14
|
+
`.trim();
|
|
15
|
+
|
|
16
|
+
// Try to load Node.js 22 built-in sqlite module
|
|
17
|
+
let DatabaseSync;
|
|
18
|
+
try {
|
|
19
|
+
DatabaseSync = require('node:sqlite').DatabaseSync;
|
|
20
|
+
} catch (err) {
|
|
21
|
+
// node:sqlite not available (<22.5.0 or older Node)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runSqliteCommand(dbPath, sql) {
|
|
25
|
+
// If sqlite3 CLI tool is installed, we can fall back to it
|
|
26
|
+
const { execSync } = require('child_process');
|
|
27
|
+
try {
|
|
28
|
+
const jsonOut = execSync(`sqlite3 "${dbPath}" -json "${sql}"`, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
|
|
29
|
+
return JSON.parse(jsonOut.trim() || '[]');
|
|
30
|
+
} catch (err) {
|
|
31
|
+
throw new Error('SQLite query failed. Make sure sqlite3 CLI tool is in your PATH or use Node.js >= 22.5');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function run(args) {
|
|
36
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
37
|
+
console.log(HELP);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const action = args[0];
|
|
42
|
+
const dbPath = args[1];
|
|
43
|
+
|
|
44
|
+
if (!action || !['schema', 'query'].includes(action)) {
|
|
45
|
+
output(fail('Invalid action. Use "schema" or "query"', 'db'));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!dbPath) {
|
|
50
|
+
output(fail('Please specify a database file path', 'db'));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const resolvedPath = path.resolve(dbPath);
|
|
55
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
56
|
+
output(fail(`Database file does not exist: ${dbPath}`, 'db'));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (action === 'schema') {
|
|
61
|
+
try {
|
|
62
|
+
let tables = [];
|
|
63
|
+
if (DatabaseSync) {
|
|
64
|
+
const db = new DatabaseSync(resolvedPath);
|
|
65
|
+
const tablesRows = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'").all();
|
|
66
|
+
|
|
67
|
+
for (const tRow of tablesRows) {
|
|
68
|
+
const tableName = tRow.name;
|
|
69
|
+
const colRows = db.prepare(`PRAGMA table_info("${tableName}")`).all();
|
|
70
|
+
const indexRows = db.prepare(`PRAGMA index_list("${tableName}")`).all();
|
|
71
|
+
|
|
72
|
+
const columns = colRows.map(c => ({
|
|
73
|
+
name: c.name,
|
|
74
|
+
type: c.type,
|
|
75
|
+
notnull: c.notnull === 1,
|
|
76
|
+
dflt_value: c.dflt_value,
|
|
77
|
+
pk: c.pk === 1
|
|
78
|
+
}));
|
|
79
|
+
|
|
80
|
+
tables.push({
|
|
81
|
+
name: tableName,
|
|
82
|
+
columns,
|
|
83
|
+
indexes: indexRows.map(i => i.name)
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
db.close();
|
|
87
|
+
} else {
|
|
88
|
+
// Fallback using sqlite3 CLI
|
|
89
|
+
const tablesRows = runSqliteCommand(resolvedPath, "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'");
|
|
90
|
+
for (const tRow of tablesRows) {
|
|
91
|
+
const tableName = tRow.name;
|
|
92
|
+
const colRows = runSqliteCommand(resolvedPath, `PRAGMA table_info("${tableName}")`);
|
|
93
|
+
const indexRows = runSqliteCommand(resolvedPath, `PRAGMA index_list("${tableName}")`);
|
|
94
|
+
tables.push({
|
|
95
|
+
name: tableName,
|
|
96
|
+
columns: colRows.map(c => ({
|
|
97
|
+
name: c.name,
|
|
98
|
+
type: c.type,
|
|
99
|
+
notnull: c.notnull === 1,
|
|
100
|
+
dflt_value: c.dflt_value,
|
|
101
|
+
pk: c.pk === 1
|
|
102
|
+
})),
|
|
103
|
+
indexes: indexRows.map(i => i.name)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
output(ok({ database: dbPath, tables }, 'db'));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
output(fail(`Failed to fetch schema: ${err.message}`, 'db'));
|
|
110
|
+
}
|
|
111
|
+
} else if (action === 'query') {
|
|
112
|
+
const querySql = args.slice(2).join(' ');
|
|
113
|
+
if (!querySql) {
|
|
114
|
+
output(fail('Please specify a SQL query', 'db'));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
let rows = [];
|
|
120
|
+
if (DatabaseSync) {
|
|
121
|
+
const db = new DatabaseSync(resolvedPath);
|
|
122
|
+
const stmt = db.prepare(querySql);
|
|
123
|
+
rows = stmt.all();
|
|
124
|
+
db.close();
|
|
125
|
+
} else {
|
|
126
|
+
rows = runSqliteCommand(resolvedPath, querySql);
|
|
127
|
+
}
|
|
128
|
+
output(ok({ query: querySql, count: rows.length, rows }, 'db'));
|
|
129
|
+
} catch (err) {
|
|
130
|
+
output(fail(`Query execution failed: ${err.message}`, 'db'));
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
module.exports = { run };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* deps-check.js — check library API freshness
|
|
5
|
+
*
|
|
6
|
+
* Fetches current package metadata from npm / PyPI / crates.io
|
|
7
|
+
* and compares against what's installed in the project.
|
|
8
|
+
* Closes the "hallucinated API" problem.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { spawnSync, execSync } = require('child_process');
|
|
12
|
+
const https = require('https');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const { output, ok, fail } = require('./output');
|
|
16
|
+
|
|
17
|
+
const HELP = `
|
|
18
|
+
fchek deps-check <lib> [--lang=auto] [--show-changelog]
|
|
19
|
+
|
|
20
|
+
Check if a library's API is current. Fetches live data from package registries.
|
|
21
|
+
Tells you: installed version, latest version, deprecated APIs, what to use instead.
|
|
22
|
+
|
|
23
|
+
Supported registries:
|
|
24
|
+
npm → https://registry.npmjs.org
|
|
25
|
+
PyPI → https://pypi.org/pypi
|
|
26
|
+
crates.io → https://crates.io/api/v1/crates
|
|
27
|
+
|
|
28
|
+
Options:
|
|
29
|
+
--lang=node|python|rust Force language (default: auto-detect from project)
|
|
30
|
+
--show-changelog Include changelog URL if available
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
fchek deps-check express
|
|
34
|
+
fchek deps-check requests --lang=python
|
|
35
|
+
fchek deps-check serde --lang=rust
|
|
36
|
+
`.trim();
|
|
37
|
+
|
|
38
|
+
function fetchJson(url, timeoutMs = 15000) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
const req = https.get(url, {
|
|
41
|
+
headers: { 'User-Agent': 'fchek/1.0', Accept: 'application/json' },
|
|
42
|
+
timeout: timeoutMs,
|
|
43
|
+
}, (res) => {
|
|
44
|
+
let data = '';
|
|
45
|
+
res.on('data', c => { data += c; });
|
|
46
|
+
res.on('end', () => {
|
|
47
|
+
try { resolve(JSON.parse(data)); }
|
|
48
|
+
catch (e) { reject(new Error(`JSON parse failed: ${e.message}`)); }
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
req.on('error', (err) => {
|
|
52
|
+
// Provide actionable error messages for common network issues
|
|
53
|
+
if (err.code === 'ECONNREFUSED' || err.code === 'ECONNRESET') {
|
|
54
|
+
reject(new Error(`No connection to registry. Check your internet connection.`));
|
|
55
|
+
} else if (err.code === 'ENOTFOUND') {
|
|
56
|
+
reject(new Error(`DNS lookup failed for registry. Check internet connection or DNS settings.`));
|
|
57
|
+
} else if (err.code === 'ETIMEDOUT') {
|
|
58
|
+
reject(new Error(`Registry request timed out. Check internet connection.`));
|
|
59
|
+
} else {
|
|
60
|
+
reject(err);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
req.on('timeout', () => {
|
|
64
|
+
req.destroy();
|
|
65
|
+
reject(new Error(`Registry request timed out after ${timeoutMs}ms. Check internet connection.`));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Offline fallback: read installed version from local project files only */
|
|
71
|
+
function offlineFallback(lib, lang, cwd) {
|
|
72
|
+
let installed = null;
|
|
73
|
+
|
|
74
|
+
if (lang === 'node') {
|
|
75
|
+
installed = installedNode(lib, cwd);
|
|
76
|
+
// Also try reading from package.json directly
|
|
77
|
+
if (!installed) {
|
|
78
|
+
try {
|
|
79
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, 'package.json'), 'utf8'));
|
|
80
|
+
installed = pkg.dependencies?.[lib] || pkg.devDependencies?.[lib] || null;
|
|
81
|
+
} catch {}
|
|
82
|
+
}
|
|
83
|
+
} else if (lang === 'python') {
|
|
84
|
+
installed = installedPython(lib);
|
|
85
|
+
} else if (lang === 'rust') {
|
|
86
|
+
// Read from Cargo.toml directly
|
|
87
|
+
try {
|
|
88
|
+
const cargo = fs.readFileSync(path.join(cwd, 'Cargo.toml'), 'utf8');
|
|
89
|
+
const m = cargo.match(new RegExp(`${lib}\\s*=\\s*["{]([0-9][^"{}\\n]+)`, 'i'));
|
|
90
|
+
installed = m?.[1]?.trim() || installedRust(lib, cwd);
|
|
91
|
+
} catch { installed = installedRust(lib, cwd); }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
lib,
|
|
96
|
+
registry: lang === 'node' ? 'npm' : lang === 'python' ? 'pypi' : 'crates.io',
|
|
97
|
+
offline: true,
|
|
98
|
+
offline_note: 'No internet connection. Showing installed version only. Cannot check for latest/deprecated.',
|
|
99
|
+
installed_version: installed,
|
|
100
|
+
latest_version: null,
|
|
101
|
+
up_to_date: null,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function detectLang(cwd) {
|
|
106
|
+
if (fs.existsSync(path.join(cwd, 'package.json'))) return 'node';
|
|
107
|
+
if (fs.existsSync(path.join(cwd, 'Cargo.toml'))) return 'rust';
|
|
108
|
+
if (fs.existsSync(path.join(cwd, 'requirements.txt'))
|
|
109
|
+
|| fs.existsSync(path.join(cwd, 'pyproject.toml'))
|
|
110
|
+
|| fs.existsSync(path.join(cwd, 'setup.py'))) return 'python';
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function installedNode(lib, cwd) {
|
|
115
|
+
try {
|
|
116
|
+
const p = path.join(cwd, 'node_modules', lib, 'package.json');
|
|
117
|
+
if (fs.existsSync(p)) return JSON.parse(fs.readFileSync(p, 'utf8')).version || null;
|
|
118
|
+
} catch {}
|
|
119
|
+
try {
|
|
120
|
+
const r = spawnSync('npm', ['list', lib, '--depth=0', '--json'], { encoding: 'utf8', cwd, timeout: 8000 });
|
|
121
|
+
return JSON.parse(r.stdout || '{}')?.dependencies?.[lib]?.version || null;
|
|
122
|
+
} catch { return null; }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function installedPython(lib) {
|
|
126
|
+
try {
|
|
127
|
+
return execSync(`python3 -c "import importlib.metadata;print(importlib.metadata.version('${lib}'))"`,
|
|
128
|
+
{ encoding: 'utf8', timeout: 5000 }).trim() || null;
|
|
129
|
+
} catch {
|
|
130
|
+
try {
|
|
131
|
+
return execSync(`pip show ${lib}`, { encoding: 'utf8', timeout: 5000 })
|
|
132
|
+
.match(/Version:\s*(.+)/)?.[1]?.trim() || null;
|
|
133
|
+
} catch { return null; }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function installedRust(lib, cwd) {
|
|
138
|
+
try {
|
|
139
|
+
const r = spawnSync('cargo', ['metadata', '--no-deps', '--format-version=1'],
|
|
140
|
+
{ encoding: 'utf8', cwd, timeout: 15000 });
|
|
141
|
+
const data = JSON.parse(r.stdout || '{}');
|
|
142
|
+
for (const pkg of data.packages || []) {
|
|
143
|
+
if (pkg.name === lib) return pkg.version;
|
|
144
|
+
for (const dep of pkg.dependencies || []) {
|
|
145
|
+
if (dep.name === lib) return dep.req?.replace(/[^0-9.]/, '') || null;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch {}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function checkNpm(lib, showChangelog) {
|
|
153
|
+
const cwd = process.cwd();
|
|
154
|
+
const installed = installedNode(lib, cwd);
|
|
155
|
+
const data = await fetchJson(`https://registry.npmjs.org/${encodeURIComponent(lib)}`);
|
|
156
|
+
const latest = data['dist-tags']?.latest || null;
|
|
157
|
+
const latestMeta = data.versions?.[latest] || {};
|
|
158
|
+
|
|
159
|
+
const deprecated = [];
|
|
160
|
+
for (const [ver, meta] of Object.entries(data.versions || {})) {
|
|
161
|
+
if (meta.deprecated) deprecated.push({ version: ver, message: meta.deprecated });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const installedDeprecated = installed
|
|
165
|
+
? (data.versions?.[installed]?.deprecated || null)
|
|
166
|
+
: null;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
lib, registry: 'npm',
|
|
170
|
+
installed_version: installed,
|
|
171
|
+
latest_version: latest,
|
|
172
|
+
up_to_date: installed === latest,
|
|
173
|
+
installed_deprecated: installedDeprecated,
|
|
174
|
+
deprecated_versions: deprecated.slice(0, 10),
|
|
175
|
+
recent_versions: Object.keys(data.versions || {}).slice(-5).reverse(),
|
|
176
|
+
description: (data.description || '').slice(0, 200),
|
|
177
|
+
homepage: latestMeta.homepage || null,
|
|
178
|
+
changelog: showChangelog ? (latestMeta.repository?.url || null) : null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function checkPypi(lib, showChangelog) {
|
|
183
|
+
const installed = installedPython(lib);
|
|
184
|
+
const data = await fetchJson(`https://pypi.org/pypi/${encodeURIComponent(lib)}/json`);
|
|
185
|
+
const info = data.info || {};
|
|
186
|
+
const latest = info.version || null;
|
|
187
|
+
|
|
188
|
+
const isDeprecated = (info.classifiers || []).some(c => /deprecated/i.test(c));
|
|
189
|
+
const desc = info.description || '';
|
|
190
|
+
const notices = desc.split('\n')
|
|
191
|
+
.filter(l => /deprecated|removed|use.*instead/i.test(l))
|
|
192
|
+
.slice(0, 5).map(l => l.trim());
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
lib, registry: 'pypi',
|
|
196
|
+
installed_version: installed,
|
|
197
|
+
latest_version: latest,
|
|
198
|
+
up_to_date: installed === latest,
|
|
199
|
+
is_deprecated: isDeprecated,
|
|
200
|
+
deprecation_notices: notices,
|
|
201
|
+
recent_versions: Object.keys(data.releases || {}).slice(-5).reverse(),
|
|
202
|
+
requires_python: info.requires_python || null,
|
|
203
|
+
description: (info.summary || '').slice(0, 200),
|
|
204
|
+
changelog: showChangelog ? (info.project_urls?.Changelog || null) : null,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function checkCrates(lib, showChangelog) {
|
|
209
|
+
const cwd = process.cwd();
|
|
210
|
+
const installed = installedRust(lib, cwd);
|
|
211
|
+
const data = await fetchJson(`https://crates.io/api/v1/crates/${encodeURIComponent(lib)}`);
|
|
212
|
+
const crate = data.crate || {};
|
|
213
|
+
const versions = (data.versions || []).slice(0, 10);
|
|
214
|
+
|
|
215
|
+
const yanked = versions.filter(v => v.yanked).map(v => ({ version: v.num }));
|
|
216
|
+
const recent = versions.slice(0, 5).map(v => ({
|
|
217
|
+
version: v.num, yanked: v.yanked, date: v.created_at?.slice(0, 10),
|
|
218
|
+
}));
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
lib, registry: 'crates.io',
|
|
222
|
+
installed_version: installed,
|
|
223
|
+
latest_version: crate.newest_version || null,
|
|
224
|
+
up_to_date: installed === crate.newest_version,
|
|
225
|
+
yanked_versions: yanked,
|
|
226
|
+
recent_versions: recent,
|
|
227
|
+
description: (crate.description || '').slice(0, 200),
|
|
228
|
+
repository: crate.repository || null,
|
|
229
|
+
changelog: showChangelog ? crate.repository : null,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function run(args) {
|
|
234
|
+
if (args.length === 0 || args[0] === '--help') { console.log(HELP); return; }
|
|
235
|
+
|
|
236
|
+
const lib = args[0];
|
|
237
|
+
const langArg = (args.find(a => a.startsWith('--lang=')) || '').replace('--lang=', '') || null;
|
|
238
|
+
const showChangelog = args.includes('--show-changelog');
|
|
239
|
+
const lang = langArg || detectLang(process.cwd());
|
|
240
|
+
|
|
241
|
+
if (!lang) {
|
|
242
|
+
return output(fail('Cannot auto-detect project language. Use --lang=node|python|rust'));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
try {
|
|
246
|
+
let result;
|
|
247
|
+
if (lang === 'node') result = await checkNpm(lib, showChangelog);
|
|
248
|
+
else if (lang === 'python') result = await checkPypi(lib, showChangelog);
|
|
249
|
+
else if (lang === 'rust') result = await checkCrates(lib, showChangelog);
|
|
250
|
+
else return output(fail(`Unsupported lang: ${lang}. Use node|python|rust`));
|
|
251
|
+
output(ok(result));
|
|
252
|
+
} catch (err) {
|
|
253
|
+
// If it looks like a network error — use offline fallback instead of failing
|
|
254
|
+
const isNetworkError = /connection|DNS|timed out|ENOTFOUND|ECONNREFUSED|ETIMEDOUT/i.test(err.message);
|
|
255
|
+
if (isNetworkError) {
|
|
256
|
+
const fallback = offlineFallback(lib, lang, process.cwd());
|
|
257
|
+
output(ok(fallback));
|
|
258
|
+
} else {
|
|
259
|
+
output(fail(`Registry error: ${err.message}`));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = { run };
|