worsoft-frontend-codegen-local-mcp 0.1.57 → 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;
|
|
@@ -2228,9 +2312,10 @@ function renderMultiLevelApiTs(model) {
|
|
|
2228
2312
|
"import request from '/@/utils/request';",
|
|
2229
2313
|
'',
|
|
2230
2314
|
model.modules.map(renderMultiLevelApiFunctions).join('\n\n'),
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2315
|
+
renderExtraApiFunctions(model),
|
|
2316
|
+
'',
|
|
2317
|
+
].join('\n');
|
|
2318
|
+
}
|
|
2234
2319
|
|
|
2235
2320
|
function renderMultiLevelFormField(field) {
|
|
2236
2321
|
const labelExpr = `getFieldLabel('${field.attrName}')`;
|
|
@@ -2968,6 +3053,39 @@ function renderBusinessStatusHelpers(model) {
|
|
|
2968
3053
|
].join('\n');
|
|
2969
3054
|
}
|
|
2970
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
|
+
|
|
2971
3089
|
function buildReplacements(model, sharedSupport) {
|
|
2972
3090
|
const menuBaseId = Date.now();
|
|
2973
3091
|
const apiModulePath = model.targetApiModule || `${model.moduleName}/${model.functionName}`;
|
|
@@ -2998,6 +3116,7 @@ function buildReplacements(model, sharedSupport) {
|
|
|
2998
3116
|
BUSINESS_STATUS_IMPORTS: renderBusinessStatusImports(model),
|
|
2999
3117
|
BUSINESS_EDIT_IF: hasBusinessBillStateEditControl(model) ? ' v-if="showEditAction(row)"' : '',
|
|
3000
3118
|
BUSINESS_STATUS_HELPERS: renderBusinessStatusHelpers(model),
|
|
3119
|
+
EXTRA_API_FUNCTIONS: renderExtraApiFunctions(model),
|
|
3001
3120
|
CUSTOM_QUERY_FIELDS_EXPR: model.pageType === 'dict' ? '[]' : 'queryableDictOptions.value',
|
|
3002
3121
|
SHOW_RIGHT_TOOLS: model.pageType === 'dict' ? 'false' : 'true',
|
|
3003
3122
|
MENU_BASE_ID: menuBaseId,
|
|
@@ -3097,8 +3216,9 @@ function ensureArguments(input) {
|
|
|
3097
3216
|
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
3098
3217
|
targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
|
|
3099
3218
|
targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
|
|
3100
|
-
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
3101
|
-
|
|
3219
|
+
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
3220
|
+
extraApis: input.extraApis === undefined ? [] : input.extraApis,
|
|
3221
|
+
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
3102
3222
|
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
3103
3223
|
writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
|
|
3104
3224
|
mergeI18nZh: input.mergeI18nZh === undefined ? true : Boolean(input.mergeI18nZh),
|
|
@@ -3163,11 +3283,12 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
3163
3283
|
disableApi: buildApiRoutePath(model.moduleName, moduleModel.disableApi).replace(/^\/+/, ''),
|
|
3164
3284
|
})),
|
|
3165
3285
|
})),
|
|
3166
|
-
summary: {
|
|
3167
|
-
totalLevels: model.levels.length,
|
|
3168
|
-
totalModules: model.modules.length,
|
|
3169
|
-
|
|
3170
|
-
|
|
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
|
+
},
|
|
3171
3292
|
note,
|
|
3172
3293
|
};
|
|
3173
3294
|
}
|
|
@@ -3228,8 +3349,9 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
3228
3349
|
listVisibleFields: model.listFields.length,
|
|
3229
3350
|
dictFields: model.optionFields.filter((field) => field.dictType).map((field) => field.attrName),
|
|
3230
3351
|
skippedAuditFields: model.fields.filter((field) => field.isAudit).map((field) => field.fieldName),
|
|
3231
|
-
childCount: model.children.length,
|
|
3232
|
-
|
|
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),
|
|
3233
3355
|
childPayloadFields: model.children.map((childModel) => ({ childTableName: childModel.tableName, payloadField: childModel.payloadField || childModel.listName })),
|
|
3234
3356
|
childVisibleFields: model.children.reduce((sum, childModel) => sum + childModel.visibleFields.length, 0),
|
|
3235
3357
|
},
|
package/package.json
CHANGED