worsoft-frontend-codegen-local-mcp 0.1.89 → 0.1.91

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.
@@ -24,8 +24,9 @@
24
24
  <!-- 主子表表单:按 PRD 表单显隐和顺序渲染字段 -->
25
25
  <el-form ref="dataFormRef" :model="form" :rules="dataRules" :disabled="detail" v-loading="loading" label-width="120px">
26
26
  <el-collapse v-model="activeCollapseNames">
27
- {{FORM_COLLAPSE_SECTIONS}}
27
+ {{FORM_COLLAPSE_SECTIONS_BEFORE_CHILDREN}}
28
28
  {{CHILD_SECTIONS}}
29
+ {{FORM_COLLAPSE_SECTIONS_AFTER_CHILDREN}}
29
30
  </el-collapse>
30
31
  </el-form>
31
32
  </el-card>
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.89';
8
+ const SERVER_VERSION = '0.1.91';
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');
@@ -2208,13 +2208,35 @@ function buildModel(safeArgs) {
2208
2208
  };
2209
2209
  }
2210
2210
 
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
- }
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
+ }
2218
2240
 
2219
2241
  function renderFormField(field) {
2220
2242
  const label = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
@@ -2545,19 +2567,68 @@ function renderDisabledBoolV2(field) {
2545
2567
  return field.readonly ? 'true' : 'false';
2546
2568
  }
2547
2569
 
2548
- function renderMicromeFormatAttr(field) {
2549
- const length = Number.parseInt(String(field.length || ''), 10);
2550
- const scale = Number.parseInt(String(field.scale || ''), 10);
2551
- if (!length || Number.isNaN(length) || Number.isNaN(scale) || scale < 0) return '';
2552
- const integerLength = Math.max(length - scale, 1);
2553
- return ` format="${integerLength}-${scale}"`;
2554
- }
2555
-
2556
- function isAttachmentLikeField(field) {
2557
- const fieldName = String(field?.fieldName || field?.attrName || '').toLowerCase();
2558
- const comment = String(field?.comment || field?.description || '').toLowerCase();
2559
- return fieldName.includes('attachment') || comment.includes('\u9644\u4ef6') || comment.includes('\u4e0a\u4f20');
2560
- }
2570
+ function renderMicromeFormatAttr(field) {
2571
+ const length = Number.parseInt(String(field.length || ''), 10);
2572
+ const scale = Number.parseInt(String(field.scale || ''), 10);
2573
+ if (!length || Number.isNaN(length) || Number.isNaN(scale) || scale < 0) return '';
2574
+ const integerLength = Math.max(length - scale, 1);
2575
+ return ` format="${integerLength}-${scale}"`;
2576
+ }
2577
+
2578
+ const FORM_LAYOUT_DOMAIN_GROUPS = [
2579
+ { key: 'contract', title: '合同信息', words: ['contract', 'sign', 'supplier', 'owner', 'customer', 'tax', 'term'] },
2580
+ { key: 'pay', title: '支付信息', words: ['pay', 'payable', 'paid', 'payment', 'advance', 'offset'] },
2581
+ { key: 'receive', title: '收款信息', words: ['receivable', 'receive', 'income', 'collect'] },
2582
+ { key: 'settle', title: '结算信息', words: ['settle', 'finsettle', 'accountingperiod'] },
2583
+ { key: 'warranty', title: '保证金/质保金信息', words: ['deposit', 'warranty', 'guarantee', 'return'] },
2584
+ { key: 'clause', title: '条款信息', words: ['breach', 'argue', 'clause', 'substance', 'content'] },
2585
+ { key: 'change', title: '变更信息', words: ['change', 'adjust', 'before', 'after'] },
2586
+ { key: 'claim', title: '索赔/扣款/折扣信息', words: ['claim', 'reply', 'deduct', 'discount'] },
2587
+ { key: 'work', title: '人员工作信息', words: ['pos', 'dept', 'ogn', 'induction', 'job', 'labor', 'retired', 'restart'] },
2588
+ { key: 'borrow', title: '借用/领用/使用信息', words: ['borrow', 'lend', 'use', 'requisition', 'admin'] },
2589
+ { key: 'document', title: '公文信息', words: ['document', 'reference', 'urgency', 'level', 'drafter', 'tomain', 'tocopy', 'tosubmit', 'year', 'no', 'proofread'] },
2590
+ ];
2591
+
2592
+ function getFormLayoutFieldText(field) {
2593
+ return [
2594
+ field?.attrName,
2595
+ field?.fieldName,
2596
+ field?.comment,
2597
+ field?.label,
2598
+ field?.description,
2599
+ field?.formType,
2600
+ field?.sqlType,
2601
+ ].filter(Boolean).join(' ').toLowerCase();
2602
+ }
2603
+
2604
+ function includesAnyLayoutWord(text, words) {
2605
+ const normalized = String(text || '').toLowerCase();
2606
+ return words.some((word) => normalized.includes(String(word).toLowerCase()));
2607
+ }
2608
+
2609
+ function isAttachmentLikeField(field) {
2610
+ const text = getFormLayoutFieldText(field);
2611
+ return field?.formType === 'upload'
2612
+ || includesAnyLayoutWord(text, ['attachment', 'file', 'upload', 'photo', 'sign'])
2613
+ || /附件|上传|图片|签名/.test(text);
2614
+ }
2615
+
2616
+ function computeFormFieldSpan(field) {
2617
+ const prop = String(field?.attrName || field?.fieldName || '');
2618
+ const propLower = prop.toLowerCase();
2619
+ const text = getFormLayoutFieldText(field);
2620
+
2621
+ if (['upload', 'textarea', 'richtext'].includes(field?.formType)) return 24;
2622
+ if (isAttachmentLikeField(field)) return 24;
2623
+ if (includesAnyLayoutWord(text, ['remark', 'remarks', 'reason', 'opinion', 'content', 'address', 'explain', 'description', 'substance', 'clause', 'breach', 'argue'])) return 24;
2624
+ if (/备注|原因|意见|内容|地址|说明|条款|违约|争议/.test(text)) return 24;
2625
+ if (propLower.includes('keywords')) return 18;
2626
+ if (prop === 'projectName') return 12;
2627
+ if (includesAnyLayoutWord(propLower, ['fullname', 'orgname', 'deptname', 'ognname', 'companyname', 'suppliername', 'customername', 'unitname'])) return 12;
2628
+ if (/项目名称|完整名称|组织名称|部门名称|单位名称|供应商名称|客户名称/.test(text)) return 12;
2629
+
2630
+ return 6;
2631
+ }
2561
2632
 
2562
2633
  function renderFieldCommentV2(field, indent = ' ') {
2563
2634
  const label = stripDictAnnotation(field.comment || field.attrName).replace(/-->/g, '').trim() || field.attrName;
@@ -2565,27 +2636,28 @@ function renderFieldCommentV2(field, indent = ' ') {
2565
2636
  }
2566
2637
 
2567
2638
  function renderFormFieldV2(field) {
2568
- const prop = field.attrName;
2569
- const labelExpr = `getMasterFieldLabel('${prop}')`;
2570
- const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
2571
- const disabledAttr = renderDisabledAttrV2(field);
2572
- const disabledBool = renderDisabledBoolV2(field);
2573
-
2574
- if (field.formType === 'upload') {
2575
- return [
2576
- renderFieldCommentV2(field),
2577
- ` <el-col :span="24" class="mb20">`,
2578
- ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2579
- ` <UploadFile v-model="form.${prop}"${disabledAttr} />`,
2639
+ const prop = field.attrName;
2640
+ const labelExpr = `getMasterFieldLabel('${prop}')`;
2641
+ const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
2642
+ const disabledAttr = renderDisabledAttrV2(field);
2643
+ const disabledBool = renderDisabledBoolV2(field);
2644
+ const span = computeFormFieldSpan(field);
2645
+
2646
+ if (field.formType === 'upload') {
2647
+ return [
2648
+ renderFieldCommentV2(field),
2649
+ ` <el-col :span="${span}" class="mb20">`,
2650
+ ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2651
+ ` <UploadFile v-model="form.${prop}"${disabledAttr} />`,
2580
2652
  ' </el-form-item>',
2581
2653
  ' </el-col>',
2582
2654
  ].join('\n');
2583
2655
  }
2584
2656
 
2585
- if (field.formType === 'select') {
2586
- return [
2587
- renderFieldCommentV2(field),
2588
- ` <el-col :span="12" class="mb20">`,
2657
+ if (field.formType === 'select') {
2658
+ return [
2659
+ renderFieldCommentV2(field),
2660
+ ` <el-col :span="${span}" class="mb20">`,
2589
2661
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2590
2662
  ` <el-select v-model="form.${prop}" :placeholder="formSelectPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr}>`,
2591
2663
  ` <el-option v-for="item in getDictOptions(${dictExpr})" :key="item.value" :label="item.label" :value="item.value" />`,
@@ -2595,12 +2667,12 @@ function renderFormFieldV2(field) {
2595
2667
  ].join('\n');
2596
2668
  }
2597
2669
 
2598
- if (field.formType === 'number') {
2670
+ if (field.formType === 'number') {
2599
2671
  const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
2600
2672
  const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
2601
- return [
2602
- renderFieldCommentV2(field),
2603
- ` <el-col :span="12" class="mb20">`,
2673
+ return [
2674
+ renderFieldCommentV2(field),
2675
+ ` <el-col :span="${span}" class="mb20">`,
2604
2676
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2605
2677
  ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" style="width: 100%"${disabledAttr} />`,
2606
2678
  ' </el-form-item>',
@@ -2608,10 +2680,10 @@ function renderFormFieldV2(field) {
2608
2680
  ].join('\n');
2609
2681
  }
2610
2682
 
2611
- if (field.formType === 'microme-operator') {
2612
- return [
2613
- renderFieldCommentV2(field),
2614
- ` <el-col :span="12" class="mb20">`,
2683
+ if (field.formType === 'microme-operator') {
2684
+ return [
2685
+ renderFieldCommentV2(field),
2686
+ ` <el-col :span="${span}" class="mb20">`,
2615
2687
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2616
2688
  ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2617
2689
  ' </el-form-item>',
@@ -2619,23 +2691,12 @@ function renderFormFieldV2(field) {
2619
2691
  ].join('\n');
2620
2692
  }
2621
2693
 
2622
- if (field.formType === 'microme-operator') {
2623
- return [
2624
- renderFieldCommentV2(field),
2625
- ' <el-col :span="12" class="mb20">',
2626
- ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2627
- ` <MicromeOperator v-model="form.${prop}"${renderMicromeFormatAttr(field)} :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${disabledAttr} />`,
2628
- ' </el-form-item>',
2629
- ' </el-col>',
2630
- ].join('\n');
2631
- }
2632
-
2633
- if (field.formType === 'datetime' || field.formType === 'date') {
2694
+ if (field.formType === 'datetime' || field.formType === 'date') {
2634
2695
  const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
2635
2696
  const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
2636
- return [
2637
- renderFieldCommentV2(field),
2638
- ` <el-col :span="12" class="mb20">`,
2697
+ return [
2698
+ renderFieldCommentV2(field),
2699
+ ` <el-col :span="${span}" class="mb20">`,
2639
2700
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2640
2701
  ` <el-date-picker type="${pickerType}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
2641
2702
  ' </el-form-item>',
@@ -2643,11 +2704,11 @@ function renderFormFieldV2(field) {
2643
2704
  ].join('\n');
2644
2705
  }
2645
2706
 
2646
- if (field.formType === 'textarea') {
2707
+ if (field.formType === 'textarea') {
2647
2708
  const textareaAttrs = renderTextareaMaxlengthAttrsV2(field);
2648
- return [
2649
- renderFieldCommentV2(field),
2650
- ` <el-col :span="24" class="mb20">`,
2709
+ return [
2710
+ renderFieldCommentV2(field),
2711
+ ` <el-col :span="${span}" class="mb20">`,
2651
2712
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2652
2713
  ` <el-input type="textarea" v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${textareaAttrs}${disabledAttr} />`,
2653
2714
  ' </el-form-item>',
@@ -2656,9 +2717,9 @@ function renderFormFieldV2(field) {
2656
2717
  }
2657
2718
 
2658
2719
  const maxlengthAttr = renderInputMaxlengthAttr(field);
2659
- return [
2660
- renderFieldCommentV2(field),
2661
- ` <el-col :span="12" class="mb20">`,
2720
+ return [
2721
+ renderFieldCommentV2(field),
2722
+ ` <el-col :span="${span}" class="mb20">`,
2662
2723
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
2663
2724
  ` <el-input v-model="form.${prop}" :placeholder="formInputPlaceholder(${labelExpr}, ${disabledBool})"${maxlengthAttr}${disabledAttr} />`,
2664
2725
  ' </el-form-item>',
@@ -2666,8 +2727,8 @@ function renderFormFieldV2(field) {
2666
2727
  ].join('\n');
2667
2728
  }
2668
2729
 
2669
- const FILL_REPORT_FIELD_NAMES = new Set(['createBy', 'createTime', 'finishedTime']);
2670
- const FILL_REPORT_FIELD_ORDER = ['createBy', 'createTime', 'finishedTime'];
2730
+ const FILL_REPORT_FIELD_NAMES = new Set(['createBy', 'createUser', 'createTime', 'finishedTime']);
2731
+ const FILL_REPORT_FIELD_ORDER = ['createBy', 'createUser', 'createTime', 'finishedTime'];
2671
2732
 
2672
2733
  function isFillReportField(field) {
2673
2734
  return FILL_REPORT_FIELD_NAMES.has(String(field?.attrName || field?.fieldName || ''));
@@ -2683,34 +2744,102 @@ function renderBusinessFormCollapseItem(title, name, fields) {
2683
2744
  ].join('\n');
2684
2745
  }
2685
2746
 
2686
- function splitBusinessFormFields(fields) {
2687
- const fillReportFields = fields
2688
- .filter(isFillReportField)
2689
- .sort((left, right) => FILL_REPORT_FIELD_ORDER.indexOf(left.attrName) - FILL_REPORT_FIELD_ORDER.indexOf(right.attrName));
2690
- if (!fillReportFields.length) {
2691
- return { basicFields: fields, fillReportFields: [] };
2692
- }
2693
- return {
2694
- basicFields: fields.filter((field) => !isFillReportField(field)),
2695
- fillReportFields,
2696
- };
2747
+ function getFormFieldOriginalIndex(fields, field) {
2748
+ return fields.indexOf(field);
2749
+ }
2750
+
2751
+ function classifyBusinessFormDomain(field) {
2752
+ const text = getFormLayoutFieldText(field).replace(/[_\-\s]/g, '');
2753
+ const group = FORM_LAYOUT_DOMAIN_GROUPS.find((item) => item.words.some((word) => text.includes(String(word).toLowerCase())));
2754
+ return group?.key || 'basic';
2697
2755
  }
2698
2756
 
2699
- function renderBusinessFormCollapseSections(model) {
2757
+ function createBusinessFormLayoutSections(model) {
2700
2758
  const fields = model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields;
2701
- const { basicFields, fillReportFields } = splitBusinessFormFields(fields);
2702
- const sections = [renderBusinessFormCollapseItem('基本信息', '0', basicFields)];
2759
+ const basicFields = [];
2760
+ const attachmentFields = [];
2761
+ const fillReportFields = [];
2762
+ const domainBuckets = new Map();
2763
+
2764
+ fields.forEach((field) => {
2765
+ if (isFillReportField(field)) {
2766
+ fillReportFields.push(field);
2767
+ return;
2768
+ }
2769
+ if (isAttachmentLikeField(field)) {
2770
+ attachmentFields.push(field);
2771
+ return;
2772
+ }
2773
+ const domain = classifyBusinessFormDomain(field);
2774
+ if (domain === 'basic') {
2775
+ basicFields.push(field);
2776
+ return;
2777
+ }
2778
+ if (!domainBuckets.has(domain)) domainBuckets.set(domain, []);
2779
+ domainBuckets.get(domain).push(field);
2780
+ });
2781
+
2782
+ const beforeChildren = [{ title: '基本信息', fields: basicFields }];
2783
+
2784
+ FORM_LAYOUT_DOMAIN_GROUPS.forEach((group) => {
2785
+ const bucket = domainBuckets.get(group.key) || [];
2786
+ if (bucket.length >= 3) {
2787
+ beforeChildren.push({ title: group.title, fields: bucket });
2788
+ } else {
2789
+ beforeChildren[0].fields.push(...bucket);
2790
+ }
2791
+ });
2792
+
2793
+ const afterChildren = [];
2794
+ if (attachmentFields.length >= 2) {
2795
+ afterChildren.push({ title: '附件信息', fields: attachmentFields });
2796
+ } else {
2797
+ beforeChildren[0].fields.push(...attachmentFields);
2798
+ }
2799
+
2800
+ beforeChildren.concat(afterChildren).forEach((section) => {
2801
+ section.fields.sort((left, right) => getFormFieldOriginalIndex(fields, left) - getFormFieldOriginalIndex(fields, right));
2802
+ });
2803
+
2703
2804
  if (fillReportFields.length) {
2704
- sections.push(renderBusinessFormCollapseItem('填报信息', '1', fillReportFields));
2805
+ afterChildren.push({
2806
+ title: '填报信息',
2807
+ fields: fillReportFields.sort((left, right) => FILL_REPORT_FIELD_ORDER.indexOf(left.attrName) - FILL_REPORT_FIELD_ORDER.indexOf(right.attrName)),
2808
+ });
2705
2809
  }
2706
- return sections.join('\n');
2810
+
2811
+ let nameIndex = 0;
2812
+ beforeChildren.filter((section) => section.fields.length).forEach((section) => {
2813
+ section.name = String(nameIndex++);
2814
+ });
2815
+ afterChildren.filter((section) => section.fields.length).forEach((section) => {
2816
+ section.name = String(nameIndex++);
2817
+ });
2818
+
2819
+ return {
2820
+ beforeChildren: beforeChildren.filter((section) => section.fields.length),
2821
+ afterChildren: afterChildren.filter((section) => section.fields.length),
2822
+ };
2823
+ }
2824
+
2825
+ function renderBusinessFormCollapseSections(model, placement = 'all') {
2826
+ const layout = createBusinessFormLayoutSections(model);
2827
+ const sections =
2828
+ placement === 'beforeChildren'
2829
+ ? layout.beforeChildren
2830
+ : placement === 'afterChildren'
2831
+ ? layout.afterChildren
2832
+ : [...layout.beforeChildren, ...layout.afterChildren];
2833
+ return sections.map((section) => renderBusinessFormCollapseItem(section.title, section.name, section.fields)).join('\n');
2707
2834
  }
2708
2835
 
2709
2836
  function renderActiveCollapseNames(model) {
2710
- const fields = model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields;
2711
- const names = ['0'];
2712
- if (fields.some(isFillReportField)) names.push('1');
2713
- model.children.forEach((childModel) => names.push(`child-${childModel.listName}`));
2837
+ const layout = createBusinessFormLayoutSections(model);
2838
+ const names = [
2839
+ ...layout.beforeChildren.map((section) => section.name),
2840
+ ...model.children.map((childModel) => `child-${childModel.listName}`),
2841
+ ...layout.afterChildren.map((section) => section.name),
2842
+ ];
2714
2843
  return `[${names.map((name) => `'${name}'`).join(', ')}]`;
2715
2844
  }
2716
2845
 
@@ -4380,6 +4509,8 @@ function buildReplacements(model, sharedSupport) {
4380
4509
  GENERATED_AT: new Date().toISOString(),
4381
4510
  FORM_FIELDS: (model.pageType === 'ledger' ? model.visibleFields.map(asReadonlyField) : model.visibleFields).map(renderFormFieldV2).join('\n'),
4382
4511
  FORM_COLLAPSE_SECTIONS: renderBusinessFormCollapseSections(model),
4512
+ FORM_COLLAPSE_SECTIONS_BEFORE_CHILDREN: renderBusinessFormCollapseSections(model, 'beforeChildren'),
4513
+ FORM_COLLAPSE_SECTIONS_AFTER_CHILDREN: renderBusinessFormCollapseSections(model, 'afterChildren'),
4383
4514
  ACTIVE_COLLAPSE_NAMES: renderActiveCollapseNames(model),
4384
4515
  TABLE_COLUMNS: model.gridFields.map((field) => renderTableColumn(field, dictRegistryRefs)).join('\n'),
4385
4516
  FORM_DEFAULTS: renderFormDefaults(model),
@@ -4434,11 +4565,12 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
4434
4565
  },
4435
4566
  ];
4436
4567
 
4437
- if (menuSqlTemplate) {
4438
- files.push({ type: 'menuSql', path: path.join(menuRoot, `${model.functionName}_menu.sql`), content: renderTemplate(menuSqlTemplate, replacements) });
4439
- }
4440
- return files;
4441
- }
4568
+ if (menuSqlTemplate) {
4569
+ files.push({ type: 'menuSql', path: path.join(menuRoot, `${model.functionName}_menu.sql`), content: renderTemplate(menuSqlTemplate, replacements) });
4570
+ }
4571
+ assertNoUnresolvedTemplatePlaceholders(files);
4572
+ return files;
4573
+ }
4442
4574
 
4443
4575
  function ensureArguments(input) {
4444
4576
  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.89",
3
+ "version": "0.1.91",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",