worsoft-frontend-codegen-local-mcp 0.1.88 → 0.1.90

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.
@@ -22,15 +22,11 @@
22
22
  </div>
23
23
  </template>
24
24
  <!-- 主子表表单:按 PRD 表单显隐和顺序渲染字段 -->
25
- <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
26
- <!-- 主表字段区:字段级注释由 MCP 根据字段名称生成 -->
27
- <el-row :gutter="24">
28
- {{FORM_FIELDS}}
29
- </el-row>
30
- <!-- 子表明细区:按 children 配置渲染多个子表 -->
31
- <el-row :gutter="24">
25
+ <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading" label-width="120px">
26
+ <el-collapse v-model="activeCollapseNames">
27
+ {{FORM_COLLAPSE_SECTIONS}}
32
28
  {{CHILD_SECTIONS}}
33
- </el-row>
29
+ </el-collapse>
34
30
  </el-form>
35
31
  </el-card>
36
32
  </div>
@@ -72,6 +68,7 @@ const pageI18nKey = '{{I18N_NAMESPACE}}';
72
68
 
73
69
  // 表单引用
74
70
  const dataFormRef = ref();
71
+ const activeCollapseNames = ref({{ACTIVE_COLLAPSE_NAMES}});
75
72
  // 页面加载状态
76
73
  const loading = ref(false);
77
74
  // 是否详情模式
@@ -22,11 +22,10 @@
22
22
  </div>
23
23
  </template>
24
24
  <!-- 主表表单:按 PRD 表单显隐和顺序渲染字段 -->
25
- <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading">
26
- <!-- 主表字段区:字段级注释由 MCP 根据字段名称生成 -->
27
- <el-row :gutter="24">
28
- {{FORM_FIELDS}}
29
- </el-row>
25
+ <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading" label-width="120px">
26
+ <el-collapse v-model="activeCollapseNames">
27
+ {{FORM_COLLAPSE_SECTIONS}}
28
+ </el-collapse>
30
29
  </el-form>
31
30
  </el-card>
32
31
  </div>
@@ -63,6 +62,7 @@ const { closeCurrentPage } = useCloseCurrentPage();
63
62
 
64
63
  // 表单引用
65
64
  const dataFormRef = ref();
65
+ const activeCollapseNames = ref({{ACTIVE_COLLAPSE_NAMES}});
66
66
  // 页面加载状态
67
67
  const loading = ref(false);
68
68
  // 是否详情模式
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.88';
8
+ const SERVER_VERSION = '0.1.90';
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');
@@ -1228,13 +1228,19 @@ function readUtf8File(filePath) {
1228
1228
  return stripBom(fs.readFileSync(filePath, 'utf8'));
1229
1229
  }
1230
1230
 
1231
- function normalizeDictType(value) {
1232
- const normalized = String(value || '').trim();
1233
- if (!normalized || normalized === '-' || normalized === '/') {
1234
- return null;
1235
- }
1236
- return normalized.replace(/^['"`]+|['"`]+$/g, '');
1237
- }
1231
+ function normalizeDictType(value) {
1232
+ const normalized = String(value || '').trim();
1233
+ if (!normalized || normalized === '-' || normalized === '/') {
1234
+ return null;
1235
+ }
1236
+ return normalized.replace(/^['"`]+|['"`]+$/g, '');
1237
+ }
1238
+
1239
+ function normalizeDictTypeFromSourceLabel(value) {
1240
+ const normalized = normalizeDictType(value);
1241
+ if (!normalized) return null;
1242
+ return /^[a-z][a-z0-9_]*$/.test(normalized) ? normalized : null;
1243
+ }
1238
1244
 
1239
1245
  function stripDictAnnotation(label) {
1240
1246
  const text = String(label || '').trim();
@@ -1433,29 +1439,33 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1433
1439
  throw new Error(contextLabel + '[' + index + '] is missing required field: type');
1434
1440
  }
1435
1441
 
1436
- const { length, scale } = normalizeStructuredLengthAndScale(inputField.length ?? inputField.maxLength, inputField.scale);
1437
- const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1438
- const formType = explicitFormType;
1439
- const explicitQueryType =
1440
- inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1441
- ? undefined
1442
+ const { length, scale } = normalizeStructuredLengthAndScale(inputField.length ?? inputField.maxLength, inputField.scale);
1443
+ const explicitFormType = normalizeStructuredFormType(inputField.formType || inputField.componentType);
1444
+ const formType = explicitFormType;
1445
+ const dictType = normalizeDictType(inputField.dictType) || normalizeDictTypeFromSourceLabel(inputField.sourceDictLabel);
1446
+ const explicitQueryType =
1447
+ inputField.queryType === undefined || inputField.queryType === null || inputField.queryType === ''
1448
+ ? undefined
1442
1449
  : Number.parseInt(String(inputField.queryType), 10);
1443
1450
  const show = parseBooleanLike(inputField.show, true);
1444
- const listShow = parseBooleanLike(inputField.listShow, show);
1445
- const formShow = parseBooleanLike(inputField.formShow, show);
1446
- const formOrder = parseOptionalOrder(inputField.formOrder, contextLabel + '[' + index + '].formOrder');
1447
-
1448
- return {
1451
+ const listShow = parseBooleanLike(inputField.listShow, show);
1452
+ const formShow = parseBooleanLike(inputField.formShow, show);
1453
+ const formOrder = parseOptionalOrder(inputField.formOrder, contextLabel + '[' + index + '].formOrder');
1454
+ if (formShow === false && formOrder !== undefined) {
1455
+ throw new Error(`${contextLabel}[${index}].formOrder must be empty when formShow=false`);
1456
+ }
1457
+
1458
+ return {
1449
1459
  fieldName,
1450
1460
  attrName: toCamelCase(fieldName),
1451
1461
  sqlType: type,
1452
1462
  length,
1453
1463
  scale,
1454
1464
  comment: label,
1455
- label,
1456
- description: String(inputField.description || '').trim(),
1457
- formType,
1458
- dictType: normalizeDictType(inputField.dictType),
1465
+ label,
1466
+ description: String(inputField.description || '').trim(),
1467
+ formType,
1468
+ dictType,
1459
1469
  notNull: parseBooleanLike(inputField.required, false),
1460
1470
  defaultValue: normalizeDefaultValue(inputField.defaultValue),
1461
1471
  readonly: parseBooleanLike(inputField.readonly, false),
@@ -1465,13 +1475,13 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1465
1475
  formOrder,
1466
1476
  width: normalizeOptionWidth(inputField.width),
1467
1477
  smart: parseBooleanLike(inputField.smart, false),
1468
- queryType: Number.isNaN(explicitQueryType)
1469
- ? undefined
1470
- : explicitQueryType !== undefined
1471
- ? explicitQueryType
1472
- : normalizeDictType(inputField.dictType) && listShow
1473
- ? 30
1474
- : undefined,
1478
+ queryType: Number.isNaN(explicitQueryType)
1479
+ ? undefined
1480
+ : explicitQueryType !== undefined
1481
+ ? explicitQueryType
1482
+ : dictType && listShow
1483
+ ? 30
1484
+ : undefined,
1475
1485
  sourceKind: normalizeStructuredSourceKind(inputField.sourceKind),
1476
1486
  primary: parseBooleanLike(inputField.primary, fieldName === 'id'),
1477
1487
  };
@@ -1714,6 +1724,73 @@ function assertParseResultPayloadConsistency(parseResult, mcpPayload, parseResul
1714
1724
  }
1715
1725
  }
1716
1726
 
1727
+ function collectPayloadFieldGroups(mcpPayload) {
1728
+ const groups = [];
1729
+ if (Array.isArray(mcpPayload.fields)) {
1730
+ groups.push({ path: 'mcpPayload.fields', fields: mcpPayload.fields });
1731
+ }
1732
+ if (Array.isArray(mcpPayload.children)) {
1733
+ mcpPayload.children.forEach((child, childIndex) => {
1734
+ if (Array.isArray(child.fields)) {
1735
+ groups.push({ path: `mcpPayload.children[${childIndex}].fields`, fields: child.fields });
1736
+ }
1737
+ });
1738
+ }
1739
+ if (Array.isArray(mcpPayload.levels)) {
1740
+ mcpPayload.levels.forEach((level, levelIndex) => {
1741
+ if (!Array.isArray(level.modules)) return;
1742
+ level.modules.forEach((module, moduleIndex) => {
1743
+ if (Array.isArray(module.fields)) {
1744
+ groups.push({ path: `mcpPayload.levels[${levelIndex}].modules[${moduleIndex}].fields`, fields: module.fields });
1745
+ }
1746
+ });
1747
+ });
1748
+ }
1749
+ return groups;
1750
+ }
1751
+
1752
+ function normalizeFieldSet(values) {
1753
+ if (!Array.isArray(values)) return new Set();
1754
+ return new Set(
1755
+ values
1756
+ .map((item) => (typeof item === 'string' ? item : item && (item.fieldName || item.name)))
1757
+ .filter(Boolean)
1758
+ .map((item) => String(item).trim())
1759
+ );
1760
+ }
1761
+
1762
+ function assertVisibilityMatrixConsistency(parseResult, mcpPayload, parseResultPath) {
1763
+ if (!parseResult.visibilityMatrix || typeof parseResult.visibilityMatrix !== 'object') return;
1764
+ const matrixGroups = Array.isArray(parseResult.visibilityMatrix.groups) ? parseResult.visibilityMatrix.groups : [];
1765
+ const fallbackVisibleFields = normalizeFieldSet(parseResult.visibilityMatrix.visibleFormFields);
1766
+ const fallbackHiddenFields = normalizeFieldSet(parseResult.visibilityMatrix.explicitHiddenFormFields || parseResult.visibilityMatrix.hiddenFormFields);
1767
+ if (!matrixGroups.length && !fallbackVisibleFields.size && !fallbackHiddenFields.size) return;
1768
+
1769
+ const issues = [];
1770
+ for (const group of collectPayloadFieldGroups(mcpPayload)) {
1771
+ const matrixGroup = matrixGroups.find((item) => item && item.path === group.path);
1772
+ const visibleFields = matrixGroup ? normalizeFieldSet(matrixGroup.visibleFormFields) : fallbackVisibleFields;
1773
+ const hiddenFields = matrixGroup ? normalizeFieldSet(matrixGroup.explicitHiddenFormFields || matrixGroup.hiddenFormFields) : fallbackHiddenFields;
1774
+ if (!visibleFields.size && !hiddenFields.size) continue;
1775
+
1776
+ for (const field of group.fields) {
1777
+ if (!field || typeof field !== 'object' || !field.fieldName) continue;
1778
+ const fieldName = String(field.fieldName);
1779
+ const expectedFormShow = visibleFields.has(fieldName) && !hiddenFields.has(fieldName);
1780
+ if (Boolean(field.formShow) !== expectedFormShow) {
1781
+ issues.push(`${group.path}.${fieldName}.formShow expected ${expectedFormShow} from visibilityMatrix, got ${field.formShow}`);
1782
+ }
1783
+ if (!expectedFormShow && field.formOrder !== undefined && field.formOrder !== null && field.formOrder !== '') {
1784
+ issues.push(`${group.path}.${fieldName}.formOrder must be empty when visibilityMatrix marks it hidden`);
1785
+ }
1786
+ }
1787
+ }
1788
+
1789
+ if (issues.length) {
1790
+ throw new Error(`parseResult visibilityMatrix conflicts with mcpPayload in ${parseResultPath}: ${issues.join('; ')}`);
1791
+ }
1792
+ }
1793
+
1717
1794
  function resolveGenerationInput(input) {
1718
1795
  if (!input.parseResultPath) {
1719
1796
  return { generationInput: input, parseResultPath: '' };
@@ -1744,6 +1821,7 @@ function resolveGenerationInput(input) {
1744
1821
  'moduleName',
1745
1822
  ]);
1746
1823
  assertParseResultPayloadConsistency(parseResult, generationInput, parsed.path);
1824
+ assertVisibilityMatrixConsistency(parseResult, generationInput, parsed.path);
1747
1825
 
1748
1826
  for (const key of ['frontendPath', 'moduleName', 'writeToDisk', 'overwrite', 'writeSupportFiles', 'mergeI18nZh']) {
1749
1827
  if (Object.prototype.hasOwnProperty.call(input, key)) {
@@ -2130,13 +2208,35 @@ function buildModel(safeArgs) {
2130
2208
  };
2131
2209
  }
2132
2210
 
2133
- function renderTemplate(templateText, replacements) {
2134
- let output = templateText;
2135
- for (const [key, value] of Object.entries(replacements)) {
2136
- output = output.split('{{' + key + '}}').join(String(value));
2137
- }
2138
- return output;
2139
- }
2211
+ function renderTemplate(templateText, replacements) {
2212
+ let output = templateText;
2213
+ for (const [key, value] of Object.entries(replacements)) {
2214
+ output = output.split('{{' + key + '}}').join(String(value));
2215
+ }
2216
+ return output;
2217
+ }
2218
+
2219
+ function findUnresolvedTemplatePlaceholders(content) {
2220
+ const matches = String(content || '').match(/\{\{[A-Z][A-Z0-9_]*\}\}/g) || [];
2221
+ return [...new Set(matches)];
2222
+ }
2223
+
2224
+ function assertNoUnresolvedTemplatePlaceholders(files) {
2225
+ const issues = files
2226
+ .map((file) => ({
2227
+ type: file.type,
2228
+ path: file.path,
2229
+ placeholders: findUnresolvedTemplatePlaceholders(file.content),
2230
+ }))
2231
+ .filter((item) => item.placeholders.length);
2232
+
2233
+ if (issues.length) {
2234
+ const detail = issues
2235
+ .map((item) => `${item.type}:${path.basename(item.path)} -> ${item.placeholders.join(', ')}`)
2236
+ .join('; ');
2237
+ throw new Error(`MCP template rendering left unresolved placeholders: ${detail}`);
2238
+ }
2239
+ }
2140
2240
 
2141
2241
  function renderFormField(field) {
2142
2242
  const label = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
@@ -2344,23 +2444,24 @@ function renderChildSection(childModel, childCount) {
2344
2444
  const deleteExpression = `deleteChild(obj, '${childModel.pk.attrName}')`;
2345
2445
 
2346
2446
  return [
2347
- ' <el-col :span="24" class="mb20">',
2348
- ` <!-- 子表区域:${sanitizeHtmlComment(childModel.comment || childModel.tableName)} -->`,
2349
- ' <div class="mb10" style="display:flex;justify-content:space-between;align-items:center;">',
2350
- ` <span style="font-weight: 600;">{{ childSectionTitle('${childModel.listName}') }}</span>`,
2351
- ' <div style="display:flex;align-items:center;gap:8px;">',
2447
+ ` <el-collapse-item :title="childSectionTitle('${childModel.listName}')" name="child-${childModel.listName}">`,
2448
+ ' <el-row :gutter="24">',
2449
+ ' <el-col :span="24" class="mb20">',
2450
+ ` <!-- 子表区域:${sanitizeHtmlComment(childModel.comment || childModel.tableName)} -->`,
2451
+ ' <div class="mb10" style="display:flex;justify-content:flex-end;align-items:center;gap:8px;">',
2352
2452
  ` <el-button icon="Plus" type="primary" :disabled="detail" @click="handleAddChild('${childModel.listName}')">{{ t('common.addBtn') }}</el-button>`,
2353
2453
  ` <el-button icon="Delete" type="danger" plain :disabled="detail || !getSelectedChildRows('${childModel.listName}').length" @click="handleDeleteSelectedChild('${childModel.listName}', '${childModel.pk.attrName}')">{{ t('common.delBtn') }}</el-button>`,
2354
2454
  ' </div>',
2355
- ' </div>',
2356
- ' <!-- 子表编辑表格:新增/删除按钮外置,表格内部隐藏新增、删除和序号控制列 -->',
2357
- ` <sc-form-table :ref="(el) => setChildTableRef('${childModel.listName}', el)" v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" hide-index hide-add hide-delete @delete="(obj) => ${deleteExpression}" :placeholder="t('common.noData')" @selection-change="(rows) => handleChildSelectionChange('${childModel.listName}', rows)">`,
2358
- ' <el-table-column type="selection" width="55" :selectable="() => !detail" />',
2455
+ ' <!-- 子表编辑表格:新增/删除按钮外置,表格内部隐藏新增、删除和序号控制列 -->',
2456
+ ` <sc-form-table :ref="(el) => setChildTableRef('${childModel.listName}', el)" v-model="form.${childModel.listName}" :addTemplate="childTemp${childModel.className}" hide-index hide-add hide-delete @delete="(obj) => ${deleteExpression}" :placeholder="t('common.noData')" @selection-change="(rows) => handleChildSelectionChange('${childModel.listName}', rows)">`,
2457
+ ' <el-table-column type="selection" width="55" :selectable="() => !detail" />',
2359
2458
  childModel.visibleFields.map((field) => renderChildTableColumn(field, childModel.listName)).join('\n'),
2360
- ' </sc-form-table>',
2361
- ' </el-col>',
2362
- ].join('\n');
2363
- }
2459
+ ' </sc-form-table>',
2460
+ ' </el-col>',
2461
+ ' </el-row>',
2462
+ ' </el-collapse-item>',
2463
+ ].join('\n');
2464
+ }
2364
2465
 
2365
2466
  function renderChildFormListDefaults(children) {
2366
2467
  if (!children.length) return '';
@@ -2485,7 +2586,7 @@ function renderFieldCommentV2(field, indent = ' ') {
2485
2586
  return indent + '<!-- ' + label + ' -->';
2486
2587
  }
2487
2588
 
2488
- function renderFormFieldV2(field) {
2589
+ function renderFormFieldV2(field) {
2489
2590
  const prop = field.attrName;
2490
2591
  const labelExpr = `getMasterFieldLabel('${prop}')`;
2491
2592
  const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
@@ -2584,10 +2685,58 @@ function renderFormFieldV2(field) {
2584
2685
  ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${maxlengthAttr}${disabledAttr} />`,
2585
2686
  ' </el-form-item>',
2586
2687
  ' </el-col>',
2587
- ].join('\n');
2588
- }
2589
-
2590
- function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2688
+ ].join('\n');
2689
+ }
2690
+
2691
+ const FILL_REPORT_FIELD_NAMES = new Set(['createBy', 'createTime', 'finishedTime']);
2692
+ const FILL_REPORT_FIELD_ORDER = ['createBy', 'createTime', 'finishedTime'];
2693
+
2694
+ function isFillReportField(field) {
2695
+ return FILL_REPORT_FIELD_NAMES.has(String(field?.attrName || field?.fieldName || ''));
2696
+ }
2697
+
2698
+ function renderBusinessFormCollapseItem(title, name, fields) {
2699
+ return [
2700
+ ` <el-collapse-item title="${title}" name="${name}">`,
2701
+ ' <el-row :gutter="24">',
2702
+ fields.map(renderFormFieldV2).join('\n'),
2703
+ ' </el-row>',
2704
+ ' </el-collapse-item>',
2705
+ ].join('\n');
2706
+ }
2707
+
2708
+ function splitBusinessFormFields(fields) {
2709
+ const fillReportFields = fields
2710
+ .filter(isFillReportField)
2711
+ .sort((left, right) => FILL_REPORT_FIELD_ORDER.indexOf(left.attrName) - FILL_REPORT_FIELD_ORDER.indexOf(right.attrName));
2712
+ if (!fillReportFields.length) {
2713
+ return { basicFields: fields, fillReportFields: [] };
2714
+ }
2715
+ return {
2716
+ basicFields: fields.filter((field) => !isFillReportField(field)),
2717
+ fillReportFields,
2718
+ };
2719
+ }
2720
+
2721
+ function renderBusinessFormCollapseSections(model) {
2722
+ const fields = model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields;
2723
+ const { basicFields, fillReportFields } = splitBusinessFormFields(fields);
2724
+ const sections = [renderBusinessFormCollapseItem('基本信息', '0', basicFields)];
2725
+ if (fillReportFields.length) {
2726
+ sections.push(renderBusinessFormCollapseItem('填报信息', '1', fillReportFields));
2727
+ }
2728
+ return sections.join('\n');
2729
+ }
2730
+
2731
+ function renderActiveCollapseNames(model) {
2732
+ const fields = model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields;
2733
+ const names = ['0'];
2734
+ if (fields.some(isFillReportField)) names.push('1');
2735
+ model.children.forEach((childModel) => names.push(`child-${childModel.listName}`));
2736
+ return `[${names.map((name) => `'${name}'`).join(', ')}]`;
2737
+ }
2738
+
2739
+ function renderMultiLevelOptionField(field, model, moduleModel, dictRegistryRefs, indent = ' ') {
2591
2740
  const parts = [
2592
2741
  `key: '${field.attrName}'`,
2593
2742
  `labelKey: '${buildMultiLevelFieldLabelKey(model, moduleModel, field)}'`,
@@ -4250,10 +4399,12 @@ function buildReplacements(model, sharedSupport) {
4250
4399
  BUSINESS_FORM_KEEP_ALIVE_COLUMN: model.pageType === 'business' ? ', keep_alive' : '',
4251
4400
  BUSINESS_FORM_KEEP_ALIVE_VALUE: model.pageType === 'business' ? ", '1'" : '',
4252
4401
  BILL_CODE: deriveBillCode(model),
4253
- GENERATED_AT: new Date().toISOString(),
4254
- FORM_FIELDS: (model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields).map(renderFormFieldV2).join('\n'),
4255
- TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
4256
- FORM_DEFAULTS: renderFormDefaults(model),
4402
+ GENERATED_AT: new Date().toISOString(),
4403
+ FORM_FIELDS: (model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields).map(renderFormFieldV2).join('\n'),
4404
+ FORM_COLLAPSE_SECTIONS: renderBusinessFormCollapseSections(model),
4405
+ ACTIVE_COLLAPSE_NAMES: renderActiveCollapseNames(model),
4406
+ TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
4407
+ FORM_DEFAULTS: renderFormDefaults(model),
4257
4408
  DICT_REGISTRY_IMPORT_BLOCK: model.dictTypes.length ? "import { DictRegistry } from '/@/enums/dict-registry';" : '',
4258
4409
  MASTER_OPTION_FIELDS: model.optionFields.map((field) => renderOptionFieldV2(field, buildFieldLabelKey(model, field), dictRegistryRefs)).join('\n'),
4259
4410
  CHILD_OPTION_GROUPS: model.children.map((childModel) => renderChildOptionGroupV2(model, childModel, dictRegistryRefs)).join('\n'),
@@ -4305,11 +4456,12 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
4305
4456
  },
4306
4457
  ];
4307
4458
 
4308
- if (menuSqlTemplate) {
4309
- files.push({ type: 'menuSql', path: path.join(menuRoot, `${model.functionName}_menu.sql`), content: renderTemplate(menuSqlTemplate, replacements) });
4310
- }
4311
- return files;
4312
- }
4459
+ if (menuSqlTemplate) {
4460
+ files.push({ type: 'menuSql', path: path.join(menuRoot, `${model.functionName}_menu.sql`), content: renderTemplate(menuSqlTemplate, replacements) });
4461
+ }
4462
+ assertNoUnresolvedTemplatePlaceholders(files);
4463
+ return files;
4464
+ }
4313
4465
 
4314
4466
  function ensureArguments(input) {
4315
4467
  if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.88",
3
+ "version": "0.1.90",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",