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/dist/index.js ADDED
@@ -0,0 +1,496 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var src_exports = {};
32
+ __export(src_exports, {
33
+ extractEndpoints: () => extractEndpoints,
34
+ generate: () => generate,
35
+ generateClientFile: () => generateClientFile,
36
+ generateTypesFile: () => generateTypesFile,
37
+ loadConfig: () => loadConfig,
38
+ parseOpenAPIFile: () => parseOpenAPIFile
39
+ });
40
+ module.exports = __toCommonJS(src_exports);
41
+
42
+ // src/generate.ts
43
+ var import_node_fs2 = __toESM(require("fs"));
44
+ var import_node_path2 = __toESM(require("path"));
45
+
46
+ // src/parser/openapi.ts
47
+ var import_node_fs = __toESM(require("fs"));
48
+ var import_node_path = __toESM(require("path"));
49
+ var import_js_yaml = __toESM(require("js-yaml"));
50
+ function parseOpenAPIFile(filePath) {
51
+ if (!import_node_fs.default.existsSync(filePath)) {
52
+ throw new Error(`OpenAPI file not found: ${filePath}`);
53
+ }
54
+ const content = import_node_fs.default.readFileSync(filePath, "utf-8");
55
+ const ext = import_node_path.default.extname(filePath).toLowerCase();
56
+ let spec;
57
+ if (ext === ".json") {
58
+ spec = JSON.parse(content);
59
+ } else {
60
+ spec = import_js_yaml.default.load(content);
61
+ }
62
+ return validateSpec(spec, filePath);
63
+ }
64
+ function validateSpec(raw, filePath) {
65
+ if (!raw || typeof raw !== "object") {
66
+ throw new Error(`Invalid OpenAPI file: ${filePath}`);
67
+ }
68
+ const s = raw;
69
+ const isOpenAPI3 = typeof s.openapi === "string" && s.openapi.startsWith("3.");
70
+ const isSwagger2 = typeof s.swagger === "string" && s.swagger.startsWith("2.");
71
+ if (!isOpenAPI3 && !isSwagger2) {
72
+ throw new Error(`Unsupported spec format in ${filePath}.
73
+ Expected OpenAPI 3.x or Swagger 2.x`);
74
+ }
75
+ if (!s.paths || typeof s.paths !== "object") {
76
+ throw new Error(`OpenAPI spec has no "paths" defined: ${filePath}`);
77
+ }
78
+ return raw;
79
+ }
80
+ function extractEndpoints(spec, filterTags) {
81
+ const endpoints = [];
82
+ for (const [urlPath, pathItem] of Object.entries(spec.paths)) {
83
+ const methods = ["get", "post", "put", "patch", "delete", "head", "options"];
84
+ for (const method of methods) {
85
+ const operation = pathItem[method];
86
+ if (!operation) continue;
87
+ const tags = operation.tags ?? [];
88
+ if (filterTags && filterTags.length > 0) {
89
+ if (!tags.some((t) => filterTags.includes(t))) continue;
90
+ }
91
+ const operationId = operation.operationId ?? buildOperationId(method, urlPath);
92
+ const allParams = [...pathItem.parameters ?? [], ...operation.parameters ?? []];
93
+ const pathParams = allParams.filter((p) => !("$ref" in p) && p.in === "path").map(normalizeParam);
94
+ const queryParams = allParams.filter((p) => !("$ref" in p) && p.in === "query").map(normalizeParam);
95
+ let bodySchema;
96
+ let bodyRequired = false;
97
+ if (operation.requestBody && !("$ref" in operation.requestBody)) {
98
+ bodyRequired = operation.requestBody.required ?? false;
99
+ const content = operation.requestBody.content;
100
+ const jsonContent = content?.["application/json"];
101
+ bodySchema = jsonContent?.schema;
102
+ }
103
+ let responseSchema;
104
+ for (const [status, response] of Object.entries(operation.responses)) {
105
+ if (status.startsWith("2") && !("$ref" in response)) {
106
+ const jsonContent = response.content?.["application/json"];
107
+ responseSchema = jsonContent?.schema;
108
+ break;
109
+ }
110
+ }
111
+ endpoints.push({
112
+ operationId,
113
+ method,
114
+ path: urlPath,
115
+ summary: operation.summary,
116
+ description: operation.description,
117
+ tags,
118
+ pathParams,
119
+ queryParams,
120
+ bodySchema,
121
+ bodyRequired,
122
+ responseSchema
123
+ });
124
+ }
125
+ }
126
+ return endpoints;
127
+ }
128
+ function normalizeParam(p) {
129
+ return {
130
+ name: p.name,
131
+ required: p.required ?? false,
132
+ schema: p.schema,
133
+ description: p.description
134
+ };
135
+ }
136
+ function buildOperationId(method, urlPath) {
137
+ const parts = urlPath.split("/").filter(Boolean).map((segment) => {
138
+ if (segment.startsWith("{") && segment.endsWith("}")) {
139
+ const name = segment.slice(1, -1);
140
+ return "By" + capitalize(name);
141
+ }
142
+ return capitalize(segment);
143
+ });
144
+ return method.toLowerCase() + parts.join("");
145
+ }
146
+ function capitalize(s) {
147
+ return s.charAt(0).toUpperCase() + s.slice(1);
148
+ }
149
+
150
+ // src/generator/types.ts
151
+ function schemaToTSType(schema, indent = 0) {
152
+ if (!schema) return "unknown";
153
+ if (schema.$ref) {
154
+ return refToTypeName(schema.$ref);
155
+ }
156
+ if (schema.allOf && schema.allOf.length > 0) {
157
+ return schema.allOf.map((s) => schemaToTSType(s, indent)).join(" & ");
158
+ }
159
+ if (schema.anyOf || schema.oneOf) {
160
+ const variants = schema.anyOf ?? schema.oneOf;
161
+ return variants.map((s) => schemaToTSType(s, indent)).join(" | ");
162
+ }
163
+ if (schema.enum) {
164
+ return schema.enum.map((v) => JSON.stringify(v)).join(" | ");
165
+ }
166
+ const nullable = schema.nullable ? " | null" : "";
167
+ switch (schema.type) {
168
+ case "string":
169
+ return `string${nullable}`;
170
+ case "number":
171
+ case "integer":
172
+ return `number${nullable}`;
173
+ case "boolean":
174
+ return `boolean${nullable}`;
175
+ case "null":
176
+ return "null";
177
+ case "array":
178
+ return `Array<${schemaToTSType(schema.items, indent)}>${nullable}`;
179
+ case "object":
180
+ return buildObjectType(schema, indent) + nullable;
181
+ default:
182
+ if (schema.properties) {
183
+ return buildObjectType(schema, indent) + nullable;
184
+ }
185
+ return `unknown${nullable}`;
186
+ }
187
+ }
188
+ function buildObjectType(schema, indent) {
189
+ if (!schema.properties || Object.keys(schema.properties).length === 0) {
190
+ return "Record<string, unknown>";
191
+ }
192
+ const required = new Set(schema.required ?? []);
193
+ const pad = " ".repeat(indent + 1);
194
+ const closePad = " ".repeat(indent);
195
+ const fields = Object.entries(schema.properties).map(([key, val]) => {
196
+ const optional = !required.has(key) ? "?" : "";
197
+ const comment = val.description ? `${pad}/** ${val.description} */
198
+ ` : "";
199
+ const safeName = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key) ? key : `'${key}'`;
200
+ return `${comment}${pad}${safeName}${optional}: ${schemaToTSType(val, indent + 1)}`;
201
+ }).join("\n");
202
+ return `{
203
+ ${fields}
204
+ ${closePad}}`;
205
+ }
206
+ function refToTypeName(ref) {
207
+ const parts = ref.split("/");
208
+ return parts[parts.length - 1] ?? "unknown";
209
+ }
210
+ function generateTypesFile(endpoints, schemas = {}) {
211
+ const lines = [];
212
+ lines.push(`// This file is auto-generated by axigen. DO NOT EDIT.`);
213
+ lines.push(`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
214
+ lines.push("");
215
+ if (Object.keys(schemas).length > 0) {
216
+ 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");
217
+ lines.push("");
218
+ for (const [name, schema] of Object.entries(schemas)) {
219
+ if (schema.description) {
220
+ lines.push(`/** ${schema.description} */`);
221
+ }
222
+ lines.push(`export type ${name} = ${schemaToTSType(schema)}`);
223
+ lines.push("");
224
+ }
225
+ }
226
+ 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");
227
+ lines.push("");
228
+ for (const ep of endpoints) {
229
+ const baseName = operationToTypeName(ep.operationId);
230
+ if (ep.pathParams.length > 0) {
231
+ lines.push(`export interface ${baseName}PathParams {`);
232
+ for (const p of ep.pathParams) {
233
+ const type = schemaToTSType(p.schema);
234
+ const comment = p.description ? ` /** ${p.description} */
235
+ ` : "";
236
+ lines.push(`${comment} ${p.name}: ${type}`);
237
+ }
238
+ lines.push(`}`);
239
+ lines.push("");
240
+ }
241
+ if (ep.queryParams.length > 0) {
242
+ lines.push(`export interface ${baseName}QueryParams {`);
243
+ for (const p of ep.queryParams) {
244
+ const optional = !p.required ? "?" : "";
245
+ const type = schemaToTSType(p.schema);
246
+ const comment = p.description ? ` /** ${p.description} */
247
+ ` : "";
248
+ lines.push(`${comment} ${p.name}${optional}: ${type}`);
249
+ }
250
+ lines.push(`}`);
251
+ lines.push("");
252
+ }
253
+ if (ep.bodySchema) {
254
+ lines.push(`export type ${baseName}Body = ${schemaToTSType(ep.bodySchema)}`);
255
+ lines.push("");
256
+ }
257
+ if (ep.responseSchema) {
258
+ lines.push(`export type ${baseName}Response = ${schemaToTSType(ep.responseSchema)}`);
259
+ lines.push("");
260
+ }
261
+ }
262
+ return lines.join("\n");
263
+ }
264
+ function operationToTypeName(operationId) {
265
+ return operationId.charAt(0).toUpperCase() + operationId.slice(1);
266
+ }
267
+
268
+ // src/generator/axios.ts
269
+ function generateClientFile(opts) {
270
+ const { endpoints, config, typesRelativePath } = opts;
271
+ const isTS = config.language !== "js";
272
+ const instanceExport = config.axiosInstanceExport ?? "axiosInstance";
273
+ const lines = [];
274
+ lines.push(`// This file is auto-generated by axigen. DO NOT EDIT.`);
275
+ lines.push(`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`);
276
+ lines.push("");
277
+ if (isTS) {
278
+ lines.push(`import type { AxiosResponse } from 'axios'`);
279
+ }
280
+ lines.push(`import { ${instanceExport} } from '${config.axiosInstancePath}'`);
281
+ if (isTS && typesRelativePath) {
282
+ const typeImports = collectTypeImports(endpoints);
283
+ if (typeImports.length > 0) {
284
+ lines.push(`import type { ${typeImports.join(", ")} } from '${typesRelativePath}'`);
285
+ }
286
+ }
287
+ lines.push("");
288
+ lines.push("");
289
+ for (const ep of endpoints) {
290
+ const fn = buildFunction(ep, isTS, instanceExport, config.jsdoc !== false);
291
+ lines.push(fn);
292
+ lines.push("");
293
+ }
294
+ return lines.join("\n");
295
+ }
296
+ function buildFunction(ep, isTS, instanceExport, jsdoc) {
297
+ const typeName = operationToTypeName(ep.operationId);
298
+ const lines = [];
299
+ if (jsdoc) {
300
+ lines.push(`/**`);
301
+ if (ep.summary) lines.push(` * ${ep.summary}`);
302
+ if (ep.description && ep.description !== ep.summary) {
303
+ lines.push(` * ${ep.description}`);
304
+ }
305
+ lines.push(` * \`${ep.method.toUpperCase()} ${ep.path}\``);
306
+ if (ep.tags.length > 0) lines.push(` * @tags ${ep.tags.join(", ")}`);
307
+ lines.push(` */`);
308
+ }
309
+ const params = buildParams(ep, typeName, isTS);
310
+ const returnType = isTS ? buildReturnType(ep, typeName) : "";
311
+ const asyncKw = "async ";
312
+ lines.push(`export ${asyncKw}function ${ep.operationId}(${params})${returnType} {`);
313
+ const callLines = buildAxiosCall(ep, instanceExport, typeName, isTS);
314
+ for (const l of callLines) {
315
+ lines.push(` ${l}`);
316
+ }
317
+ lines.push(`}`);
318
+ return lines.join("\n");
319
+ }
320
+ function buildParams(ep, typeName, isTS) {
321
+ const parts = [];
322
+ for (const p of ep.pathParams) {
323
+ const type = isTS ? `: ${typeName}PathParams['${p.name}']` : "";
324
+ parts.push(`${p.name}${type}`);
325
+ }
326
+ if (ep.bodySchema) {
327
+ const optional = !ep.bodyRequired ? "?" : "";
328
+ const type = isTS ? `: ${typeName}Body` : "";
329
+ parts.push(`data${optional}${type}`);
330
+ }
331
+ if (ep.queryParams.length > 0) {
332
+ const optional = ep.queryParams.every((p) => !p.required) ? "?" : "";
333
+ const type = isTS ? `: ${typeName}QueryParams` : "";
334
+ parts.push(`params${optional}${type}`);
335
+ }
336
+ return parts.join(", ");
337
+ }
338
+ function buildReturnType(ep, typeName) {
339
+ const responseType = ep.responseSchema ? `${typeName}Response` : "unknown";
340
+ return `: Promise<AxiosResponse<${responseType}>>`;
341
+ }
342
+ function buildAxiosCall(ep, instanceExport, _typeName, _isTS) {
343
+ const lines = [];
344
+ const interpolatedPath = ep.pathParams.length > 0 ? "`" + ep.path.replace(/\{(\w+)\}/g, "${$1}") + "`" : `'${ep.path}'`;
345
+ const hasBody = !!ep.bodySchema;
346
+ const hasQuery = ep.queryParams.length > 0;
347
+ const method = ep.method.toLowerCase();
348
+ if (method === "get" || method === "delete" || method === "head") {
349
+ if (hasQuery) {
350
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath}, { params })`);
351
+ } else {
352
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath})`);
353
+ }
354
+ } else {
355
+ const bodyArg = hasBody ? "data" : "undefined";
356
+ if (hasQuery) {
357
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath}, ${bodyArg}, { params })`);
358
+ } else {
359
+ lines.push(`return ${instanceExport}.${method}(${interpolatedPath}, ${bodyArg})`);
360
+ }
361
+ }
362
+ return lines;
363
+ }
364
+ function collectTypeImports(endpoints) {
365
+ const names = /* @__PURE__ */ new Set();
366
+ for (const ep of endpoints) {
367
+ const typeName = operationToTypeName(ep.operationId);
368
+ if (ep.pathParams.length > 0) names.add(`${typeName}PathParams`);
369
+ if (ep.queryParams.length > 0) names.add(`${typeName}QueryParams`);
370
+ if (ep.bodySchema) names.add(`${typeName}Body`);
371
+ if (ep.responseSchema) names.add(`${typeName}Response`);
372
+ }
373
+ return [...names].sort();
374
+ }
375
+
376
+ // src/generate.ts
377
+ async function generate(config, cwd) {
378
+ const specPath = import_node_path2.default.resolve(cwd, config.input);
379
+ const spec = parseOpenAPIFile(specPath);
380
+ const endpoints = extractEndpoints(spec, config.tags);
381
+ if (endpoints.length === 0) {
382
+ throw new Error("No endpoints found in the OpenAPI spec. Check your input file or tags filter.");
383
+ }
384
+ const clientPath = import_node_path2.default.resolve(cwd, config.output.client);
385
+ const typesPath = config.output.types ? import_node_path2.default.resolve(cwd, config.output.types) : void 0;
386
+ let typesRelativePath;
387
+ if (typesPath) {
388
+ typesRelativePath = toRelativeImportPath(import_node_path2.default.dirname(clientPath), typesPath);
389
+ }
390
+ const schemas = spec.components?.schemas ?? {};
391
+ let typesContent;
392
+ if (config.language !== "js" && typesPath) {
393
+ typesContent = generateTypesFile(endpoints, schemas);
394
+ }
395
+ const clientContent = generateClientFile({
396
+ endpoints,
397
+ config,
398
+ typesRelativePath
399
+ });
400
+ ensureDir(import_node_path2.default.dirname(clientPath));
401
+ import_node_fs2.default.writeFileSync(clientPath, clientContent, "utf-8");
402
+ if (typesPath && typesContent) {
403
+ ensureDir(import_node_path2.default.dirname(typesPath));
404
+ import_node_fs2.default.writeFileSync(typesPath, typesContent, "utf-8");
405
+ }
406
+ return {
407
+ clientPath,
408
+ typesPath,
409
+ endpointCount: endpoints.length
410
+ };
411
+ }
412
+ function ensureDir(dir) {
413
+ if (!import_node_fs2.default.existsSync(dir)) {
414
+ import_node_fs2.default.mkdirSync(dir, { recursive: true });
415
+ }
416
+ }
417
+ function toRelativeImportPath(fromDir, toFile) {
418
+ let rel = import_node_path2.default.relative(fromDir, toFile);
419
+ rel = rel.replace(/\.(ts|js|d\.ts)$/, "");
420
+ if (!rel.startsWith(".")) {
421
+ rel = "./" + rel;
422
+ }
423
+ return rel.replace(/\\/g, "/");
424
+ }
425
+
426
+ // src/config/loader.ts
427
+ var import_node_path3 = __toESM(require("path"));
428
+ var import_node_fs3 = __toESM(require("fs"));
429
+ var CONFIG_FILES = ["axigen.config.js", "axigen.config.cjs", "axigen.config.mjs", "axigen.config.ts"];
430
+ async function loadConfig(cwd, configPath) {
431
+ if (configPath) {
432
+ const abs = import_node_path3.default.resolve(cwd, configPath);
433
+ if (!import_node_fs3.default.existsSync(abs)) {
434
+ throw new Error(`Config file not found: ${abs}`);
435
+ }
436
+ return importConfig(abs);
437
+ }
438
+ for (const filename of CONFIG_FILES) {
439
+ const abs = import_node_path3.default.join(cwd, filename);
440
+ if (import_node_fs3.default.existsSync(abs)) {
441
+ return importConfig(abs);
442
+ }
443
+ }
444
+ throw new Error(`No config file found. Create one of: ${CONFIG_FILES.join(", ")}
445
+ Or run: axigen init`);
446
+ }
447
+ async function importConfig(filePath) {
448
+ let raw;
449
+ try {
450
+ const mod = await import(filePath);
451
+ raw = mod.default ?? mod;
452
+ } catch {
453
+ throw new Error(`Failed to load config: ${filePath}`);
454
+ }
455
+ return validateConfig(raw, filePath);
456
+ }
457
+ function validateConfig(raw, filePath) {
458
+ if (!raw || typeof raw !== "object") {
459
+ throw new Error(`Invalid config in ${filePath}: must export an object`);
460
+ }
461
+ const cfg = raw;
462
+ if (!cfg.input || typeof cfg.input !== "string") {
463
+ throw new Error(`Config error: "input" must be a string path to your OpenAPI file`);
464
+ }
465
+ if (!cfg.output || typeof cfg.output !== "object") {
466
+ throw new Error(`Config error: "output" must be an object with at least "client" path`);
467
+ }
468
+ const output = cfg.output;
469
+ if (!output.client || typeof output.client !== "string") {
470
+ throw new Error(`Config error: "output.client" is required`);
471
+ }
472
+ if (!cfg.axiosInstancePath || typeof cfg.axiosInstancePath !== "string") {
473
+ throw new Error(`Config error: "axiosInstancePath" is required (path to your axios instance)`);
474
+ }
475
+ return {
476
+ input: cfg.input,
477
+ output: {
478
+ client: output.client,
479
+ types: typeof output.types === "string" ? output.types : void 0
480
+ },
481
+ axiosInstancePath: cfg.axiosInstancePath,
482
+ axiosInstanceExport: typeof cfg.axiosInstanceExport === "string" ? cfg.axiosInstanceExport : "axiosInstance",
483
+ language: cfg.language === "js" ? "js" : "ts",
484
+ jsdoc: cfg.jsdoc !== false,
485
+ tags: Array.isArray(cfg.tags) ? cfg.tags : void 0
486
+ };
487
+ }
488
+ // Annotate the CommonJS export names for ESM import in node:
489
+ 0 && (module.exports = {
490
+ extractEndpoints,
491
+ generate,
492
+ generateClientFile,
493
+ generateTypesFile,
494
+ loadConfig,
495
+ parseOpenAPIFile
496
+ });