maolike-cli 1.0.7 → 2.0.1
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 +239 -101
- package/dist/src/commands/project/CreateCommand.js +48 -93
- package/dist/src/commands/project/DeleteCommand.js +2 -4
- package/dist/src/commands/project/DeployCommand.js +71 -0
- package/dist/src/commands/project/ListCommand.js +6 -4
- package/dist/src/commands/project/TestCommand.js +13 -22
- package/dist/src/commands/project/UpdateCommand.js +17 -45
- package/dist/src/commands/project/ViewCommand.js +1 -2
- package/dist/src/commands/system/ConnectCommand.js +59 -0
- package/dist/src/commands/system/CursorCommand.js +29 -0
- package/dist/src/commands/system/HelpCommand.js +11 -5
- package/dist/src/commands/system/InitCommand.js +1 -1
- package/dist/src/commands/system/LoadCommand.js +71 -139
- package/dist/src/constants/CommandRegistry.js +19 -1
- package/dist/src/core/pipelines/BaseExecutor.js +135 -0
- package/dist/src/core/pipelines/ProdExecutor.js +119 -0
- package/dist/src/core/pipelines/TestExecutor.js +83 -0
- package/dist/src/index.js +1 -1
- package/dist/src/services/ConfigManager.js +116 -129
- package/dist/src/ui/InitWizard.js +40 -142
- package/dist/src/ui/InteractiveShell.js +27 -6
- package/dist/src/utils/ConfigPrompter.js +157 -224
- package/dist/src/utils/Executor.js +18 -0
- package/dist/src/utils/RemoteServer.js +112 -71
- package/package.json +1 -1
- package/dist/src/commands/system/MenuCommand.js +0 -182
- package/dist/src/core/PipelineExecutor.js +0 -333
|
@@ -1,152 +1,139 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var _a;
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.ConfigManager = void 0;
|
|
4
5
|
const tslib_1 = require("tslib");
|
|
5
6
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
6
7
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
7
8
|
const os_1 = tslib_1.__importDefault(require("os"));
|
|
8
|
-
const
|
|
9
|
+
const SCHEMA_VERSION = 1;
|
|
9
10
|
/**
|
|
10
|
-
*
|
|
11
|
+
* 分层部署配置管理器。
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* 2. 自动持久化:配置发生变动自动同步到磁盘,用户无需手动调用 save。
|
|
15
|
-
* 3. 拦截隔离:私有化持久化方法,确保只能通过数据驱动更新。
|
|
13
|
+
* 项目与服务器分别持久化,CLI 不读写 desktop/app.json,避免部署配置变更
|
|
14
|
+
* 覆盖桌面应用偏好。
|
|
16
15
|
*/
|
|
17
16
|
class ConfigManager {
|
|
18
|
-
/**
|
|
19
|
-
* 初始化配置系统
|
|
20
|
-
* @private
|
|
21
|
-
* @returns 响应式配置代理
|
|
22
|
-
*/
|
|
23
|
-
static initialize() {
|
|
24
|
-
const rawConfig = this.loadInitialConfig();
|
|
25
|
-
return this.createReactiveProxy(rawConfig);
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* 强制从磁盘重新加载配置
|
|
29
|
-
* 用于多进程环境下(如交互式 Shell 执行子命令后)同步最新状态
|
|
30
|
-
*/
|
|
17
|
+
/** 强制从磁盘重新加载,供交互式 Shell 的子命令执行后刷新数据。 */
|
|
31
18
|
static reload() {
|
|
32
|
-
this.
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* 加载初始配置数据
|
|
36
|
-
* 若磁盘无配置,则返回默认值;否则将磁盘配置与默认配置合并
|
|
37
|
-
* @private
|
|
38
|
-
*/
|
|
39
|
-
static loadInitialConfig() {
|
|
40
|
-
const diskConfig = this.loadFromDisk();
|
|
41
|
-
if (!diskConfig)
|
|
42
|
-
return Object.assign({}, this.DEFAULT_CONFIG);
|
|
43
|
-
this.hasFile = true;
|
|
44
|
-
return Object.assign(Object.assign(Object.assign({}, this.DEFAULT_CONFIG), diskConfig), { servers: diskConfig.servers || [], ssh: Object.assign(Object.assign({}, this.DEFAULT_CONFIG.ssh), (diskConfig.ssh || {})), "git-list": diskConfig["git-list"] || [] });
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* 从磁盘读取 JSON 配置文件
|
|
48
|
-
* @private
|
|
49
|
-
* @returns 配置对象或在读取失败时返回 null
|
|
50
|
-
*/
|
|
51
|
-
static loadFromDisk() {
|
|
52
|
-
if (fs_extra_1.default.existsSync(this.configFile)) {
|
|
53
|
-
try {
|
|
54
|
-
return fs_extra_1.default.readJsonSync(this.configFile);
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
console.error(chalk_1.default.red(`[ConfigManager] 配置文件读取失败: ${e.message}`));
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
19
|
+
this.projects = this.readProjects();
|
|
20
|
+
this.servers = this.readServers();
|
|
62
21
|
}
|
|
63
|
-
/**
|
|
64
|
-
* 检查本地配置文件是否存在
|
|
65
|
-
* @returns true if config file exists
|
|
66
|
-
*/
|
|
22
|
+
/** 两个部署配置文件都存在时才视为已初始化。 */
|
|
67
23
|
static existsOnDisk() {
|
|
68
|
-
return this.
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
24
|
+
return fs_extra_1.default.existsSync(this.projectsFile) && fs_extra_1.default.existsSync(this.serversFile);
|
|
25
|
+
}
|
|
26
|
+
/** 创建尚不存在的空配置文件,不覆盖已有数据。 */
|
|
27
|
+
static ensureOnDisk() {
|
|
28
|
+
if (!fs_extra_1.default.existsSync(this.projectsFile))
|
|
29
|
+
this.persistProjects(this.projects);
|
|
30
|
+
if (!fs_extra_1.default.existsSync(this.serversFile))
|
|
31
|
+
this.persistServers(this.servers);
|
|
32
|
+
}
|
|
33
|
+
static replaceProjects(projects) {
|
|
34
|
+
this.persistProjects(projects);
|
|
35
|
+
this.projects = projects;
|
|
36
|
+
}
|
|
37
|
+
static replaceServers(servers) {
|
|
38
|
+
this.persistServers(servers);
|
|
39
|
+
this.servers = servers;
|
|
40
|
+
}
|
|
41
|
+
/** 写入单个项目;写前重读,避免覆盖另一个进程刚保存的其他项目。 */
|
|
42
|
+
static upsertProject(project) {
|
|
43
|
+
const projects = this.readProjects();
|
|
44
|
+
const index = projects.findIndex((item) => item.id === project.id);
|
|
45
|
+
if (index === -1)
|
|
46
|
+
projects.push(project);
|
|
47
|
+
else
|
|
48
|
+
projects[index] = project;
|
|
49
|
+
this.replaceProjects(projects);
|
|
50
|
+
}
|
|
51
|
+
static deleteProject(projectId) {
|
|
52
|
+
const projects = this.readProjects().filter((project) => project.id !== projectId);
|
|
53
|
+
this.replaceProjects(projects);
|
|
54
|
+
}
|
|
55
|
+
/** 写入单个服务器;项目环境只保存返回配置的稳定 ID。 */
|
|
56
|
+
static upsertServer(server) {
|
|
57
|
+
const servers = this.readServers();
|
|
58
|
+
const index = servers.findIndex((item) => item.id === server.id);
|
|
59
|
+
if (index === -1)
|
|
60
|
+
servers.push(server);
|
|
61
|
+
else
|
|
62
|
+
servers[index] = server;
|
|
63
|
+
this.replaceServers(servers);
|
|
64
|
+
}
|
|
65
|
+
static findProject(nameOrId) {
|
|
66
|
+
return this.projects.find((project) => project.name === nameOrId || project.id === nameOrId);
|
|
67
|
+
}
|
|
68
|
+
static getConfigPaths() {
|
|
69
|
+
return { projects: this.projectsFile, servers: this.serversFile };
|
|
70
|
+
}
|
|
71
|
+
static readProjects() {
|
|
72
|
+
const document = this.readConfigFile(this.projectsFile, {
|
|
73
|
+
schemaVersion: SCHEMA_VERSION,
|
|
74
|
+
projects: [],
|
|
75
|
+
});
|
|
76
|
+
if (!Array.isArray(document.projects)) {
|
|
77
|
+
throw new Error(`[ConfigManager] projects.json 缺少 projects 数组: ${this.projectsFile}`);
|
|
78
|
+
}
|
|
79
|
+
return document.projects;
|
|
80
|
+
}
|
|
81
|
+
static readServers() {
|
|
82
|
+
const document = this.readConfigFile(this.serversFile, {
|
|
83
|
+
schemaVersion: SCHEMA_VERSION,
|
|
84
|
+
servers: [],
|
|
104
85
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
86
|
+
if (!Array.isArray(document.servers)) {
|
|
87
|
+
throw new Error(`[ConfigManager] servers.json 缺少 servers 数组: ${this.serversFile}`);
|
|
88
|
+
}
|
|
89
|
+
return document.servers;
|
|
90
|
+
}
|
|
91
|
+
static readConfigFile(file, fallback) {
|
|
92
|
+
if (!fs_extra_1.default.existsSync(file))
|
|
93
|
+
return fallback;
|
|
94
|
+
let document;
|
|
114
95
|
try {
|
|
115
|
-
fs_extra_1.default.
|
|
116
|
-
// 使用同步写入确保在 CLI 进程结束前数据已落盘
|
|
117
|
-
fs_extra_1.default.writeJsonSync(this.configFile, this.config, { spaces: 2 });
|
|
118
|
-
this.hasFile = true;
|
|
96
|
+
document = fs_extra_1.default.readJsonSync(file);
|
|
119
97
|
}
|
|
120
|
-
catch (
|
|
121
|
-
|
|
98
|
+
catch (error) {
|
|
99
|
+
throw new Error(`[ConfigManager] 配置文件读取失败 ${file}: ${error.message}`);
|
|
122
100
|
}
|
|
101
|
+
if (document.schemaVersion !== SCHEMA_VERSION) {
|
|
102
|
+
throw new Error(`[ConfigManager] 不支持的 schemaVersion ${document.schemaVersion}: ${file}`);
|
|
103
|
+
}
|
|
104
|
+
return document;
|
|
105
|
+
}
|
|
106
|
+
static persistProjects(projects) {
|
|
107
|
+
this.writeConfigFile(this.projectsFile, {
|
|
108
|
+
schemaVersion: SCHEMA_VERSION,
|
|
109
|
+
projects,
|
|
110
|
+
});
|
|
123
111
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
112
|
+
static persistServers(servers) {
|
|
113
|
+
this.writeConfigFile(this.serversFile, {
|
|
114
|
+
schemaVersion: SCHEMA_VERSION,
|
|
115
|
+
servers,
|
|
116
|
+
});
|
|
129
117
|
}
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
118
|
+
/** 同目录临时文件 + rename,避免进程中断留下半截 JSON。 */
|
|
119
|
+
static writeConfigFile(file, document) {
|
|
120
|
+
fs_extra_1.default.ensureDirSync(this.deploymentDir);
|
|
121
|
+
const temporaryFile = `${file}.${process.pid}.tmp`;
|
|
122
|
+
try {
|
|
123
|
+
fs_extra_1.default.writeJsonSync(temporaryFile, document, { spaces: 2 });
|
|
124
|
+
fs_extra_1.default.renameSync(temporaryFile, file);
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
fs_extra_1.default.removeSync(temporaryFile);
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
136
130
|
}
|
|
137
131
|
}
|
|
138
132
|
exports.ConfigManager = ConfigManager;
|
|
139
|
-
|
|
140
|
-
ConfigManager.
|
|
141
|
-
ConfigManager.
|
|
142
|
-
ConfigManager.
|
|
143
|
-
ConfigManager.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
"git-list": [],
|
|
147
|
-
};
|
|
148
|
-
/**
|
|
149
|
-
* 响应式配置对象
|
|
150
|
-
* 修改此对象的任何属性都会自动触发磁盘更新。
|
|
151
|
-
*/
|
|
152
|
-
ConfigManager.config = ConfigManager.initialize();
|
|
133
|
+
_a = ConfigManager;
|
|
134
|
+
ConfigManager.storageRoot = path_1.default.join(os_1.default.homedir(), ".maolike", "desktop");
|
|
135
|
+
ConfigManager.deploymentDir = path_1.default.join(_a.storageRoot, "deployment");
|
|
136
|
+
ConfigManager.projectsFile = path_1.default.join(_a.deploymentDir, "projects.json");
|
|
137
|
+
ConfigManager.serversFile = path_1.default.join(_a.deploymentDir, "servers.json");
|
|
138
|
+
ConfigManager.projects = _a.readProjects();
|
|
139
|
+
ConfigManager.servers = _a.readServers();
|
|
@@ -1,124 +1,77 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
|
+
const crypto_1 = require("crypto");
|
|
4
5
|
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
5
6
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
7
|
const ConfigManager_1 = require("../services/ConfigManager");
|
|
8
|
+
const ConfigPrompter_1 = require("../utils/ConfigPrompter");
|
|
7
9
|
const Scanner_1 = require("../utils/Scanner");
|
|
8
|
-
// 辅助函数:清理拖入的路径
|
|
9
10
|
const pathFilter = (input) => input.trim().replace(/^['"]|['"]$/g, "");
|
|
10
|
-
/**
|
|
11
|
-
* 初始化向导处理类。
|
|
12
|
-
* 负责引导用户完成:
|
|
13
|
-
* 1. 全局 SSH 配置
|
|
14
|
-
* 2. 批量或手动添加 Git 项目配置
|
|
15
|
-
* 3. 生成并保存最终的配置文件
|
|
16
|
-
*/
|
|
11
|
+
/** 初始化新的 projects.json 与 servers.json。 */
|
|
17
12
|
class InitWizard {
|
|
18
13
|
constructor() {
|
|
19
|
-
this.
|
|
14
|
+
this.projects = [];
|
|
15
|
+
this.servers = [];
|
|
20
16
|
}
|
|
21
|
-
/**
|
|
22
|
-
* 启动初始化向导的主流程
|
|
23
|
-
*/
|
|
24
17
|
run() {
|
|
25
18
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
26
19
|
console.log(chalk_1.default.bold.magenta("\n✨ 欢迎使用 Maolike CLI 初始化向导 ✨\n"));
|
|
27
|
-
// 检查现有配置
|
|
28
20
|
if (ConfigManager_1.ConfigManager.existsOnDisk()) {
|
|
29
21
|
const { overwrite } = yield inquirer_1.default.prompt([
|
|
30
22
|
{
|
|
31
23
|
type: "confirm",
|
|
32
24
|
name: "overwrite",
|
|
33
|
-
message: "
|
|
25
|
+
message: "发现已存在的部署配置,是否覆盖 projects.json 和 servers.json?",
|
|
34
26
|
default: false,
|
|
35
27
|
},
|
|
36
28
|
]);
|
|
37
29
|
if (!overwrite)
|
|
38
30
|
return;
|
|
39
31
|
}
|
|
40
|
-
// 第一步: SSH 配置
|
|
41
|
-
const sshConfig = yield this.askSshConfig();
|
|
42
|
-
// 第二步: 项目配置
|
|
43
32
|
const { configProjectsNow } = yield inquirer_1.default.prompt([
|
|
44
33
|
{
|
|
45
34
|
type: "confirm",
|
|
46
35
|
name: "configProjectsNow",
|
|
47
|
-
message: "
|
|
36
|
+
message: "是否立即配置部署项目?",
|
|
48
37
|
default: true,
|
|
49
38
|
},
|
|
50
39
|
]);
|
|
51
|
-
if (configProjectsNow)
|
|
40
|
+
if (configProjectsNow)
|
|
52
41
|
yield this.configureProjects();
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
ConfigManager_1.ConfigManager.
|
|
56
|
-
ConfigManager_1.ConfigManager.config["git-list"] = this.gitList;
|
|
42
|
+
ConfigManager_1.ConfigManager.replaceServers(this.servers);
|
|
43
|
+
ConfigManager_1.ConfigManager.replaceProjects(this.projects);
|
|
44
|
+
const paths = ConfigManager_1.ConfigManager.getConfigPaths();
|
|
57
45
|
console.log(chalk_1.default.green("\n✅ 配置初始化完成!"));
|
|
58
|
-
console.log(chalk_1.default.dim(
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
/**
|
|
62
|
-
* 第一步:收集全局 SSH 连接配置
|
|
63
|
-
* @private
|
|
64
|
-
*/
|
|
65
|
-
askSshConfig() {
|
|
66
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
67
|
-
console.log(chalk_1.default.blue("--- 第一步: 配置全局 SSH (必需) ---"));
|
|
68
|
-
return inquirer_1.default.prompt([
|
|
69
|
-
{ type: "input", name: "host", message: "服务器 IP (Host):", validate: (i) => (i ? true : "Required") },
|
|
70
|
-
{ type: "number", name: "port", message: "端口 (Port):", default: 22 },
|
|
71
|
-
{ type: "input", name: "user", message: "用户名 (User):", default: "root" },
|
|
72
|
-
{ type: "password", name: "password", message: "SSH 密码 (留空则使用 Key):" },
|
|
73
|
-
{ type: "input", name: "gitUser", message: "远程 Git 账户 (Optional):" },
|
|
74
|
-
{ type: "password", name: "gitPassword", message: "远程 Git 密码/Token (Optional):" },
|
|
75
|
-
]);
|
|
46
|
+
console.log(chalk_1.default.dim(`项目配置: ${paths.projects}`));
|
|
47
|
+
console.log(chalk_1.default.dim(`服务器配置: ${paths.servers}`));
|
|
76
48
|
});
|
|
77
49
|
}
|
|
78
|
-
/**
|
|
79
|
-
* 第二步:循环配置多个 Git 项目
|
|
80
|
-
* 支持通过扫描目录或手动录入的方式添加
|
|
81
|
-
* @private
|
|
82
|
-
*/
|
|
83
50
|
configureProjects() {
|
|
84
51
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
while (loop) {
|
|
52
|
+
console.log(chalk_1.default.blue("\n--- 添加部署项目 ---"));
|
|
53
|
+
while (true) {
|
|
88
54
|
const { addMode } = yield inquirer_1.default.prompt([
|
|
89
55
|
{
|
|
90
56
|
type: "list",
|
|
91
57
|
name: "addMode",
|
|
92
58
|
message: "请选择添加方式:",
|
|
93
59
|
choices: [
|
|
94
|
-
{ name: "📂 扫描父目录批量添加
|
|
95
|
-
{ name: "📝 手动添加单项
|
|
96
|
-
{ name: "✅ 完成配置
|
|
60
|
+
{ name: "📂 扫描父目录批量添加", value: "scan" },
|
|
61
|
+
{ name: "📝 手动添加单项", value: "manual" },
|
|
62
|
+
{ name: "✅ 完成配置", value: "finish" },
|
|
97
63
|
],
|
|
98
64
|
},
|
|
99
65
|
]);
|
|
100
66
|
if (addMode === "finish")
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
else {
|
|
107
|
-
projectsToConfig.push(yield this.manualInputRepo());
|
|
108
|
-
}
|
|
109
|
-
// 批量配置
|
|
110
|
-
for (const proj of projectsToConfig) {
|
|
111
|
-
if (!proj)
|
|
112
|
-
continue; // 如果扫描为空则跳过
|
|
113
|
-
yield this.collectProjectDetails(proj);
|
|
67
|
+
return;
|
|
68
|
+
const candidates = addMode === "scan" ? yield this.scanAndSelectRepos() : [yield this.manualInputRepo()];
|
|
69
|
+
for (const candidate of candidates) {
|
|
70
|
+
yield this.collectProjectDetails(candidate);
|
|
114
71
|
}
|
|
115
72
|
}
|
|
116
73
|
});
|
|
117
74
|
}
|
|
118
|
-
/**
|
|
119
|
-
* 扫描指定目录下的所有 Git 仓库并供用户选择
|
|
120
|
-
* @private
|
|
121
|
-
*/
|
|
122
75
|
scanAndSelectRepos() {
|
|
123
76
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
124
77
|
const { parentPath } = yield inquirer_1.default.prompt([
|
|
@@ -142,95 +95,40 @@ class InitWizard {
|
|
|
142
95
|
type: "checkbox",
|
|
143
96
|
name: "selected",
|
|
144
97
|
message: `找到 ${repos.length} 个仓库,请选择:`,
|
|
145
|
-
choices: repos.map((
|
|
146
|
-
validate: (d) => (d.length > 0 ? true : "请至少选择一个"),
|
|
98
|
+
choices: repos.map((repo) => ({ name: repo.name, value: repo })),
|
|
147
99
|
},
|
|
148
100
|
]);
|
|
149
101
|
return selected;
|
|
150
102
|
}
|
|
151
|
-
catch (
|
|
152
|
-
console.log(chalk_1.default.red(`扫描失败: ${
|
|
103
|
+
catch (error) {
|
|
104
|
+
console.log(chalk_1.default.red(`扫描失败: ${error.message}`));
|
|
153
105
|
return [];
|
|
154
106
|
}
|
|
155
107
|
});
|
|
156
108
|
}
|
|
157
|
-
/**
|
|
158
|
-
* 手动录入单个项目的基础信息
|
|
159
|
-
* @private
|
|
160
|
-
*/
|
|
161
109
|
manualInputRepo() {
|
|
162
110
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
163
111
|
return inquirer_1.default.prompt([
|
|
164
|
-
{ type: "input", name: "name", message: "
|
|
112
|
+
{ type: "input", name: "name", message: "项目名称:", validate: (input) => Boolean(input) || "项目名称不能为空" },
|
|
165
113
|
{ type: "input", name: "path", message: "项目路径 (支持拖入):", filter: pathFilter },
|
|
166
114
|
]);
|
|
167
115
|
});
|
|
168
116
|
}
|
|
169
|
-
|
|
170
|
-
* 收集项目的详细构建与部署配置
|
|
171
|
-
* 包含:打包命令、产物路径、测试环境及远程路径
|
|
172
|
-
* @param proj 包含 name 和 path 的基础项目对象
|
|
173
|
-
* @private
|
|
174
|
-
*/
|
|
175
|
-
collectProjectDetails(proj) {
|
|
117
|
+
collectProjectDetails(candidate) {
|
|
176
118
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
177
|
-
console.log(chalk_1.default.cyan(`\n🔧 正在配置项目: [${
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
]);
|
|
191
|
-
let distName = "dist";
|
|
192
|
-
let distFile = undefined;
|
|
193
|
-
if (artifactType.type === "dir") {
|
|
194
|
-
const ans = yield inquirer_1.default.prompt([
|
|
195
|
-
{
|
|
196
|
-
type: "input",
|
|
197
|
-
name: "distName",
|
|
198
|
-
message: "文件夹名称:",
|
|
199
|
-
default: "dist",
|
|
200
|
-
filter: pathFilter,
|
|
201
|
-
},
|
|
202
|
-
]);
|
|
203
|
-
distName = ans.distName;
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
const ans = yield inquirer_1.default.prompt([
|
|
207
|
-
{
|
|
208
|
-
type: "input",
|
|
209
|
-
name: "distFile",
|
|
210
|
-
message: "文件路径 (逗号分隔):",
|
|
211
|
-
filter: pathFilter,
|
|
212
|
-
},
|
|
213
|
-
]);
|
|
214
|
-
distFile = ans.distFile;
|
|
215
|
-
distName = "";
|
|
216
|
-
}
|
|
217
|
-
// 测试环境配置
|
|
218
|
-
const { hasTest } = yield inquirer_1.default.prompt([{ type: "confirm", name: "hasTest", message: "需要同步测试环境?", default: true }]);
|
|
219
|
-
let testConfig = { path: "", branch: "" };
|
|
220
|
-
if (hasTest) {
|
|
221
|
-
testConfig = yield inquirer_1.default.prompt([
|
|
222
|
-
{ type: "input", name: "path", message: "测试项目路径:", filter: pathFilter },
|
|
223
|
-
{ type: "input", name: "branch", message: "测试分支:", default: "dev" },
|
|
224
|
-
]);
|
|
225
|
-
}
|
|
226
|
-
// 远程配置
|
|
227
|
-
const remote = yield inquirer_1.default.prompt([{ type: "input", name: "remotePath", message: "服务器部署路径:", filter: pathFilter }]);
|
|
228
|
-
this.gitList.push({
|
|
229
|
-
name: proj.name,
|
|
230
|
-
path: proj.path,
|
|
231
|
-
build: { cmd: baseInfo.buildCmd, distName, distFile },
|
|
232
|
-
test: { path: testConfig.path, branch: testConfig.branch },
|
|
233
|
-
server: { remotePath: remote.remotePath },
|
|
119
|
+
console.log(chalk_1.default.cyan(`\n🔧 正在配置项目: [${candidate.name}] ...`));
|
|
120
|
+
const build = yield ConfigPrompter_1.ConfigPrompter.promptBuildConfig();
|
|
121
|
+
const test = yield ConfigPrompter_1.ConfigPrompter.promptTestEnvironment(undefined, this.servers);
|
|
122
|
+
const production = yield ConfigPrompter_1.ConfigPrompter.promptProductionConfig(undefined, test, null, this.servers);
|
|
123
|
+
build.productionCommand = production.productionCommand;
|
|
124
|
+
this.projects.push({
|
|
125
|
+
id: (0, crypto_1.randomUUID)(),
|
|
126
|
+
name: candidate.name.trim(),
|
|
127
|
+
localPath: candidate.path,
|
|
128
|
+
build,
|
|
129
|
+
environments: { test, production: production.environment },
|
|
130
|
+
enabled: true,
|
|
131
|
+
sortOrder: this.projects.length,
|
|
234
132
|
});
|
|
235
133
|
});
|
|
236
134
|
}
|
|
@@ -7,6 +7,15 @@ const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
|
7
7
|
const ConfigManager_1 = require("../services/ConfigManager");
|
|
8
8
|
const CommandRegistry_1 = require("../constants/CommandRegistry");
|
|
9
9
|
const child_process_1 = require("child_process");
|
|
10
|
+
const COMMON_COMMANDS = [
|
|
11
|
+
CommandRegistry_1.CommandType.LIST,
|
|
12
|
+
CommandRegistry_1.CommandType.VIEW,
|
|
13
|
+
CommandRegistry_1.CommandType.TEST,
|
|
14
|
+
CommandRegistry_1.CommandType.DEPLOY,
|
|
15
|
+
CommandRegistry_1.CommandType.CONNECT,
|
|
16
|
+
CommandRegistry_1.CommandType.CURSOR,
|
|
17
|
+
CommandRegistry_1.CommandType.HELP,
|
|
18
|
+
];
|
|
10
19
|
/**
|
|
11
20
|
* 交互式 Shell 管理器
|
|
12
21
|
* 负责提供类终端的交互体验,支持 Tab 补全、命令历史提示及即时指令执行
|
|
@@ -17,7 +26,7 @@ class InteractiveShell {
|
|
|
17
26
|
this.line = "";
|
|
18
27
|
/** 当前显示的预测/建议字符串 */
|
|
19
28
|
this.suggestion = "";
|
|
20
|
-
/**
|
|
29
|
+
/** 从配置中读取的项目列表 */
|
|
21
30
|
this.projects = [];
|
|
22
31
|
/** 支持的基础命令列表 */
|
|
23
32
|
this.commands = [...CommandRegistry_1.ALL_COMMANDS, ...CommandRegistry_1.SHELL_COMMANDS];
|
|
@@ -30,11 +39,18 @@ class InteractiveShell {
|
|
|
30
39
|
});
|
|
31
40
|
// 加载已有项目名用于预测
|
|
32
41
|
if (ConfigManager_1.ConfigManager.existsOnDisk()) {
|
|
33
|
-
this.projects = ConfigManager_1.ConfigManager.
|
|
42
|
+
this.projects = ConfigManager_1.ConfigManager.projects;
|
|
34
43
|
}
|
|
35
44
|
this.buildSuggestionIndex();
|
|
36
45
|
// 自动启动交互模式
|
|
37
46
|
console.log(chalk_1.default.green("\n✨ 进入 Maolike 交互模式"));
|
|
47
|
+
console.log(chalk_1.default.bold.cyan("\n常用命令:"));
|
|
48
|
+
for (const command of COMMON_COMMANDS) {
|
|
49
|
+
const definition = CommandRegistry_1.COMMAND_REGISTRY[command];
|
|
50
|
+
const usage = definition.usage.replace(/^ml\s+/, "");
|
|
51
|
+
console.log(` ${chalk_1.default.yellow(usage.padEnd(26))}${chalk_1.default.gray(definition.description)}`);
|
|
52
|
+
}
|
|
53
|
+
console.log(` ${chalk_1.default.yellow("exit".padEnd(26))}${chalk_1.default.gray("退出交互模式")}`);
|
|
38
54
|
console.log(chalk_1.default.gray("提示: 输入首字母获取提示,按 Tab 或 → 补全。\n"));
|
|
39
55
|
this.loopNext();
|
|
40
56
|
}
|
|
@@ -44,11 +60,16 @@ class InteractiveShell {
|
|
|
44
60
|
* @private
|
|
45
61
|
*/
|
|
46
62
|
buildSuggestionIndex() {
|
|
63
|
+
var _a;
|
|
47
64
|
this.suggestionIndex.push(...this.commands);
|
|
48
|
-
const targetCmds = ["test", "update", "view", "delete"];
|
|
65
|
+
const targetCmds = ["test", "deploy", "update", "view", "delete"];
|
|
49
66
|
for (const cmd of targetCmds) {
|
|
50
|
-
for (const
|
|
51
|
-
|
|
67
|
+
for (const project of this.projects) {
|
|
68
|
+
if (cmd === "test" && !project.environments.test.workspacePath.trim())
|
|
69
|
+
continue;
|
|
70
|
+
if (cmd === "deploy" && !((_a = project.environments.production) === null || _a === void 0 ? void 0 : _a.workspacePath.trim()))
|
|
71
|
+
continue;
|
|
72
|
+
this.suggestionIndex.push(`${cmd} ${project.name}`);
|
|
52
73
|
}
|
|
53
74
|
}
|
|
54
75
|
}
|
|
@@ -173,7 +194,7 @@ class InteractiveShell {
|
|
|
173
194
|
// 每次循环重新加载配置,确保增删改查后补全列表是最新的
|
|
174
195
|
ConfigManager_1.ConfigManager.reload();
|
|
175
196
|
if (ConfigManager_1.ConfigManager.existsOnDisk()) {
|
|
176
|
-
this.projects = ConfigManager_1.ConfigManager.
|
|
197
|
+
this.projects = ConfigManager_1.ConfigManager.projects;
|
|
177
198
|
}
|
|
178
199
|
// 重建索引
|
|
179
200
|
this.suggestionIndex = [];
|