├── .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`) --------------------------------------------------------------------------------