UNPKG

n8n

Version:

n8n Workflow Automation Tool

149 lines 7.25 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DECORATOR_ROOT_FILENAME = void 0; exports.buildArtifactsFromRegistry = buildArtifactsFromRegistry; exports.registerSharedSchemas = registerSharedSchemas; exports.getGeneratedArtifacts = getGeneratedArtifacts; exports.generateDocs = generateDocs; exports.mergeDecoratorDocument = mergeDecoratorDocument; const zod_to_openapi_1 = require("@asteasolutions/zod-to-openapi"); const n8n_workflow_1 = require("n8n-workflow"); const node_fs_1 = __importDefault(require("node:fs")); const node_path_1 = __importDefault(require("node:path")); const node_util_1 = require("node:util"); const yaml_1 = require("yaml"); const public_api_route_resolver_1 = require("../../../public-api/public-api-route-resolver"); const decorator_routes_1 = require("./decorator-routes"); const COMPONENT_SCHEMA_REF = /^#\/components\/schemas\/(.+)$/; const SHARED_SCHEMA_DIR = 'shared/spec/schemas'; const YAML_OPTS = { aliasDuplicateObjects: false, singleQuote: true, lineWidth: 0 }; exports.DECORATOR_ROOT_FILENAME = 'openapi.decorator-routes.generated.yml'; function schemaFileName(componentName) { return `${componentName[0].toLowerCase()}${componentName.slice(1)}.generated.yml`; } function rewriteComponentRefs(node, toRef) { if (Array.isArray(node)) { node.forEach((item) => rewriteComponentRefs(item, toRef)); return; } if (node === null || typeof node !== 'object') { return; } const record = node; Object.entries(record).forEach(([key, value]) => { const match = key === '$ref' && typeof value === 'string' && COMPONENT_SCHEMA_REF.exec(value); if (match) { record[key] = toRef(match[1]); } else { rewriteComponentRefs(value, toRef); } }); } function buildArtifactsFromRegistry(registry, operations) { const document = new zod_to_openapi_1.OpenApiGeneratorV3(registry.definitions).generateDocument({ openapi: '3.0.0', info: { title: 'throwaway', version: '0.0.0' }, }); const artifacts = []; Object.entries(document.components?.schemas ?? {}).forEach(([componentName, schema]) => { rewriteComponentRefs(schema, schemaFileName); artifacts.push({ outputPath: `${SHARED_SCHEMA_DIR}/${schemaFileName(componentName)}`, content: (0, yaml_1.stringify)(schema, YAML_OPTS), }); }); operations.forEach(({ outputPath, pathKey, method }) => { const operation = document.paths?.[pathKey]?.[method]; rewriteComponentRefs(operation, (componentName) => node_path_1.default.posix.relative(node_path_1.default.posix.dirname(outputPath), `${SHARED_SCHEMA_DIR}/${schemaFileName(componentName)}`)); artifacts.push({ outputPath, content: (0, yaml_1.stringify)(operation, YAML_OPTS) }); }); return artifacts; } function registerSharedSchemas(registry, sharedResponseSchemas) { const sharedZodSchemas = new Map(); const dtoByComponentName = new Map(); sharedResponseSchemas.forEach((schema, dto) => { const clashing = dtoByComponentName.get(dto.name); if (clashing && clashing !== dto) { throw new n8n_workflow_1.UnexpectedError(`Two different shared response DTOs are both named '${dto.name}' - their generated ` + 'OpenAPI component names would collide. Rename one of the DTO classes.'); } dtoByComponentName.set(dto.name, dto); sharedZodSchemas.set(dto, registry.register(dto.name, schema)); }); return sharedZodSchemas; } function buildDecoratorRootDocument(operations) { const paths = {}; operations.forEach(({ pathKey, method, outputPath }) => { const pathItem = (paths[pathKey] ??= {}); if (pathItem[method]) { throw new n8n_workflow_1.UnexpectedError(`Two @PublicApiController routes both map to ${method.toUpperCase()} ${pathKey}.`); } pathItem[method] = { $ref: `./${outputPath}` }; }); return { openapi: '3.0.0', info: { title: 'decorator-routes', version: '0.0.0' }, paths }; } function getGeneratedArtifacts() { const registry = new zod_to_openapi_1.OpenAPIRegistry(); const sharedZodSchemas = registerSharedSchemas(registry, (0, decorator_routes_1.getSharedResponseSchemas)()); const resolveSchema = (dto, schema) => sharedZodSchemas.get(dto) ?? schema; const operations = (0, decorator_routes_1.getDecoratorGeneratedOperations)(resolveSchema); operations.forEach(({ config }) => registry.registerPath(config)); const artifacts = buildArtifactsFromRegistry(registry, operations.map(({ outputPath, pathKey, method }) => ({ outputPath, pathKey, method }))); artifacts.push({ outputPath: exports.DECORATOR_ROOT_FILENAME, content: (0, yaml_1.stringify)(buildDecoratorRootDocument(operations), YAML_OPTS), }); return artifacts; } function generateDocs(v1Dir) { const artifacts = getGeneratedArtifacts(); artifacts.forEach(({ outputPath, content }) => { const fullPath = node_path_1.default.join(v1Dir, outputPath); node_fs_1.default.mkdirSync(node_path_1.default.dirname(fullPath), { recursive: true }); node_fs_1.default.writeFileSync(fullPath, content); }); } function mergeComponents(base, extra) { if (!extra) return base; const merged = { ...base }; for (const [section, items] of Object.entries(extra)) { const target = { ...(merged[section] ?? {}) }; for (const [name, definition] of Object.entries(items)) { if (name in target && !(0, node_util_1.isDeepStrictEqual)(target[name], definition)) { throw new n8n_workflow_1.UnexpectedError(`OpenAPI component components.${section}.${name} is defined differently by a hand-written ` + 'path and a @PublicApiController route — rename one so they no longer collide.'); } target[name] = definition; } merged[section] = target; } return merged; } function mergeDecoratorDocument(base, decorator) { const paths = { ...(base.paths ?? {}) }; for (const [pathKey, methods] of Object.entries(decorator.paths ?? {})) { const existing = paths[pathKey]; if (!existing) { paths[pathKey] = methods; continue; } const combined = { ...existing }; for (const [method, operation] of Object.entries(methods)) { if (public_api_route_resolver_1.HTTP_METHODS.some((m) => m === method) && combined[method] !== undefined) { throw new n8n_workflow_1.UnexpectedError(`Duplicate OpenAPI operation ${method.toUpperCase()} ${pathKey}: it is declared by both a ` + 'hand-written (eov) path and a @PublicApiController route — remove the hand-written one.'); } combined[method] = operation; } paths[pathKey] = combined; } return { ...base, paths, components: mergeComponents(base.components, decorator.components) }; } //# sourceMappingURL=generate.js.map