UNPKG

skaya

Version:

CLI SDK for full-stack automation: scaffold frontend, backend & blockchain. Future-ready for Web3, integrations, server components & logging.

312 lines (311 loc) 15.3 kB
"use strict"; /** * @file Project scaffolding actions * @module action * @version 1.0.0 * @license MIT */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createProject = createProject; exports.createFile = createFile; exports.updateFile = updateFile; exports.startProjects = startProjects; exports.installComponents = installComponents; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const enums_1 = require("../bin/types/enums"); // Ensure ComponentType is imported const inquirer_1 = __importDefault(require("inquirer")); const configLogger_1 = require("../bin/utils/configLogger"); const templateGenerator_1 = require("./scripts/templateGenerator"); const TemplateService_1 = __importDefault(require("./services/TemplateService")); const ProjectScanner_1 = require("../bin/utils/ProjectScanner"); const execa_1 = require("execa"); /** * Creates a new project scaffold * @param {ProjectType} projectType - The type of project to create */ function createProject(projectType) { return __awaiter(this, void 0, void 0, function* () { // Prompt for project folder name const { folder } = yield inquirer_1.default.prompt([ { type: "input", name: "folder", message: `Enter ${projectType} project folder name:`, default: `${projectType}SkayaProject`, // default folder name }, ]); // todo: Add for backend and blockchain components if (projectType === enums_1.ProjectType.BLOCKCHAIN) { console.log(`⚠️ ${projectType} component creation is coming soon!`); return; } const targetPath = path_1.default.join(process.cwd(), folder); // !important: Using process.cwd() to ensure correct path resolution only while creating project if (yield fs_extra_1.default.pathExists(targetPath)) { throw new Error(`Folder ${folder} already exists.`); } // Create basic project structure based on type const { templateType, customRepo } = yield TemplateService_1.default.promptTemplateSelection(projectType); yield TemplateService_1.default.cloneTemplate(templateType, customRepo, targetPath, projectType); yield (0, configLogger_1.saveProjectConfig)(projectType, folder, templateType); console.log(`✅ ${projectType} project initialized in ${folder}`); }); } /** * Creates a new component file with optional AI generation * @param {ICreateComponentParams} params - Component creation parameters */ function createFile(params) { return __awaiter(this, void 0, void 0, function* () { const { componentType, projectType, fileName } = params; const answers = yield inquirer_1.default.prompt([ { type: "input", name: "folder", message: `Enter the folder where you want to create the ${componentType} for ${fileName}:`, default: yield (0, ProjectScanner_1.getDefaultFolderForComponentType)(projectType, componentType), }, ]); // todo: Add for backend and blockchain components if (projectType === enums_1.ProjectType.BACKEND || projectType === enums_1.ProjectType.BLOCKCHAIN) { console.log(`⚠️ ${projectType} component creation is coming soon!`); return; } const targetFolder = answers.folder; const { createdFiles, aiDescription, imports } = yield (0, templateGenerator_1.generateFromTemplate)({ projectType, componentType, fileName, targetFolder, }); yield (0, configLogger_1.saveProjectComponentConfig)(projectType, componentType, fileName, Object.assign(Object.assign({ source: aiDescription ? 'ai' : 'template', files: createdFiles }, (aiDescription && { aiPrompt: aiDescription })), (imports && { imports }) // Save imports if they exist )); for (const filePath of createdFiles) { console.log(`✅ ${componentType} file created at ${filePath}`); } }); } /** * Updates an existing component file * @param {ICreateComponentParams} params - Component update parameters */ function updateFile(params) { return __awaiter(this, void 0, void 0, function* () { const { componentType, projectType, fileName } = params; // Get the default folder for this component type const targetFolder = yield (0, ProjectScanner_1.getDefaultFolderForComponentType)(projectType, componentType); const componentPath = path_1.default.join(process.cwd(), targetFolder, fileName); // Verify the component exists if (!(yield fs_extra_1.default.pathExists(componentPath))) { throw new Error(`Component ${fileName} not found at ${componentPath}`); } if (projectType === enums_1.ProjectType.BACKEND || projectType === enums_1.ProjectType.BLOCKCHAIN) { console.log(`⚠️ ${projectType} component update is coming soon!`); return; } // Load existing config first const existingConfig = yield (0, configLogger_1.getProjectComponentConfig)(projectType, fileName); if (!existingConfig) { throw new Error(`Configuration not found for component ${fileName}. Cannot update.`); } // !dev Use updateExistingTemplateFiles: true or not const { createdFiles, aiDescription, imports } = yield (0, templateGenerator_1.generateFromTemplate)({ projectType, componentType, fileName, targetFolder, updateExistingTemplateFiles: true }); // Prepare the update data by merging with existing config const updateData = Object.assign(Object.assign(Object.assign(Object.assign({}, existingConfig), { files: createdFiles }), (aiDescription && { aiPrompt: aiDescription })), { imports: imports || existingConfig.imports, updatedAt: new Date().toISOString() // Add update timestamp }); // This will update the existing config rather than create new yield (0, configLogger_1.saveProjectComponentConfig)(projectType, componentType, fileName, updateData); // Update reverse references if imports changed if (imports && !areImportsEqual(imports, existingConfig.imports)) { yield (0, configLogger_1.updateComponentReferences)(projectType, fileName, imports, existingConfig.imports); } for (const filePath of createdFiles) { console.log(`✅ ${componentType} file updated at ${filePath}`); } }); } // Helper to compare imports function areImportsEqual(importsA = [], importsB = []) { if (importsA.length !== importsB.length) return false; const aNames = importsA.map(i => i.name).sort(); const bNames = importsB.map(i => i.name).sort(); return JSON.stringify(aNames) === JSON.stringify(bNames); } /** * Starts development environments showing all available scripts * @param {ProjectType[]} projectTypes - Array of project types to check */ function startProjects(projectTypes) { return __awaiter(this, void 0, void 0, function* () { try { console.log('🚀 Available development scripts:'); // Read global config const config = yield (0, configLogger_1.readConfig)(); const allScripts = []; // Collect all available scripts from all projects for (const projectType of projectTypes) { // Determine project directory let projectDir = process.cwd(); const projectConfig = getProjectConfig(config, projectType); const projectName = (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.name) || projectType.toLowerCase(); if (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.name) { projectDir = path_1.default.join(process.cwd(), projectConfig.name); } // Check if directory exists if (!fs_extra_1.default.existsSync(projectDir)) { console.warn(`⚠️ ${projectType} project directory not found at: ${projectDir}`); continue; } // Read package.json const packageJsonPath = path_1.default.join(projectDir, 'package.json'); if (!fs_extra_1.default.existsSync(packageJsonPath)) { console.warn(`⚠️ No package.json found in ${projectType} project at: ${projectDir}`); continue; } const packageJson = JSON.parse(fs_extra_1.default.readFileSync(packageJsonPath, 'utf-8')); const scripts = packageJson.scripts || {}; // Add all scripts to the list for (const [scriptName, scriptCommand] of Object.entries(scripts)) { allScripts.push({ projectType, projectName, scriptName, scriptCommand: scriptCommand, path: projectDir }); } } if (allScripts.length === 0) { console.error('❌ No scripts found in any project'); return; } // Let user select which scripts to run const { scriptsToRun } = yield inquirer_1.default.prompt([ { type: 'checkbox', name: 'scriptsToRun', message: 'Select scripts to run:', choices: allScripts.map(script => ({ name: `${script.projectType} (${script.projectName}): ${script.scriptName} - ${script.scriptCommand}`, value: script, short: `${script.projectType}: ${script.scriptName}` })), validate: (input) => input.length > 0 || 'You must select at least one script' } ]); // Start all selected scripts const processes = scriptsToRun.map((script) => { console.log(`🏃 Starting ${script.projectType} (${script.projectName}) with: npm run ${script.scriptName}`); return (0, execa_1.execa)('npm', ['run', script.scriptName], { cwd: script.path, stdio: 'inherit', shell: true }); }); // Wait for all processes yield Promise.all(processes); } catch (error) { console.error('❌ Failed to start projects:'); throw error; } }); } /** * Helper function to get project config based on project type */ function getProjectConfig(config, projectType) { switch (projectType) { case enums_1.ProjectType.FRONTEND: return config.frontend; case enums_1.ProjectType.BACKEND: return config.backend; case enums_1.ProjectType.BLOCKCHAIN: return config.blockchain; default: return undefined; } } /** * Installs components for specified project types * @param {ProjectType[]} projectTypes - Array of project types to install components for */ function installComponents(projectTypes) { return __awaiter(this, void 0, void 0, function* () { try { console.log(`📦 Installing dependencies for: ${projectTypes.join(', ')}`); // Read global config first const config = yield (0, configLogger_1.readConfig)(); const results = {}; for (const projectType of projectTypes) { try { console.log(`\n🔧 Starting ${projectType} installation...`); // Get project directory from config let projectDir = process.cwd(); const projectConfig = getProjectConfig(config, projectType); if (projectConfig === null || projectConfig === void 0 ? void 0 : projectConfig.name) { projectDir = path_1.default.join(process.cwd(), projectConfig.name); } else if (projectType !== enums_1.ProjectType.FRONTEND) { console.warn(`⚠️ No project name configured for ${projectType}. Using current directory.`); } // Verify project directory exists if (!fs_extra_1.default.existsSync(projectDir)) { throw new Error(`Project directory not found: ${projectDir}`); } // Check for package.json const packageJsonPath = path_1.default.join(projectDir, 'package.json'); if (!fs_extra_1.default.existsSync(packageJsonPath)) { throw new Error(`No package.json found in ${projectDir}`); } console.log(`Running npm install in directory: ${projectDir}`); // Execute installation yield (0, execa_1.execa)('npm', ['install'], { stdio: 'inherit', cwd: projectDir, shell: true }); results[projectType] = true; console.log(`✅ ${projectType} dependencies installed successfully`); } catch (error) { results[projectType] = false; console.error(`❌ Failed to install ${projectType} dependencies:`, error); } } // Print summary console.log('\n📊 Installation Summary:'); for (const [type, success] of Object.entries(results)) { console.log(` ${type}: ${success ? '✅ Success' : '❌ Failed'}`); } // Throw error if any installations failed if (Object.values(results).some(success => !success)) { throw new Error('Some installations failed'); } } catch (error) { console.error('❌ Installation failed:'); throw error; } }); }