UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

348 lines (338 loc) 13.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createProject = createProject; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const prompts_1 = __importDefault(require("prompts")); const chalk_1 = __importDefault(require("chalk")); const framework_1 = require("../utils/framework"); const monorepo_1 = require("../utils/monorepo"); const react_1 = require("../templates/frontend/react"); const vue_1 = require("../templates/frontend/vue"); const svelte_1 = require("../templates/frontend/svelte"); const spinner_1 = require("../utils/spinner"); /** * Creates a new Re-Shell project or workspace * * @param name - Name of the project/workspace * @param options - Additional options for project creation * @version 0.2.5 */ async function createProject(name, options) { // Check if we're in a monorepo const monorepoRoot = await (0, monorepo_1.findMonorepoRoot)(); const inMonorepo = !!monorepoRoot; // Determine if this is a monorepo project creation or workspace creation const isWorkspaceCreation = inMonorepo || options.type; if (isWorkspaceCreation) { await createWorkspace(name, options, monorepoRoot); } else { await createMonorepoProject(name, options); } } /** * Creates a new workspace (app/package/lib/tool) in an existing monorepo */ async function createWorkspace(name, options, monorepoRoot) { const { team, org = 're-shell', description, framework, packageManager = 'pnpm', type = 'app', port = '5173', route, spinner, } = options; const normalizedName = name.toLowerCase().replace(/\s+/g, '-'); const rootPath = monorepoRoot || process.cwd(); console.log(chalk_1.default.cyan(`Creating ${type} "${normalizedName}"...`)); // Stop spinner for interactive prompts if (spinner) { spinner.stop(); } // Interactive prompts for missing options const responses = await (0, prompts_1.default)([ { type: !framework ? 'select' : null, name: 'framework', message: 'Select a framework:', choices: (0, framework_1.getFrameworkChoices)(), initial: 1, // Default to react-ts }, { type: type === 'app' && !port ? 'text' : null, name: 'port', message: 'Development server port:', initial: '5173', validate: (value) => { const num = parseInt(value); return num > 0 && num < 65536 ? true : 'Port must be between 1 and 65535'; }, }, { type: type === 'app' && !route ? 'text' : null, name: 'route', message: 'Route path:', initial: `/${normalizedName}`, validate: (value) => (value.startsWith('/') ? true : 'Route must start with /'), }, ]); // Restart spinner for file operations if (spinner) { spinner.start(); spinner.setText(`Creating ${type} files...`); (0, spinner_1.flushOutput)(); } // Merge responses with options const finalFramework = framework || responses.framework || 'react-ts'; const finalPort = port || responses.port || '5173'; const finalRoute = route || responses.route || `/${normalizedName}`; // Validate framework if (!(0, framework_1.validateFramework)(finalFramework)) { throw new Error(`Unsupported framework: ${finalFramework}`); } // Determine workspace path based on type const typeDir = type === 'app' ? 'apps' : type === 'package' ? 'packages' : type === 'lib' ? 'libs' : 'tools'; const workspacePath = path.join(rootPath, typeDir, normalizedName); // Check if directory already exists and handle it gracefully if (fs.existsSync(workspacePath)) { if (spinner) spinner.stop(); const { action } = await (0, prompts_1.default)({ type: 'select', name: 'action', message: `Directory "${normalizedName}" already exists in ${typeDir}/. What would you like to do?`, choices: [ { title: 'Overwrite existing directory', value: 'overwrite' }, { title: 'Cancel', value: 'cancel' }, ], initial: 0, }); if (action === 'cancel') { console.log(chalk_1.default.yellow('Operation cancelled.')); return; } if (action === 'overwrite') { if (spinner) { spinner.start(); spinner.setText('Removing existing directory...'); (0, spinner_1.flushOutput)(); } await fs.remove(workspacePath); } if (spinner) { spinner.start(); spinner.setText(`Creating ${type} files...`); (0, spinner_1.flushOutput)(); } } // Get framework configuration const frameworkConfig = (0, framework_1.getFrameworkConfig)(finalFramework); // Create template context const templateContext = { name, normalizedName, framework: finalFramework, hasTypeScript: frameworkConfig.hasTypeScript || false, port: finalPort, route: type === 'app' ? finalRoute : undefined, org, team, description: description || `${name} - A ${frameworkConfig.displayName} ${type}`, packageManager, }; // Generate files using appropriate template const template = createTemplate(frameworkConfig, templateContext); const files = await template.generateFiles(); // Create workspace directory await fs.ensureDir(workspacePath); // Write all generated files for (const file of files) { const filePath = path.join(workspacePath, file.path); await fs.ensureDir(path.dirname(filePath)); await fs.writeFile(filePath, file.content); if (file.executable) { await fs.chmod(filePath, '755'); } } console.log(chalk_1.default.green(`✓ ${type.charAt(0).toUpperCase() + type.slice(1)} "${normalizedName}" created successfully!`)); console.log(chalk_1.default.gray(`Path: ${path.relative(process.cwd(), workspacePath)}`)); console.log('\nNext steps:'); console.log(` 1. cd ${path.relative(process.cwd(), workspacePath)}`); console.log(` 2. ${packageManager} install`); console.log(` 3. ${packageManager} run dev`); } /** * Creates a new monorepo project (legacy function for backward compatibility) */ async function createMonorepoProject(name, options) { const { team, org = 're-shell', description = `${name} - A Re-Shell microfrontend project`, spinner, } = options; // Normalize name to kebab-case for consistency const normalizedName = name.toLowerCase().replace(/\s+/g, '-'); console.log(chalk_1.default.cyan(`Creating Re-Shell project "${normalizedName}"...`)); // Stop spinner for interactive prompts if (spinner) { spinner.stop(); } // Ask for additional information if not provided const responses = await (0, prompts_1.default)([ { type: options.template ? null : 'select', name: 'template', message: 'Select a template:', choices: [ { title: 'React', value: 'react' }, { title: 'React with TypeScript', value: 'react-ts' }, ], initial: 1, // Default to react-ts }, { type: options.packageManager ? null : 'select', name: 'packageManager', message: 'Select a package manager:', choices: [ { title: 'npm', value: 'npm' }, { title: 'yarn', value: 'yarn' }, { title: 'pnpm', value: 'pnpm' }, ], initial: 2, // Default to pnpm }, ]); // Merge responses with options const finalOptions = { ...options, template: options.template || responses.template, packageManager: options.packageManager || responses.packageManager, }; // Restart spinner for file operations if (spinner) { spinner.start(); spinner.setText('Creating project structure...'); (0, spinner_1.flushOutput)(); } // Create project structure const projectPath = path.resolve(process.cwd(), normalizedName); // Check if directory already exists if (fs.existsSync(projectPath)) { throw new Error(`Directory already exists: ${projectPath}`); } // Create directory structure fs.mkdirSync(projectPath); fs.mkdirSync(path.join(projectPath, 'apps')); fs.mkdirSync(path.join(projectPath, 'packages')); fs.mkdirSync(path.join(projectPath, 'docs')); // Create package.json for the project const packageJson = { name: normalizedName, version: '0.1.0', description, private: true, workspaces: ['apps/*', 'packages/*'], scripts: { dev: `${finalOptions.packageManager} run --parallel -r dev`, build: `${finalOptions.packageManager} run --parallel -r build`, lint: `${finalOptions.packageManager} run --parallel -r lint`, test: `${finalOptions.packageManager} run --parallel -r test`, clean: `${finalOptions.packageManager} run --parallel -r clean`, }, author: team || org, license: 'MIT', }; fs.writeFileSync(path.join(projectPath, 'package.json'), JSON.stringify(packageJson, null, 2)); // Create workspace config if (finalOptions.packageManager === 'pnpm') { fs.writeFileSync(path.join(projectPath, 'pnpm-workspace.yaml'), `packages:\n - 'apps/*'\n - 'packages/*'\n`); } // Create README.md const readmeContent = `# ${normalizedName} ## Overview A microfrontend project created with Re-Shell CLI. ## Project Structure \`\`\` ${normalizedName}/ ├── apps/ # Microfrontend applications │ └── shell/ # Main shell application ├── packages/ # Shared libraries └── docs/ # Documentation \`\`\` ## Getting Started ### Installation \`\`\`bash # Install dependencies ${finalOptions.packageManager} install \`\`\` ### Development \`\`\`bash # Start all applications in development mode ${finalOptions.packageManager} run dev \`\`\` ### Building \`\`\`bash # Build all applications ${finalOptions.packageManager} run build \`\`\` ## Adding Microfrontends To add a new microfrontend to this project: \`\`\`bash re-shell add my-feature \`\`\` ## Documentation For more information, see the [Re-Shell documentation](https://github.com/your-org/re-shell) `; fs.writeFileSync(path.join(projectPath, 'README.md'), readmeContent); console.log(chalk_1.default.green(`\nRe-Shell project "${normalizedName}" created successfully at ${projectPath}`)); console.log('\nNext steps:'); console.log(` 1. cd ${normalizedName}`); console.log(` 2. ${finalOptions.packageManager} install`); console.log(` 3. ${finalOptions.packageManager} run dev`); console.log(` 4. re-shell add my-feature (to add your first microfrontend)`); } /** * Creates appropriate template instance based on framework */ function createTemplate(framework, context) { switch (framework.name) { case 'react': case 'react-ts': return new react_1.ReactTemplate(framework, context); case 'vue': case 'vue-ts': return new vue_1.VueTemplate(framework, context); case 'svelte': case 'svelte-ts': return new svelte_1.SvelteTemplate(framework, context); // Add more frameworks as templates are implemented default: // Fallback to React template for unsupported frameworks return new react_1.ReactTemplate(framework, context); } }