@shys/crud 1.0.1-beta.0
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/dist/cli.d.ts +2 -0
- package/dist/cli.js +305 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.js +115 -0
- package/dist/db.d.ts +5 -0
- package/dist/db.js +132 -0
- package/dist/generator.d.ts +27 -0
- package/dist/generator.js +322 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/orm.d.ts +2 -0
- package/dist/orm.js +57 -0
- package/dist/renderer.d.ts +14 -0
- package/dist/renderer.js +62 -0
- package/dist/templates.d.ts +70 -0
- package/dist/templates.js +528 -0
- package/dist/types.d.ts +43 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +5 -0
- package/dist/utils.js +33 -0
- package/package.json +51 -0
- package/templates/controller.njk +65 -0
- package/templates/crud-page-vo.njk +59 -0
- package/templates/dto-create.njk +18 -0
- package/templates/dto-index.njk +4 -0
- package/templates/dto-query.njk +71 -0
- package/templates/dto-replace.njk +21 -0
- package/templates/dto-update.njk +22 -0
- package/templates/entity.njk +60 -0
- package/templates/service.njk +20 -0
- package/templates/vo-index.njk +2 -0
- package/templates/vo-list.njk +15 -0
- package/templates/vo-single.njk +24 -0
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { resolve, dirname, relative } from 'node:path';
|
|
4
|
+
import { createInterface } from 'node:readline';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { loadConfig } from './config.js';
|
|
8
|
+
import { detectOrm } from './orm.js';
|
|
9
|
+
import { getDbConfigFromProject, resolveDefaultDbName, getTableFieldsFromDb, templateFieldsFor, } from './db.js';
|
|
10
|
+
import { buildFiles } from './generator.js';
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
const WORKSPACE_ROOT = resolve(__dirname, '..', '..');
|
|
14
|
+
const ok = (t) => console.log(`${chalk.green('✔')} ${t}`);
|
|
15
|
+
const warn = (t) => console.log(`${chalk.yellow('!')} ${t}`);
|
|
16
|
+
const err = (t) => console.log(`${chalk.red('✘')} ${t}`);
|
|
17
|
+
const info = (t) => console.log(`${chalk.cyan('ℹ')} ${t}`);
|
|
18
|
+
function printHelp() {
|
|
19
|
+
console.log(`shys-crud CLI — MidwayJS v4 模块化 CRUD 代码生成器
|
|
20
|
+
|
|
21
|
+
用法:
|
|
22
|
+
shys-crud --module <moduleName> --table <tableName> [选项]
|
|
23
|
+
npx @shys/crud --module <moduleName> --table <tableName> [选项]
|
|
24
|
+
|
|
25
|
+
参数:
|
|
26
|
+
-m, --module 模块名(如 home/order/goods)
|
|
27
|
+
-t, --table 表名(如 sys_dept/city/order_info)
|
|
28
|
+
-s, --src src 根路径(默认 ./src 或从配置文件读取)
|
|
29
|
+
-o, --output 生成文件输出路径(默认与 --src 相同)
|
|
30
|
+
--templates-path 自定义模板目录路径
|
|
31
|
+
|
|
32
|
+
选项:
|
|
33
|
+
-f, --force 强制覆盖已存在的文件
|
|
34
|
+
-n, --dry-run 模拟运行,不实际写入文件
|
|
35
|
+
-c, --compile 生成完后自动执行 mwtsc --cleanOutDir
|
|
36
|
+
--only-entity 仅生成 entity 文件,不生成 DTO/VO/Service/Controller
|
|
37
|
+
--no-db 不尝试连库,使用模板字段模式
|
|
38
|
+
--db-host 数据库 host
|
|
39
|
+
--db-port 数据库端口(默认 3306)
|
|
40
|
+
--db-user 数据库用户名
|
|
41
|
+
--db-pass 数据库密码
|
|
42
|
+
--db-name 数据库名
|
|
43
|
+
-h, --help 显示帮助信息
|
|
44
|
+
|
|
45
|
+
配置文件支持(按优先级从高到低):
|
|
46
|
+
--src 指向的目录或 cwd 下查找:
|
|
47
|
+
- crud.config.ts / crud.config.js / crud.config.mjs / crud.config.cjs
|
|
48
|
+
- .crudrc / .crudrc.json / .crudrc.yaml / .crudrc.yml / .crudrc.js
|
|
49
|
+
- .config/crudrc / .config/crudrc.json
|
|
50
|
+
- package.json 中的 "crud" 字段
|
|
51
|
+
|
|
52
|
+
配置文件示例(crud.config.ts):
|
|
53
|
+
export default {
|
|
54
|
+
baseSrcPath: './src',
|
|
55
|
+
outputPath: './src',
|
|
56
|
+
db: {
|
|
57
|
+
host: '127.0.0.1',
|
|
58
|
+
port: 3306,
|
|
59
|
+
user: 'root',
|
|
60
|
+
password: '',
|
|
61
|
+
database: 'mydb',
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
示例:
|
|
66
|
+
shys-crud --module home --table sys_dept
|
|
67
|
+
shys-crud --module order --table order_info --force
|
|
68
|
+
shys-crud -m home -t city -s ./packages/webtest/src --db-name world
|
|
69
|
+
`);
|
|
70
|
+
}
|
|
71
|
+
function parseArgs(argv) {
|
|
72
|
+
const cli = { _: [] };
|
|
73
|
+
for (let i = 0; i < argv.length; i++) {
|
|
74
|
+
const a = argv[i];
|
|
75
|
+
if (a === '--help' || a === '-h')
|
|
76
|
+
cli.help = true;
|
|
77
|
+
else if (a === '--force' || a === '-f')
|
|
78
|
+
cli.force = true;
|
|
79
|
+
else if (a === '--dry-run' || a === '-n')
|
|
80
|
+
cli.dryRun = true;
|
|
81
|
+
else if (a === '--compile' || a === '-c')
|
|
82
|
+
cli.compile = true;
|
|
83
|
+
else if (a === '--only-entity')
|
|
84
|
+
cli.onlyEntity = true;
|
|
85
|
+
else if (a === '--no-db')
|
|
86
|
+
cli.noDb = true;
|
|
87
|
+
else if (a === '--module' || a === '-m')
|
|
88
|
+
cli.moduleName = argv[++i];
|
|
89
|
+
else if (a === '--table' || a === '-t')
|
|
90
|
+
cli.tableName = argv[++i];
|
|
91
|
+
else if (a === '--src' || a === '-s')
|
|
92
|
+
cli.baseSrcPath = argv[++i];
|
|
93
|
+
else if (a === '--output' || a === '-o')
|
|
94
|
+
cli.outputPath = argv[++i];
|
|
95
|
+
else if (a === '--templates-path')
|
|
96
|
+
cli.templatesPath = argv[++i];
|
|
97
|
+
else if (a === '--db-host')
|
|
98
|
+
cli.db = { ...(cli.db || {}), host: argv[++i] };
|
|
99
|
+
else if (a === '--db-port')
|
|
100
|
+
cli.db = { ...(cli.db || {}), port: Number(argv[++i]) };
|
|
101
|
+
else if (a === '--db-user')
|
|
102
|
+
cli.db = { ...(cli.db || {}), user: argv[++i] };
|
|
103
|
+
else if (a === '--db-pass')
|
|
104
|
+
cli.db = { ...(cli.db || {}), password: argv[++i] };
|
|
105
|
+
else if (a === '--db-name')
|
|
106
|
+
cli.db = { ...(cli.db || {}), database: argv[++i] };
|
|
107
|
+
else
|
|
108
|
+
cli._.push(a);
|
|
109
|
+
}
|
|
110
|
+
return cli;
|
|
111
|
+
}
|
|
112
|
+
async function askIfMissing(cli, config) {
|
|
113
|
+
const rl = createInterface({
|
|
114
|
+
input: process.stdin,
|
|
115
|
+
output: process.stdout,
|
|
116
|
+
terminal: !!process.stdin.isTTY,
|
|
117
|
+
});
|
|
118
|
+
const ask = (q, dft) => new Promise((res) => rl.question(q + (dft != null ? ` ${chalk.gray(`[${dft}]`)} ` : ' '), (ans) => res(ans.trim() || dft || '')));
|
|
119
|
+
try {
|
|
120
|
+
const isTTY = !!process.stdin.isTTY;
|
|
121
|
+
if (!cli.moduleName)
|
|
122
|
+
cli.moduleName = isTTY
|
|
123
|
+
? await ask(`${chalk.cyan('?')} moduleName (home/order/goods):`, config.moduleName || 'home')
|
|
124
|
+
: config.moduleName || 'home';
|
|
125
|
+
if (!cli.tableName)
|
|
126
|
+
cli.tableName = isTTY
|
|
127
|
+
? await ask(`${chalk.cyan('?')} tableName (city/sys_user/order_info):`, config.tableName || 'city')
|
|
128
|
+
: config.tableName || 'city';
|
|
129
|
+
if (!cli.baseSrcPath) {
|
|
130
|
+
const defaultSrc = config.baseSrcPath || resolve(process.cwd(), 'src');
|
|
131
|
+
cli.baseSrcPath = isTTY
|
|
132
|
+
? await ask(`${chalk.cyan('?')} baseSrcPath (Midway src 根绝对路径):`, defaultSrc)
|
|
133
|
+
: defaultSrc;
|
|
134
|
+
}
|
|
135
|
+
if (!cli.outputPath && config.outputPath) {
|
|
136
|
+
cli.outputPath = config.outputPath;
|
|
137
|
+
}
|
|
138
|
+
if (!cli.noDb && config.noDb) {
|
|
139
|
+
cli.noDb = true;
|
|
140
|
+
}
|
|
141
|
+
if (!cli.force && config.force) {
|
|
142
|
+
cli.force = true;
|
|
143
|
+
}
|
|
144
|
+
if (!cli.dryRun && config.dryRun) {
|
|
145
|
+
cli.dryRun = true;
|
|
146
|
+
}
|
|
147
|
+
if (!cli.compile && config.compile) {
|
|
148
|
+
cli.compile = true;
|
|
149
|
+
}
|
|
150
|
+
if (!cli.templatesPath && config.templatesPath) {
|
|
151
|
+
cli.templatesPath = config.templatesPath;
|
|
152
|
+
}
|
|
153
|
+
if (!cli.onlyEntity && config.onlyEntity) {
|
|
154
|
+
cli.onlyEntity = true;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
rl.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
async function askDbConfig(cli, config) {
|
|
162
|
+
if (cli.noDb)
|
|
163
|
+
return;
|
|
164
|
+
const resolvedSrcPath = resolve(process.cwd(), cli.baseSrcPath || '.');
|
|
165
|
+
const autoDbCfg = await getDbConfigFromProject(resolvedSrcPath);
|
|
166
|
+
const mergedDb = { ...(config.db || {}), ...(autoDbCfg || {}), ...(cli.db || {}) };
|
|
167
|
+
const rl = createInterface({
|
|
168
|
+
input: process.stdin,
|
|
169
|
+
output: process.stdout,
|
|
170
|
+
terminal: !!process.stdin.isTTY,
|
|
171
|
+
});
|
|
172
|
+
const ask = (q, dft) => new Promise((res) => rl.question(q + (dft != null ? ` ${chalk.gray(`[${dft}]`)} ` : ' '), (ans) => res(ans.trim() || dft || '')));
|
|
173
|
+
try {
|
|
174
|
+
if (!mergedDb.host)
|
|
175
|
+
mergedDb.host = await ask(`${chalk.cyan('?')} db-host:`, '127.0.0.1');
|
|
176
|
+
if (mergedDb.port == null)
|
|
177
|
+
mergedDb.port = Number(await ask(`${chalk.cyan('?')} db-port:`, '3306'));
|
|
178
|
+
if (!mergedDb.user)
|
|
179
|
+
mergedDb.user = await ask(`${chalk.cyan('?')} db-user:`, 'root');
|
|
180
|
+
if (mergedDb.password == null)
|
|
181
|
+
mergedDb.password = await ask(`${chalk.cyan('?')} db-pass:`, '');
|
|
182
|
+
if (!mergedDb.database)
|
|
183
|
+
mergedDb.database = await ask(`${chalk.cyan('?')} db-name:`, resolveDefaultDbName(resolvedSrcPath) || '');
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
rl.close();
|
|
187
|
+
}
|
|
188
|
+
cli.db = mergedDb;
|
|
189
|
+
}
|
|
190
|
+
function ensureDir(p) {
|
|
191
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
192
|
+
}
|
|
193
|
+
function writeAll(files, opts) {
|
|
194
|
+
let created = 0, skipped = 0, overwritten = 0;
|
|
195
|
+
for (const f of files) {
|
|
196
|
+
const fileExists = existsSync(f.path);
|
|
197
|
+
const isCreateOnly = !!f.createOnly;
|
|
198
|
+
let action = 'CREATE';
|
|
199
|
+
if (fileExists) {
|
|
200
|
+
if (isCreateOnly) {
|
|
201
|
+
action = 'SKIP(exist)';
|
|
202
|
+
skipped++;
|
|
203
|
+
}
|
|
204
|
+
else if (!opts.force) {
|
|
205
|
+
action = 'SKIP(exist)';
|
|
206
|
+
skipped++;
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
action = 'OVERWRITE';
|
|
210
|
+
overwritten++;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
created++;
|
|
215
|
+
}
|
|
216
|
+
const rel = relative(process.cwd(), f.path);
|
|
217
|
+
if (opts.dryRun) {
|
|
218
|
+
console.log(` ${chalk.gray('[dry-run]')} ${action} ${rel}`);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
if (action === 'SKIP(exist)') {
|
|
222
|
+
warn(`已存在(跳过,加 --force 覆盖):${rel}`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
ensureDir(f.path);
|
|
226
|
+
writeFileSync(f.path, f.content, 'utf-8');
|
|
227
|
+
ok(`${action}: ${rel}`);
|
|
228
|
+
}
|
|
229
|
+
return { created, skipped, overwritten };
|
|
230
|
+
}
|
|
231
|
+
async function describeTableOrTemplate(cli, tableName) {
|
|
232
|
+
if (!cli.noDb && cli.db?.database) {
|
|
233
|
+
try {
|
|
234
|
+
const fields = await getTableFieldsFromDb(cli.db, tableName);
|
|
235
|
+
if (fields.length) {
|
|
236
|
+
ok(`DESCRIBE ${tableName} 成功,${fields.length} 列`);
|
|
237
|
+
return fields;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
catch (e) {
|
|
241
|
+
warn(`DB 连接失败:${e.message},回退到模板字段`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
warn('使用模板字段模式(可通过 --db-name/--db-host... 启用真实 DESCRIBE)');
|
|
245
|
+
return templateFieldsFor(tableName);
|
|
246
|
+
}
|
|
247
|
+
export async function runCli(argv = process.argv.slice(2)) {
|
|
248
|
+
const cli = parseArgs(argv);
|
|
249
|
+
if (cli.help) {
|
|
250
|
+
printHelp();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
console.log(`${chalk.cyan('shys-crud CLI')} cwd: ${process.cwd()}`);
|
|
254
|
+
const fileConfig = await loadConfig(process.cwd());
|
|
255
|
+
await askIfMissing(cli, fileConfig);
|
|
256
|
+
const baseSrcPath = resolve(process.cwd(), cli.baseSrcPath || '.');
|
|
257
|
+
if (!existsSync(baseSrcPath) || !existsSync(resolve(baseSrcPath, '../package.json'))) {
|
|
258
|
+
err(`baseSrcPath 不是一个有效的 Midway 业务包 src:${baseSrcPath}`);
|
|
259
|
+
process.exit(2);
|
|
260
|
+
}
|
|
261
|
+
const ormInfo = detectOrm(baseSrcPath);
|
|
262
|
+
info(`ORM: ${ormInfo.orm} suffix: ${ormInfo.ormSuffix}`);
|
|
263
|
+
await askDbConfig(cli, fileConfig);
|
|
264
|
+
const fields = await describeTableOrTemplate(cli, cli.tableName || '');
|
|
265
|
+
if (!fields.length) {
|
|
266
|
+
err('字段为空,无法生成');
|
|
267
|
+
process.exit(3);
|
|
268
|
+
}
|
|
269
|
+
const outputPath = cli.outputPath ? resolve(process.cwd(), cli.outputPath) : baseSrcPath;
|
|
270
|
+
const templatesPath = cli.templatesPath ? resolve(process.cwd(), cli.templatesPath) : undefined;
|
|
271
|
+
const built = buildFiles({
|
|
272
|
+
moduleName: cli.moduleName || 'home',
|
|
273
|
+
tableName: cli.tableName || '',
|
|
274
|
+
baseSrcPath,
|
|
275
|
+
ormInfo,
|
|
276
|
+
fields,
|
|
277
|
+
outputPath,
|
|
278
|
+
templatesPath,
|
|
279
|
+
onlyEntity: cli.onlyEntity,
|
|
280
|
+
});
|
|
281
|
+
console.log(`\n${chalk.cyan(`生成清单(${built.files.length})`)}`);
|
|
282
|
+
const stats = writeAll(built.files, { force: !!cli.force, dryRun: !!cli.dryRun });
|
|
283
|
+
console.log(`\n${chalk.green('完成')}:created=${stats.created} overwritten=${stats.overwritten} skipped=${stats.skipped}`);
|
|
284
|
+
const src = relative(process.cwd(), baseSrcPath);
|
|
285
|
+
console.log(`\n${chalk.cyan('下一步(建议按顺序执行):')}`);
|
|
286
|
+
console.log(` 1) 编译:cd ${src ? src + '/..' : '.'} ; npx mwtsc --cleanOutDir`);
|
|
287
|
+
console.log(` 2) 启动:node ${resolve(baseSrcPath, '../bootstrap.js')}`);
|
|
288
|
+
console.log(` 3) 验证列表:curl http://127.0.0.1:7001/api/${built.pluralKebab}?page=1&limit=2`);
|
|
289
|
+
if (cli.dryRun) {
|
|
290
|
+
info('这是 dry-run,未写任何文件。去掉 -n 即可实际写入。');
|
|
291
|
+
}
|
|
292
|
+
else if (cli.compile) {
|
|
293
|
+
info('执行 mwtsc --cleanOutDir...');
|
|
294
|
+
const { spawnSync } = await import('node:child_process');
|
|
295
|
+
const cwd = resolve(baseSrcPath, '..');
|
|
296
|
+
const res = spawnSync(process.platform === 'win32' ? 'npx.cmd' : 'npx', ['mwtsc', '--cleanOutDir'], { cwd, stdio: 'inherit', shell: false });
|
|
297
|
+
process.exit(res.status || 0);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
if (process.argv[1] && process.argv[1].endsWith('cli.js')) {
|
|
301
|
+
runCli().catch((e) => {
|
|
302
|
+
err(e.stack || String(e));
|
|
303
|
+
process.exit(1);
|
|
304
|
+
});
|
|
305
|
+
}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { cosmiconfig } from 'cosmiconfig';
|
|
2
|
+
const MODULE_NAME = 'crud';
|
|
3
|
+
export async function loadConfig(cwd = process.cwd()) {
|
|
4
|
+
const explorer = cosmiconfig(MODULE_NAME, {
|
|
5
|
+
searchPlaces: [
|
|
6
|
+
'package.json',
|
|
7
|
+
`.${MODULE_NAME}rc`,
|
|
8
|
+
`.${MODULE_NAME}rc.json`,
|
|
9
|
+
`.${MODULE_NAME}rc.yaml`,
|
|
10
|
+
`.${MODULE_NAME}rc.yml`,
|
|
11
|
+
`.${MODULE_NAME}rc.js`,
|
|
12
|
+
`.${MODULE_NAME}rc.cjs`,
|
|
13
|
+
`.${MODULE_NAME}rc.mjs`,
|
|
14
|
+
`.config/${MODULE_NAME}rc`,
|
|
15
|
+
`.config/${MODULE_NAME}rc.json`,
|
|
16
|
+
`.config/${MODULE_NAME}rc.yaml`,
|
|
17
|
+
`.config/${MODULE_NAME}rc.yml`,
|
|
18
|
+
`.config/${MODULE_NAME}rc.js`,
|
|
19
|
+
`.config/${MODULE_NAME}rc.cjs`,
|
|
20
|
+
`.config/${MODULE_NAME}rc.mjs`,
|
|
21
|
+
`${MODULE_NAME}.config.js`,
|
|
22
|
+
`${MODULE_NAME}.config.cjs`,
|
|
23
|
+
`${MODULE_NAME}.config.mjs`,
|
|
24
|
+
`${MODULE_NAME}.config.ts`,
|
|
25
|
+
`${MODULE_NAME}.config.cts`,
|
|
26
|
+
`${MODULE_NAME}.config.mts`,
|
|
27
|
+
],
|
|
28
|
+
loaders: {
|
|
29
|
+
'.ts': async (filepath) => {
|
|
30
|
+
try {
|
|
31
|
+
// @ts-ignore - tsx is optional runtime dependency
|
|
32
|
+
const tsxMod = await import('tsx');
|
|
33
|
+
const result = await tsxMod.loadConfig(filepath);
|
|
34
|
+
return result?.default ?? result;
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
try {
|
|
38
|
+
// @ts-ignore - jiti is optional runtime dependency
|
|
39
|
+
const jitiMod = await import('jiti');
|
|
40
|
+
const jiti = jitiMod.default;
|
|
41
|
+
const load = jiti(filepath, { esmResolve: true, interopDefault: true });
|
|
42
|
+
const result = load(filepath);
|
|
43
|
+
return result?.default ?? result;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
'.cts': async (filepath) => {
|
|
51
|
+
try {
|
|
52
|
+
// @ts-ignore - tsx is optional runtime dependency
|
|
53
|
+
const tsxMod = await import('tsx');
|
|
54
|
+
const result = await tsxMod.loadConfig(filepath);
|
|
55
|
+
return result?.default ?? result;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return {};
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
'.mts': async (filepath) => {
|
|
62
|
+
try {
|
|
63
|
+
// @ts-ignore - tsx is optional runtime dependency
|
|
64
|
+
const tsxMod = await import('tsx');
|
|
65
|
+
const result = await tsxMod.loadConfig(filepath);
|
|
66
|
+
return result?.default ?? result;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return {};
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
try {
|
|
75
|
+
const result = await explorer.search(cwd);
|
|
76
|
+
if (result && !result.isEmpty) {
|
|
77
|
+
return result.config;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// 配置文件加载失败时静默返回空配置
|
|
82
|
+
}
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
export function mergeConfigs(...configs) {
|
|
86
|
+
const result = {};
|
|
87
|
+
for (const config of configs) {
|
|
88
|
+
if (!config)
|
|
89
|
+
continue;
|
|
90
|
+
if (config.baseSrcPath !== undefined)
|
|
91
|
+
result.baseSrcPath = config.baseSrcPath;
|
|
92
|
+
if (config.moduleName !== undefined)
|
|
93
|
+
result.moduleName = config.moduleName;
|
|
94
|
+
if (config.tableName !== undefined)
|
|
95
|
+
result.tableName = config.tableName;
|
|
96
|
+
if (config.noDb !== undefined)
|
|
97
|
+
result.noDb = config.noDb;
|
|
98
|
+
if (config.force !== undefined)
|
|
99
|
+
result.force = config.force;
|
|
100
|
+
if (config.dryRun !== undefined)
|
|
101
|
+
result.dryRun = config.dryRun;
|
|
102
|
+
if (config.compile !== undefined)
|
|
103
|
+
result.compile = config.compile;
|
|
104
|
+
if (config.outputPath !== undefined)
|
|
105
|
+
result.outputPath = config.outputPath;
|
|
106
|
+
if (config.templatesPath !== undefined)
|
|
107
|
+
result.templatesPath = config.templatesPath;
|
|
108
|
+
if (config.onlyEntity !== undefined)
|
|
109
|
+
result.onlyEntity = config.onlyEntity;
|
|
110
|
+
if (config.db) {
|
|
111
|
+
result.db = { ...(result.db || {}), ...config.db };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
package/dist/db.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CrudDbConfig, FieldInfo } from './types.js';
|
|
2
|
+
export declare function getDbConfigFromProject(baseSrcPath: string): Promise<CrudDbConfig | null>;
|
|
3
|
+
export declare function resolveDefaultDbName(baseSrcPath: string): string;
|
|
4
|
+
export declare function getTableFieldsFromDb(dbConfig: CrudDbConfig, tableName: string): Promise<FieldInfo[]>;
|
|
5
|
+
export declare function templateFieldsFor(tableName: string): FieldInfo[];
|
package/dist/db.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { toCamelCase } from './utils.js';
|
|
5
|
+
export async function getDbConfigFromProject(baseSrcPath) {
|
|
6
|
+
const configLocalPath = resolve(baseSrcPath, 'config/config.local.ts');
|
|
7
|
+
const configDefaultPath = resolve(baseSrcPath, 'config/config.default.ts');
|
|
8
|
+
let configContent = '';
|
|
9
|
+
if (await fs.pathExists(configLocalPath)) {
|
|
10
|
+
configContent = await fs.readFile(configLocalPath, 'utf-8');
|
|
11
|
+
}
|
|
12
|
+
else if (await fs.pathExists(configDefaultPath)) {
|
|
13
|
+
configContent = await fs.readFile(configDefaultPath, 'utf-8');
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
const typeMatch = configContent.match(/type:\s*['"]([^'"]+)['"]/);
|
|
19
|
+
const hostMatch = configContent.match(/host:\s*['"]([^'"]+)['"]/);
|
|
20
|
+
const portMatch = configContent.match(/port:\s*(\d+)/);
|
|
21
|
+
const usernameMatch = configContent.match(/username:\s*['"]([^'"]+)['"]/);
|
|
22
|
+
const passwordMatch = configContent.match(/password:\s*['"]([^'"]+)['"]/);
|
|
23
|
+
const databaseMatch = configContent.match(/database:\s*['"]([^'"]+)['"]/);
|
|
24
|
+
return {
|
|
25
|
+
type: typeMatch ? typeMatch[1] : 'mysql',
|
|
26
|
+
host: hostMatch ? hostMatch[1] : '127.0.0.1',
|
|
27
|
+
port: portMatch ? parseInt(portMatch[1], 10) : 3306,
|
|
28
|
+
user: usernameMatch ? usernameMatch[1] : 'root',
|
|
29
|
+
password: passwordMatch ? passwordMatch[1] : '',
|
|
30
|
+
database: databaseMatch ? databaseMatch[1] : '',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function resolveDefaultDbName(baseSrcPath) {
|
|
34
|
+
try {
|
|
35
|
+
const cfg = readFileSync(resolve(baseSrcPath, 'config/config.default.ts'), 'utf-8');
|
|
36
|
+
const m = cfg.match(/database:\s*['"]([^'"]+)['"]/);
|
|
37
|
+
return m ? m[1] : '';
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return '';
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export async function getTableFieldsFromDb(dbConfig, tableName) {
|
|
44
|
+
const { host, port, user, password, database } = dbConfig;
|
|
45
|
+
try {
|
|
46
|
+
const mysql2 = await import('mysql2/promise');
|
|
47
|
+
const connection = await mysql2.createConnection({
|
|
48
|
+
host: host || '127.0.0.1',
|
|
49
|
+
port: parseInt(String(port || '3306'), 10),
|
|
50
|
+
user: user || 'root',
|
|
51
|
+
password: password ?? '',
|
|
52
|
+
database: database || '',
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
const [rows] = await connection.execute(`SELECT column_name, data_type, is_nullable, column_type, column_comment, column_default, character_maximum_length, extra
|
|
56
|
+
FROM information_schema.COLUMNS
|
|
57
|
+
WHERE table_schema = ? AND table_name = ?
|
|
58
|
+
ORDER BY ordinal_position`, [database, tableName]);
|
|
59
|
+
return rows
|
|
60
|
+
.map((row) => {
|
|
61
|
+
const columnName = row.COLUMN_NAME || row.column_name || '';
|
|
62
|
+
if (!columnName)
|
|
63
|
+
return null;
|
|
64
|
+
const camelName = toCamelCase(columnName);
|
|
65
|
+
const dataType = row.DATA_TYPE || row.data_type || 'varchar';
|
|
66
|
+
const nullable = (row.IS_NULLABLE || row.is_nullable || 'NO') === 'YES';
|
|
67
|
+
const comment = row.COLUMN_COMMENT || row.column_comment || '';
|
|
68
|
+
const defaultVal = row.COLUMN_DEFAULT || row.column_default;
|
|
69
|
+
const length = row.CHARACTER_MAXIMUM_LENGTH || row.character_maximum_length;
|
|
70
|
+
const extra = row.EXTRA || row.extra || '';
|
|
71
|
+
const isPk = extra.includes('PRI') || columnName.toLowerCase().includes('id');
|
|
72
|
+
const isAuto = extra.includes('auto_increment');
|
|
73
|
+
return {
|
|
74
|
+
name: columnName,
|
|
75
|
+
camel: camelName,
|
|
76
|
+
type: dataType,
|
|
77
|
+
nullable,
|
|
78
|
+
len: length,
|
|
79
|
+
pk: isPk,
|
|
80
|
+
auto: isAuto,
|
|
81
|
+
comment,
|
|
82
|
+
defaultVal,
|
|
83
|
+
};
|
|
84
|
+
})
|
|
85
|
+
.filter(Boolean);
|
|
86
|
+
}
|
|
87
|
+
finally {
|
|
88
|
+
await connection.end();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
return [];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export function templateFieldsFor(tableName) {
|
|
96
|
+
const t = tableName.toLowerCase();
|
|
97
|
+
if (t === 'city')
|
|
98
|
+
return [
|
|
99
|
+
{ name: 'ID', camel: 'id', type: 'int', len: null, nullable: false, pk: true, auto: true, defaultVal: null, comment: '城市ID,自增主键' },
|
|
100
|
+
{ name: 'Name', camel: 'name', type: 'char', len: 35, nullable: false, pk: false, auto: false, defaultVal: '', comment: '城市名称,最大35字符' },
|
|
101
|
+
{ name: 'CountryCode', camel: 'countryCode', type: 'char', len: 3, nullable: false, pk: false, auto: false, defaultVal: '', comment: '所属国家代码,3位字符' },
|
|
102
|
+
{ name: 'District', camel: 'district', type: 'char', len: 20, nullable: false, pk: false, auto: false, defaultVal: '', comment: '所在省/州/行政区' },
|
|
103
|
+
{ name: 'Population', camel: 'population', type: 'int', len: null, nullable: false, pk: false, auto: false, defaultVal: 0, comment: '城市人口' },
|
|
104
|
+
];
|
|
105
|
+
if (t === 'sys_user' || t.endsWith('_user'))
|
|
106
|
+
return [
|
|
107
|
+
{ name: 'id', camel: 'id', type: 'bigint', len: null, nullable: false, pk: true, auto: true, defaultVal: null, comment: '主键ID' },
|
|
108
|
+
{ name: 'user_name', camel: 'userName', type: 'varchar', len: 64, nullable: false, pk: false, auto: false, defaultVal: '', comment: '用户名' },
|
|
109
|
+
{ name: 'password', camel: 'password', type: 'varchar', len: 128, nullable: false, pk: false, auto: false, defaultVal: '', comment: '密码(加密后)' },
|
|
110
|
+
{ name: 'mobile', camel: 'mobile', type: 'varchar', len: 20, nullable: true, pk: false, auto: false, defaultVal: null, comment: '手机号' },
|
|
111
|
+
{ name: 'email', camel: 'email', type: 'varchar', len: 128, nullable: true, pk: false, auto: false, defaultVal: null, comment: '邮箱' },
|
|
112
|
+
{ name: 'status', camel: 'status', type: 'tinyint', len: null, nullable: false, pk: false, auto: false, defaultVal: 1, comment: '状态 1启用 0禁用' },
|
|
113
|
+
{ name: 'ctime', camel: 'ctime', type: 'datetime', len: null, nullable: true, pk: false, auto: false, defaultVal: null, comment: '创建时间' },
|
|
114
|
+
{ name: 'mtime', camel: 'mtime', type: 'datetime', len: null, nullable: true, pk: false, auto: false, defaultVal: null, comment: '更新时间' },
|
|
115
|
+
];
|
|
116
|
+
if (t.includes('order'))
|
|
117
|
+
return [
|
|
118
|
+
{ name: 'id', camel: 'id', type: 'bigint', len: null, nullable: false, pk: true, auto: true, defaultVal: null, comment: '主键ID' },
|
|
119
|
+
{ name: 'order_no', camel: 'orderNo', type: 'varchar', len: 64, nullable: false, pk: false, auto: false, defaultVal: '', comment: '订单号' },
|
|
120
|
+
{ name: 'user_id', camel: 'userId', type: 'bigint', len: null, nullable: false, pk: false, auto: false, defaultVal: 0, comment: '下单用户ID' },
|
|
121
|
+
{ name: 'total_amount', camel: 'totalAmount', type: 'decimal', len: 12, nullable: false, pk: false, auto: false, defaultVal: '0.00', comment: '订单总金额' },
|
|
122
|
+
{ name: 'status', camel: 'status', type: 'tinyint', len: null, nullable: false, pk: false, auto: false, defaultVal: 0, comment: '订单状态' },
|
|
123
|
+
{ name: 'pay_status', camel: 'payStatus', type: 'tinyint', len: null, nullable: false, pk: false, auto: false, defaultVal: 0, comment: '支付状态' },
|
|
124
|
+
{ name: 'ctime', camel: 'ctime', type: 'datetime', len: null, nullable: true, pk: false, auto: false, defaultVal: null, comment: '创建时间' },
|
|
125
|
+
{ name: 'mtime', camel: 'mtime', type: 'datetime', len: null, nullable: true, pk: false, auto: false, defaultVal: null, comment: '更新时间' },
|
|
126
|
+
];
|
|
127
|
+
return [
|
|
128
|
+
{ name: 'id', camel: 'id', type: 'int', len: null, nullable: false, pk: true, auto: true, defaultVal: null, comment: '主键ID' },
|
|
129
|
+
{ name: 'name', camel: 'name', type: 'varchar', len: 255, nullable: true, pk: false, auto: false, defaultVal: null, comment: '名称' },
|
|
130
|
+
{ name: 'ctime', camel: 'ctime', type: 'datetime', len: null, nullable: true, pk: false, auto: false, defaultVal: null, comment: '创建时间' },
|
|
131
|
+
];
|
|
132
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FieldInfo, OrmInfo } from './types.js';
|
|
2
|
+
import { TemplateRenderer } from './renderer.js';
|
|
3
|
+
export interface BuildResult {
|
|
4
|
+
files: Array<{
|
|
5
|
+
path: string;
|
|
6
|
+
content: string;
|
|
7
|
+
createOnly?: boolean;
|
|
8
|
+
}>;
|
|
9
|
+
Pascal: string;
|
|
10
|
+
pluralKebab: string;
|
|
11
|
+
Camel: string;
|
|
12
|
+
sortable: string[];
|
|
13
|
+
filterable: string[];
|
|
14
|
+
searchable: string[];
|
|
15
|
+
}
|
|
16
|
+
export interface BuildFilesParams {
|
|
17
|
+
moduleName: string;
|
|
18
|
+
tableName: string;
|
|
19
|
+
baseSrcPath: string;
|
|
20
|
+
ormInfo: OrmInfo;
|
|
21
|
+
fields: FieldInfo[];
|
|
22
|
+
outputPath?: string;
|
|
23
|
+
templatesPath?: string;
|
|
24
|
+
renderer?: TemplateRenderer;
|
|
25
|
+
onlyEntity?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare function buildFiles(params: BuildFilesParams): BuildResult;
|