@fecommunity/reactpress 4.0.0-beta.13 → 4.0.0-beta.15

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.
@@ -10,9 +10,12 @@ const { loadServerSiteUrl, waitForHttp } = require('./http');
10
10
  const { getServerBin, getServerDir, isUsingMonorepoServer, canStartLocalApi, getPidFile, } = require('./paths');
11
11
  const net = require('net');
12
12
  const { readPid, isProcessRunning, clearPidFile, writePid } = require('./process');
13
+ const { stopClient } = require('./client-lifecycle');
13
14
  const { ensureOriginalCwd } = require('./root');
14
15
  const { t } = require('./i18n');
16
+ const { getPm2ServerMemoryRestart } = require('./prod-memory');
15
17
  const { ensureBundledServerDeps } = require('./server-bundle');
18
+ const { PM2_API_APP, PM2_CLIENT_APP, isPm2RuntimeAvailable, isPm2AppOnline, getPm2AppPid, runPm2, stopPm2Apps, } = require('./pm2-runtime');
16
19
  function parseServerPort(projectRoot) {
17
20
  try {
18
21
  const url = new URL(loadServerSiteUrl(projectRoot));
@@ -67,51 +70,45 @@ function stopApi(projectRoot) {
67
70
  }
68
71
  clearPidFile(projectRoot);
69
72
  }
70
- async function startApi(projectRoot, { wait = true } = {}) {
71
- if (!(await ensureConfig(projectRoot))) {
72
- return 1;
73
- }
74
- const existing = readPid(projectRoot);
75
- if (existing && isProcessRunning(existing)) {
76
- console.log(t('lifecycle.apiAlreadyRunning', { pid: existing }));
77
- return 0;
78
- }
79
- clearPidFile(projectRoot);
80
- if (!canStartLocalApi(projectRoot)) {
81
- console.error(t('lifecycle.noServerAvailable'));
82
- return 1;
83
- }
84
- if (!isUsingMonorepoServer(projectRoot)) {
85
- const bundled = await ensureBundledServerDeps(projectRoot);
86
- if (!bundled.ok) {
87
- console.error(bundled.message || t('bundle.serverBundle.notBuilt'));
88
- return 1;
89
- }
90
- }
91
- if (isUsingMonorepoServer(projectRoot)) {
92
- console.log(t('lifecycle.startingLocalApi'));
93
- }
94
- else {
95
- console.log(t('lifecycle.startingBundledApi'));
96
- }
73
+ function stopAll(projectRoot) {
74
+ stopPm2Apps(projectRoot, [PM2_API_APP, PM2_CLIENT_APP]);
75
+ stopClient(projectRoot);
76
+ stopApi(projectRoot);
77
+ }
78
+ async function startApiWithPm2(projectRoot, { wait = true } = {}) {
79
+ const serverBin = getServerBin(projectRoot);
80
+ const serverDir = getServerDir(projectRoot);
97
81
  const serverLogDir = path.join(projectRoot, '.reactpress', 'logs', 'server');
98
82
  fs.mkdirSync(serverLogDir, { recursive: true });
99
- const child = spawn(process.execPath, [getServerBin(projectRoot)], {
100
- cwd: getServerDir(projectRoot),
101
- detached: true,
102
- stdio: 'ignore',
83
+ const started = runPm2(projectRoot, [
84
+ 'start',
85
+ serverBin,
86
+ '--name',
87
+ PM2_API_APP,
88
+ '--cwd',
89
+ serverDir,
90
+ '--max-memory-restart',
91
+ getPm2ServerMemoryRestart(),
92
+ '--update-env',
93
+ '-f',
94
+ ], {
103
95
  env: {
104
- ...process.env,
105
96
  REACTPRESS_ORIGINAL_CWD: projectRoot,
106
97
  REACTPRESS_SERVER_LOG_DIR: serverLogDir,
107
98
  },
99
+ stdio: 'ignore',
108
100
  });
109
- child.unref();
110
- writePid(projectRoot, child.pid);
111
- console.log(t('lifecycle.apiStartedBg', { pid: child.pid }));
112
- if (!wait) {
113
- return 0;
101
+ if (!started.ok) {
102
+ console.warn(t('lifecycle.processSupervisorFallback'));
103
+ return startApiDetached(projectRoot, { wait });
114
104
  }
105
+ const pid = getPm2AppPid(projectRoot, PM2_API_APP);
106
+ console.log(t('lifecycle.apiStartedBg', { pid: pid ?? PM2_API_APP }));
107
+ if (!wait)
108
+ return 0;
109
+ return waitForApiReady(projectRoot, pid);
110
+ }
111
+ async function waitForApiReady(projectRoot, pid) {
115
112
  const serverUrl = loadServerSiteUrl(projectRoot);
116
113
  const spinner = ora({
117
114
  text: t('dev.waitingApi', { url: serverUrl }),
@@ -121,8 +118,8 @@ async function startApi(projectRoot, { wait = true } = {}) {
121
118
  const deadline = Date.now() + 120_000;
122
119
  let ready = false;
123
120
  while (Date.now() < deadline) {
124
- if (!isProcessRunning(child.pid)) {
125
- spinner.fail(t('lifecycle.apiExitedEarly', { pid: child.pid }));
121
+ if (pid && !isProcessRunning(pid) && !isPm2AppOnline(projectRoot, PM2_API_APP)) {
122
+ spinner.fail(t('lifecycle.apiExitedEarly', { pid }));
126
123
  return 1;
127
124
  }
128
125
  if (await require('./http').isHttpResponding(serverUrl)) {
@@ -138,6 +135,63 @@ async function startApi(projectRoot, { wait = true } = {}) {
138
135
  spinner.succeed(t('lifecycle.apiReady', { url: serverUrl }));
139
136
  return 0;
140
137
  }
138
+ async function startApiDetached(projectRoot, { wait = true } = {}) {
139
+ const serverLogDir = path.join(projectRoot, '.reactpress', 'logs', 'server');
140
+ fs.mkdirSync(serverLogDir, { recursive: true });
141
+ const child = spawn(process.execPath, [getServerBin(projectRoot)], {
142
+ cwd: getServerDir(projectRoot),
143
+ detached: true,
144
+ stdio: 'ignore',
145
+ env: {
146
+ ...process.env,
147
+ REACTPRESS_ORIGINAL_CWD: projectRoot,
148
+ REACTPRESS_SERVER_LOG_DIR: serverLogDir,
149
+ },
150
+ });
151
+ child.unref();
152
+ writePid(projectRoot, child.pid);
153
+ console.log(t('lifecycle.apiStartedBg', { pid: child.pid }));
154
+ if (!wait)
155
+ return 0;
156
+ return waitForApiReady(projectRoot, child.pid);
157
+ }
158
+ async function startApi(projectRoot, { wait = true } = {}) {
159
+ if (!(await ensureConfig(projectRoot))) {
160
+ return 1;
161
+ }
162
+ if (isPm2AppOnline(projectRoot, PM2_API_APP)) {
163
+ const pid = getPm2AppPid(projectRoot, PM2_API_APP);
164
+ console.log(t('lifecycle.apiAlreadyRunning', { pid: pid ?? PM2_API_APP }));
165
+ return 0;
166
+ }
167
+ const existing = readPid(projectRoot);
168
+ if (existing && isProcessRunning(existing)) {
169
+ console.log(t('lifecycle.apiAlreadyRunning', { pid: existing }));
170
+ return 0;
171
+ }
172
+ clearPidFile(projectRoot);
173
+ if (!canStartLocalApi(projectRoot)) {
174
+ console.error(t('lifecycle.noServerAvailable'));
175
+ return 1;
176
+ }
177
+ if (!isUsingMonorepoServer(projectRoot)) {
178
+ const bundled = await ensureBundledServerDeps(projectRoot);
179
+ if (!bundled.ok) {
180
+ console.error(bundled.message || t('bundle.serverBundle.notBuilt'));
181
+ return 1;
182
+ }
183
+ }
184
+ if (isUsingMonorepoServer(projectRoot)) {
185
+ console.log(t('lifecycle.startingLocalApi'));
186
+ }
187
+ else {
188
+ console.log(t('lifecycle.startingBundledApi'));
189
+ }
190
+ if (isPm2RuntimeAvailable(projectRoot)) {
191
+ return startApiWithPm2(projectRoot, { wait });
192
+ }
193
+ return startApiDetached(projectRoot, { wait });
194
+ }
141
195
  async function statusApi(projectRoot) {
142
196
  const pid = readPid(projectRoot);
143
197
  const serverUrl = loadServerSiteUrl(projectRoot);
@@ -171,10 +225,10 @@ async function runLifecycleCommand(command, projectRoot = ensureOriginalCwd()) {
171
225
  case 'start:bg':
172
226
  return startApi(projectRoot, { wait: false });
173
227
  case 'stop':
174
- stopApi(projectRoot);
228
+ stopAll(projectRoot);
175
229
  return 0;
176
230
  case 'restart':
177
- stopApi(projectRoot);
231
+ stopAll(projectRoot);
178
232
  await waitForPortFree(parseServerPort(projectRoot));
179
233
  await new Promise((r) => setTimeout(r, 400));
180
234
  return startApi(projectRoot, { wait: true });
@@ -188,6 +242,7 @@ async function runLifecycleCommand(command, projectRoot = ensureOriginalCwd()) {
188
242
  module.exports = {
189
243
  startApi,
190
244
  stopApi,
245
+ stopAll,
191
246
  statusApi,
192
247
  runLifecycleCommand,
193
248
  };
package/out/lib/paths.js CHANGED
@@ -95,6 +95,9 @@ function getClientBin(projectRoot) {
95
95
  function getPidFile(projectRoot) {
96
96
  return path.join(resolveProjectRoot(projectRoot), '.reactpress', 'server.pid');
97
97
  }
98
+ function getClientPidFile(projectRoot) {
99
+ return path.join(resolveProjectRoot(projectRoot), '.reactpress', 'client.pid');
100
+ }
98
101
  module.exports = {
99
102
  getMonorepoRoot,
100
103
  resolveProjectRoot,
@@ -113,5 +116,6 @@ module.exports = {
113
116
  getThemeBin,
114
117
  getClientBin,
115
118
  getPidFile,
119
+ getClientPidFile,
116
120
  };
117
121
  //# sourceMappingURL=paths.js.map
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // @ts-nocheck
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { spawnSync } = require('child_process');
7
+ const { getBundledServerDir, getServerDir } = require('./paths');
8
+ /** Internal PM2 app names — not exposed in user-facing CLI copy. */
9
+ const PM2_API_APP = 'reactpress-api';
10
+ const PM2_CLIENT_APP = 'reactpress-client';
11
+ function isPm2Disabled() {
12
+ return process.env.REACTPRESS_DISABLE_PM2 === '1';
13
+ }
14
+ function resolvePm2Bin(projectRoot) {
15
+ if (isPm2Disabled())
16
+ return null;
17
+ const candidates = [
18
+ path.join(getServerDir(projectRoot), 'node_modules', '.bin', 'pm2'),
19
+ path.join(getBundledServerDir(), 'node_modules', '.bin', 'pm2'),
20
+ ];
21
+ for (const candidate of candidates) {
22
+ if (fs.existsSync(candidate))
23
+ return candidate;
24
+ }
25
+ const probe = spawnSync('pm2', ['--version'], { stdio: 'ignore', shell: process.platform === 'win32' });
26
+ return probe.status === 0 ? 'pm2' : null;
27
+ }
28
+ function isPm2RuntimeAvailable(projectRoot) {
29
+ return Boolean(resolvePm2Bin(projectRoot));
30
+ }
31
+ function runPm2(projectRoot, args, options = {}) {
32
+ const bin = resolvePm2Bin(projectRoot);
33
+ if (!bin) {
34
+ return { ok: false, error: 'PM2_UNAVAILABLE' };
35
+ }
36
+ const result = spawnSync(bin, args, {
37
+ cwd: options.cwd,
38
+ env: { ...process.env, ...options.env },
39
+ encoding: 'utf8',
40
+ stdio: options.stdio ?? 'pipe',
41
+ shell: options.shell ?? false,
42
+ });
43
+ if (result.status !== 0) {
44
+ const message = [result.stderr, result.stdout].filter(Boolean).join('\n').trim();
45
+ return { ok: false, error: message || `pm2 exited ${result.status}` };
46
+ }
47
+ return { ok: true, stdout: result.stdout ?? '' };
48
+ }
49
+ function readPm2Apps(projectRoot) {
50
+ const result = runPm2(projectRoot, ['jlist'], { stdio: 'pipe' });
51
+ if (!result.ok)
52
+ return [];
53
+ try {
54
+ const apps = JSON.parse(result.stdout);
55
+ return Array.isArray(apps) ? apps : [];
56
+ }
57
+ catch {
58
+ return [];
59
+ }
60
+ }
61
+ function getPm2App(projectRoot, name) {
62
+ return readPm2Apps(projectRoot).find((app) => app?.name === name) ?? null;
63
+ }
64
+ function isPm2AppOnline(projectRoot, name) {
65
+ const app = getPm2App(projectRoot, name);
66
+ return app?.pm2_env?.status === 'online';
67
+ }
68
+ function getPm2AppPid(projectRoot, name) {
69
+ const app = getPm2App(projectRoot, name);
70
+ return typeof app?.pid === 'number' ? app.pid : null;
71
+ }
72
+ function stopPm2Apps(projectRoot, names) {
73
+ const bin = resolvePm2Bin(projectRoot);
74
+ if (!bin)
75
+ return false;
76
+ const targets = names.filter((name) => getPm2App(projectRoot, name));
77
+ if (!targets.length)
78
+ return false;
79
+ runPm2(projectRoot, ['delete', ...targets], { stdio: 'ignore' });
80
+ return true;
81
+ }
82
+ module.exports = {
83
+ PM2_API_APP,
84
+ PM2_CLIENT_APP,
85
+ isPm2Disabled,
86
+ resolvePm2Bin,
87
+ isPm2RuntimeAvailable,
88
+ runPm2,
89
+ readPm2Apps,
90
+ getPm2App,
91
+ isPm2AppOnline,
92
+ getPm2AppPid,
93
+ stopPm2Apps,
94
+ };
95
+ //# sourceMappingURL=pm2-runtime.js.map
@@ -3,9 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  // @ts-nocheck
4
4
  const fs = require('fs');
5
5
  const path = require('path');
6
- const { getPidFile } = require('./paths');
7
- function readPid(projectRoot) {
8
- const pidFile = getPidFile(projectRoot);
6
+ const { getPidFile, getClientPidFile } = require('./paths');
7
+ function readPidFromFile(pidFile) {
9
8
  try {
10
9
  const raw = fs.readFileSync(pidFile, 'utf8').trim();
11
10
  const pid = Number.parseInt(raw, 10);
@@ -15,6 +14,12 @@ function readPid(projectRoot) {
15
14
  return null;
16
15
  }
17
16
  }
17
+ function readPid(projectRoot) {
18
+ return readPidFromFile(getPidFile(projectRoot));
19
+ }
20
+ function readClientPid(projectRoot) {
21
+ return readPidFromFile(getClientPidFile(projectRoot));
22
+ }
18
23
  function isProcessRunning(pid) {
19
24
  if (!pid)
20
25
  return false;
@@ -32,16 +37,31 @@ function clearPidFile(projectRoot) {
32
37
  fs.unlinkSync(pidFile);
33
38
  }
34
39
  }
35
- function writePid(projectRoot, pid) {
36
- const pidFile = getPidFile(projectRoot);
40
+ function clearClientPidFile(projectRoot) {
41
+ const pidFile = getClientPidFile(projectRoot);
42
+ if (fs.existsSync(pidFile)) {
43
+ fs.unlinkSync(pidFile);
44
+ }
45
+ }
46
+ function writePidToFile(pidFile, pid) {
37
47
  fs.mkdirSync(path.dirname(pidFile), { recursive: true });
38
48
  fs.writeFileSync(pidFile, String(pid));
39
49
  }
50
+ function writePid(projectRoot, pid) {
51
+ writePidToFile(getPidFile(projectRoot), pid);
52
+ }
53
+ function writeClientPid(projectRoot, pid) {
54
+ writePidToFile(getClientPidFile(projectRoot), pid);
55
+ }
40
56
  module.exports = {
41
57
  readPid,
58
+ readClientPid,
42
59
  isProcessRunning,
43
60
  clearPidFile,
61
+ clearClientPidFile,
44
62
  writePid,
63
+ writeClientPid,
45
64
  getPidFile,
65
+ getClientPidFile,
46
66
  };
47
67
  //# sourceMappingURL=process.js.map
@@ -122,7 +122,7 @@ async function runDoctorLogs(projectRoot, options = {}) {
122
122
  const files = listLogFiles(logDir, source);
123
123
  const w = 56;
124
124
  console.log('');
125
- console.log(` ${gradientText(t('doctor.logs.title'), [palette.primary, palette.accent], { bold: true })} ${brand.dim(t('doctor.logs.subtitle'))}`);
125
+ console.log(` ${gradientText(t('logs.title'), [palette.primary, palette.accent], { bold: true })} ${brand.dim(t('logs.subtitle'))}`);
126
126
  console.log(` ${brand.dim(t('doctor.project', { path: projectRoot }))}`);
127
127
  console.log(` ${brand.dim(t('doctor.logs.dir', { path: logDir }))}`);
128
128
  printProcessHint(projectRoot);
@@ -16,12 +16,12 @@ const CORE_PUBLISH_PACKAGES = [
16
16
  {
17
17
  name: '@fecommunity/reactpress-toolkit',
18
18
  path: 'toolkit',
19
- description: 'API client and utilities toolkit',
19
+ description: 'Official TypeScript SDK — API clients, theme SSR, plugin hooks',
20
20
  },
21
21
  {
22
22
  name: '@fecommunity/reactpress-web',
23
23
  path: 'web',
24
- description: 'Admin SPA static assets and Node static server helpers',
24
+ description: 'Official Admin SPA — WordPress-style writing UI',
25
25
  },
26
26
  {
27
27
  name: '@fecommunity/reactpress-server',
@@ -32,7 +32,7 @@ const CORE_PUBLISH_PACKAGES = [
32
32
  {
33
33
  name: '@fecommunity/reactpress',
34
34
  path: 'cli',
35
- description: t('publish.pkg.main'),
35
+ description: 'Publishing platform CLI — CMS, Admin, API, themes in one command',
36
36
  },
37
37
  ];
38
38
  function getWorkspaceRoot() {
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // @ts-nocheck
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const PATCH_MARKER = '.reactpress-api-proxy-patched';
7
+ const ENCODING_PATCH_MARKER = 'reactpress-api-proxy-encoding';
8
+ const ROUTE_REL = path.join('app', 'api', '[[...path]]', 'route.ts');
9
+ const PROXY_REL = path.join('lib', 'mock-api', 'proxy.ts');
10
+ /** Buggy pattern: body read before mock/proxy branch (breaks upstream proxy). */
11
+ const BUGGY_ROUTE_BLOCK = ` const keyword = new URL(request.url).searchParams.get('keyword')?.trim() ?? ''
12
+ const body = await readJsonBody(request)
13
+
14
+ let response: Response
15
+ if (isMockApiEnabled()) {
16
+ const mockResponse = matchMockRoute(request.method, path, { keyword, body })`;
17
+ const FIXED_ROUTE_BLOCK = ` const keyword = new URL(request.url).searchParams.get('keyword')?.trim() ?? ''
18
+
19
+ let response: Response
20
+ if (isMockApiEnabled()) {
21
+ const body = await readJsonBody(request)
22
+ const mockResponse = matchMockRoute(request.method, path, { keyword, body })`;
23
+ const BUGGY_PROXY_BLOCK = ` return new Response(upstream.body, {
24
+ status: upstream.status,
25
+ headers: upstream.headers,
26
+ })`;
27
+ const FIXED_PROXY_BLOCK = ` const responseHeaders = new Headers(upstream.headers)
28
+ // ${ENCODING_PATCH_MARKER}: Node fetch decompresses bodies; strip stale encoding headers.
29
+ responseHeaders.delete('content-encoding')
30
+ responseHeaders.delete('content-length')
31
+ responseHeaders.delete('transfer-encoding')
32
+
33
+ return new Response(upstream.body, {
34
+ status: upstream.status,
35
+ headers: responseHeaders,
36
+ })`;
37
+ function resolveApiRoutePath(themeDir) {
38
+ if (!themeDir)
39
+ return null;
40
+ const routePath = path.join(themeDir, ROUTE_REL);
41
+ return fs.existsSync(routePath) ? routePath : null;
42
+ }
43
+ function resolveApiProxyPath(themeDir) {
44
+ if (!themeDir)
45
+ return null;
46
+ const proxyPath = path.join(themeDir, PROXY_REL);
47
+ return fs.existsSync(proxyPath) ? proxyPath : null;
48
+ }
49
+ function touchPatchMarker(themeDir) {
50
+ fs.writeFileSync(path.join(themeDir, PATCH_MARKER), `${new Date().toISOString()}\n`, 'utf8');
51
+ }
52
+ function patchThemeApiRouteBodyRead(themeDir) {
53
+ const routePath = resolveApiRoutePath(themeDir);
54
+ if (!routePath)
55
+ return false;
56
+ let src = fs.readFileSync(routePath, 'utf8');
57
+ if (src.includes(FIXED_ROUTE_BLOCK) || !src.includes(BUGGY_ROUTE_BLOCK)) {
58
+ return false;
59
+ }
60
+ src = src.replace(BUGGY_ROUTE_BLOCK, FIXED_ROUTE_BLOCK);
61
+ fs.writeFileSync(routePath, src, 'utf8');
62
+ return true;
63
+ }
64
+ /**
65
+ * Node fetch() auto-decompresses upstream bodies but leaves Content-Encoding on the Response.
66
+ * Browsers then fail with ERR_CONTENT_DECODING_FAILED (HTTP 200).
67
+ */
68
+ function patchThemeApiProxyEncodingHeaders(themeDir) {
69
+ const proxyPath = resolveApiProxyPath(themeDir);
70
+ if (!proxyPath)
71
+ return false;
72
+ let src = fs.readFileSync(proxyPath, 'utf8');
73
+ if (src.includes(ENCODING_PATCH_MARKER) || !src.includes(BUGGY_PROXY_BLOCK)) {
74
+ return false;
75
+ }
76
+ src = src.replace(BUGGY_PROXY_BLOCK, FIXED_PROXY_BLOCK);
77
+ fs.writeFileSync(proxyPath, src, 'utf8');
78
+ return true;
79
+ }
80
+ /**
81
+ * App Router themes proxy /api via app/api/[[...path]]/route.ts.
82
+ * Patches known theme-starter issues before production build.
83
+ */
84
+ function patchThemeApiProxyRoute(themeDir) {
85
+ const routePatched = patchThemeApiRouteBodyRead(themeDir);
86
+ const proxyPatched = patchThemeApiProxyEncodingHeaders(themeDir);
87
+ if (routePatched || proxyPatched) {
88
+ touchPatchMarker(themeDir);
89
+ }
90
+ return routePatched || proxyPatched;
91
+ }
92
+ module.exports = {
93
+ PATCH_MARKER,
94
+ ENCODING_PATCH_MARKER,
95
+ ROUTE_REL,
96
+ PROXY_REL,
97
+ patchThemeApiProxyRoute,
98
+ patchThemeApiRouteBodyRead,
99
+ patchThemeApiProxyEncodingHeaders,
100
+ resolveApiRoutePath,
101
+ resolveApiProxyPath,
102
+ };
103
+ //# sourceMappingURL=theme-api-proxy-patch.js.map
@@ -9,6 +9,7 @@ const { spawnSync } = require('child_process');
9
9
  const { upsertNpmThemeLock } = require('./theme-lock');
10
10
  const { buildThemeEnvOverrides, syncThemeEnvFromProject } = require('./theme-env');
11
11
  const { ensurePreviewFrameAllowed } = require('./theme-preview-frame');
12
+ const { patchThemeApiProxyRoute } = require('./theme-api-proxy-patch');
12
13
  const { resolveCatalogInstallSpec } = require('./theme-registry');
13
14
  const THEME_ID_RE = /^[a-z0-9][a-z0-9-]*$/i;
14
15
  const THEME_RUNTIME_REL = path.join('.reactpress', 'runtime');
@@ -332,6 +333,7 @@ async function installThemeFromNpm(projectRoot, spec, options = {}) {
332
333
  syncThemeEnvFromProject(root, targetDir);
333
334
  }
334
335
  ensurePreviewFrameAllowed(targetDir);
336
+ patchThemeApiProxyRoute(targetDir);
335
337
  const npmSpec = parsed.kind === 'npm' ? parsed.spec : resolvedSpec;
336
338
  upsertNpmThemeLock(root, themeId, {
337
339
  spec: npmSpec,
package/package.json CHANGED
@@ -1,9 +1,13 @@
1
1
  {
2
2
  "name": "@fecommunity/reactpress",
3
- "version": "4.0.0-beta.13",
4
- "description": "ReactPress 4 — zero-dependency publishing platform. Commands: reactpress init, reactpress doctor",
3
+ "version": "4.0.0-beta.15",
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",
7
+ "homepage": "https://reactpress-docs.vercel.app/",
8
+ "bugs": {
9
+ "url": "https://github.com/fecommunity/reactpress/issues"
10
+ },
7
11
  "repository": {
8
12
  "type": "git",
9
13
  "url": "git+https://github.com/fecommunity/reactpress.git",
@@ -13,7 +17,18 @@
13
17
  "reactpress",
14
18
  "cms",
15
19
  "blog",
16
- "cli"
20
+ "blog-platform",
21
+ "cli",
22
+ "headless-cms",
23
+ "publishing-platform",
24
+ "wordpress-alternative",
25
+ "self-hosted",
26
+ "sqlite",
27
+ "nestjs",
28
+ "nextjs",
29
+ "content-management",
30
+ "typescript",
31
+ "open-source"
17
32
  ],
18
33
  "engines": {
19
34
  "node": ">=18"
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@fecommunity/reactpress-server",
3
- "version": "4.0.0-beta.13",
3
+ "version": "4.0.0-beta.15",
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.13",
10
- "deprecated": "Use the built-in API in @fecommunity/reactpress (CLI). Standalone server publishing stopped from 3.1.",
9
+ "version": "4.0.0-beta.15",
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": {
13
13
  "@fecommunity/reactpress-toolkit": "file:../toolkit",
@@ -86,12 +86,12 @@
86
86
  "typescript": "~4.9.5"
87
87
  },
88
88
  "engines": {
89
- "node": ">=16.5.0"
89
+ "node": ">=18"
90
90
  }
91
91
  },
92
92
  "../toolkit": {
93
93
  "name": "@fecommunity/reactpress-toolkit",
94
- "version": "4.0.0-beta.13",
94
+ "version": "4.0.0-beta.15",
95
95
  "dependencies": {
96
96
  "ajv": "^8.17.1",
97
97
  "axios": "^1.12.2",
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "@fecommunity/reactpress-server",
3
- "version": "4.0.0-beta.13",
4
- "deprecated": "Use the built-in API in @fecommunity/reactpress (CLI). Standalone server publishing stopped from 3.1.",
5
- "description": "ReactPress NestJS CMS API — headless REST for the publishing platform (MySQL or SQLite)",
3
+ "version": "4.0.0-beta.15",
4
+ "deprecated": "Bundled in @fecommunity/reactpress CLI from v3.1. Run `npx @fecommunity/reactpress init` for new projects.",
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",
7
7
  "license": "MIT",
8
+ "homepage": "https://reactpress-docs.vercel.app/",
9
+ "bugs": {
10
+ "url": "https://github.com/fecommunity/reactpress/issues"
11
+ },
8
12
  "main": "dist/main.js",
9
- "types": "dist/index.d.ts",
10
13
  "bin": {
11
14
  "reactpress-server": "./bin/reactpress-server.js"
12
15
  },
@@ -19,20 +22,28 @@
19
22
  ],
20
23
  "keywords": [
21
24
  "reactpress",
22
- "server",
23
25
  "nestjs",
26
+ "headless-cms",
24
27
  "cms",
25
28
  "blog",
29
+ "rest-api",
26
30
  "api",
27
- "typescript"
31
+ "openapi",
32
+ "swagger",
33
+ "typescript",
34
+ "self-hosted"
28
35
  ],
29
36
  "repository": {
30
37
  "type": "git",
31
38
  "url": "git+https://github.com/fecommunity/reactpress.git",
32
39
  "directory": "server"
33
40
  },
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "registry": "https://registry.npmjs.org/"
44
+ },
34
45
  "engines": {
35
- "node": ">=16.5.0"
46
+ "node": ">=18"
36
47
  },
37
48
  "scripts": {
38
49
  "clean": "rimraf dist",
@@ -41,9 +41,12 @@ function applyThemeWebpackResolve(config, appDir) {
41
41
  * Pass overrides to extend (your `webpack` runs after the supports-color alias).
42
42
  */
43
43
  function createReactPressNextConfig(overrides = {}) {
44
- const { webpack: userWebpack, eslint, transpilePackages, ...rest } = overrides;
44
+ const { webpack: userWebpack, eslint, transpilePackages, compress, ...rest } = overrides;
45
45
  return {
46
46
  reactStrictMode: true,
47
+ // Themes rewrite `/api` to Nest. Next's compress middleware decompresses upstream
48
+ // bodies but can still emit `Content-Encoding: br`, breaking admin fetch (ERR_CONTENT_DECODING_FAILED).
49
+ compress: compress ?? false,
47
50
  transpilePackages: ['@fecommunity/reactpress-toolkit', ...(transpilePackages ?? [])],
48
51
  eslint: {
49
52
  ignoreDuringBuilds: true,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fecommunity/reactpress-toolkit",
3
- "version": "4.0.0-beta.13",
3
+ "version": "4.0.0-beta.15",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "exports": {