@fecommunity/reactpress 3.0.0 → 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 +8 -5
- package/dist/index.js +0 -0
- package/lib/api-dev.js +4 -4
- package/lib/bootstrap.js +2 -2
- package/lib/build.js +130 -13
- package/lib/dev.js +2 -14
- package/lib/i18n/strings.js +32 -2
- 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 +63 -6
- package/lib/spawn.js +15 -10
- package/lib/status.js +1 -1
- package/package.json +7 -4
- package/server/dist/app.module.js +1 -26
- package/server/dist/app.module.js.map +1 -1
- package/server/dist/modules/article/article.controller.d.ts +1 -6
- package/server/dist/modules/article/article.controller.js +1 -45
- package/server/dist/modules/article/article.controller.js.map +1 -1
- package/server/dist/modules/article/article.entity.d.ts +0 -1
- package/server/dist/modules/article/article.entity.js +0 -5
- package/server/dist/modules/article/article.entity.js.map +1 -1
- package/server/dist/modules/article/article.module.js +2 -14
- package/server/dist/modules/article/article.module.js.map +1 -1
- package/server/dist/modules/article/article.service.d.ts +1 -9
- package/server/dist/modules/article/article.service.js +5 -59
- package/server/dist/modules/article/article.service.js.map +1 -1
- package/server/dist/modules/auth/auth.service.js +3 -4
- package/server/dist/modules/auth/auth.service.js.map +1 -1
- package/server/dist/modules/auth/jwt-auth.guard.js +1 -2
- package/server/dist/modules/auth/jwt-auth.guard.js.map +1 -1
- package/server/dist/modules/auth/jwt.strategy.js +1 -2
- package/server/dist/modules/auth/jwt.strategy.js.map +1 -1
- package/server/dist/modules/category/category.service.js +2 -3
- package/server/dist/modules/category/category.service.js.map +1 -1
- package/server/dist/modules/comment/comment.module.js +1 -10
- package/server/dist/modules/comment/comment.module.js.map +1 -1
- package/server/dist/modules/comment/comment.service.d.ts +1 -3
- package/server/dist/modules/comment/comment.service.js +5 -15
- package/server/dist/modules/comment/comment.service.js.map +1 -1
- package/server/dist/modules/knowledge/knowledge.service.js +2 -3
- package/server/dist/modules/knowledge/knowledge.service.js.map +1 -1
- package/server/dist/modules/page/page.service.js +1 -2
- package/server/dist/modules/page/page.service.js.map +1 -1
- package/server/dist/modules/smtp/smtp.service.js +1 -2
- package/server/dist/modules/smtp/smtp.service.js.map +1 -1
- package/server/dist/modules/tag/tag.service.js +2 -3
- package/server/dist/modules/tag/tag.service.js.map +1 -1
- package/server/dist/modules/user/user.controller.js +3 -4
- package/server/dist/modules/user/user.controller.js.map +1 -1
- package/server/dist/modules/user/user.service.js +9 -10
- package/server/dist/modules/user/user.service.js.map +1 -1
- package/server/dist/starter.js +1 -1
- package/server/dist/tsconfig.build.tsbuildinfo +11335 -11537
- package/server/dist/utils/oss.util.js +1 -2
- package/server/dist/utils/oss.util.js.map +1 -1
- package/templates/env.default +1 -1
- package/templates/package.json +3 -2
- package/ui/banner.js +2 -2
- package/ui/interactive.js +20 -4
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,13 +122,14 @@ 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
|
|
128
130
|
.command('build')
|
|
129
131
|
.description(t('cli.build.description'))
|
|
130
|
-
.option('-t, --target <target>', '
|
|
132
|
+
.option('-t, --target <target>', t('cli.build.target'), 'all')
|
|
131
133
|
.action(async (options) => {
|
|
132
134
|
try {
|
|
133
135
|
await runBuild(options.target, ensureOriginalCwd());
|
|
@@ -254,6 +256,7 @@ program.on('--help', () => {
|
|
|
254
256
|
console.log(t('cli.help.status'));
|
|
255
257
|
console.log(t('cli.help.doctor'));
|
|
256
258
|
console.log(t('cli.help.docker'));
|
|
259
|
+
console.log(t('cli.help.build'));
|
|
257
260
|
console.log(t('cli.help.publish'));
|
|
258
261
|
console.log('');
|
|
259
262
|
});
|
package/dist/index.js
CHANGED
|
File without changes
|
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,29 +1,146 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const chalk = require('chalk');
|
|
1
4
|
const { runSync } = require('./spawn');
|
|
2
5
|
const { ensureOriginalCwd } = require('./root');
|
|
3
6
|
const { t } = require('./i18n');
|
|
4
7
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
const FORBIDDEN_SCRIPTS = new Set(['build']);
|
|
9
|
+
|
|
10
|
+
/** @type {Record<string, { script: string, labelKey: string }[]>} */
|
|
11
|
+
const BUILD_STEPS = {
|
|
12
|
+
toolkit: [{ script: 'build:toolkit', labelKey: 'build.label.toolkit' }],
|
|
13
|
+
server: [{ script: 'build:server', labelKey: 'build.label.server' }],
|
|
14
|
+
client: [{ script: 'build:client', labelKey: 'build.label.client' }],
|
|
15
|
+
docs: [{ script: 'build:docs', labelKey: 'build.label.docs' }],
|
|
16
|
+
all: [
|
|
17
|
+
{ script: 'build:toolkit', labelKey: 'build.label.toolkit' },
|
|
18
|
+
{ script: 'build:server', labelKey: 'build.label.server' },
|
|
19
|
+
{ script: 'build:client', labelKey: 'build.label.client' },
|
|
20
|
+
],
|
|
11
21
|
};
|
|
12
22
|
|
|
23
|
+
const TARGETS = Object.keys(BUILD_STEPS);
|
|
24
|
+
|
|
25
|
+
const buildChildEnv = { REACTPRESS_BUILD_ACTIVE: '1' };
|
|
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
|
+
|
|
13
81
|
async function runBuild(target = 'all', projectRoot = ensureOriginalCwd()) {
|
|
14
|
-
|
|
15
|
-
|
|
82
|
+
if (process.env.REACTPRESS_BUILD_ACTIVE === '1') {
|
|
83
|
+
throw new Error(t('build.recursive'));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const steps = BUILD_STEPS[target];
|
|
87
|
+
if (!steps) {
|
|
16
88
|
throw new Error(
|
|
17
89
|
t('build.unknownTarget', {
|
|
18
90
|
target,
|
|
19
|
-
available:
|
|
91
|
+
available: TARGETS.join(', '),
|
|
20
92
|
})
|
|
21
93
|
);
|
|
22
94
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
95
|
+
|
|
96
|
+
const total = steps.length;
|
|
97
|
+
const buildStarted = Date.now();
|
|
98
|
+
|
|
99
|
+
if (total > 1) {
|
|
100
|
+
console.log(t('build.plan', { total }));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
for (let i = 0; i < steps.length; i++) {
|
|
104
|
+
const { script, labelKey } = steps[i];
|
|
105
|
+
if (FORBIDDEN_SCRIPTS.has(script)) {
|
|
106
|
+
throw new Error(t('build.forbiddenScript', { script }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const current = i + 1;
|
|
110
|
+
const label = t(labelKey);
|
|
111
|
+
const stepStarted = Date.now();
|
|
112
|
+
|
|
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
|
+
|
|
121
|
+
try {
|
|
122
|
+
runSync(invocation.command, invocation.args, {
|
|
123
|
+
cwd: invocation.cwd,
|
|
124
|
+
env: buildChildEnv,
|
|
125
|
+
});
|
|
126
|
+
} catch (err) {
|
|
127
|
+
console.error(chalk.red(t('build.stepFailed', { current, total, label })));
|
|
128
|
+
throw err;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const seconds = ((Date.now() - stepStarted) / 1000).toFixed(1);
|
|
132
|
+
console.log(chalk.green(t('build.stepDone', { current, total, label, seconds })));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (total > 1) {
|
|
136
|
+
const totalSeconds = ((Date.now() - buildStarted) / 1000).toFixed(1);
|
|
137
|
+
console.log(chalk.green(t('build.done', { seconds: totalSeconds })));
|
|
26
138
|
}
|
|
27
139
|
}
|
|
28
140
|
|
|
29
|
-
module.exports = {
|
|
141
|
+
module.exports = {
|
|
142
|
+
runBuild,
|
|
143
|
+
TARGETS,
|
|
144
|
+
BUILD_STEPS,
|
|
145
|
+
resolveBuildInvocation,
|
|
146
|
+
};
|
package/lib/dev.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
const path = require('path');
|
|
3
|
+
const { runBuild } = require('./build');
|
|
3
4
|
const { ensureProjectEnvironment } = require('./bootstrap');
|
|
4
5
|
const { loadServerSiteUrl, loadClientSiteUrl, waitForHttp } = require('./http');
|
|
5
6
|
const { printDevReadyBanner } = require('./dev-banner');
|
|
@@ -30,20 +31,7 @@ function shutdown(signal = 'SIGINT') {
|
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
async function buildToolkit(projectRoot) {
|
|
33
|
-
|
|
34
|
-
const build = spawn('pnpm', ['build:toolkit'], {
|
|
35
|
-
stdio: 'inherit',
|
|
36
|
-
shell: true,
|
|
37
|
-
cwd: projectRoot,
|
|
38
|
-
});
|
|
39
|
-
build.on('close', (code) => {
|
|
40
|
-
if (code !== 0) {
|
|
41
|
-
reject(new Error(t('dev.toolkitFailed', { code })));
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
resolve();
|
|
45
|
-
});
|
|
46
|
-
});
|
|
34
|
+
await runBuild('toolkit', projectRoot);
|
|
47
35
|
}
|
|
48
36
|
|
|
49
37
|
async function startDevStack(projectRoot) {
|
package/lib/i18n/strings.js
CHANGED
|
@@ -43,7 +43,9 @@ const STRINGS = {
|
|
|
43
43
|
'cli.help.status': ' reactpress status Combined status',
|
|
44
44
|
'cli.help.doctor': ' reactpress doctor Environment diagnostics',
|
|
45
45
|
'cli.help.docker': ' reactpress docker start Docker + full stack',
|
|
46
|
+
'cli.help.build': ' reactpress build -t client Build one target (toolkit|server|client|docs|all)',
|
|
46
47
|
'cli.help.publish': ' reactpress publish Publish npm packages',
|
|
48
|
+
'cli.build.target': 'Build target: toolkit | server | client | docs | all',
|
|
47
49
|
'banner.subtitle': ' · Full-stack publishing CLI ',
|
|
48
50
|
'menu.dev': 'Zero-config dev (env + DB + API + frontend)',
|
|
49
51
|
'menu.init': 'Initialize project (.reactpress + .env + database)',
|
|
@@ -55,6 +57,8 @@ const STRINGS = {
|
|
|
55
57
|
'menu.serverStop': 'Stop API',
|
|
56
58
|
'menu.serverRestart': 'Restart API',
|
|
57
59
|
'menu.build': 'Build (toolkit → server → client)',
|
|
60
|
+
'menu.buildTarget': 'What do you want to build?',
|
|
61
|
+
'menu.buildAll': 'All (toolkit → server → client)',
|
|
58
62
|
'menu.dockerStart': 'Docker dev (DB + nginx + full stack)',
|
|
59
63
|
'menu.dockerUp': 'Docker: database only',
|
|
60
64
|
'menu.dockerStop': 'Stop Docker services',
|
|
@@ -193,7 +197,18 @@ const STRINGS = {
|
|
|
193
197
|
'apiDev.ctrlCHint': '[reactpress] Press Ctrl+C to stop API.',
|
|
194
198
|
'apiDev.stopHint': '[reactpress] Stop separately: reactpress server stop',
|
|
195
199
|
'build.unknownTarget': 'Unknown build target: {target}. Available: {available}',
|
|
196
|
-
'build.
|
|
200
|
+
'build.recursive': 'Build recursion detected (pnpm run build must not call itself). Use build:toolkit, build:server, or build:client.',
|
|
201
|
+
'build.forbiddenScript': 'Invalid build script "{script}"; use granular scripts like build:toolkit.',
|
|
202
|
+
'build.stepFailed': '[reactpress] [{current}/{total}] {label} failed',
|
|
203
|
+
'build.plan': '[reactpress] Production build — {total} step(s): toolkit → server → client',
|
|
204
|
+
'build.step': '[reactpress] [{current}/{total}] {label}…',
|
|
205
|
+
'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)',
|
|
206
|
+
'build.stepSkipped': '[reactpress] Skipped {label} (not part of this project layout)',
|
|
207
|
+
'build.done': '[reactpress] Build finished in {seconds}s',
|
|
208
|
+
'build.label.toolkit': 'Toolkit',
|
|
209
|
+
'build.label.server': 'API (server)',
|
|
210
|
+
'build.label.client': 'Frontend (client)',
|
|
211
|
+
'build.label.docs': 'Documentation (docs)',
|
|
197
212
|
'pm2.startFailed': '[reactpress] PM2 failed to start API:',
|
|
198
213
|
'pm2.exitCode': 'PM2 exited with code {code}',
|
|
199
214
|
'spawn.commandFailed': 'Command failed ({command}): exit code {code}',
|
|
@@ -320,7 +335,9 @@ const STRINGS = {
|
|
|
320
335
|
'cli.help.status': ' reactpress status 综合状态',
|
|
321
336
|
'cli.help.doctor': ' reactpress doctor 环境诊断',
|
|
322
337
|
'cli.help.docker': ' reactpress docker start Docker + 全栈',
|
|
338
|
+
'cli.help.build': ' reactpress build -t client 构建指定目标 (toolkit|server|client|docs|all)',
|
|
323
339
|
'cli.help.publish': ' reactpress publish 发布 npm 包',
|
|
340
|
+
'cli.build.target': '构建目标: toolkit | server | client | docs | all',
|
|
324
341
|
'banner.subtitle': ' · 全栈发布平台 CLI ',
|
|
325
342
|
'menu.dev': '零配置开发 (env + DB + API + 前端)',
|
|
326
343
|
'menu.init': '初始化项目 (.reactpress + .env + 数据库)',
|
|
@@ -332,6 +349,8 @@ const STRINGS = {
|
|
|
332
349
|
'menu.serverStop': '停止 API',
|
|
333
350
|
'menu.serverRestart': '重启 API',
|
|
334
351
|
'menu.build': '构建 (toolkit → server → client)',
|
|
352
|
+
'menu.buildTarget': '选择要构建的目标',
|
|
353
|
+
'menu.buildAll': '全部 (toolkit → server → client)',
|
|
335
354
|
'menu.dockerStart': 'Docker 开发环境 (DB + nginx + 全栈)',
|
|
336
355
|
'menu.dockerUp': 'Docker 仅启动数据库',
|
|
337
356
|
'menu.dockerStop': '停止 Docker 服务',
|
|
@@ -470,7 +489,18 @@ const STRINGS = {
|
|
|
470
489
|
'apiDev.ctrlCHint': '[reactpress] 按 Ctrl+C 停止 API。',
|
|
471
490
|
'apiDev.stopHint': '[reactpress] 单独停止: reactpress server stop',
|
|
472
491
|
'build.unknownTarget': '未知构建目标: {target},可选: {available}',
|
|
473
|
-
'build.
|
|
492
|
+
'build.recursive': '检测到构建递归(pnpm run build 不能再次调用自身)。请使用 build:toolkit、build:server 或 build:client。',
|
|
493
|
+
'build.forbiddenScript': '无效的构建脚本 "{script}",请使用 build:toolkit 等细分脚本。',
|
|
494
|
+
'build.stepFailed': '[reactpress] [{current}/{total}] {label} 失败',
|
|
495
|
+
'build.plan': '[reactpress] 生产构建 — 共 {total} 步:toolkit → server → client',
|
|
496
|
+
'build.step': '[reactpress] [{current}/{total}] {label}…',
|
|
497
|
+
'build.stepDone': '[reactpress] [{current}/{total}] {label} ✓ ({seconds}s)',
|
|
498
|
+
'build.stepSkipped': '[reactpress] 已跳过 {label}(当前项目无对应源码包)',
|
|
499
|
+
'build.done': '[reactpress] 构建完成,耗时 {seconds}s',
|
|
500
|
+
'build.label.toolkit': 'Toolkit',
|
|
501
|
+
'build.label.server': 'API (server)',
|
|
502
|
+
'build.label.client': '前端 (client)',
|
|
503
|
+
'build.label.docs': '文档 (docs)',
|
|
474
504
|
'pm2.startFailed': '[reactpress] PM2 启动 API 失败:',
|
|
475
505
|
'pm2.exitCode': 'PM2 退出码 {code}',
|
|
476
506
|
'spawn.commandFailed': '命令失败 ({command}): 退出码 {code}',
|
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,
|