UNPKG

agentlangcli

Version:

A command-line interface tool for AgentLang

296 lines 13.3 kB
import { z } from 'zod'; import yaml from 'yaml'; import { OpenApiGeneratorV3, OpenAPIRegistry, extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi'; import { getUserModuleNames, fetchModule } from 'agentlang/out/runtime/module.js'; import * as fs from 'node:fs/promises'; import * as path from 'node:path'; import Converter from 'openapi-to-postmanv2'; import { execSync } from 'child_process'; extendZodWithOpenApi(z); const registry = new OpenAPIRegistry(); const bearerAuth = registry.registerComponent('securitySchemes', 'bearerAuth', { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', }); function getOpenApiDocumentation(registry, name, version) { const generator = new OpenApiGeneratorV3(registry.definitions); return generator.generateDocument({ openapi: '3.0.0', info: { version, title: name, description: 'This is the API', }, servers: [{ url: 'v1' }], }); } function writeDocumentation(registry, docDir, name, version) { const docs = getOpenApiDocumentation(registry, name, version); const fileContent = yaml.stringify(docs); void fs.mkdir(path.join(docDir, 'docs'), { recursive: true }); void fs.writeFile(`${docDir}/docs/openapi.yml`, fileContent, { encoding: 'utf-8', }); } function findRelationshipPaths(moduleName, entityName) { const module = fetchModule(moduleName); const relationships = module.getContainsRelationshipEntries(); function findPathsRecursive(currentEntity, visited, currentPath) { const matchingRels = relationships.filter(rel => rel.node2.origName === currentEntity); if (matchingRels.length === 0) { return currentPath.length > 0 ? [currentPath] : []; } let allPaths = []; for (const rel of matchingRels) { const node1Entity = rel.node1.origName; if (visited.has(node1Entity)) { continue; } const newVisited = new Set(visited); newVisited.add(node1Entity); const newPath = [...currentPath, rel.name, `{${node1Entity.toLowerCase()}}`, node1Entity]; const paths = findPathsRecursive(node1Entity, newVisited, newPath); allPaths = allPaths.concat(paths); } return allPaths; } const visited = new Set([entityName]); return findPathsRecursive(entityName, visited, [entityName]); } function getOneOfValues(properties) { const oneOf = properties.get('one-of') || new Set(); return Array.from(oneOf); } function createZodSchemaFromEntitySchema(schema) { return z.object(Object.fromEntries(Array.from(schema.entries()).map(([key, value]) => { const k = key; let v = value.properties && value.properties.get('one-of') ? z.enum(getOneOfValues(value.properties)) : value.type === 'String' ? z.string() : value.type === 'Int' ? z.number() : value.type === 'Number' ? z.number() : value.type === 'Email' ? z.string() : value.type === 'Date' ? z.string() : value.type === 'Time' ? z.string() : value.type === 'DateTime' ? z.string() : value.type === 'Boolean' ? z.boolean() : value.type === 'UUID' ? z.string().uuid() : value.type === 'URL' ? z.string() : value.type === 'Path' ? z.string() : value.type === 'Map' ? z.object({}) : value.type === 'Any' ? z.any() : z.any(); if (value.properties && value.properties.get('optional')) { v = v.optional(); } if (value.properties && value.properties.get('default')) { const deflt = value.properties.get('default'); switch (deflt) { case 'uuid()': v = z.string().uuid().optional(); break; case 'now()': v = z.date().optional(); break; default: v = v.default(deflt); } } return [k, v]; }))); } function registerEntityEndpoint(method, path, tags, filterPaths, entitySchema) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const endpointConfig = { method, path, security: [{ [bearerAuth.name]: [] }], tags, responses: { 200: { description: 'Success', content: { 'application/json': { schema: method === 'get' ? z.array(entitySchema) : entitySchema, }, }, }, 404: { description: 'Not Found', }, 500: { description: 'Internal Server Error', }, }, }; if (method === 'post' || method === 'put') { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access endpointConfig.request = { body: { content: { 'application/json': { schema: entitySchema, }, }, }, }; } // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access endpointConfig.parameters = filterPaths.map(path => { return { name: path, in: 'path', required: true, schema: { type: 'string' }, }; }); if (method === 'post') { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access delete endpointConfig.responses['404']; } // eslint-disable-next-line @typescript-eslint/no-unsafe-argument registry.registerPath(endpointConfig); } function generateEntitiesEntries() { const modules = getUserModuleNames(); return modules.map((moduleName) => { const module = fetchModule(moduleName); const entities = module.getEntityEntries(); return entities.map((entity) => { const entityPath = `${moduleName}/${entity.name}`; const relatinotionshipPaths = findRelationshipPaths(moduleName, entity.name); const entitySchema = createZodSchemaFromEntitySchema(entity.schema).openapi(`${entity.name}Schema`); registerEntityEndpoint('post', `/${entityPath}`, [entity.name], [], entitySchema); registerEntityEndpoint('get', `/${entityPath}`, [entity.name], [], entitySchema); registerEntityEndpoint('put', `/${entityPath}/{id}`, [entity.name], ['id'], entitySchema); registerEntityEndpoint('delete', `/${entityPath}/{id}`, [entity.name], ['id'], entitySchema); if (relatinotionshipPaths.length > 1) { relatinotionshipPaths.forEach(path => { const relationshipPath = path.reverse().join('/'); const filterPaths = path .filter(segment => segment.startsWith('{') && segment.endsWith('}')) .map(segment => segment.slice(1, -1)); registerEntityEndpoint('post', `/${relationshipPath}`, [`${entity.name} (${path[path.length - 2]})`], filterPaths, entitySchema); registerEntityEndpoint('get', `/${relationshipPath}`, [`${entity.name} (${path[path.length - 2]})`], filterPaths, entitySchema); registerEntityEndpoint('put', `/${relationshipPath}/{id}`, [`${entity.name} (${path[path.length - 2]})`], filterPaths.concat(['id']), entitySchema); registerEntityEndpoint('delete', `/${relationshipPath}/{id}`, [`${entity.name} (${path[path.length - 2]})`], filterPaths.concat(['id']), entitySchema); }); } }); }); } function generateEventsEntries() { const modules = getUserModuleNames(); return modules.map((moduleName) => { const module = fetchModule(moduleName); const events = module.getEventEntries(); return events.map((event) => { const eventPath = `${moduleName}/${event.name}`; const eventSchema = createZodSchemaFromEntitySchema(event.schema).openapi(`${event.name}Schema`); const sc = z.object({ [eventPath]: eventSchema, }); registry.registerPath({ method: 'post', path: `/${eventPath}`, security: [{ [bearerAuth.name]: [] }], tags: ['Events'], request: { body: { content: { 'application/json': { schema: sc, }, }, }, }, responses: { 200: { description: 'Success', }, 404: { description: 'Not Found', }, 500: { description: 'Internal Server Error', }, }, }); return { path: eventPath, name: event.name, schema: event.schema, }; }); }); } export const generateSwaggerDoc = async (fileName, options) => { // eslint-disable-next-line no-console console.log('Generating documentation...'); const docDir = path.dirname(fileName) === '.' ? process.cwd() : path.resolve(process.cwd(), fileName); const packagePath = path.join(docDir, 'package.json'); const packageContent = await fs.readFile(packagePath, 'utf-8'); const pkg = JSON.parse(packageContent); const name = pkg.name || 'app'; const version = pkg.version || '0.0.1'; generateEntitiesEntries(); generateEventsEntries(); writeDocumentation(registry, docDir, name, version); if (options === null || options === void 0 ? void 0 : options.outputHtml) { await generateHtmlDocumentation(registry, docDir, name, version); } if (options === null || options === void 0 ? void 0 : options.outputPostman) { await generatePostmanCollection(registry, docDir, name, version); } }; async function generateHtmlDocumentation(registry, docDir, _name, _version) { const outputPath = `${docDir}/docs/index.html`; const yamlContent = await fs.readFile(`${docDir}/docs/openapi.yml`, 'utf8'); const jsonContent = JSON.stringify(yaml.parse(yamlContent), null, 2); await fs.writeFile(`${docDir}/docs/openapi.json`, jsonContent, { encoding: 'utf-8' }); // eslint-disable-next-line no-console console.log('OpenAPI JSON generated: docs/openapi.json'); try { execSync(`redocly build-docs ${docDir}/docs/openapi.json -o ${outputPath}`); // eslint-disable-next-line no-console console.log('HTML documentation generated: docs/index.html'); } catch (error) { // eslint-disable-next-line no-console console.error('Failed to generate HTML documentation:', error); } } async function generatePostmanCollection(registry, docDir, _name, _version) { const openapiData = await fs.readFile(`${docDir}/docs/openapi.yml`, 'utf8'); // eslint-disable-next-line @typescript-eslint/no-explicit-any Converter.convert({ type: 'string', data: openapiData }, {}, (err, conversionResult) => { var _a, _b; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (!conversionResult.result) { // eslint-disable-next-line no-console, @typescript-eslint/no-unsafe-member-access console.log('Could not convert', conversionResult.reason); } else { // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access const collection = (_b = (_a = conversionResult.output) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.data; void fs.writeFile(`${docDir}/docs/postman.json`, JSON.stringify(collection, null, 2), { encoding: 'utf-8' }); // eslint-disable-next-line no-console console.log('Postman collection generated: docs/postman.json'); } }); } //# sourceMappingURL=docs.js.map