@famgia/omnify-typescript 0.0.67 → 0.0.69

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 (43) hide show
  1. package/dist/{chunk-4L77AHAC.js → chunk-6I4O23X6.js} +521 -66
  2. package/dist/chunk-6I4O23X6.js.map +1 -0
  3. package/dist/index.cjs +761 -65
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +138 -2
  6. package/dist/index.d.ts +138 -2
  7. package/dist/index.js +227 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/plugin.cjs +624 -75
  10. package/dist/plugin.cjs.map +1 -1
  11. package/dist/plugin.d.cts +6 -0
  12. package/dist/plugin.d.ts +6 -0
  13. package/dist/plugin.js +96 -11
  14. package/dist/plugin.js.map +1 -1
  15. package/package.json +3 -3
  16. package/scripts/postinstall.js +29 -40
  17. package/stubs/JapaneseAddressField.tsx.stub +289 -0
  18. package/stubs/JapaneseBankField.tsx.stub +212 -0
  19. package/stubs/JapaneseNameField.tsx.stub +194 -0
  20. package/stubs/ai-guides/checklists/react.md.stub +108 -0
  21. package/stubs/ai-guides/cursor/react-design.mdc.stub +289 -0
  22. package/stubs/ai-guides/cursor/react-form.mdc.stub +277 -0
  23. package/stubs/ai-guides/cursor/react-services.mdc.stub +304 -0
  24. package/stubs/ai-guides/cursor/react.mdc.stub +254 -0
  25. package/stubs/ai-guides/react/README.md.stub +221 -0
  26. package/stubs/ai-guides/react/antd-guide.md.stub +294 -0
  27. package/stubs/ai-guides/react/checklist.md.stub +108 -0
  28. package/stubs/ai-guides/react/datetime-guide.md.stub +137 -0
  29. package/stubs/ai-guides/react/design-philosophy.md.stub +363 -0
  30. package/stubs/ai-guides/react/i18n-guide.md.stub +211 -0
  31. package/stubs/ai-guides/react/laravel-integration.md.stub +181 -0
  32. package/stubs/ai-guides/react/service-pattern.md.stub +180 -0
  33. package/stubs/ai-guides/react/tanstack-query.md.stub +339 -0
  34. package/stubs/ai-guides/react/types-guide.md.stub +524 -0
  35. package/stubs/components-index.ts.stub +13 -0
  36. package/stubs/form-validation.ts.stub +106 -0
  37. package/stubs/rules/index.ts.stub +48 -0
  38. package/stubs/rules/kana.ts.stub +291 -0
  39. package/stubs/use-form-mutation.ts.stub +117 -0
  40. package/stubs/zod-i18n.ts.stub +32 -0
  41. package/ai-guides/antdesign-guide.md +0 -401
  42. package/ai-guides/typescript-guide.md +0 -310
  43. package/dist/chunk-4L77AHAC.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,12 +17,22 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
21
31
  var index_exports = {};
22
32
  __export(index_exports, {
23
33
  DEFAULT_VALIDATION_TEMPLATES: () => DEFAULT_VALIDATION_TEMPLATES,
34
+ STUB_FILES: () => STUB_FILES,
35
+ copyStubs: () => copyStubs,
24
36
  enumToUnionType: () => enumToUnionType,
25
37
  extractInlineEnums: () => extractInlineEnums,
26
38
  formatEnum: () => formatEnum,
@@ -28,18 +40,23 @@ __export(index_exports, {
28
40
  formatProperty: () => formatProperty,
29
41
  formatTypeAlias: () => formatTypeAlias,
30
42
  formatValidationMessage: () => formatValidationMessage,
43
+ generateAIGuides: () => generateAIGuides,
31
44
  generateEnums: () => generateEnums,
32
45
  generateInterfaces: () => generateInterfaces,
33
46
  generateModelRules: () => generateModelRules,
47
+ generatePluginEnums: () => generatePluginEnums,
34
48
  generateRulesFiles: () => generateRulesFiles,
35
49
  generateTypeScript: () => generateTypeScript,
36
50
  generateTypeScriptFiles: () => generateTypeScript,
37
51
  getPropertyType: () => getPropertyType,
52
+ getStubPaths: () => getStubPaths,
38
53
  getValidationMessages: () => getValidationMessages,
39
54
  mergeValidationTemplates: () => mergeValidationTemplates,
55
+ pluginEnumToTSEnum: () => pluginEnumToTSEnum,
40
56
  propertyToTSProperty: () => propertyToTSProperty,
41
57
  schemaToEnum: () => schemaToEnum,
42
58
  schemaToInterface: () => schemaToInterface,
59
+ shouldGenerateAIGuides: () => shouldGenerateAIGuides,
43
60
  toEnumMemberName: () => toEnumMemberName,
44
61
  toEnumName: () => toEnumName,
45
62
  toInterfaceName: () => toInterfaceName,
@@ -130,6 +147,10 @@ function getPropertyType(property, _allSchemas) {
130
147
  return "unknown";
131
148
  }
132
149
  }
150
+ if (property.type === "EnumRef") {
151
+ const enumRefProp = property;
152
+ return enumRefProp.enum;
153
+ }
133
154
  if (property.type === "Enum") {
134
155
  const enumProp = property;
135
156
  if (typeof enumProp.enum === "string") {
@@ -158,7 +179,7 @@ function propertyToTSProperties(propertyName, property, allSchemas, options = {}
158
179
  for (const field of customType.expand) {
159
180
  const fieldName = `${propertyName}_${toSnakeCase(field.suffix)}`;
160
181
  const fieldOverride = baseProp.fields?.[field.suffix];
161
- const isNullable = fieldOverride?.nullable ?? baseProp.nullable ?? false;
182
+ const isNullable = fieldOverride?.nullable ?? field.sql?.nullable ?? baseProp.nullable ?? false;
162
183
  const tsType = field.typescript?.type ?? "string";
163
184
  expandedProps.push({
164
185
  name: fieldName,
@@ -303,12 +324,24 @@ function schemaToInterface(schema, allSchemas, options = {}) {
303
324
  }
304
325
  }
305
326
  }
327
+ const enumDependencySet = /* @__PURE__ */ new Set();
328
+ if (schema.properties) {
329
+ for (const property of Object.values(schema.properties)) {
330
+ if (property.type === "EnumRef") {
331
+ const enumRefProp = property;
332
+ if (enumRefProp.enum) {
333
+ enumDependencySet.add(enumRefProp.enum);
334
+ }
335
+ }
336
+ }
337
+ }
306
338
  const schemaDisplayName = resolveDisplayName(schema.displayName, options);
307
339
  return {
308
340
  name: toInterfaceName(schema.name),
309
341
  properties,
310
342
  comment: schemaDisplayName ?? schema.name,
311
- dependencies: dependencySet.size > 0 ? Array.from(dependencySet).sort() : void 0
343
+ dependencies: dependencySet.size > 0 ? Array.from(dependencySet).sort() : void 0,
344
+ enumDependencies: enumDependencySet.size > 0 ? Array.from(enumDependencySet).sort() : void 0
312
345
  };
313
346
  }
314
347
  function formatProperty(property) {
@@ -355,7 +388,11 @@ function resolveDisplayName2(value, options = {}) {
355
388
  });
356
389
  }
357
390
  function toEnumMemberName(value) {
358
- return value.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("").replace(/[^a-zA-Z0-9]/g, "");
391
+ let result = value.split(/[-_\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("").replace(/[^a-zA-Z0-9]/g, "");
392
+ if (/^\d/.test(result)) {
393
+ result = "_" + result;
394
+ }
395
+ return result;
359
396
  }
360
397
  function toEnumName(schemaName) {
361
398
  return schemaName;
@@ -409,6 +446,46 @@ function generateEnums(schemas, options = {}) {
409
446
  }
410
447
  return enums;
411
448
  }
449
+ function pluginEnumToTSEnum(enumDef, options = {}) {
450
+ const values = enumDef.values.map((v) => {
451
+ let label;
452
+ if (v.label !== void 0) {
453
+ if (typeof v.label === "string") {
454
+ label = v.label;
455
+ } else if (options.multiLocale) {
456
+ label = v.label;
457
+ } else {
458
+ label = resolveDisplayName2(v.label, options);
459
+ }
460
+ }
461
+ return {
462
+ name: toEnumMemberName(v.value),
463
+ value: v.value,
464
+ label,
465
+ extra: v.extra
466
+ };
467
+ });
468
+ let comment;
469
+ if (enumDef.displayName !== void 0) {
470
+ if (typeof enumDef.displayName === "string") {
471
+ comment = enumDef.displayName;
472
+ } else {
473
+ comment = resolveDisplayName2(enumDef.displayName, options);
474
+ }
475
+ }
476
+ return {
477
+ name: enumDef.name,
478
+ values,
479
+ comment: comment ?? enumDef.name
480
+ };
481
+ }
482
+ function generatePluginEnums(pluginEnums, options = {}) {
483
+ const enums = [];
484
+ for (const enumDef of pluginEnums.values()) {
485
+ enums.push(pluginEnumToTSEnum(enumDef, options));
486
+ }
487
+ return enums;
488
+ }
412
489
  function isMultiLocaleLabel(label) {
413
490
  return label !== void 0 && typeof label === "object";
414
491
  }
@@ -429,7 +506,7 @@ ${enumValues}
429
506
  `);
430
507
  parts.push(`/** All ${name} values */
431
508
  `);
432
- parts.push(`export const ${name}Values = Object.values(${name});
509
+ parts.push(`export const ${name}Values = Object.values(${name}) as ${name}[];
433
510
 
434
511
  `);
435
512
  parts.push(`/** Type guard for ${name} */
@@ -447,10 +524,10 @@ ${enumValues}
447
524
  if (hasMultiLocale) {
448
525
  const labelEntries = values.filter((v) => v.label !== void 0).map((v) => {
449
526
  if (isMultiLocaleLabel(v.label)) {
450
- const locales = Object.entries(v.label).map(([locale, text]) => `${locale}: '${text}'`).join(", ");
527
+ const locales = Object.entries(v.label).map(([locale, text]) => `'${locale}': '${escapeString(text)}'`).join(", ");
451
528
  return ` [${name}.${v.name}]: { ${locales} },`;
452
529
  }
453
- return ` [${name}.${v.name}]: { default: '${v.label}' },`;
530
+ return ` [${name}.${v.name}]: { default: '${escapeString(String(v.label))}' },`;
454
531
  }).join("\n");
455
532
  parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, Record<string, string>>> = {
456
533
  ${labelEntries}
@@ -467,13 +544,15 @@ ${labelEntries}
467
544
  `);
468
545
  parts.push(` if (locale && labels[locale]) return labels[locale];
469
546
  `);
470
- parts.push(` return Object.values(labels)[0] ?? value;
547
+ parts.push(` // Fallback: ja \u2192 en \u2192 first available
548
+ `);
549
+ parts.push(` return labels['ja'] ?? labels['en'] ?? Object.values(labels)[0] ?? value;
471
550
  `);
472
551
  parts.push(`}
473
552
 
474
553
  `);
475
554
  } else {
476
- const labelEntries = values.filter((v) => v.label !== void 0).map((v) => ` [${name}.${v.name}]: '${v.label}',`).join("\n");
555
+ const labelEntries = values.filter((v) => v.label !== void 0).map((v) => ` [${name}.${v.name}]: '${escapeString(String(v.label))}',`).join("\n");
477
556
  parts.push(`const ${lowerFirst(name)}Labels: Partial<Record<${name}, string>> = {
478
557
  ${labelEntries}
479
558
  };
@@ -529,6 +608,9 @@ ${extraEntries}
529
608
  function lowerFirst(str) {
530
609
  return str.charAt(0).toLowerCase() + str.slice(1);
531
610
  }
611
+ function escapeString(str) {
612
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
613
+ }
532
614
  function enumToUnionType(enumDef) {
533
615
  const type = enumDef.values.map((v) => `'${v.value}'`).join(" | ");
534
616
  return {
@@ -1050,6 +1132,13 @@ function getZodSchemaForType(propDef, fieldName, customTypes) {
1050
1132
  case "Json":
1051
1133
  schema = "z.unknown()";
1052
1134
  break;
1135
+ case "EnumRef":
1136
+ if (typeof def.enum === "string") {
1137
+ schema = `z.nativeEnum(${def.enum})`;
1138
+ } else {
1139
+ schema = "z.string()";
1140
+ }
1141
+ break;
1053
1142
  case "Enum":
1054
1143
  if (typeof def.enum === "string") {
1055
1144
  schema = `${def.enum}Schema`;
@@ -1095,15 +1184,35 @@ function generateCompoundTypeSchemas(propName, propDef, customType, options) {
1095
1184
  for (const field of customType.expand) {
1096
1185
  const fieldName = `${toSnakeCase(propName)}_${toSnakeCase(field.suffix)}`;
1097
1186
  const fieldOverride = propFields?.[field.suffix];
1098
- const isNullable = fieldOverride?.nullable ?? propDef.nullable ?? false;
1099
- const length = fieldOverride?.length ?? field.sql?.length;
1187
+ const isNullable = fieldOverride?.nullable ?? field.sql?.nullable ?? propDef.nullable ?? false;
1188
+ const pluginRules = field.rules;
1189
+ const overrideRules = fieldOverride?.rules;
1190
+ const length = fieldOverride?.length ?? overrideRules?.maxLength ?? pluginRules?.maxLength ?? field.sql?.length;
1191
+ const minLength = overrideRules?.minLength ?? pluginRules?.minLength;
1192
+ const pattern = overrideRules?.pattern ?? pluginRules?.pattern;
1193
+ const format = overrideRules?.format ?? pluginRules?.format;
1100
1194
  let schema = "z.string()";
1195
+ if (format === "email") {
1196
+ schema = "z.string().email()";
1197
+ } else if (format === "url") {
1198
+ schema = "z.string().url()";
1199
+ } else if (format === "phone") {
1200
+ schema = "z.string()";
1201
+ } else if (format === "postal_code") {
1202
+ schema = `z.string().regex(/^\\d{3}-?\\d{4}$/)`;
1203
+ }
1101
1204
  if (!isNullable) {
1102
- schema += ".min(1)";
1205
+ const min = minLength ?? 1;
1206
+ schema += `.min(${min})`;
1207
+ } else if (minLength) {
1208
+ schema += `.min(${minLength})`;
1103
1209
  }
1104
1210
  if (length) {
1105
1211
  schema += `.max(${length})`;
1106
1212
  }
1213
+ if (pattern && !format) {
1214
+ schema += `.regex(/${pattern}/)`;
1215
+ }
1107
1216
  if (isNullable) {
1108
1217
  schema += ".optional().nullable()";
1109
1218
  }
@@ -1159,6 +1268,7 @@ function generateDisplayNames(schema, options) {
1159
1268
  schema.name
1160
1269
  );
1161
1270
  const propertyDisplayNames = {};
1271
+ const propertyPlaceholders = {};
1162
1272
  if (schema.properties) {
1163
1273
  for (const [propName, propDef] of Object.entries(schema.properties)) {
1164
1274
  const prop = propDef;
@@ -1166,19 +1276,47 @@ function generateDisplayNames(schema, options) {
1166
1276
  if (customTypes) {
1167
1277
  const customType = customTypes.get(propDef.type);
1168
1278
  if (customType?.compound && customType.expand) {
1169
- for (const field of customType.expand) {
1170
- const expandedFieldName = `${fieldName}_${toSnakeCase(field.suffix)}`;
1171
- propertyDisplayNames[expandedFieldName] = getMultiLocaleDisplayName2(
1279
+ if (prop.displayName) {
1280
+ propertyDisplayNames[fieldName] = getMultiLocaleDisplayName2(
1172
1281
  prop.displayName,
1173
1282
  locales,
1174
1283
  fallbackLocale,
1175
1284
  propName
1176
1285
  );
1177
- for (const locale of locales) {
1178
- propertyDisplayNames[expandedFieldName] = {
1179
- ...propertyDisplayNames[expandedFieldName],
1180
- [locale]: `${propertyDisplayNames[expandedFieldName][locale]} (${field.suffix})`
1181
- };
1286
+ }
1287
+ for (const field of customType.expand) {
1288
+ const expandedFieldName = `${fieldName}_${toSnakeCase(field.suffix)}`;
1289
+ const fieldOverride = prop.fields?.[field.suffix];
1290
+ const labelSource = fieldOverride?.displayName ?? field.label;
1291
+ if (labelSource) {
1292
+ propertyDisplayNames[expandedFieldName] = getMultiLocaleDisplayName2(
1293
+ labelSource,
1294
+ locales,
1295
+ fallbackLocale,
1296
+ field.suffix
1297
+ );
1298
+ } else {
1299
+ propertyDisplayNames[expandedFieldName] = getMultiLocaleDisplayName2(
1300
+ prop.displayName,
1301
+ locales,
1302
+ fallbackLocale,
1303
+ propName
1304
+ );
1305
+ for (const locale of locales) {
1306
+ propertyDisplayNames[expandedFieldName] = {
1307
+ ...propertyDisplayNames[expandedFieldName],
1308
+ [locale]: `${propertyDisplayNames[expandedFieldName][locale]} (${field.suffix})`
1309
+ };
1310
+ }
1311
+ }
1312
+ const placeholderSource = fieldOverride?.placeholder ?? field.placeholder;
1313
+ if (placeholderSource) {
1314
+ propertyPlaceholders[expandedFieldName] = getMultiLocaleDisplayName2(
1315
+ placeholderSource,
1316
+ locales,
1317
+ fallbackLocale,
1318
+ ""
1319
+ );
1182
1320
  }
1183
1321
  }
1184
1322
  continue;
@@ -1190,9 +1328,17 @@ function generateDisplayNames(schema, options) {
1190
1328
  fallbackLocale,
1191
1329
  propName
1192
1330
  );
1331
+ if (prop.placeholder) {
1332
+ propertyPlaceholders[fieldName] = getMultiLocaleDisplayName2(
1333
+ prop.placeholder,
1334
+ locales,
1335
+ fallbackLocale,
1336
+ ""
1337
+ );
1338
+ }
1193
1339
  }
1194
1340
  }
1195
- return { displayName, propertyDisplayNames };
1341
+ return { displayName, propertyDisplayNames, propertyPlaceholders };
1196
1342
  }
1197
1343
  function getExcludedFields(schema, customTypes) {
1198
1344
  const createExclude = /* @__PURE__ */ new Set();
@@ -1236,24 +1382,44 @@ function formatZodSchemasSection(schemaName, zodSchemas, displayNames, excludedF
1236
1382
  const lowerName = schemaName.charAt(0).toLowerCase() + schemaName.slice(1);
1237
1383
  parts.push(`// ============================================================================
1238
1384
  `);
1239
- parts.push(`// Display Names
1385
+ parts.push(`// I18n (Internationalization)
1240
1386
  `);
1241
1387
  parts.push(`// ============================================================================
1242
1388
 
1243
1389
  `);
1244
- parts.push(`/** Display name for ${schemaName} */
1390
+ parts.push(`/**
1245
1391
  `);
1246
- parts.push(`export const ${schemaName}DisplayName = ${JSON.stringify(displayNames.displayName, null, 2)} as const;
1247
-
1392
+ parts.push(` * Unified i18n object for ${schemaName}
1248
1393
  `);
1249
- parts.push(`/** Property display names for ${schemaName} */
1394
+ parts.push(` * Contains model label and all field labels/placeholders
1395
+ `);
1396
+ parts.push(` */
1397
+ `);
1398
+ parts.push(`export const ${lowerName}I18n = {
1399
+ `);
1400
+ parts.push(` /** Model display name */
1401
+ `);
1402
+ parts.push(` label: ${JSON.stringify(displayNames.displayName)},
1403
+ `);
1404
+ parts.push(` /** Field labels and placeholders */
1250
1405
  `);
1251
- parts.push(`export const ${schemaName}PropertyDisplayNames = {
1406
+ parts.push(` fields: {
1252
1407
  `);
1253
- for (const [propName, localeMap] of Object.entries(displayNames.propertyDisplayNames)) {
1254
- parts.push(` ${propName}: ${JSON.stringify(localeMap)},
1408
+ for (const [propName, labelMap] of Object.entries(displayNames.propertyDisplayNames)) {
1409
+ const placeholderMap = displayNames.propertyPlaceholders[propName];
1410
+ parts.push(` ${propName}: {
1411
+ `);
1412
+ parts.push(` label: ${JSON.stringify(labelMap)},
1413
+ `);
1414
+ if (placeholderMap) {
1415
+ parts.push(` placeholder: ${JSON.stringify(placeholderMap)},
1416
+ `);
1417
+ }
1418
+ parts.push(` },
1255
1419
  `);
1256
1420
  }
1421
+ parts.push(` },
1422
+ `);
1257
1423
  parts.push(`} as const;
1258
1424
 
1259
1425
  `);
@@ -1310,35 +1476,50 @@ function formatZodSchemasSection(schemaName, zodSchemas, displayNames, excludedF
1310
1476
  `);
1311
1477
  parts.push(`// ============================================================================
1312
1478
  `);
1313
- parts.push(`// Helper Functions
1479
+ parts.push(`// I18n Helper Functions
1314
1480
  `);
1315
1481
  parts.push(`// ============================================================================
1316
1482
 
1317
1483
  `);
1318
- parts.push(`/** Get display name for a specific locale */
1484
+ parts.push(`/** Get model label for a specific locale */
1319
1485
  `);
1320
- parts.push(`export function get${schemaName}DisplayName(locale: string): string {
1486
+ parts.push(`export function get${schemaName}Label(locale: string): string {
1321
1487
  `);
1322
- parts.push(` return ${schemaName}DisplayName[locale as keyof typeof ${schemaName}DisplayName] ?? ${schemaName}DisplayName['en'] ?? '${schemaName}';
1488
+ parts.push(` return ${lowerName}I18n.label[locale as keyof typeof ${lowerName}I18n.label] ?? ${lowerName}I18n.label['en'] ?? '${schemaName}';
1323
1489
  `);
1324
1490
  parts.push(`}
1325
1491
 
1326
1492
  `);
1327
- parts.push(`/** Get property display name for a specific locale */
1493
+ parts.push(`/** Get field label for a specific locale */
1328
1494
  `);
1329
- parts.push(`export function get${schemaName}PropertyDisplayName(property: string, locale: string): string {
1495
+ parts.push(`export function get${schemaName}FieldLabel(field: string, locale: string): string {
1330
1496
  `);
1331
- parts.push(` const names = ${schemaName}PropertyDisplayNames[property as keyof typeof ${schemaName}PropertyDisplayNames];
1497
+ parts.push(` const fieldI18n = ${lowerName}I18n.fields[field as keyof typeof ${lowerName}I18n.fields];
1332
1498
  `);
1333
- parts.push(` if (!names) return property;
1499
+ parts.push(` if (!fieldI18n) return field;
1334
1500
  `);
1335
- parts.push(` return names[locale as keyof typeof names] ?? names['en'] ?? property;
1501
+ parts.push(` return fieldI18n.label[locale as keyof typeof fieldI18n.label] ?? fieldI18n.label['en'] ?? field;
1502
+ `);
1503
+ parts.push(`}
1504
+
1505
+ `);
1506
+ parts.push(`/** Get field placeholder for a specific locale */
1507
+ `);
1508
+ parts.push(`export function get${schemaName}FieldPlaceholder(field: string, locale: string): string {
1509
+ `);
1510
+ parts.push(` const fieldI18n = ${lowerName}I18n.fields[field as keyof typeof ${lowerName}I18n.fields];
1511
+ `);
1512
+ parts.push(` if (!fieldI18n || !('placeholder' in fieldI18n)) return '';
1513
+ `);
1514
+ parts.push(` const placeholder = fieldI18n.placeholder as Record<string, string>;
1515
+ `);
1516
+ parts.push(` return placeholder[locale] ?? placeholder['en'] ?? '';
1336
1517
  `);
1337
1518
  parts.push(`}
1338
1519
  `);
1339
1520
  return parts.join("");
1340
1521
  }
1341
- function formatZodModelFile(schemaName) {
1522
+ function formatZodModelFile(schemaName, _hasPlaceholders = false) {
1342
1523
  const lowerName = schemaName.charAt(0).toLowerCase() + schemaName.slice(1);
1343
1524
  return `/**
1344
1525
  * ${schemaName} Model
@@ -1354,10 +1535,10 @@ import {
1354
1535
  base${schemaName}Schemas,
1355
1536
  base${schemaName}CreateSchema,
1356
1537
  base${schemaName}UpdateSchema,
1357
- ${schemaName}DisplayName,
1358
- ${schemaName}PropertyDisplayNames,
1359
- get${schemaName}DisplayName,
1360
- get${schemaName}PropertyDisplayName,
1538
+ ${lowerName}I18n,
1539
+ get${schemaName}Label,
1540
+ get${schemaName}FieldLabel,
1541
+ get${schemaName}FieldPlaceholder,
1361
1542
  } from './base/${schemaName}.js';
1362
1543
 
1363
1544
  // ============================================================================
@@ -1383,12 +1564,12 @@ export const ${lowerName}UpdateSchema = base${schemaName}UpdateSchema;
1383
1564
  export type ${schemaName}Create = z.infer<typeof ${lowerName}CreateSchema>;
1384
1565
  export type ${schemaName}Update = z.infer<typeof ${lowerName}UpdateSchema>;
1385
1566
 
1386
- // Re-export display names and helpers
1567
+ // Re-export i18n and helpers
1387
1568
  export {
1388
- ${schemaName}DisplayName,
1389
- ${schemaName}PropertyDisplayNames,
1390
- get${schemaName}DisplayName,
1391
- get${schemaName}PropertyDisplayName,
1569
+ ${lowerName}I18n,
1570
+ get${schemaName}Label,
1571
+ get${schemaName}FieldLabel,
1572
+ get${schemaName}FieldPlaceholder,
1392
1573
  };
1393
1574
 
1394
1575
  // Re-export base type for internal use
@@ -1504,13 +1685,24 @@ function generateBaseInterfaceFile(schemaName, schemas, options) {
1504
1685
  parts.push(`import type { ${commonImports.join(", ")} } from '../common.js';
1505
1686
  `);
1506
1687
  }
1688
+ if (iface.enumDependencies && iface.enumDependencies.length > 0) {
1689
+ const enumPrefix = options.enumImportPrefix ? `../${options.enumImportPrefix}` : "../enum";
1690
+ const pluginEnumNames = new Set(
1691
+ options.pluginEnums ? Array.from(options.pluginEnums.keys()) : []
1692
+ );
1693
+ for (const enumName of iface.enumDependencies) {
1694
+ const enumPath = pluginEnumNames.has(enumName) ? `${enumPrefix}/plugin/${enumName}.js` : `${enumPrefix}/${enumName}.js`;
1695
+ parts.push(`import { ${enumName} } from '${enumPath}';
1696
+ `);
1697
+ }
1698
+ }
1507
1699
  if (iface.dependencies && iface.dependencies.length > 0) {
1508
1700
  for (const dep of iface.dependencies) {
1509
1701
  parts.push(`import type { ${dep} } from './${dep}.js';
1510
1702
  `);
1511
1703
  }
1512
1704
  parts.push("\n");
1513
- } else if (commonImports.length > 0 || options.generateZodSchemas) {
1705
+ } else if (commonImports.length > 0 || options.generateZodSchemas || iface.enumDependencies && iface.enumDependencies.length > 0) {
1514
1706
  parts.push("\n");
1515
1707
  }
1516
1708
  parts.push(formatInterface(iface));
@@ -1531,15 +1723,17 @@ function generateBaseInterfaceFile(schemaName, schemas, options) {
1531
1723
  overwrite: true
1532
1724
  };
1533
1725
  }
1534
- function generateEnumFile(enumDef) {
1726
+ function generateEnumFile(enumDef, isPluginEnum = false) {
1535
1727
  const parts = [generateBaseHeader()];
1536
1728
  parts.push(formatEnum(enumDef));
1537
1729
  parts.push("\n");
1730
+ const filePath = isPluginEnum ? `plugin/${enumDef.name}.ts` : `${enumDef.name}.ts`;
1538
1731
  return {
1539
- filePath: `enum/${enumDef.name}.ts`,
1732
+ filePath,
1540
1733
  content: parts.join(""),
1541
1734
  types: [enumDef.name],
1542
- overwrite: true
1735
+ overwrite: true,
1736
+ category: "enum"
1543
1737
  };
1544
1738
  }
1545
1739
  function generateTypeAliasFile(alias) {
@@ -1547,10 +1741,11 @@ function generateTypeAliasFile(alias) {
1547
1741
  parts.push(formatTypeAlias(alias));
1548
1742
  parts.push("\n");
1549
1743
  return {
1550
- filePath: `enum/${alias.name}.ts`,
1744
+ filePath: `${alias.name}.ts`,
1551
1745
  content: parts.join(""),
1552
1746
  types: [alias.name],
1553
- overwrite: true
1747
+ overwrite: true,
1748
+ category: "enum"
1554
1749
  };
1555
1750
  }
1556
1751
  function generateModelFile(schemaName, options) {
@@ -1591,6 +1786,88 @@ function generateModelFile(schemaName, options) {
1591
1786
  // Never overwrite user models
1592
1787
  };
1593
1788
  }
1789
+ var DEFAULT_VALIDATION_MESSAGES = {
1790
+ required: {
1791
+ en: "${displayName} is required",
1792
+ ja: "${displayName}\u306F\u5FC5\u9808\u3067\u3059",
1793
+ vi: "${displayName} l\xE0 b\u1EAFt bu\u1ED9c",
1794
+ ko: "${displayName}\uC740(\uB294) \uD544\uC218\uC785\uB2C8\uB2E4",
1795
+ "zh-CN": "${displayName}\u662F\u5FC5\u586B\u9879",
1796
+ "zh-TW": "${displayName}\u70BA\u5FC5\u586B\u6B04\u4F4D",
1797
+ th: "${displayName} \u0E08\u0E33\u0E40\u0E1B\u0E47\u0E19\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E23\u0E2D\u0E01",
1798
+ es: "${displayName} es obligatorio"
1799
+ },
1800
+ minLength: {
1801
+ en: "${displayName} must be at least ${min} characters",
1802
+ ja: "${displayName}\u306F${min}\u6587\u5B57\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
1803
+ vi: "${displayName} ph\u1EA3i c\xF3 \xEDt nh\u1EA5t ${min} k\xFD t\u1EF1",
1804
+ ko: "${displayName}\uC740(\uB294) ${min}\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
1805
+ "zh-CN": "${displayName}\u81F3\u5C11\u9700\u8981${min}\u4E2A\u5B57\u7B26",
1806
+ "zh-TW": "${displayName}\u81F3\u5C11\u9700\u8981${min}\u500B\u5B57\u5143",
1807
+ th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E35\u0E2D\u0E22\u0E48\u0E32\u0E07\u0E19\u0E49\u0E2D\u0E22 ${min} \u0E15\u0E31\u0E27\u0E2D\u0E31\u0E01\u0E29\u0E23",
1808
+ es: "${displayName} debe tener al menos ${min} caracteres"
1809
+ },
1810
+ maxLength: {
1811
+ en: "${displayName} must be at most ${max} characters",
1812
+ ja: "${displayName}\u306F${max}\u6587\u5B57\u4EE5\u5185\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
1813
+ vi: "${displayName} kh\xF4ng \u0111\u01B0\u1EE3c qu\xE1 ${max} k\xFD t\u1EF1",
1814
+ ko: "${displayName}\uC740(\uB294) ${max}\uC790 \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
1815
+ "zh-CN": "${displayName}\u6700\u591A${max}\u4E2A\u5B57\u7B26",
1816
+ "zh-TW": "${displayName}\u6700\u591A${max}\u500B\u5B57\u5143",
1817
+ th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E44\u0E21\u0E48\u0E40\u0E01\u0E34\u0E19 ${max} \u0E15\u0E31\u0E27\u0E2D\u0E31\u0E01\u0E29\u0E23",
1818
+ es: "${displayName} debe tener como m\xE1ximo ${max} caracteres"
1819
+ },
1820
+ min: {
1821
+ en: "${displayName} must be at least ${min}",
1822
+ ja: "${displayName}\u306F${min}\u4EE5\u4E0A\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
1823
+ vi: "${displayName} ph\u1EA3i l\u1EDBn h\u01A1n ho\u1EB7c b\u1EB1ng ${min}",
1824
+ ko: "${displayName}\uC740(\uB294) ${min} \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4",
1825
+ "zh-CN": "${displayName}\u5FC5\u987B\u5927\u4E8E\u7B49\u4E8E${min}",
1826
+ "zh-TW": "${displayName}\u5FC5\u9808\u5927\u65BC\u7B49\u65BC${min}",
1827
+ th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E21\u0E32\u0E01\u0E01\u0E27\u0E48\u0E32\u0E2B\u0E23\u0E37\u0E2D\u0E40\u0E17\u0E48\u0E32\u0E01\u0E31\u0E1A ${min}",
1828
+ es: "${displayName} debe ser al menos ${min}"
1829
+ },
1830
+ max: {
1831
+ en: "${displayName} must be at most ${max}",
1832
+ ja: "${displayName}\u306F${max}\u4EE5\u4E0B\u3067\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
1833
+ vi: "${displayName} ph\u1EA3i nh\u1ECF h\u01A1n ho\u1EB7c b\u1EB1ng ${max}",
1834
+ ko: "${displayName}\uC740(\uB294) ${max} \uC774\uD558\uC5EC\uC57C \uD569\uB2C8\uB2E4",
1835
+ "zh-CN": "${displayName}\u5FC5\u987B\u5C0F\u4E8E\u7B49\u4E8E${max}",
1836
+ "zh-TW": "${displayName}\u5FC5\u9808\u5C0F\u65BC\u7B49\u65BC${max}",
1837
+ th: "${displayName} \u0E15\u0E49\u0E2D\u0E07\u0E19\u0E49\u0E2D\u0E22\u0E01\u0E27\u0E48\u0E32\u0E2B\u0E23\u0E37\u0E2D\u0E40\u0E17\u0E48\u0E32\u0E01\u0E31\u0E1A ${max}",
1838
+ es: "${displayName} debe ser como m\xE1ximo ${max}"
1839
+ },
1840
+ email: {
1841
+ en: "Please enter a valid email address",
1842
+ ja: "\u6709\u52B9\u306A\u30E1\u30FC\u30EB\u30A2\u30C9\u30EC\u30B9\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
1843
+ vi: "Vui l\xF2ng nh\u1EADp \u0111\u1ECBa ch\u1EC9 email h\u1EE3p l\u1EC7",
1844
+ ko: "\uC720\uD6A8\uD55C \uC774\uBA54\uC77C \uC8FC\uC18C\uB97C \uC785\uB825\uD558\uC138\uC694",
1845
+ "zh-CN": "\u8BF7\u8F93\u5165\u6709\u6548\u7684\u7535\u5B50\u90AE\u4EF6\u5730\u5740",
1846
+ "zh-TW": "\u8ACB\u8F38\u5165\u6709\u6548\u7684\u96FB\u5B50\u90F5\u4EF6\u5730\u5740",
1847
+ th: "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E01\u0E23\u0E2D\u0E01\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
1848
+ es: "Por favor, introduce una direcci\xF3n de correo electr\xF3nico v\xE1lida"
1849
+ },
1850
+ url: {
1851
+ en: "Please enter a valid URL",
1852
+ ja: "\u6709\u52B9\u306AURL\u3092\u5165\u529B\u3057\u3066\u304F\u3060\u3055\u3044",
1853
+ vi: "Vui l\xF2ng nh\u1EADp URL h\u1EE3p l\u1EC7",
1854
+ ko: "\uC720\uD6A8\uD55C URL\uC744 \uC785\uB825\uD558\uC138\uC694",
1855
+ "zh-CN": "\u8BF7\u8F93\u5165\u6709\u6548\u7684URL",
1856
+ "zh-TW": "\u8ACB\u8F38\u5165\u6709\u6548\u7684\u7DB2\u5740",
1857
+ th: "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E01\u0E23\u0E2D\u0E01 URL \u0E17\u0E35\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
1858
+ es: "Por favor, introduce una URL v\xE1lida"
1859
+ },
1860
+ pattern: {
1861
+ en: "${displayName} format is invalid",
1862
+ ja: "${displayName}\u306E\u5F62\u5F0F\u304C\u6B63\u3057\u304F\u3042\u308A\u307E\u305B\u3093",
1863
+ vi: "${displayName} kh\xF4ng \u0111\xFAng \u0111\u1ECBnh d\u1EA1ng",
1864
+ ko: "${displayName} \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
1865
+ "zh-CN": "${displayName}\u683C\u5F0F\u4E0D\u6B63\u786E",
1866
+ "zh-TW": "${displayName}\u683C\u5F0F\u4E0D\u6B63\u78BA",
1867
+ th: "\u0E23\u0E39\u0E1B\u0E41\u0E1A\u0E1A${displayName}\u0E44\u0E21\u0E48\u0E16\u0E39\u0E01\u0E15\u0E49\u0E2D\u0E07",
1868
+ es: "El formato de ${displayName} no es v\xE1lido"
1869
+ }
1870
+ };
1594
1871
  function generateCommonFile(options) {
1595
1872
  const locales = options.localeConfig?.locales ?? ["ja", "en"];
1596
1873
  const localeUnion = locales.map((l) => `'${l}'`).join(" | ");
@@ -1638,15 +1915,142 @@ export type DateString = string;
1638
1915
  overwrite: true
1639
1916
  };
1640
1917
  }
1641
- function generateIndexFile(schemas, enums, typeAliases, options) {
1918
+ function generateI18nFile(options) {
1919
+ const locales = options.localeConfig?.locales ?? ["ja", "en"];
1920
+ const defaultLocale = options.localeConfig?.defaultLocale ?? "ja";
1921
+ const fallbackLocale = options.localeConfig?.fallbackLocale ?? "en";
1922
+ const userMessages = options.localeConfig?.messages ?? {};
1923
+ const mergedMessages = {};
1924
+ for (const [key, defaultMsgs] of Object.entries(DEFAULT_VALIDATION_MESSAGES)) {
1925
+ mergedMessages[key] = {};
1926
+ for (const locale of locales) {
1927
+ if (defaultMsgs[locale]) {
1928
+ mergedMessages[key][locale] = defaultMsgs[locale];
1929
+ }
1930
+ }
1931
+ }
1932
+ for (const [key, userMsgs] of Object.entries(userMessages)) {
1933
+ if (userMsgs) {
1934
+ if (!mergedMessages[key]) {
1935
+ mergedMessages[key] = {};
1936
+ }
1937
+ for (const [locale, msg] of Object.entries(userMsgs)) {
1938
+ mergedMessages[key][locale] = msg;
1939
+ }
1940
+ }
1941
+ }
1942
+ const messagesJson = JSON.stringify(mergedMessages, null, 2);
1943
+ const content = `${generateBaseHeader()}
1944
+ import type { LocaleMap } from './common.js';
1945
+
1946
+ /**
1947
+ * Default locale for this project.
1948
+ */
1949
+ export const defaultLocale = '${defaultLocale}' as const;
1950
+
1951
+ /**
1952
+ * Fallback locale when requested locale is not found.
1953
+ */
1954
+ export const fallbackLocale = '${fallbackLocale}' as const;
1955
+
1956
+ /**
1957
+ * Supported locales in this project.
1958
+ */
1959
+ export const supportedLocales = ${JSON.stringify(locales)} as const;
1960
+
1961
+ /**
1962
+ * Validation messages for all supported locales.
1963
+ * Use getMessage(key, locale, params) to get formatted message.
1964
+ */
1965
+ export const validationMessages = ${messagesJson} as const;
1966
+
1967
+ /**
1968
+ * Get validation message for a specific key and locale.
1969
+ * Supports template placeholders: \${displayName}, \${min}, \${max}, etc.
1970
+ *
1971
+ * @param key - Message key (e.g., 'required', 'minLength')
1972
+ * @param locale - Locale code (e.g., 'ja', 'en')
1973
+ * @param params - Template parameters to replace
1974
+ * @returns Formatted message string
1975
+ *
1976
+ * @example
1977
+ * getMessage('required', 'ja', { displayName: '\u6C0F\u540D' })
1978
+ * // => '\u6C0F\u540D\u306F\u5FC5\u9808\u3067\u3059'
1979
+ */
1980
+ export function getMessage(
1981
+ key: string,
1982
+ locale: string,
1983
+ params: Record<string, string | number> = {}
1984
+ ): string {
1985
+ const messages = validationMessages[key as keyof typeof validationMessages];
1986
+ if (!messages) return key;
1987
+
1988
+ let message = (messages as LocaleMap)[locale]
1989
+ ?? (messages as LocaleMap)[fallbackLocale]
1990
+ ?? (messages as LocaleMap)[defaultLocale]
1991
+ ?? key;
1992
+
1993
+ // Replace template placeholders
1994
+ for (const [param, value] of Object.entries(params)) {
1995
+ message = message.replace(new RegExp(\`\\\\$\\{\${param}\\}\`, 'g'), String(value));
1996
+ }
1997
+
1998
+ return message;
1999
+ }
2000
+
2001
+ /**
2002
+ * Get all validation messages for a specific locale.
2003
+ *
2004
+ * @param locale - Locale code
2005
+ * @returns Object with all messages for the locale
2006
+ */
2007
+ export function getMessages(locale: string): Record<string, string> {
2008
+ const result: Record<string, string> = {};
2009
+ for (const [key, messages] of Object.entries(validationMessages)) {
2010
+ result[key] = (messages as LocaleMap)[locale]
2011
+ ?? (messages as LocaleMap)[fallbackLocale]
2012
+ ?? (messages as LocaleMap)[defaultLocale]
2013
+ ?? key;
2014
+ }
2015
+ return result;
2016
+ }
2017
+ `;
2018
+ return {
2019
+ filePath: "i18n.ts",
2020
+ content,
2021
+ types: ["validationMessages", "getMessage", "getMessages"],
2022
+ overwrite: true
2023
+ };
2024
+ }
2025
+ function generateIndexFile(schemas, enums, pluginEnums, typeAliases, options) {
1642
2026
  const parts = [generateBaseHeader()];
1643
2027
  parts.push(`// Common Types
1644
2028
  `);
1645
2029
  parts.push(`export type { LocaleMap, Locale, ValidationRule, DateTimeString, DateString } from './common.js';
1646
2030
 
1647
2031
  `);
1648
- if (enums.length > 0 || typeAliases.length > 0) {
1649
- parts.push(`// Enums
2032
+ parts.push(`// i18n (Internationalization)
2033
+ `);
2034
+ parts.push(`export {
2035
+ `);
2036
+ parts.push(` defaultLocale,
2037
+ `);
2038
+ parts.push(` fallbackLocale,
2039
+ `);
2040
+ parts.push(` supportedLocales,
2041
+ `);
2042
+ parts.push(` validationMessages,
2043
+ `);
2044
+ parts.push(` getMessage,
2045
+ `);
2046
+ parts.push(` getMessages,
2047
+ `);
2048
+ parts.push(`} from './i18n.js';
2049
+
2050
+ `);
2051
+ const enumPrefix = options.enumImportPrefix ?? "./enum";
2052
+ if (enums.length > 0) {
2053
+ parts.push(`// Schema Enums
1650
2054
  `);
1651
2055
  for (const enumDef of enums) {
1652
2056
  parts.push(`export {
@@ -1661,9 +2065,35 @@ function generateIndexFile(schemas, enums, typeAliases, options) {
1661
2065
  `);
1662
2066
  parts.push(` get${enumDef.name}Extra,
1663
2067
  `);
1664
- parts.push(`} from './enum/${enumDef.name}.js';
2068
+ parts.push(`} from '${enumPrefix}/${enumDef.name}.js';
1665
2069
  `);
1666
2070
  }
2071
+ parts.push("\n");
2072
+ }
2073
+ if (pluginEnums.length > 0) {
2074
+ parts.push(`// Plugin Enums
2075
+ `);
2076
+ for (const enumDef of pluginEnums) {
2077
+ parts.push(`export {
2078
+ `);
2079
+ parts.push(` ${enumDef.name},
2080
+ `);
2081
+ parts.push(` ${enumDef.name}Values,
2082
+ `);
2083
+ parts.push(` is${enumDef.name},
2084
+ `);
2085
+ parts.push(` get${enumDef.name}Label,
2086
+ `);
2087
+ parts.push(` get${enumDef.name}Extra,
2088
+ `);
2089
+ parts.push(`} from '${enumPrefix}/plugin/${enumDef.name}.js';
2090
+ `);
2091
+ }
2092
+ parts.push("\n");
2093
+ }
2094
+ if (typeAliases.length > 0) {
2095
+ parts.push(`// Inline Enums (Type Aliases)
2096
+ `);
1667
2097
  for (const alias of typeAliases) {
1668
2098
  parts.push(`export {
1669
2099
  `);
@@ -1677,13 +2107,13 @@ function generateIndexFile(schemas, enums, typeAliases, options) {
1677
2107
  `);
1678
2108
  parts.push(` get${alias.name}Extra,
1679
2109
  `);
1680
- parts.push(`} from './enum/${alias.name}.js';
2110
+ parts.push(`} from '${enumPrefix}/${alias.name}.js';
1681
2111
  `);
1682
2112
  }
1683
2113
  parts.push("\n");
1684
2114
  }
1685
2115
  if (options.generateZodSchemas) {
1686
- parts.push(`// Models (with Zod schemas and Create/Update types)
2116
+ parts.push(`// Models (with Zod schemas, i18n, and Create/Update types)
1687
2117
  `);
1688
2118
  for (const schema of Object.values(schemas)) {
1689
2119
  if (schema.kind === "enum") continue;
@@ -1699,13 +2129,13 @@ function generateIndexFile(schemas, enums, typeAliases, options) {
1699
2129
  `);
1700
2130
  parts.push(` ${lowerName}UpdateSchema,
1701
2131
  `);
1702
- parts.push(` ${schema.name}DisplayName,
2132
+ parts.push(` ${lowerName}I18n,
1703
2133
  `);
1704
- parts.push(` ${schema.name}PropertyDisplayNames,
2134
+ parts.push(` get${schema.name}Label,
1705
2135
  `);
1706
- parts.push(` get${schema.name}DisplayName,
2136
+ parts.push(` get${schema.name}FieldLabel,
1707
2137
  `);
1708
- parts.push(` get${schema.name}PropertyDisplayName,
2138
+ parts.push(` get${schema.name}FieldPlaceholder,
1709
2139
  `);
1710
2140
  parts.push(`} from './${schema.name}.js';
1711
2141
  `);
@@ -1751,9 +2181,47 @@ function generateIndexFile(schemas, enums, typeAliases, options) {
1751
2181
  function generateTypeScript(schemas, options = {}) {
1752
2182
  const opts = { ...DEFAULT_OPTIONS, ...options };
1753
2183
  const files = [];
2184
+ const schemasByName = /* @__PURE__ */ new Map();
2185
+ for (const schema of Object.values(schemas)) {
2186
+ const paths = schemasByName.get(schema.name) ?? [];
2187
+ paths.push(schema.relativePath ?? schema.filePath);
2188
+ schemasByName.set(schema.name, paths);
2189
+ }
2190
+ const duplicateSchemas = Array.from(schemasByName.entries()).filter(([, paths]) => paths.length > 1);
2191
+ if (duplicateSchemas.length > 0) {
2192
+ const errors = duplicateSchemas.map(
2193
+ ([name, paths]) => ` - "${name}" defined in: ${paths.join(", ")}`
2194
+ ).join("\n");
2195
+ throw new Error(
2196
+ `Duplicate schema/enum names detected. Names must be globally unique:
2197
+ ${errors}
2198
+ Hint: Rename to unique names like "Blog${duplicateSchemas[0][0]}" or "Shop${duplicateSchemas[0][0]}"`
2199
+ );
2200
+ }
2201
+ if (opts.pluginEnums && opts.pluginEnums.size > 0) {
2202
+ const conflicts = [];
2203
+ for (const schema of Object.values(schemas)) {
2204
+ if (schema.kind === "enum" && opts.pluginEnums.has(schema.name)) {
2205
+ conflicts.push(`"${schema.name}" (schema: ${schema.relativePath ?? schema.filePath}, plugin enum)`);
2206
+ }
2207
+ }
2208
+ if (conflicts.length > 0) {
2209
+ throw new Error(
2210
+ `Schema enum conflicts with plugin enum:
2211
+ - ${conflicts.join("\n - ")}
2212
+ Hint: Rename your schema enum to avoid conflict with plugin-provided enums`
2213
+ );
2214
+ }
2215
+ }
1754
2216
  const enums = generateEnums(schemas, opts);
1755
2217
  for (const enumDef of enums) {
1756
- files.push(generateEnumFile(enumDef));
2218
+ files.push(generateEnumFile(enumDef, false));
2219
+ }
2220
+ if (opts.pluginEnums && opts.pluginEnums.size > 0) {
2221
+ const pluginEnums = generatePluginEnums(opts.pluginEnums, opts);
2222
+ for (const enumDef of pluginEnums) {
2223
+ files.push(generateEnumFile(enumDef, true));
2224
+ }
1757
2225
  }
1758
2226
  const typeAliases = extractInlineEnums(schemas, opts);
1759
2227
  for (const alias of typeAliases) {
@@ -1774,12 +2242,235 @@ function generateTypeScript(schemas, options = {}) {
1774
2242
  files.push(...rulesFiles);
1775
2243
  }
1776
2244
  files.push(generateCommonFile(opts));
1777
- files.push(generateIndexFile(schemas, enums, typeAliases, opts));
2245
+ files.push(generateI18nFile(opts));
2246
+ const pluginEnumsList = opts.pluginEnums ? generatePluginEnums(opts.pluginEnums, opts) : [];
2247
+ files.push(generateIndexFile(schemas, enums, pluginEnumsList, typeAliases, opts));
1778
2248
  return files;
1779
2249
  }
2250
+
2251
+ // src/stubs.ts
2252
+ var import_fs = __toESM(require("fs"), 1);
2253
+ var import_path = __toESM(require("path"), 1);
2254
+ var import_url = require("url");
2255
+ var import_meta = {};
2256
+ var __filename = (0, import_url.fileURLToPath)(import_meta.url);
2257
+ var __dirname = import_path.default.dirname(__filename);
2258
+ var STUB_FILES = [
2259
+ // Components
2260
+ {
2261
+ stub: "JapaneseNameField.tsx.stub",
2262
+ output: "components/JapaneseNameField.tsx",
2263
+ indexExport: ""
2264
+ // Handled by components-index.ts.stub
2265
+ },
2266
+ {
2267
+ stub: "JapaneseAddressField.tsx.stub",
2268
+ output: "components/JapaneseAddressField.tsx",
2269
+ indexExport: ""
2270
+ // Handled by components-index.ts.stub
2271
+ },
2272
+ {
2273
+ stub: "JapaneseBankField.tsx.stub",
2274
+ output: "components/JapaneseBankField.tsx",
2275
+ indexExport: ""
2276
+ // Handled by components-index.ts.stub
2277
+ },
2278
+ {
2279
+ stub: "components-index.ts.stub",
2280
+ output: "components/index.ts",
2281
+ indexExport: ""
2282
+ // This IS the index
2283
+ },
2284
+ // Hooks
2285
+ {
2286
+ stub: "use-form-mutation.ts.stub",
2287
+ output: "hooks/use-form-mutation.ts",
2288
+ indexExport: `export { useFormMutation } from './use-form-mutation';
2289
+ `
2290
+ },
2291
+ // Lib
2292
+ {
2293
+ stub: "zod-i18n.ts.stub",
2294
+ output: "lib/zod-i18n.ts",
2295
+ indexExport: `export { setZodLocale, getZodLocale, getZodMessage } from './zod-i18n';
2296
+ `
2297
+ },
2298
+ {
2299
+ stub: "form-validation.ts.stub",
2300
+ output: "lib/form-validation.ts",
2301
+ indexExport: `export { zodRule, requiredRule } from './form-validation';
2302
+ export * from './rules';
2303
+ `
2304
+ },
2305
+ // Rules
2306
+ {
2307
+ stub: "rules/kana.ts.stub",
2308
+ output: "lib/rules/kana.ts",
2309
+ indexExport: ""
2310
+ // Will be handled by rules/index.ts
2311
+ },
2312
+ {
2313
+ stub: "rules/index.ts.stub",
2314
+ output: "lib/rules/index.ts",
2315
+ indexExport: ""
2316
+ // Already exported via form-validation
2317
+ }
2318
+ ];
2319
+ function copyStubs(options) {
2320
+ const { targetDir, skipIfExists = true } = options;
2321
+ const stubsDir = import_path.default.join(__dirname, "..", "stubs");
2322
+ const result = { copied: [], skipped: [] };
2323
+ const directories = /* @__PURE__ */ new Map();
2324
+ for (const { stub, output, indexExport } of STUB_FILES) {
2325
+ const stubPath = import_path.default.join(stubsDir, stub);
2326
+ const outputPath = import_path.default.join(targetDir, output);
2327
+ const outputDir = import_path.default.dirname(outputPath);
2328
+ const dirName = import_path.default.dirname(output).split("/")[0];
2329
+ if (!directories.has(dirName)) {
2330
+ directories.set(dirName, "");
2331
+ }
2332
+ directories.set(dirName, directories.get(dirName) + indexExport);
2333
+ if (!import_fs.default.existsSync(outputDir)) {
2334
+ import_fs.default.mkdirSync(outputDir, { recursive: true });
2335
+ }
2336
+ if (skipIfExists && import_fs.default.existsSync(outputPath)) {
2337
+ result.skipped.push(output);
2338
+ continue;
2339
+ }
2340
+ if (import_fs.default.existsSync(stubPath)) {
2341
+ const content = import_fs.default.readFileSync(stubPath, "utf-8");
2342
+ import_fs.default.writeFileSync(outputPath, content);
2343
+ result.copied.push(output);
2344
+ }
2345
+ }
2346
+ for (const [dirName, exports2] of directories) {
2347
+ const indexPath = import_path.default.join(targetDir, dirName, "index.ts");
2348
+ if (skipIfExists && import_fs.default.existsSync(indexPath)) {
2349
+ continue;
2350
+ }
2351
+ import_fs.default.writeFileSync(indexPath, exports2);
2352
+ result.copied.push(`${dirName}/index.ts`);
2353
+ }
2354
+ return result;
2355
+ }
2356
+ function getStubPaths() {
2357
+ return STUB_FILES.map((s) => s.output);
2358
+ }
2359
+
2360
+ // src/ai-guides/generator.ts
2361
+ var import_node_fs = require("fs");
2362
+ var import_node_path = require("path");
2363
+ var import_node_url = require("url");
2364
+ var import_meta2 = {};
2365
+ var __filename2 = (0, import_node_url.fileURLToPath)(import_meta2.url);
2366
+ var __dirname2 = (0, import_node_path.dirname)(__filename2);
2367
+ function getStubsDir() {
2368
+ const devPath = (0, import_node_path.resolve)(__dirname2, "../../stubs/ai-guides");
2369
+ if ((0, import_node_fs.existsSync)(devPath)) {
2370
+ return devPath;
2371
+ }
2372
+ const distPath = (0, import_node_path.resolve)(__dirname2, "../stubs/ai-guides");
2373
+ if ((0, import_node_fs.existsSync)(distPath)) {
2374
+ return distPath;
2375
+ }
2376
+ throw new Error("AI guides stubs not found");
2377
+ }
2378
+ function extractTypescriptBasePath(typescriptPath) {
2379
+ if (!typescriptPath) return "src";
2380
+ const normalized = typescriptPath.replace(/\\/g, "/");
2381
+ const parts = normalized.split("/").filter(Boolean);
2382
+ if (parts.length > 1) {
2383
+ return parts.slice(0, -1).join("/");
2384
+ }
2385
+ return "src";
2386
+ }
2387
+ function replacePlaceholders(content, basePath) {
2388
+ return content.replace(/\{\{TYPESCRIPT_BASE\}\}/g, basePath);
2389
+ }
2390
+ function copyStubs2(srcDir, destDir, transform) {
2391
+ const writtenFiles = [];
2392
+ if (!(0, import_node_fs.existsSync)(srcDir)) {
2393
+ return writtenFiles;
2394
+ }
2395
+ if (!(0, import_node_fs.existsSync)(destDir)) {
2396
+ (0, import_node_fs.mkdirSync)(destDir, { recursive: true });
2397
+ }
2398
+ const entries = (0, import_node_fs.readdirSync)(srcDir, { withFileTypes: true });
2399
+ for (const entry of entries) {
2400
+ const srcPath = (0, import_node_path.join)(srcDir, entry.name);
2401
+ if (entry.isDirectory()) {
2402
+ const subDestDir = (0, import_node_path.join)(destDir, entry.name);
2403
+ const subFiles = copyStubs2(srcPath, subDestDir, transform);
2404
+ writtenFiles.push(...subFiles);
2405
+ } else if (entry.isFile() && entry.name.endsWith(".stub")) {
2406
+ const destName = entry.name.slice(0, -5);
2407
+ const destPath = (0, import_node_path.join)(destDir, destName);
2408
+ let content = (0, import_node_fs.readFileSync)(srcPath, "utf-8");
2409
+ if (transform) {
2410
+ content = transform(content);
2411
+ }
2412
+ (0, import_node_fs.writeFileSync)(destPath, content);
2413
+ writtenFiles.push(destPath);
2414
+ }
2415
+ }
2416
+ return writtenFiles;
2417
+ }
2418
+ function generateAIGuides(rootDir, options = {}) {
2419
+ const stubsDir = getStubsDir();
2420
+ const basePath = options.typescriptBasePath || extractTypescriptBasePath(options.typescriptPath);
2421
+ const result = {
2422
+ claudeGuides: 0,
2423
+ claudeChecklists: 0,
2424
+ cursorRules: 0,
2425
+ files: []
2426
+ };
2427
+ const claudeSrcDir = (0, import_node_path.join)(stubsDir, "react");
2428
+ const claudeDestDir = (0, import_node_path.resolve)(rootDir, ".claude/omnify/guides/react");
2429
+ if ((0, import_node_fs.existsSync)(claudeSrcDir)) {
2430
+ const files = copyStubs2(claudeSrcDir, claudeDestDir);
2431
+ result.claudeGuides = files.length;
2432
+ result.files.push(...files);
2433
+ }
2434
+ const claudeChecklistsSrcDir = (0, import_node_path.join)(stubsDir, "checklists");
2435
+ const claudeChecklistsDestDir = (0, import_node_path.resolve)(rootDir, ".claude/omnify/checklists");
2436
+ if ((0, import_node_fs.existsSync)(claudeChecklistsSrcDir)) {
2437
+ const files = copyStubs2(claudeChecklistsSrcDir, claudeChecklistsDestDir);
2438
+ result.claudeChecklists = files.length;
2439
+ result.files.push(...files);
2440
+ }
2441
+ const cursorSrcDir = (0, import_node_path.join)(stubsDir, "cursor");
2442
+ const cursorDestDir = (0, import_node_path.resolve)(rootDir, ".cursor/rules/omnify");
2443
+ if ((0, import_node_fs.existsSync)(cursorSrcDir)) {
2444
+ const files = copyStubs2(
2445
+ cursorSrcDir,
2446
+ cursorDestDir,
2447
+ (content) => replacePlaceholders(content, basePath)
2448
+ );
2449
+ result.cursorRules = files.length;
2450
+ result.files.push(...files);
2451
+ }
2452
+ return result;
2453
+ }
2454
+ function shouldGenerateAIGuides(rootDir) {
2455
+ const claudeDir = (0, import_node_path.resolve)(rootDir, ".claude/omnify/guides/react");
2456
+ const cursorDir = (0, import_node_path.resolve)(rootDir, ".cursor/rules/omnify");
2457
+ if (!(0, import_node_fs.existsSync)(claudeDir) || !(0, import_node_fs.existsSync)(cursorDir)) {
2458
+ return true;
2459
+ }
2460
+ try {
2461
+ const claudeFiles = (0, import_node_fs.readdirSync)(claudeDir);
2462
+ const cursorFiles = (0, import_node_fs.readdirSync)(cursorDir);
2463
+ const hasReactRules = cursorFiles.some((f) => f.startsWith("react"));
2464
+ return claudeFiles.length === 0 || !hasReactRules;
2465
+ } catch {
2466
+ return true;
2467
+ }
2468
+ }
1780
2469
  // Annotate the CommonJS export names for ESM import in node:
1781
2470
  0 && (module.exports = {
1782
2471
  DEFAULT_VALIDATION_TEMPLATES,
2472
+ STUB_FILES,
2473
+ copyStubs,
1783
2474
  enumToUnionType,
1784
2475
  extractInlineEnums,
1785
2476
  formatEnum,
@@ -1787,18 +2478,23 @@ function generateTypeScript(schemas, options = {}) {
1787
2478
  formatProperty,
1788
2479
  formatTypeAlias,
1789
2480
  formatValidationMessage,
2481
+ generateAIGuides,
1790
2482
  generateEnums,
1791
2483
  generateInterfaces,
1792
2484
  generateModelRules,
2485
+ generatePluginEnums,
1793
2486
  generateRulesFiles,
1794
2487
  generateTypeScript,
1795
2488
  generateTypeScriptFiles,
1796
2489
  getPropertyType,
2490
+ getStubPaths,
1797
2491
  getValidationMessages,
1798
2492
  mergeValidationTemplates,
2493
+ pluginEnumToTSEnum,
1799
2494
  propertyToTSProperty,
1800
2495
  schemaToEnum,
1801
2496
  schemaToInterface,
2497
+ shouldGenerateAIGuides,
1802
2498
  toEnumMemberName,
1803
2499
  toEnumName,
1804
2500
  toInterfaceName,