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
|
@@ -2,113 +2,72 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ConfigPrompter = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
+
const crypto_1 = require("crypto");
|
|
5
6
|
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
6
7
|
const ConfigManager_1 = require("../services/ConfigManager");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
* 封装了 Create 和 Update 命令中通用的配置询问逻辑
|
|
10
|
-
*/
|
|
8
|
+
const pathFilter = (input) => input.trim().replace(/^['"]|['"]$/g, "");
|
|
9
|
+
/** Create 和 Update 共用的新结构配置询问逻辑。 */
|
|
11
10
|
class ConfigPrompter {
|
|
12
|
-
/**
|
|
13
|
-
* 弹出构建配置输入
|
|
14
|
-
* @param def 可选的默认值 (用于 Update 场景)
|
|
15
|
-
*/
|
|
16
11
|
static promptBuildConfig(def) {
|
|
17
12
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
18
|
-
|
|
13
|
+
var _a, _b, _c;
|
|
14
|
+
const build = yield inquirer_1.default.prompt([
|
|
19
15
|
{
|
|
20
16
|
type: "input",
|
|
21
|
-
name: "
|
|
22
|
-
message: "
|
|
23
|
-
default: (def === null || def === void 0 ? void 0 : def.
|
|
17
|
+
name: "testCommand",
|
|
18
|
+
message: "测试环境打包命令:",
|
|
19
|
+
default: (def === null || def === void 0 ? void 0 : def.testCommand) || "npm run build",
|
|
24
20
|
},
|
|
25
21
|
{
|
|
26
22
|
type: "input",
|
|
27
23
|
name: "nodeVersion",
|
|
28
24
|
message: "Node 版本 (可选, 例如 18):",
|
|
29
|
-
default: def === null || def === void 0 ? void 0 : def.nodeVersion,
|
|
25
|
+
default: (def === null || def === void 0 ? void 0 : def.nodeVersion) || "",
|
|
30
26
|
},
|
|
31
|
-
]);
|
|
32
|
-
// 判断默认产物类型
|
|
33
|
-
let defaultType = "dir";
|
|
34
|
-
if (def === null || def === void 0 ? void 0 : def.distFile) {
|
|
35
|
-
defaultType = "file";
|
|
36
|
-
}
|
|
37
|
-
const { type } = yield inquirer_1.default.prompt([
|
|
38
27
|
{
|
|
39
28
|
type: "list",
|
|
40
|
-
name: "
|
|
29
|
+
name: "artifactKind",
|
|
41
30
|
message: "构建产物类型:",
|
|
42
31
|
choices: [
|
|
43
|
-
{ name: "📂
|
|
44
|
-
{ name: "📄
|
|
32
|
+
{ name: "📂 文件夹内容 (例如 dist)", value: "directory" },
|
|
33
|
+
{ name: "📄 指定文件 (例如 dist/bundle.js)", value: "files" },
|
|
45
34
|
],
|
|
46
|
-
default:
|
|
35
|
+
default: ((_a = def === null || def === void 0 ? void 0 : def.artifact) === null || _a === void 0 ? void 0 : _a.kind) || "directory",
|
|
47
36
|
},
|
|
48
37
|
]);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
64
|
-
else {
|
|
65
|
-
const ans = yield inquirer_1.default.prompt([
|
|
66
|
-
{
|
|
67
|
-
type: "input",
|
|
68
|
-
name: "distFile",
|
|
69
|
-
message: "文件路径 (逗号分隔):",
|
|
70
|
-
default: def === null || def === void 0 ? void 0 : def.distFile,
|
|
71
|
-
filter: pathFilter,
|
|
72
|
-
},
|
|
73
|
-
]);
|
|
74
|
-
distFile = ans.distFile;
|
|
75
|
-
}
|
|
76
|
-
// dir 模式下 distFile 应为 undefined, 且我们需要返回 distName
|
|
77
|
-
// file 模式下 distName 可以为空或根据需求设置
|
|
38
|
+
const artifactKind = build.artifactKind;
|
|
39
|
+
const { artifactPaths } = yield inquirer_1.default.prompt([
|
|
40
|
+
{
|
|
41
|
+
type: "input",
|
|
42
|
+
name: "artifactPaths",
|
|
43
|
+
message: artifactKind === "directory" ? "产物文件夹:" : "产物文件路径 (逗号分隔):",
|
|
44
|
+
default: ((_c = (_b = def === null || def === void 0 ? void 0 : def.artifact) === null || _b === void 0 ? void 0 : _b.paths) === null || _c === void 0 ? void 0 : _c.join(", ")) || "dist",
|
|
45
|
+
filter: pathFilter,
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
const paths = String(artifactPaths)
|
|
49
|
+
.split(",")
|
|
50
|
+
.map((value) => value.trim())
|
|
51
|
+
.filter(Boolean);
|
|
78
52
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
53
|
+
testCommand: String(build.testCommand).trim(),
|
|
54
|
+
productionCommand: (def === null || def === void 0 ? void 0 : def.productionCommand) || null,
|
|
55
|
+
nodeVersion: String(build.nodeVersion).trim() || null,
|
|
56
|
+
artifact: {
|
|
57
|
+
kind: artifactKind,
|
|
58
|
+
paths: paths.length > 0 ? paths : ["dist"],
|
|
59
|
+
},
|
|
83
60
|
};
|
|
84
61
|
});
|
|
85
62
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
*/
|
|
90
|
-
static promptTestConfig(def) {
|
|
91
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
92
|
-
const pathFilter = (input) => input.trim().replace(/^['"]|['"]$/g, "");
|
|
93
|
-
// 如果有默认值且 path 不为空,说明之前配置过 test,默认选 true
|
|
94
|
-
const defaultHasTest = !!(def === null || def === void 0 ? void 0 : def.path);
|
|
95
|
-
const { hasTest } = yield inquirer_1.default.prompt([
|
|
96
|
-
{
|
|
97
|
-
type: "confirm",
|
|
98
|
-
name: "hasTest",
|
|
99
|
-
message: "配置测试环境同步?",
|
|
100
|
-
default: defaultHasTest,
|
|
101
|
-
},
|
|
102
|
-
]);
|
|
103
|
-
if (!hasTest) {
|
|
104
|
-
return { path: "", branch: "" };
|
|
105
|
-
}
|
|
106
|
-
return inquirer_1.default.prompt([
|
|
63
|
+
static promptTestEnvironment(def_1) {
|
|
64
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* (def, servers = ConfigManager_1.ConfigManager.servers) {
|
|
65
|
+
const environment = yield inquirer_1.default.prompt([
|
|
107
66
|
{
|
|
108
67
|
type: "input",
|
|
109
|
-
name: "
|
|
110
|
-
message: "
|
|
111
|
-
default: def === null || def === void 0 ? void 0 : def.
|
|
68
|
+
name: "workspacePath",
|
|
69
|
+
message: "测试工作区路径:",
|
|
70
|
+
default: def === null || def === void 0 ? void 0 : def.workspacePath,
|
|
112
71
|
filter: pathFilter,
|
|
113
72
|
},
|
|
114
73
|
{
|
|
@@ -117,173 +76,147 @@ class ConfigPrompter {
|
|
|
117
76
|
message: "测试分支:",
|
|
118
77
|
default: (def === null || def === void 0 ? void 0 : def.branch) || "dev",
|
|
119
78
|
},
|
|
79
|
+
{
|
|
80
|
+
type: "input",
|
|
81
|
+
name: "relativePath",
|
|
82
|
+
message: "产物复制到工作区内的相对目录 (可选):",
|
|
83
|
+
default: (def === null || def === void 0 ? void 0 : def.relativePath) || "",
|
|
84
|
+
filter: pathFilter,
|
|
85
|
+
},
|
|
120
86
|
]);
|
|
87
|
+
const remote = yield this.promptRemoteConfig(def, servers, "测试");
|
|
88
|
+
return Object.assign({ workspacePath: environment.workspacePath, branch: String(environment.branch).trim(), relativePath: environment.relativePath || null }, remote);
|
|
121
89
|
});
|
|
122
90
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
*/
|
|
127
|
-
static promptServerConfig(def) {
|
|
128
|
-
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
129
|
-
const pathFilter = (input) => input.trim().replace(/^['"]|['"]$/g, "");
|
|
130
|
-
// 1. 获取已有的唯一路径列表
|
|
131
|
-
const existingPaths = Array.from(new Set(ConfigManager_1.ConfigManager.config["git-list"].map((t) => { var _a; return (_a = t.server) === null || _a === void 0 ? void 0 : _a.remotePath; }).filter((p) => !!p && p.trim().length > 0)));
|
|
132
|
-
let finalPath = "";
|
|
133
|
-
const currentPath = def === null || def === void 0 ? void 0 : def.remotePath;
|
|
134
|
-
// 2. 如果存在已有路径,给予用户选择权
|
|
135
|
-
if (existingPaths.length > 0) {
|
|
136
|
-
const { mode } = yield inquirer_1.default.prompt([
|
|
137
|
-
{
|
|
138
|
-
type: "list",
|
|
139
|
-
name: "mode",
|
|
140
|
-
message: "服务器部署路径:",
|
|
141
|
-
choices: [
|
|
142
|
-
{ name: "✨ 输入/编辑路径", value: "new" },
|
|
143
|
-
{ name: "📂 选择已有路径", value: "existing" },
|
|
144
|
-
],
|
|
145
|
-
},
|
|
146
|
-
]);
|
|
147
|
-
if (mode === "existing") {
|
|
148
|
-
// 如果当前有默认值,尝试在列表中定位索引作为默认选中项
|
|
149
|
-
let defaultIndex = 0;
|
|
150
|
-
if (currentPath) {
|
|
151
|
-
const idx = existingPaths.indexOf(currentPath);
|
|
152
|
-
if (idx !== -1)
|
|
153
|
-
defaultIndex = idx;
|
|
154
|
-
}
|
|
155
|
-
const result = yield inquirer_1.default.prompt([
|
|
156
|
-
{
|
|
157
|
-
type: "list",
|
|
158
|
-
name: "selectedPath",
|
|
159
|
-
message: "请选择已有路径:",
|
|
160
|
-
choices: existingPaths,
|
|
161
|
-
default: defaultIndex,
|
|
162
|
-
},
|
|
163
|
-
]);
|
|
164
|
-
finalPath = result.selectedPath;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// 3. 如果没选或者选择了新建,则手动输入
|
|
168
|
-
if (!finalPath) {
|
|
169
|
-
const result = yield inquirer_1.default.prompt([
|
|
170
|
-
{
|
|
171
|
-
type: "input",
|
|
172
|
-
name: "remotePath",
|
|
173
|
-
message: existingPaths.length > 0 ? "请输入/编辑服务器部署路径:" : "服务器部署路径:",
|
|
174
|
-
default: currentPath,
|
|
175
|
-
filter: pathFilter,
|
|
176
|
-
validate: (input) => !!input || "路径不能为空",
|
|
177
|
-
},
|
|
178
|
-
]);
|
|
179
|
-
finalPath = result.remotePath;
|
|
180
|
-
}
|
|
181
|
-
// 4. 选择 SSH 配置模式
|
|
182
|
-
const servers = ConfigManager_1.ConfigManager.config.servers || [];
|
|
183
|
-
// 计算默认选中项
|
|
184
|
-
let defaultMode = "global";
|
|
185
|
-
if ((def === null || def === void 0 ? void 0 : def.serverName) && servers.some((s) => s.name === def.serverName)) {
|
|
186
|
-
defaultMode = `preset:${def.serverName}`;
|
|
187
|
-
}
|
|
188
|
-
else if (def === null || def === void 0 ? void 0 : def.host) {
|
|
189
|
-
defaultMode = "custom";
|
|
190
|
-
}
|
|
191
|
-
const { configMode } = yield inquirer_1.default.prompt([
|
|
91
|
+
static promptProductionConfig(def_1, testEnvironment_1, productionCommand_1) {
|
|
92
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* (def, testEnvironment, productionCommand, servers = ConfigManager_1.ConfigManager.servers) {
|
|
93
|
+
const { enabled } = yield inquirer_1.default.prompt([
|
|
192
94
|
{
|
|
193
|
-
type: "
|
|
194
|
-
name: "
|
|
195
|
-
message: "
|
|
196
|
-
|
|
197
|
-
{ name: "🌍 使用全局默认配置", value: "global" },
|
|
198
|
-
...(servers.length > 0
|
|
199
|
-
? [new inquirer_1.default.Separator("--- 预设服务器 ---"), ...servers.map((s) => ({ name: `🖥 ${s.name} (${s.host})`, value: `preset:${s.name}` }))]
|
|
200
|
-
: []),
|
|
201
|
-
new inquirer_1.default.Separator("--- 自定义 ---"),
|
|
202
|
-
{ name: "✏️ 单独/新建配置 (Custom)", value: "custom" },
|
|
203
|
-
],
|
|
204
|
-
default: defaultMode,
|
|
95
|
+
type: "confirm",
|
|
96
|
+
name: "enabled",
|
|
97
|
+
message: "配置生产环境同步?",
|
|
98
|
+
default: Boolean(def),
|
|
205
99
|
},
|
|
206
100
|
]);
|
|
207
|
-
if (
|
|
208
|
-
return {
|
|
209
|
-
|
|
210
|
-
if (configMode.startsWith("preset:")) {
|
|
211
|
-
const presetName = configMode.split(":")[1];
|
|
212
|
-
return { remotePath: finalPath, serverName: presetName };
|
|
213
|
-
}
|
|
214
|
-
// Custom Mode
|
|
215
|
-
const sshConfig = yield inquirer_1.default.prompt([
|
|
101
|
+
if (!enabled)
|
|
102
|
+
return { environment: null, productionCommand: null };
|
|
103
|
+
const production = yield inquirer_1.default.prompt([
|
|
216
104
|
{
|
|
217
105
|
type: "input",
|
|
218
|
-
name: "
|
|
219
|
-
message: "
|
|
220
|
-
default: def === null || def === void 0 ? void 0 : def.
|
|
221
|
-
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
type: "number",
|
|
225
|
-
name: "port",
|
|
226
|
-
message: "端口 (Port):",
|
|
227
|
-
default: (def === null || def === void 0 ? void 0 : def.port) || 22,
|
|
106
|
+
name: "workspacePath",
|
|
107
|
+
message: "生产工作区路径:",
|
|
108
|
+
default: (def === null || def === void 0 ? void 0 : def.workspacePath) || (testEnvironment === null || testEnvironment === void 0 ? void 0 : testEnvironment.workspacePath),
|
|
109
|
+
filter: pathFilter,
|
|
228
110
|
},
|
|
229
111
|
{
|
|
230
112
|
type: "input",
|
|
231
|
-
name: "
|
|
232
|
-
message: "
|
|
233
|
-
default: (def === null || def === void 0 ? void 0 : def.
|
|
234
|
-
validate: (input) => !!input || "用户名不能为空",
|
|
113
|
+
name: "branch",
|
|
114
|
+
message: "生产分支:",
|
|
115
|
+
default: (def === null || def === void 0 ? void 0 : def.branch) || "master",
|
|
235
116
|
},
|
|
236
117
|
{
|
|
237
|
-
type: "
|
|
238
|
-
name: "
|
|
239
|
-
message: "
|
|
240
|
-
default: def === null || def === void 0 ? void 0 : def.
|
|
118
|
+
type: "input",
|
|
119
|
+
name: "relativePath",
|
|
120
|
+
message: "产物复制到生产工作区内的相对目录 (可选):",
|
|
121
|
+
default: (def === null || def === void 0 ? void 0 : def.relativePath) || (testEnvironment === null || testEnvironment === void 0 ? void 0 : testEnvironment.relativePath) || "",
|
|
122
|
+
filter: pathFilter,
|
|
241
123
|
},
|
|
242
124
|
{
|
|
243
125
|
type: "input",
|
|
244
|
-
name: "
|
|
245
|
-
message: "
|
|
246
|
-
default:
|
|
126
|
+
name: "productionCommand",
|
|
127
|
+
message: "生产打包命令 (可选,留空则使用测试命令):",
|
|
128
|
+
default: productionCommand || "",
|
|
247
129
|
},
|
|
130
|
+
]);
|
|
131
|
+
const remote = yield this.promptRemoteConfig(def || undefined, servers, "生产");
|
|
132
|
+
return {
|
|
133
|
+
environment: Object.assign({ workspacePath: production.workspacePath, branch: String(production.branch).trim(), relativePath: production.relativePath || null }, remote),
|
|
134
|
+
productionCommand: String(production.productionCommand).trim() || null,
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
static promptRemoteConfig(def, servers, label) {
|
|
139
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
140
|
+
const configuredServer = servers.find((server) => server.id === (def === null || def === void 0 ? void 0 : def.serverId));
|
|
141
|
+
const { mode } = yield inquirer_1.default.prompt([
|
|
248
142
|
{
|
|
249
|
-
type: "
|
|
250
|
-
name: "
|
|
251
|
-
message:
|
|
252
|
-
|
|
143
|
+
type: "list",
|
|
144
|
+
name: "mode",
|
|
145
|
+
message: `${label}环境远程服务器:`,
|
|
146
|
+
choices: [
|
|
147
|
+
{ name: "不执行远程拉取", value: "none" },
|
|
148
|
+
...servers.map((server) => ({
|
|
149
|
+
name: `🖥 ${server.name} (${server.host})`,
|
|
150
|
+
value: `server:${server.id}`,
|
|
151
|
+
})),
|
|
152
|
+
{ name: "➕ 新建服务器预设", value: "create" },
|
|
153
|
+
],
|
|
154
|
+
default: configuredServer ? `server:${configuredServer.id}` : "none",
|
|
253
155
|
},
|
|
254
156
|
]);
|
|
255
|
-
|
|
256
|
-
|
|
157
|
+
if (mode === "none")
|
|
158
|
+
return { serverId: null, remotePath: null };
|
|
159
|
+
let serverId;
|
|
160
|
+
if (mode === "create") {
|
|
161
|
+
const server = yield this.promptNewServer(servers);
|
|
162
|
+
const shouldPersist = servers === ConfigManager_1.ConfigManager.servers;
|
|
163
|
+
if (shouldPersist)
|
|
164
|
+
ConfigManager_1.ConfigManager.upsertServer(server);
|
|
165
|
+
else
|
|
166
|
+
servers.push(server);
|
|
167
|
+
serverId = server.id;
|
|
168
|
+
console.log(`✅ 已保存服务器预设: ${server.name}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
serverId = String(mode).slice("server:".length);
|
|
172
|
+
}
|
|
173
|
+
const { remotePath } = yield inquirer_1.default.prompt([
|
|
257
174
|
{
|
|
258
|
-
type: "
|
|
259
|
-
name: "
|
|
260
|
-
message:
|
|
261
|
-
default:
|
|
175
|
+
type: "input",
|
|
176
|
+
name: "remotePath",
|
|
177
|
+
message: `${label}服务器上的项目目录:`,
|
|
178
|
+
default: def === null || def === void 0 ? void 0 : def.remotePath,
|
|
179
|
+
filter: pathFilter,
|
|
180
|
+
validate: (input) => Boolean(input) || "远程目录不能为空",
|
|
262
181
|
},
|
|
263
182
|
]);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
183
|
+
return { serverId, remotePath };
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
static promptNewServer(servers) {
|
|
187
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
188
|
+
const server = yield inquirer_1.default.prompt([
|
|
189
|
+
{
|
|
190
|
+
type: "input",
|
|
191
|
+
name: "name",
|
|
192
|
+
message: "服务器名称:",
|
|
193
|
+
validate: (input) => {
|
|
194
|
+
if (!input.trim())
|
|
195
|
+
return "服务器名称不能为空";
|
|
196
|
+
return servers.some((item) => item.name === input.trim()) ? "服务器名称已存在" : true;
|
|
277
197
|
},
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
198
|
+
},
|
|
199
|
+
{ type: "input", name: "host", message: "服务器地址:", validate: (input) => Boolean(input) || "服务器地址不能为空" },
|
|
200
|
+
{ type: "number", name: "port", message: "SSH 端口:", default: 22 },
|
|
201
|
+
{ type: "input", name: "username", message: "SSH 用户名:", default: "root", validate: (input) => Boolean(input) || "用户名不能为空" },
|
|
202
|
+
{ type: "password", name: "password", message: "SSH 密码 (可选):" },
|
|
203
|
+
{ type: "input", name: "sshPrivateKey", message: "SSH 私钥路径 (可选):", filter: pathFilter },
|
|
204
|
+
{ type: "confirm", name: "agentForward", message: "转发本机 SSH Agent (-A):", default: false },
|
|
205
|
+
{ type: "input", name: "gitUsername", message: "远程 Git HTTPS 用户名 (可选):" },
|
|
206
|
+
{ type: "password", name: "gitPassword", message: "远程 Git HTTPS 密码或 Token (可选):" },
|
|
207
|
+
]);
|
|
208
|
+
return {
|
|
209
|
+
id: (0, crypto_1.randomUUID)(),
|
|
210
|
+
name: String(server.name).trim(),
|
|
211
|
+
host: String(server.host).trim(),
|
|
212
|
+
port: Number(server.port) || 22,
|
|
213
|
+
username: String(server.username).trim(),
|
|
214
|
+
password: server.password || null,
|
|
215
|
+
sshPrivateKey: server.sshPrivateKey || null,
|
|
216
|
+
agentForward: Boolean(server.agentForward),
|
|
217
|
+
gitUsername: String(server.gitUsername || "").trim() || null,
|
|
218
|
+
gitPassword: server.gitPassword || null,
|
|
219
|
+
};
|
|
287
220
|
});
|
|
288
221
|
}
|
|
289
222
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runCommand = runCommand;
|
|
4
|
+
exports.runInteractiveCommand = runInteractiveCommand;
|
|
4
5
|
const tslib_1 = require("tslib");
|
|
5
6
|
const child_process_1 = require("child_process");
|
|
6
7
|
const util_1 = tslib_1.__importDefault(require("util"));
|
|
@@ -32,3 +33,20 @@ function runCommand(command, cwd, stepName) {
|
|
|
32
33
|
}
|
|
33
34
|
});
|
|
34
35
|
}
|
|
36
|
+
/** 执行需要直接使用当前终端输入输出的命令。 */
|
|
37
|
+
function runInteractiveCommand(executable, args, cwd) {
|
|
38
|
+
return new Promise((resolve, reject) => {
|
|
39
|
+
const child = (0, child_process_1.spawn)(executable, args, { cwd, stdio: "inherit" });
|
|
40
|
+
child.once("error", reject);
|
|
41
|
+
child.once("close", (code, signal) => {
|
|
42
|
+
if (code === 0) {
|
|
43
|
+
resolve();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const error = new Error(signal ? `${executable} 被信号 ${signal} 终止` : `${executable} 退出,状态码: ${code !== null && code !== void 0 ? code : 1}`);
|
|
47
|
+
error.code = code !== null && code !== void 0 ? code : 1;
|
|
48
|
+
error.signal = signal;
|
|
49
|
+
reject(error);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|