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.
@@ -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
- const buildAnswers = yield inquirer_1.default.prompt([
13
+ var _a, _b, _c;
14
+ const build = yield inquirer_1.default.prompt([
19
15
  {
20
16
  type: "input",
21
- name: "buildCmd",
22
- message: "打包命令:",
23
- default: (def === null || def === void 0 ? void 0 : def.cmd) || "npm run build",
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: "type",
29
+ name: "artifactKind",
41
30
  message: "构建产物类型:",
42
31
  choices: [
43
- { name: "📂 文件夹 (例如 dist)", value: "dir" },
44
- { name: "📄 文件 (例如 bundle.js)", value: "file" },
32
+ { name: "📂 文件夹内容 (例如 dist)", value: "directory" },
33
+ { name: "📄 指定文件 (例如 dist/bundle.js)", value: "files" },
45
34
  ],
46
- default: defaultType,
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
- let distName = "";
50
- let distFile = undefined;
51
- const pathFilter = (input) => input.trim().replace(/^['"]|['"]$/g, "");
52
- if (type === "dir") {
53
- const ans = yield inquirer_1.default.prompt([
54
- {
55
- type: "input",
56
- name: "distName",
57
- message: "文件夹名称:",
58
- default: (def === null || def === void 0 ? void 0 : def.distName) || "dist",
59
- filter: pathFilter,
60
- },
61
- ]);
62
- distName = ans.distName;
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
- cmd: buildAnswers.buildCmd,
80
- nodeVersion: buildAnswers.nodeVersion,
81
- distName: type === "dir" ? distName : undefined,
82
- distFile,
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
- * @param def 可选的默认值 (用于 Update 场景)
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: "path",
110
- message: "测试项目路径:",
111
- default: def === null || def === void 0 ? void 0 : def.path,
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
- * @param def 可选的默认值 (用于 Update 场景)
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: "list",
194
- name: "configMode",
195
- message: "SSH 连接配置模式:",
196
- choices: [
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 (configMode === "global") {
208
- return { remotePath: finalPath };
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: "host",
219
- message: "服务器地址 (Host):",
220
- default: def === null || def === void 0 ? void 0 : def.host,
221
- validate: (input) => !!input || "服务器地址不能为空",
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: "user",
232
- message: "用户名 (User):",
233
- default: (def === null || def === void 0 ? void 0 : def.user) || "root",
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: "password",
238
- name: "password",
239
- message: "登录密码 (可选):",
240
- default: def === null || def === void 0 ? void 0 : def.password,
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: "gitUser",
245
- message: "Git 用户名 (用于远程 Pull, 可选):",
246
- default: def === null || def === void 0 ? void 0 : def.gitUser,
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: "password",
250
- name: "gitPassword",
251
- message: "Git 密码/Token (用于远程 Pull, 可选):",
252
- default: def === null || def === void 0 ? void 0 : def.gitPassword,
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
- const { saveAsPreset } = yield inquirer_1.default.prompt([
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: "confirm",
259
- name: "saveAsPreset",
260
- message: "是否将此配置保存为常用服务器预设?",
261
- default: false,
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
- if (saveAsPreset) {
265
- const { presetName } = yield inquirer_1.default.prompt([
266
- {
267
- type: "input",
268
- name: "presetName",
269
- message: "预设名称 (例如 Aliyun-Prod):",
270
- validate: (input) => {
271
- if (!input)
272
- return "名称不能为空";
273
- if (servers.some((s) => s.name === input))
274
- return "该名称已存在";
275
- return true;
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
- if (!ConfigManager_1.ConfigManager.config.servers)
281
- ConfigManager_1.ConfigManager.config.servers = [];
282
- ConfigManager_1.ConfigManager.config.servers.push(Object.assign({ name: presetName }, sshConfig));
283
- console.log(`✅ 已保存服务器预设: ${presetName}`);
284
- return { remotePath: finalPath, serverName: presetName };
285
- }
286
- return Object.assign({ remotePath: finalPath }, sshConfig);
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
+ }