xuanwu-cli 2.3.2 → 2.3.3
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/dist/api/client.d.ts +6 -0
- package/dist/api/client.js +9 -0
- package/dist/commands/app.js +44 -0
- package/dist/commands/deploy.js +54 -2
- package/dist/commands/svc.js +36 -0
- package/package.json +1 -1
- package/src/api/client.ts +14 -0
- package/src/commands/app.ts +53 -0
- package/src/commands/deploy.ts +64 -2
- package/src/commands/svc.ts +40 -0
package/dist/api/client.d.ts
CHANGED
|
@@ -62,6 +62,11 @@ export declare class APIClient {
|
|
|
62
62
|
scaleK8sService(namespace: string, name: string, replicas: number): Promise<CLIResult<any>>;
|
|
63
63
|
execK8sService(namespace: string, name: string, command: string, podName?: string): Promise<CLIResult<any>>;
|
|
64
64
|
listK8sServicePods(namespace: string, name: string): Promise<CLIResult<any>>;
|
|
65
|
+
updateK8sService(namespace: string, name: string, options: {
|
|
66
|
+
image?: string;
|
|
67
|
+
replicas?: number;
|
|
68
|
+
port?: number;
|
|
69
|
+
}): Promise<CLIResult<any>>;
|
|
65
70
|
deleteK8sService(namespace: string, name: string): Promise<CLIResult<void>>;
|
|
66
71
|
listBuilds(options?: {
|
|
67
72
|
appCode?: string;
|
|
@@ -69,5 +74,6 @@ export declare class APIClient {
|
|
|
69
74
|
}): Promise<CLIResult<Build[]>>;
|
|
70
75
|
getBuild(id: string): Promise<CLIResult<Build>>;
|
|
71
76
|
cancelBuild(id: string): Promise<CLIResult<void>>;
|
|
77
|
+
getBuildLogs(id: string, follow?: boolean): Promise<CLIResult<any>>;
|
|
72
78
|
}
|
|
73
79
|
export declare function createClient(connection: Connection): APIClient;
|
package/dist/api/client.js
CHANGED
|
@@ -469,6 +469,9 @@ class APIClient {
|
|
|
469
469
|
async listK8sServicePods(namespace, name) {
|
|
470
470
|
return this.request('GET', `/api/cli/services/${namespace}/${name}/pods`);
|
|
471
471
|
}
|
|
472
|
+
async updateK8sService(namespace, name, options) {
|
|
473
|
+
return this.request('PUT', `/api/cli/services/${namespace}/${name}`, options);
|
|
474
|
+
}
|
|
472
475
|
async deleteK8sService(namespace, name) {
|
|
473
476
|
return this.request('DELETE', `/api/cli/services/${namespace}/${name}`);
|
|
474
477
|
}
|
|
@@ -492,6 +495,12 @@ class APIClient {
|
|
|
492
495
|
async cancelBuild(id) {
|
|
493
496
|
return this.request('POST', `/api/cli/builds/${id}/cancel`);
|
|
494
497
|
}
|
|
498
|
+
async getBuildLogs(id, follow) {
|
|
499
|
+
let url = `/api/cli/builds/${id}/logs`;
|
|
500
|
+
if (follow)
|
|
501
|
+
url += '?follow=true';
|
|
502
|
+
return this.request('GET', url);
|
|
503
|
+
}
|
|
495
504
|
}
|
|
496
505
|
exports.APIClient = APIClient;
|
|
497
506
|
function createClient(connection) {
|
package/dist/commands/app.js
CHANGED
|
@@ -237,5 +237,49 @@ function makeAppCommand() {
|
|
|
237
237
|
getAge(b.createdAt)
|
|
238
238
|
]));
|
|
239
239
|
});
|
|
240
|
+
cmd
|
|
241
|
+
.command('logs <code>')
|
|
242
|
+
.description('View application build logs')
|
|
243
|
+
.option('-b, --build <buildNumber>', 'Specific build number (default: latest)')
|
|
244
|
+
.option('-f, --follow', 'Follow log output in real-time')
|
|
245
|
+
.action(async (code, options) => {
|
|
246
|
+
const conn = store_1.configStore.getDefaultConnection();
|
|
247
|
+
if (!conn) {
|
|
248
|
+
formatter_1.OutputFormatter.error('No connection configured');
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const client = (0, client_1.createClient)(conn);
|
|
252
|
+
const buildsResult = await client.listApplicationBuilds(code);
|
|
253
|
+
if (!buildsResult.success) {
|
|
254
|
+
formatter_1.OutputFormatter.error(buildsResult.error.message);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const builds = buildsResult.data || [];
|
|
258
|
+
if (builds.length === 0) {
|
|
259
|
+
formatter_1.OutputFormatter.info('No builds found for this application');
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
let targetBuild;
|
|
263
|
+
if (options.build) {
|
|
264
|
+
targetBuild = builds.find((b) => b.buildNumber === parseInt(options.build));
|
|
265
|
+
if (!targetBuild) {
|
|
266
|
+
formatter_1.OutputFormatter.error(`Build #${options.build} not found`);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
targetBuild = builds[0];
|
|
272
|
+
}
|
|
273
|
+
formatter_1.OutputFormatter.info(`Viewing logs for build #${targetBuild.buildNumber} (ID: ${targetBuild.id})`);
|
|
274
|
+
if (options.follow) {
|
|
275
|
+
formatter_1.OutputFormatter.info('Following logs... (Ctrl+C to exit)');
|
|
276
|
+
}
|
|
277
|
+
const result = await client.getBuildLogs(targetBuild.id, options.follow);
|
|
278
|
+
if (!result.success) {
|
|
279
|
+
formatter_1.OutputFormatter.error(result.error.message);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
console.log(result.data?.logs || 'No logs available');
|
|
283
|
+
});
|
|
240
284
|
return cmd;
|
|
241
285
|
}
|
package/dist/commands/deploy.js
CHANGED
|
@@ -15,9 +15,62 @@ function parseNamespaceName(input) {
|
|
|
15
15
|
}
|
|
16
16
|
return { namespace: parts[0], name: parts[1] };
|
|
17
17
|
}
|
|
18
|
+
function getAge(timestamp) {
|
|
19
|
+
if (!timestamp)
|
|
20
|
+
return '-';
|
|
21
|
+
const created = new Date(timestamp);
|
|
22
|
+
const now = new Date();
|
|
23
|
+
const diff = Math.floor((now.getTime() - created.getTime()) / 1000);
|
|
24
|
+
if (diff < 60)
|
|
25
|
+
return `${diff}s`;
|
|
26
|
+
if (diff < 3600)
|
|
27
|
+
return `${Math.floor(diff / 60)}m`;
|
|
28
|
+
if (diff < 86400)
|
|
29
|
+
return `${Math.floor(diff / 3600)}h`;
|
|
30
|
+
return `${Math.floor(diff / 86400)}d`;
|
|
31
|
+
}
|
|
18
32
|
function makeDeployCommand() {
|
|
19
33
|
const cmd = new commander_1.Command('deploy')
|
|
20
|
-
.description('Deploy services to environment
|
|
34
|
+
.description('Deploy services to environment');
|
|
35
|
+
cmd
|
|
36
|
+
.command('history <ns-name>')
|
|
37
|
+
.description('Show deployment history for a service')
|
|
38
|
+
.action(async (nsName) => {
|
|
39
|
+
const conn = store_1.configStore.getDefaultConnection();
|
|
40
|
+
if (!conn) {
|
|
41
|
+
formatter_1.OutputFormatter.error('No connection configured');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const parsed = parseNamespaceName(nsName);
|
|
45
|
+
if (!parsed) {
|
|
46
|
+
formatter_1.OutputFormatter.error('Invalid format. Use: <namespace>/<service-name>');
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const { namespace, name: serviceName } = parsed;
|
|
50
|
+
const client = (0, client_1.createClient)(conn);
|
|
51
|
+
const result = await client.listServiceDeployments(namespace, serviceName);
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
formatter_1.OutputFormatter.error(result.error.message);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const data = result.data;
|
|
57
|
+
const deployments = data?.deployments || data || [];
|
|
58
|
+
if (deployments.length === 0) {
|
|
59
|
+
formatter_1.OutputFormatter.info('No deployments found');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
formatter_1.OutputFormatter.table(['ID', 'Image', 'Status', 'Build', 'Created'], deployments.map((d) => [
|
|
63
|
+
d.id.substring(0, 8),
|
|
64
|
+
d.image?.substring(0, 40) || '-',
|
|
65
|
+
d.status || '-',
|
|
66
|
+
d.build?.buildNumber ? `#${d.build.buildNumber}` : '-',
|
|
67
|
+
getAge(d.createdAt)
|
|
68
|
+
]));
|
|
69
|
+
if (data?.pagination) {
|
|
70
|
+
formatter_1.OutputFormatter.info(`Page ${data.pagination.page}/${data.pagination.totalPages}, Total: ${data.pagination.total}`);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
cmd
|
|
21
74
|
.argument('<ns-name>', 'Target namespace and service name (format: namespace/service-name)')
|
|
22
75
|
.requiredOption('-i, --image <image>', 'Container image (required)')
|
|
23
76
|
.option('-p, --project <code>', 'Project code')
|
|
@@ -44,7 +97,6 @@ function makeDeployCommand() {
|
|
|
44
97
|
}
|
|
45
98
|
const { namespace, name: serviceName } = parsed;
|
|
46
99
|
const client = (0, client_1.createClient)(conn);
|
|
47
|
-
// 使用 CLI API 进行部署
|
|
48
100
|
const result = await client.deployService(namespace, serviceName, options.image, {
|
|
49
101
|
projectCode: options.project,
|
|
50
102
|
replicas: options.replicas ? parseInt(options.replicas) : 1,
|
package/dist/commands/svc.js
CHANGED
|
@@ -268,6 +268,42 @@ function makeSvcCommand() {
|
|
|
268
268
|
p.ip || '-'
|
|
269
269
|
]));
|
|
270
270
|
});
|
|
271
|
+
cmd
|
|
272
|
+
.command('update <ns>/<name>')
|
|
273
|
+
.description('Update service configuration')
|
|
274
|
+
.option('-i, --image <image>', 'Container image')
|
|
275
|
+
.option('-r, --replicas <num>', 'Number of replicas')
|
|
276
|
+
.option('--port <port>', 'Container port')
|
|
277
|
+
.action(async (nsName, options) => {
|
|
278
|
+
const parsed = parseNamespaceName(nsName);
|
|
279
|
+
if (!parsed) {
|
|
280
|
+
formatter_1.OutputFormatter.error('Invalid format. Use: <namespace>/<service-name>');
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const conn = store_1.configStore.getDefaultConnection();
|
|
284
|
+
if (!conn) {
|
|
285
|
+
formatter_1.OutputFormatter.error('No connection configured');
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
const updateData = {};
|
|
289
|
+
if (options.image)
|
|
290
|
+
updateData.image = options.image;
|
|
291
|
+
if (options.replicas)
|
|
292
|
+
updateData.replicas = parseInt(options.replicas);
|
|
293
|
+
if (options.port)
|
|
294
|
+
updateData.port = parseInt(options.port);
|
|
295
|
+
if (Object.keys(updateData).length === 0) {
|
|
296
|
+
formatter_1.OutputFormatter.error('No update options provided. Use --image, --replicas, or --port');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const client = (0, client_1.createClient)(conn);
|
|
300
|
+
const result = await client.updateK8sService(parsed.namespace, parsed.name, updateData);
|
|
301
|
+
if (!result.success) {
|
|
302
|
+
formatter_1.OutputFormatter.error(result.error.message);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
formatter_1.OutputFormatter.success(`Service "${nsName}" updated`);
|
|
306
|
+
});
|
|
271
307
|
cmd
|
|
272
308
|
.command('delete <ns>/<name>')
|
|
273
309
|
.alias('rm')
|
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -554,6 +554,14 @@ export class APIClient {
|
|
|
554
554
|
return this.request('GET', `/api/cli/services/${namespace}/${name}/pods`)
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
+
async updateK8sService(namespace: string, name: string, options: {
|
|
558
|
+
image?: string
|
|
559
|
+
replicas?: number
|
|
560
|
+
port?: number
|
|
561
|
+
}): Promise<CLIResult<any>> {
|
|
562
|
+
return this.request('PUT', `/api/cli/services/${namespace}/${name}`, options)
|
|
563
|
+
}
|
|
564
|
+
|
|
557
565
|
async deleteK8sService(namespace: string, name: string): Promise<CLIResult<void>> {
|
|
558
566
|
return this.request<void>('DELETE', `/api/cli/services/${namespace}/${name}`)
|
|
559
567
|
}
|
|
@@ -578,6 +586,12 @@ export class APIClient {
|
|
|
578
586
|
async cancelBuild(id: string): Promise<CLIResult<void>> {
|
|
579
587
|
return this.request<void>('POST', `/api/cli/builds/${id}/cancel`)
|
|
580
588
|
}
|
|
589
|
+
|
|
590
|
+
async getBuildLogs(id: string, follow?: boolean): Promise<CLIResult<any>> {
|
|
591
|
+
let url = `/api/cli/builds/${id}/logs`
|
|
592
|
+
if (follow) url += '?follow=true'
|
|
593
|
+
return this.request('GET', url)
|
|
594
|
+
}
|
|
581
595
|
}
|
|
582
596
|
|
|
583
597
|
export function createClient(connection: Connection): APIClient {
|
package/src/commands/app.ts
CHANGED
|
@@ -265,5 +265,58 @@ export function makeAppCommand(): Command {
|
|
|
265
265
|
)
|
|
266
266
|
})
|
|
267
267
|
|
|
268
|
+
cmd
|
|
269
|
+
.command('logs <code>')
|
|
270
|
+
.description('View application build logs')
|
|
271
|
+
.option('-b, --build <buildNumber>', 'Specific build number (default: latest)')
|
|
272
|
+
.option('-f, --follow', 'Follow log output in real-time')
|
|
273
|
+
.action(async (code, options) => {
|
|
274
|
+
const conn = configStore.getDefaultConnection()
|
|
275
|
+
if (!conn) {
|
|
276
|
+
OutputFormatter.error('No connection configured')
|
|
277
|
+
return
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const client = createClient(conn)
|
|
281
|
+
|
|
282
|
+
const buildsResult = await client.listApplicationBuilds(code)
|
|
283
|
+
if (!buildsResult.success) {
|
|
284
|
+
OutputFormatter.error(buildsResult.error!.message)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const builds = buildsResult.data || []
|
|
289
|
+
if (builds.length === 0) {
|
|
290
|
+
OutputFormatter.info('No builds found for this application')
|
|
291
|
+
return
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let targetBuild: any
|
|
295
|
+
if (options.build) {
|
|
296
|
+
targetBuild = builds.find((b: any) => b.buildNumber === parseInt(options.build))
|
|
297
|
+
if (!targetBuild) {
|
|
298
|
+
OutputFormatter.error(`Build #${options.build} not found`)
|
|
299
|
+
return
|
|
300
|
+
}
|
|
301
|
+
} else {
|
|
302
|
+
targetBuild = builds[0]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
OutputFormatter.info(`Viewing logs for build #${targetBuild.buildNumber} (ID: ${targetBuild.id})`)
|
|
306
|
+
|
|
307
|
+
if (options.follow) {
|
|
308
|
+
OutputFormatter.info('Following logs... (Ctrl+C to exit)')
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const result = await client.getBuildLogs(targetBuild.id, options.follow)
|
|
312
|
+
|
|
313
|
+
if (!result.success) {
|
|
314
|
+
OutputFormatter.error(result.error!.message)
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
console.log(result.data?.logs || 'No logs available')
|
|
319
|
+
})
|
|
320
|
+
|
|
268
321
|
return cmd
|
|
269
322
|
}
|
package/src/commands/deploy.ts
CHANGED
|
@@ -15,9 +15,72 @@ function parseNamespaceName(input: string): { namespace: string; name: string }
|
|
|
15
15
|
return { namespace: parts[0], name: parts[1] }
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function getAge(timestamp?: string): string {
|
|
19
|
+
if (!timestamp) return '-'
|
|
20
|
+
const created = new Date(timestamp)
|
|
21
|
+
const now = new Date()
|
|
22
|
+
const diff = Math.floor((now.getTime() - created.getTime()) / 1000)
|
|
23
|
+
if (diff < 60) return `${diff}s`
|
|
24
|
+
if (diff < 3600) return `${Math.floor(diff / 60)}m`
|
|
25
|
+
if (diff < 86400) return `${Math.floor(diff / 3600)}h`
|
|
26
|
+
return `${Math.floor(diff / 86400)}d`
|
|
27
|
+
}
|
|
28
|
+
|
|
18
29
|
export function makeDeployCommand(): Command {
|
|
19
30
|
const cmd = new Command('deploy')
|
|
20
|
-
.description('Deploy services to environment
|
|
31
|
+
.description('Deploy services to environment')
|
|
32
|
+
|
|
33
|
+
cmd
|
|
34
|
+
.command('history <ns-name>')
|
|
35
|
+
.description('Show deployment history for a service')
|
|
36
|
+
.action(async (nsName) => {
|
|
37
|
+
const conn = configStore.getDefaultConnection()
|
|
38
|
+
if (!conn) {
|
|
39
|
+
OutputFormatter.error('No connection configured')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const parsed = parseNamespaceName(nsName)
|
|
44
|
+
if (!parsed) {
|
|
45
|
+
OutputFormatter.error('Invalid format. Use: <namespace>/<service-name>')
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const { namespace, name: serviceName } = parsed
|
|
50
|
+
|
|
51
|
+
const client = createClient(conn)
|
|
52
|
+
const result = await client.listServiceDeployments(namespace, serviceName)
|
|
53
|
+
|
|
54
|
+
if (!result.success) {
|
|
55
|
+
OutputFormatter.error(result.error!.message)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const data = result.data as any
|
|
60
|
+
const deployments = data?.deployments || data || []
|
|
61
|
+
|
|
62
|
+
if (deployments.length === 0) {
|
|
63
|
+
OutputFormatter.info('No deployments found')
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
OutputFormatter.table(
|
|
68
|
+
['ID', 'Image', 'Status', 'Build', 'Created'],
|
|
69
|
+
deployments.map((d: any) => [
|
|
70
|
+
d.id.substring(0, 8),
|
|
71
|
+
d.image?.substring(0, 40) || '-',
|
|
72
|
+
d.status || '-',
|
|
73
|
+
d.build?.buildNumber ? `#${d.build.buildNumber}` : '-',
|
|
74
|
+
getAge(d.createdAt)
|
|
75
|
+
])
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (data?.pagination) {
|
|
79
|
+
OutputFormatter.info(`Page ${data.pagination.page}/${data.pagination.totalPages}, Total: ${data.pagination.total}`)
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
cmd
|
|
21
84
|
.argument('<ns-name>', 'Target namespace and service name (format: namespace/service-name)')
|
|
22
85
|
.requiredOption('-i, --image <image>', 'Container image (required)')
|
|
23
86
|
.option('-p, --project <code>', 'Project code')
|
|
@@ -48,7 +111,6 @@ export function makeDeployCommand(): Command {
|
|
|
48
111
|
|
|
49
112
|
const client = createClient(conn)
|
|
50
113
|
|
|
51
|
-
// 使用 CLI API 进行部署
|
|
52
114
|
const result = await client.deployService(
|
|
53
115
|
namespace,
|
|
54
116
|
serviceName,
|
package/src/commands/svc.ts
CHANGED
|
@@ -323,6 +323,46 @@ export function makeSvcCommand(): Command {
|
|
|
323
323
|
)
|
|
324
324
|
})
|
|
325
325
|
|
|
326
|
+
cmd
|
|
327
|
+
.command('update <ns>/<name>')
|
|
328
|
+
.description('Update service configuration')
|
|
329
|
+
.option('-i, --image <image>', 'Container image')
|
|
330
|
+
.option('-r, --replicas <num>', 'Number of replicas')
|
|
331
|
+
.option('--port <port>', 'Container port')
|
|
332
|
+
.action(async (nsName, options) => {
|
|
333
|
+
const parsed = parseNamespaceName(nsName)
|
|
334
|
+
if (!parsed) {
|
|
335
|
+
OutputFormatter.error('Invalid format. Use: <namespace>/<service-name>')
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const conn = configStore.getDefaultConnection()
|
|
340
|
+
if (!conn) {
|
|
341
|
+
OutputFormatter.error('No connection configured')
|
|
342
|
+
return
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const updateData: any = {}
|
|
346
|
+
if (options.image) updateData.image = options.image
|
|
347
|
+
if (options.replicas) updateData.replicas = parseInt(options.replicas)
|
|
348
|
+
if (options.port) updateData.port = parseInt(options.port)
|
|
349
|
+
|
|
350
|
+
if (Object.keys(updateData).length === 0) {
|
|
351
|
+
OutputFormatter.error('No update options provided. Use --image, --replicas, or --port')
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const client = createClient(conn)
|
|
356
|
+
const result = await client.updateK8sService(parsed.namespace, parsed.name, updateData)
|
|
357
|
+
|
|
358
|
+
if (!result.success) {
|
|
359
|
+
OutputFormatter.error(result.error!.message)
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
OutputFormatter.success(`Service "${nsName}" updated`)
|
|
364
|
+
})
|
|
365
|
+
|
|
326
366
|
cmd
|
|
327
367
|
.command('delete <ns>/<name>')
|
|
328
368
|
.alias('rm')
|