api-farmer 0.1.3 → 0.1.5
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 +26 -1
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +13 -0
- package/dist/generate-SAL57AwU.mjs +336 -0
- package/dist/index.d.mts +379 -0
- package/dist/index.mjs +4 -0
- package/dist/utils-DachPo4u.mjs +119 -0
- package/package.json +24 -42
- package/dist/chunk-2227C45C.js +0 -131
- package/dist/chunk-RIXLRGPZ.js +0 -338
- package/dist/cli.cjs +0 -471
- package/dist/cli.d.cts +0 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -14
- package/dist/generate-4J5VPBTS.js +0 -15
- package/dist/index.cjs +0 -521
- package/dist/index.d.cts +0 -334
- package/dist/index.d.ts +0 -334
- package/dist/index.js +0 -76
package/README.md
CHANGED
|
@@ -83,6 +83,7 @@ export default defineConfig({
|
|
|
83
83
|
verb() {},
|
|
84
84
|
url() {},
|
|
85
85
|
entity() {},
|
|
86
|
+
name() {},
|
|
86
87
|
fn() {},
|
|
87
88
|
comment() {},
|
|
88
89
|
type() {},
|
|
@@ -97,6 +98,25 @@ export default defineConfig({
|
|
|
97
98
|
})
|
|
98
99
|
```
|
|
99
100
|
|
|
101
|
+
#### `name` Transformer
|
|
102
|
+
|
|
103
|
+
The `name` transformer is a convenience option that derives both `fn` and `type` from a single function. When set, `fn` is the return value, and `type` is automatically capitalized from `fn`. This avoids writing duplicate logic for `fn` and `type`.
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
export default defineConfig({
|
|
107
|
+
transformer: {
|
|
108
|
+
name({ verb, entity }) {
|
|
109
|
+
return `api${verb}${entity}`
|
|
110
|
+
// fn -> apiGetUsers
|
|
111
|
+
// type -> ApiGetUsers
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
> [!NOTE]
|
|
118
|
+
> If `name` is set, `fn` and `type` transformers will be ignored.
|
|
119
|
+
|
|
100
120
|
### Configuration Options
|
|
101
121
|
|
|
102
122
|
```ts
|
|
@@ -155,7 +175,7 @@ export interface Config {
|
|
|
155
175
|
uncountableNouns?: string[]
|
|
156
176
|
/**
|
|
157
177
|
* Whether to clean the output directory before generating.
|
|
158
|
-
* @default
|
|
178
|
+
* @default true
|
|
159
179
|
*/
|
|
160
180
|
clean?: boolean
|
|
161
181
|
/**
|
|
@@ -163,6 +183,11 @@ export interface Config {
|
|
|
163
183
|
* @see https://openapi-ts.dev/node
|
|
164
184
|
*/
|
|
165
185
|
openapiTsOptions?: OpenAPITSOptions
|
|
186
|
+
/**
|
|
187
|
+
* Whether to exclude deprecated API endpoints.
|
|
188
|
+
* @default false
|
|
189
|
+
*/
|
|
190
|
+
excludeDeprecated?: boolean
|
|
166
191
|
}
|
|
167
192
|
```
|
|
168
193
|
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { r as getCliVersion } from "./utils-DachPo4u.mjs";
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
//#region src/cli.ts
|
|
5
|
+
const program = new Command();
|
|
6
|
+
program.version(getCliVersion());
|
|
7
|
+
program.action(async () => {
|
|
8
|
+
const { generate } = await import("./generate-SAL57AwU.mjs").then((n) => n.i);
|
|
9
|
+
return generate();
|
|
10
|
+
});
|
|
11
|
+
program.parse();
|
|
12
|
+
//#endregion
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { a as getResponseMetadataItems, c as isRequiredRequestBody, d as readTemplateFile, f as CWD, i as getRequestBodyContentType, l as readSchema, p as SUPPORTED_HTTP_METHODS } from "./utils-DachPo4u.mjs";
|
|
2
|
+
import pluralize from "pluralize";
|
|
3
|
+
import { camelize, groupBy, isArray, merge, pascalCase, upperFirst } from "rattail";
|
|
4
|
+
import { resolve } from "path";
|
|
5
|
+
import ejs from "ejs";
|
|
6
|
+
import fse from "fs-extra";
|
|
7
|
+
import openapiTS, { astToString } from "openapi-typescript";
|
|
8
|
+
import prettier from "prettier";
|
|
9
|
+
import { logger } from "rslog";
|
|
10
|
+
import ts from "typescript";
|
|
11
|
+
import { loadConfig } from "unconfig";
|
|
12
|
+
//#region \0rolldown/runtime.js
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __exportAll = (all, no_symbols) => {
|
|
15
|
+
let target = {};
|
|
16
|
+
for (var name in all) __defProp(target, name, {
|
|
17
|
+
get: all[name],
|
|
18
|
+
enumerable: true
|
|
19
|
+
});
|
|
20
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
21
|
+
return target;
|
|
22
|
+
};
|
|
23
|
+
//#endregion
|
|
24
|
+
//#region src/transformer.ts
|
|
25
|
+
function transformModuleName({ name }) {
|
|
26
|
+
return camelize(name);
|
|
27
|
+
}
|
|
28
|
+
function transformUrl({ path }) {
|
|
29
|
+
return path.replace(/{/g, ":").replace(/}/g, "");
|
|
30
|
+
}
|
|
31
|
+
function transformComment({ summary, description, path, method }) {
|
|
32
|
+
return `
|
|
33
|
+
/**${summary ? `\n* ${summary}` : ""}${description && summary !== description ? `\n* @description ${description}\n*` : ""}
|
|
34
|
+
* @url ${path}
|
|
35
|
+
* @method ${method.toLocaleUpperCase()}
|
|
36
|
+
*/
|
|
37
|
+
`.trim();
|
|
38
|
+
}
|
|
39
|
+
function transformVerb({ method }) {
|
|
40
|
+
switch (method) {
|
|
41
|
+
case "post": return "Create";
|
|
42
|
+
case "put": return "Update";
|
|
43
|
+
default: return pascalCase(method);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function transformEntity({ path, method, uncountableNouns }) {
|
|
47
|
+
const words = path.split("/").filter(Boolean);
|
|
48
|
+
return words.reduce((entity, word, index) => {
|
|
49
|
+
if (word.includes("{")) return entity;
|
|
50
|
+
word = word.replace(/\.([a-z])/g, (_, p) => p.toUpperCase());
|
|
51
|
+
const isUncountableNoun = uncountableNouns.includes(word);
|
|
52
|
+
word = pascalCase(word);
|
|
53
|
+
word = isUncountableNoun ? word : pluralize.singular(word);
|
|
54
|
+
if (method === "get" && index === words.length - 1) word = isUncountableNoun ? `${word}List` : pluralize.plural(word);
|
|
55
|
+
return `${entity}${word}`;
|
|
56
|
+
}, "");
|
|
57
|
+
}
|
|
58
|
+
function transformFn({ verb, entity }) {
|
|
59
|
+
return `api${verb}${entity}`;
|
|
60
|
+
}
|
|
61
|
+
function transformType({ verb, entity }) {
|
|
62
|
+
return `Api${verb}${entity}`;
|
|
63
|
+
}
|
|
64
|
+
function transformTypeValue({ fullPath, method }) {
|
|
65
|
+
return `paths['${fullPath}']['${method}']`;
|
|
66
|
+
}
|
|
67
|
+
function transformTypeQuery({ type }) {
|
|
68
|
+
return `${type}Query`;
|
|
69
|
+
}
|
|
70
|
+
function transformTypeQueryValue({ type }) {
|
|
71
|
+
return `${type}['parameters']['query']`;
|
|
72
|
+
}
|
|
73
|
+
function transformTypeRequestBody({ type }) {
|
|
74
|
+
return `${type}RequestBody`;
|
|
75
|
+
}
|
|
76
|
+
function transformTypeRequestBodyValue({ type, required, requestContentType }) {
|
|
77
|
+
return required ? `${type}['requestBody']['content']['${requestContentType}']` : `NonNullable<${type}['requestBody']>['content']['${requestContentType}'] | undefined`;
|
|
78
|
+
}
|
|
79
|
+
function transformTypeResponseBody({ type }) {
|
|
80
|
+
return `${type}ResponseBody`;
|
|
81
|
+
}
|
|
82
|
+
function transformTypeResponseBodyValue({ type, responseMetadataItems }) {
|
|
83
|
+
return responseMetadataItems.map(({ status, responseContentType }) => `${type}['responses']['${status}']['content']['${responseContentType}']`).join(" | ");
|
|
84
|
+
}
|
|
85
|
+
function createTransformer() {
|
|
86
|
+
return {
|
|
87
|
+
moduleName: transformModuleName,
|
|
88
|
+
verb: transformVerb,
|
|
89
|
+
url: transformUrl,
|
|
90
|
+
comment: transformComment,
|
|
91
|
+
entity: transformEntity,
|
|
92
|
+
fn: transformFn,
|
|
93
|
+
type: transformType,
|
|
94
|
+
typeValue: transformTypeValue,
|
|
95
|
+
typeQuery: transformTypeQuery,
|
|
96
|
+
typeQueryValue: transformTypeQueryValue,
|
|
97
|
+
typeRequestBody: transformTypeRequestBody,
|
|
98
|
+
typeRequestBodyValue: transformTypeRequestBodyValue,
|
|
99
|
+
typeResponseBody: transformTypeResponseBody,
|
|
100
|
+
typeResponseBodyValue: transformTypeResponseBodyValue
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/config.ts
|
|
105
|
+
function defineConfig(config) {
|
|
106
|
+
return config;
|
|
107
|
+
}
|
|
108
|
+
async function getConfig() {
|
|
109
|
+
const { config } = await loadConfig({ sources: [{ files: "api-farmer.config" }] });
|
|
110
|
+
return config ?? {};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/generate.ts
|
|
114
|
+
var generate_exports = /* @__PURE__ */ __exportAll({
|
|
115
|
+
detectCollisions: () => detectCollisions,
|
|
116
|
+
generate: () => generate,
|
|
117
|
+
generateTypes: () => generateTypes,
|
|
118
|
+
partitionApiModules: () => partitionApiModules,
|
|
119
|
+
renderApiModules: () => renderApiModules,
|
|
120
|
+
transformPayloads: () => transformPayloads
|
|
121
|
+
});
|
|
122
|
+
function transformPayloads(pathItems, options) {
|
|
123
|
+
const { transformer, path, fullPath, base, uncountableNouns, validateStatus, excludeDeprecated } = options;
|
|
124
|
+
return Object.entries(pathItems).filter(([key]) => SUPPORTED_HTTP_METHODS.includes(key)).filter(([, operation]) => !(excludeDeprecated && operation.deprecated)).reduce((payloads, [method, operation]) => {
|
|
125
|
+
const url = transformer.url({
|
|
126
|
+
path,
|
|
127
|
+
base,
|
|
128
|
+
fullPath
|
|
129
|
+
});
|
|
130
|
+
const args = {
|
|
131
|
+
path,
|
|
132
|
+
base,
|
|
133
|
+
fullPath,
|
|
134
|
+
url,
|
|
135
|
+
method,
|
|
136
|
+
uncountableNouns,
|
|
137
|
+
operation
|
|
138
|
+
};
|
|
139
|
+
const entity = transformer.entity(args);
|
|
140
|
+
const verb = transformer.verb(args);
|
|
141
|
+
const comment = transformer.comment({
|
|
142
|
+
...args,
|
|
143
|
+
...operation
|
|
144
|
+
});
|
|
145
|
+
const requestContentType = operation.requestBody ? getRequestBodyContentType(operation.requestBody) : void 0;
|
|
146
|
+
const responseMetadataItems = getResponseMetadataItems(operation, validateStatus);
|
|
147
|
+
const nameArgs = {
|
|
148
|
+
...args,
|
|
149
|
+
verb,
|
|
150
|
+
entity
|
|
151
|
+
};
|
|
152
|
+
const fn = transformer.name ? transformer.name(nameArgs) : transformer.fn(nameArgs);
|
|
153
|
+
const type = transformer.name ? upperFirst(fn) : transformer.type(nameArgs);
|
|
154
|
+
const typeValue = transformer.typeValue({
|
|
155
|
+
...args,
|
|
156
|
+
verb,
|
|
157
|
+
entity
|
|
158
|
+
});
|
|
159
|
+
const typeQuery = transformer.typeQuery({
|
|
160
|
+
...args,
|
|
161
|
+
type,
|
|
162
|
+
verb,
|
|
163
|
+
entity
|
|
164
|
+
});
|
|
165
|
+
const typeQueryValue = transformer.typeQueryValue({
|
|
166
|
+
...args,
|
|
167
|
+
type,
|
|
168
|
+
verb,
|
|
169
|
+
entity
|
|
170
|
+
});
|
|
171
|
+
const typeRequestBody = transformer.typeRequestBody({
|
|
172
|
+
...args,
|
|
173
|
+
type,
|
|
174
|
+
verb,
|
|
175
|
+
entity
|
|
176
|
+
});
|
|
177
|
+
const typeRequestBodyValue = operation.requestBody && requestContentType ? transformer.typeRequestBodyValue({
|
|
178
|
+
...args,
|
|
179
|
+
type,
|
|
180
|
+
verb,
|
|
181
|
+
entity,
|
|
182
|
+
required: isRequiredRequestBody(operation.requestBody),
|
|
183
|
+
requestContentType
|
|
184
|
+
}) : "undefined";
|
|
185
|
+
const typeResponseBody = transformer.typeResponseBody({
|
|
186
|
+
...args,
|
|
187
|
+
type,
|
|
188
|
+
verb,
|
|
189
|
+
entity
|
|
190
|
+
});
|
|
191
|
+
const typeResponseBodyValue = responseMetadataItems.length > 0 ? transformer.typeResponseBodyValue({
|
|
192
|
+
...args,
|
|
193
|
+
type,
|
|
194
|
+
verb,
|
|
195
|
+
entity,
|
|
196
|
+
responseMetadataItems
|
|
197
|
+
}) : "undefined";
|
|
198
|
+
payloads.push({
|
|
199
|
+
comment,
|
|
200
|
+
fn,
|
|
201
|
+
url,
|
|
202
|
+
method,
|
|
203
|
+
verb,
|
|
204
|
+
entity,
|
|
205
|
+
requestContentType,
|
|
206
|
+
type,
|
|
207
|
+
typeValue,
|
|
208
|
+
typeQuery,
|
|
209
|
+
typeQueryValue,
|
|
210
|
+
typeRequestBody,
|
|
211
|
+
typeRequestBodyValue,
|
|
212
|
+
typeResponseBody,
|
|
213
|
+
typeResponseBodyValue
|
|
214
|
+
});
|
|
215
|
+
return payloads;
|
|
216
|
+
}, []);
|
|
217
|
+
}
|
|
218
|
+
function partitionApiModules(schema, options) {
|
|
219
|
+
const { base, transformer, uncountableNouns, validateStatus, excludeDeprecated } = options;
|
|
220
|
+
const schemaPaths = schema.paths ?? {};
|
|
221
|
+
const keyToPaths = groupBy(base ? Object.keys(schemaPaths).map((key) => key.replace(base, "")) : Object.keys(schemaPaths), (key) => key.split("/")[1]);
|
|
222
|
+
return Object.entries(keyToPaths).reduce((apiModules, [name, paths]) => {
|
|
223
|
+
const payloads = paths.reduce((payloads, path) => {
|
|
224
|
+
const fullPath = base ? base + path : path;
|
|
225
|
+
const pathItems = schemaPaths[fullPath];
|
|
226
|
+
payloads.push(...transformPayloads(pathItems, {
|
|
227
|
+
...options,
|
|
228
|
+
path,
|
|
229
|
+
fullPath,
|
|
230
|
+
transformer,
|
|
231
|
+
uncountableNouns,
|
|
232
|
+
validateStatus,
|
|
233
|
+
excludeDeprecated
|
|
234
|
+
}));
|
|
235
|
+
return payloads;
|
|
236
|
+
}, []);
|
|
237
|
+
apiModules.push({
|
|
238
|
+
name: transformer.moduleName({ name }),
|
|
239
|
+
payloads
|
|
240
|
+
});
|
|
241
|
+
return apiModules;
|
|
242
|
+
}, []);
|
|
243
|
+
}
|
|
244
|
+
function renderApiModules(apiModules, options) {
|
|
245
|
+
const { output, ts, typesOnly, overrides, preset } = options;
|
|
246
|
+
const templateFile = readTemplateFile(preset);
|
|
247
|
+
const typesFilename = options.typesFilename.replace(".ts", "");
|
|
248
|
+
return Promise.all(apiModules.map((apiModule) => new Promise((promiseResolve) => {
|
|
249
|
+
const data = {
|
|
250
|
+
apiModule,
|
|
251
|
+
typesFilename,
|
|
252
|
+
ts,
|
|
253
|
+
typesOnly
|
|
254
|
+
};
|
|
255
|
+
prettier.format(ejs.render(templateFile, data), {
|
|
256
|
+
parser: "typescript",
|
|
257
|
+
semi: false,
|
|
258
|
+
singleQuote: true,
|
|
259
|
+
printWidth: 120
|
|
260
|
+
}).then((content) => {
|
|
261
|
+
const path = resolve(output, `${apiModule.name}.${ts ? "ts" : "js"}`);
|
|
262
|
+
if ((!overrides || isArray(overrides) && !overrides.includes(apiModule.name)) && fse.existsSync(path)) {
|
|
263
|
+
logger.warn(`File already exists, skip: ${path}`);
|
|
264
|
+
promiseResolve(content);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
fse.outputFileSync(path, content);
|
|
268
|
+
logger.success(`Generated ${path}`);
|
|
269
|
+
promiseResolve(content);
|
|
270
|
+
});
|
|
271
|
+
})));
|
|
272
|
+
}
|
|
273
|
+
async function generateTypes(schema, output, typesFilename, openapiTsOptions) {
|
|
274
|
+
const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Blob"));
|
|
275
|
+
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
|
|
276
|
+
const contents = astToString(await openapiTS(schema, {
|
|
277
|
+
defaultNonNullable: false,
|
|
278
|
+
transform(schemaObject) {
|
|
279
|
+
if (schemaObject.format === "binary") return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
|
|
280
|
+
},
|
|
281
|
+
...openapiTsOptions
|
|
282
|
+
}));
|
|
283
|
+
const typesFilepath = resolve(CWD, output, typesFilename);
|
|
284
|
+
fse.outputFileSync(typesFilepath, contents);
|
|
285
|
+
logger.success(`Generated ${typesFilepath}`);
|
|
286
|
+
}
|
|
287
|
+
function detectCollisions(apiModules) {
|
|
288
|
+
const fnMap = /* @__PURE__ */ new Map();
|
|
289
|
+
for (const apiModule of apiModules) for (const payload of apiModule.payloads) {
|
|
290
|
+
const entries = fnMap.get(payload.fn) ?? [];
|
|
291
|
+
entries.push({
|
|
292
|
+
module: apiModule.name,
|
|
293
|
+
url: payload.url,
|
|
294
|
+
method: payload.method
|
|
295
|
+
});
|
|
296
|
+
fnMap.set(payload.fn, entries);
|
|
297
|
+
}
|
|
298
|
+
for (const [fn, entries] of fnMap) if (entries.length > 1) {
|
|
299
|
+
const endpoints = entries.map((e) => `[${e.method.toUpperCase()}] ${e.url}`).join(", ");
|
|
300
|
+
logger.warn(`API name collision: "${fn}" is generated by multiple endpoints: ${endpoints}. Consider using the "transformer" option to customize API naming.`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async function generate(userOptions = {}) {
|
|
304
|
+
const { base, ts = true, typesOnly = false, overrides = true, preset = "axle", input = "./schema.json", output = "./src/apis/generated", typesFilename = "_types.ts", clean = true, validateStatus = (status) => status >= 200 && status < 300, transformer = {}, uncountableNouns = [], openapiTsOptions = {}, excludeDeprecated = false } = merge(await getConfig(), userOptions);
|
|
305
|
+
const mergedTransformer = {
|
|
306
|
+
...createTransformer(),
|
|
307
|
+
...transformer
|
|
308
|
+
};
|
|
309
|
+
const schema = await readSchema(input);
|
|
310
|
+
if (clean) {
|
|
311
|
+
fse.removeSync(resolve(CWD, output));
|
|
312
|
+
logger.info(`Cleaned output directory: ${resolve(CWD, output)}`);
|
|
313
|
+
}
|
|
314
|
+
logger.info("Generating API modules...");
|
|
315
|
+
if (openapiTsOptions.excludeDeprecated === void 0) openapiTsOptions.excludeDeprecated = excludeDeprecated;
|
|
316
|
+
if (ts) await generateTypes(schema, output, typesFilename, openapiTsOptions);
|
|
317
|
+
const apiModules = partitionApiModules(schema, {
|
|
318
|
+
base,
|
|
319
|
+
uncountableNouns,
|
|
320
|
+
transformer: mergedTransformer,
|
|
321
|
+
validateStatus,
|
|
322
|
+
excludeDeprecated
|
|
323
|
+
});
|
|
324
|
+
detectCollisions(apiModules);
|
|
325
|
+
await renderApiModules(apiModules, {
|
|
326
|
+
output,
|
|
327
|
+
typesFilename,
|
|
328
|
+
ts,
|
|
329
|
+
typesOnly,
|
|
330
|
+
overrides,
|
|
331
|
+
preset
|
|
332
|
+
});
|
|
333
|
+
logger.success("Done");
|
|
334
|
+
}
|
|
335
|
+
//#endregion
|
|
336
|
+
export { transformUrl as C, transformTypeValue as S, transformTypeQueryValue as _, partitionApiModules as a, transformTypeResponseBody as b, defineConfig as c, transformComment as d, transformEntity as f, transformTypeQuery as g, transformType as h, generate_exports as i, getConfig as l, transformModuleName as m, generate as n, renderApiModules as o, transformFn as p, generateTypes as r, transformPayloads as s, detectCollisions as t, createTransformer as u, transformTypeRequestBody as v, transformVerb as w, transformTypeResponseBodyValue as x, transformTypeRequestBodyValue as y };
|