@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
442 lines (441 loc) • 18.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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.WorkspaceMigrationManager = void 0;
exports.createWorkspaceMigrationManager = createWorkspaceMigrationManager;
exports.checkForUpgrades = checkForUpgrades;
exports.validateWorkspace = validateWorkspace;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const yaml = __importStar(require("yaml"));
const semver = __importStar(require("semver"));
const error_handler_1 = require("./error-handler");
const workspace_backup_1 = require("./workspace-backup");
// Version compatibility matrix
const VERSION_COMPATIBILITY = {
'1.0.0': ['1.0.1', '1.0.2', '1.1.0'],
'1.0.1': ['1.0.2', '1.1.0', '1.1.1'],
'1.0.2': ['1.1.0', '1.1.1', '1.2.0'],
'1.1.0': ['1.1.1', '1.1.2', '1.2.0'],
'1.1.1': ['1.1.2', '1.2.0', '2.0.0'],
'1.1.2': ['1.2.0', '2.0.0'],
'1.2.0': ['1.2.1', '2.0.0'],
'1.2.1': ['2.0.0'],
'2.0.0': ['2.0.1', '2.1.0'],
'2.0.1': ['2.1.0', '2.1.1'],
'2.1.0': ['2.1.1', '2.2.0'],
'2.1.1': ['2.2.0']
};
// Workspace migration manager
class WorkspaceMigrationManager {
constructor(rootPath = process.cwd()) {
this.migrationSteps = new Map();
this.rootPath = rootPath;
this.initializeMigrationSteps();
}
// Create migration plan
async createMigrationPlan(currentVersion, targetVersion) {
if (!semver.valid(currentVersion) || !semver.valid(targetVersion)) {
throw new error_handler_1.ValidationError('Invalid version format');
}
if (semver.gte(currentVersion, targetVersion)) {
throw new error_handler_1.ValidationError('Target version must be higher than current version');
}
const steps = this.findMigrationPath(currentVersion, targetVersion);
const hasBreakingChanges = steps.some(step => step.breaking);
const backupRequired = hasBreakingChanges || steps.length > 1;
const estimatedDuration = steps.length * 30; // 30 seconds per step
return {
currentVersion,
targetVersion,
steps,
hasBreakingChanges,
backupRequired,
estimatedDuration
};
}
// Execute migration
async executeMigration(workspaceFile, plan, options = {}) {
const startTime = Date.now();
const result = {
success: false,
fromVersion: plan.currentVersion,
toVersion: plan.targetVersion,
stepsExecuted: [],
errors: [],
warnings: [],
duration: 0
};
try {
// Load workspace definition
let definition = await this.loadWorkspaceDefinition(workspaceFile);
// Create backup if required
if ((plan.backupRequired || options.backup) && !options.dryRun) {
try {
const backupManager = await (0, workspace_backup_1.createWorkspaceBackupManager)(this.rootPath);
result.backupId = await backupManager.createBackup(workspaceFile, {
name: `pre-migration-${plan.currentVersion}-to-${plan.targetVersion}`,
description: `Automatic backup before migration from ${plan.currentVersion} to ${plan.targetVersion}`,
includeState: true,
includeTemplates: true
});
}
catch (error) {
result.warnings.push(`Failed to create backup: ${error.message}`);
}
}
// Execute migration steps
for (const step of plan.steps) {
try {
if (options.dryRun) {
result.stepsExecuted.push(`[DRY-RUN] ${step.id}`);
continue;
}
// Validate before execution if step has validation
if (step.validate && !options.skipValidation) {
const validation = await step.validate(definition);
if (!validation.valid) {
throw new error_handler_1.ValidationError(`Pre-migration validation failed for step ${step.id}: ${validation.errors.join(', ')}`);
}
result.warnings.push(...validation.warnings);
}
// Execute migration step
definition = await step.execute(definition);
result.stepsExecuted.push(step.id);
}
catch (error) {
result.errors.push(`Step ${step.id} failed: ${error.message}`);
throw error;
}
}
// Save migrated definition
if (!options.dryRun) {
definition.version = plan.targetVersion;
await this.saveWorkspaceDefinition(workspaceFile, definition);
}
result.success = true;
result.duration = Date.now() - startTime;
}
catch (error) {
result.errors.push(error.message);
result.duration = Date.now() - startTime;
}
return result;
}
// Check for available upgrades
async checkUpgrades(currentVersion) {
const available = VERSION_COMPATIBILITY[currentVersion] || [];
const recommended = available.find(v => semver.diff(currentVersion, v) === 'minor') ||
available[0];
const breaking = available.filter(v => semver.major(v) > semver.major(currentVersion));
return { available, recommended, breaking };
}
// Validate workspace definition
async validateDefinition(definition) {
const result = {
valid: true,
errors: [],
warnings: [],
suggestions: []
};
// Check version format
if (!semver.valid(definition.version)) {
result.errors.push('Invalid version format');
result.valid = false;
}
// Check required fields
if (!definition.name) {
result.errors.push('Workspace name is required');
result.valid = false;
}
if (!definition.workspaces || Object.keys(definition.workspaces).length === 0) {
result.warnings.push('No workspaces defined');
}
// Check workspace consistency
for (const [name, workspace] of Object.entries(definition.workspaces)) {
if (!workspace.type) {
result.errors.push(`Workspace '${name}' missing type`);
result.valid = false;
}
if (!definition.types[workspace.type]) {
result.errors.push(`Workspace '${name}' references undefined type '${workspace.type}'`);
result.valid = false;
}
if (!workspace.path) {
result.warnings.push(`Workspace '${name}' missing path`);
}
}
// Check dependency references
if (definition.dependencies) {
for (const [workspace, deps] of Object.entries(definition.dependencies)) {
if (!definition.workspaces[workspace]) {
result.errors.push(`Dependencies defined for unknown workspace '${workspace}'`);
result.valid = false;
}
for (const dep of deps) {
if (!definition.workspaces[dep.name]) {
result.errors.push(`Workspace '${workspace}' depends on unknown workspace '${dep.name}'`);
result.valid = false;
}
}
}
}
// Suggestions for improvements
if (!definition.description) {
result.suggestions.push('Consider adding a workspace description');
}
if (!definition.scripts || Object.keys(definition.scripts).length === 0) {
result.suggestions.push('Consider defining common scripts');
}
return result;
}
// Get migration history
async getMigrationHistory(workspaceFile) {
const historyFile = path.join(this.rootPath, '.re-shell', 'migration-history.json');
try {
if (await fs.pathExists(historyFile)) {
return await fs.readJson(historyFile);
}
}
catch (error) {
// Return empty history if file doesn't exist or is corrupted
}
return { migrations: [] };
}
// Record migration in history
async recordMigration(fromVersion, toVersion, backupId) {
const historyFile = path.join(this.rootPath, '.re-shell', 'migration-history.json');
let history;
try {
history = await fs.readJson(historyFile);
}
catch (error) {
history = { migrations: [] };
}
history.migrations.push({
date: new Date().toISOString(),
fromVersion,
toVersion,
backupId
});
await fs.ensureDir(path.dirname(historyFile));
await fs.writeJson(historyFile, history, { spaces: 2 });
}
// Find migration path between versions
findMigrationPath(fromVersion, toVersion) {
const path = [];
let currentVersion = fromVersion;
// Simple version incremental migration
while (semver.lt(currentVersion, toVersion)) {
const nextVersion = this.getNextVersion(currentVersion, toVersion);
const stepId = `${currentVersion}-to-${nextVersion}`;
const step = this.migrationSteps.get(stepId);
if (step) {
path.push(step);
}
else {
// Create generic migration step
path.push(this.createGenericMigrationStep(currentVersion, nextVersion));
}
currentVersion = nextVersion;
}
return path;
}
// Get next version in migration path
getNextVersion(currentVersion, targetVersion) {
const compatible = VERSION_COMPATIBILITY[currentVersion];
if (!compatible) {
// If no compatibility info, increment minor version
return semver.inc(currentVersion, 'minor');
}
// Find the closest version to target
const candidates = compatible.filter(v => semver.lte(v, targetVersion));
return candidates.length > 0 ? candidates[0] : compatible[0];
}
// Create generic migration step
createGenericMigrationStep(fromVersion, toVersion) {
return {
id: `${fromVersion}-to-${toVersion}`,
name: `Update version from ${fromVersion} to ${toVersion}`,
description: `Generic version update migration`,
fromVersion,
toVersion,
breaking: semver.major(toVersion) > semver.major(fromVersion),
execute: async (definition) => {
definition.version = toVersion;
return definition;
}
};
}
// Initialize migration steps
initializeMigrationSteps() {
// Add specific migration steps here
// Migration from 1.0.0 to 1.1.0 - Add workspace types
this.migrationSteps.set('1.0.0-to-1.1.0', {
id: '1.0.0-to-1.1.0',
name: 'Add workspace types support',
description: 'Introduces workspace type definitions for better organization',
fromVersion: '1.0.0',
toVersion: '1.1.0',
breaking: false,
execute: async (definition) => {
if (!definition.types) {
definition.types = {
app: {
name: 'Application',
description: 'Frontend application',
framework: 'react'
},
lib: {
name: 'Library',
description: 'Shared library',
framework: 'typescript'
}
};
}
// Ensure all workspaces have a type
for (const [name, workspace] of Object.entries(definition.workspaces)) {
if (!workspace.type) {
workspace.type = 'app'; // Default type
}
}
return definition;
},
validate: async (definition) => {
const result = {
valid: true,
errors: [],
warnings: [],
suggestions: []
};
if (definition.types) {
result.warnings.push('Workspace types already exist, migration may overwrite them');
}
return result;
}
});
// Migration from 1.1.0 to 1.2.0 - Add build configuration
this.migrationSteps.set('1.1.0-to-1.2.0', {
id: '1.1.0-to-1.2.0',
name: 'Add build configuration',
description: 'Introduces centralized build configuration',
fromVersion: '1.1.0',
toVersion: '1.2.0',
breaking: false,
execute: async (definition) => {
if (!definition.build) {
definition.build = {
parallel: true,
cache: true
};
}
return definition;
}
});
// Migration from 1.2.0 to 2.0.0 - Breaking changes
this.migrationSteps.set('1.2.0-to-2.0.0', {
id: '1.2.0-to-2.0.0',
name: 'Migrate to v2 schema',
description: 'Major schema update with breaking changes',
fromVersion: '1.2.0',
toVersion: '2.0.0',
breaking: true,
execute: async (definition) => {
// Migrate workspace entries to new format
for (const [name, workspace] of Object.entries(definition.workspaces)) {
// Convert old 'port' field to dev.port
if ('port' in workspace && typeof workspace.port === 'string') {
if (!workspace.dev) {
workspace.dev = {};
}
workspace.dev.port = parseInt(workspace.port);
delete workspace.port;
}
// Convert old 'framework' field to type config
if ('framework' in workspace && typeof workspace.framework === 'string') {
if (definition.types[workspace.type]) {
definition.types[workspace.type].framework = workspace.framework;
}
delete workspace.framework;
}
}
return definition;
},
rollback: async (definition) => {
// Rollback v2 changes to v1 format
for (const [name, workspace] of Object.entries(definition.workspaces)) {
if (workspace.dev?.port) {
workspace.port = workspace.dev.port.toString();
}
}
definition.version = '1.2.0';
return definition;
}
});
}
// Helper methods
async loadWorkspaceDefinition(filePath) {
if (!(await fs.pathExists(filePath))) {
throw new error_handler_1.ValidationError(`Workspace file not found: ${filePath}`);
}
const content = await fs.readFile(filePath, 'utf8');
return yaml.parse(content);
}
async saveWorkspaceDefinition(filePath, definition) {
const content = yaml.stringify(definition);
await fs.writeFile(filePath, content, 'utf8');
}
}
exports.WorkspaceMigrationManager = WorkspaceMigrationManager;
// Utility functions
async function createWorkspaceMigrationManager(rootPath) {
return new WorkspaceMigrationManager(rootPath);
}
// Quick upgrade check
async function checkForUpgrades(workspaceFile) {
const manager = new WorkspaceMigrationManager();
const definition = await manager['loadWorkspaceDefinition'](workspaceFile);
const upgrades = await manager.checkUpgrades(definition.version);
return {
currentVersion: definition.version,
...upgrades
};
}
// Validate workspace
async function validateWorkspace(workspaceFile) {
const manager = new WorkspaceMigrationManager();
const definition = await manager['loadWorkspaceDefinition'](workspaceFile);
return await manager.validateDefinition(definition);
}