nodebbs 0.0.6 → 0.0.8
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 +5 -149
- package/dist/interactive.js +42 -2
- package/oclif.manifest.json +13 -83
- package/package.json +3 -3
- package/dist/commands/db/generate.d.ts +0 -8
- package/dist/commands/db/generate.js +0 -18
- package/dist/commands/db/migrate.d.ts +0 -8
- package/dist/commands/db/migrate.js +0 -18
- /package/dist/commands/logs/{index.d.ts → all.d.ts} +0 -0
- /package/dist/commands/logs/{index.js → all.js} +0 -0
package/README.md
CHANGED
|
@@ -11,14 +11,11 @@ NodeBBS CLI 是一个专为全栈开发者设计的命令行工具,用于简
|
|
|
11
11
|
|
|
12
12
|
### 特性
|
|
13
13
|
|
|
14
|
-
- �️ **交互式菜单** -
|
|
15
|
-
- �🚀 **快速启动** - 一键启动开发环境
|
|
14
|
+
- �️ **交互式菜单** - 支持键盘导航的可视化命令选择
|
|
16
15
|
- 🎯 **全栈友好** - 命令设计贴近开发者思维
|
|
17
|
-
- 🔧 **服务级控制** - 可以单独管理每个服务
|
|
18
16
|
- 📊 **实时日志** - 方便查看各服务日志
|
|
19
17
|
- 💾 **数据库管理** - 内置数据库迁移和管理工具
|
|
20
18
|
- 🌐 **内置模板** - 无需本地配置文件即可使用
|
|
21
|
-
- 🇨🇳 **中文界面** - 所有输出均为中文
|
|
22
19
|
|
|
23
20
|
## 📦 安装
|
|
24
21
|
|
|
@@ -45,28 +42,11 @@ npx nodebbs [command]
|
|
|
45
42
|
|
|
46
43
|
## 🚀 快速开始
|
|
47
44
|
|
|
48
|
-
### 1. 启动服务
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
# 启动所有服务(生产模式,包含完整检查)
|
|
52
|
-
npx nodebbs start
|
|
53
|
-
|
|
54
|
-
# 重新构建并启动(跳过检查,用于更新)
|
|
55
|
-
npx nodebbs start --build
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
### 2. 查看服务状态
|
|
59
|
-
|
|
60
45
|
```bash
|
|
61
|
-
|
|
62
|
-
npx nodebbs status
|
|
46
|
+
npx nodebbs
|
|
63
47
|
```
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
- **Web 前端**: http://localhost:3100
|
|
68
|
-
- **API 文档**: http://localhost:7100/docs
|
|
69
|
-
- **健康检查**: http://localhost:7100/api
|
|
48
|
+
- **start**: 开始部署(首次使用推荐选择此项)
|
|
49
|
+
- **rebuild**: 重新构建并启动(跳过检查,通常在代码更新后选择此项)
|
|
70
50
|
|
|
71
51
|
## 📚 命令参考
|
|
72
52
|
|
|
@@ -217,22 +197,6 @@ npx nodebbs shell:redis
|
|
|
217
197
|
|
|
218
198
|
### 数据库管理
|
|
219
199
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
#### `nodebbs db:generate`
|
|
223
|
-
生成数据库迁移文件
|
|
224
|
-
|
|
225
|
-
```bash
|
|
226
|
-
npx nodebbs db:generate
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
#### `nodebbs db:migrate`
|
|
230
|
-
执行数据库迁移
|
|
231
|
-
|
|
232
|
-
```bash
|
|
233
|
-
npx nodebbs db:migrate
|
|
234
|
-
```
|
|
235
|
-
|
|
236
200
|
#### `nodebbs db:push`
|
|
237
201
|
推送数据库 schema
|
|
238
202
|
|
|
@@ -278,83 +242,7 @@ npx nodebbs clean --cache
|
|
|
278
242
|
|
|
279
243
|
## 🎯 使用场景
|
|
280
244
|
|
|
281
|
-
###
|
|
282
|
-
|
|
283
|
-
```bash
|
|
284
|
-
# 1. 启动服务
|
|
285
|
-
npx nodebbs start
|
|
286
|
-
|
|
287
|
-
# 2. 初始化数据库
|
|
288
|
-
npx nodebbs db:push
|
|
289
|
-
npx nodebbs db:seed
|
|
290
|
-
|
|
291
|
-
# 3. 查看服务状态
|
|
292
|
-
npx nodebbs status
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
### 场景 2:日常开发
|
|
296
|
-
|
|
297
|
-
```bash
|
|
298
|
-
# 启动服务
|
|
299
|
-
npx nodebbs start
|
|
300
|
-
|
|
301
|
-
# 查看 API 日志
|
|
302
|
-
npx nodebbs logs:api
|
|
303
|
-
|
|
304
|
-
# 进入 API 容器调试
|
|
305
|
-
npx nodebbs shell:api
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
# 重置测试数据
|
|
310
|
-
npx nodebbs db:reset
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
### 场景 3:代码更新后重新构建
|
|
314
|
-
|
|
315
|
-
```bash
|
|
316
|
-
# 停止服务
|
|
317
|
-
npx nodebbs stop
|
|
318
|
-
|
|
319
|
-
# 重新构建并启动
|
|
320
|
-
npx nodebbs start --build
|
|
321
|
-
|
|
322
|
-
# 查看日志确认启动成功
|
|
323
|
-
npx nodebbs logs
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
### 场景 4:生产部署
|
|
327
|
-
|
|
328
|
-
```bash
|
|
329
|
-
# 部署到生产环境
|
|
330
|
-
npx nodebbs start -e production
|
|
331
|
-
|
|
332
|
-
# 查看服务状态
|
|
333
|
-
npx nodebbs status
|
|
334
|
-
|
|
335
|
-
# 查看日志
|
|
336
|
-
npx nodebbs logs
|
|
337
|
-
|
|
338
|
-
# 执行数据库迁移
|
|
339
|
-
npx nodebbs db:migrate
|
|
340
|
-
```
|
|
341
|
-
|
|
342
|
-
### 场景 5:数据库操作
|
|
343
|
-
|
|
344
|
-
```bash
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
# 运行迁移
|
|
348
|
-
npx nodebbs db:migrate
|
|
349
|
-
|
|
350
|
-
# 填充数据
|
|
351
|
-
npx nodebbs db:seed
|
|
352
|
-
|
|
353
|
-
# 进入数据库命令行
|
|
354
|
-
npx nodebbs shell:db
|
|
355
|
-
```
|
|
356
|
-
|
|
357
|
-
### 场景 6:离线服务器部署
|
|
245
|
+
### 离线服务器部署
|
|
358
246
|
|
|
359
247
|
1. **在开发机打包**:
|
|
360
248
|
```bash
|
|
@@ -491,38 +379,6 @@ npx nodebbs stop
|
|
|
491
379
|
npx nodebbs stop --volumes
|
|
492
380
|
```
|
|
493
381
|
|
|
494
|
-
## 📝 开发
|
|
495
|
-
|
|
496
|
-
### 本地开发
|
|
497
|
-
|
|
498
|
-
```bash
|
|
499
|
-
# 克隆仓库
|
|
500
|
-
git clone https://github.com/aiprojecthub/nodebbs.git
|
|
501
|
-
cd nodebbs
|
|
502
|
-
|
|
503
|
-
# 安装依赖
|
|
504
|
-
pnpm install
|
|
505
|
-
|
|
506
|
-
# 构建
|
|
507
|
-
pnpm build
|
|
508
|
-
|
|
509
|
-
# 运行
|
|
510
|
-
./bin/run.js [command]
|
|
511
|
-
```
|
|
512
|
-
|
|
513
|
-
### 测试命令
|
|
514
|
-
|
|
515
|
-
```bash
|
|
516
|
-
# 查看帮助
|
|
517
|
-
./bin/run.js --help
|
|
518
|
-
|
|
519
|
-
# 测试 start 命令
|
|
520
|
-
./bin/run.js start --help
|
|
521
|
-
|
|
522
|
-
# 测试 logs 命令
|
|
523
|
-
./bin/run.js logs --help
|
|
524
|
-
```
|
|
525
|
-
|
|
526
382
|
## 🔗 相关链接
|
|
527
383
|
|
|
528
384
|
- [NodeBBS 项目](https://github.com/aiprojecthub/nodebbs)
|
package/dist/interactive.js
CHANGED
|
@@ -29,8 +29,13 @@ export async function runInteractive(root) {
|
|
|
29
29
|
}
|
|
30
30
|
await navigate(tree, [], config);
|
|
31
31
|
}
|
|
32
|
+
const GLOBAL_PRIORITY = ['start', 'stop', 'restart', 'rebuild', 'status', 'logs', 'shell', 'db', 'pack'];
|
|
33
|
+
const SCOPED_PRIORITIES = {
|
|
34
|
+
'logs': ['all', 'web', 'api', 'db', 'redis'],
|
|
35
|
+
};
|
|
32
36
|
async function navigate(node, breadcrumbs, config) {
|
|
33
|
-
const
|
|
37
|
+
const currentPath = breadcrumbs.join(':');
|
|
38
|
+
const priorityOrder = SCOPED_PRIORITIES[currentPath] || GLOBAL_PRIORITY;
|
|
34
39
|
while (true) {
|
|
35
40
|
const keys = Object.keys(node.children).sort((a, b) => {
|
|
36
41
|
const indexA = priorityOrder.indexOf(a);
|
|
@@ -47,7 +52,16 @@ async function navigate(node, breadcrumbs, config) {
|
|
|
47
52
|
// Otherwise sort alphabetically
|
|
48
53
|
return a.localeCompare(b);
|
|
49
54
|
});
|
|
50
|
-
const choices =
|
|
55
|
+
const choices = [];
|
|
56
|
+
// 如果当前节点本身就是一个命令,添加运行选项
|
|
57
|
+
if (node.command) {
|
|
58
|
+
choices.push({
|
|
59
|
+
name: `${node.command.description || node.command.id}`,
|
|
60
|
+
value: '__SELF__',
|
|
61
|
+
short: 'all'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
const subChoices = keys.map(key => {
|
|
51
65
|
const child = node.children[key];
|
|
52
66
|
const hasSubcommands = Object.keys(child.children).length > 0;
|
|
53
67
|
let label = key;
|
|
@@ -83,6 +97,7 @@ async function navigate(node, breadcrumbs, config) {
|
|
|
83
97
|
short: key
|
|
84
98
|
};
|
|
85
99
|
});
|
|
100
|
+
choices.push(...subChoices);
|
|
86
101
|
// 添加导航选项
|
|
87
102
|
// 即使支持 Esc,保留显式选项也有助于发现性
|
|
88
103
|
if (breadcrumbs.length > 0) {
|
|
@@ -116,6 +131,31 @@ async function navigate(node, breadcrumbs, config) {
|
|
|
116
131
|
if (selection === '__BACK__') {
|
|
117
132
|
return;
|
|
118
133
|
}
|
|
134
|
+
if (selection === '__SELF__' && node.command) {
|
|
135
|
+
console.log(`正在运行: ${node.command.id}`);
|
|
136
|
+
try {
|
|
137
|
+
await config.runCommand(node.command.id);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
// Ctrl+C: 退出程序
|
|
141
|
+
if (error.name === 'ExitPromptError') {
|
|
142
|
+
console.log('\n用户退出。');
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
145
|
+
// 手动取消: 返回菜单
|
|
146
|
+
if (error.name === 'CancelError') {
|
|
147
|
+
console.log('\n操作已取消。');
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
console.error(error);
|
|
151
|
+
}
|
|
152
|
+
console.log('\n命令执行完成。');
|
|
153
|
+
// 动态导入 input 以保持轻量
|
|
154
|
+
const { input } = await import('@inquirer/prompts');
|
|
155
|
+
await input({ message: '按回车键继续...' });
|
|
156
|
+
// 继续循环
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
119
159
|
const selectedNode = node.children[selection];
|
|
120
160
|
if (Object.keys(selectedNode.children).length > 0) {
|
|
121
161
|
// 有子命令,进入子菜单
|
package/oclif.manifest.json
CHANGED
|
@@ -91,76 +91,6 @@
|
|
|
91
91
|
"backup.js"
|
|
92
92
|
]
|
|
93
93
|
},
|
|
94
|
-
"db:generate": {
|
|
95
|
-
"aliases": [],
|
|
96
|
-
"args": {},
|
|
97
|
-
"description": "生成 Prisma Client (db:generate)",
|
|
98
|
-
"flags": {
|
|
99
|
-
"env": {
|
|
100
|
-
"char": "e",
|
|
101
|
-
"description": "部署环境 (production, lowmem, basic)",
|
|
102
|
-
"name": "env",
|
|
103
|
-
"hasDynamicHelp": false,
|
|
104
|
-
"multiple": false,
|
|
105
|
-
"options": [
|
|
106
|
-
"production",
|
|
107
|
-
"lowmem",
|
|
108
|
-
"basic"
|
|
109
|
-
],
|
|
110
|
-
"type": "option"
|
|
111
|
-
}
|
|
112
|
-
},
|
|
113
|
-
"hasDynamicHelp": false,
|
|
114
|
-
"hiddenAliases": [],
|
|
115
|
-
"id": "db:generate",
|
|
116
|
-
"pluginAlias": "nodebbs",
|
|
117
|
-
"pluginName": "nodebbs",
|
|
118
|
-
"pluginType": "core",
|
|
119
|
-
"strict": true,
|
|
120
|
-
"enableJsonFlag": false,
|
|
121
|
-
"isESM": true,
|
|
122
|
-
"relativePath": [
|
|
123
|
-
"dist",
|
|
124
|
-
"commands",
|
|
125
|
-
"db",
|
|
126
|
-
"generate.js"
|
|
127
|
-
]
|
|
128
|
-
},
|
|
129
|
-
"db:migrate": {
|
|
130
|
-
"aliases": [],
|
|
131
|
-
"args": {},
|
|
132
|
-
"description": "执行数据库迁移 (db:migrate)",
|
|
133
|
-
"flags": {
|
|
134
|
-
"env": {
|
|
135
|
-
"char": "e",
|
|
136
|
-
"description": "部署环境 (production, lowmem, basic)",
|
|
137
|
-
"name": "env",
|
|
138
|
-
"hasDynamicHelp": false,
|
|
139
|
-
"multiple": false,
|
|
140
|
-
"options": [
|
|
141
|
-
"production",
|
|
142
|
-
"lowmem",
|
|
143
|
-
"basic"
|
|
144
|
-
],
|
|
145
|
-
"type": "option"
|
|
146
|
-
}
|
|
147
|
-
},
|
|
148
|
-
"hasDynamicHelp": false,
|
|
149
|
-
"hiddenAliases": [],
|
|
150
|
-
"id": "db:migrate",
|
|
151
|
-
"pluginAlias": "nodebbs",
|
|
152
|
-
"pluginName": "nodebbs",
|
|
153
|
-
"pluginType": "core",
|
|
154
|
-
"strict": true,
|
|
155
|
-
"enableJsonFlag": false,
|
|
156
|
-
"isESM": true,
|
|
157
|
-
"relativePath": [
|
|
158
|
-
"dist",
|
|
159
|
-
"commands",
|
|
160
|
-
"db",
|
|
161
|
-
"migrate.js"
|
|
162
|
-
]
|
|
163
|
-
},
|
|
164
94
|
"db:push": {
|
|
165
95
|
"aliases": [],
|
|
166
96
|
"args": {},
|
|
@@ -266,10 +196,10 @@
|
|
|
266
196
|
"seed.js"
|
|
267
197
|
]
|
|
268
198
|
},
|
|
269
|
-
"logs:
|
|
199
|
+
"logs:all": {
|
|
270
200
|
"aliases": [],
|
|
271
201
|
"args": {},
|
|
272
|
-
"description": "
|
|
202
|
+
"description": "查看所有服务日志",
|
|
273
203
|
"flags": {
|
|
274
204
|
"env": {
|
|
275
205
|
"char": "e",
|
|
@@ -287,7 +217,7 @@
|
|
|
287
217
|
},
|
|
288
218
|
"hasDynamicHelp": false,
|
|
289
219
|
"hiddenAliases": [],
|
|
290
|
-
"id": "logs:
|
|
220
|
+
"id": "logs:all",
|
|
291
221
|
"pluginAlias": "nodebbs",
|
|
292
222
|
"pluginName": "nodebbs",
|
|
293
223
|
"pluginType": "core",
|
|
@@ -298,13 +228,13 @@
|
|
|
298
228
|
"dist",
|
|
299
229
|
"commands",
|
|
300
230
|
"logs",
|
|
301
|
-
"
|
|
231
|
+
"all.js"
|
|
302
232
|
]
|
|
303
233
|
},
|
|
304
|
-
"logs:
|
|
234
|
+
"logs:api": {
|
|
305
235
|
"aliases": [],
|
|
306
236
|
"args": {},
|
|
307
|
-
"description": "
|
|
237
|
+
"description": "查看 API 服务日志",
|
|
308
238
|
"flags": {
|
|
309
239
|
"env": {
|
|
310
240
|
"char": "e",
|
|
@@ -322,7 +252,7 @@
|
|
|
322
252
|
},
|
|
323
253
|
"hasDynamicHelp": false,
|
|
324
254
|
"hiddenAliases": [],
|
|
325
|
-
"id": "logs:
|
|
255
|
+
"id": "logs:api",
|
|
326
256
|
"pluginAlias": "nodebbs",
|
|
327
257
|
"pluginName": "nodebbs",
|
|
328
258
|
"pluginType": "core",
|
|
@@ -333,13 +263,13 @@
|
|
|
333
263
|
"dist",
|
|
334
264
|
"commands",
|
|
335
265
|
"logs",
|
|
336
|
-
"
|
|
266
|
+
"api.js"
|
|
337
267
|
]
|
|
338
268
|
},
|
|
339
|
-
"logs": {
|
|
269
|
+
"logs:db": {
|
|
340
270
|
"aliases": [],
|
|
341
271
|
"args": {},
|
|
342
|
-
"description": "
|
|
272
|
+
"description": "查看数据库日志",
|
|
343
273
|
"flags": {
|
|
344
274
|
"env": {
|
|
345
275
|
"char": "e",
|
|
@@ -357,7 +287,7 @@
|
|
|
357
287
|
},
|
|
358
288
|
"hasDynamicHelp": false,
|
|
359
289
|
"hiddenAliases": [],
|
|
360
|
-
"id": "logs",
|
|
290
|
+
"id": "logs:db",
|
|
361
291
|
"pluginAlias": "nodebbs",
|
|
362
292
|
"pluginName": "nodebbs",
|
|
363
293
|
"pluginType": "core",
|
|
@@ -368,7 +298,7 @@
|
|
|
368
298
|
"dist",
|
|
369
299
|
"commands",
|
|
370
300
|
"logs",
|
|
371
|
-
"
|
|
301
|
+
"db.js"
|
|
372
302
|
]
|
|
373
303
|
},
|
|
374
304
|
"logs:redis": {
|
|
@@ -788,5 +718,5 @@
|
|
|
788
718
|
]
|
|
789
719
|
}
|
|
790
720
|
},
|
|
791
|
-
"version": "0.0.
|
|
721
|
+
"version": "0.0.8"
|
|
792
722
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodebbs",
|
|
3
3
|
"description": "NodeBBS 论坛系统专业运维工具",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.8",
|
|
5
5
|
"author": "wengqianshan",
|
|
6
6
|
"bin": {
|
|
7
7
|
"nodebbs": "./bin/run.js"
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
],
|
|
59
59
|
"topicSeparator": " ",
|
|
60
60
|
"topics": {
|
|
61
|
-
"
|
|
62
|
-
"description": "
|
|
61
|
+
"logs": {
|
|
62
|
+
"description": "查看服务日志"
|
|
63
63
|
},
|
|
64
64
|
"db": {
|
|
65
65
|
"description": "数据库操作 (备份, 迁移, 种子数据等)"
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
export default class DbGenerate extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static flags: {
|
|
5
|
-
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
-
};
|
|
7
|
-
run(): Promise<void>;
|
|
8
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
import { execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
|
-
import { logger } from '../../utils/logger.js';
|
|
4
|
-
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
5
|
-
export default class DbGenerate extends Command {
|
|
6
|
-
static description = '生成 Prisma Client (db:generate)';
|
|
7
|
-
static flags = {
|
|
8
|
-
env: EnvFlag,
|
|
9
|
-
};
|
|
10
|
-
async run() {
|
|
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...');
|
|
15
|
-
await execCompose(files, 'api', ['npm', 'run', 'db:generate'], isBuiltIn);
|
|
16
|
-
logger.success('数据库迁移文件生成完成');
|
|
17
|
-
}
|
|
18
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
export default class DbMigrate extends Command {
|
|
3
|
-
static description: string;
|
|
4
|
-
static flags: {
|
|
5
|
-
env: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
6
|
-
};
|
|
7
|
-
run(): Promise<void>;
|
|
8
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { Command } from '@oclif/core';
|
|
2
|
-
import { execCompose, getComposeFiles } from '../../utils/docker.js';
|
|
3
|
-
import { logger } from '../../utils/logger.js';
|
|
4
|
-
import { EnvFlag, selectEnvironment } from '../../utils/selection.js';
|
|
5
|
-
export default class DbMigrate extends Command {
|
|
6
|
-
static description = '执行数据库迁移 (db:migrate)';
|
|
7
|
-
static flags = {
|
|
8
|
-
env: EnvFlag,
|
|
9
|
-
};
|
|
10
|
-
async run() {
|
|
11
|
-
const { flags } = await this.parse(DbMigrate);
|
|
12
|
-
const env = await selectEnvironment(flags.env);
|
|
13
|
-
const { files, isBuiltIn } = await getComposeFiles(env);
|
|
14
|
-
logger.info('正在执行数据库迁移...');
|
|
15
|
-
await execCompose(files, 'api', ['npm', 'run', 'db:migrate'], isBuiltIn);
|
|
16
|
-
logger.success('数据库迁移完成');
|
|
17
|
-
}
|
|
18
|
-
}
|
|
File without changes
|
|
File without changes
|