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
package/README.md
CHANGED
|
@@ -1,153 +1,291 @@
|
|
|
1
|
-
# Maolike CLI
|
|
1
|
+
# Maolike CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Maolike CLI 用于管理多项目构建、测试环境同步和生产部署。它与 Maolike Desktop 共用服务器和项目配置,适合在本地终端或受控的 CI 流程中使用。
|
|
4
4
|
|
|
5
|
-
## ✨
|
|
5
|
+
## ✨ 核心优势
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
7
|
+
- ⚡ **快速同步**:一条 `ml test` 命令可完成构建和产物复制;测试工作区是 Git 仓库时还会提交并推送变更。
|
|
8
|
+
- 🚀 **测试与生产分流**:测试、生产环境分别配置构建命令、工作区、分支、产物落点和远程服务器,避免环境混用。
|
|
9
|
+
- 📦 **灵活产物**:既可同步整个构建目录,也可选择多个指定文件,并通过 `relativePath` 精确控制工作区落点。
|
|
10
|
+
- 🌐 **多服务器复用**:服务器预设集中保存在 `servers.json`,项目通过稳定 `serverId` 引用,不必重复维护 SSH 信息。
|
|
11
|
+
- 🌍 **Node.js 环境感知**:项目可指定 Node.js 版本,构建前通过 nvm 自动切换,缺失时尝试安装。
|
|
12
|
+
- 🔐 **多种 SSH 认证**:支持密码、私钥和 SSH Agent;远程 Git 公钥认证失败且 Agent 为空时可重载密钥并重试一次。
|
|
13
|
+
- 🖥️ **持续 SSH Shell**:`ml connect` 打开同一个远程 TTY 会话,保留 `cd` 状态并同步终端窗口大小。
|
|
14
|
+
- 🛡️ **部署边界明确**:生产部署前展示目标并确认;产物相对路径禁止越出工作区;服务器引用失效时明确终止。
|
|
15
|
+
- 🤖 **交互式管理**:直接运行 `ml` 获得命令提示、Tab 补全和项目名称补全,也可使用独立命令完成日常操作。
|
|
16
|
+
- 🔄 **共享 Desktop 配置**:与 Maolike Desktop 共用项目和服务器数据,同时保持应用偏好与部署配置相互隔离。
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
## 🚀 安装
|
|
18
|
+
## 安装
|
|
18
19
|
|
|
19
20
|
```bash
|
|
20
|
-
# 全局安装
|
|
21
21
|
npm install -g maolike-cli
|
|
22
|
+
ml --version
|
|
23
|
+
ml help
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 快速开始
|
|
27
|
+
|
|
28
|
+
### 1. 初始化配置
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ml init
|
|
22
32
|
```
|
|
23
33
|
|
|
24
|
-
|
|
34
|
+
初始化会创建:
|
|
35
|
+
|
|
36
|
+
```text
|
|
37
|
+
~/.maolike/desktop/deployment/projects.json
|
|
38
|
+
~/.maolike/desktop/deployment/servers.json
|
|
39
|
+
```
|
|
25
40
|
|
|
26
|
-
|
|
41
|
+
如果配置已经存在,`ml init` 会先要求确认是否覆盖。CLI 不会读取或修改 `~/.maolike/desktop/app.json`。
|
|
27
42
|
|
|
28
|
-
###
|
|
43
|
+
### 2. 添加项目
|
|
29
44
|
|
|
30
|
-
|
|
45
|
+
在源项目目录执行:
|
|
31
46
|
|
|
32
|
-
|
|
47
|
+
```bash
|
|
48
|
+
ml create
|
|
49
|
+
```
|
|
33
50
|
|
|
34
|
-
|
|
51
|
+
向导会配置构建命令、Node.js 版本、构建产物、测试工作区和可选的生产环境。测试或生产环境还可以关联服务器预设和远程目录。
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
### 3. 同步测试环境
|
|
37
54
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
- **Global**: 使用全局默认配置。
|
|
42
|
-
- **Preset**: 选择一个已保存的服务器预设(如 "Aliyun-Dev")。
|
|
43
|
-
- **Custom**: 为当前项目单独配置 SSH 信息(支持同时保存为新预设)。
|
|
55
|
+
```bash
|
|
56
|
+
ml test <项目名称>
|
|
57
|
+
```
|
|
44
58
|
|
|
45
|
-
|
|
59
|
+
命令会执行测试构建,把产物复制到测试工作区;如果工作区是 Git 仓库,还会切换并拉取分支,然后提交并推送变更。配置了远程服务器时,最后还会更新远程 Git 分支。
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
### 4. 部署生产环境
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
```bash
|
|
64
|
+
ml deploy <项目名称>
|
|
65
|
+
```
|
|
51
66
|
|
|
52
|
-
|
|
67
|
+
生产部署会显示目标信息并要求确认。可以使用现有产物或跳过确认:
|
|
53
68
|
|
|
54
|
-
|
|
69
|
+
```bash
|
|
70
|
+
ml deploy <项目名称> --skip-build
|
|
71
|
+
ml deploy <项目名称> --yes
|
|
72
|
+
```
|
|
55
73
|
|
|
56
|
-
|
|
57
|
-
- 支持切换服务器连接模式(例如从 Custom 切换到 Preset)。
|
|
74
|
+
`--yes` 只跳过生产部署确认;生产工作区存在文件变更时,命令仍会询问 Git 提交备注,因此当前不支持完全非交互的生产发布。
|
|
58
75
|
|
|
59
|
-
###
|
|
76
|
+
### 5. 连接服务器
|
|
60
77
|
|
|
61
|
-
|
|
78
|
+
```bash
|
|
79
|
+
ml connect
|
|
80
|
+
```
|
|
62
81
|
|
|
63
|
-
|
|
82
|
+
命令从 `servers.json` 选择服务器并建立持续的交互式 SSH Shell。远程会话会保留 `cd` 状态,连接当前终端的标准输入、输出和错误流,并同步终端窗口大小。输入 `exit` 或按 `Ctrl-D` 退出。
|
|
64
83
|
|
|
65
|
-
|
|
66
|
-
- **流水线步骤**:
|
|
67
|
-
1. **Build**: 在本地执行构建命令 (支持自动切换 Node 版本)。
|
|
68
|
-
2. **Artifacts**: 提取构建产物 (dist 目录或指定文件)。
|
|
69
|
-
3. **Sync Test**: 将产物复制到配置的**测试项目**中,并自动提交 Git 记录。
|
|
70
|
-
4. **Deploy**: (如果配置了 Server) 连接 SSH 并触发远程 Git Pull。
|
|
84
|
+
`ml connect` 只能在 TTY 中运行,不支持管道或非交互式 CI 调用。
|
|
71
85
|
|
|
72
|
-
|
|
86
|
+
## 命令
|
|
73
87
|
|
|
74
|
-
|
|
88
|
+
| 命令 | 用途 |
|
|
89
|
+
| ------------------ | -------------------------- |
|
|
90
|
+
| `ml init` | 初始化分层部署配置 |
|
|
91
|
+
| `ml create` | 创建项目配置 |
|
|
92
|
+
| `ml update <name>` | 更新项目配置 |
|
|
93
|
+
| `ml delete <name>` | 删除项目配置 |
|
|
94
|
+
| `ml list` | 列出项目 |
|
|
95
|
+
| `ml view <name>` | 查看项目配置 |
|
|
96
|
+
| `ml load [file]` | 导入项目或服务器配置 |
|
|
97
|
+
| `ml test <name>` | 构建并同步测试环境 |
|
|
98
|
+
| `ml deploy <name>` | 部署生产环境 |
|
|
99
|
+
| `ml connect` | 选择服务器并打开 SSH Shell |
|
|
100
|
+
| `ml cursor` | 重新安装 Cursor++ |
|
|
101
|
+
| `ml help` | 显示帮助 |
|
|
102
|
+
| `ml` | 进入交互式 Shell |
|
|
75
103
|
|
|
76
|
-
|
|
104
|
+
### 管理项目
|
|
77
105
|
|
|
78
|
-
|
|
106
|
+
```bash
|
|
107
|
+
ml list
|
|
108
|
+
ml view my-project
|
|
109
|
+
ml update my-project
|
|
110
|
+
ml delete my-project
|
|
111
|
+
```
|
|
79
112
|
|
|
80
|
-
|
|
113
|
+
`ml list` 以 `sortOrder` 排序。项目删除只移除配置,不删除本地文件。
|
|
81
114
|
|
|
82
|
-
|
|
83
|
-
2. **Project Custom**: 如果项目单独配置了 `host`,严格使用项目内的 SSH 配置。
|
|
84
|
-
3. **Global Default (最低优先级)**: 仅当上述两者都未配置时,回退使用全局 SSH 配置。
|
|
115
|
+
### 导入配置
|
|
85
116
|
|
|
86
|
-
|
|
117
|
+
`ml load` 默认读取当前目录的 `projects.json`,也可以指定文件:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
ml load
|
|
121
|
+
ml load ./deployment/projects.json
|
|
122
|
+
ml load ./deployment/servers.json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
导入文件必须使用 `schemaVersion: 1`,并包含 `projects` 或 `servers` 数组。已有配置时可以选择合并、覆盖或取消;合并遇到相同 `id` 会逐项确认。
|
|
126
|
+
|
|
127
|
+
## 配置文件
|
|
128
|
+
|
|
129
|
+
CLI 使用以下两个部署配置文件:
|
|
130
|
+
|
|
131
|
+
```text
|
|
132
|
+
~/.maolike/desktop/deployment/projects.json
|
|
133
|
+
~/.maolike/desktop/deployment/servers.json
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
两个文件都使用 `schemaVersion: 1`。项目通过服务器稳定 `id` 引用服务器预设;没有 `serverId` 时不会执行远程拉取。
|
|
137
|
+
|
|
138
|
+
### projects.json
|
|
139
|
+
|
|
140
|
+
最小示例:
|
|
87
141
|
|
|
88
142
|
```json
|
|
89
143
|
{
|
|
90
|
-
"
|
|
91
|
-
|
|
92
|
-
"ssh": {
|
|
93
|
-
"host": "192.168.1.100",
|
|
94
|
-
"port": 22,
|
|
95
|
-
"user": "root",
|
|
96
|
-
"password": "default_password"
|
|
97
|
-
},
|
|
98
|
-
// 服务器预设列表
|
|
99
|
-
"servers": [
|
|
100
|
-
{
|
|
101
|
-
"name": "Aliyun-Prod",
|
|
102
|
-
"host": "47.1.1.1",
|
|
103
|
-
"user": "root",
|
|
104
|
-
"password": "prod_password"
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
"name": "Office-Dev",
|
|
108
|
-
"host": "192.168.50.50",
|
|
109
|
-
"user": "dev",
|
|
110
|
-
"password": "dev_password"
|
|
111
|
-
}
|
|
112
|
-
],
|
|
113
|
-
"git-list": [
|
|
144
|
+
"schemaVersion": 1,
|
|
145
|
+
"projects": [
|
|
114
146
|
{
|
|
115
|
-
"
|
|
116
|
-
"
|
|
147
|
+
"id": "demo",
|
|
148
|
+
"name": "demo",
|
|
149
|
+
"localPath": "/Users/mike/projects/demo",
|
|
117
150
|
"build": {
|
|
118
|
-
"
|
|
119
|
-
"
|
|
120
|
-
"
|
|
151
|
+
"testCommand": "pnpm build",
|
|
152
|
+
"productionCommand": "pnpm build:prod",
|
|
153
|
+
"nodeVersion": "20",
|
|
154
|
+
"artifact": {
|
|
155
|
+
"kind": "directory",
|
|
156
|
+
"paths": ["dist"]
|
|
157
|
+
}
|
|
121
158
|
},
|
|
122
|
-
"
|
|
123
|
-
"
|
|
124
|
-
|
|
159
|
+
"environments": {
|
|
160
|
+
"test": {
|
|
161
|
+
"workspacePath": "/Users/mike/deploy/demo-test",
|
|
162
|
+
"branch": "dev",
|
|
163
|
+
"relativePath": "public/assets",
|
|
164
|
+
"serverId": "test-server",
|
|
165
|
+
"remotePath": "/www/demo-test"
|
|
166
|
+
},
|
|
167
|
+
"production": null
|
|
125
168
|
},
|
|
126
|
-
"
|
|
127
|
-
|
|
128
|
-
"serverName": "Aliyun-Prod" // 引用预设
|
|
129
|
-
}
|
|
169
|
+
"enabled": true,
|
|
170
|
+
"sortOrder": 0
|
|
130
171
|
}
|
|
131
172
|
]
|
|
132
173
|
}
|
|
133
174
|
```
|
|
134
175
|
|
|
135
|
-
|
|
176
|
+
常用字段:
|
|
177
|
+
|
|
178
|
+
- `build.testCommand`:测试构建命令。
|
|
179
|
+
- `build.productionCommand`:生产构建命令;为空时复用测试命令。
|
|
180
|
+
- `build.nodeVersion`:可选 Node.js 版本。
|
|
181
|
+
- `artifact.kind`:`directory` 复制目录内容,`files` 复制所有指定文件。
|
|
182
|
+
- `artifact.paths`:产物目录或文件路径。
|
|
183
|
+
- `environments.test`:必需的测试工作区和分支。
|
|
184
|
+
- `environments.production`:可选的生产工作区和分支。
|
|
185
|
+
- `relativePath`:产物在本地工作区内的相对落点。
|
|
186
|
+
- `serverId` / `remotePath`:远程服务器预设和远程 Git 目录。
|
|
187
|
+
|
|
188
|
+
`relativePath` 同时用于测试和生产工作区。为空时复制到工作区根目录;绝对路径和包含 `..` 的越界路径会被拒绝。它与远程服务器的 `remotePath` 是两个独立字段。
|
|
189
|
+
|
|
190
|
+
### servers.json
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"schemaVersion": 1,
|
|
195
|
+
"servers": [
|
|
196
|
+
{
|
|
197
|
+
"id": "test-server",
|
|
198
|
+
"name": "测试服务器",
|
|
199
|
+
"host": "test.example.com",
|
|
200
|
+
"port": 22,
|
|
201
|
+
"username": "root",
|
|
202
|
+
"password": null,
|
|
203
|
+
"sshPrivateKey": "/Users/mike/.ssh/deploy_test",
|
|
204
|
+
"agentForward": true,
|
|
205
|
+
"gitUsername": null,
|
|
206
|
+
"gitPassword": null
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
服务器可使用密码、`sshPrivateKey` 或 SSH Agent。启用 `agentForward` 时,当前终端必须存在 `SSH_AUTH_SOCK`,并且远端 SSH 服务允许 Agent Forwarding。
|
|
213
|
+
|
|
214
|
+
远程 Git 使用 SSH 且出现公钥认证失败时,如果 Agent 为空并满足 Agent 转发条件,CLI 会尝试重载密钥并只重试一次。远程 Git 使用 HTTPS 时,可以配置 `gitUsername` 和 `gitPassword`。
|
|
215
|
+
|
|
216
|
+
## 交互式 CLI
|
|
217
|
+
|
|
218
|
+
直接运行:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
ml
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
交互模式支持命令和项目名称补全。项目创建、更新或删除后,Shell 会重新读取 `projects.json` 并刷新补全列表。输入 `exit` 或 `quit` 退出。
|
|
225
|
+
|
|
226
|
+
## 重新安装 Cursor++
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
ml cursor
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
命令严格先执行卸载,再在卸载成功后执行安装。任一步失败都会返回非零退出码。
|
|
233
|
+
|
|
234
|
+
## 从 v1.0.7 升级
|
|
235
|
+
|
|
236
|
+
v2.0.0 不再读取或自动迁移旧版 `~/.maolike/config.json`。升级前请备份旧文件,然后运行 `ml init` 按新结构重新创建项目和服务器配置。
|
|
237
|
+
|
|
238
|
+
旧字段的大致对应关系:
|
|
239
|
+
|
|
240
|
+
- `git-list[]` → `projects[]`
|
|
241
|
+
- `path` → `localPath`
|
|
242
|
+
- `build.cmd` → `build.testCommand`
|
|
243
|
+
- `build.distName` / `distFile` → `build.artifact.kind` / `paths`
|
|
244
|
+
- `test.path` / `test.branch` → `environments.test.workspacePath` / `branch`
|
|
245
|
+
- `server.serverName` → 对应环境的 `serverId`,必须填写服务器稳定 ID
|
|
246
|
+
- `server.remotePath` → 对应环境的 `remotePath`
|
|
247
|
+
- SSH 字段 `user` → `username`
|
|
248
|
+
|
|
249
|
+
`ml load` 不能直接导入旧版 `config.json`。旧版 `ml menu` 已移除,改为直接运行 `ml` 进入交互式 Shell。
|
|
250
|
+
|
|
251
|
+
## 使用限制与安全说明
|
|
252
|
+
|
|
253
|
+
- `ml connect` 需要真实 TTY,不适合管道、CI 或其他非交互输入。
|
|
254
|
+
- `ml deploy --yes` 仍可能要求输入生产 Git 提交备注。
|
|
255
|
+
- 测试工作区可以不是 Git 仓库;生产工作区必须是 Git 仓库。
|
|
256
|
+
- CLI 连接部署服务器时未配置固定主机指纹校验;首次连接前请确认目标主机可信。
|
|
257
|
+
- `servers.json` 可能包含密码或 Token,不要提交到代码仓库,应使用安全的密钥管理方式。
|
|
258
|
+
|
|
259
|
+
## 💡 常见问题(FAQ)
|
|
260
|
+
|
|
261
|
+
**Q:为什么构建时提示找不到 nvm?**
|
|
262
|
+
|
|
263
|
+
如果项目配置了 `build.nodeVersion`,CLI 会通过 nvm 切换或安装对应的 Node.js 版本。请先确认当前终端可以执行 `nvm --version`;macOS/Linux 还需要确保用户的交互式 Shell 能正确加载 nvm。
|
|
264
|
+
|
|
265
|
+
**Q:为什么远程更新提示找不到服务器?**
|
|
266
|
+
|
|
267
|
+
项目环境保存的是 `servers.json` 中服务器的稳定 `id`,不是展示名称。服务器被删除或 ID 发生变化后,CLI 会停止远程更新,不会自动连接其他服务器。请运行 `ml update <项目名称>` 重新选择服务器。
|
|
268
|
+
|
|
269
|
+
**Q:如何新增并复用服务器预设?**
|
|
270
|
+
|
|
271
|
+
运行 `ml create` 或 `ml update <项目名称>`,在测试或生产环境的远程服务器选项中选择“新建服务器预设”。保存后,其他项目可以继续选择同一服务器。
|
|
272
|
+
|
|
273
|
+
**Q:为什么 `ml connect` 没有可选服务器?**
|
|
274
|
+
|
|
275
|
+
`ml connect` 只读取 `servers.json`。请先通过 `ml init`、`ml create` 或 `ml update` 创建服务器预设,也可以使用 `ml load ./servers.json` 导入新格式的服务器配置。
|
|
276
|
+
|
|
277
|
+
**Q:为什么 `ml deploy --yes` 仍然要求输入?**
|
|
136
278
|
|
|
137
|
-
|
|
279
|
+
`--yes` 只跳过生产部署确认。生产工作区存在文件变更时,CLI 仍会询问 Git 提交备注,以避免生成没有明确说明的生产提交。
|
|
138
280
|
|
|
139
|
-
**Q
|
|
140
|
-
A: 如果你在 `build.nodeVersion` 指定了版本,工具会尝试调用系统 `nvm`。请确保你的命令行环境(特别是 Mac/Linux 的 setup)已正确配置 `nvm` 环境变量。
|
|
281
|
+
**Q:升级后为什么看不到 v1.0.7 的项目?**
|
|
141
282
|
|
|
142
|
-
|
|
143
|
-
A: 你在项目配置中指定了 `serverName`,但 `config.json` 的 `servers` 列表中删除了该预设。为了安全(防止连错服务器),CLI 会直接终止部署。请使用 `ml update`重新选择。
|
|
283
|
+
v2.0.0 不会自动读取或迁移旧版 `~/.maolike/config.json`。请按“从 v1.0.7 升级”章节备份旧配置,并使用 `ml init` 按新的 `projects.json` / `servers.json` 结构重新配置。
|
|
144
284
|
|
|
145
|
-
|
|
146
|
-
A: 使用 `ml create` 或 `ml update`,在 SSH 配置环节选择 "Custom",输入完信息后,CLI 会询问 "是否保存为常用服务器预设",选择 Yes 并输入名称即可。
|
|
285
|
+
## 📮 联系方式
|
|
147
286
|
|
|
148
|
-
|
|
287
|
+
如有问题或建议,请发送邮件至 [masonmaolike@gmail.com](mailto:masonmaolike@gmail.com)。
|
|
149
288
|
|
|
150
|
-
##
|
|
289
|
+
## License
|
|
151
290
|
|
|
152
|
-
|
|
153
|
-
📧 Email: masonmaolike@gmail.com
|
|
291
|
+
MIT
|
|
@@ -1,6 +1,7 @@
|
|
|
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 BaseCommand_1 = tslib_1.__importDefault(require("../../utils/BaseCommand"));
|
|
5
6
|
const inquirer_1 = tslib_1.__importDefault(require("inquirer"));
|
|
6
7
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
@@ -8,109 +9,80 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
|
8
9
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
9
10
|
const ConfigManager_1 = require("../../services/ConfigManager");
|
|
10
11
|
const ConfigPrompter_1 = require("../../utils/ConfigPrompter");
|
|
11
|
-
/**
|
|
12
|
-
* 项目创建/配置添加命令
|
|
13
|
-
*/
|
|
12
|
+
/** 在当前目录创建项目部署配置。 */
|
|
14
13
|
class CreateCommand extends BaseCommand_1.default {
|
|
15
|
-
/**
|
|
16
|
-
* 初始化命令配置
|
|
17
|
-
*/
|
|
18
14
|
init() {
|
|
19
15
|
this.program.command("create").description("在当前目录快速创建/添加项目配置").action(this.action.bind(this));
|
|
20
16
|
}
|
|
21
|
-
/**
|
|
22
|
-
* 执行核心逻辑
|
|
23
|
-
*/
|
|
24
17
|
action() {
|
|
25
18
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
26
|
-
|
|
19
|
+
var _a, _b;
|
|
20
|
+
if (!ConfigManager_1.ConfigManager.existsOnDisk()) {
|
|
21
|
+
console.log(chalk_1.default.red("配置未初始化,请先运行 `ml init`"));
|
|
27
22
|
return;
|
|
28
23
|
}
|
|
29
24
|
const cwd = process.cwd();
|
|
30
|
-
this.
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const conflictResult = yield this.handleNameConflict(initialName);
|
|
35
|
-
if (conflictResult.action === "cancel")
|
|
25
|
+
this.printGitStatus(cwd);
|
|
26
|
+
const { name: initialName, localPath } = yield this.promptBasicInfo(cwd);
|
|
27
|
+
const conflict = yield this.handleNameConflict(initialName);
|
|
28
|
+
if (conflict.action === "cancel")
|
|
36
29
|
return;
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
test: testConfig,
|
|
51
|
-
server: serverConfig,
|
|
30
|
+
const existing = conflict.targetIndex === -1 ? undefined : ConfigManager_1.ConfigManager.projects[conflict.targetIndex];
|
|
31
|
+
const build = yield ConfigPrompter_1.ConfigPrompter.promptBuildConfig(existing === null || existing === void 0 ? void 0 : existing.build);
|
|
32
|
+
const test = yield ConfigPrompter_1.ConfigPrompter.promptTestEnvironment(existing === null || existing === void 0 ? void 0 : existing.environments.test);
|
|
33
|
+
const production = yield ConfigPrompter_1.ConfigPrompter.promptProductionConfig(existing === null || existing === void 0 ? void 0 : existing.environments.production, test, existing === null || existing === void 0 ? void 0 : existing.build.productionCommand);
|
|
34
|
+
build.productionCommand = production.productionCommand;
|
|
35
|
+
const project = {
|
|
36
|
+
id: (existing === null || existing === void 0 ? void 0 : existing.id) || (0, crypto_1.randomUUID)(),
|
|
37
|
+
name: conflict.finalName,
|
|
38
|
+
localPath,
|
|
39
|
+
build,
|
|
40
|
+
environments: { test, production: production.environment },
|
|
41
|
+
enabled: (_a = existing === null || existing === void 0 ? void 0 : existing.enabled) !== null && _a !== void 0 ? _a : true,
|
|
42
|
+
sortOrder: (_b = existing === null || existing === void 0 ? void 0 : existing.sortOrder) !== null && _b !== void 0 ? _b : this.nextSortOrder(),
|
|
52
43
|
};
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
if (conflict.targetIndex !== -1) {
|
|
45
|
+
ConfigManager_1.ConfigManager.upsertProject(project);
|
|
46
|
+
console.log(chalk_1.default.green(`\n✅ 项目 [${project.name}] 配置已更新覆盖!`));
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
ConfigManager_1.ConfigManager.upsertProject(project);
|
|
50
|
+
console.log(chalk_1.default.green(`\n✅ 项目 [${project.name}] 添加成功!`));
|
|
51
|
+
}
|
|
55
52
|
});
|
|
56
53
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
*/
|
|
60
|
-
checkPreconditions() {
|
|
61
|
-
if (!ConfigManager_1.ConfigManager.existsOnDisk()) {
|
|
62
|
-
console.log(chalk_1.default.red("配置未初始化,请先运行 `ml init`"));
|
|
63
|
-
return false;
|
|
64
|
-
}
|
|
65
|
-
return true;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* 检查当前目录 Git 状态
|
|
69
|
-
* @param cwd 当前工作目录
|
|
70
|
-
*/
|
|
71
|
-
checkGitStatus(cwd) {
|
|
72
|
-
const hasGit = fs_extra_1.default.existsSync(path_1.default.join(cwd, ".git"));
|
|
73
|
-
if (hasGit) {
|
|
54
|
+
printGitStatus(cwd) {
|
|
55
|
+
if (fs_extra_1.default.existsSync(path_1.default.join(cwd, ".git"))) {
|
|
74
56
|
console.log(chalk_1.default.green(`\n检测到 Git 仓库: ${cwd}`));
|
|
75
57
|
}
|
|
76
58
|
else {
|
|
77
|
-
console.log(chalk_1.default.yellow(
|
|
59
|
+
console.log(chalk_1.default.yellow("\n当前目录没有 .git 文件夹,但仍可继续配置。"));
|
|
78
60
|
}
|
|
79
61
|
}
|
|
80
|
-
/**
|
|
81
|
-
* 弹出基础信息输入
|
|
82
|
-
* @param cwd 默认路径
|
|
83
|
-
*/
|
|
84
62
|
promptBasicInfo(cwd) {
|
|
85
63
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
86
|
-
const defaultName = path_1.default.basename(cwd);
|
|
87
64
|
return inquirer_1.default.prompt([
|
|
88
65
|
{
|
|
89
66
|
type: "input",
|
|
90
67
|
name: "name",
|
|
91
68
|
message: "项目名称:",
|
|
92
|
-
default:
|
|
93
|
-
validate: (input) =>
|
|
69
|
+
default: path_1.default.basename(cwd),
|
|
70
|
+
validate: (input) => Boolean(input) || "请输入名称",
|
|
94
71
|
},
|
|
95
72
|
{
|
|
96
73
|
type: "input",
|
|
97
|
-
name: "
|
|
74
|
+
name: "localPath",
|
|
98
75
|
message: "本地路径:",
|
|
99
76
|
default: cwd,
|
|
100
|
-
filter: (
|
|
77
|
+
filter: (input) => input.trim().replace(/^['"]|['"]$/g, ""),
|
|
101
78
|
},
|
|
102
79
|
]);
|
|
103
80
|
});
|
|
104
81
|
}
|
|
105
|
-
/**
|
|
106
|
-
* 处理项目名称冲突逻辑
|
|
107
|
-
* @param initialName 初始尝试名称
|
|
108
|
-
*/
|
|
109
82
|
handleNameConflict(initialName) {
|
|
110
83
|
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
111
|
-
|
|
112
|
-
let
|
|
113
|
-
let targetIndex = config["git-list"].findIndex((t) => t.name === projectName);
|
|
84
|
+
let projectName = initialName.trim();
|
|
85
|
+
let targetIndex = ConfigManager_1.ConfigManager.projects.findIndex((project) => project.name === projectName);
|
|
114
86
|
if (targetIndex !== -1) {
|
|
115
87
|
const { action } = yield inquirer_1.default.prompt([
|
|
116
88
|
{
|
|
@@ -118,16 +90,14 @@ class CreateCommand extends BaseCommand_1.default {
|
|
|
118
90
|
name: "action",
|
|
119
91
|
message: chalk_1.default.yellow(`⚠️ 项目 [${projectName}] 配置已存在,请选择操作:`),
|
|
120
92
|
choices: [
|
|
121
|
-
{ name: "🔄 覆盖现有配置
|
|
122
|
-
{ name: "✏️ 创建为新项目
|
|
123
|
-
{ name: "❌ 放弃
|
|
93
|
+
{ name: "🔄 覆盖现有配置", value: "overwrite" },
|
|
94
|
+
{ name: "✏️ 创建为新项目", value: "rename" },
|
|
95
|
+
{ name: "❌ 放弃", value: "cancel" },
|
|
124
96
|
],
|
|
125
97
|
},
|
|
126
98
|
]);
|
|
127
|
-
if (action === "cancel")
|
|
128
|
-
console.log(chalk_1.default.dim("操作已取消"));
|
|
99
|
+
if (action === "cancel")
|
|
129
100
|
return { action: "cancel", finalName: projectName, targetIndex: -1 };
|
|
130
|
-
}
|
|
131
101
|
if (action === "rename") {
|
|
132
102
|
const { newName } = yield inquirer_1.default.prompt([
|
|
133
103
|
{
|
|
@@ -135,36 +105,21 @@ class CreateCommand extends BaseCommand_1.default {
|
|
|
135
105
|
name: "newName",
|
|
136
106
|
message: "请输入新项目名称:",
|
|
137
107
|
validate: (input) => {
|
|
138
|
-
if (!input)
|
|
108
|
+
if (!input.trim())
|
|
139
109
|
return "必填字段";
|
|
140
|
-
|
|
141
|
-
return "名称已被占用";
|
|
142
|
-
return true;
|
|
110
|
+
return ConfigManager_1.ConfigManager.projects.some((project) => project.name === input.trim()) ? "名称已被占用" : true;
|
|
143
111
|
},
|
|
144
112
|
},
|
|
145
113
|
]);
|
|
146
|
-
projectName = newName;
|
|
114
|
+
projectName = newName.trim();
|
|
147
115
|
targetIndex = -1;
|
|
148
116
|
}
|
|
149
117
|
}
|
|
150
118
|
return { action: "continue", finalName: projectName, targetIndex };
|
|
151
119
|
});
|
|
152
120
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
* @param item 新项目配置项
|
|
156
|
-
* @param targetIndex 如果是覆盖,则为索引,否则为 -1
|
|
157
|
-
*/
|
|
158
|
-
saveConfig(item, targetIndex) {
|
|
159
|
-
const config = ConfigManager_1.ConfigManager.config;
|
|
160
|
-
if (targetIndex !== -1) {
|
|
161
|
-
config["git-list"][targetIndex] = item;
|
|
162
|
-
console.log(chalk_1.default.green(`\n✅ 项目 [${item.name}] 配置已更新覆盖!`));
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
config["git-list"].push(item);
|
|
166
|
-
console.log(chalk_1.default.green(`\n✅ 项目 [${item.name}] 添加成功!`));
|
|
167
|
-
}
|
|
121
|
+
nextSortOrder() {
|
|
122
|
+
return ConfigManager_1.ConfigManager.projects.reduce((max, project) => Math.max(max, project.sortOrder), -1) + 1;
|
|
168
123
|
}
|
|
169
124
|
}
|
|
170
125
|
exports.default = CreateCommand;
|
|
@@ -25,8 +25,7 @@ class DeleteCommand extends BaseCommand_1.default {
|
|
|
25
25
|
console.log(chalk_1.default.red("配置未初始化"));
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
-
const
|
|
29
|
-
const taskIndex = config["git-list"].findIndex((item) => item.name === name);
|
|
28
|
+
const taskIndex = ConfigManager_1.ConfigManager.projects.findIndex((project) => project.name === name || project.id === name);
|
|
30
29
|
if (taskIndex === -1) {
|
|
31
30
|
console.log(chalk_1.default.red(`未找到项目: ${name}`));
|
|
32
31
|
return;
|
|
@@ -40,8 +39,7 @@ class DeleteCommand extends BaseCommand_1.default {
|
|
|
40
39
|
},
|
|
41
40
|
]);
|
|
42
41
|
if (confirm) {
|
|
43
|
-
|
|
44
|
-
config["git-list"].splice(taskIndex, 1);
|
|
42
|
+
ConfigManager_1.ConfigManager.deleteProject(ConfigManager_1.ConfigManager.projects[taskIndex].id);
|
|
45
43
|
console.log(chalk_1.default.green(`\n✅ 项目 [${name}] 配置已删除`));
|
|
46
44
|
}
|
|
47
45
|
else {
|