@mindbase/deploy 1.1.5 → 1.2.0

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/bin/shared.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import * as clack from '@clack/prompts';
3
- import { runApp, runProject, showConfig } from '../src/app.js';
3
+ import { runApp } from '../src/app.js';
4
4
  import { runInit } from '../src/init/app.js';
5
5
  import { createServerCommand } from '../src/server/app.js';
6
6
 
@@ -14,14 +14,12 @@ export function createDeployProgram(): Command {
14
14
  .description('部署工具')
15
15
  .version('1.0.0')
16
16
  .action(async () => {
17
- clack.intro('deploy');
18
17
  try {
19
18
  await runApp();
20
19
  } catch (err) {
21
20
  clack.log.error(`执行失败: ${err}`);
22
21
  process.exit(1);
23
22
  }
24
- clack.outro('再见');
25
23
  });
26
24
 
27
25
  program
@@ -37,27 +35,6 @@ export function createDeployProgram(): Command {
37
35
  }
38
36
  });
39
37
 
40
- program
41
- .command('run [path]')
42
- .description('部署单个项目(path 缺省为 cwd,可传目录或 deploy.config.json)')
43
- .option('-s, --server <server>', '覆盖项目配置中的 server')
44
- .action(async (path: string | undefined, options: { server?: string }) => {
45
- clack.intro('deploy run');
46
- try {
47
- await runProject(path, options.server);
48
- } catch (err) {
49
- clack.log.error(`执行失败: ${err}`);
50
- process.exit(1);
51
- }
52
- });
53
-
54
- program
55
- .command('config')
56
- .description('查看全局 servers 配置')
57
- .action(() => {
58
- showConfig();
59
- });
60
-
61
38
  // 服务器管理子命令
62
39
  program.addCommand(createServerCommand());
63
40
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindbase/deploy",
3
- "version": "1.1.5",
3
+ "version": "1.2.0",
4
4
  "description": "MindBase 部署工具",
5
5
  "type": "module",
6
6
  "bin": {
package/src/app.ts CHANGED
@@ -2,7 +2,7 @@ import * as clack from '@clack/prompts';
2
2
  import pc from 'picocolors';
3
3
  import { relative } from 'path';
4
4
  import { noteBox } from '@mindbase/cli-ui';
5
- import { getConfig, getConfigPath, discoverProjectConfigs, loadProjectConfig } from './config/manager.js';
5
+ import { getConfig, discoverProjectConfigs } from './config/manager.js';
6
6
  import type { ProjectConfig } from './config/schema.js';
7
7
  import { connectSftp } from './ssh/client.js';
8
8
  import { runSteps } from './script/runner.js';
@@ -17,86 +17,18 @@ function handleCancel<T>(result: T | symbol): T {
17
17
  }
18
18
 
19
19
  /**
20
- * 查看配置
21
- */
22
- export function showConfig(): void {
23
- clack.intro('配置信息');
24
- const config = getConfig();
25
- const path = getConfigPath();
26
- noteBox(`配置文件: ${path}\n\n${JSON.stringify(config, null, 2)}`, '当前配置');
27
- clack.outro('完成');
28
- }
29
-
30
- /**
31
- * 显示项目配置摘要并让用户确认部署
32
- */
33
- async function confirmProjectDeploy(project: ProjectConfig, serverId: string): Promise<boolean> {
34
- const summary = [
35
- `名称: ${project.name}`,
36
- `服务器: ${serverId}`,
37
- `本地目录: ${project.localDir}`,
38
- `远端目录: ${project.remoteDir}`,
39
- `步骤数: ${project.steps.length}`,
40
- ].join('\n');
41
- noteBox(summary, '即将部署');
42
- return handleCancel<boolean>(
43
- await clack.confirm({ message: '确认部署?', initialValue: true })
44
- );
45
- }
46
-
47
- /**
48
- * 直接运行指定项目(单项目模式)
49
- */
50
- export async function runProject(targetPath?: string, serverOverride?: string): Promise<void> {
51
- const project = loadProjectConfig(targetPath);
52
- const globalConfig = getConfig();
53
-
54
- const serverId = serverOverride || project.server;
55
- const serverConfig = globalConfig.servers[serverId];
56
- if (!serverConfig) {
57
- clack.log.error(`服务器 "${serverId}" 不存在,请先运行 deploy server add`);
58
- process.exit(1);
59
- }
60
-
61
- clack.intro(`部署 ${pc.cyan(project.name)} → ${pc.cyan(serverId)}`);
62
-
63
- if (!(await confirmProjectDeploy(project, serverId))) {
64
- clack.log.info('已取消');
65
- clack.outro('完成');
66
- return;
67
- }
68
-
69
- const spinner = clack.spinner();
70
- spinner.start('连接服务器...');
71
- const sftp = await connectSftp(project.name, serverConfig);
72
- spinner.stop('已连接');
73
-
74
- try {
75
- await runSteps(project.steps, project, sftp);
76
- clack.log.success(`${project.name} 部署完成`);
77
- } catch (err) {
78
- clack.log.error(`部署失败: ${err}`);
79
- } finally {
80
- await sftp.end();
81
- }
82
-
83
- clack.outro('完成');
84
- }
85
-
86
- /**
87
- * 交互式主流程:扫描 cwd 下所有 deploy.config.json → 多选 → 按 server 分组部署
20
+ * 交互式主流程:选 server → 选项目 → 按笛卡尔分组部署
88
21
  */
89
22
  export async function runApp(): Promise<void> {
90
23
  const cwd = process.cwd();
91
24
  const discovered = discoverProjectConfigs(cwd);
92
25
  const globalConfig = getConfig();
93
26
 
27
+ // 边界校验(intro 之前)
94
28
  if (discovered.length === 0) {
95
29
  clack.log.error(`未在 ${cwd} 下找到任何 deploy.config.json,请先运行 deploy init`);
96
30
  return;
97
31
  }
98
-
99
- // 0 个服务器直接报错
100
32
  if (Object.keys(globalConfig.servers).length === 0) {
101
33
  clack.log.error('没有已配置的服务器,请先运行 deploy server add');
102
34
  return;
@@ -104,8 +36,21 @@ export async function runApp(): Promise<void> {
104
36
 
105
37
  clack.intro('deploy');
106
38
 
107
- // 选择项目
108
- let selected: { path: string; config: ProjectConfig }[];
39
+ // 多选 server
40
+ const serverIds = Object.keys(globalConfig.servers);
41
+ const selectedServers = handleCancel<string[]>(
42
+ await clack.multiselect({
43
+ message: '选择要部署到的服务器(空格切换,a 全选)',
44
+ options: serverIds.map((id) => ({
45
+ value: id,
46
+ label: `${id} (${globalConfig.servers[id].host})`,
47
+ })),
48
+ required: true,
49
+ })
50
+ );
51
+
52
+ // 多选项目
53
+ let selectedProjects: typeof discovered;
109
54
 
110
55
  if (discovered.length === 1) {
111
56
  const only = discovered[0];
@@ -117,57 +62,69 @@ export async function runApp(): Promise<void> {
117
62
  clack.outro('已取消');
118
63
  return;
119
64
  }
120
- selected = [only];
65
+ selectedProjects = [only];
121
66
  } else {
122
- selected = handleCancel<typeof discovered>(
67
+ selectedProjects = handleCancel<typeof discovered>(
123
68
  await clack.multiselect({
124
69
  message: '选择要部署的项目(空格切换,a 全选)',
125
70
  options: discovered.map((item) => ({
126
71
  value: item,
127
- label: `${item.config.name} (${relative(cwd, item.path) || item.path}) → ${pc.dim(item.config.server)}`,
72
+ label: `${item.config.name} (${relative(cwd, item.path) || item.path})`,
128
73
  })),
129
74
  required: true,
130
75
  })
131
76
  );
132
77
  }
133
78
 
134
- // 校验所有选中项目的 server 都存在
135
- const missingServers = new Set<string>();
136
- for (const item of selected) {
137
- if (!globalConfig.servers[item.config.server]) {
138
- missingServers.add(item.config.server);
139
- }
140
- }
141
- if (missingServers.size > 0) {
142
- clack.log.error(`以下服务器未配置: ${[...missingServers].join(', ')}`);
143
- clack.outro('已中止');
144
- return;
145
- }
79
+ // 确认摘要
80
+ const summaryLines = [
81
+ `服务器: ${selectedServers.map((s) => `${s} (${globalConfig.servers[s].host})`).join(', ')}`,
82
+ `项目: ${selectedProjects.map((p) => p.config.name).join(', ')}`,
83
+ `组合: ${selectedServers.length} × ${selectedProjects.length} = ${selectedServers.length * selectedProjects.length} 次部署`,
84
+ ].join('\n');
85
+ noteBox(summaryLines, '即将部署');
146
86
 
147
- // server 分组
148
- const groups = new Map<string, { path: string; config: ProjectConfig }[]>();
149
- for (const item of selected) {
150
- const arr = groups.get(item.config.server) ?? [];
151
- arr.push(item);
152
- groups.set(item.config.server, arr);
87
+ const confirmed = handleCancel<boolean>(
88
+ await clack.confirm({ message: '确认开始部署?', initialValue: true })
89
+ );
90
+ if (!confirmed) {
91
+ clack.outro('已取消');
92
+ return;
153
93
  }
154
94
 
155
- // 依次连接每个 server,组内项目依次部署
156
- for (const [serverId, items] of groups) {
95
+ // server 分组遍历(笛卡尔:每个 server 部署全部选中项目)
96
+ let anyFailure = false;
97
+ for (const serverId of selectedServers) {
157
98
  const serverConfig = globalConfig.servers[serverId];
158
99
  const spinner = clack.spinner();
159
- spinner.start(`连接 ${serverId}...`);
160
- const sftp = await connectSftp('deploy', serverConfig);
161
- spinner.stop('已连接');
100
+ spinner.start(`连接 ${serverId} (${serverConfig.host})...`);
101
+ let sftp;
102
+ try {
103
+ sftp = await connectSftp('deploy', serverConfig);
104
+ } catch (err) {
105
+ spinner.stop(`连接失败: ${serverId}`);
106
+ clack.log.error(`${serverId} 连接失败: ${err}`);
107
+ anyFailure = true;
108
+ const cont = handleCancel<boolean>(
109
+ await clack.confirm({ message: `${serverId} 不可用,是否跳过并继续下一个服务器?` })
110
+ );
111
+ if (!cont) {
112
+ clack.outro('已中止');
113
+ return;
114
+ }
115
+ continue;
116
+ }
117
+ spinner.stop(`已连接 ${serverId}`);
162
118
 
163
119
  try {
164
- for (const { config } of items) {
120
+ for (const { config } of selectedProjects) {
165
121
  clack.log.step(`开始部署 ${pc.cyan(config.name)} → ${pc.cyan(serverId)}`);
166
122
  try {
167
123
  await runSteps(config.steps, config, sftp);
168
- clack.log.success(`${config.name} 部署完成`);
124
+ clack.log.success(`${config.name} → ${serverId} 完成`);
169
125
  } catch (err) {
170
- clack.log.error(`${config.name} 部署失败: ${err}`);
126
+ anyFailure = true;
127
+ clack.log.error(`${config.name} → ${serverId} 失败: ${err}`);
171
128
  const cont = handleCancel<boolean>(
172
129
  await clack.confirm({ message: '是否继续部署下一个项目?' })
173
130
  );
@@ -179,5 +136,5 @@ export async function runApp(): Promise<void> {
179
136
  }
180
137
  }
181
138
 
182
- clack.outro('全部完成');
139
+ clack.outro(anyFailure ? '完成(存在失败项)' : '全部完成');
183
140
  }
@@ -85,8 +85,6 @@ export interface ProjectConfig {
85
85
  name: string;
86
86
  /** 本地目录(绝对路径或相对配置文件的路径,加载时解析为绝对路径) */
87
87
  localDir: string;
88
- /** 目标服务器 ID(引用全局 servers) */
89
- server: string;
90
88
  /** 远端目录 */
91
89
  remoteDir: string;
92
90
  /** 部署步骤 */
package/src/init/app.ts CHANGED
@@ -384,7 +384,6 @@ export async function runInit(targetPath?: string): Promise<void> {
384
384
  const projectConfig: ProjectConfig = {
385
385
  name,
386
386
  localDir,
387
- server: serverId,
388
387
  remoteDir,
389
388
  steps,
390
389
  };