import type { ChildProcess } from 'child_process' import { spawn } from 'child_process' import path from 'path' import fs, { existsSync } from 'fs-extra' import { Command } from '@oclif/command' import chokidar from 'chokidar' import chalk from 'chalk' import { getSalesAppMonorepoDir, getTempDir } from '../utils/directory' import { createTempDir } from '../utils/createTempDir' import { logger } from '../utils/logger' import { runWebpackDevServer } from '../utils/webpack' const defaultIgnored = [ '.DS_Store', 'README.md', '.gitignore', 'node_modules/**', '**/node_modules/**', '.git/**', 'package.json', ] export default class Dev extends Command { public static description = 'Start your Sales App in development mode' public static examples: string[] | undefined = ['$ sales-app dev {account}'] public static args: Command['ctor']['args'] = [ { name: 'account', description: 'your VTEX account', required: true, }, ] private staticServerProcess: ChildProcess | null = null public async run() { const { args } = this.parse(Dev) const monorepoDir = getSalesAppMonorepoDir(args.account) const monorepoSrcDir = path.join(monorepoDir, 'src') const tempDir = getTempDir(monorepoDir) if (!existsSync(monorepoSrcDir)) { logger.error( `${chalk.red( 'Error' )} - Sales App project not created. Please run "sales-app create" first` ) return } await createTempDir(monorepoDir) this.log(`${chalk.green('Success')} - Project started successfully`) this.log( `${chalk.blue('Info')} - Watching for changes in ${monorepoSrcDir}` ) const watcher = chokidar.watch(monorepoSrcDir, { persistent: true, ignored: defaultIgnored, }) await runWebpackDevServer(monorepoDir) this.runStaticServer(args.account, tempDir) watcher .on('add', (filePath) => { logger.log(`${chalk.green('Added')} - File added: ${filePath}`) const destFilePath = path.join( tempDir, 'src', path.relative(monorepoSrcDir, filePath) ) fs.copyFileSync(filePath, destFilePath) logger.log( `${chalk.green('Copied')} - Copied ${filePath} to ${destFilePath}` ) }) .on('change', (filePath) => { logger.log(`${chalk.yellow('Changed')} - File changed: ${filePath}`) const destFilePath = path.join( tempDir, 'src', path.relative(monorepoSrcDir, filePath) ) fs.copyFileSync(filePath, destFilePath) logger.log( `${chalk.yellow('Copied')} - Copied ${filePath} to ${destFilePath}` ) }) .on('unlink', (filePath) => { const destFilePath = path.join( tempDir, 'src', path.relative(monorepoSrcDir, filePath) ) try { if (existsSync(destFilePath)) { fs.unlinkSync(destFilePath) logger.log(`${chalk.red('Deleted')} - Deleted ${destFilePath}`) } } catch (error) { logger.error( `${chalk.red( 'Error' )} - Error deleting file ${destFilePath}: ${error}` ) } }) .on('error', (error) => { console.error(`${chalk.red('Watcher Error')} - ${error}`) }) process.on('SIGINT', () => this.terminateProcesses()) process.on('SIGTERM', () => this.terminateProcesses()) } private terminateProcesses() { logger.log( `${chalk.yellow('Warning')} - Received termination signal. Stopping...` ) this.staticServerProcess?.kill(1) process.exit() } private runStaticServer(account: string, tempDir: string) { const child = spawn('node', ['dist/server/index', '--account', account], { cwd: tempDir, stdio: 'inherit', shell: true, }) child.on('error', (err) => { logger.log(`${chalk.red('Error')} - Error starting proxy server: ${err}`) this.terminateProcesses() }) child.on('close', (code) => { if (code !== 0) { logger.log( `${chalk.red('Error')} - proxy server exited with code ${code}` ) } this.terminateProcesses() }) this.staticServerProcess = child } }