Add static type reference model generation to all apps to prevent using magic strings (#1404)

This commit is contained in:
Leendert de Borst
2025-12-22 13:26:12 +01:00
parent cc28fd8cc2
commit 40050983e5
15 changed files with 730 additions and 79 deletions
@@ -0,0 +1,67 @@
// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
package net.aliasvault.app.vaultstore.models
/**
* Field types for rendering and validation.
*/
object FieldType {
/**
* Text field type.
*/
const val TEXT = "Text"
/**
* Password field type.
*/
const val PASSWORD = "Password"
/**
* Hidden field type.
*/
const val HIDDEN = "Hidden"
/**
* Email field type.
*/
const val EMAIL = "Email"
/**
* URL field type.
*/
const val U_R_L = "URL"
/**
* Date field type.
*/
const val DATE = "Date"
/**
* Number field type.
*/
const val NUMBER = "Number"
/**
* Phone field type.
*/
const val PHONE = "Phone"
/**
* TextArea field type.
*/
const val TEXT_AREA = "TextArea"
/**
* All available field types.
*/
val all = listOf(TEXT, PASSWORD, HIDDEN, EMAIL, U_R_L, DATE, NUMBER, PHONE, TEXT_AREA)
/**
* Checks if a string value is a valid field type.
*/
fun isValid(value: String?): Boolean {
return value in all
}
}
@@ -0,0 +1,42 @@
// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
package net.aliasvault.app.vaultstore.models
/**
* Item types supported by the vault.
*/
object ItemType {
/**
* Login item type.
*/
const val LOGIN = "Login"
/**
* Alias item type.
*/
const val ALIAS = "Alias"
/**
* CreditCard item type.
*/
const val CREDIT_CARD = "CreditCard"
/**
* Note item type.
*/
const val NOTE = "Note"
/**
* All available item types.
*/
val all = listOf(LOGIN, ALIAS, CREDIT_CARD, NOTE)
/**
* Checks if a string value is a valid item type.
*/
fun isValid(value: String?): Boolean {
return value in all
}
}
@@ -0,0 +1,44 @@
// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
import Foundation
/// Field types for rendering and validation.
public struct FieldType {
/// Text field type.
public static let text = "Text"
/// Password field type.
public static let password = "Password"
/// Hidden field type.
public static let hidden = "Hidden"
/// Email field type.
public static let email = "Email"
/// URL field type.
public static let uRL = "URL"
/// Date field type.
public static let date = "Date"
/// Number field type.
public static let number = "Number"
/// Phone field type.
public static let phone = "Phone"
/// TextArea field type.
public static let textArea = "TextArea"
/// All available field types.
public static let all = [text, password, hidden, email, uRL, date, number, phone, textArea]
/// Checks if a string value is a valid field type.
public static func isValid(_ value: String?) -> Bool {
guard let value = value else { return false }
return all.contains(value)
}
}
@@ -0,0 +1,29 @@
// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
import Foundation
/// Item types supported by the vault.
public struct ItemType {
/// Login item type.
public static let login = "Login"
/// Alias item type.
public static let alias = "Alias"
/// CreditCard item type.
public static let creditCard = "CreditCard"
/// Note item type.
public static let note = "Note"
/// All available item types.
public static let all = [login, alias, creditCard, note]
/// Checks if a string value is a valid item type.
public static func isValid(_ value: String?) -> Bool {
guard let value = value else { return false }
return all.contains(value)
}
}
@@ -5,8 +5,8 @@
@* FieldBlock component - renders a single field based on its type *@
@switch (Field.FieldType)
{
case "Password":
case "Hidden":
case FieldType.Password:
case FieldType.Hidden:
<div class="col-span-6">
<CopyPastePasswordFormRow
Id="@GetFieldId()"
@@ -14,7 +14,7 @@
Value="@(Field.Value ?? string.Empty)" />
</div>
break;
case "TextArea":
case FieldType.TextArea:
<div class="col-span-6">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@GetLabel()</label>
<div class="p-3 bg-gray-50 dark:bg-gray-700 rounded-lg text-gray-900 dark:text-white whitespace-pre-wrap text-sm">
@@ -22,7 +22,7 @@
</div>
</div>
break;
case "URL":
case FieldType.URL:
<div class="col-span-6">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@GetLabel()</label>
@if (!string.IsNullOrEmpty(Field.Value))
@@ -40,7 +40,7 @@
}
</div>
break;
case "Date":
case FieldType.Date:
<div class="@(FullWidth ? "col-span-6" : "col-span-6 sm:col-span-3")">
<CopyPasteFormRow
Id="@GetFieldId()"
@@ -1,4 +1,5 @@
@using AliasVault.Client.Main.Models
@using AliasClientDb.Models
@inject NavigationManager NavigationManager
@* ItemCard component - displays an item in a card format with appropriate icon and information *@
@@ -67,7 +68,7 @@
/// </summary>
private string GetDisplayText()
{
if (Obj.ItemType == ItemTypes.CreditCard)
if (Obj.ItemType == ItemType.CreditCard)
{
// For credit cards, show masked card number if available
if (!string.IsNullOrEmpty(Obj.CardNumber) && Obj.CardNumber.Length >= 4)
@@ -78,7 +79,7 @@
return string.Empty;
}
if (Obj.ItemType == ItemTypes.Note)
if (Obj.ItemType == ItemType.Note)
{
// For notes, no secondary text needed
return string.Empty;
@@ -1,12 +1,14 @@
@using AliasVault.Client.Main.Models
@using AliasClientDb.Models
@using AliasVault.Client.Main.Utilities
@using ItemTypeClass = AliasClientDb.Models.ItemType
@* ItemIcon component - displays contextually appropriate icons based on item type *@
@* For Login/Alias: Uses the Logo field if available, falls back to key placeholder *@
@* For CreditCard: Shows card brand icons (Visa, MC, Amex, Discover) based on card number *@
@* For Note: Shows a document/note icon *@
@if (ItemType == ItemTypes.Note)
@if (ItemType == ItemTypeClass.Note)
{
@* Note icon - document style *@
<svg class="@SizeClass flex-shrink-0" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
@@ -17,7 +19,7 @@
<rect x="10" y="22" width="8" height="1.5" rx="0.75" fill="#ffe096"/>
</svg>
}
else if (ItemType == ItemTypes.CreditCard)
else if (ItemType == ItemTypeClass.CreditCard)
{
@* Credit card icon - detect brand and show appropriate icon *@
@switch (CardBrandDetector.Detect(CardNumber))
@@ -92,7 +94,7 @@ else
/// Gets or sets the item type (Login, Alias, CreditCard, Note).
/// </summary>
[Parameter]
public string ItemType { get; set; } = ItemTypes.Login;
public string ItemType { get; set; } = ItemTypeClass.Login;
/// <summary>
/// Gets or sets the logo bytes for Login/Alias items.
@@ -1,4 +1,6 @@
@using AliasVault.Client.Main.Models
@using AliasClientDb.Models
@using ItemTypeClass = AliasClientDb.Models.ItemType
@using Microsoft.Extensions.Localization
@inject IStringLocalizerFactory LocalizerFactory
@@ -22,7 +24,7 @@
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
@if (SelectedType == ItemTypes.Alias && !IsEditMode && OnRegenerateAlias.HasDelegate)
@if (SelectedType == ItemType.Alias && !IsEditMode && OnRegenerateAlias.HasDelegate)
{
<button type="button"
@onclick="HandleRegenerate"
@@ -41,7 +43,7 @@
{
<div class="fixed inset-0 z-10" @onclick="CloseDropdown"></div>
<div class="absolute z-20 mt-1 w-full bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-lg overflow-hidden">
@foreach (var itemType in ItemTypes.All)
@foreach (var itemType in ItemType.All)
{
<button type="button"
@onclick="() => SelectType(itemType)"
@@ -72,7 +74,7 @@
/// The currently selected item type.
/// </summary>
[Parameter]
public string SelectedType { get; set; } = ItemTypes.Login;
public string SelectedType { get; set; } = ItemTypeClass.Login;
/// <summary>
/// Callback when item type changes.
@@ -135,10 +137,10 @@
{
return itemType switch
{
ItemTypes.Login => Localizer["TypeLogin"],
ItemTypes.Alias => Localizer["TypeAlias"],
ItemTypes.CreditCard => Localizer["TypeCreditCard"],
ItemTypes.Note => Localizer["TypeNote"],
ItemType.Login => Localizer["TypeLogin"],
ItemType.Alias => Localizer["TypeAlias"],
ItemType.CreditCard => Localizer["TypeCreditCard"],
ItemType.Note => Localizer["TypeNote"],
_ => itemType
};
}
@@ -147,10 +149,10 @@
{
var svg = itemType switch
{
ItemTypes.Login => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>""",
ItemTypes.Alias => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>""",
ItemTypes.CreditCard => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>""",
ItemTypes.Note => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>""",
ItemType.Login => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>""",
ItemType.Alias => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>""",
ItemType.CreditCard => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>""",
ItemType.Note => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>""",
_ => """<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>"""
};
return new MarkupString(svg);
@@ -10,6 +10,7 @@
@implements IAsyncDisposable
@using AliasClientDb
@using AliasClientDb.Models
@using ItemTypeClass = AliasClientDb.Models.ItemType
<button @ref="buttonRef" @onclick="TogglePopup" id="quickIdentityButton" class="px-4 py-2 text-sm font-medium text-white bg-gradient-to-r from-primary-500 to-primary-600 hover:from-primary-600 hover:to-primary-700 focus:outline-none dark:from-primary-400 dark:to-primary-500 dark:hover:from-primary-500 dark:hover:to-primary-600 rounded-md shadow-sm transition duration-150 ease-in-out transform hover:scale-105 active:scale-95 focus:shadow-outline">
@Localizer["NewAliasButtonShort"] <span class="hidden md:inline">@Localizer["NewAliasButtonText"].Value.Substring(1).Trim()</span>
@@ -23,7 +24,7 @@
@* Item Type Selector *@
<div class="mb-4">
<div class="flex gap-1">
@foreach (var itemType in ItemTypes.All)
@foreach (var itemType in ItemType.All)
{
<button type="button"
@onclick="() => SelectItemType(itemType)"
@@ -43,7 +44,7 @@
<EditFormRow Id="serviceName" Label="@Localizer["NameLabel"]" Placeholder="@GetNamePlaceholder()" @bind-Value="Model.ServiceName"></EditFormRow>
<ValidationMessage For="() => Model.ServiceName"/>
</div>
@if (Model.ItemType == ItemTypes.Login || Model.ItemType == ItemTypes.Alias)
@if (Model.ItemType == ItemType.Login || Model.ItemType == ItemType.Alias)
{
<div class="mb-4">
<EditFormRow Id="serviceUrl" Label="@Localizer["WebsiteUrlLabel"]" OnFocus="OnFocusUrlInput" @bind-Value="Model.ServiceUrl"></EditFormRow>
@@ -52,7 +53,7 @@
}
<div class="flex justify-between items-center">
<button id="quickIdentitySubmit" type="submit" class="@GetSubmitButtonClasses() text-white font-bold py-2 px-4 rounded flex items-center gap-2">
@if (Model.ItemType == ItemTypes.Alias)
@if (Model.ItemType == ItemType.Alias)
{
@Localizer["CreateButton"]
}
@@ -149,7 +150,7 @@
// Clear the input fields and default to Alias type
Model = new();
Model.ItemType = ItemTypes.Alias;
Model.ItemType = ItemType.Alias;
Model.ServiceUrl = ItemService.DefaultServiceUrl;
await UpdatePopupStyle();
@@ -194,7 +195,7 @@
/// </summary>
private async Task HandleFormSubmit()
{
if (Model.ItemType == ItemTypes.Alias)
if (Model.ItemType == ItemType.Alias)
{
await CreateAlias();
}
@@ -221,7 +222,7 @@
var item = new Item
{
Name = Model.ServiceName,
ItemType = ItemTypes.Alias,
ItemType = ItemType.Alias,
FieldValues = new List<FieldValue>()
};
@@ -279,10 +280,10 @@
{
return Model.ItemType switch
{
ItemTypes.Alias => Localizer["CreateNewAliasTitle"],
ItemTypes.Login => Localizer["CreateNewLoginTitle"],
ItemTypes.CreditCard => Localizer["CreateNewCreditCardTitle"],
ItemTypes.Note => Localizer["CreateNewNoteTitle"],
ItemType.Alias => Localizer["CreateNewAliasTitle"],
ItemType.Login => Localizer["CreateNewLoginTitle"],
ItemType.CreditCard => Localizer["CreateNewCreditCardTitle"],
ItemType.Note => Localizer["CreateNewNoteTitle"],
_ => Localizer["CreateNewAliasTitle"]
};
}
@@ -294,10 +295,10 @@
{
return Model.ItemType switch
{
ItemTypes.Login => Localizer["NamePlaceholderLogin"],
ItemTypes.Alias => Localizer["NamePlaceholderAlias"],
ItemTypes.CreditCard => Localizer["NamePlaceholderCard"],
ItemTypes.Note => Localizer["NamePlaceholderNote"],
ItemType.Login => Localizer["NamePlaceholderLogin"],
ItemType.Alias => Localizer["NamePlaceholderAlias"],
ItemType.CreditCard => Localizer["NamePlaceholderCard"],
ItemType.Note => Localizer["NamePlaceholderNote"],
_ => Localizer["NamePlaceholderLogin"]
};
}
@@ -307,7 +308,7 @@
/// </summary>
private string GetSubmitButtonClasses()
{
return Model.ItemType == ItemTypes.Alias
return Model.ItemType == ItemType.Alias
? "bg-green-600 hover:bg-green-700"
: "bg-primary-600 hover:bg-primary-700";
}
@@ -319,10 +320,10 @@
{
return itemType switch
{
ItemTypes.Login => Localizer["TypeLogin"],
ItemTypes.Alias => Localizer["TypeAlias"],
ItemTypes.CreditCard => Localizer["TypeCard"],
ItemTypes.Note => Localizer["TypeNote"],
ItemType.Login => Localizer["TypeLogin"],
ItemType.Alias => Localizer["TypeAlias"],
ItemType.CreditCard => Localizer["TypeCard"],
ItemType.Note => Localizer["TypeNote"],
_ => itemType
};
}
@@ -334,10 +335,10 @@
{
var svg = itemType switch
{
ItemTypes.Login => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>""",
ItemTypes.Alias => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>""",
ItemTypes.CreditCard => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>""",
ItemTypes.Note => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>""",
ItemType.Login => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" /></svg>""",
ItemType.Alias => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>""",
ItemType.CreditCard => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z" /></svg>""",
ItemType.Note => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>""",
_ => """<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>"""
};
return new MarkupString(svg);
@@ -368,7 +369,7 @@
/// <summary>
/// The item type to create.
/// </summary>
public string ItemType { get; set; } = ItemTypes.Alias;
public string ItemType { get; set; } = ItemTypeClass.Alias;
/// <summary>
/// The service name.
@@ -71,7 +71,7 @@ else
}
@* Notes Section - When visible (left side for non-Note types) *@
@if (ShouldShowField(FieldKey.NotesContent) && Obj.ItemType != ItemTypes.Note)
@if (ShouldShowField(FieldKey.NotesContent) && Obj.ItemType != ItemType.Note)
{
<div class="col-span-1 md:col-span-1 lg:col-span-1">
<RemovableSection CanRemove="@CanRemoveField(FieldKey.NotesContent)" OnRemove="() => RemoveOptionalField(FieldKey.NotesContent)">
@@ -206,7 +206,7 @@ else
}
</div>
}
else if (Obj.ItemType == ItemTypes.Login && !ManuallyAddedFields.Contains(FieldKey.LoginEmail))
else if (Obj.ItemType == ItemType.Login && !ManuallyAddedFields.Contains(FieldKey.LoginEmail))
{
@* Show + Email button for Login type when email is not visible *@
<div class="col-span-6">
@@ -232,7 +232,7 @@ else
}
@* Alias Identity Section - For Alias type *@
@if (Obj.ItemType == ItemTypes.Alias)
@if (Obj.ItemType == ItemType.Alias)
{
<div class="col-span-1 md:col-span-1 lg:col-span-2">
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
@@ -276,7 +276,7 @@ else
}
@* Credit Card Section - For CreditCard type *@
@if (Obj.ItemType == ItemTypes.CreditCard)
@if (Obj.ItemType == ItemType.CreditCard)
{
<div class="col-span-1 md:col-span-1 lg:col-span-2">
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
@@ -318,7 +318,7 @@ else
}
@* Notes Section - For Note type (main content area) *@
@if (Obj.ItemType == ItemTypes.Note)
@if (Obj.ItemType == ItemType.Note)
{
<div class="col-span-1 md:col-span-1 lg:col-span-2">
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
@@ -519,7 +519,7 @@ else
/// </summary>
private bool HasLoginFields()
{
return Obj.ItemType == ItemTypes.Login || Obj.ItemType == ItemTypes.Alias;
return Obj.ItemType == ItemType.Login || Obj.ItemType == ItemType.Alias;
}
/// <summary>
@@ -789,7 +789,7 @@ else
}
// Generate alias for Alias type in create mode
if (newType == ItemTypes.Alias && !EditMode && !HasAliasValues())
if (newType == ItemType.Alias && !EditMode && !HasAliasValues())
{
await GenerateRandomAlias();
}
@@ -6,6 +6,7 @@
@using AliasVault.RazorComponents.Tables
@using AliasVault.Client.Main.Models
@using AliasVault.Client.Main.Components.Items
@using AliasClientDb.Models
@using AliasVault.Client.Main.Components.Folders
@using Microsoft.Extensions.Localization
@implements IAsyncDisposable
@@ -428,10 +429,10 @@ else
{
ItemFilterType.Passkeys => filtered.Where(x => x.HasPasskey),
ItemFilterType.Attachments => filtered.Where(x => x.HasAttachment),
ItemFilterType.Login => filtered.Where(x => x.ItemType == ItemTypes.Login),
ItemFilterType.Alias => filtered.Where(x => x.ItemType == ItemTypes.Alias),
ItemFilterType.CreditCard => filtered.Where(x => x.ItemType == ItemTypes.CreditCard),
ItemFilterType.Note => filtered.Where(x => x.ItemType == ItemTypes.Note),
ItemFilterType.Login => filtered.Where(x => x.ItemType == ItemType.Login),
ItemFilterType.Alias => filtered.Where(x => x.ItemType == ItemType.Alias),
ItemFilterType.CreditCard => filtered.Where(x => x.ItemType == ItemType.CreditCard),
ItemFilterType.Note => filtered.Where(x => x.ItemType == ItemType.Note),
_ => filtered, // All
};
@@ -43,7 +43,7 @@ else
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
<div class="items-center flex space-x-4">
<ItemIcon
ItemType="@(Item.ItemType ?? ItemTypes.Login)"
ItemType="@(Item.ItemType ?? ItemType.Login)"
Logo="@Item.Logo?.FileData"
CardNumber="@GetCardNumber()"
AltText="@(Item.Name ?? "Item")"
@@ -78,7 +78,7 @@ else
</div>
@* Recent emails - only for Login/Alias *@
@if ((Item.ItemType == ItemTypes.Login || Item.ItemType == ItemTypes.Alias) && !string.IsNullOrEmpty(GetEmailAddress()))
@if ((Item.ItemType == ItemType.Login || Item.ItemType == ItemType.Alias) && !string.IsNullOrEmpty(GetEmailAddress()))
{
<RecentEmails EmailAddress="@GetEmailAddress()" />
}
@@ -90,7 +90,7 @@ else
}
@* Notes - if present (left column for non-Note types) *@
@if (Item.ItemType != ItemTypes.Note && GroupedFields.TryGetValue(FieldCategory.Notes, out var notesFields) && notesFields.Count > 0)
@if (Item.ItemType != ItemType.Note && GroupedFields.TryGetValue(FieldCategory.Notes, out var notesFields) && notesFields.Count > 0)
{
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["NotesSection"]</h3>
@@ -113,7 +113,7 @@ else
@* Right column *@
<div class="col-span-1 md:col-span-2 lg:col-span-2">
@* Login fields - for Login/Alias *@
@if ((Item.ItemType == ItemTypes.Login || Item.ItemType == ItemTypes.Alias) &&
@if ((Item.ItemType == ItemType.Login || Item.ItemType == ItemType.Alias) &&
GroupedFields.TryGetValue(FieldCategory.Login, out var loginFields) && loginFields.Count > 0)
{
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
@@ -181,7 +181,7 @@ else
}
@* Alias fields - for Alias type only *@
@if (Item.ItemType == ItemTypes.Alias &&
@if (Item.ItemType == ItemType.Alias &&
GroupedFields.TryGetValue(FieldCategory.Alias, out var aliasFields) && aliasFields.Count > 0)
{
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
@@ -209,7 +209,7 @@ else
}
@* Card fields - for CreditCard type *@
@if (Item.ItemType == ItemTypes.CreditCard &&
@if (Item.ItemType == ItemType.CreditCard &&
GroupedFields.TryGetValue(FieldCategory.Card, out var cardFields) && cardFields.Count > 0)
{
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
@@ -226,7 +226,7 @@ else
}
@* Notes - for Note type (main content area) *@
@if (Item.ItemType == ItemTypes.Note && GroupedFields.TryGetValue(FieldCategory.Notes, out var noteTypeNotesFields) && noteTypeNotesFields.Count > 0)
@if (Item.ItemType == ItemType.Note && GroupedFields.TryGetValue(FieldCategory.Notes, out var noteTypeNotesFields) && noteTypeNotesFields.Count > 0)
{
<div class="p-4 mb-4 bg-white border border-gray-200 rounded-lg shadow-sm 2xl:col-span-2 dark:border-gray-700 sm:p-6 dark:bg-gray-800">
<h3 class="mb-4 text-xl font-semibold dark:text-white">@Localizer["NotesSection"]</h3>
@@ -0,0 +1,73 @@
// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
#nullable enable
namespace AliasClientDb.Models;
/// <summary>
/// Field types for rendering and validation.
/// </summary>
public static class FieldType
{
/// <summary>
/// Text field type.
/// </summary>
public const string Text = "Text";
/// <summary>
/// Password field type.
/// </summary>
public const string Password = "Password";
/// <summary>
/// Hidden field type.
/// </summary>
public const string Hidden = "Hidden";
/// <summary>
/// Email field type.
/// </summary>
public const string Email = "Email";
/// <summary>
/// URL field type.
/// </summary>
public const string URL = "URL";
/// <summary>
/// Date field type.
/// </summary>
public const string Date = "Date";
/// <summary>
/// Number field type.
/// </summary>
public const string Number = "Number";
/// <summary>
/// Phone field type.
/// </summary>
public const string Phone = "Phone";
/// <summary>
/// TextArea field type.
/// </summary>
public const string TextArea = "TextArea";
/// <summary>
/// All available field types.
/// </summary>
public static readonly string[] All = { Text, Password, Hidden, Email, URL, Date, Number, Phone, TextArea };
/// <summary>
/// Checks if a string value is a valid field type.
/// </summary>
/// <param name="value">The value to check.</param>
/// <returns>True if the value is a valid field type.</returns>
public static bool IsValid(string? value)
{
return value == Text || value == Password || value == Hidden || value == Email || value == URL || value == Date || value == Number || value == Phone || value == TextArea;
}
}
@@ -1,34 +1,33 @@
//-----------------------------------------------------------------------
// <copyright file="ItemTypes.cs" company="aliasvault">
// Copyright (c) aliasvault. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
namespace AliasVault.Client.Main.Models;
#nullable enable
namespace AliasClientDb.Models;
/// <summary>
/// Constants for item types.
/// Item types supported by the vault.
/// </summary>
public static class ItemTypes
public static class ItemType
{
/// <summary>
/// Login item type - username/password credentials.
/// Login item type.
/// </summary>
public const string Login = "Login";
/// <summary>
/// Alias item type - login with auto-generated identity.
/// Alias item type.
/// </summary>
public const string Alias = "Alias";
/// <summary>
/// Credit card item type - payment card information.
/// CreditCard item type.
/// </summary>
public const string CreditCard = "CreditCard";
/// <summary>
/// Note item type - secure notes only.
/// Note item type.
/// </summary>
public const string Note = "Note";
+393 -3
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env node
/**
* Generates FieldKey constants and SystemFieldRegistry for C#, Swift, and Kotlin from TypeScript source.
* Generates FieldKey, FieldType, ItemType constants and SystemFieldRegistry for C#, Swift, and Kotlin from TypeScript source.
* All type definitions are dynamically extracted from the TypeScript source files.
*/
@@ -10,11 +10,18 @@ const path = require('path');
// Paths
const REPO_ROOT = path.join(__dirname, '../../..');
const TS_SOURCE = path.join(REPO_ROOT, 'core/models/src/vault/FieldKey.ts');
const TS_ITEM_SOURCE = path.join(REPO_ROOT, 'core/models/src/vault/Item.ts');
const TS_REGISTRY_SOURCE = path.join(REPO_ROOT, 'core/models/src/vault/SystemFieldRegistry.ts');
const CS_OUTPUT = path.join(REPO_ROOT, 'apps/server/Databases/AliasClientDb/Models/FieldKey.cs');
const CS_FIELD_TYPE_OUTPUT = path.join(REPO_ROOT, 'apps/server/Databases/AliasClientDb/Models/FieldType.cs');
const CS_ITEM_TYPE_OUTPUT = path.join(REPO_ROOT, 'apps/server/Databases/AliasClientDb/Models/ItemType.cs');
const CS_REGISTRY_OUTPUT = path.join(REPO_ROOT, 'apps/server/Databases/AliasClientDb/Models/SystemFieldRegistry.cs');
const SWIFT_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/ios/VaultModels/FieldKey.swift');
const SWIFT_FIELD_TYPE_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/ios/VaultModels/FieldType.swift');
const SWIFT_ITEM_TYPE_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/ios/VaultModels/ItemType.swift');
const KOTLIN_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/android/app/src/main/java/net/aliasvault/app/vaultstore/models/FieldKey.kt');
const KOTLIN_FIELD_TYPE_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/android/app/src/main/java/net/aliasvault/app/vaultstore/models/FieldType.kt');
const KOTLIN_ITEM_TYPE_OUTPUT = path.join(REPO_ROOT, 'apps/mobile-app/android/app/src/main/java/net/aliasvault/app/vaultstore/models/ItemType.kt');
/**
* Parse the TypeScript FieldKey.ts file and extract constants
@@ -52,6 +59,58 @@ function parseTypeScriptFieldKeys(tsContent) {
return fieldKeys;
}
/**
* Parse FieldTypes from TypeScript Item.ts source
* Returns an array of field type names in order
*/
function parseFieldTypes(tsContent) {
const fieldTypes = [];
// Find the FieldTypes constant
const match = tsContent.match(/export const FieldTypes\s*=\s*\{([^}]+)\}/s);
if (!match) {
console.warn('Warning: Could not find FieldTypes in source');
return fieldTypes;
}
const body = match[1];
// Match each type: Text: 'Text',
const typeRegex = /(\w+):\s*'(\w+)'/g;
let typeMatch;
while ((typeMatch = typeRegex.exec(body)) !== null) {
fieldTypes.push(typeMatch[1]);
}
return fieldTypes;
}
/**
* Parse ItemTypes from TypeScript Item.ts source
* Returns an array of item type names in order
*/
function parseItemTypes(tsContent) {
const itemTypes = [];
// Find the ItemTypes constant
const match = tsContent.match(/export const ItemTypes\s*=\s*\{([^}]+)\}/s);
if (!match) {
console.warn('Warning: Could not find ItemTypes in source');
return itemTypes;
}
const body = match[1];
// Match each type: Login: 'Login',
const typeRegex = /(\w+):\s*'(\w+)'/g;
let typeMatch;
while ((typeMatch = typeRegex.exec(body)) !== null) {
itemTypes.push(typeMatch[1]);
}
return itemTypes;
}
/**
* Parse FieldCategories from TypeScript source
* Returns an array of category names in order
@@ -284,6 +343,282 @@ object FieldKey {`;
return header + '\n' + fields + footer;
}
/**
* Generate C# static class for FieldType
*/
function generateCSharpFieldType(fieldTypes) {
const header = `// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
#nullable enable
namespace AliasClientDb.Models;
/// <summary>
/// Field types for rendering and validation.
/// </summary>
public static class FieldType
{`;
const fields = fieldTypes
.map(type => {
return ` /// <summary>
/// ${type} field type.
/// </summary>
public const string ${type} = "${type}";`;
})
.join('\n\n');
const allTypes = `
/// <summary>
/// All available field types.
/// </summary>
public static readonly string[] All = { ${fieldTypes.map(t => t).join(', ')} };
/// <summary>
/// Checks if a string value is a valid field type.
/// </summary>
/// <param name="value">The value to check.</param>
/// <returns>True if the value is a valid field type.</returns>
public static bool IsValid(string? value)
{
return ${fieldTypes.map(t => `value == ${t}`).join(' || ')};
}`;
const footer = `
}
`;
return header + '\n' + fields + allTypes + footer;
}
/**
* Generate C# static class for ItemType
*/
function generateCSharpItemType(itemTypes) {
const header = `// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
#nullable enable
namespace AliasClientDb.Models;
/// <summary>
/// Item types supported by the vault.
/// </summary>
public static class ItemType
{`;
const fields = itemTypes
.map(type => {
return ` /// <summary>
/// ${type} item type.
/// </summary>
public const string ${type} = "${type}";`;
})
.join('\n\n');
const allTypes = `
/// <summary>
/// All available item types.
/// </summary>
public static readonly string[] All = { ${itemTypes.map(t => t).join(', ')} };
/// <summary>
/// Checks if a string value is a valid item type.
/// </summary>
/// <param name="value">The value to check.</param>
/// <returns>True if the value is a valid item type.</returns>
public static bool IsValid(string? value)
{
return ${itemTypes.map(t => `value == ${t}`).join(' || ')};
}`;
const footer = `
}
`;
return header + '\n' + fields + allTypes + footer;
}
/**
* Generate Swift struct for FieldType
*/
function generateSwiftFieldType(fieldTypes) {
const header = `// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
import Foundation
/// Field types for rendering and validation.
public struct FieldType {`;
const fields = fieldTypes
.map(type => {
// Convert PascalCase to camelCase for Swift
const swiftName = type.charAt(0).toLowerCase() + type.slice(1);
return ` /// ${type} field type.
public static let ${swiftName} = "${type}"`;
})
.join('\n\n');
const allTypes = `
/// All available field types.
public static let all = [${fieldTypes.map(t => t.charAt(0).toLowerCase() + t.slice(1)).join(', ')}]
/// Checks if a string value is a valid field type.
public static func isValid(_ value: String?) -> Bool {
guard let value = value else { return false }
return all.contains(value)
}`;
const footer = `
}
`;
return header + '\n' + fields + allTypes + footer;
}
/**
* Generate Swift struct for ItemType
*/
function generateSwiftItemType(itemTypes) {
const header = `// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
import Foundation
/// Item types supported by the vault.
public struct ItemType {`;
const fields = itemTypes
.map(type => {
// Convert PascalCase to camelCase for Swift
const swiftName = type.charAt(0).toLowerCase() + type.slice(1);
return ` /// ${type} item type.
public static let ${swiftName} = "${type}"`;
})
.join('\n\n');
const allTypes = `
/// All available item types.
public static let all = [${itemTypes.map(t => t.charAt(0).toLowerCase() + t.slice(1)).join(', ')}]
/// Checks if a string value is a valid item type.
public static func isValid(_ value: String?) -> Bool {
guard let value = value else { return false }
return all.contains(value)
}`;
const footer = `
}
`;
return header + '\n' + fields + allTypes + footer;
}
/**
* Generate Kotlin object for FieldType
*/
function generateKotlinFieldType(fieldTypes) {
const header = `// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
package net.aliasvault.app.vaultstore.models
/**
* Field types for rendering and validation.
*/
object FieldType {`;
const fields = fieldTypes
.map(type => {
// Convert to SCREAMING_SNAKE_CASE for Kotlin constants
const kotlinName = type.replace(/([A-Z])/g, '_$1').toUpperCase().replace(/^_/, '');
return ` /**
* ${type} field type.
*/
const val ${kotlinName} = "${type}"`;
})
.join('\n\n');
const allTypes = `
/**
* All available field types.
*/
val all = listOf(${fieldTypes.map(t => t.replace(/([A-Z])/g, '_$1').toUpperCase().replace(/^_/, '')).join(', ')})
/**
* Checks if a string value is a valid field type.
*/
fun isValid(value: String?): Boolean {
return value in all
}`;
const footer = `
}
`;
return header + '\n' + fields + allTypes + footer;
}
/**
* Generate Kotlin object for ItemType
*/
function generateKotlinItemType(itemTypes) {
const header = `// <auto-generated />
// This file is auto-generated from core/models/src/vault/Item.ts
// Do not edit this file directly. Run 'npm run generate:models' to regenerate.
package net.aliasvault.app.vaultstore.models
/**
* Item types supported by the vault.
*/
object ItemType {`;
const fields = itemTypes
.map(type => {
// Convert to SCREAMING_SNAKE_CASE for Kotlin constants
const kotlinName = type.replace(/([A-Z])/g, '_$1').toUpperCase().replace(/^_/, '');
return ` /**
* ${type} item type.
*/
const val ${kotlinName} = "${type}"`;
})
.join('\n\n');
const allTypes = `
/**
* All available item types.
*/
val all = listOf(${itemTypes.map(t => t.replace(/([A-Z])/g, '_$1').toUpperCase().replace(/^_/, '')).join(', ')})
/**
* Checks if a string value is a valid item type.
*/
fun isValid(value: String?): Boolean {
return value in all
}`;
const footer = `
}
`;
return header + '\n' + fields + allTypes + footer;
}
/**
* Parse the TypeScript SystemFieldRegistry.ts file and extract field definitions
*/
@@ -626,6 +961,23 @@ function main() {
throw new Error('No field keys found in source file');
}
// Read TypeScript Item.ts source for FieldTypes and ItemTypes
if (!fs.existsSync(TS_ITEM_SOURCE)) {
throw new Error(`Source file not found: ${TS_ITEM_SOURCE}`);
}
const tsItemContent = fs.readFileSync(TS_ITEM_SOURCE, 'utf8');
const fieldTypes = parseFieldTypes(tsItemContent);
const itemTypes = parseItemTypes(tsItemContent);
if (fieldTypes.length === 0) {
throw new Error('No field types found in Item.ts source file');
}
if (itemTypes.length === 0) {
throw new Error('No item types found in Item.ts source file');
}
// Read TypeScript SystemFieldRegistry source
if (!fs.existsSync(TS_REGISTRY_SOURCE)) {
throw new Error(`Source file not found: ${TS_REGISTRY_SOURCE}`);
@@ -644,6 +996,8 @@ function main() {
}
console.log(`Parsed ${Object.keys(fieldKeys).length} field keys`);
console.log(`Parsed ${fieldTypes.length} field types: ${fieldTypes.join(', ')}`);
console.log(`Parsed ${itemTypes.length} item types: ${itemTypes.join(', ')}`);
console.log(`Parsed ${categories.length} field categories: ${categories.join(', ')}`);
console.log(`Parsed ${itemTypeFieldConfigProps.length} ItemTypeFieldConfig properties`);
console.log(`Parsed ${systemFieldDefProps.length} SystemFieldDefinition properties`);
@@ -655,6 +1009,18 @@ function main() {
fs.writeFileSync(CS_OUTPUT, csContent, 'utf8');
console.log(`Generated: ${CS_OUTPUT}`);
// Generate C# FieldType
ensureDir(CS_FIELD_TYPE_OUTPUT);
const csFieldTypeContent = generateCSharpFieldType(fieldTypes);
fs.writeFileSync(CS_FIELD_TYPE_OUTPUT, csFieldTypeContent, 'utf8');
console.log(`Generated: ${CS_FIELD_TYPE_OUTPUT}`);
// Generate C# ItemType
ensureDir(CS_ITEM_TYPE_OUTPUT);
const csItemTypeContent = generateCSharpItemType(itemTypes);
fs.writeFileSync(CS_ITEM_TYPE_OUTPUT, csItemTypeContent, 'utf8');
console.log(`Generated: ${CS_ITEM_TYPE_OUTPUT}`);
// Generate C# SystemFieldRegistry
ensureDir(CS_REGISTRY_OUTPUT);
const csRegistryContent = generateCSharpSystemFieldRegistry(
@@ -666,18 +1032,42 @@ function main() {
fs.writeFileSync(CS_REGISTRY_OUTPUT, csRegistryContent, 'utf8');
console.log(`Generated: ${CS_REGISTRY_OUTPUT}`);
// Generate Swift
// Generate Swift FieldKey
ensureDir(SWIFT_OUTPUT);
const swiftContent = generateSwift(fieldKeys);
fs.writeFileSync(SWIFT_OUTPUT, swiftContent, 'utf8');
console.log(`Generated: ${SWIFT_OUTPUT}`);
// Generate Kotlin
// Generate Swift FieldType
ensureDir(SWIFT_FIELD_TYPE_OUTPUT);
const swiftFieldTypeContent = generateSwiftFieldType(fieldTypes);
fs.writeFileSync(SWIFT_FIELD_TYPE_OUTPUT, swiftFieldTypeContent, 'utf8');
console.log(`Generated: ${SWIFT_FIELD_TYPE_OUTPUT}`);
// Generate Swift ItemType
ensureDir(SWIFT_ITEM_TYPE_OUTPUT);
const swiftItemTypeContent = generateSwiftItemType(itemTypes);
fs.writeFileSync(SWIFT_ITEM_TYPE_OUTPUT, swiftItemTypeContent, 'utf8');
console.log(`Generated: ${SWIFT_ITEM_TYPE_OUTPUT}`);
// Generate Kotlin FieldKey
ensureDir(KOTLIN_OUTPUT);
const kotlinContent = generateKotlin(fieldKeys);
fs.writeFileSync(KOTLIN_OUTPUT, kotlinContent, 'utf8');
console.log(`Generated: ${KOTLIN_OUTPUT}`);
// Generate Kotlin FieldType
ensureDir(KOTLIN_FIELD_TYPE_OUTPUT);
const kotlinFieldTypeContent = generateKotlinFieldType(fieldTypes);
fs.writeFileSync(KOTLIN_FIELD_TYPE_OUTPUT, kotlinFieldTypeContent, 'utf8');
console.log(`Generated: ${KOTLIN_FIELD_TYPE_OUTPUT}`);
// Generate Kotlin ItemType
ensureDir(KOTLIN_ITEM_TYPE_OUTPUT);
const kotlinItemTypeContent = generateKotlinItemType(itemTypes);
fs.writeFileSync(KOTLIN_ITEM_TYPE_OUTPUT, kotlinItemTypeContent, 'utf8');
console.log(`Generated: ${KOTLIN_ITEM_TYPE_OUTPUT}`);
console.log('\nCode generation complete!');
}