import generate from "@babel/generator"; import { parse } from "@babel/parser"; import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"; import prettier from "prettier"; import defaultConfig from "../griddorc"; import { AddDeclOptions, AstModuleOptions, ElementType, FileType } from "./types"; function generateAstFromSourceFile(sourceFile: string) { const modulesIndexFile = readFileSync(sourceFile, "utf8"); const ast = parse(modulesIndexFile, { sourceType: "module", plugins: ["jsx", "typescript"] }); return ast; } function addComponentToDefaultExport({ ast, name }) { const index = ast.program.body.findIndex( (entry: any) => entry.type === "ExportDefaultDeclaration" ); ast.program.body[index].declaration.properties.push({ type: "ObjectProperty", method: false, key: { type: "Identifier", name: name }, computed: false, shorthand: true, value: { type: "Identifier", name: name }, extra: { shorthand: true } }); return ast; } function addComponentToNamedExport({ ast, name }) { const namedExportIndex = ast.program.body.findIndex( (entry: any) => entry.type === "ExportNamedDeclaration" ); ast.program.body[namedExportIndex].specifiers.push({ type: "ExportSpecifier", local: { type: "Identifier", name: name }, exported: { type: "Identifier", name: name } }); return ast; } function addElementToVarDecl( options: AstModuleOptions, declarationName: string = "EXPORTS" ) { const exportEntry = { type: "ObjectProperty", key: { type: "Identifier", name: options.name }, value: { type: "Identifier", name: options.name }, shorthand: true }; const VAR_DECL_INDEX = options.ast["program"]["body"].findIndex( // TODO Remove any (entry: any) => { if ( entry.type === "VariableDeclaration" && entry["declarations"][0].id["name"] === declarationName ) { return true; } } ); options.ast["program"]["body"][VAR_DECL_INDEX]["declarations"][0][ "init" ].properties.push(exportEntry); return options.ast; } function addImportDeclaration(options: AstModuleOptions) { const importDeclaration = `${options.alias || "."}/${options.name}`; const exportEntry = { type: "ImportDeclaration", specifiers: [ { type: "ImportDefaultSpecifier", local: { type: "Identifier", name: options.name } } ], source: { type: "StringLiteral", extra: { rawValue: importDeclaration, raw: `'${importDeclaration}'` }, value: importDeclaration } }; options.ast["program"]["body"].unshift(exportEntry); return options.ast; } function addLazyImportDeclaration(options: AstModuleOptions) { const importDeclaration = `${options.alias || "."}/${options.name}`; const REACT_LAZY_INDEX = options.ast["program"]["body"].findIndex( // TODO Remove any (entry: any) => entry.type === "VariableDeclaration" && entry?.declarations[0].init?.callee?.object?.name === "React" && entry?.declarations[0].init?.callee?.property?.name === "lazy" ); const REACT_IMPORT_INDEX = options.ast["program"]["body"].findIndex( // TODO Remove any (entry: any) => entry.type === "ImportDeclaration" && entry.specifiers[0].local.name === "React" ); const lazyImport = { type: "VariableDeclaration", declarations: [ { type: "VariableDeclarator", id: { type: "Identifier", name: options.name }, init: { type: "CallExpression", callee: { type: "MemberExpression", object: { type: "Identifier", name: "React" }, computed: false, property: { type: "Identifier", name: "lazy" } }, arguments: [ { type: "ArrowFunctionExpression", id: null, generator: false, async: false, params: [], body: { type: "CallExpression", callee: { type: "Import" }, arguments: [ { type: "StringLiteral", value: importDeclaration } ] } } ] } } ], kind: "const" }; const IMPORT_INDEX = REACT_LAZY_INDEX !== -1 ? REACT_LAZY_INDEX : REACT_IMPORT_INDEX + 1; options.ast["program"]["body"].splice(IMPORT_INDEX, 0, lazyImport); return options.ast; } function generateCodeWithPrettier(code: any) { const ROOT = process.cwd(); let prettierConfig = { useTabs: true }; if (existsSync(`${ROOT}/.prettierrc.json`)) { prettierConfig = require(`${ROOT}/.prettierrc.json`); } const codeWithPrettier = prettier.format(code, { ...prettierConfig, parser: "typescript" }); return codeWithPrettier; } function codeFromAST(ast: any) { const { code } = generate(ast, { comments: true, concise: false, minified: false }); return generateCodeWithPrettier(code); } function writeFile(baseFolder: string, file: string, data: string) { const dstFile = `${baseFolder}/${file}`; // Try to create a new directory try { mkdirSync(baseFolder, { recursive: true }); } catch (e) { console.log(`El directorio ${baseFolder} ya existe. Pass.`); } // try to write the file try { if (existsSync(dstFile)) { console.warn(`Warning: El archivo ${dstFile} ya existe.`); } else { writeFileSync(dstFile, data); } } catch (err) { console.log(err); } } function writeSchemaFile(baseFolder: string, name: string, data: string) { writeFile(baseFolder, `${name}.ts`, data); } function writeGriddoFile(baseFolder: string, name: string, data: string) { writeFile(`${baseFolder}/${name}`, "index.tsx", data); } function writeStorybookFile(baseFolder: string, name: string, data: string) { writeFile(`${baseFolder}/${name}`, "stories.tsx", data); } function writeStylesFile(baseFolder: string, name: string, data: string) { writeFile(`${baseFolder}/${name}`, "styles.module.css", data); } function addDecl({ type, ast, name, alias }: AddDeclOptions) { if (type === "import") { return addImportDeclaration({ ast, name, alias }); } if (type === "lazyImport") { return addLazyImportDeclaration({ ast, name, alias }); } if (type === "namedExport") { return addComponentToNamedExport({ ast, name }); } if (type === "defaultExport") { return addComponentToDefaultExport({ ast, name }); } } function addTypeReference({ ast, name }) { const typeReferenceIndex = ast.program.body.findIndex( (entry: any) => entry.type === "TSTypeAliasDeclaration" ); const typeReference = { type: "TSTypeReference", typeName: { type: "TSQualifiedName", left: { type: "Identifier", name: "AutoType" }, right: { type: "Identifier", name: name } } }; if ( Array.isArray(ast.program.body[typeReferenceIndex].typeAnnotation.types) ) { ast.program.body[typeReferenceIndex].typeAnnotation.types.push( typeReference ); } else if (ast.program.body[typeReferenceIndex].typeAnnotation.typeName) { ast.program.body[typeReferenceIndex].typeAnnotation = { type: "TSUnionType", types: [ ast.program.body[typeReferenceIndex].typeAnnotation.typeName, typeReference ] }; delete ast.program.body[typeReferenceIndex].typeAnnotation.typeName; } return ast; } function checkIfElementExists({ type, name }) { let exit = false; try { if ( existsSync(`${defaultConfig[type].ui}/${name}/index.tsx`) || existsSync(`${defaultConfig[type].schema}/${name}.ts`) ) { console.log(`${name} ya existe [${type}]`); exit = true; process.exit(1); } } catch (err) { console.log(err); exit = true; } return exit; } function readTemplate( baseFolder: string, type: ElementType, filetype: FileType ) { const output = readFileSync(`${baseFolder}/${type}.${filetype}`, "utf8"); return output; } function isGriddoProject() { const ROOT = process.cwd(); const packageJson = require(`${ROOT}/package.json`); const dependencies = Object.keys(packageJson.dependencies); return ( dependencies.includes("@griddo/ax") && dependencies.includes("@griddo/core") && dependencies.includes("@griddo/cx") ); } export { generateAstFromSourceFile, addComponentToNamedExport, addElementToVarDecl, addComponentToDefaultExport, addImportDeclaration, codeFromAST, writeGriddoFile, writeSchemaFile, addDecl, addTypeReference, checkIfElementExists, readTemplate, writeStorybookFile, writeStylesFile, isGriddoProject };