@fecommunity/reactpress 3.0.1 → 3.0.2
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 +67 -21
- package/bin/reactpress.js +6 -4
- package/lib/api-dev.js +4 -4
- package/lib/bootstrap.js +2 -2
- package/lib/build.js +73 -2
- package/lib/i18n/strings.js +2 -0
- package/lib/lifecycle.js +8 -8
- package/lib/paths.js +39 -19
- package/lib/pm2.js +2 -2
- package/lib/publish.js +9 -0
- package/lib/root.js +53 -4
- package/lib/spawn.js +14 -10
- package/lib/status.js +1 -1
- package/package.json +1 -1
- package/templates/env.default +1 -1
- package/templates/package.json +3 -2
- package/ui/banner.js +2 -2
- package/ui/interactive.js +1 -1
package/README.md
CHANGED
|
@@ -1,47 +1,93 @@
|
|
|
1
|
-
# @fecommunity/reactpress
|
|
1
|
+
# @fecommunity/reactpress
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
ReactPress 3.x 主包:一条命令完成站点初始化、本地开发、构建与生产部署。内置 NestJS API(站点项目无 `server/` 时自动使用 bundle),Monorepo 贡献者可使用仓库内 `server/` 源码联调。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/@fecommunity/reactpress)
|
|
6
|
+
[](https://github.com/fecommunity/reactpress/blob/master/cli/LICENSE)
|
|
7
|
+
|
|
8
|
+
完整文档:[github.com/fecommunity/reactpress](https://github.com/fecommunity/reactpress) · 中文说明见仓库 [README-zh_CN.md](https://github.com/fecommunity/reactpress/blob/master/README-zh_CN.md)
|
|
6
9
|
|
|
7
10
|
## 安装
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
|
-
npm install -g @fecommunity/reactpress
|
|
13
|
+
npm install -g @fecommunity/reactpress@3
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
全局命令为
|
|
16
|
+
全局命令为 **`reactpress`**(推荐)。
|
|
14
17
|
|
|
15
|
-
>
|
|
18
|
+
> **兼容**:仍提供 `reactpress-cli` 别名,会映射到 `reactpress server *` 并打印弃用提示,**3.1 将移除**。旧包名 `@fecommunity/reactpress-cli` 请迁移到本包。
|
|
16
19
|
|
|
17
20
|
## 快速开始
|
|
18
21
|
|
|
22
|
+
### 新建站点(零配置)
|
|
23
|
+
|
|
19
24
|
```bash
|
|
20
25
|
mkdir my-blog && cd my-blog
|
|
21
|
-
reactpress
|
|
22
|
-
reactpress
|
|
26
|
+
reactpress init
|
|
27
|
+
reactpress dev
|
|
23
28
|
```
|
|
24
29
|
|
|
25
|
-
|
|
30
|
+
| 服务 | 默认地址 |
|
|
31
|
+
|------|----------|
|
|
32
|
+
| 前台 | http://localhost:3001 |
|
|
33
|
+
| 后台 | http://localhost:3001/admin |
|
|
34
|
+
| API / Swagger | http://localhost:3002/api |
|
|
35
|
+
|
|
36
|
+
### 在本仓库(Monorepo)开发
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/fecommunity/reactpress.git
|
|
40
|
+
cd reactpress
|
|
41
|
+
pnpm install
|
|
42
|
+
pnpm init # 生成 .env、.reactpress/config.json
|
|
43
|
+
pnpm dev # 等价于 reactpress dev
|
|
44
|
+
```
|
|
26
45
|
|
|
27
46
|
## 常用命令
|
|
28
47
|
|
|
29
48
|
| 命令 | 说明 |
|
|
30
49
|
|------|------|
|
|
31
|
-
| `reactpress
|
|
32
|
-
| `reactpress
|
|
33
|
-
| `reactpress
|
|
34
|
-
| `reactpress-
|
|
35
|
-
| `reactpress-
|
|
36
|
-
| `reactpress-
|
|
37
|
-
| `reactpress
|
|
50
|
+
| `reactpress` | 交互式菜单 |
|
|
51
|
+
| `reactpress init [dir]` | 初始化项目(`-f` 强制) |
|
|
52
|
+
| `reactpress dev` | 环境检查 → 构建 toolkit → API + 前台 |
|
|
53
|
+
| `reactpress dev --api-only` | 仅 API |
|
|
54
|
+
| `reactpress dev --client-only` | 仅前台(需 API 已运行) |
|
|
55
|
+
| `reactpress build -t <target>` | 构建:`toolkit` \| `server` \| `client` \| `docs` \| `all` |
|
|
56
|
+
| `reactpress server start` | 后台启动 API(`--bg` 不等待健康检查) |
|
|
57
|
+
| `reactpress server stop` / `restart` / `status` | API 生命周期 |
|
|
58
|
+
| `reactpress start` | 生产:API + 前台 |
|
|
59
|
+
| `reactpress status` | 统一状态(配置、端口、健康检查) |
|
|
60
|
+
| `reactpress doctor` | 环境诊断 |
|
|
61
|
+
| `reactpress docker up` / `down` / `start` / `status` / `logs` | Docker MySQL 与编排 |
|
|
62
|
+
| `reactpress db backup` | 数据库备份(`mysqldump`) |
|
|
63
|
+
| `reactpress publish` | Monorepo 发包向导(维护者) |
|
|
64
|
+
|
|
65
|
+
站点级配置(`reactpress-cli` 时代)仍通过项目内 **`pnpm exec reactpress-cli config`** 或 `.reactpress/config.json` 管理;3.x 主路径为 `reactpress` + 根目录 `.env`。
|
|
66
|
+
|
|
67
|
+
## 环境要求
|
|
68
|
+
|
|
69
|
+
- **Node.js** ≥ 18
|
|
70
|
+
- **pnpm** ≥ 8(Monorepo / `pnpm dev`)
|
|
71
|
+
- **MySQL**:Docker(`reactpress docker up`)或 `.env` / `.reactpress/config.json` 中的外部库
|
|
72
|
+
|
|
73
|
+
## 故障排查
|
|
74
|
+
|
|
75
|
+
| 现象 | 处理 |
|
|
76
|
+
|------|------|
|
|
77
|
+
| `Missing script: build:toolkit` | 勿将 `REACTPRESS_ORIGINAL_CWD` 指到全局 npm 安装目录;在项目根执行,或 `unset REACTPRESS_ORIGINAL_CWD` 后重试 |
|
|
78
|
+
| 误用 bundle API、未走 Monorepo `server/` | 确保在含 `pnpm-workspace.yaml` 的仓库根目录运行;升级至最新 `@fecommunity/reactpress` |
|
|
79
|
+
| 端口占用 | `reactpress doctor` 或修改 `.env` 中 `SERVER_PORT` / `CLIENT_PORT` |
|
|
80
|
+
|
|
81
|
+
## 相关包
|
|
38
82
|
|
|
39
|
-
|
|
83
|
+
| 包 | 用途 |
|
|
84
|
+
|----|------|
|
|
85
|
+
| `@fecommunity/reactpress-client` | 仅部署 Next.js 前台 |
|
|
86
|
+
| `@fecommunity/reactpress-toolkit` | OpenAPI 生成的 TS SDK |
|
|
87
|
+
| `@fecommunity/reactpress-template-*` | 项目模板 |
|
|
40
88
|
|
|
41
|
-
-
|
|
42
|
-
- macOS / Linux / Windows
|
|
43
|
-
- 默认使用 Docker 运行嵌入式 MySQL;也可在 `.reactpress/config.json` 中配置外部数据库
|
|
89
|
+
`@fecommunity/reactpress-server`、独立安装 `@fecommunity/reactpress-cli` 已弃用,请使用本包。
|
|
44
90
|
|
|
45
91
|
## 许可证
|
|
46
92
|
|
|
47
|
-
MIT © FECommunity
|
|
93
|
+
MIT © [FECommunity](https://github.com/fecommunity)
|
package/bin/reactpress.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
const { Command } = require('commander');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const chalk = require('chalk');
|
|
11
|
-
const { ensureOriginalCwd
|
|
11
|
+
const { ensureOriginalCwd } = require('../lib/root');
|
|
12
|
+
const { getCliPackageRoot } = require('../lib/paths');
|
|
12
13
|
const { ensureProjectEnvironment, initMonorepoProject } = require('../lib/bootstrap');
|
|
13
14
|
const { runDev } = require('../lib/dev');
|
|
14
15
|
const { runApiDev } = require('../lib/api-dev');
|
|
@@ -24,7 +25,7 @@ const { getClientBin } = require('../lib/paths');
|
|
|
24
25
|
const { runInteractiveLoop } = require('../ui/interactive');
|
|
25
26
|
const { t } = require('../lib/i18n');
|
|
26
27
|
|
|
27
|
-
const rootPkg = require(path.join(
|
|
28
|
+
const rootPkg = require(path.join(getCliPackageRoot(), 'package.json'));
|
|
28
29
|
|
|
29
30
|
const program = new Command();
|
|
30
31
|
|
|
@@ -62,7 +63,7 @@ program
|
|
|
62
63
|
const projectRoot = ensureOriginalCwd();
|
|
63
64
|
try {
|
|
64
65
|
if (options.clientOnly) {
|
|
65
|
-
await runNodeScript(getClientBin(), [], { cwd: projectRoot });
|
|
66
|
+
await runNodeScript(getClientBin(projectRoot), [], { cwd: projectRoot });
|
|
66
67
|
return;
|
|
67
68
|
}
|
|
68
69
|
if (options.apiOnly) {
|
|
@@ -121,7 +122,8 @@ clientCmd
|
|
|
121
122
|
.option('--pm2', t('cli.client.start.pm2'))
|
|
122
123
|
.action(async (options) => {
|
|
123
124
|
const args = options.pm2 ? ['--pm2'] : [];
|
|
124
|
-
|
|
125
|
+
const projectRoot = ensureOriginalCwd();
|
|
126
|
+
await runNodeScript(getClientBin(projectRoot), args, { cwd: projectRoot });
|
|
125
127
|
});
|
|
126
128
|
|
|
127
129
|
program
|
package/lib/api-dev.js
CHANGED
|
@@ -2,7 +2,7 @@ const { spawn, spawnSync } = require('child_process');
|
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { ensureProjectEnvironment } = require('./bootstrap');
|
|
4
4
|
const { isUsingMonorepoServer } = require('./paths');
|
|
5
|
-
const {
|
|
5
|
+
const { ensureOriginalCwd } = require('./root');
|
|
6
6
|
const { t } = require('./i18n');
|
|
7
7
|
|
|
8
8
|
let apiChild;
|
|
@@ -11,7 +11,7 @@ function stopApiDev(projectRoot) {
|
|
|
11
11
|
if (apiChild && !apiChild.killed) {
|
|
12
12
|
apiChild.kill('SIGTERM');
|
|
13
13
|
}
|
|
14
|
-
if (!isUsingMonorepoServer()) {
|
|
14
|
+
if (!isUsingMonorepoServer(projectRoot)) {
|
|
15
15
|
spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], {
|
|
16
16
|
cwd: projectRoot,
|
|
17
17
|
stdio: 'inherit',
|
|
@@ -20,7 +20,7 @@ function stopApiDev(projectRoot) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function startApiDev(projectRoot) {
|
|
23
|
-
if (isUsingMonorepoServer()) {
|
|
23
|
+
if (isUsingMonorepoServer(projectRoot)) {
|
|
24
24
|
console.log(t('apiDev.modeServer'));
|
|
25
25
|
apiChild = spawn('pnpm', ['run', '--dir', './server', 'dev'], {
|
|
26
26
|
cwd: projectRoot,
|
|
@@ -54,7 +54,7 @@ function startApiDev(projectRoot) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
async function runApiDev(projectRoot =
|
|
57
|
+
async function runApiDev(projectRoot = ensureOriginalCwd()) {
|
|
58
58
|
try {
|
|
59
59
|
await ensureProjectEnvironment(projectRoot);
|
|
60
60
|
} catch (err) {
|
package/lib/bootstrap.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const { pathToFileURL } = require('url');
|
|
4
|
-
const {
|
|
4
|
+
const { ensureOriginalCwd, isMonorepoCheckout } = require('./root');
|
|
5
5
|
const { getCliPackageRoot } = require('./paths');
|
|
6
6
|
const { t } = require('./i18n');
|
|
7
7
|
|
|
@@ -68,7 +68,7 @@ async function initMonorepoProject(projectRoot, { force = false } = {}) {
|
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
async function ensureProjectEnvironment(projectRoot =
|
|
71
|
+
async function ensureProjectEnvironment(projectRoot = ensureOriginalCwd()) {
|
|
72
72
|
const root = path.resolve(projectRoot);
|
|
73
73
|
const { setProjectCwd } = await importCliModule('utils/cli-context.js');
|
|
74
74
|
setProjectCwd(root);
|
package/lib/build.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
1
3
|
const chalk = require('chalk');
|
|
2
4
|
const { runSync } = require('./spawn');
|
|
3
5
|
const { ensureOriginalCwd } = require('./root');
|
|
@@ -22,6 +24,60 @@ const TARGETS = Object.keys(BUILD_STEPS);
|
|
|
22
24
|
|
|
23
25
|
const buildChildEnv = { REACTPRESS_BUILD_ACTIVE: '1' };
|
|
24
26
|
|
|
27
|
+
function readPackageScripts(packageJsonPath) {
|
|
28
|
+
try {
|
|
29
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
30
|
+
return pkg.scripts || {};
|
|
31
|
+
} catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Prefer workspace package scripts over root package.json aliases. */
|
|
37
|
+
function resolveBuildInvocation(script, projectRoot) {
|
|
38
|
+
const root = path.resolve(projectRoot);
|
|
39
|
+
|
|
40
|
+
if (script === 'build:toolkit') {
|
|
41
|
+
const toolkitDir = path.join(root, 'toolkit');
|
|
42
|
+
if (fs.existsSync(path.join(toolkitDir, 'package.json'))) {
|
|
43
|
+
return { command: 'pnpm', args: ['run', 'build'], cwd: toolkitDir };
|
|
44
|
+
}
|
|
45
|
+
const rootScripts = readPackageScripts(path.join(root, 'package.json'));
|
|
46
|
+
if (rootScripts['build:toolkit']) {
|
|
47
|
+
return { command: 'pnpm', args: ['run', 'build:toolkit'], cwd: root };
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (script === 'build:server') {
|
|
53
|
+
const serverDir = path.join(root, 'server');
|
|
54
|
+
if (fs.existsSync(path.join(serverDir, 'package.json'))) {
|
|
55
|
+
return { command: 'pnpm', args: ['run', 'build'], cwd: serverDir };
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (script === 'build:client') {
|
|
60
|
+
const clientDir = path.join(root, 'client');
|
|
61
|
+
if (fs.existsSync(path.join(clientDir, 'package.json'))) {
|
|
62
|
+
return { command: 'pnpm', args: ['run', 'build'], cwd: clientDir };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (script === 'build:docs') {
|
|
67
|
+
const docsDir = path.join(root, 'docs');
|
|
68
|
+
if (fs.existsSync(path.join(docsDir, 'package.json'))) {
|
|
69
|
+
return { command: 'pnpm', args: ['run', 'build'], cwd: docsDir };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const rootScripts = readPackageScripts(path.join(root, 'package.json'));
|
|
74
|
+
if (rootScripts[script]) {
|
|
75
|
+
return { command: 'pnpm', args: ['run', script], cwd: root };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
25
81
|
async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
|
|
26
82
|
if (process.env.REACTPRESS_BUILD_ACTIVE === '1') {
|
|
27
83
|
throw new Error(t('build.recursive'));
|
|
@@ -55,8 +111,18 @@ async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
|
|
|
55
111
|
const stepStarted = Date.now();
|
|
56
112
|
|
|
57
113
|
console.log(t('build.step', { current, total, label }));
|
|
114
|
+
|
|
115
|
+
const invocation = resolveBuildInvocation(script, projectRoot);
|
|
116
|
+
if (!invocation) {
|
|
117
|
+
console.log(chalk.yellow(t('build.stepSkipped', { label })));
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
58
121
|
try {
|
|
59
|
-
runSync(
|
|
122
|
+
runSync(invocation.command, invocation.args, {
|
|
123
|
+
cwd: invocation.cwd,
|
|
124
|
+
env: buildChildEnv,
|
|
125
|
+
});
|
|
60
126
|
} catch (err) {
|
|
61
127
|
console.error(chalk.red(t('build.stepFailed', { current, total, label })));
|
|
62
128
|
throw err;
|
|
@@ -72,4 +138,9 @@ async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
|
|
|
72
138
|
}
|
|
73
139
|
}
|
|
74
140
|
|
|
75
|
-
module.exports = {
|
|
141
|
+
module.exports = {
|
|
142
|
+
runBuild,
|
|
143
|
+
TARGETS,
|
|
144
|
+
BUILD_STEPS,
|
|
145
|
+
resolveBuildInvocation,
|
|
146
|
+
};
|
package/lib/i18n/strings.js
CHANGED
|
@@ -203,6 +203,7 @@ const STRINGS = {
|
|
|
203
203
|
'build.plan': '[reactpress] Production build — {total} step(s): toolkit → server → client',
|
|
204
204
|
'build.step': '[reactpress] [{current}/{total}] {label}…',
|
|
205
205
|
'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)',
|
|
206
|
+
'build.stepSkipped': '[reactpress] Skipped {label} (not part of this project layout)',
|
|
206
207
|
'build.done': '[reactpress] Build finished in {seconds}s',
|
|
207
208
|
'build.label.toolkit': 'Toolkit',
|
|
208
209
|
'build.label.server': 'API (server)',
|
|
@@ -494,6 +495,7 @@ const STRINGS = {
|
|
|
494
495
|
'build.plan': '[reactpress] 生产构建 — 共 {total} 步:toolkit → server → client',
|
|
495
496
|
'build.step': '[reactpress] [{current}/{total}] {label}…',
|
|
496
497
|
'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)',
|
|
498
|
+
'build.stepSkipped': '[reactpress] 已跳过 {label}(当前项目无对应源码包)',
|
|
497
499
|
'build.done': '[reactpress] 构建完成,耗时 {seconds}s',
|
|
498
500
|
'build.label.toolkit': 'Toolkit',
|
|
499
501
|
'build.label.server': 'API (server)',
|
package/lib/lifecycle.js
CHANGED
|
@@ -8,7 +8,7 @@ const {
|
|
|
8
8
|
getPidFile,
|
|
9
9
|
} = require('./paths');
|
|
10
10
|
const { readPid, isProcessRunning, clearPidFile, writePid } = require('./process');
|
|
11
|
-
const {
|
|
11
|
+
const { ensureOriginalCwd } = require('./root');
|
|
12
12
|
const { t } = require('./i18n');
|
|
13
13
|
|
|
14
14
|
async function ensureConfig(projectRoot) {
|
|
@@ -46,7 +46,7 @@ async function startApi(projectRoot, { wait = true } = {}) {
|
|
|
46
46
|
}
|
|
47
47
|
clearPidFile(projectRoot);
|
|
48
48
|
|
|
49
|
-
if (!isUsingMonorepoServer()) {
|
|
49
|
+
if (!isUsingMonorepoServer(projectRoot)) {
|
|
50
50
|
console.warn(t('lifecycle.noServerSrc'));
|
|
51
51
|
const start = spawnSync('pnpm', ['exec', 'reactpress-cli', 'start'], {
|
|
52
52
|
cwd: projectRoot,
|
|
@@ -56,8 +56,8 @@ async function startApi(projectRoot, { wait = true } = {}) {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
console.log(t('lifecycle.startingLocalApi'));
|
|
59
|
-
const child = spawn(process.execPath, [getServerBin()], {
|
|
60
|
-
cwd: getServerDir(),
|
|
59
|
+
const child = spawn(process.execPath, [getServerBin(projectRoot)], {
|
|
60
|
+
cwd: getServerDir(projectRoot),
|
|
61
61
|
detached: true,
|
|
62
62
|
stdio: 'ignore',
|
|
63
63
|
env: {
|
|
@@ -90,7 +90,7 @@ async function statusApi(projectRoot) {
|
|
|
90
90
|
const { isHttpResponding } = require('./http');
|
|
91
91
|
const httpOk = await isHttpResponding(serverUrl);
|
|
92
92
|
|
|
93
|
-
const source = isUsingMonorepoServer()
|
|
93
|
+
const source = isUsingMonorepoServer(projectRoot)
|
|
94
94
|
? t('lifecycle.source.monorepo')
|
|
95
95
|
: t('lifecycle.source.bundle');
|
|
96
96
|
|
|
@@ -119,7 +119,7 @@ async function statusApi(projectRoot) {
|
|
|
119
119
|
);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
async function runLifecycleCommand(command, projectRoot =
|
|
122
|
+
async function runLifecycleCommand(command, projectRoot = ensureOriginalCwd()) {
|
|
123
123
|
switch (command) {
|
|
124
124
|
case 'start':
|
|
125
125
|
return startApi(projectRoot, { wait: true });
|
|
@@ -127,7 +127,7 @@ async function runLifecycleCommand(command, projectRoot = process.env.REACTPRESS
|
|
|
127
127
|
return startApi(projectRoot, { wait: false });
|
|
128
128
|
case 'stop':
|
|
129
129
|
stopApi(projectRoot);
|
|
130
|
-
if (!isUsingMonorepoServer()) {
|
|
130
|
+
if (!isUsingMonorepoServer(projectRoot)) {
|
|
131
131
|
spawnSync('pnpm', ['exec', 'reactpress-cli', 'stop'], {
|
|
132
132
|
cwd: projectRoot,
|
|
133
133
|
stdio: 'inherit',
|
|
@@ -136,7 +136,7 @@ async function runLifecycleCommand(command, projectRoot = process.env.REACTPRESS
|
|
|
136
136
|
return 0;
|
|
137
137
|
case 'restart':
|
|
138
138
|
stopApi(projectRoot);
|
|
139
|
-
if (!isUsingMonorepoServer()) {
|
|
139
|
+
if (!isUsingMonorepoServer(projectRoot)) {
|
|
140
140
|
spawnSync('pnpm', ['exec', 'reactpress-cli', 'restart'], {
|
|
141
141
|
cwd: projectRoot,
|
|
142
142
|
stdio: 'inherit',
|
package/lib/paths.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const { getMonorepoRoot } = require('./root');
|
|
3
|
+
const { ensureOriginalCwd, getMonorepoRoot } = require('./root');
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
return path.
|
|
5
|
+
function resolveProjectRoot(projectRoot) {
|
|
6
|
+
return path.resolve(projectRoot || ensureOriginalCwd());
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
function
|
|
10
|
-
return
|
|
9
|
+
function getMonorepoServerDir(projectRoot) {
|
|
10
|
+
return path.join(resolveProjectRoot(projectRoot), 'server');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function hasMonorepoServerSource(projectRoot) {
|
|
14
|
+
return fs.existsSync(
|
|
15
|
+
path.join(getMonorepoServerDir(projectRoot), 'src', 'main.ts')
|
|
16
|
+
);
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
function getCliPackageRoot() {
|
|
@@ -28,39 +34,53 @@ function getBundledServerDir() {
|
|
|
28
34
|
return path.join(getCliPackageRoot(), 'server');
|
|
29
35
|
}
|
|
30
36
|
|
|
31
|
-
function getServerDir() {
|
|
32
|
-
if (hasMonorepoServerSource()) {
|
|
33
|
-
return getMonorepoServerDir();
|
|
37
|
+
function getServerDir(projectRoot) {
|
|
38
|
+
if (hasMonorepoServerSource(projectRoot)) {
|
|
39
|
+
return getMonorepoServerDir(projectRoot);
|
|
34
40
|
}
|
|
35
41
|
return getBundledServerDir();
|
|
36
42
|
}
|
|
37
43
|
|
|
38
|
-
function getServerBin() {
|
|
39
|
-
return path.join(getServerDir(), 'bin', 'reactpress-server.js');
|
|
44
|
+
function getServerBin(projectRoot) {
|
|
45
|
+
return path.join(getServerDir(projectRoot), 'bin', 'reactpress-server.js');
|
|
40
46
|
}
|
|
41
47
|
|
|
42
|
-
function getSwaggerPath() {
|
|
43
|
-
return path.join(getServerDir(), 'public', 'swagger.json');
|
|
48
|
+
function getSwaggerPath(projectRoot) {
|
|
49
|
+
return path.join(getServerDir(projectRoot), 'public', 'swagger.json');
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
function getServerMain() {
|
|
47
|
-
return path.join(getServerDir(), 'dist', 'main.js');
|
|
52
|
+
function getServerMain(projectRoot) {
|
|
53
|
+
return path.join(getServerDir(projectRoot), 'dist', 'main.js');
|
|
48
54
|
}
|
|
49
55
|
|
|
50
|
-
function isUsingMonorepoServer() {
|
|
51
|
-
return hasMonorepoServerSource();
|
|
56
|
+
function isUsingMonorepoServer(projectRoot) {
|
|
57
|
+
return hasMonorepoServerSource(projectRoot);
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
function getClientBin() {
|
|
55
|
-
|
|
60
|
+
function getClientBin(projectRoot) {
|
|
61
|
+
const binPath = path.join(
|
|
62
|
+
resolveProjectRoot(projectRoot),
|
|
63
|
+
'client',
|
|
64
|
+
'bin',
|
|
65
|
+
'reactpress-client.js'
|
|
66
|
+
);
|
|
67
|
+
if (!fs.existsSync(binPath)) {
|
|
68
|
+
const err = new Error(
|
|
69
|
+
`Client entry not found: ${binPath}. Run from a ReactPress monorepo root or use reactpress-cli for site-only projects.`
|
|
70
|
+
);
|
|
71
|
+
err.code = 'REACTPRESS_CLIENT_NOT_FOUND';
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
return binPath;
|
|
56
75
|
}
|
|
57
76
|
|
|
58
77
|
function getPidFile(projectRoot) {
|
|
59
|
-
return path.join(projectRoot, '.reactpress', 'server.pid');
|
|
78
|
+
return path.join(resolveProjectRoot(projectRoot), '.reactpress', 'server.pid');
|
|
60
79
|
}
|
|
61
80
|
|
|
62
81
|
module.exports = {
|
|
63
82
|
getMonorepoRoot,
|
|
83
|
+
resolveProjectRoot,
|
|
64
84
|
getMonorepoServerDir,
|
|
65
85
|
hasMonorepoServerSource,
|
|
66
86
|
isUsingMonorepoServer,
|
package/lib/pm2.js
CHANGED
|
@@ -5,9 +5,9 @@ const { t } = require('./i18n');
|
|
|
5
5
|
|
|
6
6
|
function startApiWithPm2(projectRoot = ensureOriginalCwd()) {
|
|
7
7
|
return new Promise((resolve, reject) => {
|
|
8
|
-
const child = spawn(process.execPath, [getServerBin(), '--pm2'], {
|
|
8
|
+
const child = spawn(process.execPath, [getServerBin(projectRoot), '--pm2'], {
|
|
9
9
|
stdio: 'inherit',
|
|
10
|
-
cwd: getServerDir(),
|
|
10
|
+
cwd: getServerDir(projectRoot),
|
|
11
11
|
env: {
|
|
12
12
|
...process.env,
|
|
13
13
|
REACTPRESS_ORIGINAL_CWD: projectRoot,
|
package/lib/publish.js
CHANGED
|
@@ -389,6 +389,15 @@ function buildPackage(pkg) {
|
|
|
389
389
|
execSync('pnpm run prebuild && pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
|
|
390
390
|
} else if (pkg.path === 'toolkit') {
|
|
391
391
|
execSync('pnpm run build', { cwd: path.join(process.cwd(), pkg.path), stdio: 'inherit' });
|
|
392
|
+
} else if (pkg.path === 'cli') {
|
|
393
|
+
execSync('node scripts/sync-bundled-core.mjs', {
|
|
394
|
+
cwd: path.join(process.cwd(), pkg.path),
|
|
395
|
+
stdio: 'inherit',
|
|
396
|
+
});
|
|
397
|
+
execSync('node scripts/install-bundled-runtime.mjs', {
|
|
398
|
+
cwd: path.join(process.cwd(), pkg.path),
|
|
399
|
+
stdio: 'inherit',
|
|
400
|
+
});
|
|
392
401
|
} else if (pkg.path === 'templates/hello-world' || pkg.path === 'templates/twentytwentyfive') {
|
|
393
402
|
// Templates don't need to be built, just validate package.json
|
|
394
403
|
console.log(chalk.gray(' Templates do not require building, skipping...'));
|
package/lib/root.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
|
|
4
|
+
const CLI_PACKAGE_NAME = '@fecommunity/reactpress';
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Install root: monorepo checkout (repo root) or published @fecommunity/reactpress package root.
|
|
6
8
|
* cli/lib -> ../.. when pnpm-workspace.yaml exists; published lib/ -> .. only.
|
|
@@ -14,8 +16,51 @@ function getMonorepoRoot() {
|
|
|
14
16
|
return packageRoot;
|
|
15
17
|
}
|
|
16
18
|
|
|
19
|
+
function isPublishedCliRoot(dir) {
|
|
20
|
+
const resolved = path.resolve(dir);
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(
|
|
23
|
+
fs.readFileSync(path.join(resolved, 'package.json'), 'utf8')
|
|
24
|
+
);
|
|
25
|
+
if (pkg.name !== CLI_PACKAGE_NAME) return false;
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return !fs.existsSync(path.join(resolved, 'pnpm-workspace.yaml'));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isProjectRoot(dir) {
|
|
33
|
+
const resolved = path.resolve(dir);
|
|
34
|
+
if (isPublishedCliRoot(resolved)) return false;
|
|
35
|
+
return (
|
|
36
|
+
fs.existsSync(path.join(resolved, '.reactpress', 'config.json')) ||
|
|
37
|
+
fs.existsSync(path.join(resolved, 'pnpm-workspace.yaml')) ||
|
|
38
|
+
fs.existsSync(path.join(resolved, 'server', 'src', 'main.ts')) ||
|
|
39
|
+
fs.existsSync(path.join(resolved, 'toolkit', 'package.json'))
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function findProjectRoot(startDir = process.cwd()) {
|
|
44
|
+
let dir = path.resolve(startDir);
|
|
45
|
+
while (true) {
|
|
46
|
+
if (isProjectRoot(dir)) return dir;
|
|
47
|
+
const parent = path.dirname(dir);
|
|
48
|
+
if (parent === dir) break;
|
|
49
|
+
dir = parent;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
17
54
|
function getProjectRoot() {
|
|
18
|
-
|
|
55
|
+
const envRoot = process.env.REACTPRESS_ORIGINAL_CWD;
|
|
56
|
+
if (envRoot) {
|
|
57
|
+
const resolved = path.resolve(envRoot);
|
|
58
|
+
if (isProjectRoot(resolved)) return resolved;
|
|
59
|
+
}
|
|
60
|
+
const discovered = findProjectRoot(process.cwd());
|
|
61
|
+
if (discovered) return discovered;
|
|
62
|
+
if (envRoot) return path.resolve(envRoot);
|
|
63
|
+
return path.resolve(process.cwd());
|
|
19
64
|
}
|
|
20
65
|
|
|
21
66
|
function ensureOriginalCwd() {
|
|
@@ -24,10 +69,11 @@ function ensureOriginalCwd() {
|
|
|
24
69
|
return root;
|
|
25
70
|
}
|
|
26
71
|
|
|
27
|
-
function isMonorepoCheckout(cwd
|
|
72
|
+
function isMonorepoCheckout(cwd) {
|
|
73
|
+
const resolved = path.resolve(cwd || process.cwd());
|
|
28
74
|
return (
|
|
29
|
-
fs.existsSync(path.join(
|
|
30
|
-
fs.existsSync(path.join(
|
|
75
|
+
fs.existsSync(path.join(resolved, 'pnpm-workspace.yaml')) ||
|
|
76
|
+
fs.existsSync(path.join(resolved, 'server', 'src', 'main.ts'))
|
|
31
77
|
);
|
|
32
78
|
}
|
|
33
79
|
|
|
@@ -36,4 +82,7 @@ module.exports = {
|
|
|
36
82
|
getProjectRoot,
|
|
37
83
|
ensureOriginalCwd,
|
|
38
84
|
isMonorepoCheckout,
|
|
85
|
+
isProjectRoot,
|
|
86
|
+
findProjectRoot,
|
|
87
|
+
isPublishedCliRoot,
|
|
39
88
|
};
|
package/lib/spawn.js
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
const { spawn, spawnSync } = require('child_process');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const {
|
|
4
|
+
const { ensureOriginalCwd } = require('./root');
|
|
5
5
|
const { getCliPackageRoot } = require('./paths');
|
|
6
6
|
const { t, resolveLocale } = require('./i18n');
|
|
7
7
|
|
|
8
|
+
function resolveSpawnCwd(options = {}) {
|
|
9
|
+
return options.cwd ? path.resolve(options.cwd) : ensureOriginalCwd();
|
|
10
|
+
}
|
|
11
|
+
|
|
8
12
|
function runSync(command, args, options = {}) {
|
|
13
|
+
const cwd = resolveSpawnCwd(options);
|
|
9
14
|
const result = spawnSync(command, args, {
|
|
10
|
-
cwd
|
|
15
|
+
cwd,
|
|
11
16
|
stdio: 'inherit',
|
|
12
17
|
env: {
|
|
13
18
|
...process.env,
|
|
14
19
|
REACTPRESS_LANG: process.env.REACTPRESS_LANG || resolveLocale(),
|
|
15
|
-
REACTPRESS_ORIGINAL_CWD:
|
|
16
|
-
options.cwd || process.env.REACTPRESS_ORIGINAL_CWD || process.cwd(),
|
|
20
|
+
REACTPRESS_ORIGINAL_CWD: cwd,
|
|
17
21
|
...options.env,
|
|
18
22
|
},
|
|
19
23
|
shell: options.shell ?? false,
|
|
@@ -29,15 +33,15 @@ function runSync(command, args, options = {}) {
|
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
function runNodeScript(scriptPath, args = [], options = {}) {
|
|
36
|
+
const cwd = resolveSpawnCwd(options);
|
|
32
37
|
return new Promise((resolve, reject) => {
|
|
33
38
|
const child = spawn(process.execPath, [scriptPath, ...args], {
|
|
34
39
|
stdio: 'inherit',
|
|
35
|
-
cwd
|
|
40
|
+
cwd,
|
|
36
41
|
env: {
|
|
37
42
|
...process.env,
|
|
38
43
|
REACTPRESS_LANG: process.env.REACTPRESS_LANG || resolveLocale(),
|
|
39
|
-
REACTPRESS_ORIGINAL_CWD:
|
|
40
|
-
options.cwd || process.env.REACTPRESS_ORIGINAL_CWD || process.cwd(),
|
|
44
|
+
REACTPRESS_ORIGINAL_CWD: cwd,
|
|
41
45
|
...options.env,
|
|
42
46
|
},
|
|
43
47
|
});
|
|
@@ -58,15 +62,15 @@ function runNodeScript(scriptPath, args = [], options = {}) {
|
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
function spawnDetached(scriptPath, args = [], options = {}) {
|
|
65
|
+
const cwd = resolveSpawnCwd(options);
|
|
61
66
|
const child = spawn(process.execPath, [scriptPath, ...args], {
|
|
62
67
|
stdio: options.stdio ?? 'ignore',
|
|
63
68
|
detached: true,
|
|
64
|
-
cwd
|
|
69
|
+
cwd,
|
|
65
70
|
env: {
|
|
66
71
|
...process.env,
|
|
67
72
|
REACTPRESS_LANG: process.env.REACTPRESS_LANG || resolveLocale(),
|
|
68
|
-
REACTPRESS_ORIGINAL_CWD:
|
|
69
|
-
options.cwd || process.env.REACTPRESS_ORIGINAL_CWD || process.cwd(),
|
|
73
|
+
REACTPRESS_ORIGINAL_CWD: cwd,
|
|
70
74
|
...options.env,
|
|
71
75
|
},
|
|
72
76
|
});
|
package/lib/status.js
CHANGED
|
@@ -37,7 +37,7 @@ async function printUnifiedStatus(projectRoot = ensureOriginalCwd()) {
|
|
|
37
37
|
checkHealth(healthUrl),
|
|
38
38
|
]);
|
|
39
39
|
|
|
40
|
-
const apiSource = isUsingMonorepoServer()
|
|
40
|
+
const apiSource = isUsingMonorepoServer(projectRoot)
|
|
41
41
|
? t('status.apiSource.monorepo')
|
|
42
42
|
: t('status.apiSource.bundle');
|
|
43
43
|
|
package/package.json
CHANGED
package/templates/env.default
CHANGED
package/templates/package.json
CHANGED
package/ui/banner.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const chalk = require('chalk');
|
|
2
2
|
const { brand } = require('./theme');
|
|
3
|
-
const { getMonorepoRoot } = require('../lib/root');
|
|
4
3
|
const path = require('path');
|
|
4
|
+
const { getCliPackageRoot } = require('../lib/paths');
|
|
5
5
|
const { t } = require('../lib/i18n');
|
|
6
6
|
|
|
7
7
|
function printBanner() {
|
|
8
|
-
const version = require(path.join(
|
|
8
|
+
const version = require(path.join(getCliPackageRoot(), 'package.json')).version;
|
|
9
9
|
console.log('');
|
|
10
10
|
console.log(brand.primary(' ╭─────────────────────────────────────────╮'));
|
|
11
11
|
console.log(
|
package/ui/interactive.js
CHANGED
|
@@ -64,7 +64,7 @@ async function runMenuAction(action, projectRoot) {
|
|
|
64
64
|
await runApiDev(projectRoot);
|
|
65
65
|
return false;
|
|
66
66
|
case 'dev:client':
|
|
67
|
-
await runNodeScript(getClientBin(), [], { cwd: projectRoot });
|
|
67
|
+
await runNodeScript(getClientBin(projectRoot), [], { cwd: projectRoot });
|
|
68
68
|
return false;
|
|
69
69
|
case 'server:start': {
|
|
70
70
|
const code = await runLifecycleCommand('start', projectRoot);
|