@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
JavaScript
;
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';
}
}