@claude-flow/cli 3.5.43 → 3.5.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +199 -196
- package/dist/src/commands/claims.d.ts.map +1 -1
- package/dist/src/commands/claims.js +352 -34
- package/dist/src/commands/claims.js.map +1 -1
- package/dist/src/commands/config.d.ts.map +1 -1
- package/dist/src/commands/config.js +66 -34
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/deployment.d.ts.map +1 -1
- package/dist/src/commands/deployment.js +538 -46
- package/dist/src/commands/deployment.js.map +1 -1
- package/dist/src/commands/migrate.d.ts.map +1 -1
- package/dist/src/commands/migrate.js +531 -39
- package/dist/src/commands/migrate.js.map +1 -1
- package/dist/src/commands/providers.d.ts.map +1 -1
- package/dist/src/commands/providers.js +163 -29
- package/dist/src/commands/providers.js.map +1 -1
- package/dist/src/init/executor.d.ts.map +1 -1
- package/dist/src/init/executor.js +2 -3
- package/dist/src/init/executor.js.map +1 -1
- package/dist/src/mcp-client.d.ts.map +1 -1
- package/dist/src/mcp-client.js +3 -0
- package/dist/src/mcp-client.js.map +1 -1
- package/dist/src/mcp-server.d.ts +3 -1
- package/dist/src/mcp-server.d.ts.map +1 -1
- package/dist/src/mcp-server.js +31 -4
- package/dist/src/mcp-server.js.map +1 -1
- package/dist/src/mcp-tools/guidance-tools.d.ts +15 -0
- package/dist/src/mcp-tools/guidance-tools.d.ts.map +1 -0
- package/dist/src/mcp-tools/guidance-tools.js +617 -0
- package/dist/src/mcp-tools/guidance-tools.js.map +1 -0
- package/dist/src/mcp-tools/index.d.ts +1 -0
- package/dist/src/mcp-tools/index.d.ts.map +1 -1
- package/dist/src/mcp-tools/index.js +1 -0
- package/dist/src/mcp-tools/index.js.map +1 -1
- package/dist/src/services/config-file-manager.d.ts +37 -0
- package/dist/src/services/config-file-manager.d.ts.map +1 -0
- package/dist/src/services/config-file-manager.js +224 -0
- package/dist/src/services/config-file-manager.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -1
|
@@ -5,52 +5,249 @@
|
|
|
5
5
|
* Created with ❤️ by ruv.io
|
|
6
6
|
*/
|
|
7
7
|
import { output } from '../output.js';
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
// ============================================
|
|
11
|
+
// State Helpers
|
|
12
|
+
// ============================================
|
|
13
|
+
function getStateDir(cwd) {
|
|
14
|
+
return path.join(cwd, '.claude-flow');
|
|
15
|
+
}
|
|
16
|
+
function getStatePath(cwd) {
|
|
17
|
+
return path.join(getStateDir(cwd), 'deployments.json');
|
|
18
|
+
}
|
|
19
|
+
function emptyState() {
|
|
20
|
+
return { environments: {}, history: [], activeDeployment: undefined };
|
|
21
|
+
}
|
|
22
|
+
function loadDeploymentState(cwd) {
|
|
23
|
+
const filePath = getStatePath(cwd);
|
|
24
|
+
if (!fs.existsSync(filePath)) {
|
|
25
|
+
return emptyState();
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return emptyState();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function saveDeploymentState(cwd, state) {
|
|
36
|
+
const dir = getStateDir(cwd);
|
|
37
|
+
if (!fs.existsSync(dir)) {
|
|
38
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
const filePath = getStatePath(cwd);
|
|
41
|
+
const tmpPath = filePath + '.tmp';
|
|
42
|
+
fs.writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf-8');
|
|
43
|
+
fs.renameSync(tmpPath, filePath);
|
|
44
|
+
}
|
|
45
|
+
function generateId() {
|
|
46
|
+
const ts = Date.now().toString(36);
|
|
47
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
48
|
+
return `dep-${ts}-${rand}`;
|
|
49
|
+
}
|
|
50
|
+
function readProjectVersion(cwd) {
|
|
51
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
52
|
+
if (!fs.existsSync(pkgPath)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
57
|
+
return pkg.version ?? null;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// ============================================
|
|
8
64
|
// Deploy subcommand
|
|
65
|
+
// ============================================
|
|
9
66
|
const deployCommand = {
|
|
10
67
|
name: 'deploy',
|
|
11
68
|
description: 'Deploy to target environment',
|
|
12
69
|
options: [
|
|
13
70
|
{ name: 'env', short: 'e', type: 'string', description: 'Environment: dev, staging, prod', default: 'staging' },
|
|
14
|
-
{ name: 'version', short: 'v', type: 'string', description: 'Version to deploy'
|
|
71
|
+
{ name: 'version', short: 'v', type: 'string', description: 'Version to deploy' },
|
|
15
72
|
{ name: 'dry-run', short: 'd', type: 'boolean', description: 'Simulate deployment without changes' },
|
|
16
|
-
{ name: '
|
|
17
|
-
{ name: 'rollback-on-fail', type: 'boolean', description: 'Auto rollback on failure', default: 'true' },
|
|
73
|
+
{ name: 'description', type: 'string', description: 'Deployment description' },
|
|
18
74
|
],
|
|
19
75
|
examples: [
|
|
20
76
|
{ command: 'claude-flow deployment deploy -e prod', description: 'Deploy to production' },
|
|
21
77
|
{ command: 'claude-flow deployment deploy --dry-run', description: 'Simulate deployment' },
|
|
22
78
|
],
|
|
23
79
|
action: async (ctx) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
80
|
+
try {
|
|
81
|
+
const envName = String(ctx.flags['env'] || 'staging');
|
|
82
|
+
const dryRun = Boolean(ctx.flags['dry-run']);
|
|
83
|
+
const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
|
|
84
|
+
let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
|
|
85
|
+
if (!version) {
|
|
86
|
+
version = readProjectVersion(ctx.cwd) || '0.0.0';
|
|
87
|
+
}
|
|
88
|
+
const state = loadDeploymentState(ctx.cwd);
|
|
89
|
+
// Ensure environment exists; auto-create if it doesn't
|
|
90
|
+
if (!state.environments[envName]) {
|
|
91
|
+
state.environments[envName] = {
|
|
92
|
+
name: envName,
|
|
93
|
+
type: envName === 'prod' || envName === 'production' ? 'production' : envName === 'staging' ? 'staging' : 'local',
|
|
94
|
+
createdAt: new Date().toISOString(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const record = {
|
|
98
|
+
id: generateId(),
|
|
99
|
+
environment: envName,
|
|
100
|
+
version,
|
|
101
|
+
status: 'deployed',
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
description,
|
|
104
|
+
};
|
|
105
|
+
if (dryRun) {
|
|
106
|
+
output.writeln();
|
|
107
|
+
output.printInfo('Dry run - no changes will be made');
|
|
108
|
+
output.writeln();
|
|
109
|
+
output.writeln(output.bold('Deployment Preview'));
|
|
110
|
+
output.printTable({
|
|
111
|
+
columns: [
|
|
112
|
+
{ key: 'field', header: 'Field' },
|
|
113
|
+
{ key: 'value', header: 'Value' },
|
|
114
|
+
],
|
|
115
|
+
data: [
|
|
116
|
+
{ field: 'ID', value: record.id },
|
|
117
|
+
{ field: 'Environment', value: envName },
|
|
118
|
+
{ field: 'Version', value: version },
|
|
119
|
+
{ field: 'Status', value: 'deployed (dry-run)' },
|
|
120
|
+
{ field: 'Description', value: description || '-' },
|
|
121
|
+
],
|
|
122
|
+
});
|
|
123
|
+
return { success: true };
|
|
124
|
+
}
|
|
125
|
+
state.history.push(record);
|
|
126
|
+
state.activeDeployment = record.id;
|
|
127
|
+
saveDeploymentState(ctx.cwd, state);
|
|
128
|
+
output.writeln();
|
|
129
|
+
output.printSuccess(`Deployed version ${version} to ${envName}`);
|
|
130
|
+
output.writeln();
|
|
131
|
+
output.printTable({
|
|
132
|
+
columns: [
|
|
133
|
+
{ key: 'field', header: 'Field' },
|
|
134
|
+
{ key: 'value', header: 'Value' },
|
|
135
|
+
],
|
|
136
|
+
data: [
|
|
137
|
+
{ field: 'ID', value: record.id },
|
|
138
|
+
{ field: 'Environment', value: envName },
|
|
139
|
+
{ field: 'Version', value: version },
|
|
140
|
+
{ field: 'Status', value: record.status },
|
|
141
|
+
{ field: 'Timestamp', value: record.timestamp },
|
|
142
|
+
{ field: 'Description', value: description || '-' },
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
return { success: true, data: record };
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
149
|
+
output.printError('Deploy failed', msg);
|
|
150
|
+
return { success: false, exitCode: 1 };
|
|
151
|
+
}
|
|
30
152
|
},
|
|
31
153
|
};
|
|
154
|
+
// ============================================
|
|
32
155
|
// Status subcommand
|
|
156
|
+
// ============================================
|
|
33
157
|
const statusCommand = {
|
|
34
158
|
name: 'status',
|
|
35
159
|
description: 'Check deployment status across environments',
|
|
36
160
|
options: [
|
|
37
161
|
{ name: 'env', short: 'e', type: 'string', description: 'Specific environment to check' },
|
|
38
|
-
{ name: 'watch', short: 'w', type: 'boolean', description: 'Watch for changes' },
|
|
39
162
|
],
|
|
40
163
|
examples: [
|
|
41
164
|
{ command: 'claude-flow deployment status', description: 'Show all environments' },
|
|
42
165
|
{ command: 'claude-flow deployment status -e prod', description: 'Check production' },
|
|
43
166
|
],
|
|
44
167
|
action: async (ctx) => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
168
|
+
try {
|
|
169
|
+
const state = loadDeploymentState(ctx.cwd);
|
|
170
|
+
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
|
|
171
|
+
output.writeln();
|
|
172
|
+
output.writeln(output.bold('Deployment Status'));
|
|
173
|
+
output.writeln();
|
|
174
|
+
// Active deployment
|
|
175
|
+
if (state.activeDeployment) {
|
|
176
|
+
const active = state.history.find(r => r.id === state.activeDeployment);
|
|
177
|
+
if (active) {
|
|
178
|
+
output.printInfo(`Active deployment: ${active.id} (v${active.version} on ${active.environment})`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
output.writeln(output.dim('No active deployment'));
|
|
183
|
+
}
|
|
184
|
+
// Environments table
|
|
185
|
+
const envEntries = Object.values(state.environments);
|
|
186
|
+
if (filterEnv) {
|
|
187
|
+
const env = state.environments[filterEnv];
|
|
188
|
+
if (!env) {
|
|
189
|
+
output.printWarning(`Environment '${filterEnv}' not found`);
|
|
190
|
+
return { success: true };
|
|
191
|
+
}
|
|
192
|
+
output.writeln();
|
|
193
|
+
output.writeln(output.bold('Environment'));
|
|
194
|
+
output.printTable({
|
|
195
|
+
columns: [
|
|
196
|
+
{ key: 'name', header: 'Name' },
|
|
197
|
+
{ key: 'type', header: 'Type' },
|
|
198
|
+
{ key: 'url', header: 'URL' },
|
|
199
|
+
{ key: 'createdAt', header: 'Created' },
|
|
200
|
+
],
|
|
201
|
+
data: [{ name: env.name, type: env.type, url: env.url || '-', createdAt: env.createdAt }],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
else if (envEntries.length > 0) {
|
|
205
|
+
output.writeln();
|
|
206
|
+
output.writeln(output.bold('Environments'));
|
|
207
|
+
output.printTable({
|
|
208
|
+
columns: [
|
|
209
|
+
{ key: 'name', header: 'Name' },
|
|
210
|
+
{ key: 'type', header: 'Type' },
|
|
211
|
+
{ key: 'url', header: 'URL' },
|
|
212
|
+
{ key: 'createdAt', header: 'Created' },
|
|
213
|
+
],
|
|
214
|
+
data: envEntries.map(e => ({ name: e.name, type: e.type, url: e.url || '-', createdAt: e.createdAt })),
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
output.writeln(output.dim('No environments configured'));
|
|
219
|
+
}
|
|
220
|
+
// Recent history (last 5)
|
|
221
|
+
let recent = [...state.history].reverse().slice(0, 5);
|
|
222
|
+
if (filterEnv) {
|
|
223
|
+
recent = recent.filter(r => r.environment === filterEnv);
|
|
224
|
+
}
|
|
225
|
+
if (recent.length > 0) {
|
|
226
|
+
output.writeln();
|
|
227
|
+
output.writeln(output.bold('Recent Deployments'));
|
|
228
|
+
output.printTable({
|
|
229
|
+
columns: [
|
|
230
|
+
{ key: 'id', header: 'ID' },
|
|
231
|
+
{ key: 'environment', header: 'Env' },
|
|
232
|
+
{ key: 'version', header: 'Version' },
|
|
233
|
+
{ key: 'status', header: 'Status' },
|
|
234
|
+
{ key: 'timestamp', header: 'Time' },
|
|
235
|
+
],
|
|
236
|
+
data: recent.map(r => ({ ...r })),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
return { success: true };
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
243
|
+
output.printError('Status check failed', msg);
|
|
244
|
+
return { success: false, exitCode: 1 };
|
|
245
|
+
}
|
|
51
246
|
},
|
|
52
247
|
};
|
|
248
|
+
// ============================================
|
|
53
249
|
// Rollback subcommand
|
|
250
|
+
// ============================================
|
|
54
251
|
const rollbackCommand = {
|
|
55
252
|
name: 'rollback',
|
|
56
253
|
description: 'Rollback to previous deployment',
|
|
@@ -64,15 +261,82 @@ const rollbackCommand = {
|
|
|
64
261
|
{ command: 'claude-flow deployment rollback -e prod -v v3.0.0', description: 'Rollback to specific version' },
|
|
65
262
|
],
|
|
66
263
|
action: async (ctx) => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
264
|
+
try {
|
|
265
|
+
const envName = String(ctx.flags['env'] || '');
|
|
266
|
+
if (!envName) {
|
|
267
|
+
output.printError('Environment is required', 'Use --env or -e to specify');
|
|
268
|
+
return { success: false, exitCode: 1 };
|
|
269
|
+
}
|
|
270
|
+
const targetVersion = ctx.flags['version'] ? String(ctx.flags['version']) : null;
|
|
271
|
+
const state = loadDeploymentState(ctx.cwd);
|
|
272
|
+
// Find deployments for this environment in reverse chronological order
|
|
273
|
+
const envHistory = state.history
|
|
274
|
+
.filter(r => r.environment === envName && r.status === 'deployed')
|
|
275
|
+
.reverse();
|
|
276
|
+
if (envHistory.length < 2 && !targetVersion) {
|
|
277
|
+
output.printWarning('No previous deployment to rollback to');
|
|
278
|
+
return { success: false, exitCode: 1 };
|
|
279
|
+
}
|
|
280
|
+
let rollbackTo;
|
|
281
|
+
if (targetVersion) {
|
|
282
|
+
rollbackTo = envHistory.find(r => r.version === targetVersion);
|
|
283
|
+
if (!rollbackTo) {
|
|
284
|
+
output.printError(`Version '${targetVersion}' not found in deployment history for '${envName}'`);
|
|
285
|
+
return { success: false, exitCode: 1 };
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
// Rollback to the deployment before the most recent one
|
|
290
|
+
rollbackTo = envHistory[1];
|
|
291
|
+
}
|
|
292
|
+
// Mark current active deployment for this env as rolled-back
|
|
293
|
+
const current = envHistory[0];
|
|
294
|
+
if (current) {
|
|
295
|
+
const idx = state.history.findIndex(r => r.id === current.id);
|
|
296
|
+
if (idx >= 0) {
|
|
297
|
+
state.history[idx].status = 'rolled-back';
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
// Create a new record for the rollback
|
|
301
|
+
const record = {
|
|
302
|
+
id: generateId(),
|
|
303
|
+
environment: envName,
|
|
304
|
+
version: rollbackTo.version,
|
|
305
|
+
status: 'deployed',
|
|
306
|
+
timestamp: new Date().toISOString(),
|
|
307
|
+
description: `Rollback from ${current?.version || 'unknown'} to ${rollbackTo.version}`,
|
|
308
|
+
};
|
|
309
|
+
state.history.push(record);
|
|
310
|
+
state.activeDeployment = record.id;
|
|
311
|
+
saveDeploymentState(ctx.cwd, state);
|
|
312
|
+
output.writeln();
|
|
313
|
+
output.printSuccess(`Rolled back ${envName} to version ${rollbackTo.version}`);
|
|
314
|
+
output.writeln();
|
|
315
|
+
output.printTable({
|
|
316
|
+
columns: [
|
|
317
|
+
{ key: 'field', header: 'Field' },
|
|
318
|
+
{ key: 'value', header: 'Value' },
|
|
319
|
+
],
|
|
320
|
+
data: [
|
|
321
|
+
{ field: 'Rollback ID', value: record.id },
|
|
322
|
+
{ field: 'Environment', value: envName },
|
|
323
|
+
{ field: 'From Version', value: current?.version || 'unknown' },
|
|
324
|
+
{ field: 'To Version', value: rollbackTo.version },
|
|
325
|
+
{ field: 'Timestamp', value: record.timestamp },
|
|
326
|
+
],
|
|
327
|
+
});
|
|
328
|
+
return { success: true, data: record };
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
332
|
+
output.printError('Rollback failed', msg);
|
|
333
|
+
return { success: false, exitCode: 1 };
|
|
334
|
+
}
|
|
73
335
|
},
|
|
74
336
|
};
|
|
75
|
-
//
|
|
337
|
+
// ============================================
|
|
338
|
+
// History subcommand (logs)
|
|
339
|
+
// ============================================
|
|
76
340
|
const historyCommand = {
|
|
77
341
|
name: 'history',
|
|
78
342
|
description: 'View deployment history',
|
|
@@ -85,44 +349,153 @@ const historyCommand = {
|
|
|
85
349
|
{ command: 'claude-flow deployment history -e prod', description: 'Production history' },
|
|
86
350
|
],
|
|
87
351
|
action: async (ctx) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
352
|
+
try {
|
|
353
|
+
const state = loadDeploymentState(ctx.cwd);
|
|
354
|
+
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
|
|
355
|
+
const limit = Number(ctx.flags['limit']) || 10;
|
|
356
|
+
let records = [...state.history].reverse();
|
|
357
|
+
if (filterEnv) {
|
|
358
|
+
records = records.filter(r => r.environment === filterEnv);
|
|
359
|
+
}
|
|
360
|
+
records = records.slice(0, limit);
|
|
361
|
+
output.writeln();
|
|
362
|
+
output.writeln(output.bold('Deployment History'));
|
|
363
|
+
if (filterEnv) {
|
|
364
|
+
output.writeln(output.dim(`Filtered by environment: ${filterEnv}`));
|
|
365
|
+
}
|
|
366
|
+
output.writeln();
|
|
367
|
+
if (records.length === 0) {
|
|
368
|
+
output.writeln(output.dim('No deployment history found'));
|
|
369
|
+
return { success: true };
|
|
370
|
+
}
|
|
371
|
+
output.printTable({
|
|
372
|
+
columns: [
|
|
373
|
+
{ key: 'id', header: 'ID' },
|
|
374
|
+
{ key: 'environment', header: 'Env' },
|
|
375
|
+
{ key: 'version', header: 'Version' },
|
|
376
|
+
{ key: 'status', header: 'Status' },
|
|
377
|
+
{ key: 'timestamp', header: 'Time' },
|
|
378
|
+
{ key: 'description', header: 'Description' },
|
|
379
|
+
],
|
|
380
|
+
data: records.map(r => ({
|
|
381
|
+
...r,
|
|
382
|
+
description: r.description || '-',
|
|
383
|
+
})),
|
|
384
|
+
});
|
|
385
|
+
output.writeln();
|
|
386
|
+
output.writeln(output.dim(`Showing ${records.length} of ${state.history.length} total records`));
|
|
387
|
+
return { success: true };
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
391
|
+
output.printError('Failed to load history', msg);
|
|
392
|
+
return { success: false, exitCode: 1 };
|
|
393
|
+
}
|
|
94
394
|
},
|
|
95
395
|
};
|
|
396
|
+
// ============================================
|
|
96
397
|
// Environments subcommand
|
|
398
|
+
// ============================================
|
|
97
399
|
const environmentsCommand = {
|
|
98
400
|
name: 'environments',
|
|
99
401
|
description: 'Manage deployment environments',
|
|
100
402
|
aliases: ['envs'],
|
|
101
403
|
options: [
|
|
102
|
-
{ name: 'action', short: 'a', type: 'string', description: 'Action: list,
|
|
404
|
+
{ name: 'action', short: 'a', type: 'string', description: 'Action: list, add, remove', default: 'list' },
|
|
103
405
|
{ name: 'name', short: 'n', type: 'string', description: 'Environment name' },
|
|
406
|
+
{ name: 'type', short: 't', type: 'string', description: 'Environment type: local, staging, production', default: 'local' },
|
|
407
|
+
{ name: 'url', short: 'u', type: 'string', description: 'Environment URL' },
|
|
104
408
|
],
|
|
105
409
|
examples: [
|
|
106
410
|
{ command: 'claude-flow deployment environments', description: 'List environments' },
|
|
107
|
-
{ command: 'claude-flow deployment envs -a
|
|
411
|
+
{ command: 'claude-flow deployment envs -a add -n preview -t staging', description: 'Add environment' },
|
|
412
|
+
{ command: 'claude-flow deployment envs -a remove -n preview', description: 'Remove environment' },
|
|
108
413
|
],
|
|
109
414
|
action: async (ctx) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
415
|
+
try {
|
|
416
|
+
const action = String(ctx.flags['action'] || 'list');
|
|
417
|
+
const state = loadDeploymentState(ctx.cwd);
|
|
418
|
+
if (action === 'list') {
|
|
419
|
+
const envs = Object.values(state.environments);
|
|
420
|
+
output.writeln();
|
|
421
|
+
output.writeln(output.bold('Deployment Environments'));
|
|
422
|
+
output.writeln();
|
|
423
|
+
if (envs.length === 0) {
|
|
424
|
+
output.writeln(output.dim('No environments configured. Use --action add to create one.'));
|
|
425
|
+
return { success: true };
|
|
426
|
+
}
|
|
427
|
+
output.printTable({
|
|
428
|
+
columns: [
|
|
429
|
+
{ key: 'name', header: 'Name' },
|
|
430
|
+
{ key: 'type', header: 'Type' },
|
|
431
|
+
{ key: 'url', header: 'URL' },
|
|
432
|
+
{ key: 'createdAt', header: 'Created' },
|
|
433
|
+
],
|
|
434
|
+
data: envs.map(e => ({ name: e.name, type: e.type, url: e.url || '-', createdAt: e.createdAt })),
|
|
435
|
+
});
|
|
436
|
+
return { success: true };
|
|
437
|
+
}
|
|
438
|
+
if (action === 'add') {
|
|
439
|
+
const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
|
|
440
|
+
if (!name) {
|
|
441
|
+
output.printError('Environment name is required', 'Use --name or -n to specify');
|
|
442
|
+
return { success: false, exitCode: 1 };
|
|
443
|
+
}
|
|
444
|
+
if (state.environments[name]) {
|
|
445
|
+
output.printWarning(`Environment '${name}' already exists`);
|
|
446
|
+
return { success: false, exitCode: 1 };
|
|
447
|
+
}
|
|
448
|
+
const envType = String(ctx.flags['type'] || 'local');
|
|
449
|
+
const url = ctx.flags['url'] ? String(ctx.flags['url']) : undefined;
|
|
450
|
+
state.environments[name] = {
|
|
451
|
+
name,
|
|
452
|
+
type: envType,
|
|
453
|
+
url,
|
|
454
|
+
createdAt: new Date().toISOString(),
|
|
455
|
+
};
|
|
456
|
+
saveDeploymentState(ctx.cwd, state);
|
|
457
|
+
output.writeln();
|
|
458
|
+
output.printSuccess(`Added environment '${name}' (${envType})`);
|
|
459
|
+
if (url) {
|
|
460
|
+
output.writeln(output.dim(` URL: ${url}`));
|
|
461
|
+
}
|
|
462
|
+
return { success: true };
|
|
463
|
+
}
|
|
464
|
+
if (action === 'remove') {
|
|
465
|
+
const name = ctx.flags['name'] ? String(ctx.flags['name']) : null;
|
|
466
|
+
if (!name) {
|
|
467
|
+
output.printError('Environment name is required', 'Use --name or -n to specify');
|
|
468
|
+
return { success: false, exitCode: 1 };
|
|
469
|
+
}
|
|
470
|
+
if (!state.environments[name]) {
|
|
471
|
+
output.printWarning(`Environment '${name}' not found`);
|
|
472
|
+
return { success: false, exitCode: 1 };
|
|
473
|
+
}
|
|
474
|
+
delete state.environments[name];
|
|
475
|
+
saveDeploymentState(ctx.cwd, state);
|
|
476
|
+
output.writeln();
|
|
477
|
+
output.printSuccess(`Removed environment '${name}'`);
|
|
478
|
+
return { success: true };
|
|
479
|
+
}
|
|
480
|
+
output.printError(`Unknown action '${action}'`, 'Valid actions: list, add, remove');
|
|
481
|
+
return { success: false, exitCode: 1 };
|
|
482
|
+
}
|
|
483
|
+
catch (err) {
|
|
484
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
485
|
+
output.printError('Environments command failed', msg);
|
|
486
|
+
return { success: false, exitCode: 1 };
|
|
487
|
+
}
|
|
116
488
|
},
|
|
117
489
|
};
|
|
490
|
+
// ============================================
|
|
118
491
|
// Logs subcommand
|
|
492
|
+
// ============================================
|
|
119
493
|
const logsCommand = {
|
|
120
494
|
name: 'logs',
|
|
121
495
|
description: 'View deployment logs',
|
|
122
496
|
options: [
|
|
123
497
|
{ name: 'deployment', short: 'd', type: 'string', description: 'Deployment ID' },
|
|
124
498
|
{ name: 'env', short: 'e', type: 'string', description: 'Environment' },
|
|
125
|
-
{ name: 'follow', short: 'f', type: 'boolean', description: 'Follow log output' },
|
|
126
499
|
{ name: 'lines', short: 'n', type: 'number', description: 'Number of lines', default: '50' },
|
|
127
500
|
],
|
|
128
501
|
examples: [
|
|
@@ -130,24 +503,142 @@ const logsCommand = {
|
|
|
130
503
|
{ command: 'claude-flow deployment logs -d dep-123', description: 'View specific deployment' },
|
|
131
504
|
],
|
|
132
505
|
action: async (ctx) => {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
506
|
+
try {
|
|
507
|
+
const state = loadDeploymentState(ctx.cwd);
|
|
508
|
+
const filterEnv = ctx.flags['env'] ? String(ctx.flags['env']) : null;
|
|
509
|
+
const deploymentId = ctx.flags['deployment'] ? String(ctx.flags['deployment']) : null;
|
|
510
|
+
const limit = Number(ctx.flags['lines']) || 50;
|
|
511
|
+
output.writeln();
|
|
512
|
+
output.writeln(output.bold('Deployment Logs'));
|
|
513
|
+
output.writeln();
|
|
514
|
+
let records = [...state.history].reverse();
|
|
515
|
+
if (deploymentId) {
|
|
516
|
+
records = records.filter(r => r.id === deploymentId);
|
|
517
|
+
if (records.length === 0) {
|
|
518
|
+
output.printWarning(`Deployment '${deploymentId}' not found`);
|
|
519
|
+
return { success: false, exitCode: 1 };
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (filterEnv) {
|
|
523
|
+
records = records.filter(r => r.environment === filterEnv);
|
|
524
|
+
}
|
|
525
|
+
records = records.slice(0, limit);
|
|
526
|
+
if (records.length === 0) {
|
|
527
|
+
output.writeln(output.dim('No deployment logs found'));
|
|
528
|
+
return { success: true };
|
|
529
|
+
}
|
|
530
|
+
output.printTable({
|
|
531
|
+
columns: [
|
|
532
|
+
{ key: 'id', header: 'ID' },
|
|
533
|
+
{ key: 'environment', header: 'Env' },
|
|
534
|
+
{ key: 'version', header: 'Version' },
|
|
535
|
+
{ key: 'status', header: 'Status' },
|
|
536
|
+
{ key: 'timestamp', header: 'Time' },
|
|
537
|
+
{ key: 'description', header: 'Description' },
|
|
538
|
+
],
|
|
539
|
+
data: records.map(r => ({
|
|
540
|
+
...r,
|
|
541
|
+
description: r.description || '-',
|
|
542
|
+
})),
|
|
543
|
+
});
|
|
544
|
+
output.writeln();
|
|
545
|
+
output.writeln(output.dim(`${records.length} entries shown`));
|
|
546
|
+
return { success: true };
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
550
|
+
output.printError('Failed to load logs', msg);
|
|
551
|
+
return { success: false, exitCode: 1 };
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
};
|
|
555
|
+
// ============================================
|
|
556
|
+
// Release subcommand
|
|
557
|
+
// ============================================
|
|
558
|
+
const releaseCommand = {
|
|
559
|
+
name: 'release',
|
|
560
|
+
description: 'Create a new release deployment',
|
|
561
|
+
options: [
|
|
562
|
+
{ name: 'version', short: 'v', type: 'string', description: 'Release version' },
|
|
563
|
+
{ name: 'env', short: 'e', type: 'string', description: 'Target environment', default: 'production' },
|
|
564
|
+
{ name: 'description', short: 'd', type: 'string', description: 'Release description' },
|
|
565
|
+
],
|
|
566
|
+
examples: [
|
|
567
|
+
{ command: 'claude-flow deployment release -v 3.5.0', description: 'Release version 3.5.0' },
|
|
568
|
+
{ command: 'claude-flow deployment release -v 3.5.0 -d "Major update"', description: 'Release with description' },
|
|
569
|
+
],
|
|
570
|
+
action: async (ctx) => {
|
|
571
|
+
try {
|
|
572
|
+
const envName = String(ctx.flags['env'] || 'production');
|
|
573
|
+
const description = ctx.flags['description'] ? String(ctx.flags['description']) : undefined;
|
|
574
|
+
let version = ctx.flags['version'] ? String(ctx.flags['version']) : null;
|
|
575
|
+
if (!version) {
|
|
576
|
+
const pkgVersion = readProjectVersion(ctx.cwd);
|
|
577
|
+
if (!pkgVersion) {
|
|
578
|
+
output.printError('Version is required', 'Use --version or -v, or ensure package.json has a version field');
|
|
579
|
+
return { success: false, exitCode: 1 };
|
|
580
|
+
}
|
|
581
|
+
version = pkgVersion;
|
|
582
|
+
}
|
|
583
|
+
const state = loadDeploymentState(ctx.cwd);
|
|
584
|
+
// Ensure environment exists
|
|
585
|
+
if (!state.environments[envName]) {
|
|
586
|
+
state.environments[envName] = {
|
|
587
|
+
name: envName,
|
|
588
|
+
type: envName === 'prod' || envName === 'production' ? 'production' : 'staging',
|
|
589
|
+
createdAt: new Date().toISOString(),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
const record = {
|
|
593
|
+
id: generateId(),
|
|
594
|
+
environment: envName,
|
|
595
|
+
version,
|
|
596
|
+
status: 'deployed',
|
|
597
|
+
timestamp: new Date().toISOString(),
|
|
598
|
+
description: description || `Release ${version}`,
|
|
599
|
+
};
|
|
600
|
+
state.history.push(record);
|
|
601
|
+
state.activeDeployment = record.id;
|
|
602
|
+
saveDeploymentState(ctx.cwd, state);
|
|
603
|
+
output.writeln();
|
|
604
|
+
output.printSuccess(`Released version ${version} to ${envName}`);
|
|
605
|
+
output.writeln();
|
|
606
|
+
output.printTable({
|
|
607
|
+
columns: [
|
|
608
|
+
{ key: 'field', header: 'Field' },
|
|
609
|
+
{ key: 'value', header: 'Value' },
|
|
610
|
+
],
|
|
611
|
+
data: [
|
|
612
|
+
{ field: 'Release ID', value: record.id },
|
|
613
|
+
{ field: 'Environment', value: envName },
|
|
614
|
+
{ field: 'Version', value: version },
|
|
615
|
+
{ field: 'Status', value: record.status },
|
|
616
|
+
{ field: 'Timestamp', value: record.timestamp },
|
|
617
|
+
{ field: 'Description', value: record.description || '-' },
|
|
618
|
+
],
|
|
619
|
+
});
|
|
620
|
+
return { success: true, data: record };
|
|
621
|
+
}
|
|
622
|
+
catch (err) {
|
|
623
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
624
|
+
output.printError('Release failed', msg);
|
|
625
|
+
return { success: false, exitCode: 1 };
|
|
626
|
+
}
|
|
139
627
|
},
|
|
140
628
|
};
|
|
629
|
+
// ============================================
|
|
141
630
|
// Main deployment command
|
|
631
|
+
// ============================================
|
|
142
632
|
export const deploymentCommand = {
|
|
143
633
|
name: 'deployment',
|
|
144
634
|
description: 'Deployment management, environments, rollbacks',
|
|
145
635
|
aliases: ['deploy'],
|
|
146
|
-
subcommands: [deployCommand, statusCommand, rollbackCommand, historyCommand, environmentsCommand, logsCommand],
|
|
636
|
+
subcommands: [deployCommand, statusCommand, rollbackCommand, historyCommand, environmentsCommand, logsCommand, releaseCommand],
|
|
147
637
|
examples: [
|
|
148
638
|
{ command: 'claude-flow deployment deploy -e prod', description: 'Deploy to production' },
|
|
149
639
|
{ command: 'claude-flow deployment status', description: 'Check all environments' },
|
|
150
640
|
{ command: 'claude-flow deployment rollback -e prod', description: 'Rollback production' },
|
|
641
|
+
{ command: 'claude-flow deployment release -v 3.5.0', description: 'Create a release' },
|
|
151
642
|
],
|
|
152
643
|
action: async () => {
|
|
153
644
|
output.writeln();
|
|
@@ -162,6 +653,7 @@ export const deploymentCommand = {
|
|
|
162
653
|
'history - View deployment history',
|
|
163
654
|
'environments - Manage deployment environments',
|
|
164
655
|
'logs - View deployment logs',
|
|
656
|
+
'release - Create a new release',
|
|
165
657
|
]);
|
|
166
658
|
output.writeln();
|
|
167
659
|
output.writeln('Features:');
|
|
@@ -172,7 +664,7 @@ export const deploymentCommand = {
|
|
|
172
664
|
'Deployment previews for PRs',
|
|
173
665
|
]);
|
|
174
666
|
output.writeln();
|
|
175
|
-
output.writeln(output.dim('Created with
|
|
667
|
+
output.writeln(output.dim('Created with love by ruv.io'));
|
|
176
668
|
return { success: true };
|
|
177
669
|
},
|
|
178
670
|
};
|