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