auto-gen-crud 0.1.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/API.md +130 -0
- package/README.md +234 -0
- package/dist/adapters/ant-design-vue.d.ts +8 -0
- package/dist/adapters/element-plus.d.ts +8 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/naive.d.ts +8 -0
- package/dist/adapters/types.d.ts +162 -0
- package/dist/components/AutoCrud.vue.d.ts +398 -0
- package/dist/components/CopyText.vue.d.ts +6 -0
- package/dist/components/CrudModal.vue.d.ts +47 -0
- package/dist/components/DecodeText.vue.d.ts +8 -0
- package/dist/components/DynamicForm.vue.d.ts +63 -0
- package/dist/components/DynamicFormItem.vue.d.ts +20 -0
- package/dist/components/DynamicTable.vue.d.ts +30 -0
- package/dist/context.d.ts +36 -0
- package/dist/core/date-format-utils.d.ts +8 -0
- package/dist/core/form-runtime-cache.d.ts +27 -0
- package/dist/core/form-utils.d.ts +51 -0
- package/dist/core/object-utils.d.ts +43 -0
- package/dist/core/table-utils.d.ts +19 -0
- package/dist/core/types.d.ts +443 -0
- package/dist/generator/fields.d.ts +27 -0
- package/dist/generator/index.d.ts +14 -0
- package/dist/generator/index.js +740 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/openapi.d.ts +10 -0
- package/dist/generator/operations.d.ts +8 -0
- package/dist/generator/schema.d.ts +32 -0
- package/dist/generator/string-utils.d.ts +11 -0
- package/dist/generator/templates.d.ts +6 -0
- package/dist/generator/types.d.ts +185 -0
- package/dist/generator/writer.d.ts +8 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useModalForm.d.ts +11 -0
- package/dist/hooks/useSearchForm.d.ts +10 -0
- package/dist/hooks/useTable.d.ts +11 -0
- package/dist/index-Dc-solHn.js +299 -0
- package/dist/index-Dc-solHn.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +985 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/style.css +1 -0
- package/dist/useTable-BwDSqD1q.js +214 -0
- package/dist/useTable-BwDSqD1q.js.map +1 -0
- package/dist/utils/clipboard.d.ts +16 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +532 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/message.d.ts +9 -0
- package/dist/utils/request.d.ts +8 -0
- package/dist/utils/sm4-core.d.ts +12 -0
- package/dist/utils/sm4.d.ts +29 -0
- package/docs/AI_AGENT_GUIDE.md +210 -0
- package/mcp/auto-crud-mcp.mjs +502 -0
- package/mcp/auto-gen-crud-mcp.mjs +586 -0
- package/package.json +93 -0
- package/skills/auto-gen-crud-agent/SKILL.md +114 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { dirname, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
+
|
|
9
|
+
const docs = {
|
|
10
|
+
readme: readText("README.md"),
|
|
11
|
+
api: readText("API.md"),
|
|
12
|
+
agentGuide: readText("docs/AI_AGENT_GUIDE.md"),
|
|
13
|
+
generatorTypes: readFirstText(["src/generator/types.ts", "dist/generator/types.d.ts"]),
|
|
14
|
+
coreTypes: readFirstText(["src/core/types.ts", "dist/core/types.d.ts"]),
|
|
15
|
+
tableUtils: readFirstText(["src/core/table-utils.ts", "dist/core/table-utils.d.ts"]),
|
|
16
|
+
formUtils: readFirstText(["src/core/form-utils.ts", "dist/core/form-utils.d.ts"]),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const formComponents = [
|
|
20
|
+
"input",
|
|
21
|
+
"textarea",
|
|
22
|
+
"select",
|
|
23
|
+
"multipleSelect",
|
|
24
|
+
"radio",
|
|
25
|
+
"checkbox",
|
|
26
|
+
"inputNumber",
|
|
27
|
+
"datePicker",
|
|
28
|
+
"timePicker",
|
|
29
|
+
"switch",
|
|
30
|
+
"cascader",
|
|
31
|
+
"slider",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const uiInstallMap = {
|
|
35
|
+
"element-plus": {
|
|
36
|
+
install: "npm install auto-gen-crud element-plus",
|
|
37
|
+
imports: 'import ElementPlus from "element-plus";\nimport "element-plus/dist/index.css";',
|
|
38
|
+
use: "app.use(ElementPlus).use(createAutoCrud());",
|
|
39
|
+
},
|
|
40
|
+
naive: {
|
|
41
|
+
install: "npm install auto-gen-crud naive-ui",
|
|
42
|
+
imports: 'import naive from "naive-ui";',
|
|
43
|
+
use: 'app.use(naive).use(createAutoCrud({ ui: "naive" }));',
|
|
44
|
+
},
|
|
45
|
+
"ant-design-vue": {
|
|
46
|
+
install: "npm install auto-gen-crud ant-design-vue",
|
|
47
|
+
imports: 'import Antd from "ant-design-vue";\nimport "ant-design-vue/dist/reset.css";',
|
|
48
|
+
use: 'app.use(Antd).use(createAutoCrud({ ui: "ant-design-vue" }));',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const tools = [
|
|
53
|
+
{
|
|
54
|
+
name: "vac_get_overview",
|
|
55
|
+
description: "获取 auto-gen-crud 的安装方式、核心设计、UI adapter 选择和最小示例摘要。",
|
|
56
|
+
inputSchema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
topic: {
|
|
60
|
+
type: "string",
|
|
61
|
+
enum: ["all", "install", "example", "adapter", "migration"],
|
|
62
|
+
description: "要聚焦的主题。默认 all。",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "vac_get_api_reference",
|
|
69
|
+
description: "查询 auto-gen-crud 的 API 文档片段,例如 createAutoCrud、useTable、CrudFormItem。",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
symbol: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "API 名称,如 createAutoCrud、useSearchForm、useTable、useModalForm、CrudFormItem、CrudTableColumn、Adapter。",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ["symbol"],
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: "vac_get_agent_guide",
|
|
83
|
+
description: "获取 VSCode AI agent 使用 auto-gen-crud 生成页面时必须遵守的工程边界、默认约定和 prompt 建议。",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
type: "object",
|
|
86
|
+
properties: {
|
|
87
|
+
topic: {
|
|
88
|
+
type: "string",
|
|
89
|
+
enum: ["all", "workflow", "rules", "mcp", "prompt"],
|
|
90
|
+
description: "要聚焦的主题。默认 all。",
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "vac_get_generator_reference",
|
|
97
|
+
description: "查询 auto-gen-crud/generator 的 OpenAPI 生成器用法、参数、requestAdapter 和输出目录约定。",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {
|
|
101
|
+
topic: {
|
|
102
|
+
type: "string",
|
|
103
|
+
enum: ["all", "usage", "options", "requestAdapter", "output", "defaults"],
|
|
104
|
+
description: "要聚焦的主题。默认 all。",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: "vac_list_schema_fields",
|
|
111
|
+
description: "列出表单字段、表格列、安装配置等 schema 支持的字段和组件类型。",
|
|
112
|
+
inputSchema: {
|
|
113
|
+
type: "object",
|
|
114
|
+
properties: {
|
|
115
|
+
schema: {
|
|
116
|
+
type: "string",
|
|
117
|
+
enum: ["form", "table", "install", "components"],
|
|
118
|
+
description: "要查询的 schema 类型。",
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
required: ["schema"],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "vac_generate_crud_page",
|
|
126
|
+
description: "根据实体名和字段生成一个可复制的 Vue3 + auto-gen-crud 页面示例。",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
type: "object",
|
|
129
|
+
properties: {
|
|
130
|
+
entityName: {
|
|
131
|
+
type: "string",
|
|
132
|
+
description: "实体中文名或业务名,例如 用户、订单。",
|
|
133
|
+
},
|
|
134
|
+
rowTypeName: {
|
|
135
|
+
type: "string",
|
|
136
|
+
description: "TS 行数据类型名,默认 RowData。",
|
|
137
|
+
},
|
|
138
|
+
ui: {
|
|
139
|
+
type: "string",
|
|
140
|
+
enum: ["element-plus", "naive", "ant-design-vue"],
|
|
141
|
+
description: "目标 UI。默认 element-plus。",
|
|
142
|
+
},
|
|
143
|
+
fields: {
|
|
144
|
+
type: "array",
|
|
145
|
+
description: "字段列表。每项支持 label、prop、component、table、search、modal、required、copy、decode、dateTime。",
|
|
146
|
+
items: {
|
|
147
|
+
type: "object",
|
|
148
|
+
properties: {
|
|
149
|
+
label: { type: "string" },
|
|
150
|
+
prop: { type: "string" },
|
|
151
|
+
component: { type: "string", enum: formComponents },
|
|
152
|
+
table: { type: "boolean" },
|
|
153
|
+
search: { type: "boolean" },
|
|
154
|
+
modal: { type: "boolean" },
|
|
155
|
+
required: { type: "boolean" },
|
|
156
|
+
copy: { type: "boolean" },
|
|
157
|
+
decode: { type: "boolean" },
|
|
158
|
+
dateTime: { oneOf: [{ type: "boolean" }, { type: "string" }] },
|
|
159
|
+
},
|
|
160
|
+
required: ["label", "prop"],
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
required: ["entityName", "fields"],
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "vac_generate_install_snippet",
|
|
169
|
+
description: "生成指定 UI 库下的 Vue app 初始化代码,包括依赖安装命令和 createAutoCrud 配置。",
|
|
170
|
+
inputSchema: {
|
|
171
|
+
type: "object",
|
|
172
|
+
properties: {
|
|
173
|
+
ui: {
|
|
174
|
+
type: "string",
|
|
175
|
+
enum: ["element-plus", "naive", "ant-design-vue"],
|
|
176
|
+
description: "目标 UI。默认 element-plus。",
|
|
177
|
+
},
|
|
178
|
+
withDecode: {
|
|
179
|
+
type: "boolean",
|
|
180
|
+
description: "是否包含敏感信息 decode/decodeLog 示例。",
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
name: "vac_search_docs",
|
|
187
|
+
description: "在 README、API 和核心类型文件中搜索关键词,返回相关上下文。",
|
|
188
|
+
inputSchema: {
|
|
189
|
+
type: "object",
|
|
190
|
+
properties: {
|
|
191
|
+
query: {
|
|
192
|
+
type: "string",
|
|
193
|
+
description: "搜索关键词,如 pagination、decode、options。",
|
|
194
|
+
},
|
|
195
|
+
limit: {
|
|
196
|
+
type: "number",
|
|
197
|
+
description: "最多返回多少条,默认 8。",
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
required: ["query"],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
function readText(relativePath) {
|
|
206
|
+
return readFileSync(resolve(rootDir, relativePath), "utf8");
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function readFirstText(relativePaths) {
|
|
210
|
+
const matchedPath = relativePaths.find(relativePath => existsSync(resolve(rootDir, relativePath)));
|
|
211
|
+
if (!matchedPath) return "";
|
|
212
|
+
return readText(matchedPath);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function jsonRpc(id, result) {
|
|
216
|
+
return { jsonrpc: "2.0", id, result };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function jsonRpcError(id, code, message, data) {
|
|
220
|
+
return { jsonrpc: "2.0", id, error: { code, message, data } };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
function textContent(text) {
|
|
224
|
+
return { content: [{ type: "text", text }] };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function sectionByHeading(text, heading) {
|
|
228
|
+
const lines = text.split(/\r?\n/);
|
|
229
|
+
const index = lines.findIndex(line => line.replace(/^#+\s*/, "").trim().toLowerCase() === heading.toLowerCase());
|
|
230
|
+
if (index < 0) return "";
|
|
231
|
+
const level = (lines[index].match(/^#+/) || [""])[0].length;
|
|
232
|
+
const end = lines.findIndex((line, lineIndex) => lineIndex > index && /^#+\s+/.test(line) && (line.match(/^#+/) || [""])[0].length <= level);
|
|
233
|
+
return lines.slice(index, end < 0 ? undefined : end).join("\n").trim();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function compact(text, max = 3500) {
|
|
237
|
+
if (text.length <= max) return text;
|
|
238
|
+
return `${text.slice(0, max)}\n\n...内容已截断,可用 vac_search_docs 继续查询。`;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function handleToolCall(name, args = {}) {
|
|
242
|
+
switch (name) {
|
|
243
|
+
case "vac_get_overview":
|
|
244
|
+
return textContent(getOverview(args.topic || "all"));
|
|
245
|
+
case "vac_get_api_reference":
|
|
246
|
+
return textContent(getApiReference(args.symbol));
|
|
247
|
+
case "vac_get_agent_guide":
|
|
248
|
+
return textContent(getAgentGuide(args.topic || "all"));
|
|
249
|
+
case "vac_get_generator_reference":
|
|
250
|
+
return textContent(getGeneratorReference(args.topic || "all"));
|
|
251
|
+
case "vac_list_schema_fields":
|
|
252
|
+
return textContent(getSchemaFields(args.schema));
|
|
253
|
+
case "vac_generate_crud_page":
|
|
254
|
+
return textContent(generateCrudPage(args));
|
|
255
|
+
case "vac_generate_install_snippet":
|
|
256
|
+
return textContent(generateInstallSnippet(args.ui || "element-plus", Boolean(args.withDecode)));
|
|
257
|
+
case "vac_search_docs":
|
|
258
|
+
return textContent(searchDocs(args.query, args.limit || 8));
|
|
259
|
+
default:
|
|
260
|
+
throw new Error(`未知工具:${name}。可用工具:${tools.map(tool => tool.name).join(", ")}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function getOverview(topic) {
|
|
265
|
+
const map = {
|
|
266
|
+
install: sectionByHeading(docs.readme, "安装和初始化"),
|
|
267
|
+
example: sectionByHeading(docs.readme, "最小示例"),
|
|
268
|
+
adapter: `${sectionByHeading(docs.readme, "为什么要用 Adapter")}\n\n${sectionByHeading(docs.api, "Adapter 学习重点")}`,
|
|
269
|
+
migration: sectionByHeading(docs.readme, "从旧 auto-gen-crud 迁移"),
|
|
270
|
+
};
|
|
271
|
+
if (topic !== "all") return compact(map[topic] || docs.readme);
|
|
272
|
+
return compact(
|
|
273
|
+
[
|
|
274
|
+
sectionByHeading(docs.readme, "设计特点"),
|
|
275
|
+
sectionByHeading(docs.readme, "安装和初始化"),
|
|
276
|
+
sectionByHeading(docs.readme, "最小示例"),
|
|
277
|
+
sectionByHeading(docs.readme, "为什么要用 Adapter"),
|
|
278
|
+
].join("\n\n")
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function getApiReference(symbol) {
|
|
283
|
+
const normalized = String(symbol || "").trim();
|
|
284
|
+
const heading = normalized.toLowerCase();
|
|
285
|
+
const headingMap = {
|
|
286
|
+
createautocrud: "createAutoCrud",
|
|
287
|
+
usesearchform: "useSearchForm",
|
|
288
|
+
usetable: "useTable",
|
|
289
|
+
usemodalform: "useModalForm",
|
|
290
|
+
crudformitem: "CrudFormItem",
|
|
291
|
+
crudtablecolumn: "CrudTableColumn",
|
|
292
|
+
adapter: "Adapter 学习重点",
|
|
293
|
+
};
|
|
294
|
+
const section = sectionByHeading(docs.api, headingMap[heading] || normalized);
|
|
295
|
+
if (section) return section;
|
|
296
|
+
|
|
297
|
+
const typeContext = searchInText(docs.coreTypes, normalized, 6)
|
|
298
|
+
.map(item => `src/core/types.ts:${item.line}\n${item.context}`)
|
|
299
|
+
.join("\n\n");
|
|
300
|
+
if (typeContext) return typeContext;
|
|
301
|
+
|
|
302
|
+
return `没有找到 ${normalized}。建议查询:createAutoCrud、useSearchForm、useTable、useModalForm、CrudFormItem、CrudTableColumn、Adapter。`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function getAgentGuide(topic) {
|
|
306
|
+
const map = {
|
|
307
|
+
workflow: sectionByHeading(docs.agentGuide, "推荐工作流"),
|
|
308
|
+
rules: sectionByHeading(docs.agentGuide, "Agent 必须遵守的边界"),
|
|
309
|
+
mcp: sectionByHeading(docs.agentGuide, "MCP 接入"),
|
|
310
|
+
prompt: sectionByHeading(docs.agentGuide, "Agent Prompt 建议"),
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
if (topic !== "all") return compact(map[topic] || docs.agentGuide);
|
|
314
|
+
return compact(docs.agentGuide);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function getGeneratorReference(topic) {
|
|
318
|
+
const sections = {
|
|
319
|
+
usage: sectionByHeading(docs.agentGuide, "OpenAPI 生成器"),
|
|
320
|
+
output: sectionByHeading(docs.agentGuide, "生成器输出约定"),
|
|
321
|
+
defaults: [
|
|
322
|
+
"生成器默认约定:",
|
|
323
|
+
"- pageContext 默认生成空对象。",
|
|
324
|
+
"- getDataList 默认展开 ...searchForm.data。",
|
|
325
|
+
"- enums.ts 默认生成 TypeTextMap。",
|
|
326
|
+
"- generateTypeOptions 默认为 false。",
|
|
327
|
+
"- maxRetries 内部限制不超过 5。",
|
|
328
|
+
].join("\n"),
|
|
329
|
+
requestAdapter: searchInText(docs.generatorTypes, "RequestAdapterOptions", 12)
|
|
330
|
+
.map(item => `src/generator/types.ts:${item.line}\n${item.context}`)
|
|
331
|
+
.join("\n\n"),
|
|
332
|
+
options: searchInText(docs.generatorTypes, "GenerateCrudPageOptions", 36)
|
|
333
|
+
.map(item => `src/generator/types.ts:${item.line}\n${item.context}`)
|
|
334
|
+
.join("\n\n"),
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
if (topic !== "all") return compact(sections[topic] || docs.agentGuide);
|
|
338
|
+
return compact([
|
|
339
|
+
sections.usage,
|
|
340
|
+
sections.output,
|
|
341
|
+
sections.defaults,
|
|
342
|
+
sections.options,
|
|
343
|
+
].filter(Boolean).join("\n\n"));
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function getSchemaFields(schema) {
|
|
347
|
+
switch (schema) {
|
|
348
|
+
case "components":
|
|
349
|
+
return `CrudFormComponent 支持:\n${formComponents.map(name => `- ${name}`).join("\n")}`;
|
|
350
|
+
case "install":
|
|
351
|
+
return sectionByHeading(docs.api, "AutoCrudInstallOptions");
|
|
352
|
+
case "form":
|
|
353
|
+
return `${sectionByHeading(docs.api, "CrudFormItem")}\n\n常用 component:${formComponents.join(", ")}`;
|
|
354
|
+
case "table":
|
|
355
|
+
return sectionByHeading(docs.api, "CrudTableColumn");
|
|
356
|
+
default:
|
|
357
|
+
return "schema 必须是 form、table、install 或 components。";
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function generateInstallSnippet(ui, withDecode) {
|
|
362
|
+
const config = uiInstallMap[ui] || uiInstallMap["element-plus"];
|
|
363
|
+
const options = ui === "element-plus" ? "" : `{ ui: "${ui}" }`;
|
|
364
|
+
const decodeOptions = withDecode
|
|
365
|
+
? `{
|
|
366
|
+
${ui === "element-plus" ? "" : `ui: "${ui}",\n `}decode: async value => String(value),
|
|
367
|
+
decodeLog: async (value, row) => {
|
|
368
|
+
console.log("记录查看日志", value, row);
|
|
369
|
+
},
|
|
370
|
+
}`
|
|
371
|
+
: options;
|
|
372
|
+
return `安装:\n\n\`\`\`bash\n${config.install}\n\`\`\`\n\nmain.ts:\n\n\`\`\`ts\nimport { createApp } from "vue";
|
|
373
|
+
import { createAutoCrud } from "auto-gen-crud";
|
|
374
|
+
import App from "./App.vue";
|
|
375
|
+
${config.imports}
|
|
376
|
+
import "auto-gen-crud/style.css";
|
|
377
|
+
|
|
378
|
+
createApp(App)
|
|
379
|
+
.use(${ui === "element-plus" ? "ElementPlus" : ui === "naive" ? "naive" : "Antd"})
|
|
380
|
+
.use(createAutoCrud(${decodeOptions}))
|
|
381
|
+
.mount("#app");
|
|
382
|
+
\`\`\``;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function generateCrudPage(args) {
|
|
386
|
+
const entityName = safeText(args.entityName || "数据");
|
|
387
|
+
const rowTypeName = safeIdentifier(args.rowTypeName || "RowData");
|
|
388
|
+
const fields = Array.isArray(args.fields) && args.fields.length > 0 ? args.fields : [{ label: "名称", prop: "name", component: "input", table: true, search: true, modal: true, required: true }];
|
|
389
|
+
const normalizedFields = fields.map(field => ({
|
|
390
|
+
label: safeText(field.label || field.prop),
|
|
391
|
+
prop: safeProp(field.prop),
|
|
392
|
+
component: formComponents.includes(field.component) ? field.component : "input",
|
|
393
|
+
table: field.table !== false,
|
|
394
|
+
search: Boolean(field.search),
|
|
395
|
+
modal: field.modal !== false,
|
|
396
|
+
required: Boolean(field.required),
|
|
397
|
+
copy: Boolean(field.copy),
|
|
398
|
+
decode: Boolean(field.decode),
|
|
399
|
+
dateTime: field.dateTime,
|
|
400
|
+
}));
|
|
401
|
+
|
|
402
|
+
const typeFields = normalizedFields.map(field => ` ${field.prop}: ${field.component === "inputNumber" || field.component === "slider" ? "number" : field.component === "switch" ? "boolean" : "string"};`).join("\n");
|
|
403
|
+
const searchColumns = normalizedFields.filter(field => field.search).map(toFormItem).join(",\n ");
|
|
404
|
+
const modalColumns = normalizedFields.filter(field => field.modal).map(toFormItem).join(",\n ");
|
|
405
|
+
const tableColumns = normalizedFields.filter(field => field.table).map(toTableColumn).join(",\n ");
|
|
406
|
+
|
|
407
|
+
return `\`\`\`vue
|
|
408
|
+
<script setup lang="ts">
|
|
409
|
+
import { AutoCrud, useModalForm, useSearchForm, useTable } from "auto-gen-crud";
|
|
410
|
+
|
|
411
|
+
interface ${rowTypeName} extends Record<string, unknown> {
|
|
412
|
+
${typeFields}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const searchForm = useSearchForm({
|
|
416
|
+
columns: [
|
|
417
|
+
${searchColumns || toFormItem(normalizedFields[0])},
|
|
418
|
+
],
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
const modalForm = useModalForm<${rowTypeName}>({
|
|
422
|
+
columns: [
|
|
423
|
+
${modalColumns || toFormItem(normalizedFields[0])},
|
|
424
|
+
],
|
|
425
|
+
async onSubmit(data, mode) {
|
|
426
|
+
console.log("${entityName}提交", mode, data);
|
|
427
|
+
modalForm.close();
|
|
428
|
+
await table.load(searchForm.data);
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
const table = useTable<${rowTypeName}>({
|
|
433
|
+
columns: [
|
|
434
|
+
${tableColumns || toTableColumn(normalizedFields[0])},
|
|
435
|
+
{
|
|
436
|
+
title: "操作",
|
|
437
|
+
actions: [
|
|
438
|
+
{ key: "edit", label: "编辑", type: "primary", onClick: row => modalForm.openEdit(row) },
|
|
439
|
+
{ key: "view", label: "查看", type: "link", onClick: row => modalForm.openView(row) },
|
|
440
|
+
],
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
async getDataList({ pagination, searchData }) {
|
|
444
|
+
console.log("${entityName}查询", pagination, searchData);
|
|
445
|
+
return { data: [], total: 0 };
|
|
446
|
+
},
|
|
447
|
+
});
|
|
448
|
+
</script>
|
|
449
|
+
|
|
450
|
+
<template>
|
|
451
|
+
<AutoCrud
|
|
452
|
+
title="${entityName}"
|
|
453
|
+
:search-form-config="searchForm"
|
|
454
|
+
:table-config="table"
|
|
455
|
+
:modal-form-config="modalForm"
|
|
456
|
+
/>
|
|
457
|
+
</template>
|
|
458
|
+
\`\`\``;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function toFormItem(field) {
|
|
462
|
+
const extra = field.required ? ", required: true" : "";
|
|
463
|
+
return `{ label: "${field.label}", prop: "${field.prop}", component: "${field.component}"${extra} }`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function toTableColumn(field) {
|
|
467
|
+
const attrs = [
|
|
468
|
+
`title: "${field.label}"`,
|
|
469
|
+
`field: "${field.prop}"`,
|
|
470
|
+
field.copy ? "copy: true" : "",
|
|
471
|
+
field.decode ? "decode: true" : "",
|
|
472
|
+
field.dateTime ? `dateTime: ${field.dateTime === true ? "true" : JSON.stringify(field.dateTime)}` : "",
|
|
473
|
+
].filter(Boolean);
|
|
474
|
+
return `{ ${attrs.join(", ")} }`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function safeIdentifier(value) {
|
|
478
|
+
const text = String(value).replace(/[^\w$]/g, "");
|
|
479
|
+
return text || "RowData";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function safeProp(value) {
|
|
483
|
+
return String(value || "name").replace(/[^a-zA-Z0-9_.$]/g, "") || "name";
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function safeText(value) {
|
|
487
|
+
return String(value || "").replace(/["\\]/g, "");
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function searchDocs(query, limit) {
|
|
491
|
+
const parts = [
|
|
492
|
+
["README.md", docs.readme],
|
|
493
|
+
["API.md", docs.api],
|
|
494
|
+
["docs/AI_AGENT_GUIDE.md", docs.agentGuide],
|
|
495
|
+
["src/generator/types.ts", docs.generatorTypes],
|
|
496
|
+
["src/core/types.ts", docs.coreTypes],
|
|
497
|
+
["src/core/form-utils.ts", docs.formUtils],
|
|
498
|
+
["src/core/table-utils.ts", docs.tableUtils],
|
|
499
|
+
];
|
|
500
|
+
const results = parts.flatMap(([file, text]) => searchInText(text, query, 3).map(item => ({ file, ...item }))).slice(0, limit);
|
|
501
|
+
if (results.length === 0) return `没有找到 "${query}"。`;
|
|
502
|
+
return results.map(item => `${item.file}:${item.line}\n${item.context}`).join("\n\n---\n\n");
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
function searchInText(text, query, radius) {
|
|
506
|
+
const keyword = String(query || "").toLowerCase();
|
|
507
|
+
if (!keyword) return [];
|
|
508
|
+
const lines = text.split(/\r?\n/);
|
|
509
|
+
const matches = [];
|
|
510
|
+
lines.forEach((line, index) => {
|
|
511
|
+
if (line.toLowerCase().includes(keyword)) {
|
|
512
|
+
const start = Math.max(0, index - radius);
|
|
513
|
+
const end = Math.min(lines.length, index + radius + 1);
|
|
514
|
+
matches.push({
|
|
515
|
+
line: index + 1,
|
|
516
|
+
context: lines.slice(start, end).join("\n"),
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
return matches;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
async function handleRequest(request) {
|
|
524
|
+
const { id, method, params = {} } = request;
|
|
525
|
+
if (method === "initialize") {
|
|
526
|
+
return jsonRpc(id, {
|
|
527
|
+
protocolVersion: params.protocolVersion || "2024-11-05",
|
|
528
|
+
capabilities: {
|
|
529
|
+
tools: {},
|
|
530
|
+
},
|
|
531
|
+
serverInfo: {
|
|
532
|
+
name: "auto-gen-crud-mcp",
|
|
533
|
+
version: "0.1.0",
|
|
534
|
+
},
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (method === "tools/list") {
|
|
539
|
+
return jsonRpc(id, { tools });
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (method === "tools/call") {
|
|
543
|
+
try {
|
|
544
|
+
const result = handleToolCall(params.name, params.arguments || {});
|
|
545
|
+
return jsonRpc(id, result);
|
|
546
|
+
} catch (error) {
|
|
547
|
+
return jsonRpcError(id, -32000, error instanceof Error ? error.message : String(error));
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (method === "notifications/initialized") return null;
|
|
552
|
+
|
|
553
|
+
return jsonRpcError(id, -32601, `不支持的方法:${method}`);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
let buffer = "";
|
|
557
|
+
|
|
558
|
+
process.stdin.setEncoding("utf8");
|
|
559
|
+
process.stdin.on("data", chunk => {
|
|
560
|
+
buffer += chunk;
|
|
561
|
+
drainBuffer().catch(error => {
|
|
562
|
+
writeMessage(jsonRpcError(null, -32000, error instanceof Error ? error.message : String(error)));
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
async function drainBuffer() {
|
|
567
|
+
while (true) {
|
|
568
|
+
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
569
|
+
if (headerEnd < 0) return;
|
|
570
|
+
const header = buffer.slice(0, headerEnd);
|
|
571
|
+
const match = header.match(/Content-Length:\s*(\d+)/i);
|
|
572
|
+
if (!match) throw new Error("缺少 Content-Length header");
|
|
573
|
+
const length = Number(match[1]);
|
|
574
|
+
const bodyStart = headerEnd + 4;
|
|
575
|
+
if (buffer.length < bodyStart + length) return;
|
|
576
|
+
const body = buffer.slice(bodyStart, bodyStart + length);
|
|
577
|
+
buffer = buffer.slice(bodyStart + length);
|
|
578
|
+
const response = await handleRequest(JSON.parse(body));
|
|
579
|
+
if (response) writeMessage(response);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function writeMessage(message) {
|
|
584
|
+
const body = JSON.stringify(message);
|
|
585
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`);
|
|
586
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "auto-gen-crud",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "基于配置驱动的 Vue 3 CRUD 组件包,初始化时可选择 Element Plus、Naive UI 或 Ant Design Vue。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"style": "./dist/style.css",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"mcp",
|
|
13
|
+
"docs",
|
|
14
|
+
"skills",
|
|
15
|
+
"README.md",
|
|
16
|
+
"API.md"
|
|
17
|
+
],
|
|
18
|
+
"bin": {
|
|
19
|
+
"auto-gen-crud-mcp": "./mcp/auto-gen-crud-mcp.mjs"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./hooks": {
|
|
27
|
+
"types": "./dist/hooks/index.d.ts",
|
|
28
|
+
"import": "./dist/hooks/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./adapters": {
|
|
31
|
+
"types": "./dist/adapters/index.d.ts",
|
|
32
|
+
"import": "./dist/adapters/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./utils": {
|
|
35
|
+
"types": "./dist/utils/index.d.ts",
|
|
36
|
+
"import": "./dist/utils/index.js"
|
|
37
|
+
},
|
|
38
|
+
"./generator": {
|
|
39
|
+
"types": "./dist/generator/index.d.ts",
|
|
40
|
+
"import": "./dist/generator/index.js"
|
|
41
|
+
},
|
|
42
|
+
"./style.css": "./dist/style.css"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": [
|
|
45
|
+
"*.css",
|
|
46
|
+
"**/*.css"
|
|
47
|
+
],
|
|
48
|
+
"scripts": {
|
|
49
|
+
"dev": "vite --host 127.0.0.1",
|
|
50
|
+
"build": "vite build",
|
|
51
|
+
"demo:smoke": "npm run build && node demo/smoke.mjs",
|
|
52
|
+
"generator:smoke": "npm run build && node test-fixtures/generator/smoke.mjs",
|
|
53
|
+
"generator:test": "npm run build && node test-fixtures/generator/generator.test.mjs",
|
|
54
|
+
"generator:demo": "npm run build && node test-fixtures/generator/generate-demo.mjs",
|
|
55
|
+
"form-props:test": "npm run build && node test-fixtures/generator/form-props.test.mjs",
|
|
56
|
+
"form-events:test": "node test-fixtures/generator/form-events.test.mjs",
|
|
57
|
+
"search-select-change:test": "node test-fixtures/generator/search-select-change.test.mjs",
|
|
58
|
+
"form-colspan:test": "node test-fixtures/generator/form-colspan.test.mjs",
|
|
59
|
+
"naive-date-format:test": "npm run build && node test-fixtures/generator/naive-date-format.test.mjs",
|
|
60
|
+
"pagination:test": "npm run build && node test-fixtures/generator/pagination.test.mjs",
|
|
61
|
+
"table-scroll:test": "npm run build && node test-fixtures/generator/table-scroll.test.mjs",
|
|
62
|
+
"mcp": "node mcp/auto-gen-crud-mcp.mjs",
|
|
63
|
+
"type-check": "vue-tsc --noEmit -p tsconfig.json",
|
|
64
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
65
|
+
"pack:local": "npm run build && npm pack --pack-destination .",
|
|
66
|
+
"release:check": "node scripts/publish.mjs",
|
|
67
|
+
"release:publish": "node scripts/publish.mjs --publish"
|
|
68
|
+
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"dayjs": "^1.11.13"
|
|
71
|
+
},
|
|
72
|
+
"peerDependencies": {
|
|
73
|
+
"vue": "^3.5.0"
|
|
74
|
+
},
|
|
75
|
+
"devDependencies": {
|
|
76
|
+
"@vitejs/plugin-vue": "^6.0.1",
|
|
77
|
+
"typescript": "^5.3.3",
|
|
78
|
+
"vite": "^7.3.2",
|
|
79
|
+
"vite-plugin-dts": "^4.5.4",
|
|
80
|
+
"vue": "^3.5.24",
|
|
81
|
+
"vue-tsc": "^3.1.3"
|
|
82
|
+
},
|
|
83
|
+
"keywords": [
|
|
84
|
+
"vue",
|
|
85
|
+
"crud",
|
|
86
|
+
"element-plus",
|
|
87
|
+
"naive-ui",
|
|
88
|
+
"ant-design-vue"
|
|
89
|
+
],
|
|
90
|
+
"author": "",
|
|
91
|
+
"license": "ISC",
|
|
92
|
+
"packageManager": "pnpm@10.4.1"
|
|
93
|
+
}
|