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.
@@ -7,192 +7,124 @@ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
7
  const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
8
8
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
9
9
  const path_1 = tslib_1.__importDefault(require("path"));
10
- /**
11
- * 外部配置导入命令
12
- */
10
+ /** 导入新的 projects.json 或 servers.json。 */
13
11
  class LoadCommand extends BaseCommand_1.default {
14
- /**
15
- * 初始化命令配置
16
- */
17
12
  init() {
18
- this.program.command("load [filePath]").description("从外部 JSON 文件加载/导入配置").action(this.action.bind(this));
13
+ this.program.command("load [filePath]").description("导入 projects.json 或 servers.json").action(this.action.bind(this));
19
14
  }
20
- /**
21
- * 执行核心逻辑
22
- * @param filePath 外部文件路径
23
- */
24
15
  action(filePath) {
25
16
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
26
- const targetFile = this.resolveTargetFile(filePath);
17
+ const targetFile = filePath ? path_1.default.resolve(process.cwd(), filePath) : path_1.default.join(process.cwd(), "projects.json");
27
18
  if (!fs_extra_1.default.existsSync(targetFile)) {
28
19
  console.log(chalk_1.default.red(`❌ 找不到配置文件: ${targetFile}`));
29
20
  return;
30
21
  }
31
- const loadedConfig = yield this.readAndValidateConfig(targetFile);
32
- if (!loadedConfig)
22
+ const document = this.readAndValidateConfig(targetFile);
23
+ if (!document)
33
24
  return;
34
- this.printConfigSummary(targetFile, loadedConfig);
25
+ const kind = "projects" in document ? "projects" : "servers";
26
+ const count = "projects" in document ? document.projects.length : document.servers.length;
27
+ console.log(chalk_1.default.blue(`📦 成功读取 ${kind}.json: ${targetFile}`));
28
+ console.log(chalk_1.default.dim(` - 包含记录数: ${count}`));
35
29
  if (!ConfigManager_1.ConfigManager.existsOnDisk()) {
36
- this.initConfigDirectly(loadedConfig);
30
+ ConfigManager_1.ConfigManager.ensureOnDisk();
31
+ this.overwrite(document);
32
+ console.log(chalk_1.default.green("\n✅ 部署配置初始化成功!"));
37
33
  return;
38
34
  }
39
- const strategy = yield this.promptImportStrategy();
35
+ const strategy = yield this.promptImportStrategy(kind);
40
36
  if (strategy === "cancel")
41
37
  return;
42
38
  if (strategy === "overwrite") {
43
- this.overwriteConfig(loadedConfig);
44
- }
45
- else {
46
- yield this.mergeConfig(loadedConfig);
39
+ this.overwrite(document);
40
+ console.log(chalk_1.default.green(`\n✅ ${kind}.json 已覆盖!`));
41
+ return;
47
42
  }
43
+ yield this.merge(document);
48
44
  });
49
45
  }
50
- /**
51
- * 解析目标文件绝对路径
52
- */
53
- resolveTargetFile(filePath) {
54
- if (!filePath) {
55
- return path_1.default.join(process.cwd(), ".maolike", "config.json");
56
- }
57
- return path_1.default.resolve(process.cwd(), filePath);
58
- }
59
- /**
60
- * 读取并校验配置文件内容
61
- */
62
46
  readAndValidateConfig(targetFile) {
63
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
64
- try {
65
- const loadedConfig = yield fs_extra_1.default.readJson(targetFile);
66
- if (!loadedConfig["git-list"] && !loadedConfig.ssh) {
67
- throw new Error("JSON 缺少必要的 'git-list' 或 'ssh' 字段");
68
- }
69
- return loadedConfig;
70
- }
71
- catch (e) {
72
- console.log(chalk_1.default.red(`❌ 配置文件格式错误: ${e.message}`));
73
- return null;
47
+ try {
48
+ const document = fs_extra_1.default.readJsonSync(targetFile);
49
+ if (document.schemaVersion !== 1) {
50
+ throw new Error(`不支持的 schemaVersion: ${document.schemaVersion}`);
74
51
  }
75
- });
76
- }
77
- /**
78
- * 打印导入配置概览
79
- */
80
- printConfigSummary(targetFile, config) {
81
- var _a;
82
- console.log(chalk_1.default.blue(`📦 成功读取配置: ${targetFile}`));
83
- const itemCount = ((_a = config["git-list"]) === null || _a === void 0 ? void 0 : _a.length) || 0;
84
- console.log(chalk_1.default.dim(` - 包含项目数: ${itemCount}`));
85
- if (config.ssh) {
86
- console.log(chalk_1.default.dim(` - 包含 SSH 配置: ${config.ssh.host}`));
52
+ if ("projects" in document && Array.isArray(document.projects))
53
+ return document;
54
+ if ("servers" in document && Array.isArray(document.servers))
55
+ return document;
56
+ throw new Error("JSON 必须包含 projects 或 servers 数组");
57
+ }
58
+ catch (error) {
59
+ console.log(chalk_1.default.red(`❌ 配置文件格式错误: ${error.message}`));
60
+ return null;
87
61
  }
88
62
  }
89
- /**
90
- * 直接初始化配置(当本地无配置时)
91
- */
92
- initConfigDirectly(loadedConfig) {
93
- ConfigManager_1.ConfigManager.config["git-list"] = loadedConfig["git-list"] || [];
94
- if (loadedConfig.ssh)
95
- ConfigManager_1.ConfigManager.config.ssh = loadedConfig.ssh;
96
- console.log(chalk_1.default.green(`\n✅ 全局配置初始化成功!`));
97
- }
98
- /**
99
- * 交互式询问导入策略
100
- */
101
- promptImportStrategy() {
63
+ promptImportStrategy(kind) {
102
64
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
103
65
  const { strategy } = yield inquirer_1.default.prompt([
104
66
  {
105
67
  type: "list",
106
68
  name: "strategy",
107
- message: "请选择导入策略:",
69
+ message: `请选择 ${kind}.json 导入策略:`,
108
70
  choices: [
109
- { name: "➕ 合并 (Merge) - 追加项目,同名覆盖", value: "merge" },
110
- { name: "💥 覆盖 (Overwrite) - 完全替换现有全局配置", value: "overwrite" },
111
- { name: "❌ 取消 (Cancel)", value: "cancel" },
71
+ { name: "➕ 合并 - 同 ID 时逐项确认", value: "merge" },
72
+ { name: "💥 覆盖 - 完全替换当前文件", value: "overwrite" },
73
+ { name: "❌ 取消", value: "cancel" },
112
74
  ],
113
75
  },
114
76
  ]);
115
77
  return strategy;
116
78
  });
117
79
  }
118
- /**
119
- * 覆盖现有全局配置
120
- */
121
- overwriteConfig(loadedConfig) {
122
- const currentConfig = ConfigManager_1.ConfigManager.config;
123
- if (loadedConfig.ssh)
124
- currentConfig.ssh = loadedConfig.ssh;
125
- if (loadedConfig["git-list"])
126
- currentConfig["git-list"] = loadedConfig["git-list"];
127
- console.log(chalk_1.default.green(`\n✅ 全局配置已覆盖!`));
80
+ overwrite(document) {
81
+ if ("projects" in document)
82
+ ConfigManager_1.ConfigManager.replaceProjects(document.projects);
83
+ else
84
+ ConfigManager_1.ConfigManager.replaceServers(document.servers);
128
85
  }
129
- /**
130
- * 合并配置逻辑
131
- */
132
- mergeConfig(loadedConfig) {
86
+ merge(document) {
133
87
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
134
- const currentConfig = ConfigManager_1.ConfigManager.config;
135
- // 是否合并 SSH
136
- if (loadedConfig.ssh) {
137
- const { mergeSsh } = yield inquirer_1.default.prompt([
88
+ if ("projects" in document) {
89
+ const projects = yield this.mergeItems(document.projects, [...ConfigManager_1.ConfigManager.projects], "项目");
90
+ ConfigManager_1.ConfigManager.replaceProjects(projects);
91
+ }
92
+ else {
93
+ const servers = yield this.mergeItems(document.servers, [...ConfigManager_1.ConfigManager.servers], "服务器");
94
+ ConfigManager_1.ConfigManager.replaceServers(servers);
95
+ }
96
+ });
97
+ }
98
+ mergeItems(incoming, current, label) {
99
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
100
+ let added = 0;
101
+ let updated = 0;
102
+ let skipped = 0;
103
+ for (const item of incoming) {
104
+ const index = current.findIndex((existing) => existing.id === item.id);
105
+ if (index === -1) {
106
+ current.push(item);
107
+ added++;
108
+ continue;
109
+ }
110
+ const { overwrite } = yield inquirer_1.default.prompt([
138
111
  {
139
112
  type: "confirm",
140
- name: "mergeSsh",
141
- message: "检测到 SSH 配置,是否覆盖当前全局 SSH 设置?",
113
+ name: "overwrite",
114
+ message: `${label} [${item.name}] ID 已存在,是否覆盖?`,
142
115
  default: false,
143
116
  },
144
117
  ]);
145
- if (mergeSsh)
146
- currentConfig.ssh = loadedConfig.ssh;
147
- }
148
- const newItems = loadedConfig["git-list"] || [];
149
- let added = 0;
150
- let updated = 0;
151
- let skipped = 0;
152
- for (const item of newItems) {
153
- const index = currentConfig["git-list"].findIndex((t) => t.name === item.name);
154
- if (index !== -1) {
155
- const conflictAction = yield this.handleConflict(item, currentConfig["git-list"][index]);
156
- if (conflictAction === "overwrite") {
157
- currentConfig["git-list"][index] = item;
158
- updated++;
159
- console.log(chalk_1.default.yellow(` -> 已覆盖 [${item.name}]`));
160
- }
161
- else {
162
- skipped++;
163
- console.log(chalk_1.default.dim(` -> 已跳过 [${item.name}]`));
164
- }
118
+ if (overwrite) {
119
+ current[index] = item;
120
+ updated++;
165
121
  }
166
122
  else {
167
- currentConfig["git-list"].push(item);
168
- added++;
123
+ skipped++;
169
124
  }
170
125
  }
171
126
  console.log(chalk_1.default.green(`\n✅ 合并完成: 新增 ${added}, 更新 ${updated}, 跳过 ${skipped}。`));
172
- });
173
- }
174
- /**
175
- * 处理项目同名冲突
176
- */
177
- handleConflict(newItem, existingItem) {
178
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
179
- console.log(chalk_1.default.yellow(`\n⚠️ 发现同名冲突: [${newItem.name}]`));
180
- console.log(chalk_1.default.bold(" 当前配置 (Local):"));
181
- console.log(chalk_1.default.dim(JSON.stringify(existingItem, null, 2).replace(/^/gm, " ")));
182
- console.log(chalk_1.default.bold(" 导入配置 (Incoming):"));
183
- console.log(chalk_1.default.cyan(JSON.stringify(newItem, null, 2).replace(/^/gm, " ")));
184
- const { conflictAction } = yield inquirer_1.default.prompt([
185
- {
186
- type: "list",
187
- name: "conflictAction",
188
- message: "请选择操作:",
189
- choices: [
190
- { name: "🔒 保留当前配置 (Keep Local)", value: "keep" },
191
- { name: "🔄 使用导入配置覆盖 (Overwrite)", value: "overwrite" },
192
- ],
193
- },
194
- ]);
195
- return conflictAction;
127
+ return current;
196
128
  });
197
129
  }
198
130
  }
@@ -15,6 +15,9 @@ var CommandType;
15
15
  CommandType["VIEW"] = "view";
16
16
  CommandType["LOAD"] = "load";
17
17
  CommandType["TEST"] = "test";
18
+ CommandType["DEPLOY"] = "deploy";
19
+ CommandType["CONNECT"] = "connect";
20
+ CommandType["CURSOR"] = "cursor";
18
21
  CommandType["HELP"] = "help";
19
22
  })(CommandType || (exports.CommandType = CommandType = {}));
20
23
  /**
@@ -54,7 +57,7 @@ exports.COMMAND_REGISTRY = {
54
57
  },
55
58
  [CommandType.LOAD]: {
56
59
  command: CommandType.LOAD,
57
- description: "从文件导入/合并配置",
60
+ description: "导入 projects.json 或 servers.json",
58
61
  usage: "ml load [file]",
59
62
  },
60
63
  [CommandType.TEST]: {
@@ -62,6 +65,21 @@ exports.COMMAND_REGISTRY = {
62
65
  description: "构建并部署指定项目",
63
66
  usage: "ml test <name>",
64
67
  },
68
+ [CommandType.DEPLOY]: {
69
+ command: CommandType.DEPLOY,
70
+ description: "部署项目到生产环境",
71
+ usage: "ml deploy <name> [options]",
72
+ },
73
+ [CommandType.CONNECT]: {
74
+ command: CommandType.CONNECT,
75
+ description: "选择服务器并打开 SSH 连接",
76
+ usage: "ml connect",
77
+ },
78
+ [CommandType.CURSOR]: {
79
+ command: CommandType.CURSOR,
80
+ description: "重新安装 Cursor++",
81
+ usage: "ml cursor",
82
+ },
65
83
  [CommandType.HELP]: {
66
84
  command: CommandType.HELP,
67
85
  description: "显示此帮助信息",
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BaseExecutor = void 0;
4
+ exports.targetRootWithRelativePath = targetRootWithRelativePath;
5
+ const tslib_1 = require("tslib");
6
+ const path_1 = tslib_1.__importDefault(require("path"));
7
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
8
+ const ora_1 = tslib_1.__importDefault(require("ora"));
9
+ const child_process_1 = require("child_process");
10
+ const RemoteServer_1 = require("../../utils/RemoteServer");
11
+ /** 计算目标目录,并禁止 relativePath 跳出工作区。 */
12
+ function targetRootWithRelativePath(root, relativePath) {
13
+ const value = relativePath === null || relativePath === void 0 ? void 0 : relativePath.trim();
14
+ if (!value)
15
+ return root;
16
+ if (path_1.default.isAbsolute(value))
17
+ throw new Error(`相对路径不能是绝对路径: ${value}`);
18
+ const parts = value.split(/[\\/]+/).filter((part) => part && part !== ".");
19
+ if (parts.includes(".."))
20
+ throw new Error(`相对路径不能跳出目标仓库: ${value}`);
21
+ return path_1.default.join(root, ...parts);
22
+ }
23
+ /** 测试与生产流水线的公共构建、产物和远程拉取逻辑。 */
24
+ class BaseExecutor {
25
+ constructor(task, servers) {
26
+ this.task = task;
27
+ this.servers = servers;
28
+ this.repoPath = task.localPath;
29
+ this.repoName = task.name;
30
+ }
31
+ executeBuild() {
32
+ return tslib_1.__awaiter(this, arguments, void 0, function* (useProductionCommand = false) {
33
+ var _a, _b;
34
+ const configuredCommand = useProductionCommand
35
+ ? ((_a = this.task.build.productionCommand) === null || _a === void 0 ? void 0 : _a.trim()) || this.task.build.testCommand.trim()
36
+ : this.task.build.testCommand.trim();
37
+ if (!configuredCommand)
38
+ return;
39
+ const spinner = (0, ora_1.default)(`执行构建: ${configuredCommand}`).start();
40
+ let finalCommand = configuredCommand;
41
+ const nodeVersion = (_b = this.task.build.nodeVersion) === null || _b === void 0 ? void 0 : _b.trim();
42
+ if (nodeVersion) {
43
+ spinner.info(chalk_1.default.blue(`配置了 Node 版本 (${nodeVersion}),尝试切换 (若缺失则自动安装)...`));
44
+ if (process.platform === "win32") {
45
+ finalCommand = `(nvm use ${nodeVersion} || nvm install ${nodeVersion}) && nvm use ${nodeVersion} && ${configuredCommand}`;
46
+ }
47
+ else {
48
+ const shell = process.env.SHELL || "/bin/bash";
49
+ const escapedCommand = configuredCommand.replace(/"/g, '\\"');
50
+ finalCommand = `${shell} -i -c "(nvm use ${nodeVersion} || nvm install ${nodeVersion}) && nvm use ${nodeVersion} && ${escapedCommand}"`;
51
+ }
52
+ }
53
+ let outputBuffer = "";
54
+ try {
55
+ yield new Promise((resolve, reject) => {
56
+ var _a, _b;
57
+ const child = (0, child_process_1.spawn)(finalCommand, {
58
+ cwd: this.repoPath,
59
+ shell: true,
60
+ stdio: ["ignore", "pipe", "pipe"],
61
+ });
62
+ const updateSpinner = (data) => {
63
+ const output = data.toString();
64
+ outputBuffer += output;
65
+ const lines = output
66
+ .split("\n")
67
+ .map((line) => line.trim())
68
+ .filter(Boolean);
69
+ const lastLine = lines.at(-1);
70
+ if (lastLine)
71
+ spinner.text = `正在构建... ${lastLine.slice(0, 20)}${lastLine.length > 20 ? "..." : ""}`;
72
+ };
73
+ (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on("data", updateSpinner);
74
+ (_b = child.stderr) === null || _b === void 0 ? void 0 : _b.on("data", updateSpinner);
75
+ child.once("error", reject);
76
+ child.once("close", (code) => (code === 0 ? resolve() : reject(new Error(`Exit code ${code}`))));
77
+ });
78
+ spinner.succeed("构建成功");
79
+ }
80
+ catch (error) {
81
+ spinner.fail(`构建失败: ${error.message}`);
82
+ console.error(chalk_1.default.red("\n--- 错误日志 ---"));
83
+ console.error(outputBuffer);
84
+ console.error(chalk_1.default.red("----------------"));
85
+ throw error;
86
+ }
87
+ });
88
+ }
89
+ prepareArtifacts() {
90
+ const paths = this.task.build.artifact.paths.map((value) => value.trim()).filter(Boolean);
91
+ if (this.task.build.artifact.kind === "files") {
92
+ return paths.map((file) => ({
93
+ src: path_1.default.resolve(this.repoPath, file),
94
+ destName: path_1.default.basename(file),
95
+ isDir: false,
96
+ }));
97
+ }
98
+ const directory = paths[0] || "dist";
99
+ return [{ src: path_1.default.resolve(this.repoPath, directory), destName: "", isDir: true }];
100
+ }
101
+ deployToServer(environment) {
102
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
103
+ var _a, _b;
104
+ const serverId = (_a = environment.serverId) === null || _a === void 0 ? void 0 : _a.trim();
105
+ if (!serverId)
106
+ return;
107
+ const remotePath = (_b = environment.remotePath) === null || _b === void 0 ? void 0 : _b.trim();
108
+ if (!remotePath)
109
+ throw new Error(`服务器 ${serverId} 缺少 remotePath`);
110
+ const profile = this.servers.find((server) => server.id === serverId);
111
+ if (!profile)
112
+ throw new Error(`指定了服务器 \`${serverId}\` 但未找到`);
113
+ const connectSpinner = (0, ora_1.default)(`连接服务器并更新代码: ${profile.host}`).start();
114
+ const server = new RemoteServer_1.RemoteServer(profile);
115
+ try {
116
+ yield server.connect();
117
+ connectSpinner.succeed("SSH 连接成功");
118
+ if (environment.branch.trim()) {
119
+ yield server.exec(`git checkout ${(0, RemoteServer_1.quoteShellValue)(environment.branch.trim())}`, remotePath, `Git Checkout ${environment.branch.trim()}`);
120
+ }
121
+ yield server.exec(server.getGitPullCommand(), remotePath, "Git Pull", { retryAfterAgentReload: true });
122
+ console.log(chalk_1.default.green("\n✅ 远程服务器代码已更新至最新状态"));
123
+ }
124
+ catch (error) {
125
+ if (connectSpinner.isSpinning)
126
+ connectSpinner.fail(`远程操作失败: ${error.message}`);
127
+ throw error;
128
+ }
129
+ finally {
130
+ server.disconnect();
131
+ }
132
+ });
133
+ }
134
+ }
135
+ exports.BaseExecutor = BaseExecutor;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
5
+ const path_1 = tslib_1.__importDefault(require("path"));
6
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const ora_1 = tslib_1.__importDefault(require("ora"));
8
+ const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
9
+ const simple_git_1 = tslib_1.__importDefault(require("simple-git"));
10
+ const BaseExecutor_1 = require("./BaseExecutor");
11
+ /** 生产环境流水线。 */
12
+ class ProdExecutor extends BaseExecutor_1.BaseExecutor {
13
+ constructor(task, servers, options = {}) {
14
+ super(task, servers);
15
+ this.options = options;
16
+ }
17
+ getEnvName() {
18
+ return "生产环境";
19
+ }
20
+ run() {
21
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
22
+ console.log(chalk_1.default.bold.cyan(`\n🚀 启动生产环境流水线: [${this.repoName}]`));
23
+ try {
24
+ if (this.options.skipBuild)
25
+ console.log(chalk_1.default.dim("⏭️ 跳过构建步骤"));
26
+ else
27
+ yield this.executeBuild(true);
28
+ const artifacts = this.prepareArtifacts();
29
+ yield this.syncToProdProject(artifacts);
30
+ const production = this.task.environments.production;
31
+ if (production)
32
+ yield this.deployToServer(production);
33
+ console.log(chalk_1.default.bold.green(`\n✨ [${this.repoName}] 生产环境部署完成!`));
34
+ }
35
+ catch (error) {
36
+ console.error(chalk_1.default.bold.red(`\n❌ 生产环境部署中断: ${error.message}`));
37
+ throw error;
38
+ }
39
+ });
40
+ }
41
+ syncToProdProject(artifacts) {
42
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
43
+ const production = this.task.environments.production;
44
+ if (!production)
45
+ throw new Error("未配置生产环境");
46
+ const workspacePath = production.workspacePath.trim();
47
+ if (!workspacePath)
48
+ throw new Error("未配置生产工作区路径");
49
+ if (!fs_extra_1.default.existsSync(workspacePath))
50
+ throw new Error(`生产环境路径不存在: ${workspacePath}`);
51
+ const git = (0, simple_git_1.default)(workspacePath);
52
+ if (!(yield git.checkIsRepo()))
53
+ throw new Error("生产路径不是 Git 仓库");
54
+ let spinner = (0, ora_1.default)(`切换并同步生产分支: ${production.branch}`).start();
55
+ try {
56
+ yield git.checkout(production.branch);
57
+ yield git.pull();
58
+ spinner.succeed(`生产分支已同步: ${production.branch}`);
59
+ }
60
+ catch (error) {
61
+ spinner.fail(`准备生产分支失败: ${error.message}`);
62
+ throw error;
63
+ }
64
+ const targetRoot = (0, BaseExecutor_1.targetRootWithRelativePath)(workspacePath, production.relativePath);
65
+ yield fs_extra_1.default.ensureDir(targetRoot);
66
+ spinner = (0, ora_1.default)(`复制构建产物到: ${targetRoot}`).start();
67
+ let copied = 0;
68
+ for (const artifact of artifacts) {
69
+ if (!fs_extra_1.default.existsSync(artifact.src)) {
70
+ spinner.warn(`产物不存在(跳过): ${artifact.src}`);
71
+ continue;
72
+ }
73
+ const destination = artifact.isDir && artifact.destName === "" ? targetRoot : path_1.default.join(targetRoot, artifact.destName);
74
+ yield fs_extra_1.default.copy(artifact.src, destination, { overwrite: true });
75
+ copied++;
76
+ }
77
+ spinner.succeed(`生产产物复制完成 (${copied})`);
78
+ const status = yield git.status();
79
+ if (status.files.length === 0) {
80
+ console.log(chalk_1.default.yellow("\n⚠️ 没有文件变更,跳过提交"));
81
+ }
82
+ else {
83
+ const { commitMessage } = yield inquirer_1.default.prompt([
84
+ {
85
+ type: "input",
86
+ name: "commitMessage",
87
+ message: "请输入提交备注:",
88
+ default: `chore(prod): ${this.repoName} 发布`,
89
+ validate: (input) => Boolean(input.trim()) || "提交备注不能为空",
90
+ },
91
+ ]);
92
+ spinner = (0, ora_1.default)("提交并推送生产代码...").start();
93
+ try {
94
+ yield git.add(".");
95
+ yield git.commit(commitMessage.trim());
96
+ yield git.push();
97
+ spinner.succeed("生产代码推送成功");
98
+ }
99
+ catch (error) {
100
+ spinner.fail(`生产代码推送失败: ${error.message}`);
101
+ throw error;
102
+ }
103
+ }
104
+ const testBranch = this.task.environments.test.branch.trim();
105
+ if (testBranch) {
106
+ spinner = (0, ora_1.default)(`切换并同步测试分支: ${testBranch}`).start();
107
+ try {
108
+ yield git.checkout(testBranch);
109
+ yield git.pull();
110
+ spinner.succeed(`已切回测试分支: ${testBranch}`);
111
+ }
112
+ catch (error) {
113
+ spinner.warn(`切回测试分支失败: ${error.message}`);
114
+ }
115
+ }
116
+ });
117
+ }
118
+ }
119
+ exports.default = ProdExecutor;
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
5
+ const path_1 = tslib_1.__importDefault(require("path"));
6
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const ora_1 = tslib_1.__importDefault(require("ora"));
8
+ const simple_git_1 = tslib_1.__importDefault(require("simple-git"));
9
+ const GitProcessor_1 = tslib_1.__importDefault(require("../GitProcessor"));
10
+ const BaseExecutor_1 = require("./BaseExecutor");
11
+ /** 测试环境流水线。 */
12
+ class TestExecutor extends BaseExecutor_1.BaseExecutor {
13
+ constructor(task, servers) {
14
+ super(task, servers);
15
+ }
16
+ getEnvName() {
17
+ return "测试环境";
18
+ }
19
+ run() {
20
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
21
+ console.log(chalk_1.default.bold.cyan(`\n🚀 启动流水线: [${this.repoName}]`));
22
+ try {
23
+ yield this.executeBuild();
24
+ const artifacts = this.prepareArtifacts();
25
+ yield this.syncToTestProject(artifacts);
26
+ yield this.deployToServer(this.task.environments.test);
27
+ console.log(chalk_1.default.bold.green(`\n✨ [${this.repoName}] 流水线执行完毕!`));
28
+ }
29
+ catch (error) {
30
+ console.error(chalk_1.default.bold.red(`\n❌ 流水线中断: ${error.message}`));
31
+ throw error;
32
+ }
33
+ });
34
+ }
35
+ syncToTestProject(artifacts) {
36
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
37
+ const environment = this.task.environments.test;
38
+ const workspacePath = environment.workspacePath.trim();
39
+ if (!workspacePath)
40
+ throw new Error("未配置测试工作区路径");
41
+ if (!fs_extra_1.default.existsSync(workspacePath))
42
+ throw new Error(`测试路径不存在: ${workspacePath}`);
43
+ const git = (0, simple_git_1.default)(workspacePath);
44
+ const isRepo = yield git.checkIsRepo();
45
+ if (isRepo && environment.branch.trim()) {
46
+ const spinner = (0, ora_1.default)(`[TestEnv] 切换并同步分支: ${environment.branch}`).start();
47
+ try {
48
+ yield git.checkout(environment.branch);
49
+ yield git.pull();
50
+ spinner.succeed(`测试分支已同步: ${environment.branch}`);
51
+ }
52
+ catch (error) {
53
+ spinner.fail(`无法准备测试环境分支: ${error.message}`);
54
+ throw error;
55
+ }
56
+ }
57
+ const targetRoot = (0, BaseExecutor_1.targetRootWithRelativePath)(workspacePath, environment.relativePath);
58
+ yield fs_extra_1.default.ensureDir(targetRoot);
59
+ const spinner = (0, ora_1.default)(`复制构建产物到: ${targetRoot}`).start();
60
+ let copied = 0;
61
+ try {
62
+ for (const artifact of artifacts) {
63
+ if (!fs_extra_1.default.existsSync(artifact.src)) {
64
+ spinner.warn(`产物不存在(跳过): ${artifact.src}`);
65
+ continue;
66
+ }
67
+ const destination = artifact.isDir && artifact.destName === "" ? targetRoot : path_1.default.join(targetRoot, artifact.destName);
68
+ yield fs_extra_1.default.copy(artifact.src, destination, { overwrite: true });
69
+ copied++;
70
+ }
71
+ spinner.succeed(`测试产物复制完成 (${copied})`);
72
+ }
73
+ catch (error) {
74
+ spinner.fail(`同步测试失败: ${error.message}`);
75
+ throw error;
76
+ }
77
+ if (isRepo) {
78
+ yield new GitProcessor_1.default(workspacePath, `${this.repoName}(测试环境)`, `chore(test): 同步来自 ${this.repoName} 的构建产物`).execute();
79
+ }
80
+ });
81
+ }
82
+ }
83
+ exports.default = TestExecutor;
package/dist/src/index.js CHANGED
@@ -14,7 +14,7 @@ class MaolikeCLI {
14
14
  /** 初始化配置与指令加载 */
15
15
  initialize() {
16
16
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
17
- this.program.name("maolike").alias("ml").description("Maolike CLI: 自动化构建与部署工具").version("1.0.3");
17
+ this.program.name("maolike").alias("ml").description("Maolike CLI: 自动化构建与部署工具").version("2.0.0");
18
18
  yield new CommandLoader_1.CommandLoader(this.program).load();
19
19
  this.registerDefaultAction();
20
20
  // 捕获未知命令