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,35 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.RemoteServer = void 0;
|
|
4
|
-
exports.
|
|
4
|
+
exports.quoteShellValue = quoteShellValue;
|
|
5
5
|
const tslib_1 = require("tslib");
|
|
6
6
|
const node_ssh_1 = require("node-ssh");
|
|
7
7
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
8
8
|
const ora_1 = tslib_1.__importDefault(require("ora"));
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
function quoteShellValue(value) {
|
|
11
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
12
|
+
}
|
|
13
|
+
/** 基于 node-ssh 的远程服务器交互包装。 */
|
|
13
14
|
class RemoteServer {
|
|
14
15
|
constructor(config) {
|
|
15
|
-
this.ssh = new node_ssh_1.NodeSSH();
|
|
16
16
|
this.config = config;
|
|
17
|
+
this.ssh = new node_ssh_1.NodeSSH();
|
|
17
18
|
}
|
|
18
|
-
/**
|
|
19
|
-
* 建立 SSH 连接
|
|
20
|
-
* @throws {Error} 连接失败时抛出错误
|
|
21
|
-
*/
|
|
22
19
|
connect() {
|
|
23
20
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
24
21
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
port: this.config.port || 22,
|
|
28
|
-
username: this.config.user,
|
|
29
|
-
password: this.config.password,
|
|
30
|
-
keepaliveInterval: 10000,
|
|
31
|
-
});
|
|
32
|
-
return true;
|
|
22
|
+
const agentForwardConfig = this.config.agentForward && process.env.SSH_AUTH_SOCK ? { agent: process.env.SSH_AUTH_SOCK, agentForward: true } : {};
|
|
23
|
+
yield this.ssh.connect(Object.assign(Object.assign({ host: this.config.host, port: this.config.port || 22, username: this.config.username, password: this.config.password || undefined, privateKeyPath: this.config.sshPrivateKey || undefined }, agentForwardConfig), { keepaliveInterval: 10000 }));
|
|
33
24
|
}
|
|
34
25
|
catch (error) {
|
|
35
26
|
throw new Error(`SSH 连接失败 [${this.config.host}]: ${error.message}`);
|
|
@@ -37,26 +28,73 @@ class RemoteServer {
|
|
|
37
28
|
});
|
|
38
29
|
}
|
|
39
30
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* @param taskName 任务描述文案
|
|
31
|
+
* 在当前终端打开远程交互式 Shell。
|
|
32
|
+
*
|
|
33
|
+
* 连接配置仍由 node-ssh 管理,密码和私钥不会经过命令行参数或日志。
|
|
44
34
|
*/
|
|
35
|
+
openInteractiveShell() {
|
|
36
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
var _a, _b;
|
|
38
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
39
|
+
throw new Error("`ml connect` 需要在交互式终端中运行");
|
|
40
|
+
}
|
|
41
|
+
yield this.connect();
|
|
42
|
+
const shell = yield this.ssh.requestShell({
|
|
43
|
+
term: process.env.TERM || "xterm-256color",
|
|
44
|
+
cols: process.stdout.columns || 80,
|
|
45
|
+
rows: process.stdout.rows || 24,
|
|
46
|
+
});
|
|
47
|
+
const stdinWasPaused = process.stdin.isPaused();
|
|
48
|
+
const stdinWasRaw = process.stdin.isRaw;
|
|
49
|
+
const resize = () => {
|
|
50
|
+
shell.setWindow(process.stdout.rows || 24, process.stdout.columns || 80, 0, 0);
|
|
51
|
+
};
|
|
52
|
+
(_b = (_a = process.stdin).setRawMode) === null || _b === void 0 ? void 0 : _b.call(_a, true);
|
|
53
|
+
process.stdin.resume();
|
|
54
|
+
process.stdin.pipe(shell);
|
|
55
|
+
shell.pipe(process.stdout);
|
|
56
|
+
shell.stderr.pipe(process.stderr);
|
|
57
|
+
process.stdout.on("resize", resize);
|
|
58
|
+
try {
|
|
59
|
+
yield new Promise((resolve, reject) => {
|
|
60
|
+
shell.once("close", resolve);
|
|
61
|
+
shell.once("error", reject);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
process.stdout.removeListener("resize", resize);
|
|
66
|
+
process.stdin.unpipe(shell);
|
|
67
|
+
shell.unpipe(process.stdout);
|
|
68
|
+
shell.stderr.unpipe(process.stderr);
|
|
69
|
+
if (process.stdin.setRawMode)
|
|
70
|
+
process.stdin.setRawMode(stdinWasRaw !== null && stdinWasRaw !== void 0 ? stdinWasRaw : false);
|
|
71
|
+
if (stdinWasPaused)
|
|
72
|
+
process.stdin.pause();
|
|
73
|
+
shell.destroy();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
45
77
|
exec(command_1, cwd_1) {
|
|
46
|
-
return tslib_1.__awaiter(this, arguments, void 0, function* (command, cwd, taskName = "Task") {
|
|
78
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* (command, cwd, taskName = "Task", options = {}) {
|
|
79
|
+
var _a;
|
|
47
80
|
const spinner = (0, ora_1.default)(`${taskName} (Remote: ${this.config.host})`).start();
|
|
48
81
|
try {
|
|
49
|
-
const
|
|
50
|
-
|
|
82
|
+
const remoteCommand = cwd ? `cd ${quoteShellValue(cwd)} && ${command}` : command;
|
|
83
|
+
let result = yield this.ssh.execCommand(remoteCommand);
|
|
84
|
+
let stderr = result.stderr || result.stdout || "";
|
|
85
|
+
if (result.code !== 0 && options.retryAfterAgentReload && this.isGitAuthError(stderr) && (yield this.reloadSshAgentIfEmpty())) {
|
|
86
|
+
spinner.text = `${taskName}: SSH 密钥已重载,正在重试...`;
|
|
87
|
+
result = yield this.ssh.execCommand(remoteCommand);
|
|
88
|
+
stderr = result.stderr || result.stdout || "";
|
|
89
|
+
}
|
|
51
90
|
if (result.code !== 0) {
|
|
52
91
|
spinner.fail(`${taskName} 失败`);
|
|
53
|
-
console.error(chalk_1.default.red(`\n[Remote Stderr]:\n${
|
|
54
|
-
throw new Error(
|
|
92
|
+
console.error(chalk_1.default.red(`\n[Remote Stderr]:\n${stderr}`));
|
|
93
|
+
throw new Error(this.formatExecError(stderr, (_a = result.code) !== null && _a !== void 0 ? _a : -1));
|
|
55
94
|
}
|
|
56
95
|
spinner.succeed(`${taskName} 完成`);
|
|
57
|
-
if (result.stdout)
|
|
96
|
+
if (result.stdout)
|
|
58
97
|
console.log(chalk_1.default.gray(`[Remote Output]:\n${result.stdout.trim()}`));
|
|
59
|
-
}
|
|
60
98
|
return result.stdout;
|
|
61
99
|
}
|
|
62
100
|
catch (error) {
|
|
@@ -66,63 +104,66 @@ class RemoteServer {
|
|
|
66
104
|
}
|
|
67
105
|
});
|
|
68
106
|
}
|
|
69
|
-
/**
|
|
70
|
-
* 上传单个文件到服务器
|
|
71
|
-
* @param localPath 本地绝对路径
|
|
72
|
-
* @param remotePath 远程绝对目标路径
|
|
73
|
-
*/
|
|
74
107
|
uploadFile(localPath, remotePath) {
|
|
75
108
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
76
109
|
return this.ssh.putFile(localPath, remotePath);
|
|
77
110
|
});
|
|
78
111
|
}
|
|
79
|
-
/**
|
|
80
|
-
* 递归上传整个目录
|
|
81
|
-
* @param localPath 本地源目录
|
|
82
|
-
* @param remotePath 远程目标目录
|
|
83
|
-
*/
|
|
84
112
|
uploadDirectory(localPath, remotePath) {
|
|
85
113
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
86
|
-
return this.ssh.putDirectory(localPath, remotePath, {
|
|
87
|
-
recursive: true,
|
|
88
|
-
concurrency: 10,
|
|
89
|
-
});
|
|
114
|
+
return this.ssh.putDirectory(localPath, remotePath, { recursive: true, concurrency: 10 });
|
|
90
115
|
});
|
|
91
116
|
}
|
|
92
|
-
/**
|
|
93
|
-
* 生成带身份验证信息的 Git Pull 命令
|
|
94
|
-
* 优先从 SSH 配置中读取凭据并注入到 git-c 辅助程序中
|
|
95
|
-
* @returns 拼接好的命令行字符串
|
|
96
|
-
*/
|
|
97
117
|
getGitPullCommand() {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
118
|
+
var _a;
|
|
119
|
+
const username = (_a = this.config.gitUsername) === null || _a === void 0 ? void 0 : _a.trim();
|
|
120
|
+
const password = this.config.gitPassword || "";
|
|
121
|
+
const gitPrefix = `GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND=${quoteShellValue("ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new")}`;
|
|
122
|
+
if (!username)
|
|
123
|
+
return `${gitPrefix} git pull`;
|
|
124
|
+
const helper = `!f() { echo "username=${username}"; echo "password=${password}"; }; f`;
|
|
125
|
+
return `${gitPrefix} git -c credential.helper=${quoteShellValue(helper)} pull`;
|
|
104
126
|
}
|
|
105
|
-
/**
|
|
106
|
-
* 断开 SSH 链接释放资源
|
|
107
|
-
*/
|
|
108
127
|
disconnect() {
|
|
109
128
|
this.ssh.dispose();
|
|
110
129
|
}
|
|
130
|
+
reloadSshAgentIfEmpty() {
|
|
131
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
132
|
+
if (!this.config.agentForward || !process.env.SSH_AUTH_SOCK)
|
|
133
|
+
return false;
|
|
134
|
+
if ((yield runSshAdd(["-l"], "ignore")) !== 1)
|
|
135
|
+
return false;
|
|
136
|
+
console.log(chalk_1.default.yellow("本机 SSH Agent 没有已加载的密钥,正在自动重载..."));
|
|
137
|
+
const args = process.platform === "darwin" ? ["--apple-load-keychain"] : [];
|
|
138
|
+
yield runSshAdd(args, "inherit");
|
|
139
|
+
if ((yield runSshAdd(["-l"], "ignore")) === 0) {
|
|
140
|
+
console.log(chalk_1.default.green("SSH Agent 密钥重载成功,将重新尝试远程 Git Pull。"));
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
console.warn(chalk_1.default.yellow("SSH Agent 自动重载失败或重载后仍没有密钥,保留原始 Git 错误。"));
|
|
144
|
+
return false;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
isGitAuthError(stderr) {
|
|
148
|
+
return /Permission denied \(publickey\)/i.test(stderr) || /Could not read from remote repository/i.test(stderr);
|
|
149
|
+
}
|
|
150
|
+
formatExecError(stderr, code) {
|
|
151
|
+
if (this.isGitAuthError(stderr)) {
|
|
152
|
+
const hint = this.config.agentForward
|
|
153
|
+
? process.env.SSH_AUTH_SOCK
|
|
154
|
+
? "请确认本机 SSH Agent 已加载对应私钥,并且仓库成员权限正常。"
|
|
155
|
+
: "已配置 agentForward,但当前终端没有 SSH_AUTH_SOCK。"
|
|
156
|
+
: "可开启服务器的 agentForward,或在服务器上配置 deploy key。";
|
|
157
|
+
return `远程 Git 认证失败 (SSH publickey)。${hint}`;
|
|
158
|
+
}
|
|
159
|
+
return `远程命令执行出错 (Exit Code: ${code})`;
|
|
160
|
+
}
|
|
111
161
|
}
|
|
112
162
|
exports.RemoteServer = RemoteServer;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
function createRemoteConnection(globalSsh, itemSsh) {
|
|
119
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
120
|
-
const merged = Object.assign(Object.assign(Object.assign({}, globalSsh), itemSsh), { host: (itemSsh === null || itemSsh === void 0 ? void 0 : itemSsh.host) || (globalSsh === null || globalSsh === void 0 ? void 0 : globalSsh.host) || "", port: (itemSsh === null || itemSsh === void 0 ? void 0 : itemSsh.port) || (globalSsh === null || globalSsh === void 0 ? void 0 : globalSsh.port) || 22, user: (itemSsh === null || itemSsh === void 0 ? void 0 : itemSsh.user) || (globalSsh === null || globalSsh === void 0 ? void 0 : globalSsh.user) || "" });
|
|
121
|
-
if (!merged.host || !merged.user) {
|
|
122
|
-
throw new Error("无效的 SSH 配置: 缺少 host 或 user");
|
|
123
|
-
}
|
|
124
|
-
const server = new RemoteServer(merged);
|
|
125
|
-
yield server.connect();
|
|
126
|
-
return server;
|
|
163
|
+
function runSshAdd(args, stdio) {
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
const child = (0, child_process_1.spawn)("ssh-add", args, { stdio });
|
|
166
|
+
child.once("error", () => resolve(-1));
|
|
167
|
+
child.once("close", (code) => resolve(code !== null && code !== void 0 ? code : -1));
|
|
127
168
|
});
|
|
128
169
|
}
|
package/package.json
CHANGED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
const tslib_1 = require("tslib");
|
|
4
|
-
const BaseCommand_1 = tslib_1.__importDefault(require("../../utils/BaseCommand"));
|
|
5
|
-
const ConfigManager_1 = require("../../services/ConfigManager");
|
|
6
|
-
const InitWizard_1 = tslib_1.__importDefault(require("../../ui/InitWizard"));
|
|
7
|
-
const Scanner_1 = require("../../utils/Scanner");
|
|
8
|
-
const GitProcessor_1 = tslib_1.__importDefault(require("../../core/GitProcessor"));
|
|
9
|
-
const PipelineExecutor_1 = tslib_1.__importDefault(require("../../core/PipelineExecutor"));
|
|
10
|
-
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
11
|
-
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
12
|
-
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
13
|
-
const path_1 = tslib_1.__importDefault(require("path"));
|
|
14
|
-
/**
|
|
15
|
-
* 仪表盘菜单命令
|
|
16
|
-
* 提供可视化交互界面,集成初始化、扫描、配置运行等多种功能
|
|
17
|
-
*/
|
|
18
|
-
class MenuCommand extends BaseCommand_1.default {
|
|
19
|
-
init() {
|
|
20
|
-
this.program.command("menu").description("打开交互式操作菜单").action(this.action.bind(this));
|
|
21
|
-
}
|
|
22
|
-
action() {
|
|
23
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
24
|
-
while (true) {
|
|
25
|
-
// 每次循环重新读取配置状态,确保 init 后能刷新菜单项
|
|
26
|
-
const config = ConfigManager_1.ConfigManager.config;
|
|
27
|
-
const hasConfig = ConfigManager_1.ConfigManager.existsOnDisk();
|
|
28
|
-
const choices = [
|
|
29
|
-
{ name: "📂 扫描本地目录 (Scan Local)", value: "scan" },
|
|
30
|
-
{ name: "✨ 初始化配置 (Init)", value: "init" },
|
|
31
|
-
];
|
|
32
|
-
if (hasConfig) {
|
|
33
|
-
choices.unshift({
|
|
34
|
-
name: "🚀 从配置已保存项目运行 (Run from Config)",
|
|
35
|
-
value: "run_config",
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
choices.push(new inquirer_1.default.Separator());
|
|
39
|
-
choices.push({ name: "🚪 退出 (Exit)", value: "exit" });
|
|
40
|
-
console.log(""); // 增加一点空行间距
|
|
41
|
-
const { mode } = yield inquirer_1.default.prompt([
|
|
42
|
-
{
|
|
43
|
-
type: "list",
|
|
44
|
-
name: "mode",
|
|
45
|
-
message: "请选择操作模式:",
|
|
46
|
-
choices,
|
|
47
|
-
pageSize: 10,
|
|
48
|
-
},
|
|
49
|
-
]);
|
|
50
|
-
if (mode === "exit") {
|
|
51
|
-
console.log(chalk_1.default.gray("👋 再见!"));
|
|
52
|
-
process.exit(0);
|
|
53
|
-
}
|
|
54
|
-
try {
|
|
55
|
-
yield this.handleMenuSelection(mode, config);
|
|
56
|
-
// 操作完成后暂停一下可能更好?或者直接换行
|
|
57
|
-
console.log(chalk_1.default.dim("\n----------------------------------------\n"));
|
|
58
|
-
}
|
|
59
|
-
catch (e) {
|
|
60
|
-
console.error(chalk_1.default.red(`\n❌ 操作执行出错: ${e.message}\n`));
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* 处理主菜单项的选择分发
|
|
67
|
-
* @param mode 选择的模式标识
|
|
68
|
-
* @param config 当前全局配置对象
|
|
69
|
-
* @private
|
|
70
|
-
*/
|
|
71
|
-
handleMenuSelection(mode, config) {
|
|
72
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
73
|
-
if (mode === "init") {
|
|
74
|
-
yield new InitWizard_1.default().run();
|
|
75
|
-
}
|
|
76
|
-
else if (mode === "run_config") {
|
|
77
|
-
yield this.handleRunConfig(config);
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
yield this.handleScanMode();
|
|
81
|
-
}
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* 处理“从配置运行”模式
|
|
86
|
-
* 列出所有已保存的项目并开启流水线
|
|
87
|
-
* @param config 全局配置对象
|
|
88
|
-
* @private
|
|
89
|
-
*/
|
|
90
|
-
handleRunConfig(config) {
|
|
91
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
92
|
-
if (!config || !config["git-list"] || config["git-list"].length === 0) {
|
|
93
|
-
console.log(chalk_1.default.yellow("暂无配置项目"));
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const { selected } = yield inquirer_1.default.prompt([
|
|
97
|
-
{
|
|
98
|
-
type: "list",
|
|
99
|
-
name: "selected",
|
|
100
|
-
message: "选择要在测试环境更新的项目:",
|
|
101
|
-
choices: config["git-list"].map((i) => ({
|
|
102
|
-
name: i.name,
|
|
103
|
-
value: i.name,
|
|
104
|
-
})),
|
|
105
|
-
},
|
|
106
|
-
]);
|
|
107
|
-
console.log(chalk_1.default.blue(`开始同步 ${selected}...`));
|
|
108
|
-
yield this.runPipeline(selected);
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* 处理“扫描本地模式”
|
|
113
|
-
* 扫描指定父目录下的 Git 仓库并执行自动化同步(按需读取局部配置)
|
|
114
|
-
* @private
|
|
115
|
-
*/
|
|
116
|
-
handleScanMode() {
|
|
117
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
118
|
-
const { targetPath } = yield inquirer_1.default.prompt([
|
|
119
|
-
{
|
|
120
|
-
type: "input",
|
|
121
|
-
name: "targetPath",
|
|
122
|
-
message: "📂 请拖入或输入包含 Git 项目的父文件夹路径:",
|
|
123
|
-
filter: (i) => i.trim(),
|
|
124
|
-
},
|
|
125
|
-
]);
|
|
126
|
-
const repos = yield (0, Scanner_1.scanForGitRepos)(targetPath);
|
|
127
|
-
if (repos.length === 0) {
|
|
128
|
-
console.log(chalk_1.default.yellow("未找到 Git 仓库"));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
const { selectedRepos } = yield inquirer_1.default.prompt([
|
|
132
|
-
{
|
|
133
|
-
type: "checkbox",
|
|
134
|
-
name: "selectedRepos",
|
|
135
|
-
message: "请勾选要处理的项目:",
|
|
136
|
-
choices: repos.map((r) => ({ name: r.name, value: r })),
|
|
137
|
-
},
|
|
138
|
-
]);
|
|
139
|
-
for (const repo of selectedRepos) {
|
|
140
|
-
const localConfigPath = path_1.default.join(repo.path, ".maolike", "config.json");
|
|
141
|
-
if (fs_extra_1.default.existsSync(localConfigPath)) {
|
|
142
|
-
try {
|
|
143
|
-
const localConfig = yield fs_extra_1.default.readJson(localConfigPath);
|
|
144
|
-
yield new PipelineExecutor_1.default(repo.path, repo.name, localConfig).run();
|
|
145
|
-
}
|
|
146
|
-
catch (e) {
|
|
147
|
-
yield new GitProcessor_1.default(repo.path, repo.name).execute();
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
yield new GitProcessor_1.default(repo.path, repo.name).execute();
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* 为指定项目启动流水线执行器
|
|
158
|
-
* @param name 项目名称
|
|
159
|
-
* @private
|
|
160
|
-
*/
|
|
161
|
-
runPipeline(name) {
|
|
162
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
163
|
-
if (!ConfigManager_1.ConfigManager.existsOnDisk()) {
|
|
164
|
-
console.log(chalk_1.default.red("请先 init"));
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
const config = ConfigManager_1.ConfigManager.config;
|
|
168
|
-
const task = config["git-list"].find((item) => item.name === name);
|
|
169
|
-
if (!task) {
|
|
170
|
-
console.log(chalk_1.default.red(`未找到项目: ${name}`));
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
console.log(chalk_1.default.magenta(`🚀 正在启动 [${name}] 测试环境同步流程...`));
|
|
174
|
-
yield new PipelineExecutor_1.default(task.path, name, {
|
|
175
|
-
version: "2.0",
|
|
176
|
-
ssh: config.ssh,
|
|
177
|
-
"git-list": [task],
|
|
178
|
-
}).run();
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
exports.default = MenuCommand;
|