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