@minij/cli 0.0.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/CLAUDE.md +23 -0
- package/README.md +203 -0
- package/bin/minij.js +3 -0
- package/deploy.sh +33 -0
- package/local_deploy.sh +14 -0
- package/package.json +28 -0
- package/src/commands/create.ts +94 -0
- package/src/commands/init.ts +118 -0
- package/src/commands/server-deploy.ts +40 -0
- package/src/constants.ts +11 -0
- package/src/index.ts +53 -0
- package/src/services/gitlab.ts +94 -0
- package/src/utils/config.ts +28 -0
- package/src/utils/loader.ts +25 -0
- package/src/utils/ssh.ts +47 -0
- package/src/utils/template-replacer.ts +84 -0
- package/tsconfig.json +16 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# 小吉 AI 执行规范 (High Performance)
|
|
2
|
+
|
|
3
|
+
## 核心原则
|
|
4
|
+
- **优先 CLI**:所有操作(建站、建仓、初始化、部署)必须通过 `minij` CLI 完成。
|
|
5
|
+
- **环境隔离**:仅限 608 内网。API: `http://192.168.50.41:8888`。
|
|
6
|
+
- **禁止手动**:禁止使用 git clone/init/push 替代 CLI,除非用户明确要求。
|
|
7
|
+
|
|
8
|
+
## 指令映射表
|
|
9
|
+
| 需求场景 | 动作 / 预设 | 命令 |
|
|
10
|
+
| :--- | :--- | :--- |
|
|
11
|
+
| **新建项目** | 需确认 <英文名> | `minij create <name> [--port <port>]` |
|
|
12
|
+
| **部署/发布** | 需确认 <项目名> | `minij server-deploy <name>` |
|
|
13
|
+
| **初始化环境** | 直接执行 | `minij init` |
|
|
14
|
+
|
|
15
|
+
## 业务逻辑约束
|
|
16
|
+
1. **参数校验**:
|
|
17
|
+
- 英文名:仅限字母、数字、-、_。
|
|
18
|
+
- 端口:3000-3100 (默认 3001)。
|
|
19
|
+
2. **缺省策略**:
|
|
20
|
+
- 无英文名:主动询问。
|
|
21
|
+
- 无端口:直接执行 `minij create <name>` 由 CLI 接管提示。
|
|
22
|
+
- 中文名:默认使用英文名,无需确认。
|
|
23
|
+
3. **基础设施**:GitLab (`git.minij.com`),用户 (`deployer-bot`)。
|
package/README.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# @minij/cli
|
|
2
|
+
|
|
3
|
+
minij 一键建站方案的本地触发端,负责本地项目初始化、模板替换,并协调 `minij-orchestrator-server` 完成 GitLab 仓库创建和 CI/CD 变量注入。
|
|
4
|
+
|
|
5
|
+
> **注意:需在minij 608 内网中使用。**
|
|
6
|
+
> orchestrator 服务固定部署在 `http://192.168.50.41:8888`,不在内网无法连接。
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 安装
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @minij/cli
|
|
14
|
+
# 或
|
|
15
|
+
pnpm add -g @minij/cli
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## 前置要求
|
|
19
|
+
|
|
20
|
+
- Node.js >= 18
|
|
21
|
+
- pnpm
|
|
22
|
+
- Git
|
|
23
|
+
- SSH 公钥已配置到 `git.minij.com`
|
|
24
|
+
- 处于minij 608 内网环境
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## 命令
|
|
29
|
+
|
|
30
|
+
### minij create \<name\>
|
|
31
|
+
|
|
32
|
+
一键创建项目:克隆模板 → 替换项目名 → 创建 GitLab 仓库 → 注入 CI/CD 变量 → 初始化 git 并推送。
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
minij create my-project
|
|
36
|
+
# 或指定 Node 端口(跳过交互提示)
|
|
37
|
+
minij create my-project --port 3002
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**选项**
|
|
41
|
+
|
|
42
|
+
| 选项 | 说明 |
|
|
43
|
+
|------|------|
|
|
44
|
+
| `-p, --port <port>` | Node 服务端口,范围 3000-3100,默认 3001 |
|
|
45
|
+
|
|
46
|
+
**执行流程**
|
|
47
|
+
|
|
48
|
+
1. 从 `git@git.minij.com:dataverse/minij-bi-admin-template.git` 克隆模板
|
|
49
|
+
2. 删除 `.git` 目录
|
|
50
|
+
3. 全局替换模板中的 `minij-bi-admin-template` 为项目名
|
|
51
|
+
4. 写入 `backend/.env` 的 `PORT` 值
|
|
52
|
+
5. 调用 orchestrator `POST /api/create-repo` 创建 GitLab 私有仓库
|
|
53
|
+
6. 调用 orchestrator `POST /api/setup-ci-variables` 注入 CI/CD 变量
|
|
54
|
+
7. `git init → commit → push -u origin main`
|
|
55
|
+
|
|
56
|
+
**完成后输出**
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
✓ 项目 my-project 创建完成!
|
|
60
|
+
|
|
61
|
+
目录: /path/to/my-project
|
|
62
|
+
远程仓库: git@git.minij.com:dataverse/my-project.git
|
|
63
|
+
Node 端口: 3002
|
|
64
|
+
|
|
65
|
+
访问地址(CI 部署完成后):
|
|
66
|
+
https://dataverse.minij.com/DataVerse/my-project/
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
### minij init
|
|
72
|
+
|
|
73
|
+
在当前目录生成 `CLAUDE.md`(AI 助手执行规则),并检查 SSH 到 GitLab 的连通���。
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
minij init
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
SSH 检查失败时会提示配置步骤:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
ssh-keygen -t ed25519 -C "your_email@example.com"
|
|
83
|
+
cat ~/.ssh/id_ed25519.pub
|
|
84
|
+
# 将公钥添加到 git.minij.com → Settings → SSH Keys
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### minij server-deploy \<name\>
|
|
90
|
+
|
|
91
|
+
调用 orchestrator `POST /api/setup-router` 为项目分配端口并配置 Nginx 路由。
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
minij server-deploy my-project
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> 当前 DataVerse 体系前端通过 CI/CD 直接部署,通常不需要单独调用此命令。
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 完整工作流
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# 1. 检查环境(首次使用)
|
|
105
|
+
minij init
|
|
106
|
+
|
|
107
|
+
# 2. 一键创建项目
|
|
108
|
+
minij create my-project --port 3002
|
|
109
|
+
|
|
110
|
+
# 3. 进入项目目录,开始开发
|
|
111
|
+
cd my-project
|
|
112
|
+
|
|
113
|
+
# 4. 推送代码触发 CI/CD 自动部署
|
|
114
|
+
git add .
|
|
115
|
+
git commit -m "feat: ..."
|
|
116
|
+
git push
|
|
117
|
+
|
|
118
|
+
# 5. 部署完成后访问
|
|
119
|
+
# https://dataverse.minij.com/DataVerse/my-project/
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 本地开发测试
|
|
125
|
+
|
|
126
|
+
### 1. 安装依赖并构建
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pnpm install
|
|
130
|
+
pnpm build
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 2. 本地挂载 CLI
|
|
134
|
+
|
|
135
|
+
在 `minij-cli` 目录执行:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pnpm link --global
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
挂载完成后,可直接在终端使用:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
minij --version
|
|
145
|
+
minij init
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 3. 测试创建项目全流程
|
|
149
|
+
|
|
150
|
+
> 需确保当前机器已连接minij 608 内网,且 `minij-orchestrator-server` 已部署在 `http://192.168.50.41:8888`。
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
minij create test-demo --port 3002
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
该命令会验证以下完整链路:
|
|
157
|
+
|
|
158
|
+
1. 克隆模板仓库
|
|
159
|
+
2. 替换项目名
|
|
160
|
+
3. 调用 orchestrator 创建 GitLab 仓库
|
|
161
|
+
4. 注入 GitLab CI/CD 变量
|
|
162
|
+
5. 初始化 git 并推送到远程仓库
|
|
163
|
+
|
|
164
|
+
### 4. 修改代码后重新测试
|
|
165
|
+
|
|
166
|
+
每次修改 CLI 源码后,重新执行:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
pnpm build
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
然后再次运行:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
minij create another-demo --port 3003
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 5. 如需取消全局挂载
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pnpm unlink --global @minij/cli
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 配置
|
|
187
|
+
|
|
188
|
+
CLI 默认连接 `http://192.168.50.41:8888`,无需手动配置。
|
|
189
|
+
|
|
190
|
+
如需覆盖,可编辑 `~/.minij/config.json`:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"ORCHESTRATOR_URL": "http://192.168.50.41:8888"
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## 项目名规范
|
|
201
|
+
|
|
202
|
+
- 只能包含字母、数字、中划线、下划线
|
|
203
|
+
- 示例:`my-project`、`data_dashboard`、`bi2025`
|
package/bin/minij.js
ADDED
package/deploy.sh
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
cd "$(dirname "$0")"
|
|
6
|
+
|
|
7
|
+
echo "📦 构建项目..."
|
|
8
|
+
pnpm build
|
|
9
|
+
|
|
10
|
+
echo "🔍 检查 Git 工作区..."
|
|
11
|
+
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
12
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
13
|
+
echo "❌ 检测到未提交的 Git 变更,已取消发布。"
|
|
14
|
+
echo ""
|
|
15
|
+
echo "当前变更:"
|
|
16
|
+
git status --short
|
|
17
|
+
echo ""
|
|
18
|
+
echo "请先处理后再重试,你可以选择:"
|
|
19
|
+
echo "1. 提交改动后重新执行 bash deploy.sh"
|
|
20
|
+
echo "2. 临时 stash 改动后重新执行 bash deploy.sh"
|
|
21
|
+
echo "3. 如确需跳过检查,手动执行:pnpm publish --no-git-checks --registry=https://registry.npmjs.org/"
|
|
22
|
+
exit 1
|
|
23
|
+
fi
|
|
24
|
+
echo "✅ Git 工作区干净"
|
|
25
|
+
else
|
|
26
|
+
echo "⚠️ 当前目录不在 Git 仓库中,跳过 Git 状态检查"
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
echo "🚀 发布到 npm..."
|
|
30
|
+
pnpm publish --tag beta --access public --registry=https://registry.npmjs.org/
|
|
31
|
+
|
|
32
|
+
echo "✅ 发布完成!"
|
|
33
|
+
echo "查看: https://www.npmjs.com/package/@minij/cli"
|
package/local_deploy.sh
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@minij/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "minij 一键建站方案 - 本地触发端",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"minij": "./bin/minij.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"axios": "^1.6.0",
|
|
12
|
+
"chalk": "^5.3.0",
|
|
13
|
+
"commander": "^11.1.0",
|
|
14
|
+
"fs-extra": "^11.2.0",
|
|
15
|
+
"ora": "^8.0.1",
|
|
16
|
+
"shelljs": "^0.8.5"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/fs-extra": "^11.0.4",
|
|
20
|
+
"@types/node": "^20.10.0",
|
|
21
|
+
"@types/shelljs": "^0.8.15",
|
|
22
|
+
"typescript": "^5.3.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"prepublish": "npm run build"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import shelljs from 'shelljs';
|
|
3
|
+
const shell = shelljs;
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import readline from 'node:readline/promises';
|
|
7
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
8
|
+
import { TEMPLATE_URL } from '../constants.js';
|
|
9
|
+
import { withLoader } from '../utils/loader.js';
|
|
10
|
+
import { applyTemplateReplacements } from '../utils/template-replacer.js';
|
|
11
|
+
import { createGitlabRepo, initLocalGitRepo, setupCiVariables } from '../services/gitlab.js';
|
|
12
|
+
|
|
13
|
+
export interface CreateCommandOptions {
|
|
14
|
+
port?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function createCommand(name: string, options: CreateCommandOptions = {}): Promise<void> {
|
|
18
|
+
validateProjectName(name);
|
|
19
|
+
|
|
20
|
+
const projectPath = path.resolve(name);
|
|
21
|
+
if (await fs.pathExists(projectPath)) {
|
|
22
|
+
throw new Error(`目录已存在: ${projectPath}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const backendPort = await resolveBackendPort(options.port);
|
|
26
|
+
|
|
27
|
+
await withLoader(`正在克隆模板到 ${name}...`, async () => {
|
|
28
|
+
const result = shell.exec(`git clone ${TEMPLATE_URL} ${name}`, { silent: true });
|
|
29
|
+
if (result.code !== 0) {
|
|
30
|
+
throw new Error(`克隆失败: ${result.stderr}`);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await withLoader('正在清理 .git 目录...', async () => {
|
|
35
|
+
await fs.remove(path.join(projectPath, '.git'));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await withLoader('正在替换模板内容...', async () => {
|
|
39
|
+
await applyTemplateReplacements(projectPath, name, backendPort);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const { sshUrl, projectId } = await createGitlabRepo(name);
|
|
43
|
+
await setupCiVariables(projectId, name, backendPort);
|
|
44
|
+
await initLocalGitRepo(projectPath, sshUrl);
|
|
45
|
+
|
|
46
|
+
const url = `https://dataverse.minij.com/DataVerse/${name}/`;
|
|
47
|
+
|
|
48
|
+
console.log(chalk.green(`\n✓ 项目 ${name} 创建完成!`));
|
|
49
|
+
console.log(chalk.white('\n项目信息:'));
|
|
50
|
+
console.log(chalk.gray(`目录: ${projectPath}`));
|
|
51
|
+
console.log(chalk.gray(`远程仓库: ${sshUrl}`));
|
|
52
|
+
console.log(chalk.gray(`Node 端口: ${backendPort}`));
|
|
53
|
+
console.log(chalk.yellow('\n提示:GitLab CI/CD 正在构建,约 20S后可访问'));
|
|
54
|
+
console.log(chalk.white('\n访问地址:'));
|
|
55
|
+
console.log(chalk.bold.blue(`\x1b]8;;${url}\x1b\\${url}\x1b]8;;\x1b\\`));
|
|
56
|
+
console.log(chalk.gray('\n下一步:'));
|
|
57
|
+
console.log(chalk.gray(`cd ${name}`));
|
|
58
|
+
console.log(chalk.gray(`minij server-deploy ${name}`));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function validateProjectName(name: string): void {
|
|
62
|
+
const validNamePattern = /^[a-zA-Z0-9_-]+$/;
|
|
63
|
+
if (!validNamePattern.test(name)) {
|
|
64
|
+
throw new Error('项目名仅支持字母、数字、中划线和下划线');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function resolveBackendPort(inputPort?: string): Promise<string> {
|
|
69
|
+
if (inputPort) {
|
|
70
|
+
validateBackendPort(inputPort);
|
|
71
|
+
return inputPort;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const rl = readline.createInterface({ input, output });
|
|
75
|
+
try {
|
|
76
|
+
const answer = await rl.question('请输入 Node 服务端口号(3000-3100,默认 3001): ');
|
|
77
|
+
const backendPort = answer.trim() || '3001';
|
|
78
|
+
validateBackendPort(backendPort);
|
|
79
|
+
return backendPort;
|
|
80
|
+
} finally {
|
|
81
|
+
rl.close();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function validateBackendPort(port: string): void {
|
|
86
|
+
if (!/^\d+$/.test(port)) {
|
|
87
|
+
throw new Error('Node 服务端口号必须是数字,范围 3000-3100,默认 3001');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const numericPort = Number(port);
|
|
91
|
+
if (numericPort < 3000 || numericPort > 3100) {
|
|
92
|
+
throw new Error('Node 服务端口号必须在 3000 到 3100 之间,默认 3001');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { checkSshConnectivity } from '../utils/ssh.js';
|
|
4
|
+
|
|
5
|
+
const CLAUDE_MD_CONTENT = `# 小吉 AI 执行规则
|
|
6
|
+
|
|
7
|
+
你是小吉建站系统的 AI 助手。
|
|
8
|
+
|
|
9
|
+
## 总原则
|
|
10
|
+
|
|
11
|
+
- 本项目中的建站、创建仓库、初始化代码、推送代码、部署服务,**优先使用 \`minij\` CLI**。
|
|
12
|
+
- **不要手动执行** clone 模板、修改项目名、创建 GitLab 仓库、初始化 git、推送代码,除非用户明确要求你不要使用 CLI。
|
|
13
|
+
- 当用户表达的目标已经对应到现有 CLI 能力时,直接调用 CLI,不要自己拆步骤重做。
|
|
14
|
+
|
|
15
|
+
## 环境要求
|
|
16
|
+
|
|
17
|
+
> **重要**:minij CLI 需在 **小吉 608 内网** 中使用。
|
|
18
|
+
> orchestrator 服务固定部署在 \`http://192.168.50.41:8888\`,不在内网无法连接。
|
|
19
|
+
|
|
20
|
+
## 创建项目
|
|
21
|
+
|
|
22
|
+
当用户提出以下任一需求时:
|
|
23
|
+
- 新建项目
|
|
24
|
+
- 创建项目
|
|
25
|
+
- 帮我建一个项目
|
|
26
|
+
- 帮我初始化一个新项目
|
|
27
|
+
- 帮我从模板创建项目
|
|
28
|
+
|
|
29
|
+
你的处理规则:
|
|
30
|
+
|
|
31
|
+
1. 如果用户**还没有提供项目英文名**,先询问项目英文名。
|
|
32
|
+
2. 如果用户**已经提供项目英文名和 Node 端口**,直接执行:
|
|
33
|
+
|
|
34
|
+
\`\`\`bash
|
|
35
|
+
minij create <项目英文名> --port <Node端口>
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
3. 如果用户**只提供了项目英文名**,直接执行:
|
|
39
|
+
|
|
40
|
+
\`\`\`bash
|
|
41
|
+
minij create <项目英文名>
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
此时 CLI 会继续询问 Node 服务端口(3000-3100,默认 3001)。
|
|
45
|
+
|
|
46
|
+
4. 执行 \`minij create\` 时,不要改用手动 git 命令替代。
|
|
47
|
+
5. 项目显示名默认直接使用英文项目名,不再单独询问中文项目名。
|
|
48
|
+
|
|
49
|
+
## 部署项目
|
|
50
|
+
|
|
51
|
+
当用户提出以下任一需求时:
|
|
52
|
+
- 部署项目
|
|
53
|
+
- 发布项目
|
|
54
|
+
- 帮我部署
|
|
55
|
+
- 帮我发布服务
|
|
56
|
+
|
|
57
|
+
直接执行:
|
|
58
|
+
|
|
59
|
+
\`\`\`bash
|
|
60
|
+
minij server-deploy <项目名>
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
如果用户没有提供项目名,先询问项目名。
|
|
64
|
+
|
|
65
|
+
## 初始化环境
|
|
66
|
+
|
|
67
|
+
当用户提出以下任一需求时:
|
|
68
|
+
- 初始化环境
|
|
69
|
+
- 检查环境
|
|
70
|
+
- 初始化小吉环境
|
|
71
|
+
|
|
72
|
+
直接执行:
|
|
73
|
+
|
|
74
|
+
\`\`\`bash
|
|
75
|
+
minij init
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
## 禁止事项
|
|
79
|
+
|
|
80
|
+
在以下场景中,禁止跳过 CLI 直接手动操作:
|
|
81
|
+
- 创建项目
|
|
82
|
+
- 从模板生成项目
|
|
83
|
+
- 创建 GitLab 仓库
|
|
84
|
+
- 初始化项目 git
|
|
85
|
+
- 首次推送代码
|
|
86
|
+
- 部署服务
|
|
87
|
+
|
|
88
|
+
除非用户明确说:
|
|
89
|
+
- 不要用 minij
|
|
90
|
+
- 我想手动执行
|
|
91
|
+
- 只给我命令,不要帮我执行
|
|
92
|
+
|
|
93
|
+
## 额外约束
|
|
94
|
+
|
|
95
|
+
- 项目英文名只能包含字母、数字、中划线、下划线
|
|
96
|
+
- Node 服务端口必须在 3000-3100 之间,默认 3001
|
|
97
|
+
- 必须在小吉 608 内网环境中执行,orchestrator 地址:\`http://192.168.50.41:8888\`
|
|
98
|
+
- 默认 GitLab 主机是 \`git.minij.com\`
|
|
99
|
+
- 默认创建仓库用户是 \`deployer-bot\`
|
|
100
|
+
`;
|
|
101
|
+
|
|
102
|
+
export async function initCommand(): Promise<void> {
|
|
103
|
+
await fs.writeFile('CLAUDE.md', CLAUDE_MD_CONTENT, 'utf-8');
|
|
104
|
+
console.log(chalk.green('✓ CLAUDE.md 文件已生成'));
|
|
105
|
+
|
|
106
|
+
const sshResult = await checkSshConnectivity();
|
|
107
|
+
|
|
108
|
+
if (sshResult.success) {
|
|
109
|
+
console.log(chalk.green(`✓ ${sshResult.message}`));
|
|
110
|
+
} else {
|
|
111
|
+
console.log(chalk.red(`✗ ${sshResult.message}`));
|
|
112
|
+
console.log(chalk.yellow('\n请按以下步骤配置 SSH 公钥:'));
|
|
113
|
+
console.log(chalk.cyan('1. 生成 SSH 密钥对:ssh-keygen -t ed25519 -C "your_email@example.com"'));
|
|
114
|
+
console.log(chalk.cyan('2. 查看公钥:cat ~/.ssh/id_ed25519.pub'));
|
|
115
|
+
console.log(chalk.cyan('3. 复制公钥到 GitLab -> Settings -> SSH Keys'));
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
import { withLoader } from '../utils/loader.js';
|
|
4
|
+
import { getOrchestratorUrl } from '../utils/config.js';
|
|
5
|
+
|
|
6
|
+
interface SetupRouterResponse {
|
|
7
|
+
success: boolean;
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function serverDeployCommand(name: string): Promise<void> {
|
|
12
|
+
const orchestratorUrl = await getOrchestratorUrl();
|
|
13
|
+
|
|
14
|
+
// 调用远程接口设置路由
|
|
15
|
+
const resultUrl = await withLoader('正在部署服务...', async () => {
|
|
16
|
+
try {
|
|
17
|
+
const response = await axios.post<SetupRouterResponse>(
|
|
18
|
+
`${orchestratorUrl}/api/setup-router`,
|
|
19
|
+
{
|
|
20
|
+
projectName: name
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (!response.data.success) {
|
|
25
|
+
throw new Error('部署失败');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return response.data.url;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
if (axios.isAxiosError(error)) {
|
|
31
|
+
throw new Error(`API 请求失败: ${error.message}`);
|
|
32
|
+
}
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log(chalk.green(`\n✓ 服务部署完成!`));
|
|
38
|
+
console.log(chalk.cyan(`\n访问地址:`));
|
|
39
|
+
console.log(chalk.bold.underline(resultUrl));
|
|
40
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// 模板地址配置
|
|
2
|
+
export const TEMPLATE_URL = 'git@git.minij.com:dataverse/minij-bi-admin-template.git';
|
|
3
|
+
|
|
4
|
+
// GitLab 配置
|
|
5
|
+
export const GITLAB_HOST = 'git.minij.com';
|
|
6
|
+
|
|
7
|
+
// 编排服务地址(固定部署在小吉 608 内网,需在该内网环境下使用)
|
|
8
|
+
export const DEFAULT_ORCHESTRATOR_URL = 'http://192.168.50.41:8888';
|
|
9
|
+
|
|
10
|
+
// 配置文件路径
|
|
11
|
+
export const CONFIG_PATH = `${process.env.HOME}/.minij/config.json`;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { initCommand } from './commands/init.js';
|
|
6
|
+
import { serverDeployCommand } from './commands/server-deploy.js';
|
|
7
|
+
import { createCommand } from './commands/create.js';
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('minij')
|
|
13
|
+
.description('minij一键建站方案 - 本地触发端')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('create <name>')
|
|
18
|
+
.description('一键创建项目:克隆模板、替换项目名、创建 GitLab 仓库、初始化 git 并推送')
|
|
19
|
+
.option('-p, --port <port>', 'Node 服务端口号(3000-3100,默认 3001)')
|
|
20
|
+
.action(async (name: string, options: { port?: string }) => {
|
|
21
|
+
try {
|
|
22
|
+
await createCommand(name, options);
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(chalk.red(`\n错误: ${(error as Error).message}`));
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
program
|
|
30
|
+
.command('init')
|
|
31
|
+
.description('初始化项目环境,生成 CLAUDE.md 并检查 SSH 配置')
|
|
32
|
+
.action(async () => {
|
|
33
|
+
try {
|
|
34
|
+
await initCommand();
|
|
35
|
+
} catch (error) {
|
|
36
|
+
console.error(chalk.red(`\n错误: ${(error as Error).message}`));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
program
|
|
42
|
+
.command('server-deploy <name>')
|
|
43
|
+
.description('调用远程接口部署服务')
|
|
44
|
+
.action(async (name: string) => {
|
|
45
|
+
try {
|
|
46
|
+
await serverDeployCommand(name);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.error(chalk.red(`\n错误: ${(error as Error).message}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
program.parse();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import shelljs from 'shelljs';
|
|
2
|
+
const shell = shelljs;
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import { withLoader } from '../utils/loader.js';
|
|
5
|
+
import { getOrchestratorUrl } from '../utils/config.js';
|
|
6
|
+
|
|
7
|
+
interface CreateRepoResponse {
|
|
8
|
+
success?: boolean;
|
|
9
|
+
sshUrl: string;
|
|
10
|
+
projectId: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface CreateGitlabRepoResult {
|
|
14
|
+
sshUrl: string;
|
|
15
|
+
projectId: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function createGitlabRepo(name: string): Promise<CreateGitlabRepoResult> {
|
|
19
|
+
const orchestratorUrl = await getOrchestratorUrl();
|
|
20
|
+
|
|
21
|
+
return withLoader('正在创建 GitLab 仓库...', async () => {
|
|
22
|
+
try {
|
|
23
|
+
const response = await axios.post<CreateRepoResponse>(
|
|
24
|
+
`${orchestratorUrl}/api/create-repo`,
|
|
25
|
+
{ projectName: name, userName: 'deployer-bot' }
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
if (!response.data.sshUrl || !response.data.projectId) {
|
|
29
|
+
throw new Error('接口未返回完整的仓库信息');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
sshUrl: response.data.sshUrl,
|
|
34
|
+
projectId: response.data.projectId,
|
|
35
|
+
};
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (axios.isAxiosError(error)) {
|
|
38
|
+
throw new Error(`API 请求失败: ${error.message}`);
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function setupCiVariables(
|
|
46
|
+
projectId: number,
|
|
47
|
+
name: string,
|
|
48
|
+
backendPort: string
|
|
49
|
+
): Promise<void> {
|
|
50
|
+
const orchestratorUrl = await getOrchestratorUrl();
|
|
51
|
+
|
|
52
|
+
await withLoader('正在绑定 CI/CD 变量...', async () => {
|
|
53
|
+
try {
|
|
54
|
+
await axios.post(`${orchestratorUrl}/api/setup-ci-variables`, {
|
|
55
|
+
projectId,
|
|
56
|
+
name,
|
|
57
|
+
backendPort,
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (axios.isAxiosError(error)) {
|
|
61
|
+
throw new Error(`API 请求失败: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
throw error;
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export async function initLocalGitRepo(projectPath: string, sshUrl: string): Promise<void> {
|
|
69
|
+
await withLoader('正在初始化 Git 仓库...', async () => {
|
|
70
|
+
const result = shell.exec('git init', { silent: true, cwd: projectPath });
|
|
71
|
+
if (result.code !== 0) throw new Error(`git init 失败: ${result.stderr}`);
|
|
72
|
+
|
|
73
|
+
const branchResult = shell.exec('git branch -M main', { silent: true, cwd: projectPath });
|
|
74
|
+
if (branchResult.code !== 0) throw new Error(`git branch 失败: ${branchResult.stderr}`);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
await withLoader('正在添加远程仓库...', async () => {
|
|
78
|
+
const result = shell.exec(`git remote add origin ${sshUrl}`, { silent: true, cwd: projectPath });
|
|
79
|
+
if (result.code !== 0) throw new Error(`添加远程仓库失败: ${result.stderr}`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await withLoader('正在提交初始代码...', async () => {
|
|
83
|
+
const addResult = shell.exec('git add .', { silent: true, cwd: projectPath });
|
|
84
|
+
if (addResult.code !== 0) throw new Error(`git add 失败: ${addResult.stderr}`);
|
|
85
|
+
|
|
86
|
+
const commitResult = shell.exec('git commit -m "chore: init project"', { silent: true, cwd: projectPath });
|
|
87
|
+
if (commitResult.code !== 0) throw new Error(`git commit 失败: ${commitResult.stderr}`);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
await withLoader('正在推送初始代码...', async () => {
|
|
91
|
+
const result = shell.exec('git push -u origin main', { silent: true, cwd: projectPath });
|
|
92
|
+
if (result.code !== 0) throw new Error(`git push 失败: ${result.stderr}`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { CONFIG_PATH, DEFAULT_ORCHESTRATOR_URL } from '../constants.js';
|
|
4
|
+
|
|
5
|
+
export interface MinijConfig {
|
|
6
|
+
ORCHESTRATOR_URL: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function loadConfig(): Promise<MinijConfig> {
|
|
10
|
+
try {
|
|
11
|
+
const config = await fs.readJson(CONFIG_PATH);
|
|
12
|
+
return config as MinijConfig;
|
|
13
|
+
} catch {
|
|
14
|
+
// 配置文件不存在,返回默认值
|
|
15
|
+
return { ORCHESTRATOR_URL: DEFAULT_ORCHESTRATOR_URL };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export async function saveConfig(config: MinijConfig): Promise<void> {
|
|
20
|
+
await fs.ensureFile(CONFIG_PATH);
|
|
21
|
+
await fs.writeJson(CONFIG_PATH, config, { spaces: 2 });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function getOrchestratorUrl(): Promise<string> {
|
|
25
|
+
const config = await loadConfig();
|
|
26
|
+
console.log(chalk.gray(`提示:minij CLI 需在小吉 608 内网中使用,服务地址:${config.ORCHESTRATOR_URL}`));
|
|
27
|
+
return config.ORCHESTRATOR_URL;
|
|
28
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import ora, { Ora } from 'ora';
|
|
2
|
+
|
|
3
|
+
export function createLoader(text: string): Ora {
|
|
4
|
+
return ora({
|
|
5
|
+
text,
|
|
6
|
+
color: 'cyan'
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function withLoader<T>(
|
|
11
|
+
text: string,
|
|
12
|
+
fn: () => Promise<T>
|
|
13
|
+
): Promise<T> {
|
|
14
|
+
const spinner = createLoader(text);
|
|
15
|
+
spinner.start();
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const result = await fn();
|
|
19
|
+
spinner.succeed();
|
|
20
|
+
return result;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
spinner.fail();
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/utils/ssh.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import shelljs from 'shelljs';
|
|
2
|
+
const { exec } = shelljs;
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { GITLAB_HOST } from '../constants.js';
|
|
5
|
+
|
|
6
|
+
export interface SshCheckResult {
|
|
7
|
+
success: boolean;
|
|
8
|
+
message: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function checkSshConnectivity(): Promise<SshCheckResult> {
|
|
12
|
+
return new Promise((resolve) => {
|
|
13
|
+
const command = `ssh -T git@${GITLAB_HOST} 2>&1`;
|
|
14
|
+
|
|
15
|
+
exec(command, { silent: true }, (code, stdout, stderr) => {
|
|
16
|
+
const output = stdout + stderr;
|
|
17
|
+
const lowerOutput = output.toLowerCase();
|
|
18
|
+
|
|
19
|
+
// 成功标志:GitLab 系列或 Gitee 的欢迎语,或包含用户名提示
|
|
20
|
+
const isSuccess =
|
|
21
|
+
lowerOutput.includes('welcome to gitlab') ||
|
|
22
|
+
lowerOutput.includes('welcome to gitee') ||
|
|
23
|
+
lowerOutput.includes('you\'ve successfully authenticated') ||
|
|
24
|
+
// 私有部署的 GitLab 通常也会包含用户名
|
|
25
|
+
lowerOutput.includes('hi ');
|
|
26
|
+
|
|
27
|
+
// 明确失败标志
|
|
28
|
+
const isFail =
|
|
29
|
+
lowerOutput.includes('permission denied') ||
|
|
30
|
+
lowerOutput.includes('publickey') ||
|
|
31
|
+
lowerOutput.includes('no such host') ||
|
|
32
|
+
lowerOutput.includes('connection refused');
|
|
33
|
+
|
|
34
|
+
if (isSuccess || (!isFail && output.length > 0)) {
|
|
35
|
+
resolve({
|
|
36
|
+
success: true,
|
|
37
|
+
message: 'SSH 连接成功'
|
|
38
|
+
});
|
|
39
|
+
} else {
|
|
40
|
+
resolve({
|
|
41
|
+
success: false,
|
|
42
|
+
message: 'SSH 连接失败,请先配置公钥'
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const TEMPLATE_NAME = 'minij-bi-admin-template';
|
|
5
|
+
|
|
6
|
+
export async function applyTemplateReplacements(
|
|
7
|
+
projectPath: string,
|
|
8
|
+
name: string,
|
|
9
|
+
backendPort: string
|
|
10
|
+
): Promise<void> {
|
|
11
|
+
await replaceJsonField(path.join(projectPath, 'package.json'), (pkg) => {
|
|
12
|
+
pkg.name = name;
|
|
13
|
+
return pkg;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
await replaceInFile(
|
|
17
|
+
path.join(projectPath, 'frontend/index.html'),
|
|
18
|
+
/<title>[^<]*<\/title>/,
|
|
19
|
+
`<title>${name}</title>`
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
await replaceAllInFile(path.join(projectPath, 'frontend/vite.config.ts'), TEMPLATE_NAME, name);
|
|
23
|
+
|
|
24
|
+
await replaceInFile(
|
|
25
|
+
path.join(projectPath, 'frontend/src/router/index.ts'),
|
|
26
|
+
/createWebHistory\([^)]*\)/g,
|
|
27
|
+
`createWebHistory('/DataVerse/${name}/')`
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
await replaceAllInFile(path.join(projectPath, 'frontend/src/router/guards.ts'), TEMPLATE_NAME, name);
|
|
31
|
+
await replaceAllInFile(path.join(projectPath, 'frontend/src/layouts/AdminLayout.vue'), TEMPLATE_NAME, name);
|
|
32
|
+
await replaceAllInFile(path.join(projectPath, 'frontend/src/views/home/HomeView.vue'), TEMPLATE_NAME, name);
|
|
33
|
+
await replaceAllInFile(path.join(projectPath, '.gitlab-ci.yml'), TEMPLATE_NAME, name);
|
|
34
|
+
await replaceAllInFile(path.join(projectPath, 'README.md'), TEMPLATE_NAME, name);
|
|
35
|
+
|
|
36
|
+
await replaceEnvValue(path.join(projectPath, 'backend/.env'), 'PORT', backendPort);
|
|
37
|
+
|
|
38
|
+
// 替换前端生产环境 API 地址
|
|
39
|
+
await replaceEnvValue(
|
|
40
|
+
path.join(projectPath, 'frontend/.env.production'),
|
|
41
|
+
'VITE_API_BASE_URL',
|
|
42
|
+
`https://node.minij.com/DataVerse/${name}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function replaceInFile(
|
|
47
|
+
filePath: string,
|
|
48
|
+
pattern: RegExp | string,
|
|
49
|
+
replacement: string
|
|
50
|
+
): Promise<void> {
|
|
51
|
+
if (!(await fs.pathExists(filePath))) return;
|
|
52
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
53
|
+
await fs.writeFile(filePath, content.replace(pattern, replacement), 'utf-8');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function replaceAllInFile(
|
|
57
|
+
filePath: string,
|
|
58
|
+
search: string,
|
|
59
|
+
replacement: string
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
if (!(await fs.pathExists(filePath))) return;
|
|
62
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
63
|
+
if (!content.includes(search)) return;
|
|
64
|
+
await fs.writeFile(filePath, content.split(search).join(replacement), 'utf-8');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function replaceEnvValue(filePath: string, key: string, value: string): Promise<void> {
|
|
68
|
+
if (!(await fs.pathExists(filePath))) return;
|
|
69
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
70
|
+
const pattern = new RegExp(`^${key}=.*$`, 'm');
|
|
71
|
+
const modified = pattern.test(content)
|
|
72
|
+
? content.replace(pattern, `${key}=${value}`)
|
|
73
|
+
: `${content.trimEnd()}\n${key}=${value}\n`;
|
|
74
|
+
await fs.writeFile(filePath, modified, 'utf-8');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function replaceJsonField(
|
|
78
|
+
filePath: string,
|
|
79
|
+
transform: (obj: Record<string, unknown>) => Record<string, unknown>
|
|
80
|
+
): Promise<void> {
|
|
81
|
+
if (!(await fs.pathExists(filePath))) return;
|
|
82
|
+
const obj = await fs.readJson(filePath);
|
|
83
|
+
await fs.writeJson(filePath, transform(obj), { spaces: 2 });
|
|
84
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"outDir": "./dist",
|
|
7
|
+
"rootDir": "./src",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"declaration": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"],
|
|
15
|
+
"exclude": ["node_modules", "dist"]
|
|
16
|
+
}
|