├── .github
└── workflows
│ ├── csharp.tests.yml
│ └── node.tests.yml
├── .gitignore
├── README.md
├── converter.js
├── index.js
├── lib
├── Directory.Build.props
├── Directory.Build.targets
├── csharp-models-to-json
│ ├── Config.cs
│ ├── EnumCollector.cs
│ ├── ExtraInfo.cs
│ ├── ModelCollector.cs
│ ├── Program.cs
│ ├── Util.cs
│ └── csharp-models-to-json.csproj
└── csharp-models-to-json_test
│ ├── EnumCollector_test.cs
│ ├── ModelCollector_test.cs
│ └── csharp-models-to-json_test.csproj
├── package-lock.json
├── package.json
└── test-files
├── TestFile.cs
├── test-config.json
└── test-file-exists.js
/.github/workflows/csharp.tests.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: C# CI
5 |
6 | on: [push, pull_request]
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | defaults:
14 | run:
15 | working-directory: ./lib/csharp-models-to-json_test
16 |
17 | steps:
18 | - uses: actions/checkout@v4
19 | - name: Setup .NET
20 | uses: actions/setup-dotnet@v4
21 | with:
22 | dotnet-version: 8.0.x
23 | - name: Restore dependencies
24 | run: dotnet restore
25 | - name: Build
26 | run: dotnet build --no-restore
27 | - name: Test
28 | run: dotnet test --no-build --verbosity normal
29 |
30 |
--------------------------------------------------------------------------------
/.github/workflows/node.tests.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
3 |
4 | name: Node.js CI
5 |
6 | on: [push, pull_request]
7 |
8 | jobs:
9 | build:
10 |
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [16.x, 20.x]
16 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
17 |
18 | steps:
19 | - uses: actions/checkout@v4
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v4
22 | with:
23 | node-version: ${{ matrix.node-version }}
24 | cache: 'npm'
25 | - run: npm ci
26 | - run: npm run test
27 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | lib/csharp-models-to-json/bin
4 | lib/csharp-models-to-json/obj
5 | lib/csharp-models-to-json_test/obj/
6 | lib/csharp-models-to-json_test/bin/
7 | /test-files/api.d.ts
8 |
9 | **/.vs/*
10 | .vscode/*
11 | .idea/*
12 | *.user
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # C# models to TypeScript
2 |
3 | This is a tool that consumes your C# domain models and types and creates TypeScript declaration files from them. There's other tools that does this but what makes this one different is that it internally uses [Roslyn (the .NET compiler platform)](https://github.com/dotnet/roslyn) to parse the source files, which removes the need to create and maintain our own parser.
4 |
5 |
6 | [![NPM version][npm-image]][npm-url]
7 |
8 |
9 | ## Dependencies
10 |
11 | * [.NET Core SDK](https://www.microsoft.com/net/download/macos)
12 |
13 |
14 | ## Install
15 |
16 | ```
17 | $ npm install --save csharp-models-to-typescript
18 | ```
19 |
20 | ## How to use
21 |
22 | 1. Add a config file to your project that contains for example...
23 |
24 | ```
25 | {
26 | "include": [
27 | "./models/**/*.cs",
28 | "./enums/**/*.cs"
29 | ],
30 | "exclude": [
31 | "./models/foo/bar.cs"
32 | ],
33 | "namespace": "Api",
34 | "output": "./api.d.ts",
35 | "includeComments": true,
36 | "camelCase": false,
37 | "camelCaseEnums": false,
38 | "camelCaseOptions": {
39 | "pascalCase": false,
40 | "preserveConsecutiveUppercase": false,
41 | "locale": "en-US"
42 | },
43 | "numericEnums": false,
44 | "validateEmitDefaultValue": false,
45 | "omitFilePathComment": false,
46 | "omitSemicolon": false,
47 | "stringLiteralTypesInsteadOfEnums": false,
48 | "customTypeTranslations": {
49 | "ProductName": "string",
50 | "ProductNumber": "string"
51 | }
52 | }
53 | ```
54 |
55 | 2. Add a npm script to your package.json that references your config file...
56 |
57 | ```
58 | "scripts": {
59 | "generate-types": "csharp-models-to-typescript --config=your-config-file.json"
60 | },
61 | ```
62 |
63 | 3. Run the npm script `generate-types` and the output file specified in your config should be created and populated with your models.
64 |
65 |
66 | ## License
67 |
68 | MIT © [Jonathan Svenheden](https://github.com/svenheden)
69 |
70 | [npm-image]: https://img.shields.io/npm/v/csharp-models-to-typescript.svg
71 | [npm-url]: https://npmjs.org/package/csharp-models-to-typescript
72 |
--------------------------------------------------------------------------------
/converter.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const camelcase = require('camelcase');
3 |
4 | const flatten = arr => arr.reduce((a, b) => a.concat(b), []);
5 |
6 | const arrayRegex = /^(.+)\[\]$/;
7 | const simpleCollectionRegex = /^(?:I?List|IReadOnlyList|IEnumerable|ICollection|IReadOnlyCollection|HashSet)<([\w\d]+)>\??$/;
8 | const collectionRegex = /^(?:I?List|IReadOnlyList|IEnumerable|ICollection|IReadOnlyCollection|HashSet)<(.+)>\??$/;
9 | const simpleDictionaryRegex = /^(?:I?Dictionary|SortedDictionary|IReadOnlyDictionary)<([\w\d]+)\s*,\s*([\w\d]+)>\??$/;
10 | const dictionaryRegex = /^(?:I?Dictionary|SortedDictionary|IReadOnlyDictionary)<([\w\d]+)\s*,\s*(.+)>\??$/;
11 |
12 | const defaultTypeTranslations = {
13 | int: 'number',
14 | double: 'number',
15 | float: 'number',
16 | Int32: 'number',
17 | Int64: 'number',
18 | short: 'number',
19 | long: 'number',
20 | decimal: 'number',
21 | bool: 'boolean',
22 | DateTime: 'string',
23 | DateTimeOffset: 'string',
24 | Guid: 'string',
25 | dynamic: 'any',
26 | object: 'any',
27 | };
28 |
29 | const createConverter = config => {
30 | const typeTranslations = Object.assign({}, defaultTypeTranslations, config.customTypeTranslations);
31 |
32 | const convert = json => {
33 | const content = json.map(file => {
34 | const filename = path.relative(process.cwd(), file.FileName);
35 |
36 | const rows = flatten([
37 | ...file.Models.map(model => convertModel(model, filename)),
38 | ...file.Enums.map(enum_ => convertEnum(enum_, filename)),
39 | ]);
40 |
41 | return rows
42 | .map(row => config.namespace ? ` ${row}` : row)
43 | .join('\n');
44 | });
45 |
46 | const filteredContent = content.filter(x => x.length > 0);
47 |
48 | if (config.namespace) {
49 | return [
50 | `declare module ${config.namespace} {`,
51 | ...filteredContent,
52 | '}',
53 | ].join('\n');
54 | } else {
55 | return filteredContent.join('\n');
56 | }
57 | };
58 |
59 | const convertModel = (model, filename) => {
60 | const rows = [];
61 |
62 | if (model.BaseClasses) {
63 | model.IndexSignature = model.BaseClasses.find(type => type.match(dictionaryRegex));
64 | model.BaseClasses = model.BaseClasses.filter(type => !type.match(dictionaryRegex));
65 | }
66 |
67 | const members = [...(model.Fields || []), ...(model.Properties || [])];
68 | const baseClasses = model.BaseClasses && model.BaseClasses.length ? ` extends ${model.BaseClasses.join(', ')}` : '';
69 |
70 | if (!config.omitFilePathComment) {
71 | rows.push(`// ${filename}`);
72 | }
73 | let classCommentRows = formatComment(model.ExtraInfo, '')
74 | if (classCommentRows) {
75 | rows.push(classCommentRows);
76 | }
77 |
78 | rows.push(`export interface ${model.ModelName}${baseClasses} {`);
79 |
80 | const propertySemicolon = config.omitSemicolon ? '' : ';';
81 |
82 | if (model.IndexSignature) {
83 | rows.push(` ${convertIndexType(model.IndexSignature)}${propertySemicolon}`);
84 | }
85 |
86 | members.forEach(member => {
87 | let memberCommentRows = formatComment(member.ExtraInfo, ' ')
88 | if (memberCommentRows) {
89 | rows.push(memberCommentRows);
90 | }
91 |
92 | rows.push(` ${convertProperty(member)}${propertySemicolon}`);
93 | });
94 |
95 | rows.push(`}\n`);
96 |
97 | return rows;
98 | };
99 |
100 | const convertEnum = (enum_, filename) => {
101 | const rows = [];
102 | if (!config.omitFilePathComment) {
103 | rows.push(`// ${filename}`);
104 | }
105 |
106 | const entries = Object.entries(enum_.Values);
107 |
108 | let classCommentRows = formatComment(enum_.ExtraInfo, '')
109 | if (classCommentRows) {
110 | rows.push(classCommentRows);
111 | }
112 |
113 | const getEnumStringValue = (value) => config.camelCaseEnums
114 | ? camelcase(value)
115 | : value;
116 |
117 | const lastValueSemicolon = config.omitSemicolon ? '' : ';';
118 |
119 | if (config.stringLiteralTypesInsteadOfEnums) {
120 | rows.push(`export type ${enum_.Identifier} =`);
121 |
122 | entries.forEach(([key], i) => {
123 | const delimiter = (i === entries.length - 1) ? lastValueSemicolon : ' |';
124 | rows.push(` '${getEnumStringValue(key)}'${delimiter}`);
125 | });
126 |
127 | rows.push('');
128 | } else {
129 | rows.push(`export enum ${enum_.Identifier} {`);
130 |
131 | entries.forEach(([key, entry]) => {
132 | let classCommentRows = formatComment(entry.ExtraInfo, ' ')
133 | if (classCommentRows) {
134 | rows.push(classCommentRows);
135 | }
136 | if (config.numericEnums) {
137 | if (entry.Value == null) {
138 | rows.push(` ${key},`);
139 | } else {
140 | rows.push(` ${key} = ${entry.Value},`);
141 | }
142 | } else {
143 | rows.push(` ${key} = '${getEnumStringValue(key)}',`);
144 | }
145 | });
146 |
147 | rows.push(`}\n`);
148 | }
149 |
150 | return rows;
151 | };
152 |
153 | const formatComment = (extraInfo, indentation) => {
154 | if (!config.includeComments || !extraInfo || (!extraInfo.Obsolete && !extraInfo.Summary)) {
155 | return undefined;
156 | }
157 |
158 | let comment = '';
159 | comment += `${indentation}/**\n`;
160 |
161 | if (extraInfo.Summary) {
162 | let commentLines = extraInfo.Summary.split(/\r?\n/);
163 | commentLines = commentLines.map((e) => {
164 | return `${indentation} * ${replaceCommentTags(e)}\n`;
165 | })
166 | comment += commentLines.join('');
167 | }
168 | if (extraInfo.Remarks) {
169 | comment += `${indentation} *\n`;
170 | comment += `${indentation} * @remarks\n`;
171 | let commentLines = extraInfo.Remarks.split(/\r?\n/);
172 | commentLines = commentLines.map((e) => {
173 | return `${indentation} * ${replaceCommentTags(e)}\n`;
174 | })
175 | comment += commentLines.join('');
176 | }
177 |
178 | if (extraInfo.Obsolete) {
179 | if (extraInfo.Summary) {
180 | comment += `${indentation} *\n`;
181 | }
182 |
183 | let obsoleteMessage = '';
184 | if (extraInfo.ObsoleteMessage) {
185 | obsoleteMessage = ' ' + replaceCommentTags(extraInfo.ObsoleteMessage);
186 | }
187 | comment += `${indentation} * @deprecated${obsoleteMessage}\n`;
188 | }
189 |
190 | comment += `${indentation} */`;
191 |
192 | return comment;
193 | }
194 |
195 | const replaceCommentTags = comment => {
196 | return comment
197 | .replace(//gi, '{@link $1}')
198 | .replace(/(.+)<\/see>/gi, '{@link $1 | $2}')
199 | .replace('', '@inheritDoc');
200 | }
201 |
202 | const convertProperty = property => {
203 | const optional = property.Type.endsWith('?') || (config.validateEmitDefaultValue &&
204 | property.ExtraInfo != null &&
205 | !property.ExtraInfo.EmitDefaultValue);
206 | const identifier = convertIdentifier(optional ? `${property.Identifier.split(' ')[0]}?` : property.Identifier.split(' ')[0]);
207 |
208 | const type = parseType(property.Type);
209 |
210 | return `${identifier}: ${type}`;
211 | };
212 |
213 | const convertIndexType = indexType => {
214 | const dictionary = indexType.match(dictionaryRegex);
215 | const simpleDictionary = indexType.match(simpleDictionaryRegex);
216 |
217 | propType = simpleDictionary ? dictionary[2] : parseType(dictionary[2]);
218 |
219 | return `[key: ${convertType(dictionary[1])}]: ${convertType(propType)}`;
220 | };
221 |
222 | const convertRecord = indexType => {
223 | const dictionary = indexType.match(dictionaryRegex);
224 | const simpleDictionary = indexType.match(simpleDictionaryRegex);
225 |
226 | propType = simpleDictionary ? dictionary[2] : parseType(dictionary[2]);
227 |
228 | return `Record<${convertType(dictionary[1])}, ${convertType(propType)}>`;
229 | };
230 |
231 | const parseType = propType => {
232 | const array = propType.match(arrayRegex);
233 | if (array) {
234 | propType = array[1];
235 | }
236 |
237 | const collection = propType.match(collectionRegex);
238 | const dictionary = propType.match(dictionaryRegex);
239 |
240 | let type;
241 |
242 | if (collection) {
243 | const simpleCollection = propType.match(simpleCollectionRegex);
244 | propType = simpleCollection ? collection[1] : parseType(collection[1]);
245 | type = `${convertType(propType)}[]`;
246 | } else if (dictionary) {
247 | type = `${convertRecord(propType)}`;
248 | } else {
249 | const optional = propType.endsWith('?');
250 | type = convertType(optional ? propType.slice(0, propType.length - 1) : propType);
251 | }
252 |
253 | return array ? `${type}[]` : type;
254 | };
255 |
256 | const convertIdentifier = identifier => config.camelCase ? camelcase(identifier, config.camelCaseOptions) : identifier;
257 | const convertType = type => type in typeTranslations ? typeTranslations[type] : type;
258 |
259 | return convert;
260 | };
261 |
262 | module.exports = createConverter;
263 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | const fs = require('fs');
4 | const process = require('process');
5 | const path = require('path');
6 | const { spawn } = require('child_process');
7 |
8 | const createConverter = require('./converter');
9 |
10 | const configArg = process.argv.find(x => x.startsWith('--config='));
11 |
12 | if (!configArg) {
13 | return console.error('No configuration file for `csharp-models-to-typescript` provided.');
14 | }
15 |
16 | const configPath = configArg.substr('--config='.length);
17 | let config;
18 |
19 | try {
20 | unparsedConfig = fs.readFileSync(configPath, 'utf8');
21 | } catch (error) {
22 | return console.error(`Configuration file "${configPath}" not found.`);
23 | }
24 |
25 | try {
26 | config = JSON.parse(unparsedConfig);
27 | } catch (error) {
28 | return console.error(`Configuration file "${configPath}" contains invalid JSON.`);
29 | }
30 |
31 | const output = config.output || 'types.d.ts';
32 |
33 | const converter = createConverter({
34 | customTypeTranslations: config.customTypeTranslations || {},
35 | namespace: config.namespace,
36 | includeComments: config.includeComments ?? true,
37 | camelCase: config.camelCase || false,
38 | camelCaseOptions: config.camelCaseOptions || {},
39 | camelCaseEnums: config.camelCaseEnums || false,
40 | numericEnums: config.numericEnums || false,
41 | validateEmitDefaultValue: config.validateEmitDefaultValue || false,
42 | omitFilePathComment: config.omitFilePathComment || false,
43 | omitSemicolon: config.omitSemicolon || false,
44 | stringLiteralTypesInsteadOfEnums: config.stringLiteralTypesInsteadOfEnums || false
45 | });
46 |
47 | let timer = process.hrtime();
48 |
49 | const dotnetProject = path.join(__dirname, 'lib/csharp-models-to-json');
50 | const dotnetProcess = spawn('dotnet', ['run', `--project "${dotnetProject}"`, `"${path.resolve(configPath)}"`], { shell: true });
51 |
52 | let stdout = '';
53 |
54 | dotnetProcess.stdout.on('data', data => {
55 | stdout += data;
56 | });
57 |
58 | dotnetProcess.stderr.on('data', err => {
59 | console.error(err.toString());
60 | });
61 |
62 | dotnetProcess.stdout.on('end', () => {
63 | let json;
64 |
65 | //console.log(stdout);
66 |
67 | try {
68 | // Extract the JSON content between the markers
69 | const startMarker = '<<<<<>>>>>';
70 | const endMarker = '<<<<<>>>>>';
71 | const startIndex = stdout.indexOf(startMarker);
72 | const endIndex = stdout.indexOf(endMarker);
73 |
74 | if (startIndex !== -1 && endIndex !== -1 && endIndex > startIndex) {
75 | const jsonString = stdout.substring(startIndex + startMarker.length, endIndex).trim();
76 | json = JSON.parse(jsonString);
77 | } else {
78 | throw new Error('JSON markers not found or invalid order of markers.');
79 | }
80 | } catch (error) {
81 | return console.error([
82 | 'The output from `csharp-models-to-json` contains invalid JSON.',
83 | error.message,
84 | stdout
85 | ].join('\n\n'));
86 | }
87 |
88 | const types = converter(json);
89 |
90 | fs.writeFile(output, types, err => {
91 | if (err) {
92 | return console.error(err);
93 | }
94 |
95 | timer = process.hrtime(timer);
96 | console.log('Done in %d.%d seconds.', timer[0], timer[1]);
97 | });
98 | });
99 |
--------------------------------------------------------------------------------
/lib/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
--------------------------------------------------------------------------------
/lib/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 |
5 |
--------------------------------------------------------------------------------
/lib/csharp-models-to-json/Config.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace CSharpModelsToJson
4 | {
5 | public class Config
6 | {
7 | public List Include { get; set; }
8 | public List Exclude { get; set; }
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/lib/csharp-models-to-json/EnumCollector.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.CSharp.Syntax;
4 |
5 | namespace CSharpModelsToJson
6 | {
7 | public class Enum
8 | {
9 | public string Identifier { get; set; }
10 | public ExtraInfo ExtraInfo { get; set; }
11 | public Dictionary Values { get; set; }
12 | }
13 |
14 | public class EnumValue
15 | {
16 | public string Value { get; set; }
17 | public ExtraInfo ExtraInfo { get; set; }
18 | }
19 |
20 |
21 | public class EnumCollector: CSharpSyntaxWalker
22 | {
23 | public readonly List Enums = new List();
24 |
25 | public override void VisitEnumDeclaration(EnumDeclarationSyntax node)
26 | {
27 | var values = new Dictionary();
28 |
29 | foreach (var member in node.Members) {
30 | var equalsValue = member.EqualsValue != null
31 | ? member.EqualsValue.Value.ToString()
32 | : null;
33 |
34 | var value = new EnumValue
35 | {
36 | Value = equalsValue?.Replace("_", ""),
37 | ExtraInfo = new ExtraInfo
38 | {
39 | Obsolete = Util.IsObsolete(member.AttributeLists),
40 | ObsoleteMessage = Util.GetObsoleteMessage(member.AttributeLists),
41 | Summary = Util.GetSummaryMessage(member),
42 | Remarks = Util.GetRemarksMessage(member),
43 | }
44 | };
45 |
46 | values[member.Identifier.ToString()] = value;
47 | }
48 |
49 | this.Enums.Add(new Enum() {
50 | Identifier = node.Identifier.ToString(),
51 | ExtraInfo = new ExtraInfo
52 | {
53 | Obsolete = Util.IsObsolete(node.AttributeLists),
54 | ObsoleteMessage = Util.GetObsoleteMessage(node.AttributeLists),
55 | Summary = Util.GetSummaryMessage(node),
56 | Remarks = Util.GetRemarksMessage(node),
57 | },
58 | Values = values
59 | });
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/lib/csharp-models-to-json/ExtraInfo.cs:
--------------------------------------------------------------------------------
1 |
2 | namespace CSharpModelsToJson
3 | {
4 | public class ExtraInfo
5 | {
6 | public bool Obsolete { get; set; }
7 | public string ObsoleteMessage { get; set; }
8 | public string Summary { get; set; }
9 | public string Remarks { get; set; }
10 | public bool EmitDefaultValue { get; set; }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/lib/csharp-models-to-json/ModelCollector.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Linq;
3 | using Microsoft.CodeAnalysis;
4 | using Microsoft.CodeAnalysis.CSharp;
5 | using Microsoft.CodeAnalysis.CSharp.Syntax;
6 |
7 | namespace CSharpModelsToJson
8 | {
9 | public class Model
10 | {
11 | public string ModelName { get; set; }
12 | public IEnumerable Fields { get; set; }
13 | public IEnumerable Properties { get; set; }
14 | public IEnumerable BaseClasses { get; set; }
15 | public ExtraInfo ExtraInfo { get; set; }
16 | }
17 |
18 | public class Field
19 | {
20 | public string Identifier { get; set; }
21 | public string Type { get; set; }
22 | }
23 |
24 | public class Property
25 | {
26 | public string Identifier { get; set; }
27 | public string Type { get; set; }
28 | public ExtraInfo ExtraInfo { get; set; }
29 | }
30 |
31 | public class ModelCollector : CSharpSyntaxWalker
32 | {
33 | public readonly List Models = new List();
34 |
35 | public override void VisitClassDeclaration(ClassDeclarationSyntax node)
36 | {
37 | var model = CreateModel(node);
38 |
39 | Models.Add(model);
40 | }
41 |
42 | public override void VisitInterfaceDeclaration(InterfaceDeclarationSyntax node)
43 | {
44 | var model = CreateModel(node);
45 |
46 | Models.Add(model);
47 | }
48 |
49 | public override void VisitRecordDeclaration(RecordDeclarationSyntax node)
50 | {
51 | var model = new Model()
52 | {
53 | ModelName = $"{node.Identifier.ToString()}{node.TypeParameterList?.ToString()}",
54 | Fields = node.ParameterList?.Parameters
55 | .Where(field => IsAccessible(field.Modifiers))
56 | .Where(property => !IsIgnored(property.AttributeLists))
57 | .Select((field) => new Field
58 | {
59 | Identifier = field.Identifier.ToString(),
60 | Type = field.Type.ToString(),
61 | }),
62 | Properties = node.Members.OfType()
63 | .Where(property => IsAccessible(property.Modifiers))
64 | .Where(property => !IsIgnored(property.AttributeLists))
65 | .Select(ConvertProperty),
66 | BaseClasses = new List(),
67 | };
68 |
69 | Models.Add(model);
70 | }
71 |
72 | private static Model CreateModel(TypeDeclarationSyntax node)
73 | {
74 | return new Model()
75 | {
76 | ModelName = $"{node.Identifier.ToString()}{node.TypeParameterList?.ToString()}",
77 | Fields = node.Members.OfType()
78 | .Where(field => IsAccessible(field.Modifiers))
79 | .Where(property => !IsIgnored(property.AttributeLists))
80 | .Select(ConvertField),
81 | Properties = node.Members.OfType()
82 | .Where(property => IsAccessible(property.Modifiers))
83 | .Where(property => !IsIgnored(property.AttributeLists))
84 | .Select(ConvertProperty),
85 | BaseClasses = node.BaseList?.Types.Select(s => s.ToString()),
86 | ExtraInfo = new ExtraInfo
87 | {
88 | Obsolete = Util.IsObsolete(node.AttributeLists),
89 | ObsoleteMessage = Util.GetObsoleteMessage(node.AttributeLists),
90 | Summary = Util.GetSummaryMessage(node),
91 | Remarks = Util.GetRemarksMessage(node),
92 | }
93 | };
94 | }
95 |
96 | private static bool IsIgnored(SyntaxList propertyAttributeLists) =>
97 | propertyAttributeLists.Any(attributeList =>
98 | attributeList.Attributes.Any(attribute =>
99 | attribute.Name.ToString().Equals("JsonIgnore") ||
100 | attribute.Name.ToString().Equals("IgnoreDataMember")));
101 |
102 | private static bool IsAccessible(SyntaxTokenList modifiers) => modifiers.All(modifier =>
103 | modifier.ToString() != "const" &&
104 | modifier.ToString() != "static" &&
105 | modifier.ToString() != "private"
106 | );
107 |
108 | private static Field ConvertField(FieldDeclarationSyntax field) => new Field
109 | {
110 | Identifier = field.Declaration.Variables.First().GetText().ToString(),
111 | Type = field.Declaration.Type.ToString(),
112 | };
113 |
114 | private static Property ConvertProperty(PropertyDeclarationSyntax property) => new Property
115 | {
116 | Identifier = property.Identifier.ToString(),
117 | Type = property.Type.ToString(),
118 | ExtraInfo = new ExtraInfo
119 | {
120 | Obsolete = Util.IsObsolete(property.AttributeLists),
121 | ObsoleteMessage = Util.GetObsoleteMessage(property.AttributeLists),
122 | Summary = Util.GetSummaryMessage(property),
123 | Remarks = Util.GetRemarksMessage(property),
124 | EmitDefaultValue = Util.GetEmitDefaultValue(property.AttributeLists),
125 | }
126 | };
127 | }
128 | }
--------------------------------------------------------------------------------
/lib/csharp-models-to-json/Program.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Text;
3 | using System.Text.Json;
4 | using System.Text.Json.Serialization;
5 | using Microsoft.CodeAnalysis;
6 | using Microsoft.CodeAnalysis.CSharp;
7 | using Microsoft.CodeAnalysis.CSharp.Syntax;
8 | using Ganss.IO;
9 |
10 | namespace CSharpModelsToJson
11 | {
12 | class File
13 | {
14 | public string FileName { get; set; }
15 | public IEnumerable Models { get; set; }
16 | public IEnumerable Enums { get; set; }
17 | }
18 |
19 | class Program
20 | {
21 | static void Main(string[] args)
22 | {
23 | Config? config = null;
24 | if (System.IO.File.Exists(args[0])) {
25 | var configJson = System.IO.File.ReadAllText(args[0]);
26 | var opts = new JsonSerializerOptions {
27 | PropertyNameCaseInsensitive = true
28 | };
29 | config = JsonSerializer.Deserialize(configJson, opts);
30 | }
31 |
32 | var includes = config?.Include ?? [];
33 | var excludes = config?.Exclude ?? [];
34 |
35 | List files = new List();
36 |
37 | foreach (string fileName in getFileNames(includes, excludes)) {
38 | files.Add(parseFile(fileName));
39 | }
40 |
41 | JsonSerializerOptions options = new()
42 | {
43 | DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
44 | };
45 |
46 | string json = JsonSerializer.Serialize(files, options);
47 |
48 | var sb = new StringBuilder();
49 | sb.AppendLine("<<<<<>>>>>");
50 | sb.AppendLine(json);
51 | sb.AppendLine("<<<<<>>>>>");
52 |
53 | System.Console.OutputEncoding = System.Text.Encoding.UTF8;
54 | System.Console.WriteLine(sb.ToString());
55 | }
56 |
57 | static List getFileNames(List includes, List excludes) {
58 | List fileNames = new List();
59 |
60 | foreach (var path in expandGlobPatterns(includes)) {
61 | fileNames.Add(path);
62 | }
63 |
64 | foreach (var path in expandGlobPatterns(excludes)) {
65 | fileNames.Remove(path);
66 | }
67 |
68 | return fileNames;
69 | }
70 |
71 | static List expandGlobPatterns(List globPatterns) {
72 | List fileNames = new List();
73 |
74 | foreach (string pattern in globPatterns) {
75 | var paths = Glob.Expand(pattern);
76 |
77 | foreach (var path in paths) {
78 | fileNames.Add(path.FullName);
79 | }
80 | }
81 |
82 | return fileNames;
83 | }
84 |
85 | static File parseFile(string path) {
86 | string source = System.IO.File.ReadAllText(path);
87 | SyntaxTree tree = CSharpSyntaxTree.ParseText(source);
88 | var root = (CompilationUnitSyntax) tree.GetRoot();
89 |
90 | var modelCollector = new ModelCollector();
91 | var enumCollector = new EnumCollector();
92 |
93 | modelCollector.Visit(root);
94 | enumCollector.Visit(root);
95 |
96 | return new File() {
97 | FileName = System.IO.Path.GetFullPath(path),
98 | Models = modelCollector.Models,
99 | Enums = enumCollector.Enums
100 | };
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/lib/csharp-models-to-json/Util.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.CodeAnalysis;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.CSharp.Syntax;
4 | using System;
5 | using System.Linq;
6 | using System.Text.RegularExpressions;
7 |
8 | namespace CSharpModelsToJson
9 | {
10 | internal static class Util
11 | {
12 | internal static bool IsObsolete(SyntaxList attributeLists) =>
13 | attributeLists.Any(attributeList =>
14 | attributeList.Attributes.Any(attribute =>
15 | attribute.Name.ToString().Equals("Obsolete") || attribute.Name.ToString().Equals("ObsoleteAttribute")));
16 |
17 | internal static string GetObsoleteMessage(SyntaxList attributeLists)
18 | {
19 | foreach (var attributeList in attributeLists)
20 | {
21 | var obsoleteAttribute =
22 | attributeList.Attributes.FirstOrDefault(attribute =>
23 | attribute.Name.ToString().Equals("Obsolete") || attribute.Name.ToString().Equals("ObsoleteAttribute"));
24 |
25 | if (obsoleteAttribute != null)
26 | {
27 | return obsoleteAttribute.ArgumentList == null
28 | ? null
29 | : obsoleteAttribute.ArgumentList.Arguments.ToString()?.TrimStart('@').Trim('"');
30 | }
31 | }
32 |
33 | return null;
34 | }
35 |
36 | internal static string GetSummaryMessage(SyntaxNode classItem)
37 | {
38 | return GetCommentTag(classItem, "summary");
39 | }
40 |
41 | internal static string GetRemarksMessage(SyntaxNode classItem)
42 | {
43 | return GetCommentTag(classItem, "remarks");
44 | }
45 |
46 | private static string GetCommentTag(SyntaxNode classItem, string xmlTag)
47 | {
48 | var documentComment = classItem.GetDocumentationCommentTriviaSyntax();
49 |
50 | if (documentComment == null)
51 | return null;
52 |
53 | var summaryElement = documentComment.Content
54 | .OfType()
55 | .FirstOrDefault(_ => _.StartTag.Name.LocalName.Text == xmlTag);
56 |
57 | if (summaryElement == null)
58 | return null;
59 |
60 | var summaryText = summaryElement.DescendantTokens()
61 | .Where(_ => _.Kind() == SyntaxKind.XmlTextLiteralToken)
62 | .Select(_ => _.Text.Trim())
63 | .ToList();
64 |
65 | var summaryContent = summaryElement.Content.ToString();
66 | summaryContent = Regex.Replace(summaryContent, @"^\s*///\s*", string.Empty, RegexOptions.Multiline);
67 | summaryContent = Regex.Replace(summaryContent, "^", Environment.NewLine, RegexOptions.Multiline);
68 | summaryContent = Regex.Replace(summaryContent, "", string.Empty);
69 |
70 | return summaryContent.Trim();
71 | }
72 |
73 | public static DocumentationCommentTriviaSyntax GetDocumentationCommentTriviaSyntax(this SyntaxNode node)
74 | {
75 | if (node == null)
76 | {
77 | return null;
78 | }
79 |
80 | foreach (var leadingTrivia in node.GetLeadingTrivia())
81 | {
82 | var structure = leadingTrivia.GetStructure() as DocumentationCommentTriviaSyntax;
83 |
84 | if (structure != null)
85 | {
86 | return structure;
87 | }
88 | }
89 |
90 | return null;
91 | }
92 |
93 | internal static bool GetEmitDefaultValue(SyntaxList attributeLists)
94 | {
95 | var dataMemberAttribute = attributeLists
96 | .SelectMany(attributeList => attributeList.Attributes)
97 | .FirstOrDefault(attribute => attribute.Name.ToString().Equals("DataMember") || attribute.Name.ToString().Equals("DataMemberAttribute"));
98 |
99 | if (dataMemberAttribute?.ArgumentList == null)
100 | return true;
101 |
102 | var emitDefaultValueArgument = dataMemberAttribute.ArgumentList.Arguments.FirstOrDefault(x => x.ToString().StartsWith("EmitDefaultValue"));
103 |
104 | if (emitDefaultValueArgument == null)
105 | return true;
106 |
107 | return !emitDefaultValueArgument.ToString().EndsWith("false");
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/lib/csharp-models-to-json/csharp-models-to-json.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net8.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/lib/csharp-models-to-json_test/EnumCollector_test.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.CSharp.Syntax;
4 | using NUnit.Framework;
5 |
6 | namespace CSharpModelsToJson.Tests
7 | {
8 | [TestFixture]
9 | public class EnumCollectorTest
10 | {
11 | [Test]
12 | public void ReturnEnumWithMissingValues()
13 | {
14 | var tree = CSharpSyntaxTree.ParseText(@"
15 | public enum SampleEnum
16 | {
17 | A,
18 | B = 7,
19 | C,
20 | D = 4,
21 | E
22 | }"
23 | );
24 |
25 | var root = (CompilationUnitSyntax)tree.GetRoot();
26 |
27 | var enumCollector = new EnumCollector();
28 | enumCollector.VisitEnumDeclaration(root.DescendantNodes().OfType().First());
29 |
30 | var model = enumCollector.Enums.First();
31 |
32 | Assert.That(model, Is.Not.Null);
33 | Assert.That(model.Values, Is.Not.Null);
34 |
35 | Assert.That(model.Values["A"].Value, Is.Null);
36 | Assert.That(model.Values["B"].Value, Is.EqualTo("7"));
37 | Assert.That(model.Values["C"].Value, Is.Null);
38 | Assert.That(model.Values["D"].Value, Is.EqualTo("4"));
39 | Assert.That(model.Values["E"].Value, Is.Null);
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/lib/csharp-models-to-json_test/ModelCollector_test.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using Microsoft.CodeAnalysis.CSharp;
3 | using Microsoft.CodeAnalysis.CSharp.Syntax;
4 | using NUnit.Framework;
5 |
6 | namespace CSharpModelsToJson.Tests
7 | {
8 | [TestFixture]
9 | public class ModelCollectorTest
10 | {
11 | [Test]
12 | public void BasicInheritance_ReturnsInheritedClass()
13 | {
14 | var tree = CSharpSyntaxTree.ParseText(@"
15 | public class A : B, C, D
16 | {
17 | public void AMember()
18 | {
19 | }
20 | }"
21 | );
22 |
23 | var root = (CompilationUnitSyntax)tree.GetRoot();
24 |
25 | var modelCollector = new ModelCollector();
26 | modelCollector.VisitClassDeclaration(root.DescendantNodes().OfType().First());
27 |
28 | Assert.That(modelCollector.Models, Is.Not.Null);
29 | Assert.That(modelCollector.Models.First().BaseClasses, Is.EqualTo(new[] { "B", "C", "D" }));
30 | }
31 |
32 | [Test]
33 | public void InterfaceImport_ReturnsSyntaxClassFromInterface()
34 | {
35 | var tree = CSharpSyntaxTree.ParseText(@"
36 | public interface IPhoneNumber {
37 | string Label { get; set; }
38 | string Number { get; set; }
39 | int MyProperty { get; set; }
40 | }
41 |
42 | public interface IPoint
43 | {
44 | // Property signatures:
45 | int x
46 | {
47 | get;
48 | set;
49 | }
50 |
51 | int y
52 | {
53 | get;
54 | set;
55 | }
56 | }
57 |
58 |
59 | public class X {
60 | public IPhoneNumber test { get; set; }
61 | public IPoint test2 { get; set; }
62 | }"
63 | );
64 |
65 | var root = (CompilationUnitSyntax)tree.GetRoot();
66 |
67 | var modelCollector = new ModelCollector();
68 | modelCollector.Visit(root);
69 |
70 | Assert.That(modelCollector.Models, Is.Not.Null);
71 | Assert.That(modelCollector.Models.Count, Is.EqualTo(3));
72 | Assert.That(modelCollector.Models.First().Properties.Count(), Is.EqualTo(3));
73 | }
74 |
75 |
76 | [Test]
77 | public void TypedInheritance_ReturnsInheritance()
78 | {
79 | var tree = CSharpSyntaxTree.ParseText(@"
80 | public class A : IController
81 | {
82 | public void AMember()
83 | {
84 | }
85 | }"
86 | );
87 |
88 | var root = (CompilationUnitSyntax)tree.GetRoot();
89 |
90 | var modelCollector = new ModelCollector();
91 | modelCollector.VisitClassDeclaration(root.DescendantNodes().OfType().First());
92 |
93 | Assert.That(modelCollector.Models, Is.Not.Null);
94 | Assert.That(modelCollector.Models.First().BaseClasses, Is.EqualTo(new[] { "IController" }));
95 | }
96 |
97 | [Test]
98 | public void AccessibilityRespected_ReturnsPublicOnly()
99 | {
100 | var tree = CSharpSyntaxTree.ParseText(@"
101 | public class A : IController
102 | {
103 | const int A_Constant = 0;
104 |
105 | private string B { get; set }
106 |
107 | static string C { get; set }
108 |
109 | public string Included { get; set }
110 |
111 | public void AMember()
112 | {
113 | }
114 | }"
115 | );
116 |
117 | var root = (CompilationUnitSyntax)tree.GetRoot();
118 |
119 | var modelCollector = new ModelCollector();
120 | modelCollector.VisitClassDeclaration(root.DescendantNodes().OfType().First());
121 |
122 | Assert.That(modelCollector.Models, Is.Not.Null);
123 | Assert.That(modelCollector.Models.First().Properties, Is.Not.Null);
124 | Assert.That(modelCollector.Models.First().Properties.Count(), Is.EqualTo(1));
125 | }
126 |
127 | [Test]
128 | public void IgnoresJsonIgnored_ReturnsOnlyNotIgnored()
129 | {
130 | var tree = CSharpSyntaxTree.ParseText(@"
131 | public class A : IController
132 | {
133 | const int A_Constant = 0;
134 |
135 | private string B { get; set }
136 |
137 | static string C { get; set }
138 |
139 | public string Included { get; set }
140 |
141 | [JsonIgnore]
142 | public string Ignored { get; set; }
143 |
144 | [IgnoreDataMember]
145 | public string Ignored2 { get; set; }
146 |
147 | public void AMember()
148 | {
149 | }
150 | }"
151 | );
152 |
153 | var root = (CompilationUnitSyntax)tree.GetRoot();
154 |
155 | var modelCollector = new ModelCollector();
156 | modelCollector.VisitClassDeclaration(root.DescendantNodes().OfType().First());
157 |
158 | Assert.That(modelCollector.Models, Is.Not.Null);
159 | Assert.That(modelCollector.Models.First().Properties, Is.Not.Null);
160 | Assert.That(modelCollector.Models.First().Properties.Count(), Is.EqualTo(1));
161 |
162 | }
163 |
164 | [Test]
165 | public void DictionaryInheritance_ReturnsIndexAccessor()
166 | {
167 | var tree = CSharpSyntaxTree.ParseText(@"public class A : Dictionary { }");
168 |
169 | var root = (CompilationUnitSyntax)tree.GetRoot();
170 |
171 | var modelCollector = new ModelCollector();
172 | modelCollector.VisitClassDeclaration(root.DescendantNodes().OfType().First());
173 |
174 | Assert.That(modelCollector.Models, Is.Not.Null);
175 | Assert.That(modelCollector.Models.First().BaseClasses, Is.Not.Null);
176 | Assert.That(modelCollector.Models.First().BaseClasses, Is.EqualTo(new[] { "Dictionary" }));
177 | }
178 |
179 | [Test]
180 | public void ReturnObsoleteClassInfo()
181 | {
182 | var tree = CSharpSyntaxTree.ParseText(@"
183 | [Obsolete(@""test"")]
184 | public class A
185 | {
186 | [Obsolete(@""test prop"")]
187 | public string A { get; set }
188 |
189 | public string B { get; set }
190 | }"
191 | );
192 |
193 | var root = (CompilationUnitSyntax)tree.GetRoot();
194 |
195 | var modelCollector = new ModelCollector();
196 | modelCollector.VisitClassDeclaration(root.DescendantNodes().OfType().First());
197 |
198 | var model = modelCollector.Models.First();
199 |
200 | Assert.That(model, Is.Not.Null);
201 | Assert.That(model.Properties, Is.Not.Null);
202 |
203 | Assert.That(model.ExtraInfo.Obsolete, Is.True);
204 | Assert.That(model.ExtraInfo.ObsoleteMessage, Is.EqualTo("test"));
205 |
206 | Assert.That(model.Properties.First(x => x.Identifier.Equals("A")).ExtraInfo.Obsolete, Is.True);
207 | Assert.That(model.Properties.First(x => x.Identifier.Equals("A")).ExtraInfo.ObsoleteMessage, Is.EqualTo("test prop"));
208 |
209 | Assert.That(model.Properties.First(x => x.Identifier.Equals("B")).ExtraInfo.Obsolete, Is.False);
210 | Assert.That(model.Properties.First(x => x.Identifier.Equals("B")).ExtraInfo.ObsoleteMessage, Is.Null);
211 | }
212 |
213 | [Test]
214 | public void ReturnObsoleteEnumInfo()
215 | {
216 | var tree = CSharpSyntaxTree.ParseText(@"
217 | [Obsolete(@""test"")]
218 | public enum A
219 | {
220 | A = 0,
221 | B = 1,
222 | }"
223 | );
224 |
225 | var root = (CompilationUnitSyntax)tree.GetRoot();
226 |
227 | var enumCollector = new EnumCollector();
228 | enumCollector.VisitEnumDeclaration(root.DescendantNodes().OfType().First());
229 |
230 | var model = enumCollector.Enums.First();
231 |
232 | Assert.That(model, Is.Not.Null) ;
233 | Assert.That(model.Values, Is.Not.Null);
234 |
235 | Assert.That(model.ExtraInfo.Obsolete, Is.True);
236 | Assert.That(model.ExtraInfo.ObsoleteMessage, Is.EqualTo("test"));
237 | }
238 |
239 | [Test]
240 | public void EnumBinaryValue()
241 | {
242 | var tree = CSharpSyntaxTree.ParseText(@"
243 | public enum A {
244 | A = 1, // decimal: 1
245 | B = 1_002, // decimal: 1002
246 | C = 0b011, // binary: 3 in decimal
247 | D = 0b_0000_0100, // binary: 4 in decimal
248 | E = 0x005, // hexadecimal: 5 in decimal
249 | F = 0x000_01a, // hexadecimal: 26 in decimal
250 | }"
251 | );
252 |
253 | var root = (CompilationUnitSyntax)tree.GetRoot();
254 |
255 | var enumCollector = new EnumCollector();
256 | enumCollector.VisitEnumDeclaration(root.DescendantNodes().OfType().First());
257 |
258 | var model = enumCollector.Enums.First();
259 |
260 | Assert.That(model, Is.Not.Null);
261 | Assert.That(model.Values, Is.Not.Null);
262 |
263 | Assert.That(model.Values["A"].Value, Is.EqualTo("1"));
264 | Assert.That(model.Values["B"].Value, Is.EqualTo("1002"));
265 | Assert.That(model.Values["C"].Value, Is.EqualTo("0b011"));
266 | Assert.That(model.Values["D"].Value, Is.EqualTo("0b00000100"));
267 | Assert.That(model.Values["E"].Value, Is.EqualTo("0x005"));
268 | Assert.That(model.Values["F"].Value, Is.EqualTo("0x00001a"));
269 | }
270 |
271 | [Test]
272 | public void ReturnEmmitDefaultValueInfo()
273 | {
274 | var tree = CSharpSyntaxTree.ParseText(@"
275 | public class A
276 | {
277 | [DataMember(EmitDefaultValue = false)]
278 | public bool Prop1 { get; set; }
279 |
280 | [DataMember(EmitDefaultValue = true)]
281 | public bool Prop2 { get; set; }
282 |
283 | [DataMember( EmitDefaultValue = false )]
284 | public bool Prop3 { get; set; }
285 |
286 | [DataMember]
287 | public bool Prop4 { get; set; }
288 |
289 | public bool Prop5 { get; set; }
290 | }"
291 | );
292 |
293 | var root = (CompilationUnitSyntax)tree.GetRoot();
294 |
295 | var modelCollector = new ModelCollector();
296 | modelCollector.Visit(root);
297 |
298 | Assert.That(modelCollector.Models, Is.Not.Null);
299 | Assert.That(modelCollector.Models.Count, Is.EqualTo(1));
300 |
301 | var properties = modelCollector.Models.First().Properties;
302 |
303 | Assert.That(properties.First(x => x.Identifier == "Prop1").ExtraInfo.EmitDefaultValue, Is.False);
304 | Assert.That(properties.First(x => x.Identifier == "Prop2").ExtraInfo.EmitDefaultValue, Is.True);
305 | Assert.That(properties.First(x => x.Identifier == "Prop3").ExtraInfo.EmitDefaultValue, Is.False);
306 | Assert.That(properties.First(x => x.Identifier == "Prop4").ExtraInfo.EmitDefaultValue, Is.True);
307 | Assert.That(properties.First(x => x.Identifier == "Prop5").ExtraInfo.EmitDefaultValue, Is.True);
308 | }
309 |
310 | }
311 | }
--------------------------------------------------------------------------------
/lib/csharp-models-to-json_test/csharp-models-to-json_test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Exe
4 | net8.0
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {BAEFF23F-0CE8-48B5-899A-56251439C30C}
15 | csharp-models-to-json
16 |
17 |
18 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "csharp-models-to-typescript",
3 | "version": "1.2.0",
4 | "lockfileVersion": 2,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "csharp-models-to-typescript",
9 | "version": "1.2.0",
10 | "license": "MIT",
11 | "dependencies": {
12 | "camelcase": "^6.0.0"
13 | },
14 | "bin": {
15 | "csharp-models-to-typescript": "index.js"
16 | },
17 | "devDependencies": {
18 | "rimraf": "^5.0.7"
19 | },
20 | "engines": {
21 | "node": ">=6.0.0"
22 | }
23 | },
24 | "node_modules/@isaacs/cliui": {
25 | "version": "8.0.2",
26 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
27 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
28 | "dev": true,
29 | "dependencies": {
30 | "string-width": "^5.1.2",
31 | "string-width-cjs": "npm:string-width@^4.2.0",
32 | "strip-ansi": "^7.0.1",
33 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
34 | "wrap-ansi": "^8.1.0",
35 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
36 | },
37 | "engines": {
38 | "node": ">=12"
39 | }
40 | },
41 | "node_modules/@pkgjs/parseargs": {
42 | "version": "0.11.0",
43 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
44 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
45 | "dev": true,
46 | "optional": true,
47 | "engines": {
48 | "node": ">=14"
49 | }
50 | },
51 | "node_modules/ansi-regex": {
52 | "version": "6.0.1",
53 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
54 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
55 | "dev": true,
56 | "engines": {
57 | "node": ">=12"
58 | },
59 | "funding": {
60 | "url": "https://github.com/chalk/ansi-regex?sponsor=1"
61 | }
62 | },
63 | "node_modules/ansi-styles": {
64 | "version": "6.2.1",
65 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
66 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
67 | "dev": true,
68 | "engines": {
69 | "node": ">=12"
70 | },
71 | "funding": {
72 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
73 | }
74 | },
75 | "node_modules/balanced-match": {
76 | "version": "1.0.2",
77 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
78 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
79 | "dev": true
80 | },
81 | "node_modules/brace-expansion": {
82 | "version": "2.0.1",
83 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
84 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
85 | "dev": true,
86 | "dependencies": {
87 | "balanced-match": "^1.0.0"
88 | }
89 | },
90 | "node_modules/camelcase": {
91 | "version": "6.0.0",
92 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz",
93 | "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==",
94 | "engines": {
95 | "node": ">=10"
96 | },
97 | "funding": {
98 | "url": "https://github.com/sponsors/sindresorhus"
99 | }
100 | },
101 | "node_modules/color-convert": {
102 | "version": "2.0.1",
103 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
104 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
105 | "dev": true,
106 | "dependencies": {
107 | "color-name": "~1.1.4"
108 | },
109 | "engines": {
110 | "node": ">=7.0.0"
111 | }
112 | },
113 | "node_modules/color-name": {
114 | "version": "1.1.4",
115 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
116 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
117 | "dev": true
118 | },
119 | "node_modules/cross-spawn": {
120 | "version": "7.0.6",
121 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
122 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
123 | "dev": true,
124 | "dependencies": {
125 | "path-key": "^3.1.0",
126 | "shebang-command": "^2.0.0",
127 | "which": "^2.0.1"
128 | },
129 | "engines": {
130 | "node": ">= 8"
131 | }
132 | },
133 | "node_modules/eastasianwidth": {
134 | "version": "0.2.0",
135 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
136 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
137 | "dev": true
138 | },
139 | "node_modules/emoji-regex": {
140 | "version": "9.2.2",
141 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
142 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
143 | "dev": true
144 | },
145 | "node_modules/foreground-child": {
146 | "version": "3.1.1",
147 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
148 | "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
149 | "dev": true,
150 | "dependencies": {
151 | "cross-spawn": "^7.0.0",
152 | "signal-exit": "^4.0.1"
153 | },
154 | "engines": {
155 | "node": ">=14"
156 | },
157 | "funding": {
158 | "url": "https://github.com/sponsors/isaacs"
159 | }
160 | },
161 | "node_modules/glob": {
162 | "version": "10.3.16",
163 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz",
164 | "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==",
165 | "dev": true,
166 | "dependencies": {
167 | "foreground-child": "^3.1.0",
168 | "jackspeak": "^3.1.2",
169 | "minimatch": "^9.0.1",
170 | "minipass": "^7.0.4",
171 | "path-scurry": "^1.11.0"
172 | },
173 | "bin": {
174 | "glob": "dist/esm/bin.mjs"
175 | },
176 | "engines": {
177 | "node": ">=16 || 14 >=14.18"
178 | },
179 | "funding": {
180 | "url": "https://github.com/sponsors/isaacs"
181 | }
182 | },
183 | "node_modules/is-fullwidth-code-point": {
184 | "version": "3.0.0",
185 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
186 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
187 | "dev": true,
188 | "engines": {
189 | "node": ">=8"
190 | }
191 | },
192 | "node_modules/isexe": {
193 | "version": "2.0.0",
194 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
195 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
196 | "dev": true
197 | },
198 | "node_modules/jackspeak": {
199 | "version": "3.1.2",
200 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
201 | "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
202 | "dev": true,
203 | "dependencies": {
204 | "@isaacs/cliui": "^8.0.2"
205 | },
206 | "engines": {
207 | "node": ">=14"
208 | },
209 | "funding": {
210 | "url": "https://github.com/sponsors/isaacs"
211 | },
212 | "optionalDependencies": {
213 | "@pkgjs/parseargs": "^0.11.0"
214 | }
215 | },
216 | "node_modules/lru-cache": {
217 | "version": "10.2.2",
218 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
219 | "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
220 | "dev": true,
221 | "engines": {
222 | "node": "14 || >=16.14"
223 | }
224 | },
225 | "node_modules/minimatch": {
226 | "version": "9.0.4",
227 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
228 | "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
229 | "dev": true,
230 | "dependencies": {
231 | "brace-expansion": "^2.0.1"
232 | },
233 | "engines": {
234 | "node": ">=16 || 14 >=14.17"
235 | },
236 | "funding": {
237 | "url": "https://github.com/sponsors/isaacs"
238 | }
239 | },
240 | "node_modules/minipass": {
241 | "version": "7.1.1",
242 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
243 | "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
244 | "dev": true,
245 | "engines": {
246 | "node": ">=16 || 14 >=14.17"
247 | }
248 | },
249 | "node_modules/path-key": {
250 | "version": "3.1.1",
251 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
252 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
253 | "dev": true,
254 | "engines": {
255 | "node": ">=8"
256 | }
257 | },
258 | "node_modules/path-scurry": {
259 | "version": "1.11.1",
260 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
261 | "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
262 | "dev": true,
263 | "dependencies": {
264 | "lru-cache": "^10.2.0",
265 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
266 | },
267 | "engines": {
268 | "node": ">=16 || 14 >=14.18"
269 | },
270 | "funding": {
271 | "url": "https://github.com/sponsors/isaacs"
272 | }
273 | },
274 | "node_modules/rimraf": {
275 | "version": "5.0.7",
276 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz",
277 | "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==",
278 | "dev": true,
279 | "dependencies": {
280 | "glob": "^10.3.7"
281 | },
282 | "bin": {
283 | "rimraf": "dist/esm/bin.mjs"
284 | },
285 | "engines": {
286 | "node": ">=14.18"
287 | },
288 | "funding": {
289 | "url": "https://github.com/sponsors/isaacs"
290 | }
291 | },
292 | "node_modules/shebang-command": {
293 | "version": "2.0.0",
294 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
295 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
296 | "dev": true,
297 | "dependencies": {
298 | "shebang-regex": "^3.0.0"
299 | },
300 | "engines": {
301 | "node": ">=8"
302 | }
303 | },
304 | "node_modules/shebang-regex": {
305 | "version": "3.0.0",
306 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
307 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
308 | "dev": true,
309 | "engines": {
310 | "node": ">=8"
311 | }
312 | },
313 | "node_modules/signal-exit": {
314 | "version": "4.1.0",
315 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
316 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
317 | "dev": true,
318 | "engines": {
319 | "node": ">=14"
320 | },
321 | "funding": {
322 | "url": "https://github.com/sponsors/isaacs"
323 | }
324 | },
325 | "node_modules/string-width": {
326 | "version": "5.1.2",
327 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
328 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
329 | "dev": true,
330 | "dependencies": {
331 | "eastasianwidth": "^0.2.0",
332 | "emoji-regex": "^9.2.2",
333 | "strip-ansi": "^7.0.1"
334 | },
335 | "engines": {
336 | "node": ">=12"
337 | },
338 | "funding": {
339 | "url": "https://github.com/sponsors/sindresorhus"
340 | }
341 | },
342 | "node_modules/string-width-cjs": {
343 | "name": "string-width",
344 | "version": "4.2.3",
345 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
346 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
347 | "dev": true,
348 | "dependencies": {
349 | "emoji-regex": "^8.0.0",
350 | "is-fullwidth-code-point": "^3.0.0",
351 | "strip-ansi": "^6.0.1"
352 | },
353 | "engines": {
354 | "node": ">=8"
355 | }
356 | },
357 | "node_modules/string-width-cjs/node_modules/ansi-regex": {
358 | "version": "5.0.1",
359 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
360 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
361 | "dev": true,
362 | "engines": {
363 | "node": ">=8"
364 | }
365 | },
366 | "node_modules/string-width-cjs/node_modules/emoji-regex": {
367 | "version": "8.0.0",
368 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
369 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
370 | "dev": true
371 | },
372 | "node_modules/string-width-cjs/node_modules/strip-ansi": {
373 | "version": "6.0.1",
374 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
375 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
376 | "dev": true,
377 | "dependencies": {
378 | "ansi-regex": "^5.0.1"
379 | },
380 | "engines": {
381 | "node": ">=8"
382 | }
383 | },
384 | "node_modules/strip-ansi": {
385 | "version": "7.1.0",
386 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
387 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
388 | "dev": true,
389 | "dependencies": {
390 | "ansi-regex": "^6.0.1"
391 | },
392 | "engines": {
393 | "node": ">=12"
394 | },
395 | "funding": {
396 | "url": "https://github.com/chalk/strip-ansi?sponsor=1"
397 | }
398 | },
399 | "node_modules/strip-ansi-cjs": {
400 | "name": "strip-ansi",
401 | "version": "6.0.1",
402 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
403 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
404 | "dev": true,
405 | "dependencies": {
406 | "ansi-regex": "^5.0.1"
407 | },
408 | "engines": {
409 | "node": ">=8"
410 | }
411 | },
412 | "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
413 | "version": "5.0.1",
414 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
415 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
416 | "dev": true,
417 | "engines": {
418 | "node": ">=8"
419 | }
420 | },
421 | "node_modules/which": {
422 | "version": "2.0.2",
423 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
424 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
425 | "dev": true,
426 | "dependencies": {
427 | "isexe": "^2.0.0"
428 | },
429 | "bin": {
430 | "node-which": "bin/node-which"
431 | },
432 | "engines": {
433 | "node": ">= 8"
434 | }
435 | },
436 | "node_modules/wrap-ansi": {
437 | "version": "8.1.0",
438 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
439 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
440 | "dev": true,
441 | "dependencies": {
442 | "ansi-styles": "^6.1.0",
443 | "string-width": "^5.0.1",
444 | "strip-ansi": "^7.0.1"
445 | },
446 | "engines": {
447 | "node": ">=12"
448 | },
449 | "funding": {
450 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
451 | }
452 | },
453 | "node_modules/wrap-ansi-cjs": {
454 | "name": "wrap-ansi",
455 | "version": "7.0.0",
456 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
457 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
458 | "dev": true,
459 | "dependencies": {
460 | "ansi-styles": "^4.0.0",
461 | "string-width": "^4.1.0",
462 | "strip-ansi": "^6.0.0"
463 | },
464 | "engines": {
465 | "node": ">=10"
466 | },
467 | "funding": {
468 | "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
469 | }
470 | },
471 | "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
472 | "version": "5.0.1",
473 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
474 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
475 | "dev": true,
476 | "engines": {
477 | "node": ">=8"
478 | }
479 | },
480 | "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
481 | "version": "4.3.0",
482 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
483 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
484 | "dev": true,
485 | "dependencies": {
486 | "color-convert": "^2.0.1"
487 | },
488 | "engines": {
489 | "node": ">=8"
490 | },
491 | "funding": {
492 | "url": "https://github.com/chalk/ansi-styles?sponsor=1"
493 | }
494 | },
495 | "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
496 | "version": "8.0.0",
497 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
498 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
499 | "dev": true
500 | },
501 | "node_modules/wrap-ansi-cjs/node_modules/string-width": {
502 | "version": "4.2.3",
503 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
504 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
505 | "dev": true,
506 | "dependencies": {
507 | "emoji-regex": "^8.0.0",
508 | "is-fullwidth-code-point": "^3.0.0",
509 | "strip-ansi": "^6.0.1"
510 | },
511 | "engines": {
512 | "node": ">=8"
513 | }
514 | },
515 | "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
516 | "version": "6.0.1",
517 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
518 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
519 | "dev": true,
520 | "dependencies": {
521 | "ansi-regex": "^5.0.1"
522 | },
523 | "engines": {
524 | "node": ">=8"
525 | }
526 | }
527 | },
528 | "dependencies": {
529 | "@isaacs/cliui": {
530 | "version": "8.0.2",
531 | "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
532 | "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
533 | "dev": true,
534 | "requires": {
535 | "string-width": "^5.1.2",
536 | "string-width-cjs": "npm:string-width@^4.2.0",
537 | "strip-ansi": "^7.0.1",
538 | "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
539 | "wrap-ansi": "^8.1.0",
540 | "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
541 | }
542 | },
543 | "@pkgjs/parseargs": {
544 | "version": "0.11.0",
545 | "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
546 | "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
547 | "dev": true,
548 | "optional": true
549 | },
550 | "ansi-regex": {
551 | "version": "6.0.1",
552 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
553 | "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
554 | "dev": true
555 | },
556 | "ansi-styles": {
557 | "version": "6.2.1",
558 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
559 | "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
560 | "dev": true
561 | },
562 | "balanced-match": {
563 | "version": "1.0.2",
564 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
565 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
566 | "dev": true
567 | },
568 | "brace-expansion": {
569 | "version": "2.0.1",
570 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
571 | "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
572 | "dev": true,
573 | "requires": {
574 | "balanced-match": "^1.0.0"
575 | }
576 | },
577 | "camelcase": {
578 | "version": "6.0.0",
579 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz",
580 | "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w=="
581 | },
582 | "color-convert": {
583 | "version": "2.0.1",
584 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
585 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
586 | "dev": true,
587 | "requires": {
588 | "color-name": "~1.1.4"
589 | }
590 | },
591 | "color-name": {
592 | "version": "1.1.4",
593 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
594 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
595 | "dev": true
596 | },
597 | "cross-spawn": {
598 | "version": "7.0.6",
599 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
600 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
601 | "dev": true,
602 | "requires": {
603 | "path-key": "^3.1.0",
604 | "shebang-command": "^2.0.0",
605 | "which": "^2.0.1"
606 | }
607 | },
608 | "eastasianwidth": {
609 | "version": "0.2.0",
610 | "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
611 | "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
612 | "dev": true
613 | },
614 | "emoji-regex": {
615 | "version": "9.2.2",
616 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
617 | "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
618 | "dev": true
619 | },
620 | "foreground-child": {
621 | "version": "3.1.1",
622 | "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
623 | "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
624 | "dev": true,
625 | "requires": {
626 | "cross-spawn": "^7.0.0",
627 | "signal-exit": "^4.0.1"
628 | }
629 | },
630 | "glob": {
631 | "version": "10.3.16",
632 | "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.16.tgz",
633 | "integrity": "sha512-JDKXl1DiuuHJ6fVS2FXjownaavciiHNUU4mOvV/B793RLh05vZL1rcPnCSaOgv1hDT6RDlY7AB7ZUvFYAtPgAw==",
634 | "dev": true,
635 | "requires": {
636 | "foreground-child": "^3.1.0",
637 | "jackspeak": "^3.1.2",
638 | "minimatch": "^9.0.1",
639 | "minipass": "^7.0.4",
640 | "path-scurry": "^1.11.0"
641 | }
642 | },
643 | "is-fullwidth-code-point": {
644 | "version": "3.0.0",
645 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
646 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
647 | "dev": true
648 | },
649 | "isexe": {
650 | "version": "2.0.0",
651 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
652 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
653 | "dev": true
654 | },
655 | "jackspeak": {
656 | "version": "3.1.2",
657 | "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.1.2.tgz",
658 | "integrity": "sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==",
659 | "dev": true,
660 | "requires": {
661 | "@isaacs/cliui": "^8.0.2",
662 | "@pkgjs/parseargs": "^0.11.0"
663 | }
664 | },
665 | "lru-cache": {
666 | "version": "10.2.2",
667 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz",
668 | "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==",
669 | "dev": true
670 | },
671 | "minimatch": {
672 | "version": "9.0.4",
673 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
674 | "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
675 | "dev": true,
676 | "requires": {
677 | "brace-expansion": "^2.0.1"
678 | }
679 | },
680 | "minipass": {
681 | "version": "7.1.1",
682 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
683 | "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
684 | "dev": true
685 | },
686 | "path-key": {
687 | "version": "3.1.1",
688 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
689 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
690 | "dev": true
691 | },
692 | "path-scurry": {
693 | "version": "1.11.1",
694 | "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
695 | "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
696 | "dev": true,
697 | "requires": {
698 | "lru-cache": "^10.2.0",
699 | "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
700 | }
701 | },
702 | "rimraf": {
703 | "version": "5.0.7",
704 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.7.tgz",
705 | "integrity": "sha512-nV6YcJo5wbLW77m+8KjH8aB/7/rxQy9SZ0HY5shnwULfS+9nmTtVXAJET5NdZmCzA4fPI/Hm1wo/Po/4mopOdg==",
706 | "dev": true,
707 | "requires": {
708 | "glob": "^10.3.7"
709 | }
710 | },
711 | "shebang-command": {
712 | "version": "2.0.0",
713 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
714 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
715 | "dev": true,
716 | "requires": {
717 | "shebang-regex": "^3.0.0"
718 | }
719 | },
720 | "shebang-regex": {
721 | "version": "3.0.0",
722 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
723 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
724 | "dev": true
725 | },
726 | "signal-exit": {
727 | "version": "4.1.0",
728 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
729 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
730 | "dev": true
731 | },
732 | "string-width": {
733 | "version": "5.1.2",
734 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
735 | "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
736 | "dev": true,
737 | "requires": {
738 | "eastasianwidth": "^0.2.0",
739 | "emoji-regex": "^9.2.2",
740 | "strip-ansi": "^7.0.1"
741 | }
742 | },
743 | "string-width-cjs": {
744 | "version": "npm:string-width@4.2.3",
745 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
746 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
747 | "dev": true,
748 | "requires": {
749 | "emoji-regex": "^8.0.0",
750 | "is-fullwidth-code-point": "^3.0.0",
751 | "strip-ansi": "^6.0.1"
752 | },
753 | "dependencies": {
754 | "ansi-regex": {
755 | "version": "5.0.1",
756 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
757 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
758 | "dev": true
759 | },
760 | "emoji-regex": {
761 | "version": "8.0.0",
762 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
763 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
764 | "dev": true
765 | },
766 | "strip-ansi": {
767 | "version": "6.0.1",
768 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
769 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
770 | "dev": true,
771 | "requires": {
772 | "ansi-regex": "^5.0.1"
773 | }
774 | }
775 | }
776 | },
777 | "strip-ansi": {
778 | "version": "7.1.0",
779 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
780 | "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
781 | "dev": true,
782 | "requires": {
783 | "ansi-regex": "^6.0.1"
784 | }
785 | },
786 | "strip-ansi-cjs": {
787 | "version": "npm:strip-ansi@6.0.1",
788 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
789 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
790 | "dev": true,
791 | "requires": {
792 | "ansi-regex": "^5.0.1"
793 | },
794 | "dependencies": {
795 | "ansi-regex": {
796 | "version": "5.0.1",
797 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
798 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
799 | "dev": true
800 | }
801 | }
802 | },
803 | "which": {
804 | "version": "2.0.2",
805 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
806 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
807 | "dev": true,
808 | "requires": {
809 | "isexe": "^2.0.0"
810 | }
811 | },
812 | "wrap-ansi": {
813 | "version": "8.1.0",
814 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
815 | "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
816 | "dev": true,
817 | "requires": {
818 | "ansi-styles": "^6.1.0",
819 | "string-width": "^5.0.1",
820 | "strip-ansi": "^7.0.1"
821 | }
822 | },
823 | "wrap-ansi-cjs": {
824 | "version": "npm:wrap-ansi@7.0.0",
825 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
826 | "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
827 | "dev": true,
828 | "requires": {
829 | "ansi-styles": "^4.0.0",
830 | "string-width": "^4.1.0",
831 | "strip-ansi": "^6.0.0"
832 | },
833 | "dependencies": {
834 | "ansi-regex": {
835 | "version": "5.0.1",
836 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
837 | "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
838 | "dev": true
839 | },
840 | "ansi-styles": {
841 | "version": "4.3.0",
842 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
843 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
844 | "dev": true,
845 | "requires": {
846 | "color-convert": "^2.0.1"
847 | }
848 | },
849 | "emoji-regex": {
850 | "version": "8.0.0",
851 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
852 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
853 | "dev": true
854 | },
855 | "string-width": {
856 | "version": "4.2.3",
857 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
858 | "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
859 | "dev": true,
860 | "requires": {
861 | "emoji-regex": "^8.0.0",
862 | "is-fullwidth-code-point": "^3.0.0",
863 | "strip-ansi": "^6.0.1"
864 | }
865 | },
866 | "strip-ansi": {
867 | "version": "6.0.1",
868 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
869 | "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
870 | "dev": true,
871 | "requires": {
872 | "ansi-regex": "^5.0.1"
873 | }
874 | }
875 | }
876 | }
877 | }
878 | }
879 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "csharp-models-to-typescript",
3 | "version": "1.2.0",
4 | "title": "C# models to TypeScript",
5 | "author": "Jonathan Svenheden ",
6 | "license": "MIT",
7 | "repository": {
8 | "type": "git",
9 | "url": "https://github.com/svenheden/csharp-models-to-typescript.git"
10 | },
11 | "engines": {
12 | "node": ">=6.0.0"
13 | },
14 | "bin": {
15 | "csharp-models-to-typescript": "./index.js"
16 | },
17 | "scripts": {
18 | "test": "rimraf -I ./test-files/api.d.ts && node index.js --config=./test-files/test-config.json && node ./test-files/test-file-exists.js --config=./test-files/test-config.json"
19 | },
20 | "dependencies": {
21 | "camelcase": "^6.0.0"
22 | },
23 | "devDependencies": {
24 | "rimraf": "^5.0.7"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test-files/TestFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.Runtime.Serialization;
5 |
6 | namespace TestNamespace
7 | {
8 | ///
9 | /// Sample class comment.
10 | ///
11 | public class TestClass
12 | {
13 | ///
14 | /// Sample comment.
15 | ///
16 | public int IntProperty { get; set; }
17 |
18 | [Obsolete("obsolete test prop")]
19 | public string StringProperty { get; set; }
20 |
21 | [DataMember(EmitDefaultValue = false)]
22 | public DateTime DateTimeProperty { get; set; }
23 |
24 | public bool BooleanProperty { get; set; }
25 | }
26 |
27 | public enum TestEnum {
28 | A = 1, // decimal: 1
29 | B = 1_002, // decimal: 1002
30 | C = 0b011, // binary: 3 in decimal
31 | D = 0b_0000_0100, // binary: 4 in decimal
32 | E = 0x005, // hexadecimal: 5 in decimal
33 | F = 0x000_01a, // hexadecimal: 26 in decimal
34 | [Obsolete("obsolete test enum")]
35 | G // 27 in decimal
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test-files/test-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": [
3 | "test-files/*.cs"
4 | ],
5 | "exclude": [ ],
6 | "output": "./test-files/api.d.ts",
7 | "camelCase": false,
8 | "camelCaseEnums": false,
9 | "camelCaseOptions": {
10 | "pascalCase": false,
11 | "preserveConsecutiveUppercase": false,
12 | "locale": "en-US"
13 | },
14 | "includeComments": true,
15 | "numericEnums": true,
16 | "validateEmitDefaultValue": true,
17 | "omitSemicolon": true,
18 | "omitFilePathComment": true,
19 | "stringLiteralTypesInsteadOfEnums": false,
20 | "customTypeTranslations": {
21 | "ProductName": "string",
22 | "ProductNumber": "string",
23 | "byte": "number",
24 | "uint": "number"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/test-files/test-file-exists.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 |
3 | const configArg = process.argv.find(x => x.startsWith('--config='));
4 |
5 | if (!configArg) {
6 | throw Error('No configuration file for `csharp-models-to-typescript` provided.');
7 | }
8 |
9 | const configPath = configArg.substr('--config='.length);
10 | let config;
11 |
12 | try {
13 | unparsedConfig = fs.readFileSync(configPath, 'utf8');
14 | } catch (error) {
15 | throw Error(`Configuration file "${configPath}" not found.\n${error.message}`);
16 | }
17 |
18 | try {
19 | config = JSON.parse(unparsedConfig);
20 | } catch (error) {
21 | throw Error(`Configuration file "${configPath}" contains invalid JSON.\n${error.message}`);
22 | }
23 |
24 | const output = config.output || 'types.d.ts';
25 |
26 | if (!fs.existsSync(output))
27 | throw Error(`Can't find output file: ${output}`)
28 |
29 | const file = fs.readFileSync(output, 'utf8')
30 | if (file.length === 0)
31 | throw Error(`File '${output}' is empty`)
--------------------------------------------------------------------------------