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.
390 lines (368 loc) • 14.5 kB
JavaScript
/**
* SDK Package Generator
* Generates complete SDK packages ready to publish
* Makes the tool a developer's dream with production-ready SDKs
*/
/**
* Generates complete SDK packages for discovered APIs
*/
export function generateSDKPackages(apis, packageName, baseUrl) {
const packages = [];
if (apis.length === 0) {
return packages;
}
const url = new URL(apis[0].baseUrl);
const defaultPackageName = packageName || `api-client-${url.hostname.replace(/\./g, '-')}`;
const apiBaseUrl = baseUrl || `${url.protocol}//${url.host}`;
packages.push(generateTypeScriptSDK(apis, defaultPackageName, apiBaseUrl));
packages.push(generatePythonSDK(apis, defaultPackageName, apiBaseUrl));
packages.push(generateGoSDK(apis, defaultPackageName, apiBaseUrl));
return packages;
}
function generateTypeScriptSDK(apis, packageName, baseUrl) {
const files = {};
// package.json
files['package.json'] = JSON.stringify({
name: packageName,
version: '1.0.0',
description: 'Auto-generated API client SDK',
main: 'dist/index.js',
types: 'dist/index.d.ts',
scripts: {
build: 'tsc',
test: 'jest',
'test:ci': 'jest --ci --coverage',
},
keywords: ['api', 'client', 'sdk', 'auto-generated'],
author: '',
license: 'MIT',
dependencies: {},
devDependencies: {
'@types/node': '^20.0.0',
'@types/jest': '^29.5.0',
typescript: '^5.0.0',
jest: '^29.5.0',
'ts-jest': '^29.1.0',
},
}, null, 2);
// tsconfig.json
files['tsconfig.json'] = JSON.stringify({
compilerOptions: {
target: 'ES2020',
module: 'commonjs',
lib: ['ES2020'],
outDir: './dist',
rootDir: './src',
strict: true,
esModuleInterop: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
declaration: true,
declarationMap: true,
},
include: ['src/**/*'],
exclude: ['node_modules', 'dist'],
}, null, 2);
// src/index.ts
let indexCode = `// Auto-generated API Client SDK\n`;
indexCode += `// Generated by APX\n\n`;
indexCode += `export class ApiClient {\n`;
indexCode += ` private baseUrl: string;\n`;
indexCode += ` private headers: Record<string, string>;\n\n`;
indexCode += ` constructor(baseUrl: string = '${baseUrl}', headers: Record<string, string> = {}) {\n`;
indexCode += ` this.baseUrl = baseUrl;\n`;
indexCode += ` this.headers = headers;\n`;
indexCode += ` }\n\n`;
// Generate methods for each API
for (const api of apis) {
const url = new URL(api.baseUrl);
const methodName = generateMethodName(url.pathname, api.method);
indexCode += generateTypeScriptMethod(api, methodName);
}
indexCode += `}\n\n`;
indexCode += `export default ApiClient;\n`;
files['src/index.ts'] = indexCode;
// README.md
files['README.md'] = `# ${packageName}\n\nAuto-generated API client SDK.\n\n## Installation\n\n\`\`\`bash\nnpm install ${packageName}\n\`\`\`\n\n## Usage\n\n\`\`\`typescript\nimport ApiClient from '${packageName}';\n\nconst client = new ApiClient();\nconst data = await client.getData();\n\`\`\`\n`;
return {
language: 'typescript',
packageName,
files,
description: 'TypeScript SDK with npm package files',
};
}
function generatePythonSDK(apis, packageName, baseUrl) {
const files = {};
// pyproject.toml
files['pyproject.toml'] = `[build-system]\nrequires = ["setuptools>=61.0"]\nbuild-backend = "setuptools.build_meta"\n\n[project]\nname = "${packageName}"\nversion = "1.0.0"\ndescription = "Auto-generated API client SDK"\nrequires-python = ">=3.8"\ndependencies = [\n "requests>=2.28.0",\n]\n\n[project.optional-dependencies]\ndev = [\n "pytest>=7.0.0",\n]\n`;
// src/__init__.py
let initCode = `# Auto-generated API Client SDK\n`;
initCode += `# Generated by APX\n\n`;
initCode += `import requests\nfrom typing import Dict, Any, Optional\n\n`;
initCode += `class ApiClient:\n`;
initCode += ` def __init__(self, base_url: str = '${baseUrl}', headers: Optional[Dict[str, str]] = None):\n`;
initCode += ` self.base_url = base_url\n`;
initCode += ` self.headers = headers or {}\n\n`;
// Generate methods for each API
for (const api of apis) {
const url = new URL(api.baseUrl);
const methodName = generateMethodName(url.pathname, api.method);
initCode += generatePythonMethod(api, methodName);
}
files['src/__init__.py'] = initCode;
// README.md
files['README.md'] = `# ${packageName}\n\nAuto-generated API client SDK.\n\n## Installation\n\n\`\`\`bash\npip install ${packageName}\n\`\`\`\n\n## Usage\n\n\`\`\`python\nfrom ${packageName} import ApiClient\n\nclient = ApiClient()\ndata = client.get_data()\n\`\`\`\n\n## CI/CD\n\nThis package includes GitHub Actions workflows for automated testing and publishing.\n`;
// GitHub Actions workflow
files['.github/workflows/ci.yml'] = `name: CI
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python \${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: \${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
run: |
pip install -e ".[dev]"
- name: Test with pytest
run: |
pytest --cov=. --cov-report=xml
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.xml
fail_ci_if_error: false
publish:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install build tools
run: pip install build twine
- name: Build package
run: python -m build
- name: Publish to PyPI
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: \${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
`;
return {
language: 'python',
packageName,
files,
description: 'Python SDK with PyPI-ready package files and CI/CD',
};
}
function generateGoSDK(apis, packageName, baseUrl) {
const files = {};
// go.mod
files['go.mod'] = `module ${packageName}\n\ngo 1.21\n\nrequire (\n\tgithub.com/stretchr/testify v1.8.4\n)\n`;
// client.go
let clientCode = `// Auto-generated API Client SDK\n`;
clientCode += `// Generated by Smart API Finder & Documenter\n\n`;
clientCode += `package ${packageName.replace(/-/g, '')}\n\n`;
clientCode += `import (\n`;
clientCode += `\t"bytes"\n`;
clientCode += `\t"encoding/json"\n`;
clientCode += `\t"fmt"\n`;
clientCode += `\t"net/http"\n`;
clientCode += `\t"net/url"\n`;
clientCode += `)\n\n`;
clientCode += `type ApiClient struct {\n`;
clientCode += `\tBaseURL string\n`;
clientCode += `\tHeaders map[string]string\n`;
clientCode += `\tClient *http.Client\n`;
clientCode += `}\n\n`;
clientCode += `func NewApiClient(baseURL string, headers map[string]string) *ApiClient {\n`;
clientCode += `\tif baseURL == "" {\n`;
clientCode += `\t\tbaseURL = "${baseUrl}"\n`;
clientCode += `\t}\n`;
clientCode += `\treturn &ApiClient{\n`;
clientCode += `\t\tBaseURL: baseURL,\n`;
clientCode += `\t\tHeaders: headers,\n`;
clientCode += `\t\tClient: &http.Client{},\n`;
clientCode += `\t}\n`;
clientCode += `}\n\n`;
// Generate methods for each API
for (const api of apis) {
const url = new URL(api.baseUrl);
const methodName = generateMethodName(url.pathname, api.method);
clientCode += generateGoMethod(api, methodName);
}
files['client.go'] = clientCode;
// README.md
files['README.md'] = `# ${packageName}\n\nAuto-generated API client SDK.\n\n## Installation\n\n\`\`\`bash\ngo get ${packageName}\n\`\`\`\n\n## Usage\n\n\`\`\`go\nimport "${packageName}"\n\nclient := ${packageName}.NewApiClient("", nil)\ndata, err := client.GetData()\n\`\`\`\n\n## CI/CD\n\nThis package includes GitHub Actions workflows for automated testing.\n`;
// GitHub Actions workflow
files['.github/workflows/ci.yml'] = `name: CI
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
go-version: ["1.19", "1.20", "1.21"]
steps:
- uses: actions/checkout@v3
- name: Set up Go \${{ matrix.go-version }}
uses: actions/setup-go@v4
with:
go-version: \${{ matrix.go-version }}
cache-dependency-path: go.sum
- name: Download dependencies
run: go mod download
- name: Run tests
run: go test -v -coverprofile=coverage.out ./...
- name: Upload coverage
uses: codecov/codecov-action@v3
with:
files: ./coverage.out
fail_ci_if_error: false
`;
return {
language: 'go',
packageName,
files,
description: 'Go SDK with Go module files and CI/CD',
};
}
// Helper functions
function generateMethodName(path, method) {
const parts = path.split('/').filter(Boolean);
const lastPart = parts[parts.length - 1] || 'data';
const camelCase = lastPart
.split(/[-_]/)
.map((word, i) => (i === 0 ? word : capitalize(word)))
.join('');
return `${method.toLowerCase()}${capitalize(camelCase)}`;
}
function capitalize(str) {
if (!str)
return '';
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
function generateTypeScriptMethod(api, methodName) {
const url = new URL(api.baseUrl);
let code = ` async ${methodName}(params?: Record<string, any>): Promise<any> {\n`;
code += ` const url = new URL('${url.pathname}', this.baseUrl);\n`;
code += ` if (params) {\n`;
code += ` Object.entries(params).forEach(([key, value]) => {\n`;
code += ` url.searchParams.set(key, String(value));\n`;
code += ` });\n`;
code += ` }\n\n`;
code += ` const response = await fetch(url.toString(), {\n`;
code += ` method: '${api.method}',\n`;
code += ` headers: { ...this.headers },\n`;
if (api.method === 'POST' && api.body) {
code += ` body: JSON.stringify(params || {}),\n`;
}
code += ` });\n\n`;
code += ` if (!response.ok) {\n`;
code += ` throw new Error(\`API request failed: \${response.status}\`);\n`;
code += ` }\n\n`;
code += ` return response.json();\n`;
code += ` }\n\n`;
return code;
}
function generatePythonMethod(api, methodName) {
const url = new URL(api.baseUrl);
let code = ` def ${methodName}(self, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:\n`;
code += ` """${api.method} ${url.pathname}"""\n`;
code += ` url = f"{self.base_url}${url.pathname}"\n`;
code += ` \n`;
code += ` kwargs = {'headers': self.headers}\n`;
if (api.method === 'POST') {
code += ` if params:\n`;
code += ` kwargs['json'] = params\n`;
}
else {
code += ` if params:\n`;
code += ` kwargs['params'] = params\n`;
}
code += ` \n`;
code += ` response = requests.${api.method.toLowerCase()}(url, **kwargs)\n`;
code += ` response.raise_for_status()\n`;
code += ` return response.json()\n\n`;
return code;
}
function generateGoMethod(api, methodName) {
const url = new URL(api.baseUrl);
const goMethodName = capitalize(methodName);
let code = `func (c *ApiClient) ${goMethodName}(params map[string]string) (map[string]interface{}, error) {\n`;
code += `\turlStr := fmt.Sprintf("%s%s", c.BaseURL, "${url.pathname}")\n`;
code += `\t\n`;
if (api.method === 'POST') {
code += `\tvar req *http.Request\n`;
code += `\tvar err error\n`;
code += `\t\n`;
code += `\tif params != nil {\n`;
code += `\t\tjsonData, _ := json.Marshal(params)\n`;
code += `\t\treq, err = http.NewRequest("${api.method}", urlStr, bytes.NewBuffer(jsonData))\n`;
code += `\t} else {\n`;
code += `\t\treq, err = http.NewRequest("${api.method}", urlStr, nil)\n`;
code += `\t}\n`;
}
else {
code += `\turlObj, err := url.Parse(urlStr)\n`;
code += `\tif err != nil {\n`;
code += `\t\treturn nil, err\n`;
code += `\t}\n`;
code += `\t\n`;
code += `\tif params != nil {\n`;
code += `\t\tq := urlObj.Query()\n`;
code += `\t\tfor k, v := range params {\n`;
code += `\t\t\tq.Set(k, v)\n`;
code += `\t\t}\n`;
code += `\t\turlObj.RawQuery = q.Encode()\n`;
code += `\t}\n`;
code += `\t\n`;
code += `\tvar req *http.Request\n`;
code += `\treq, err = http.NewRequest("${api.method}", urlObj.String(), nil)\n`;
}
code += `\tif err != nil {\n`;
code += `\t\treturn nil, err\n`;
code += `\t}\n`;
code += `\tif err != nil {\n`;
code += `\t\treturn nil, err\n`;
code += `\t}\n`;
code += `\t\n`;
code += `\tfor k, v := range c.Headers {\n`;
code += `\t\treq.Header.Set(k, v)\n`;
code += `\t}\n`;
code += `\t\n`;
code += `\tresp, err := c.Client.Do(req)\n`;
code += `\tif err != nil {\n`;
code += `\t\treturn nil, err\n`;
code += `\t}\n`;
code += `\tdefer resp.Body.Close()\n`;
code += `\t\n`;
code += `\tvar result map[string]interface{}\n`;
code += `\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n`;
code += `\t\treturn nil, err\n`;
code += `\t}\n`;
code += `\t\n`;
code += `\treturn result, nil\n`;
code += `}\n\n`;
return code;
}
//# sourceMappingURL=sdk-generator.js.map