xuanwu-cli 1.0.0 → 2.1.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.
@@ -97,3 +97,108 @@ export interface CLIResult<T = any> {
97
97
  duration?: number
98
98
  }
99
99
  }
100
+
101
+ // ===================
102
+ // Application Types
103
+ // ===================
104
+
105
+ export interface Application {
106
+ id: string
107
+ code: string
108
+ name: string
109
+ gitRepo: string
110
+ gitBranch: string
111
+ buildPath: string
112
+ dockerfile: string
113
+ buildArgs?: Record<string, string>
114
+ imageName: string
115
+ jenkinsConfigId?: string
116
+ jenkinsJobName?: string
117
+ createdAt: string
118
+ updatedAt: string
119
+ }
120
+
121
+ export interface CreateApplicationDto {
122
+ name: string
123
+ code?: string
124
+ gitRepo: string
125
+ gitBranch?: string
126
+ imageName?: string
127
+ buildPath?: string
128
+ dockerfile?: string
129
+ buildArgs?: Record<string, string>
130
+ jenkinsConfigId?: string
131
+ jenkinsJobName?: string
132
+ }
133
+
134
+ export interface UpdateApplicationDto {
135
+ name?: string
136
+ gitRepo?: string
137
+ gitBranch?: string
138
+ imageName?: string
139
+ buildPath?: string
140
+ dockerfile?: string
141
+ buildArgs?: Record<string, string>
142
+ jenkinsConfigId?: string
143
+ jenkinsJobName?: string
144
+ }
145
+
146
+ // ===================
147
+ // Build Types
148
+ // ===================
149
+
150
+ export interface Build {
151
+ id: string
152
+ buildNumber: number
153
+ status: string
154
+ imageTag: string
155
+ commitSha?: string
156
+ commitMessage?: string
157
+ commitAuthor?: string
158
+ createdAt: string
159
+ completedAt?: string
160
+ }
161
+
162
+ export interface TriggerBuildOptions {
163
+ gitBranch?: string
164
+ commitSha?: string
165
+ commitMessage?: string
166
+ commitAuthor?: string
167
+ }
168
+
169
+ // ===================
170
+ // Service Types (K8s)
171
+ // ===================
172
+
173
+ export interface K8sService {
174
+ name: string
175
+ namespace: string
176
+ type: string
177
+ clusterIP: string
178
+ externalIP?: string
179
+ ports: { port: number; targetPort: number; protocol: string }[]
180
+ age: string
181
+ }
182
+
183
+ export interface K8sDeployment {
184
+ name: string
185
+ namespace: string
186
+ replicas: number
187
+ readyReplicas: number
188
+ availableReplicas: number
189
+ image: string
190
+ labels?: Record<string, string>
191
+ createdAt: string
192
+ }
193
+
194
+ export interface K8sPod {
195
+ name: string
196
+ namespace: string
197
+ status: string
198
+ ready: string
199
+ restarts: number
200
+ age: string
201
+ ip: string
202
+ node: string
203
+ containers: { name: string; image: string }[]
204
+ }
package/src/index.ts CHANGED
@@ -7,121 +7,37 @@ import { configStore } from './config/store'
7
7
  import { makeConnectCommand } from './commands/connect'
8
8
  import { makeEnvCommand } from './commands/env'
9
9
  import { makeDeployCommand } from './commands/deploy'
10
+ import { makeAppCommand } from './commands/app'
10
11
  import { makeSvcCommand } from './commands/svc'
11
12
  import { makeBuildCommand } from './commands/build'
12
13
  import { makeScaleCommand } from './commands/scale'
13
14
  import { makeLogsCommand } from './commands/logs'
14
15
  import { makePodsCommand } from './commands/pods'
16
+ import { makeLoginCommand } from './commands/auth/login'
17
+ import { makeLogoutCommand } from './commands/auth/logout'
18
+ import { makeWhoamiCommand } from './commands/auth/whoami'
19
+ import { makeTokensCommand } from './commands/auth/tokens'
15
20
 
16
21
  const program = new Command()
17
22
 
18
23
  program
19
- .name('xuanwu')
24
+ .name('xw')
20
25
  .description('玄武工厂平台 CLI 工具')
21
- .version('1.0.0')
26
+ .version('2.0.0')
22
27
  .option('-o, --output <format>', 'Output format (human|json)', 'human')
23
28
 
24
- // 添加子命令
25
29
  program.addCommand(makeConnectCommand())
26
30
  program.addCommand(makeEnvCommand())
27
- program.addCommand(makeDeployCommand())
31
+ program.addCommand(makeAppCommand())
28
32
  program.addCommand(makeSvcCommand())
29
33
  program.addCommand(makeBuildCommand())
34
+ program.addCommand(makeDeployCommand())
30
35
  program.addCommand(makeScaleCommand())
31
36
  program.addCommand(makeLogsCommand())
32
37
  program.addCommand(makePodsCommand())
38
+ program.addCommand(makeLoginCommand())
39
+ program.addCommand(makeLogoutCommand())
40
+ program.addCommand(makeWhoamiCommand())
41
+ program.addCommand(makeTokensCommand())
33
42
 
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
43
  program.parse(process.argv)
@@ -0,0 +1,41 @@
1
+ import { SessionManager } from './session'
2
+
3
+ export interface ApiOptions {
4
+ method?: string
5
+ body?: any
6
+ headers?: Record<string, string>
7
+ }
8
+
9
+ export async function authenticatedFetch(
10
+ url: string,
11
+ options: ApiOptions = {}
12
+ ): Promise<Response> {
13
+ const sessionManager = new SessionManager()
14
+ const session = await sessionManager.loadSession()
15
+
16
+ if (!session) {
17
+ throw new Error('Not logged in. Please run: xw login')
18
+ }
19
+
20
+ if (sessionManager.isExpired(session)) {
21
+ await sessionManager.clearSession()
22
+ throw new Error('Session expired. Please run: xw login')
23
+ }
24
+
25
+ const response = await fetch(url, {
26
+ method: options.method || 'GET',
27
+ headers: {
28
+ 'Authorization': `Bearer ${session.token}`,
29
+ 'Content-Type': 'application/json',
30
+ ...options.headers
31
+ },
32
+ body: options.body ? JSON.stringify(options.body) : undefined
33
+ })
34
+
35
+ if (response.status === 401) {
36
+ await sessionManager.clearSession()
37
+ throw new Error('Authentication failed. Please run: xw login')
38
+ }
39
+
40
+ return response
41
+ }
@@ -0,0 +1,50 @@
1
+ import * as fs from 'fs/promises'
2
+ import * as path from 'path'
3
+ import * as os from 'os'
4
+
5
+ export interface Session {
6
+ token: string
7
+ userId: string
8
+ userName: string
9
+ userEmail: string
10
+ userRole: string
11
+ deviceId: string
12
+ expiresAt: string | null
13
+ apiUrl: string
14
+ }
15
+
16
+ export class SessionManager {
17
+ private sessionPath: string
18
+
19
+ constructor() {
20
+ this.sessionPath = path.join(os.homedir(), '.xw', 'session.json')
21
+ }
22
+
23
+ async saveSession(session: Session): Promise<void> {
24
+ const dir = path.dirname(this.sessionPath)
25
+ await fs.mkdir(dir, { recursive: true })
26
+ await fs.writeFile(this.sessionPath, JSON.stringify(session, null, 2))
27
+ }
28
+
29
+ async loadSession(): Promise<Session | null> {
30
+ try {
31
+ const content = await fs.readFile(this.sessionPath, 'utf-8')
32
+ return JSON.parse(content)
33
+ } catch {
34
+ return null
35
+ }
36
+ }
37
+
38
+ async clearSession(): Promise<void> {
39
+ try {
40
+ await fs.unlink(this.sessionPath)
41
+ } catch {
42
+ // Ignore errors
43
+ }
44
+ }
45
+
46
+ isExpired(session: Session): boolean {
47
+ if (!session.expiresAt) return false
48
+ return new Date(session.expiresAt) < new Date()
49
+ }
50
+ }
package/test/REPORT.md ADDED
@@ -0,0 +1,78 @@
1
+ # 集成测试报告 - CLI API v2.0
2
+
3
+ > 测试时间: 2026-03-11 06:13:57
4
+ > 测试环境: localhost:3000
5
+ > 测试用户: test@cli.local (ADMIN)
6
+
7
+ ## 执行摘要
8
+
9
+ | 指标 | 数值 |
10
+ |------|------|
11
+ | 总测试数 | 17 |
12
+ | 通过 | 17 ✅ |
13
+ | 失败 | 0 ❌ |
14
+ | **通过率** | **100.0%** |
15
+
16
+ ## 测试结果详情
17
+
18
+ ### Application API (使用 code 标识)
19
+
20
+ | # | 测试项 | 状态 | 说明 |
21
+ |---|--------|------|------|
22
+ | 1 | listApplications | ✅ PASS | 列出所有应用 |
23
+ | 2 | createApplication | ✅ PASS | 创建应用 (code: xw-cli-test-app) |
24
+ | 3 | getApplication | ✅ PASS | 获取应用详情 |
25
+ | 4 | updateApplication | ✅ PASS | 更新应用 |
26
+ | 5 | triggerApplicationBuild | ✅ PASS | 触发构建 (buildNumber: 1, status: QUEUED) |
27
+ | 6 | listApplicationBuilds | ✅ PASS | 列出应用构建 |
28
+ | 7 | listBuilds | ✅ PASS | 列出所有构建 |
29
+
30
+ ### Build API
31
+
32
+ | # | 测试项 | 状态 | 说明 |
33
+ |---|--------|------|------|
34
+ | 8 | getBuild | ✅ PASS | 获取构建详情 |
35
+
36
+ ### Service API (使用 ns/name 路径)
37
+
38
+ | # | 测试项 | 状态 | 说明 |
39
+ |---|--------|------|------|
40
+ | 9 | listK8sServices | ✅ PASS | 列出所有 K8s 服务 |
41
+ | 10 | listK8sServices (namespace) | ✅ PASS | 列出指定 namespace 服务 |
42
+ | 11 | getK8sService | ✅ PASS | 获取服务详情 (cli-test-fix/nginx) |
43
+ | 12 | getK8sServiceStatus | ✅ PASS | 获取部署状态 |
44
+ | 13 | getK8sServiceLogs | ✅ PASS | 获取服务日志 |
45
+ | 14 | listK8sServicePods | ✅ PASS | 列出 Pods |
46
+ | 15 | scaleK8sService | ✅ PASS | 扩缩容 (扩到 2 副本) |
47
+ | 16 | restartK8sService | ✅ PASS | 重启服务 |
48
+ | 17 | scaleK8sService (restore) | ✅ PASS | 恢复扩缩容 (缩到 1 副本) |
49
+
50
+ ## 测试数据
51
+
52
+ - **应用**: xw-cli-test-app
53
+ - **命名空间**: cli-test-fix
54
+ - **服务**: nginx (image: nginx:latest)
55
+
56
+ ## API 路由验证
57
+
58
+ 所有设计文档中定义的 CLI API 路由均已实现并正常工作:
59
+
60
+ - `GET /api/cli/apps` ✅
61
+ - `POST /api/applications` ✅
62
+ - `GET /api/cli/apps/:code` ✅
63
+ - `PUT /api/cli/apps/:code` ✅
64
+ - `POST /api/cli/apps/:code/build` ✅
65
+ - `GET /api/cli/apps/:code/builds` ✅
66
+ - `GET /api/cli/builds` ✅
67
+ - `GET /api/cli/builds/:id` ✅
68
+ - `GET /api/cli/services` ✅
69
+ - `GET /api/cli/services/:ns/:name` ✅
70
+ - `GET /api/cli/services/:ns/:name/status` ✅
71
+ - `GET /api/cli/services/:ns/:name/logs` ✅
72
+ - `POST /api/cli/services/:ns/:name/restart` ✅
73
+ - `POST /api/cli/services/:ns/:name/scale` ✅
74
+ - `GET /api/cli/services/:ns/:name/pods` ✅
75
+
76
+ ## 结论
77
+
78
+ CLI v2.0 重构成功!所有 17 个集成测试全部通过,API 功能完整可用。