nodebbs 0.2.0 → 0.3.0-beta.1
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 +64 -307
- package/dist/commands/db/import.d.ts +9 -0
- package/dist/commands/db/import.js +115 -0
- package/dist/commands/pack/index.js +60 -45
- package/dist/commands/restart/index.js +13 -7
- package/dist/commands/start/index.js +29 -10
- package/dist/commands/upgrade/index.d.ts +8 -0
- package/dist/commands/upgrade/index.js +39 -0
- package/dist/interactive.js +1 -1
- package/dist/templates/docker-compose.build.yml +10 -0
- package/dist/templates/docker-compose.lowmem.yml +10 -45
- package/dist/templates/docker-compose.ports.yml +8 -0
- package/dist/templates/docker-compose.prod.yml +11 -42
- package/dist/templates/docker-compose.yml +9 -13
- package/dist/templates/dockerignore +120 -0
- package/dist/templates/env +6 -10
- package/dist/utils/docker.js +53 -12
- package/dist/utils/env.d.ts +5 -0
- package/dist/utils/env.js +90 -16
- package/dist/utils/selection.d.ts +2 -0
- package/dist/utils/selection.js +17 -19
- package/oclif.manifest.json +122 -65
- package/package.json +1 -1
- package/dist/commands/rebuild/index.d.ts +0 -6
- package/dist/commands/rebuild/index.js +0 -11
- package/dist/templates/init-db.sql +0 -14
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# ============================================
|
|
2
|
+
# .dockerignore - Turborepo Monorepo
|
|
3
|
+
# ============================================
|
|
4
|
+
# 作用:优化 Docker 构建上下文
|
|
5
|
+
# 原则:只把源码和必要文件打包进镜像
|
|
6
|
+
# 构建缓存、依赖、日志、运行时数据等全部排除
|
|
7
|
+
# ============================================
|
|
8
|
+
|
|
9
|
+
# ========================
|
|
10
|
+
# Node / pnpm 依赖
|
|
11
|
+
# ========================
|
|
12
|
+
# node_modules 会在 Docker 内重新安装
|
|
13
|
+
node_modules
|
|
14
|
+
# pnpm 全局存储目录
|
|
15
|
+
.pnpm-store
|
|
16
|
+
|
|
17
|
+
# ========================
|
|
18
|
+
# Turborepo 缓存
|
|
19
|
+
# ========================
|
|
20
|
+
.turbo
|
|
21
|
+
|
|
22
|
+
# ========================
|
|
23
|
+
# 构建输出
|
|
24
|
+
# ========================
|
|
25
|
+
.next # Next.js 构建目录
|
|
26
|
+
dist # Fastify / TS 构建目录
|
|
27
|
+
build
|
|
28
|
+
out # Next.js 静态导出目录
|
|
29
|
+
coverage # 测试覆盖率报告
|
|
30
|
+
|
|
31
|
+
# ========================
|
|
32
|
+
# 日志文件
|
|
33
|
+
# ========================
|
|
34
|
+
*.log
|
|
35
|
+
npm-debug.log*
|
|
36
|
+
yarn-debug.log*
|
|
37
|
+
yarn-error.log*
|
|
38
|
+
pnpm-debug.log*
|
|
39
|
+
lerna-debug.log*
|
|
40
|
+
|
|
41
|
+
# ========================
|
|
42
|
+
# 环境变量文件(运行时通过 docker-compose 注入)
|
|
43
|
+
# ========================
|
|
44
|
+
.env* # 排除所有本地环境文件
|
|
45
|
+
!.env.example # 保留示例文件
|
|
46
|
+
|
|
47
|
+
# ========================
|
|
48
|
+
# 测试相关文件
|
|
49
|
+
# ========================
|
|
50
|
+
__tests__ # 单元测试目录
|
|
51
|
+
*.test.* # 测试文件
|
|
52
|
+
*.spec.* # 测试文件
|
|
53
|
+
.nyc_output # 测试覆盖率缓存
|
|
54
|
+
|
|
55
|
+
# ========================
|
|
56
|
+
# 版本控制相关
|
|
57
|
+
# ========================
|
|
58
|
+
.git
|
|
59
|
+
.gitignore
|
|
60
|
+
.gitattributes
|
|
61
|
+
|
|
62
|
+
# ========================
|
|
63
|
+
# 编辑器 / 操作系统临时文件
|
|
64
|
+
# ========================
|
|
65
|
+
.vscode
|
|
66
|
+
.idea
|
|
67
|
+
.DS_Store
|
|
68
|
+
Thumbs.db
|
|
69
|
+
desktop.ini
|
|
70
|
+
*.swp
|
|
71
|
+
*.swo
|
|
72
|
+
*.swn
|
|
73
|
+
.history
|
|
74
|
+
|
|
75
|
+
# ========================
|
|
76
|
+
# CI/CD 和文档
|
|
77
|
+
# ========================
|
|
78
|
+
.github
|
|
79
|
+
.gitlab-ci.yml
|
|
80
|
+
.travis.yml
|
|
81
|
+
.circleci
|
|
82
|
+
docs
|
|
83
|
+
*.md
|
|
84
|
+
!README.md # 保留 README
|
|
85
|
+
|
|
86
|
+
# ========================
|
|
87
|
+
# 缓存 / 临时文件
|
|
88
|
+
# ========================
|
|
89
|
+
.cache
|
|
90
|
+
tmp
|
|
91
|
+
temp
|
|
92
|
+
.temp
|
|
93
|
+
*.tsbuildinfo # TypeScript 构建缓存
|
|
94
|
+
|
|
95
|
+
# ========================
|
|
96
|
+
# 运行时数据(使用 Docker 卷挂载)
|
|
97
|
+
# ========================
|
|
98
|
+
apps/api/uploads/ # 用户上传文件
|
|
99
|
+
|
|
100
|
+
# ========================
|
|
101
|
+
# 本地数据库文件(开发用)
|
|
102
|
+
# ========================
|
|
103
|
+
*.db
|
|
104
|
+
*.sqlite
|
|
105
|
+
|
|
106
|
+
# ========================
|
|
107
|
+
# 数据库 dump(非 migration)
|
|
108
|
+
# ========================
|
|
109
|
+
dump/
|
|
110
|
+
backups/
|
|
111
|
+
|
|
112
|
+
# ========================
|
|
113
|
+
# 进程管理器
|
|
114
|
+
# ========================
|
|
115
|
+
.pm2
|
|
116
|
+
|
|
117
|
+
# ========================
|
|
118
|
+
# docker-compose 文件(不打包进镜像)
|
|
119
|
+
# ========================
|
|
120
|
+
docker-compose*.yml
|
package/dist/templates/env
CHANGED
|
@@ -44,18 +44,14 @@ APP_URL=http://localhost:3100
|
|
|
44
44
|
# ========================================
|
|
45
45
|
WEB_PORT=3100
|
|
46
46
|
|
|
47
|
-
# API
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
# 生产环境: https://api.yourdomain.com
|
|
51
|
-
NEXT_PUBLIC_API_URL=http://192.168.0.100:7100
|
|
52
|
-
|
|
53
|
-
# 应用 URL(公网访问地址)
|
|
54
|
-
# 开发环境: http://localhost:3100
|
|
55
|
-
# 生产环境: https://yourdomain.com
|
|
56
|
-
NEXT_PUBLIC_APP_URL=http://localhost:3100
|
|
47
|
+
# Web 服务内部访问 API 的地址
|
|
48
|
+
# 通常保持默认 http://api:7100 即可,走 Docker 内部网络
|
|
49
|
+
SERVER_API_URL=http://api:7100
|
|
57
50
|
|
|
58
51
|
# ========================================
|
|
59
52
|
# 时区配置
|
|
60
53
|
# ========================================
|
|
61
54
|
TZ=Asia/Shanghai
|
|
55
|
+
|
|
56
|
+
API_IMAGE=ghcr.io/aiprojecthub/nodebbs-api:latest
|
|
57
|
+
WEB_IMAGE=ghcr.io/aiprojecthub/nodebbs-web:latest
|
package/dist/utils/docker.js
CHANGED
|
@@ -4,7 +4,17 @@ import path from 'node:path';
|
|
|
4
4
|
import { exists } from 'node:fs';
|
|
5
5
|
import { promisify } from 'node:util';
|
|
6
6
|
import { getTemplateDir, getTemplatePath } from './template.js';
|
|
7
|
+
import { getDeploymentMode } from './selection.js';
|
|
7
8
|
const fileExists = promisify(exists);
|
|
9
|
+
async function getDockerEnv() {
|
|
10
|
+
const mode = await getDeploymentMode();
|
|
11
|
+
const envs = { ...process.env, COMPOSE_LOG_LEVEL: 'ERROR' };
|
|
12
|
+
if (mode === 'source') {
|
|
13
|
+
envs['API_IMAGE'] = 'nodebbs-api:local';
|
|
14
|
+
envs['WEB_IMAGE'] = 'nodebbs-web:local';
|
|
15
|
+
}
|
|
16
|
+
return envs;
|
|
17
|
+
}
|
|
8
18
|
/**
|
|
9
19
|
* 获取 Docker Compose 文件路径配置
|
|
10
20
|
*
|
|
@@ -18,21 +28,54 @@ const fileExists = promisify(exists);
|
|
|
18
28
|
export async function getComposeFiles(env) {
|
|
19
29
|
const workDir = process.cwd();
|
|
20
30
|
let isBuiltIn = false;
|
|
31
|
+
// 1. 确定 Base 文件
|
|
21
32
|
// 检查当前目录是否存在 docker-compose.yml
|
|
22
33
|
let baseFile = path.join(workDir, 'docker-compose.yml');
|
|
23
|
-
|
|
34
|
+
// 2. 确定构建配置 (仅源码模式)和环境 Override
|
|
35
|
+
const templateDir = getTemplateDir();
|
|
24
36
|
if (!await fileExists(baseFile)) {
|
|
25
37
|
// 使用内置模板
|
|
26
38
|
isBuiltIn = true;
|
|
27
|
-
templateDir = getTemplateDir();
|
|
28
39
|
baseFile = getTemplatePath('docker-compose.yml');
|
|
29
40
|
}
|
|
30
41
|
const files = [baseFile];
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
// 映射 environment 到对应的 override 文件名
|
|
43
|
+
const envFileMap = {
|
|
44
|
+
'production': 'docker-compose.prod.yml',
|
|
45
|
+
'lowmem': 'docker-compose.lowmem.yml',
|
|
46
|
+
'basic': 'docker-compose.ports.yml'
|
|
47
|
+
};
|
|
48
|
+
if (env in envFileMap) {
|
|
49
|
+
const fileName = envFileMap[env];
|
|
50
|
+
// 注意:basic 对应的 ports.yml 是新增的,可能不存在于旧项目的根目录,
|
|
51
|
+
// 但如果是在 CLI 内部运行,templateDir 里肯定有。
|
|
52
|
+
// 如果是 production/lowmem,用户目录可能有 override。
|
|
53
|
+
const localOverride = path.join(workDir, fileName);
|
|
54
|
+
const templateOverride = path.join(templateDir, fileName);
|
|
55
|
+
// 策略调整:
|
|
56
|
+
// basic -> 总是尝试加载 ports.yml (通常只存在于 template,或者用户没覆盖)
|
|
57
|
+
// production/lowmem -> 尝试加载对应的 override
|
|
58
|
+
if (await fileExists(localOverride)) {
|
|
59
|
+
files.push(localOverride);
|
|
60
|
+
}
|
|
61
|
+
else if (await fileExists(templateOverride)) {
|
|
62
|
+
files.push(templateOverride);
|
|
63
|
+
}
|
|
33
64
|
}
|
|
34
|
-
|
|
35
|
-
|
|
65
|
+
// 3. 确定构建配置 (仅源码模式)
|
|
66
|
+
const mode = await getDeploymentMode();
|
|
67
|
+
if (mode === 'source') {
|
|
68
|
+
const buildFileName = 'docker-compose.build.yml';
|
|
69
|
+
const localBuild = path.join(workDir, buildFileName);
|
|
70
|
+
const templateBuild = path.join(templateDir, buildFileName);
|
|
71
|
+
if (await fileExists(localBuild)) {
|
|
72
|
+
files.push(localBuild);
|
|
73
|
+
}
|
|
74
|
+
else if (isBuiltIn && await fileExists(templateBuild)) {
|
|
75
|
+
// 只有在使用内置 Base 时,才默认加载内置 Build。
|
|
76
|
+
// 如果用户自定义了 Base,通常他们会自己把 build 写进去,或者自己提供 build.yml
|
|
77
|
+
files.push(templateBuild);
|
|
78
|
+
}
|
|
36
79
|
}
|
|
37
80
|
return { files, isBuiltIn };
|
|
38
81
|
}
|
|
@@ -68,16 +111,13 @@ export async function runCompose(files, args, isBuiltIn = false) {
|
|
|
68
111
|
const composeArgs = files.flatMap(f => ['-f', f]);
|
|
69
112
|
if (isBuiltIn) {
|
|
70
113
|
composeArgs.push('--project-directory', process.cwd());
|
|
71
|
-
// 将 INIT_DB_PATH 设置为内置 sql 文件
|
|
72
|
-
const templateDir = path.dirname(files[0]);
|
|
73
|
-
process.env.INIT_DB_PATH = path.join(templateDir, 'init-db.sql');
|
|
74
114
|
}
|
|
75
115
|
composeArgs.push(...args);
|
|
76
116
|
// 使用 stdio: 'inherit' 实时显示输出
|
|
77
117
|
// 设置 COMPOSE_LOG_LEVEL=ERROR 抑制变量未设置的警告
|
|
78
118
|
await execa('docker', ['compose', ...composeArgs], {
|
|
79
119
|
stdio: 'inherit',
|
|
80
|
-
env:
|
|
120
|
+
env: await getDockerEnv()
|
|
81
121
|
});
|
|
82
122
|
}
|
|
83
123
|
/**
|
|
@@ -99,7 +139,7 @@ export async function execCompose(files, service, command, isBuiltIn = false) {
|
|
|
99
139
|
composeArgs.push('exec', service, ...command);
|
|
100
140
|
await execa('docker', ['compose', ...composeArgs], {
|
|
101
141
|
stdio: 'inherit',
|
|
102
|
-
env:
|
|
142
|
+
env: await getDockerEnv()
|
|
103
143
|
});
|
|
104
144
|
}
|
|
105
145
|
/**
|
|
@@ -121,10 +161,11 @@ export async function waitForHealth(files, isBuiltIn = false) {
|
|
|
121
161
|
composeArgs.push('--project-directory', process.cwd());
|
|
122
162
|
}
|
|
123
163
|
const pgArgs = [...composeArgs, 'exec', '-T', 'postgres', 'pg_isready', '-U', 'postgres'];
|
|
164
|
+
const envs = await getDockerEnv();
|
|
124
165
|
let retries = 15;
|
|
125
166
|
while (retries > 0) {
|
|
126
167
|
try {
|
|
127
|
-
await execa('docker', ['compose', ...pgArgs]);
|
|
168
|
+
await execa('docker', ['compose', ...pgArgs], { env: envs });
|
|
128
169
|
logger.success('PostgreSQL 已就绪');
|
|
129
170
|
break;
|
|
130
171
|
}
|
package/dist/utils/env.d.ts
CHANGED
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
export declare function getEnvValue(key: string): Promise<string | undefined>;
|
|
2
|
+
export declare function setEnvValue(key: string, value: string): Promise<void>;
|
|
3
|
+
export declare function unsetEnvValue(key: string): Promise<void>;
|
|
1
4
|
export declare function initEnv(): Promise<void>;
|
|
5
|
+
export declare function initDockerIgnore(): Promise<void>;
|
|
6
|
+
export declare function initImageEnv(): Promise<void>;
|
|
2
7
|
export declare function checkEnv(envType: 'production' | 'lowmem' | 'basic'): Promise<void>;
|
package/dist/utils/env.js
CHANGED
|
@@ -6,12 +6,52 @@ import { confirm } from '@inquirer/prompts';
|
|
|
6
6
|
import dotenv from 'dotenv';
|
|
7
7
|
import { getTemplatePath } from './template.js';
|
|
8
8
|
const fileExists = promisify(exists);
|
|
9
|
-
export async function
|
|
9
|
+
export async function getEnvValue(key) {
|
|
10
|
+
if (!await fileExists('.env'))
|
|
11
|
+
return undefined;
|
|
12
|
+
try {
|
|
13
|
+
const content = await fs.readFile('.env', 'utf-8');
|
|
14
|
+
const config = dotenv.parse(content);
|
|
15
|
+
return config[key];
|
|
16
|
+
}
|
|
17
|
+
catch (e) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function setEnvValue(key, value) {
|
|
22
|
+
let content = '';
|
|
10
23
|
if (await fileExists('.env')) {
|
|
11
|
-
|
|
24
|
+
content = await fs.readFile('.env', 'utf-8');
|
|
25
|
+
}
|
|
26
|
+
const regex = new RegExp(`^${key}=.*`, 'm');
|
|
27
|
+
if (regex.test(content)) {
|
|
28
|
+
content = content.replace(regex, `${key}=${value}`);
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
if (content && !content.endsWith('\n'))
|
|
32
|
+
content += '\n';
|
|
33
|
+
content += `${key}=${value}\n`;
|
|
34
|
+
}
|
|
35
|
+
await fs.writeFile('.env', content, 'utf-8');
|
|
36
|
+
}
|
|
37
|
+
export async function unsetEnvValue(key) {
|
|
38
|
+
if (!await fileExists('.env'))
|
|
12
39
|
return;
|
|
40
|
+
let content = await fs.readFile('.env', 'utf-8');
|
|
41
|
+
const regex = new RegExp(`^${key}=.*\n?`, 'm');
|
|
42
|
+
if (regex.test(content)) {
|
|
43
|
+
content = content.replace(regex, '');
|
|
44
|
+
await fs.writeFile('.env', content, 'utf-8');
|
|
13
45
|
}
|
|
14
|
-
|
|
46
|
+
}
|
|
47
|
+
export async function initEnv() {
|
|
48
|
+
const currentEnv = await fileExists('.env') ? dotenv.parse(await fs.readFile('.env', 'utf-8')) : {};
|
|
49
|
+
// 如果已存在关键配置(如 POSTGRES_PASSWORD),则视为无需初始化
|
|
50
|
+
if (currentEnv.POSTGRES_PASSWORD) {
|
|
51
|
+
logger.info('.env 文件已配置,跳过创建');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
logger.info('正在创建/更新 .env 文件...');
|
|
15
55
|
// 检查模板文件
|
|
16
56
|
let templateContent = '';
|
|
17
57
|
let sourceFile = '';
|
|
@@ -54,14 +94,10 @@ export async function initEnv() {
|
|
|
54
94
|
message: '设置 API_PORT (后端端口):',
|
|
55
95
|
default: '7100'
|
|
56
96
|
});
|
|
57
|
-
const
|
|
58
|
-
const nextPublicApiUrl = await input({
|
|
59
|
-
message: '设置 NEXT_PUBLIC_API_URL (前端访问后端的地址):',
|
|
60
|
-
default: defaultApiUrl
|
|
61
|
-
});
|
|
97
|
+
const serverApiUrl = `http://api:${apiPort}`;
|
|
62
98
|
const defaultAppUrl = `http://localhost:${webPort}`;
|
|
63
|
-
const
|
|
64
|
-
message: '设置
|
|
99
|
+
const appUrl = await input({
|
|
100
|
+
message: '设置 APP_URL (应用访问地址):',
|
|
65
101
|
default: defaultAppUrl
|
|
66
102
|
});
|
|
67
103
|
const corsOrigin = await input({
|
|
@@ -75,8 +111,8 @@ export async function initEnv() {
|
|
|
75
111
|
console.log(`JWT_SECRET: ${jwtSecret.substring(0, 3)}******`);
|
|
76
112
|
console.log(`WEB_PORT: ${webPort}`);
|
|
77
113
|
console.log(`API_PORT: ${apiPort}`);
|
|
78
|
-
console.log(`
|
|
79
|
-
console.log(`
|
|
114
|
+
console.log(`APP_URL: ${appUrl}`);
|
|
115
|
+
console.log(`SERVER_API_URL: ${serverApiUrl}`);
|
|
80
116
|
console.log(`CORS_ORIGIN: ${corsOrigin}`);
|
|
81
117
|
console.log('==========================================\n');
|
|
82
118
|
// 3. 确认生成
|
|
@@ -98,11 +134,9 @@ export async function initEnv() {
|
|
|
98
134
|
'JWT_SECRET': jwtSecret,
|
|
99
135
|
'WEB_PORT': webPort,
|
|
100
136
|
'API_PORT': apiPort,
|
|
101
|
-
'
|
|
102
|
-
'
|
|
137
|
+
'SERVER_API_URL': serverApiUrl,
|
|
138
|
+
'APP_URL': appUrl,
|
|
103
139
|
'CORS_ORIGIN': corsOrigin,
|
|
104
|
-
// 如果 API_PORT 变了,模板里可能还需要联动修改 APP_URL 用于 OAuth 回调,这里简单处理
|
|
105
|
-
'APP_URL': nextPublicAppUrl
|
|
106
140
|
};
|
|
107
141
|
// 针对每个 Key 进行替换。
|
|
108
142
|
// 策略:查找 `KEY=...` 并替换为 `KEY=newValue`
|
|
@@ -118,6 +152,11 @@ export async function initEnv() {
|
|
|
118
152
|
}
|
|
119
153
|
}
|
|
120
154
|
// 5. 写入文件
|
|
155
|
+
// 如果原文件中有 DEPLOY_ENV 等其他配置,保留它们
|
|
156
|
+
// 简单策略:如果 currentEnv 有 DEPLOY_ENV,确保 newEnv 里也有
|
|
157
|
+
if (currentEnv.DEPLOY_ENV && !newEnv.includes('DEPLOY_ENV=')) {
|
|
158
|
+
newEnv += `\nDEPLOY_ENV=${currentEnv.DEPLOY_ENV}`;
|
|
159
|
+
}
|
|
121
160
|
await fs.writeFile('.env', newEnv, 'utf-8');
|
|
122
161
|
// isBuiltIn is not defined in the new logic, need to determine it
|
|
123
162
|
// based on whether sourceFile was .env.docker.example or a template path
|
|
@@ -129,6 +168,41 @@ export async function initEnv() {
|
|
|
129
168
|
logger.success(`已从 ${sourceFile} 复制并填充配置生成 .env`);
|
|
130
169
|
}
|
|
131
170
|
}
|
|
171
|
+
export async function initDockerIgnore() {
|
|
172
|
+
if (await fileExists('.dockerignore')) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
logger.info('正在创建 .dockerignore 文件...');
|
|
176
|
+
const templatePath = getTemplatePath('dockerignore');
|
|
177
|
+
const content = await fs.readFile(templatePath, 'utf-8');
|
|
178
|
+
await fs.writeFile('.dockerignore', content, 'utf-8');
|
|
179
|
+
logger.success('已创建 .dockerignore');
|
|
180
|
+
}
|
|
181
|
+
export async function initImageEnv() {
|
|
182
|
+
const { getDeploymentMode } = await import('./selection.js');
|
|
183
|
+
const mode = await getDeploymentMode();
|
|
184
|
+
logger.info(`当前部署模式: ${mode === 'image' ? '镜像部署' : '源码部署'}`);
|
|
185
|
+
if (mode !== 'image')
|
|
186
|
+
return;
|
|
187
|
+
logger.info('检测到镜像部署模式,请配置镜像地址...');
|
|
188
|
+
// initEnv 已经执行,尝试从 .env 获取默认值
|
|
189
|
+
const currentApiImage = await getEnvValue('API_IMAGE') || 'ghcr.io/aiprojecthub/nodebbs-api:latest';
|
|
190
|
+
const currentWebImage = await getEnvValue('WEB_IMAGE') || 'ghcr.io/aiprojecthub/nodebbs-web:latest';
|
|
191
|
+
let defaultVersion = 'latest';
|
|
192
|
+
const match = currentApiImage.match(/:([^:]+)$/);
|
|
193
|
+
if (match)
|
|
194
|
+
defaultVersion = match[1];
|
|
195
|
+
const { input } = await import('@inquirer/prompts');
|
|
196
|
+
const version = await input({
|
|
197
|
+
message: '设置镜像版本 (默认为 latest):',
|
|
198
|
+
default: defaultVersion
|
|
199
|
+
});
|
|
200
|
+
// 仅替换版本号部分
|
|
201
|
+
const newApiImage = currentApiImage.replace(/:[^:]+$/, `:${version}`);
|
|
202
|
+
const newWebImage = currentWebImage.replace(/:[^:]+$/, `:${version}`);
|
|
203
|
+
await setEnvValue('API_IMAGE', newApiImage);
|
|
204
|
+
await setEnvValue('WEB_IMAGE', newWebImage);
|
|
205
|
+
}
|
|
132
206
|
export async function checkEnv(envType) {
|
|
133
207
|
logger.info('正在检查环境配置...');
|
|
134
208
|
// 加载 .env
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type EnvType = 'production' | 'lowmem' | 'basic';
|
|
2
|
+
export type DeploymentMode = 'source' | 'image';
|
|
2
3
|
export declare const EnvFlag: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
3
4
|
export declare function setStoredEnv(env: EnvType): Promise<void>;
|
|
5
|
+
export declare function getDeploymentMode(): Promise<DeploymentMode>;
|
|
4
6
|
export declare function clearStoredEnv(): Promise<void>;
|
|
5
7
|
export declare function selectEnvironment(env?: string, options?: {
|
|
6
8
|
prompt?: string;
|
package/dist/utils/selection.js
CHANGED
|
@@ -3,39 +3,37 @@ import { select } from '@inquirer/prompts';
|
|
|
3
3
|
import { logger } from './logger.js';
|
|
4
4
|
import fs from 'node:fs/promises';
|
|
5
5
|
import path from 'node:path';
|
|
6
|
-
|
|
6
|
+
import { getEnvValue, setEnvValue, unsetEnvValue } from './env.js';
|
|
7
7
|
export const EnvFlag = Flags.string({
|
|
8
8
|
char: 'e',
|
|
9
9
|
description: '部署环境 (production, lowmem, basic)',
|
|
10
10
|
options: ['production', 'lowmem', 'basic'],
|
|
11
11
|
});
|
|
12
12
|
async function getStoredEnv() {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (['production', 'lowmem', 'basic'].includes(env)) {
|
|
17
|
-
return env;
|
|
18
|
-
}
|
|
13
|
+
const env = await getEnvValue('DEPLOY_ENV');
|
|
14
|
+
if (env && ['production', 'lowmem', 'basic'].includes(env)) {
|
|
15
|
+
return env;
|
|
19
16
|
}
|
|
20
|
-
catch { }
|
|
21
17
|
return null;
|
|
22
18
|
}
|
|
23
19
|
export async function setStoredEnv(env) {
|
|
20
|
+
await setEnvValue('DEPLOY_ENV', env);
|
|
21
|
+
}
|
|
22
|
+
export async function getDeploymentMode() {
|
|
23
|
+
// 自动检测:检查当前目录下是否有 package.json 且 name='nodebbs-forum'
|
|
24
24
|
try {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const packageJsonPath = path.resolve(process.cwd(), 'package.json');
|
|
26
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
27
|
+
const pkg = JSON.parse(content);
|
|
28
|
+
if (pkg.name === 'nodebbs-forum') {
|
|
29
|
+
return 'source';
|
|
30
|
+
}
|
|
29
31
|
}
|
|
32
|
+
catch { }
|
|
33
|
+
return 'image';
|
|
30
34
|
}
|
|
31
35
|
export async function clearStoredEnv() {
|
|
32
|
-
|
|
33
|
-
const filepath = path.resolve(process.cwd(), ENV_MARKER_FILE);
|
|
34
|
-
await fs.rm(filepath, { force: true });
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
logger.warning(`无法清除环境标记: ${error}`);
|
|
38
|
-
}
|
|
36
|
+
await unsetEnvValue('DEPLOY_ENV');
|
|
39
37
|
}
|
|
40
38
|
export async function selectEnvironment(env, options = {}) {
|
|
41
39
|
if (env) {
|