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
JavaScript
;
/**
* @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;
}
});
}