UNPKG

genezio

Version:

Command line utility to interact with Genezio infrastructure.

601 lines (591 loc) 23.7 kB
import Mustache from "mustache"; import { AstNodeType, } from "../../models/genezioModels.js"; import { TriggerType } from "../../projectConfiguration/yaml/models.js"; import { storageTs } from "../templates/nodeSdkTs.js"; import path from "path"; const TYPESCRIPT_RESERVED_WORDS = [ "abstract", "as", "asserts", "async", "await", "boolean", "break", "case", "catch", "class", "const", "constructor", "continue", "declare", "default", "delete", "do", "else", "enum", "export", "extends", "false", "finally", "for", "from", "function", "get", "if", "implements", "import", "in", "infer", "instanceof", "interface", "is", "keyof", "let", "module", "namespace", "never", "new", "null", "number", "object", "of", "package", "private", "protected", "public", "readonly", "require", "global", "return", "set", "static", "string", "super", "switch", "symbol", "this", "throw", "true", "try", "type", "typeof", "unique", "unknown", "var", "void", "while", "with", "yield", "async", "await", "of", ]; const indexTemplate = `/** * This is an auto generated code. This code should not be modified since the file can be overwritten * if new genezio commands are executed. */ {{#imports}} import { {{#models}}{{{name}}}{{^last}}, {{/last}}{{/models}} } from "./{{{path}}}"; {{/imports}} export { {{#exports}}{{{name}}}{{^last}}, {{/last}}{{/exports}} }; `; const modelTemplate = `/** * This is an auto generated code. This code should not be modified since the file can be overwritten * if new genezio commands are executed. */ {{#imports}} import { {{#models}}{{{name}}}{{^last}}, {{/last}}{{/models}} } from "./{{{path}}}"; {{/imports}} {{#externalTypes}} export {{{type}}} {{/externalTypes}} `; const template = `/** * This is an auto generated code. This code should not be modified since the file can be overwritten * if new genezio commands are executed. */ import { Remote } from "genezio-remote"; {{#imports}} import { {{#models}}{{{name}}}{{^last}}, {{/last}}{{/models}} } from "./{{{path}}}"; {{/imports}} {{#hasGnzContext}} import { StorageManager } from "./storage"; {{/hasGnzContext}} {{#externalTypes}} export {{{type}}} {{/externalTypes}} {{#classDocLines.length}} /** {{#classDocLines}} * {{{.}}} {{/classDocLines}} */ {{/classDocLines.length}} export class {{{className}}} { static remote = new Remote("{{{_url}}}"); {{#methods}} {{#hasGnzContextAsFirstParameter}} {{#methodDocLines.length}} /** {{#methodDocLines}} * {{{.}}} {{/methodDocLines}} */ {{/methodDocLines.length}} static async {{{name}}}({{#parameters}}{{{name}}}{{^last}}, {{/last}}{{/parameters}}){{{returnType}}} { return await {{{className}}}.remote.call({{{methodCaller}}} {"token": StorageManager.getStorage().getItem("token"), "isGnzContext": true}, {{#sendParameters}}{{{name}}}{{^last}}, {{/last}}{{/sendParameters}}); } {{/hasGnzContextAsFirstParameter}} {{^hasGnzContextAsFirstParameter}} {{#methodDocLines.length}} /** {{#methodDocLines}} * {{{.}}} {{/methodDocLines}} */ {{/methodDocLines.length}} static async {{{name}}}({{#parameters}}{{{name}}}{{^last}}, {{/last}}{{/parameters}}){{{returnType}}} { return await {{{className}}}.remote.call({{{methodCaller}}}{{#sendParameters}}{{{name}}}{{^last}}, {{/last}}{{/sendParameters}}); } {{/hasGnzContextAsFirstParameter}} {{/methods}} } `; class SdkGenerator { async generateSdk(sdkGeneratorInput) { const generateSdkOutput = { files: [], }; const modelViews = []; const indexModel = { imports: [], exports: [], }; for (const classInfo of sdkGeneratorInput.classesInfo) { const externalTypes = []; const _url = "%%%link_to_be_replace%%%"; const classConfiguration = classInfo.classConfiguration; let classDefinition = undefined; if (classInfo.program.body === undefined) { continue; } for (const elem of classInfo.program.body) { if (elem.type === AstNodeType.ClassDefinition) { classDefinition = elem; } else { externalTypes.push(elem); } } if (classDefinition === undefined) { continue; } // @ts-expect-error A refactor need to be performed here to avoid this error const view = { className: classDefinition.name, classDocLines: classDefinition.docString?.replace(/\n+$/, "").split("\n") || [], _url: _url, methods: [], externalTypes: [], imports: [], }; let exportClassChecker = false; for (const methodDefinition of classDefinition.methods) { const methodConfiguration = classConfiguration.methods.find((m) => m.name === methodDefinition.name); const methodConfigurationType = methodConfiguration?.type || classConfiguration.type; if (methodConfigurationType !== TriggerType.jsonrpc || classConfiguration.type !== TriggerType.jsonrpc) { continue; } exportClassChecker = true; // @ts-expect-error A refactor need to be performed here to avoid this error const methodView = { // methods start with lowercase name: methodDefinition.name.charAt(0).toLowerCase() + methodDefinition.name.slice(1), parameters: [], returnType: this.getReturnType(methodDefinition.returnType), methodCaller: methodDefinition.params.length === 0 ? `"${classDefinition.name}.${methodDefinition.name}"` : `"${classDefinition.name}.${methodDefinition.name}", `, methodDocLines: methodDefinition.docString?.replace(/\n+$/, "").split("\n") || [], }; methodView.parameters = methodDefinition.params .map((e) => { // GnzContext is a special type used to pass auth token and other information to the remote call if (e.paramType.type === AstNodeType.CustomNodeLiteral && e.paramType.rawValue === "GnzContext") { methodView.hasGnzContextAsFirstParameter = true; view.hasGnzContext = true; return undefined; } return { name: (TYPESCRIPT_RESERVED_WORDS.includes(e.name) ? e.name + "_" : e.name) + (e.optional ? "?" : "") + ": " + this.getParamType(e.paramType) + (e.defaultValue ? " = " + (e.defaultValue.type === AstNodeType.StringLiteral ? "'" + e.defaultValue.value + "'" : e.defaultValue.value) : ""), last: false, }; }) .filter((e) => e !== undefined); methodView.sendParameters = methodDefinition.params .map((e) => { // GnzContext is a special type used to pass auth token and other information to the remote call if (e.paramType.type === AstNodeType.CustomNodeLiteral && e.paramType.rawValue === "GnzContext") { methodView.hasGnzContextAsFirstParameter = true; view.hasGnzContext = true; return undefined; } return { name: TYPESCRIPT_RESERVED_WORDS.includes(e.name) ? e.name + "_" : e.name, last: false, }; }) .filter((e) => e !== undefined); if (methodView.parameters.length > 0) { methodView.parameters[methodView.parameters.length - 1].last = true; methodView.sendParameters[methodView.sendParameters.length - 1].last = true; } view.methods.push(methodView); } for (const externalType of externalTypes) { if (externalType.path) { let currentView = undefined; for (const parentType of externalTypes) { const isUsed = this.isExternalTypeUsedByOtherType(externalType, parentType); if (isUsed && parentType.path && parentType.path !== externalType.path) { currentView = this.addViewIfNotExists(modelViews, parentType, view, classInfo, classDefinition); } const classPath = classInfo.classConfiguration.path.replace(/\\/g, "/"); if (currentView && !classPath.includes(externalType.path)) { this.addImportToCurrentView(currentView, externalType); } } if (this.isExternalTypeUsedInMethod(externalType, classDefinition.methods)) { // @ts-expect-error A refactor need to be performed here to avoid this error currentView = view; const classPath = classDefinition.path?.replace(/\\/g, "/") ?? classInfo.classConfiguration.path.replace(/\\/g, "/"); if (currentView && !classPath.includes(externalType.path)) { this.addImportToCurrentView(currentView, externalType); } } currentView = this.addViewIfNotExists(modelViews, externalType, view, classInfo, classDefinition); if (!currentView?.externalTypes.find( // eslint-disable-next-line @typescript-eslint/no-explicit-any (e) => e.name === externalType.name)) { currentView?.externalTypes.push({ type: this.generateExternalType(externalType), // eslint-disable-next-line @typescript-eslint/no-explicit-any name: externalType.name, }); } } } for (const modelView of modelViews) { for (const importType of modelView.imports) { if (importType.models.length > 0) { importType.models[importType.models.length - 1].last = true; } } } for (const importType of view.imports) { if (importType.models.length > 0) { importType.models[importType.models.length - 1].last = true; } } if (!exportClassChecker) { continue; } this.addClassItemsToIndex(indexModel, classInfo.program.body, classDefinition.path ?? "", classDefinition.name); const rawSdkClassName = `${classDefinition.name}.sdk.ts`; const sdkClassName = rawSdkClassName.charAt(0).toLowerCase() + rawSdkClassName.slice(1); generateSdkOutput.files.push({ path: sdkClassName, data: Mustache.render(template, view), className: classDefinition.name, }); for (const modelView of modelViews) { generateSdkOutput.files.push({ path: modelView.path + ".ts", data: Mustache.render(modelTemplate, modelView), className: "", }); } } generateSdkOutput.files.push({ className: "StorageManager", path: "storage.ts", data: storageTs, }); indexModel.imports.push({ path: "storage", models: [{ name: "Storage" }, { name: "StorageManager" }], }); indexModel.exports.push({ name: "Storage" }, { name: "StorageManager" }); if (indexModel.exports.length > 0) { indexModel.exports[indexModel.exports.length - 1].last = true; } for (const importStatement of indexModel.imports) { if (importStatement.models.length > 0) { importStatement.models[importStatement.models.length - 1].last = true; } } generateSdkOutput.files.push({ className: "index.ts", path: "index.ts", data: Mustache.render(indexTemplate, indexModel), }); return generateSdkOutput; } getReturnType(returnType) { if (!returnType) { return ""; } let value = this.getParamType(returnType); if (returnType.type !== AstNodeType.PromiseType) { value = `Promise<${value}>`; } return `: ${value}`; } getParamType(elem) { switch (elem.type) { case AstNodeType.CustomNodeLiteral: return elem.rawValue; case AstNodeType.StringLiteral: return "string"; case AstNodeType.BigIntLiteral: return "bigint"; case AstNodeType.IntegerLiteral: case AstNodeType.FloatLiteral: case AstNodeType.DoubleLiteral: return "number"; case AstNodeType.BooleanLiteral: return "boolean"; case AstNodeType.AnyLiteral: return "any"; case AstNodeType.ArrayType: return `Array<${this.getParamType(elem.generic)}>`; case AstNodeType.PromiseType: return `Promise<${this.getParamType(elem.generic)}>`; case AstNodeType.Enum: return elem.name; case AstNodeType.TypeAlias: return elem.name; case AstNodeType.UnionType: return elem.params .map((e) => this.getParamType(e)) .join(" | "); case AstNodeType.TypeLiteral: return `{${elem.properties .map((e) => { if (e.type.type === AstNodeType.MapType && elem.properties.length === 1) { return `[key: ${this.getParamType(e.type.genericKey)}]: ${this.getParamType(e.type.genericValue)}`; } else { return `${e.name}${e.optional ? "?" : ""}: ${this.getParamType(e.type)}`; } }) .join(", ")}}`; case AstNodeType.DateType: return "Date"; case AstNodeType.MapType: return `{[key: ${this.getParamType(elem.genericKey)}]: ${this.getParamType(elem.genericValue)}}`; case AstNodeType.VoidLiteral: return "void"; } return "any"; } generateExternalType(type) { if (type.type === AstNodeType.TypeAlias) { const typeAlias = type; return `type ${typeAlias.name} = ${this.getParamType(typeAlias.aliasType)};`; } else if (type.type === AstNodeType.Enum) { const enumType = type; return `enum ${enumType.name} {${enumType.cases .map((c) => { if (c.type === AstNodeType.StringLiteral) { return `${c.name} = "${c.value}"`; } else if (c.type === AstNodeType.DoubleLiteral) { if (c.value !== undefined && c.value !== null) { return `${c.name} = ${c.value}`; } else { return `${c.name}`; } } }) .join(", ")}}`; } else if (type.type === AstNodeType.StructLiteral) { const typeAlias = type; return `type ${typeAlias.name} = ${this.getParamType(typeAlias.typeLiteral)};`; } return ""; } isExternalTypeUsedByOtherType(externalType, type) { if (type.type === AstNodeType.TypeAlias) { const typeAlias = type; return this.isExternalTypeUsedByOtherType(externalType, typeAlias.aliasType); } else if (type.type === AstNodeType.Enum) { return false; } else if (type.type === AstNodeType.StructLiteral) { const typeAlias = type; return this.isExternalTypeUsedByOtherType(externalType, typeAlias.typeLiteral); } else if (type.type === AstNodeType.ArrayType) { return this.isExternalTypeUsedByOtherType(externalType, type.generic); } else if (type.type === AstNodeType.PromiseType) { return this.isExternalTypeUsedByOtherType(externalType, type.generic); } else if (type.type === AstNodeType.UnionType) { return type.params.some((e) => this.isExternalTypeUsedByOtherType(externalType, e)); } else if (type.type === AstNodeType.TypeLiteral) { return type.properties.some((e) => this.isExternalTypeUsedByOtherType(externalType, e.type)); } else if (type.type === AstNodeType.DateType) { return false; } else if (type.type === AstNodeType.CustomNodeLiteral) { // eslint-disable-next-line @typescript-eslint/no-explicit-any if (type.rawValue === externalType.name) { return true; } return false; } else if (type.type === AstNodeType.StringLiteral) { return false; } else if (type.type === AstNodeType.IntegerLiteral || type.type === AstNodeType.FloatLiteral || type.type === AstNodeType.DoubleLiteral) { return false; } else if (type.type === AstNodeType.BooleanLiteral) { return false; } else if (type.type === AstNodeType.AnyLiteral) { return false; } else if (type.type === AstNodeType.MapType) { return (this.isExternalTypeUsedByOtherType(externalType, type.genericKey) || this.isExternalTypeUsedByOtherType(externalType, type.genericValue)); } return false; } isExternalTypeUsedInMethod(externalType, methods) { return methods.some((m) => this.isExternalTypeUsedByOtherType(externalType, m.returnType) || m.params.some((p) => this.isExternalTypeUsedByOtherType(externalType, p.paramType))); } addImportToCurrentView(currentView, externalType) { let found = false; for (const importType of currentView.imports) { if (importType.path === externalType.path && // eslint-disable-next-line @typescript-eslint/no-explicit-any !importType.models.find((e) => e.name === externalType.name)) { // eslint-disable-next-line @typescript-eslint/no-explicit-any importType.models.push({ name: externalType.name }); found = true; break; } } if (!found) { let relativePath = path.relative(currentView.path || ".", externalType.path || "."); while (relativePath.substring(0, 3) == "../") { relativePath = relativePath.substring(3); } // eslint-disable-next-line @typescript-eslint/no-explicit-any if (!currentView.imports.find((e) => e.path === relativePath)) { currentView.imports.push({ path: relativePath.replace(/\\/g, "/"), // eslint-disable-next-line @typescript-eslint/no-explicit-any models: [{ name: externalType.name }], }); } } } addClassItemsToIndex(indexModel, classItems, classPath, className) { for (const originalClassItem of classItems) { const classItem = { ...originalClassItem }; if (classItem.path === classPath) { const rawSdkClassPath = `${className}.sdk`; const sdkClassPath = rawSdkClassPath.charAt(0).toLowerCase() + rawSdkClassPath.slice(1); classItem.path = sdkClassPath; } const index = indexModel.imports.findIndex((i) => i.path === classItem.path); if (index !== -1) { if ( // eslint-disable-next-line @typescript-eslint/no-explicit-any indexModel.imports[index].models.find((i) => i.name === classItem.name)) { continue; } else { indexModel.imports[index].models.push({ // eslint-disable-next-line @typescript-eslint/no-explicit-any name: classItem.name, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any indexModel.exports.push({ name: classItem.name }); } } else { indexModel.imports.push({ path: classItem.path || "", // eslint-disable-next-line @typescript-eslint/no-explicit-any models: [{ name: classItem.name }], }); // eslint-disable-next-line @typescript-eslint/no-explicit-any indexModel.exports.push({ name: classItem.name }); } } } addViewIfNotExists(modelViews, type, classView, classInfo, classDefinition) { let found = false; let currentView = undefined; for (const modelView of modelViews) { if (modelView.path === type.path) { currentView = modelView; found = true; break; } } if (!found) { const classPath = classDefinition?.path?.replace(/\\/g, "/") ?? classInfo.classConfiguration.path.replace(/\\/g, "/"); // @ts-expect-error A refactor need to be performed here to avoid this cast if (!classPath.includes(type.path)) { currentView = { path: type.path || "", externalTypes: [], imports: [], }; modelViews.push(currentView); } else { // @ts-expect-error A refactor need to be performed here to avoid this cast currentView = classView; } } return currentView; } } const supportedLanguages = ["ts", "typescript"]; export default { SdkGenerator, supportedLanguages };