befly 3.2.0 → 3.3.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/index.ts +138 -0
- package/checks/conflict.ts +35 -25
- package/checks/table.ts +6 -6
- package/commands/addon.ts +57 -0
- package/commands/build.ts +74 -0
- package/commands/dev.ts +94 -0
- package/commands/index.ts +252 -0
- package/commands/script.ts +308 -0
- package/commands/start.ts +80 -0
- package/commands/syncApi.ts +328 -0
- package/{scripts → commands}/syncDb/apply.ts +2 -2
- package/{scripts → commands}/syncDb/constants.ts +13 -7
- package/{scripts → commands}/syncDb/ddl.ts +7 -5
- package/{scripts → commands}/syncDb/helpers.ts +18 -18
- package/{scripts → commands}/syncDb/index.ts +37 -23
- package/{scripts → commands}/syncDb/sqlite.ts +1 -1
- package/{scripts → commands}/syncDb/state.ts +10 -4
- package/{scripts → commands}/syncDb/table.ts +7 -7
- package/{scripts → commands}/syncDb/tableCreate.ts +7 -6
- package/{scripts → commands}/syncDb/types.ts +5 -5
- package/{scripts → commands}/syncDb/version.ts +1 -1
- package/commands/syncDb.ts +35 -0
- package/commands/syncDev.ts +174 -0
- package/commands/syncMenu.ts +368 -0
- package/config/env.ts +4 -4
- package/config/menu.json +67 -0
- package/{utils/crypto.ts → lib/cipher.ts} +16 -67
- package/lib/database.ts +296 -0
- package/{utils → lib}/dbHelper.ts +102 -56
- package/{utils → lib}/jwt.ts +124 -151
- package/{utils → lib}/logger.ts +47 -24
- package/lib/middleware.ts +271 -0
- package/{utils → lib}/redisHelper.ts +4 -4
- package/{utils/validate.ts → lib/validator.ts} +101 -78
- package/lifecycle/bootstrap.ts +63 -0
- package/lifecycle/checker.ts +165 -0
- package/lifecycle/cluster.ts +241 -0
- package/lifecycle/lifecycle.ts +139 -0
- package/lifecycle/loader.ts +513 -0
- package/main.ts +14 -12
- package/package.json +21 -9
- package/paths.ts +34 -0
- package/plugins/cache.ts +187 -0
- package/plugins/db.ts +4 -4
- package/plugins/logger.ts +1 -1
- package/plugins/redis.ts +4 -4
- package/router/api.ts +155 -0
- package/router/root.ts +53 -0
- package/router/static.ts +76 -0
- package/types/api.d.ts +0 -36
- package/types/befly.d.ts +8 -6
- package/types/common.d.ts +1 -1
- package/types/context.d.ts +3 -3
- package/types/util.d.ts +45 -0
- package/util.ts +301 -0
- package/config/fields.ts +0 -55
- package/config/regexAliases.ts +0 -51
- package/config/reserved.ts +0 -96
- package/scripts/syncDb/tests/constants.test.ts +0 -105
- package/scripts/syncDb/tests/ddl.test.ts +0 -134
- package/scripts/syncDb/tests/helpers.test.ts +0 -70
- package/scripts/syncDb.ts +0 -10
- package/types/index.d.ts +0 -450
- package/types/index.ts +0 -438
- package/types/validator.ts +0 -43
- package/utils/colors.ts +0 -221
- package/utils/database.ts +0 -348
- package/utils/helper.ts +0 -812
- package/utils/index.ts +0 -33
- package/utils/requestContext.ts +0 -167
- /package/{scripts → commands}/syncDb/schema.ts +0 -0
- /package/{utils → lib}/sqlBuilder.ts +0 -0
- /package/{utils → lib}/xml.ts +0 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build、Start、Sync、Addon、SyncApi、SyncMenu、SyncDev 命令实现
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from 'pathe';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import ora from 'ora';
|
|
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
|
+
}
|
|
21
|
+
|
|
22
|
+
// ========== Build 命令 ==========
|
|
23
|
+
interface BuildOptions {
|
|
24
|
+
outdir: string;
|
|
25
|
+
minify: boolean;
|
|
26
|
+
sourcemap: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function buildCommand(options: BuildOptions) {
|
|
30
|
+
try {
|
|
31
|
+
const projectRoot = getProjectRoot();
|
|
32
|
+
const mainFile = join(projectRoot, 'main.ts');
|
|
33
|
+
|
|
34
|
+
if (!existsSync(mainFile)) {
|
|
35
|
+
Logger.error('未找到 main.ts 文件');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const spinner = ora({
|
|
40
|
+
text: '正在构建项目...',
|
|
41
|
+
color: 'cyan',
|
|
42
|
+
spinner: 'dots'
|
|
43
|
+
}).start();
|
|
44
|
+
|
|
45
|
+
const args = ['build', mainFile, '--outdir', options.outdir, '--target', 'bun'];
|
|
46
|
+
|
|
47
|
+
if (options.minify) {
|
|
48
|
+
args.push('--minify');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (options.sourcemap) {
|
|
52
|
+
args.push('--sourcemap');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const proc = Bun.spawn(['bun', ...args], {
|
|
56
|
+
cwd: projectRoot,
|
|
57
|
+
stdout: 'pipe',
|
|
58
|
+
stderr: 'pipe'
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
await proc.exited;
|
|
62
|
+
|
|
63
|
+
if (proc.exitCode === 0) {
|
|
64
|
+
spinner.succeed('项目构建完成');
|
|
65
|
+
Logger.success(`输出目录: ${options.outdir}`);
|
|
66
|
+
} else {
|
|
67
|
+
spinner.fail('项目构建失败');
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
Logger.error('构建失败:');
|
|
72
|
+
console.error(error);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ========== Start 命令 ==========
|
|
78
|
+
interface StartOptions {
|
|
79
|
+
port: string;
|
|
80
|
+
host: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function startCommand(options: StartOptions) {
|
|
84
|
+
try {
|
|
85
|
+
const projectRoot = getProjectRoot();
|
|
86
|
+
const mainFile = join(projectRoot, 'main.ts');
|
|
87
|
+
|
|
88
|
+
if (!existsSync(mainFile)) {
|
|
89
|
+
Logger.error('未找到 main.ts 文件');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
process.env.NODE_ENV = 'production';
|
|
94
|
+
process.env.APP_PORT = options.port;
|
|
95
|
+
process.env.APP_HOST = options.host;
|
|
96
|
+
|
|
97
|
+
Logger.info('正在启动生产服务器...\n');
|
|
98
|
+
Logger.info(`端口: ${options.port}`);
|
|
99
|
+
Logger.info(`主机: ${options.host}`);
|
|
100
|
+
Logger.info(`环境: production\n`);
|
|
101
|
+
|
|
102
|
+
const proc = Bun.spawn(['bun', 'run', mainFile], {
|
|
103
|
+
cwd: projectRoot,
|
|
104
|
+
stdout: 'inherit',
|
|
105
|
+
stderr: 'inherit',
|
|
106
|
+
stdin: 'inherit',
|
|
107
|
+
env: {
|
|
108
|
+
...process.env,
|
|
109
|
+
FORCE_COLOR: '1'
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await proc.exited;
|
|
114
|
+
process.exit(proc.exitCode || 0);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
Logger.error('启动失败:');
|
|
117
|
+
console.error(error);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ========== Sync 命令 ==========
|
|
123
|
+
interface SyncOptions {
|
|
124
|
+
table?: string;
|
|
125
|
+
force: boolean;
|
|
126
|
+
dryRun: boolean;
|
|
127
|
+
drop: boolean;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export async function syncCommand(options: SyncOptions) {
|
|
131
|
+
try {
|
|
132
|
+
const projectRoot = getProjectRoot();
|
|
133
|
+
const syncScript = join(projectRoot, 'node_modules', 'befly', 'scripts', 'syncTable.ts');
|
|
134
|
+
|
|
135
|
+
if (!existsSync(syncScript)) {
|
|
136
|
+
Logger.error('未找到同步脚本,请确保已安装 befly');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const spinner = ora({
|
|
141
|
+
text: '正在同步数据库表...',
|
|
142
|
+
color: 'cyan',
|
|
143
|
+
spinner: 'dots'
|
|
144
|
+
}).start();
|
|
145
|
+
|
|
146
|
+
const args = ['run', syncScript];
|
|
147
|
+
|
|
148
|
+
if (options.table) {
|
|
149
|
+
args.push('--table', options.table);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (options.force) {
|
|
153
|
+
args.push('--force');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (options.dryRun) {
|
|
157
|
+
args.push('--dry-run');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (options.drop) {
|
|
161
|
+
args.push('--drop');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const proc = Bun.spawn(['bun', ...args], {
|
|
165
|
+
cwd: projectRoot,
|
|
166
|
+
stdout: 'inherit',
|
|
167
|
+
stderr: 'inherit',
|
|
168
|
+
stdin: 'inherit'
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await proc.exited;
|
|
172
|
+
|
|
173
|
+
if (proc.exitCode === 0) {
|
|
174
|
+
spinner.succeed('数据库同步完成');
|
|
175
|
+
} else {
|
|
176
|
+
spinner.fail('数据库同步失败');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
} catch (error) {
|
|
180
|
+
Logger.error('同步失败:');
|
|
181
|
+
console.error(error);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ========== Addon 命令 ==========
|
|
187
|
+
export const addonCommand = {
|
|
188
|
+
async install(name: string, options: { source?: string }) {
|
|
189
|
+
const spinner = ora({
|
|
190
|
+
text: `正在安装插件: ${name}`,
|
|
191
|
+
color: 'cyan',
|
|
192
|
+
spinner: 'dots'
|
|
193
|
+
}).start();
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
// TODO: 实现插件安装逻辑
|
|
197
|
+
// 1. 从 source 或默认源下载插件
|
|
198
|
+
// 2. 解压到 addons 目录
|
|
199
|
+
// 3. 安装插件依赖
|
|
200
|
+
// 4. 执行插件安装脚本
|
|
201
|
+
|
|
202
|
+
spinner.succeed(`插件 ${name} 安装成功`);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
spinner.fail(`插件 ${name} 安装失败`);
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
|
|
209
|
+
async uninstall(name: string, options: { keepData: boolean }) {
|
|
210
|
+
const spinner = ora({
|
|
211
|
+
text: `正在卸载插件: ${name}`,
|
|
212
|
+
color: 'cyan',
|
|
213
|
+
spinner: 'dots'
|
|
214
|
+
}).start();
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
// TODO: 实现插件卸载逻辑
|
|
218
|
+
// 1. 执行插件卸载脚本
|
|
219
|
+
// 2. 删除插件文件
|
|
220
|
+
// 3. 可选:删除插件数据
|
|
221
|
+
|
|
222
|
+
spinner.succeed(`插件 ${name} 卸载成功`);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
spinner.fail(`插件 ${name} 卸载失败`);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
},
|
|
228
|
+
|
|
229
|
+
async list() {
|
|
230
|
+
try {
|
|
231
|
+
const projectRoot = getProjectRoot();
|
|
232
|
+
const addonsDir = join(projectRoot, 'addons');
|
|
233
|
+
|
|
234
|
+
if (!existsSync(addonsDir)) {
|
|
235
|
+
Logger.info('未找到 addons 目录');
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// TODO: 读取已安装的插件列表
|
|
240
|
+
Logger.info('已安装的插件:\n');
|
|
241
|
+
Logger.info('(功能开发中)');
|
|
242
|
+
} catch (error) {
|
|
243
|
+
Logger.error('获取插件列表失败:');
|
|
244
|
+
console.error(error);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// ========== 导出新增的同步命令 ==========
|
|
250
|
+
export { syncApiCommand } from './syncApi.js';
|
|
251
|
+
export { syncMenuCommand } from './syncMenu.js';
|
|
252
|
+
export { syncDevCommand } from './syncDev.js';
|
|
@@ -0,0 +1,308 @@
|
|
|
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');
|
|
115
|
+
|
|
116
|
+
if (!existsSync(beflyAddonsDir)) {
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 读取 @befly 目录下的所有 addon
|
|
121
|
+
const addonDirs = readdirSync(beflyAddonsDir);
|
|
122
|
+
|
|
123
|
+
for (const addonDir of addonDirs) {
|
|
124
|
+
// 只处理 addon-* 开头的目录
|
|
125
|
+
if (!addonDir.startsWith('addon-')) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const scriptsDir = join(beflyAddonsDir, addonDir, 'scripts');
|
|
130
|
+
|
|
131
|
+
if (!existsSync(scriptsDir)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const scriptFiles = readdirSync(scriptsDir);
|
|
137
|
+
|
|
138
|
+
for (const file of scriptFiles) {
|
|
139
|
+
if (file.endsWith('.ts')) {
|
|
140
|
+
const scriptName = file.replace(/\.ts$/, '');
|
|
141
|
+
const scriptPath = join(scriptsDir, file);
|
|
142
|
+
|
|
143
|
+
results.push({
|
|
144
|
+
addonName: addonDir,
|
|
145
|
+
scriptName,
|
|
146
|
+
scriptPath
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
} catch {
|
|
151
|
+
// 忽略无法读取的目录
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch (error) {
|
|
155
|
+
// 静默失败
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return results;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 构建所有可用脚本的列表
|
|
163
|
+
*/
|
|
164
|
+
function buildScriptItems(projectRoot: string): ScriptItem[] {
|
|
165
|
+
const items: ScriptItem[] = [];
|
|
166
|
+
|
|
167
|
+
// CLI 脚本
|
|
168
|
+
const cliScripts = scanCliScripts();
|
|
169
|
+
for (const script of cliScripts) {
|
|
170
|
+
items.push({
|
|
171
|
+
name: script.scriptName,
|
|
172
|
+
source: 'cli',
|
|
173
|
+
displayName: `[CLI] ${script.scriptName}`,
|
|
174
|
+
path: script.scriptPath
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 项目脚本
|
|
179
|
+
const projectScriptsDir = join(projectRoot, 'scripts');
|
|
180
|
+
const projectList = safeList(projectScriptsDir);
|
|
181
|
+
for (const name of projectList) {
|
|
182
|
+
items.push({
|
|
183
|
+
name,
|
|
184
|
+
source: 'project',
|
|
185
|
+
displayName: `[Project] ${name}`,
|
|
186
|
+
path: resolve(projectScriptsDir, `${name}.ts`)
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Addon 脚本
|
|
191
|
+
const addonScripts = scanAddonScripts(projectRoot);
|
|
192
|
+
for (const addon of addonScripts) {
|
|
193
|
+
items.push({
|
|
194
|
+
name: addon.scriptName,
|
|
195
|
+
source: `addon:${addon.addonName}`,
|
|
196
|
+
displayName: `[${addon.addonName}] ${addon.scriptName}`,
|
|
197
|
+
path: addon.scriptPath
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return items;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* 运行脚本
|
|
206
|
+
*/
|
|
207
|
+
async function runScript(scriptPath: string, args: string[] = []): Promise<number> {
|
|
208
|
+
const bunExe = process.execPath || 'bun';
|
|
209
|
+
const child = Bun.spawn({
|
|
210
|
+
cmd: [bunExe, scriptPath, ...args],
|
|
211
|
+
stdio: ['inherit', 'inherit', 'inherit'],
|
|
212
|
+
cwd: process.cwd(),
|
|
213
|
+
env: { ...process.env }
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const code = await child.exited;
|
|
217
|
+
return code ?? 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Script 命令处理函数
|
|
222
|
+
*/
|
|
223
|
+
export async function scriptCommand(options: { dryRun?: boolean; plan?: boolean } = {}) {
|
|
224
|
+
try {
|
|
225
|
+
// 查找项目根目录
|
|
226
|
+
const projectRoot = findProjectRoot();
|
|
227
|
+
if (!projectRoot) {
|
|
228
|
+
Logger.error('未找到项目根目录(缺少 package.json)');
|
|
229
|
+
process.exit(1);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 构建脚本列表
|
|
233
|
+
const items = buildScriptItems(projectRoot);
|
|
234
|
+
|
|
235
|
+
if (items.length === 0) {
|
|
236
|
+
Logger.warn('没有找到可用的脚本');
|
|
237
|
+
process.exit(0);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// 使用 inquirer 选择脚本
|
|
241
|
+
const { selectedScript } = await inquirer.prompt([
|
|
242
|
+
{
|
|
243
|
+
type: 'list',
|
|
244
|
+
name: 'selectedScript',
|
|
245
|
+
message: '请选择要执行的脚本:',
|
|
246
|
+
choices: items.map((item) => ({
|
|
247
|
+
name: item.displayName,
|
|
248
|
+
value: item
|
|
249
|
+
})),
|
|
250
|
+
loop: false
|
|
251
|
+
}
|
|
252
|
+
]);
|
|
253
|
+
|
|
254
|
+
// 询问是否添加 --plan 参数
|
|
255
|
+
let scriptArgs: string[] = [];
|
|
256
|
+
if (options.plan !== false) {
|
|
257
|
+
const { addPlan } = await inquirer.prompt([
|
|
258
|
+
{
|
|
259
|
+
type: 'confirm',
|
|
260
|
+
name: 'addPlan',
|
|
261
|
+
message: '是否添加 --plan 参数(预演模式)?',
|
|
262
|
+
default: false
|
|
263
|
+
}
|
|
264
|
+
]);
|
|
265
|
+
|
|
266
|
+
if (addPlan) {
|
|
267
|
+
scriptArgs.push('--plan');
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 预演模式
|
|
272
|
+
if (options.dryRun) {
|
|
273
|
+
Logger.info(`[预演模式] 将执行: ${selectedScript.displayName}`);
|
|
274
|
+
Logger.info(`[预演模式] 脚本路径: ${selectedScript.path}`);
|
|
275
|
+
if (scriptArgs.length > 0) {
|
|
276
|
+
Logger.info(`[预演模式] 参数: ${scriptArgs.join(' ')}`);
|
|
277
|
+
}
|
|
278
|
+
process.exit(0);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// 确认执行
|
|
282
|
+
const { confirm } = await inquirer.prompt([
|
|
283
|
+
{
|
|
284
|
+
type: 'confirm',
|
|
285
|
+
name: 'confirm',
|
|
286
|
+
message: `确认执行 ${selectedScript.displayName}?`,
|
|
287
|
+
default: true
|
|
288
|
+
}
|
|
289
|
+
]);
|
|
290
|
+
|
|
291
|
+
if (!confirm) {
|
|
292
|
+
Logger.info('已取消执行');
|
|
293
|
+
process.exit(0);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// 执行脚本
|
|
297
|
+
const argsInfo = scriptArgs.length > 0 ? ` (参数: ${scriptArgs.join(' ')})` : '';
|
|
298
|
+
Logger.info(`正在执行: ${selectedScript.displayName}${argsInfo}\n`);
|
|
299
|
+
|
|
300
|
+
const exitCode = await runScript(selectedScript.path, scriptArgs);
|
|
301
|
+
process.exit(exitCode);
|
|
302
|
+
} catch (error) {
|
|
303
|
+
if (error instanceof Error) {
|
|
304
|
+
Logger.error(`执行失败: ${error.message}`);
|
|
305
|
+
}
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Start 命令 - 启动生产服务器
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from 'pathe';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { Logger } from '../lib/logger.js';
|
|
8
|
+
import { ClusterManager } from '../lifecycle/cluster.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
|
+
}
|
|
21
|
+
|
|
22
|
+
interface StartOptions {
|
|
23
|
+
port: string;
|
|
24
|
+
host: string;
|
|
25
|
+
cluster?: string; // 集群模式:数字或 'max'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function startCommand(options: StartOptions) {
|
|
29
|
+
try {
|
|
30
|
+
const projectRoot = getProjectRoot();
|
|
31
|
+
const mainFile = join(projectRoot, 'main.ts');
|
|
32
|
+
|
|
33
|
+
if (!existsSync(mainFile)) {
|
|
34
|
+
Logger.error('未找到 main.ts 文件');
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 设置生产环境变量
|
|
39
|
+
process.env.NODE_ENV = 'production';
|
|
40
|
+
process.env.APP_PORT = options.port;
|
|
41
|
+
process.env.APP_HOST = options.host;
|
|
42
|
+
|
|
43
|
+
// 检查是否使用集群模式
|
|
44
|
+
if (options.cluster) {
|
|
45
|
+
// 集群模式
|
|
46
|
+
const instances = options.cluster === 'max' ? 'max' : parseInt(options.cluster);
|
|
47
|
+
|
|
48
|
+
const clusterManager = new ClusterManager({
|
|
49
|
+
instances,
|
|
50
|
+
startPort: parseInt(options.port),
|
|
51
|
+
host: options.host,
|
|
52
|
+
projectRoot,
|
|
53
|
+
mainFile
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await clusterManager.start();
|
|
57
|
+
} else {
|
|
58
|
+
// 单进程模式
|
|
59
|
+
Logger.info('正在启动生产服务器...\n');
|
|
60
|
+
Logger.info(`端口: ${options.port}`);
|
|
61
|
+
Logger.info(`主机: ${options.host}`);
|
|
62
|
+
Logger.info(`环境: production\n`);
|
|
63
|
+
|
|
64
|
+
// 检查环境变量文件
|
|
65
|
+
const envFile = join(projectRoot, '.env.production');
|
|
66
|
+
if (existsSync(envFile)) {
|
|
67
|
+
Logger.info(`环境变量文件: .env.production\n`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 直接导入并运行 main.ts(Bun 会自动加载 .env.production)
|
|
71
|
+
await import(mainFile);
|
|
72
|
+
|
|
73
|
+
// 注意:正常情况下不会执行到这里,因为 main.ts 会启动服务器并持续运行
|
|
74
|
+
}
|
|
75
|
+
} catch (error) {
|
|
76
|
+
Logger.error('启动失败:');
|
|
77
|
+
console.error(error);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|