@fecommunity/reactpress-cli 0.1.0 → 0.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/reactpress.js +246 -0
- package/lib/api-dev-runner.js +6 -0
- package/lib/api-dev.js +84 -0
- package/lib/bootstrap.js +109 -0
- package/lib/build.js +23 -0
- package/lib/dev.js +109 -0
- package/lib/docker.js +156 -0
- package/lib/http.js +80 -0
- package/lib/lifecycle.js +138 -0
- package/lib/paths.js +75 -0
- package/lib/pm2.js +31 -0
- package/lib/process.js +45 -0
- package/lib/publish.js +947 -0
- package/lib/root.js +31 -0
- package/lib/spawn.js +85 -0
- package/lib/status.js +54 -0
- package/package.json +32 -52
- package/ui/banner.js +16 -0
- package/ui/interactive.js +158 -0
- package/ui/theme.js +25 -0
- package/toolkit/dist/api/Article.d.ts +0 -123
- package/toolkit/dist/api/Article.js +0 -188
- package/toolkit/dist/api/Auth.d.ts +0 -32
- package/toolkit/dist/api/Auth.js +0 -58
- package/toolkit/dist/api/Category.d.ts +0 -51
- package/toolkit/dist/api/Category.js +0 -84
- package/toolkit/dist/api/Comment.d.ts +0 -60
- package/toolkit/dist/api/Comment.js +0 -97
- package/toolkit/dist/api/File.d.ts +0 -42
- package/toolkit/dist/api/File.js +0 -71
- package/toolkit/dist/api/HttpClient.d.ts +0 -41
- package/toolkit/dist/api/HttpClient.js +0 -96
- package/toolkit/dist/api/Knowledge.d.ts +0 -78
- package/toolkit/dist/api/Knowledge.js +0 -123
- package/toolkit/dist/api/Page.d.ts +0 -60
- package/toolkit/dist/api/Page.js +0 -97
- package/toolkit/dist/api/Search.d.ts +0 -32
- package/toolkit/dist/api/Search.js +0 -58
- package/toolkit/dist/api/Setting.d.ts +0 -24
- package/toolkit/dist/api/Setting.js +0 -45
- package/toolkit/dist/api/Smtp.d.ts +0 -33
- package/toolkit/dist/api/Smtp.js +0 -58
- package/toolkit/dist/api/Tag.d.ts +0 -60
- package/toolkit/dist/api/Tag.js +0 -97
- package/toolkit/dist/api/User.d.ts +0 -43
- package/toolkit/dist/api/User.js +0 -72
- package/toolkit/dist/api/View.d.ts +0 -51
- package/toolkit/dist/api/View.js +0 -84
- package/toolkit/dist/api/index.d.ts +0 -13
- package/toolkit/dist/api/index.js +0 -30
- package/toolkit/dist/api/instance.d.ts +0 -47
- package/toolkit/dist/api/instance.js +0 -71
- package/toolkit/dist/config/env.d.ts +0 -1
- package/toolkit/dist/config/env.js +0 -89
- package/toolkit/dist/config/global.d.ts +0 -34
- package/toolkit/dist/config/global.js +0 -382
- package/toolkit/dist/config/i18n.d.ts +0 -4
- package/toolkit/dist/config/i18n.js +0 -43
- package/toolkit/dist/config/index.d.ts +0 -15
- package/toolkit/dist/config/index.js +0 -25
- package/toolkit/dist/index.d.ts +0 -11
- package/toolkit/dist/index.js +0 -38
- package/toolkit/dist/locales/en.json +0 -97
- package/toolkit/dist/locales/zh.json +0 -97
- package/toolkit/dist/types/index.d.ts +0 -245
- package/toolkit/dist/types/index.js +0 -12
- package/toolkit/dist/utils/index.d.ts +0 -33
- package/toolkit/dist/utils/index.js +0 -59
- package/toolkit/package.json +0 -19
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ReactPress unified CLI — init, dev, build, server, docker, publish.
|
|
5
|
+
* Run without arguments for an interactive menu (Claude Code–style).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { Command } = require('commander');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const chalk = require('chalk');
|
|
11
|
+
const { ensureOriginalCwd, getMonorepoRoot } = require('../lib/root');
|
|
12
|
+
const { ensureProjectEnvironment, initMonorepoProject } = require('../lib/bootstrap');
|
|
13
|
+
const { runDev } = require('../lib/dev');
|
|
14
|
+
const { runApiDev } = require('../lib/api-dev');
|
|
15
|
+
const { runLifecycleCommand } = require('../lib/lifecycle');
|
|
16
|
+
const { runDockerCommand } = require('../lib/docker');
|
|
17
|
+
const { printUnifiedStatus } = require('../lib/status');
|
|
18
|
+
const { runBuild } = require('../lib/build');
|
|
19
|
+
const { startApiWithPm2 } = require('../lib/pm2');
|
|
20
|
+
const { runNodeScript, runReactpressCli } = require('../lib/spawn');
|
|
21
|
+
const { getClientBin } = require('../lib/paths');
|
|
22
|
+
const { runInteractiveLoop } = require('../ui/interactive');
|
|
23
|
+
|
|
24
|
+
const rootPkg = require(path.join(getMonorepoRoot(), 'package.json'));
|
|
25
|
+
|
|
26
|
+
const program = new Command();
|
|
27
|
+
|
|
28
|
+
program
|
|
29
|
+
.name('reactpress')
|
|
30
|
+
.description('ReactPress 全栈 CLI — 初始化、开发、构建、部署、发布')
|
|
31
|
+
.version(rootPkg.version);
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.command('init')
|
|
35
|
+
.description('初始化项目 (.reactpress/config.json + .env + Docker MySQL)')
|
|
36
|
+
.argument('[directory]', '项目目录', '.')
|
|
37
|
+
.option('-f, --force', '覆盖已有配置')
|
|
38
|
+
.action(async (directory, options) => {
|
|
39
|
+
const projectRoot = path.resolve(directory);
|
|
40
|
+
process.env.REACTPRESS_ORIGINAL_CWD = projectRoot;
|
|
41
|
+
const { isMonorepoCheckout } = require('../lib/bootstrap');
|
|
42
|
+
if (isMonorepoCheckout(projectRoot)) {
|
|
43
|
+
const result = await initMonorepoProject(projectRoot, { force: !!options.force });
|
|
44
|
+
console.log(`[reactpress] ${result.message}`);
|
|
45
|
+
process.exit(result.ok ? 0 : 1);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const args = ['init', directory];
|
|
49
|
+
if (options.force) args.push('--force');
|
|
50
|
+
runReactpressCli(args, { cwd: projectRoot });
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
program
|
|
54
|
+
.command('dev')
|
|
55
|
+
.description('零配置开发: 环境检查 + toolkit 构建 + API + 前端')
|
|
56
|
+
.option('--api-only', '仅启动 API (watch)')
|
|
57
|
+
.option('--client-only', '仅启动前端')
|
|
58
|
+
.action(async (options) => {
|
|
59
|
+
const projectRoot = ensureOriginalCwd();
|
|
60
|
+
try {
|
|
61
|
+
if (options.clientOnly) {
|
|
62
|
+
await runNodeScript(getClientBin(), [], { cwd: projectRoot });
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (options.apiOnly) {
|
|
66
|
+
await runApiDev(projectRoot);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
await runDev(projectRoot);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
console.error(chalk.red('[reactpress]'), err.message || err);
|
|
72
|
+
process.exit(err.exitCode ?? 1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const serverCmd = program.command('server').description('管理 API 服务');
|
|
77
|
+
|
|
78
|
+
serverCmd
|
|
79
|
+
.command('start')
|
|
80
|
+
.description('启动 API(等待 HTTP 就绪)')
|
|
81
|
+
.option('--pm2', '使用 PM2 启动(生产)')
|
|
82
|
+
.option('--bg', '后台启动,不等待 HTTP')
|
|
83
|
+
.action(async (options) => {
|
|
84
|
+
const projectRoot = ensureOriginalCwd();
|
|
85
|
+
try {
|
|
86
|
+
if (options.pm2) {
|
|
87
|
+
await startApiWithPm2(projectRoot);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const cmd = options.bg ? 'start:bg' : 'start';
|
|
91
|
+
const code = await runLifecycleCommand(cmd, projectRoot);
|
|
92
|
+
process.exit(code ?? 0);
|
|
93
|
+
} catch (err) {
|
|
94
|
+
console.error(chalk.red('[reactpress]'), err.message || err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
serverCmd.command('stop').description('停止 API').action(async () => {
|
|
100
|
+
const code = await runLifecycleCommand('stop', ensureOriginalCwd());
|
|
101
|
+
process.exit(code ?? 0);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
serverCmd.command('restart').description('重启 API').action(async () => {
|
|
105
|
+
const code = await runLifecycleCommand('restart', ensureOriginalCwd());
|
|
106
|
+
process.exit(code ?? 0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
serverCmd.command('status').description('查看 API 状态').action(async () => {
|
|
110
|
+
await runLifecycleCommand('status', ensureOriginalCwd());
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const clientCmd = program.command('client').description('管理前端');
|
|
114
|
+
|
|
115
|
+
clientCmd
|
|
116
|
+
.command('start')
|
|
117
|
+
.description('启动 Next.js 客户端')
|
|
118
|
+
.option('--pm2', '使用 PM2 启动')
|
|
119
|
+
.action(async (options) => {
|
|
120
|
+
const args = options.pm2 ? ['--pm2'] : [];
|
|
121
|
+
await runNodeScript(getClientBin(), args, { cwd: ensureOriginalCwd() });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
program
|
|
125
|
+
.command('build')
|
|
126
|
+
.description('构建生产产物')
|
|
127
|
+
.option('-t, --target <target>', 'toolkit | server | client | docs | all', 'all')
|
|
128
|
+
.action(async (options) => {
|
|
129
|
+
try {
|
|
130
|
+
await runBuild(options.target, ensureOriginalCwd());
|
|
131
|
+
} catch (err) {
|
|
132
|
+
console.error(chalk.red('[reactpress]'), err.message || err);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
const dockerCmd = program.command('docker').description('Docker 开发环境 (MySQL + nginx)');
|
|
138
|
+
|
|
139
|
+
dockerCmd
|
|
140
|
+
.command('up')
|
|
141
|
+
.description('仅启动 Docker 服务并等待 MySQL')
|
|
142
|
+
.action(async () => {
|
|
143
|
+
await runDockerCommand('up', ensureOriginalCwd());
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
dockerCmd
|
|
147
|
+
.command('down')
|
|
148
|
+
.alias('stop')
|
|
149
|
+
.description('停止 Docker 服务')
|
|
150
|
+
.action(async () => {
|
|
151
|
+
await runDockerCommand('down', ensureOriginalCwd());
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
dockerCmd
|
|
155
|
+
.command('start')
|
|
156
|
+
.description('启动 Docker + 全栈开发 (API + 前端)')
|
|
157
|
+
.action(async () => {
|
|
158
|
+
await runDockerCommand('start', ensureOriginalCwd());
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
dockerCmd.command('restart').description('重启 Docker 服务').action(async () => {
|
|
162
|
+
await runDockerCommand('restart', ensureOriginalCwd());
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
dockerCmd.command('status').description('查看 Docker 容器状态').action(async () => {
|
|
166
|
+
await runDockerCommand('status', ensureOriginalCwd());
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
dockerCmd
|
|
170
|
+
.command('logs [service]')
|
|
171
|
+
.description('查看 Docker 日志 (db | nginx)')
|
|
172
|
+
.action(async (service) => {
|
|
173
|
+
await runDockerCommand('logs', ensureOriginalCwd(), service ? [service] : []);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
program
|
|
177
|
+
.command('status')
|
|
178
|
+
.description('查看项目、API、前端、Docker 综合状态')
|
|
179
|
+
.action(async () => {
|
|
180
|
+
await printUnifiedStatus(ensureOriginalCwd());
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
program
|
|
184
|
+
.command('publish')
|
|
185
|
+
.description('构建并发布 npm 包 (交互式)')
|
|
186
|
+
.option('--build', '仅构建所有包')
|
|
187
|
+
.option('--publish', '交互式发布')
|
|
188
|
+
.action(async (options) => {
|
|
189
|
+
const prev = process.argv.slice();
|
|
190
|
+
const args = [process.argv[0], process.argv[1]];
|
|
191
|
+
if (options.build) args.push('--build');
|
|
192
|
+
else if (options.publish) args.push('--publish');
|
|
193
|
+
else args.push('--publish');
|
|
194
|
+
process.argv = args;
|
|
195
|
+
try {
|
|
196
|
+
await require('../lib/publish').main();
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.error(chalk.red('[reactpress]'), err.message || err);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
} finally {
|
|
201
|
+
process.argv = prev;
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
program
|
|
206
|
+
.command('start')
|
|
207
|
+
.description('生产模式: 启动 API + 前端')
|
|
208
|
+
.action(async () => {
|
|
209
|
+
const projectRoot = ensureOriginalCwd();
|
|
210
|
+
const { spawn } = require('child_process');
|
|
211
|
+
const code = await runLifecycleCommand('start', projectRoot);
|
|
212
|
+
if (code !== 0) process.exit(code);
|
|
213
|
+
const child = spawn('pnpm', ['run', '--dir', './client', 'start'], {
|
|
214
|
+
stdio: 'inherit',
|
|
215
|
+
shell: true,
|
|
216
|
+
cwd: projectRoot,
|
|
217
|
+
});
|
|
218
|
+
child.on('close', (c) => process.exit(c ?? 0));
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
program.on('--help', () => {
|
|
222
|
+
console.log('');
|
|
223
|
+
console.log(chalk.gray('示例:'));
|
|
224
|
+
console.log(' reactpress 交互式菜单');
|
|
225
|
+
console.log(' reactpress dev 零配置全栈开发');
|
|
226
|
+
console.log(' reactpress init --force 重新初始化配置');
|
|
227
|
+
console.log(' reactpress server start 启动 API');
|
|
228
|
+
console.log(' reactpress status 综合状态');
|
|
229
|
+
console.log(' reactpress docker start Docker + 全栈');
|
|
230
|
+
console.log(' reactpress publish 发布 npm 包');
|
|
231
|
+
console.log('');
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
async function main() {
|
|
235
|
+
const argv = process.argv.slice(2);
|
|
236
|
+
if (argv.length === 0) {
|
|
237
|
+
await runInteractiveLoop();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
program.parse(process.argv);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
main().catch((err) => {
|
|
244
|
+
console.error(chalk.red('[reactpress]'), err.message || err);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
});
|
package/lib/api-dev.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const { spawn, spawnSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureProjectEnvironment } = require('./bootstrap');
|
|
4
|
+
const { isUsingMonorepoServer } = require('./paths');
|
|
5
|
+
const { getMonorepoRoot } = require('./root');
|
|
6
|
+
|
|
7
|
+
let apiChild;
|
|
8
|
+
|
|
9
|
+
function stopApiDev(projectRoot) {
|
|
10
|
+
if (apiChild && !apiChild.killed) {
|
|
11
|
+
apiChild.kill('SIGTERM');
|
|
12
|
+
}
|
|
13
|
+
if (!isUsingMonorepoServer()) {
|
|
14
|
+
spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], {
|
|
15
|
+
cwd: projectRoot,
|
|
16
|
+
stdio: 'inherit',
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function startApiDev(projectRoot) {
|
|
22
|
+
if (isUsingMonorepoServer()) {
|
|
23
|
+
console.log('[reactpress] 开发模式: server/ (nest start --watch)');
|
|
24
|
+
apiChild = spawn('pnpm', ['run', '--dir', './server', 'dev'], {
|
|
25
|
+
cwd: projectRoot,
|
|
26
|
+
stdio: 'inherit',
|
|
27
|
+
shell: true,
|
|
28
|
+
env: {
|
|
29
|
+
...process.env,
|
|
30
|
+
REACTPRESS_ORIGINAL_CWD: projectRoot,
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
console.log('[reactpress] 开发模式: reactpress-cli(未找到 server/src)');
|
|
35
|
+
const start = spawnSync('pnpm', ['exec', 'reactpress-cli', 'start'], {
|
|
36
|
+
cwd: projectRoot,
|
|
37
|
+
stdio: 'inherit',
|
|
38
|
+
});
|
|
39
|
+
if (start.status !== 0) {
|
|
40
|
+
process.exit(start.status ?? 1);
|
|
41
|
+
}
|
|
42
|
+
console.log('[reactpress] API 已由 reactpress-cli 启动。');
|
|
43
|
+
process.stdin.resume();
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (apiChild) {
|
|
48
|
+
apiChild.on('close', (code) => {
|
|
49
|
+
process.exit(code ?? 0);
|
|
50
|
+
});
|
|
51
|
+
console.log('[reactpress] 按 Ctrl+C 停止 API。');
|
|
52
|
+
console.log('[reactpress] 单独停止: reactpress server stop');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function runApiDev(projectRoot = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) {
|
|
57
|
+
try {
|
|
58
|
+
await ensureProjectEnvironment(projectRoot);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error('[reactpress] 环境准备失败:', err.message || err);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
process.on('SIGINT', () => {
|
|
65
|
+
stopApiDev(projectRoot);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
});
|
|
68
|
+
process.on('SIGTERM', () => {
|
|
69
|
+
stopApiDev(projectRoot);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
startApiDev(projectRoot);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function getApiDevScriptPath() {
|
|
77
|
+
return path.join(__dirname, 'api-dev-runner.js');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = {
|
|
81
|
+
runApiDev,
|
|
82
|
+
stopApiDev,
|
|
83
|
+
getApiDevScriptPath,
|
|
84
|
+
};
|
package/lib/bootstrap.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { pathToFileURL } = require('url');
|
|
4
|
+
const { getMonorepoRoot, isMonorepoCheckout } = require('./root');
|
|
5
|
+
const { getCliPackageRoot } = require('./paths');
|
|
6
|
+
|
|
7
|
+
async function importCliModule(relativePath) {
|
|
8
|
+
const modulePath = path.join(getCliPackageRoot(), 'dist', relativePath);
|
|
9
|
+
return import(pathToFileURL(modulePath).href);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async function copyTemplateFile(src, dest) {
|
|
13
|
+
await fs.promises.mkdir(path.dirname(dest), { recursive: true });
|
|
14
|
+
await fs.promises.copyFile(src, dest);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function initMonorepoProject(projectRoot, { force = false } = {}) {
|
|
18
|
+
const { getProjectPaths, getTemplatesDir } = await importCliModule('utils/paths.js');
|
|
19
|
+
const { saveConfig, syncEnvFromConfig } = await importCliModule('services/config.js');
|
|
20
|
+
const { ensureDatabase, ensureDatabaseHostPort } = await importCliModule('services/database.js');
|
|
21
|
+
|
|
22
|
+
const paths = getProjectPaths(projectRoot);
|
|
23
|
+
const templatesDir = getTemplatesDir();
|
|
24
|
+
|
|
25
|
+
if (fs.existsSync(paths.configPath) && !force) {
|
|
26
|
+
const config = await (await importCliModule('services/config.js')).loadConfig(projectRoot);
|
|
27
|
+
await ensureDatabaseHostPort(projectRoot, undefined, config);
|
|
28
|
+
const dbResult = await ensureDatabase(projectRoot, config);
|
|
29
|
+
if (!dbResult.ok) {
|
|
30
|
+
return { ok: false, projectRoot, message: dbResult.message };
|
|
31
|
+
}
|
|
32
|
+
return { ok: true, projectRoot, message: '配置已存在,数据库已就绪。' };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
await fs.promises.mkdir(paths.reactpressDir, { recursive: true });
|
|
36
|
+
await copyTemplateFile(
|
|
37
|
+
path.join(templatesDir, 'docker-compose.yml'),
|
|
38
|
+
paths.dockerComposePath
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const config = JSON.parse(
|
|
42
|
+
await fs.promises.readFile(path.join(templatesDir, 'config.default.json'), 'utf8')
|
|
43
|
+
);
|
|
44
|
+
await saveConfig(projectRoot, config);
|
|
45
|
+
await syncEnvFromConfig(projectRoot, config);
|
|
46
|
+
|
|
47
|
+
if (!fs.existsSync(paths.envPath) || force) {
|
|
48
|
+
await copyTemplateFile(path.join(templatesDir, 'env.default'), paths.envPath);
|
|
49
|
+
await syncEnvFromConfig(projectRoot, config);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
await ensureDatabaseHostPort(projectRoot, undefined, config);
|
|
53
|
+
const dbResult = await ensureDatabase(projectRoot, config);
|
|
54
|
+
|
|
55
|
+
if (!dbResult.ok) {
|
|
56
|
+
return {
|
|
57
|
+
ok: true,
|
|
58
|
+
projectRoot,
|
|
59
|
+
message: `项目已创建,但数据库未就绪: ${dbResult.message}。请确认 Docker 已启动后重试 reactpress dev。`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
ok: true,
|
|
65
|
+
projectRoot,
|
|
66
|
+
message: 'ReactPress 开发环境已就绪(配置 + 数据库)。',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function ensureProjectEnvironment(projectRoot = getMonorepoRoot()) {
|
|
71
|
+
const root = path.resolve(projectRoot);
|
|
72
|
+
const { setProjectCwd } = await importCliModule('utils/cli-context.js');
|
|
73
|
+
setProjectCwd(root);
|
|
74
|
+
|
|
75
|
+
const { isReactPressProject, loadConfig } = await importCliModule('services/config.js');
|
|
76
|
+
const { ensureDatabase, ensureDatabaseHostPort } = await importCliModule('services/database.js');
|
|
77
|
+
|
|
78
|
+
if (!(await isReactPressProject(root))) {
|
|
79
|
+
if (isMonorepoCheckout(root)) {
|
|
80
|
+
const result = await initMonorepoProject(root);
|
|
81
|
+
if (!result.ok) {
|
|
82
|
+
throw new Error(result.message || '初始化失败');
|
|
83
|
+
}
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const { initProject } = await importCliModule('services/init.js');
|
|
88
|
+
const result = await initProject({ directory: root, force: false });
|
|
89
|
+
if (!result.ok) {
|
|
90
|
+
throw new Error(result.message || 'reactpress-cli init 失败');
|
|
91
|
+
}
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const config = await loadConfig(root);
|
|
96
|
+
await ensureDatabaseHostPort(root, undefined, config);
|
|
97
|
+
const dbResult = await ensureDatabase(root, config);
|
|
98
|
+
if (!dbResult.ok) {
|
|
99
|
+
throw new Error(dbResult.message || '数据库未就绪,请检查 Docker 与 .env 中的 DB_* 配置');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { ok: true, projectRoot: root, message: '数据库已就绪' };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
ensureProjectEnvironment,
|
|
107
|
+
initMonorepoProject,
|
|
108
|
+
isMonorepoCheckout,
|
|
109
|
+
};
|
package/lib/build.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const { runSync } = require('./spawn');
|
|
2
|
+
const { ensureOriginalCwd } = require('./root');
|
|
3
|
+
|
|
4
|
+
const TARGETS = {
|
|
5
|
+
toolkit: ['build:toolkit'],
|
|
6
|
+
server: ['build:server'],
|
|
7
|
+
client: ['build:client'],
|
|
8
|
+
docs: ['build:docs'],
|
|
9
|
+
all: ['build'],
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
|
|
13
|
+
const script = TARGETS[target];
|
|
14
|
+
if (!script) {
|
|
15
|
+
throw new Error(`未知构建目标: ${target},可选: ${Object.keys(TARGETS).join(', ')}`);
|
|
16
|
+
}
|
|
17
|
+
for (const name of script) {
|
|
18
|
+
console.log(`[reactpress] 构建: ${name}`);
|
|
19
|
+
runSync('pnpm', ['run', name], { cwd: projectRoot });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { runBuild, TARGETS };
|
package/lib/dev.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureProjectEnvironment } = require('./bootstrap');
|
|
4
|
+
const { loadServerSiteUrl, waitForHttp } = require('./http');
|
|
5
|
+
const { ensureOriginalCwd } = require('./root');
|
|
6
|
+
|
|
7
|
+
const API_READY_TIMEOUT_MS = 180_000;
|
|
8
|
+
|
|
9
|
+
let apiChild;
|
|
10
|
+
let webChild;
|
|
11
|
+
let shuttingDown = false;
|
|
12
|
+
|
|
13
|
+
function shutdown(signal = 'SIGINT') {
|
|
14
|
+
if (shuttingDown) return;
|
|
15
|
+
shuttingDown = true;
|
|
16
|
+
if (webChild && !webChild.killed) {
|
|
17
|
+
webChild.kill(signal);
|
|
18
|
+
}
|
|
19
|
+
if (apiChild && !apiChild.killed) {
|
|
20
|
+
apiChild.kill(signal);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function buildToolkit(projectRoot) {
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const build = spawn('pnpm', ['build:toolkit'], {
|
|
27
|
+
stdio: 'inherit',
|
|
28
|
+
shell: true,
|
|
29
|
+
cwd: projectRoot,
|
|
30
|
+
});
|
|
31
|
+
build.on('close', (code) => {
|
|
32
|
+
if (code !== 0) {
|
|
33
|
+
reject(new Error(`toolkit 构建失败,退出码: ${code}`));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
resolve();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function startDevStack(projectRoot) {
|
|
42
|
+
const serverUrl = loadServerSiteUrl(projectRoot);
|
|
43
|
+
const apiDevRunner = path.join(__dirname, 'api-dev-runner.js');
|
|
44
|
+
|
|
45
|
+
console.log('[reactpress] 正在启动 API(首次可能需安装依赖,请稍候)…');
|
|
46
|
+
apiChild = spawn(process.execPath, [apiDevRunner], {
|
|
47
|
+
stdio: 'inherit',
|
|
48
|
+
cwd: projectRoot,
|
|
49
|
+
env: {
|
|
50
|
+
...process.env,
|
|
51
|
+
REACTPRESS_ORIGINAL_CWD: projectRoot,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
apiChild.on('close', (code) => {
|
|
56
|
+
if (shuttingDown) {
|
|
57
|
+
process.exit(code ?? 0);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (webChild && !webChild.killed) {
|
|
61
|
+
webChild.kill('SIGINT');
|
|
62
|
+
}
|
|
63
|
+
process.exit(code ?? 1);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log(`[reactpress] 等待 API 就绪: ${serverUrl}`);
|
|
67
|
+
const ready = await waitForHttp(serverUrl, API_READY_TIMEOUT_MS);
|
|
68
|
+
if (!ready) {
|
|
69
|
+
console.error(
|
|
70
|
+
`[reactpress] API 在 ${API_READY_TIMEOUT_MS / 1000}s 内未就绪。请检查 Docker/MySQL 与 .env 中的 DB_* 配置。`
|
|
71
|
+
);
|
|
72
|
+
shutdown('SIGINT');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log('[reactpress] API 已就绪,正在启动前端…');
|
|
77
|
+
webChild = spawn('pnpm', ['run', '--dir', './client', 'dev'], {
|
|
78
|
+
stdio: 'inherit',
|
|
79
|
+
shell: true,
|
|
80
|
+
cwd: projectRoot,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
webChild.on('close', (code) => {
|
|
84
|
+
if (!shuttingDown) {
|
|
85
|
+
shutdown('SIGINT');
|
|
86
|
+
}
|
|
87
|
+
process.exit(code ?? 0);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function runDev(projectRoot = ensureOriginalCwd()) {
|
|
92
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
93
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = await ensureProjectEnvironment(projectRoot);
|
|
97
|
+
if (result.message && !result.message.includes('已就绪')) {
|
|
98
|
+
console.log(`[reactpress] ${result.message}`);
|
|
99
|
+
}
|
|
100
|
+
} catch (err) {
|
|
101
|
+
console.error('[reactpress] 环境准备失败:', err.message || err);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await buildToolkit(projectRoot);
|
|
106
|
+
await startDevStack(projectRoot);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { runDev, buildToolkit, startDevStack };
|