import fs from 'fs' import { platform } from 'node:process' import colors from 'colors' import { execSync, ExecSyncOptionsWithStringEncoding } from 'child_process' import * as esbuild from 'esbuild' import { ConfigFile, IndexState } from './types' export const execConfig: ExecSyncOptionsWithStringEncoding = { encoding: 'utf-8', shell: '/bin/bash' } export const esbuildNodeVersion = 'node18' export const tscNodeVersion = 'esnext' export const esBuildConfig: esbuild.BuildOptions = { bundle: false, platform: 'node', target: esbuildNodeVersion, minifyWhitespace: true, packages: 'external', } type logColors = null | 'grey' | 'green' | 'white' | 'magenta' let previousText = '' export const log = (text: string, color: logColors, state: IndexState, allModes?: boolean) => { // AllModes means that this will log out even if dev mode is not required. // remove all the lines and whitespace around the logs.. const removeSpacesAndNewlines = (str: string): string => str.replace(/\s+/g, '') const consoleLog = () => { let textToPrint = text if (color) textToPrint = colors[color](text) if (removeSpacesAndNewlines(text) !== previousText) { if (text !== '') { console.info(textToPrint) } previousText = removeSpacesAndNewlines(text) } } // If it has nodemon, or watch, don't print it, unless it's dev mode const nodmonRegex = /\[nodemon\].*/g const containsNodemon = nodmonRegex.test(text) const watchRegex = /\[watch\]/ const containsWatch = watchRegex.test(text) if (allModes) { if (!containsNodemon && !containsWatch) { consoleLog() } } else if (state.devMode) { consoleLog() } } export const tickLog = ( text: string, tickColor: logColors, textColor: logColors, state: IndexState, allModes?: boolean, ) => { const consoleLog = () => { let textToPrint = text let tickToPrint = '' if (textColor) textToPrint = colors[textColor](text) if (tickColor) tickToPrint = colors[tickColor](' ✓ ') console.info(tickToPrint, textToPrint) } if (allModes) { consoleLog() } else if (state.devMode) { consoleLog() } } export const checkIfInitShouldRun = (): boolean => { try { if (fs.existsSync('./API')) { return true } console.info(`\n ${colors.red.bold("API folder doesn't exist. Please run:")}`) console.info(`\n ${colors.green.bold('npx wappi init')}`) return process.exit(1) } catch (error) { console.info(`\n ${colors.red.bold("API folder doesn't exist. Please run:")}`) console.info(`\n . ${colors.green.bold('npx wappi init')}`) return process.exit(1) } } export const getDirectories = (): any[] | any => fs.readdirSync('./API/', { withFileTypes: true }).filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) .filter((dirname) => { switch (dirname) { case 'Core': return false case 'CoreHelpers': return false case 'DataLayer': return false default: return true } }) .map((dirname) => ({ title: dirname, value: dirname, })) export const getAllDirectories = (): any[] | any => fs.readdirSync('./API/', { withFileTypes: true }).filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) export const getAllNamespaceDirectories = (namespace: string) => fs.readdirSync(`./API/${namespace}`, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) export const getNamespaceDirectories: any = (namespace: string) => { if (!namespace) process.exit(1) return fs.readdirSync(`./API/${namespace}`, { withFileTypes: true }).filter((dirent) => dirent.isDirectory()) .map((dirent) => dirent.name) .map((dirname) => ({ title: dirname, value: dirname, })) } export const getConfigFile = async () => (JSON.parse(fs.readFileSync('./wappi.json', 'utf8'))) export const clearLogLines = (numberOfLinesToRemove: number) => { process.stdout.moveCursor(0, -numberOfLinesToRemove) process.stdout.clearLine(1) } export const clearLastLogLineIfEmpty = () => { process.stdout.moveCursor(0, -1) process.stdout.clearLine(1) } export const sudoCommand = () => { switch (platform) { case 'darwin': return 'sudo' default: return '' } } export const requiredPackages: string[] = [ 'eslint', 'esbuild', 'jest', 'node-notifier', 'nodemon', 'ts-node', 'tsx', 'wappi', ] export const listRequiredPackages = () => { console.info(`\n ${colors.grey('wappi requires the following packages to be installed globally:')}`) requiredPackages.forEach((packageName) => { console.info(` ${colors.grey(` • ${packageName}`)}`) }) } export const installAllPackages = async (packagesToInstall: string[], ciPipeline?: boolean) => { let sudo = '' // let sudo = sudoCommand() if (ciPipeline) { sudo = '' } console.info(`\n ${colors.yellow('Adding/Updating packages globally now:')}`) try { packagesToInstall.forEach((packageName) => { execSync( `${sudo} rm -rf /opt/homebrew/lib/node_modules/${packageName}`, execConfig, ) }) packagesToInstall.forEach((packageName) => { execSync( `${sudo} npm install ${packageName} -g`, execConfig, ) }) console.info(`\n ${colors.grey('Globally Installed Packages: ')}\n`) const newPackageList = execSync( 'npm list -g', execConfig, ) console.info(colors.grey(newPackageList)) } catch (error) { console.info('Failed... Trying update again') packagesToInstall.forEach((packageName) => { execSync( `${sudo} rm -rf /opt/homebrew/lib/node_modules/${packageName}`, execConfig, ) }) execSync( 'npx wappi u', execConfig, ) } } export const checkPackagesExist = async (state: IndexState) => { const packageList = execSync( 'npm list -g', execConfig, ) const packageArray = packageList.split('\n') const packages = packageArray.map((row) => row.split(' ')[1]) .filter((p) => p !== undefined) .map((row) => row.split('@')[0]) const configFile = await getConfigFile() if (configFile.prisma) { requiredPackages.push('prisma') } const missingPackages = requiredPackages.filter(( requiredPackage, ) => !packages.includes(requiredPackage)) if (missingPackages.length >= 1) { listRequiredPackages() console.info(`\n\n ${colors.red('You are missing the following: ')}`) missingPackages.forEach((packageName) => { console.info(` ${colors.grey(` • ${packageName}`)}`) }) await installAllPackages(missingPackages, state.ciPipeline) } }