@icib.dev/api-client 1.1.1 → 1.1.3
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 +5 -1
- package/dist/scripts/generate.js +85 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -61,9 +61,13 @@ BASE_PATH=/v2 npx api-client-generate
|
|
|
61
61
|
# Custom client base URL (default: from spec URL, BASE_URL, or spec host)
|
|
62
62
|
npx api-client-generate --base-url https://api.mycompany.com
|
|
63
63
|
BASE_URL=https://api.mycompany.com npx api-client-generate
|
|
64
|
+
|
|
65
|
+
# Override client.ts (by default, existing client is preserved if you customized it)
|
|
66
|
+
npx api-client-generate --override-client # prompts for confirmation
|
|
67
|
+
npx api-client-generate --override-client --yes # skip confirmation (e.g. CI)
|
|
64
68
|
```
|
|
65
69
|
|
|
66
|
-
The client is generated in your project directory (e.g. `./api/`).
|
|
70
|
+
The client is generated in your project directory (e.g. `./api/`). If `client.ts` already exists, it is **not** overwritten unless you pass `--override-client` (which prompts for confirmation; use `--yes` to skip the prompt).
|
|
67
71
|
|
|
68
72
|
### From the library repo (maintainers)
|
|
69
73
|
|
package/dist/scripts/generate.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
2
|
+
import { readFileSync, mkdirSync, writeFileSync, existsSync } from "fs";
|
|
3
|
+
import { createInterface } from "readline";
|
|
3
4
|
import { dirname, join } from "path";
|
|
4
5
|
import { fileURLToPath } from "url";
|
|
5
6
|
import SwaggerParser from "@apidevtools/swagger-parser";
|
|
@@ -20,6 +21,8 @@ function parseArgs() {
|
|
|
20
21
|
let out = DEFAULT_OUT;
|
|
21
22
|
let basePath = process.env.BASE_PATH;
|
|
22
23
|
let baseUrl = process.env.BASE_URL;
|
|
24
|
+
let overrideClient = false;
|
|
25
|
+
let yes = false;
|
|
23
26
|
for (let i = 0; i < args.length; i++) {
|
|
24
27
|
if (args[i] === "--url" && args[i + 1]) {
|
|
25
28
|
url = args[++i];
|
|
@@ -35,8 +38,24 @@ function parseArgs() {
|
|
|
35
38
|
args[i + 1]) {
|
|
36
39
|
baseUrl = args[++i];
|
|
37
40
|
}
|
|
41
|
+
else if (args[i] === "--override-client" || args[i] === "--overwrite-client") {
|
|
42
|
+
overrideClient = true;
|
|
43
|
+
}
|
|
44
|
+
else if (args[i] === "--yes" || args[i] === "-y") {
|
|
45
|
+
yes = true;
|
|
46
|
+
}
|
|
38
47
|
}
|
|
39
|
-
return { url, out, basePath, baseUrl };
|
|
48
|
+
return { url, out, basePath, baseUrl, overrideClient, yes };
|
|
49
|
+
}
|
|
50
|
+
function askConfirmation(question) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
53
|
+
rl.question(question, (answer) => {
|
|
54
|
+
rl.close();
|
|
55
|
+
const normalized = answer.trim().toLowerCase();
|
|
56
|
+
resolve(normalized === "y" || normalized === "yes");
|
|
57
|
+
});
|
|
58
|
+
});
|
|
40
59
|
}
|
|
41
60
|
async function fetchSpec(url) {
|
|
42
61
|
const res = await fetch(url);
|
|
@@ -92,7 +111,11 @@ function getDefinitions(doc) {
|
|
|
92
111
|
function getPaths(doc) {
|
|
93
112
|
return doc.paths ?? {};
|
|
94
113
|
}
|
|
95
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Extract response schema. Compatible with:
|
|
116
|
+
* - OpenAPI 2.0: response.schema
|
|
117
|
+
* - OpenAPI 3.0: response.content['application/json'].schema
|
|
118
|
+
*/
|
|
96
119
|
function getResponseSchema(response) {
|
|
97
120
|
if (!response || typeof response !== "object")
|
|
98
121
|
return undefined;
|
|
@@ -111,6 +134,23 @@ function getResponseSchema(response) {
|
|
|
111
134
|
}
|
|
112
135
|
return undefined;
|
|
113
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Extract request body schema from OpenAPI 3.0 requestBody.content.
|
|
139
|
+
* OpenAPI 2.0 uses parameters with in: "body" (handled separately in extractOperations).
|
|
140
|
+
*/
|
|
141
|
+
function getRequestBodySchema(requestBody) {
|
|
142
|
+
if (!requestBody || typeof requestBody !== "object")
|
|
143
|
+
return undefined;
|
|
144
|
+
const rb = requestBody;
|
|
145
|
+
const content = rb.content;
|
|
146
|
+
if (content) {
|
|
147
|
+
const jsonContent = content["application/json"] ??
|
|
148
|
+
content["*/*"] ??
|
|
149
|
+
Object.values(content)[0];
|
|
150
|
+
return jsonContent?.schema;
|
|
151
|
+
}
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
114
154
|
function schemaToTsType(schema, definitions, refsSeen = new Set()) {
|
|
115
155
|
if (!schema)
|
|
116
156
|
return "unknown";
|
|
@@ -269,6 +309,29 @@ function extractOperations(paths, definitions) {
|
|
|
269
309
|
};
|
|
270
310
|
}
|
|
271
311
|
}
|
|
312
|
+
// OpenAPI 3.0: body in requestBody (OAS 2.0 uses parameters in: "body" above)
|
|
313
|
+
if (!bodyParam && op.requestBody) {
|
|
314
|
+
const bodySchema = getRequestBodySchema(op.requestBody);
|
|
315
|
+
if (bodySchema) {
|
|
316
|
+
const propertyDescriptions = {};
|
|
317
|
+
if (bodySchema.properties) {
|
|
318
|
+
for (const [propName, propSchema] of Object.entries(bodySchema.properties)) {
|
|
319
|
+
const desc = propSchema
|
|
320
|
+
.description ??
|
|
321
|
+
propSchema.title;
|
|
322
|
+
if (desc)
|
|
323
|
+
propertyDescriptions[propName] = desc;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
bodyParam = {
|
|
327
|
+
name: "data",
|
|
328
|
+
schema: bodySchema,
|
|
329
|
+
propertyDescriptions: Object.keys(propertyDescriptions).length > 0
|
|
330
|
+
? propertyDescriptions
|
|
331
|
+
: undefined,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
272
335
|
const successResponse = op.responses?.["200"] ?? op.responses?.["201"];
|
|
273
336
|
const respSchema = getResponseSchema(successResponse);
|
|
274
337
|
const respDesc = successResponse?.description ?? "";
|
|
@@ -676,7 +739,7 @@ function generateIndex(contextTags) {
|
|
|
676
739
|
return exports.join("\n");
|
|
677
740
|
}
|
|
678
741
|
async function main() {
|
|
679
|
-
const { url, out, basePath: basePathOverride, baseUrl: baseUrlOverride, } = parseArgs();
|
|
742
|
+
const { url, out, basePath: basePathOverride, baseUrl: baseUrlOverride, overrideClient, yes, } = parseArgs();
|
|
680
743
|
console.log(`Fetching spec from ${url}...`);
|
|
681
744
|
const rawSpec = await loadRawSpec(url);
|
|
682
745
|
const doc = await parseSpec(rawSpec);
|
|
@@ -713,7 +776,23 @@ async function main() {
|
|
|
713
776
|
? `${baseUrl.replace(/\/$/, "")}${basePath}`
|
|
714
777
|
: baseUrl;
|
|
715
778
|
writeFileSync(join(typesDir, "index.ts"), generateTypes(definitions));
|
|
716
|
-
|
|
779
|
+
const clientPath = join(outDir, "client.ts");
|
|
780
|
+
const clientExists = existsSync(clientPath);
|
|
781
|
+
let writeClient = !clientExists;
|
|
782
|
+
if (clientExists) {
|
|
783
|
+
if (overrideClient) {
|
|
784
|
+
if (yes) {
|
|
785
|
+
writeClient = true;
|
|
786
|
+
}
|
|
787
|
+
else {
|
|
788
|
+
const confirmed = await askConfirmation("This will overwrite your client.ts. Are you sure? (y/N) ");
|
|
789
|
+
writeClient = confirmed;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (writeClient) {
|
|
794
|
+
writeFileSync(clientPath, generateClient(clientBaseUrl));
|
|
795
|
+
}
|
|
717
796
|
const sortedTags = [...byTag.keys()].sort();
|
|
718
797
|
for (const tag of sortedTags) {
|
|
719
798
|
const ctxName = sanitizeContextName(tag);
|
|
@@ -736,7 +815,7 @@ async function main() {
|
|
|
736
815
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
737
816
|
console.log(`Generated API client in ${outDir}`);
|
|
738
817
|
console.log(` - types/index.ts`);
|
|
739
|
-
console.log(` - client.ts`);
|
|
818
|
+
console.log(` - client.ts${writeClient ? "" : " (skipped, use --override-client to overwrite)"}`);
|
|
740
819
|
console.log(` - apiClient.ts`);
|
|
741
820
|
console.log(` - contexts/*.ts (${sortedTags.length} files)`);
|
|
742
821
|
console.log(` - index.ts`);
|