@fecommunity/reactpress-server 1.0.0-beta.9 → 1.0.0

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,203 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { program } = require('commander');
4
- const { execSync } = require('child_process');
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
+
5
9
  const path = require('path');
6
10
  const fs = require('fs');
7
- const chalk = require('chalk');
8
- const inquirer = require('inquirer');
9
- const mysql = require('mysql2/promise');
11
+ const { spawn, spawnSync } = require('child_process');
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();
17
+
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
10
34
 
11
- program
12
- .name('@fecommunity/reactpress-server')
13
- .description('ReactPress Server CLI')
14
- .version('1.0.0');
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');
15
48
 
16
- // Database connection testing function
17
- async function testDatabaseConnection(config) {
49
+ // Function to check if PM2 is installed
50
+ function isPM2Installed() {
18
51
  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}'`);
34
-
35
- if (databases.length === 0) {
36
- console.log(chalk.yellow(`⚠️ Database '${config.dbDatabase}' does not exist.`));
37
-
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
- ]);
46
-
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;
54
- }
55
- }
56
- } else {
57
- console.log(chalk.green(`✅ Database '${config.dbDatabase}' exists and is accessible.`));
58
- }
59
-
60
- await connection.end();
52
+ require.resolve('pm2');
61
53
  return true;
62
-
63
- } catch (error) {
64
- console.log(chalk.red('❌ Database connection failed:'));
65
-
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}`));
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;
74
61
  }
75
-
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
- ]);
84
-
85
- return retry ? 'retry' : false;
86
62
  }
87
63
  }
88
64
 
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
112
- }
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
- };
139
-
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
- }
152
- }
153
- } else {
154
- // 交互式配置收集
155
- console.log(chalk.cyan('📋 Please configure your ReactPress server settings:\n'));
156
-
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
- }
205
- }
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)';
215
- }
216
- },
217
- {
218
- type: 'confirm',
219
- name: 'skipDbSetup',
220
- message: 'Skip database setup instructions?',
221
- default: false
222
- }
223
- ]);
224
-
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
- ]);
240
-
241
- if (!continueAnyway) {
242
- console.log(chalk.yellow('⚠️ Initialization cancelled.'));
243
- process.exit(0);
244
- }
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)'));
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
294
71
  });
72
+
73
+ if (installResult.status !== 0) {
74
+ console.error('[ReactPress Server] Failed to install PM2');
75
+ return false;
76
+ }
77
+
78
+ return true;
79
+ }
295
80
 
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'));
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');
313
88
  process.exit(1);
314
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...');
315
95
 
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
- }
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
- }
96
+ // Try to build the server
97
+ const buildResult = spawnSync('npm', ['run', 'build'], {
98
+ stdio: 'inherit',
99
+ cwd: serverDir
100
+ });
358
101
 
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
- });
376
- }
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'));
102
+ if (buildResult.status !== 0) {
103
+ console.error('[ReactPress Server] Failed to build server');
387
104
  process.exit(1);
388
105
  }
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'));
106
+ }
107
+
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';
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
+ });
397
128
 
398
- const cwd = process.cwd();
399
- const serverPath = path.join(cwd, 'server');
129
+ pm2.on('close', (code) => {
130
+ console.log(`[ReactPress Server] PM2 process exited with code ${code}`);
131
+ process.exit(code);
132
+ });
400
133
 
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.'));
134
+ pm2.on('error', (error) => {
135
+ console.error('[ReactPress Server] Failed to start with PM2:', error);
404
136
  process.exit(1);
405
- }
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
+ });
406
144
 
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'));
145
+ pm2.on('close', (code) => {
146
+ console.log(`[ReactPress Server] PM2 process exited with code ${code}`);
147
+ process.exit(code);
148
+ });
149
+
150
+ pm2.on('error', (error) => {
151
+ console.error('[ReactPress Server] Failed to start with PM2:', error);
415
152
  process.exit(1);
416
- }
417
- });
153
+ });
154
+ }
155
+ }
418
156
 
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], {
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...');
162
+
163
+ // Try to build the server
164
+ const buildResult = spawnSync('npm', ['run', 'build'], {
435
165
  stdio: 'inherit',
436
- env: process.env
166
+ cwd: serverDir
437
167
  });
438
168
 
439
- child.on('exit', (code) => {
440
- process.exit(code);
441
- });
442
- });
169
+ if (buildResult.status !== 0) {
170
+ console.error('[ReactPress Server] Failed to build server');
171
+ process.exit(1);
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;
179
+ } else {
180
+ console.log(`[ReactPress Server] Using existing REACTPRESS_ORIGINAL_CWD: ${process.env.REACTPRESS_ORIGINAL_CWD}`);
181
+ }
443
182
 
444
- program.parse();
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';
188
+
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
+ }
196
+ }
197
+
198
+ // Main execution
199
+ if (usePM2) {
200
+ startWithPM2();
201
+ } else {
202
+ startWithNode();
203
+ }
@@ -0,0 +1 @@
1
+ export {};