@fecommunity/reactpress-cli 0.1.0 → 0.2.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 +7 -0
- 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 +33 -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
package/lib/docker.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
const { spawn, execSync } = require('child_process');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { ensureOriginalCwd } = require('./root');
|
|
4
|
+
|
|
5
|
+
function isDockerRunning() {
|
|
6
|
+
try {
|
|
7
|
+
execSync('docker info', { stdio: 'ignore' });
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function stopDockerServices(projectRoot) {
|
|
15
|
+
console.log('[reactpress] 正在停止 Docker 服务…');
|
|
16
|
+
try {
|
|
17
|
+
execSync('docker-compose -f docker-compose.dev.yml down', {
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
cwd: projectRoot,
|
|
20
|
+
});
|
|
21
|
+
console.log('[reactpress] Docker 服务已停止。');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
console.error('[reactpress] 停止 Docker 失败:', error.message);
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function startDockerServices(projectRoot) {
|
|
29
|
+
console.log('[reactpress] 正在启动 Docker 服务…');
|
|
30
|
+
if (!isDockerRunning()) {
|
|
31
|
+
throw new Error('Docker 未运行,请先启动 Docker Desktop。');
|
|
32
|
+
}
|
|
33
|
+
execSync('docker-compose -f docker-compose.dev.yml up -d', {
|
|
34
|
+
stdio: 'inherit',
|
|
35
|
+
cwd: projectRoot,
|
|
36
|
+
});
|
|
37
|
+
console.log('[reactpress] Docker 服务已启动。');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function waitForMysql(maxAttempts = 30) {
|
|
41
|
+
console.log('[reactpress] 等待 MySQL 就绪…');
|
|
42
|
+
let attempts = 0;
|
|
43
|
+
while (attempts < maxAttempts) {
|
|
44
|
+
try {
|
|
45
|
+
execSync('docker exec reactpress_db mysql -u reactpress -preactpress -e "SELECT 1"', {
|
|
46
|
+
stdio: 'ignore',
|
|
47
|
+
});
|
|
48
|
+
console.log('[reactpress] MySQL 已就绪。');
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
attempts += 1;
|
|
52
|
+
if (attempts % 5 === 0) {
|
|
53
|
+
console.log(`[reactpress] 等待 MySQL… (${attempts}/${maxAttempts})`);
|
|
54
|
+
}
|
|
55
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
console.error('[reactpress] MySQL 在超时时间内未就绪。');
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function dockerStartWithDev(projectRoot) {
|
|
63
|
+
startDockerServices(projectRoot);
|
|
64
|
+
const ready = await waitForMysql();
|
|
65
|
+
if (!ready) {
|
|
66
|
+
throw new Error('MySQL 未就绪');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { buildToolkit } = require('./dev');
|
|
70
|
+
await buildToolkit(projectRoot);
|
|
71
|
+
|
|
72
|
+
const apiRunner = path.join(__dirname, 'api-dev-runner.js');
|
|
73
|
+
console.log('[reactpress] 启动 API + 前端 (Docker MySQL)…');
|
|
74
|
+
console.log('[reactpress] 访问: http://localhost:8080 (nginx) / http://localhost:3001 (client)');
|
|
75
|
+
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const child = spawn(
|
|
78
|
+
'npx',
|
|
79
|
+
[
|
|
80
|
+
'concurrently',
|
|
81
|
+
'-n',
|
|
82
|
+
'api,web',
|
|
83
|
+
'-c',
|
|
84
|
+
'blue,green',
|
|
85
|
+
`node "${apiRunner}"`,
|
|
86
|
+
'pnpm run --dir ./client dev',
|
|
87
|
+
],
|
|
88
|
+
{
|
|
89
|
+
stdio: 'inherit',
|
|
90
|
+
shell: true,
|
|
91
|
+
cwd: projectRoot,
|
|
92
|
+
env: {
|
|
93
|
+
...process.env,
|
|
94
|
+
REACTPRESS_ORIGINAL_CWD: projectRoot,
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
child.on('error', reject);
|
|
100
|
+
child.on('close', (code) => {
|
|
101
|
+
if (code !== 0) {
|
|
102
|
+
reject(Object.assign(new Error(`开发进程退出: ${code}`), { exitCode: code }));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
resolve();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function runDockerCommand(command, projectRoot = ensureOriginalCwd(), extraArgs = []) {
|
|
111
|
+
switch (command) {
|
|
112
|
+
case 'up':
|
|
113
|
+
startDockerServices(projectRoot);
|
|
114
|
+
await waitForMysql();
|
|
115
|
+
return;
|
|
116
|
+
case 'down':
|
|
117
|
+
stopDockerServices(projectRoot);
|
|
118
|
+
return;
|
|
119
|
+
case 'start':
|
|
120
|
+
await dockerStartWithDev(projectRoot);
|
|
121
|
+
return;
|
|
122
|
+
case 'stop':
|
|
123
|
+
stopDockerServices(projectRoot);
|
|
124
|
+
return;
|
|
125
|
+
case 'restart':
|
|
126
|
+
stopDockerServices(projectRoot);
|
|
127
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
128
|
+
startDockerServices(projectRoot);
|
|
129
|
+
await waitForMysql();
|
|
130
|
+
return;
|
|
131
|
+
case 'status':
|
|
132
|
+
execSync('docker-compose -f docker-compose.dev.yml ps', {
|
|
133
|
+
stdio: 'inherit',
|
|
134
|
+
cwd: projectRoot,
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
case 'logs': {
|
|
138
|
+
const service = extraArgs[0] || '';
|
|
139
|
+
execSync(`docker-compose -f docker-compose.dev.yml logs -f ${service}`.trim(), {
|
|
140
|
+
stdio: 'inherit',
|
|
141
|
+
cwd: projectRoot,
|
|
142
|
+
});
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
default:
|
|
146
|
+
throw new Error(`未知 docker 命令: ${command}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
runDockerCommand,
|
|
152
|
+
startDockerServices,
|
|
153
|
+
stopDockerServices,
|
|
154
|
+
waitForMysql,
|
|
155
|
+
isDockerRunning,
|
|
156
|
+
};
|
package/lib/http.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function loadServerSiteUrl(projectRoot) {
|
|
6
|
+
const envPath = path.join(projectRoot, '.env');
|
|
7
|
+
try {
|
|
8
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
9
|
+
const match = content.match(/^SERVER_SITE_URL=(.+)$/m);
|
|
10
|
+
if (match) {
|
|
11
|
+
return match[1].trim().replace(/^['"]|['"]$/g, '');
|
|
12
|
+
}
|
|
13
|
+
} catch {
|
|
14
|
+
// ignore
|
|
15
|
+
}
|
|
16
|
+
return 'http://localhost:3002';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function loadClientSiteUrl(projectRoot) {
|
|
20
|
+
const envPath = path.join(projectRoot, '.env');
|
|
21
|
+
try {
|
|
22
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
23
|
+
const match = content.match(/^CLIENT_SITE_URL=(.+)$/m);
|
|
24
|
+
if (match) {
|
|
25
|
+
return match[1].trim().replace(/^['"]|['"]$/g, '');
|
|
26
|
+
}
|
|
27
|
+
} catch {
|
|
28
|
+
// ignore
|
|
29
|
+
}
|
|
30
|
+
return 'http://localhost:3001';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isHttpResponding(url, timeoutMs = 2000) {
|
|
34
|
+
return new Promise((resolve) => {
|
|
35
|
+
let parsed;
|
|
36
|
+
try {
|
|
37
|
+
parsed = new URL(url);
|
|
38
|
+
} catch {
|
|
39
|
+
resolve(false);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const port = parsed.port || (parsed.protocol === 'https:' ? 443 : 80);
|
|
44
|
+
const req = http.request(
|
|
45
|
+
{
|
|
46
|
+
hostname: parsed.hostname,
|
|
47
|
+
port,
|
|
48
|
+
path: parsed.pathname || '/',
|
|
49
|
+
method: 'GET',
|
|
50
|
+
timeout: timeoutMs,
|
|
51
|
+
},
|
|
52
|
+
(res) => resolve(res.statusCode > 0)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
req.on('timeout', () => {
|
|
56
|
+
req.destroy();
|
|
57
|
+
resolve(false);
|
|
58
|
+
});
|
|
59
|
+
req.on('error', () => resolve(false));
|
|
60
|
+
req.end();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function waitForHttp(url, timeoutMs = 120_000, intervalMs = 500) {
|
|
65
|
+
const deadline = Date.now() + timeoutMs;
|
|
66
|
+
while (Date.now() < deadline) {
|
|
67
|
+
if (await isHttpResponding(url)) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = {
|
|
76
|
+
loadServerSiteUrl,
|
|
77
|
+
loadClientSiteUrl,
|
|
78
|
+
isHttpResponding,
|
|
79
|
+
waitForHttp,
|
|
80
|
+
};
|
package/lib/lifecycle.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
const { spawn, spawnSync } = require('child_process');
|
|
2
|
+
const { ensureProjectEnvironment } = require('./bootstrap');
|
|
3
|
+
const { loadServerSiteUrl, waitForHttp } = require('./http');
|
|
4
|
+
const {
|
|
5
|
+
getServerBin,
|
|
6
|
+
getServerDir,
|
|
7
|
+
isUsingMonorepoServer,
|
|
8
|
+
getPidFile,
|
|
9
|
+
} = require('./paths');
|
|
10
|
+
const { readPid, isProcessRunning, clearPidFile, writePid } = require('./process');
|
|
11
|
+
const { getMonorepoRoot } = require('./root');
|
|
12
|
+
|
|
13
|
+
async function ensureConfig(projectRoot) {
|
|
14
|
+
try {
|
|
15
|
+
await ensureProjectEnvironment(projectRoot);
|
|
16
|
+
return true;
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error('[reactpress] 环境准备失败:', err.message || err);
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function stopApi(projectRoot) {
|
|
24
|
+
const pid = readPid(projectRoot);
|
|
25
|
+
if (pid && isProcessRunning(pid)) {
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 'SIGTERM');
|
|
28
|
+
console.log(`[reactpress] 已停止 API 进程 (pid ${pid})`);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.warn(`[reactpress] 停止 pid ${pid} 失败:`, err.message);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
clearPidFile(projectRoot);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async function startApi(projectRoot, { wait = true } = {}) {
|
|
37
|
+
if (!(await ensureConfig(projectRoot))) {
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const existing = readPid(projectRoot);
|
|
42
|
+
if (existing && isProcessRunning(existing)) {
|
|
43
|
+
console.log(`[reactpress] API 已在运行 (pid ${existing})`);
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
clearPidFile(projectRoot);
|
|
47
|
+
|
|
48
|
+
if (!isUsingMonorepoServer()) {
|
|
49
|
+
console.warn('[reactpress] 未检测到 server/src,回退到 reactpress-cli start');
|
|
50
|
+
const start = spawnSync('pnpm', ['exec', 'reactpress-cli', 'start'], {
|
|
51
|
+
cwd: projectRoot,
|
|
52
|
+
stdio: 'inherit',
|
|
53
|
+
});
|
|
54
|
+
return start.status ?? 1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log('[reactpress] 正在启动本地 API (server/)…');
|
|
58
|
+
const child = spawn(process.execPath, [getServerBin()], {
|
|
59
|
+
cwd: getServerDir(),
|
|
60
|
+
detached: true,
|
|
61
|
+
stdio: 'ignore',
|
|
62
|
+
env: {
|
|
63
|
+
...process.env,
|
|
64
|
+
REACTPRESS_ORIGINAL_CWD: projectRoot,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
child.unref();
|
|
69
|
+
writePid(projectRoot, child.pid);
|
|
70
|
+
console.log(`[reactpress] API 已后台启动 (pid ${child.pid})`);
|
|
71
|
+
|
|
72
|
+
if (!wait) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const serverUrl = loadServerSiteUrl(projectRoot);
|
|
77
|
+
const ready = await waitForHttp(serverUrl);
|
|
78
|
+
if (!ready) {
|
|
79
|
+
console.error(`[reactpress] API 在 120s 内未就绪: ${serverUrl}`);
|
|
80
|
+
return 1;
|
|
81
|
+
}
|
|
82
|
+
console.log(`[reactpress] API 已就绪: ${serverUrl}`);
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function statusApi(projectRoot) {
|
|
87
|
+
const pid = readPid(projectRoot);
|
|
88
|
+
const serverUrl = loadServerSiteUrl(projectRoot);
|
|
89
|
+
const { isHttpResponding } = require('./http');
|
|
90
|
+
const httpOk = await isHttpResponding(serverUrl);
|
|
91
|
+
|
|
92
|
+
console.log('[reactpress] API 状态');
|
|
93
|
+
console.log(` 来源: ${isUsingMonorepoServer() ? 'monorepo server/' : 'reactpress-cli bundle'}`);
|
|
94
|
+
console.log(` PID 文件: ${getPidFile(projectRoot)}`);
|
|
95
|
+
console.log(` 记录 PID: ${pid ?? '(无)'}`);
|
|
96
|
+
console.log(` 进程存活: ${pid ? (isProcessRunning(pid) ? '是' : '否') : '—'}`);
|
|
97
|
+
console.log(` HTTP (${serverUrl}): ${httpOk ? '可访问' : '不可访问'}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function runLifecycleCommand(command, projectRoot = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) {
|
|
101
|
+
switch (command) {
|
|
102
|
+
case 'start':
|
|
103
|
+
return startApi(projectRoot, { wait: true });
|
|
104
|
+
case 'start:bg':
|
|
105
|
+
return startApi(projectRoot, { wait: false });
|
|
106
|
+
case 'stop':
|
|
107
|
+
stopApi(projectRoot);
|
|
108
|
+
if (!isUsingMonorepoServer()) {
|
|
109
|
+
spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], {
|
|
110
|
+
cwd: projectRoot,
|
|
111
|
+
stdio: 'inherit',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
case 'restart':
|
|
116
|
+
stopApi(projectRoot);
|
|
117
|
+
if (!isUsingMonorepoServer()) {
|
|
118
|
+
spawnSync('pnpm', ['exec', 'reactpress-cli', 'restart'], {
|
|
119
|
+
cwd: projectRoot,
|
|
120
|
+
stdio: 'inherit',
|
|
121
|
+
});
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
return startApi(projectRoot, { wait: true });
|
|
125
|
+
case 'status':
|
|
126
|
+
await statusApi(projectRoot);
|
|
127
|
+
return 0;
|
|
128
|
+
default:
|
|
129
|
+
throw new Error(`未知 lifecycle 命令: ${command}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
startApi,
|
|
135
|
+
stopApi,
|
|
136
|
+
statusApi,
|
|
137
|
+
runLifecycleCommand,
|
|
138
|
+
};
|
package/lib/paths.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getMonorepoRoot } = require('./root');
|
|
4
|
+
|
|
5
|
+
function getMonorepoServerDir() {
|
|
6
|
+
return path.join(getMonorepoRoot(), 'server');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function hasMonorepoServerSource() {
|
|
10
|
+
return fs.existsSync(path.join(getMonorepoServerDir(), 'src', 'main.ts'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getCliPackageRoot() {
|
|
14
|
+
const ownRoot = path.join(__dirname, '..');
|
|
15
|
+
if (fs.existsSync(path.join(ownRoot, 'dist', 'index.js'))) {
|
|
16
|
+
return ownRoot;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return path.dirname(
|
|
20
|
+
require.resolve('@fecommunity/reactpress-cli-core/package.json')
|
|
21
|
+
);
|
|
22
|
+
} catch {
|
|
23
|
+
return path.dirname(require.resolve('@fecommunity/reactpress-cli/package.json'));
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getBundledServerDir() {
|
|
28
|
+
return path.join(getCliPackageRoot(), 'server');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getServerDir() {
|
|
32
|
+
if (hasMonorepoServerSource()) {
|
|
33
|
+
return getMonorepoServerDir();
|
|
34
|
+
}
|
|
35
|
+
return getBundledServerDir();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getServerBin() {
|
|
39
|
+
return path.join(getServerDir(), 'bin', 'reactpress-server.js');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getSwaggerPath() {
|
|
43
|
+
return path.join(getServerDir(), 'public', 'swagger.json');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getServerMain() {
|
|
47
|
+
return path.join(getServerDir(), 'dist', 'main.js');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isUsingMonorepoServer() {
|
|
51
|
+
return hasMonorepoServerSource();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getClientBin() {
|
|
55
|
+
return path.join(getMonorepoRoot(), 'client', 'bin', 'reactpress-client.js');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function getPidFile(projectRoot) {
|
|
59
|
+
return path.join(projectRoot, '.reactpress', 'server.pid');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
getMonorepoRoot,
|
|
64
|
+
getMonorepoServerDir,
|
|
65
|
+
hasMonorepoServerSource,
|
|
66
|
+
isUsingMonorepoServer,
|
|
67
|
+
getCliPackageRoot,
|
|
68
|
+
getBundledServerDir,
|
|
69
|
+
getServerDir,
|
|
70
|
+
getServerBin,
|
|
71
|
+
getSwaggerPath,
|
|
72
|
+
getServerMain,
|
|
73
|
+
getClientBin,
|
|
74
|
+
getPidFile,
|
|
75
|
+
};
|
package/lib/pm2.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const { getServerBin, getServerDir } = require('./paths');
|
|
3
|
+
const { ensureOriginalCwd } = require('./root');
|
|
4
|
+
|
|
5
|
+
function startApiWithPm2(projectRoot = ensureOriginalCwd()) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const child = spawn(process.execPath, [getServerBin(), '--pm2'], {
|
|
8
|
+
stdio: 'inherit',
|
|
9
|
+
cwd: getServerDir(),
|
|
10
|
+
env: {
|
|
11
|
+
...process.env,
|
|
12
|
+
REACTPRESS_ORIGINAL_CWD: projectRoot,
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
child.on('error', (error) => {
|
|
17
|
+
console.error('[reactpress] PM2 启动 API 失败:', error);
|
|
18
|
+
reject(error);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
child.on('close', (code) => {
|
|
22
|
+
if (code !== 0) {
|
|
23
|
+
reject(Object.assign(new Error(`PM2 退出码 ${code}`), { exitCode: code }));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
resolve();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { startApiWithPm2 };
|
package/lib/process.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { getPidFile } = require('./paths');
|
|
4
|
+
|
|
5
|
+
function readPid(projectRoot) {
|
|
6
|
+
const pidFile = getPidFile(projectRoot);
|
|
7
|
+
try {
|
|
8
|
+
const raw = fs.readFileSync(pidFile, 'utf8').trim();
|
|
9
|
+
const pid = Number.parseInt(raw, 10);
|
|
10
|
+
return Number.isFinite(pid) ? pid : null;
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isProcessRunning(pid) {
|
|
17
|
+
if (!pid) return false;
|
|
18
|
+
try {
|
|
19
|
+
process.kill(pid, 0);
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function clearPidFile(projectRoot) {
|
|
27
|
+
const pidFile = getPidFile(projectRoot);
|
|
28
|
+
if (fs.existsSync(pidFile)) {
|
|
29
|
+
fs.unlinkSync(pidFile);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function writePid(projectRoot, pid) {
|
|
34
|
+
const pidFile = getPidFile(projectRoot);
|
|
35
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
36
|
+
fs.writeFileSync(pidFile, String(pid));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
readPid,
|
|
41
|
+
isProcessRunning,
|
|
42
|
+
clearPidFile,
|
|
43
|
+
writePid,
|
|
44
|
+
getPidFile,
|
|
45
|
+
};
|