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

484 lines (483 loc) 19.3 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.importProject = importProject; exports.exportProject = exportProject; exports.backupProject = backupProject; exports.restoreProject = restoreProject; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const chalk_1 = __importDefault(require("chalk")); const monorepo_1 = require("../utils/monorepo"); async function importProject(sourcePath, options = {}) { try { if (options.spinner) { options.spinner.text = 'Analyzing source project...'; } // Validate source path const resolvedSource = path.resolve(sourcePath); if (!await fs.pathExists(resolvedSource)) { throw new Error(`Source path does not exist: ${sourcePath}`); } // Analyze the source project const projectConfig = await analyzeProject(resolvedSource, options); if (options.verbose) { console.log('\n' + chalk_1.default.blue('Project Analysis:')); console.log(` Name: ${projectConfig.name}`); console.log(` Type: ${projectConfig.type}`); console.log(` Framework: ${projectConfig.framework}`); console.log(` Package Manager: ${projectConfig.packageManager}`); console.log(` Workspaces: ${projectConfig.workspaces?.length || 0}`); } // Create backup if requested if (options.backup) { await createProjectBackup(resolvedSource, options); } // Import to Re-Shell structure if (projectConfig.type === 'monorepo') { await importMonorepo(resolvedSource, projectConfig, options); } else { await importStandaloneProject(resolvedSource, projectConfig, options); } if (options.spinner) { options.spinner.succeed(chalk_1.default.green('Project imported successfully!')); } // Display next steps console.log('\n' + chalk_1.default.bold('Next Steps:')); console.log('1. Review the imported project structure'); console.log('2. Install dependencies: pnpm install'); console.log('3. Run health check: re-shell doctor'); console.log('4. Start development: re-shell serve'); } catch (error) { if (options.spinner) { options.spinner.fail(chalk_1.default.red('Import failed')); } throw error; } } async function exportProject(targetPath, options = {}) { try { if (options.spinner) { options.spinner.text = 'Preparing export...'; } const monorepoRoot = await (0, monorepo_1.findMonorepoRoot)(process.cwd()); if (!monorepoRoot) { throw new Error('Not in a Re-Shell monorepo. Run this command from within a monorepo.'); } const resolvedTarget = path.resolve(targetPath); // Check if target exists if (await fs.pathExists(resolvedTarget) && !options.force) { throw new Error(`Target path already exists: ${targetPath}. Use --force to overwrite.`); } // Create export structure await createExportStructure(monorepoRoot, resolvedTarget, options); if (options.spinner) { options.spinner.succeed(chalk_1.default.green('Project exported successfully!')); } console.log('\n' + chalk_1.default.bold('Export Complete:')); console.log(` Location: ${resolvedTarget}`); console.log(` Includes: Source code, configurations, documentation`); } catch (error) { if (options.spinner) { options.spinner.fail(chalk_1.default.red('Export failed')); } throw error; } } async function backupProject(options = {}) { try { const monorepoRoot = await (0, monorepo_1.findMonorepoRoot)(process.cwd()); if (!monorepoRoot) { throw new Error('Not in a Re-Shell monorepo'); } const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const backupName = `re-shell-backup-${timestamp}`; const backupPath = path.join(path.dirname(monorepoRoot), backupName); if (options.spinner) { options.spinner.text = 'Creating backup...'; } await createProjectBackup(monorepoRoot, { ...options, customPath: backupPath }); if (options.spinner) { options.spinner.succeed(chalk_1.default.green('Backup created successfully!')); } console.log('\n' + chalk_1.default.bold('Backup Details:')); console.log(` Location: ${backupPath}`); console.log(` Size: ${await getDirectorySize(backupPath)}`); } catch (error) { if (options.spinner) { options.spinner.fail(chalk_1.default.red('Backup failed')); } throw error; } } async function restoreProject(backupPath, targetPath, options = {}) { try { if (options.spinner) { options.spinner.text = 'Restoring from backup...'; } const resolvedBackup = path.resolve(backupPath); const resolvedTarget = path.resolve(targetPath); if (!await fs.pathExists(resolvedBackup)) { throw new Error(`Backup path does not exist: ${backupPath}`); } if (await fs.pathExists(resolvedTarget) && !options.force) { throw new Error(`Target path already exists: ${targetPath}. Use --force to overwrite.`); } // Copy backup to target await fs.copy(resolvedBackup, resolvedTarget, { overwrite: options.force, filter: (src) => { const relative = path.relative(resolvedBackup, src); return !relative.includes('node_modules') && !relative.includes('.git'); } }); if (options.spinner) { options.spinner.succeed(chalk_1.default.green('Project restored successfully!')); } console.log('\n' + chalk_1.default.bold('Restore Complete:')); console.log(` Location: ${resolvedTarget}`); console.log(' Remember to run: pnpm install'); } catch (error) { if (options.spinner) { options.spinner.fail(chalk_1.default.red('Restore failed')); } throw error; } } async function analyzeProject(projectPath, options) { const packageJsonPath = path.join(projectPath, 'package.json'); if (!await fs.pathExists(packageJsonPath)) { throw new Error('No package.json found in the source project'); } const packageJson = await fs.readJson(packageJsonPath); // Detect project type const isMonorepo = !!(packageJson.workspaces || await fs.pathExists(path.join(projectPath, 'lerna.json')) || await fs.pathExists(path.join(projectPath, 'nx.json')) || await fs.pathExists(path.join(projectPath, 'turbo.json'))); // Detect framework const framework = detectFramework(packageJson); // Detect package manager const packageManager = detectPackageManager(projectPath); // Get workspaces for monorepos let workspaces = []; if (isMonorepo) { workspaces = await getWorkspaces(projectPath, packageJson); } // Check for common tools const hasGit = await fs.pathExists(path.join(projectPath, '.git')); const hasTypeScript = !!(packageJson.devDependencies?.typescript || packageJson.dependencies?.typescript || await fs.pathExists(path.join(projectPath, 'tsconfig.json'))); const hasESLint = !!(packageJson.devDependencies?.eslint || packageJson.dependencies?.eslint); const hasPrettier = !!(packageJson.devDependencies?.prettier || packageJson.dependencies?.prettier); const hasTesting = !!(packageJson.devDependencies?.jest || packageJson.devDependencies?.vitest || packageJson.devDependencies?.['@testing-library/react']); return { name: packageJson.name || 'imported-project', type: isMonorepo ? 'monorepo' : 'standalone', framework, packageManager, workspaces, dependencies: packageJson.dependencies || {}, devDependencies: packageJson.devDependencies || {}, scripts: packageJson.scripts || {}, hasGit, hasTypeScript, hasESLint, hasPrettier, hasTesting }; } function detectFramework(packageJson) { const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (deps.react) { return deps.typescript ? 'react-ts' : 'react'; } if (deps.vue) { return deps.typescript ? 'vue-ts' : 'vue'; } if (deps.svelte) { return deps.typescript ? 'svelte-ts' : 'svelte'; } if (deps.next) { return 'next'; } if (deps.nuxt) { return 'nuxt'; } if (deps.angular || deps['@angular/core']) { return 'angular'; } return 'vanilla'; } function detectPackageManager(projectPath) { if (fs.existsSync(path.join(projectPath, 'pnpm-lock.yaml'))) return 'pnpm'; if (fs.existsSync(path.join(projectPath, 'yarn.lock'))) return 'yarn'; if (fs.existsSync(path.join(projectPath, 'bun.lockb'))) return 'bun'; return 'npm'; } async function getWorkspaces(projectPath, packageJson) { if (packageJson.workspaces) { if (Array.isArray(packageJson.workspaces)) { return packageJson.workspaces; } else if (packageJson.workspaces.packages) { return packageJson.workspaces.packages; } } // Check for Lerna const lernaPath = path.join(projectPath, 'lerna.json'); if (await fs.pathExists(lernaPath)) { const lernaConfig = await fs.readJson(lernaPath); return lernaConfig.packages || ['packages/*']; } // Check for Nx const nxPath = path.join(projectPath, 'nx.json'); if (await fs.pathExists(nxPath)) { // Nx uses workspace.json or project.json files return ['apps/*', 'libs/*']; } return []; } async function importMonorepo(sourcePath, config, options) { const targetPath = path.join(process.cwd(), config.name); if (options.dryRun) { console.log(chalk_1.default.blue('DRY RUN - Would import monorepo:')); console.log(` Source: ${sourcePath}`); console.log(` Target: ${targetPath}`); console.log(` Workspaces: ${config.workspaces?.join(', ')}`); return; } // Create Re-Shell monorepo structure await fs.ensureDir(targetPath); // Copy root files const rootFiles = ['package.json', '.gitignore', 'README.md', 'tsconfig.json', '.eslintrc.js']; for (const file of rootFiles) { const srcFile = path.join(sourcePath, file); const destFile = path.join(targetPath, file); if (await fs.pathExists(srcFile)) { await fs.copy(srcFile, destFile); } } // Update package.json for Re-Shell const packageJsonPath = path.join(targetPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); // Add Re-Shell specific configurations packageJson.devDependencies = { ...packageJson.devDependencies, '@re-shell/cli': 'latest' }; packageJson.scripts = { ...packageJson.scripts, 'dev': 're-shell serve', 'build': 're-shell build', 'lint': 're-shell workspace list --json | jq -r ".[] | .path" | xargs -I {} pnpm --filter {} lint' }; await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); // Copy workspaces if (config.workspaces) { for (const workspace of config.workspaces) { const sourceDirs = await expandGlob(path.join(sourcePath, workspace)); for (const sourceDir of sourceDirs) { const relativePath = path.relative(sourcePath, sourceDir); const targetDir = path.join(targetPath, relativePath); await fs.copy(sourceDir, targetDir, { filter: (src) => { const relative = path.relative(sourceDir, src); return !relative.includes('node_modules') && !relative.includes('dist'); } }); } } } if (options.verbose) { console.log(chalk_1.default.green(`✓ Imported monorepo with ${config.workspaces?.length || 0} workspaces`)); } } async function importStandaloneProject(sourcePath, config, options) { const monorepoRoot = await (0, monorepo_1.findMonorepoRoot)(process.cwd()); if (!monorepoRoot) { throw new Error('Not in a Re-Shell monorepo. Create one first with "re-shell init"'); } const targetPath = path.join(monorepoRoot, 'apps', config.name); if (options.dryRun) { console.log(chalk_1.default.blue('DRY RUN - Would import standalone project:')); console.log(` Source: ${sourcePath}`); console.log(` Target: ${targetPath}`); console.log(` Framework: ${config.framework}`); return; } // Copy project to apps directory await fs.copy(sourcePath, targetPath, { filter: (src) => { const relative = path.relative(sourcePath, src); return !relative.includes('node_modules') && !relative.includes('dist'); } }); // Update package.json for workspace const packageJsonPath = path.join(targetPath, 'package.json'); const packageJson = await fs.readJson(packageJsonPath); packageJson.name = `@${path.basename(monorepoRoot)}/${config.name}`; packageJson.private = true; await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 }); // Update root package.json workspaces const rootPackageJsonPath = path.join(monorepoRoot, 'package.json'); const rootPackageJson = await fs.readJson(rootPackageJsonPath); if (!rootPackageJson.workspaces) { rootPackageJson.workspaces = []; } const workspacePattern = `apps/${config.name}`; if (!rootPackageJson.workspaces.includes(workspacePattern)) { rootPackageJson.workspaces.push(workspacePattern); } await fs.writeJson(rootPackageJsonPath, rootPackageJson, { spaces: 2 }); if (options.verbose) { console.log(chalk_1.default.green(`✓ Imported standalone project as workspace`)); } } async function createProjectBackup(sourcePath, options) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5); const backupName = `backup-${timestamp}`; const backupPath = options.customPath || path.join(path.dirname(sourcePath), backupName); await fs.copy(sourcePath, backupPath, { filter: (src) => { const relative = path.relative(sourcePath, src); return !relative.includes('node_modules') && !relative.includes('.git') && !relative.includes('dist') && !relative.includes('build'); } }); // Create backup manifest const manifest = { created: new Date().toISOString(), source: sourcePath, backup: backupPath, version: '1.0.0' }; await fs.writeJson(path.join(backupPath, '.backup-manifest.json'), manifest, { spaces: 2 }); if (options.verbose) { console.log(chalk_1.default.green(`✓ Backup created: ${backupPath}`)); } } async function createExportStructure(sourcePath, targetPath, options) { await fs.ensureDir(targetPath); // Copy essential files and directories const itemsToCopy = [ 'package.json', 'apps', 'packages', 'libs', 'tools', '.gitignore', 'README.md', 'tsconfig.json', '.eslintrc.js', 'turbo.json', 'nx.json' ]; for (const item of itemsToCopy) { const srcPath = path.join(sourcePath, item); const destPath = path.join(targetPath, item); if (await fs.pathExists(srcPath)) { await fs.copy(srcPath, destPath, { filter: (src) => { const relative = path.relative(srcPath, src); return !relative.includes('node_modules') && !relative.includes('.git') && !relative.includes('dist') && !relative.includes('build'); } }); } } // Create export manifest const manifest = { exported: new Date().toISOString(), source: sourcePath, export: targetPath, version: '1.0.0', tool: 'Re-Shell CLI' }; await fs.writeJson(path.join(targetPath, '.export-manifest.json'), manifest, { spaces: 2 }); if (options.verbose) { console.log(chalk_1.default.green(`✓ Export created: ${targetPath}`)); } } async function expandGlob(pattern) { const baseDir = path.dirname(pattern); const globPattern = path.basename(pattern); if (!globPattern.includes('*')) { const fullPath = path.resolve(pattern); return await fs.pathExists(fullPath) ? [fullPath] : []; } try { const items = await fs.readdir(baseDir, { withFileTypes: true }); const matches = []; for (const item of items) { if (item.isDirectory()) { const fullPath = path.join(baseDir, item.name); if (await fs.pathExists(path.join(fullPath, 'package.json'))) { matches.push(fullPath); } } } return matches; } catch (error) { return []; } } async function getDirectorySize(dirPath) { try { // Simple estimation - actual recursive calculation would be too slow const stats = await fs.stat(dirPath); return '< 1MB (estimated)'; } catch (error) { return 'Unknown'; } }