axigen 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.
package/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # axigen
2
+
3
+ > Generate typed Axios client functions from OpenAPI / Swagger specs
4
+
5
+ [![npm version](https://img.shields.io/npm/v/axigen)](https://www.npmjs.com/package/axigen)
6
+ [![license](https://img.shields.io/npm/l/axigen)](./LICENSE)
7
+
8
+ ## نصب
9
+
10
+ ```bash
11
+ npm install -D axigen
12
+ # یا
13
+ pnpm add -D axigen
14
+ ```
15
+
16
+ ## شروع سریع
17
+
18
+ **۱. ساخت فایل کانفیگ:**
19
+
20
+ ```bash
21
+ npx axigen init
22
+ ```
23
+
24
+ **۲. ویرایش `axigen.config.js`:**
25
+
26
+ ```js
27
+ /** @type {import('axigen').AxigenConfig} */
28
+ module.exports = {
29
+ input: "./openapi.yaml",
30
+ output: {
31
+ client: "./src/api/client.ts",
32
+ types: "./src/api/types.ts",
33
+ },
34
+ axiosInstancePath: "../lib/axios",
35
+ language: "ts",
36
+ jsdoc: true,
37
+ };
38
+ ```
39
+
40
+ **۳. Generate:**
41
+
42
+ ```bash
43
+ npx axigen generate
44
+ # یا کوتاه‌تر:
45
+ npx axigen
46
+ ```
47
+
48
+ ## خروجی
49
+
50
+ از این OpenAPI:
51
+
52
+ ```yaml
53
+ paths:
54
+ /users/{userId}:
55
+ get:
56
+ operationId: getUserById
57
+ parameters:
58
+ - name: userId
59
+ in: path
60
+ required: true
61
+ ```
62
+
63
+ این کد generate میشه:
64
+
65
+ ```ts
66
+ /**
67
+ * دریافت کاربر با ID
68
+ * `GET /users/{userId}`
69
+ */
70
+ export async function getUserById(userId: GetUserByIdPathParams["userId"]): Promise<AxiosResponse<GetUserByIdResponse>> {
71
+ return axiosInstance.get(`/users/${userId}`);
72
+ }
73
+ ```
74
+
75
+ ## گزینه‌های کانفیگ
76
+
77
+ | گزینه | نوع | پیش‌فرض | توضیح |
78
+ | --------------------- | -------------- | --------------- | ----------------------------------- |
79
+ | `input` | `string` | — | مسیر فایل OpenAPI (yaml یا json) |
80
+ | `output.client` | `string` | — | مسیر خروجی فایل توابع |
81
+ | `output.types` | `string` | — | مسیر خروجی فایل types (اختیاری) |
82
+ | `axiosInstancePath` | `string` | — | مسیر import مربوط به axios instance |
83
+ | `axiosInstanceExport` | `string` | `axiosInstance` | نام export |
84
+ | `language` | `'ts' \| 'js'` | `'ts'` | زبان خروجی |
85
+ | `jsdoc` | `boolean` | `true` | اضافه کردن JSDoc |
86
+ | `tags` | `string[]` | — | فیلتر بر اساس تگ |
87
+
88
+ ## دستورات CLI
89
+
90
+ ```bash
91
+ axigen generate [--config <path>] [--cwd <path>]
92
+ axigen init
93
+ axigen --version
94
+ axigen --help
95
+ ```
96
+
97
+ ## استفاده programmatic
98
+
99
+ ```ts
100
+ import { generate, loadConfig } from "axigen";
101
+
102
+ const config = await loadConfig(process.cwd());
103
+ const result = await generate(config, process.cwd());
104
+ console.log(`Generated ${result.endpointCount} endpoints`);
105
+ ```
106
+
107
+ ## لایسنس
108
+
109
+ MIT
@@ -0,0 +1,525 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/cli/index.ts
5
+ import { Command } from "commander";
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ import path4 from "path";
9
+ import fs4 from "fs";
10
+
11
+ // src/config/loader.ts
12
+ import path from "path";
13
+ import fs from "fs";
14
+ var CONFIG_FILES = ["axigen.config.js", "axigen.config.cjs", "axigen.config.mjs", "axigen.config.ts"];
15
+ async function loadConfig(cwd, configPath) {
16
+ if (configPath) {
17
+ const abs = path.resolve(cwd, configPath);
18
+ if (!fs.existsSync(abs)) {
19
+ throw new Error(`Config file not found: ${abs}`);
20
+ }
21
+ return importConfig(abs);
22
+ }
23
+ for (const filename of CONFIG_FILES) {
24
+ const abs = path.join(cwd, filename);
25
+ if (fs.existsSync(abs)) {
26
+ return importConfig(abs);
27
+ }
28
+ }
29
+ throw new Error(`No config file found. Create one of: ${CONFIG_FILES.join(", ")}
30
+ Or run: axigen init`);
31
+ }
32
+ async function importConfig(filePath) {
33
+ let raw;
34
+ try {
35
+ const mod = await import(filePath);
36
+ raw = mod.default ?? mod;
37
+ } catch {
38
+ throw new Error(`Failed to load config: ${filePath}`);
39
+ }
40
+ return validateConfig(raw, filePath);
41
+ }
42
+ function validateConfig(raw, filePath) {
43
+ if (!raw || typeof raw !== "object") {
44
+ throw new Error(`Invalid config in ${filePath}: must export an object`);
45
+ }
46
+ const cfg = raw;
47
+ if (!cfg.input || typeof cfg.input !== "string") {
48
+ throw new Error(`Config error: "input" must be a string path to your OpenAPI file`);
49
+ }
50
+ if (!cfg.output || typeof cfg.output !== "object") {
51
+ throw new Error(`Config error: "output" must be an object with at least "client" path`);
52
+ }
53
+ const output = cfg.output;
54
+ if (!output.client || typeof output.client !== "string") {
55
+ throw new Error(`Config error: "output.client" is required`);
56
+ }
57
+ if (!cfg.axiosInstancePath || typeof cfg.axiosInstancePath !== "string") {
58
+ throw new Error(`Config error: "axiosInstancePath" is required (path to your axios instance)`);
59
+ }
60
+ return {
61
+ input: cfg.input,
62
+ output: {
63
+ client: output.client,
64
+ types: typeof output.types === "string" ? output.types : void 0
65
+ },
66
+ axiosInstancePath: cfg.axiosInstancePath,
67
+ axiosInstanceExport: typeof cfg.axiosInstanceExport === "string" ? cfg.axiosInstanceExport : "axiosInstance",
68
+ language: cfg.language === "js" ? "js" : "ts",
69
+ jsdoc: cfg.jsdoc !== false,
70
+ tags: Array.isArray(cfg.tags) ? cfg.tags : void 0
71
+ };
72
+ }
73
+
74
+ // src/generate.ts
75
+ import fs3 from "fs";
76
+ import path3 from "path";
77
+
78
+ // src/parser/openapi.ts
79
+ import fs2 from "fs";
80
+ import path2 from "path";
81
+ import yaml from "js-yaml";
82
+ function parseOpenAPIFile(filePath) {
83
+ if (!fs2.existsSync(filePath)) {
84
+ throw new Error(`OpenAPI file not found: ${filePath}`);
85
+ }
86
+ const content = fs2.readFileSync(filePath, "utf-8");
87
+ const ext = path2.extname(filePath).toLowerCase();
88
+ let spec;
89
+ if (ext === ".json") {
90
+ spec = JSON.parse(content);
91
+ } else {
92
+ spec = yaml.load(content);
93
+ }
94
+ return validateSpec(spec, filePath);
95
+ }
96
+ function validateSpec(raw, filePath) {
97
+ if (!raw || typeof raw !== "object") {
98
+ throw new Error(`Invalid OpenAPI file: ${filePath}`);
99
+ }
100
+ const s = raw;
101
+ const isOpenAPI3 = typeof s.openapi === "string" && s.openapi.startsWith("3.");
102
+ const isSwagger2 = typeof s.swagger === "string" && s.swagger.startsWith("2.");
103
+ if (!isOpenAPI3 && !isSwagger2) {
104
+ throw new Error(`Unsupported spec format in ${filePath}.
105
+ Expected OpenAPI 3.x or Swagger 2.x`);
106
+ }
107
+ if (!s.paths || typeof s.paths !== "object") {
108
+ throw new Error(`OpenAPI spec has no "paths" defined: ${filePath}`);
109
+ }
110
+ return raw;
111
+ }
112
+ function extractEndpoints(spec, filterTags) {
113
+ const endpoints = [];
114
+ for (const [urlPath, pathItem] of Object.entries(spec.paths)) {
115
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
116
+ for (const method of methods) {
117
+ const operation = pathItem[method];
118
+ if (!operation) continue;
119
+ const tags = operation.tags ?? [];
120
+ if (filterTags && filterTags.length > 0) {
121
+ if (!tags.some((t) => filterTags.includes(t))) continue;
122
+ }
123
+ const operationId = operation.operationId ?? buildOperationId(method, urlPath);
124
+ const allParams = [...pathItem.parameters ?? [], ...operation.parameters ?? []];
125
+ const pathParams = allParams.filter((p) => !("$ref" in p) && p.in === "path").map(normalizeParam);
126
+ const queryParams = allParams.filter((p) => !("$ref" in p) && p.in === "query").map(normalizeParam);
127
+ let bodySchema;
128
+ let bodyRequired = false;
129
+ if (operation.requestBody && !("$ref" in operation.requestBody)) {
130
+ bodyRequired = operation.requestBody.required ?? false;
131
+ const content = operation.requestBody.content;
132
+ const jsonContent = content?.["application/json"];
133
+ bodySchema = jsonContent?.schema;
134
+ }
135
+ let responseSchema;
136
+ for (const [status, response] of Object.entries(operation.responses)) {
137
+ if (status.startsWith("2") && !("$ref" in response)) {
138
+ const jsonContent = response.content?.["application/json"];
139
+ responseSchema = jsonContent?.schema;
140
+ break;
141
+ }
142
+ }
143
+ endpoints.push({
144
+ operationId,
145
+ method,
146
+ path: urlPath,
147
+ summary: operation.summary,
148
+ description: operation.description,
149
+ tags,
150
+ pathParams,
151
+ queryParams,
152
+ bodySchema,
153
+ bodyRequired,
154
+ responseSchema
155
+ });
156
+ }
157
+ }
158
+ return endpoints;
159
+ }
160
+ function normalizeParam(p) {
161
+ return {
162
+ name: p.name,
163
+ required: p.required ?? false,
164
+ schema: p.schema,
165
+ description: p.description
166
+ };
167
+ }
168
+ function buildOperationId(method, urlPath) {
169
+ const parts = urlPath.split("/").filter(Boolean).map((segment) => {
170
+ if (segment.startsWith("{") && segment.endsWith("}")) {
171
+ const name = segment.slice(1, -1);
172
+ return "By" + capitalize(name);
173
+ }
174
+ return capitalize(segment);
175
+ });
176
+ return method.toLowerCase() + parts.join("");
177
+ }
178
+ function capitalize(s) {
179
+ return s.charAt(0).toUpperCase() + s.slice(1);
180
+ }
181
+
182
+ // src/generator/types.ts
183
+ function schemaToTSType(schema, indent = 0) {
184
+ if (!schema) return "unknown";
185
+ if (schema.$ref) {
186
+ return refToTypeName(schema.$ref);
187
+ }
188
+ if (schema.allOf && schema.allOf.length > 0) {
189
+ return schema.allOf.map((s) => schemaToTSType(s, indent)).join(" & ");
190
+ }
191
+ if (schema.anyOf || schema.oneOf) {
192
+ const variants = schema.anyOf ?? schema.oneOf;
193
+ return variants.map((s) => schemaToTSType(s, indent)).join(" | ");
194
+ }
195
+ if (schema.enum) {
196
+ return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
197
+ }
198
+ const nullable = schema.nullable ? " | null" : "";
199
+ switch (schema.type) {
200
+ case "string":
201
+ return `string${nullable}`;
202
+ case "number":
203
+ case "integer":
204
+ return `number${nullable}`;
205
+ case "boolean":
206
+ return `boolean${nullable}`;
207
+ case "null":
208
+ return "null";
209
+ case "array":
210
+ return `Array<${schemaToTSType(schema.items, indent)}>${nullable}`;
211
+ case "object":
212
+ return buildObjectType(schema, indent) + nullable;
213
+ default:
214
+ if (schema.properties) {
215
+ return buildObjectType(schema, indent) + nullable;
216
+ }
217
+ return `unknown${nullable}`;
218
+ }
219
+ }
220
+ function buildObjectType(schema, indent) {
221
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
222
+ return "Record<string, unknown>";
223
+ }
224
+ const required = new Set(schema.required ?? []);
225
+ const pad = " ".repeat(indent + 1);
226
+ const closePad = " ".repeat(indent);
227
+ const fields = Object.entries(schema.properties).map(([key, val]) => {
228
+ const optional = !required.has(key) ? "?" : "";
229
+ const comment = val.description ? `${pad}/** ${val.description} */
230
+ ` : "";
231
+ const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
232
+ return `${comment}${pad}${safeName}${optional}: ${schemaToTSType(val, indent + 1)}`;
233
+ }).join("\n");
234
+ return `{
235
+ ${fields}
236
+ ${closePad}}`;
237
+ }
238
+ function refToTypeName(ref) {
239
+ const parts = ref.split("/");
240
+ return parts[parts.length - 1] ?? "unknown";
241
+ }
242
+ function generateTypesFile(endpoints, schemas = {}) {
243
+ const lines = [];
244
+ lines.push(`// This file is auto-generated by axigen. DO NOT EDIT.`);
245
+ lines.push(`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
246
+ lines.push("");
247
+ if (Object.keys(schemas).length > 0) {
248
+ lines.push("// \u2500\u2500\u2500 Component Schemas \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
249
+ lines.push("");
250
+ for (const [name, schema] of Object.entries(schemas)) {
251
+ if (schema.description) {
252
+ lines.push(`/** ${schema.description} */`);
253
+ }
254
+ lines.push(`export type ${name} = ${schemaToTSType(schema)}`);
255
+ lines.push("");
256
+ }
257
+ }
258
+ lines.push("// \u2500\u2500\u2500 Endpoint Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
259
+ lines.push("");
260
+ for (const ep of endpoints) {
261
+ const baseName = operationToTypeName(ep.operationId);
262
+ if (ep.pathParams.length > 0) {
263
+ lines.push(`export interface ${baseName}PathParams {`);
264
+ for (const p of ep.pathParams) {
265
+ const type = schemaToTSType(p.schema);
266
+ const comment = p.description ? ` /** ${p.description} */
267
+ ` : "";
268
+ lines.push(`${comment} ${p.name}: ${type}`);
269
+ }
270
+ lines.push(`}`);
271
+ lines.push("");
272
+ }
273
+ if (ep.queryParams.length > 0) {
274
+ lines.push(`export interface ${baseName}QueryParams {`);
275
+ for (const p of ep.queryParams) {
276
+ const optional = !p.required ? "?" : "";
277
+ const type = schemaToTSType(p.schema);
278
+ const comment = p.description ? ` /** ${p.description} */
279
+ ` : "";
280
+ lines.push(`${comment} ${p.name}${optional}: ${type}`);
281
+ }
282
+ lines.push(`}`);
283
+ lines.push("");
284
+ }
285
+ if (ep.bodySchema) {
286
+ lines.push(`export type ${baseName}Body = ${schemaToTSType(ep.bodySchema)}`);
287
+ lines.push("");
288
+ }
289
+ if (ep.responseSchema) {
290
+ lines.push(`export type ${baseName}Response = ${schemaToTSType(ep.responseSchema)}`);
291
+ lines.push("");
292
+ }
293
+ }
294
+ return lines.join("\n");
295
+ }
296
+ function operationToTypeName(operationId) {
297
+ return operationId.charAt(0).toUpperCase() + operationId.slice(1);
298
+ }
299
+
300
+ // src/generator/axios.ts
301
+ function generateClientFile(opts) {
302
+ const { endpoints, config, typesRelativePath } = opts;
303
+ const isTS = config.language !== "js";
304
+ const instanceExport = config.axiosInstanceExport ?? "axiosInstance";
305
+ const lines = [];
306
+ lines.push(`// This file is auto-generated by axigen. DO NOT EDIT.`);
307
+ lines.push(`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
308
+ lines.push("");
309
+ if (isTS) {
310
+ lines.push(`import type { AxiosResponse } from 'axios'`);
311
+ }
312
+ lines.push(`import { ${instanceExport} } from '${config.axiosInstancePath}'`);
313
+ if (isTS && typesRelativePath) {
314
+ const typeImports = collectTypeImports(endpoints);
315
+ if (typeImports.length > 0) {
316
+ lines.push(`import type { ${typeImports.join(", ")} } from '${typesRelativePath}'`);
317
+ }
318
+ }
319
+ lines.push("");
320
+ lines.push("");
321
+ for (const ep of endpoints) {
322
+ const fn = buildFunction(ep, isTS, instanceExport, config.jsdoc !== false);
323
+ lines.push(fn);
324
+ lines.push("");
325
+ }
326
+ return lines.join("\n");
327
+ }
328
+ function buildFunction(ep, isTS, instanceExport, jsdoc) {
329
+ const typeName = operationToTypeName(ep.operationId);
330
+ const lines = [];
331
+ if (jsdoc) {
332
+ lines.push(`/**`);
333
+ if (ep.summary) lines.push(` * ${ep.summary}`);
334
+ if (ep.description && ep.description !== ep.summary) {
335
+ lines.push(` * ${ep.description}`);
336
+ }
337
+ lines.push(` * \`${ep.method.toUpperCase()} ${ep.path}\``);
338
+ if (ep.tags.length > 0) lines.push(` * @tags ${ep.tags.join(", ")}`);
339
+ lines.push(` */`);
340
+ }
341
+ const params = buildParams(ep, typeName, isTS);
342
+ const returnType = isTS ? buildReturnType(ep, typeName) : "";
343
+ const asyncKw = "async ";
344
+ lines.push(`export ${asyncKw}function ${ep.operationId}(${params})${returnType} {`);
345
+ const callLines = buildAxiosCall(ep, instanceExport, typeName, isTS);
346
+ for (const l of callLines) {
347
+ lines.push(` ${l}`);
348
+ }
349
+ lines.push(`}`);
350
+ return lines.join("\n");
351
+ }
352
+ function buildParams(ep, typeName, isTS) {
353
+ const parts = [];
354
+ for (const p of ep.pathParams) {
355
+ const type = isTS ? `: ${typeName}PathParams['${p.name}']` : "";
356
+ parts.push(`${p.name}${type}`);
357
+ }
358
+ if (ep.bodySchema) {
359
+ const optional = !ep.bodyRequired ? "?" : "";
360
+ const type = isTS ? `: ${typeName}Body` : "";
361
+ parts.push(`data${optional}${type}`);
362
+ }
363
+ if (ep.queryParams.length > 0) {
364
+ const optional = ep.queryParams.every((p) => !p.required) ? "?" : "";
365
+ const type = isTS ? `: ${typeName}QueryParams` : "";
366
+ parts.push(`params${optional}${type}`);
367
+ }
368
+ return parts.join(", ");
369
+ }
370
+ function buildReturnType(ep, typeName) {
371
+ const responseType = ep.responseSchema ? `${typeName}Response` : "unknown";
372
+ return `: Promise<AxiosResponse<${responseType}>>`;
373
+ }
374
+ function buildAxiosCall(ep, instanceExport, _typeName, _isTS) {
375
+ const lines = [];
376
+ const interpolatedPath = ep.pathParams.length > 0 ? "`" + ep.path.replace(/\{(\w+)\}/g, "${$1}") + "`" : `'${ep.path}'`;
377
+ const hasBody = !!ep.bodySchema;
378
+ const hasQuery = ep.queryParams.length > 0;
379
+ const method = ep.method.toLowerCase();
380
+ if (method === "get" || method === "delete" || method === "head") {
381
+ if (hasQuery) {
382
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath}, { params })`);
383
+ } else {
384
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath})`);
385
+ }
386
+ } else {
387
+ const bodyArg = hasBody ? "data" : "undefined";
388
+ if (hasQuery) {
389
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath}, ${bodyArg}, { params })`);
390
+ } else {
391
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath}, ${bodyArg})`);
392
+ }
393
+ }
394
+ return lines;
395
+ }
396
+ function collectTypeImports(endpoints) {
397
+ const names = /* @__PURE__ */ new Set();
398
+ for (const ep of endpoints) {
399
+ const typeName = operationToTypeName(ep.operationId);
400
+ if (ep.pathParams.length > 0) names.add(`${typeName}PathParams`);
401
+ if (ep.queryParams.length > 0) names.add(`${typeName}QueryParams`);
402
+ if (ep.bodySchema) names.add(`${typeName}Body`);
403
+ if (ep.responseSchema) names.add(`${typeName}Response`);
404
+ }
405
+ return [...names].sort();
406
+ }
407
+
408
+ // src/generate.ts
409
+ async function generate(config, cwd) {
410
+ const specPath = path3.resolve(cwd, config.input);
411
+ const spec = parseOpenAPIFile(specPath);
412
+ const endpoints = extractEndpoints(spec, config.tags);
413
+ if (endpoints.length === 0) {
414
+ throw new Error("No endpoints found in the OpenAPI spec. Check your input file or tags filter.");
415
+ }
416
+ const clientPath = path3.resolve(cwd, config.output.client);
417
+ const typesPath = config.output.types ? path3.resolve(cwd, config.output.types) : void 0;
418
+ let typesRelativePath;
419
+ if (typesPath) {
420
+ typesRelativePath = toRelativeImportPath(path3.dirname(clientPath), typesPath);
421
+ }
422
+ const schemas = spec.components?.schemas ?? {};
423
+ let typesContent;
424
+ if (config.language !== "js" && typesPath) {
425
+ typesContent = generateTypesFile(endpoints, schemas);
426
+ }
427
+ const clientContent = generateClientFile({
428
+ endpoints,
429
+ config,
430
+ typesRelativePath
431
+ });
432
+ ensureDir(path3.dirname(clientPath));
433
+ fs3.writeFileSync(clientPath, clientContent, "utf-8");
434
+ if (typesPath && typesContent) {
435
+ ensureDir(path3.dirname(typesPath));
436
+ fs3.writeFileSync(typesPath, typesContent, "utf-8");
437
+ }
438
+ return {
439
+ clientPath,
440
+ typesPath,
441
+ endpointCount: endpoints.length
442
+ };
443
+ }
444
+ function ensureDir(dir) {
445
+ if (!fs3.existsSync(dir)) {
446
+ fs3.mkdirSync(dir, { recursive: true });
447
+ }
448
+ }
449
+ function toRelativeImportPath(fromDir, toFile) {
450
+ let rel = path3.relative(fromDir, toFile);
451
+ rel = rel.replace(/\.(ts|js|d\.ts)$/, "");
452
+ if (!rel.startsWith(".")) {
453
+ rel = "./" + rel;
454
+ }
455
+ return rel.replace(/\\/g, "/");
456
+ }
457
+
458
+ // src/cli/index.ts
459
+ var program = new Command();
460
+ program.name("axigen").description("Generate typed Axios client functions from OpenAPI / Swagger specs").version(getVersion());
461
+ program.command("generate", { isDefault: true }).alias("gen").description("Generate axios client from OpenAPI spec").option("-c, --config <path>", "Path to config file (default: axigen.config.js)").option("--cwd <path>", "Working directory (default: process.cwd())").action(async (opts) => {
462
+ const cwd = opts.cwd ? path4.resolve(opts.cwd) : process.cwd();
463
+ const spinner = ora({
464
+ text: chalk.dim("Loading config..."),
465
+ color: "cyan"
466
+ }).start();
467
+ try {
468
+ const config = await loadConfig(cwd, opts.config);
469
+ spinner.text = chalk.dim(`Parsing ${config.input}...`);
470
+ const result = await generate(config, cwd);
471
+ spinner.succeed(chalk.green("Generated successfully!"));
472
+ console.log("");
473
+ console.log(chalk.bold(" Output:"));
474
+ console.log(` ${chalk.cyan("client")} \u2192 ${chalk.white(relPath(cwd, result.clientPath))}`);
475
+ if (result.typesPath) {
476
+ console.log(` ${chalk.cyan("types")} \u2192 ${chalk.white(relPath(cwd, result.typesPath))}`);
477
+ }
478
+ console.log("");
479
+ console.log(` ${chalk.green("\u2713")} ${chalk.bold(result.endpointCount)} endpoint${result.endpointCount !== 1 ? "s" : ""} generated`);
480
+ console.log("");
481
+ } catch (err) {
482
+ spinner.fail(chalk.red("Generation failed"));
483
+ console.error("");
484
+ console.error(chalk.red(" Error: ") + (err instanceof Error ? err.message : String(err)));
485
+ console.error("");
486
+ process.exit(1);
487
+ }
488
+ });
489
+ program.command("init").description("Create a starter axigen.config.js in the current directory").option("--cwd <path>", "Working directory").action((opts) => {
490
+ const cwd = opts.cwd ? path4.resolve(opts.cwd) : process.cwd();
491
+ const dest = path4.join(cwd, "axigen.config.js");
492
+ if (fs4.existsSync(dest)) {
493
+ console.log(chalk.yellow(" axigen.config.js already exists."));
494
+ return;
495
+ }
496
+ const template = `/** @type {import('axigen').AxigenConfig} */
497
+ module.exports = {
498
+ input: './openapi.yaml',
499
+
500
+ output: {
501
+ client: './src/api/client.ts',
502
+ types: './src/api/types.ts',
503
+ },
504
+ axiosInstancePath: '../lib/axios',
505
+ language: 'ts',
506
+ jsdoc: true,
507
+ }
508
+ `;
509
+ fs4.writeFileSync(dest, template, "utf-8");
510
+ console.log(chalk.green(` \u2713 Created axigen.config.js`));
511
+ console.log(chalk.dim(` Edit it and run: axigen generate`));
512
+ });
513
+ program.parse();
514
+ function relPath(cwd, abs) {
515
+ return path4.relative(cwd, abs);
516
+ }
517
+ function getVersion() {
518
+ try {
519
+ const pkgPath = new URL("../../package.json", import.meta.url).pathname;
520
+ const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
521
+ return pkg.version;
522
+ } catch {
523
+ return "0.0.0";
524
+ }
525
+ }