claude-flow
Version:
Ruflo - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration
672 lines • 29.5 kB
JavaScript
/**
* V3 CLI Deployment Command
* Deployment management, environments, rollbacks
*
* Created with ❤️ by ruv.io
*/
import { output } from '../output.js';
import * as fs from 'fs';
import * as path from 'path';
// ============================================
// State Helpers
// ============================================
function getStateDir(cwd) {
return path.join(cwd, '.claude-flow');
}
function getStatePath(cwd) {
return path.join(getStateDir(cwd), 'deployments.json');
}
function emptyState() {
return { environments: {}, history: [], activeDeployment: undefined };
}
function loadDeploymentState(cwd) {
const filePath = getStatePath(cwd);
if (!fs.existsSync(filePath)) {
return emptyState();
}
try {
const raw = fs.readFileSync(filePath, 'utf-8');
return JSON.parse(raw);
}
catch {
return emptyState();
}
}
function saveDeploymentState(cwd, state) {
const dir = getStateDir(cwd);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const filePath = getStatePath(cwd);
const tmpPath = filePath + '.tmp';
fs.writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf-8');
fs.renameSync(tmpPath, filePath);
}
function generateId() {
const ts = Date.now().toString(36);
const rand = Math.random().toString(36).slice(2, 8);
return `dep-${ts}-${rand}`;
}
function readProjectVersion(cwd) {
const pkgPath = path.join(cwd, 'package.json');
if (!fs.existsSync(pkgPath)) {
return null;
}
try {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
return pkg.version ?? null;
}
catch {
return null;
}
}
// ============================================
// Deploy subcommand
// ============================================
const deployCommand = {
name: 'deploy',
description: 'Deploy to target environment',
options: [
{ name: 'env', short: 'e', type: 'string', description: 'Environment: dev, staging, prod', default: 'staging' },
{ name: 'version', short: 'v', type: 'string', description: 'Version to deploy' },
{ name: 'dry-run', short: 'd', type: 'boolean', description: 'Simulate deployment without changes' },
{ name: 'description', type: 'string', description: 'Deployment description' },
],
examples: [
{ command: 'claude-flow deployment deploy -e prod', description: 'Deploy to production' },
{ command: 'claude-flow deployment deploy --dry-run', description: 'Simulate deployment' },
],
action: async (ctx) => {
try {
const envName = String(ctx.flags['env'] || 'staging');
const dryRun = Boolean(ctx.flags['dry-run']);
const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
if (!version) {
version = readProjectVersion(ctx.cwd) || '0.0.0';
}
const state = loadDeploymentState(ctx.cwd);
// Ensure environment exists; auto-create if it doesn't
if (!state.environments[envName]) {
state.environments[envName] = {
name: envName,
type: envName === 'prod' || envName === 'production' ? 'production' : envName === 'staging' ? 'staging' : 'local',
createdAt: new Date().toISOString(),
};
}
const record = {
id: generateId(),
environment: envName,
version,
status: 'deployed',
timestamp: new Date().toISOString(),
description,
};
if (dryRun) {
output.writeln();
output.printInfo('Dry run - no changes will be made');
output.writeln();
output.writeln(output.bold('Deployment Preview'));
output.printTable({
columns: [
{ key: 'field', header: 'Field' },
{ key: 'value', header: 'Value' },
],
data: [
{ field: 'ID', value: record.id },
{ field: 'Environment', value: envName },
{ field: 'Version', value: version },
{ field: 'Status', value: 'deployed (dry-run)' },
{ field: 'Description', value: description || '-' },
],
});
return { success: true };
}
state.history.push(record);
state.activeDeployment = record.id;
saveDeploymentState(ctx.cwd, state);
output.writeln();
output.printSuccess(`Deployed version ${version} to ${envName}`);
output.writeln();
output.printTable({
columns: [
{ key: 'field', header: 'Field' },
{ key: 'value', header: 'Value' },
],
data: [
{ field: 'ID', value: record.id },
{ field: 'Environment', value: envName },
{ field: 'Version', value: version },
{ field: 'Status', value: record.status },
{ field: 'Timestamp', value: record.timestamp },
{ field: 'Description', value: description || '-' },
],
});
return { success: true, data: record };
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.printError('Deploy failed', msg);
return { success: false, exitCode: 1 };
}
},
};
// ============================================
// Status subcommand
// ============================================
const statusCommand = {
name: 'status',
description: 'Check deployment status across environments',
options: [
{ name: 'env', short: 'e', type: 'string', description: 'Specific environment to check' },
],
examples: [
{ command: 'claude-flow deployment status', description: 'Show all environments' },
{ command: 'claude-flow deployment status -e prod', description: 'Check production' },
],
action: async (ctx) => {
try {
const state = loadDeploymentState(ctx.cwd);
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
output.writeln();
output.writeln(output.bold('Deployment Status'));
output.writeln();
// Active deployment
if (state.activeDeployment) {
const active = state.history.find(r => r.id === state.activeDeployment);
if (active) {
output.printInfo(`Active deployment: ${active.id} (v${active.version} on ${active.environment})`);
}
}
else {
output.writeln(output.dim('No active deployment'));
}
// Environments table
const envEntries = Object.values(state.environments);
if (filterEnv) {
const env = state.environments[filterEnv];
if (!env) {
output.printWarning(`Environment '${filterEnv}' not found`);
return { success: true };
}
output.writeln();
output.writeln(output.bold('Environment'));
output.printTable({
columns: [
{ key: 'name', header: 'Name' },
{ key: 'type', header: 'Type' },
{ key: 'url', header: 'URL' },
{ key: 'createdAt', header: 'Created' },
],
data: [{ name: env.name, type: env.type, url: env.url || '-', createdAt: env.createdAt }],
});
}
else if (envEntries.length > 0) {
output.writeln();
output.writeln(output.bold('Environments'));
output.printTable({
columns: [
{ key: 'name', header: 'Name' },
{ key: 'type', header: 'Type' },
{ key: 'url', header: 'URL' },
{ key: 'createdAt', header: 'Created' },
],
data: envEntries.map(e => ({ name: e.name, type: e.type, url: e.url || '-', createdAt: e.createdAt })),
});
}
else {
output.writeln(output.dim('No environments configured'));
}
// Recent history (last 5)
let recent = [...state.history].reverse().slice(0, 5);
if (filterEnv) {
recent = recent.filter(r => r.environment === filterEnv);
}
if (recent.length > 0) {
output.writeln();
output.writeln(output.bold('Recent Deployments'));
output.printTable({
columns: [
{ key: 'id', header: 'ID' },
{ key: 'environment', header: 'Env' },
{ key: 'version', header: 'Version' },
{ key: 'status', header: 'Status' },
{ key: 'timestamp', header: 'Time' },
],
data: recent.map(r => ({ ...r })),
});
}
return { success: true };
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.printError('Status check failed', msg);
return { success: false, exitCode: 1 };
}
},
};
// ============================================
// Rollback subcommand
// ============================================
const rollbackCommand = {
name: 'rollback',
description: 'Rollback to previous deployment',
options: [
{ name: 'env', short: 'e', type: 'string', description: 'Environment to rollback', required: true },
{ name: 'version', short: 'v', type: 'string', description: 'Specific version to rollback to' },
{ name: 'steps', short: 's', type: 'number', description: 'Number of versions to rollback', default: '1' },
],
examples: [
{ command: 'claude-flow deployment rollback -e prod', description: 'Rollback production' },
{ command: 'claude-flow deployment rollback -e prod -v v3.0.0', description: 'Rollback to specific version' },
],
action: async (ctx) => {
try {
const envName = String(ctx.flags['env'] || '');
if (!envName) {
output.printError('Environment is required', 'Use --env or -e to specify');
return { success: false, exitCode: 1 };
}
const targetVersion = ctx.flags['version'] ? String(ctx.flags['version']) : null;
const state = loadDeploymentState(ctx.cwd);
// Find deployments for this environment in reverse chronological order
const envHistory = state.history
.filter(r => r.environment === envName && r.status === 'deployed')
.reverse();
if (envHistory.length < 2 && !targetVersion) {
output.printWarning('No previous deployment to rollback to');
return { success: false, exitCode: 1 };
}
let rollbackTo;
if (targetVersion) {
rollbackTo = envHistory.find(r => r.version === targetVersion);
if (!rollbackTo) {
output.printError(`Version '${targetVersion}' not found in deployment history for '${envName}'`);
return { success: false, exitCode: 1 };
}
}
else {
// Rollback to the deployment before the most recent one
rollbackTo = envHistory[1];
}
// Mark current active deployment for this env as rolled-back
const current = envHistory[0];
if (current) {
const idx = state.history.findIndex(r => r.id === current.id);
if (idx >= 0) {
state.history[idx].status = 'rolled-back';
}
}
// Create a new record for the rollback
const record = {
id: generateId(),
environment: envName,
version: rollbackTo.version,
status: 'deployed',
timestamp: new Date().toISOString(),
description: `Rollback from ${current?.version || 'unknown'} to ${rollbackTo.version}`,
};
state.history.push(record);
state.activeDeployment = record.id;
saveDeploymentState(ctx.cwd, state);
output.writeln();
output.printSuccess(`Rolled back ${envName} to version ${rollbackTo.version}`);
output.writeln();
output.printTable({
columns: [
{ key: 'field', header: 'Field' },
{ key: 'value', header: 'Value' },
],
data: [
{ field: 'Rollback ID', value: record.id },
{ field: 'Environment', value: envName },
{ field: 'From Version', value: current?.version || 'unknown' },
{ field: 'To Version', value: rollbackTo.version },
{ field: 'Timestamp', value: record.timestamp },
],
});
return { success: true, data: record };
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.printError('Rollback failed', msg);
return { success: false, exitCode: 1 };
}
},
};
// ============================================
// History subcommand (logs)
// ============================================
const historyCommand = {
name: 'history',
description: 'View deployment history',
options: [
{ name: 'env', short: 'e', type: 'string', description: 'Filter by environment' },
{ name: 'limit', short: 'l', type: 'number', description: 'Number of entries', default: '10' },
],
examples: [
{ command: 'claude-flow deployment history', description: 'Show all history' },
{ command: 'claude-flow deployment history -e prod', description: 'Production history' },
],
action: async (ctx) => {
try {
const state = loadDeploymentState(ctx.cwd);
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
const limit = Number(ctx.flags['limit']) || 10;
let records = [...state.history].reverse();
if (filterEnv) {
records = records.filter(r => r.environment === filterEnv);
}
records = records.slice(0, limit);
output.writeln();
output.writeln(output.bold('Deployment History'));
if (filterEnv) {
output.writeln(output.dim(`Filtered by environment: ${filterEnv}`));
}
output.writeln();
if (records.length === 0) {
output.writeln(output.dim('No deployment history found'));
return { success: true };
}
output.printTable({
columns: [
{ key: 'id', header: 'ID' },
{ key: 'environment', header: 'Env' },
{ key: 'version', header: 'Version' },
{ key: 'status', header: 'Status' },
{ key: 'timestamp', header: 'Time' },
{ key: 'description', header: 'Description' },
],
data: records.map(r => ({
...r,
description: r.description || '-',
})),
});
output.writeln();
output.writeln(output.dim(`Showing ${records.length} of ${state.history.length} total records`));
return { success: true };
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.printError('Failed to load history', msg);
return { success: false, exitCode: 1 };
}
},
};
// ============================================
// Environments subcommand
// ============================================
const environmentsCommand = {
name: 'environments',
description: 'Manage deployment environments',
aliases: ['envs'],
options: [
{ name: 'action', short: 'a', type: 'string', description: 'Action: list, add, remove', default: 'list' },
{ name: 'name', short: 'n', type: 'string', description: 'Environment name' },
{ name: 'type', short: 't', type: 'string', description: 'Environment type: local, staging, production', default: 'local' },
{ name: 'url', short: 'u', type: 'string', description: 'Environment URL' },
],
examples: [
{ command: 'claude-flow deployment environments', description: 'List environments' },
{ command: 'claude-flow deployment envs -a add -n preview -t staging', description: 'Add environment' },
{ command: 'claude-flow deployment envs -a remove -n preview', description: 'Remove environment' },
],
action: async (ctx) => {
try {
const action = String(ctx.flags['action'] || 'list');
const state = loadDeploymentState(ctx.cwd);
if (action === 'list') {
const envs = Object.values(state.environments);
output.writeln();
output.writeln(output.bold('Deployment Environments'));
output.writeln();
if (envs.length === 0) {
output.writeln(output.dim('No environments configured. Use --action add to create one.'));
return { success: true };
}
output.printTable({
columns: [
{ key: 'name', header: 'Name' },
{ key: 'type', header: 'Type' },
{ key: 'url', header: 'URL' },
{ key: 'createdAt', header: 'Created' },
],
data: envs.map(e => ({ name: e.name, type: e.type, url: e.url || '-', createdAt: e.createdAt })),
});
return { success: true };
}
if (action === 'add') {
const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
if (!name) {
output.printError('Environment name is required', 'Use --name or -n to specify');
return { success: false, exitCode: 1 };
}
if (state.environments[name]) {
output.printWarning(`Environment '${name}' already exists`);
return { success: false, exitCode: 1 };
}
const envType = String(ctx.flags['type'] || 'local');
const url = ctx.flags['url'] ? String(ctx.flags['url']) : undefined;
state.environments[name] = {
name,
type: envType,
url,
createdAt: new Date().toISOString(),
};
saveDeploymentState(ctx.cwd, state);
output.writeln();
output.printSuccess(`Added environment '${name}' (${envType})`);
if (url) {
output.writeln(output.dim(` URL: ${url}`));
}
return { success: true };
}
if (action === 'remove') {
const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
if (!name) {
output.printError('Environment name is required', 'Use --name or -n to specify');
return { success: false, exitCode: 1 };
}
if (!state.environments[name]) {
output.printWarning(`Environment '${name}' not found`);
return { success: false, exitCode: 1 };
}
delete state.environments[name];
saveDeploymentState(ctx.cwd, state);
output.writeln();
output.printSuccess(`Removed environment '${name}'`);
return { success: true };
}
output.printError(`Unknown action '${action}'`, 'Valid actions: list, add, remove');
return { success: false, exitCode: 1 };
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.printError('Environments command failed', msg);
return { success: false, exitCode: 1 };
}
},
};
// ============================================
// Logs subcommand
// ============================================
const logsCommand = {
name: 'logs',
description: 'View deployment logs',
options: [
{ name: 'deployment', short: 'd', type: 'string', description: 'Deployment ID' },
{ name: 'env', short: 'e', type: 'string', description: 'Environment' },
{ name: 'lines', short: 'n', type: 'number', description: 'Number of lines', default: '50' },
],
examples: [
{ command: 'claude-flow deployment logs -e prod', description: 'View production logs' },
{ command: 'claude-flow deployment logs -d dep-123', description: 'View specific deployment' },
],
action: async (ctx) => {
try {
const state = loadDeploymentState(ctx.cwd);
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
const deploymentId = ctx.flags['deployment'] ? String(ctx.flags['deployment']) : null;
const limit = Number(ctx.flags['lines']) || 50;
output.writeln();
output.writeln(output.bold('Deployment Logs'));
output.writeln();
let records = [...state.history].reverse();
if (deploymentId) {
records = records.filter(r => r.id === deploymentId);
if (records.length === 0) {
output.printWarning(`Deployment '${deploymentId}' not found`);
return { success: false, exitCode: 1 };
}
}
if (filterEnv) {
records = records.filter(r => r.environment === filterEnv);
}
records = records.slice(0, limit);
if (records.length === 0) {
output.writeln(output.dim('No deployment logs found'));
return { success: true };
}
output.printTable({
columns: [
{ key: 'id', header: 'ID' },
{ key: 'environment', header: 'Env' },
{ key: 'version', header: 'Version' },
{ key: 'status', header: 'Status' },
{ key: 'timestamp', header: 'Time' },
{ key: 'description', header: 'Description' },
],
data: records.map(r => ({
...r,
description: r.description || '-',
})),
});
output.writeln();
output.writeln(output.dim(`${records.length} entries shown`));
return { success: true };
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.printError('Failed to load logs', msg);
return { success: false, exitCode: 1 };
}
},
};
// ============================================
// Release subcommand
// ============================================
const releaseCommand = {
name: 'release',
description: 'Create a new release deployment',
options: [
{ name: 'version', short: 'v', type: 'string', description: 'Release version' },
{ name: 'env', short: 'e', type: 'string', description: 'Target environment', default: 'production' },
{ name: 'description', short: 'd', type: 'string', description: 'Release description' },
],
examples: [
{ command: 'claude-flow deployment release -v 3.5.0', description: 'Release version 3.5.0' },
{ command: 'claude-flow deployment release -v 3.5.0 -d "Major update"', description: 'Release with description' },
],
action: async (ctx) => {
try {
const envName = String(ctx.flags['env'] || 'production');
const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
if (!version) {
const pkgVersion = readProjectVersion(ctx.cwd);
if (!pkgVersion) {
output.printError('Version is required', 'Use --version or -v, or ensure package.json has a version field');
return { success: false, exitCode: 1 };
}
version = pkgVersion;
}
const state = loadDeploymentState(ctx.cwd);
// Ensure environment exists
if (!state.environments[envName]) {
state.environments[envName] = {
name: envName,
type: envName === 'prod' || envName === 'production' ? 'production' : 'staging',
createdAt: new Date().toISOString(),
};
}
const record = {
id: generateId(),
environment: envName,
version,
status: 'deployed',
timestamp: new Date().toISOString(),
description: description || `Release ${version}`,
};
state.history.push(record);
state.activeDeployment = record.id;
saveDeploymentState(ctx.cwd, state);
output.writeln();
output.printSuccess(`Released version ${version} to ${envName}`);
output.writeln();
output.printTable({
columns: [
{ key: 'field', header: 'Field' },
{ key: 'value', header: 'Value' },
],
data: [
{ field: 'Release ID', value: record.id },
{ field: 'Environment', value: envName },
{ field: 'Version', value: version },
{ field: 'Status', value: record.status },
{ field: 'Timestamp', value: record.timestamp },
{ field: 'Description', value: record.description || '-' },
],
});
return { success: true, data: record };
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
output.printError('Release failed', msg);
return { success: false, exitCode: 1 };
}
},
};
// ============================================
// Main deployment command
// ============================================
export const deploymentCommand = {
name: 'deployment',
description: 'Deployment management, environments, rollbacks',
aliases: ['deploy'],
subcommands: [deployCommand, statusCommand, rollbackCommand, historyCommand, environmentsCommand, logsCommand, releaseCommand],
examples: [
{ command: 'claude-flow deployment deploy -e prod', description: 'Deploy to production' },
{ command: 'claude-flow deployment status', description: 'Check all environments' },
{ command: 'claude-flow deployment rollback -e prod', description: 'Rollback production' },
{ command: 'claude-flow deployment release -v 3.5.0', description: 'Create a release' },
],
action: async () => {
output.writeln();
output.writeln(output.bold('RuFlo Deployment'));
output.writeln(output.dim('Multi-environment deployment management'));
output.writeln();
output.writeln('Subcommands:');
output.printList([
'deploy - Deploy to target environment',
'status - Check deployment status',
'rollback - Rollback to previous version',
'history - View deployment history',
'environments - Manage deployment environments',
'logs - View deployment logs',
'release - Create a new release',
]);
output.writeln();
output.writeln('Features:');
output.printList([
'Zero-downtime rolling deployments',
'Automatic rollback on failure',
'Environment-specific configurations',
'Deployment previews for PRs',
]);
output.writeln();
output.writeln(output.dim('Created with love by ruv.io'));
return { success: true };
},
};
export default deploymentCommand;
//# sourceMappingURL=deployment.js.map