worsoft-frontend-codegen-local-mcp 0.1.57 → 0.1.60
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.60';
|
|
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,89 @@ 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 = normalizeExtraApiUrl(String(item.url || '').trim(), currentTargetApiModule);
|
|
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,
|
|
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 normalizeExtraApiUrl(rawUrl, currentTargetApiModule) {
|
|
1488
|
+
if (!rawUrl) return '';
|
|
1489
|
+
if (/^https?:\/\//i.test(rawUrl)) return rawUrl;
|
|
1490
|
+
let url = rawUrl.startsWith('/') ? rawUrl : '/' + rawUrl;
|
|
1491
|
+
const moduleRoot = String(currentTargetApiModule || '').split('/')[0] || '';
|
|
1492
|
+
if (moduleRoot && !url.startsWith('/' + moduleRoot + '/')) {
|
|
1493
|
+
url = '/' + moduleRoot + url;
|
|
1494
|
+
}
|
|
1495
|
+
return url;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function ensureFieldExists(fields, fieldName, tableName, role) {
|
|
1499
|
+
const field = fields.find((item) => item.fieldName === fieldName);
|
|
1500
|
+
if (!field) throw new Error(role + ' field "' + fieldName + '" was not found on table ' + tableName);
|
|
1501
|
+
return field;
|
|
1502
|
+
}
|
|
1410
1503
|
|
|
1411
1504
|
function detectStatusField(fields, explicitName) {
|
|
1412
1505
|
if (explicitName) {
|
|
@@ -1518,9 +1611,10 @@ function buildMultiLevelDictModel(safeArgs) {
|
|
|
1518
1611
|
targetI18nKey: resolvedTargets.targetI18nKey,
|
|
1519
1612
|
frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
|
|
1520
1613
|
style: safeArgs.style,
|
|
1521
|
-
levels: builtLevels,
|
|
1522
|
-
modules: allModules,
|
|
1523
|
-
|
|
1614
|
+
levels: builtLevels,
|
|
1615
|
+
modules: allModules,
|
|
1616
|
+
extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
|
|
1617
|
+
dictTypes,
|
|
1524
1618
|
pk: parentModule.pk,
|
|
1525
1619
|
fields: parentModule.fields,
|
|
1526
1620
|
optionFields: parentModule.optionFields,
|
|
@@ -1591,11 +1685,11 @@ function buildModel(safeArgs) {
|
|
|
1591
1685
|
|
|
1592
1686
|
const derivedFunctionName = toCamelCase(safeArgs.tableName);
|
|
1593
1687
|
const apiPath = safeArgs.apiPath || derivedFunctionName;
|
|
1594
|
-
const resolvedTargets = resolveGenerationTargets({
|
|
1595
|
-
moduleName: safeArgs.moduleName,
|
|
1596
|
-
functionName: derivedFunctionName,
|
|
1597
|
-
apiPath,
|
|
1598
|
-
targetViewDir: safeArgs.targetViewDir,
|
|
1688
|
+
const resolvedTargets = resolveGenerationTargets({
|
|
1689
|
+
moduleName: safeArgs.moduleName,
|
|
1690
|
+
functionName: derivedFunctionName,
|
|
1691
|
+
apiPath,
|
|
1692
|
+
targetViewDir: safeArgs.targetViewDir,
|
|
1599
1693
|
targetApiModule: safeArgs.targetApiModule,
|
|
1600
1694
|
targetI18nKey: safeArgs.targetI18nKey,
|
|
1601
1695
|
});
|
|
@@ -1622,9 +1716,10 @@ function buildModel(safeArgs) {
|
|
|
1622
1716
|
dictTypes,
|
|
1623
1717
|
frontendPath: normalizeFrontendRootPath(safeArgs.frontendPath),
|
|
1624
1718
|
style: safeArgs.style,
|
|
1719
|
+
extraApis: normalizeExtraApis(safeArgs.extraApis, resolvedTargets.targetApiModule),
|
|
1625
1720
|
children,
|
|
1626
1721
|
};
|
|
1627
|
-
}
|
|
1722
|
+
}
|
|
1628
1723
|
|
|
1629
1724
|
function renderTemplate(templateText, replacements) {
|
|
1630
1725
|
let output = templateText;
|
|
@@ -2228,9 +2323,10 @@ function renderMultiLevelApiTs(model) {
|
|
|
2228
2323
|
"import request from '/@/utils/request';",
|
|
2229
2324
|
'',
|
|
2230
2325
|
model.modules.map(renderMultiLevelApiFunctions).join('\n\n'),
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2326
|
+
renderExtraApiFunctions(model),
|
|
2327
|
+
'',
|
|
2328
|
+
].join('\n');
|
|
2329
|
+
}
|
|
2234
2330
|
|
|
2235
2331
|
function renderMultiLevelFormField(field) {
|
|
2236
2332
|
const labelExpr = `getFieldLabel('${field.attrName}')`;
|
|
@@ -2968,6 +3064,39 @@ function renderBusinessStatusHelpers(model) {
|
|
|
2968
3064
|
].join('\n');
|
|
2969
3065
|
}
|
|
2970
3066
|
|
|
3067
|
+
function sanitizeComment(value) {
|
|
3068
|
+
return String(value || '').replace(/\*\//g, '* /').replace(/\r?\n/g, ' ').trim();
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
function renderExtraApiFunctions(model) {
|
|
3072
|
+
if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
|
|
3073
|
+
return model.extraApis
|
|
3074
|
+
.map((api) => {
|
|
3075
|
+
const requestField = api.requestType === 'data' ? 'data' : 'params';
|
|
3076
|
+
const lines = [
|
|
3077
|
+
'',
|
|
3078
|
+
`// 额外接口:${sanitizeComment(api.description)}`,
|
|
3079
|
+
];
|
|
3080
|
+
if (api.usedBy) {
|
|
3081
|
+
lines.push(`// 使用场景:${sanitizeComment(api.usedBy)}`);
|
|
3082
|
+
}
|
|
3083
|
+
if (api.source) {
|
|
3084
|
+
lines.push(`// 来源说明:${sanitizeComment(api.source)}`);
|
|
3085
|
+
}
|
|
3086
|
+
lines.push(
|
|
3087
|
+
`export function ${api.functionName}(payload?: any) {`,
|
|
3088
|
+
' return request({',
|
|
3089
|
+
` url: '${api.url}',`,
|
|
3090
|
+
` method: '${api.method}',`,
|
|
3091
|
+
` ${requestField}: payload,`,
|
|
3092
|
+
' });',
|
|
3093
|
+
'}'
|
|
3094
|
+
);
|
|
3095
|
+
return lines.join('\n');
|
|
3096
|
+
})
|
|
3097
|
+
.join('\n');
|
|
3098
|
+
}
|
|
3099
|
+
|
|
2971
3100
|
function buildReplacements(model, sharedSupport) {
|
|
2972
3101
|
const menuBaseId = Date.now();
|
|
2973
3102
|
const apiModulePath = model.targetApiModule || `${model.moduleName}/${model.functionName}`;
|
|
@@ -2998,6 +3127,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
2998
3127
|
BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
|
|
2999
3128
|
BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
|
|
3000
3129
|
BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
|
|
3130
|
+
EXTRA_API_FUNCTIONS: renderExtraApiFunctions(model),
|
|
3001
3131
|
CUSTOM_QUERY_FIELDS_EXPR: model.pageType === 'dict' ? '[]' : 'queryableDictOptions.value',
|
|
3002
3132
|
SHOW_RIGHT_TOOLS: model.pageType === 'dict' ? 'false' : 'true',
|
|
3003
3133
|
MENU_BASE_ID: menuBaseId,
|
|
@@ -3097,8 +3227,9 @@ function ensureArguments(input) {
|
|
|
3097
3227
|
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
3098
3228
|
targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
|
|
3099
3229
|
targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
|
|
3100
|
-
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
3101
|
-
|
|
3230
|
+
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
3231
|
+
extraApis: input.extraApis === undefined ? [] : input.extraApis,
|
|
3232
|
+
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
3102
3233
|
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
3103
3234
|
writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
|
|
3104
3235
|
mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
|
|
@@ -3163,11 +3294,12 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
3163
3294
|
disableApi: buildApiRoutePath(model.moduleName, moduleModel.disableApi).replace(/^\/+/, ''),
|
|
3164
3295
|
})),
|
|
3165
3296
|
})),
|
|
3166
|
-
summary: {
|
|
3167
|
-
totalLevels: model.levels.length,
|
|
3168
|
-
totalModules: model.modules.length,
|
|
3169
|
-
|
|
3170
|
-
|
|
3297
|
+
summary: {
|
|
3298
|
+
totalLevels: model.levels.length,
|
|
3299
|
+
totalModules: model.modules.length,
|
|
3300
|
+
extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
|
|
3301
|
+
dictFields: model.modules.flatMap((moduleModel) => moduleModel.optionFields.filter((field) => field.dictType).map((field) => `${moduleModel.key}.${field.attrName}`)),
|
|
3302
|
+
},
|
|
3171
3303
|
note,
|
|
3172
3304
|
};
|
|
3173
3305
|
}
|
|
@@ -3228,8 +3360,9 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
3228
3360
|
listVisibleFields: model.listFields.length,
|
|
3229
3361
|
dictFields: model.optionFields.filter((field) => field.dictType).map((field) => field.attrName),
|
|
3230
3362
|
skippedAuditFields: model.fields.filter((field) => field.isAudit).map((field) => field.fieldName),
|
|
3231
|
-
childCount: model.children.length,
|
|
3232
|
-
|
|
3363
|
+
childCount: model.children.length,
|
|
3364
|
+
extraApis: model.extraApis.map((api) => ({ functionName: api.functionName, description: api.description, url: api.url, method: api.method })),
|
|
3365
|
+
childTables: model.children.map((childModel) => childModel.tableName),
|
|
3233
3366
|
childPayloadFields: model.children.map((childModel) => ({ childTableName: childModel.tableName, payloadField: childModel.payloadField || childModel.listName })),
|
|
3234
3367
|
childVisibleFields: model.children.reduce((sum, childModel) => sum + childModel.visibleFields.length, 0),
|
|
3235
3368
|
},
|
package/package.json
CHANGED