nodebbs 0.0.0 → 0.0.2

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.
Files changed (59) hide show
  1. package/README.md +378 -249
  2. package/dist/commands/db/generate.d.ts +5 -0
  3. package/dist/commands/db/generate.js +12 -0
  4. package/dist/commands/db/index.d.ts +5 -0
  5. package/dist/commands/db/index.js +11 -0
  6. package/dist/commands/db/migrate.d.ts +5 -0
  7. package/dist/commands/db/migrate.js +12 -0
  8. package/dist/commands/db/push.d.ts +5 -0
  9. package/dist/commands/db/push.js +12 -0
  10. package/dist/commands/db/reset.d.ts +5 -0
  11. package/dist/commands/db/reset.js +22 -0
  12. package/dist/commands/db/seed.d.ts +5 -0
  13. package/dist/commands/db/seed.js +12 -0
  14. package/dist/commands/deploy/index.d.ts +8 -0
  15. package/dist/commands/deploy/index.js +95 -0
  16. package/dist/commands/dev/index.d.ts +9 -0
  17. package/dist/commands/dev/index.js +59 -0
  18. package/dist/commands/logs/api.d.ts +5 -0
  19. package/dist/commands/logs/api.js +11 -0
  20. package/dist/commands/logs/db.d.ts +5 -0
  21. package/dist/commands/logs/db.js +11 -0
  22. package/dist/commands/logs/index.d.ts +5 -0
  23. package/dist/commands/logs/index.js +11 -0
  24. package/dist/commands/logs/redis.d.ts +5 -0
  25. package/dist/commands/logs/redis.js +11 -0
  26. package/dist/commands/logs/web.d.ts +5 -0
  27. package/dist/commands/logs/web.js +11 -0
  28. package/dist/commands/restart/index.d.ts +5 -0
  29. package/dist/commands/restart/index.js +12 -0
  30. package/dist/commands/setup/index.d.ts +5 -0
  31. package/dist/commands/setup/index.js +12 -0
  32. package/dist/commands/shell/api.d.ts +5 -0
  33. package/dist/commands/shell/api.js +11 -0
  34. package/dist/commands/shell/db.d.ts +5 -0
  35. package/dist/commands/shell/db.js +11 -0
  36. package/dist/commands/shell/redis.d.ts +5 -0
  37. package/dist/commands/shell/redis.js +11 -0
  38. package/dist/commands/shell/web.d.ts +5 -0
  39. package/dist/commands/shell/web.js +11 -0
  40. package/dist/commands/status/index.d.ts +5 -0
  41. package/dist/commands/status/index.js +11 -0
  42. package/dist/commands/stop/index.d.ts +8 -0
  43. package/dist/commands/stop/index.js +37 -0
  44. package/dist/templates/docker-compose.lowmem.yml +126 -0
  45. package/dist/templates/docker-compose.prod.yml +120 -0
  46. package/dist/templates/docker-compose.yml +127 -0
  47. package/dist/templates/init-db.sql +14 -0
  48. package/dist/utils/docker.d.ts +8 -0
  49. package/dist/utils/docker.js +101 -0
  50. package/dist/utils/env.d.ts +2 -0
  51. package/dist/utils/env.js +108 -0
  52. package/dist/utils/logger.d.ts +7 -0
  53. package/dist/utils/logger.js +14 -0
  54. package/oclif.manifest.json +441 -26
  55. package/package.json +9 -3
  56. package/dist/commands/hello/index.d.ts +0 -12
  57. package/dist/commands/hello/index.js +0 -19
  58. package/dist/commands/hello/world.d.ts +0 -8
  59. package/dist/commands/hello/world.js +0 -14
@@ -0,0 +1,126 @@
1
+ # 低内存环境 Docker Compose 覆盖配置
2
+ # 适用于 1C1G 或 1C2G 的低配服务器
3
+ # 使用方式: docker compose -f docker-compose.yml -f docker-compose.lowmem.yml up -d
4
+
5
+ services:
6
+ # PostgreSQL 数据库 - 低内存优化
7
+ postgres:
8
+ restart: always
9
+ ports: [] # 不暴露端口到主机,只在内部网络访问
10
+ environment:
11
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
12
+ healthcheck:
13
+ interval: 30s
14
+ timeout: 10s
15
+ retries: 5
16
+ start_period: 40s
17
+ deploy:
18
+ resources:
19
+ limits:
20
+ cpus: '0.5'
21
+ memory: 256M
22
+ reservations:
23
+ cpus: '0.1'
24
+ memory: 128M
25
+ logging:
26
+ driver: "json-file"
27
+ options:
28
+ max-size: "5m"
29
+ max-file: "2"
30
+
31
+ # Redis 缓存 - 低内存优化
32
+ redis:
33
+ restart: always
34
+ command: >
35
+ redis-server
36
+ --requirepass ${REDIS_PASSWORD}
37
+ --appendonly yes
38
+ --appendfsync everysec
39
+ --maxmemory 128mb
40
+ --maxmemory-policy allkeys-lru
41
+ --save 900 1
42
+ --save 300 10
43
+ ports: [] # 不暴露端口到主机
44
+ healthcheck:
45
+ interval: 30s
46
+ timeout: 10s
47
+ retries: 5
48
+ start_period: 20s
49
+ deploy:
50
+ resources:
51
+ limits:
52
+ cpus: '0.3'
53
+ memory: 128M
54
+ reservations:
55
+ cpus: '0.1'
56
+ memory: 64M
57
+ logging:
58
+ driver: "json-file"
59
+ options:
60
+ max-size: "5m"
61
+ max-file: "2"
62
+
63
+ # API 服务 - 低内存优化
64
+ api:
65
+ restart: always
66
+ environment:
67
+ USER_CACHE_TTL: ${USER_CACHE_TTL:-300}
68
+ JWT_SECRET: ${JWT_SECRET}
69
+ CORS_ORIGIN: ${CORS_ORIGIN}
70
+ APP_URL: ${APP_URL}
71
+ # Node.js 内存限制
72
+ NODE_OPTIONS: "--max-old-space-size=384"
73
+ volumes:
74
+ - api_uploads:/app/apps/api/uploads
75
+ healthcheck:
76
+ start_period: 90s # 低内存环境启动更慢
77
+ interval: 60s
78
+ deploy:
79
+ resources:
80
+ limits:
81
+ cpus: '0.7'
82
+ memory: 512M
83
+ reservations:
84
+ cpus: '0.2'
85
+ memory: 256M
86
+ logging:
87
+ driver: "json-file"
88
+ options:
89
+ max-size: "10m"
90
+ max-file: "3"
91
+
92
+ # Web 前端服务 - 低内存优化
93
+ web:
94
+ restart: always
95
+ build:
96
+ args:
97
+ NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
98
+ NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
99
+ environment:
100
+ NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
101
+ NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
102
+ # Node.js 内存限制
103
+ NODE_OPTIONS: "--max-old-space-size=384"
104
+ healthcheck:
105
+ start_period: 90s # 低内存环境启动更慢
106
+ interval: 60s
107
+ deploy:
108
+ resources:
109
+ limits:
110
+ cpus: '0.5'
111
+ memory: 512M
112
+ reservations:
113
+ cpus: '0.2'
114
+ memory: 256M
115
+ logging:
116
+ driver: "json-file"
117
+ options:
118
+ max-size: "10m"
119
+ max-file: "3"
120
+
121
+ # 网络配置
122
+ networks:
123
+ nodebbs-network:
124
+ ipam:
125
+ config:
126
+ - subnet: 172.28.0.0/16
@@ -0,0 +1,120 @@
1
+ # 生产环境 Docker Compose 覆盖配置
2
+ # 使用方式: docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
3
+ # 本文件只包含与 docker-compose.yml 的差异部分
4
+
5
+ services:
6
+ # PostgreSQL 数据库 - 生产环境优化
7
+ postgres:
8
+ restart: always
9
+ ports: [] # 生产环境不暴露端口到主机,只在内部网络访问
10
+ environment:
11
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} # 生产环境必须设置强密码
12
+ healthcheck:
13
+ interval: 30s
14
+ timeout: 10s
15
+ retries: 5
16
+ start_period: 40s
17
+ deploy:
18
+ resources:
19
+ limits:
20
+ cpus: '1'
21
+ memory: 512M
22
+ reservations:
23
+ cpus: '0.25'
24
+ memory: 256M
25
+ logging:
26
+ driver: "json-file"
27
+ options:
28
+ max-size: "10m"
29
+ max-file: "3"
30
+
31
+ # Redis 缓存 - 生产环境优化
32
+ redis:
33
+ restart: always
34
+ command: >
35
+ redis-server
36
+ --requirepass ${REDIS_PASSWORD}
37
+ --appendonly yes
38
+ --appendfsync everysec
39
+ --maxmemory 256mb
40
+ --maxmemory-policy allkeys-lru
41
+ ports: [] # 生产环境不暴露端口到主机
42
+ healthcheck:
43
+ interval: 30s
44
+ timeout: 10s
45
+ retries: 5
46
+ start_period: 20s
47
+ deploy:
48
+ resources:
49
+ limits:
50
+ cpus: '0.5'
51
+ memory: 256M
52
+ reservations:
53
+ cpus: '0.1'
54
+ memory: 128M
55
+ logging:
56
+ driver: "json-file"
57
+ options:
58
+ max-size: "10m"
59
+ max-file: "3"
60
+
61
+ # API 服务 - 生产环境优化
62
+ api:
63
+ restart: always
64
+ environment:
65
+ USER_CACHE_TTL: ${USER_CACHE_TTL:-300} # 生产环境缓存时间更长
66
+ JWT_SECRET: ${JWT_SECRET} # 生产环境必须设置
67
+ CORS_ORIGIN: ${CORS_ORIGIN} # 生产环境必须明确设置
68
+ APP_URL: ${APP_URL} # 生产环境实际域名
69
+ NODE_OPTIONS: "--max-old-space-size=512" # 限制 Node.js 内存使用
70
+ volumes:
71
+ - api_uploads:/app/apps/api/uploads # 生产环境不挂载源代码
72
+ healthcheck:
73
+ start_period: 60s
74
+ deploy:
75
+ resources:
76
+ limits:
77
+ cpus: '1'
78
+ memory: 768M
79
+ reservations:
80
+ cpus: '0.3'
81
+ memory: 384M
82
+ logging:
83
+ driver: "json-file"
84
+ options:
85
+ max-size: "20m"
86
+ max-file: "5"
87
+
88
+ # Web 前端服务 - 生产环境优化
89
+ web:
90
+ restart: always
91
+ build:
92
+ args:
93
+ NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
94
+ NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
95
+ environment:
96
+ NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
97
+ NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL}
98
+ NODE_OPTIONS: "--max-old-space-size=512" # 限制 Node.js 内存使用
99
+ healthcheck:
100
+ start_period: 60s
101
+ deploy:
102
+ resources:
103
+ limits:
104
+ cpus: '1'
105
+ memory: 768M
106
+ reservations:
107
+ cpus: '0.3'
108
+ memory: 384M
109
+ logging:
110
+ driver: "json-file"
111
+ options:
112
+ max-size: "20m"
113
+ max-file: "5"
114
+
115
+ # 网络 - 生产环境使用固定子网
116
+ networks:
117
+ nodebbs-network:
118
+ ipam:
119
+ config:
120
+ - subnet: 172.28.0.0/16
@@ -0,0 +1,127 @@
1
+ services:
2
+ # PostgreSQL 数据库
3
+ postgres:
4
+ image: postgres:16-alpine
5
+ container_name: nodebbs-postgres
6
+ restart: unless-stopped
7
+ environment:
8
+ POSTGRES_USER: postgres
9
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres_password}
10
+ POSTGRES_DB: ${POSTGRES_DB:-nodebbs}
11
+ TZ: Asia/Shanghai
12
+ volumes:
13
+ - postgres_data:/var/lib/postgresql/data
14
+ - ${INIT_DB_PATH:-./scripts/init-db.sql}:/docker-entrypoint-initdb.d/init.sql:ro
15
+ ports:
16
+ - "${POSTGRES_PORT:-5432}:5432"
17
+ healthcheck:
18
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
19
+ interval: 10s
20
+ timeout: 5s
21
+ retries: 5
22
+ networks:
23
+ - nodebbs-network
24
+
25
+ # Redis 缓存
26
+ redis:
27
+ image: redis:7-alpine
28
+ container_name: nodebbs-redis
29
+ restart: unless-stopped
30
+ command: redis-server --requirepass ${REDIS_PASSWORD:-redis_password} --appendonly yes
31
+ environment:
32
+ TZ: Asia/Shanghai
33
+ volumes:
34
+ - redis_data:/data
35
+ ports:
36
+ - "${REDIS_PORT:-6379}:6379"
37
+ healthcheck:
38
+ test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
39
+ interval: 10s
40
+ timeout: 5s
41
+ retries: 5
42
+ networks:
43
+ - nodebbs-network
44
+
45
+ # API 服务
46
+ api:
47
+ build:
48
+ context: . # 从 monorepo 根目录构建(turbo prune 需要完整的 workspace)
49
+ dockerfile: apps/api/Dockerfile
50
+ container_name: nodebbs-api
51
+ restart: unless-stopped
52
+ environment:
53
+ NODE_ENV: production
54
+ APP_NAME: ${APP_NAME:-nodebbs}
55
+ HOST: 0.0.0.0
56
+ PORT: 7100
57
+ DATABASE_URL: postgres://postgres:${POSTGRES_PASSWORD:-postgres_password}@postgres:5432/${POSTGRES_DB:-nodebbs}
58
+ REDIS_URL: redis://default:${REDIS_PASSWORD:-redis_password}@redis:6379/0
59
+ USER_CACHE_TTL: ${USER_CACHE_TTL:-120}
60
+ JWT_SECRET: ${JWT_SECRET:-change-this-to-a-secure-random-string-in-production}
61
+ JWT_ACCESS_TOKEN_EXPIRES_IN: ${JWT_ACCESS_TOKEN_EXPIRES_IN:-1y}
62
+ CORS_ORIGIN: ${CORS_ORIGIN:-*}
63
+ APP_URL: ${APP_URL:-http://localhost:3100}
64
+ TZ: Asia/Shanghai
65
+ volumes:
66
+ - api_uploads:/app/apps/api/uploads
67
+ - ./apps/api/src:/app/apps/api/src:ro
68
+ ports:
69
+ - "${API_PORT:-7100}:7100"
70
+ depends_on:
71
+ postgres:
72
+ condition: service_healthy
73
+ redis:
74
+ condition: service_healthy
75
+ healthcheck:
76
+ test: ["CMD", "node", "-e", "require('http').get('http://localhost:7100/api', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
77
+ interval: 30s
78
+ timeout: 10s
79
+ retries: 3
80
+ start_period: 40s
81
+ networks:
82
+ - nodebbs-network
83
+
84
+ # Web 前端服务
85
+ web:
86
+ build:
87
+ context: . # 从 monorepo 根目录构建(turbo prune 需要完整的 workspace)
88
+ dockerfile: apps/web/Dockerfile
89
+ args:
90
+ NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:7100}
91
+ NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3100}
92
+ container_name: nodebbs-web
93
+ restart: unless-stopped
94
+ environment:
95
+ NODE_ENV: production
96
+ APP_NAME: ${APP_NAME:-nodebbs}
97
+ PORT: 3100
98
+ NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:7100}
99
+ NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-http://localhost:3100}
100
+ TZ: Asia/Shanghai
101
+ ports:
102
+ - "${WEB_PORT:-3100}:3100"
103
+ depends_on:
104
+ api:
105
+ condition: service_healthy
106
+ healthcheck:
107
+ test: ["CMD", "node", "-e", "require('http').get('http://localhost:3100', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"]
108
+ interval: 30s
109
+ timeout: 10s
110
+ retries: 3
111
+ start_period: 40s
112
+ networks:
113
+ - nodebbs-network
114
+
115
+ # 数据卷
116
+ volumes:
117
+ postgres_data:
118
+ driver: local
119
+ redis_data:
120
+ driver: local
121
+ api_uploads:
122
+ driver: local
123
+
124
+ # 网络
125
+ networks:
126
+ nodebbs-network:
127
+ driver: bridge
@@ -0,0 +1,14 @@
1
+ -- PostgreSQL 初始化脚本
2
+ -- 这个脚本会在数据库首次创建时执行
3
+
4
+ -- 创建扩展(如果需要)
5
+ -- CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
6
+ -- CREATE EXTENSION IF NOT EXISTS "pg_trgm";
7
+
8
+ -- 设置时区
9
+ SET timezone = 'Asia/Shanghai';
10
+
11
+ -- 数据库已由 POSTGRES_DB 环境变量创建
12
+ -- 这里可以添加其他初始化 SQL
13
+
14
+ \echo '数据库初始化完成'
@@ -0,0 +1,8 @@
1
+ export declare function getComposeFiles(env: string): Promise<{
2
+ files: string[];
3
+ isBuiltIn: boolean;
4
+ }>;
5
+ export declare function checkDocker(): Promise<void>;
6
+ export declare function runCompose(files: string[], args: string[], isBuiltIn?: boolean): Promise<void>;
7
+ export declare function execCompose(files: string[], service: string, command: string[], isBuiltIn?: boolean): Promise<void>;
8
+ export declare function waitForHealth(files: string[], isBuiltIn?: boolean): Promise<void>;
@@ -0,0 +1,101 @@
1
+ import { execa } from 'execa';
2
+ import { logger } from './logger.js';
3
+ import path from 'node:path';
4
+ import { exists } from 'node:fs';
5
+ import { promisify } from 'node:util';
6
+ import { fileURLToPath } from 'node:url';
7
+ const fileExists = promisify(exists);
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ export async function getComposeFiles(env) {
10
+ const workDir = process.cwd();
11
+ let isBuiltIn = false;
12
+ // Check current directory for docker-compose.yml
13
+ let baseFile = path.join(workDir, 'docker-compose.yml');
14
+ let templateDir = workDir;
15
+ if (!await fileExists(baseFile)) {
16
+ // Use built-in templates
17
+ isBuiltIn = true;
18
+ // In production (dist/utils/docker.js), templates are in dist/templates
19
+ // __dirname is dist/utils, so ../templates points to dist/templates
20
+ templateDir = path.join(__dirname, '..', 'templates');
21
+ baseFile = path.join(templateDir, 'docker-compose.yml');
22
+ // Verify the built-in template exists
23
+ if (!await fileExists(baseFile)) {
24
+ logger.error('内置模板未找到,请确保 CLI 正确安装。');
25
+ throw new Error('Built-in templates not found');
26
+ }
27
+ }
28
+ const files = [baseFile];
29
+ if (env === 'production') {
30
+ files.push(path.join(templateDir, 'docker-compose.prod.yml'));
31
+ }
32
+ else if (env === 'lowmem') {
33
+ files.push(path.join(templateDir, 'docker-compose.lowmem.yml'));
34
+ }
35
+ return { files, isBuiltIn };
36
+ }
37
+ export async function checkDocker() {
38
+ logger.info('正在检查 Docker 环境...');
39
+ try {
40
+ await execa('docker', ['--version']);
41
+ await execa('docker', ['compose', 'version']);
42
+ logger.success('Docker 环境检查通过');
43
+ }
44
+ catch (error) {
45
+ logger.error('未找到 Docker 或 Docker Compose,请先安装。');
46
+ throw error;
47
+ }
48
+ }
49
+ export async function runCompose(files, args, isBuiltIn = false) {
50
+ const composeArgs = files.flatMap(f => ['-f', f]);
51
+ if (isBuiltIn) {
52
+ composeArgs.push('--project-directory', process.cwd());
53
+ // Set INIT_DB_PATH to the built-in sql file
54
+ const templateDir = path.dirname(files[0]);
55
+ process.env.INIT_DB_PATH = path.join(templateDir, 'init-db.sql');
56
+ }
57
+ composeArgs.push(...args);
58
+ // Use stdio: 'inherit' to show output in real-time
59
+ await execa('docker', ['compose', ...composeArgs], { stdio: 'inherit' });
60
+ }
61
+ export async function execCompose(files, service, command, isBuiltIn = false) {
62
+ const composeArgs = files.flatMap(f => ['-f', f]);
63
+ if (isBuiltIn) {
64
+ composeArgs.push('--project-directory', process.cwd());
65
+ }
66
+ composeArgs.push('exec', service, ...command);
67
+ await execa('docker', ['compose', ...composeArgs], { stdio: 'inherit' });
68
+ }
69
+ export async function waitForHealth(files, isBuiltIn = false) {
70
+ logger.info('正在等待服务就绪...');
71
+ // Wait for Postgres
72
+ logger.info('等待 PostgreSQL...');
73
+ const composeArgs = files.flatMap(f => ['-f', f]);
74
+ if (isBuiltIn) {
75
+ composeArgs.push('--project-directory', process.cwd());
76
+ }
77
+ const pgArgs = [...composeArgs, 'exec', '-T', 'postgres', 'pg_isready', '-U', 'postgres'];
78
+ let retries = 15;
79
+ while (retries > 0) {
80
+ try {
81
+ await execa('docker', ['compose', ...pgArgs]);
82
+ logger.success('PostgreSQL 已就绪');
83
+ break;
84
+ }
85
+ catch {
86
+ retries--;
87
+ await new Promise(resolve => setTimeout(resolve, 2000));
88
+ }
89
+ }
90
+ if (retries === 0) {
91
+ logger.warning('PostgreSQL 可能尚未就绪');
92
+ }
93
+ // Wait for Redis
94
+ logger.info('等待 Redis...');
95
+ await new Promise(resolve => setTimeout(resolve, 5000));
96
+ logger.success('Redis 已就绪');
97
+ // Wait for API
98
+ logger.info('等待 API 服务...');
99
+ await new Promise(resolve => setTimeout(resolve, 10000));
100
+ logger.success('API 服务已就绪');
101
+ }
@@ -0,0 +1,2 @@
1
+ export declare function initEnv(): Promise<void>;
2
+ export declare function checkEnv(envType: 'production' | 'lowmem' | 'basic'): Promise<void>;
@@ -0,0 +1,108 @@
1
+ import fs from 'node:fs/promises';
2
+ import { exists } from 'node:fs';
3
+ import { promisify } from 'node:util';
4
+ import { logger } from './logger.js';
5
+ import { confirm } from '@inquirer/prompts';
6
+ import dotenv from 'dotenv';
7
+ const fileExists = promisify(exists);
8
+ export async function initEnv() {
9
+ if (await fileExists('.env')) {
10
+ logger.info('.env 文件已存在,跳过创建');
11
+ return;
12
+ }
13
+ logger.info('正在创建 .env 文件...');
14
+ if (await fileExists('.env.docker.example')) {
15
+ await fs.copyFile('.env.docker.example', '.env');
16
+ logger.warning('请编辑 .env 文件并修改以下配置:');
17
+ logger.warning(' - POSTGRES_PASSWORD (数据库密码)');
18
+ logger.warning(' - REDIS_PASSWORD (Redis 密码)');
19
+ logger.warning(' - JWT_SECRET (JWT 密钥)');
20
+ const edit = await confirm({
21
+ message: '是否现在编辑 .env 文件?',
22
+ default: false
23
+ });
24
+ if (edit) {
25
+ logger.info('请手动编辑 .env 文件后再次运行命令。');
26
+ process.exit(0);
27
+ }
28
+ }
29
+ else {
30
+ // If .env.docker.example doesn't exist, maybe we are in the wrong directory or need to look in project/
31
+ if (await fileExists('project/.env.docker.example')) {
32
+ await fs.copyFile('project/.env.docker.example', '.env');
33
+ logger.success('已从 project/.env.docker.example 复制 .env');
34
+ }
35
+ else {
36
+ logger.warning('未找到 .env.docker.example!跳过 .env 创建。');
37
+ }
38
+ }
39
+ }
40
+ export async function checkEnv(envType) {
41
+ logger.info('正在检查环境配置...');
42
+ // Load .env
43
+ const envConfig = dotenv.config().parsed || {};
44
+ let warnings = 0;
45
+ let errors = 0;
46
+ const defaults = {
47
+ POSTGRES_PASSWORD: ['your_secure_postgres_password_here', 'postgres_password'],
48
+ REDIS_PASSWORD: ['your_secure_redis_password_here', 'redis_password'],
49
+ JWT_SECRET: ['change-this-to-a-secure-random-string-in-production']
50
+ };
51
+ const isProd = envType === 'production' || envType === 'lowmem';
52
+ if (defaults.POSTGRES_PASSWORD.includes(envConfig.POSTGRES_PASSWORD)) {
53
+ if (isProd) {
54
+ logger.error('生产环境必须修改 POSTGRES_PASSWORD');
55
+ errors++;
56
+ }
57
+ else {
58
+ logger.warning('建议修改 POSTGRES_PASSWORD');
59
+ warnings++;
60
+ }
61
+ }
62
+ if (defaults.REDIS_PASSWORD.includes(envConfig.REDIS_PASSWORD)) {
63
+ if (isProd) {
64
+ logger.error('生产环境必须修改 REDIS_PASSWORD');
65
+ errors++;
66
+ }
67
+ else {
68
+ logger.warning('建议修改 REDIS_PASSWORD');
69
+ warnings++;
70
+ }
71
+ }
72
+ if (defaults.JWT_SECRET.includes(envConfig.JWT_SECRET)) {
73
+ if (isProd) {
74
+ logger.error('生产环境必须修改 JWT_SECRET');
75
+ errors++;
76
+ }
77
+ else {
78
+ logger.warning('建议修改 JWT_SECRET');
79
+ warnings++;
80
+ }
81
+ }
82
+ if (isProd) {
83
+ if (!envConfig.CORS_ORIGIN || envConfig.CORS_ORIGIN === '*') {
84
+ logger.warning('生产环境建议设置具体的 CORS_ORIGIN');
85
+ warnings++;
86
+ }
87
+ if (!envConfig.APP_URL || envConfig.APP_URL === 'http://localhost:3100') {
88
+ logger.warning('生产环境建议设置实际的 APP_URL');
89
+ warnings++;
90
+ }
91
+ }
92
+ if (errors > 0) {
93
+ logger.error(`发现 ${errors} 个配置错误,无法继续。`);
94
+ process.exit(1);
95
+ }
96
+ if (warnings > 0) {
97
+ logger.warning(`发现 ${warnings} 个配置警告。`);
98
+ const cont = await confirm({
99
+ message: '是否继续?',
100
+ default: false
101
+ });
102
+ if (!cont)
103
+ process.exit(0);
104
+ }
105
+ else {
106
+ logger.success('环境配置检查通过');
107
+ }
108
+ }
@@ -0,0 +1,7 @@
1
+ export declare const logger: {
2
+ info: (msg: string) => void;
3
+ success: (msg: string) => void;
4
+ warning: (msg: string) => void;
5
+ error: (msg: string) => void;
6
+ header: (msg: string) => void;
7
+ };
@@ -0,0 +1,14 @@
1
+ import chalk from 'chalk';
2
+ export const logger = {
3
+ info: (msg) => console.log(chalk.blue('[信息]'), msg),
4
+ success: (msg) => console.log(chalk.green('[成功]'), msg),
5
+ warning: (msg) => console.log(chalk.yellow('[警告]'), msg),
6
+ error: (msg) => console.log(chalk.red('[错误]'), msg),
7
+ header: (msg) => {
8
+ console.log('');
9
+ console.log(chalk.blue('========================================'));
10
+ console.log(chalk.blue(` ${msg}`));
11
+ console.log(chalk.blue('========================================'));
12
+ console.log('');
13
+ }
14
+ };