befly 3.4.7 → 3.4.9
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/index.ts +28 -21
- package/bin/launcher.ts +10 -7
- package/commands/build.ts +1 -12
- package/commands/dev.ts +1 -11
- package/commands/index.ts +1 -12
- package/commands/start.ts +1 -12
- package/commands/util.ts +58 -0
- package/lib/logger.ts +12 -0
- package/package.json +2 -2
- package/commands/addon.ts +0 -57
- package/commands/script.ts +0 -303
package/bin/index.ts
CHANGED
|
@@ -18,16 +18,24 @@ await launch(import.meta.path);
|
|
|
18
18
|
* 此时环境变量已正确加载
|
|
19
19
|
*/
|
|
20
20
|
import { Command } from 'commander';
|
|
21
|
-
import { scriptCommand } from '../commands/script.js';
|
|
22
21
|
import { devCommand } from '../commands/dev.js';
|
|
23
22
|
import { buildCommand } from '../commands/build.js';
|
|
24
23
|
import { startCommand } from '../commands/start.js';
|
|
25
24
|
import { syncDbCommand } from '../commands/syncDb.js';
|
|
26
|
-
import { addonCommand } from '../commands/addon.js';
|
|
27
25
|
import { syncApiCommand } from '../commands/syncApi.js';
|
|
28
26
|
import { syncMenuCommand } from '../commands/syncMenu.js';
|
|
29
27
|
import { syncDevCommand } from '../commands/syncDev.js';
|
|
30
28
|
import { Logger } from '../lib/logger.js';
|
|
29
|
+
import { join } from 'pathe';
|
|
30
|
+
import { getPackageVersion } from '../commands/util.js';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 读取 package.json 版本号
|
|
34
|
+
*/
|
|
35
|
+
function getVersion(): string {
|
|
36
|
+
const coreDir = join(import.meta.dir, '..');
|
|
37
|
+
return getPackageVersion(coreDir);
|
|
38
|
+
}
|
|
31
39
|
|
|
32
40
|
/**
|
|
33
41
|
* Bun 版本要求
|
|
@@ -109,40 +117,39 @@ checkBunVersion();
|
|
|
109
117
|
|
|
110
118
|
const program = new Command();
|
|
111
119
|
|
|
112
|
-
program.name('befly').description('Befly CLI - 为 Befly 框架提供命令行工具').version(
|
|
120
|
+
program.name('befly').description('Befly CLI - 为 Befly 框架提供命令行工具').version(getVersion());
|
|
113
121
|
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
/**
|
|
123
|
+
* 包装命令处理函数,在执行后打印环境
|
|
124
|
+
*/
|
|
125
|
+
function wrapCommand<T extends (...args: any[]) => any>(fn: T): T {
|
|
126
|
+
return (async (...args: any[]) => {
|
|
127
|
+
const result = await fn(...args);
|
|
128
|
+
Logger.printEnv();
|
|
129
|
+
return result;
|
|
130
|
+
}) as T;
|
|
131
|
+
}
|
|
116
132
|
|
|
117
133
|
// dev 命令 - 开发服务器
|
|
118
|
-
program.command('dev').description('启动开发服务器').option('-p, --port <number>', '端口号', '3000').option('-h, --host <string>', '主机地址', '0.0.0.0').option('--no-sync', '跳过表同步', false).option('-v, --verbose', '详细日志', false).action(devCommand);
|
|
134
|
+
program.command('dev').description('启动开发服务器').option('-p, --port <number>', '端口号', '3000').option('-h, --host <string>', '主机地址', '0.0.0.0').option('--no-sync', '跳过表同步', false).option('-v, --verbose', '详细日志', false).action(wrapCommand(devCommand));
|
|
119
135
|
|
|
120
136
|
// build 命令 - 构建项目
|
|
121
|
-
program.command('build').description('构建项目').option('-o, --outdir <path>', '输出目录', 'dist').option('--minify', '压缩代码', false).option('--sourcemap', '生成 sourcemap', false).action(buildCommand);
|
|
137
|
+
program.command('build').description('构建项目').option('-o, --outdir <path>', '输出目录', 'dist').option('--minify', '压缩代码', false).option('--sourcemap', '生成 sourcemap', false).action(wrapCommand(buildCommand));
|
|
122
138
|
|
|
123
139
|
// start 命令 - 启动生产服务器
|
|
124
|
-
program.command('start').description('启动生产服务器').option('-p, --port <number>', '端口号', '3000').option('-h, --host <string>', '主机地址', '0.0.0.0').option('-c, --cluster <instances>', '集群模式(数字或 max)').action(startCommand);
|
|
140
|
+
program.command('start').description('启动生产服务器').option('-p, --port <number>', '端口号', '3000').option('-h, --host <string>', '主机地址', '0.0.0.0').option('-c, --cluster <instances>', '集群模式(数字或 max)').action(wrapCommand(startCommand));
|
|
125
141
|
|
|
126
142
|
// syncDb 命令 - 同步数据库
|
|
127
|
-
program.command('syncDb').description('同步数据库表结构').option('-t, --table <name>', '指定表名').option('--dry-run', '预览模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(syncDbCommand);
|
|
143
|
+
program.command('syncDb').description('同步数据库表结构').option('-t, --table <name>', '指定表名').option('--dry-run', '预览模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncDbCommand));
|
|
128
144
|
|
|
129
145
|
// syncApi 命令 - 同步 API 接口
|
|
130
|
-
program.command('syncApi').description('同步 API 接口到数据库').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(syncApiCommand);
|
|
146
|
+
program.command('syncApi').description('同步 API 接口到数据库').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncApiCommand));
|
|
131
147
|
|
|
132
148
|
// syncMenu 命令 - 同步菜单
|
|
133
|
-
program.command('syncMenu').description('同步菜单配置到数据库').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(syncMenuCommand);
|
|
149
|
+
program.command('syncMenu').description('同步菜单配置到数据库').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncMenuCommand));
|
|
134
150
|
|
|
135
151
|
// syncDev 命令 - 同步开发者账号
|
|
136
|
-
program.command('syncDev').description('同步开发者管理员账号').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(syncDevCommand);
|
|
137
|
-
|
|
138
|
-
// addon 命令 - 插件管理
|
|
139
|
-
const addon = program.command('addon').description('管理 Befly 插件');
|
|
140
|
-
|
|
141
|
-
addon.command('install <name>').description('安装插件').option('-s, --source <url>', '插件源地址').action(addonCommand.install);
|
|
142
|
-
|
|
143
|
-
addon.command('uninstall <name>').description('卸载插件').option('--keep-data', '保留插件数据', false).action(addonCommand.uninstall);
|
|
144
|
-
|
|
145
|
-
addon.command('list').description('列出已安装的插件').action(addonCommand.list);
|
|
152
|
+
program.command('syncDev').description('同步开发者管理员账号').option('--plan', '计划模式,只显示不执行', false).option('-e, --env <environment>', '指定环境 (development, production, test)').action(wrapCommand(syncDevCommand));
|
|
146
153
|
|
|
147
154
|
// 显示建议和错误
|
|
148
155
|
program.showSuggestionAfterError();
|
package/bin/launcher.ts
CHANGED
|
@@ -71,12 +71,15 @@ export async function launch(entryFile: string): Promise<void> {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
// 确定环境名称
|
|
74
|
-
const
|
|
74
|
+
const longEnvIndex = process.argv.indexOf('--env');
|
|
75
|
+
const shortEnvIndex = process.argv.indexOf('-e');
|
|
75
76
|
let envInput = 'development'; // 默认环境
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
envInput = process.argv[
|
|
78
|
+
// 检查 --env 或 -e 参数
|
|
79
|
+
if (longEnvIndex !== -1 && process.argv[longEnvIndex + 1]) {
|
|
80
|
+
envInput = process.argv[longEnvIndex + 1];
|
|
81
|
+
} else if (shortEnvIndex !== -1 && process.argv[shortEnvIndex + 1]) {
|
|
82
|
+
envInput = process.argv[shortEnvIndex + 1];
|
|
80
83
|
} else if (process.env.NODE_ENV) {
|
|
81
84
|
// 如果设置了 NODE_ENV 环境变量
|
|
82
85
|
envInput = process.env.NODE_ENV;
|
|
@@ -86,10 +89,10 @@ export async function launch(entryFile: string): Promise<void> {
|
|
|
86
89
|
const envName = resolveEnvName(envInput);
|
|
87
90
|
const envFile = `.env.${envName}`;
|
|
88
91
|
|
|
89
|
-
// 过滤掉 --env 参数(已通过 --env-file 处理)
|
|
92
|
+
// 过滤掉 --env/-e 参数(已通过 --env-file 处理)
|
|
90
93
|
const filteredArgs = process.argv.slice(2).filter((arg, i, arr) => {
|
|
91
|
-
if (arg === '--env') return false;
|
|
92
|
-
if (arr[i - 1] === '--env') return false;
|
|
94
|
+
if (arg === '--env' || arg === '-e') return false;
|
|
95
|
+
if (arr[i - 1] === '--env' || arr[i - 1] === '-e') return false;
|
|
93
96
|
return true;
|
|
94
97
|
});
|
|
95
98
|
|
package/commands/build.ts
CHANGED
|
@@ -6,18 +6,7 @@ import { join } from 'pathe';
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
import { Logger } from '../lib/logger.js';
|
|
9
|
-
|
|
10
|
-
function getProjectRoot(): string {
|
|
11
|
-
let current = process.cwd();
|
|
12
|
-
const path = require('node:path');
|
|
13
|
-
while (current !== path.parse(current).root) {
|
|
14
|
-
if (existsSync(join(current, 'package.json'))) {
|
|
15
|
-
return current;
|
|
16
|
-
}
|
|
17
|
-
current = path.dirname(current);
|
|
18
|
-
}
|
|
19
|
-
return process.cwd();
|
|
20
|
-
}
|
|
9
|
+
import { getProjectRoot } from './util.js';
|
|
21
10
|
|
|
22
11
|
interface BuildOptions {
|
|
23
12
|
outdir: string;
|
package/commands/dev.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { join } from 'pathe';
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { Befly } from '../main.js';
|
|
9
|
+
import { getProjectRoot } from './util.js';
|
|
9
10
|
|
|
10
11
|
interface DevOptions {
|
|
11
12
|
port: string;
|
|
@@ -14,17 +15,6 @@ interface DevOptions {
|
|
|
14
15
|
verbose: boolean;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
function getProjectRoot(): string {
|
|
18
|
-
let current = process.cwd();
|
|
19
|
-
while (current !== require('node:path').parse(current).root) {
|
|
20
|
-
if (existsSync(join(current, 'package.json'))) {
|
|
21
|
-
return current;
|
|
22
|
-
}
|
|
23
|
-
current = require('node:path').dirname(current);
|
|
24
|
-
}
|
|
25
|
-
return process.cwd();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
18
|
export async function devCommand(options: DevOptions) {
|
|
29
19
|
try {
|
|
30
20
|
const projectRoot = getProjectRoot();
|
package/commands/index.ts
CHANGED
|
@@ -6,18 +6,7 @@ import { join } from 'pathe';
|
|
|
6
6
|
import { existsSync } from 'node:fs';
|
|
7
7
|
import ora from 'ora';
|
|
8
8
|
import { Logger } from '../lib/logger.js';
|
|
9
|
-
|
|
10
|
-
function getProjectRoot(): string {
|
|
11
|
-
let current = process.cwd();
|
|
12
|
-
const path = require('node:path');
|
|
13
|
-
while (current !== path.parse(current).root) {
|
|
14
|
-
if (existsSync(join(current, 'package.json'))) {
|
|
15
|
-
return current;
|
|
16
|
-
}
|
|
17
|
-
current = path.dirname(current);
|
|
18
|
-
}
|
|
19
|
-
return process.cwd();
|
|
20
|
-
}
|
|
9
|
+
import { getProjectRoot } from './util.js';
|
|
21
10
|
|
|
22
11
|
// ========== Build 命令 ==========
|
|
23
12
|
interface BuildOptions {
|
package/commands/start.ts
CHANGED
|
@@ -7,18 +7,7 @@ import { existsSync } from 'node:fs';
|
|
|
7
7
|
import { Logger } from '../lib/logger.js';
|
|
8
8
|
import { ClusterManager } from '../lifecycle/cluster.js';
|
|
9
9
|
import { Befly } from '../main.js';
|
|
10
|
-
|
|
11
|
-
function getProjectRoot(): string {
|
|
12
|
-
let current = process.cwd();
|
|
13
|
-
const path = require('node:path');
|
|
14
|
-
while (current !== path.parse(current).root) {
|
|
15
|
-
if (existsSync(join(current, 'package.json'))) {
|
|
16
|
-
return current;
|
|
17
|
-
}
|
|
18
|
-
current = path.dirname(current);
|
|
19
|
-
}
|
|
20
|
-
return process.cwd();
|
|
21
|
-
}
|
|
10
|
+
import { getProjectRoot } from './util.js';
|
|
22
11
|
|
|
23
12
|
interface StartOptions {
|
|
24
13
|
port: string;
|
package/commands/util.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands 工具函数
|
|
3
|
+
* 提供命令间可复用的通用功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { join, parse, dirname } from 'pathe';
|
|
7
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 获取项目根目录
|
|
11
|
+
* 向上查找包含 package.json 的目录
|
|
12
|
+
*
|
|
13
|
+
* @returns 项目根目录路径
|
|
14
|
+
*/
|
|
15
|
+
export function getProjectRoot(): string {
|
|
16
|
+
let current = process.cwd();
|
|
17
|
+
const root = parse(current).root;
|
|
18
|
+
|
|
19
|
+
while (current !== root) {
|
|
20
|
+
if (existsSync(join(current, 'package.json'))) {
|
|
21
|
+
return current;
|
|
22
|
+
}
|
|
23
|
+
current = dirname(current);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return process.cwd();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 读取 package.json 文件内容
|
|
31
|
+
*
|
|
32
|
+
* @param pkgPath package.json 文件路径
|
|
33
|
+
* @returns package.json 的内容对象
|
|
34
|
+
*/
|
|
35
|
+
export function readPackageJson(pkgPath: string): Record<string, any> {
|
|
36
|
+
try {
|
|
37
|
+
const content = readFileSync(pkgPath, 'utf-8');
|
|
38
|
+
return JSON.parse(content);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`读取 package.json 失败: ${error}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* 获取指定目录的 package.json 版本号
|
|
46
|
+
*
|
|
47
|
+
* @param dir 目录路径
|
|
48
|
+
* @returns 版本号字符串
|
|
49
|
+
*/
|
|
50
|
+
export function getPackageVersion(dir: string): string {
|
|
51
|
+
try {
|
|
52
|
+
const pkgPath = join(dir, 'package.json');
|
|
53
|
+
const pkg = readPackageJson(pkgPath);
|
|
54
|
+
return pkg.version || '0.0.0';
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return '0.0.0';
|
|
57
|
+
}
|
|
58
|
+
}
|
package/lib/logger.ts
CHANGED
|
@@ -211,4 +211,16 @@ export class Logger {
|
|
|
211
211
|
static clearCache(): void {
|
|
212
212
|
this.currentFiles.clear();
|
|
213
213
|
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 打印当前运行环境
|
|
217
|
+
* 用于命令开始时提示用户当前环境
|
|
218
|
+
*/
|
|
219
|
+
static printEnv(): void {
|
|
220
|
+
const env = process.env.NODE_ENV || 'development';
|
|
221
|
+
const envColor = env === 'production' ? chalk.red : env === 'test' ? chalk.yellow : chalk.green;
|
|
222
|
+
console.log(chalk.gray('────────────────────────────────────────'));
|
|
223
|
+
console.log(chalk.bold('运行环境: ') + envColor.bold(env.toUpperCase()));
|
|
224
|
+
console.log(chalk.gray('────────────────────────────────────────'));
|
|
225
|
+
}
|
|
214
226
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.9",
|
|
4
4
|
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -81,5 +81,5 @@
|
|
|
81
81
|
"ora": "^9.0.0",
|
|
82
82
|
"pathe": "^2.0.3"
|
|
83
83
|
},
|
|
84
|
-
"gitHead": "
|
|
84
|
+
"gitHead": "813660c1474980368c593c0d06d04239f3c012ba"
|
|
85
85
|
}
|
package/commands/addon.ts
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Addon 命令 - 插件管理
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { existsSync, mkdirSync, rmSync } from 'node:fs';
|
|
6
|
-
import { join } from 'pathe';
|
|
7
|
-
import ora from 'ora';
|
|
8
|
-
import { Logger } from '../lib/logger.js';
|
|
9
|
-
|
|
10
|
-
export const addonCommand = {
|
|
11
|
-
async install(name: string, options: { source?: string }) {
|
|
12
|
-
const spinner = ora({
|
|
13
|
-
text: `正在安装插件: ${name}`,
|
|
14
|
-
color: 'cyan',
|
|
15
|
-
spinner: 'dots'
|
|
16
|
-
}).start();
|
|
17
|
-
|
|
18
|
-
try {
|
|
19
|
-
// TODO: 实现插件安装逻辑
|
|
20
|
-
// 1. 从 source 或默认源下载插件
|
|
21
|
-
// 2. 解压到 addons 目录
|
|
22
|
-
// 3. 安装插件依赖
|
|
23
|
-
// 4. 执行插件安装脚本
|
|
24
|
-
|
|
25
|
-
spinner.warn(`插件安装功能开发中`);
|
|
26
|
-
} catch (error) {
|
|
27
|
-
spinner.fail(`插件 ${name} 安装失败`);
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
},
|
|
31
|
-
|
|
32
|
-
async uninstall(name: string, options: { keepData: boolean }) {
|
|
33
|
-
const spinner = ora({
|
|
34
|
-
text: `正在卸载插件: ${name}`,
|
|
35
|
-
color: 'cyan',
|
|
36
|
-
spinner: 'dots'
|
|
37
|
-
}).start();
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
// TODO: 实现插件卸载逻辑
|
|
41
|
-
// 1. 执行插件卸载脚本
|
|
42
|
-
// 2. 删除插件文件
|
|
43
|
-
// 3. 可选:删除插件数据
|
|
44
|
-
|
|
45
|
-
spinner.warn(`插件卸载功能开发中`);
|
|
46
|
-
} catch (error) {
|
|
47
|
-
spinner.fail(`插件 ${name} 卸载失败`);
|
|
48
|
-
throw error;
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
|
|
52
|
-
async list() {
|
|
53
|
-
// TODO: 读取已安装的插件列表
|
|
54
|
-
console.log('已安装的插件:\n');
|
|
55
|
-
console.log('(功能开发中)');
|
|
56
|
-
}
|
|
57
|
-
};
|
package/commands/script.ts
DELETED
|
@@ -1,303 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Script 命令 - 列出并执行项目脚本
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { join, dirname, parse, resolve, basename } from 'pathe';
|
|
6
|
-
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
7
|
-
import { Glob } from 'bun';
|
|
8
|
-
import inquirer from 'inquirer';
|
|
9
|
-
import { Logger } from '../lib/logger.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* 脚本项接口
|
|
13
|
-
*/
|
|
14
|
-
interface ScriptItem {
|
|
15
|
-
/** 脚本名称 */
|
|
16
|
-
name: string;
|
|
17
|
-
/** 脚本来源 (core、project 或 addon 名称) */
|
|
18
|
-
source: string;
|
|
19
|
-
/** 显示名称 */
|
|
20
|
-
displayName: string;
|
|
21
|
-
/** 脚本完整路径 */
|
|
22
|
-
path: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* 查找项目根目录(向上查找 package.json)
|
|
27
|
-
*/
|
|
28
|
-
function findProjectRoot(startDir: string): string | null {
|
|
29
|
-
let currentDir = startDir;
|
|
30
|
-
const root = parse(currentDir).root;
|
|
31
|
-
|
|
32
|
-
while (currentDir !== root) {
|
|
33
|
-
const packagePath = join(currentDir, 'package.json');
|
|
34
|
-
if (existsSync(packagePath)) {
|
|
35
|
-
return currentDir;
|
|
36
|
-
}
|
|
37
|
-
currentDir = dirname(currentDir);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* 安全地列出目录下的所有 .ts 脚本文件
|
|
45
|
-
*/
|
|
46
|
-
function safeList(dir: string): string[] {
|
|
47
|
-
try {
|
|
48
|
-
if (!existsSync(dir)) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const glob = new Glob('*.ts');
|
|
53
|
-
const files = Array.from(
|
|
54
|
-
glob.scanSync({
|
|
55
|
-
cwd: dir,
|
|
56
|
-
absolute: false,
|
|
57
|
-
onlyFiles: true,
|
|
58
|
-
dot: false
|
|
59
|
-
})
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
return files.map((f) => basename(f).replace(/\.ts$/, '')).sort();
|
|
63
|
-
} catch {
|
|
64
|
-
return [];
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 扫描 CLI 自带的 scripts
|
|
70
|
-
*/
|
|
71
|
-
function scanCliScripts(): Array<{ scriptName: string; scriptPath: string }> {
|
|
72
|
-
const results: Array<{ scriptName: string; scriptPath: string }> = [];
|
|
73
|
-
|
|
74
|
-
try {
|
|
75
|
-
// 获取 CLI 包的根目录(当前文件在 commands 目录下)
|
|
76
|
-
const cliRoot = resolve(__dirname, '..');
|
|
77
|
-
const scriptsDir = join(cliRoot, 'scripts');
|
|
78
|
-
|
|
79
|
-
if (!existsSync(scriptsDir)) {
|
|
80
|
-
return results;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const scriptFiles = readdirSync(scriptsDir);
|
|
84
|
-
|
|
85
|
-
for (const file of scriptFiles) {
|
|
86
|
-
// 只处理直接子级的 .ts 文件
|
|
87
|
-
if (file.endsWith('.ts')) {
|
|
88
|
-
const scriptName = file.replace(/\.ts$/, '');
|
|
89
|
-
const scriptPath = join(scriptsDir, file);
|
|
90
|
-
|
|
91
|
-
// 确保是文件而不是目录
|
|
92
|
-
if (statSync(scriptPath).isFile()) {
|
|
93
|
-
results.push({
|
|
94
|
-
scriptName,
|
|
95
|
-
scriptPath
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} catch {
|
|
101
|
-
// 静默失败
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
return results;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* 扫描 node_modules/@befly-addon/* 下的 scripts
|
|
109
|
-
*/
|
|
110
|
-
function scanAddonScripts(projectRoot: string): Array<{ addonName: string; scriptName: string; scriptPath: string }> {
|
|
111
|
-
const results: Array<{ addonName: string; scriptName: string; scriptPath: string }> = [];
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
const beflyAddonsDir = join(projectRoot, 'node_modules', '@befly-addon');
|
|
115
|
-
|
|
116
|
-
if (!existsSync(beflyAddonsDir)) {
|
|
117
|
-
return results;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// 读取 @befly-addon 目录下的所有 addon
|
|
121
|
-
const addonDirs = readdirSync(beflyAddonsDir);
|
|
122
|
-
|
|
123
|
-
for (const addonDir of addonDirs) {
|
|
124
|
-
const scriptsDir = join(beflyAddonsDir, addonDir, 'scripts');
|
|
125
|
-
|
|
126
|
-
if (!existsSync(scriptsDir)) {
|
|
127
|
-
continue;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
const scriptFiles = readdirSync(scriptsDir);
|
|
132
|
-
|
|
133
|
-
for (const file of scriptFiles) {
|
|
134
|
-
if (file.endsWith('.ts')) {
|
|
135
|
-
const scriptName = file.replace(/\.ts$/, '');
|
|
136
|
-
const scriptPath = join(scriptsDir, file);
|
|
137
|
-
|
|
138
|
-
results.push({
|
|
139
|
-
addonName: addonDir,
|
|
140
|
-
scriptName,
|
|
141
|
-
scriptPath
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
} catch {
|
|
146
|
-
// 忽略无法读取的目录
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
} catch (error) {
|
|
150
|
-
// 静默失败
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return results;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* 构建所有可用脚本的列表
|
|
158
|
-
*/
|
|
159
|
-
function buildScriptItems(projectRoot: string): ScriptItem[] {
|
|
160
|
-
const items: ScriptItem[] = [];
|
|
161
|
-
|
|
162
|
-
// CLI 脚本
|
|
163
|
-
const cliScripts = scanCliScripts();
|
|
164
|
-
for (const script of cliScripts) {
|
|
165
|
-
items.push({
|
|
166
|
-
name: script.scriptName,
|
|
167
|
-
source: 'cli',
|
|
168
|
-
displayName: `[CLI] ${script.scriptName}`,
|
|
169
|
-
path: script.scriptPath
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// 项目脚本
|
|
174
|
-
const projectScriptsDir = join(projectRoot, 'scripts');
|
|
175
|
-
const projectList = safeList(projectScriptsDir);
|
|
176
|
-
for (const name of projectList) {
|
|
177
|
-
items.push({
|
|
178
|
-
name,
|
|
179
|
-
source: 'project',
|
|
180
|
-
displayName: `[Project] ${name}`,
|
|
181
|
-
path: resolve(projectScriptsDir, `${name}.ts`)
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Addon 脚本
|
|
186
|
-
const addonScripts = scanAddonScripts(projectRoot);
|
|
187
|
-
for (const addon of addonScripts) {
|
|
188
|
-
items.push({
|
|
189
|
-
name: addon.scriptName,
|
|
190
|
-
source: `addon:${addon.addonName}`,
|
|
191
|
-
displayName: `[${addon.addonName}] ${addon.scriptName}`,
|
|
192
|
-
path: addon.scriptPath
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
return items;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* 运行脚本
|
|
201
|
-
*/
|
|
202
|
-
async function runScript(scriptPath: string, args: string[] = []): Promise<number> {
|
|
203
|
-
const bunExe = process.execPath || 'bun';
|
|
204
|
-
const child = Bun.spawn({
|
|
205
|
-
cmd: [bunExe, scriptPath, ...args],
|
|
206
|
-
stdio: ['inherit', 'inherit', 'inherit'],
|
|
207
|
-
cwd: process.cwd(),
|
|
208
|
-
env: { ...process.env }
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const code = await child.exited;
|
|
212
|
-
return code ?? 0;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Script 命令处理函数
|
|
217
|
-
*/
|
|
218
|
-
export async function scriptCommand(options: { dryRun?: boolean; plan?: boolean } = {}) {
|
|
219
|
-
try {
|
|
220
|
-
// 查找项目根目录
|
|
221
|
-
const projectRoot = findProjectRoot();
|
|
222
|
-
if (!projectRoot) {
|
|
223
|
-
Logger.error('未找到项目根目录(缺少 package.json)');
|
|
224
|
-
process.exit(1);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// 构建脚本列表
|
|
228
|
-
const items = buildScriptItems(projectRoot);
|
|
229
|
-
|
|
230
|
-
if (items.length === 0) {
|
|
231
|
-
Logger.warn('没有找到可用的脚本');
|
|
232
|
-
process.exit(0);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// 使用 inquirer 选择脚本
|
|
236
|
-
const { selectedScript } = await inquirer.prompt([
|
|
237
|
-
{
|
|
238
|
-
type: 'list',
|
|
239
|
-
name: 'selectedScript',
|
|
240
|
-
message: '请选择要执行的脚本:',
|
|
241
|
-
choices: items.map((item) => ({
|
|
242
|
-
name: item.displayName,
|
|
243
|
-
value: item
|
|
244
|
-
})),
|
|
245
|
-
loop: false
|
|
246
|
-
}
|
|
247
|
-
]);
|
|
248
|
-
|
|
249
|
-
// 询问是否添加 --plan 参数
|
|
250
|
-
let scriptArgs: string[] = [];
|
|
251
|
-
if (options.plan !== false) {
|
|
252
|
-
const { addPlan } = await inquirer.prompt([
|
|
253
|
-
{
|
|
254
|
-
type: 'confirm',
|
|
255
|
-
name: 'addPlan',
|
|
256
|
-
message: '是否添加 --plan 参数(预演模式)?',
|
|
257
|
-
default: false
|
|
258
|
-
}
|
|
259
|
-
]);
|
|
260
|
-
|
|
261
|
-
if (addPlan) {
|
|
262
|
-
scriptArgs.push('--plan');
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// 预演模式
|
|
267
|
-
if (options.dryRun) {
|
|
268
|
-
Logger.info(`[预演模式] 将执行: ${selectedScript.displayName}`);
|
|
269
|
-
Logger.info(`[预演模式] 脚本路径: ${selectedScript.path}`);
|
|
270
|
-
if (scriptArgs.length > 0) {
|
|
271
|
-
Logger.info(`[预演模式] 参数: ${scriptArgs.join(' ')}`);
|
|
272
|
-
}
|
|
273
|
-
process.exit(0);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// 确认执行
|
|
277
|
-
const { confirm } = await inquirer.prompt([
|
|
278
|
-
{
|
|
279
|
-
type: 'confirm',
|
|
280
|
-
name: 'confirm',
|
|
281
|
-
message: `确认执行 ${selectedScript.displayName}?`,
|
|
282
|
-
default: true
|
|
283
|
-
}
|
|
284
|
-
]);
|
|
285
|
-
|
|
286
|
-
if (!confirm) {
|
|
287
|
-
Logger.info('已取消执行');
|
|
288
|
-
process.exit(0);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// 执行脚本
|
|
292
|
-
const argsInfo = scriptArgs.length > 0 ? ` (参数: ${scriptArgs.join(' ')})` : '';
|
|
293
|
-
Logger.info(`正在执行: ${selectedScript.displayName}${argsInfo}\n`);
|
|
294
|
-
|
|
295
|
-
const exitCode = await runScript(selectedScript.path, scriptArgs);
|
|
296
|
-
process.exit(exitCode);
|
|
297
|
-
} catch (error) {
|
|
298
|
-
if (error instanceof Error) {
|
|
299
|
-
Logger.error(`执行失败: ${error.message}`);
|
|
300
|
-
}
|
|
301
|
-
process.exit(1);
|
|
302
|
-
}
|
|
303
|
-
}
|