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
@@ -0,0 +1,90 @@
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
+ import { DeployOptions } from '../config/types'
10
+
11
+ export function makeDeployCommand(): Command {
12
+ const cmd = new Command('deploy')
13
+ .description('Deploy services to environment')
14
+ .argument('<namespace>', 'Target namespace')
15
+ .argument('<service-name>', 'Service name')
16
+ .option('-t, --type <type>', 'Service type (application|database|image)', 'image')
17
+ .option('--git <url>', 'Git repository URL')
18
+ .option('--git-branch <branch>', 'Git branch', 'main')
19
+ .option('--build-type <type>', 'Build type (template|dockerfile)', 'template')
20
+ .option('--language <lang>', 'Language (java-springboot|nodejs|python|golang)')
21
+ .option('--dockerfile-path <path>', 'Dockerfile path')
22
+ .option('--db-type <type>', 'Database type (mysql|redis|postgres|elasticsearch)')
23
+ .option('--db-version <version>', 'Database version')
24
+ .option('--root-password <password>', 'Root password')
25
+ .option('--password <password>', 'Password')
26
+ .option('--user <user>', 'Username')
27
+ .option('--database <name>', 'Database name')
28
+ .option('--image <image>', 'Container image')
29
+ .option('-p, --port <port>', 'Container port')
30
+ .option('-r, --replicas <num>', 'Number of replicas', '1')
31
+ .option('--cpu <value>', 'CPU limit')
32
+ .option('--memory <value>', 'Memory limit')
33
+ .option('--domain <prefix>', 'Domain prefix')
34
+ .option('-e, --env <key=value>', 'Environment variables', (val, memo: Record<string, string> = {}) => {
35
+ const [key, value] = val.split('=')
36
+ memo[key] = value
37
+ return memo
38
+ }, {})
39
+ .action(async (namespace, serviceName, options) => {
40
+ const conn = configStore.getDefaultConnection()
41
+ if (!conn) {
42
+ OutputFormatter.error('No connection configured')
43
+ return
44
+ }
45
+
46
+ const type = options.type as 'application' | 'database' | 'image'
47
+
48
+ const deployOptions: DeployOptions = {
49
+ namespace,
50
+ serviceName,
51
+ type,
52
+ port: options.port ? parseInt(options.port) : undefined,
53
+ replicas: options.replicas ? parseInt(options.replicas) : undefined,
54
+ cpu: options.cpu,
55
+ memory: options.memory,
56
+ domain: options.domain,
57
+ envVars: options.env
58
+ }
59
+
60
+ // 根据类型添加配置
61
+ if (type === 'application') {
62
+ deployOptions.git = options.git
63
+ deployOptions.gitBranch = options.gitBranch
64
+ deployOptions.buildType = options.buildType
65
+ deployOptions.language = options.language
66
+ deployOptions.dockerfilePath = options.dockerfilePath
67
+ } else if (type === 'database') {
68
+ deployOptions.dbType = options.dbType
69
+ deployOptions.dbVersion = options.dbVersion
70
+ deployOptions.rootPassword = options.rootPassword
71
+ deployOptions.password = options.password
72
+ deployOptions.user = options.user
73
+ deployOptions.database = options.database
74
+ } else if (type === 'image') {
75
+ deployOptions.image = options.image
76
+ }
77
+
78
+ const client = createClient(conn)
79
+ const result = await client.deploy(deployOptions)
80
+
81
+ if (!result.success) {
82
+ OutputFormatter.error(result.error!.message)
83
+ return
84
+ }
85
+
86
+ OutputFormatter.success(`Service "${serviceName}" deployed to ${namespace}`)
87
+ })
88
+
89
+ return cmd
90
+ }
@@ -0,0 +1,144 @@
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
+ export function makeEnvCommand(): Command {
11
+ const cmd = new Command('env')
12
+ .description('Manage environments (K8s namespaces)')
13
+
14
+ cmd
15
+ .command('ls')
16
+ .description('List accessible environments')
17
+ .action(async () => {
18
+ const conn = configStore.getDefaultConnection()
19
+ if (!conn) {
20
+ OutputFormatter.error('No connection configured. Run: xuanwu connect add <name> -e <endpoint> -t <token>')
21
+ return
22
+ }
23
+
24
+ const client = createClient(conn)
25
+ const result = await client.listNamespaces()
26
+
27
+ if (!result.success) {
28
+ OutputFormatter.error(result.error!.message)
29
+ return
30
+ }
31
+
32
+ const spaces = result.data || []
33
+ if (spaces.length === 0) {
34
+ OutputFormatter.info('No environments found')
35
+ return
36
+ }
37
+
38
+ OutputFormatter.table(
39
+ ['Name', 'Identifier', 'Environment', 'Status'],
40
+ spaces.map((s: any) => [
41
+ s.name || s.identifier,
42
+ s.identifier,
43
+ s.environment,
44
+ s.status || 'active'
45
+ ])
46
+ )
47
+ })
48
+
49
+ cmd
50
+ .command('create <namespace>')
51
+ .description('Create a new environment (K8s namespace)')
52
+ .option('-p, --project-id <id>', 'Project ID')
53
+ .option('-e, --environment <env>', 'Environment type (development|staging|production)', 'development')
54
+ .action(async (namespace, options) => {
55
+ const conn = configStore.getDefaultConnection()
56
+ if (!conn) {
57
+ OutputFormatter.error('No connection configured')
58
+ return
59
+ }
60
+
61
+ const client = createClient(conn)
62
+
63
+ let projectId = options.projectId
64
+
65
+ // 如果没有指定 project-id,尝试获取第一个项目
66
+ if (!projectId) {
67
+ const projectsResult = await client.getProjects()
68
+ if (!projectsResult.success || !projectsResult.data || projectsResult.data.length === 0) {
69
+ OutputFormatter.error('No projects found. Please specify --project-id')
70
+ return
71
+ }
72
+ projectId = projectsResult.data[0].id
73
+ OutputFormatter.info(`Using project: ${projectsResult.data[0].name}`)
74
+ }
75
+
76
+ const result = await client.createNamespaceWithProject(namespace, projectId, options.environment)
77
+
78
+ if (!result.success) {
79
+ OutputFormatter.error(result.error!.message)
80
+ return
81
+ }
82
+
83
+ OutputFormatter.success(`Environment "${namespace}" created`)
84
+ })
85
+
86
+ cmd
87
+ .command('rm <namespace>')
88
+ .description('Delete an environment')
89
+ .action(async (namespace) => {
90
+ const conn = configStore.getDefaultConnection()
91
+ if (!conn) {
92
+ OutputFormatter.error('No connection configured')
93
+ return
94
+ }
95
+
96
+ const client = createClient(conn)
97
+
98
+ // 先获取 namespace ID
99
+ const infoResult = await client.getNamespaceInfo(namespace)
100
+ if (!infoResult.success || !infoResult.data) {
101
+ OutputFormatter.error(`Environment "${namespace}" not found`)
102
+ return
103
+ }
104
+
105
+ const envData = Array.isArray(infoResult.data) ? infoResult.data[0] : infoResult.data
106
+ const id = envData.id
107
+ const result = await client.deleteNamespace(id)
108
+
109
+ if (!result.success) {
110
+ OutputFormatter.error(result.error!.message)
111
+ return
112
+ }
113
+
114
+ OutputFormatter.success(`Environment "${namespace}" deleted`)
115
+ })
116
+
117
+ cmd
118
+ .command('info <namespace>')
119
+ .description('Show environment details')
120
+ .action(async (namespace) => {
121
+ const conn = configStore.getDefaultConnection()
122
+ if (!conn) {
123
+ OutputFormatter.error('No connection configured')
124
+ return
125
+ }
126
+
127
+ const client = createClient(conn)
128
+ const result = await client.getNamespaceInfo(namespace)
129
+
130
+ if (!result.success) {
131
+ OutputFormatter.error(result.error!.message)
132
+ return
133
+ }
134
+
135
+ const info = result.data
136
+ OutputFormatter.info(`Name: ${info.name}`)
137
+ OutputFormatter.info(`Identifier: ${info.identifier}`)
138
+ OutputFormatter.info(`Namespace: ${info.namespace}`)
139
+ OutputFormatter.info(`Environment: ${info.environment}`)
140
+ OutputFormatter.info(`Status: ${info.status}`)
141
+ })
142
+
143
+ return cmd
144
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 日志命令
3
+ */
4
+
5
+ import { Command } from 'commander'
6
+ import { configStore } from '../config/store'
7
+ import { createClient, APIClient } from '../api/client'
8
+ import { OutputFormatter } from '../output/formatter'
9
+
10
+ export function makeLogsCommand(): Command {
11
+ const cmd = new Command('logs')
12
+ .description('View service logs')
13
+ .argument('<namespace>', 'Target namespace')
14
+ .argument('<service-name>', 'Service name')
15
+ .option('-n, --lines <num>', 'Number of lines', '100')
16
+ .option('-f, --follow', 'Follow logs in real-time (SSE)')
17
+ .action(async (namespace, serviceName, options) => {
18
+ const conn = configStore.getDefaultConnection()
19
+ if (!conn) {
20
+ OutputFormatter.error('No connection configured')
21
+ return
22
+ }
23
+
24
+ const client = createClient(conn)
25
+
26
+ if (options.follow) {
27
+ // 使用 SSE 实时日志
28
+ await client.streamLogs(namespace, serviceName)
29
+ } else {
30
+ const result = await client.getLogs(
31
+ namespace,
32
+ serviceName,
33
+ parseInt(options.lines),
34
+ false
35
+ )
36
+
37
+ if (!result.success) {
38
+ OutputFormatter.error(result.error!.message)
39
+ return
40
+ }
41
+
42
+ console.log(result.data?.logs || result.data || '')
43
+ }
44
+ })
45
+
46
+ return cmd
47
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Pods 命令
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 makePodsCommand(): Command {
22
+ const cmd = new Command('pods')
23
+ .description('List pods')
24
+ .argument('<namespace>', 'Target namespace')
25
+ .argument('<service-name>', 'Service name')
26
+ .action(async (namespace, serviceName) => {
27
+ const conn = configStore.getDefaultConnection()
28
+ if (!conn) {
29
+ OutputFormatter.error('No connection configured')
30
+ return
31
+ }
32
+
33
+ const client = createClient(conn)
34
+ const result = await client.listPods(namespace, serviceName)
35
+
36
+ if (!result.success) {
37
+ OutputFormatter.error(result.error!.message)
38
+ return
39
+ }
40
+
41
+ const pods = Array.isArray(result.data) ? result.data : (result.data?.items || [])
42
+ if (pods.length === 0) {
43
+ OutputFormatter.info('No pods found')
44
+ return
45
+ }
46
+
47
+ OutputFormatter.table(
48
+ ['Name', 'Status', 'Ready', 'Restarts', 'Age'],
49
+ pods.map((p: any) => [
50
+ p.metadata?.name || 'unknown',
51
+ p.status?.phase || 'Unknown',
52
+ `${p.status?.containerStatuses?.filter((c: any) => c.ready).length || 0}/${p.spec?.containers?.length || 0}`,
53
+ p.status?.containerStatuses?.[0]?.restartCount?.toString() || '0',
54
+ getAge(p.metadata?.creationTimestamp)
55
+ ])
56
+ )
57
+ })
58
+
59
+ return cmd
60
+ }
@@ -0,0 +1,35 @@
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
+ export function makeScaleCommand(): Command {
11
+ const cmd = new Command('scale')
12
+ .description('Scale services')
13
+ .argument('<namespace>', 'Target namespace')
14
+ .argument('<service-name>', 'Service name')
15
+ .requiredOption('-r, --replicas <num>', 'Number of replicas')
16
+ .action(async (namespace, serviceName, options) => {
17
+ const conn = configStore.getDefaultConnection()
18
+ if (!conn) {
19
+ OutputFormatter.error('No connection configured')
20
+ return
21
+ }
22
+
23
+ const client = createClient(conn)
24
+ const result = await client.scale(namespace, serviceName, parseInt(options.replicas))
25
+
26
+ if (!result.success) {
27
+ OutputFormatter.error(result.error!.message)
28
+ return
29
+ }
30
+
31
+ OutputFormatter.success(`Scaled to ${options.replicas} replicas`)
32
+ })
33
+
34
+ return cmd
35
+ }
@@ -0,0 +1,114 @@
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 makeSvcCommand(): Command {
22
+ const cmd = new Command('svc')
23
+ .description('Manage services')
24
+
25
+ cmd
26
+ .command('ls <namespace>')
27
+ .description('List services in namespace')
28
+ .action(async (namespace) => {
29
+ const conn = configStore.getDefaultConnection()
30
+ if (!conn) {
31
+ OutputFormatter.error('No connection configured')
32
+ return
33
+ }
34
+
35
+ const client = createClient(conn)
36
+ const result = await client.listServices(namespace)
37
+
38
+ if (!result.success) {
39
+ OutputFormatter.error(result.error!.message)
40
+ return
41
+ }
42
+
43
+ const services = result.data || []
44
+ if (services.length === 0) {
45
+ OutputFormatter.info(`No services in ${namespace}`)
46
+ return
47
+ }
48
+
49
+ OutputFormatter.table(
50
+ ['Name', 'Ready', 'Up-to-date', 'Available', 'Age'],
51
+ services.map((s: any) => [
52
+ s.metadata?.name || 'unknown',
53
+ `${s.status?.readyReplicas || 0}/${s.status?.replicas || 0}`,
54
+ s.status?.updatedReplicas || 0,
55
+ s.status?.availableReplicas || 0,
56
+ getAge(s.metadata?.creationTimestamp)
57
+ ])
58
+ )
59
+ })
60
+
61
+ cmd
62
+ .command('status <namespace> <service-name>')
63
+ .description('Get service status')
64
+ .action(async (namespace, serviceName) => {
65
+ const conn = configStore.getDefaultConnection()
66
+ if (!conn) {
67
+ OutputFormatter.error('No connection configured')
68
+ return
69
+ }
70
+
71
+ const client = createClient(conn)
72
+ const result = await client.getServiceStatus(namespace, serviceName)
73
+
74
+ if (!result.success) {
75
+ OutputFormatter.error(result.error!.message)
76
+ return
77
+ }
78
+
79
+ const info = result.data
80
+ OutputFormatter.info(`Service: ${info.name}`)
81
+ OutputFormatter.info(`Namespace: ${info.namespace}`)
82
+ OutputFormatter.info(`Replicas: ${info.replicas}`)
83
+ OutputFormatter.info(`Ready Replicas: ${info.readyReplicas}`)
84
+ OutputFormatter.info(`Available Replicas: ${info.availableReplicas}`)
85
+ OutputFormatter.info(`Image: ${info.image}`)
86
+
87
+ if (info.labels) {
88
+ OutputFormatter.info(`Labels: ${JSON.stringify(info.labels)}`)
89
+ }
90
+ })
91
+
92
+ cmd
93
+ .command('rm <namespace> <service-name>')
94
+ .description('Delete a service')
95
+ .action(async (namespace, serviceName) => {
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.deleteService(namespace, serviceName)
104
+
105
+ if (!result.success) {
106
+ OutputFormatter.error(result.error!.message)
107
+ return
108
+ }
109
+
110
+ OutputFormatter.success(`Service "${serviceName}" deleted`)
111
+ })
112
+
113
+ return cmd
114
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * 配置存储
3
+ */
4
+
5
+ import * as fs from 'fs'
6
+ import * as path from 'path'
7
+ import * as os from 'os'
8
+ import { Config, Connection } from './types'
9
+
10
+ const CONFIG_DIR = path.join(os.homedir(), '.xuanwu')
11
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
12
+
13
+ const DEFAULT_CONFIG: Config = {
14
+ version: '1.0',
15
+ connections: []
16
+ }
17
+
18
+ export class ConfigStore {
19
+ private config: Config
20
+
21
+ constructor() {
22
+ this.config = this.load()
23
+ }
24
+
25
+ private load(): Config {
26
+ try {
27
+ if (fs.existsSync(CONFIG_FILE)) {
28
+ const data = fs.readFileSync(CONFIG_FILE, 'utf-8')
29
+ return JSON.parse(data)
30
+ }
31
+ } catch (error) {
32
+ console.error('Failed to load config:', error)
33
+ }
34
+ return { ...DEFAULT_CONFIG }
35
+ }
36
+
37
+ private save(): void {
38
+ try {
39
+ if (!fs.existsSync(CONFIG_DIR)) {
40
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
41
+ }
42
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(this.config, null, 2))
43
+ } catch (error) {
44
+ console.error('Failed to save config:', error)
45
+ throw error
46
+ }
47
+ }
48
+
49
+ getConnections(): Connection[] {
50
+ return this.config.connections
51
+ }
52
+
53
+ getConnection(name: string): Connection | undefined {
54
+ return this.config.connections.find(c => c.name === name)
55
+ }
56
+
57
+ getDefaultConnection(): Connection | undefined {
58
+ return this.config.connections.find(c => c.isDefault)
59
+ }
60
+
61
+ addConnection(connection: Connection): void {
62
+ // Remove existing with same name
63
+ this.config.connections = this.config.connections.filter(c => c.name !== connection.name)
64
+ this.config.connections.push(connection)
65
+ this.save()
66
+ }
67
+
68
+ removeConnection(name: string): void {
69
+ this.config.connections = this.config.connections.filter(c => c.name !== name)
70
+ this.save()
71
+ }
72
+
73
+ setDefaultConnection(name: string): void {
74
+ this.config.connections = this.config.connections.map(c => ({
75
+ ...c,
76
+ isDefault: c.name === name
77
+ }))
78
+ this.save()
79
+ }
80
+
81
+ getConfig(): Config {
82
+ return this.config
83
+ }
84
+ }
85
+
86
+ export const configStore = new ConfigStore()
@@ -0,0 +1,99 @@
1
+ /**
2
+ * xuanwu-cli 类型定义
3
+ */
4
+
5
+ export interface Connection {
6
+ name: string
7
+ endpoint: string
8
+ token: string
9
+ isDefault: boolean
10
+ }
11
+
12
+ export interface Config {
13
+ version: string
14
+ connections: Connection[]
15
+ }
16
+
17
+ export interface ServiceType {
18
+ type: 'application' | 'database' | 'image'
19
+ }
20
+
21
+ export interface ApplicationOptions extends ServiceType {
22
+ git?: string
23
+ gitBranch?: string
24
+ buildType?: 'template' | 'dockerfile'
25
+ language?: string
26
+ dockerfilePath?: string
27
+ }
28
+
29
+ export interface DatabaseOptions extends ServiceType {
30
+ dbType: 'mysql' | 'redis' | 'postgres' | 'elasticsearch'
31
+ dbVersion: string
32
+ rootPassword?: string
33
+ password?: string
34
+ user?: string
35
+ database?: string
36
+ }
37
+
38
+ export interface ImageOptions extends ServiceType {
39
+ image: string
40
+ }
41
+
42
+ export interface DeployOptions {
43
+ namespace: string
44
+ serviceName: string
45
+ type: 'application' | 'database' | 'image'
46
+ port?: number
47
+ replicas?: number
48
+ cpu?: string
49
+ memory?: string
50
+ domain?: string
51
+ envVars?: Record<string, string>
52
+ volume?: string
53
+ // application
54
+ git?: string
55
+ gitBranch?: string
56
+ buildType?: 'template' | 'dockerfile'
57
+ language?: string
58
+ dockerfilePath?: string
59
+ // database
60
+ dbType?: 'mysql' | 'redis' | 'postgres' | 'elasticsearch'
61
+ dbVersion?: string
62
+ rootPassword?: string
63
+ password?: string
64
+ user?: string
65
+ database?: string
66
+ // image
67
+ image?: string
68
+ }
69
+
70
+ export interface ServiceInfo {
71
+ name: string
72
+ type: string
73
+ status: string
74
+ ready: string
75
+ image: string
76
+ age: string
77
+ ports: number[]
78
+ endpoint?: string
79
+ }
80
+
81
+ export interface NamespaceInfo {
82
+ name: string
83
+ status: string
84
+ age: string
85
+ }
86
+
87
+ export interface CLIResult<T = any> {
88
+ success: boolean
89
+ data?: T
90
+ error?: {
91
+ code: string
92
+ message: string
93
+ details?: any
94
+ }
95
+ meta?: {
96
+ timestamp: string
97
+ duration?: number
98
+ }
99
+ }