openapi-generator-plus
Version:
Modular OpenAPI code generator written in TypeScript and Node.js
262 lines • 10.3 kB
JavaScript
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
;