UNPKG

@gati-framework/cli

Version:

CLI tool for Gati framework - create, develop, build and deploy cloud-native applications

216 lines 8.09 kB
/** * @module cli/codegen/bundle-generator * @description Generate deployment bundles containing all manifests and schemas */ import * as crypto from 'crypto'; /** * Generate deployment bundle from manifests and schemas */ export class BundleGenerator { generate(handlers, modules, schemas, options = {}) { const opts = { version: '1.0.0', includeMetadata: true, ...options, }; // Validate manifests this.validateManifests(handlers, modules); // Build version graph const versionGraph = this.buildVersionGraph(handlers); // Collect transformers const transformers = this.collectTransformers(handlers); // Build metadata const metadata = this.buildMetadata(opts); // Create bundle const bundle = { version: opts.version, generated: new Date().toISOString(), checksum: '', handlers, modules, schemas, versionGraph, transformers, metadata, }; // Calculate checksum bundle.checksum = this.calculateChecksum(bundle); return bundle; } validateManifests(handlers, modules) { // Validate handler dependencies const moduleIds = new Set(modules.map(m => m.moduleId)); for (const handler of handlers) { for (const moduleDep of handler.dependencies.modules) { if (!moduleIds.has(moduleDep)) { throw new Error(`Handler "${handler.handlerId}" depends on module "${moduleDep}" which is not in the bundle`); } } } // Validate unique handler IDs const handlerIds = new Set(); for (const handler of handlers) { if (handlerIds.has(handler.handlerId)) { throw new Error(`Duplicate handler ID: ${handler.handlerId}`); } handlerIds.add(handler.handlerId); } // Validate unique module IDs const moduleIdSet = new Set(); for (const module of modules) { if (moduleIdSet.has(module.moduleId)) { throw new Error(`Duplicate module ID: ${module.moduleId}`); } moduleIdSet.add(module.moduleId); } } buildVersionGraph(handlers) { const nodes = []; const edges = []; // Group handlers by path const handlersByPath = new Map(); for (const handler of handlers) { const existing = handlersByPath.get(handler.path) || []; existing.push(handler); handlersByPath.set(handler.path, existing); } // Build nodes and edges for (const [path, pathHandlers] of handlersByPath) { // Sort by timestamp (extracted from timescapeVersion) const sorted = pathHandlers.sort((a, b) => { const tsA = this.extractTimestamp(a.timescapeVersion); const tsB = this.extractTimestamp(b.timescapeVersion); return tsA - tsB; }); // Create nodes for (const handler of sorted) { nodes.push({ version: handler.timescapeVersion, handlerId: handler.handlerId, timestamp: this.extractTimestamp(handler.timescapeVersion), }); } // Create edges between consecutive versions for (let i = 0; i < sorted.length - 1; i++) { edges.push({ from: sorted[i].timescapeVersion, to: sorted[i + 1].timescapeVersion, breaking: false, // TODO: Implement breaking change detection }); } } return { nodes, edges }; } extractTimestamp(timescapeVersion) { // Extract timestamp from version string like "v1732186200-users-001" const match = timescapeVersion.match(/v?(\d+)/); return match ? parseInt(match[1], 10) : Date.now(); } collectTransformers(handlers) { const transformers = []; // Group handlers by path const handlersByPath = new Map(); for (const handler of handlers) { const existing = handlersByPath.get(handler.path) || []; existing.push(handler); handlersByPath.set(handler.path, existing); } // Collect transformers between versions for (const [path, pathHandlers] of handlersByPath) { const sorted = pathHandlers.sort((a, b) => { const tsA = this.extractTimestamp(a.timescapeVersion); const tsB = this.extractTimestamp(b.timescapeVersion); return tsA - tsB; }); for (let i = 0; i < sorted.length - 1; i++) { const fromVersion = sorted[i].timescapeVersion; const toVersion = sorted[i + 1].timescapeVersion; transformers.push({ fromVersion, toVersion, path: `transformers/${fromVersion}-to-${toVersion}.ts`, }); } } return transformers; } buildMetadata(options) { const metadata = {}; if (options.includeMetadata) { if (options.projectName) { metadata.projectName = options.projectName; } if (options.environment) { metadata.environment = options.environment; } } return metadata; } calculateChecksum(bundle) { // Create deterministic JSON string const { checksum, ...bundleWithoutChecksum } = bundle; const content = JSON.stringify(bundleWithoutChecksum, null, 0); // Calculate SHA-256 hash const hash = crypto.createHash('sha256').update(content).digest('hex'); return `sha256:${hash}`; } /** * Create manifest index for fast lookup */ createIndex(bundle) { const handlerIndex = new Map(); const moduleIndex = new Map(); const pathIndex = new Map(); // Index handlers for (const handler of bundle.handlers) { handlerIndex.set(handler.handlerId, handler); const pathHandlers = pathIndex.get(handler.path) || []; pathHandlers.push(handler); pathIndex.set(handler.path, pathHandlers); } // Index modules for (const module of bundle.modules) { moduleIndex.set(module.moduleId, module); } return { handlers: handlerIndex, modules: moduleIndex, paths: pathIndex, }; } /** * Validate bundle integrity */ validateBundle(bundle) { const errors = []; // Verify checksum const expectedChecksum = this.calculateChecksum({ ...bundle, checksum: '', }); if (bundle.checksum !== expectedChecksum) { errors.push(`Checksum mismatch: expected ${expectedChecksum}, got ${bundle.checksum}`); } // Validate handler references for (const handler of bundle.handlers) { // Check schema references if (handler.gtypes.request && handler.gtypes.request !== 'any' && !bundle.schemas[handler.gtypes.request]) { errors.push(`Handler "${handler.handlerId}" references missing schema: ${handler.gtypes.request}`); } if (handler.gtypes.response && handler.gtypes.response !== 'any' && !bundle.schemas[handler.gtypes.response]) { errors.push(`Handler "${handler.handlerId}" references missing schema: ${handler.gtypes.response}`); } } return { valid: errors.length === 0, errors, }; } } /** * Create bundle generator instance */ export function createBundleGenerator() { return new BundleGenerator(); } //# sourceMappingURL=bundle-generator.js.map