@ls-apis/cli 0.0.2 → 0.0.4
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/data/apis.json +103 -3
- package/package.json +1 -1
- package/dist/packages/cli/src/categories.js +0 -13
- package/dist/packages/cli/src/colors.js +0 -19
- package/dist/packages/cli/src/config.js +0 -36
- package/dist/packages/cli/src/formatter.js +0 -73
- package/dist/packages/cli/src/index.js +0 -178
- package/dist/packages/cli/src/providers.js +0 -21
- package/dist/packages/cli/src/qa.js +0 -14
- package/dist/packages/cli/src/search.js +0 -50
- package/dist/packages/cli/src/types.js +0 -1
package/data/apis.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"timestamp": "2026-05-
|
|
2
|
+
"timestamp": "2026-05-29T02:09:29.894Z",
|
|
3
3
|
"providers": [
|
|
4
4
|
{
|
|
5
5
|
"name": "apis-guru",
|
|
@@ -26145,6 +26145,16 @@
|
|
|
26145
26145
|
"openapiSpec": null,
|
|
26146
26146
|
"sources": ["github-public-apis"]
|
|
26147
26147
|
},
|
|
26148
|
+
{
|
|
26149
|
+
"name": "Amanah Sunnah",
|
|
26150
|
+
"description": "Semantic search across Quran, Hadith, Tafsir & 18K+ Rijal narrators",
|
|
26151
|
+
"link": "https://sunnah.amanahagent.cloud/developers",
|
|
26152
|
+
"auth": "apiKey",
|
|
26153
|
+
"cors": "yes",
|
|
26154
|
+
"categories": ["Books"],
|
|
26155
|
+
"openapiSpec": null,
|
|
26156
|
+
"sources": ["github-public-apis"]
|
|
26157
|
+
},
|
|
26148
26158
|
{
|
|
26149
26159
|
"name": "Bhagavad Gita",
|
|
26150
26160
|
"description": "Open Source Shrimad Bhagavad Gita API including 21+ authors translation in Sanskrit/English/Hindi",
|
|
@@ -28875,6 +28885,16 @@
|
|
|
28875
28885
|
"openapiSpec": null,
|
|
28876
28886
|
"sources": ["github-public-apis"]
|
|
28877
28887
|
},
|
|
28888
|
+
{
|
|
28889
|
+
"name": "QR & Barcode",
|
|
28890
|
+
"description": "QR codes and barcodes (Code 128, EAN-13, Data Matrix, PDF417 + more). SVG or PNG output",
|
|
28891
|
+
"link": "https://solsigs.com/qrapi/",
|
|
28892
|
+
"auth": null,
|
|
28893
|
+
"cors": "yes",
|
|
28894
|
+
"categories": ["Development"],
|
|
28895
|
+
"openapiSpec": null,
|
|
28896
|
+
"sources": ["github-public-apis"]
|
|
28897
|
+
},
|
|
28878
28898
|
{
|
|
28879
28899
|
"name": "QR code",
|
|
28880
28900
|
"description": "Create an easy to read QR code and URL shortener",
|
|
@@ -29763,7 +29783,7 @@
|
|
|
29763
29783
|
"cors": "unknown",
|
|
29764
29784
|
"categories": ["Email"],
|
|
29765
29785
|
"openapiSpec": null,
|
|
29766
|
-
"sources": ["github-public-apis"
|
|
29786
|
+
"sources": ["github-public-apis"]
|
|
29767
29787
|
},
|
|
29768
29788
|
{
|
|
29769
29789
|
"name": "Mailtrap",
|
|
@@ -30265,6 +30285,16 @@
|
|
|
30265
30285
|
"openapiSpec": null,
|
|
30266
30286
|
"sources": ["github-public-apis"]
|
|
30267
30287
|
},
|
|
30288
|
+
{
|
|
30289
|
+
"name": "EconPulse",
|
|
30290
|
+
"description": "Live economic data — CPI, PPI, energy, treasury rates, BTC premium",
|
|
30291
|
+
"link": "https://econpulse.io",
|
|
30292
|
+
"auth": "apiKey",
|
|
30293
|
+
"cors": "yes",
|
|
30294
|
+
"categories": ["Finance"],
|
|
30295
|
+
"openapiSpec": null,
|
|
30296
|
+
"sources": ["github-public-apis"]
|
|
30297
|
+
},
|
|
30268
30298
|
{
|
|
30269
30299
|
"name": "Fed Treasury",
|
|
30270
30300
|
"description": "U.S. Department of the Treasury Data",
|
|
@@ -32175,6 +32205,16 @@
|
|
|
32175
32205
|
"openapiSpec": null,
|
|
32176
32206
|
"sources": ["github-public-apis"]
|
|
32177
32207
|
},
|
|
32208
|
+
{
|
|
32209
|
+
"name": "HackMyIP",
|
|
32210
|
+
"description": "IP geolocation, ISP and privacy/VPN scoring, email breach checks, DNS and WHOIS lookups",
|
|
32211
|
+
"link": "https://hackmyip.com/api",
|
|
32212
|
+
"auth": null,
|
|
32213
|
+
"cors": "yes",
|
|
32214
|
+
"categories": ["Geocoding"],
|
|
32215
|
+
"openapiSpec": null,
|
|
32216
|
+
"sources": ["github-public-apis"]
|
|
32217
|
+
},
|
|
32178
32218
|
{
|
|
32179
32219
|
"name": "HelloSalut",
|
|
32180
32220
|
"description": "Get hello translation following user language",
|
|
@@ -33473,7 +33513,7 @@
|
|
|
33473
33513
|
"cors": "unknown",
|
|
33474
33514
|
"categories": ["Government"],
|
|
33475
33515
|
"openapiSpec": null,
|
|
33476
|
-
"sources": ["github-public-apis"
|
|
33516
|
+
"sources": ["github-public-apis"]
|
|
33477
33517
|
},
|
|
33478
33518
|
{
|
|
33479
33519
|
"name": "Open Government, Switzerland",
|
|
@@ -35145,6 +35185,16 @@
|
|
|
35145
35185
|
"openapiSpec": null,
|
|
35146
35186
|
"sources": ["github-public-apis"]
|
|
35147
35187
|
},
|
|
35188
|
+
{
|
|
35189
|
+
"name": "Open Scholarships",
|
|
35190
|
+
"description": "Free, openly-licensed directory of US scholarships and student aid from official sources",
|
|
35191
|
+
"link": "https://scholarships.grudged.io",
|
|
35192
|
+
"auth": null,
|
|
35193
|
+
"cors": "yes",
|
|
35194
|
+
"categories": ["Open Data"],
|
|
35195
|
+
"openapiSpec": null,
|
|
35196
|
+
"sources": ["github-public-apis"]
|
|
35197
|
+
},
|
|
35148
35198
|
{
|
|
35149
35199
|
"name": "openAFRICA",
|
|
35150
35200
|
"description": "Large datasets repository of African open data",
|
|
@@ -38015,6 +38065,16 @@
|
|
|
38015
38065
|
"openapiSpec": null,
|
|
38016
38066
|
"sources": ["github-public-apis"]
|
|
38017
38067
|
},
|
|
38068
|
+
{
|
|
38069
|
+
"name": "Audexum",
|
|
38070
|
+
"description": "Text-to-speech REST API with 43 voices and 33 languages",
|
|
38071
|
+
"link": "https://audexum.com/docs",
|
|
38072
|
+
"auth": "apiKey",
|
|
38073
|
+
"cors": "yes",
|
|
38074
|
+
"categories": ["Text Analysis"],
|
|
38075
|
+
"openapiSpec": null,
|
|
38076
|
+
"sources": ["github-public-apis"]
|
|
38077
|
+
},
|
|
38018
38078
|
{
|
|
38019
38079
|
"name": "Cloudmersive Natural Language Processing",
|
|
38020
38080
|
"description": "Natural language processing and text analysis",
|
|
@@ -38495,6 +38555,16 @@
|
|
|
38495
38555
|
"openapiSpec": null,
|
|
38496
38556
|
"sources": ["github-public-apis"]
|
|
38497
38557
|
},
|
|
38558
|
+
{
|
|
38559
|
+
"name": "Orizn Visa",
|
|
38560
|
+
"description": "Visa requirements for 199 countries, 39K+ passport-destination pairs in 15 languages",
|
|
38561
|
+
"link": "https://visa.orizn.app",
|
|
38562
|
+
"auth": "apiKey",
|
|
38563
|
+
"cors": "yes",
|
|
38564
|
+
"categories": ["Transportation"],
|
|
38565
|
+
"openapiSpec": null,
|
|
38566
|
+
"sources": ["github-public-apis"]
|
|
38567
|
+
},
|
|
38498
38568
|
{
|
|
38499
38569
|
"name": "OpenSky Network",
|
|
38500
38570
|
"description": "Free real-time ADS-B aviation data",
|
|
@@ -39085,6 +39155,16 @@
|
|
|
39085
39155
|
"openapiSpec": null,
|
|
39086
39156
|
"sources": ["github-public-apis", "publicapis-dev"]
|
|
39087
39157
|
},
|
|
39158
|
+
{
|
|
39159
|
+
"name": "RedirHub",
|
|
39160
|
+
"description": "URL redirect management with custom domains, HTTPS, analytics, and REST API",
|
|
39161
|
+
"link": "https://redirhub.com",
|
|
39162
|
+
"auth": "apiKey",
|
|
39163
|
+
"cors": "yes",
|
|
39164
|
+
"categories": ["Url Shorteners"],
|
|
39165
|
+
"openapiSpec": null,
|
|
39166
|
+
"sources": ["github-public-apis"]
|
|
39167
|
+
},
|
|
39088
39168
|
{
|
|
39089
39169
|
"name": "Short Link",
|
|
39090
39170
|
"description": "Short URLs support so many domains",
|
|
@@ -41105,6 +41185,16 @@
|
|
|
41105
41185
|
"openapiSpec": null,
|
|
41106
41186
|
"sources": ["publicapis-dev"]
|
|
41107
41187
|
},
|
|
41188
|
+
{
|
|
41189
|
+
"name": "FreeCustom.Email",
|
|
41190
|
+
"description": "Disposable email inboxes with OTP extraction and real-time WebSocket events.",
|
|
41191
|
+
"link": "https://freecustom.email",
|
|
41192
|
+
"auth": "apiKey",
|
|
41193
|
+
"cors": null,
|
|
41194
|
+
"categories": ["Email"],
|
|
41195
|
+
"openapiSpec": null,
|
|
41196
|
+
"sources": ["publicapis-dev"]
|
|
41197
|
+
},
|
|
41108
41198
|
{
|
|
41109
41199
|
"name": "NoParam",
|
|
41110
41200
|
"description": "Email validation API for real-time & bulk checking.",
|
|
@@ -41905,6 +41995,16 @@
|
|
|
41905
41995
|
"openapiSpec": null,
|
|
41906
41996
|
"sources": ["publicapis-dev"]
|
|
41907
41997
|
},
|
|
41998
|
+
{
|
|
41999
|
+
"name": "BuildData",
|
|
42000
|
+
"description": "Canadian construction and development data from 17 cities.",
|
|
42001
|
+
"link": "https://builddata.ca",
|
|
42002
|
+
"auth": "apiKey",
|
|
42003
|
+
"cors": null,
|
|
42004
|
+
"categories": ["Government"],
|
|
42005
|
+
"openapiSpec": null,
|
|
42006
|
+
"sources": ["publicapis-dev"]
|
|
42007
|
+
},
|
|
41908
42008
|
{
|
|
41909
42009
|
"name": "civicAPI",
|
|
41910
42010
|
"description": "Provides live and historic election results for races across the world.",
|
package/package.json
CHANGED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { getCategories } from './search.js';
|
|
2
|
-
import { formatList } from './formatter.js';
|
|
3
|
-
import { initColors } from './colors.js';
|
|
4
|
-
export function handleCategories(apis, argv, config) {
|
|
5
|
-
const noColor = argv.color === false;
|
|
6
|
-
initColors(noColor ?? !config.colors);
|
|
7
|
-
const categories = getCategories(apis);
|
|
8
|
-
const output = formatList(categories, 'categories', {
|
|
9
|
-
sort: argv.sort,
|
|
10
|
-
output: argv.output,
|
|
11
|
-
});
|
|
12
|
-
console.log(output);
|
|
13
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
let useColor = false;
|
|
3
|
-
export function initColors(noColor) {
|
|
4
|
-
if (noColor === true || process.env.NO_COLOR) {
|
|
5
|
-
useColor = false;
|
|
6
|
-
}
|
|
7
|
-
else {
|
|
8
|
-
useColor = true;
|
|
9
|
-
}
|
|
10
|
-
chalk.level = useColor ? 3 : 0;
|
|
11
|
-
}
|
|
12
|
-
export const color = {
|
|
13
|
-
bold: (text) => (useColor ? chalk.bold(text) : text),
|
|
14
|
-
dim: (text) => (useColor ? chalk.dim(text) : text),
|
|
15
|
-
cyan: (text) => (useColor ? chalk.cyan(text) : text),
|
|
16
|
-
green: (text) => (useColor ? chalk.green(text) : text),
|
|
17
|
-
yellow: (text) => (useColor ? chalk.yellow(text) : text),
|
|
18
|
-
red: (text) => (useColor ? chalk.red(text) : text),
|
|
19
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { readFile, writeFile, access } from 'node:fs/promises';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
export const CONFIG_PATH = join(homedir(), '.ls-apis');
|
|
5
|
-
const DEFAULTS = {
|
|
6
|
-
limit: 20,
|
|
7
|
-
descriptionMaxLength: 250,
|
|
8
|
-
colors: true,
|
|
9
|
-
};
|
|
10
|
-
export async function loadConfig() {
|
|
11
|
-
try {
|
|
12
|
-
const raw = await readFile(CONFIG_PATH, 'utf-8');
|
|
13
|
-
const parsed = JSON.parse(raw);
|
|
14
|
-
return {
|
|
15
|
-
limit: parsed.limit ?? DEFAULTS.limit,
|
|
16
|
-
descriptionMaxLength: parsed.descriptionMaxLength ?? DEFAULTS.descriptionMaxLength,
|
|
17
|
-
colors: parsed.colors ?? DEFAULTS.colors,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
catch {
|
|
21
|
-
await ensureConfigExists();
|
|
22
|
-
return DEFAULTS;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async function ensureConfigExists() {
|
|
26
|
-
try {
|
|
27
|
-
await access(CONFIG_PATH);
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
await writeFile(CONFIG_PATH, JSON.stringify(DEFAULTS, null, 2) + '\n');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
export async function getConfig() {
|
|
34
|
-
const config = await loadConfig();
|
|
35
|
-
return { config, filePath: CONFIG_PATH };
|
|
36
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { color } from './colors.js';
|
|
2
|
-
function truncate(text, maxLength) {
|
|
3
|
-
if (text.length <= maxLength) {
|
|
4
|
-
return text;
|
|
5
|
-
}
|
|
6
|
-
return text.slice(0, maxLength) + '...';
|
|
7
|
-
}
|
|
8
|
-
function formatText(results, total, limit, options) {
|
|
9
|
-
const maxLen = options.descriptionMaxLength ?? 250;
|
|
10
|
-
const lines = [];
|
|
11
|
-
lines.push(color.bold(`Found ${total} APIs:`));
|
|
12
|
-
for (const api of results.slice(0, limit)) {
|
|
13
|
-
lines.push(color.cyan(` ${api.name}`));
|
|
14
|
-
lines.push(` ${color.dim('Description:')} ${truncate(api.description ?? 'No description', maxLen)}`);
|
|
15
|
-
lines.push(` ${color.dim('Link:')} ${api.link}`);
|
|
16
|
-
// Test missing braces - should trigger ESLint error
|
|
17
|
-
if (api.auth !== undefined && api.auth !== null) {
|
|
18
|
-
lines.push(` ${color.dim('Auth:')} ${color.yellow(api.auth)}`);
|
|
19
|
-
}
|
|
20
|
-
if (api.categories.length > 0) {
|
|
21
|
-
lines.push(` ${color.dim('Categories:')} ${color.green(api.categories.join(', '))}`);
|
|
22
|
-
}
|
|
23
|
-
if (api.openapiSpec !== undefined && api.openapiSpec !== null) {
|
|
24
|
-
lines.push(` ${color.dim('OpenAPI Spec:')} ${api.openapiSpec}`);
|
|
25
|
-
}
|
|
26
|
-
if (api.sources.length > 0) {
|
|
27
|
-
lines.push(` ${color.dim('Sources:')} ${api.sources.join(', ')}`);
|
|
28
|
-
}
|
|
29
|
-
lines.push('');
|
|
30
|
-
}
|
|
31
|
-
if (results.length > limit) {
|
|
32
|
-
lines.push(` ${color.dim('... and ' + (results.length - limit) + ' more')}`);
|
|
33
|
-
}
|
|
34
|
-
return lines.join('\n');
|
|
35
|
-
}
|
|
36
|
-
function formatJson(results, limit) {
|
|
37
|
-
return JSON.stringify(results.slice(0, limit), null, 2);
|
|
38
|
-
}
|
|
39
|
-
export function formatResults(results, total, limit, options) {
|
|
40
|
-
if (options.output === 'json') {
|
|
41
|
-
return formatJson(results, limit);
|
|
42
|
-
}
|
|
43
|
-
return formatText(results, total, limit, options);
|
|
44
|
-
}
|
|
45
|
-
export function formatList(items, label, options) {
|
|
46
|
-
const sorted = [...items.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
47
|
-
if (options.sort === 'count') {
|
|
48
|
-
sorted.sort((a, b) => b[1] - a[1]);
|
|
49
|
-
}
|
|
50
|
-
if (options.output === 'json') {
|
|
51
|
-
return JSON.stringify(sorted.map(([name, count]) => ({ name, count })), null, 2);
|
|
52
|
-
}
|
|
53
|
-
const lines = [`Found ${sorted.length} ${label}:`];
|
|
54
|
-
for (const [name, count] of sorted) {
|
|
55
|
-
lines.push(` ${name.padEnd(20)} (${count} APIs)`);
|
|
56
|
-
}
|
|
57
|
-
return lines.join('\n');
|
|
58
|
-
}
|
|
59
|
-
export function formatProviders(providers, options) {
|
|
60
|
-
const sorted = [...providers].sort((a, b) => a.name.localeCompare(b.name));
|
|
61
|
-
if (options.sort === 'count') {
|
|
62
|
-
sorted.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
63
|
-
}
|
|
64
|
-
if (options.output === 'json') {
|
|
65
|
-
return JSON.stringify(sorted, null, 2);
|
|
66
|
-
}
|
|
67
|
-
const lines = [`Found ${sorted.length} providers:`];
|
|
68
|
-
for (const provider of sorted) {
|
|
69
|
-
const countStr = provider.count !== undefined ? ` (${provider.count} APIs)` : '';
|
|
70
|
-
lines.push(` ${provider.name.padEnd(20)} ${color.dim(provider.url)}${countStr}`);
|
|
71
|
-
}
|
|
72
|
-
return lines.join('\n');
|
|
73
|
-
}
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { join, dirname } from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import yargs from 'yargs';
|
|
6
|
-
import { hideBin } from 'yargs/helpers';
|
|
7
|
-
import { initColors } from './colors.js';
|
|
8
|
-
import { loadConfig, getConfig } from './config.js';
|
|
9
|
-
import { search } from './search.js';
|
|
10
|
-
import { formatResults } from './formatter.js';
|
|
11
|
-
import { handleCategories } from './categories.js';
|
|
12
|
-
import { handleProviders } from './providers.js';
|
|
13
|
-
import { runQa } from './qa.js';
|
|
14
|
-
let version;
|
|
15
|
-
async function getVersion() {
|
|
16
|
-
if (!version) {
|
|
17
|
-
const packageJsonPath = join(dirname(fileURLToPath(import.meta.url)), '../package.json');
|
|
18
|
-
const pkg = JSON.parse(await readFile(packageJsonPath, 'utf-8'));
|
|
19
|
-
version = pkg.version;
|
|
20
|
-
}
|
|
21
|
-
return version;
|
|
22
|
-
}
|
|
23
|
-
export async function run(argv) {
|
|
24
|
-
const config = await loadConfig();
|
|
25
|
-
const ver = await getVersion();
|
|
26
|
-
const dataFile = join(dirname(fileURLToPath(import.meta.url)), '../data/apis.json');
|
|
27
|
-
const data = await readFile(dataFile, 'utf-8');
|
|
28
|
-
const { apis, providers } = JSON.parse(data);
|
|
29
|
-
let exitEarly = false;
|
|
30
|
-
const args = await yargs(argv)
|
|
31
|
-
.scriptName('ls-apis')
|
|
32
|
-
.strict()
|
|
33
|
-
.version(ver)
|
|
34
|
-
.alias('version', 'V')
|
|
35
|
-
.alias('version', 'v')
|
|
36
|
-
.usage('$0 <command> [options]')
|
|
37
|
-
.usage('$0 [options]')
|
|
38
|
-
.example('$0 -q weather', 'Search for weather APIs')
|
|
39
|
-
.example('$0 -c weather', 'Filter by weather category')
|
|
40
|
-
.example('$0 -q weather -c storage', 'Search weather in storage category')
|
|
41
|
-
.example('$0 -a oauth', 'Filter by OAuth auth')
|
|
42
|
-
.example('$0 -q weather -l 50', 'Limit results to 50')
|
|
43
|
-
.example('$0 -q weather -o json', 'Output as JSON')
|
|
44
|
-
.example('$0 -q weather -s name', 'Sort results by name')
|
|
45
|
-
.command({
|
|
46
|
-
command: 'categories',
|
|
47
|
-
describe: 'List all API categories',
|
|
48
|
-
builder: (yargs) => {
|
|
49
|
-
return yargs
|
|
50
|
-
.option('sort', {
|
|
51
|
-
alias: 's',
|
|
52
|
-
type: 'string',
|
|
53
|
-
choices: ['name', 'count'],
|
|
54
|
-
default: 'name',
|
|
55
|
-
describe: 'Sort by name or count',
|
|
56
|
-
})
|
|
57
|
-
.option('output', {
|
|
58
|
-
alias: 'o',
|
|
59
|
-
type: 'string',
|
|
60
|
-
choices: ['text', 'json'],
|
|
61
|
-
default: 'text',
|
|
62
|
-
describe: 'Output format',
|
|
63
|
-
});
|
|
64
|
-
},
|
|
65
|
-
handler: (argv) => {
|
|
66
|
-
handleCategories(apis, argv, config);
|
|
67
|
-
exitEarly = true;
|
|
68
|
-
},
|
|
69
|
-
})
|
|
70
|
-
.command({
|
|
71
|
-
command: 'providers',
|
|
72
|
-
describe: 'List all data providers',
|
|
73
|
-
builder: (yargs) => {
|
|
74
|
-
return yargs
|
|
75
|
-
.option('sort', {
|
|
76
|
-
alias: 's',
|
|
77
|
-
type: 'string',
|
|
78
|
-
choices: ['name', 'count'],
|
|
79
|
-
default: 'name',
|
|
80
|
-
describe: 'Sort by name or count',
|
|
81
|
-
})
|
|
82
|
-
.option('output', {
|
|
83
|
-
alias: 'o',
|
|
84
|
-
type: 'string',
|
|
85
|
-
choices: ['text', 'json'],
|
|
86
|
-
default: 'text',
|
|
87
|
-
describe: 'Output format',
|
|
88
|
-
});
|
|
89
|
-
},
|
|
90
|
-
handler: (argv) => {
|
|
91
|
-
handleProviders(providers, apis, argv, config);
|
|
92
|
-
exitEarly = true;
|
|
93
|
-
},
|
|
94
|
-
})
|
|
95
|
-
.command({
|
|
96
|
-
command: 'config',
|
|
97
|
-
describe: 'Show config settings',
|
|
98
|
-
handler: async () => {
|
|
99
|
-
const { config, filePath } = await getConfig();
|
|
100
|
-
console.log(`Config file: ${filePath}`);
|
|
101
|
-
console.log(JSON.stringify(config, null, 2));
|
|
102
|
-
exitEarly = true;
|
|
103
|
-
},
|
|
104
|
-
})
|
|
105
|
-
.command({
|
|
106
|
-
command: 'qa',
|
|
107
|
-
describe: 'Run QA validation on apis.json',
|
|
108
|
-
builder: (yargs) => {
|
|
109
|
-
return yargs.option('file', {
|
|
110
|
-
alias: 'f',
|
|
111
|
-
type: 'string',
|
|
112
|
-
describe: 'Custom output file path (default: qa-output/issues.json)',
|
|
113
|
-
});
|
|
114
|
-
},
|
|
115
|
-
handler: async (argv) => {
|
|
116
|
-
await runQa(config.descriptionMaxLength, argv.file);
|
|
117
|
-
exitEarly = true;
|
|
118
|
-
},
|
|
119
|
-
})
|
|
120
|
-
.option('query', {
|
|
121
|
-
alias: 'q',
|
|
122
|
-
type: 'string',
|
|
123
|
-
describe: 'Search query (filters name, description)',
|
|
124
|
-
})
|
|
125
|
-
.option('category', { alias: 'c', type: 'string', describe: 'Filter by category' })
|
|
126
|
-
.option('auth', {
|
|
127
|
-
alias: 'a',
|
|
128
|
-
type: 'string',
|
|
129
|
-
describe: 'Filter by auth (apiKey, OAuth, no)',
|
|
130
|
-
})
|
|
131
|
-
.option('limit', {
|
|
132
|
-
alias: 'l',
|
|
133
|
-
type: 'number',
|
|
134
|
-
default: config.limit,
|
|
135
|
-
describe: 'Max results to show',
|
|
136
|
-
})
|
|
137
|
-
.option('output', {
|
|
138
|
-
alias: 'o',
|
|
139
|
-
type: 'string',
|
|
140
|
-
choices: ['text', 'json'],
|
|
141
|
-
default: 'text',
|
|
142
|
-
describe: 'Output format',
|
|
143
|
-
})
|
|
144
|
-
.option('sort', {
|
|
145
|
-
alias: 's',
|
|
146
|
-
type: 'string',
|
|
147
|
-
choices: ['name', 'category', 'auth'],
|
|
148
|
-
describe: 'Sort results by field',
|
|
149
|
-
})
|
|
150
|
-
.option('no-color', { type: 'boolean', describe: 'Disable colors in output' })
|
|
151
|
-
.help()
|
|
152
|
-
.alias('help', 'h')
|
|
153
|
-
.alias('help', '?')
|
|
154
|
-
.parse();
|
|
155
|
-
if (exitEarly) {
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
const noColor = args.color === false;
|
|
159
|
-
initColors(noColor ?? !config.colors);
|
|
160
|
-
const results = search(apis, {
|
|
161
|
-
query: args.query,
|
|
162
|
-
category: args.category,
|
|
163
|
-
auth: args.auth,
|
|
164
|
-
sort: args.sort,
|
|
165
|
-
limit: args.limit,
|
|
166
|
-
});
|
|
167
|
-
const output = formatResults(results, results.length, args.limit, {
|
|
168
|
-
output: args.output,
|
|
169
|
-
descriptionMaxLength: config.descriptionMaxLength,
|
|
170
|
-
});
|
|
171
|
-
console.log(output);
|
|
172
|
-
}
|
|
173
|
-
if (process.argv[1] && !process.argv[1].includes('vitest')) {
|
|
174
|
-
run(hideBin(process.argv)).catch((err) => {
|
|
175
|
-
console.error('Error:', err.message);
|
|
176
|
-
process.exit(1);
|
|
177
|
-
});
|
|
178
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { formatProviders } from './formatter.js';
|
|
2
|
-
import { initColors } from './colors.js';
|
|
3
|
-
export function handleProviders(providers, apis, argv, config) {
|
|
4
|
-
const noColor = argv.color === false;
|
|
5
|
-
initColors(noColor ?? !config.colors);
|
|
6
|
-
const counts = new Map();
|
|
7
|
-
for (const api of apis) {
|
|
8
|
-
for (const source of api.sources) {
|
|
9
|
-
counts.set(source, (counts.get(source) ?? 0) + 1);
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
const providersWithCounts = providers.map((p) => ({
|
|
13
|
-
...p,
|
|
14
|
-
count: counts.get(p.name) ?? 0,
|
|
15
|
-
}));
|
|
16
|
-
const output = formatProviders(providersWithCounts, {
|
|
17
|
-
sort: argv.sort,
|
|
18
|
-
output: argv.output,
|
|
19
|
-
});
|
|
20
|
-
console.log(output);
|
|
21
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { realpathSync } from 'node:fs';
|
|
5
|
-
export async function runQa(descriptionMaxLength, outputFile) {
|
|
6
|
-
const currentDir = dirname(realpathSync(fileURLToPath(import.meta.url)));
|
|
7
|
-
const projectRoot = join(currentDir, '../../..');
|
|
8
|
-
const script = join(projectRoot, 'packages/aggregator/src/qa/index.ts');
|
|
9
|
-
const args = ['npx', 'tsx', script];
|
|
10
|
-
if (outputFile) {
|
|
11
|
-
args.push('--output', outputFile);
|
|
12
|
-
}
|
|
13
|
-
execSync(args.join(' '), { cwd: projectRoot, stdio: 'inherit' });
|
|
14
|
-
}
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
export function search(apis, options) {
|
|
2
|
-
let results = apis;
|
|
3
|
-
if (options.query) {
|
|
4
|
-
const q = options.query.toLowerCase();
|
|
5
|
-
results = results.filter((api) => api.name.toLowerCase().includes(q) || api.description?.toLowerCase().includes(q));
|
|
6
|
-
}
|
|
7
|
-
if (options.category) {
|
|
8
|
-
const cat = options.category.toLowerCase();
|
|
9
|
-
results = results.filter((api) => api.categories.some((c) => c.toLowerCase().includes(cat)));
|
|
10
|
-
}
|
|
11
|
-
if (options.auth) {
|
|
12
|
-
const auth = options.auth.toLowerCase();
|
|
13
|
-
results = results.filter((api) => {
|
|
14
|
-
if (auth === 'no') {
|
|
15
|
-
return !api.auth;
|
|
16
|
-
}
|
|
17
|
-
return api.auth?.toLowerCase().includes(auth);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
if (options.sort) {
|
|
21
|
-
results = [...results].sort((a, b) => {
|
|
22
|
-
switch (options.sort) {
|
|
23
|
-
case 'name':
|
|
24
|
-
return a.name.localeCompare(b.name);
|
|
25
|
-
case 'category': {
|
|
26
|
-
const catA = a.categories[0] ?? '';
|
|
27
|
-
const catB = b.categories[0] ?? '';
|
|
28
|
-
return catA.localeCompare(catB);
|
|
29
|
-
}
|
|
30
|
-
case 'auth': {
|
|
31
|
-
const authA = a.auth ?? '';
|
|
32
|
-
const authB = b.auth ?? '';
|
|
33
|
-
return authA.localeCompare(authB);
|
|
34
|
-
}
|
|
35
|
-
default:
|
|
36
|
-
return 0;
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
return results;
|
|
41
|
-
}
|
|
42
|
-
export function getCategories(apis) {
|
|
43
|
-
const map = new Map();
|
|
44
|
-
for (const api of apis) {
|
|
45
|
-
for (const cat of api.categories) {
|
|
46
|
-
map.set(cat, (map.get(cat) ?? 0) + 1);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
return map;
|
|
50
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|