@lark-apaas/fullstack-cli 0.1.0-alpha.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 +89 -0
- package/bin/sync-scripts.js +185 -0
- package/dist/config/postinstall.config.d.ts +22 -0
- package/dist/config/postinstall.config.js +28 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/postinstall.config.d.ts +22 -0
- package/dist/postinstall.config.js +34 -0
- package/package.json +34 -0
- package/templates/.gitignore.append +1 -0
- package/templates/scripts/build.sh +49 -0
- package/templates/scripts/dev.sh +39 -0
- package/templates/scripts/gen-db-schema.ts +48 -0
- package/templates/scripts/gen-openapi.ts +24 -0
- package/templates/scripts/run.sh +4 -0
- package/templates/type.ts +4 -0
package/README.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @lark-apaas/fullstack-cli
|
|
2
|
+
|
|
3
|
+
> Fullstack 模板文件自动派生工具
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
自动派生和管理 fullstack 模板项目的文件和目录。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install --save-dev @lark-apaas/fullstack-cli
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 使用
|
|
16
|
+
|
|
17
|
+
### 自动派生
|
|
18
|
+
|
|
19
|
+
每次运行 `npm install` 时,会根据配置自动派生文件到项目:
|
|
20
|
+
|
|
21
|
+
**派生内容:**
|
|
22
|
+
- `scripts/` - 脚本目录(总是覆盖)
|
|
23
|
+
- `.gitignore` - 追加 fullstack-cli 相关忽略规则
|
|
24
|
+
|
|
25
|
+
### 重要提示
|
|
26
|
+
|
|
27
|
+
⚠️ **`scripts/` 目录由 `@lark-apaas/fullstack-cli` 自动管理,请勿手动修改!**
|
|
28
|
+
|
|
29
|
+
每次安装依赖时,所有脚本文件会被自动覆盖为最新版本。
|
|
30
|
+
|
|
31
|
+
## 配置
|
|
32
|
+
|
|
33
|
+
派生规则通过 `src/postinstall.config.ts` 配置:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
export default {
|
|
37
|
+
// 派生规则
|
|
38
|
+
sync: [
|
|
39
|
+
// 派生整个目录
|
|
40
|
+
{
|
|
41
|
+
from: 'templates/scripts',
|
|
42
|
+
to: 'scripts',
|
|
43
|
+
type: 'directory',
|
|
44
|
+
overwrite: true,
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
// 追加内容到文件
|
|
48
|
+
{
|
|
49
|
+
from: 'templates/.gitignore.append',
|
|
50
|
+
to: '.gitignore',
|
|
51
|
+
type: 'append',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
|
|
55
|
+
// 文件权限设置
|
|
56
|
+
permissions: {
|
|
57
|
+
'**/*.sh': 0o755,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 支持的 sync type
|
|
63
|
+
|
|
64
|
+
- **directory** - 派生整个目录
|
|
65
|
+
- `overwrite: true` - 总是覆盖
|
|
66
|
+
- `overwrite: false` - 只复制不存在的文件
|
|
67
|
+
|
|
68
|
+
- **file** - 派生单个文件
|
|
69
|
+
- `overwrite: true` - 总是覆盖
|
|
70
|
+
- `overwrite: false` - 仅当文件不存在时复制
|
|
71
|
+
|
|
72
|
+
- **append** - 追加内容到文件末尾
|
|
73
|
+
- 自动去重,不会重复追加
|
|
74
|
+
|
|
75
|
+
## 工作原理
|
|
76
|
+
|
|
77
|
+
1. 在 `postinstall` 钩子中自动运行
|
|
78
|
+
2. 检测是否在用户项目中(通过 `INIT_CWD`)
|
|
79
|
+
3. 读取 `dist/postinstall.config.js` 配置(由 `src/postinstall.config.ts` 编译而来)
|
|
80
|
+
4. 根据配置规则派生文件/目录
|
|
81
|
+
5. 设置文件权限
|
|
82
|
+
|
|
83
|
+
## 自定义需求
|
|
84
|
+
|
|
85
|
+
如需自定义脚本或配置,请联系框架团队。
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
// 主函数
|
|
7
|
+
async function main() {
|
|
8
|
+
// 检测是否在用户项目中(通过 INIT_CWD 判断)
|
|
9
|
+
const userProjectRoot = process.env.INIT_CWD || process.cwd();
|
|
10
|
+
const pluginRoot = path.resolve(__dirname, '..');
|
|
11
|
+
|
|
12
|
+
// 如果在插件自己的目录,跳过
|
|
13
|
+
const isInstallingPlugin = __dirname.includes('/node_modules/@lark-apaas/fullstack-cli/');
|
|
14
|
+
if (isInstallingPlugin || userProjectRoot === pluginRoot) {
|
|
15
|
+
console.log('[fullstack-cli] Skip syncing (installing plugin itself)');
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 检查是否存在 package.json,确保是有效的项目
|
|
20
|
+
const userPackageJson = path.join(userProjectRoot, 'package.json');
|
|
21
|
+
if (!fs.existsSync(userPackageJson)) {
|
|
22
|
+
console.log('[fullstack-cli] Skip syncing (not a valid npm project)');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
console.log('[fullstack-cli] Starting sync...');
|
|
28
|
+
|
|
29
|
+
// 加载配置文件(从 dist 目录加载编译后的配置)
|
|
30
|
+
const configPath = path.join(pluginRoot, 'dist', 'postinstall.config.js');
|
|
31
|
+
const { default: config } = await import(`file://${configPath}`);
|
|
32
|
+
|
|
33
|
+
if (!config || !config.sync) {
|
|
34
|
+
console.warn('[fullstack-cli] No sync configuration found');
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 执行同步规则
|
|
39
|
+
for (const rule of config.sync) {
|
|
40
|
+
await syncRule(rule, pluginRoot, userProjectRoot);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 设置权限
|
|
44
|
+
if (config.permissions) {
|
|
45
|
+
setPermissions(config.permissions, userProjectRoot);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log('[fullstack-cli] Sync completed successfully ✅');
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('[fullstack-cli] Failed to sync:', error.message);
|
|
51
|
+
// 不阻塞安装过程
|
|
52
|
+
process.exit(0);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 执行单个同步规则
|
|
58
|
+
*/
|
|
59
|
+
async function syncRule(rule, pluginRoot, userProjectRoot) {
|
|
60
|
+
const srcPath = path.join(pluginRoot, rule.from);
|
|
61
|
+
const destPath = path.join(userProjectRoot, rule.to);
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(srcPath)) {
|
|
64
|
+
console.warn(`[fullstack-cli] Source not found: ${rule.from}`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
switch (rule.type) {
|
|
69
|
+
case 'directory':
|
|
70
|
+
syncDirectory(srcPath, destPath, rule.overwrite);
|
|
71
|
+
break;
|
|
72
|
+
case 'file':
|
|
73
|
+
syncFile(srcPath, destPath, rule.overwrite);
|
|
74
|
+
break;
|
|
75
|
+
case 'append':
|
|
76
|
+
appendToFile(srcPath, destPath);
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
console.warn(`[fullstack-cli] Unknown sync type: ${rule.type}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 同步单个文件
|
|
85
|
+
*/
|
|
86
|
+
function syncFile(src, dest, overwrite = true) {
|
|
87
|
+
// 确保目标目录存在
|
|
88
|
+
const destDir = path.dirname(dest);
|
|
89
|
+
if (!fs.existsSync(destDir)) {
|
|
90
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 检查目标文件是否存在
|
|
94
|
+
if (fs.existsSync(dest) && !overwrite) {
|
|
95
|
+
console.log(`[fullstack-cli] ○ ${path.basename(dest)} (skipped, already exists)`);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 复制文件
|
|
100
|
+
fs.copyFileSync(src, dest);
|
|
101
|
+
console.log(`[fullstack-cli] ✓ ${path.basename(dest)}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 同步整个目录
|
|
106
|
+
*/
|
|
107
|
+
function syncDirectory(src, dest, overwrite = true) {
|
|
108
|
+
// 确保目标目录存在
|
|
109
|
+
if (!fs.existsSync(dest)) {
|
|
110
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 读取源目录所有文件
|
|
114
|
+
const files = fs.readdirSync(src);
|
|
115
|
+
let count = 0;
|
|
116
|
+
|
|
117
|
+
files.forEach(file => {
|
|
118
|
+
const srcFile = path.join(src, file);
|
|
119
|
+
const destFile = path.join(dest, file);
|
|
120
|
+
const stats = fs.statSync(srcFile);
|
|
121
|
+
|
|
122
|
+
if (stats.isDirectory()) {
|
|
123
|
+
// 递归处理子目录
|
|
124
|
+
syncDirectory(srcFile, destFile, overwrite);
|
|
125
|
+
} else {
|
|
126
|
+
// 复制文件
|
|
127
|
+
if (overwrite || !fs.existsSync(destFile)) {
|
|
128
|
+
fs.copyFileSync(srcFile, destFile);
|
|
129
|
+
console.log(`[fullstack-cli] ✓ ${path.relative(dest, destFile)}`);
|
|
130
|
+
count++;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (count > 0) {
|
|
136
|
+
console.log(`[fullstack-cli] Synced ${count} files to ${path.basename(dest)}/`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 追加内容到文件
|
|
142
|
+
*/
|
|
143
|
+
function appendToFile(src, dest) {
|
|
144
|
+
const content = fs.readFileSync(src, 'utf-8');
|
|
145
|
+
|
|
146
|
+
// 读取目标文件内容(如果存在)
|
|
147
|
+
let existingContent = '';
|
|
148
|
+
if (fs.existsSync(dest)) {
|
|
149
|
+
existingContent = fs.readFileSync(dest, 'utf-8');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// 检查是否已包含相同内容(避免重复追加)
|
|
153
|
+
if (existingContent.includes(content.trim())) {
|
|
154
|
+
console.log(`[fullstack-cli] ○ ${path.basename(dest)} (already contains content)`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 追加内容
|
|
159
|
+
fs.appendFileSync(dest, content);
|
|
160
|
+
console.log(`[fullstack-cli] ✓ ${path.basename(dest)} (appended)`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* 设置文件权限
|
|
165
|
+
*/
|
|
166
|
+
function setPermissions(permissions, projectRoot) {
|
|
167
|
+
for (const [pattern, mode] of Object.entries(permissions)) {
|
|
168
|
+
// 简单实现:只支持 **/*.sh 这种模式
|
|
169
|
+
if (pattern === '**/*.sh') {
|
|
170
|
+
const scriptsDir = path.join(projectRoot, 'scripts');
|
|
171
|
+
if (fs.existsSync(scriptsDir)) {
|
|
172
|
+
const files = fs.readdirSync(scriptsDir);
|
|
173
|
+
files.forEach(file => {
|
|
174
|
+
if (file.endsWith('.sh')) {
|
|
175
|
+
const filePath = path.join(scriptsDir, file);
|
|
176
|
+
fs.chmodSync(filePath, mode);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 运行主函数
|
|
185
|
+
main();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fullstack-cli 派生配置
|
|
3
|
+
* 定义哪些文件/目录需要同步到用户项目
|
|
4
|
+
*/
|
|
5
|
+
export interface SyncRule {
|
|
6
|
+
/** 源文件/目录路径(相对于插件根目录) */
|
|
7
|
+
from: string;
|
|
8
|
+
/** 目标文件/目录路径(相对于用户项目根目录) */
|
|
9
|
+
to: string;
|
|
10
|
+
/** 同步类型 */
|
|
11
|
+
type: 'directory' | 'file' | 'append';
|
|
12
|
+
/** 是否覆盖已存在的文件(仅 directory 和 file 类型有效) */
|
|
13
|
+
overwrite?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface PostinstallConfig {
|
|
16
|
+
/** 派生规则 */
|
|
17
|
+
sync: SyncRule[];
|
|
18
|
+
/** 文件权限设置 */
|
|
19
|
+
permissions?: Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
declare const config: PostinstallConfig;
|
|
22
|
+
export default config;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fullstack-cli 派生配置
|
|
3
|
+
* 定义哪些文件/目录需要同步到用户项目
|
|
4
|
+
*/
|
|
5
|
+
const config = {
|
|
6
|
+
// 派生规则
|
|
7
|
+
sync: [
|
|
8
|
+
// 1. 派生 scripts 目录(总是覆盖)
|
|
9
|
+
{
|
|
10
|
+
from: 'templates/scripts',
|
|
11
|
+
to: 'scripts',
|
|
12
|
+
type: 'directory',
|
|
13
|
+
overwrite: true,
|
|
14
|
+
},
|
|
15
|
+
// 2. 追加内容到 .gitignore
|
|
16
|
+
{
|
|
17
|
+
from: 'templates/.gitignore.append',
|
|
18
|
+
to: '.gitignore',
|
|
19
|
+
type: 'append',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
// 文件权限设置
|
|
23
|
+
permissions: {
|
|
24
|
+
// 所有 .sh 文件设置为可执行
|
|
25
|
+
'**/*.sh': 0o755,
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
export default config;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fullstack-cli 派生配置
|
|
3
|
+
* 定义哪些文件/目录需要同步到用户项目
|
|
4
|
+
*/
|
|
5
|
+
export interface SyncRule {
|
|
6
|
+
/** 源文件/目录路径(相对于插件根目录) */
|
|
7
|
+
from: string;
|
|
8
|
+
/** 目标文件/目录路径(相对于用户项目根目录) */
|
|
9
|
+
to: string;
|
|
10
|
+
/** 同步类型 */
|
|
11
|
+
type: 'directory' | 'file' | 'append';
|
|
12
|
+
/** 是否覆盖已存在的文件(仅 directory 和 file 类型有效) */
|
|
13
|
+
overwrite?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface PostinstallConfig {
|
|
16
|
+
/** 派生规则 */
|
|
17
|
+
sync: SyncRule[];
|
|
18
|
+
/** 文件权限设置 */
|
|
19
|
+
permissions?: Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
declare const config: PostinstallConfig;
|
|
22
|
+
export default config;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fullstack-cli 派生配置
|
|
3
|
+
* 定义哪些文件/目录需要同步到用户项目
|
|
4
|
+
*/
|
|
5
|
+
const config = {
|
|
6
|
+
// 派生规则
|
|
7
|
+
sync: [
|
|
8
|
+
// 1. 派生 scripts 目录(总是覆盖)
|
|
9
|
+
{
|
|
10
|
+
from: 'templates/scripts',
|
|
11
|
+
to: 'scripts',
|
|
12
|
+
type: 'directory',
|
|
13
|
+
overwrite: true,
|
|
14
|
+
},
|
|
15
|
+
// 2. 追加内容到 .gitignore
|
|
16
|
+
{
|
|
17
|
+
from: 'templates/.gitignore.append',
|
|
18
|
+
to: '.gitignore',
|
|
19
|
+
type: 'append',
|
|
20
|
+
},
|
|
21
|
+
// 3. 派生 server/type.ts 文件(总是覆盖)
|
|
22
|
+
{
|
|
23
|
+
from: 'templates/server/type.ts',
|
|
24
|
+
to: 'server/type.ts',
|
|
25
|
+
type: 'file',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
// 文件权限设置
|
|
29
|
+
permissions: {
|
|
30
|
+
// 所有 .sh 文件设置为可执行
|
|
31
|
+
'**/*.sh': 0o755,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export default config;
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lark-apaas/fullstack-cli",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "CLI tool for fullstack template management",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"templates",
|
|
10
|
+
"bin"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"postinstall": "node bin/sync-scripts.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"fullstack",
|
|
19
|
+
"cli",
|
|
20
|
+
"template"
|
|
21
|
+
],
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0",
|
|
25
|
+
"npm": ">=9.0.0"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^22.0.0",
|
|
32
|
+
"typescript": "^5.9.2"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# ===
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(pwd)"
|
|
5
|
+
OUT_DIR="$ROOT_DIR/dist/server"
|
|
6
|
+
|
|
7
|
+
echo "===================="
|
|
8
|
+
echo "0) 更新 openapi 代码"
|
|
9
|
+
echo "===================="
|
|
10
|
+
npm run gen:openapi
|
|
11
|
+
|
|
12
|
+
echo "===================="
|
|
13
|
+
echo "1) 清理 dist 目录"
|
|
14
|
+
echo "===================="
|
|
15
|
+
rm -rf "$ROOT_DIR/dist"
|
|
16
|
+
|
|
17
|
+
echo "===================="
|
|
18
|
+
echo "2) 构建项目"
|
|
19
|
+
echo "===================="
|
|
20
|
+
npm run build:prod
|
|
21
|
+
echo "✅ Build completed"
|
|
22
|
+
|
|
23
|
+
echo "===================="
|
|
24
|
+
echo "3) 准备 server 产物目录"
|
|
25
|
+
echo "===================="
|
|
26
|
+
mkdir -p "$OUT_DIR/dist/client"
|
|
27
|
+
|
|
28
|
+
# 拷贝 HTML
|
|
29
|
+
cp "$ROOT_DIR/dist/client/"*.html "$OUT_DIR/dist/client/" || true
|
|
30
|
+
|
|
31
|
+
# 拷贝 package.json
|
|
32
|
+
cp "$ROOT_DIR/package.json" "$OUT_DIR/"
|
|
33
|
+
[ -f "$ROOT_DIR/pnpm-lock.yaml" ] && cp "$ROOT_DIR/pnpm-lock.yaml" "$OUT_DIR/"
|
|
34
|
+
|
|
35
|
+
# 拷贝 run.sh 文件
|
|
36
|
+
cp "$ROOT_DIR/scripts/run.sh" "$OUT_DIR/"
|
|
37
|
+
|
|
38
|
+
# 清理无用文件
|
|
39
|
+
rm -rf "$ROOT_DIR/dist/scripts"
|
|
40
|
+
rm -rf "$ROOT_DIR/dist/tsconfig.node.tsbuildinfo"
|
|
41
|
+
|
|
42
|
+
echo "===================="
|
|
43
|
+
echo "4) 复制 node_modules(仅生产依赖)"
|
|
44
|
+
echo "===================="
|
|
45
|
+
|
|
46
|
+
npm prune --omit=dev
|
|
47
|
+
cp -R "$ROOT_DIR/node_modules" "$OUT_DIR/"
|
|
48
|
+
|
|
49
|
+
echo "✅ node_modules ready"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Ensure the script always runs from the project root
|
|
5
|
+
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
|
6
|
+
|
|
7
|
+
# Split and clean logs for server and client
|
|
8
|
+
split_logs() {
|
|
9
|
+
local log_dir=$1
|
|
10
|
+
|
|
11
|
+
awk -v server_log="${log_dir}/server.std.log" -v client_log="${log_dir}/client.std.log" '
|
|
12
|
+
{
|
|
13
|
+
# Remove all ANSI escape sequences (not just colors)
|
|
14
|
+
gsub(/\x1b\[[0-9;]*[a-zA-Z]/, "") # CSI sequences (colors, cursor movement, etc.)
|
|
15
|
+
gsub(/\x1b\][^\x07]*\x07/, "") # OSC sequences
|
|
16
|
+
gsub(/\x1b[=>]/, "") # Other escape sequences
|
|
17
|
+
|
|
18
|
+
# Write to appropriate log file and stdout
|
|
19
|
+
if ($0 ~ /\[server\]/) {
|
|
20
|
+
print $0 >> server_log
|
|
21
|
+
fflush(server_log)
|
|
22
|
+
} else if ($0 ~ /\[client\]/) {
|
|
23
|
+
print $0 >> client_log
|
|
24
|
+
fflush(client_log)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Always print to stdout (terminal)
|
|
28
|
+
print $0
|
|
29
|
+
fflush()
|
|
30
|
+
}
|
|
31
|
+
'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
LOG_DIR=${LOG_DIR:-logs}
|
|
35
|
+
mkdir -p "${LOG_DIR}"
|
|
36
|
+
|
|
37
|
+
concurrently -p "[{time}] [{name}]" -t "yyyy-MM-dd HH:mm:ss" -n "server,client" -c "blue,green" \
|
|
38
|
+
"npm run dev:server" \
|
|
39
|
+
"sleep 2 && npm run dev:client" 2>&1 | split_logs "${LOG_DIR}"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { postprocessDrizzleSchema } from '@lark-apaas/devtool-kits';
|
|
5
|
+
|
|
6
|
+
const OUT_DIR = path.resolve(process.cwd(), 'server/database/.introspect');
|
|
7
|
+
const SCHEMA_FILE = path.resolve(process.cwd(), 'server/database/schema.ts');
|
|
8
|
+
|
|
9
|
+
function run(): void {
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const spawnArgs = ['--yes', 'drizzle-kit', 'introspect', '--config', 'drizzle.config.ts', ...args];
|
|
12
|
+
|
|
13
|
+
const result = spawnSync('npx', spawnArgs, { stdio: 'inherit' });
|
|
14
|
+
if (result.error) {
|
|
15
|
+
console.error('[gen-db-schema] Execution failed:', result.error);
|
|
16
|
+
process.exit(result.status ?? 1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if ((result.status ?? 0) !== 0) {
|
|
20
|
+
process.exit(result.status ?? 1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const generatedSchema = path.join(OUT_DIR, 'schema.ts');
|
|
24
|
+
if (!fs.existsSync(generatedSchema)) {
|
|
25
|
+
console.warn('[gen-db-schema] schema.ts not generated');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fs.mkdirSync(path.dirname(SCHEMA_FILE), { recursive: true });
|
|
30
|
+
fs.copyFileSync(generatedSchema, SCHEMA_FILE);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const stats = postprocessDrizzleSchema(SCHEMA_FILE);
|
|
34
|
+
if (stats?.unmatchedUnknown?.length) {
|
|
35
|
+
console.warn('[gen-db-schema] Unmatched custom types detected');
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.warn('[gen-db-schema] postprocess failed:', error);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (fs.existsSync(OUT_DIR)) {
|
|
42
|
+
fs.rmSync(OUT_DIR, { recursive: true, force: true });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (require.main === module) {
|
|
47
|
+
run();
|
|
48
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { NestFactory } from '@nestjs/core';
|
|
2
|
+
import { DevToolsModule } from '@lark-apaas/fullstack-nestjs-core';
|
|
3
|
+
|
|
4
|
+
import { AppModule } from '../server/app.module';
|
|
5
|
+
|
|
6
|
+
async function generateOpenApi() {
|
|
7
|
+
const app = await NestFactory.create(AppModule, {logger: false});
|
|
8
|
+
const basePath = process.env.CLIENT_BASE_PATH;
|
|
9
|
+
app.setGlobalPrefix(basePath);
|
|
10
|
+
|
|
11
|
+
await DevToolsModule.mount(app, {
|
|
12
|
+
basePath,
|
|
13
|
+
docsPath: '/api_docs',
|
|
14
|
+
needSetupServer: false,
|
|
15
|
+
needGenerateClientSdk: true,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
await app.close();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
generateOpenApi().catch((err) => {
|
|
22
|
+
console.error('[OpenAPI] Failed to generate schema', err);
|
|
23
|
+
process.exitCode = 1;
|
|
24
|
+
});
|