Hazzajenko / TypeForge

A robust CLI tool for mapping C# classes to TypeScript types, ensuring model consistency across your C# backend and TypeScript frontend in API development

Home Page:https://github.com/Hazzajenko/TypeForge

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

TypeForge

TypeForge is a powerful tool designed to map C# classes to TypeScript types. Whether you're developing an API or building a complex application, TypeForge provides an efficient solution to maintain consistent models across your C# backend and TypeScript frontend.

Features

  • Transform C# classes into TypeScript types or interfaces
  • Control casing of filenames, typenames, and property names
  • Adjust the depth of directory traversal
  • Flatten the file structure if needed
  • Maintain or remove the root folder
  • Handle nullable types
  • Generate an index file for easy exports
  • Option to end lines with semicolons

Installation

To use TypeForge in your project, follow these steps:

  1. Install the TypeForge.Cli package from NuGet by running the following command in the Package Manager Console or by using the .NET CLI:

    dotnet tool install --global TypeForge.Cli

    This command will install the TypeForge.Cli tool globally on your machine.

  2. Once the installation is complete, you can verify that the tool is installed correctly by running the following command:

    forge --help

    This command will display the help menu for the TypeForge.Cli tool.

  1. You're all set! You can now start using TypeForge by providing the necessary input and configuration to generate TypeScript types or interfaces from your C# classes.

If you prefer to manually download and install the TypeForge.Cli package, you can find it on NuGet.org.

Please refer below for detailed instructions on how to utilize TypeForge in your project.

CLI Arguments

The CLI tool supports the following arguments:

Argument Description
input Directory of files to convert. Leave empty to use config.
output Directory to output files to. Leave empty to use config.

Additionally, the CLI supports the following options:

Option Description
tm, typeModel Type or Interface. Default = Type
tc, typeNameCase Type name case. Default = PascalCase
tp, typeNamePrefix Type name prefix. Default = null. Example = I
ts, typeNameSuffix Type name suffix. Default = null. Example = Model
pc, propertyNameCase Property name case. Default = CamelCase
fc, fileNameCase File name case. Default = KebabCase. Example = kebab-case
fp, fileNamePrefix File name prefix, Default = null. Example = I
fs, fileNameSuffix File name suffix, Default = null. Example = Model
fd, folderNameCase Folder name case, Default = KebabCase
nt, nullableType Nullable type. Default = QuestionMark. QuestionMark or Null Or Undefined
gi, generateIndexFile Generate an index file to export all generated files. Default = true
es, endLinesWithSemicolon End lines with semicolon. Default = false
ic, includeChildren Include children. Default = true
d, depth Depth. Default = -1; -1 = infinite; IncludeChildren must be true
f, flatten Flatten. Default = false; If true, all files will be in the same folder
kr, keepRootFolder Keep root folder. Default = true; If false, the root folder will be removed

Config File

Alternatively, you can specify your options in a JSON config file.

{
  "FolderNameCase": "KebabCase",
  "TypeNamePrefix": "",
  "TypeNameSuffix": "Model",
  "FileNamePrefix": "",
  "FileNameSuffix": "Model",
  "ExportModelType": "Type",
  "PropertyNameCase": "CamelCase",
  "FileNameCase": "KebabCase",
  "NullableType": "?",
  "GenerateIndexFile": true,
  "EndLinesWithSemicolon": false,
  "Directories": [
    {
      "Input": "C:/directory/to/backend/TestNamespace",
      "IncludeChildren": true,
      "Flatten": false,
      "Depth": -1,
      "KeepRootFolder": true,
      "Output": "C:/directory/to/frontend/output"
    },
    {
      "Input": "C:/directory/to/backend/OtherNameSpace",
      "IncludeChildren": true,
      "Flatten": false,
      "Depth": -1,
      "KeepRootFolder": true,
      "Output": "C:/directory/to/frontend/output"
    },
    {
      "Input": "C:/directory/to/backend/OtherNameSpaceTest",
      "IncludeChildren": true,
      "Flatten": false,
      "Depth": -1,
      "KeepRootFolder": true,
      "Output": "C:/directory/to/frontend/output2"
    }
  ]
}

This file is JSON formatted and it is very easy to adapt to your needs. You can define multiple directory mappings with specific options, as well as global settings for the tool.


Type or Interface (-tm, --typeModel)

The typeModel option determines whether to use TypeScript's type or interface for the generated TypeScript definition. Here are the available options:

  • "Type" - Uses type for the TypeScript definition.
  • "Interface" - Uses interface for the TypeScript definition.

Here are examples of how this would affect your output:

Using "Type"

// When TypeModel === "Type"
export type Example = {
    property: string
}

Using "Interface"

// When TypeModel === "Interface"
export interface Example {
    property: string
}

Nullable Types (-nt, --nullableType)

The nullableType option determines how nullable properties are represented in the generated types. Here are the available options:

  • "?" - Adds an optional property operator (?) to the property.
  • "Undefined" - Makes the property a union with undefined.
  • "Null" - Makes the property a union with null.

Here are examples of how this would affect your output:

Using "?"

// When NullableType === "?"
export type Example = {
    property?: string
}

Using "Undefined"

// When NullableType === "Undefined"
export type Example = {
    property: string | undefined
}

Using "Null"

// When NullableType === "Null"
export type Example = {
    property: string | null
}

End Lines With Semicolon (-es, --endLinesWithSemicolon)

The endLinesWithSemicolon option determines whether to end lines with a semicolon in the generated TypeScript definition. Here are the available options:

  • true - Ends lines with a semicolon.
  • false - Does not end lines with a semicolon.

Here are examples of how this would affect your output:

With true

// When EndLinesWithSemicolon === true
export type Example = {
    property: string;
}

With false

// When EndLinesWithSemicolon === false
export type Example = {
    property: string
}

Type Name Case (-tc, --typeNameCase)

The typeNameCase option determines the case of type names in the generated TypeScript definition. Here are the available options:

  • PascalCase - Makes property names Pascal cased.
  • CamelCase - Makes property names camel cased.

Here are examples of how this would affect your output:

Pascal Case (Default)

// When TypeNameCase === "PascalCase"
export type ExampleModel = {
   propertyName: string
}

Camel Case

// When TypeNameCase === "CamelCase"
export type exampleModel = {
    propertyName: string
}

Type Name Prefix (-tp, --typeNamePrefix)

The typeNamePrefix option lets you specify a prefix that will be added to the beginning of all type names.

Consider a scenario where you have a type named Example. If you set the typeNamePrefix to "I", the output type would be named IExample.

Here is an example:

Without Prefix

// When TypeNamePrefix is empty
export type Example = {
    propertyName: string;
};

With Prefix

// When TypeNamePrefix === "I"
export type IExample = {
    propertyName: string;
};

Remember that the value you set for typeNamePrefix will be prepended to all generated type names.


Type Name Suffix (-ts, --typeNameSuffix)

The typeNameSuffix option allows you to specify a suffix that will be added to the end of all type names.

Consider a scenario where you have a type named Example. If you set the typeNameSuffix to "Model", the output type would be named ExampleModel.

Here is an example:

Without Suffix

// When TypeNameSuffix is empty
export type Example = {
    propertyName: string
}

With Suffix

// When TypeNameSuffix === "Model"
export type ExampleModel = {
    propertyName: string
}

Remember that the value you set for typeNameSuffix will be appended to all generated type names.


Property Name Case (-pc, --propertyNameCase)

The propertyNameCase option determines the case of property names in the generated TypeScript definition. Here are the available options:

  • CamelCase - Makes property names camel cased.
  • PascalCase - Makes property names Pascal cased.
  • KebabCase - Makes property names kebab cased.
  • SnakeCase - Makes property names snake cased.

Here are examples of how this would affect your output:

Camel Case

// When PropertyNameCase === "CamelCase"
export type Example = {
    propertyName: string
}

Pascal Case

// When PropertyNameCase === "PascalCase"
export type Example = {
    PropertyName: string
}

Kebab Case

// When PropertyNameCase === "KebabCase"
export type Example = {
    'property-name': string
}

Snake Case

// When PropertyNameCase === "SnakeCase"
export type Example = {
    property_name: string
}

Please note that in TypeScript, Kebab Case and Snake Case would require accessing the properties using bracket notation. For example, for Kebab Case, you would need to access the property-name like so: example['property-name'].


File Name Case (-fc, --fileNameCase)

The fileNameCase option determines the case of file names in the generated TypeScript definition. Here are the available options:

  • CamelCase - Makes property names camel cased.
  • PascalCase - Makes property names Pascal cased.
  • KebabCase - Makes property names kebab cased.

Here are examples of how this would affect your output:

Kebab Case (Default)

C:/directory/to/backend/
└── Requests
    ├── File1.cs
    ├── File2.cs
    └── File3.cs

C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    ├── file-1.ts
    ├── file-2.ts
    └── file-3.ts

Camel Case

C:/directory/to/backend/
└── Requests
    ├── File1.cs
    ├── File2.cs
    └── File3.cs

C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    ├── file1.ts
    ├── file2.ts
    └── file3.ts

Pascal Case

C:/directory/to/backend/
└── Requests
    ├── File1.cs
    ├── File2.cs
    └── File3.cs
C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    ├── File1.ts
    ├── File2.ts
    └── File3.ts

Please note that in TypeScript, Kebab Case and Snake Case would require accessing the properties using bracket notation. For example, for Kebab Case, you would need to access the property-name like so: example['property-name'].


File Name Prefix (-fp, --fileNamePrefix)

The fileNamePrefix option lets you specify a prefix that will be added to the beginning of all file names.

Consider a scenario where you have a file named example.ts. If you set the fileNamePrefix to "I", the output file would be named Iexample.ts.

Here is an example:

Without Prefix

// When FileNamePrefix is empty
C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    └── example.ts

With Prefix

// When FileNamePrefix === "I"
C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    └── Iexample.ts

Remember that the value you set for fileNamePrefix will be prepended to all generated file names.


File Name Suffix (-fs, --fileNameSuffix)

The fileNameSuffix option allows you to specify a suffix that will be added to the end of all file names.

Consider a scenario where you have a file named example.ts. If you set the fileNameSuffix to "model", the output file would be named example.model.ts.

Here is an example:

Without Suffix

C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    └── example.ts

With Suffix

C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    └── example.model.ts

Remember that the value you set for fileNameSuffix will be appended to all generated file names.


Folder Name Case (-fd, --folderNameCase)

The folderNameCase option allows you to specify the case format for folder names.

Consider a scenario where you have a folder named MyFolder and you set the folderNameCase to "KebabCase". The output folder name would be my-folder.

Here are examples of different folder name cases:

KebabCase (Default)

Input:

C:/directory/to/backend/
└── MyFolder
    └── example.cs

Output:

C:/directory/to/frontend/output/
├── index.ts
└── my-folder
    ├── index.ts
    └── example.ts

CamelCase

Input:

C:/directory/to/backend/
└── MyFolder
    └── example.cs

Output:

C:/directory/to/frontend/output/
├── index.ts
└── myFolder
    ├── index.ts
    └── example.ts

PascalCase

Input:

C:/directory/to/backend/
└── MyFolder
    └── example.cs

Output:

C:/directory/to/frontend/output/
├── index.ts
└── MyFolder
    ├── index.ts
    └── example.ts

Remember that the folderNameCase option defines the case format for all generated folder names.

Generate Index File (-gi, --generateIndexFile)

The generateIndexFile option controls whether an index file should be generated to export all the generated files.

When generateIndexFile is set to true, an index file will be created to provide a convenient way to import all the generated files in one place.

Here is an example of how the index file is generated:

// Generated Index File

export * from './file1'
export * from './file2'
export * from './file3'
// ... and so on
C:/directory/to/frontend/output/
├── index.ts
└── requests
    ├── index.ts
    ├── file-1.ts
    ├── file-2.ts
    └── file-3.ts

By default, generateIndexFile is set to true, but you can disable it by setting it to false if you don't want an index file to be generated.


Directory Specific Options


Include Children (-ic, --includeChildren)

The includeChildren option controls whether to include files from all the subdirectories when processing a directory.

By default, includeChildren is set to true, which means that when you specify a directory, all the files in that directory and its subdirectories will be processed.

Here is an example to illustrate how includeChildren works with a folder structure:

In this example

"Directories": [
  {
    "Input": "C:/directory/to/backend/Requests",
    "Output": "C:/directory/to/frontend/output/"
  },
  {
    "Input": "C:/directory/to/backend/Responses",
    "Output": "C:/directory/to/frontend/output/"
  }
]
C:/directory/to/backend/
├── Requests
│   ├── File1.cs
│   ├── File2.cs
│   └── SubNamespace
│       ├── File3.cs
│       └── File4.cs
└── Responses
    ├── File5.cs
    └── SubNamespace
        ├── File6.cs
        └── File7.cs

Example 1: includeChildren = true

If includeChildren is set to true, all the files in the specified directory and its subdirectories will be processed:

C:/directory/to/frontend/output/
├── index.ts
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   ├── file-2.ts
│   └── sub-namespace
│       ├── index.ts
│       ├── file-3.ts
│       └── file-4.ts
└── responses
    ├── index.ts
    ├── file-5.ts
    └── sub-namespace
        ├── index.ts
        ├── file-6.ts
        └── file-7.ts

Example 2: includeChildren = false

If includeChildren is set to false, only the files in the specified directory will be processed, and the files in the subdirectories will be excluded:

C:/directory/to/frontend/output/
├── index.ts
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   └── file-2.ts
└── responses
    ├── index.ts
    └── file-5.ts

Depth (-d, --depth)

The depth option allows you to control the number of levels of subdirectories to include when processing a directory. It works in conjunction with the includeChildren option.

By default, depth is set to -1, which means that all subdirectories will be processed.

Here is an example to illustrate how depth works with a folder structure:

In this example

"Directories": [
  {
    "Input": "C:/directory/to/backend/Requests",
    "IncludeChildren": true,
    "Output": "C:/directory/to/frontend/output/"
  },
  {
    "Input": "C:/directory/to/backend/Responses",
    "IncludeChildren": true,
    "Output": "C:/directory/to/frontend/output/"
  }
]
C:/directory/to/backend/
├── Requests
│   ├── File1.cs
│   ├── File2.cs
│   └── SubNamespace
│       ├── File3.cs
│       ├── File4.cs
│       └── SubSubNamespace
│           ├── File5.cs
│           └── File6.cs
└── Responses
    ├── File7.cs
    └── SubNamespace
        ├── File8.cs
        ├── File9.cs
        └── SubSubNamespace
            ├── File10.cs
            └── File11.cs

Example 1: depth = -1 (Default)

If depth is set to -1, all subdirectories will be processed:

C:/directory/to/frontend/output/
├── index.ts
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   ├── file-2.ts
│   └── sub-namespace
│       ├── index.ts
│       ├── file-3.ts
│       ├── file-4.ts
│       └── sub-sub-namespace
│           ├── index.ts
│           ├── file-5.ts
│           └── file-6.ts
└── responses
    ├── index.ts
    ├── file-7.ts
    └── sub-namespace
        ├── index.ts
        ├── file-8.ts
        ├── file-9.ts
        └── sub-sub-namespace
            ├── index.ts
            ├── file-10.ts
            └── file-11.ts

Example 2: depth <= 1

If depth is less than or equal to 1, only the files in the specified directory will be processed:

C:/directory/to/frontend/output/
├── index.ts
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   └── file-2.ts
└── responses
    ├── index.ts
    └── file-5.ts

Example 3: depth = 2

If depth is set to 2, the files in the specified directory and its immediate subdirectories will be processed:

C:/directory/to/frontend/output/
├── index.ts
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   ├── file-2.ts
└── responses
    ├── index.ts
    ├── file-5.ts
    └─── sub-namespace
        ├── index.ts
        ├── file-6.ts
        └── file-7.ts

Note: The depth option works in conjunction with the includeChildren option. If includeChildren is set to false, the depth setting will have no effect, as only the specified directory will be processed.


Flatten (-f, --flatten)

The flatten option controls whether to flatten the output structure and place all generated files in the same directory, regardless of their original directory structure.

By default, flatten is set to false, which means that the generated files will be organized in the same folder structure as the input files.

Here is an example to illustrate how flatten works with a folder structure:

In this example

"Directories": [
  {
    "Input": "C:/directory/to/backend/Requests",
    "Output": "C:/directory/to/frontend/output/"
  },
  {
    "Input": "C:/directory/to/backend/Responses",
    "Output": "C:/directory/to/frontend/output/"
  }
]
C:/directory/to/backend/
├── Requests
│   ├── File1.cs
│   ├── File2.cs
│   └── SubNamespace
│       ├── File3.cs
│       └── File4.cs
└── Responses
    ├── File5.cs
    └── SubNamespace
        ├── File6.cs
        └── File7.cs

Example 1: flatten = false

If flatten is set to false, the generated files will be organized in the same folder structure as the input files:

C:/directory/to/frontend/output/
├── index.ts
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   ├── file-2.ts
│   └── sub-namespace
│       ├── index.ts
│       ├── file-3.ts
│       └── file-4.ts
└── responses
    ├── index.ts
    ├── file-5.ts
    └── sub-namespace
        ├── index.ts
        ├── file-6.ts
        └── file-7.ts

Example 2: flatten = true

If flatten is set to true, all the generated files will be placed in the same directory, regardless of their original directory structure:

C:/directory/to/frontend/output/
├── index.ts
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   ├── file-2.ts
│   ├── file-3.ts
│   └── file-4.ts
└── responses
    ├── index.ts
    ├── file-5.ts
    ├── file-6.ts
    └── file-7.ts

Note: The flatten option can be useful if you prefer a flat output structure and do not need to preserve the original directory hierarchy of the input files.


Keep Root Folder (-kr, --keepRootFolder)

The keepRootFolder option controls whether to keep the root folder of the input files in the output structure.

By default, keepRootFolder is set to true, which means that the root folder of each input directory will be preserved in the output structure.

Here is an example to illustrate how keepRootFolder works with a folder structure:

In this example

"Directories": [
  {
    "Input": "C:/directory/to/backend/Requests",
    "Output": "C:/directory/to/frontend/output/"
  },
  {
    "Input": "C:/directory/to/backend/Responses",
    "Output": "C:/directory/to/frontend/output/"
  }
]
C:/directory/to/backend/
├── Requests
│   ├── File1.cs
│   ├── File2.cs
│   └── SubNamespace
│       ├── File3.cs
│       └── File4.cs
└── Responses
    ├── File5.cs
    └── SubNamespace
        ├── File6.cs
        └── File7.cs

Example 1: keepRootFolder = true

If keepRootFolder is set to true, the root folder of each input directory will be preserved in the output structure:

C:/directory/to/frontend/output/
├── requests
│   ├── index.ts
│   ├── file-1.ts
│   ├── file-2.ts
│   └── sub-namespace
│       ├── index.ts
│       ├── file-3.ts
│       └── file-4.ts
└── responses
    ├── index.ts
    ├── file-5.ts
    └── sub-namespace
        ├── index.ts
        ├── file-6.ts
        └── file-7.ts

Example 2: keepRootFolder = false

If keepRootFolder is set to false, the root folder of each input directory will not be included in the output structure:

C:/directory/to/frontend/output/
├── index.ts
├── file-1.ts
├── file-2.ts
├── file-5.ts
├── sub-namespace
│   ├── index.ts
│   ├── file-3.ts
│   └── file-4.ts
└── sub-namespace
    ├── index.ts
    ├── file-6.ts
    └── file-7.ts

Note: The keepRootFolder option can be useful if you want to maintain the original directory structure of the input files in the output, or if you prefer a flatter output structure by excluding the root folders.


About

A robust CLI tool for mapping C# classes to TypeScript types, ensuring model consistency across your C# backend and TypeScript frontend in API development

https://github.com/Hazzajenko/TypeForge

License:MIT License


Languages

Language:C# 88.7%Language:HTML 4.7%Language:TypeScript 4.5%Language:Dockerfile 1.0%Language:Makefile 0.8%Language:PowerShell 0.3%