xuanwu-cli 1.0.0 → 2.0.0
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 +138 -88
- package/dist/api/client.d.ts +25 -1
- package/dist/api/client.js +92 -0
- package/dist/commands/app.d.ts +5 -0
- package/dist/commands/app.js +241 -0
- package/dist/commands/build.js +74 -17
- package/dist/commands/svc.d.ts +1 -1
- package/dist/commands/svc.js +219 -24
- package/dist/config/types.d.ts +92 -0
- package/dist/index.js +5 -116
- package/package.json +3 -2
- package/src/api/client.ts +111 -1
- package/src/commands/app.ts +269 -0
- package/src/commands/build.ts +78 -18
- package/src/commands/svc.ts +266 -25
- package/src/config/types.ts +105 -0
- package/src/index.ts +5 -97
- package/test/REPORT.md +78 -0
- package/test/integration.js +299 -0
package/dist/config/types.d.ts
CHANGED
|
@@ -84,3 +84,95 @@ export interface CLIResult<T = any> {
|
|
|
84
84
|
duration?: number;
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
|
+
export interface Application {
|
|
88
|
+
id: string;
|
|
89
|
+
code: string;
|
|
90
|
+
name: string;
|
|
91
|
+
gitRepo: string;
|
|
92
|
+
gitBranch: string;
|
|
93
|
+
buildPath: string;
|
|
94
|
+
dockerfile: string;
|
|
95
|
+
buildArgs?: Record<string, string>;
|
|
96
|
+
imageName: string;
|
|
97
|
+
jenkinsConfigId?: string;
|
|
98
|
+
jenkinsJobName?: string;
|
|
99
|
+
createdAt: string;
|
|
100
|
+
updatedAt: string;
|
|
101
|
+
}
|
|
102
|
+
export interface CreateApplicationDto {
|
|
103
|
+
name: string;
|
|
104
|
+
code?: string;
|
|
105
|
+
gitRepo: string;
|
|
106
|
+
gitBranch?: string;
|
|
107
|
+
imageName?: string;
|
|
108
|
+
buildPath?: string;
|
|
109
|
+
dockerfile?: string;
|
|
110
|
+
buildArgs?: Record<string, string>;
|
|
111
|
+
jenkinsConfigId?: string;
|
|
112
|
+
jenkinsJobName?: string;
|
|
113
|
+
}
|
|
114
|
+
export interface UpdateApplicationDto {
|
|
115
|
+
name?: string;
|
|
116
|
+
gitRepo?: string;
|
|
117
|
+
gitBranch?: string;
|
|
118
|
+
imageName?: string;
|
|
119
|
+
buildPath?: string;
|
|
120
|
+
dockerfile?: string;
|
|
121
|
+
buildArgs?: Record<string, string>;
|
|
122
|
+
jenkinsConfigId?: string;
|
|
123
|
+
jenkinsJobName?: string;
|
|
124
|
+
}
|
|
125
|
+
export interface Build {
|
|
126
|
+
id: string;
|
|
127
|
+
buildNumber: number;
|
|
128
|
+
status: string;
|
|
129
|
+
imageTag: string;
|
|
130
|
+
commitSha?: string;
|
|
131
|
+
commitMessage?: string;
|
|
132
|
+
commitAuthor?: string;
|
|
133
|
+
createdAt: string;
|
|
134
|
+
completedAt?: string;
|
|
135
|
+
}
|
|
136
|
+
export interface TriggerBuildOptions {
|
|
137
|
+
gitBranch?: string;
|
|
138
|
+
commitSha?: string;
|
|
139
|
+
commitMessage?: string;
|
|
140
|
+
commitAuthor?: string;
|
|
141
|
+
}
|
|
142
|
+
export interface K8sService {
|
|
143
|
+
name: string;
|
|
144
|
+
namespace: string;
|
|
145
|
+
type: string;
|
|
146
|
+
clusterIP: string;
|
|
147
|
+
externalIP?: string;
|
|
148
|
+
ports: {
|
|
149
|
+
port: number;
|
|
150
|
+
targetPort: number;
|
|
151
|
+
protocol: string;
|
|
152
|
+
}[];
|
|
153
|
+
age: string;
|
|
154
|
+
}
|
|
155
|
+
export interface K8sDeployment {
|
|
156
|
+
name: string;
|
|
157
|
+
namespace: string;
|
|
158
|
+
replicas: number;
|
|
159
|
+
readyReplicas: number;
|
|
160
|
+
availableReplicas: number;
|
|
161
|
+
image: string;
|
|
162
|
+
labels?: Record<string, string>;
|
|
163
|
+
createdAt: string;
|
|
164
|
+
}
|
|
165
|
+
export interface K8sPod {
|
|
166
|
+
name: string;
|
|
167
|
+
namespace: string;
|
|
168
|
+
status: string;
|
|
169
|
+
ready: string;
|
|
170
|
+
restarts: number;
|
|
171
|
+
age: string;
|
|
172
|
+
ip: string;
|
|
173
|
+
node: string;
|
|
174
|
+
containers: {
|
|
175
|
+
name: string;
|
|
176
|
+
image: string;
|
|
177
|
+
}[];
|
|
178
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -2,44 +2,12 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* xuanwu-cli 主入口
|
|
4
4
|
*/
|
|
5
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
-
}
|
|
11
|
-
Object.defineProperty(o, k2, desc);
|
|
12
|
-
}) : (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
o[k2] = m[k];
|
|
15
|
-
}));
|
|
16
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
-
}) : function(o, v) {
|
|
19
|
-
o["default"] = v;
|
|
20
|
-
});
|
|
21
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
-
var ownKeys = function(o) {
|
|
23
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
-
var ar = [];
|
|
25
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
-
return ar;
|
|
27
|
-
};
|
|
28
|
-
return ownKeys(o);
|
|
29
|
-
};
|
|
30
|
-
return function (mod) {
|
|
31
|
-
if (mod && mod.__esModule) return mod;
|
|
32
|
-
var result = {};
|
|
33
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
-
__setModuleDefault(result, mod);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
})();
|
|
38
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
6
|
const commander_1 = require("commander");
|
|
40
7
|
const connect_1 = require("./commands/connect");
|
|
41
8
|
const env_1 = require("./commands/env");
|
|
42
9
|
const deploy_1 = require("./commands/deploy");
|
|
10
|
+
const app_1 = require("./commands/app");
|
|
43
11
|
const svc_1 = require("./commands/svc");
|
|
44
12
|
const build_1 = require("./commands/build");
|
|
45
13
|
const scale_1 = require("./commands/scale");
|
|
@@ -47,96 +15,17 @@ const logs_1 = require("./commands/logs");
|
|
|
47
15
|
const pods_1 = require("./commands/pods");
|
|
48
16
|
const program = new commander_1.Command();
|
|
49
17
|
program
|
|
50
|
-
.name('
|
|
18
|
+
.name('xw')
|
|
51
19
|
.description('玄武工厂平台 CLI 工具')
|
|
52
|
-
.version('
|
|
20
|
+
.version('2.0.0')
|
|
53
21
|
.option('-o, --output <format>', 'Output format (human|json)', 'human');
|
|
54
|
-
// 添加子命令
|
|
55
22
|
program.addCommand((0, connect_1.makeConnectCommand)());
|
|
56
23
|
program.addCommand((0, env_1.makeEnvCommand)());
|
|
57
|
-
program.addCommand((0,
|
|
24
|
+
program.addCommand((0, app_1.makeAppCommand)());
|
|
58
25
|
program.addCommand((0, svc_1.makeSvcCommand)());
|
|
59
26
|
program.addCommand((0, build_1.makeBuildCommand)());
|
|
27
|
+
program.addCommand((0, deploy_1.makeDeployCommand)());
|
|
60
28
|
program.addCommand((0, scale_1.makeScaleCommand)());
|
|
61
29
|
program.addCommand((0, logs_1.makeLogsCommand)());
|
|
62
30
|
program.addCommand((0, pods_1.makePodsCommand)());
|
|
63
|
-
// 快捷命令:直接 deploy 也支持
|
|
64
|
-
program
|
|
65
|
-
.command('up <namespace> <service-name>')
|
|
66
|
-
.description('Build and deploy a service')
|
|
67
|
-
.action(async (namespace, serviceName) => {
|
|
68
|
-
const { makeBuildCommand } = await Promise.resolve().then(() => __importStar(require('./commands/build')));
|
|
69
|
-
const buildCmd = makeBuildCommand();
|
|
70
|
-
// 简化:只调用部署
|
|
71
|
-
console.log('Use: xuanwu build <namespace> <service-name> && xuanwu deploy <namespace> <service-name>');
|
|
72
|
-
});
|
|
73
|
-
// restart 命令
|
|
74
|
-
program
|
|
75
|
-
.command('restart <namespace> <service-name>')
|
|
76
|
-
.description('Restart a service')
|
|
77
|
-
.action(async (namespace, serviceName) => {
|
|
78
|
-
console.log('Restarting service... (not implemented)');
|
|
79
|
-
});
|
|
80
|
-
// exec 命令
|
|
81
|
-
program
|
|
82
|
-
.command('exec <namespace> <service-name>')
|
|
83
|
-
.description('Execute command in container')
|
|
84
|
-
.option('-c, --command <cmd>', 'Command to execute', 'ls -la')
|
|
85
|
-
.option('-p, --pod <pod-name>', 'Specific pod name')
|
|
86
|
-
.action(async (namespace, serviceName, options) => {
|
|
87
|
-
const { configStore } = await Promise.resolve().then(() => __importStar(require('./config/store')));
|
|
88
|
-
const { createClient } = await Promise.resolve().then(() => __importStar(require('./api/client')));
|
|
89
|
-
const { OutputFormatter } = await Promise.resolve().then(() => __importStar(require('./output/formatter')));
|
|
90
|
-
const conn = configStore.getDefaultConnection();
|
|
91
|
-
if (!conn) {
|
|
92
|
-
OutputFormatter.error('No connection configured');
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const client = createClient(conn);
|
|
96
|
-
const result = await client.exec(namespace, serviceName, options.command, options.pod);
|
|
97
|
-
if (!result.success) {
|
|
98
|
-
OutputFormatter.error(result.error.message);
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const data = result.data;
|
|
102
|
-
if (data.stdout)
|
|
103
|
-
console.log(data.stdout);
|
|
104
|
-
if (data.stderr)
|
|
105
|
-
console.error(data.stderr);
|
|
106
|
-
if (data.exitCode !== 0) {
|
|
107
|
-
process.exit(data.exitCode);
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
// top 命令
|
|
111
|
-
program
|
|
112
|
-
.command('top <namespace> <service-name>')
|
|
113
|
-
.description('Show resource usage')
|
|
114
|
-
.option('-p, --pod <pod-name>', 'Specific pod name')
|
|
115
|
-
.action(async (namespace, serviceName, options) => {
|
|
116
|
-
const { configStore } = await Promise.resolve().then(() => __importStar(require('./config/store')));
|
|
117
|
-
const { createClient } = await Promise.resolve().then(() => __importStar(require('./api/client')));
|
|
118
|
-
const { OutputFormatter } = await Promise.resolve().then(() => __importStar(require('./output/formatter')));
|
|
119
|
-
const conn = configStore.getDefaultConnection();
|
|
120
|
-
if (!conn) {
|
|
121
|
-
OutputFormatter.error('No connection configured');
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
const client = createClient(conn);
|
|
125
|
-
const result = await client.getMetrics(namespace, serviceName, options.pod);
|
|
126
|
-
if (!result.success) {
|
|
127
|
-
OutputFormatter.error(result.error.message);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const pods = result.data?.pods || [];
|
|
131
|
-
if (pods.length === 0) {
|
|
132
|
-
OutputFormatter.info('No pod metrics found');
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
OutputFormatter.table(['Pod Name', 'CPU', 'Memory'], pods.map((p) => [
|
|
136
|
-
p.name,
|
|
137
|
-
p.containers?.[0]?.cpuRaw || 'N/A',
|
|
138
|
-
p.containers?.[0]?.memoryRaw || 'N/A'
|
|
139
|
-
]));
|
|
140
|
-
});
|
|
141
|
-
// 解析参数
|
|
142
31
|
program.parse(process.argv);
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'
|
|
6
|
-
import { Connection, CLIResult, ServiceInfo, NamespaceInfo, DeployOptions } from '../config/types'
|
|
6
|
+
import { Connection, CLIResult, ServiceInfo, NamespaceInfo, DeployOptions, Application, Build, CreateApplicationDto, UpdateApplicationDto, TriggerBuildOptions } from '../config/types'
|
|
7
7
|
import { OutputFormatter } from '../output/formatter'
|
|
8
8
|
|
|
9
9
|
export class APIClient {
|
|
@@ -86,6 +86,17 @@ export class APIClient {
|
|
|
86
86
|
return this.request<any[]>('GET', '/api/projects')
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
async createProject(name: string, description?: string): Promise<CLIResult<any>> {
|
|
90
|
+
return this.request('POST', '/api/projects', {
|
|
91
|
+
name,
|
|
92
|
+
description: description || ''
|
|
93
|
+
})
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async deleteProject(projectId: string): Promise<CLIResult<void>> {
|
|
97
|
+
return this.request('DELETE', `/api/projects/${projectId}`)
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
async createNamespaceWithProject(name: string, projectId: string, environment: string = 'development'): Promise<CLIResult<any>> {
|
|
90
101
|
return this.request('POST', '/api/deploy-spaces', {
|
|
91
102
|
name,
|
|
@@ -373,6 +384,105 @@ export class APIClient {
|
|
|
373
384
|
command
|
|
374
385
|
})
|
|
375
386
|
}
|
|
387
|
+
|
|
388
|
+
// ===================
|
|
389
|
+
// Application (CLI API - using code)
|
|
390
|
+
// ===================
|
|
391
|
+
|
|
392
|
+
async listApplications(): Promise<CLIResult<Application[]>> {
|
|
393
|
+
return this.request<Application[]>('GET', '/api/cli/apps')
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
async getApplication(code: string): Promise<CLIResult<Application>> {
|
|
397
|
+
return this.request<Application>('GET', `/api/cli/apps/${code}`)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async createApplication(dto: CreateApplicationDto): Promise<CLIResult<Application>> {
|
|
401
|
+
return this.request<Application>('POST', '/api/applications', dto)
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async updateApplication(code: string, dto: UpdateApplicationDto): Promise<CLIResult<Application>> {
|
|
405
|
+
return this.request<Application>('PUT', `/api/cli/apps/${code}`, dto)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async deleteApplication(code: string): Promise<CLIResult<void>> {
|
|
409
|
+
return this.request<void>('DELETE', `/api/cli/apps/${code}`)
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async triggerApplicationBuild(code: string, options?: TriggerBuildOptions): Promise<CLIResult<Build>> {
|
|
413
|
+
return this.request<Build>('POST', `/api/cli/apps/${code}/build`, options)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async listApplicationBuilds(code: string): Promise<CLIResult<Build[]>> {
|
|
417
|
+
return this.request<Build[]>('GET', `/api/cli/apps/${code}/builds`)
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ===================
|
|
421
|
+
// Service (CLI API - using ns/name)
|
|
422
|
+
// ===================
|
|
423
|
+
|
|
424
|
+
async listK8sServices(namespace?: string): Promise<CLIResult<any>> {
|
|
425
|
+
const url = namespace ? `/api/cli/services?namespace=${namespace}` : '/api/cli/services'
|
|
426
|
+
return this.request('GET', url)
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async getK8sService(namespace: string, name: string): Promise<CLIResult<any>> {
|
|
430
|
+
return this.request('GET', `/api/cli/services/${namespace}/${name}`)
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
async getK8sServiceStatus(namespace: string, name: string): Promise<CLIResult<any>> {
|
|
434
|
+
return this.request('GET', `/api/cli/services/${namespace}/${name}/status`)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async getK8sServiceLogs(namespace: string, name: string, lines?: number, follow?: boolean): Promise<CLIResult<any>> {
|
|
438
|
+
let url = `/api/cli/services/${namespace}/${name}/logs`
|
|
439
|
+
const params: string[] = []
|
|
440
|
+
if (lines) params.push(`lines=${lines}`)
|
|
441
|
+
if (follow) params.push(`follow=true`)
|
|
442
|
+
if (params.length > 0) url += '?' + params.join('&')
|
|
443
|
+
return this.request('GET', url)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async restartK8sService(namespace: string, name: string): Promise<CLIResult<any>> {
|
|
447
|
+
return this.request('POST', `/api/cli/services/${namespace}/${name}/restart`)
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async scaleK8sService(namespace: string, name: string, replicas: number): Promise<CLIResult<any>> {
|
|
451
|
+
return this.request('POST', `/api/cli/services/${namespace}/${name}/scale`, { replicas })
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async execK8sService(namespace: string, name: string, command: string, podName?: string): Promise<CLIResult<any>> {
|
|
455
|
+
return this.request('POST', `/api/cli/services/${namespace}/${name}/exec`, { command, podName })
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
async listK8sServicePods(namespace: string, name: string): Promise<CLIResult<any>> {
|
|
459
|
+
return this.request('GET', `/api/cli/services/${namespace}/${name}/pods`)
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async deleteK8sService(namespace: string, name: string): Promise<CLIResult<void>> {
|
|
463
|
+
return this.request<void>('DELETE', `/api/cli/services/${namespace}/${name}`)
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ===================
|
|
467
|
+
// Build (CLI API)
|
|
468
|
+
// ===================
|
|
469
|
+
|
|
470
|
+
async listBuilds(options?: { appCode?: string; status?: string }): Promise<CLIResult<Build[]>> {
|
|
471
|
+
let url = '/api/cli/builds'
|
|
472
|
+
const params: string[] = []
|
|
473
|
+
if (options?.appCode) params.push(`appCode=${options.appCode}`)
|
|
474
|
+
if (options?.status) params.push(`status=${options.status}`)
|
|
475
|
+
if (params.length > 0) url += '?' + params.join('&')
|
|
476
|
+
return this.request<Build[]>('GET', url)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async getBuild(id: string): Promise<CLIResult<Build>> {
|
|
480
|
+
return this.request<Build>('GET', `/api/cli/builds/${id}`)
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
async cancelBuild(id: string): Promise<CLIResult<void>> {
|
|
484
|
+
return this.request<void>('POST', `/api/cli/builds/${id}/cancel`)
|
|
485
|
+
}
|
|
376
486
|
}
|
|
377
487
|
|
|
378
488
|
export function createClient(connection: Connection): APIClient {
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 应用管理命令
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander'
|
|
6
|
+
import { configStore } from '../config/store'
|
|
7
|
+
import { createClient } from '../api/client'
|
|
8
|
+
import { OutputFormatter } from '../output/formatter'
|
|
9
|
+
|
|
10
|
+
function getAge(timestamp?: string): string {
|
|
11
|
+
if (!timestamp) return '-'
|
|
12
|
+
const created = new Date(timestamp)
|
|
13
|
+
const now = new Date()
|
|
14
|
+
const diff = Math.floor((now.getTime() - created.getTime()) / 1000)
|
|
15
|
+
if (diff < 60) return `${diff}s`
|
|
16
|
+
if (diff < 3600) return `${Math.floor(diff / 60)}m`
|
|
17
|
+
if (diff < 86400) return `${Math.floor(diff / 3600)}h`
|
|
18
|
+
return `${Math.floor(diff / 86400)}d`
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function makeAppCommand(): Command {
|
|
22
|
+
const cmd = new Command('app')
|
|
23
|
+
.description('Manage applications')
|
|
24
|
+
|
|
25
|
+
cmd
|
|
26
|
+
.command('ls')
|
|
27
|
+
.alias('list')
|
|
28
|
+
.description('List applications')
|
|
29
|
+
.option('-n, --name <name>', 'Filter by name')
|
|
30
|
+
.action(async (options) => {
|
|
31
|
+
const conn = configStore.getDefaultConnection()
|
|
32
|
+
if (!conn) {
|
|
33
|
+
OutputFormatter.error('No connection configured')
|
|
34
|
+
return
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const client = createClient(conn)
|
|
38
|
+
const result = await client.listApplications()
|
|
39
|
+
|
|
40
|
+
if (!result.success) {
|
|
41
|
+
OutputFormatter.error(result.error!.message)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let apps = result.data || []
|
|
46
|
+
if (options.name) {
|
|
47
|
+
apps = apps.filter((app: any) => app.name.includes(options.name) || app.code.includes(options.name))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (apps.length === 0) {
|
|
51
|
+
OutputFormatter.info('No applications found')
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
OutputFormatter.table(
|
|
56
|
+
['Code', 'Name', 'Git Repo', 'Image Name', 'Updated'],
|
|
57
|
+
apps.map((app: any) => [
|
|
58
|
+
app.code,
|
|
59
|
+
app.name,
|
|
60
|
+
app.gitRepo.substring(0, 40) + (app.gitRepo.length > 40 ? '...' : ''),
|
|
61
|
+
app.imageName,
|
|
62
|
+
getAge(app.updatedAt)
|
|
63
|
+
])
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
cmd
|
|
68
|
+
.command('get <code>')
|
|
69
|
+
.alias('describe')
|
|
70
|
+
.description('Get application details')
|
|
71
|
+
.action(async (code) => {
|
|
72
|
+
const conn = configStore.getDefaultConnection()
|
|
73
|
+
if (!conn) {
|
|
74
|
+
OutputFormatter.error('No connection configured')
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const client = createClient(conn)
|
|
79
|
+
const result = await client.getApplication(code)
|
|
80
|
+
|
|
81
|
+
if (!result.success) {
|
|
82
|
+
OutputFormatter.error(result.error!.message)
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const app = result.data!
|
|
87
|
+
OutputFormatter.info(`Application: ${app.name}`)
|
|
88
|
+
OutputFormatter.info(`Code: ${app.code}`)
|
|
89
|
+
OutputFormatter.info(`Git Repo: ${app.gitRepo}`)
|
|
90
|
+
OutputFormatter.info(`Git Branch: ${app.gitBranch}`)
|
|
91
|
+
OutputFormatter.info(`Image Name: ${app.imageName}`)
|
|
92
|
+
OutputFormatter.info(`Build Path: ${app.buildPath}`)
|
|
93
|
+
OutputFormatter.info(`Dockerfile: ${app.dockerfile}`)
|
|
94
|
+
if (app.jenkinsJobName) {
|
|
95
|
+
OutputFormatter.info(`Jenkins Job: ${app.jenkinsJobName}`)
|
|
96
|
+
}
|
|
97
|
+
OutputFormatter.info(`Created: ${app.createdAt}`)
|
|
98
|
+
OutputFormatter.info(`Updated: ${app.updatedAt}`)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
cmd
|
|
102
|
+
.command('create')
|
|
103
|
+
.description('Create a new application')
|
|
104
|
+
.requiredOption('-n, --name <name>', 'Application name')
|
|
105
|
+
.option('-c, --code <code>', 'Application code (technical identifier)')
|
|
106
|
+
.option('-g, --git <url>', 'Git repository URL')
|
|
107
|
+
.option('-b, --branch <branch>', 'Git branch', 'main')
|
|
108
|
+
.option('-i, --image <image>', 'Container image name')
|
|
109
|
+
.option('--build-path <path>', 'Build path', '.')
|
|
110
|
+
.option('--dockerfile <path>', 'Dockerfile path', 'Dockerfile')
|
|
111
|
+
.action(async (options) => {
|
|
112
|
+
const conn = configStore.getDefaultConnection()
|
|
113
|
+
if (!conn) {
|
|
114
|
+
OutputFormatter.error('No connection configured')
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const client = createClient(conn)
|
|
119
|
+
const result = await client.createApplication({
|
|
120
|
+
name: options.name,
|
|
121
|
+
code: options.code,
|
|
122
|
+
gitRepo: options.git,
|
|
123
|
+
gitBranch: options.branch,
|
|
124
|
+
imageName: options.image,
|
|
125
|
+
buildPath: options.buildPath,
|
|
126
|
+
dockerfile: options.dockerfile
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
if (!result.success) {
|
|
130
|
+
OutputFormatter.error(result.error!.message)
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
OutputFormatter.success(`Application "${result.data!.name}" created with code "${result.data!.code}"`)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
cmd
|
|
138
|
+
.command('update <code>')
|
|
139
|
+
.description('Update an application')
|
|
140
|
+
.option('-n, --name <name>', 'Application name')
|
|
141
|
+
.option('-g, --git <url>', 'Git repository URL')
|
|
142
|
+
.option('-b, --branch <branch>', 'Git branch')
|
|
143
|
+
.option('-i, --image <image>', 'Container image name')
|
|
144
|
+
.option('--build-path <path>', 'Build path')
|
|
145
|
+
.option('--dockerfile <path>', 'Dockerfile path')
|
|
146
|
+
.action(async (code, options) => {
|
|
147
|
+
const conn = configStore.getDefaultConnection()
|
|
148
|
+
if (!conn) {
|
|
149
|
+
OutputFormatter.error('No connection configured')
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const updateData: any = {}
|
|
154
|
+
if (options.name) updateData.name = options.name
|
|
155
|
+
if (options.git) updateData.gitRepo = options.git
|
|
156
|
+
if (options.branch) updateData.gitBranch = options.branch
|
|
157
|
+
if (options.image) updateData.imageName = options.image
|
|
158
|
+
if (options.buildPath) updateData.buildPath = options.buildPath
|
|
159
|
+
if (options.dockerfile) updateData.dockerfile = options.dockerfile
|
|
160
|
+
|
|
161
|
+
const client = createClient(conn)
|
|
162
|
+
const result = await client.updateApplication(code, updateData)
|
|
163
|
+
|
|
164
|
+
if (!result.success) {
|
|
165
|
+
OutputFormatter.error(result.error!.message)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
OutputFormatter.success(`Application "${code}" updated`)
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
cmd
|
|
173
|
+
.command('rm <code>')
|
|
174
|
+
.alias('delete')
|
|
175
|
+
.description('Delete an application')
|
|
176
|
+
.action(async (code) => {
|
|
177
|
+
const conn = configStore.getDefaultConnection()
|
|
178
|
+
if (!conn) {
|
|
179
|
+
OutputFormatter.error('No connection configured')
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const client = createClient(conn)
|
|
184
|
+
const result = await client.deleteApplication(code)
|
|
185
|
+
|
|
186
|
+
if (!result.success) {
|
|
187
|
+
OutputFormatter.error(result.error!.message)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
OutputFormatter.success(`Application "${code}" deleted`)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
cmd
|
|
195
|
+
.command('build <code>')
|
|
196
|
+
.description('Trigger a build for application')
|
|
197
|
+
.option('-b, --branch <branch>', 'Git branch')
|
|
198
|
+
.option('--sha <sha>', 'Commit SHA')
|
|
199
|
+
.option('--message <message>', 'Commit message')
|
|
200
|
+
.option('--author <author>', 'Commit author')
|
|
201
|
+
.action(async (code, options) => {
|
|
202
|
+
const conn = configStore.getDefaultConnection()
|
|
203
|
+
if (!conn) {
|
|
204
|
+
OutputFormatter.error('No connection configured')
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const client = createClient(conn)
|
|
209
|
+
OutputFormatter.info('Starting build...')
|
|
210
|
+
|
|
211
|
+
const buildOptions: any = {}
|
|
212
|
+
if (options.branch) buildOptions.gitBranch = options.branch
|
|
213
|
+
if (options.sha) buildOptions.commitSha = options.sha
|
|
214
|
+
if (options.message) buildOptions.commitMessage = options.message
|
|
215
|
+
if (options.author) buildOptions.commitAuthor = options.author
|
|
216
|
+
|
|
217
|
+
const result = await client.triggerApplicationBuild(code, buildOptions)
|
|
218
|
+
|
|
219
|
+
if (!result.success) {
|
|
220
|
+
OutputFormatter.error(result.error!.message)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const build = result.data!
|
|
225
|
+
OutputFormatter.success(`Build #${build.buildNumber} started (ID: ${build.id})`)
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
cmd
|
|
229
|
+
.command('builds <code>')
|
|
230
|
+
.description('List builds for application')
|
|
231
|
+
.option('-s, --status <status>', 'Filter by status (SUCCESS|FAILED|RUNNING|PENDING)')
|
|
232
|
+
.action(async (code, options) => {
|
|
233
|
+
const conn = configStore.getDefaultConnection()
|
|
234
|
+
if (!conn) {
|
|
235
|
+
OutputFormatter.error('No connection configured')
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const client = createClient(conn)
|
|
240
|
+
const result = await client.listApplicationBuilds(code)
|
|
241
|
+
|
|
242
|
+
if (!result.success) {
|
|
243
|
+
OutputFormatter.error(result.error!.message)
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let builds = result.data || []
|
|
248
|
+
if (options.status) {
|
|
249
|
+
builds = builds.filter((b: any) => b.status === options.status)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (builds.length === 0) {
|
|
253
|
+
OutputFormatter.info('No builds found')
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
OutputFormatter.table(
|
|
258
|
+
['Build', 'Status', 'Image Tag', 'Created'],
|
|
259
|
+
builds.map((b: any) => [
|
|
260
|
+
`#${b.buildNumber}`,
|
|
261
|
+
b.status,
|
|
262
|
+
b.imageTag || '-',
|
|
263
|
+
getAge(b.createdAt)
|
|
264
|
+
])
|
|
265
|
+
)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
return cmd
|
|
269
|
+
}
|