UNPKG

skaya

Version:

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

339 lines (338 loc) 15.3 kB
#!/usr/bin/env ts-node "use strict"; /** * @file CLI interface for full-stack web3 project scaffolding tool * @module cli * @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 }); const commander_1 = require("commander"); const action_1 = require("../src/action"); const enums_1 = require("./types/enums"); const validator_1 = require("./utils/validator"); const prompt_1 = require("./utils/prompt"); const errorHandler_1 = require("./utils/errorHandler"); const inquirer_1 = __importDefault(require("inquirer")); const fs_1 = require("fs"); const path_1 = require("path"); const configLogger_1 = require("./utils/configLogger"); const ProjectScanner_1 = require("./utils/ProjectScanner"); // Read package.json to get version const packageJsonPath = (0, path_1.join)(__dirname, '..', 'package.json'); const packageJson = JSON.parse((0, fs_1.readFileSync)(packageJsonPath, 'utf-8')); const program = new commander_1.Command(); program .name(packageJson.name) .version(packageJson.version) .description(packageJson.description); // Project initialization command program .command("init [type]") .description("Initialize a new project") .action((type) => __awaiter(void 0, void 0, void 0, function* () { try { // If type wasn't provided, prompt the user to select one if (!type) { const answer = yield inquirer_1.default.prompt([ { type: 'list', name: 'projectType', message: 'Please select a project type first:', choices: [ { name: 'Frontend', value: enums_1.ProjectType.FRONTEND }, { name: 'Backend', value: enums_1.ProjectType.BACKEND }, { name: 'Blockchain', value: enums_1.ProjectType.BLOCKCHAIN } ], } ]); type = answer.projectType; } if (type && !(0, validator_1.isValidProjectType)(type)) { throw new Error(`Invalid project type "${type}". Use '${enums_1.ProjectType.FRONTEND}' or '${enums_1.ProjectType.BACKEND}'.`); } yield (0, action_1.createProject)(type); console.log(`✅ Successfully created ${type} project`); } catch (error) { (0, errorHandler_1.handleCliError)(error, "project initialization"); } })); // Component creation command program .command("create [type]") .allowUnknownOption() .description("Create a new component (interactive mode if no type specified)") .option(`-p, --project <type>", "Project type (${enums_1.ProjectType.FRONTEND} or ${enums_1.ProjectType.BACKEND}) or ${enums_1.ProjectType.BLOCKCHAIN}`) .option("-f, --filename <name>", "Filename for the component") .option("-a, --ai <boolean>", "Use AI to generate the component", false) .option("-d, --description <text>", "Description of the component") .action((type, options) => __awaiter(void 0, void 0, void 0, function* () { var _a; try { let projectType; let componentType; let fileName = options.filename; // Interactive mode if (!type && !options.project) { const answers = yield inquirer_1.default.prompt([ { type: 'list', name: 'projectType', message: 'Select project type:', choices: Object.values(enums_1.ProjectType) }, { type: 'list', name: 'componentType', message: 'Select component type:', choices: (answers) => { if (answers.projectType === enums_1.ProjectType.FRONTEND) { return Object.values(enums_1.FrontendComponentType); } else if (answers.projectType === enums_1.ProjectType.BACKEND) { return Object.values(enums_1.BackendComponentType); } else if (answers.projectType === enums_1.ProjectType.BLOCKCHAIN) { return Object.values(enums_1.BlokchainComponentType); } return []; } }, { type: 'input', name: 'fileName', message: 'Enter filename (without extension):', when: () => !fileName, validate: (input) => !!input || 'Filename is required' } ]); projectType = answers.projectType; componentType = answers.componentType; fileName = fileName || answers.fileName; } // Partial interactive (project type specified) else if (!type && options.project) { projectType = options.project.toLowerCase(); if (!(0, validator_1.isValidProjectType)(projectType)) { throw new Error(`Invalid project type. Use '${enums_1.ProjectType.FRONTEND}' or '${enums_1.ProjectType.BACKEND}'.`); } componentType = yield (0, prompt_1.promptComponentType)(projectType); // Prompt for additional info if not provided const additionalAnswers = yield inquirer_1.default.prompt([ { type: 'input', name: 'fileName', message: 'Enter filename (without extension):', when: () => !fileName, validate: (input) => !!input || 'Filename is required' }, ]); fileName = fileName || additionalAnswers.fileName; } // Non-interactive mode else { projectType = ((_a = options.project) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || enums_1.ProjectType.FRONTEND; if (!(0, validator_1.isValidProjectType)(projectType)) { throw new Error(`Invalid project type. Use '${enums_1.ProjectType.FRONTEND}' or '${enums_1.ProjectType.BACKEND}'.`); } if (projectType === enums_1.ProjectType.FRONTEND && type && !(0, validator_1.isValidFrontendComponent)(type)) { throw new Error(`Invalid frontend componene. Ust type. Use '${Object.values(enums_1.FrontendComponentType).join("' or '")}'.`); } if (projectType === enums_1.ProjectType.BACKEND && type && !(0, validator_1.isValidBackendComponent)(type)) { throw new Error(`Invalid backend component type '${Object.values(enums_1.BackendComponentType).join("' or '")}'.`); } componentType = type; } if (!fileName) { throw new Error('Filename is required to save the AI_generated component'); } fileName = fileName.charAt(0).toUpperCase() + fileName.slice(1).toLowerCase(); const params = { componentType, projectType, fileName, }; yield (0, action_1.createFile)(params); // Log the component creation yield (0, configLogger_1.logComponentCreation)({ componentType, projectType, fileName, }); } catch (error) { (0, errorHandler_1.handleCliError)(error, "component creation"); } })); // Update command in your CLI file program .command("update [type]") .allowUnknownOption() .description("Update an existing component (interactive mode)") .option(`-p, --project <type>", "Project type (${enums_1.ProjectType.FRONTEND} or ${enums_1.ProjectType.BACKEND}) or ${enums_1.ProjectType.BLOCKCHAIN}`) .option("-a, --ai <boolean>", "Use AI to update the component", false) .option("-d, --description <text>", "New description of the component") .action((type, options) => __awaiter(void 0, void 0, void 0, function* () { try { let projectType; let componentType; // Interactive mode for project type if not specified if (!options.project) { const projectAnswer = yield inquirer_1.default.prompt([ { type: 'list', name: 'projectType', message: 'Select project type:', choices: Object.values(enums_1.ProjectType) } ]); projectType = projectAnswer.projectType; } else { projectType = options.project.toLowerCase(); if (!(0, validator_1.isValidProjectType)(projectType)) { throw new Error(`Invalid project type. Use '${enums_1.ProjectType.FRONTEND}' or '${enums_1.ProjectType.BACKEND}'.`); } } // Interactive mode for component type if not specified if (!type) { componentType = yield (0, prompt_1.promptComponentType)(projectType); } else { if (projectType === enums_1.ProjectType.FRONTEND && !(0, validator_1.isValidFrontendComponent)(type)) { throw new Error(`Invalid frontend component type. Use '${Object.values(enums_1.FrontendComponentType).join("' or '")}'.`); } if (projectType === enums_1.ProjectType.BACKEND && !(0, validator_1.isValidBackendComponent)(type)) { throw new Error(`Invalid backend component type '${Object.values(enums_1.BackendComponentType).join("' or '")}'.`); } componentType = type; } // Scan for existing components const existingComponents = yield (0, ProjectScanner_1.scanExistingComponents)(projectType, componentType); if (existingComponents.length === 0) { throw new Error(`No existing ${componentType} components found to update.`); } // Let user select which component to update const { selectedComponent } = yield inquirer_1.default.prompt([ { type: 'list', name: 'selectedComponent', message: `Select ${componentType} to update:`, choices: existingComponents.map(comp => ({ name: comp.name, value: comp.name })) } ]); const params = { componentType, projectType, fileName: selectedComponent, }; yield (0, action_1.updateFile)(params); console.log(`✅ Successfully updated ${componentType} component: ${selectedComponent}`); } catch (error) { (0, errorHandler_1.handleCliError)(error, "component update"); } })); program .command("start") .description("Start development environment(s) for your project(s)") .option("-a, --all", "Start all project types (frontend, backend, blockchain)") .action((options) => __awaiter(void 0, void 0, void 0, function* () { try { let projectTypes; const { selectedTypes } = yield inquirer_1.default.prompt([ { type: 'checkbox', name: 'selectedTypes', message: 'Which project types do you want to start?', choices: Object.values(enums_1.ProjectType).map(type => ({ name: type, value: type, checked: options.all // Select all if --all flag is used })), validate: (input) => input.length > 0 || 'You must select at least one project type' } ]); projectTypes = selectedTypes; // Start all selected projects yield (0, action_1.startProjects)(projectTypes); } catch (error) { (0, errorHandler_1.handleCliError)(error, "project startup"); } })); // Installation command program .command("install") .description("Install project components (frontend, backend, blockchain, or all)") .option("-a, --all", "Install all components (frontend, backend, blockchain)") .option("-f, --frontend", "Install frontend components") .option("-b, --backend", "Install backend components") .option("-c, --blockchain", "Install blockchain components") .action((options) => __awaiter(void 0, void 0, void 0, function* () { try { let projectTypes = []; if (options.all) { projectTypes = [ enums_1.ProjectType.FRONTEND, enums_1.ProjectType.BACKEND, enums_1.ProjectType.BLOCKCHAIN ]; } else { // If no specific options provided, prompt the user if (!options.frontend && !options.backend && !options.blockchain) { const answers = yield inquirer_1.default.prompt([ { type: 'checkbox', name: 'components', message: 'Which components would you like to install?', choices: [ { name: 'Frontend', value: enums_1.ProjectType.FRONTEND }, { name: 'Backend', value: enums_1.ProjectType.BACKEND }, { name: 'Blockchain', value: enums_1.ProjectType.BLOCKCHAIN } ], validate: (input) => input.length > 0 || 'You must select at least one component' } ]); projectTypes = answers.components; } else { // Add selected components based on flags if (options.frontend) projectTypes.push(enums_1.ProjectType.FRONTEND); if (options.backend) projectTypes.push(enums_1.ProjectType.BACKEND); if (options.blockchain) projectTypes.push(enums_1.ProjectType.BLOCKCHAIN); } } if (projectTypes.length === 0) { throw new Error('No components selected for installation'); } yield (0, action_1.installComponents)(projectTypes); console.log('✅ Installation completed successfully'); } catch (error) { (0, errorHandler_1.handleCliError)(error, "component installation"); } })); // Parse CLI arguments program.parseAsync(process.argv).catch((error) => { (0, errorHandler_1.handleCliError)(error, "argument parsing"); });