worsoft-frontend-codegen-local-mcp 0.1.33 → 0.1.36
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 +83 -48
- package/package.json +1 -1
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.36';
|
|
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');
|
|
@@ -148,19 +148,19 @@ export function createCrudSchema(
|
|
|
148
148
|
const TOOL_SCHEMA = {
|
|
149
149
|
type: 'object',
|
|
150
150
|
properties: {
|
|
151
|
-
featureTitle: { type: 'string', description: 'Feature title from structured metadata.' },
|
|
152
|
-
tableName: { type: 'string', description: '
|
|
153
|
-
tableComment: { type: 'string', description: '
|
|
154
|
-
apiPath: { type: 'string', description: 'Backend API base path from structured metadata, for example iwmEmpOutsourcePerson.' },
|
|
151
|
+
featureTitle: { type: 'string', description: 'Feature title from pre-parsed structured metadata.' },
|
|
152
|
+
tableName: { type: 'string', description: 'Canonical main table name from PRD-aligned structured metadata.' },
|
|
153
|
+
tableComment: { type: 'string', description: 'Canonical main table comment or feature label from PRD-aligned structured metadata.' },
|
|
154
|
+
apiPath: { type: 'string', description: 'Backend API base path from pre-parsed structured metadata, for example iwmEmpOutsourcePerson.' },
|
|
155
155
|
pageType: {
|
|
156
156
|
type: 'string',
|
|
157
157
|
enum: ['feature', 'dictionary', 'non_standard', 'business', 'dict'],
|
|
158
|
-
description: 'Structured page type from parseResult. Dictionary pages are restricted to dialog-based templates.',
|
|
158
|
+
description: 'Structured page type from parseResult. MCP consumes this value but does not derive it. Dictionary pages are restricted to dialog-based templates.',
|
|
159
159
|
},
|
|
160
|
-
style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: '
|
|
161
|
-
fields: {
|
|
162
|
-
type: 'array',
|
|
163
|
-
description: 'Structured main-table field metadata
|
|
160
|
+
style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Final style id from parseResult or translated mcpPayload. MCP validates it but does not infer it.' },
|
|
161
|
+
fields: {
|
|
162
|
+
type: 'array',
|
|
163
|
+
description: 'Structured main-table field metadata already translated by the caller. MCP only consumes these low-level generation parameters.',
|
|
164
164
|
items: {
|
|
165
165
|
type: 'object',
|
|
166
166
|
properties: {
|
|
@@ -177,8 +177,10 @@ const TOOL_SCHEMA = {
|
|
|
177
177
|
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
178
178
|
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
179
179
|
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
180
|
-
description: { type: 'string', description: 'Field description or notes.' },
|
|
181
|
-
|
|
180
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
181
|
+
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata, for example text, textarea, select, number or datetime.' },
|
|
182
|
+
formType: { type: 'string', description: 'Explicit form control type translated by the caller. MCP consumes this value first and only falls back to legacy heuristics when omitted.' },
|
|
183
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
182
184
|
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
|
|
183
185
|
},
|
|
184
186
|
required: ['fieldName', 'label', 'type'],
|
|
@@ -187,7 +189,7 @@ const TOOL_SCHEMA = {
|
|
|
187
189
|
},
|
|
188
190
|
children: {
|
|
189
191
|
type: 'array',
|
|
190
|
-
description: 'Optional direct child-table structures for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form.',
|
|
192
|
+
description: 'Optional direct child-table structures for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form. The caller must determine semantic truth before passing them in.',
|
|
191
193
|
items: {
|
|
192
194
|
type: 'object',
|
|
193
195
|
properties: {
|
|
@@ -216,8 +218,10 @@ const TOOL_SCHEMA = {
|
|
|
216
218
|
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
217
219
|
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
218
220
|
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
219
|
-
description: { type: 'string', description: 'Field description or notes.' },
|
|
220
|
-
|
|
221
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
222
|
+
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
|
|
223
|
+
formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
|
|
224
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
221
225
|
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
|
|
222
226
|
},
|
|
223
227
|
required: ['fieldName', 'label', 'type'],
|
|
@@ -243,8 +247,8 @@ const TOOL_SCHEMA = {
|
|
|
243
247
|
items: {
|
|
244
248
|
type: 'object',
|
|
245
249
|
properties: {
|
|
246
|
-
tableName: { type: 'string', description: '
|
|
247
|
-
tableComment: { type: 'string', description: '
|
|
250
|
+
tableName: { type: 'string', description: 'Canonical module table name from PRD-aligned structured metadata.' },
|
|
251
|
+
tableComment: { type: 'string', description: 'Canonical module display label from PRD-aligned structured metadata.' },
|
|
248
252
|
apiPath: { type: 'string', description: 'Backend API base path for this module.' },
|
|
249
253
|
primaryKey: { type: 'string', description: 'Primary key field name. Defaults to id when omitted.' },
|
|
250
254
|
queryParentField: { type: 'string', description: 'Direct parent foreign key field used for page queries.' },
|
|
@@ -271,8 +275,10 @@ const TOOL_SCHEMA = {
|
|
|
271
275
|
queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
|
|
272
276
|
dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
|
|
273
277
|
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
274
|
-
description: { type: 'string', description: 'Field description or notes.' },
|
|
275
|
-
|
|
278
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
279
|
+
componentType: { type: 'string', description: 'Explicit component type from PRD structured metadata.' },
|
|
280
|
+
formType: { type: 'string', description: 'Explicit form control type translated by the caller.' },
|
|
281
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
276
282
|
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' }
|
|
277
283
|
},
|
|
278
284
|
required: ['fieldName', 'label', 'type'],
|
|
@@ -303,11 +309,6 @@ const TOOL_SCHEMA = {
|
|
|
303
309
|
type: 'string',
|
|
304
310
|
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.',
|
|
305
311
|
},
|
|
306
|
-
generationMode: {
|
|
307
|
-
type: 'string',
|
|
308
|
-
enum: ['create_new', 'overwrite_existing', 'patch_existing'],
|
|
309
|
-
description: 'Structured generation mode from parseResult. MCP currently uses this as a pass-through runtime hint and result annotation.',
|
|
310
|
-
},
|
|
311
312
|
writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
|
|
312
313
|
overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
|
|
313
314
|
writeSupportFiles: {
|
|
@@ -850,15 +851,34 @@ function findDictType(comment) {
|
|
|
850
851
|
return extractDictType(comment);
|
|
851
852
|
}
|
|
852
853
|
|
|
853
|
-
function mapFieldType(field) {
|
|
854
|
-
if (field.dictType) return 'select';
|
|
855
|
-
if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
|
|
856
|
-
if (field.sqlType === 'DATE') return 'date';
|
|
857
|
-
if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
|
|
858
|
-
if (field.sqlType === 'TEXT') return 'textarea';
|
|
859
|
-
if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
|
|
860
|
-
return 'text';
|
|
861
|
-
}
|
|
854
|
+
function mapFieldType(field) {
|
|
855
|
+
if (field.dictType) return 'select';
|
|
856
|
+
if (field.sqlType === 'DATETIME' || field.sqlType === 'TIMESTAMP') return 'datetime';
|
|
857
|
+
if (field.sqlType === 'DATE') return 'date';
|
|
858
|
+
if (['INT', 'BIGINT', 'DECIMAL', 'NUMERIC'].includes(field.sqlType)) return 'number';
|
|
859
|
+
if (field.sqlType === 'TEXT') return 'textarea';
|
|
860
|
+
if (field.sqlType === 'VARCHAR' && field.length && Number(field.length) > 64) return 'textarea';
|
|
861
|
+
return 'text';
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
function normalizeStructuredFormType(value) {
|
|
865
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
866
|
+
if (!normalized) return '';
|
|
867
|
+
if (normalized === 'date') return 'date';
|
|
868
|
+
if (normalized === 'datetime') return 'datetime';
|
|
869
|
+
if (normalized === 'microme-operator') return 'number';
|
|
870
|
+
if (normalized === 'upload' || normalized === 'picker') {
|
|
871
|
+
throw new Error(
|
|
872
|
+
'Explicit component/form type "' +
|
|
873
|
+
normalized +
|
|
874
|
+
'" is not yet supported by worsoft-codegen-local templates. Please keep it in parseResult for downstream handling or extend MCP template support first.'
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
if (['text', 'select', 'textarea', 'number'].includes(normalized)) {
|
|
878
|
+
return normalized;
|
|
879
|
+
}
|
|
880
|
+
throw new Error('Unsupported explicit component/form type: ' + normalized);
|
|
881
|
+
}
|
|
862
882
|
|
|
863
883
|
function escapeForRegex(value) {
|
|
864
884
|
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
@@ -1013,6 +1033,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
|
|
|
1013
1033
|
}
|
|
1014
1034
|
|
|
1015
1035
|
const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
|
|
1036
|
+
const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
|
|
1016
1037
|
const explicitQueryType =
|
|
1017
1038
|
inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
|
|
1018
1039
|
? undefined
|
|
@@ -1028,6 +1049,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
|
|
|
1028
1049
|
comment: label,
|
|
1029
1050
|
label,
|
|
1030
1051
|
description: String(inputField.description || '').trim(),
|
|
1052
|
+
formType: explicitFormType,
|
|
1031
1053
|
dictType: normalizeDictType(inputField.dictType),
|
|
1032
1054
|
notNull: parseBooleanLike(inputField.required, false),
|
|
1033
1055
|
defaultValue: normalizeDefaultValue(inputField.defaultValue),
|
|
@@ -1221,12 +1243,29 @@ function getStylePreset(styleId) {
|
|
|
1221
1243
|
function normalizePageTypeInput(pageType) {
|
|
1222
1244
|
if (pageType === undefined || pageType === null || pageType === '') return '';
|
|
1223
1245
|
const normalized = String(pageType).trim();
|
|
1224
|
-
if (normalized === 'dict'
|
|
1225
|
-
|
|
1246
|
+
if (normalized === 'dict' || normalized === 'business') {
|
|
1247
|
+
throw new Error(`Legacy pageType alias "${normalized}" is not allowed. Use canonical pageType "dictionary" or "feature" from parseResult.`);
|
|
1248
|
+
}
|
|
1226
1249
|
if (['feature', 'dictionary', 'non_standard'].includes(normalized)) return normalized;
|
|
1227
1250
|
throw new Error('Unsupported pageType: ' + normalized);
|
|
1228
1251
|
}
|
|
1229
1252
|
|
|
1253
|
+
function rejectSemanticStageInputs(input) {
|
|
1254
|
+
const forbiddenKeys = [
|
|
1255
|
+
'parseResult',
|
|
1256
|
+
'prdFile',
|
|
1257
|
+
'apiDocFile',
|
|
1258
|
+
'fieldMappings',
|
|
1259
|
+
'dictionaryMeta',
|
|
1260
|
+
'listQueryMeta',
|
|
1261
|
+
'fieldUiMeta',
|
|
1262
|
+
];
|
|
1263
|
+
const present = forbiddenKeys.filter((key) => Object.prototype.hasOwnProperty.call(input, key));
|
|
1264
|
+
if (present.length) {
|
|
1265
|
+
throw new Error(`worsoft_codegen_local_generate_frontend only accepts translated low-level generation parameters. Unsupported semantic-stage keys: ${present.join(', ')}`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1230
1269
|
function validatePageTypeAndStyle(pageType, style) {
|
|
1231
1270
|
if (!pageType) return;
|
|
1232
1271
|
if (pageType === 'non_standard') {
|
|
@@ -1242,9 +1281,9 @@ function hasRuntimeSupport(stylePreset) {
|
|
|
1242
1281
|
return Boolean(stylePreset.runtime && stylePreset.runtime.supported && stylePreset.runtime.templateDir);
|
|
1243
1282
|
}
|
|
1244
1283
|
|
|
1245
|
-
function normalizeFields(parsed) {
|
|
1246
|
-
return parsed.fields.map((field) => ({ ...field, formType: mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
|
|
1247
|
-
}
|
|
1284
|
+
function normalizeFields(parsed) {
|
|
1285
|
+
return parsed.fields.map((field) => ({ ...field, formType: field.formType || mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
|
|
1286
|
+
}
|
|
1248
1287
|
|
|
1249
1288
|
function ensureFieldExists(fields, fieldName, tableName, role) {
|
|
1250
1289
|
const field = fields.find((item) => item.fieldName === fieldName);
|
|
@@ -1357,7 +1396,6 @@ function buildMultiLevelDictModel(safeArgs) {
|
|
|
1357
1396
|
targetViewDir: resolvedTargets.targetViewDir,
|
|
1358
1397
|
targetApiModule: resolvedTargets.targetApiModule,
|
|
1359
1398
|
targetI18nKey: resolvedTargets.targetI18nKey,
|
|
1360
|
-
generationMode: safeArgs.generationMode,
|
|
1361
1399
|
frontendPath: path.resolve(safeArgs.frontendPath),
|
|
1362
1400
|
style: safeArgs.style,
|
|
1363
1401
|
levels: builtLevels,
|
|
@@ -1452,7 +1490,6 @@ function buildModel(safeArgs) {
|
|
|
1452
1490
|
targetViewDir: resolvedTargets.targetViewDir,
|
|
1453
1491
|
targetApiModule: resolvedTargets.targetApiModule,
|
|
1454
1492
|
targetI18nKey: resolvedTargets.targetI18nKey,
|
|
1455
|
-
generationMode: safeArgs.generationMode,
|
|
1456
1493
|
pk: pkField,
|
|
1457
1494
|
fields,
|
|
1458
1495
|
optionFields,
|
|
@@ -2618,11 +2655,12 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
|
|
|
2618
2655
|
return files;
|
|
2619
2656
|
}
|
|
2620
2657
|
|
|
2621
|
-
function ensureArguments(input) {
|
|
2622
|
-
if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2658
|
+
function ensureArguments(input) {
|
|
2659
|
+
if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
|
|
2660
|
+
rejectSemanticStageInputs(input);
|
|
2661
|
+
for (const key of TOOL_SCHEMA.required) {
|
|
2662
|
+
if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
|
|
2663
|
+
}
|
|
2626
2664
|
|
|
2627
2665
|
const style = String(input.style);
|
|
2628
2666
|
const pageType = normalizePageTypeInput(input.pageType);
|
|
@@ -2655,7 +2693,6 @@ function ensureArguments(input) {
|
|
|
2655
2693
|
targetViewDir: input.targetViewDir ? String(input.targetViewDir) : '',
|
|
2656
2694
|
targetApiModule: input.targetApiModule ? String(input.targetApiModule) : '',
|
|
2657
2695
|
targetI18nKey: input.targetI18nKey ? String(input.targetI18nKey) : '',
|
|
2658
|
-
generationMode: input.generationMode ? String(input.generationMode) : 'create_new',
|
|
2659
2696
|
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
2660
2697
|
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
2661
2698
|
writeSupportFiles: input.writeSupportFiles === undefined ? true : Boolean(input.writeSupportFiles),
|
|
@@ -2699,7 +2736,6 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
2699
2736
|
targetViewDir: model.targetViewDir,
|
|
2700
2737
|
targetApiModule: model.targetApiModule,
|
|
2701
2738
|
targetI18nKey: model.targetI18nKey,
|
|
2702
|
-
generationMode: model.generationMode || safeArgs.generationMode,
|
|
2703
2739
|
writeToDisk: safeArgs.writeToDisk,
|
|
2704
2740
|
sideEffects: {
|
|
2705
2741
|
writeSupportFiles: safeArgs.writeSupportFiles,
|
|
@@ -2756,7 +2792,6 @@ function buildManifest(model, safeArgs, stylePreset, files, note) {
|
|
|
2756
2792
|
targetViewDir: model.targetViewDir,
|
|
2757
2793
|
targetApiModule: model.targetApiModule,
|
|
2758
2794
|
targetI18nKey: model.targetI18nKey,
|
|
2759
|
-
generationMode: model.generationMode || safeArgs.generationMode,
|
|
2760
2795
|
writeToDisk: safeArgs.writeToDisk,
|
|
2761
2796
|
sideEffects: {
|
|
2762
2797
|
writeSupportFiles: safeArgs.writeSupportFiles,
|
package/package.json
CHANGED