apx-toolkit
Version:
Automatically discover APIs and generate complete integration packages: code in 12 languages, TypeScript types, test suites, SDK packages, API documentation, mock servers, performance reports, and contract tests. Saves 2-4 weeks of work in seconds.
259 lines • 10.8 kB
JavaScript
#!/usr/bin/env node
/**
* APX CLI - Command Line Interface for APX Toolkit
* Allows developers to run APX locally without Apify platform
*/
import { runAPXCore } from './core-runner.js';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import * as fs from 'fs';
import * as path from 'path';
import { sanitizePath, sanitizeFilename, validateURL } from './utils/security.js';
async function main() {
const argv = await yargs(hideBin(process.argv))
.option('url', {
alias: 'u',
type: 'string',
description: 'The starting URL for API discovery',
demandOption: true,
})
.option('output', {
alias: 'o',
type: 'string',
description: 'Local directory to save generated files (default: ./apx-output)',
default: './apx-output',
})
.option('login-url', {
type: 'string',
description: 'URL to initiate the OAuth/Login flow',
})
.option('api-key', {
type: 'string',
description: 'API key for authentication (added as X-API-Key header)',
})
.option('bearer-token', {
type: 'string',
description: 'Bearer token for authentication (added as Authorization header)',
})
.option('max-pages', {
type: 'number',
description: 'Maximum pages to scrape (default: 100)',
default: 100,
})
.option('max-concurrency', {
type: 'number',
description: 'Maximum concurrent requests (default: 5)',
default: 5,
})
.option('discovery-timeout', {
type: 'number',
description: 'Discovery timeout in milliseconds (default: 10000)',
default: 10000,
})
.option('min-response-size', {
type: 'number',
description: 'Minimum response size in bytes (default: 1000)',
default: 1000,
})
.option('api-patterns', {
type: 'string',
description: 'Comma-separated URL patterns to match (e.g., "/api/,/v1/data")',
})
.option('data-path', {
type: 'string',
description: 'JSONPath to extract data (e.g., "data.items")',
})
.option('pagination-type', {
type: 'string',
choices: ['auto', 'offset', 'page', 'cursor'],
description: 'Type of pagination to use (default: auto)',
default: 'auto',
})
.option('export-formats', {
type: 'string',
description: 'Comma-separated export formats (openapi,postman,curl,insomnia)',
default: 'openapi,postman,curl',
})
.option('generate-docs', {
type: 'boolean',
description: 'Generate API documentation (default: true)',
default: true,
})
.option('interaction-simulation', {
type: 'boolean',
description: 'Enable interaction simulation (default: true)',
default: true,
})
.option('interaction-wait-time', {
type: 'number',
description: 'Wait time after interactions in milliseconds (default: 2000)',
default: 2000,
})
.option('oauth-flow', {
type: 'boolean',
description: 'Enable OAuth flow (requires login-url)',
default: false,
})
.help()
.alias('help', 'h')
.version()
.alias('version', 'v')
.parse();
// Construct the input object for the core runner
const coreInput = {
startUrls: [{ url: argv.url }],
maxPages: argv['max-pages'],
maxConcurrency: argv['max-concurrency'],
discoveryTimeout: argv['discovery-timeout'],
minResponseSize: argv['min-response-size'],
dataPath: argv['data-path'],
paginationType: argv['pagination-type'],
generateDocumentation: argv['generate-docs'],
exportFormats: argv['export-formats']?.split(',').map(f => f.trim()),
enableInteractionSimulation: argv['interaction-simulation'],
interactionWaitTime: argv['interaction-wait-time'],
loginUrl: argv['login-url'],
oauthFlow: argv['oauth-flow'],
};
// Add API patterns if provided
if (argv['api-patterns']) {
coreInput.apiPatterns = argv['api-patterns'].split(',').map(p => p.trim());
}
// Add authentication if provided
if (argv['api-key']) {
coreInput.apiKey = argv['api-key'];
}
if (argv['bearer-token']) {
coreInput.bearerToken = argv['bearer-token'];
}
try {
console.log('🚀 APX CLI - The API Toolkit');
console.log('='.repeat(60));
console.log(`📋 Target URL: ${argv.url}`);
console.log(`📁 Output Directory: ${argv.output}`);
console.log('='.repeat(60));
console.log('');
const result = await runAPXCore(coreInput, {
onProgress: (msg) => console.log(msg),
onError: (err) => console.error(`❌ Error: ${err.message}`),
});
// Save results to local files
// Validate and sanitize output directory
const outputDir = sanitizePath(argv.output || './apx-output', process.cwd());
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}
// Validate URL
try {
validateURL(argv.url, true); // Allow localhost for CLI
}
catch (error) {
console.error(`❌ Invalid URL: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
}
// Save code snippets
const codeSnippetsDir = path.join(outputDir, 'code-snippets');
if (!fs.existsSync(codeSnippetsDir)) {
fs.mkdirSync(codeSnippetsDir, { recursive: true });
}
for (const [apiKey, snippets] of Object.entries(result.artifacts.codeSnippets)) {
const safeKey = apiKey.replace(/[^a-zA-Z0-9]/g, '_');
fs.writeFileSync(path.join(codeSnippetsDir, `${safeKey}.json`), JSON.stringify(snippets, null, 2));
}
// Save TypeScript types
if (result.artifacts.typescriptTypes) {
fs.writeFileSync(path.join(outputDir, 'types.d.ts'), result.artifacts.typescriptTypes);
}
// Save test suites
if (result.artifacts.testSuites.length > 0) {
const testSuitesDir = path.join(outputDir, 'test-suites');
if (!fs.existsSync(testSuitesDir)) {
fs.mkdirSync(testSuitesDir, { recursive: true });
}
for (const suite of result.artifacts.testSuites) {
const filename = sanitizeFilename(suite.filename || `test-${Date.now()}.js`);
fs.writeFileSync(path.join(testSuitesDir, filename), suite.code || '');
}
}
// Save SDK packages
if (result.artifacts.sdkPackages.length > 0) {
const sdkDir = path.join(outputDir, 'sdk-packages');
if (!fs.existsSync(sdkDir)) {
fs.mkdirSync(sdkDir, { recursive: true });
}
for (const pkg of result.artifacts.sdkPackages) {
const pkgDir = path.join(sdkDir, pkg.packageName || 'sdk');
if (!fs.existsSync(pkgDir)) {
fs.mkdirSync(pkgDir, { recursive: true });
}
for (const [filename, content] of Object.entries(pkg.files || {})) {
const safeFilename = sanitizeFilename(filename);
const filePath = sanitizePath(path.join(pkgDir, safeFilename), pkgDir);
const dir = path.dirname(filePath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const fileContent = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
fs.writeFileSync(filePath, fileContent);
}
}
}
// Save documentation
if (result.artifacts.documentation.length > 0) {
const docsDir = path.join(outputDir, 'documentation');
if (!fs.existsSync(docsDir)) {
fs.mkdirSync(docsDir, { recursive: true });
}
for (const doc of result.artifacts.documentation) {
const filename = sanitizeFilename(doc.filename || `doc-${doc.format}.${doc.format === 'openapi' ? 'json' : 'json'}`);
const content = typeof doc.content === 'string' ? doc.content : JSON.stringify(doc.content, null, 2);
fs.writeFileSync(path.join(docsDir, filename), content);
}
}
// Save examples
if (result.artifacts.examples.length > 0) {
fs.writeFileSync(path.join(outputDir, 'examples.json'), JSON.stringify(result.artifacts.examples, null, 2));
}
// Save summary
fs.writeFileSync(path.join(outputDir, 'summary.json'), JSON.stringify({
summary: result.summary,
statistics: result.statistics,
}, null, 2));
// Save all extracted data
if (result.data.length > 0) {
fs.writeFileSync(path.join(outputDir, 'data.json'), JSON.stringify(result.data, null, 2));
}
console.log('');
console.log('🎉 APX Toolkit run successful!');
console.log('='.repeat(60));
console.log(`📁 Artifacts saved to: ${outputDir}`);
console.log(`📊 Summary:`);
console.log(` APIs Discovered: ${result.summary.apisDiscovered} (REST, GraphQL, WebSocket)`);
console.log(` Requests Processed: ${result.summary.requestsProcessed}`);
console.log(` Items Extracted: ${result.summary.itemsExtracted}`);
console.log(` Total Duration: ${result.summary.totalDuration.toFixed(1)}s`);
console.log('');
console.log(`📂 Generated Files:`);
console.log(` - Code Snippets: ${Object.keys(result.artifacts.codeSnippets).length} API(s)`);
console.log(` - TypeScript Types: ${result.artifacts.typescriptTypes ? 'Yes' : 'No'}`);
console.log(` - Test Suites: ${result.artifacts.testSuites.length}`);
console.log(` - SDK Packages: ${result.artifacts.sdkPackages.length}`);
console.log(` - Documentation: ${result.artifacts.documentation.length} format(s)`);
console.log('');
}
catch (error) {
console.error('');
console.error('❌ APX CLI encountered a fatal error');
console.error('='.repeat(60));
console.error(error instanceof Error ? error.message : String(error));
if (error instanceof Error && error.stack) {
console.error('');
console.error('Stack trace:');
console.error(error.stack);
}
console.error('');
process.exit(1);
}
}
main();
//# sourceMappingURL=cli.js.map