worsoft-frontend-codegen-local-mcp 0.1.19 → 0.1.21
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,12 +5,11 @@ 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.21';
|
|
9
9
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
10
10
|
const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
|
|
11
11
|
const TEMPLATE_LIBRARY_ROOT = path.resolve(__dirname, '..', 'template');
|
|
12
12
|
const STYLE_CATALOG_PATH = path.join(__dirname, 'assets', 'style-catalog.json');
|
|
13
|
-
const DEFAULT_DESIGN_FILE = path.resolve(__dirname, '..', 'sql', 'SQL 闁荤姳鐒﹀畷姗€顢橀崫銉﹀珰閻庢稒蓱椤?md');
|
|
14
13
|
const STYLE_CATALOG = loadStyleCatalog();
|
|
15
14
|
const DEFAULT_DICT_REGISTRY_KEYS = {
|
|
16
15
|
add_start_stop: 'COMMON_STATUS',
|
|
@@ -124,41 +123,87 @@ export function createCrudSchema(
|
|
|
124
123
|
}
|
|
125
124
|
`;
|
|
126
125
|
|
|
127
|
-
const TOOL_SCHEMA = {
|
|
128
|
-
type: 'object',
|
|
129
|
-
properties: {
|
|
130
|
-
|
|
131
|
-
tableName: { type: 'string', description: 'Target main table name from the
|
|
132
|
-
|
|
126
|
+
const TOOL_SCHEMA = {
|
|
127
|
+
type: 'object',
|
|
128
|
+
properties: {
|
|
129
|
+
featureTitle: { type: 'string', description: 'Feature title from the API document or requirement document.' },
|
|
130
|
+
tableName: { type: 'string', description: 'Target main table name from the structured feature metadata.' },
|
|
131
|
+
tableComment: { type: 'string', description: 'Main table comment or feature label for menu/title generation.' },
|
|
132
|
+
apiPath: { type: 'string', description: 'Backend API base path from the API document, for example iwmEmpOutsourcePerson.' },
|
|
133
|
+
style: { type: 'string', enum: Object.keys(STYLE_CATALOG), description: 'Style id from assets/style-catalog.json.' },
|
|
134
|
+
fields: {
|
|
135
|
+
type: 'array',
|
|
136
|
+
description: 'Structured main-table field metadata parsed from the API document and requirement document.',
|
|
137
|
+
items: {
|
|
138
|
+
type: 'object',
|
|
139
|
+
properties: {
|
|
140
|
+
fieldName: { type: 'string', description: 'Backend field name.' },
|
|
141
|
+
label: { type: 'string', description: 'Display label.' },
|
|
142
|
+
type: { type: 'string', description: 'Normalized or source field type, for example String, Long, Integer, LocalDate, LocalDateTime, DECIMAL, VARCHAR.' },
|
|
143
|
+
length: { type: ['string', 'number'], description: 'Field length or precision string, for example 30 or 10,2.' },
|
|
144
|
+
scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
|
|
145
|
+
required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
|
|
146
|
+
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
147
|
+
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
148
|
+
dictType: { type: 'string', description: 'Dictionary type code from the API document.' },
|
|
149
|
+
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
150
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
151
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
152
|
+
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
|
|
153
|
+
},
|
|
154
|
+
required: ['fieldName', 'label', 'type'],
|
|
155
|
+
additionalProperties: false,
|
|
156
|
+
},
|
|
157
|
+
},
|
|
133
158
|
children: {
|
|
134
159
|
type: 'array',
|
|
135
|
-
description: 'Optional direct child-table
|
|
160
|
+
description: 'Optional direct child-table structures for master_child_jump. When provided, MCP renders all listed direct child tables on the same main form.',
|
|
136
161
|
items: {
|
|
137
162
|
type: 'object',
|
|
138
163
|
properties: {
|
|
139
164
|
childTableName: { type: 'string', description: 'Child table name.' },
|
|
165
|
+
childTableComment: { type: 'string', description: 'Child table comment or display label.' },
|
|
140
166
|
mainField: { type: 'string', description: 'Main table relation field.' },
|
|
141
167
|
childField: { type: 'string', description: 'Child table relation field.' },
|
|
142
|
-
payloadField: { type: 'string', description: '
|
|
168
|
+
payloadField: { type: 'string', description: 'Backend payload list field name from the API document, for example certificateList.' },
|
|
143
169
|
relationType: { type: 'string', description: 'Optional relation type label, for example 1:N.' },
|
|
170
|
+
fields: {
|
|
171
|
+
type: 'array',
|
|
172
|
+
description: 'Structured child-table field metadata.',
|
|
173
|
+
items: {
|
|
174
|
+
type: 'object',
|
|
175
|
+
properties: {
|
|
176
|
+
fieldName: { type: 'string', description: 'Backend field name.' },
|
|
177
|
+
label: { type: 'string', description: 'Display label.' },
|
|
178
|
+
type: { type: 'string', description: 'Normalized or source field type.' },
|
|
179
|
+
length: { type: ['string', 'number'], description: 'Field length or precision string.' },
|
|
180
|
+
scale: { type: ['string', 'number'], description: 'Optional decimal scale.' },
|
|
181
|
+
required: { type: ['boolean', 'string'], description: 'Whether the field is required.' },
|
|
182
|
+
readonly: { type: ['boolean', 'string'], description: 'Whether the field is readonly on the page.' },
|
|
183
|
+
show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
|
|
184
|
+
dictType: { type: 'string', description: 'Dictionary type code from the API document.' },
|
|
185
|
+
defaultValue: { type: ['string', 'number', 'boolean'], description: 'Optional default value.' },
|
|
186
|
+
description: { type: 'string', description: 'Field description or notes.' },
|
|
187
|
+
sourceKind: { type: 'string', enum: ['entity', 'display', 'virtual', 'common'], description: 'Field source kind.' },
|
|
188
|
+
primary: { type: ['boolean', 'string'], description: 'Whether the field is the primary key.' },
|
|
189
|
+
},
|
|
190
|
+
required: ['fieldName', 'label', 'type'],
|
|
191
|
+
additionalProperties: false,
|
|
192
|
+
},
|
|
193
|
+
},
|
|
144
194
|
},
|
|
145
|
-
required: ['childTableName', 'mainField', 'childField'],
|
|
195
|
+
required: ['childTableName', 'mainField', 'childField', 'payloadField', 'fields'],
|
|
146
196
|
additionalProperties: false,
|
|
147
197
|
},
|
|
148
198
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
|
|
158
|
-
},
|
|
159
|
-
required: ['tableName', 'style', 'frontendPath'],
|
|
160
|
-
additionalProperties: false,
|
|
161
|
-
};
|
|
199
|
+
frontendPath: { type: 'string', description: 'Absolute frontend output root path.' },
|
|
200
|
+
moduleName: { type: 'string', description: 'Relative frontend module path, for example admin/test.' },
|
|
201
|
+
writeToDisk: { type: 'boolean', default: true, description: 'Whether to write generated files.' },
|
|
202
|
+
overwrite: { type: 'boolean', default: true, description: 'Whether to overwrite existing files. If false, existing files are skipped.' },
|
|
203
|
+
},
|
|
204
|
+
required: ['tableName', 'style', 'frontendPath', 'fields'],
|
|
205
|
+
additionalProperties: false,
|
|
206
|
+
};
|
|
162
207
|
|
|
163
208
|
function loadStyleCatalog() {
|
|
164
209
|
return JSON.parse(fs.readFileSync(STYLE_CATALOG_PATH, 'utf8'));
|
|
@@ -613,244 +658,124 @@ function splitLength(value) {
|
|
|
613
658
|
return { length: parts[0] || '', scale: parts[1] || '' };
|
|
614
659
|
}
|
|
615
660
|
|
|
616
|
-
function normalizeDefaultValue(value) {
|
|
617
|
-
const normalized = String(value || '').trim();
|
|
618
|
-
if (!normalized || normalized === '-' || normalized === '/') {
|
|
619
|
-
return '';
|
|
620
|
-
}
|
|
621
|
-
return normalized;
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
function parseRequiredFlag(value) {
|
|
625
|
-
const normalized = String(value || '').trim().toLowerCase();
|
|
626
|
-
return ['y', 'yes', '1', 'true', 'required'].includes(normalized);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
function parseMarkdownRow(line) {
|
|
630
|
-
const trimmed = line.trim();
|
|
631
|
-
if (!trimmed.startsWith('|')) return [];
|
|
632
|
-
return trimmed
|
|
633
|
-
.slice(1, trimmed.endsWith('|') ? -1 : undefined)
|
|
634
|
-
.split('|')
|
|
635
|
-
.map((cell) => cell.trim());
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function isMarkdownSeparatorRow(cells) {
|
|
639
|
-
return cells.length > 0 && cells.every((cell) => /^:?-{3,}:?$/.test(cell.replace(/\s+/g, '')));
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
function findHeaderIndex(headers, patterns) {
|
|
643
|
-
return headers.findIndex((header) => patterns.some((pattern) => pattern.test(header)));
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function findPrimaryKeyFromText(text, fields) {
|
|
647
|
-
const quotedMatch = text.match(/PRIMARY\s+KEY\s*\(\s*`?([a-zA-Z0-9_]+)`?\s*\)/i);
|
|
648
|
-
if (quotedMatch) {
|
|
649
|
-
return quotedMatch[1];
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const commentPk = fields.find((field) => /主键|primary\s+key|\bpk\b/i.test(field.comment));
|
|
653
|
-
if (commentPk) {
|
|
654
|
-
return commentPk.fieldName;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
const idField = fields.find((field) => field.fieldName === 'id');
|
|
658
|
-
if (idField) {
|
|
659
|
-
return idField.fieldName;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
return fields[0] ? fields[0].fieldName : null;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
function parseMarkdownTableSection(tableName, tableComment, sectionLines) {
|
|
666
|
-
const firstTableLineIndex = sectionLines.findIndex((line) => line.trim().startsWith('|'));
|
|
667
|
-
if (firstTableLineIndex < 0) {
|
|
668
|
-
throw new Error('Could not find markdown field table for ' + tableName);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const tableLines = [];
|
|
672
|
-
for (let index = firstTableLineIndex; index < sectionLines.length; index += 1) {
|
|
673
|
-
const line = sectionLines[index].trim();
|
|
674
|
-
if (!line.startsWith('|')) break;
|
|
675
|
-
tableLines.push(line);
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
const rows = tableLines.map(parseMarkdownRow).filter((cells) => cells.length > 0);
|
|
679
|
-
if (rows.length < 3) {
|
|
680
|
-
throw new Error('Markdown field table is incomplete for ' + tableName);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
const headers = rows[0];
|
|
684
|
-
const dataRows = rows.slice(1).filter((cells) => !isMarkdownSeparatorRow(cells));
|
|
685
|
-
const fieldNameIndex = findHeaderIndex(headers, [/field/i, /\u5B57\u6BB5/i, /\u5B57\u6BB5\u540D/i, /\u5217\u540D/i]);
|
|
686
|
-
const sqlTypeIndex = findHeaderIndex(headers, [/type/i, /\u7C7B\u578B/i, /\u5B57\u6BB5\u7C7B\u578B/i, /\u6570\u636E\u7C7B\u578B/i]);
|
|
687
|
-
const lengthIndex = findHeaderIndex(headers, [/length/i, /\u957F\u5EA6/i, /\u5B57\u6BB5\u957F\u5EA6/i]);
|
|
688
|
-
const requiredIndex = findHeaderIndex(headers, [/required/i, /\u5FC5\u586B/i, /\u662F\u5426\u5FC5\u586B/i, /\u4E0D\u80FD\u4E3A\u7A7A/i]);
|
|
689
|
-
const defaultIndex = findHeaderIndex(headers, [/default/i, /\u9ED8\u8BA4/i, /\u9ED8\u8BA4\u503C/i]);
|
|
690
|
-
const commentIndex = findHeaderIndex(headers, [/comment/i, /\u5907\u6CE8/i, /\u8BF4\u660E/i, /\u5B57\u6BB5\u8BF4\u660E/i]);
|
|
691
|
-
|
|
692
|
-
const dictTypeIndex = findHeaderIndex(headers, [/dict/i, /dict_?type/i, /\u5B57\u5178/i, /\u5B57\u5178\u7C7B\u578B/i]);
|
|
693
|
-
|
|
694
|
-
if (fieldNameIndex < 0 || sqlTypeIndex < 0) {
|
|
695
|
-
throw new Error('Markdown field table headers are missing required columns for ' + tableName);
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
const fields = [];
|
|
699
|
-
for (const row of dataRows) {
|
|
700
|
-
const fieldName = String(row[fieldNameIndex] || '').trim();
|
|
701
|
-
if (!fieldName || fieldName === '-') continue;
|
|
702
|
-
|
|
703
|
-
const sqlType = String(row[sqlTypeIndex] || '').trim().toUpperCase();
|
|
704
|
-
if (!sqlType) continue;
|
|
705
|
-
|
|
706
|
-
const lengthRaw = lengthIndex >= 0 ? row[lengthIndex] : '';
|
|
707
|
-
const comment = String(commentIndex >= 0 ? row[commentIndex] : '').trim() || fieldName;
|
|
708
|
-
const explicitDictType = dictTypeIndex >= 0 ? row[dictTypeIndex] : '';
|
|
709
|
-
const { length, scale } = splitLength(lengthRaw);
|
|
710
|
-
|
|
711
|
-
fields.push({
|
|
712
|
-
fieldName,
|
|
713
|
-
attrName: toCamelCase(fieldName),
|
|
714
|
-
sqlType,
|
|
715
|
-
length,
|
|
716
|
-
scale,
|
|
717
|
-
comment,
|
|
718
|
-
dictType: resolveDictType(comment, explicitDictType),
|
|
719
|
-
notNull: requiredIndex >= 0 ? parseRequiredFlag(row[requiredIndex]) : false,
|
|
720
|
-
defaultValue: defaultIndex >= 0 ? normalizeDefaultValue(row[defaultIndex]) : '',
|
|
721
|
-
});
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
if (!fields.length) {
|
|
725
|
-
throw new Error('No fields were parsed from markdown table ' + tableName);
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
const sectionText = sectionLines.join('\n');
|
|
729
|
-
const pkName = findPrimaryKeyFromText(sectionText, fields);
|
|
730
|
-
const pkField = fields.find((field) => field.fieldName === pkName) || fields[0];
|
|
731
|
-
return { pkField, fields, tableComment };
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
function parseMarkdownDesignTables(markdownText) {
|
|
735
|
-
const lines = stripBom(markdownText).replace(/\r\n?/g, '\n').split('\n');
|
|
736
|
-
const tables = new Map();
|
|
737
|
-
|
|
738
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
739
|
-
const heading = lines[index].trim();
|
|
740
|
-
const headingMatch = heading.match(/^###\s+\S+\s+([a-zA-Z0-9_]+)\s*[\(\uFF08]([^)\n\uFF09]+)[\)\uFF09]/);
|
|
741
|
-
if (!headingMatch) continue;
|
|
742
|
-
|
|
743
|
-
const tableName = headingMatch[1];
|
|
744
|
-
const tableComment = headingMatch[2].trim();
|
|
745
|
-
const sectionLines = [];
|
|
746
|
-
let nextIndex = index + 1;
|
|
747
|
-
while (nextIndex < lines.length && !/^(##|###)\s+/.test(lines[nextIndex])) {
|
|
748
|
-
sectionLines.push(lines[nextIndex]);
|
|
749
|
-
nextIndex += 1;
|
|
750
|
-
}
|
|
751
|
-
|
|
752
|
-
tables.set(tableName, parseMarkdownTableSection(tableName, tableComment, sectionLines));
|
|
753
|
-
index = nextIndex - 1;
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
return tables;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
function extractIdentifiersFromLine(line) {
|
|
760
|
-
return [...String(line || '').matchAll(/`?([a-zA-Z][a-zA-Z0-9_]*)`?/g)].map((match) => match[1]);
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
function stripMarkdownSyntax(text) {
|
|
764
|
-
return String(text || '')
|
|
765
|
-
.replace(/\*\*/g, '')
|
|
766
|
-
.replace(/`/g, '')
|
|
767
|
-
.replace(/\r\n?/g, '\n');
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function parseMarkdownRelations(markdownText) {
|
|
771
|
-
void markdownText;
|
|
772
|
-
return [];
|
|
661
|
+
function normalizeDefaultValue(value) {
|
|
662
|
+
const normalized = String(value || '').trim();
|
|
663
|
+
if (!normalized || normalized === '-' || normalized === '/') {
|
|
664
|
+
return '';
|
|
665
|
+
}
|
|
666
|
+
return normalized;
|
|
773
667
|
}
|
|
774
668
|
|
|
775
|
-
function
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
if (!titleLine) {
|
|
779
|
-
return '';
|
|
669
|
+
function parseBooleanLike(value, defaultValue = false) {
|
|
670
|
+
if (value === undefined || value === null || value === '') {
|
|
671
|
+
return defaultValue;
|
|
780
672
|
}
|
|
781
|
-
|
|
782
|
-
|
|
673
|
+
if (typeof value === 'boolean') {
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
const normalized = String(value).trim().toLowerCase();
|
|
677
|
+
if (['true', '1', 'yes', 'y', 'required', '是', '显示'].includes(normalized)) {
|
|
678
|
+
return true;
|
|
679
|
+
}
|
|
680
|
+
if (['false', '0', 'no', 'n', '否', '不显示'].includes(normalized)) {
|
|
681
|
+
return false;
|
|
682
|
+
}
|
|
683
|
+
return defaultValue;
|
|
783
684
|
}
|
|
784
685
|
|
|
785
|
-
function
|
|
686
|
+
function normalizeStructuredSourceKind(value) {
|
|
687
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
688
|
+
if (['entity', 'display', 'virtual', 'common'].includes(normalized)) {
|
|
689
|
+
return normalized;
|
|
690
|
+
}
|
|
691
|
+
return 'entity';
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function normalizeApiPath(value) {
|
|
695
|
+
return String(value || '')
|
|
696
|
+
.trim()
|
|
697
|
+
.replace(/^\/+/, '')
|
|
698
|
+
.replace(/\/+$/, '');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function normalizeStructuredSqlType(value) {
|
|
702
|
+
const normalized = String(value || '').trim();
|
|
703
|
+
const upper = normalized.replace(/\s+/g, '').toUpperCase();
|
|
704
|
+
|
|
705
|
+
if (!upper) return 'VARCHAR';
|
|
706
|
+
if (['STRING', 'TEXT', 'VARCHAR', 'CHAR'].includes(upper) || ['文本', '字符', '字符串'].includes(normalized)) return 'VARCHAR';
|
|
707
|
+
if (['LONG', 'BIGINT', 'LONGINTEGER'].includes(upper) || ['长整数', '长整型'].includes(normalized)) return 'BIGINT';
|
|
708
|
+
if (['INT', 'INTEGER', 'SHORT', 'SMALLINT'].includes(upper) || ['整数', '整型'].includes(normalized)) return 'INT';
|
|
709
|
+
if (['DECIMAL', 'NUMERIC', 'BIGDECIMAL', 'DOUBLE', 'FLOAT'].includes(upper) || ['小数', '金额'].includes(normalized)) return 'DECIMAL';
|
|
710
|
+
if (['LOCALDATE', 'DATE'].includes(upper) || normalized === '日期') return 'DATE';
|
|
711
|
+
if (['LOCALDATETIME', 'DATETIME', 'TIMESTAMP'].includes(upper) || ['日期时间', '时间'].includes(normalized)) return 'DATETIME';
|
|
712
|
+
if (['TEXTAREA', 'LONGTEXT', 'TEXT'].includes(upper) || ['长文本', '多行文本'].includes(normalized)) return 'TEXT';
|
|
713
|
+
return upper;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function normalizeStructuredLengthAndScale(lengthValue, scaleValue) {
|
|
717
|
+
const parsed = splitLength(lengthValue);
|
|
718
|
+
const normalizedScale = scaleValue === undefined || scaleValue === null || scaleValue === '' ? parsed.scale : String(scaleValue).trim();
|
|
786
719
|
return {
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
relations: [],
|
|
720
|
+
length: parsed.length,
|
|
721
|
+
scale: normalizedScale || '',
|
|
790
722
|
};
|
|
791
723
|
}
|
|
724
|
+
|
|
725
|
+
function normalizeStructuredField(inputField, index, contextLabel) {
|
|
726
|
+
if (!inputField || typeof inputField !== 'object') {
|
|
727
|
+
throw new Error(contextLabel + '[' + index + '] must be an object');
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const fieldName = String(inputField.fieldName || '').trim();
|
|
731
|
+
const label = String(inputField.label || inputField.description || fieldName).trim();
|
|
732
|
+
const type = normalizeStructuredSqlType(inputField.type);
|
|
733
|
+
|
|
734
|
+
if (!fieldName) {
|
|
735
|
+
throw new Error(contextLabel + '[' + index + '] is missing required field: fieldName');
|
|
736
|
+
}
|
|
737
|
+
if (!label) {
|
|
738
|
+
throw new Error(contextLabel + '[' + index + '] is missing required field: label');
|
|
739
|
+
}
|
|
740
|
+
if (!String(inputField.type || '').trim()) {
|
|
741
|
+
throw new Error(contextLabel + '[' + index + '] is missing required field: type');
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
const { length, scale } = normalizeStructuredLengthAndScale(inputField.length, inputField.scale);
|
|
745
|
+
|
|
746
|
+
return {
|
|
747
|
+
fieldName,
|
|
748
|
+
attrName: toCamelCase(fieldName),
|
|
749
|
+
sqlType: type,
|
|
750
|
+
length,
|
|
751
|
+
scale,
|
|
752
|
+
comment: label,
|
|
753
|
+
label,
|
|
754
|
+
description: String(inputField.description || '').trim(),
|
|
755
|
+
dictType: normalizeDictType(inputField.dictType),
|
|
756
|
+
notNull: parseBooleanLike(inputField.required, false),
|
|
757
|
+
defaultValue: normalizeDefaultValue(inputField.defaultValue),
|
|
758
|
+
readonly: parseBooleanLike(inputField.readonly, false),
|
|
759
|
+
show: parseBooleanLike(inputField.show, true),
|
|
760
|
+
sourceKind: normalizeStructuredSourceKind(inputField.sourceKind),
|
|
761
|
+
primary: parseBooleanLike(inputField.primary, fieldName === 'id'),
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function normalizeStructuredFieldArray(inputFields, contextLabel) {
|
|
766
|
+
if (!Array.isArray(inputFields) || !inputFields.length) {
|
|
767
|
+
throw new Error(contextLabel + ' must be a non-empty array');
|
|
768
|
+
}
|
|
769
|
+
return inputFields.map((field, index) => normalizeStructuredField(field, index, contextLabel));
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function findPrimaryKeyFromStructuredFields(fields) {
|
|
773
|
+
return fields.find((field) => field.primary) || fields.find((field) => field.fieldName === 'id') || fields[0];
|
|
774
|
+
}
|
|
792
775
|
|
|
793
|
-
function
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
throw new Error('Could not find table definition for ' + tableName + ' in Markdown design file');
|
|
797
|
-
}
|
|
798
|
-
return parsed;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
function buildQuestionMarkSegmentRegex(segment) {
|
|
802
|
-
return new RegExp('^' + escapeForRegex(segment).replace(/\\\?/g, '.') + '$', 'i');
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
function tryResolveGarbledPath(resolvedPath) {
|
|
806
|
-
if (!resolvedPath.includes('?')) {
|
|
807
|
-
return null;
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
const parsed = path.parse(resolvedPath);
|
|
811
|
-
const segments = resolvedPath.slice(parsed.root.length).split(path.sep).filter(Boolean);
|
|
812
|
-
let currentPath = parsed.root;
|
|
813
|
-
|
|
814
|
-
for (const segment of segments) {
|
|
815
|
-
const exactPath = path.join(currentPath, segment);
|
|
816
|
-
if (fs.existsSync(exactPath)) {
|
|
817
|
-
currentPath = exactPath;
|
|
818
|
-
continue;
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
if (!segment.includes('?') || !fs.existsSync(currentPath) || !fs.statSync(currentPath).isDirectory()) {
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
const matcher = buildQuestionMarkSegmentRegex(segment);
|
|
826
|
-
const matches = fs.readdirSync(currentPath).filter((name) => matcher.test(name));
|
|
827
|
-
if (matches.length !== 1) {
|
|
828
|
-
throw new Error(
|
|
829
|
-
'Source file path could not be resolved uniquely from garbled input: ' +
|
|
830
|
-
resolvedPath +
|
|
831
|
-
'. Matched entries: ' +
|
|
832
|
-
(matches.length ? matches.join(', ') : 'none')
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
currentPath = path.join(currentPath, matches[0]);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
return fs.existsSync(currentPath) ? currentPath : null;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function resolveSourcePath(inputPath) {
|
|
843
|
-
const resolvedPath = path.resolve(inputPath);
|
|
844
|
-
if (fs.existsSync(resolvedPath)) {
|
|
845
|
-
return resolvedPath;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
const recoveredPath = tryResolveGarbledPath(resolvedPath);
|
|
849
|
-
if (recoveredPath) {
|
|
850
|
-
return recoveredPath;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
throw new Error('Source file does not exist: ' + resolvedPath);
|
|
776
|
+
function parseRequiredFlag(value) {
|
|
777
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
778
|
+
return ['y', 'yes', '1', 'true', 'required'].includes(normalized);
|
|
854
779
|
}
|
|
855
780
|
|
|
856
781
|
function formatRelationCandidate(relation) {
|
|
@@ -866,107 +791,31 @@ function formatRelationCandidate(relation) {
|
|
|
866
791
|
return candidate;
|
|
867
792
|
}
|
|
868
793
|
|
|
869
|
-
function
|
|
870
|
-
|
|
871
|
-
.
|
|
872
|
-
.
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
overwrite: safeArgs.overwrite,
|
|
883
|
-
};
|
|
884
|
-
|
|
885
|
-
if (sourceFile) {
|
|
886
|
-
retryArguments.designFile = sourceFile;
|
|
887
|
-
}
|
|
888
|
-
|
|
794
|
+
function buildRetryArguments(safeArgs) {
|
|
795
|
+
const retryArguments = {
|
|
796
|
+
...(safeArgs.featureTitle ? { featureTitle: safeArgs.featureTitle } : {}),
|
|
797
|
+
tableName: safeArgs.tableName,
|
|
798
|
+
...(safeArgs.tableComment ? { tableComment: safeArgs.tableComment } : {}),
|
|
799
|
+
style: safeArgs.style,
|
|
800
|
+
frontendPath: safeArgs.frontendPath,
|
|
801
|
+
moduleName: safeArgs.moduleName,
|
|
802
|
+
writeToDisk: safeArgs.writeToDisk,
|
|
803
|
+
overwrite: safeArgs.overwrite,
|
|
804
|
+
fields: safeArgs.fields,
|
|
805
|
+
};
|
|
806
|
+
|
|
889
807
|
if (safeArgs.children && safeArgs.children.length) {
|
|
890
|
-
retryArguments.children = safeArgs.children
|
|
891
|
-
childTableName: relation.childTableName,
|
|
892
|
-
mainField: relation.mainField,
|
|
893
|
-
childField: relation.childField,
|
|
894
|
-
...(relation.payloadField ? { payloadField: relation.payloadField } : {}),
|
|
895
|
-
relationType: relation.relationType || '',
|
|
896
|
-
}));
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
if (childTableName) {
|
|
900
|
-
retryArguments.childTableName = childTableName;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
if (safeArgs.mainField) {
|
|
904
|
-
retryArguments.mainField = safeArgs.mainField;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
if (safeArgs.childField) {
|
|
908
|
-
retryArguments.childField = safeArgs.childField;
|
|
808
|
+
retryArguments.children = safeArgs.children;
|
|
909
809
|
}
|
|
910
810
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
if (safeArgs.relationType) {
|
|
916
|
-
retryArguments.relationType = safeArgs.relationType;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
return retryArguments;
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
function buildRelationCorrectionEntry(safeArgs, sourceFile, candidates, selectedChildTableName) {
|
|
923
|
-
return {
|
|
924
|
-
field: 'childTableName',
|
|
925
|
-
title: '请选择子表',
|
|
926
|
-
tableName: safeArgs.tableName,
|
|
927
|
-
style: safeArgs.style,
|
|
928
|
-
designFile: sourceFile,
|
|
929
|
-
description: candidates.length
|
|
930
|
-
? 'If the current child relation is not the one you want, pass childTableName to choose a candidate from the design file.'
|
|
931
|
-
: 'No child relation was found. Check the child relation section in the design file.',
|
|
932
|
-
currentValue: selectedChildTableName || '',
|
|
933
|
-
options: candidates.map((item) => item.childTableName),
|
|
934
|
-
candidates,
|
|
935
|
-
example: candidates.length ? buildRetryArguments(safeArgs, sourceFile, candidates[0].childTableName) : buildRetryArguments(safeArgs, sourceFile, selectedChildTableName),
|
|
936
|
-
retryArguments: candidates.map((item) => buildRetryArguments(safeArgs, sourceFile, item.childTableName)),
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
function createRelationResolutionError(message, safeArgs, sourceFile, candidates, selectedChildTableName, status) {
|
|
941
|
-
const error = new Error(message);
|
|
942
|
-
error.details = {
|
|
943
|
-
type: 'relation_resolution',
|
|
944
|
-
status,
|
|
945
|
-
tableName: safeArgs.tableName,
|
|
946
|
-
designFile: sourceFile,
|
|
947
|
-
message,
|
|
948
|
-
candidates,
|
|
949
|
-
correctionEntry: buildRelationCorrectionEntry(safeArgs, sourceFile, candidates, selectedChildTableName),
|
|
950
|
-
};
|
|
951
|
-
return error;
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
function loadSourceDocument(safeArgs) {
|
|
955
|
-
const inputPath = safeArgs.designFile || DEFAULT_DESIGN_FILE;
|
|
956
|
-
const sourcePath = resolveSourcePath(inputPath);
|
|
957
|
-
const text = readUtf8File(sourcePath);
|
|
958
|
-
return {
|
|
959
|
-
path: sourcePath,
|
|
960
|
-
text,
|
|
961
|
-
designDoc: parseMarkdownDesignFile(text),
|
|
962
|
-
};
|
|
963
|
-
}
|
|
811
|
+
return retryArguments;
|
|
812
|
+
}
|
|
964
813
|
|
|
965
814
|
function normalizeChildrenInput(inputChildren) {
|
|
966
|
-
if (inputChildren === undefined || inputChildren === null) {
|
|
967
|
-
return [];
|
|
968
|
-
}
|
|
969
|
-
if (!Array.isArray(inputChildren)) {
|
|
815
|
+
if (inputChildren === undefined || inputChildren === null) {
|
|
816
|
+
return [];
|
|
817
|
+
}
|
|
818
|
+
if (!Array.isArray(inputChildren)) {
|
|
970
819
|
throw new Error('children must be an array');
|
|
971
820
|
}
|
|
972
821
|
return inputChildren.map((item, index) => {
|
|
@@ -974,79 +823,26 @@ function normalizeChildrenInput(inputChildren) {
|
|
|
974
823
|
throw new Error('children[' + index + '] must be an object');
|
|
975
824
|
}
|
|
976
825
|
const childTableName = item.childTableName ? String(item.childTableName) : '';
|
|
826
|
+
const childTableComment = item.childTableComment ? String(item.childTableComment) : '';
|
|
977
827
|
const mainField = item.mainField ? String(item.mainField) : '';
|
|
978
828
|
const childField = item.childField ? String(item.childField) : '';
|
|
979
829
|
const payloadField = item.payloadField ? String(item.payloadField) : '';
|
|
980
830
|
const relationType = item.relationType ? String(item.relationType) : '';
|
|
981
|
-
const
|
|
831
|
+
const fields = normalizeStructuredFieldArray(item.fields, 'children[' + index + '].fields');
|
|
832
|
+
const missingFields = ['childTableName', 'mainField', 'childField', 'payloadField'].filter(
|
|
833
|
+
(field) => !({ childTableName, mainField, childField, payloadField })[field]
|
|
834
|
+
);
|
|
982
835
|
if (missingFields.length) {
|
|
983
836
|
throw new Error('children[' + index + '] is missing required fields: ' + missingFields.join(', '));
|
|
984
837
|
}
|
|
985
|
-
return { childTableName, mainField, childField, payloadField, relationType };
|
|
838
|
+
return { childTableName, childTableComment, mainField, childField, payloadField, relationType, fields };
|
|
986
839
|
});
|
|
987
840
|
}
|
|
988
|
-
|
|
989
|
-
function
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
childTableName: relation.childTableName,
|
|
994
|
-
mainField: relation.mainField,
|
|
995
|
-
childField: relation.childField,
|
|
996
|
-
payloadField: relation.payloadField || '',
|
|
997
|
-
relationType: relation.relationType || '',
|
|
998
|
-
}));
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
const missingFields = ['childTableName', 'mainField', 'childField'].filter((field) => !safeArgs[field]);
|
|
1002
|
-
if (missingFields.length) {
|
|
1003
|
-
const message = 'master_child_jump requires either children[] or legacy relation fields. Missing: ' + missingFields.join(', ') + '.';
|
|
1004
|
-
const error = new Error(message);
|
|
1005
|
-
error.details = {
|
|
1006
|
-
type: 'relation_input_required',
|
|
1007
|
-
status: 'missing_required_relation_input',
|
|
1008
|
-
tableName: safeArgs.tableName,
|
|
1009
|
-
designFile: sourceDocument.path,
|
|
1010
|
-
message,
|
|
1011
|
-
requiredFields: ['children[] or childTableName/mainField/childField'],
|
|
1012
|
-
correctionEntry: {
|
|
1013
|
-
fields: ['children', 'childTableName', 'mainField', 'childField'],
|
|
1014
|
-
example: {
|
|
1015
|
-
...buildRetryArguments(safeArgs, sourceDocument.path, safeArgs.childTableName || '<child_table_name>'),
|
|
1016
|
-
children: [
|
|
1017
|
-
{
|
|
1018
|
-
childTableName: safeArgs.childTableName || '<child_table_name>',
|
|
1019
|
-
mainField: safeArgs.mainField || '<main_field>',
|
|
1020
|
-
childField: safeArgs.childField || '<child_field>',
|
|
1021
|
-
...(safeArgs.childPayloadField ? { payloadField: safeArgs.childPayloadField } : {}),
|
|
1022
|
-
relationType: safeArgs.relationType || '1:N',
|
|
1023
|
-
},
|
|
1024
|
-
],
|
|
1025
|
-
mainField: safeArgs.mainField || '<main_field>',
|
|
1026
|
-
childField: safeArgs.childField || '<child_field>',
|
|
1027
|
-
...(safeArgs.childPayloadField ? { childPayloadField: safeArgs.childPayloadField } : {}),
|
|
1028
|
-
},
|
|
1029
|
-
},
|
|
1030
|
-
};
|
|
1031
|
-
throw error;
|
|
1032
|
-
}
|
|
1033
|
-
|
|
1034
|
-
return [
|
|
1035
|
-
{
|
|
1036
|
-
mainTableName: safeArgs.tableName,
|
|
1037
|
-
childTableName: safeArgs.childTableName,
|
|
1038
|
-
mainField: safeArgs.mainField,
|
|
1039
|
-
childField: safeArgs.childField,
|
|
1040
|
-
payloadField: safeArgs.childPayloadField || '',
|
|
1041
|
-
relationType: safeArgs.relationType || '',
|
|
1042
|
-
},
|
|
1043
|
-
];
|
|
1044
|
-
}
|
|
1045
|
-
|
|
1046
|
-
function getStylePreset(styleId) {
|
|
1047
|
-
const preset = STYLE_CATALOG[styleId];
|
|
1048
|
-
if (!preset) throw new Error('Unsupported style: ' + styleId);
|
|
1049
|
-
return preset;
|
|
841
|
+
|
|
842
|
+
function getStylePreset(styleId) {
|
|
843
|
+
const preset = STYLE_CATALOG[styleId];
|
|
844
|
+
if (!preset) throw new Error('Unsupported style: ' + styleId);
|
|
845
|
+
return preset;
|
|
1050
846
|
}
|
|
1051
847
|
|
|
1052
848
|
function resolveSharedTemplates(stylePreset) {
|
|
@@ -1066,70 +862,74 @@ function normalizeFields(parsed) {
|
|
|
1066
862
|
return parsed.fields.map((field) => ({ ...field, formType: mapFieldType(field), isAudit: isAuditField(field.fieldName) }));
|
|
1067
863
|
}
|
|
1068
864
|
|
|
1069
|
-
function ensureFieldExists(fields, fieldName, tableName, role) {
|
|
1070
|
-
const field = fields.find((item) => item.fieldName === fieldName);
|
|
1071
|
-
if (!field) throw new Error(role + ' field "' + fieldName + '" was not found on table ' + tableName);
|
|
1072
|
-
return field;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
function buildChildModels(
|
|
865
|
+
function ensureFieldExists(fields, fieldName, tableName, role) {
|
|
866
|
+
const field = fields.find((item) => item.fieldName === fieldName);
|
|
867
|
+
if (!field) throw new Error(role + ' field "' + fieldName + '" was not found on table ' + tableName);
|
|
868
|
+
return field;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function buildChildModels(safeArgs, mainFields, mainPk) {
|
|
1076
872
|
if (safeArgs.style !== 'master_child_jump') return [];
|
|
1077
873
|
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
const
|
|
874
|
+
if (!safeArgs.children.length) {
|
|
875
|
+
throw new Error('master_child_jump requires children[] with structured child table metadata');
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
return safeArgs.children.map((relation) => {
|
|
879
|
+
const childPk = findPrimaryKeyFromStructuredFields(relation.fields);
|
|
880
|
+
const childFields = normalizeFields({
|
|
881
|
+
fields: relation.fields,
|
|
882
|
+
});
|
|
883
|
+
const mainRelationField = ensureFieldExists(mainFields, relation.mainField, safeArgs.tableName, 'Main relation');
|
|
884
|
+
const childRelationField = ensureFieldExists(childFields, relation.childField, relation.childTableName, 'Child relation');
|
|
1084
885
|
const childVisibleFields = childFields.filter(
|
|
1085
|
-
(field) => field.fieldName !==
|
|
886
|
+
(field) => field.fieldName !== childPk.fieldName && !field.isAudit && field.fieldName !== relation.childField && field.show !== false
|
|
1086
887
|
);
|
|
1087
|
-
const payloadField =
|
|
1088
|
-
|
|
1089
|
-
const listName = payloadField || toCamelCase(relation.childTableName) + 'List';
|
|
888
|
+
const payloadField = relation.payloadField;
|
|
889
|
+
const listName = payloadField;
|
|
1090
890
|
|
|
1091
891
|
return {
|
|
1092
892
|
tableName: relation.childTableName,
|
|
1093
|
-
tableComment:
|
|
893
|
+
tableComment: relation.childTableComment || relation.childTableName,
|
|
1094
894
|
className: toPascalCase(relation.childTableName),
|
|
1095
895
|
functionName: toCamelCase(relation.childTableName),
|
|
1096
896
|
listName,
|
|
1097
897
|
payloadField,
|
|
1098
|
-
payloadFieldSource:
|
|
1099
|
-
pk:
|
|
898
|
+
payloadFieldSource: 'arguments',
|
|
899
|
+
pk: childPk,
|
|
1100
900
|
fields: childFields,
|
|
1101
901
|
visibleFields: childVisibleFields,
|
|
1102
|
-
mainField: mainRelationField,
|
|
1103
|
-
childField: childRelationField,
|
|
1104
|
-
relationType: relation.relationType,
|
|
902
|
+
mainField: mainRelationField,
|
|
903
|
+
childField: childRelationField,
|
|
904
|
+
relationType: relation.relationType,
|
|
1105
905
|
};
|
|
1106
906
|
});
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
function buildModel(safeArgs) {
|
|
1110
|
-
const
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
const
|
|
1115
|
-
const
|
|
1116
|
-
const
|
|
1117
|
-
const
|
|
1118
|
-
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
function buildModel(safeArgs) {
|
|
910
|
+
const pkField = findPrimaryKeyFromStructuredFields(safeArgs.fields);
|
|
911
|
+
const fields = normalizeFields({
|
|
912
|
+
fields: safeArgs.fields,
|
|
913
|
+
});
|
|
914
|
+
const visibleFields = fields.filter((field) => field.fieldName !== pkField.fieldName && !field.isAudit && field.show !== false);
|
|
915
|
+
const gridFields = visibleFields.slice(0, 8);
|
|
916
|
+
const children = buildChildModels(safeArgs, fields, pkField);
|
|
917
|
+
const childDictTypes = children.flatMap((child) => child.visibleFields.map((field) => field.dictType).filter(Boolean));
|
|
918
|
+
const dictTypes = [...new Set([...visibleFields.map((field) => field.dictType).filter(Boolean), ...childDictTypes])];
|
|
919
|
+
|
|
1119
920
|
return {
|
|
1120
|
-
|
|
1121
|
-
designFile: sourceDocument.path,
|
|
1122
|
-
featureTitle: sourceDocument.designDoc.featureTitle || mainParsed.tableComment,
|
|
921
|
+
featureTitle: safeArgs.featureTitle || safeArgs.tableComment || safeArgs.tableName,
|
|
1123
922
|
tableName: safeArgs.tableName,
|
|
1124
|
-
tableComment:
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
923
|
+
tableComment: safeArgs.tableComment || safeArgs.featureTitle || safeArgs.tableName,
|
|
924
|
+
apiPath: safeArgs.apiPath || toCamelCase(safeArgs.tableName),
|
|
925
|
+
className: toPascalCase(safeArgs.tableName),
|
|
926
|
+
functionName: toCamelCase(safeArgs.tableName),
|
|
927
|
+
moduleName: normalizeModuleName(safeArgs.moduleName),
|
|
928
|
+
pk: pkField,
|
|
929
|
+
fields,
|
|
930
|
+
visibleFields,
|
|
931
|
+
gridFields,
|
|
932
|
+
dictTypes,
|
|
1133
933
|
frontendPath: path.resolve(safeArgs.frontendPath),
|
|
1134
934
|
style: safeArgs.style,
|
|
1135
935
|
children,
|
|
@@ -1264,11 +1064,11 @@ function renderDefaultLine(field) {
|
|
|
1264
1064
|
return ` ${field.attrName}: '',`;
|
|
1265
1065
|
}
|
|
1266
1066
|
|
|
1267
|
-
function renderFormDefaults(model) {
|
|
1268
|
-
const lines = [` ${model.pk.attrName}: '',`];
|
|
1269
|
-
for (const field of model.
|
|
1270
|
-
return lines.join('\n');
|
|
1271
|
-
}
|
|
1067
|
+
function renderFormDefaults(model) {
|
|
1068
|
+
const lines = [` ${model.pk.attrName}: '',`];
|
|
1069
|
+
for (const field of model.fields.filter((item) => item.fieldName !== model.pk.fieldName && !item.isAudit)) lines.push(renderDefaultLine(field));
|
|
1070
|
+
return lines.join('\n');
|
|
1071
|
+
}
|
|
1272
1072
|
|
|
1273
1073
|
function renderChildTempDefaults(childModel) {
|
|
1274
1074
|
if (!childModel) return '';
|
|
@@ -1571,10 +1371,10 @@ function renderFormRulesV2(visibleFields) {
|
|
|
1571
1371
|
return lines.join('\n');
|
|
1572
1372
|
}
|
|
1573
1373
|
|
|
1574
|
-
function buildReplacements(model, sharedSupport) {
|
|
1575
|
-
const menuBaseId = Date.now();
|
|
1576
|
-
const apiModulePath = `${model.moduleName}/${model.functionName}`;
|
|
1577
|
-
const routePath = `${model.moduleName}/${model.functionName}`;
|
|
1374
|
+
function buildReplacements(model, sharedSupport) {
|
|
1375
|
+
const menuBaseId = Date.now();
|
|
1376
|
+
const apiModulePath = model.apiPath || `${model.moduleName}/${model.functionName}`;
|
|
1377
|
+
const routePath = `${model.moduleName}/${model.functionName}`;
|
|
1578
1378
|
const permissionPrefix = `${model.moduleName}/${model.functionName}`.replace(/\//g, '_');
|
|
1579
1379
|
const dictRegistryRefs = sharedSupport.dictRegistry.keyByValue;
|
|
1580
1380
|
const i18nNamespace = buildI18nNamespace(model);
|
|
@@ -1649,30 +1449,29 @@ function renderFiles(model, stylePreset, sharedSupport, localeZhSupport) {
|
|
|
1649
1449
|
return files;
|
|
1650
1450
|
}
|
|
1651
1451
|
|
|
1652
|
-
function ensureArguments(input) {
|
|
1653
|
-
if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
|
|
1654
|
-
for (const key of TOOL_SCHEMA.required) {
|
|
1655
|
-
if (
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
const style = String(input.style);
|
|
1659
|
-
getStylePreset(style);
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1452
|
+
function ensureArguments(input) {
|
|
1453
|
+
if (!input || typeof input !== 'object') throw new Error('Arguments must be an object');
|
|
1454
|
+
for (const key of TOOL_SCHEMA.required) {
|
|
1455
|
+
if (input[key] === undefined || input[key] === null || input[key] === '') throw new Error(key + ' is required');
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const style = String(input.style);
|
|
1459
|
+
getStylePreset(style);
|
|
1460
|
+
const fields = normalizeStructuredFieldArray(input.fields, 'fields');
|
|
1461
|
+
|
|
1462
|
+
return {
|
|
1463
|
+
featureTitle: input.featureTitle ? String(input.featureTitle) : '',
|
|
1464
|
+
tableName: String(input.tableName),
|
|
1465
|
+
tableComment: input.tableComment ? String(input.tableComment) : '',
|
|
1466
|
+
apiPath: normalizeApiPath(input.apiPath),
|
|
1467
|
+
style,
|
|
1468
|
+
fields,
|
|
1665
1469
|
children: normalizeChildrenInput(input.children),
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
frontendPath: String(input.frontendPath),
|
|
1672
|
-
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
1673
|
-
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
1674
|
-
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
1675
|
-
};
|
|
1470
|
+
frontendPath: String(input.frontendPath),
|
|
1471
|
+
moduleName: input.moduleName ? String(input.moduleName) : 'admin/test',
|
|
1472
|
+
writeToDisk: input.writeToDisk === undefined ? true : Boolean(input.writeToDisk),
|
|
1473
|
+
overwrite: input.overwrite === undefined ? true : Boolean(input.overwrite),
|
|
1474
|
+
};
|
|
1676
1475
|
}
|
|
1677
1476
|
|
|
1678
1477
|
function maybeWriteFiles(files, writeToDisk, overwrite) {
|
|
@@ -1696,7 +1495,7 @@ function maybeWriteFiles(files, writeToDisk, overwrite) {
|
|
|
1696
1495
|
}
|
|
1697
1496
|
}
|
|
1698
1497
|
|
|
1699
|
-
function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, note) {
|
|
1498
|
+
function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, note) {
|
|
1700
1499
|
const relations = model.children.map((childModel) => ({
|
|
1701
1500
|
childTableName: childModel.tableName,
|
|
1702
1501
|
childTableComment: childModel.tableComment,
|
|
@@ -1709,39 +1508,37 @@ function buildManifest(model, safeArgs, stylePreset, sharedTemplates, files, not
|
|
|
1709
1508
|
}));
|
|
1710
1509
|
const selectedList = relations.map((relation) => formatRelationCandidate(relation));
|
|
1711
1510
|
|
|
1712
|
-
return {
|
|
1713
|
-
mode: 'local-template',
|
|
1714
|
-
style: safeArgs.style,
|
|
1715
|
-
styleLabel: stylePreset.label,
|
|
1716
|
-
runtimeSupported: hasRuntimeSupport(stylePreset),
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
writeToDisk: safeArgs.writeToDisk,
|
|
1511
|
+
return {
|
|
1512
|
+
mode: 'local-template',
|
|
1513
|
+
style: safeArgs.style,
|
|
1514
|
+
styleLabel: stylePreset.label,
|
|
1515
|
+
runtimeSupported: hasRuntimeSupport(stylePreset),
|
|
1516
|
+
tableName: model.tableName,
|
|
1517
|
+
tableComment: model.tableComment,
|
|
1518
|
+
apiPath: model.apiPath,
|
|
1519
|
+
moduleName: model.moduleName,
|
|
1520
|
+
writeToDisk: safeArgs.writeToDisk,
|
|
1723
1521
|
selectedTemplates: sharedTemplates,
|
|
1724
|
-
files: files.map((file) => ({ type: file.type, path: file.path, bytes: Buffer.byteLength(file.content, 'utf8'), status: file.status || (safeArgs.writeToDisk ? 'success' : 'rendered') })),
|
|
1725
|
-
relation: relations.length === 1 ? relations[0] : null,
|
|
1726
|
-
relations,
|
|
1727
|
-
relationResolution: model.children.length
|
|
1728
|
-
? {
|
|
1729
|
-
status: '
|
|
1730
|
-
tableName: safeArgs.tableName,
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
: null,
|
|
1522
|
+
files: files.map((file) => ({ type: file.type, path: file.path, bytes: Buffer.byteLength(file.content, 'utf8'), status: file.status || (safeArgs.writeToDisk ? 'success' : 'rendered') })),
|
|
1523
|
+
relation: relations.length === 1 ? relations[0] : null,
|
|
1524
|
+
relations,
|
|
1525
|
+
relationResolution: model.children.length
|
|
1526
|
+
? {
|
|
1527
|
+
status: 'structured_input',
|
|
1528
|
+
tableName: safeArgs.tableName,
|
|
1529
|
+
source: 'arguments',
|
|
1530
|
+
message:
|
|
1531
|
+
model.children.length > 1
|
|
1532
|
+
? 'Direct child relations were provided by the caller as structured API-first metadata. MCP generated a single main form with multiple child tables.'
|
|
1533
|
+
: 'The relation was provided by the caller as structured API-first metadata. MCP generated files directly.',
|
|
1534
|
+
selected: selectedList.length === 1 ? selectedList[0] : null,
|
|
1535
|
+
selectedList,
|
|
1536
|
+
correctionEntry: {
|
|
1537
|
+
fields: ['children'],
|
|
1538
|
+
example: buildRetryArguments(safeArgs),
|
|
1539
|
+
},
|
|
1540
|
+
}
|
|
1541
|
+
: null,
|
|
1745
1542
|
summary: {
|
|
1746
1543
|
totalFields: model.fields.length,
|
|
1747
1544
|
visibleFields: model.visibleFields.length,
|
|
@@ -1808,7 +1605,18 @@ async function onMessage(message) {
|
|
|
1808
1605
|
}
|
|
1809
1606
|
|
|
1810
1607
|
if (method === 'tools/list') {
|
|
1811
|
-
writeMessage(
|
|
1608
|
+
writeMessage(
|
|
1609
|
+
successResponse(id, {
|
|
1610
|
+
tools: [
|
|
1611
|
+
{
|
|
1612
|
+
name: TOOL_NAME,
|
|
1613
|
+
description:
|
|
1614
|
+
'Generate Worsoft frontend files and menu SQL from API-first structured feature metadata. In master_child_jump mode, the caller must provide children[] with explicit payloadField and child field metadata.',
|
|
1615
|
+
inputSchema: TOOL_SCHEMA,
|
|
1616
|
+
},
|
|
1617
|
+
],
|
|
1618
|
+
})
|
|
1619
|
+
);
|
|
1812
1620
|
return;
|
|
1813
1621
|
}
|
|
1814
1622
|
|