@fecommunity/reactpress-server 1.0.0-beta.15 → 1.0.0-beta.16
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 +288 -33
- package/bin/reactpress-server.js +172 -636
- package/dist/app.module.js +2 -2
- package/dist/app.module.js.map +1 -1
- package/dist/generate-swagger.d.ts +1 -0
- package/dist/generate-swagger.js +73 -0
- package/dist/generate-swagger.js.map +1 -0
- package/dist/main.d.ts +5 -0
- package/dist/main.js +258 -39
- package/dist/main.js.map +1 -1
- package/dist/modules/setting/setting.constant.d.ts +2 -3
- package/dist/modules/setting/setting.constant.js +4 -3
- package/dist/modules/setting/setting.constant.js.map +1 -1
- package/dist/modules/setting/setting.service.js +34 -11
- package/dist/modules/setting/setting.service.js.map +1 -1
- package/dist/public/favicon.png +0 -0
- package/dist/public/index.html +439 -0
- package/dist/public/swagger/custom.css +11 -0
- package/dist/starter.d.ts +1 -0
- package/dist/starter.js +84 -0
- package/dist/starter.js.map +1 -0
- package/dist/tsconfig.build.tsbuildinfo +1007 -50
- package/package.json +39 -34
- package/public/favicon.png +0 -0
- package/public/index.html +439 -0
- package/public/swagger/custom.css +11 -0
package/bin/reactpress-server.js
CHANGED
|
@@ -1,667 +1,203 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
/**
|
|
4
|
+
* ReactPress Server CLI Entry Point
|
|
5
|
+
* This script allows starting the ReactPress server via npx
|
|
6
|
+
* Supports both regular and PM2 startup modes
|
|
7
|
+
*/
|
|
8
|
+
|
|
8
9
|
const path = require('path');
|
|
9
|
-
const
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
const { spawn, spawnSync } = require('child_process');
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// Capture the original working directory where npx was executed
|
|
14
|
+
// BUT prioritize the REACTPRESS_ORIGINAL_CWD environment variable if it exists
|
|
15
|
+
// This ensures consistency when running via pnpm dev from root directory
|
|
16
|
+
const originalCwd = process.env.REACTPRESS_ORIGINAL_CWD || process.cwd();
|
|
13
17
|
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
// Get command line arguments
|
|
19
|
+
const args = process.argv.slice(2);
|
|
20
|
+
const usePM2 = args.includes('--pm2');
|
|
21
|
+
const showHelp = args.includes('--help') || args.includes('-h');
|
|
22
|
+
|
|
23
|
+
// Show help if requested
|
|
24
|
+
if (showHelp) {
|
|
25
|
+
console.log(`
|
|
26
|
+
ReactPress Server - NestJS-based backend API for ReactPress CMS
|
|
27
|
+
|
|
28
|
+
Usage:
|
|
29
|
+
npx @fecommunity/reactpress-server [options]
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--pm2 Start server with PM2 process manager
|
|
33
|
+
--help, -h Show this help message
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
npx @fecommunity/reactpress-server # Start server normally
|
|
37
|
+
npx @fecommunity/reactpress-server --pm2 # Start server with PM2
|
|
38
|
+
npx @fecommunity/reactpress-server --help # Show this help message
|
|
39
|
+
`);
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Get the directory where this script is located
|
|
44
|
+
const binDir = __dirname;
|
|
45
|
+
const serverDir = path.join(binDir, '..');
|
|
46
|
+
const distPath = path.join(serverDir, 'dist', 'main.js');
|
|
47
|
+
const ecosystemPath = path.join(serverDir, 'ecosystem.config.js');
|
|
48
|
+
|
|
49
|
+
// Function to check if PM2 is installed
|
|
50
|
+
function isPM2Installed() {
|
|
51
|
+
try {
|
|
52
|
+
require.resolve('pm2');
|
|
53
|
+
return true;
|
|
54
|
+
} catch (e) {
|
|
55
|
+
// Check if PM2 is installed globally
|
|
56
|
+
try {
|
|
57
|
+
spawnSync('pm2', ['--version'], { stdio: 'ignore' });
|
|
58
|
+
return true;
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
30
62
|
}
|
|
31
63
|
}
|
|
32
64
|
|
|
33
|
-
//
|
|
34
|
-
|
|
35
|
-
console.log(
|
|
65
|
+
// Function to install PM2
|
|
66
|
+
function installPM2() {
|
|
67
|
+
console.log('[ReactPress Server] Installing PM2...');
|
|
68
|
+
const installResult = spawnSync('npm', ['install', 'pm2', '--no-save'], {
|
|
69
|
+
stdio: 'inherit',
|
|
70
|
+
cwd: serverDir
|
|
71
|
+
});
|
|
36
72
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
73
|
+
if (installResult.status !== 0) {
|
|
74
|
+
console.error('[ReactPress Server] Failed to install PM2');
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
41
77
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Function to start with PM2
|
|
82
|
+
function startWithPM2() {
|
|
83
|
+
// Check if PM2 is installed
|
|
84
|
+
if (!isPM2Installed()) {
|
|
85
|
+
// Try to install PM2
|
|
86
|
+
if (!installPM2()) {
|
|
87
|
+
console.error('[ReactPress Server] Cannot start with PM2');
|
|
88
|
+
process.exit(1);
|
|
51
89
|
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Check if the server is built
|
|
93
|
+
if (!fs.existsSync(distPath)) {
|
|
94
|
+
console.log('[ReactPress Server] Server not built yet. Building...');
|
|
52
95
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
await conn.query(`USE \`${database.trim()}\``);
|
|
66
|
-
console.log(chalk.green(`Database '${database}' exists and accessible`));
|
|
67
|
-
} catch (dbError) {
|
|
68
|
-
if (dbError.code === 'ER_BAD_DB_ERROR') {
|
|
69
|
-
await conn.query(`CREATE DATABASE \`${database.trim()}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
|
|
70
|
-
console.log(chalk.green(`Database '${database}' created successfully`));
|
|
71
|
-
} else {
|
|
72
|
-
throw dbError;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
await conn.end();
|
|
77
|
-
console.log(chalk.green('✅ Database connection test passed'));
|
|
78
|
-
|
|
79
|
-
res.json({
|
|
80
|
-
success: true,
|
|
81
|
-
message: `Successfully connected to database '${database}' on ${host}:${port}`
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
} catch (error) {
|
|
85
|
-
console.log(chalk.red('❌ Database connection failed:'), error.message);
|
|
86
|
-
|
|
87
|
-
let errorMessage = 'Database connection failed: ';
|
|
88
|
-
|
|
89
|
-
switch (error.code) {
|
|
90
|
-
case 'ECONNREFUSED':
|
|
91
|
-
errorMessage += `Cannot connect to MySQL server at ${host}:${port}. Please ensure MySQL is running.`;
|
|
92
|
-
break;
|
|
93
|
-
case 'ER_ACCESS_DENIED_ERROR':
|
|
94
|
-
errorMessage += `Access denied for user '${user}'. Please check your username and password.`;
|
|
95
|
-
break;
|
|
96
|
-
case 'ENOTFOUND':
|
|
97
|
-
errorMessage += `Host '${host}' not found. Please check the hostname.`;
|
|
98
|
-
break;
|
|
99
|
-
case 'ETIMEDOUT':
|
|
100
|
-
errorMessage += `Connection timeout to ${host}:${port}. Please check your network and firewall settings.`;
|
|
101
|
-
break;
|
|
102
|
-
case 'ER_BAD_DB_ERROR':
|
|
103
|
-
errorMessage += `Database '${database}' does not exist and cannot be created. Please check your permissions.`;
|
|
104
|
-
break;
|
|
105
|
-
default:
|
|
106
|
-
errorMessage += error.message;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
res.json({ success: false, message: errorMessage });
|
|
96
|
+
// Try to build the server
|
|
97
|
+
const buildResult = spawnSync('npm', ['run', 'build'], {
|
|
98
|
+
stdio: 'inherit',
|
|
99
|
+
cwd: serverDir
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (buildResult.status !== 0) {
|
|
103
|
+
console.error('[ReactPress Server] Failed to build server');
|
|
104
|
+
process.exit(1);
|
|
110
105
|
}
|
|
111
|
-
}
|
|
106
|
+
}
|
|
112
107
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
user: db.user.trim(),
|
|
123
|
-
password: db.password,
|
|
124
|
-
database: db.database.trim()
|
|
125
|
-
});
|
|
126
|
-
await testConn.end();
|
|
127
|
-
} catch (error) {
|
|
128
|
-
return res.json({
|
|
129
|
-
success: false,
|
|
130
|
-
message: 'Final database validation failed. Please test your connection again.'
|
|
131
|
-
});
|
|
108
|
+
console.log('[ReactPress Server] Starting with PM2...');
|
|
109
|
+
|
|
110
|
+
// Use ecosystem.config.js if it exists, otherwise use direct command
|
|
111
|
+
let pm2Command = 'pm2';
|
|
112
|
+
try {
|
|
113
|
+
// Try to resolve PM2 path
|
|
114
|
+
pm2Command = path.join(serverDir, 'node_modules', '.bin', 'pm2');
|
|
115
|
+
if (!fs.existsSync(pm2Command)) {
|
|
116
|
+
pm2Command = 'pm2';
|
|
132
117
|
}
|
|
118
|
+
} catch (e) {
|
|
119
|
+
pm2Command = 'pm2';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if ecosystem.config.js exists
|
|
123
|
+
if (fs.existsSync(ecosystemPath)) {
|
|
124
|
+
const pm2 = spawn(pm2Command, ['start', ecosystemPath], {
|
|
125
|
+
stdio: 'inherit',
|
|
126
|
+
cwd: serverDir
|
|
127
|
+
});
|
|
133
128
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
DB_PORT=${db.port}
|
|
139
|
-
DB_USER=${db.user}
|
|
140
|
-
DB_PASSWD=${db.password}
|
|
141
|
-
DB_DATABASE=${db.database}
|
|
142
|
-
CLIENT_SITE_URL=${site.clientUrl}
|
|
143
|
-
SERVER_SITE_URL=${site.serverUrl}
|
|
144
|
-
`;
|
|
129
|
+
pm2.on('close', (code) => {
|
|
130
|
+
console.log(`[ReactPress Server] PM2 process exited with code ${code}`);
|
|
131
|
+
process.exit(code);
|
|
132
|
+
});
|
|
145
133
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
pm2.on('error', (error) => {
|
|
135
|
+
console.error('[ReactPress Server] Failed to start with PM2:', error);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
// Fallback to direct start
|
|
140
|
+
const pm2 = spawn(pm2Command, ['start', distPath, '--name', 'reactpress-server'], {
|
|
141
|
+
stdio: 'inherit',
|
|
142
|
+
cwd: serverDir
|
|
143
|
+
});
|
|
149
144
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
serverUrl: site.serverUrl
|
|
145
|
+
pm2.on('close', (code) => {
|
|
146
|
+
console.log(`[ReactPress Server] PM2 process exited with code ${code}`);
|
|
147
|
+
process.exit(code);
|
|
154
148
|
});
|
|
155
149
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
startServer();
|
|
162
|
-
}, 2000);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
const server = app.listen(PORT, () => {
|
|
166
|
-
const url = `http://localhost:${PORT}`;
|
|
167
|
-
console.log(chalk.green(`✅ Installation wizard: ${url}`));
|
|
168
|
-
open(url);
|
|
169
|
-
console.log(chalk.gray('Press Ctrl+C to cancel\n'));
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
process.on('SIGINT', () => {
|
|
173
|
-
console.log(chalk.yellow('\n⚠️ Installation cancelled'));
|
|
174
|
-
server.close();
|
|
175
|
-
process.exit(0);
|
|
176
|
-
});
|
|
150
|
+
pm2.on('error', (error) => {
|
|
151
|
+
console.error('[ReactPress Server] Failed to start with PM2:', error);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
}
|
|
177
155
|
}
|
|
178
156
|
|
|
179
|
-
//
|
|
180
|
-
function
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
console.log(chalk.red('❌ No configuration found!'));
|
|
185
|
-
console.log(chalk.yellow('Please run the installation first.'));
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
console.log(chalk.blue('🚀 Starting ReactPress Server...'));
|
|
190
|
-
console.log(chalk.cyan(`🌍 Server will be available at: http://localhost:${SERVER_PORT}/api`));
|
|
191
|
-
|
|
192
|
-
// Start the NestJS server
|
|
193
|
-
const serverDir = path.dirname(__dirname); // server directory
|
|
194
|
-
|
|
195
|
-
// Check if we're in development or installed package
|
|
196
|
-
const isDevMode = fs.existsSync(path.join(serverDir, 'src'));
|
|
197
|
-
|
|
198
|
-
let server;
|
|
199
|
-
if (isDevMode) {
|
|
200
|
-
// Development mode - try different nest commands
|
|
201
|
-
const nestCommands = [
|
|
202
|
-
['npx', ['nest', 'start', '--watch']],
|
|
203
|
-
['npm', ['run', 'dev']],
|
|
204
|
-
['node', ['node_modules/.bin/nest', 'start', '--watch']]
|
|
205
|
-
];
|
|
157
|
+
// Function to start with regular Node.js
|
|
158
|
+
function startWithNode() {
|
|
159
|
+
// Check if the server is built
|
|
160
|
+
if (!fs.existsSync(distPath)) {
|
|
161
|
+
console.log('[ReactPress Server] Server not built yet. Building...');
|
|
206
162
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
stdio: 'inherit',
|
|
213
|
-
env: { ...process.env, NODE_ENV: 'development' }
|
|
214
|
-
});
|
|
215
|
-
commandFound = true;
|
|
216
|
-
break;
|
|
217
|
-
} catch (error) {
|
|
218
|
-
console.log(chalk.yellow(`⚠️ ${cmd} not available, trying next option...`));
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
163
|
+
// Try to build the server
|
|
164
|
+
const buildResult = spawnSync('npm', ['run', 'build'], {
|
|
165
|
+
stdio: 'inherit',
|
|
166
|
+
cwd: serverDir
|
|
167
|
+
});
|
|
222
168
|
|
|
223
|
-
if (
|
|
224
|
-
console.
|
|
225
|
-
|
|
226
|
-
return;
|
|
169
|
+
if (buildResult.status !== 0) {
|
|
170
|
+
console.error('[ReactPress Server] Failed to build server');
|
|
171
|
+
process.exit(1);
|
|
227
172
|
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ONLY set the environment variable if it's not already set
|
|
176
|
+
// This preserves the value set by set-env.js when running pnpm dev from root
|
|
177
|
+
if (!process.env.REACTPRESS_ORIGINAL_CWD) {
|
|
178
|
+
process.env.REACTPRESS_ORIGINAL_CWD = originalCwd;
|
|
228
179
|
} else {
|
|
229
|
-
|
|
230
|
-
const mainPath = path.join(serverDir, 'dist', 'main.js');
|
|
231
|
-
if (fs.existsSync(mainPath)) {
|
|
232
|
-
server = spawn('node', [mainPath], {
|
|
233
|
-
cwd: serverDir,
|
|
234
|
-
stdio: 'inherit',
|
|
235
|
-
env: { ...process.env, NODE_ENV: 'production' }
|
|
236
|
-
});
|
|
237
|
-
} else {
|
|
238
|
-
console.log(chalk.red('❌ Server files not found. Please ensure the package is properly installed.'));
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
180
|
+
console.log(`[ReactPress Server] Using existing REACTPRESS_ORIGINAL_CWD: ${process.env.REACTPRESS_ORIGINAL_CWD}`);
|
|
241
181
|
}
|
|
242
|
-
|
|
243
|
-
server.on('error', (error) => {
|
|
244
|
-
console.log(chalk.red('❌ Failed to start server:'), error.message);
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
// Open browser after server starts
|
|
248
|
-
setTimeout(() => {
|
|
249
|
-
open(`http://localhost:${SERVER_PORT}/api`);
|
|
250
|
-
}, 5000);
|
|
251
|
-
}
|
|
252
182
|
|
|
253
|
-
//
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
<meta charset="UTF-8">
|
|
259
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
260
|
-
<title>ReactPress › Installation</title>
|
|
261
|
-
<style>
|
|
262
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
263
|
-
body {
|
|
264
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
265
|
-
background: #f1f1f1;
|
|
266
|
-
color: #444;
|
|
267
|
-
line-height: 1.4;
|
|
268
|
-
}
|
|
269
|
-
.wp-container {
|
|
270
|
-
max-width: 600px;
|
|
271
|
-
margin: 40px auto;
|
|
272
|
-
background: #fff;
|
|
273
|
-
border: 1px solid #ccd0d4;
|
|
274
|
-
box-shadow: 0 1px 3px rgba(0,0,0,0.04);
|
|
275
|
-
}
|
|
276
|
-
.wp-header {
|
|
277
|
-
background: #0073aa;
|
|
278
|
-
color: #fff;
|
|
279
|
-
padding: 24px;
|
|
280
|
-
text-align: center;
|
|
281
|
-
border-bottom: 1px solid #005a87;
|
|
282
|
-
}
|
|
283
|
-
.wp-header h1 { font-size: 24px; font-weight: 400; margin-bottom: 8px; }
|
|
284
|
-
.wp-header p { font-size: 16px; opacity: 0.9; }
|
|
285
|
-
.wp-content { padding: 24px; }
|
|
286
|
-
.wp-step { display: none; }
|
|
287
|
-
.wp-step.active { display: block; }
|
|
288
|
-
.wp-step h2 { font-size: 22px; font-weight: 600; margin-bottom: 20px; color: #1d2327; }
|
|
289
|
-
.wp-description {
|
|
290
|
-
background: #f0f6fc;
|
|
291
|
-
border: 1px solid #c3e4f0;
|
|
292
|
-
border-left: 4px solid #0073aa;
|
|
293
|
-
padding: 12px 16px;
|
|
294
|
-
margin-bottom: 20px;
|
|
295
|
-
font-size: 14px;
|
|
296
|
-
color: #0c4a6e;
|
|
297
|
-
}
|
|
298
|
-
.wp-form-table { width: 100%; margin-bottom: 20px; }
|
|
299
|
-
.wp-form-table th {
|
|
300
|
-
text-align: left;
|
|
301
|
-
padding: 10px 0;
|
|
302
|
-
width: 140px;
|
|
303
|
-
vertical-align: top;
|
|
304
|
-
font-weight: 600;
|
|
305
|
-
color: #1d2327;
|
|
306
|
-
}
|
|
307
|
-
.wp-form-table td { padding: 10px 0; }
|
|
308
|
-
.wp-form-table input[type="text"],
|
|
309
|
-
.wp-form-table input[type="password"],
|
|
310
|
-
.wp-form-table input[type="number"],
|
|
311
|
-
.wp-form-table input[type="url"] {
|
|
312
|
-
width: 100%;
|
|
313
|
-
max-width: 320px;
|
|
314
|
-
padding: 8px 12px;
|
|
315
|
-
border: 1px solid #8c8f94;
|
|
316
|
-
border-radius: 4px;
|
|
317
|
-
font-size: 14px;
|
|
318
|
-
line-height: 1.4;
|
|
319
|
-
transition: border-color 0.15s ease-in-out;
|
|
320
|
-
}
|
|
321
|
-
.wp-form-table input:focus {
|
|
322
|
-
border-color: #0073aa;
|
|
323
|
-
outline: 0;
|
|
324
|
-
box-shadow: 0 0 0 1px #0073aa;
|
|
325
|
-
}
|
|
326
|
-
.wp-form-table .description {
|
|
327
|
-
font-size: 13px;
|
|
328
|
-
color: #646970;
|
|
329
|
-
margin-top: 4px;
|
|
330
|
-
}
|
|
331
|
-
.wp-button {
|
|
332
|
-
background: #0073aa;
|
|
333
|
-
color: #fff;
|
|
334
|
-
border: 1px solid #0073aa;
|
|
335
|
-
padding: 8px 16px;
|
|
336
|
-
font-size: 14px;
|
|
337
|
-
font-weight: 600;
|
|
338
|
-
border-radius: 3px;
|
|
339
|
-
cursor: pointer;
|
|
340
|
-
transition: all 0.15s ease-in-out;
|
|
341
|
-
text-decoration: none;
|
|
342
|
-
display: inline-block;
|
|
343
|
-
}
|
|
344
|
-
.wp-button:hover { background: #005a87; border-color: #005a87; }
|
|
345
|
-
.wp-button:disabled {
|
|
346
|
-
background: #f0f0f1;
|
|
347
|
-
border-color: #dcdcde;
|
|
348
|
-
color: #a7aaad;
|
|
349
|
-
cursor: not-allowed;
|
|
350
|
-
}
|
|
351
|
-
.wp-button.button-large { padding: 12px 24px; font-size: 16px; }
|
|
352
|
-
.wp-notice {
|
|
353
|
-
border: 1px solid #c3c4c7;
|
|
354
|
-
border-left-width: 4px;
|
|
355
|
-
padding: 12px;
|
|
356
|
-
margin: 16px 0;
|
|
357
|
-
background: #fff;
|
|
358
|
-
}
|
|
359
|
-
.wp-notice.notice-success { border-left-color: #00a32a; background: #f7fcf7; }
|
|
360
|
-
.wp-notice.notice-error { border-left-color: #d63638; background: #fcf2f2; }
|
|
361
|
-
.wp-notice p { margin: 0; font-size: 14px; }
|
|
362
|
-
.wp-progress { background: #f0f0f1; height: 4px; margin-bottom: 20px; }
|
|
363
|
-
.wp-progress-bar { background: #0073aa; height: 100%; transition: width 0.3s ease; }
|
|
364
|
-
.wp-loader {
|
|
365
|
-
display: inline-block;
|
|
366
|
-
width: 16px;
|
|
367
|
-
height: 16px;
|
|
368
|
-
border: 2px solid #f3f3f3;
|
|
369
|
-
border-top: 2px solid #0073aa;
|
|
370
|
-
border-radius: 50%;
|
|
371
|
-
animation: wp-spin 1s linear infinite;
|
|
372
|
-
margin-right: 8px;
|
|
373
|
-
}
|
|
374
|
-
@keyframes wp-spin {
|
|
375
|
-
0% { transform: rotate(0deg); }
|
|
376
|
-
100% { transform: rotate(360deg); }
|
|
377
|
-
}
|
|
378
|
-
.wp-footer {
|
|
379
|
-
background: #f6f7f7;
|
|
380
|
-
border-top: 1px solid #c3c4c7;
|
|
381
|
-
padding: 16px 24px;
|
|
382
|
-
text-align: center;
|
|
383
|
-
font-size: 13px;
|
|
384
|
-
color: #646970;
|
|
385
|
-
}
|
|
386
|
-
.text-center { text-align: center; }
|
|
387
|
-
</style>
|
|
388
|
-
</head>
|
|
389
|
-
<body>
|
|
390
|
-
<div class="wp-container">
|
|
391
|
-
<div class="wp-header">
|
|
392
|
-
<h1>ReactPress</h1>
|
|
393
|
-
<p>5-Minute Installation</p>
|
|
394
|
-
</div>
|
|
395
|
-
|
|
396
|
-
<div class="wp-progress">
|
|
397
|
-
<div class="wp-progress-bar" id="progressBar" style="width: 33%;"></div>
|
|
398
|
-
</div>
|
|
399
|
-
|
|
400
|
-
<div class="wp-content">
|
|
401
|
-
<!-- Step 1: Database Configuration -->
|
|
402
|
-
<div class="wp-step active" id="step1">
|
|
403
|
-
<h2>Database Connection Information</h2>
|
|
404
|
-
|
|
405
|
-
<div class="wp-description">
|
|
406
|
-
You should have this information available from your web hosting provider. If you don't have it, contact them before continuing.
|
|
407
|
-
</div>
|
|
408
|
-
|
|
409
|
-
<table class="wp-form-table">
|
|
410
|
-
<tr>
|
|
411
|
-
<th><label for="dbHost">Database Host</label></th>
|
|
412
|
-
<td>
|
|
413
|
-
<input type="text" id="dbHost" value="127.0.0.1" />
|
|
414
|
-
<p class="description">Usually "localhost" or "127.0.0.1"</p>
|
|
415
|
-
</td>
|
|
416
|
-
</tr>
|
|
417
|
-
<tr>
|
|
418
|
-
<th><label for="dbPort">Database Port</label></th>
|
|
419
|
-
<td>
|
|
420
|
-
<input type="number" id="dbPort" value="3306" />
|
|
421
|
-
<p class="description">Default MySQL port is 3306</p>
|
|
422
|
-
</td>
|
|
423
|
-
</tr>
|
|
424
|
-
<tr>
|
|
425
|
-
<th><label for="dbUser">Username</label></th>
|
|
426
|
-
<td>
|
|
427
|
-
<input type="text" id="dbUser" value="" placeholder="Database username" />
|
|
428
|
-
<p class="description">Your MySQL username</p>
|
|
429
|
-
</td>
|
|
430
|
-
</tr>
|
|
431
|
-
<tr>
|
|
432
|
-
<th><label for="dbPassword">Password</label></th>
|
|
433
|
-
<td>
|
|
434
|
-
<input type="password" id="dbPassword" value="" placeholder="Database password" />
|
|
435
|
-
<p class="description">Your MySQL password</p>
|
|
436
|
-
</td>
|
|
437
|
-
</tr>
|
|
438
|
-
<tr>
|
|
439
|
-
<th><label for="dbName">Database Name</label></th>
|
|
440
|
-
<td>
|
|
441
|
-
<input type="text" id="dbName" value="reactpress" />
|
|
442
|
-
<p class="description">The name of the database you want to use with ReactPress</p>
|
|
443
|
-
</td>
|
|
444
|
-
</tr>
|
|
445
|
-
</table>
|
|
446
|
-
|
|
447
|
-
<div id="dbResult"></div>
|
|
448
|
-
|
|
449
|
-
<p class="text-center">
|
|
450
|
-
<button class="wp-button button-large" onclick="testDatabase()" id="testBtn">
|
|
451
|
-
Test Database Connection
|
|
452
|
-
</button>
|
|
453
|
-
</p>
|
|
454
|
-
</div>
|
|
455
|
-
|
|
456
|
-
<!-- Step 2: Site Configuration -->
|
|
457
|
-
<div class="wp-step" id="step2">
|
|
458
|
-
<h2>Site Information</h2>
|
|
459
|
-
|
|
460
|
-
<div class="wp-description">
|
|
461
|
-
Please provide the following information. Don't worry, you can always change these settings later.
|
|
462
|
-
</div>
|
|
463
|
-
|
|
464
|
-
<table class="wp-form-table">
|
|
465
|
-
<tr>
|
|
466
|
-
<th><label for="clientUrl">Client Site URL</label></th>
|
|
467
|
-
<td>
|
|
468
|
-
<input type="url" id="clientUrl" value="http://localhost:3001" />
|
|
469
|
-
<p class="description">The URL where your ReactPress frontend will be accessible</p>
|
|
470
|
-
</td>
|
|
471
|
-
</tr>
|
|
472
|
-
<tr>
|
|
473
|
-
<th><label for="serverUrl">API Server URL</label></th>
|
|
474
|
-
<td>
|
|
475
|
-
<input type="url" id="serverUrl" value="http://localhost:3002" />
|
|
476
|
-
<p class="description">The URL where your ReactPress API server will run</p>
|
|
477
|
-
</td>
|
|
478
|
-
</tr>
|
|
479
|
-
</table>
|
|
480
|
-
|
|
481
|
-
<div id="installResult"></div>
|
|
482
|
-
|
|
483
|
-
<p class="text-center">
|
|
484
|
-
<button class="wp-button button-large" onclick="installReactPress()" id="installBtn">
|
|
485
|
-
Install & Start ReactPress
|
|
486
|
-
</button>
|
|
487
|
-
</p>
|
|
488
|
-
</div>
|
|
489
|
-
|
|
490
|
-
<!-- Step 3: Success -->
|
|
491
|
-
<div class="wp-step" id="step3">
|
|
492
|
-
<h2>🎉 Installation Complete!</h2>
|
|
493
|
-
|
|
494
|
-
<div class="wp-notice notice-success">
|
|
495
|
-
<p><strong>ReactPress has been installed and is starting...</strong></p>
|
|
496
|
-
</div>
|
|
497
|
-
|
|
498
|
-
<p>Your ReactPress server is now starting up. You can:</p>
|
|
499
|
-
|
|
500
|
-
<ul style="margin: 16px 0 16px 20px;">
|
|
501
|
-
<li>Close this browser window</li>
|
|
502
|
-
<li>The server API will open automatically at <strong>http://localhost:3002/api</strong></li>
|
|
503
|
-
<li>Access your admin panel at <strong>http://localhost:3001/admin</strong> (when client is running)</li>
|
|
504
|
-
</ul>
|
|
505
|
-
|
|
506
|
-
<div class="wp-description">
|
|
507
|
-
<strong>Note:</strong> To restart the server in the future, simply run <code>npx @fecommunity/reactpress-server</code> again.
|
|
508
|
-
</div>
|
|
509
|
-
</div>
|
|
510
|
-
</div>
|
|
511
|
-
|
|
512
|
-
<div class="wp-footer">
|
|
513
|
-
<p>Need help? <a href="https://github.com/fecommunity/reactpress/issues" target="_blank">ReactPress Documentation</a></p>
|
|
514
|
-
</div>
|
|
515
|
-
</div>
|
|
516
|
-
|
|
517
|
-
<script>
|
|
518
|
-
let currentStep = 1;
|
|
519
|
-
const totalSteps = 3;
|
|
520
|
-
let isTestingConnection = false;
|
|
521
|
-
|
|
522
|
-
function updateProgress() {
|
|
523
|
-
const progress = (currentStep / totalSteps) * 100;
|
|
524
|
-
document.getElementById('progressBar').style.width = progress + '%';
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function showStep(step) {
|
|
528
|
-
document.querySelectorAll('.wp-step').forEach(s => s.classList.remove('active'));
|
|
529
|
-
document.getElementById('step' + step).classList.add('active');
|
|
530
|
-
currentStep = step;
|
|
531
|
-
updateProgress();
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function showNotice(message, type = 'error', containerId = 'dbResult') {
|
|
535
|
-
const container = document.getElementById(containerId);
|
|
536
|
-
container.innerHTML = \`
|
|
537
|
-
<div class="wp-notice notice-\${type}">
|
|
538
|
-
<p>\${message}</p>
|
|
539
|
-
</div>
|
|
540
|
-
\`;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function validateDatabaseInputs() {
|
|
544
|
-
const host = document.getElementById('dbHost').value.trim();
|
|
545
|
-
const port = document.getElementById('dbPort').value.trim();
|
|
546
|
-
const user = document.getElementById('dbUser').value.trim();
|
|
547
|
-
const password = document.getElementById('dbPassword').value;
|
|
548
|
-
const database = document.getElementById('dbName').value.trim();
|
|
549
|
-
|
|
550
|
-
if (!host || !port || !user || !database) {
|
|
551
|
-
showNotice('Please fill in all required fields.', 'error');
|
|
552
|
-
return false;
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
556
|
-
showNotice('Please enter a valid port number (1-65535).', 'error');
|
|
557
|
-
return false;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
return { host, port, user, password, database };
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
async function testDatabase() {
|
|
564
|
-
if (isTestingConnection) return;
|
|
565
|
-
|
|
566
|
-
const dbData = validateDatabaseInputs();
|
|
567
|
-
if (!dbData) return;
|
|
568
|
-
|
|
569
|
-
isTestingConnection = true;
|
|
570
|
-
const testBtn = document.getElementById('testBtn');
|
|
571
|
-
const originalText = testBtn.innerHTML;
|
|
572
|
-
|
|
573
|
-
testBtn.disabled = true;
|
|
574
|
-
testBtn.innerHTML = '<span class="wp-loader"></span>Testing connection...';
|
|
575
|
-
|
|
576
|
-
try {
|
|
577
|
-
const response = await fetch('/test-db', {
|
|
578
|
-
method: 'POST',
|
|
579
|
-
headers: { 'Content-Type': 'application/json' },
|
|
580
|
-
body: JSON.stringify(dbData)
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
const result = await response.json();
|
|
584
|
-
|
|
585
|
-
if (result.success) {
|
|
586
|
-
showNotice(result.message, 'success');
|
|
587
|
-
setTimeout(() => {
|
|
588
|
-
showStep(2);
|
|
589
|
-
}, 1500);
|
|
590
|
-
} else {
|
|
591
|
-
showNotice(result.message, 'error');
|
|
592
|
-
}
|
|
593
|
-
} catch (error) {
|
|
594
|
-
showNotice('Failed to connect to server. Please try again.', 'error');
|
|
595
|
-
} finally {
|
|
596
|
-
isTestingConnection = false;
|
|
597
|
-
testBtn.disabled = false;
|
|
598
|
-
testBtn.innerHTML = originalText;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
async function installReactPress() {
|
|
603
|
-
const dbData = validateDatabaseInputs();
|
|
604
|
-
if (!dbData) {
|
|
605
|
-
showStep(1);
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
const clientUrl = document.getElementById('clientUrl').value.trim();
|
|
610
|
-
const serverUrl = document.getElementById('serverUrl').value.trim();
|
|
611
|
-
|
|
612
|
-
if (!clientUrl || !serverUrl) {
|
|
613
|
-
showNotice('Please fill in all site URLs.', 'error', 'installResult');
|
|
614
|
-
return;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
const installBtn = document.getElementById('installBtn');
|
|
618
|
-
const originalText = installBtn.innerHTML;
|
|
619
|
-
|
|
620
|
-
installBtn.disabled = true;
|
|
621
|
-
installBtn.innerHTML = '<span class="wp-loader"></span>Installing & Starting...';
|
|
622
|
-
|
|
623
|
-
try {
|
|
624
|
-
const response = await fetch('/install', {
|
|
625
|
-
method: 'POST',
|
|
626
|
-
headers: { 'Content-Type': 'application/json' },
|
|
627
|
-
body: JSON.stringify({
|
|
628
|
-
db: dbData,
|
|
629
|
-
site: { clientUrl, serverUrl }
|
|
630
|
-
})
|
|
631
|
-
});
|
|
632
|
-
|
|
633
|
-
const result = await response.json();
|
|
634
|
-
|
|
635
|
-
if (result.success) {
|
|
636
|
-
showStep(3);
|
|
637
|
-
// Auto-close and redirect to server after a delay
|
|
638
|
-
setTimeout(() => {
|
|
639
|
-
if (result.serverUrl) {
|
|
640
|
-
window.open(result.serverUrl + '/api', '_blank');
|
|
641
|
-
}
|
|
642
|
-
}, 3000);
|
|
643
|
-
} else {
|
|
644
|
-
showNotice(result.message, 'error', 'installResult');
|
|
645
|
-
}
|
|
646
|
-
} catch (error) {
|
|
647
|
-
showNotice('Installation failed. Please try again.', 'error', 'installResult');
|
|
648
|
-
} finally {
|
|
649
|
-
installBtn.disabled = false;
|
|
650
|
-
installBtn.innerHTML = originalText;
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// Initialize
|
|
655
|
-
updateProgress();
|
|
656
|
-
document.getElementById('dbHost').focus();
|
|
657
|
-
</script>
|
|
658
|
-
</body>
|
|
659
|
-
</html>`;
|
|
660
|
-
}
|
|
183
|
+
// Change to the server directory
|
|
184
|
+
process.chdir(serverDir);
|
|
185
|
+
|
|
186
|
+
// Set environment variables
|
|
187
|
+
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
|
|
661
188
|
|
|
662
|
-
//
|
|
663
|
-
|
|
664
|
-
|
|
189
|
+
// Import and run the server
|
|
190
|
+
try {
|
|
191
|
+
require(distPath);
|
|
192
|
+
} catch (error) {
|
|
193
|
+
console.error('[ReactPress Server] Failed to start server:', error);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
}
|
|
665
196
|
}
|
|
666
197
|
|
|
667
|
-
|
|
198
|
+
// Main execution
|
|
199
|
+
if (usePM2) {
|
|
200
|
+
startWithPM2();
|
|
201
|
+
} else {
|
|
202
|
+
startWithNode();
|
|
203
|
+
}
|