dto2ts 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,218 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.parseOpenApi = parseOpenApi;
37
+ const fs = __importStar(require("fs"));
38
+ const naming_1 = require("../utils/naming");
39
+ const resources_1 = require("./resources");
40
+ async function loadDoc(input) {
41
+ if (/^https?:\/\//i.test(input)) {
42
+ const res = await fetch(input);
43
+ if (!res.ok)
44
+ throw new Error(`OpenAPI indirilemedi (${res.status}): ${input}`);
45
+ return (await res.json());
46
+ }
47
+ return JSON.parse(fs.readFileSync(input, "utf8"));
48
+ }
49
+ function refName(ref) {
50
+ return ref.split("/").pop() ?? ref;
51
+ }
52
+ function isNullable(schema) {
53
+ if (!schema)
54
+ return false;
55
+ if (schema.nullable === true)
56
+ return true; // OpenAPI 3.0
57
+ if (Array.isArray(schema.type) && schema.type.includes("null"))
58
+ return true; // 3.1
59
+ return false;
60
+ }
61
+ /** Bir şemayı TS tip ifadesine çevir (inline nested objeleri type literal olarak). */
62
+ function schemaToTsType(schema, config) {
63
+ if (!schema)
64
+ return "unknown";
65
+ if (schema.$ref)
66
+ return refName(schema.$ref);
67
+ // allOf: ilk $ref'i temel al; yoksa unknown.
68
+ if (Array.isArray(schema.allOf)) {
69
+ const ref = schema.allOf.find((s) => s.$ref);
70
+ if (ref)
71
+ return refName(ref.$ref);
72
+ return "unknown";
73
+ }
74
+ if (Array.isArray(schema.oneOf) || Array.isArray(schema.anyOf)) {
75
+ const variants = (schema.oneOf ?? schema.anyOf);
76
+ const parts = variants.filter((s) => s && !s.$ref?.includes("null")).map((s) => schemaToTsType(s, config));
77
+ return parts.length ? [...new Set(parts)].join(" | ") : "unknown";
78
+ }
79
+ // type bir dizi olabilir (3.1): ["string","null"]
80
+ const type = Array.isArray(schema.type)
81
+ ? schema.type.find((t) => t !== "null")
82
+ : schema.type;
83
+ if (schema.enum && Array.isArray(schema.enum)) {
84
+ return schema.enum
85
+ .map((v) => (typeof v === "string" ? JSON.stringify(v) : String(v)))
86
+ .join(" | ");
87
+ }
88
+ switch (type) {
89
+ case "string":
90
+ if (["date-time", "date", "time"].includes(schema.format)) {
91
+ return config.dateAsString ? "string" : "Date";
92
+ }
93
+ return "string";
94
+ case "integer":
95
+ case "number":
96
+ return "number";
97
+ case "boolean":
98
+ return "boolean";
99
+ case "array":
100
+ return `${schemaToTsType(schema.items, config)}[]`;
101
+ case "object":
102
+ default: {
103
+ if (schema.properties) {
104
+ const props = Object.entries(schema.properties).map(([key, val]) => {
105
+ const req = Array.isArray(schema.required) && schema.required.includes(key);
106
+ const opt = !req || isNullable(val) ? "?" : "";
107
+ const name = config.camelCase ? (0, naming_1.toCamelCase)(key) : key;
108
+ return `${name}${opt}: ${schemaToTsType(val, config)}`;
109
+ });
110
+ return `{ ${props.join("; ")} }`;
111
+ }
112
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
113
+ return `Record<string, ${schemaToTsType(schema.additionalProperties, config)}>`;
114
+ }
115
+ if (schema.additionalProperties === true)
116
+ return "Record<string, unknown>";
117
+ return "unknown";
118
+ }
119
+ }
120
+ }
121
+ function schemaToModel(name, schema, config) {
122
+ // Enum modeli
123
+ if (schema.enum && Array.isArray(schema.enum) && (schema.type === "string" || schema.type === "integer")) {
124
+ const members = schema.enum.map((v, i) => {
125
+ if (typeof v === "number")
126
+ return { name: `Value${v}`, value: v };
127
+ const raw = String(v);
128
+ const memberName = (0, naming_1.toPascalCase)(raw.replace(/[^A-Za-z0-9_]/g, "_")) || `Value${i}`;
129
+ return { name: memberName, value: raw };
130
+ });
131
+ return { name, kind: "enum", properties: [], members };
132
+ }
133
+ // allOf ile kalıtım
134
+ let extendsList = [];
135
+ let effective = schema;
136
+ if (Array.isArray(schema.allOf)) {
137
+ extendsList = schema.allOf.filter((s) => s.$ref).map((s) => refName(s.$ref));
138
+ const objPart = schema.allOf.find((s) => s.properties);
139
+ if (objPart)
140
+ effective = objPart;
141
+ }
142
+ if (!effective.properties) {
143
+ // Alanı olmayan obje -> boş interface (yine de üret).
144
+ return { name, kind: "interface", properties: [], members: [], extends: extendsList };
145
+ }
146
+ const required = Array.isArray(effective.required) ? effective.required : [];
147
+ const properties = Object.entries(effective.properties).map(([key, val]) => {
148
+ const optional = !required.includes(key) || isNullable(val);
149
+ return {
150
+ name: config.camelCase ? (0, naming_1.toCamelCase)(key) : key,
151
+ originalName: key,
152
+ type: schemaToTsType(val, config),
153
+ optional,
154
+ description: val.description,
155
+ };
156
+ });
157
+ return { name, kind: "interface", properties, members: [], extends: extendsList, description: schema.description };
158
+ }
159
+ /** paths bölümünden kaynak (resource) base path'lerini türet. */
160
+ function resourcesFromPaths(doc, models, config) {
161
+ const paths = doc.paths;
162
+ if (!paths || typeof paths !== "object")
163
+ return [];
164
+ // path -> bu path'te referans verilen şema adları
165
+ const modelNames = new Set(models.filter((m) => m.kind === "interface").map((m) => m.name));
166
+ const byResource = new Map();
167
+ const collectRefModels = (obj, acc) => {
168
+ if (!obj || typeof obj !== "object")
169
+ return;
170
+ if (typeof obj.$ref === "string")
171
+ acc.add(refName(obj.$ref));
172
+ for (const v of Object.values(obj))
173
+ collectRefModels(v, acc);
174
+ };
175
+ for (const [rawPath, ops] of Object.entries(paths)) {
176
+ // "/api/products" veya "/api/products/{id}" -> kök "/api/products"
177
+ const base = rawPath.replace(/\/\{[^}]+\}.*$/, "");
178
+ const refs = new Set();
179
+ collectRefModels(ops, refs);
180
+ const model = [...refs].find((r) => modelNames.has(r));
181
+ const existing = byResource.get(base);
182
+ if (!existing) {
183
+ byResource.set(base, { basePath: base, model });
184
+ }
185
+ else if (!existing.model && model) {
186
+ existing.model = model;
187
+ }
188
+ }
189
+ const resources = [];
190
+ for (const { basePath, model } of byResource.values()) {
191
+ if (!model)
192
+ continue;
193
+ if (config.entities && config.entities.length && !config.entities.includes(model))
194
+ continue;
195
+ const segment = basePath.split("/").filter(Boolean).pop() ?? model;
196
+ const name = (0, naming_1.toPascalCase)(segment.replace(/s$/, "")); // kaba tekilleştirme
197
+ const modelDef = models.find((m) => m.name === model);
198
+ const idProp = modelDef?.properties.find((p) => p.originalName.toLowerCase() === "id");
199
+ const idType = idProp && (idProp.type === "number" || idProp.type === "string") ? idProp.type : config.idType ?? "number";
200
+ resources.push({ model, name: name || model, basePath, idType });
201
+ }
202
+ return resources;
203
+ }
204
+ async function parseOpenApi(config) {
205
+ const doc = await loadDoc(config.input);
206
+ const schemas = doc.components?.schemas ?? doc.definitions ?? {};
207
+ const models = [];
208
+ for (const [name, schema] of Object.entries(schemas)) {
209
+ const model = schemaToModel(name, schema, config);
210
+ if (model)
211
+ models.push(model);
212
+ }
213
+ // Önce paths'ten kaynak türetmeyi dene; boşsa convention fallback.
214
+ let resources = resourcesFromPaths(doc, models, config);
215
+ if (!resources.length)
216
+ resources = (0, resources_1.buildResources)(models, config);
217
+ return { models, resources };
218
+ }
@@ -0,0 +1,10 @@
1
+ import { GenConfig } from "../config";
2
+ import { ModelDef, ResourceDef } from "../ir";
3
+ /**
4
+ * Modellerden CRUD service üretilecek "resource"ları türet.
5
+ *
6
+ * - config.entities verilmişse yalnızca onlar.
7
+ * - verilmemişse: id alanı olan tüm interface modelleri aday kabul edilir.
8
+ * - base path: config.basePaths override'ı > convention (/api/<çoğul>).
9
+ */
10
+ export declare function buildResources(models: ModelDef[], config: GenConfig): ResourceDef[];
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildResources = buildResources;
4
+ const naming_1 = require("../utils/naming");
5
+ /** Bir modelin "id" alanının TS tipini tahmin et. */
6
+ function guessIdType(model, fallback) {
7
+ const idProp = model.properties.find((p) => p.originalName.toLowerCase() === "id" || p.originalName.toLowerCase() === `${model.name.toLowerCase()}id`);
8
+ if (idProp && (idProp.type === "number" || idProp.type === "string"))
9
+ return idProp.type;
10
+ return fallback;
11
+ }
12
+ /**
13
+ * Modellerden CRUD service üretilecek "resource"ları türet.
14
+ *
15
+ * - config.entities verilmişse yalnızca onlar.
16
+ * - verilmemişse: id alanı olan tüm interface modelleri aday kabul edilir.
17
+ * - base path: config.basePaths override'ı > convention (/api/<çoğul>).
18
+ */
19
+ function buildResources(models, config) {
20
+ if (config.generateServices === false)
21
+ return [];
22
+ const interfaces = models.filter((m) => m.kind === "interface");
23
+ const wanted = config.entities;
24
+ const candidates = interfaces.filter((m) => {
25
+ if (wanted && wanted.length)
26
+ return wanted.includes(m.name);
27
+ // convention: id alanı olanlar entity sayılır.
28
+ return m.properties.some((p) => p.originalName.toLowerCase() === "id");
29
+ });
30
+ return candidates.map((m) => {
31
+ const resourceName = (0, naming_1.stripEntitySuffix)(m.name) || m.name;
32
+ const override = config.basePaths?.[m.name];
33
+ const basePath = override ?? `/api/${(0, naming_1.pluralize)(resourceName)}`;
34
+ return {
35
+ model: m.name,
36
+ name: resourceName,
37
+ basePath,
38
+ idType: guessIdType(m, config.idType ?? "number"),
39
+ };
40
+ });
41
+ }
@@ -0,0 +1,4 @@
1
+ export declare function ensureDir(dir: string): void;
2
+ export declare function writeFile(filePath: string, content: string): void;
3
+ /** Verilen yol bir dizinse, içindeki tüm .cs dosyalarını (recursive) döndür. Dosyaysa kendisini. */
4
+ export declare function collectCsFiles(input: string): string[];
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.ensureDir = ensureDir;
37
+ exports.writeFile = writeFile;
38
+ exports.collectCsFiles = collectCsFiles;
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ function ensureDir(dir) {
42
+ fs.mkdirSync(dir, { recursive: true });
43
+ }
44
+ function writeFile(filePath, content) {
45
+ ensureDir(path.dirname(filePath));
46
+ fs.writeFileSync(filePath, content, "utf8");
47
+ }
48
+ /** Verilen yol bir dizinse, içindeki tüm .cs dosyalarını (recursive) döndür. Dosyaysa kendisini. */
49
+ function collectCsFiles(input) {
50
+ const stat = fs.statSync(input);
51
+ if (stat.isFile())
52
+ return [input];
53
+ const out = [];
54
+ const walk = (dir) => {
55
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
56
+ const full = path.join(dir, entry.name);
57
+ if (entry.isDirectory()) {
58
+ if (entry.name === "bin" || entry.name === "obj" || entry.name === "node_modules")
59
+ continue;
60
+ walk(full);
61
+ }
62
+ else if (entry.isFile() && entry.name.endsWith(".cs")) {
63
+ out.push(full);
64
+ }
65
+ }
66
+ };
67
+ walk(input);
68
+ return out;
69
+ }
@@ -0,0 +1,10 @@
1
+ /** İlk harfi küçük yap (PascalCase -> camelCase). */
2
+ export declare function toCamelCase(name: string): string;
3
+ /** İlk harfi büyük yap (camelCase -> PascalCase). */
4
+ export declare function toPascalCase(name: string): string;
5
+ /** "ProductDto" / "Product" -> "products" gibi kaba bir çoğullama (base path için). */
6
+ export declare function pluralize(word: string): string;
7
+ /** Model adından "Dto"/"Model"/"Entity" son eklerini at: "ProductDto" -> "Product". */
8
+ export declare function stripEntitySuffix(name: string): string;
9
+ /** Dosya adı için: PascalCase -> kebab-case. "ProductDto" -> "product-dto". */
10
+ export declare function toKebabCase(name: string): string;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toCamelCase = toCamelCase;
4
+ exports.toPascalCase = toPascalCase;
5
+ exports.pluralize = pluralize;
6
+ exports.stripEntitySuffix = stripEntitySuffix;
7
+ exports.toKebabCase = toKebabCase;
8
+ /** İlk harfi küçük yap (PascalCase -> camelCase). */
9
+ function toCamelCase(name) {
10
+ if (!name)
11
+ return name;
12
+ // Ardışık büyük harf bloklarını da düzgün ele al: "ID" -> "id", "URLName" -> "urlName"
13
+ if (/^[A-Z0-9]+$/.test(name))
14
+ return name.toLowerCase();
15
+ return name.charAt(0).toLowerCase() + name.slice(1);
16
+ }
17
+ /** İlk harfi büyük yap (camelCase -> PascalCase). */
18
+ function toPascalCase(name) {
19
+ if (!name)
20
+ return name;
21
+ return name.charAt(0).toUpperCase() + name.slice(1);
22
+ }
23
+ /** "ProductDto" / "Product" -> "products" gibi kaba bir çoğullama (base path için). */
24
+ function pluralize(word) {
25
+ const lower = word.toLowerCase();
26
+ if (/[^aeiou]y$/.test(lower))
27
+ return lower.slice(0, -1) + "ies";
28
+ if (/(s|x|z|ch|sh)$/.test(lower))
29
+ return lower + "es";
30
+ return lower + "s";
31
+ }
32
+ /** Model adından "Dto"/"Model"/"Entity" son eklerini at: "ProductDto" -> "Product". */
33
+ function stripEntitySuffix(name) {
34
+ return name.replace(/(Dto|DTO|Model|Entity|ViewModel|Vm|Response|Request)$/, (m, _g, offset) => (offset > 0 ? "" : m));
35
+ }
36
+ /** Dosya adı için: PascalCase -> kebab-case. "ProductDto" -> "product-dto". */
37
+ function toKebabCase(name) {
38
+ return name
39
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
40
+ .replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2")
41
+ .toLowerCase();
42
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "dto2ts",
3
+ "version": "0.1.0",
4
+ "description": "C# API (OpenAPI/Swagger veya .cs DTO) kaynaklarından TypeScript model ve fetch tabanlı CRUD service kodu üreten CLI.",
5
+ "keywords": [
6
+ "csharp",
7
+ "dotnet",
8
+ "openapi",
9
+ "swagger",
10
+ "codegen",
11
+ "typescript",
12
+ "dto",
13
+ "crud",
14
+ "fetch"
15
+ ],
16
+ "license": "MIT",
17
+ "type": "commonjs",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "bin": {
21
+ "dto2ts": "./dist/cli.js"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "README.md"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.json",
29
+ "dev": "tsc -p tsconfig.json --watch",
30
+ "prepublishOnly": "npm run build",
31
+ "start": "node ./dist/cli.js"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "dependencies": {
37
+ "commander": "^12.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.14.0",
41
+ "typescript": "^5.5.0"
42
+ }
43
+ }