@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 CHANGED
@@ -1,47 +1,93 @@
1
- # @fecommunity/reactpress-cli
1
+ # @fecommunity/reactpress
2
2
 
3
- 零配置一键初始化与管理 ReactPress CMS & 博客服务器。内置 NestJS 服务端,无需单独克隆 [fecommunity/reactpress](https://github.com/fecommunity/reactpress)。
3
+ ReactPress 3.x 主包:一条命令完成站点初始化、本地开发、构建与生产部署。内置 NestJS API(站点项目无 `server/` 时自动使用 bundle),Monorepo 贡献者可使用仓库内 `server/` 源码联调。
4
4
 
5
- 完整文档与贡献指南见:[github.com/fecommunity/reactpress-cli](https://github.com/fecommunity/reactpress-cli)
5
+ [![npm version](https://img.shields.io/npm/v/@fecommunity/reactpress.svg)](https://www.npmjs.com/package/@fecommunity/reactpress)
6
+ [![License: MIT](https://img.shields.io/npm/l/@fecommunity/reactpress.svg)](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-cli
13
+ npm install -g @fecommunity/reactpress@3
11
14
  ```
12
15
 
13
- 全局命令为 `reactpress-cli`(与 npm 包名无关)。
16
+ 全局命令为 **`reactpress`**(推荐)。
14
17
 
15
- > npm 上的无作用域包名 `reactpress-cli` 已被占用,本包发布为 `@fecommunity/reactpress-cli`。
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-cli init
22
- reactpress-cli start
26
+ reactpress init
27
+ reactpress dev
23
28
  ```
24
29
 
25
- 浏览器访问 `http://localhost:3002`(API 文档:`/api`)。
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-cli init [dir]` | 初始化项目 |
32
- | `reactpress-cli start` | 启动服务(自动准备数据库) |
33
- | `reactpress-cli stop` | 停止服务 |
34
- | `reactpress-cli restart` | 重启服务 |
35
- | `reactpress-cli status` | 查看状态 |
36
- | `reactpress-cli config [key] [value]` | 查看/修改配置 |
37
- | `reactpress-cli config server.port 3003 --apply` | 改端口并重启 |
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
- - Node.js 18+
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, getMonorepoRoot } = require('../lib/root');
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(getMonorepoRoot(), 'package.json'));
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
- await runNodeScript(getClientBin(), args, { cwd: ensureOriginalCwd() });
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 { getMonorepoRoot } = require('./root');
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 = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) {
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 { getMonorepoRoot, isMonorepoCheckout } = require('./root');
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 = getMonorepoRoot()) {
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('pnpm', ['run', script], { cwd: projectRoot, env: buildChildEnv });
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 = { runBuild, TARGETS, BUILD_STEPS };
141
+ module.exports = {
142
+ runBuild,
143
+ TARGETS,
144
+ BUILD_STEPS,
145
+ resolveBuildInvocation,
146
+ };
@@ -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 { getMonorepoRoot } = require('./root');
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 = process.env.REACTPRESS_ORIGINAL_CWD || getMonorepoRoot()) {
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 getMonorepoServerDir() {
6
- return path.join(getMonorepoRoot(), 'server');
5
+ function resolveProjectRoot(projectRoot) {
6
+ return path.resolve(projectRoot || ensureOriginalCwd());
7
7
  }
8
8
 
9
- function hasMonorepoServerSource() {
10
- return fs.existsSync(path.join(getMonorepoServerDir(), 'src', 'main.ts'));
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
- return path.join(getMonorepoRoot(), 'client', 'bin', 'reactpress-client.js');
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
- return path.resolve(process.env.REACTPRESS_ORIGINAL_CWD || process.cwd());
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 = getMonorepoRoot()) {
72
+ function isMonorepoCheckout(cwd) {
73
+ const resolved = path.resolve(cwd || process.cwd());
28
74
  return (
29
- fs.existsSync(path.join(cwd, 'pnpm-workspace.yaml')) ||
30
- fs.existsSync(path.join(cwd, 'server', 'src', 'main.ts'))
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 { getMonorepoRoot } = require('./root');
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: options.cwd || getMonorepoRoot(),
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: options.cwd || getMonorepoRoot(),
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: options.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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fecommunity/reactpress",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "ReactPress 3.0 — zero-config CMS: one package, one reactpress command",
5
5
  "author": "fecommunity",
6
6
  "license": "MIT",
@@ -1,4 +1,4 @@
1
- # ReactPress — auto-generated by reactpress-cli (do not edit DB_* unless you know what you are doing)
1
+ # ReactPress — auto-generated by reactpress init (do not edit DB_* unless you know what you are doing)
2
2
  DB_HOST=127.0.0.1
3
3
  DB_PORT=3306
4
4
  DB_USER=reactpress
@@ -4,7 +4,8 @@
4
4
  "version": "1.0.0",
5
5
  "description": "ReactPress CMS & Blog site",
6
6
  "scripts": {
7
- "start": "reactpress-cli start",
8
- "stop": "reactpress-cli stop"
7
+ "dev": "reactpress dev",
8
+ "start": "reactpress server start",
9
+ "stop": "reactpress server stop"
9
10
  }
10
11
  }
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(getMonorepoRoot(), 'package.json')).version;
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);