@fecommunity/reactpress 4.0.0-beta.17 → 4.0.0-beta.18
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/out/bin/reactpress-theme-client.js +4 -0
- package/out/bin/reactpress.js +3 -2
- package/out/core/services/admin-static.js +79 -26
- package/out/lib/bootstrap.js +9 -0
- package/out/lib/i18n/strings.js +18 -8
- package/out/lib/lifecycle.js +9 -0
- package/out/lib/pm2-runtime.js +15 -0
- package/out/lib/project-logs.js +318 -12
- package/package.json +1 -1
- package/server/bin/reactpress-server.js +17 -0
- package/server/package-lock.json +3 -3
- package/server/package.json +1 -1
- package/toolkit/package.json +1 -1
|
@@ -36,6 +36,10 @@ function runStartCommand() {
|
|
|
36
36
|
return ['npm', ['run', 'start']];
|
|
37
37
|
}
|
|
38
38
|
function ensureBuilt() {
|
|
39
|
+
const { ensureBundledPlugins } = require('../core/services/local-site');
|
|
40
|
+
if (ensureBundledPlugins(originalCwd)) {
|
|
41
|
+
console.log('[ReactPress Client] Seeded bundled plugins into project');
|
|
42
|
+
}
|
|
39
43
|
const adminSynced = ensureAdminStaticForTheme(originalCwd, clientDir);
|
|
40
44
|
const apiProxyPatched = patchThemeApiProxyRoute(clientDir);
|
|
41
45
|
const { activeTheme } = readActiveThemeManifest(originalCwd);
|
package/out/bin/reactpress.js
CHANGED
|
@@ -39,9 +39,10 @@ const LEGACY_COMMANDS = new Set([
|
|
|
39
39
|
function attachLogsOptions(cmd) {
|
|
40
40
|
return cmd
|
|
41
41
|
.option('--tail <n>', t('cli.logs.tailOption'), '50')
|
|
42
|
-
.option('--source <name>', t('cli.logs.sourceOption')
|
|
42
|
+
.option('--source <name>', t('cli.logs.sourceOption'))
|
|
43
43
|
.option('--grep <pattern>', t('cli.logs.grepOption'))
|
|
44
|
-
.option('--list', t('cli.logs.listOption'))
|
|
44
|
+
.option('--list', t('cli.logs.listOption'))
|
|
45
|
+
.option('-f, --follow', t('cli.logs.followOption'));
|
|
45
46
|
}
|
|
46
47
|
function createLogsAction() {
|
|
47
48
|
return async (directory, options) => {
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.resolveBundledAdminDistDir = resolveBundledAdminDistDir;
|
|
7
7
|
exports.resolveWebPackageRoot = resolveWebPackageRoot;
|
|
8
|
+
exports.adminSourceIsNewerThanTheme = adminSourceIsNewerThanTheme;
|
|
8
9
|
exports.ensureAdminRewritesInNextConfig = ensureAdminRewritesInNextConfig;
|
|
9
10
|
exports.ensureAdminStaticForTheme = ensureAdminStaticForTheme;
|
|
10
11
|
exports.shouldRebuildThemeAfterAdminSync = shouldRebuildThemeAfterAdminSync;
|
|
@@ -26,6 +27,45 @@ function resolveWebPackageRoot(projectRoot) {
|
|
|
26
27
|
}
|
|
27
28
|
return null;
|
|
28
29
|
}
|
|
30
|
+
function readAdminMainAssetId(indexHtmlPath) {
|
|
31
|
+
try {
|
|
32
|
+
const html = fs_extra_1.default.readFileSync(indexHtmlPath, 'utf8');
|
|
33
|
+
return html.match(/assets\/(index-[^"]+\.js)/)?.[1] ?? null;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function resolveAdminDistSource(projectRoot) {
|
|
40
|
+
const bundled = resolveBundledAdminDistDir();
|
|
41
|
+
const webRoot = resolveWebPackageRoot(projectRoot);
|
|
42
|
+
const webDist = webRoot ? node_path_1.default.join(webRoot, 'dist') : null;
|
|
43
|
+
const webIndex = webDist ? node_path_1.default.join(webDist, 'index.html') : null;
|
|
44
|
+
if (webIndex && fs_extra_1.default.existsSync(webIndex)) {
|
|
45
|
+
if (!bundled)
|
|
46
|
+
return webDist;
|
|
47
|
+
const bundledIndex = node_path_1.default.join(bundled, 'index.html');
|
|
48
|
+
const webMtime = fs_extra_1.default.statSync(webIndex).mtimeMs;
|
|
49
|
+
const bundledMtime = fs_extra_1.default.statSync(bundledIndex).mtimeMs;
|
|
50
|
+
return webMtime >= bundledMtime ? webDist : bundled;
|
|
51
|
+
}
|
|
52
|
+
return bundled;
|
|
53
|
+
}
|
|
54
|
+
/** True when CLI/web admin-dist is newer than the theme's copied public/admin bundle. */
|
|
55
|
+
function adminSourceIsNewerThanTheme(projectRoot, themeDir) {
|
|
56
|
+
const source = resolveAdminDistSource(projectRoot);
|
|
57
|
+
const sourceIndex = source ? node_path_1.default.join(source, 'index.html') : null;
|
|
58
|
+
const themeAdminIndex = node_path_1.default.join(themeDir, 'public', 'admin', 'index.html');
|
|
59
|
+
if (!sourceIndex || !fs_extra_1.default.existsSync(sourceIndex))
|
|
60
|
+
return false;
|
|
61
|
+
if (!fs_extra_1.default.existsSync(themeAdminIndex))
|
|
62
|
+
return true;
|
|
63
|
+
const sourceId = readAdminMainAssetId(sourceIndex);
|
|
64
|
+
const themeId = readAdminMainAssetId(themeAdminIndex);
|
|
65
|
+
if (sourceId && themeId)
|
|
66
|
+
return sourceId !== themeId;
|
|
67
|
+
return fs_extra_1.default.statSync(sourceIndex).mtimeMs > fs_extra_1.default.statSync(themeAdminIndex).mtimeMs;
|
|
68
|
+
}
|
|
29
69
|
function syncDirToPublicAdmin(sourceDist, themeDir) {
|
|
30
70
|
const target = node_path_1.default.join(themeDir, 'public', 'admin');
|
|
31
71
|
fs_extra_1.default.removeSync(target);
|
|
@@ -78,40 +118,53 @@ function ensureAdminRewritesInNextConfig(themeDir) {
|
|
|
78
118
|
function finalizeAdminThemeSetup(themeDir) {
|
|
79
119
|
return ensureAdminRewritesInNextConfig(themeDir);
|
|
80
120
|
}
|
|
121
|
+
function syncAdminFromWeb(projectRoot, themeDir) {
|
|
122
|
+
const webRoot = resolveWebPackageRoot(projectRoot);
|
|
123
|
+
if (!webRoot)
|
|
124
|
+
return false;
|
|
125
|
+
const webDist = node_path_1.default.join(webRoot, 'dist', 'index.html');
|
|
126
|
+
if (!fs_extra_1.default.existsSync(webDist)) {
|
|
127
|
+
console.log('[ReactPress Client] Building admin SPA (web/)…');
|
|
128
|
+
const buildWeb = (0, node_child_process_1.spawnSync)('pnpm', ['run', 'build'], { cwd: webRoot, stdio: 'inherit' });
|
|
129
|
+
if (buildWeb.status !== 0)
|
|
130
|
+
process.exit(buildWeb.status || 1);
|
|
131
|
+
}
|
|
132
|
+
const syncScript = node_path_1.default.join(webRoot, 'bin', 'reactpress-web.js');
|
|
133
|
+
const publicAdmin = node_path_1.default.join(themeDir, 'public', 'admin');
|
|
134
|
+
console.log('[ReactPress Client] Syncing admin static → theme public/admin…');
|
|
135
|
+
const sync = (0, node_child_process_1.spawnSync)(process.execPath, [syncScript, 'sync-public', publicAdmin], {
|
|
136
|
+
stdio: 'inherit',
|
|
137
|
+
cwd: webRoot,
|
|
138
|
+
});
|
|
139
|
+
if (sync.status !== 0)
|
|
140
|
+
process.exit(sync.status || 1);
|
|
141
|
+
return finalizeAdminThemeSetup(themeDir) || true;
|
|
142
|
+
}
|
|
143
|
+
function syncAdminFromSource(sourceDist, themeDir) {
|
|
144
|
+
console.log('[ReactPress Client] Syncing bundled admin static → theme public/admin…');
|
|
145
|
+
syncDirToPublicAdmin(sourceDist, themeDir);
|
|
146
|
+
return finalizeAdminThemeSetup(themeDir) || true;
|
|
147
|
+
}
|
|
81
148
|
/** Returns true when admin static or rewrites were newly applied (theme rebuild recommended). */
|
|
82
149
|
function ensureAdminStaticForTheme(projectRoot, themeDir) {
|
|
83
150
|
const adminIndex = node_path_1.default.join(themeDir, 'public', 'admin', 'index.html');
|
|
84
|
-
|
|
151
|
+
const needsResync = !fs_extra_1.default.existsSync(adminIndex) || adminSourceIsNewerThanTheme(projectRoot, themeDir);
|
|
152
|
+
if (!needsResync) {
|
|
85
153
|
return finalizeAdminThemeSetup(themeDir);
|
|
86
154
|
}
|
|
87
|
-
const
|
|
88
|
-
if (
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
const buildWeb = (0, node_child_process_1.spawnSync)('pnpm', ['run', 'build'], { cwd: webRoot, stdio: 'inherit' });
|
|
93
|
-
if (buildWeb.status !== 0)
|
|
94
|
-
process.exit(buildWeb.status || 1);
|
|
155
|
+
const source = resolveAdminDistSource(projectRoot);
|
|
156
|
+
if (source && fs_extra_1.default.existsSync(node_path_1.default.join(source, 'index.html'))) {
|
|
157
|
+
const webRoot = resolveWebPackageRoot(projectRoot);
|
|
158
|
+
if (webRoot && node_path_1.default.resolve(source) === node_path_1.default.resolve(node_path_1.default.join(webRoot, 'dist'))) {
|
|
159
|
+
return syncAdminFromWeb(projectRoot, themeDir);
|
|
95
160
|
}
|
|
96
|
-
|
|
97
|
-
const publicAdmin = node_path_1.default.join(themeDir, 'public', 'admin');
|
|
98
|
-
console.log('[ReactPress Client] Syncing admin static → theme public/admin…');
|
|
99
|
-
const sync = (0, node_child_process_1.spawnSync)(process.execPath, [syncScript, 'sync-public', publicAdmin], {
|
|
100
|
-
stdio: 'inherit',
|
|
101
|
-
cwd: webRoot,
|
|
102
|
-
});
|
|
103
|
-
if (sync.status !== 0)
|
|
104
|
-
process.exit(sync.status || 1);
|
|
105
|
-
return finalizeAdminThemeSetup(themeDir) || true;
|
|
161
|
+
return syncAdminFromSource(source, themeDir);
|
|
106
162
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
console.warn('[ReactPress Client] Admin static missing and bundled admin-dist not found');
|
|
110
|
-
return false;
|
|
163
|
+
if (syncAdminFromWeb(projectRoot, themeDir)) {
|
|
164
|
+
return true;
|
|
111
165
|
}
|
|
112
|
-
console.
|
|
113
|
-
|
|
114
|
-
return finalizeAdminThemeSetup(themeDir) || true;
|
|
166
|
+
console.warn('[ReactPress Client] Admin static missing and bundled admin-dist not found');
|
|
167
|
+
return false;
|
|
115
168
|
}
|
|
116
169
|
function shouldRebuildThemeAfterAdminSync(themeDir) {
|
|
117
170
|
const adminIndex = node_path_1.default.join(themeDir, 'public', 'admin', 'index.html');
|
package/out/lib/bootstrap.js
CHANGED
|
@@ -43,6 +43,15 @@ async function ensureProjectEnvironment(projectRoot = (0, root_1.ensureOriginalC
|
|
|
43
43
|
applyZeroDependencyDefaults();
|
|
44
44
|
const root = node_path_1.default.resolve(projectRoot);
|
|
45
45
|
(0, cli_context_1.setProjectCwd)(root);
|
|
46
|
+
try {
|
|
47
|
+
const { ensureBundledPlugins } = require('../core/services/local-site');
|
|
48
|
+
if (ensureBundledPlugins(root)) {
|
|
49
|
+
console.log(`[reactpress] ${(0, i18n_1.t)('init.pluginsSeeded')}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// bundled plugins are optional; ignore when CLI core is unavailable
|
|
54
|
+
}
|
|
46
55
|
if (!(await (0, config_1.isReactPressProject)(root))) {
|
|
47
56
|
const result = await (0, init_1.initProject)({ directory: root, force: false });
|
|
48
57
|
if (!result.ok) {
|
package/out/lib/i18n/strings.js
CHANGED
|
@@ -76,12 +76,13 @@ const STRINGS = {
|
|
|
76
76
|
'cli.doctor.logs.sourceOption': 'Log source: error, request, response, or all',
|
|
77
77
|
'cli.doctor.logs.grepOption': 'Filter log lines by regex',
|
|
78
78
|
'cli.doctor.logs.listOption': 'List available log files only',
|
|
79
|
-
'cli.logs.description': 'View
|
|
79
|
+
'cli.logs.description': 'View API logs (PM2 process output or structured error/request/response)',
|
|
80
80
|
'cli.logs.descriptionAlias': 'View API logs (alias of reactpress logs)',
|
|
81
81
|
'cli.logs.tailOption': 'Number of lines to show per log file',
|
|
82
|
-
'cli.logs.sourceOption': 'Log source: error, request, response, or all',
|
|
82
|
+
'cli.logs.sourceOption': 'Log source: process (PM2), error, request, response, or all',
|
|
83
83
|
'cli.logs.grepOption': 'Filter log lines by regex',
|
|
84
84
|
'cli.logs.listOption': 'List available log files only',
|
|
85
|
+
'cli.logs.followOption': 'Stream logs continuously (PM2 when supervised, otherwise tail -f)',
|
|
85
86
|
'cli.stop.description': 'Stop API and site services started by init',
|
|
86
87
|
'cli.db.description': 'Database operations',
|
|
87
88
|
'cli.db.backup': 'Backup current project database with mysqldump',
|
|
@@ -433,15 +434,19 @@ const STRINGS = {
|
|
|
433
434
|
'doctor.logs.subtitle': 'API log viewer',
|
|
434
435
|
'doctor.logs.dir': 'Log dir {path}',
|
|
435
436
|
'doctor.logs.empty': 'No API log files found yet.',
|
|
436
|
-
'doctor.logs.emptyHint': 'Start the API with reactpress init — logs are written to .reactpress/logs/server/',
|
|
437
|
+
'doctor.logs.emptyHint': 'Start the API with reactpress init — logs are written to .reactpress/logs/server/ (PM2 output: pm2-out.log / pm2-error.log)',
|
|
437
438
|
'doctor.logs.listHeader': 'Available log files',
|
|
438
439
|
'doctor.logs.fileHeader': '{file} (showing {count} line(s))',
|
|
439
440
|
'doctor.logs.noMatchingLines': 'No matching log lines.',
|
|
440
441
|
'doctor.logs.badPattern': 'Invalid grep pattern: {pattern}',
|
|
441
442
|
'doctor.logs.noPid': 'No API pid file at {path}',
|
|
443
|
+
'doctor.logs.activePid': 'API listening on port {port} (pid {pid})',
|
|
444
|
+
'doctor.logs.pm2Supervised': 'API supervised by PM2 (pid {pid})',
|
|
445
|
+
'doctor.logs.pm2Unavailable': 'PM2 is not available for live log streaming.',
|
|
446
|
+
'doctor.logs.followWindows': 'Live follow is not supported here. Open: {path}',
|
|
442
447
|
'doctor.logs.pidRunning': 'API pid {pid} (recorded in {path})',
|
|
443
448
|
'doctor.logs.pidStale': 'Stale pid {pid} in {path} — process not running',
|
|
444
|
-
'doctor.logs.moreHint': 'Use --tail, --source, --grep, or --list for more control (see reactpress logs --help)',
|
|
449
|
+
'doctor.logs.moreHint': 'Use --tail, --source, --grep, --follow, or --list for more control (see reactpress logs --help)',
|
|
445
450
|
'status.title': 'ReactPress project status',
|
|
446
451
|
'status.dir': 'Project {path}',
|
|
447
452
|
'status.apiSource': 'API source {source}',
|
|
@@ -752,12 +757,13 @@ const STRINGS = {
|
|
|
752
757
|
'cli.doctor.logs.sourceOption': '日志类型:error、request、response 或 all',
|
|
753
758
|
'cli.doctor.logs.grepOption': '用正则过滤日志行',
|
|
754
759
|
'cli.doctor.logs.listOption': '仅列出可用日志文件',
|
|
755
|
-
'cli.logs.description': '查看 API 日志(error
|
|
760
|
+
'cli.logs.description': '查看 API 日志(PM2 进程输出或结构化 error/request/response)',
|
|
756
761
|
'cli.logs.descriptionAlias': '查看 API 日志(同 reactpress logs)',
|
|
757
762
|
'cli.logs.tailOption': '每个日志文件显示的行数',
|
|
758
|
-
'cli.logs.sourceOption': '日志类型:error、request、response 或 all',
|
|
763
|
+
'cli.logs.sourceOption': '日志类型:process(PM2)、error、request、response 或 all',
|
|
759
764
|
'cli.logs.grepOption': '用正则过滤日志行',
|
|
760
765
|
'cli.logs.listOption': '仅列出可用日志文件',
|
|
766
|
+
'cli.logs.followOption': '持续输出日志(PM2 托管时用 pm2 logs,否则 tail -f)',
|
|
761
767
|
'cli.stop.description': '停止 init 启动的 API 与站点服务',
|
|
762
768
|
'cli.db.description': '数据库运维',
|
|
763
769
|
'cli.db.backup': '使用 mysqldump 备份当前项目数据库',
|
|
@@ -1108,15 +1114,19 @@ const STRINGS = {
|
|
|
1108
1114
|
'doctor.logs.subtitle': 'API 日志查看',
|
|
1109
1115
|
'doctor.logs.dir': '日志目录 {path}',
|
|
1110
1116
|
'doctor.logs.empty': '尚未找到 API 日志文件。',
|
|
1111
|
-
'doctor.logs.emptyHint': '运行 reactpress init 启动 API
|
|
1117
|
+
'doctor.logs.emptyHint': '运行 reactpress init 启动 API 后,日志写入 .reactpress/logs/server/(PM2 输出:pm2-out.log / pm2-error.log)',
|
|
1112
1118
|
'doctor.logs.listHeader': '可用日志文件',
|
|
1113
1119
|
'doctor.logs.fileHeader': '{file}(显示 {count} 行)',
|
|
1114
1120
|
'doctor.logs.noMatchingLines': '没有匹配的日志行。',
|
|
1115
1121
|
'doctor.logs.badPattern': 'grep 正则无效:{pattern}',
|
|
1116
1122
|
'doctor.logs.noPid': '未找到 API pid 文件:{path}',
|
|
1123
|
+
'doctor.logs.activePid': 'API 正在监听端口 {port}(pid {pid})',
|
|
1124
|
+
'doctor.logs.pm2Supervised': 'API 由 PM2 托管(pid {pid})',
|
|
1125
|
+
'doctor.logs.pm2Unavailable': '无法使用 PM2 实时输出日志。',
|
|
1126
|
+
'doctor.logs.followWindows': '当前环境不支持持续跟踪,请直接打开:{path}',
|
|
1117
1127
|
'doctor.logs.pidRunning': 'API 进程 pid {pid}(记录在 {path})',
|
|
1118
1128
|
'doctor.logs.pidStale': 'pid 文件 {path} 中的 {pid} 已失效(进程未运行)',
|
|
1119
|
-
'doctor.logs.moreHint': '可用 --tail、--source、--grep、--list 进一步筛选(见 reactpress logs --help)',
|
|
1129
|
+
'doctor.logs.moreHint': '可用 --tail、--source、--grep、--follow、--list 进一步筛选(见 reactpress logs --help)',
|
|
1120
1130
|
'status.title': 'ReactPress 项目状态',
|
|
1121
1131
|
'status.dir': '项目目录 {path}',
|
|
1122
1132
|
'status.apiSource': 'API 来源 {source}',
|
package/out/lib/lifecycle.js
CHANGED
|
@@ -80,6 +80,8 @@ async function startApiWithPm2(projectRoot, { wait = true } = {}) {
|
|
|
80
80
|
const serverDir = getServerDir(projectRoot);
|
|
81
81
|
const serverLogDir = path.join(projectRoot, '.reactpress', 'logs', 'server');
|
|
82
82
|
fs.mkdirSync(serverLogDir, { recursive: true });
|
|
83
|
+
const pm2OutLog = path.join(serverLogDir, 'pm2-out.log');
|
|
84
|
+
const pm2ErrLog = path.join(serverLogDir, 'pm2-error.log');
|
|
83
85
|
const started = runPm2(projectRoot, [
|
|
84
86
|
'start',
|
|
85
87
|
serverBin,
|
|
@@ -87,6 +89,10 @@ async function startApiWithPm2(projectRoot, { wait = true } = {}) {
|
|
|
87
89
|
PM2_API_APP,
|
|
88
90
|
'--cwd',
|
|
89
91
|
serverDir,
|
|
92
|
+
'--output',
|
|
93
|
+
pm2OutLog,
|
|
94
|
+
'--error',
|
|
95
|
+
pm2ErrLog,
|
|
90
96
|
'--max-memory-restart',
|
|
91
97
|
getPm2ServerMemoryRestart(),
|
|
92
98
|
'--update-env',
|
|
@@ -103,6 +109,9 @@ async function startApiWithPm2(projectRoot, { wait = true } = {}) {
|
|
|
103
109
|
return startApiDetached(projectRoot, { wait });
|
|
104
110
|
}
|
|
105
111
|
const pid = getPm2AppPid(projectRoot, PM2_API_APP);
|
|
112
|
+
if (pid) {
|
|
113
|
+
writePid(projectRoot, pid);
|
|
114
|
+
}
|
|
106
115
|
console.log(t('lifecycle.apiStartedBg', { pid: pid ?? PM2_API_APP }));
|
|
107
116
|
if (!wait)
|
|
108
117
|
return 0;
|
package/out/lib/pm2-runtime.js
CHANGED
|
@@ -69,6 +69,20 @@ function getPm2AppPid(projectRoot, name) {
|
|
|
69
69
|
const app = getPm2App(projectRoot, name);
|
|
70
70
|
return typeof app?.pid === 'number' ? app.pid : null;
|
|
71
71
|
}
|
|
72
|
+
function getPm2AppLogPaths(projectRoot, name = PM2_API_APP) {
|
|
73
|
+
const serverLogDir = path.join(projectRoot, '.reactpress', 'logs', 'server');
|
|
74
|
+
const defaults = {
|
|
75
|
+
out: path.join(serverLogDir, 'pm2-out.log'),
|
|
76
|
+
err: path.join(serverLogDir, 'pm2-error.log'),
|
|
77
|
+
};
|
|
78
|
+
const app = getPm2App(projectRoot, name);
|
|
79
|
+
if (!app)
|
|
80
|
+
return defaults;
|
|
81
|
+
return {
|
|
82
|
+
out: app.pm2_env?.pm_out_log_path || defaults.out,
|
|
83
|
+
err: app.pm2_env?.pm_err_log_path || defaults.err,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
72
86
|
function stopPm2Apps(projectRoot, names) {
|
|
73
87
|
const bin = resolvePm2Bin(projectRoot);
|
|
74
88
|
if (!bin)
|
|
@@ -90,6 +104,7 @@ module.exports = {
|
|
|
90
104
|
getPm2App,
|
|
91
105
|
isPm2AppOnline,
|
|
92
106
|
getPm2AppPid,
|
|
107
|
+
getPm2AppLogPaths,
|
|
93
108
|
stopPm2Apps,
|
|
94
109
|
};
|
|
95
110
|
//# sourceMappingURL=pm2-runtime.js.map
|
package/out/lib/project-logs.js
CHANGED
|
@@ -3,35 +3,259 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
// @ts-nocheck
|
|
4
4
|
const fs = require('fs');
|
|
5
5
|
const path = require('path');
|
|
6
|
+
const { spawnSync } = require('child_process');
|
|
6
7
|
const { brand, divider, gradientText, palette, sectionHeader } = require('../ui/theme');
|
|
7
8
|
const { getServerDir, getPidFile } = require('./paths');
|
|
8
9
|
const { readPid, isProcessRunning } = require('./process');
|
|
10
|
+
const { loadServerSiteUrl } = require('./http');
|
|
11
|
+
const { getPm2App, PM2_API_APP, isPm2AppOnline, getPm2AppLogPaths, resolvePm2Bin, } = require('./pm2-runtime');
|
|
9
12
|
const { t } = require('./i18n');
|
|
10
13
|
const LOG_SOURCES = ['error', 'request', 'response'];
|
|
14
|
+
const PROCESS_LOG_SOURCES = ['pm2-out', 'pm2-error'];
|
|
15
|
+
const PM2_LOG_FILES = {
|
|
16
|
+
'pm2-out': 'pm2-out.log',
|
|
17
|
+
'pm2-error': 'pm2-error.log',
|
|
18
|
+
};
|
|
19
|
+
function measureLogDir(logDir) {
|
|
20
|
+
if (!fs.existsSync(logDir))
|
|
21
|
+
return -1;
|
|
22
|
+
let total = listLogFiles(logDir, 'all').reduce((sum, file) => sum + file.size, 0);
|
|
23
|
+
for (const filename of Object.values(PM2_LOG_FILES)) {
|
|
24
|
+
const filePath = path.join(logDir, filename);
|
|
25
|
+
try {
|
|
26
|
+
if (fs.existsSync(filePath)) {
|
|
27
|
+
total += fs.statSync(filePath).size;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// ignore
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return total;
|
|
35
|
+
}
|
|
36
|
+
function getPm2ServerLogDir(projectRoot) {
|
|
37
|
+
try {
|
|
38
|
+
const app = getPm2App(projectRoot, PM2_API_APP);
|
|
39
|
+
const dir = app?.pm2_env?.env?.REACTPRESS_SERVER_LOG_DIR;
|
|
40
|
+
if (typeof dir === 'string' && dir.trim()) {
|
|
41
|
+
return path.resolve(dir.trim());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// ignore
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
function parseUrlPort(urlString) {
|
|
50
|
+
try {
|
|
51
|
+
const url = new URL(urlString);
|
|
52
|
+
const port = Number(url.port);
|
|
53
|
+
if (port > 0)
|
|
54
|
+
return port;
|
|
55
|
+
return url.protocol === 'https:' ? 443 : 80;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function resolveListeningPid(port) {
|
|
62
|
+
if (!port || process.platform === 'win32')
|
|
63
|
+
return null;
|
|
64
|
+
const result = spawnSync('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t'], {
|
|
65
|
+
encoding: 'utf8',
|
|
66
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
67
|
+
});
|
|
68
|
+
if (result.status !== 0 || !result.stdout.trim())
|
|
69
|
+
return null;
|
|
70
|
+
const pids = result.stdout
|
|
71
|
+
.trim()
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((line) => Number.parseInt(line.trim(), 10))
|
|
74
|
+
.filter((pid) => Number.isFinite(pid));
|
|
75
|
+
return pids[0] ?? null;
|
|
76
|
+
}
|
|
77
|
+
function resolveLogDirFromPid(pid) {
|
|
78
|
+
if (!pid || process.platform === 'win32')
|
|
79
|
+
return null;
|
|
80
|
+
const result = spawnSync('lsof', ['-Fn', '-p', String(pid)], {
|
|
81
|
+
encoding: 'utf8',
|
|
82
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
83
|
+
});
|
|
84
|
+
if (result.status !== 0)
|
|
85
|
+
return null;
|
|
86
|
+
for (const line of result.stdout.split('\n')) {
|
|
87
|
+
if (!line.startsWith('n') || !line.includes('.log'))
|
|
88
|
+
continue;
|
|
89
|
+
const logPath = line.slice(1);
|
|
90
|
+
const match = logPath.match(/^(.*\/logs)\/(?:error|request|response)\//);
|
|
91
|
+
if (match)
|
|
92
|
+
return match[1];
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
function resolveActiveServerLogDir(projectRoot) {
|
|
97
|
+
const port = parseUrlPort(loadServerSiteUrl(projectRoot));
|
|
98
|
+
if (!port)
|
|
99
|
+
return null;
|
|
100
|
+
const pid = resolveListeningPid(port);
|
|
101
|
+
if (!pid)
|
|
102
|
+
return null;
|
|
103
|
+
return resolveLogDirFromPid(pid);
|
|
104
|
+
}
|
|
105
|
+
function collectLogDirCandidates(projectRoot) {
|
|
106
|
+
const candidates = [
|
|
107
|
+
path.join(projectRoot, '.reactpress', 'logs', 'server'),
|
|
108
|
+
path.join(getServerDir(projectRoot), 'logs'),
|
|
109
|
+
];
|
|
110
|
+
const pm2Dir = getPm2ServerLogDir(projectRoot);
|
|
111
|
+
if (pm2Dir)
|
|
112
|
+
candidates.push(pm2Dir);
|
|
113
|
+
const activeDir = resolveActiveServerLogDir(projectRoot);
|
|
114
|
+
if (activeDir)
|
|
115
|
+
candidates.push(activeDir);
|
|
116
|
+
return [...new Set(candidates.map((dir) => path.resolve(dir)))];
|
|
117
|
+
}
|
|
11
118
|
function getProjectServerLogDir(projectRoot) {
|
|
12
119
|
const preferred = path.join(projectRoot, '.reactpress', 'logs', 'server');
|
|
13
|
-
if (fs.existsSync(preferred)) {
|
|
14
|
-
return preferred;
|
|
15
|
-
}
|
|
16
120
|
const bundled = path.join(getServerDir(projectRoot), 'logs');
|
|
17
|
-
|
|
18
|
-
|
|
121
|
+
const pm2Dir = getPm2ServerLogDir(projectRoot);
|
|
122
|
+
const activeDir = resolveActiveServerLogDir(projectRoot);
|
|
123
|
+
const localCandidates = [preferred, bundled];
|
|
124
|
+
if (pm2Dir)
|
|
125
|
+
localCandidates.push(pm2Dir);
|
|
126
|
+
let bestLocal = preferred;
|
|
127
|
+
let bestLocalScore = -1;
|
|
128
|
+
for (const dir of [...new Set(localCandidates.map((d) => path.resolve(d)))]) {
|
|
129
|
+
const score = measureLogDir(dir);
|
|
130
|
+
if (score > bestLocalScore) {
|
|
131
|
+
bestLocalScore = score;
|
|
132
|
+
bestLocal = dir;
|
|
133
|
+
}
|
|
19
134
|
}
|
|
20
|
-
|
|
135
|
+
if (bestLocalScore > 0) {
|
|
136
|
+
return bestLocal;
|
|
137
|
+
}
|
|
138
|
+
if (activeDir && fs.existsSync(activeDir) && measureLogDir(activeDir) > 0) {
|
|
139
|
+
return activeDir;
|
|
140
|
+
}
|
|
141
|
+
return bestLocal;
|
|
21
142
|
}
|
|
22
143
|
function normalizeSource(source) {
|
|
23
144
|
const value = String(source || 'error').toLowerCase();
|
|
24
145
|
if (value === 'all')
|
|
25
146
|
return 'all';
|
|
147
|
+
if (value === 'process' || value === 'pm2')
|
|
148
|
+
return 'process';
|
|
26
149
|
if (LOG_SOURCES.includes(value))
|
|
27
150
|
return value;
|
|
28
151
|
return 'error';
|
|
29
152
|
}
|
|
30
|
-
function
|
|
153
|
+
function measureStructuredLogDir(logDir) {
|
|
154
|
+
if (!fs.existsSync(logDir))
|
|
155
|
+
return 0;
|
|
156
|
+
let total = 0;
|
|
157
|
+
for (const name of LOG_SOURCES) {
|
|
158
|
+
const dir = path.join(logDir, name);
|
|
159
|
+
if (!fs.existsSync(dir))
|
|
160
|
+
continue;
|
|
161
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
162
|
+
if (!entry.endsWith('.log'))
|
|
163
|
+
continue;
|
|
164
|
+
try {
|
|
165
|
+
total += fs.statSync(path.join(dir, entry)).size;
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
// ignore
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return total;
|
|
173
|
+
}
|
|
174
|
+
function resolveLogSource(projectRoot, sourceOption) {
|
|
175
|
+
if (sourceOption != null && String(sourceOption).trim() !== '') {
|
|
176
|
+
return normalizeSource(sourceOption);
|
|
177
|
+
}
|
|
178
|
+
const logDir = path.join(projectRoot, '.reactpress', 'logs', 'server');
|
|
179
|
+
if (measureStructuredLogDir(logDir) > 0) {
|
|
180
|
+
return 'error';
|
|
181
|
+
}
|
|
182
|
+
if (isPm2AppOnline(projectRoot, PM2_API_APP)) {
|
|
183
|
+
return 'process';
|
|
184
|
+
}
|
|
185
|
+
return 'error';
|
|
186
|
+
}
|
|
187
|
+
function listProcessLogFiles(projectRoot, logDir, source = 'process') {
|
|
188
|
+
const normalized = normalizeSource(source);
|
|
189
|
+
const names = normalized === 'all'
|
|
190
|
+
? PROCESS_LOG_SOURCES
|
|
191
|
+
: normalized === 'process'
|
|
192
|
+
? PROCESS_LOG_SOURCES
|
|
193
|
+
: PROCESS_LOG_SOURCES.includes(normalized)
|
|
194
|
+
? [normalized]
|
|
195
|
+
: [];
|
|
196
|
+
if (!names.length)
|
|
197
|
+
return [];
|
|
198
|
+
const pm2Paths = getPm2AppLogPaths(projectRoot, PM2_API_APP);
|
|
199
|
+
const pathBySource = {
|
|
200
|
+
'pm2-out': pm2Paths.out,
|
|
201
|
+
'pm2-error': pm2Paths.err,
|
|
202
|
+
};
|
|
203
|
+
const files = [];
|
|
204
|
+
for (const name of names) {
|
|
205
|
+
const localPath = path.join(logDir, PM2_LOG_FILES[name]);
|
|
206
|
+
const candidates = [];
|
|
207
|
+
if (fs.existsSync(localPath)) {
|
|
208
|
+
candidates.push(localPath);
|
|
209
|
+
}
|
|
210
|
+
const remotePath = pathBySource[name];
|
|
211
|
+
if (remotePath &&
|
|
212
|
+
path.resolve(remotePath) !== path.resolve(localPath) &&
|
|
213
|
+
fs.existsSync(remotePath)) {
|
|
214
|
+
candidates.push(remotePath);
|
|
215
|
+
}
|
|
216
|
+
const seen = new Set();
|
|
217
|
+
for (const fullPath of candidates) {
|
|
218
|
+
const resolved = path.resolve(fullPath);
|
|
219
|
+
if (seen.has(resolved))
|
|
220
|
+
continue;
|
|
221
|
+
seen.add(resolved);
|
|
222
|
+
let stat;
|
|
223
|
+
try {
|
|
224
|
+
stat = fs.statSync(resolved);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (!stat.isFile())
|
|
230
|
+
continue;
|
|
231
|
+
files.push({
|
|
232
|
+
source: name,
|
|
233
|
+
path: resolved,
|
|
234
|
+
name: path.basename(resolved),
|
|
235
|
+
mtimeMs: stat.mtimeMs,
|
|
236
|
+
size: stat.size,
|
|
237
|
+
});
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
files.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
242
|
+
return files;
|
|
243
|
+
}
|
|
244
|
+
function listLogFiles(logDir, source = 'error', projectRoot = null) {
|
|
31
245
|
const normalized = normalizeSource(source);
|
|
32
|
-
|
|
246
|
+
if (normalized === 'process' || PROCESS_LOG_SOURCES.includes(normalized)) {
|
|
247
|
+
return listProcessLogFiles(projectRoot || '', logDir, normalized);
|
|
248
|
+
}
|
|
249
|
+
const sources = normalized === 'all'
|
|
250
|
+
? [...PROCESS_LOG_SOURCES, ...LOG_SOURCES]
|
|
251
|
+
: [normalized];
|
|
33
252
|
const files = [];
|
|
253
|
+
if (normalized === 'all' && projectRoot) {
|
|
254
|
+
files.push(...listProcessLogFiles(projectRoot, logDir, 'process'));
|
|
255
|
+
}
|
|
34
256
|
for (const name of sources) {
|
|
257
|
+
if (PROCESS_LOG_SOURCES.includes(name))
|
|
258
|
+
continue;
|
|
35
259
|
const dir = path.join(logDir, name);
|
|
36
260
|
if (!fs.existsSync(dir))
|
|
37
261
|
continue;
|
|
@@ -104,9 +328,20 @@ function formatTimestamp(ms) {
|
|
|
104
328
|
return new Date(ms).toISOString().replace('T', ' ').slice(0, 19);
|
|
105
329
|
}
|
|
106
330
|
function printProcessHint(projectRoot) {
|
|
331
|
+
if (isPm2AppOnline(projectRoot, PM2_API_APP)) {
|
|
332
|
+
const pid = getPm2App(projectRoot, PM2_API_APP)?.pid;
|
|
333
|
+
console.log(` ${brand.dim(t('doctor.logs.pm2Supervised', { pid: pid ?? PM2_API_APP }))}`);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
107
336
|
const pid = readPid(projectRoot);
|
|
108
337
|
const pidFile = getPidFile(projectRoot);
|
|
109
338
|
if (!pid) {
|
|
339
|
+
const port = parseUrlPort(loadServerSiteUrl(projectRoot));
|
|
340
|
+
const activePid = port ? resolveListeningPid(port) : null;
|
|
341
|
+
if (activePid && isProcessRunning(activePid)) {
|
|
342
|
+
console.log(` ${brand.dim(t('doctor.logs.activePid', { pid: activePid, port }))}`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
110
345
|
console.log(` ${brand.dim(t('doctor.logs.noPid', { path: pidFile }))}`);
|
|
111
346
|
return;
|
|
112
347
|
}
|
|
@@ -115,11 +350,69 @@ function printProcessHint(projectRoot) {
|
|
|
115
350
|
? t('doctor.logs.pidRunning', { pid, path: pidFile })
|
|
116
351
|
: t('doctor.logs.pidStale', { pid, path: pidFile }))}`);
|
|
117
352
|
}
|
|
353
|
+
function pickPrimaryLogFile(files, source) {
|
|
354
|
+
if (!files.length)
|
|
355
|
+
return null;
|
|
356
|
+
if (source === 'process') {
|
|
357
|
+
return (files.find((file) => file.source === 'pm2-error') ||
|
|
358
|
+
files.find((file) => file.source === 'pm2-out') ||
|
|
359
|
+
files[0]);
|
|
360
|
+
}
|
|
361
|
+
const grouped = new Map();
|
|
362
|
+
for (const file of files) {
|
|
363
|
+
if (!grouped.has(file.source))
|
|
364
|
+
grouped.set(file.source, file);
|
|
365
|
+
}
|
|
366
|
+
const order = source === 'all' ? [...PROCESS_LOG_SOURCES, ...LOG_SOURCES] : [source];
|
|
367
|
+
for (const name of order) {
|
|
368
|
+
const file = grouped.get(name);
|
|
369
|
+
if (file)
|
|
370
|
+
return file;
|
|
371
|
+
}
|
|
372
|
+
return files[0];
|
|
373
|
+
}
|
|
374
|
+
async function runFollowLogs(projectRoot, { tail, source, logDir, files }) {
|
|
375
|
+
const usePm2Stream = source === 'process' && isPm2AppOnline(projectRoot, PM2_API_APP);
|
|
376
|
+
if (usePm2Stream) {
|
|
377
|
+
const bin = resolvePm2Bin(projectRoot);
|
|
378
|
+
if (!bin) {
|
|
379
|
+
console.error(` ${brand.warn(t('doctor.logs.pm2Unavailable'))}`);
|
|
380
|
+
return 1;
|
|
381
|
+
}
|
|
382
|
+
const { spawn } = require('child_process');
|
|
383
|
+
await new Promise((resolve) => {
|
|
384
|
+
const child = spawn(bin, ['logs', PM2_API_APP, '--lines', String(tail)], {
|
|
385
|
+
stdio: 'inherit',
|
|
386
|
+
});
|
|
387
|
+
child.on('exit', (code) => resolve(code ?? 0));
|
|
388
|
+
});
|
|
389
|
+
return 0;
|
|
390
|
+
}
|
|
391
|
+
const primary = pickPrimaryLogFile(files, source);
|
|
392
|
+
if (!primary) {
|
|
393
|
+
console.log(` ${brand.warn(t('doctor.logs.empty'))}`);
|
|
394
|
+
console.log('');
|
|
395
|
+
return 1;
|
|
396
|
+
}
|
|
397
|
+
if (process.platform === 'win32') {
|
|
398
|
+
console.log(` ${brand.dim(t('doctor.logs.followWindows', { path: primary.path }))}`);
|
|
399
|
+
console.log('');
|
|
400
|
+
return 0;
|
|
401
|
+
}
|
|
402
|
+
const { spawn } = require('child_process');
|
|
403
|
+
await new Promise((resolve) => {
|
|
404
|
+
const child = spawn('tail', ['-n', String(tail), '-f', primary.path], {
|
|
405
|
+
stdio: 'inherit',
|
|
406
|
+
});
|
|
407
|
+
child.on('exit', (code) => resolve(code ?? 0));
|
|
408
|
+
});
|
|
409
|
+
return 0;
|
|
410
|
+
}
|
|
118
411
|
async function runDoctorLogs(projectRoot, options = {}) {
|
|
119
412
|
const tail = Math.max(1, Math.min(parseInt(String(options.tail ?? '50'), 10) || 50, 5000));
|
|
120
|
-
const source =
|
|
413
|
+
const source = resolveLogSource(projectRoot, options.source);
|
|
121
414
|
const logDir = getProjectServerLogDir(projectRoot);
|
|
122
|
-
const files = listLogFiles(logDir, source);
|
|
415
|
+
const files = listLogFiles(logDir, source, projectRoot);
|
|
123
416
|
const w = 56;
|
|
124
417
|
console.log('');
|
|
125
418
|
console.log(` ${gradientText(t('logs.title'), [palette.primary, palette.accent], { bold: true })} ${brand.dim(t('logs.subtitle'))}`);
|
|
@@ -127,6 +420,9 @@ async function runDoctorLogs(projectRoot, options = {}) {
|
|
|
127
420
|
console.log(` ${brand.dim(t('doctor.logs.dir', { path: logDir }))}`);
|
|
128
421
|
printProcessHint(projectRoot);
|
|
129
422
|
console.log(` ${divider(w)}`);
|
|
423
|
+
if (options.follow) {
|
|
424
|
+
return runFollowLogs(projectRoot, { tail, source, logDir, files });
|
|
425
|
+
}
|
|
130
426
|
if (options.list) {
|
|
131
427
|
if (!files.length) {
|
|
132
428
|
console.log(` ${brand.warn(t('doctor.logs.empty'))}`);
|
|
@@ -153,8 +449,13 @@ async function runDoctorLogs(projectRoot, options = {}) {
|
|
|
153
449
|
if (!grouped.has(file.source))
|
|
154
450
|
grouped.set(file.source, file);
|
|
155
451
|
}
|
|
452
|
+
const displayOrder = source === 'all'
|
|
453
|
+
? [...PROCESS_LOG_SOURCES, ...LOG_SOURCES]
|
|
454
|
+
: source === 'process'
|
|
455
|
+
? PROCESS_LOG_SOURCES
|
|
456
|
+
: [source];
|
|
156
457
|
let printed = 0;
|
|
157
|
-
for (const name of
|
|
458
|
+
for (const name of displayOrder) {
|
|
158
459
|
const file = grouped.get(name);
|
|
159
460
|
if (!file)
|
|
160
461
|
continue;
|
|
@@ -171,7 +472,7 @@ async function runDoctorLogs(projectRoot, options = {}) {
|
|
|
171
472
|
continue;
|
|
172
473
|
}
|
|
173
474
|
for (const line of lines) {
|
|
174
|
-
const isError = /\[ERROR\]/i.test(line) || name === 'error';
|
|
475
|
+
const isError = /\[ERROR\]/i.test(line) || name === 'error' || name === 'pm2-error';
|
|
175
476
|
console.log(` ${isError ? brand.warn(line) : brand.dim(line)}`);
|
|
176
477
|
}
|
|
177
478
|
printed += lines.length;
|
|
@@ -188,7 +489,12 @@ async function runDoctorLogs(projectRoot, options = {}) {
|
|
|
188
489
|
}
|
|
189
490
|
module.exports = {
|
|
190
491
|
LOG_SOURCES,
|
|
492
|
+
PROCESS_LOG_SOURCES,
|
|
191
493
|
getProjectServerLogDir,
|
|
494
|
+
collectLogDirCandidates,
|
|
495
|
+
measureLogDir,
|
|
496
|
+
resolveActiveServerLogDir,
|
|
497
|
+
resolveLogSource,
|
|
192
498
|
listLogFiles,
|
|
193
499
|
readTailLines,
|
|
194
500
|
filterLines,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fecommunity/reactpress",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.18",
|
|
4
4
|
"description": "ReactPress CLI — publishing platform for modern developers. CMS, Admin, API, and Next.js themes in one command. Self-hosted, MIT. `npx @fecommunity/reactpress init`",
|
|
5
5
|
"author": "fecommunity",
|
|
6
6
|
"license": "MIT",
|
|
@@ -196,6 +196,23 @@ function startWithNode() {
|
|
|
196
196
|
console.log(`[ReactPress Server] Using existing REACTPRESS_ORIGINAL_CWD: ${process.env.REACTPRESS_ORIGINAL_CWD}`);
|
|
197
197
|
}
|
|
198
198
|
|
|
199
|
+
const siteRoot = process.env.REACTPRESS_ORIGINAL_CWD;
|
|
200
|
+
for (const localSiteModule of [
|
|
201
|
+
path.join(serverDir, '..', 'cli', 'out', 'core', 'services', 'local-site.js'),
|
|
202
|
+
path.join(serverDir, '..', 'out', 'core', 'services', 'local-site.js'),
|
|
203
|
+
]) {
|
|
204
|
+
if (!siteRoot || !fs.existsSync(localSiteModule)) continue;
|
|
205
|
+
try {
|
|
206
|
+
const { ensureBundledPlugins } = require(localSiteModule);
|
|
207
|
+
if (ensureBundledPlugins(siteRoot)) {
|
|
208
|
+
console.log('[ReactPress Server] Seeded bundled plugins into project');
|
|
209
|
+
}
|
|
210
|
+
} catch {
|
|
211
|
+
// ignore — plugins seeding is best-effort
|
|
212
|
+
}
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
199
216
|
// Change to the server directory
|
|
200
217
|
process.chdir(serverDir);
|
|
201
218
|
|
package/server/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fecommunity/reactpress-server",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.18",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@fecommunity/reactpress-server",
|
|
9
|
-
"version": "4.0.0-beta.
|
|
9
|
+
"version": "4.0.0-beta.18",
|
|
10
10
|
"deprecated": "Bundled in @fecommunity/reactpress CLI from v3.1. Run `npx @fecommunity/reactpress init` for new projects.",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"dependencies": {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
},
|
|
92
92
|
"../toolkit": {
|
|
93
93
|
"name": "@fecommunity/reactpress-toolkit",
|
|
94
|
-
"version": "4.0.0-beta.
|
|
94
|
+
"version": "4.0.0-beta.18",
|
|
95
95
|
"dependencies": {
|
|
96
96
|
"ajv": "^8.17.1",
|
|
97
97
|
"axios": "^1.12.2",
|
package/server/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fecommunity/reactpress-server",
|
|
3
|
-
"version": "4.0.0-beta.
|
|
3
|
+
"version": "4.0.0-beta.18",
|
|
4
4
|
"deprecated": "Bundled in @fecommunity/reactpress CLI from v3.1. Run `npx @fecommunity/reactpress init` for new projects.",
|
|
5
5
|
"description": "ReactPress NestJS REST API (deprecated). Bundled in @fecommunity/reactpress CLI from v3.1. Headless CMS for articles, media, plugins, and themes",
|
|
6
6
|
"author": "fecommunity",
|