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.
Files changed (45) hide show
  1. package/README.md +171 -0
  2. package/bin/xuanwu +20 -0
  3. package/dist/api/client.d.ts +30 -0
  4. package/dist/api/client.js +332 -0
  5. package/dist/commands/build.d.ts +5 -0
  6. package/dist/commands/build.js +57 -0
  7. package/dist/commands/connect.d.ts +5 -0
  8. package/dist/commands/connect.js +67 -0
  9. package/dist/commands/deploy.d.ts +5 -0
  10. package/dist/commands/deploy.js +85 -0
  11. package/dist/commands/env.d.ts +5 -0
  12. package/dist/commands/env.js +119 -0
  13. package/dist/commands/logs.d.ts +5 -0
  14. package/dist/commands/logs.js +39 -0
  15. package/dist/commands/pods.d.ts +5 -0
  16. package/dist/commands/pods.js +56 -0
  17. package/dist/commands/scale.d.ts +5 -0
  18. package/dist/commands/scale.js +32 -0
  19. package/dist/commands/svc.d.ts +5 -0
  20. package/dist/commands/svc.js +100 -0
  21. package/dist/config/store.d.ts +18 -0
  22. package/dist/config/store.js +108 -0
  23. package/dist/config/types.d.ts +86 -0
  24. package/dist/config/types.js +5 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +142 -0
  27. package/dist/output/formatter.d.ts +15 -0
  28. package/dist/output/formatter.js +95 -0
  29. package/docs/DESIGN.md +363 -0
  30. package/docs//345/276/205/344/274/230/345/214/226.md +89 -0
  31. package/package.json +31 -0
  32. package/src/api/client.ts +380 -0
  33. package/src/commands/build.ts +67 -0
  34. package/src/commands/connect.ts +75 -0
  35. package/src/commands/deploy.ts +90 -0
  36. package/src/commands/env.ts +144 -0
  37. package/src/commands/logs.ts +47 -0
  38. package/src/commands/pods.ts +60 -0
  39. package/src/commands/scale.ts +35 -0
  40. package/src/commands/svc.ts +114 -0
  41. package/src/config/store.ts +86 -0
  42. package/src/config/types.ts +99 -0
  43. package/src/index.ts +127 -0
  44. package/src/output/formatter.ts +112 -0
  45. 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
+ }