worsoft-frontend-codegen-local-mcp 0.1.53 → 0.1.54

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.
Files changed (2) hide show
  1. package/mcp_server.js +138 -60
  2. 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.53';
8
+ const SERVER_VERSION = '0.1.54';
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');
@@ -462,9 +462,9 @@ function toConstantCase(value) {
462
462
  .toUpperCase();
463
463
  }
464
464
 
465
- function parseDictRegistryEntries(fileContent) {
466
- const blockMatch = String(fileContent || '').match(/export const DictRegistry\s*=\s*{([\s\S]*?)}\s*as const;/);
467
- if (!blockMatch) return [];
465
+ function parseDictRegistryEntries(fileContent) {
466
+ const blockMatch = String(fileContent || '').match(/export const DictRegistry\s*=\s*{([\s\S]*?)}\s*as const;/);
467
+ if (!blockMatch) return [];
468
468
 
469
469
  const entries = [];
470
470
  const entryRegex = /^\s*([A-Z0-9_]+)\s*:\s*['"]([^'"]+)['"]\s*,?\s*$/gm;
@@ -473,23 +473,78 @@ function parseDictRegistryEntries(fileContent) {
473
473
  entries.push({ key: match[1], value: match[2] });
474
474
  match = entryRegex.exec(blockMatch[1]);
475
475
  }
476
- return entries;
477
- }
478
-
479
- function renderDictRegistryContent(entries) {
480
- const lines = entries.map((entry) => ` ${entry.key}: '${entry.value}',`);
481
- return [
482
- 'export const DictRegistry = {',
483
- ...lines,
484
- '} as const;',
485
- '',
486
- 'export type DictRegistryKey = keyof typeof DictRegistry;',
487
- 'export type DictType = (typeof DictRegistry)[DictRegistryKey];',
488
- '',
489
- ].join('\n');
490
- }
491
-
492
- function isPlainObject(value) {
476
+ return entries;
477
+ }
478
+
479
+ function renderDictRegistryContent(entries) {
480
+ const lines = entries.map((entry) => ` ${entry.key}: '${entry.value}',`);
481
+ return [
482
+ 'export const DictRegistry = {',
483
+ ...lines,
484
+ '} as const;',
485
+ '',
486
+ 'export type DictRegistryKey = keyof typeof DictRegistry;',
487
+ 'export type DictType = (typeof DictRegistry)[DictRegistryKey];',
488
+ '',
489
+ 'export const DictSemanticValues = {',
490
+ ' billState: {',
491
+ " editing: '0',",
492
+ " processing: '1',",
493
+ " paused: '2',",
494
+ " terminated: '3',",
495
+ " completed: '4',",
496
+ ' },',
497
+ '} as const;',
498
+ '',
499
+ 'export const isBillStateEditing = (value: unknown): boolean =>',
500
+ " String(value ?? '') === DictSemanticValues.billState.editing;",
501
+ '',
502
+ ].join('\n');
503
+ }
504
+
505
+ function patchDictRegistryContent(fileContent, entriesToAdd) {
506
+ const source = String(fileContent || '');
507
+ const blockMatch = source.match(/export const DictRegistry\s*=\s*{([\s\S]*?)}\s*as const;/);
508
+ if (!blockMatch) return null;
509
+
510
+ const block = blockMatch[0];
511
+ const insertAt = block.lastIndexOf('}');
512
+ if (insertAt < 0) return null;
513
+
514
+ const linesToAdd = entriesToAdd.map((entry) => ` ${entry.key}: '${entry.value}',`).join('\n');
515
+ if (!linesToAdd) return source;
516
+
517
+ const prefix = block.slice(0, insertAt).replace(/\s*$/, '');
518
+ const suffix = block.slice(insertAt);
519
+ const nextBlock = `${prefix}\n${linesToAdd}\n${suffix}`;
520
+
521
+ return source.replace(block, nextBlock);
522
+ }
523
+
524
+ const DICT_REGISTRY_SEMANTIC_HELPERS = [
525
+ '',
526
+ 'export const DictSemanticValues = {',
527
+ ' billState: {',
528
+ " editing: '0',",
529
+ " processing: '1',",
530
+ " paused: '2',",
531
+ " terminated: '3',",
532
+ " completed: '4',",
533
+ ' },',
534
+ '} as const;',
535
+ '',
536
+ 'export const isBillStateEditing = (value: unknown): boolean =>',
537
+ " String(value ?? '') === DictSemanticValues.billState.editing;",
538
+ '',
539
+ ].join('\n');
540
+
541
+ function ensureDictRegistrySemanticHelpers(fileContent) {
542
+ const source = String(fileContent || '').replace(/\s*$/, '\n');
543
+ if (source.includes('export const isBillStateEditing')) return source;
544
+ return `${source}${DICT_REGISTRY_SEMANTIC_HELPERS}`;
545
+ }
546
+
547
+ function isPlainObject(value) {
493
548
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
494
549
  }
495
550
 
@@ -720,33 +775,43 @@ function prepareZhCnLocaleFile(model, mergeExisting) {
720
775
  };
721
776
  }
722
777
 
723
- function prepareDictRegistry(frontendPath, dictTypes) {
724
- const registryPath = path.join(frontendPath, 'src', 'enums', 'dict-registry.ts');
725
- const exists = fs.existsSync(registryPath);
726
- const existingEntries = exists ? parseDictRegistryEntries(readUtf8File(registryPath)) : [];
727
- const entries = existingEntries.map((entry) => ({ ...entry }));
728
- const keyByValue = new Map(entries.map((entry) => [entry.value, entry.key]));
729
- const existingByKey = new Map(entries.map((entry) => [entry.key, entry.value]));
730
- const usedKeys = new Set(entries.map((entry) => entry.key));
731
- let changed = !exists;
732
-
733
- for (const dictType of dictTypes) {
734
- if (!dictType || keyByValue.has(dictType)) continue;
735
- const key = buildUniqueDictRegistryKey(dictType, usedKeys, existingByKey);
736
- entries.push({ key, value: dictType });
737
- keyByValue.set(dictType, key);
738
- existingByKey.set(key, dictType);
739
- usedKeys.add(key);
740
- changed = true;
741
- }
742
-
743
- return {
744
- path: registryPath,
745
- entries,
746
- keyByValue,
747
- needsWrite: changed,
748
- };
749
- }
778
+ function prepareDictRegistry(frontendPath, dictTypes) {
779
+ const registryPath = path.join(frontendPath, 'src', 'enums', 'dict-registry.ts');
780
+ const exists = fs.existsSync(registryPath);
781
+ const currentContent = exists ? readUtf8File(registryPath) : '';
782
+ const existingEntries = exists ? parseDictRegistryEntries(currentContent) : [];
783
+ const entries = existingEntries.map((entry) => ({ ...entry }));
784
+ const keyByValue = new Map(entries.map((entry) => [entry.value, entry.key]));
785
+ const existingByKey = new Map(entries.map((entry) => [entry.key, entry.value]));
786
+ const usedKeys = new Set(entries.map((entry) => entry.key));
787
+ const entriesToAdd = [];
788
+ let changed = !exists;
789
+
790
+ for (const dictType of dictTypes) {
791
+ if (!dictType || keyByValue.has(dictType)) continue;
792
+ const key = buildUniqueDictRegistryKey(dictType, usedKeys, existingByKey);
793
+ const entry = { key, value: dictType };
794
+ entries.push(entry);
795
+ entriesToAdd.push(entry);
796
+ keyByValue.set(dictType, key);
797
+ existingByKey.set(key, dictType);
798
+ usedKeys.add(key);
799
+ changed = true;
800
+ }
801
+
802
+ const patchedContent = exists ? patchDictRegistryContent(currentContent, entriesToAdd) : renderDictRegistryContent(entries);
803
+ const content = patchedContent ? ensureDictRegistrySemanticHelpers(patchedContent) : null;
804
+ const needsSemanticHelpers = exists && Boolean(content) && content !== currentContent;
805
+
806
+ return {
807
+ path: registryPath,
808
+ entries,
809
+ keyByValue,
810
+ content,
811
+ isCompatible: !exists || Boolean(content),
812
+ needsWrite: changed || needsSemanticHelpers,
813
+ };
814
+ }
750
815
 
751
816
  function ensureCrudSchemaSupportFile(frontendPath) {
752
817
  const schemaPath = path.join(frontendPath, 'src', 'utils', 'crudSchema.ts');
@@ -817,10 +882,16 @@ function maybeWriteSharedSupport(sharedSupport, writeToDisk) {
817
882
  writeSupportFile(sharedSupport.crudSchema.path, sharedSupport.crudSchema.content);
818
883
  }
819
884
 
820
- if (sharedSupport.dictRegistry.needsWrite) {
821
- writeSupportFile(sharedSupport.dictRegistry.path, renderDictRegistryContent(sharedSupport.dictRegistry.entries));
822
- }
823
- }
885
+ if (sharedSupport.dictRegistry.needsWrite) {
886
+ if (!sharedSupport.dictRegistry.isCompatible) {
887
+ throw new Error(
888
+ 'Detected an existing src/enums/dict-registry.ts that MCP could not merge safely. ' +
889
+ 'Please align the DictRegistry export shape manually before enabling writeSupportFiles.'
890
+ );
891
+ }
892
+ writeSupportFile(sharedSupport.dictRegistry.path, sharedSupport.dictRegistry.content);
893
+ }
894
+ }
824
895
 
825
896
  function buildSupportNote(sharedSupport, localeZhSupport) {
826
897
  const notes = [];
@@ -831,14 +902,21 @@ function buildSupportNote(sharedSupport, localeZhSupport) {
831
902
  );
832
903
  }
833
904
 
834
- if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
835
- notes.push(
836
- 'Detected an existing src/utils/crudSchema.ts that does not match the expected helper signature. ' +
837
- 'MCP preserved the existing file and did not overwrite it. Generated pages now depend on that file being manually aligned.'
838
- );
839
- }
840
-
841
- if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
905
+ if (sharedSupport.crudSchema.exists && !sharedSupport.crudSchema.isCompatible) {
906
+ notes.push(
907
+ 'Detected an existing src/utils/crudSchema.ts that does not match the expected helper signature. ' +
908
+ 'MCP preserved the existing file and did not overwrite it. Generated pages now depend on that file being manually aligned.'
909
+ );
910
+ }
911
+
912
+ if (sharedSupport.dictRegistry.exists && !sharedSupport.dictRegistry.isCompatible) {
913
+ notes.push(
914
+ 'Detected an existing src/enums/dict-registry.ts that MCP could not merge safely. ' +
915
+ 'MCP preserved the existing file and did not overwrite it.'
916
+ );
917
+ }
918
+
919
+ if (localeZhSupport.exists && !localeZhSupport.isCompatible) {
842
920
  notes.push(
843
921
  `Detected an existing ${path.relative(localeZhSupport.frontendPath, localeZhSupport.path).replace(/\\/g, '/')} that MCP could not parse. ` +
844
922
  'The file was preserved and not updated, so new Chinese i18n keys may need to be merged manually.'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.53",
3
+ "version": "0.1.54",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",