UNPKG

grapi-cli

Version:

a cli tool to generate loopback 4 applications with extra features like caching & fuzzy search

266 lines (265 loc) 10.4 kB
import chalk from 'chalk'; import { existsSync, readFileSync, writeFileSync, statSync, readdirSync, promises as fsPromises } from 'fs'; import { exec, spawn } from 'child_process'; import { join } from 'path'; const execPromise = (command) => { return new Promise((resolve, reject) => { exec(command, (error, stdout, stderr) => { resolve({ error, stdout, stderr }); }); }); }; export async function execute(command, message) { if (message) console.log(chalk.blue(message)); return execPromise(command); } export function applyPatches(patches) { try { Object.keys((patches)).forEach(patchKey => { const patch = patches[patchKey]; Object.keys(patch).forEach(subPatchKey => { let { path: filePath } = patch[subPatchKey]; const { replacement, isRegex, replaceAll, } = patch[subPatchKey]; let { searchString, } = patch[subPatchKey]; if (existsSync(filePath)) { const data = readFileSync(filePath, 'utf8'); let replace = false; if (data) { if (replacement === '') replace = true; if (replacement !== '' && !data.includes(replacement)) { replace = true; } if (replace) { searchString = isRegex ? new RegExp(searchString) : searchString; const updatedContent = data[replaceAll ? 'replaceAll' : 'replace'](searchString, replacement); if (!updatedContent) throw new Error('failed to update the content.'); writeFileSync(filePath, updatedContent, 'utf8'); console.log('file updated successfully.'); } } else { throw new Error('no content found.'); } } }); }); } catch (error) { throw error; } } export async function getNpmGlobalDir() { const response = await execute(`npm list -g`); if (!response.stdout) return ''; const lines = response.stdout.split('\n'); const subdirectory = lines[0].trim(); return `${subdirectory}/node_modules/@loopback/cli`; } // Recursive function to get files export function getFiles(dir, files = []) { if (!existsSync(dir)) return []; const fileList = readdirSync(dir); for (const file of fileList) { const name = `${dir}/${file}`; if (statSync(name).isDirectory()) { getFiles(name, files); } else { files.push(name); } } return files; } export function isJson(item) { let value = typeof item !== 'string' ? JSON.stringify(item) : item; try { value = JSON.parse(value); } catch (e) { return false; } return typeof value === 'object' && value !== null; } export function toKebabCase(str) { return str .replace(/([a-z])([A-Z])/g, '$1-$2') .replace(/[\s_]+/g, '-') .toLowerCase(); } export function toPascalCase(str) { return str.match(/[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g) .map(x => x.charAt(0).toUpperCase() + x.slice(1).toLowerCase()) .join(''); } export function toCamelCase(str) { return str .replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) { if (+match === 0) return ''; return index === 0 ? match.toLowerCase() : match.toUpperCase(); }); } export function addDecoratorToMethod(addDecoratorTo, name, decoratorArguments) { const authDecorator = addDecoratorTo?.getDecorator(name); if (!authDecorator) { addDecoratorTo?.addDecorator({ name, arguments: decoratorArguments }); } } export function addDecoratorToParameter(addDecoratorTo, name, decoratorArguments) { const authDecorator = addDecoratorTo?.getDecorator(name); if (!authDecorator) { addDecoratorTo?.addDecorator({ name, arguments: decoratorArguments }); } } export function addImport(addImportTo, defaultImport, moduleSpecifier, replace = false) { let existingImport = addImportTo?.getImportDeclaration(moduleSpecifier); if (!existingImport) { addImportTo?.addImportDeclaration({ defaultImport: `{${defaultImport}}`, moduleSpecifier }); } else { existingImport = addImportTo?.getImportDeclaration(moduleSpecifier); if (replace) { existingImport.getNamedImports().forEach((eachImport) => { let importText = eachImport.getText(); const pattern = new RegExp(`\\b${importText}\\b`); if (!pattern.test(defaultImport)) { defaultImport += `,${importText}`; } }); existingImport.remove(); addImportTo?.addImportDeclaration({ defaultImport: `{${defaultImport}}`, moduleSpecifier }); } } } export function isLoopBackApp(packageJson) { const { dependencies } = packageJson; if (!dependencies['@loopback/core']) return false; return true; } export function prompt(command, flags, args) { let flagString = '', argString = ''; if (args && Object.keys(args).length) { Object.keys(args).forEach(key => { argString += `${args[key]} `; }); argString = argString.slice(0, -1); } if (flags && Object.keys(flags).length) { Object.keys(flags).forEach(key => { const flag = flags[key]; flagString += `--${key}=${flag} `; }); flagString = flagString.slice(0, -1); } const options = { stdio: 'inherit', // Inherit standard I/O streams from the parent process shell: true, // Run the command in a shell }; let completeCommand = `lb4 ${command}${argString ? ` ${argString}` : ''}${flagString ? ` ${flagString}` : ''}`; const lb4 = spawn(`lb4 ${command}${argString ? ` ${argString}` : ''}${flagString ? ` ${flagString}` : ''}`, [], options); lb4.on('error', (err) => { console.error(`Error starting lb4 command: ${err.message}`); }); lb4.on('close', (code) => { if (code !== null) { console.log(`lb4 command exited with code ${code}`); } else { console.log(`lb4 command was terminated`); } }); } export async function copyFiles(srcDir, destDir, files) { if (!existsSync(destDir)) await fsPromises.mkdir(destDir); return Promise.all(files.map(file => fsPromises.copyFile(join(srcDir, file), join(destDir, file)))); } export function findVersionedFile(filePattern, directory = '.') { try { // Split the pattern around the asterisk const [prefix, suffix] = filePattern.split('*'); // Create different regex patterns based on whether there's content after * let regex; if (suffix && suffix !== '.patch') { // Case 1: Pattern has extra content after version (like -build-schema.patch) regex = new RegExp(`^${prefix .replace(/\./g, '\\.') .replace(/\+/g, '\\+') .replace(/\(/g, '\\(') .replace(/\)/g, '\\)')}[\\d\\.]+${suffix .replace(/\./g, '\\.') .replace(/\+/g, '\\+') .replace(/\(/g, '\\(') .replace(/\)/g, '\\)')}$`); } else { // Case 2: Pattern only has version followed by .patch regex = new RegExp(`^${prefix .replace(/\./g, '\\.') .replace(/\+/g, '\\+') .replace(/\(/g, '\\(') .replace(/\)/g, '\\)')}[\\d\\.]+\\.patch$`); } // Read directory contents const files = readdirSync(directory); // Find matching file const matchingFile = files.find((file) => regex.test(file)); if (!matchingFile) { throw new Error(`No file found matching pattern: ${filePattern}`); } return matchingFile; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to find file: ${error.message}`); } throw new Error('An unknown error occurred while finding the file'); } } export async function removeCodeSection(filePath, startPattern, endPattern, additionalLines = 0) { // Read the file const fileContent = await fsPromises.readFile(filePath, 'utf8'); // Split content into lines const lines = fileContent.split('\n'); // Create pattern matcher functions const matchesStartPattern = typeof startPattern === 'string' ? (line) => line.includes(startPattern) : (line) => startPattern.test(line); const matchesEndPattern = typeof endPattern === 'string' ? (line) => line.includes(endPattern) : (line) => endPattern.test(line); // Find start and end line numbers let startLine = -1; let endLine = -1; for (let i = 0; i < lines.length; i++) { if (startLine === -1 && matchesStartPattern(lines[i])) { startLine = i + 1; // Convert to 1-based indexing } if (startLine !== -1 && endLine === -1 && matchesEndPattern(lines[i])) { endLine = i + 1 + additionalLines; // Convert to 1-based indexing and add additional lines break; } } if (startLine === -1 || endLine === -1) { console.log('Pattern not found in the file.'); return; } // Make sure endLine doesn't exceed file length endLine = Math.min(endLine, lines.length); // Create a new array excluding the specified range const newLines = lines.filter((_, index) => { const lineNumber = index + 1; // Convert to 1-based indexing return lineNumber < startLine || lineNumber > endLine; }); // Join lines back together const newContent = newLines.join('\n'); // Write the modified content back to the file fsPromises.writeFile(filePath, newContent); const linesRemoved = endLine - startLine + 1; console.log(`Removed ${linesRemoved} lines (${startLine}-${endLine}) from ${filePath}`); }