@interopio/iocd-cli
Version:
CLI tool for setting up, building and packaging io.Connect Desktop platforms
328 lines • 14.5 kB
JavaScript
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.AppService = void 0;
const child_process_1 = require("child_process");
const fs_1 = require("fs");
const path_1 = require("path");
const logger_1 = require("../utils/logger");
const error_handler_1 = require("../utils/error.handler");
const concurrently_1 = __importDefault(require("concurrently"));
/**
* Service for managing template applications.
*
* This service:
* 1. Discovers all apps in the apps/ folder of generated projects
* 2. Installs dependencies for all apps (npm install)
* 3. Builds apps and copies them to modifications folder (npm run build)
* 4. Provides progress feedback during operations
*/
class AppService {
logger = logger_1.Logger.getInstance();
/**
* Install dependencies for all applications found in the apps/ directory
*/
async installAllApps() {
const appsDir = (0, path_1.join)(process.cwd(), 'apps');
if (!(0, fs_1.existsSync)(appsDir)) {
this.logger.debug('No apps/ directory found - skipping app installs');
return;
}
const apps = this.discoverApps(appsDir);
if (apps.length === 0) {
this.logger.info('No applications found in apps/ directory');
return;
}
this.logger.info(`Found ${apps.length} applications to install dependencies for:`);
apps.forEach(app => this.logger.info(` - ${app.name}`));
for (const app of apps) {
await this.installApp(app);
}
}
/**
* Build all applications found in the apps/ directory
*/
async buildAllApps() {
const appsDir = (0, path_1.join)(process.cwd(), 'apps');
if (!(0, fs_1.existsSync)(appsDir)) {
this.logger.debug('No apps/ directory found - skipping app builds');
return;
}
const apps = this.discoverApps(appsDir);
if (apps.length === 0) {
this.logger.debug('No applications found in apps/ directory');
return;
}
this.logger.info(`Found ${apps.length} template applications to build: ${apps.map(a => a.name).join(", ")}`);
for (const app of apps) {
await this.buildApp(app);
await this.copyModifications(app, "prod");
}
this.logger.info('All template applications built and moved successfully!');
}
async startAllAppsInDevMode() {
const appsDir = (0, path_1.join)(process.cwd(), 'apps');
if (!(0, fs_1.existsSync)(appsDir)) {
this.logger.info('No apps/ directory found - skipping app starts');
return;
}
const apps = this.discoverApps(appsDir);
if (apps.length === 0) {
this.logger.info('No applications found in apps/ directory');
return;
}
this.logger.info(`Found ${apps.length} template applications to start in dev mode: ${apps.map(a => a.name).join(", ")}`);
// read the iocd.app.json and copy modifications
apps.forEach(app => this.copyModifications(app, "dev"));
this.startAppsInParallel(apps);
this.logger.info('All template applications started in dev mode successfully!');
}
/**
* Build a specific application
*/
async buildApp(appConfig) {
const appPath = (0, path_1.join)(process.cwd(), 'apps', appConfig.name);
this.logger.info(`Building application ${appConfig.name}...`);
try {
// Check if build script exists
const packageJsonPath = (0, path_1.join)(appPath, 'package.json');
const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
if (!packageJson.scripts?.build) {
this.logger.warn(`No build script found for ${appConfig.name} - skipping`);
return;
}
// Execute npm run build in the app directory
this.logger.debug(`Executing: npm run build in ${appPath}`);
(0, child_process_1.execSync)('npm run build', {
cwd: appPath,
stdio: 'pipe',
encoding: 'utf-8'
});
this.logger.info(`Application ${appConfig.name} built successfully!`);
}
catch (error) {
this.logger.error(`Failed to build application ${appConfig.name}: ${error}`);
throw new error_handler_1.CLIError(`Failed to build application: ${appConfig.name}. ${error}`, { code: error_handler_1.ErrorCode.BUILD_ERROR });
}
}
/**
* Start a specific application in development mode
*/
async startAppInDevMode(appName) {
const appPath = (0, path_1.join)(process.cwd(), 'apps', appName);
this.logger.info(`Starting ${appName} in dev mode...`);
try {
// Check if build script exists
const packageJsonPath = (0, path_1.join)(appPath, 'package.json');
const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8'));
if (!packageJson.scripts?.build) {
this.logger.warn(`No build script found for ${appName} - skipping`);
return;
}
// Execute npm run dev in the app directory
this.logger.debug(`Executing: npm run dev in ${appPath}`);
(0, child_process_1.exec)('npm run dev', {
cwd: appPath,
encoding: 'utf-8'
});
this.logger.info(`${appName} started successfully!`);
}
catch (error) {
this.logger.error(`Failed to start ${appName}: ${error}`);
throw new error_handler_1.CLIError(`Failed to start application: ${appName}. ${error}`, { code: error_handler_1.ErrorCode.BUILD_ERROR });
}
}
async getAllApps() {
const appsDir = (0, path_1.join)(process.cwd(), 'apps');
if (!(0, fs_1.existsSync)(appsDir)) {
this.logger.debug('No apps/ directory found - skipping app listing');
return [];
}
const apps = this.discoverApps(appsDir);
if (apps.length === 0) {
this.logger.debug('No applications found in apps/ directory');
return [];
}
return apps;
}
/**
* Discover all app directories in the apps/ folder and return detailed app information
*/
discoverApps(appsDir) {
try {
return (0, fs_1.readdirSync)(appsDir)
.map(item => {
const appPath = (0, path_1.join)(appsDir, item);
const isDirectory = (0, fs_1.statSync)(appPath).isDirectory();
if (!isDirectory) {
return null;
}
const hasPackageJson = (0, fs_1.existsSync)((0, path_1.join)(appPath, 'package.json'));
const iocdAppJsonPath = (0, path_1.join)(appPath, 'iocd.app.json');
const hasAppJson = (0, fs_1.existsSync)(iocdAppJsonPath);
if (!hasPackageJson) {
this.logger.debug(`Skipping ${item}: no package.json found`);
return null;
}
if (!hasAppJson) {
this.logger.debug(`Skipping ${item}: no iocd.app.json found`);
return null;
}
try {
// Parse the iocd.app.json configuration
const configContent = (0, fs_1.readFileSync)(iocdAppJsonPath, 'utf-8');
const config = JSON.parse(configContent);
this.logger.debug(`Discovered app: ${item} with config: ${JSON.stringify(config)}`);
return {
name: item,
path: appPath,
config: config
};
}
catch (error) {
this.logger.error(`Failed to parse iocd.app.json for ${item}: ${error}`);
return null;
}
})
.filter((app) => app !== null);
}
catch (error) {
this.logger.error(`Error discovering apps: ${error}`);
return [];
}
}
/**
* Install dependencies for a specific application
*/
async installApp(appConfig) {
const appPath = appConfig.path;
this.logger.info(`Installing dependencies for ${appConfig.name}...`);
try {
// Check if package.json exists
const packageJsonPath = (0, path_1.join)(appPath, 'package.json');
if (!(0, fs_1.existsSync)(packageJsonPath)) {
this.logger.warn(` No package.json found for ${appConfig.name} - skipping`);
return;
}
// Execute npm install in the app directory
this.logger.debug(`Executing: npm install in ${appPath}`);
(0, child_process_1.execSync)('npm install', {
cwd: appPath,
stdio: 'pipe',
encoding: 'utf-8'
});
this.logger.info(`Dependencies for ${appConfig.name} installed successfully!`);
}
catch (error) {
this.logger.error(`Failed to install dependencies for ${appConfig.name}: ${error}`);
throw new error_handler_1.CLIError(`Failed to install dependencies for application: ${appConfig.name}. ${error}`, { code: error_handler_1.ErrorCode.BUILD_ERROR });
}
}
async startAppsInParallel(apps) {
const tasks = apps.map(app => ({
command: 'npm run start',
name: app.name,
cwd: app.path
}));
this.logger.debug(JSON.stringify(tasks));
// Start all apps
(0, concurrently_1.default)(tasks, {
prefix: 'name', // add app name before each log line
killOthersOn: ['failure', 'success'], // stop all if one fails
restartTries: 0,
}).result.then(() => console.log('All processes exited successfully!'), () => console.error('One of the processes failed'));
}
copyModifications(appConfig, mode) {
const modificationsConfigs = appConfig.config[mode]?.modifications;
if (!modificationsConfigs) {
this.logger.warn(`No modifications found for ${appConfig.name} in ${mode} mode - skipping`);
return;
}
this.logger.info(`Copying ${modificationsConfigs.length} modification(s) for ${appConfig.name} in ${mode} mode`);
// Copy each modification
for (const config of modificationsConfigs) {
const srcPath = (0, path_1.join)(appConfig.path, config.source);
const destPath = (0, path_1.join)(process.cwd(), config.destination);
this.logger.debug(`Copying modification from ${srcPath} to ${destPath}`);
try {
// Check if source exists
if (!(0, fs_1.existsSync)(srcPath)) {
this.logger.warn(`Source path does not exist: ${srcPath} - skipping`);
continue;
}
// Get source stats to determine if it's a file or directory
const srcStats = (0, fs_1.statSync)(srcPath);
if (srcStats.isFile()) {
// Handle file copying
this.copyFile(srcPath, destPath);
}
else if (srcStats.isDirectory()) {
// Handle directory copying
this.copyDirectory(srcPath, destPath);
}
else {
this.logger.warn(`Source is neither file nor directory: ${srcPath} - skipping`);
continue;
}
this.logger.debug(`Successfully copied ${config.source} to ${config.destination}`);
}
catch (error) {
this.logger.error(`Failed to copy ${config.source} to ${config.destination}: ${error}`);
throw new error_handler_1.CLIError(`Failed to copy modification for ${appConfig.name}: ${error}`, { code: error_handler_1.ErrorCode.FILE_SYSTEM_ERROR });
}
}
this.logger.info(`Modifications copied successfully!`);
}
/**
* Copy a single file, creating destination directory if needed
*/
copyFile(srcPath, destPath) {
// Ensure destination directory exists
const destDir = (0, path_1.dirname)(destPath);
if (!(0, fs_1.existsSync)(destDir)) {
(0, fs_1.mkdirSync)(destDir, { recursive: true });
this.logger.debug(`Created destination directory: ${destDir}`);
}
// Copy the file
(0, fs_1.copyFileSync)(srcPath, destPath);
this.logger.debug(`Copied file: ${srcPath} → ${destPath}`);
}
/**
* Copy a directory recursively, wiping out target first if it exists
*/
copyDirectory(srcPath, destPath) {
// If destination exists, remove it completely
if ((0, fs_1.existsSync)(destPath)) {
this.logger.debug(`Removing existing destination directory: ${destPath}`);
(0, fs_1.rmSync)(destPath, { recursive: true, force: true });
}
// Create destination directory
(0, fs_1.mkdirSync)(destPath, { recursive: true });
this.logger.debug(`Created destination directory: ${destPath}`);
// Copy all contents recursively
this.copyDirectoryContents(srcPath, destPath);
}
/**
* Recursively copy directory contents
*/
copyDirectoryContents(srcDir, destDir) {
const items = (0, fs_1.readdirSync)(srcDir);
for (const item of items) {
const srcItemPath = (0, path_1.join)(srcDir, item);
const destItemPath = (0, path_1.join)(destDir, item);
const itemStats = (0, fs_1.statSync)(srcItemPath);
if (itemStats.isFile()) {
(0, fs_1.copyFileSync)(srcItemPath, destItemPath);
this.logger.debug(`Copied file: ${srcItemPath} → ${destItemPath}`);
}
else if (itemStats.isDirectory()) {
(0, fs_1.mkdirSync)(destItemPath, { recursive: true });
this.copyDirectoryContents(srcItemPath, destItemPath);
}
}
}
}
exports.AppService = AppService;
//# sourceMappingURL=app.service.js.map
;