import { copyFile, readFile } from "fs/promises"; import * as handlebars from 'handlebars'; import { join } from 'path'; import { Project } from "ts-morph"; import { Logger } from 'winston'; import { analyzeFiles } from "./analyzeTypescriptFiles"; import { createFile, createPath } from "./fileMethods"; /** * Generate the Client Templates. * @param options */ export async function generateClientTemplate(options: { pathToClassInterfaceTemplate: string, pathToFunctionInterfaceTemplate: string, pathToBackendInterfaceTemplate: string, outputDir: string, inputDir: string, tsConfigFilePath: string, // A Logger. logger?: Logger }) { // If there is a Logger Show a hint of the folders. if (options.logger) { options.logger.info('Templates will be stored in ' + options.outputDir); } // Define an Array containing elements that should be copied // Copying the stuff is relevant for const filesToCopy: { src: string[], name: string, path: string[] }[] = [ { name: 'nopeDispatcher.ts', path: ['dispatcher'], src: ['lib', 'dispatcher'] }, { name: 'nopeRemoteObservable.ts', path: ['observables'], src: ['lib', 'observables'] } ]; // Copy the Files. for (const file of filesToCopy) { // Create the Output dir (if it doenst exists) await createPath(join(options.outputDir, ...file.path)); // Copy the File. await copyFile( join(__dirname, '..', '..', '..', ...file.src, file.name), join(options.outputDir, ...file.path, file.name) ); if (options.logger) { options.logger.info('Copied -> ' + join(options.outputDir, ...file.path, file.name)); } } // Function to Determine new project files. const project = new Project({ tsConfigFilePath: options.tsConfigFilePath, addFilesFromTsConfig: false, }); // Readin the Sources of the Dir. project.addSourceFilesAtPaths(options.inputDir); // Readin the Source-Files. const sourceFiles = project.getSourceFiles(); // Generate the Files const result = analyzeFiles(sourceFiles); // Enrich the Information. result.classes = result.classes.map(item => { // Extract the Class uri const classUri = item.classDecorator.decoratorSettings.exportsElementsToDispatcher ? item.classDecorator.decoratorSettings.exportsElementsToDispatcher.uri || item.className : item.className; (item as any).orginalName = item.className; (item as any).fileName = item.className + '_ClientInterface'; item.className = item.className + '_ClientInterface'; item.methods = item.methods.map(m => { const methodUri = m.decoratorSettings.exportMethodToDispatcher ? m.decoratorSettings.exportMethodToDispatcher.uri || m.name : m.name; (m as any).uri = classUri + '_' + methodUri; return m; }); item.properties = item.properties.map(p => { const methodUri = p.decoratorSettings.exportPropertyToDispatcher ? p.decoratorSettings.exportPropertyToDispatcher.uri || p.name : p.name; (p as any).uri = classUri + '_' + methodUri; return p; }); return item; }); result.functions = result.functions.map(item => { // Adding a generatorName (item as any).generatorName = 'generate_' + item.name + '_Accessor'; (item as any).orginalName = item.name; (item as any).fileName = item.name + '_ClientInterface'; return item; }); // load the File. const clientTemplate = await readFile(options.pathToClassInterfaceTemplate, { encoding: 'utf-8' }) // Renderfuncting const renderClientInterface = handlebars.compile(clientTemplate); // Generate the Files. const classFiles: { name: string, content: string, }[] = result.classes.map(item => { return { name: (item as any).fileName + '.ts', content: renderClientInterface(item) } }); // load the File. const functionTemplate = await readFile(options.pathToFunctionInterfaceTemplate, { encoding: 'utf-8' }) // Renderfuncting const renderFunctionInterface = handlebars.compile(functionTemplate); const functionFiles: { name: string, content: string, }[] = result.functions.map(func => { return { name: (func as any).fileName + '.ts', content: renderFunctionInterface(func) } }); // load the File. const interfaceTemplate = await readFile(options.pathToBackendInterfaceTemplate, { encoding: 'utf-8' }) // Renderfuncting const renderInterface = handlebars.compile(interfaceTemplate); await createFile( // Generate the Path. join(options.outputDir, 'BackendInterface.ts'), renderInterface(result) ); if (options.logger) { options.logger.info('Generated -> ' + join(options.outputDir, 'BackendInterface.ts')); } for (const file of classFiles) { // Define the File Name: const fileName = join(options.outputDir, 'clients', file.name); await createFile( // Generate the Path. fileName, file.content ); if (options.logger) { options.logger.info('Generated -> ' + fileName); } // Function to Determine new project files. const project = new Project({ tsConfigFilePath: options.tsConfigFilePath, addFilesFromTsConfig: false, }); project.addSourceFileAtPath(fileName); // Readin the Source-Files. const sourceFiles = project.getSourceFiles(); for (const file of sourceFiles) { file.formatText(); await file.save(); } } for (const file of functionFiles) { // Define the File Name: const fileName = join(options.outputDir, 'functions', file.name); await createFile( // Generate the Path. fileName, file.content ); if (options.logger) { options.logger.info('Generated -> ' + fileName); } // Function to Determine new project files. const project = new Project({ tsConfigFilePath: options.tsConfigFilePath, addFilesFromTsConfig: false, }); project.addSourceFileAtPath(fileName); // Readin the Source-Files. const sourceFiles = project.getSourceFiles(); for (const file of sourceFiles) { file.formatText(); await file.save(); } } // Compile the Template and parse the Code. return true; }