worsoft-frontend-codegen-local-mcp 0.1.56 → 0.1.59
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/mcp_server.js
CHANGED
|
@@ -5,7 +5,7 @@ const fs = require('fs');
|
|
|
5
5
|
const path = require('path');
|
|
6
6
|
|
|
7
7
|
const SERVER_NAME = 'worsoft-codegen-local';
|
|
8
|
-
const SERVER_VERSION = '0.1.
|
|
8
|
+
const SERVER_VERSION = '0.1.59';
|
|
9
9
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
10
10
|
const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
|
|
11
11
|
const STYLE_CATALOG_PATH = path.join(__dirname, 'assets', 'style-catalog.json');
|
|
@@ -312,11 +312,30 @@ const TOOL_SCHEMA = {
|
|
|
312
312
|
type: 'string',
|
|
313
313
|
description: 'Explicit relative api module path under src/api without extension, for example admin/iwmSysTrade. When provided, MCP writes the api file directly to this target path.',
|
|
314
314
|
},
|
|
315
|
-
targetI18nKey: {
|
|
316
|
-
type: 'string',
|
|
317
|
-
description: 'Explicit zh-cn i18n namespace, for example admin.iwmSysTrade. When provided, MCP writes zh-cn content to the matching file path and namespace instead of deriving them from moduleName and functionName.',
|
|
318
|
-
},
|
|
319
|
-
|
|
315
|
+
targetI18nKey: {
|
|
316
|
+
type: 'string',
|
|
317
|
+
description: 'Explicit zh-cn i18n namespace, for example admin.iwmSysTrade. When provided, MCP writes zh-cn content to the matching file path and namespace instead of deriving them from moduleName and functionName.',
|
|
318
|
+
},
|
|
319
|
+
extraApis: {
|
|
320
|
+
type: 'array',
|
|
321
|
+
description: 'Additional API methods parsed from the API document and belonging to the current targetApiModule. Each method is rendered to api.ts with a purpose comment for later task-split usage.',
|
|
322
|
+
items: {
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
functionName: { type: 'string', description: 'Exported frontend API function name, for example getCurrentCountByTradeDictId.' },
|
|
326
|
+
description: { type: 'string', description: 'API purpose comment. It must exactly match the API document interface title text after removing the section number and "接口N:" prefix; do not summarize or rewrite it.' },
|
|
327
|
+
url: { type: 'string', description: 'Request URL. Absolute backend path is preferred, for example /admin/xxx/count.' },
|
|
328
|
+
method: { type: 'string', enum: ['get', 'post', 'put', 'delete'], description: 'HTTP method.' },
|
|
329
|
+
requestType: { type: 'string', enum: ['params', 'data'], description: 'Whether arguments are sent as query params or request body.' },
|
|
330
|
+
targetApiModule: { type: 'string', description: 'Optional target api module. If provided, it must equal the current targetApiModule.' },
|
|
331
|
+
source: { type: 'string', description: 'Source note from API doc or PRD.' },
|
|
332
|
+
usedBy: { type: 'string', description: 'Optional downstream usage hint for task-split.' },
|
|
333
|
+
},
|
|
334
|
+
required: ['functionName', 'description', 'url', 'method'],
|
|
335
|
+
additionalProperties: false,
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
|
|
320
339
|
overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
|
|
321
340
|
writeSupportFiles: {
|
|
322
341
|
type: 'boolean',
|
|
@@ -1398,15 +1417,78 @@ function hasRuntimeSupport(stylePreset) {
|
|
|
1398
1417
|
return Boolean(stylePreset.runtime && stylePreset.runtime.supported && stylePreset.runtime.templateDir);
|
|
1399
1418
|
}
|
|
1400
1419
|
|
|
1401
|
-
function normalizeFields(parsed) {
|
|
1402
|
-
return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
function
|
|
1406
|
-
const
|
|
1407
|
-
if (
|
|
1408
|
-
|
|
1409
|
-
}
|
|
1420
|
+
function normalizeFields(parsed) {
|
|
1421
|
+
return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function sanitizeIdentifier(value, label) {
|
|
1425
|
+
const name = String(value || '').trim();
|
|
1426
|
+
if (!/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name)) {
|
|
1427
|
+
throw new Error(label + ' must be a valid JavaScript identifier');
|
|
1428
|
+
}
|
|
1429
|
+
return name;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function normalizeExtraApis(inputExtraApis, currentTargetApiModule) {
|
|
1433
|
+
if (inputExtraApis === undefined || inputExtraApis === null) return [];
|
|
1434
|
+
if (!Array.isArray(inputExtraApis)) {
|
|
1435
|
+
throw new Error('extraApis must be an array');
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const seen = new Set();
|
|
1439
|
+
return inputExtraApis.map((item, index) => {
|
|
1440
|
+
if (!item || typeof item !== 'object') {
|
|
1441
|
+
throw new Error('extraApis[' + index + '] must be an object');
|
|
1442
|
+
}
|
|
1443
|
+
const functionName = sanitizeIdentifier(item.functionName, 'extraApis[' + index + '].functionName');
|
|
1444
|
+
if (seen.has(functionName)) {
|
|
1445
|
+
throw new Error('Duplicate extra API functionName found: ' + functionName);
|
|
1446
|
+
}
|
|
1447
|
+
seen.add(functionName);
|
|
1448
|
+
|
|
1449
|
+
const method = String(item.method || '').trim().toLowerCase();
|
|
1450
|
+
if (!['get', 'post', 'put', 'delete'].includes(method)) {
|
|
1451
|
+
throw new Error('extraApis[' + index + '].method must be one of get, post, put, delete');
|
|
1452
|
+
}
|
|
1453
|
+
const url = String(item.url || '').trim();
|
|
1454
|
+
if (!url) {
|
|
1455
|
+
throw new Error('extraApis[' + index + '].url is required');
|
|
1456
|
+
}
|
|
1457
|
+
const description = String(item.description || '').trim();
|
|
1458
|
+
if (!description) {
|
|
1459
|
+
throw new Error('extraApis[' + index + '].description is required');
|
|
1460
|
+
}
|
|
1461
|
+
const targetApiModule = item.targetApiModule ? normalizeModuleName(String(item.targetApiModule)) : '';
|
|
1462
|
+
if (targetApiModule && currentTargetApiModule && targetApiModule !== currentTargetApiModule) {
|
|
1463
|
+
throw new Error(
|
|
1464
|
+
'extraApis[' +
|
|
1465
|
+
index +
|
|
1466
|
+
'].targetApiModule must equal current targetApiModule. Cross-module extra API generation is blocked to avoid overwriting other API files.'
|
|
1467
|
+
);
|
|
1468
|
+
}
|
|
1469
|
+
const requestType = item.requestType ? String(item.requestType).trim() : method === 'get' || method === 'delete' ? 'params' : 'data';
|
|
1470
|
+
if (!['params', 'data'].includes(requestType)) {
|
|
1471
|
+
throw new Error('extraApis[' + index + '].requestType must be params or data');
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
return {
|
|
1475
|
+
functionName,
|
|
1476
|
+
description,
|
|
1477
|
+
url: url.startsWith('/') ? url : '/' + url,
|
|
1478
|
+
method,
|
|
1479
|
+
requestType,
|
|
1480
|
+
targetApiModule,
|
|
1481
|
+
source: item.source ? String(item.source).trim() : '',
|
|
1482
|
+
usedBy: item.usedBy ? String(item.usedBy).trim() : '',
|
|
1483
|
+
};
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function ensureFieldExists(fields, fieldName, tableName, role) {
|
|
1488
|
+
const field = fields.find((item) => item.fieldName === fieldName);
|
|
1489
|
+
if (!field) throw new Error(role + ' field "' + fieldName + '" was not found on table ' + tableName);
|
|
1490
|
+
return field;
|
|
1491
|
+
}
|
|
1410
1492
|
|
|
1411
1493
|
function detectStatusField(fields, explicitName) {
|
|
1412
1494
|
if (explicitName) {
|
|
@@ -1518,9 +1600,10 @@ function buildMultiLevelDictModel(safeArgs) {
|
|
|
1518
1600
|
targetI18nKey: resolvedTargets.targetI18nKey,
|
|
1519
1601
|
frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
|
|
1520
1602
|
style: safeArgs.style,
|
|
1521
|
-
levels: builtLevels,
|
|
1522
|
-
modules: allModules,
|
|
1523
|
-
|
|
1603
|
+
levels: builtLevels,
|
|
1604
|
+
modules: allModules,
|
|
1605
|
+
extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
|
|
1606
|
+
dictTypes,
|
|
1524
1607
|
pk: parentModule.pk,
|
|
1525
1608
|
fields: parentModule.fields,
|
|
1526
1609
|
optionFields: parentModule.optionFields,
|
|
@@ -1591,11 +1674,11 @@ function buildModel(safeArgs) {
|
|
|
1591
1674
|
|
|
1592
1675
|
const derivedFunctionName = toCamelCase(safeArgs.tableName);
|
|
1593
1676
|
const apiPath = safeArgs.apiPath || derivedFunctionName;
|
|
1594
|
-
const resolvedTargets = resolveGenerationTargets({
|
|
1595
|
-
moduleName: safeArgs.moduleName,
|
|
1596
|
-
functionName: derivedFunctionName,
|
|
1597
|
-
apiPath,
|
|
1598
|
-
targetViewDir: safeArgs.targetViewDir,
|
|
1677
|
+
const resolvedTargets = resolveGenerationTargets({
|
|
1678
|
+
moduleName: safeArgs.moduleName,
|
|
1679
|
+
functionName: derivedFunctionName,
|
|
1680
|
+
apiPath,
|
|
1681
|
+
targetViewDir: safeArgs.targetViewDir,
|
|
1599
1682
|
targetApiModule: safeArgs.targetApiModule,
|
|
1600
1683
|
targetI18nKey: safeArgs.targetI18nKey,
|
|
1601
1684
|
});
|
|
@@ -1622,9 +1705,10 @@ function buildModel(safeArgs) {
|
|
|
1622
1705
|
dictTypes,
|
|
1623
1706
|
frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
|
|
1624
1707
|
style: safeArgs.style,
|
|
1708
|
+
extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
|
|
1625
1709
|
children,
|
|
1626
1710
|
};
|
|
1627
|
-
}
|
|
1711
|
+
}
|
|
1628
1712
|
|
|
1629
1713
|
function renderTemplate(templateText, replacements) {
|
|
1630
1714
|
let output = templateText;
|
|
@@ -1914,7 +1998,10 @@ function renderOptionFieldV2(field, labelKey, dictRegistryRefs, indent = ' ')
|
|
|
1914
1998
|
}
|
|
1915
1999
|
if (field.listShow !== field.show) {
|
|
1916
2000
|
parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
|
|
1917
|
-
}
|
|
2001
|
+
}
|
|
2002
|
+
if (field.formShow !== field.show) {
|
|
2003
|
+
parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
|
|
2004
|
+
}
|
|
1918
2005
|
|
|
1919
2006
|
return `${indent}{ ${parts.join(', ')} },`;
|
|
1920
2007
|
}
|
|
@@ -2051,6 +2138,7 @@ function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs
|
|
|
2051
2138
|
if (width) parts.push(`width: '${width}'`);
|
|
2052
2139
|
if (field.show === false) parts.push('show: false');
|
|
2053
2140
|
if (field.listShow !== field.show) parts.push(`listShow: ${field.listShow ? 'true' : 'false'}`);
|
|
2141
|
+
if (field.formShow !== field.show) parts.push(`formShow: ${field.formShow ? 'true' : 'false'}`);
|
|
2054
2142
|
if (field.smart) parts.push('smart: true');
|
|
2055
2143
|
if (field.dictType) {
|
|
2056
2144
|
parts.push(`dictType: ${getDictRegistryReference(field.dictType, dictRegistryRefs)}`);
|
|
@@ -2224,9 +2312,10 @@ function renderMultiLevelApiTs(model) {
|
|
|
2224
2312
|
"import request from '/@/utils/request';",
|
|
2225
2313
|
'',
|
|
2226
2314
|
model.modules.map(renderMultiLevelApiFunctions).join('\n\n'),
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2315
|
+
renderExtraApiFunctions(model),
|
|
2316
|
+
'',
|
|
2317
|
+
].join('\n');
|
|
2318
|
+
}
|
|
2230
2319
|
|
|
2231
2320
|
function renderMultiLevelFormField(field) {
|
|
2232
2321
|
const labelExpr = `getFieldLabel('${field.attrName}')`;
|
|
@@ -2964,6 +3053,39 @@ function renderBusinessStatusHelpers(model) {
|
|
|
2964
3053
|
].join('\n');
|
|
2965
3054
|
}
|
|
2966
3055
|
|
|
3056
|
+
function sanitizeComment(value) {
|
|
3057
|
+
return String(value || '').replace(/\*\//g, '* /').replace(/\r?\n/g, ' ').trim();
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
function renderExtraApiFunctions(model) {
|
|
3061
|
+
if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
|
|
3062
|
+
return model.extraApis
|
|
3063
|
+
.map((api) => {
|
|
3064
|
+
const requestField = api.requestType === 'data' ? 'data' : 'params';
|
|
3065
|
+
const lines = [
|
|
3066
|
+
'',
|
|
3067
|
+
`// 额外接口:${sanitizeComment(api.description)}`,
|
|
3068
|
+
];
|
|
3069
|
+
if (api.usedBy) {
|
|
3070
|
+
lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
|
|
3071
|
+
}
|
|
3072
|
+
if (api.source) {
|
|
3073
|
+
lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
|
|
3074
|
+
}
|
|
3075
|
+
lines.push(
|
|
3076
|
+
`export function ${api.functionName}(payload?: any) {`,
|
|
3077
|
+
' return request({',
|
|
3078
|
+
` url: '${api.url}',`,
|
|
3079
|
+
` method: '${api.method}',`,
|
|
3080
|
+
` ${requestField}: payload,`,
|
|
3081
|
+
' });',
|
|
3082
|
+
'}'
|
|
3083
|
+
);
|
|
3084
|
+
return lines.join('\n');
|
|
3085
|
+
})
|
|
3086
|
+
.join('\n');
|
|
3087
|
+
}
|
|
3088
|
+
|
|
2967
3089
|
function buildReplacements(model, sharedSupport) {
|
|
2968
3090
|
const menuBaseId = Date.now();
|
|
2969
3091
|
const apiModulePath = model.targetApiModule || `${model.moduleName}/${model.functionName}`;
|
|
@@ -2994,6 +3116,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
2994
3116
|
BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
|
|
2995
3117
|
BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
|
|
2996
3118
|
BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
|
|
3119
|
+
EXTRA_API_FUNCTIONS: renderExtraApiFunctions(model),
|
|
2997
3120
|
CUSTOM_QUERY_FIELDS_EXPR: model.pageType === 'dict' ? '[]' : 'queryableDictOptions.value',
|
|
2998
3121
|
SHOW_RIGHT_TOOLS: model.pageType === 'dict' ? 'false' : 'true',
|
|
2999
3122
|
MENU_BASE_ID: menuBaseId,
|
|
@@ -3093,8 +3216,9 @@ function ensureArguments(input) {
|
|
|
3093
3216
|
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
3094
3217
|
targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
|
|
3095
3218
|
targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
|
|
3096
|
-
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
3097
|
-
|
|
3219
|
+
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
3220
|
+
extraApis: input.extraApis === undefined ? [] : input.extraApis,
|
|
3221
|
+
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
3098
3222
|
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
3099
3223
|
writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
|
|
3100
3224
|
mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
|
|
@@ -3159,11 +3283,12 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
3159
3283
|
disableApi: buildApiRoutePath(model.moduleName, moduleModel.disableApi).replace(/^\/+/, ''),
|
|
3160
3284
|
})),
|
|
3161
3285
|
})),
|
|
3162
|
-
summary: {
|
|
3163
|
-
totalLevels: model.levels.length,
|
|
3164
|
-
totalModules: model.modules.length,
|
|
3165
|
-
|
|
3166
|
-
|
|
3286
|
+
summary: {
|
|
3287
|
+
totalLevels: model.levels.length,
|
|
3288
|
+
totalModules: model.modules.length,
|
|
3289
|
+
extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
|
|
3290
|
+
dictFields: model.modules.flatMap((moduleModel) => moduleModel.optionFields.filter((field) => field.dictType).map((field) => `${moduleModel.key}.${field.attrName}`)),
|
|
3291
|
+
},
|
|
3167
3292
|
note,
|
|
3168
3293
|
};
|
|
3169
3294
|
}
|
|
@@ -3224,8 +3349,9 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
3224
3349
|
listVisibleFields: model.listFields.length,
|
|
3225
3350
|
dictFields: model.optionFields.filter((field) => field.dictType).map((field) => field.attrName),
|
|
3226
3351
|
skippedAuditFields: model.fields.filter((field) => field.isAudit).map((field) => field.fieldName),
|
|
3227
|
-
childCount: model.children.length,
|
|
3228
|
-
|
|
3352
|
+
childCount: model.children.length,
|
|
3353
|
+
extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
|
|
3354
|
+
childTables: model.children.map((childModel) => childModel.tableName),
|
|
3229
3355
|
childPayloadFields: model.children.map((childModel) => ({ childTableName: childModel.tableName, payloadField: childModel.payloadField || childModel.listName })),
|
|
3230
3356
|
childVisibleFields: model.children.reduce((sum, childModel) => sum + childModel.visibleFields.length, 0),
|
|
3231
3357
|
},
|
package/package.json
CHANGED