xuanwu-cli 1.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 +171 -0
- package/bin/xuanwu +20 -0
- package/dist/api/client.d.ts +30 -0
- package/dist/api/client.js +332 -0
- package/dist/commands/build.d.ts +5 -0
- package/dist/commands/build.js +57 -0
- package/dist/commands/connect.d.ts +5 -0
- package/dist/commands/connect.js +67 -0
- package/dist/commands/deploy.d.ts +5 -0
- package/dist/commands/deploy.js +85 -0
- package/dist/commands/env.d.ts +5 -0
- package/dist/commands/env.js +119 -0
- package/dist/commands/logs.d.ts +5 -0
- package/dist/commands/logs.js +39 -0
- package/dist/commands/pods.d.ts +5 -0
- package/dist/commands/pods.js +56 -0
- package/dist/commands/scale.d.ts +5 -0
- package/dist/commands/scale.js +32 -0
- package/dist/commands/svc.d.ts +5 -0
- package/dist/commands/svc.js +100 -0
- package/dist/config/store.d.ts +18 -0
- package/dist/config/store.js +108 -0
- package/dist/config/types.d.ts +86 -0
- package/dist/config/types.js +5 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +142 -0
- package/dist/output/formatter.d.ts +15 -0
- package/dist/output/formatter.js +95 -0
- package/docs/DESIGN.md +363 -0
- package/docs//345/276/205/344/274/230/345/214/226.md +89 -0
- package/package.json +31 -0
- package/src/api/client.ts +380 -0
- package/src/commands/build.ts +67 -0
- package/src/commands/connect.ts +75 -0
- package/src/commands/deploy.ts +90 -0
- package/src/commands/env.ts +144 -0
- package/src/commands/logs.ts +47 -0
- package/src/commands/pods.ts +60 -0
- package/src/commands/scale.ts +35 -0
- package/src/commands/svc.ts +114 -0
- package/src/config/store.ts +86 -0
- package/src/config/types.ts +99 -0
- package/src/index.ts +127 -0
- package/src/output/formatter.ts +112 -0
- package/tsconfig.json +17 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* xuanwu-cli 主入口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander'
|
|
6
|
+
import { configStore } from './config/store'
|
|
7
|
+
import { makeConnectCommand } from './commands/connect'
|
|
8
|
+
import { makeEnvCommand } from './commands/env'
|
|
9
|
+
import { makeDeployCommand } from './commands/deploy'
|
|
10
|
+
import { makeSvcCommand } from './commands/svc'
|
|
11
|
+
import { makeBuildCommand } from './commands/build'
|
|
12
|
+
import { makeScaleCommand } from './commands/scale'
|
|
13
|
+
import { makeLogsCommand } from './commands/logs'
|
|
14
|
+
import { makePodsCommand } from './commands/pods'
|
|
15
|
+
|
|
16
|
+
const program = new Command()
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.name('xuanwu')
|
|
20
|
+
.description('玄武工厂平台 CLI 工具')
|
|
21
|
+
.version('1.0.0')
|
|
22
|
+
.option('-o, --output <format>', 'Output format (human|json)', 'human')
|
|
23
|
+
|
|
24
|
+
// 添加子命令
|
|
25
|
+
program.addCommand(makeConnectCommand())
|
|
26
|
+
program.addCommand(makeEnvCommand())
|
|
27
|
+
program.addCommand(makeDeployCommand())
|
|
28
|
+
program.addCommand(makeSvcCommand())
|
|
29
|
+
program.addCommand(makeBuildCommand())
|
|
30
|
+
program.addCommand(makeScaleCommand())
|
|
31
|
+
program.addCommand(makeLogsCommand())
|
|
32
|
+
program.addCommand(makePodsCommand())
|
|
33
|
+
|
|
34
|
+
// 快捷命令:直接 deploy 也支持
|
|
35
|
+
program
|
|
36
|
+
.command('up <namespace> <service-name>')
|
|
37
|
+
.description('Build and deploy a service')
|
|
38
|
+
.action(async (namespace, serviceName) => {
|
|
39
|
+
const { makeBuildCommand } = await import('./commands/build')
|
|
40
|
+
const buildCmd = makeBuildCommand()
|
|
41
|
+
// 简化:只调用部署
|
|
42
|
+
console.log('Use: xuanwu build <namespace> <service-name> && xuanwu deploy <namespace> <service-name>')
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// restart 命令
|
|
46
|
+
program
|
|
47
|
+
.command('restart <namespace> <service-name>')
|
|
48
|
+
.description('Restart a service')
|
|
49
|
+
.action(async (namespace, serviceName) => {
|
|
50
|
+
console.log('Restarting service... (not implemented)')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// exec 命令
|
|
54
|
+
program
|
|
55
|
+
.command('exec <namespace> <service-name>')
|
|
56
|
+
.description('Execute command in container')
|
|
57
|
+
.option('-c, --command <cmd>', 'Command to execute', 'ls -la')
|
|
58
|
+
.option('-p, --pod <pod-name>', 'Specific pod name')
|
|
59
|
+
.action(async (namespace, serviceName, options) => {
|
|
60
|
+
const { configStore } = await import('./config/store')
|
|
61
|
+
const { createClient } = await import('./api/client')
|
|
62
|
+
const { OutputFormatter } = await import('./output/formatter')
|
|
63
|
+
|
|
64
|
+
const conn = configStore.getDefaultConnection()
|
|
65
|
+
if (!conn) {
|
|
66
|
+
OutputFormatter.error('No connection configured')
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const client = createClient(conn)
|
|
71
|
+
const result = await client.exec(namespace, serviceName, options.command, options.pod)
|
|
72
|
+
|
|
73
|
+
if (!result.success) {
|
|
74
|
+
OutputFormatter.error(result.error!.message)
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const data = result.data
|
|
79
|
+
if (data.stdout) console.log(data.stdout)
|
|
80
|
+
if (data.stderr) console.error(data.stderr)
|
|
81
|
+
if (data.exitCode !== 0) {
|
|
82
|
+
process.exit(data.exitCode)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// top 命令
|
|
87
|
+
program
|
|
88
|
+
.command('top <namespace> <service-name>')
|
|
89
|
+
.description('Show resource usage')
|
|
90
|
+
.option('-p, --pod <pod-name>', 'Specific pod name')
|
|
91
|
+
.action(async (namespace, serviceName, options) => {
|
|
92
|
+
const { configStore } = await import('./config/store')
|
|
93
|
+
const { createClient } = await import('./api/client')
|
|
94
|
+
const { OutputFormatter } = await import('./output/formatter')
|
|
95
|
+
|
|
96
|
+
const conn = configStore.getDefaultConnection()
|
|
97
|
+
if (!conn) {
|
|
98
|
+
OutputFormatter.error('No connection configured')
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const client = createClient(conn)
|
|
103
|
+
const result = await client.getMetrics(namespace, serviceName, options.pod)
|
|
104
|
+
|
|
105
|
+
if (!result.success) {
|
|
106
|
+
OutputFormatter.error(result.error!.message)
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const pods = result.data?.pods || []
|
|
111
|
+
if (pods.length === 0) {
|
|
112
|
+
OutputFormatter.info('No pod metrics found')
|
|
113
|
+
return
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
OutputFormatter.table(
|
|
117
|
+
['Pod Name', 'CPU', 'Memory'],
|
|
118
|
+
pods.map((p: any) => [
|
|
119
|
+
p.name,
|
|
120
|
+
p.containers?.[0]?.cpuRaw || 'N/A',
|
|
121
|
+
p.containers?.[0]?.memoryRaw || 'N/A'
|
|
122
|
+
])
|
|
123
|
+
)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
// 解析参数
|
|
127
|
+
program.parse(process.argv)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 输出格式化
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CLIResult, ServiceInfo, NamespaceInfo } from '../config/types'
|
|
6
|
+
|
|
7
|
+
export type OutputFormat = 'human' | 'json'
|
|
8
|
+
|
|
9
|
+
export class OutputFormatter {
|
|
10
|
+
static format<T>(result: CLIResult<T>, format: OutputFormat = 'human'): string {
|
|
11
|
+
if (format === 'json') {
|
|
12
|
+
return JSON.stringify(result, null, 2)
|
|
13
|
+
}
|
|
14
|
+
return this.formatHuman(result)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private static formatHuman(result: CLIResult): string {
|
|
18
|
+
if (!result.success) {
|
|
19
|
+
const error = result.error!
|
|
20
|
+
return `✗ Error: ${error.message}`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const data = result.data
|
|
24
|
+
|
|
25
|
+
// 数组列表
|
|
26
|
+
if (Array.isArray(data)) {
|
|
27
|
+
if (data.length === 0) {
|
|
28
|
+
return '(empty)'
|
|
29
|
+
}
|
|
30
|
+
return data.map(item => this.formatItem(item)).join('\n')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 对象详情
|
|
34
|
+
if (typeof data === 'object' && data !== null) {
|
|
35
|
+
return this.formatObject(data)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return String(data)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
private static formatItem(item: any): string {
|
|
42
|
+
if (item.name) {
|
|
43
|
+
return `${item.name}`
|
|
44
|
+
}
|
|
45
|
+
return JSON.stringify(item)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private static formatObject(obj: any): string {
|
|
49
|
+
const lines: string[] = []
|
|
50
|
+
|
|
51
|
+
// Namespace 列表
|
|
52
|
+
if (obj.identifier !== undefined || obj.name !== undefined) {
|
|
53
|
+
lines.push(`Name: ${obj.identifier || obj.name}`)
|
|
54
|
+
if (obj.status) lines.push(`Status: ${obj.status}`)
|
|
55
|
+
if (obj.environment) lines.push(`Environment: ${obj.environment}`)
|
|
56
|
+
return lines.join('\n')
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Service 列表
|
|
60
|
+
if (Array.isArray(obj)) {
|
|
61
|
+
return obj.map((s: any) => {
|
|
62
|
+
let line = [
|
|
63
|
+
s.name,
|
|
64
|
+
s.type ? `(${s.type})` : '',
|
|
65
|
+
s.status ? `→ ${s.status}` : ''
|
|
66
|
+
].filter(Boolean).join(' ')
|
|
67
|
+
if (s.endpoint) line += `\n ${s.endpoint}`
|
|
68
|
+
return line
|
|
69
|
+
}).join('\n\n')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 单个对象
|
|
73
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
74
|
+
if (value !== undefined && value !== null) {
|
|
75
|
+
lines.push(`${key}: ${JSON.stringify(value)}`)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return lines.join('\n')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 打印成功消息
|
|
83
|
+
static success(message: string): void {
|
|
84
|
+
console.log(`✓ ${message}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 打印错误消息
|
|
88
|
+
static error(message: string): void {
|
|
89
|
+
console.error(`✗ ${message}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 打印信息
|
|
93
|
+
static info(message: string): void {
|
|
94
|
+
console.log(message)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 打印表格
|
|
98
|
+
static table(headers: string[], rows: string[][]): void {
|
|
99
|
+
const colWidths = headers.map((h, i) =>
|
|
100
|
+
Math.max(h.length, ...rows.map(r => String(r[i] || '').length))
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Header
|
|
104
|
+
console.log(headers.map((h, i) => h.padEnd(colWidths[i])).join(' '))
|
|
105
|
+
console.log(colWidths.map(w => '-'.repeat(w)).join(' '))
|
|
106
|
+
|
|
107
|
+
// Rows
|
|
108
|
+
for (const row of rows) {
|
|
109
|
+
console.log(row.map((c, i) => String(c || '').padEnd(colWidths[i])).join(' '))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"lib": ["ES2020"],
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"declaration": true
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|