@fecommunity/reactpress-server 1.0.0-beta.11 → 1.0.0-beta.13

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.
@@ -1,444 +1,626 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { program } = require('commander');
4
- const { execSync } = require('child_process');
5
- const path = require('path');
6
- const fs = require('fs');
7
- const chalk = require('chalk');
8
- const inquirer = require('inquirer');
3
+ const express = require('express');
9
4
  const mysql = require('mysql2/promise');
5
+ const chalk = require('chalk');
6
+ const open = require('open');
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { spawn } = require('child_process');
10
10
 
11
- program
12
- .name('@fecommunity/reactpress-server')
13
- .description('ReactPress Server CLI')
14
- .version('1.0.0');
11
+ const PORT = 3003;
12
+ const SERVER_PORT = 3002;
15
13
 
16
- // Database connection testing function
17
- async function testDatabaseConnection(config) {
18
- try {
19
- console.log(chalk.cyan('🔍 Testing database connection...'));
20
-
21
- const connection = await mysql.createConnection({
22
- host: config.dbHost,
23
- port: parseInt(config.dbPort),
24
- user: config.dbUser,
25
- password: config.dbPassword
26
- });
27
-
28
- // Test basic connection
29
- await connection.ping();
30
- console.log(chalk.green('✅ Database connection successful!'));
31
-
32
- // Check if database exists
33
- const [databases] = await connection.execute(`SHOW DATABASES LIKE '${config.dbDatabase}'`);
14
+ // Main entry point with auto-detection
15
+ async function main() {
16
+ const args = process.argv.slice(2);
17
+
18
+ // Check if .env exists in current directory
19
+ const envPath = path.join(process.cwd(), '.env');
20
+ const hasEnv = fs.existsSync(envPath);
21
+
22
+ console.log(chalk.blue('🚀 ReactPress Server'));
23
+
24
+ if (!hasEnv) {
25
+ console.log(chalk.yellow('⚠️ No configuration found. Starting installation wizard...'));
26
+ await runInstallationWizard();
27
+ } else {
28
+ console.log(chalk.green('✅ Configuration found. Starting server...'));
29
+ startServer();
30
+ }
31
+ }
32
+
33
+ // Installation wizard
34
+ async function runInstallationWizard() {
35
+ console.log(chalk.cyan('🌍 Starting installation wizard...'));
36
+
37
+ const app = express();
38
+ app.use(express.json());
39
+ app.use(express.urlencoded({ extended: true }));
40
+ app.get('/', (req, res) => res.send(getWordPressHTML()));
41
+
42
+ // Test database connection
43
+ app.post('/test-db', async (req, res) => {
44
+ const { host, port, user, password, database } = req.body;
45
+
46
+ if (!host || !port || !user || !database) {
47
+ return res.json({
48
+ success: false,
49
+ message: 'All database fields are required.'
50
+ });
51
+ }
34
52
 
35
- if (databases.length === 0) {
36
- console.log(chalk.yellow(`⚠️ Database '${config.dbDatabase}' does not exist.`));
53
+ try {
54
+ console.log(chalk.cyan(`Testing connection to ${user}@${host}:${port}/${database}...`));
37
55
 
38
- const { createDb } = await inquirer.prompt([
39
- {
40
- type: 'confirm',
41
- name: 'createDb',
42
- message: `Create database '${config.dbDatabase}' now?`,
43
- default: true
44
- }
45
- ]);
56
+ const conn = await mysql.createConnection({
57
+ host: host.trim(),
58
+ port: parseInt(port),
59
+ user: user.trim(),
60
+ password: password,
61
+ connectTimeout: 5000,
62
+ acquireTimeout: 5000,
63
+ timeout: 5000
64
+ });
46
65
 
47
- if (createDb) {
48
- try {
49
- await connection.execute(`CREATE DATABASE IF NOT EXISTS \`${config.dbDatabase}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
50
- console.log(chalk.green(`✅ Database '${config.dbDatabase}' created successfully!`));
51
- } catch (createError) {
52
- console.log(chalk.red(`❌ Failed to create database: ${createError.message}`));
53
- return false;
66
+ try {
67
+ await conn.query(`USE \`${database.trim()}\``);
68
+ console.log(chalk.green(`Database '${database}' exists and accessible`));
69
+ } catch (dbError) {
70
+ if (dbError.code === 'ER_BAD_DB_ERROR') {
71
+ await conn.query(`CREATE DATABASE \`${database.trim()}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci`);
72
+ console.log(chalk.green(`Database '${database}' created successfully`));
73
+ } else {
74
+ throw dbError;
54
75
  }
55
76
  }
56
- } else {
57
- console.log(chalk.green(`✅ Database '${config.dbDatabase}' exists and is accessible.`));
77
+
78
+ await conn.end();
79
+ console.log(chalk.green('✅ Database connection test passed'));
80
+
81
+ res.json({
82
+ success: true,
83
+ message: `Successfully connected to database '${database}' on ${host}:${port}`
84
+ });
85
+
86
+ } catch (error) {
87
+ console.log(chalk.red('❌ Database connection failed:'), error.message);
88
+
89
+ let errorMessage = 'Database connection failed: ';
90
+
91
+ switch (error.code) {
92
+ case 'ECONNREFUSED':
93
+ errorMessage += `Cannot connect to MySQL server at ${host}:${port}. Please ensure MySQL is running.`;
94
+ break;
95
+ case 'ER_ACCESS_DENIED_ERROR':
96
+ errorMessage += `Access denied for user '${user}'. Please check your username and password.`;
97
+ break;
98
+ case 'ENOTFOUND':
99
+ errorMessage += `Host '${host}' not found. Please check the hostname.`;
100
+ break;
101
+ case 'ETIMEDOUT':
102
+ errorMessage += `Connection timeout to ${host}:${port}. Please check your network and firewall settings.`;
103
+ break;
104
+ case 'ER_BAD_DB_ERROR':
105
+ errorMessage += `Database '${database}' does not exist and cannot be created. Please check your permissions.`;
106
+ break;
107
+ default:
108
+ errorMessage += error.message;
109
+ }
110
+
111
+ res.json({ success: false, message: errorMessage });
58
112
  }
113
+ });
114
+
115
+ // Install and start server
116
+ app.post('/install', async (req, res) => {
117
+ const { db, site } = req.body;
59
118
 
60
- await connection.end();
61
- return true;
119
+ // Final validation
120
+ try {
121
+ const testConn = await mysql.createConnection({
122
+ host: db.host.trim(),
123
+ port: parseInt(db.port),
124
+ user: db.user.trim(),
125
+ password: db.password,
126
+ database: db.database.trim()
127
+ });
128
+ await testConn.end();
129
+ } catch (error) {
130
+ return res.json({
131
+ success: false,
132
+ message: 'Final database validation failed. Please test your connection again.'
133
+ });
134
+ }
62
135
 
63
- } catch (error) {
64
- console.log(chalk.red('❌ Database connection failed:'));
136
+ const env = `# ReactPress Configuration
137
+ # Generated on ${new Date().toISOString()}
138
+
139
+ DB_HOST=${db.host}
140
+ DB_PORT=${db.port}
141
+ DB_USER=${db.user}
142
+ DB_PASSWD=${db.password}
143
+ DB_DATABASE=${db.database}
144
+ CLIENT_SITE_URL=${site.clientUrl}
145
+ SERVER_SITE_URL=${site.serverUrl}
146
+ `;
65
147
 
66
- if (error.code === 'ECONNREFUSED') {
67
- console.log(chalk.yellow(' - Make sure MySQL server is running'));
68
- console.log(chalk.yellow(` - Check if MySQL is listening on ${config.dbHost}:${config.dbPort}`));
69
- } else if (error.code === 'ER_ACCESS_DENIED_ERROR') {
70
- console.log(chalk.yellow(' - Check username and password'));
71
- console.log(chalk.yellow(' - Make sure user has proper permissions'));
72
- } else {
73
- console.log(chalk.yellow(` - Error: ${error.message}`));
74
- }
148
+ const envPath = path.join(process.cwd(), '.env');
149
+ fs.writeFileSync(envPath, env);
150
+ console.log(chalk.green('✅ Configuration saved to .env file'));
75
151
 
76
- const { retry } = await inquirer.prompt([
77
- {
78
- type: 'confirm',
79
- name: 'retry',
80
- message: 'Would you like to re-enter database configuration?',
81
- default: true
82
- }
83
- ]);
152
+ res.json({
153
+ success: true,
154
+ message: 'ReactPress installed successfully! Starting server...',
155
+ serverUrl: site.serverUrl
156
+ });
84
157
 
85
- return retry ? 'retry' : false;
158
+ // Close installation server and start ReactPress server
159
+ setTimeout(() => {
160
+ console.log(chalk.green('\n🎉 Installation completed!'));
161
+ console.log(chalk.cyan('🚀 Starting ReactPress Server...'));
162
+ server.close();
163
+ startServer();
164
+ }, 2000);
165
+ });
166
+
167
+ const server = app.listen(PORT, () => {
168
+ const url = `http://localhost:${PORT}`;
169
+ console.log(chalk.green(`✅ Installation wizard: ${url}`));
170
+ open(url);
171
+ console.log(chalk.gray('Press Ctrl+C to cancel\n'));
172
+ });
173
+
174
+ process.on('SIGINT', () => {
175
+ console.log(chalk.yellow('\n⚠️ Installation cancelled'));
176
+ server.close();
177
+ process.exit(0);
178
+ });
179
+ }
180
+
181
+ // Start ReactPress server
182
+ function startServer() {
183
+ const envPath = path.join(process.cwd(), '.env');
184
+
185
+ if (!fs.existsSync(envPath)) {
186
+ console.log(chalk.red('❌ No configuration found!'));
187
+ console.log(chalk.yellow('Please run the installation first.'));
188
+ return;
86
189
  }
190
+
191
+ console.log(chalk.blue('🚀 Starting ReactPress Server...'));
192
+ console.log(chalk.cyan(`🌍 Server will be available at: http://localhost:${SERVER_PORT}/api`));
193
+
194
+ // Start the NestJS server
195
+ const serverDir = path.dirname(__dirname); // server directory
196
+ const server = spawn('npm', ['run', 'dev'], {
197
+ cwd: serverDir,
198
+ stdio: 'inherit',
199
+ env: { ...process.env, NODE_ENV: 'development' }
200
+ });
201
+
202
+ server.on('error', (error) => {
203
+ console.log(chalk.red('❌ Failed to start server:'), error.message);
204
+ });
205
+
206
+ // Open browser after server starts
207
+ setTimeout(() => {
208
+ open(`http://localhost:${SERVER_PORT}/api`);
209
+ }, 5000);
87
210
  }
88
211
 
89
- // Init command - 初始化服务器配置
90
- program
91
- .command('init')
92
- .description('Initialize ReactPress server configuration')
93
- .option('-d, --database <type>', 'Database type (mysql|postgres)', 'mysql')
94
- .option('-p, --port <port>', 'Server port', '3002')
95
- .option('--skip-db-setup', 'Skip database setup')
96
- .option('-y, --yes', 'Use default values without prompts')
97
- .option('--auto', 'Alias for --yes (use default values without prompts)')
98
- .action(async (options) => {
99
- console.log(chalk.blue('🚀 Initializing ReactPress Server...\n'));
100
-
101
- const cwd = process.cwd();
102
- const envPath = path.join(cwd, '.env');
103
-
104
- // 检查.env文件是否已存在
105
- if (fs.existsSync(envPath)) {
106
- const { overwrite } = await inquirer.prompt([
107
- {
108
- type: 'confirm',
109
- name: 'overwrite',
110
- message: 'Configuration file (.env) already exists. Overwrite?',
111
- default: false
212
+ // WordPress-style HTML
213
+ function getWordPressHTML() {
214
+ return `<!DOCTYPE html>
215
+ <html lang="en">
216
+ <head>
217
+ <meta charset="UTF-8">
218
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
219
+ <title>ReactPress › Installation</title>
220
+ <style>
221
+ * { margin: 0; padding: 0; box-sizing: border-box; }
222
+ body {
223
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
224
+ background: #f1f1f1;
225
+ color: #444;
226
+ line-height: 1.4;
112
227
  }
113
- ]);
114
-
115
- if (!overwrite) {
116
- console.log(chalk.yellow('⚠️ Initialization cancelled.'));
117
- process.exit(0);
118
- }
119
- }
120
-
121
- let answers;
122
- let dbTestResult = false;
123
-
124
- // Configuration and database testing loop
125
- while (!dbTestResult) {
126
- if (options.yes || options.auto) {
127
- // 使用默认值,不进行交互
128
- console.log(chalk.cyan('📋 Using default configuration values...\n'));
129
- answers = {
130
- dbHost: '127.0.0.1',
131
- dbPort: '3306',
132
- dbUser: 'reactpress',
133
- dbPassword: 'reactpress',
134
- dbDatabase: 'reactpress',
135
- clientUrl: 'http://localhost:3001',
136
- serverPort: options.port || '3002',
137
- skipDbSetup: options.skipDbSetup || false
138
- };
228
+ .wp-container {
229
+ max-width: 600px;
230
+ margin: 40px auto;
231
+ background: #fff;
232
+ border: 1px solid #ccd0d4;
233
+ box-shadow: 0 1px 3px rgba(0,0,0,0.04);
234
+ }
235
+ .wp-header {
236
+ background: #0073aa;
237
+ color: #fff;
238
+ padding: 24px;
239
+ text-align: center;
240
+ border-bottom: 1px solid #005a87;
241
+ }
242
+ .wp-header h1 { font-size: 24px; font-weight: 400; margin-bottom: 8px; }
243
+ .wp-header p { font-size: 16px; opacity: 0.9; }
244
+ .wp-content { padding: 24px; }
245
+ .wp-step { display: none; }
246
+ .wp-step.active { display: block; }
247
+ .wp-step h2 { font-size: 22px; font-weight: 600; margin-bottom: 20px; color: #1d2327; }
248
+ .wp-description {
249
+ background: #f0f6fc;
250
+ border: 1px solid #c3e4f0;
251
+ border-left: 4px solid #0073aa;
252
+ padding: 12px 16px;
253
+ margin-bottom: 20px;
254
+ font-size: 14px;
255
+ color: #0c4a6e;
256
+ }
257
+ .wp-form-table { width: 100%; margin-bottom: 20px; }
258
+ .wp-form-table th {
259
+ text-align: left;
260
+ padding: 10px 0;
261
+ width: 140px;
262
+ vertical-align: top;
263
+ font-weight: 600;
264
+ color: #1d2327;
265
+ }
266
+ .wp-form-table td { padding: 10px 0; }
267
+ .wp-form-table input[type="text"],
268
+ .wp-form-table input[type="password"],
269
+ .wp-form-table input[type="number"],
270
+ .wp-form-table input[type="url"] {
271
+ width: 100%;
272
+ max-width: 320px;
273
+ padding: 8px 12px;
274
+ border: 1px solid #8c8f94;
275
+ border-radius: 4px;
276
+ font-size: 14px;
277
+ line-height: 1.4;
278
+ transition: border-color 0.15s ease-in-out;
279
+ }
280
+ .wp-form-table input:focus {
281
+ border-color: #0073aa;
282
+ outline: 0;
283
+ box-shadow: 0 0 0 1px #0073aa;
284
+ }
285
+ .wp-form-table .description {
286
+ font-size: 13px;
287
+ color: #646970;
288
+ margin-top: 4px;
289
+ }
290
+ .wp-button {
291
+ background: #0073aa;
292
+ color: #fff;
293
+ border: 1px solid #0073aa;
294
+ padding: 8px 16px;
295
+ font-size: 14px;
296
+ font-weight: 600;
297
+ border-radius: 3px;
298
+ cursor: pointer;
299
+ transition: all 0.15s ease-in-out;
300
+ text-decoration: none;
301
+ display: inline-block;
302
+ }
303
+ .wp-button:hover { background: #005a87; border-color: #005a87; }
304
+ .wp-button:disabled {
305
+ background: #f0f0f1;
306
+ border-color: #dcdcde;
307
+ color: #a7aaad;
308
+ cursor: not-allowed;
309
+ }
310
+ .wp-button.button-large { padding: 12px 24px; font-size: 16px; }
311
+ .wp-notice {
312
+ border: 1px solid #c3c4c7;
313
+ border-left-width: 4px;
314
+ padding: 12px;
315
+ margin: 16px 0;
316
+ background: #fff;
317
+ }
318
+ .wp-notice.notice-success { border-left-color: #00a32a; background: #f7fcf7; }
319
+ .wp-notice.notice-error { border-left-color: #d63638; background: #fcf2f2; }
320
+ .wp-notice p { margin: 0; font-size: 14px; }
321
+ .wp-progress { background: #f0f0f1; height: 4px; margin-bottom: 20px; }
322
+ .wp-progress-bar { background: #0073aa; height: 100%; transition: width 0.3s ease; }
323
+ .wp-loader {
324
+ display: inline-block;
325
+ width: 16px;
326
+ height: 16px;
327
+ border: 2px solid #f3f3f3;
328
+ border-top: 2px solid #0073aa;
329
+ border-radius: 50%;
330
+ animation: wp-spin 1s linear infinite;
331
+ margin-right: 8px;
332
+ }
333
+ @keyframes wp-spin {
334
+ 0% { transform: rotate(0deg); }
335
+ 100% { transform: rotate(360deg); }
336
+ }
337
+ .wp-footer {
338
+ background: #f6f7f7;
339
+ border-top: 1px solid #c3c4c7;
340
+ padding: 16px 24px;
341
+ text-align: center;
342
+ font-size: 13px;
343
+ color: #646970;
344
+ }
345
+ .text-center { text-align: center; }
346
+ </style>
347
+ </head>
348
+ <body>
349
+ <div class="wp-container">
350
+ <div class="wp-header">
351
+ <h1>ReactPress</h1>
352
+ <p>5-Minute Installation</p>
353
+ </div>
139
354
 
140
- // In auto mode, skip database testing if --skip-db-setup is provided
141
- if (options.skipDbSetup) {
142
- console.log(chalk.yellow('⚠️ Skipping database connection test (--skip-db-setup enabled)'));
143
- dbTestResult = true;
144
- } else {
145
- dbTestResult = await testDatabaseConnection(answers);
146
- if (dbTestResult === 'retry') {
147
- console.log(chalk.yellow('\n⚠️ Switching to interactive mode for database configuration...\n'));
148
- options.yes = false;
149
- options.auto = false;
150
- continue;
151
- }
355
+ <div class="wp-progress">
356
+ <div class="wp-progress-bar" id="progressBar" style="width: 33%;"></div>
357
+ </div>
358
+
359
+ <div class="wp-content">
360
+ <!-- Step 1: Database Configuration -->
361
+ <div class="wp-step active" id="step1">
362
+ <h2>Database Connection Information</h2>
363
+
364
+ <div class="wp-description">
365
+ You should have this information available from your web hosting provider. If you don't have it, contact them before continuing.
366
+ </div>
367
+
368
+ <table class="wp-form-table">
369
+ <tr>
370
+ <th><label for="dbHost">Database Host</label></th>
371
+ <td>
372
+ <input type="text" id="dbHost" value="127.0.0.1" />
373
+ <p class="description">Usually "localhost" or "127.0.0.1"</p>
374
+ </td>
375
+ </tr>
376
+ <tr>
377
+ <th><label for="dbPort">Database Port</label></th>
378
+ <td>
379
+ <input type="number" id="dbPort" value="3306" />
380
+ <p class="description">Default MySQL port is 3306</p>
381
+ </td>
382
+ </tr>
383
+ <tr>
384
+ <th><label for="dbUser">Username</label></th>
385
+ <td>
386
+ <input type="text" id="dbUser" value="" placeholder="Database username" />
387
+ <p class="description">Your MySQL username</p>
388
+ </td>
389
+ </tr>
390
+ <tr>
391
+ <th><label for="dbPassword">Password</label></th>
392
+ <td>
393
+ <input type="password" id="dbPassword" value="" placeholder="Database password" />
394
+ <p class="description">Your MySQL password</p>
395
+ </td>
396
+ </tr>
397
+ <tr>
398
+ <th><label for="dbName">Database Name</label></th>
399
+ <td>
400
+ <input type="text" id="dbName" value="reactpress" />
401
+ <p class="description">The name of the database you want to use with ReactPress</p>
402
+ </td>
403
+ </tr>
404
+ </table>
405
+
406
+ <div id="dbResult"></div>
407
+
408
+ <p class="text-center">
409
+ <button class="wp-button button-large" onclick="testDatabase()" id="testBtn">
410
+ Test Database Connection
411
+ </button>
412
+ </p>
413
+ </div>
414
+
415
+ <!-- Step 2: Site Configuration -->
416
+ <div class="wp-step" id="step2">
417
+ <h2>Site Information</h2>
418
+
419
+ <div class="wp-description">
420
+ Please provide the following information. Don't worry, you can always change these settings later.
421
+ </div>
422
+
423
+ <table class="wp-form-table">
424
+ <tr>
425
+ <th><label for="clientUrl">Client Site URL</label></th>
426
+ <td>
427
+ <input type="url" id="clientUrl" value="http://localhost:3001" />
428
+ <p class="description">The URL where your ReactPress frontend will be accessible</p>
429
+ </td>
430
+ </tr>
431
+ <tr>
432
+ <th><label for="serverUrl">API Server URL</label></th>
433
+ <td>
434
+ <input type="url" id="serverUrl" value="http://localhost:3002" />
435
+ <p class="description">The URL where your ReactPress API server will run</p>
436
+ </td>
437
+ </tr>
438
+ </table>
439
+
440
+ <div id="installResult"></div>
441
+
442
+ <p class="text-center">
443
+ <button class="wp-button button-large" onclick="installReactPress()" id="installBtn">
444
+ Install & Start ReactPress
445
+ </button>
446
+ </p>
447
+ </div>
448
+
449
+ <!-- Step 3: Success -->
450
+ <div class="wp-step" id="step3">
451
+ <h2>🎉 Installation Complete!</h2>
452
+
453
+ <div class="wp-notice notice-success">
454
+ <p><strong>ReactPress has been installed and is starting...</strong></p>
455
+ </div>
456
+
457
+ <p>Your ReactPress server is now starting up. You can:</p>
458
+
459
+ <ul style="margin: 16px 0 16px 20px;">
460
+ <li>Close this browser window</li>
461
+ <li>The server API will open automatically at <strong>http://localhost:3002/api</strong></li>
462
+ <li>Access your admin panel at <strong>http://localhost:3001/admin</strong> (when client is running)</li>
463
+ </ul>
464
+
465
+ <div class="wp-description">
466
+ <strong>Note:</strong> To restart the server in the future, simply run <code>npx @fecommunity/reactpress-server</code> again.
467
+ </div>
468
+ </div>
469
+ </div>
470
+
471
+ <div class="wp-footer">
472
+ <p>Need help? <a href="https://github.com/fecommunity/reactpress/issues" target="_blank">ReactPress Documentation</a></p>
473
+ </div>
474
+ </div>
475
+
476
+ <script>
477
+ let currentStep = 1;
478
+ const totalSteps = 3;
479
+ let isTestingConnection = false;
480
+
481
+ function updateProgress() {
482
+ const progress = (currentStep / totalSteps) * 100;
483
+ document.getElementById('progressBar').style.width = progress + '%';
152
484
  }
153
- } else {
154
- // 交互式配置收集
155
- console.log(chalk.cyan('📋 Please configure your ReactPress server settings:\n'));
156
485
 
157
- answers = await inquirer.prompt([
158
- {
159
- type: 'input',
160
- name: 'dbHost',
161
- message: 'Database Host:',
162
- default: answers ? answers.dbHost : '127.0.0.1'
163
- },
164
- {
165
- type: 'input',
166
- name: 'dbPort',
167
- message: 'Database Port:',
168
- default: answers ? answers.dbPort : '3306',
169
- validate: (input) => {
170
- const port = parseInt(input);
171
- return port > 0 && port <= 65535 ? true : 'Please enter a valid port number (1-65535)';
172
- }
173
- },
174
- {
175
- type: 'input',
176
- name: 'dbUser',
177
- message: 'Database Username:',
178
- default: answers ? answers.dbUser : 'reactpress'
179
- },
180
- {
181
- type: 'password',
182
- name: 'dbPassword',
183
- message: 'Database Password:',
184
- default: answers ? answers.dbPassword : 'reactpress',
185
- mask: '*'
186
- },
187
- {
188
- type: 'input',
189
- name: 'dbDatabase',
190
- message: 'Database Name:',
191
- default: answers ? answers.dbDatabase : 'reactpress'
192
- },
193
- {
194
- type: 'input',
195
- name: 'clientUrl',
196
- message: 'Client Site URL:',
197
- default: answers ? answers.clientUrl : 'http://localhost:3001',
198
- validate: (input) => {
199
- try {
200
- new URL(input);
201
- return true;
202
- } catch {
203
- return 'Please enter a valid URL (e.g., http://localhost:3001)';
204
- }
486
+ function showStep(step) {
487
+ document.querySelectorAll('.wp-step').forEach(s => s.classList.remove('active'));
488
+ document.getElementById('step' + step).classList.add('active');
489
+ currentStep = step;
490
+ updateProgress();
491
+ }
492
+
493
+ function showNotice(message, type = 'error', containerId = 'dbResult') {
494
+ const container = document.getElementById(containerId);
495
+ container.innerHTML = \`
496
+ <div class="wp-notice notice-\${type}">
497
+ <p>\${message}</p>
498
+ </div>
499
+ \`;
500
+ }
501
+
502
+ function validateDatabaseInputs() {
503
+ const host = document.getElementById('dbHost').value.trim();
504
+ const port = document.getElementById('dbPort').value.trim();
505
+ const user = document.getElementById('dbUser').value.trim();
506
+ const password = document.getElementById('dbPassword').value;
507
+ const database = document.getElementById('dbName').value.trim();
508
+
509
+ if (!host || !port || !user || !database) {
510
+ showNotice('Please fill in all required fields.', 'error');
511
+ return false;
205
512
  }
206
- },
207
- {
208
- type: 'input',
209
- name: 'serverPort',
210
- message: 'Server Port:',
211
- default: answers ? answers.serverPort : (options.port || '3002'),
212
- validate: (input) => {
213
- const port = parseInt(input);
214
- return port > 0 && port <= 65535 ? true : 'Please enter a valid port number (1-65535)';
513
+
514
+ if (isNaN(port) || port < 1 || port > 65535) {
515
+ showNotice('Please enter a valid port number (1-65535).', 'error');
516
+ return false;
215
517
  }
216
- },
217
- {
218
- type: 'confirm',
219
- name: 'skipDbSetup',
220
- message: 'Skip database setup instructions?',
221
- default: false
222
- }
223
- ]);
518
+
519
+ return { host, port, user, password, database };
520
+ }
224
521
 
225
- // Test database connection
226
- if (!answers.skipDbSetup) {
227
- dbTestResult = await testDatabaseConnection(answers);
228
- if (dbTestResult === 'retry') {
229
- console.log(chalk.yellow('\n🔄 Let\'s try the database configuration again...\n'));
230
- continue;
231
- } else if (!dbTestResult) {
232
- const { continueAnyway } = await inquirer.prompt([
233
- {
234
- type: 'confirm',
235
- name: 'continueAnyway',
236
- message: 'Continue with configuration file creation anyway?',
237
- default: false
238
- }
239
- ]);
522
+ async function testDatabase() {
523
+ if (isTestingConnection) return;
524
+
525
+ const dbData = validateDatabaseInputs();
526
+ if (!dbData) return;
527
+
528
+ isTestingConnection = true;
529
+ const testBtn = document.getElementById('testBtn');
530
+ const originalText = testBtn.innerHTML;
240
531
 
241
- if (!continueAnyway) {
242
- console.log(chalk.yellow('⚠️ Initialization cancelled.'));
243
- process.exit(0);
532
+ testBtn.disabled = true;
533
+ testBtn.innerHTML = '<span class="wp-loader"></span>Testing connection...';
534
+
535
+ try {
536
+ const response = await fetch('/test-db', {
537
+ method: 'POST',
538
+ headers: { 'Content-Type': 'application/json' },
539
+ body: JSON.stringify(dbData)
540
+ });
541
+
542
+ const result = await response.json();
543
+
544
+ if (result.success) {
545
+ showNotice(result.message, 'success');
546
+ setTimeout(() => {
547
+ showStep(2);
548
+ }, 1500);
549
+ } else {
550
+ showNotice(result.message, 'error');
551
+ }
552
+ } catch (error) {
553
+ showNotice('Failed to connect to server. Please try again.', 'error');
554
+ } finally {
555
+ isTestingConnection = false;
556
+ testBtn.disabled = false;
557
+ testBtn.innerHTML = originalText;
244
558
  }
245
- dbTestResult = true; // Continue despite failed connection
246
- }
247
- } else {
248
- console.log(chalk.yellow('⚠️ Skipping database connection test.'));
249
- dbTestResult = true;
250
- }
251
- }
252
- }
253
-
254
- const serverUrl = `http://localhost:${answers.serverPort}`;
255
-
256
- // 创建.env文件 - 使用用户输入的配置
257
- const envContent = `# Database Config
258
- DB_HOST=${answers.dbHost}
259
- DB_PORT=${answers.dbPort}
260
- DB_USER=${answers.dbUser}
261
- DB_PASSWD=${answers.dbPassword}
262
- DB_DATABASE=${answers.dbDatabase}
263
-
264
-
265
- # Client Config
266
- CLIENT_SITE_URL=${answers.clientUrl}
267
-
268
- # Server Config
269
- SERVER_SITE_URL=${serverUrl}
270
- `;
271
-
272
- fs.writeFileSync(envPath, envContent);
273
- console.log(chalk.green('\n✅ Configuration saved to .env file'));
274
-
275
- // 更新 options 以使用用户输入的值
276
- options.skipDbSetup = answers.skipDbSetup;
277
- options.database = options.database || 'mysql';
278
- options.port = answers.serverPort;
279
-
280
- if (!options.skipDbSetup) {
281
- console.log(chalk.yellow('\n🗄️ Database setup:'));
282
- console.log(chalk.gray(`Please ensure your ${options.database} server is running and create the database:`));
283
- console.log(chalk.cyan(`CREATE DATABASE reactpress;`));
284
- console.log(chalk.gray(`CREATE USER 'reactpress'@'localhost' IDENTIFIED BY 'reactpress';`));
285
- console.log(chalk.gray(`GRANT ALL PRIVILEGES ON reactpress.* TO 'reactpress'@'localhost';`));
286
- console.log(chalk.gray(`FLUSH PRIVILEGES;`));
287
- }
288
-
289
- console.log(chalk.green('\n🎉 ReactPress Server initialized successfully!'));
290
- console.log(chalk.cyan('\nNext steps:'));
291
- console.log(chalk.gray('1. Start the server: npx @fecommunity/reactpress-server start'));
292
- console.log(chalk.gray('2. Visit API: http://localhost:' + options.port + '/api'));
293
- console.log(chalk.gray('3. API docs: http://localhost:' + options.port + '/api (Swagger UI)'));
294
- });
295
-
296
- // Start command - 启动服务器
297
- program
298
- .command('start')
299
- .description('Start ReactPress server')
300
- .option('-p, --port <port>', 'Server port')
301
- .option('-d, --dev', 'Start in development mode')
302
- .option('--watch', 'Watch for file changes (dev mode)')
303
- .action(async (options) => {
304
- console.log(chalk.blue('🚀 Starting ReactPress Server...\n'));
305
-
306
- const cwd = process.cwd();
307
- const envPath = path.join(cwd, '.env');
308
-
309
- // 检查.env文件是否存在
310
- if (!fs.existsSync(envPath)) {
311
- console.log(chalk.red('❌ Configuration file (.env) not found!'));
312
- console.log(chalk.yellow('Please run: npx @fecommunity/reactpress-server init'));
313
- process.exit(1);
314
- }
315
-
316
- // 检查是否在项目根目录(有server目录)
317
- const serverPath = path.join(cwd, 'server');
318
- const hasServerDir = fs.existsSync(serverPath);
319
-
320
- let serverMainPath;
321
- if (hasServerDir) {
322
- // 在项目根目录,使用相对路径
323
- serverMainPath = path.join(serverPath, 'dist', 'main.js');
324
- const packageDir = serverPath;
325
-
326
- // 检查构建文件是否存在
327
- if (!fs.existsSync(serverMainPath)) {
328
- console.log(chalk.yellow('⚠️ Built server not found, building...'));
329
- try {
330
- execSync('pnpm run build:server', { cwd: cwd, stdio: 'inherit' });
331
- } catch (error) {
332
- console.log(chalk.red('❌ Build failed'));
333
- process.exit(1);
334
559
  }
335
- }
336
- } else {
337
- // 可能在独立安装的@reactpress/server包中
338
- try {
339
- const serverPackagePath = require.resolve('@fecommunity/reactpress-server/package.json');
340
- const serverPackageDir = path.dirname(serverPackagePath);
341
- serverMainPath = path.join(serverPackageDir, 'dist', 'main.js');
342
- } catch (error) {
343
- console.log(chalk.red('❌ ReactPress server package not found!'));
344
- console.log(chalk.yellow('Please install: npm install @fecommunity/reactpress-server'));
345
- process.exit(1);
346
- }
347
- }
348
-
349
- // 设置环境变量
350
- const env = {
351
- ...process.env,
352
- NODE_ENV: options.dev ? 'development' : 'production'
353
- };
354
-
355
- if (options.port) {
356
- env.SERVER_PORT = options.port;
357
- }
358
-
359
- try {
360
- if (options.dev || options.watch) {
361
- console.log(chalk.yellow('🔄 Starting in development mode...'));
362
- if (hasServerDir) {
363
- execSync('pnpm run dev:server', {
364
- cwd: cwd,
365
- stdio: 'inherit',
366
- env
367
- });
368
- } else {
369
- console.log(chalk.yellow('⚠️ Development mode using built server (source code not available)'));
370
- console.log(chalk.cyan('Starting server with NODE_ENV=development...'));
371
- execSync(`node ${serverMainPath}`, {
372
- cwd: process.cwd(),
373
- stdio: 'inherit',
374
- env
375
- });
560
+
561
+ async function installReactPress() {
562
+ const dbData = validateDatabaseInputs();
563
+ if (!dbData) {
564
+ showStep(1);
565
+ return;
566
+ }
567
+
568
+ const clientUrl = document.getElementById('clientUrl').value.trim();
569
+ const serverUrl = document.getElementById('serverUrl').value.trim();
570
+
571
+ if (!clientUrl || !serverUrl) {
572
+ showNotice('Please fill in all site URLs.', 'error', 'installResult');
573
+ return;
574
+ }
575
+
576
+ const installBtn = document.getElementById('installBtn');
577
+ const originalText = installBtn.innerHTML;
578
+
579
+ installBtn.disabled = true;
580
+ installBtn.innerHTML = '<span class="wp-loader"></span>Installing & Starting...';
581
+
582
+ try {
583
+ const response = await fetch('/install', {
584
+ method: 'POST',
585
+ headers: { 'Content-Type': 'application/json' },
586
+ body: JSON.stringify({
587
+ db: dbData,
588
+ site: { clientUrl, serverUrl }
589
+ })
590
+ });
591
+
592
+ const result = await response.json();
593
+
594
+ if (result.success) {
595
+ showStep(3);
596
+ // Auto-close and redirect to server after a delay
597
+ setTimeout(() => {
598
+ if (result.serverUrl) {
599
+ window.open(result.serverUrl + '/api', '_blank');
600
+ }
601
+ }, 3000);
602
+ } else {
603
+ showNotice(result.message, 'error', 'installResult');
604
+ }
605
+ } catch (error) {
606
+ showNotice('Installation failed. Please try again.', 'error', 'installResult');
607
+ } finally {
608
+ installBtn.disabled = false;
609
+ installBtn.innerHTML = originalText;
610
+ }
376
611
  }
377
- } else {
378
- console.log(chalk.green('🌟 Starting in production mode...'));
379
- execSync(`node ${serverMainPath}`, {
380
- cwd: process.cwd(),
381
- stdio: 'inherit',
382
- env
383
- });
384
- }
385
- } catch (error) {
386
- console.log(chalk.red('❌ Failed to start server'));
387
- process.exit(1);
388
- }
389
- });
390
-
391
- // Build command - 构建服务器
392
- program
393
- .command('build')
394
- .description('Build ReactPress server')
395
- .action(() => {
396
- console.log(chalk.blue('🔨 Building ReactPress Server...\n'));
397
-
398
- const cwd = process.cwd();
399
- const serverPath = path.join(cwd, 'server');
400
-
401
- if (!fs.existsSync(serverPath)) {
402
- console.log(chalk.red('❌ Server directory not found!'));
403
- console.log(chalk.yellow('This command should be run from the project root directory.'));
404
- process.exit(1);
405
- }
406
-
407
- try {
408
- execSync('pnpm run build:server', {
409
- cwd: cwd,
410
- stdio: 'inherit'
411
- });
412
- console.log(chalk.green('✅ Build completed successfully!'));
413
- } catch (error) {
414
- console.log(chalk.red('❌ Build failed'));
415
- process.exit(1);
416
- }
417
- });
612
+
613
+ // Initialize
614
+ updateProgress();
615
+ document.getElementById('dbHost').focus();
616
+ </script>
617
+ </body>
618
+ </html>`;
619
+ }
418
620
 
419
- // Dev command - 开发模式启动
420
- program
421
- .command('dev')
422
- .description('Start server in development mode')
423
- .option('-p, --port <port>', 'Server port')
424
- .action((options) => {
425
- console.log(chalk.blue('🔄 Starting ReactPress Server in development mode...\n'));
426
-
427
- // 调用start命令的dev模式
428
- const args = ['start', '--dev'];
429
- if (options.port) {
430
- args.push('--port', options.port);
431
- }
432
-
433
- // 重新调用当前脚本
434
- const child = require('child_process').spawn('node', [__filename, ...args], {
435
- stdio: 'inherit',
436
- env: process.env
437
- });
438
-
439
- child.on('exit', (code) => {
440
- process.exit(code);
441
- });
442
- });
621
+ // Run the main function
622
+ if (require.main === module) {
623
+ main().catch(console.error);
624
+ }
443
625
 
444
- program.parse();
626
+ module.exports = { main, startServer };