nodebbs 0.0.2 → 0.0.3
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 +83 -99
- package/dist/commands/clean/index.d.ts +11 -0
- package/dist/commands/clean/index.js +93 -0
- package/dist/commands/db/backup.d.ts +9 -0
- package/dist/commands/db/backup.js +78 -0
- package/dist/commands/db/generate.d.ts +3 -0
- package/dist/commands/db/generate.js +9 -3
- package/dist/commands/db/index.d.ts +3 -0
- package/dist/commands/db/index.js +9 -3
- package/dist/commands/db/migrate.d.ts +3 -0
- package/dist/commands/db/migrate.js +8 -2
- package/dist/commands/db/push.d.ts +3 -0
- package/dist/commands/db/push.js +8 -2
- package/dist/commands/db/reset.d.ts +3 -0
- package/dist/commands/db/reset.js +10 -4
- package/dist/commands/db/seed.d.ts +3 -0
- package/dist/commands/db/seed.js +9 -3
- package/dist/commands/logs/api.d.ts +3 -0
- package/dist/commands/logs/api.js +8 -2
- package/dist/commands/logs/db.d.ts +3 -0
- package/dist/commands/logs/db.js +8 -2
- package/dist/commands/logs/index.d.ts +3 -0
- package/dist/commands/logs/index.js +8 -2
- package/dist/commands/logs/redis.d.ts +3 -0
- package/dist/commands/logs/redis.js +8 -2
- package/dist/commands/logs/web.d.ts +3 -0
- package/dist/commands/logs/web.js +9 -3
- package/dist/commands/restart/index.d.ts +3 -0
- package/dist/commands/restart/index.js +10 -2
- package/dist/commands/shell/api.d.ts +3 -0
- package/dist/commands/shell/api.js +9 -3
- package/dist/commands/shell/db.d.ts +3 -0
- package/dist/commands/shell/db.js +9 -3
- package/dist/commands/shell/redis.d.ts +3 -0
- package/dist/commands/shell/redis.js +9 -3
- package/dist/commands/shell/web.d.ts +3 -0
- package/dist/commands/shell/web.js +9 -3
- package/dist/commands/{dev → start}/index.d.ts +2 -2
- package/dist/commands/start/index.js +110 -0
- package/dist/commands/status/index.d.ts +3 -0
- package/dist/commands/status/index.js +8 -2
- package/dist/commands/stop/index.d.ts +1 -0
- package/dist/commands/stop/index.js +7 -1
- package/dist/templates/env +61 -0
- package/dist/utils/docker.js +3 -11
- package/dist/utils/env.js +19 -9
- package/dist/utils/selection.d.ts +5 -0
- package/dist/utils/selection.js +24 -0
- package/dist/utils/template.d.ts +11 -0
- package/dist/utils/template.js +18 -0
- package/oclif.manifest.json +380 -89
- package/package.json +21 -21
- package/dist/commands/deploy/index.d.ts +0 -8
- package/dist/commands/deploy/index.js +0 -95
- package/dist/commands/dev/index.js +0 -59
- package/dist/commands/setup/index.d.ts +0 -5
- package/dist/commands/setup/index.js +0 -12
package/README.md
CHANGED
|
@@ -40,36 +40,24 @@ npx nodebbs [command]
|
|
|
40
40
|
|
|
41
41
|
## 🚀 快速开始
|
|
42
42
|
|
|
43
|
-
### 1.
|
|
43
|
+
### 1. 启动服务
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
#
|
|
47
|
-
npx nodebbs
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
这会创建 `.env` 文件,请编辑该文件并修改:
|
|
51
|
-
- `POSTGRES_PASSWORD` - 数据库密码
|
|
52
|
-
- `REDIS_PASSWORD` - Redis 密码
|
|
53
|
-
- `JWT_SECRET` - JWT 密钥
|
|
46
|
+
# 启动所有服务(生产模式,包含完整检查)
|
|
47
|
+
npx nodebbs start
|
|
54
48
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```bash
|
|
58
|
-
# 启动所有服务
|
|
59
|
-
npx nodebbs dev
|
|
60
|
-
|
|
61
|
-
# 或者重新构建并启动
|
|
62
|
-
npx nodebbs dev --rebuild
|
|
49
|
+
# 重新构建并启动(跳过检查,用于更新)
|
|
50
|
+
npx nodebbs start --build
|
|
63
51
|
```
|
|
64
52
|
|
|
65
|
-
###
|
|
53
|
+
### 2. 查看服务状态
|
|
66
54
|
|
|
67
55
|
```bash
|
|
68
56
|
# 查看所有服务状态
|
|
69
57
|
npx nodebbs status
|
|
70
58
|
```
|
|
71
59
|
|
|
72
|
-
###
|
|
60
|
+
### 3. 访问应用
|
|
73
61
|
|
|
74
62
|
- **Web 前端**: http://localhost:3100
|
|
75
63
|
- **API 文档**: http://localhost:7100/docs
|
|
@@ -77,62 +65,47 @@ npx nodebbs status
|
|
|
77
65
|
|
|
78
66
|
## 📚 命令参考
|
|
79
67
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
#### `nodebbs setup`
|
|
83
|
-
初始化项目(首次使用)
|
|
68
|
+
#### `nodebbs start`
|
|
69
|
+
启动 NodeBBS 服务
|
|
84
70
|
|
|
85
71
|
```bash
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
#### `nodebbs dev`
|
|
90
|
-
启动开发环境
|
|
91
|
-
|
|
92
|
-
```bash
|
|
93
|
-
# 启动所有服务
|
|
94
|
-
npx nodebbs dev
|
|
95
|
-
|
|
96
|
-
# 重新构建并启动
|
|
97
|
-
npx nodebbs dev --rebuild
|
|
98
|
-
npx nodebbs dev -r
|
|
72
|
+
# 交互式启动(推荐初次使用)
|
|
73
|
+
npx nodebbs start
|
|
99
74
|
|
|
100
75
|
# 指定环境启动
|
|
101
|
-
npx nodebbs
|
|
102
|
-
|
|
103
|
-
|
|
76
|
+
npx nodebbs start -e production
|
|
77
|
+
|
|
78
|
+
# 重新构建并启动(跳过检查)
|
|
79
|
+
npx nodebbs start --build
|
|
104
80
|
```
|
|
105
81
|
|
|
106
82
|
**参数**:
|
|
107
83
|
- `-e, --env` - 部署环境(production, lowmem, basic)
|
|
108
|
-
- `-
|
|
84
|
+
- `-b, --build` - 重新构建镜像并启动(跳过健康检查和初始化)
|
|
109
85
|
|
|
110
|
-
|
|
111
|
-
部署到生产环境
|
|
112
|
-
|
|
113
|
-
```bash
|
|
114
|
-
# 完整部署流程
|
|
115
|
-
npx nodebbs deploy
|
|
116
|
-
|
|
117
|
-
# 指定环境部署
|
|
118
|
-
npx nodebbs deploy -e production
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
包含:
|
|
86
|
+
启动流程包含:
|
|
122
87
|
- Docker 环境检查
|
|
123
88
|
- 环境变量验证
|
|
124
89
|
- 镜像构建
|
|
125
90
|
- 服务启动
|
|
126
|
-
-
|
|
127
|
-
-
|
|
91
|
+
- 健康检查(默认开启,使用 `--build` 跳过)
|
|
92
|
+
- 数据库初始化(默认开启,使用 `--build` 跳过)
|
|
93
|
+
|
|
94
|
+
#### `nodebbs restart`
|
|
95
|
+
重启所有服务(不更新镜像,支持环境选择)
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
npx nodebbs restart
|
|
99
|
+
npx nodebbs restart -e production
|
|
100
|
+
```
|
|
128
101
|
|
|
129
102
|
#### `nodebbs stop`
|
|
130
|
-
|
|
103
|
+
停止服务(支持环境选择)
|
|
131
104
|
|
|
132
105
|
```bash
|
|
133
|
-
# 停止所有服务
|
|
134
106
|
npx nodebbs stop
|
|
135
|
-
|
|
107
|
+
npx nodebbs stop -e production
|
|
108
|
+
```
|
|
136
109
|
# 停止服务并删除数据卷(危险!)
|
|
137
110
|
npx nodebbs stop --volumes
|
|
138
111
|
npx nodebbs stop -v
|
|
@@ -141,27 +114,25 @@ npx nodebbs stop -v
|
|
|
141
114
|
**参数**:
|
|
142
115
|
- `-v, --volumes` - 同时删除数据卷
|
|
143
116
|
|
|
144
|
-
#### `nodebbs restart`
|
|
145
|
-
重启所有服务
|
|
146
|
-
|
|
147
|
-
```bash
|
|
148
|
-
npx nodebbs restart
|
|
149
|
-
```
|
|
150
|
-
|
|
151
117
|
#### `nodebbs status`
|
|
152
118
|
查看服务状态
|
|
153
119
|
|
|
154
120
|
```bash
|
|
155
121
|
npx nodebbs status
|
|
122
|
+
# 查看指定环境状态
|
|
123
|
+
npx nodebbs status -e production
|
|
156
124
|
```
|
|
157
125
|
|
|
158
126
|
### 日志管理
|
|
159
127
|
|
|
160
128
|
#### `nodebbs logs`
|
|
161
|
-
|
|
129
|
+
查看服务日志
|
|
162
130
|
|
|
163
131
|
```bash
|
|
132
|
+
# 查看所有日志
|
|
164
133
|
npx nodebbs logs
|
|
134
|
+
# 查看指定环境日志
|
|
135
|
+
npx nodebbs logs -e production
|
|
165
136
|
```
|
|
166
137
|
|
|
167
138
|
#### `nodebbs logs:api`
|
|
@@ -194,11 +165,14 @@ npx nodebbs logs:redis
|
|
|
194
165
|
|
|
195
166
|
### Shell 访问
|
|
196
167
|
|
|
197
|
-
#### `nodebbs shell
|
|
198
|
-
|
|
168
|
+
#### `nodebbs shell`
|
|
169
|
+
进入容器 Shell (支持 api, web, db, redis)
|
|
199
170
|
|
|
200
171
|
```bash
|
|
172
|
+
# 进入 API 容器
|
|
201
173
|
npx nodebbs shell:api
|
|
174
|
+
# 进入生产环境 API 容器
|
|
175
|
+
npx nodebbs shell:api -e production
|
|
202
176
|
```
|
|
203
177
|
|
|
204
178
|
#### `nodebbs shell:web`
|
|
@@ -266,30 +240,49 @@ npx nodebbs db:seed
|
|
|
266
240
|
npx nodebbs db:reset
|
|
267
241
|
```
|
|
268
242
|
|
|
243
|
+
#### `nodebbs db:backup`
|
|
244
|
+
备份数据库
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
npx nodebbs db:backup
|
|
248
|
+
npx nodebbs db:backup -o backup.sql
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
#### `nodebbs clean`
|
|
252
|
+
清理 Docker 缓存和残留资源
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
# 交互式选择清理项目
|
|
256
|
+
npx nodebbs clean
|
|
257
|
+
|
|
258
|
+
# 自动清理所有(构建缓存、无用镜像、网络)
|
|
259
|
+
npx nodebbs clean -a
|
|
260
|
+
|
|
261
|
+
# 仅清理构建缓存
|
|
262
|
+
npx nodebbs clean --cache
|
|
263
|
+
```
|
|
264
|
+
|
|
269
265
|
## 🎯 使用场景
|
|
270
266
|
|
|
271
267
|
### 场景 1:新开发者加入项目
|
|
272
268
|
|
|
273
269
|
```bash
|
|
274
|
-
# 1.
|
|
275
|
-
npx nodebbs
|
|
270
|
+
# 1. 启动服务
|
|
271
|
+
npx nodebbs start
|
|
276
272
|
|
|
277
|
-
# 2.
|
|
278
|
-
npx nodebbs dev
|
|
279
|
-
|
|
280
|
-
# 3. 初始化数据库
|
|
273
|
+
# 2. 初始化数据库
|
|
281
274
|
npx nodebbs db:push
|
|
282
275
|
npx nodebbs db:seed
|
|
283
276
|
|
|
284
|
-
#
|
|
277
|
+
# 3. 查看服务状态
|
|
285
278
|
npx nodebbs status
|
|
286
279
|
```
|
|
287
280
|
|
|
288
281
|
### 场景 2:日常开发
|
|
289
282
|
|
|
290
283
|
```bash
|
|
291
|
-
#
|
|
292
|
-
npx nodebbs
|
|
284
|
+
# 启动服务
|
|
285
|
+
npx nodebbs start
|
|
293
286
|
|
|
294
287
|
# 查看 API 日志
|
|
295
288
|
npx nodebbs logs:api
|
|
@@ -311,7 +304,7 @@ npx nodebbs db:reset
|
|
|
311
304
|
npx nodebbs stop
|
|
312
305
|
|
|
313
306
|
# 重新构建并启动
|
|
314
|
-
npx nodebbs
|
|
307
|
+
npx nodebbs start --build
|
|
315
308
|
|
|
316
309
|
# 查看日志确认启动成功
|
|
317
310
|
npx nodebbs logs
|
|
@@ -321,7 +314,7 @@ npx nodebbs logs
|
|
|
321
314
|
|
|
322
315
|
```bash
|
|
323
316
|
# 部署到生产环境
|
|
324
|
-
npx nodebbs
|
|
317
|
+
npx nodebbs start -e production
|
|
325
318
|
|
|
326
319
|
# 查看服务状态
|
|
327
320
|
npx nodebbs status
|
|
@@ -355,13 +348,11 @@ npx nodebbs shell:db
|
|
|
355
348
|
|
|
356
349
|
| Makefile 命令 | NodeBBS CLI 命令 |
|
|
357
350
|
|--------------|-----------------|
|
|
358
|
-
| `make
|
|
359
|
-
| `make
|
|
360
|
-
| `make
|
|
361
|
-
| `make rebuild` | `npx nodebbs
|
|
362
|
-
| `ENV=prod make rebuild` | `npx nodebbs deploy -e production` |
|
|
351
|
+
| `make up` | `npx nodebbs start` |
|
|
352
|
+
| `make build` | `npx nodebbs start --build` |
|
|
353
|
+
| `make rebuild` | `npx nodebbs start --build` |
|
|
354
|
+
| `ENV=prod make rebuild` | `npx nodebbs start -e production --build` |
|
|
363
355
|
| `make down` | `npx nodebbs stop` |
|
|
364
|
-
| `make restart` | `npx nodebbs restart` |
|
|
365
356
|
| `make ps` | `npx nodebbs status` |
|
|
366
357
|
| `make logs` | `npx nodebbs logs` |
|
|
367
358
|
| `make logs-api` | `npx nodebbs logs:api` |
|
|
@@ -400,6 +391,8 @@ API_URL=http://localhost:7100
|
|
|
400
391
|
CORS_ORIGIN=*
|
|
401
392
|
```
|
|
402
393
|
|
|
394
|
+
> **提示**:可以使用 `openssl rand -hex 32` 命令生成安全的随机密钥。
|
|
395
|
+
|
|
403
396
|
## 🛠️ 高级用法
|
|
404
397
|
|
|
405
398
|
### 内置模板
|
|
@@ -408,7 +401,7 @@ NodeBBS CLI 内置了 Docker Compose 配置文件,即使项目中没有这些
|
|
|
408
401
|
|
|
409
402
|
```bash
|
|
410
403
|
# 在任何目录运行
|
|
411
|
-
npx nodebbs
|
|
404
|
+
npx nodebbs start
|
|
412
405
|
```
|
|
413
406
|
|
|
414
407
|
CLI 会自动:
|
|
@@ -474,7 +467,7 @@ npx nodebbs stop --volumes
|
|
|
474
467
|
|
|
475
468
|
```bash
|
|
476
469
|
# 克隆仓库
|
|
477
|
-
git clone https://github.com/
|
|
470
|
+
git clone https://github.com/aiprojecthub/nodebbs.git
|
|
478
471
|
cd nodebbs
|
|
479
472
|
|
|
480
473
|
# 安装依赖
|
|
@@ -493,26 +486,17 @@ pnpm build
|
|
|
493
486
|
# 查看帮助
|
|
494
487
|
./bin/run.js --help
|
|
495
488
|
|
|
496
|
-
# 测试
|
|
497
|
-
./bin/run.js
|
|
489
|
+
# 测试 start 命令
|
|
490
|
+
./bin/run.js start --help
|
|
498
491
|
|
|
499
492
|
# 测试 logs 命令
|
|
500
493
|
./bin/run.js logs --help
|
|
501
494
|
```
|
|
502
495
|
|
|
503
|
-
## 🤝 贡献
|
|
504
|
-
|
|
505
|
-
欢迎贡献!请查看 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详情。
|
|
506
|
-
|
|
507
|
-
## 📄 许可证
|
|
508
|
-
|
|
509
|
-
MIT License - 详见 [LICENSE](LICENSE) 文件
|
|
510
|
-
|
|
511
496
|
## 🔗 相关链接
|
|
512
497
|
|
|
513
|
-
- [NodeBBS 项目](https://github.com/
|
|
514
|
-
- [问题反馈](https://github.com/
|
|
515
|
-
- [更新日志](CHANGELOG.md)
|
|
498
|
+
- [NodeBBS 项目](https://github.com/aiprojecthub/nodebbs)
|
|
499
|
+
- [问题反馈](https://github.com/aiprojecthub/nodebbs/issues)
|
|
516
500
|
|
|
517
501
|
## 💡 提示
|
|
518
502
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Clean extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
cache: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
images: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { checkbox, confirm } from '@inquirer/prompts';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
export default class Clean extends Command {
|
|
6
|
+
static description = '清理 Docker 缓存和残留资源';
|
|
7
|
+
static flags = {
|
|
8
|
+
all: Flags.boolean({
|
|
9
|
+
char: 'a',
|
|
10
|
+
description: '清理所有 (构建缓存、无用镜像、网络)',
|
|
11
|
+
default: false,
|
|
12
|
+
}),
|
|
13
|
+
cache: Flags.boolean({
|
|
14
|
+
description: '清理构建缓存',
|
|
15
|
+
default: false,
|
|
16
|
+
}),
|
|
17
|
+
images: Flags.boolean({
|
|
18
|
+
description: '清理无用镜像 (dangling)',
|
|
19
|
+
default: false,
|
|
20
|
+
}),
|
|
21
|
+
force: Flags.boolean({
|
|
22
|
+
char: 'f',
|
|
23
|
+
description: '跳过确认提示',
|
|
24
|
+
default: false,
|
|
25
|
+
}),
|
|
26
|
+
};
|
|
27
|
+
async run() {
|
|
28
|
+
const { flags } = await this.parse(Clean);
|
|
29
|
+
let targets = [];
|
|
30
|
+
// Determine targets from flags
|
|
31
|
+
if (flags.all) {
|
|
32
|
+
targets = ['cache', 'images', 'networks'];
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
if (flags.cache)
|
|
36
|
+
targets.push('cache');
|
|
37
|
+
if (flags.images)
|
|
38
|
+
targets.push('images');
|
|
39
|
+
}
|
|
40
|
+
// Interactive selection if no flags
|
|
41
|
+
if (targets.length === 0) {
|
|
42
|
+
targets = await checkbox({
|
|
43
|
+
message: '请选择要清理的项目:',
|
|
44
|
+
choices: [
|
|
45
|
+
{ name: '构建缓存 (Build Cache)', value: 'cache' },
|
|
46
|
+
{ name: '无用镜像 (Dangling Images)', value: 'images' },
|
|
47
|
+
{ name: '无用网络 (Unused Networks)', value: 'networks' },
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
if (targets.length === 0) {
|
|
52
|
+
logger.info('未选择任何清理项目。');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Confirmation
|
|
56
|
+
if (!flags.force) {
|
|
57
|
+
logger.warning(`即将清理: ${targets.join(', ')}`);
|
|
58
|
+
const confirmed = await confirm({
|
|
59
|
+
message: '确认继续?',
|
|
60
|
+
default: false
|
|
61
|
+
});
|
|
62
|
+
if (!confirmed) {
|
|
63
|
+
logger.info('操作已取消。');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Execute
|
|
68
|
+
try {
|
|
69
|
+
if (targets.includes('cache')) {
|
|
70
|
+
logger.info('正在清理构建缓存...');
|
|
71
|
+
await execa('docker', ['builder', 'prune', '-f'], { stdio: 'inherit' });
|
|
72
|
+
logger.success('构建缓存已清理');
|
|
73
|
+
}
|
|
74
|
+
if (targets.includes('images')) {
|
|
75
|
+
logger.info('正在清理无用镜像...');
|
|
76
|
+
await execa('docker', ['image', 'prune', '-f'], { stdio: 'inherit' });
|
|
77
|
+
logger.success('无用镜像已清理');
|
|
78
|
+
}
|
|
79
|
+
if (targets.includes('networks')) {
|
|
80
|
+
logger.info('正在清理无用网络...');
|
|
81
|
+
await execa('docker', ['network', 'prune', '-f'], { stdio: 'inherit' });
|
|
82
|
+
logger.success('无用网络已清理');
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
logger.error('清理过程中发生错误');
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
logger.error(error.message);
|
|
89
|
+
}
|
|
90
|
+
this.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DbBackup extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
output: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import { getComposeFiles } from '../../utils/docker.js';
|
|
3
|
+
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { execa } from 'execa';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import dotenv from 'dotenv';
|
|
8
|
+
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
9
|
+
export default class DbBackup extends Command {
|
|
10
|
+
static description = '备份数据库 (PostgreSQL)';
|
|
11
|
+
static flags = {
|
|
12
|
+
env: EnvFlag,
|
|
13
|
+
output: Flags.string({
|
|
14
|
+
char: 'o',
|
|
15
|
+
description: '输出文件路径',
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
const { flags } = await this.parse(DbBackup);
|
|
20
|
+
// 1. Select Environment
|
|
21
|
+
const env = await selectEnvironment(flags.env);
|
|
22
|
+
// 2. Determine Output File
|
|
23
|
+
let outputFile = flags.output;
|
|
24
|
+
if (!outputFile) {
|
|
25
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
26
|
+
outputFile = `backup_${timestamp}.sql`;
|
|
27
|
+
}
|
|
28
|
+
// Ensure absolute path
|
|
29
|
+
const outputPath = path.resolve(process.cwd(), outputFile);
|
|
30
|
+
// 3. Get Compose Files
|
|
31
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
32
|
+
logger.info(`正在备份数据库到: ${outputPath}`);
|
|
33
|
+
logger.info('环境: ' + env);
|
|
34
|
+
// Load Env Config
|
|
35
|
+
const envConfig = dotenv.config().parsed || {};
|
|
36
|
+
const dbUser = envConfig.POSTGRES_USER || 'postgres';
|
|
37
|
+
const dbName = envConfig.POSTGRES_DB || 'nodebbs';
|
|
38
|
+
const dbPassword = envConfig.POSTGRES_PASSWORD;
|
|
39
|
+
// 4. Construct Compose Args
|
|
40
|
+
const composeArgs = files.flatMap(f => ['-f', f]);
|
|
41
|
+
if (isBuiltIn) {
|
|
42
|
+
composeArgs.push('--project-directory', process.cwd());
|
|
43
|
+
}
|
|
44
|
+
// 5. Run pg_dump
|
|
45
|
+
const dumpArgs = [...composeArgs, 'exec', '-T'];
|
|
46
|
+
// Inject password if available
|
|
47
|
+
if (dbPassword) {
|
|
48
|
+
dumpArgs.push('-e', `PGPASSWORD=${dbPassword}`);
|
|
49
|
+
}
|
|
50
|
+
dumpArgs.push('postgres', 'pg_dump', '-U', dbUser, dbName);
|
|
51
|
+
try {
|
|
52
|
+
const subprocess = execa('docker', ['compose', ...dumpArgs]);
|
|
53
|
+
if (subprocess.stdout) {
|
|
54
|
+
subprocess.stdout.pipe(fs.createWriteStream(outputPath));
|
|
55
|
+
}
|
|
56
|
+
await subprocess;
|
|
57
|
+
// check file size
|
|
58
|
+
const stats = fs.statSync(outputPath);
|
|
59
|
+
if (stats.size === 0) {
|
|
60
|
+
logger.error('备份文件为空,备份可能失败。');
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
logger.success(`数据库备份成功!文件大小: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
logger.error('数据库备份失败');
|
|
68
|
+
if (error instanceof Error) {
|
|
69
|
+
logger.error(error.message);
|
|
70
|
+
}
|
|
71
|
+
// Cleanup empty file if failed
|
|
72
|
+
if (fs.existsSync(outputPath) && fs.statSync(outputPath).size === 0) {
|
|
73
|
+
fs.unlinkSync(outputPath);
|
|
74
|
+
}
|
|
75
|
+
this.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class DbGenerate extends Command {
|
|
3
3
|
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
};
|
|
4
7
|
run(): Promise<void>;
|
|
5
8
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
4
5
|
export default class DbGenerate extends Command {
|
|
5
|
-
static description = '
|
|
6
|
+
static description = '生成 Prisma Client (db:generate)';
|
|
7
|
+
static flags = {
|
|
8
|
+
env: EnvFlag,
|
|
9
|
+
};
|
|
6
10
|
async run() {
|
|
7
|
-
const {
|
|
8
|
-
|
|
11
|
+
const { flags } = await this.parse(DbGenerate);
|
|
12
|
+
const env = await selectEnvironment(flags.env);
|
|
13
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
14
|
+
logger.info('正在生成 Prisma Client...');
|
|
9
15
|
await execCompose(files, 'api', ['npm', 'run', 'db:generate'], isBuiltIn);
|
|
10
16
|
logger.success('数据库迁移文件生成完成');
|
|
11
17
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class Db extends Command {
|
|
3
3
|
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
};
|
|
4
7
|
run(): Promise<void>;
|
|
5
8
|
}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
4
5
|
export default class Db extends Command {
|
|
5
|
-
static description = '
|
|
6
|
+
static description = '数据库管理命令';
|
|
7
|
+
static flags = {
|
|
8
|
+
env: EnvFlag,
|
|
9
|
+
};
|
|
6
10
|
async run() {
|
|
7
|
-
const {
|
|
8
|
-
|
|
11
|
+
const { flags } = await this.parse(Db);
|
|
12
|
+
const env = await selectEnvironment(flags.env);
|
|
13
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
14
|
+
logger.info('正在进入数据库 shell...');
|
|
9
15
|
await execCompose(files, 'api', ['npm', 'run', 'db:studio'], isBuiltIn);
|
|
10
16
|
}
|
|
11
17
|
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class DbMigrate extends Command {
|
|
3
3
|
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
};
|
|
4
7
|
run(): Promise<void>;
|
|
5
8
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
4
5
|
export default class DbMigrate extends Command {
|
|
5
|
-
static description = '执行数据库迁移';
|
|
6
|
+
static description = '执行数据库迁移 (db:migrate)';
|
|
7
|
+
static flags = {
|
|
8
|
+
env: EnvFlag,
|
|
9
|
+
};
|
|
6
10
|
async run() {
|
|
7
|
-
const {
|
|
11
|
+
const { flags } = await this.parse(DbMigrate);
|
|
12
|
+
const env = await selectEnvironment(flags.env);
|
|
13
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
8
14
|
logger.info('正在执行数据库迁移...');
|
|
9
15
|
await execCompose(files, 'api', ['npm', 'run', 'db:migrate'], isBuiltIn);
|
|
10
16
|
logger.success('数据库迁移完成');
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class DbPush extends Command {
|
|
3
3
|
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
};
|
|
4
7
|
run(): Promise<void>;
|
|
5
8
|
}
|
package/dist/commands/db/push.js
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
import { execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
3
|
import { logger } from '../../utils/logger.js';
|
|
4
|
+
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
4
5
|
export default class DbPush extends Command {
|
|
5
|
-
static description = '推送数据库 schema';
|
|
6
|
+
static description = '推送数据库 schema (db:push)';
|
|
7
|
+
static flags = {
|
|
8
|
+
env: EnvFlag,
|
|
9
|
+
};
|
|
6
10
|
async run() {
|
|
7
|
-
const {
|
|
11
|
+
const { flags } = await this.parse(DbPush);
|
|
12
|
+
const env = await selectEnvironment(flags.env);
|
|
13
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
8
14
|
logger.info('正在推送数据库 schema...');
|
|
9
15
|
await execCompose(files, 'api', ['npm', 'run', 'db:push'], isBuiltIn);
|
|
10
16
|
logger.success('Schema 推送完成');
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
export default class DbReset extends Command {
|
|
3
3
|
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
+
};
|
|
4
7
|
run(): Promise<void>;
|
|
5
8
|
}
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { confirm } from '@inquirer/prompts';
|
|
2
3
|
import { execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
4
|
import { logger } from '../../utils/logger.js';
|
|
4
|
-
import {
|
|
5
|
+
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
5
6
|
export default class DbReset extends Command {
|
|
6
|
-
static description = '
|
|
7
|
+
static description = '重置数据库 (db:reset) - 危险操作!';
|
|
8
|
+
static flags = {
|
|
9
|
+
env: EnvFlag,
|
|
10
|
+
};
|
|
7
11
|
async run() {
|
|
8
|
-
const {
|
|
9
|
-
|
|
12
|
+
const { flags } = await this.parse(DbReset);
|
|
13
|
+
const env = await selectEnvironment(flags.env);
|
|
14
|
+
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
15
|
+
logger.warning('警告:这将清除所有数据!');
|
|
10
16
|
const confirmReset = await confirm({
|
|
11
17
|
message: '确认继续?',
|
|
12
18
|
default: false
|