UNPKG

openapi-generator-plus

Version:

Modular OpenAPI code generator written in TypeScript and Node.js

262 lines 10.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = require("fs"); const core_1 = require("@openapi-generator-plus/core"); const getopts_1 = __importDefault(require("getopts")); const path_1 = __importDefault(require("path")); const config_1 = require("./config"); const node_watch_1 = __importDefault(require("node-watch")); const glob_1 = require("glob"); const generator_1 = require("./generator"); const ansi_colors_1 = __importDefault(require("ansi-colors")); const usage_1 = require("./usage"); const log_1 = require("./log"); const node_fetch_1 = __importDefault(require("node-fetch")); const utils_1 = require("@openapi-generator-plus/core/dist/utils"); function createMyGeneratorContext() { return (0, core_1.createGeneratorContext)({ log: log_1.log, }); } async function createCodegenInputPossiblyWithUrl(inputPathOrUrl) { if ((0, utils_1.isURL)(inputPathOrUrl)) { console.info(ansi_colors_1.default.bold.green('Downloading:'), inputPathOrUrl); const startTime = Date.now(); const response = await (0, node_fetch_1.default)(inputPathOrUrl); if (!response.ok) { throw new Error(`Download failed with status ${response.status}: ${inputPathOrUrl}`); } const text = await response.text(); console.info(ansi_colors_1.default.bold.green(`Downloaded in ${Date.now() - startTime}ms:`), inputPathOrUrl); const tempDir = await fs_1.promises.mkdtemp('openapi-generator-plus'); const tempFile = path_1.default.resolve(tempDir, path_1.default.basename(inputPathOrUrl) || 'openapi.yaml'); fs_1.promises.writeFile(tempFile, text); try { return await (0, core_1.createCodegenInput)(tempFile); } finally { await fs_1.promises.unlink(tempFile); await fs_1.promises.rmdir(tempDir); } } else { return await (0, core_1.createCodegenInput)(inputPathOrUrl); } } async function generate(config, generatorConstructor) { const generator = (0, core_1.constructGenerator)(config, createMyGeneratorContext(), generatorConstructor); const state = (0, core_1.createCodegenState)(config, generator); state.log = log_1.log; let input; try { input = await createCodegenInputPossiblyWithUrl(config.inputPath); } catch (error) { console.error(ansi_colors_1.default.bold.red('Failed to get the API specification:'), error); return false; } let doc; try { doc = (0, core_1.createCodegenDocument)(input, state); } catch (error) { console.error(ansi_colors_1.default.bold.red('Failed to process the API specification:'), error); return false; } try { await generator.exportTemplates(config.outputPath, doc); } catch (error) { console.error(ansi_colors_1.default.bold.red('Failed to generate templates:'), error); return false; } return true; } async function clean(notModifiedSince, config, generatorConstructor) { const generator = (0, core_1.constructGenerator)(config, createMyGeneratorContext(), generatorConstructor); const cleanPathPatterns = generator.cleanPathPatterns(); if (!cleanPathPatterns) { return; } const outputPath = config.outputPath; if (typeof outputPath !== 'string') { throw new Error('outputPath must be a string value'); } console.log(ansi_colors_1.default.bold.yellow('Cleaning:'), cleanPathPatterns.map(p => path_1.default.join(outputPath, p)).join(' ')); const paths = []; for (const pattern of cleanPathPatterns) { paths.push(...await (0, glob_1.glob)(pattern, { cwd: outputPath, follow: false, })); } const dirsToCheck = []; const resolvedOutputPath = path_1.default.resolve(outputPath); for (const aPath of paths) { const absolutePath = path_1.default.resolve(outputPath, aPath); if (!absolutePath.startsWith(resolvedOutputPath)) { console.warn(ansi_colors_1.default.bold.red('Invalid clean path not under outputPath:'), absolutePath); continue; } try { const stats = await fs_1.promises.stat(absolutePath); if (stats.isDirectory()) { dirsToCheck.push(absolutePath); } else if (stats.mtime.getTime() < notModifiedSince) { await fs_1.promises.unlink(absolutePath); } } catch (error) { console.error(ansi_colors_1.default.bold.red('Failed to clean path:'), absolutePath, error); } } for (const absolutePath of dirsToCheck) { const files = await fs_1.promises.readdir(absolutePath); if (files.length === 0) { await fs_1.promises.rmdir(absolutePath); } } } async function generateCommand(argv) { const commandLineOptions = (0, getopts_1.default)(argv, { alias: { config: 'c', output: 'o', generator: 'g', version: 'v', }, boolean: ['watch', 'clean'], unknown: (option) => { console.log(`Unknown option: ${option}`); return false; }, }); if (commandLineOptions.version) { const version = require(path_1.default.resolve(__dirname, '../package.json')).version; console.log(version); process.exit(0); } let config; try { config = await (0, config_1.createConfig)(commandLineOptions); } catch (error) { console.error(`Failed to open config file: ${error}`); process.exit(1); } if (!config.inputPath) { console.warn('API specification not specified'); (0, usage_1.usage)(); process.exit(1); } if (!config.outputPath) { console.warn('Output path not specified'); (0, usage_1.usage)(); process.exit(1); } if (!config.generator) { console.warn('Generator not specified'); (0, usage_1.usage)(); process.exit(1); } let generatorConstructor; try { generatorConstructor = await (0, generator_1.loadGeneratorConstructor)(config.generator); } catch (error) { console.error(`Failed to load generator template: ${config.generator}`, error); process.exit(1); } const startTime = Date.now(); const beforeFilesystemTimestamp = await currentFilesystemTimestamp(config.outputPath); let result; try { result = await generate(config, generatorConstructor); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error) { if (error.message) { console.error(ansi_colors_1.default.bold.red('Failed to generate:'), error.message); } else { console.error(ansi_colors_1.default.bold.red('Failed to generate:'), error); } process.exit(1); } if (result) { console.log(ansi_colors_1.default.bold.green(`Generated in ${Date.now() - startTime}ms:`), config.outputPath); } if (result && commandLineOptions.clean) { await clean(beforeFilesystemTimestamp, config, generatorConstructor); } if (commandLineOptions.watch) { const watchPaths = []; if (config.inputPath.indexOf('://') === -1) { watchPaths.push(config.inputPath); } else { console.warn(ansi_colors_1.default.red('Not watching for API specification changes as it is not a local file path:'), config.inputPath); } const generatorWatchPaths = (0, core_1.constructGenerator)(config, createMyGeneratorContext(), generatorConstructor).watchPaths(); if (generatorWatchPaths) { watchPaths.push(...generatorWatchPaths); } if (!watchPaths.length) { console.warn('No paths are available to watch'); process.exit(1); } let running = false; (0, node_watch_1.default)(watchPaths, { recursive: true }, async () => { if (running) { return; } running = true; const startTime = Date.now(); const beforeFilesystemTimestamp = await currentFilesystemTimestamp(config.outputPath); console.log(ansi_colors_1.default.cyan('Rebuilding:'), config.inputPath); try { const result = await generate(config, generatorConstructor); if (result) { console.log(ansi_colors_1.default.bold.green(`Generated in ${Date.now() - startTime}ms:`), config.outputPath); if (commandLineOptions.clean) { await clean(beforeFilesystemTimestamp, config, generatorConstructor); } } running = false; } catch (error) { console.error(ansi_colors_1.default.bold.red('Failed to generate:'), error); running = false; } }); } if (!result) { process.exit(1); } } exports.default = generateCommand; /** * Calculate a current timestamp for files before generation using the filesystem to workaround * differences between the current time (as determined by Date.now()) and timestamps that * files in the filesystem will receive when created, due to filesystem time resolution * differences on different platforms. * @param outputPath the output path for the generator * @returns a timestamp to use as the timestamp before files are generated */ async function currentFilesystemTimestamp(outputPath) { if (typeof outputPath !== 'string') { throw new Error('outputPath must be a string value'); } const tempName = `.temp-${Date.now()}`; const tempPath = path_1.default.join(outputPath, tempName); await fs_1.promises.mkdir(outputPath, { recursive: true }); await fs_1.promises.writeFile(tempPath, ''); const stats = await fs_1.promises.stat(tempPath); await fs_1.promises.unlink(tempPath); return stats.mtime.getTime(); } //# sourceMappingURL=generate.js.map