api-farmer 0.0.19 → 0.0.21
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 +4 -0
- package/dist/chunk-GMB5TIVL.js +285 -0
- package/dist/{chunk-SKKHIIUE.js → chunk-PTGO2Y5X.js} +49 -15
- package/dist/cli.cjs +164 -101995
- package/dist/cli.js +2 -3
- package/dist/{generate-IJGTBA2I.js → generate-HKA2M6ER.js} +2 -3
- package/dist/index.cjs +242 -102105
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +10 -5
- package/package.json +4 -4
- package/templates/axios.ejs +8 -3
- package/templates/axle.ejs +4 -2
- package/dist/acorn-O63CA7L4.js +0 -3047
- package/dist/angular-U44OZXUW.js +0 -2794
- package/dist/babel-VWJW3TNP.js +0 -6946
- package/dist/chunk-6OIOYGN7.js +0 -17
- package/dist/chunk-K46PCBCF.js +0 -22981
- package/dist/estree-OIZOUIW7.js +0 -4374
- package/dist/flow-TDKVTHFE.js +0 -26897
- package/dist/glimmer-AWQE7API.js +0 -2847
- package/dist/graphql-IJWEVF4G.js +0 -1247
- package/dist/html-7SW5PDZO.js +0 -2716
- package/dist/markdown-2BNVGUT3.js +0 -3484
- package/dist/meriyah-L52O7E6A.js +0 -2474
- package/dist/postcss-AOWY4ITF.js +0 -5057
- package/dist/typescript-ANND5E4Y.js +0 -13104
- package/dist/yaml-MB3PL3NY.js +0 -4223
package/README.md
CHANGED
|
@@ -210,6 +210,10 @@ export interface ApiModulePayload {
|
|
|
210
210
|
* such as User, Comment, Post, etc.
|
|
211
211
|
*/
|
|
212
212
|
entity: string
|
|
213
|
+
/**
|
|
214
|
+
* The request content type of the API endpoint, such as 'application/json', 'application/x-www-form-urlencoded'.
|
|
215
|
+
*/
|
|
216
|
+
requestContentType?: string
|
|
213
217
|
/**
|
|
214
218
|
* The type name of the API endpoint,
|
|
215
219
|
* such as ApiGetUsers, ApiCreatePost, ApiUpdateComment, etc.
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CWD,
|
|
3
|
+
SUPPORTED_HTTP_METHODS,
|
|
4
|
+
getRequestBodyContentType,
|
|
5
|
+
getResponseMetadataItems,
|
|
6
|
+
hasQueryParameter,
|
|
7
|
+
isRequiredRequestBody,
|
|
8
|
+
readSchema,
|
|
9
|
+
readTemplateFile
|
|
10
|
+
} from "./chunk-PTGO2Y5X.js";
|
|
11
|
+
|
|
12
|
+
// src/generate.ts
|
|
13
|
+
import { resolve } from "path";
|
|
14
|
+
import ejs from "ejs";
|
|
15
|
+
import fse from "fs-extra";
|
|
16
|
+
import openapiTS, { astToString } from "openapi-typescript";
|
|
17
|
+
import prettier from "prettier";
|
|
18
|
+
import { groupBy, isArray, merge } from "rattail";
|
|
19
|
+
import { logger } from "rslog";
|
|
20
|
+
|
|
21
|
+
// src/config.ts
|
|
22
|
+
import { loadConfig } from "unconfig";
|
|
23
|
+
function defineConfig(config) {
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
async function getConfig() {
|
|
27
|
+
const { config } = await loadConfig({
|
|
28
|
+
sources: [
|
|
29
|
+
{
|
|
30
|
+
files: "api-farmer.config"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
});
|
|
34
|
+
return config ?? {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/transformer.ts
|
|
38
|
+
import pluralize from "pluralize";
|
|
39
|
+
import { camelize, pascalCase } from "rattail";
|
|
40
|
+
function transformModuleName({ name }) {
|
|
41
|
+
return camelize(name);
|
|
42
|
+
}
|
|
43
|
+
function transformUrl({ path, base }) {
|
|
44
|
+
return (base ? path.replace(base, "") : path).replace(/{/g, ":").replace(/}/g, "");
|
|
45
|
+
}
|
|
46
|
+
function transformVerb({ method }) {
|
|
47
|
+
switch (method) {
|
|
48
|
+
case "post":
|
|
49
|
+
return "Create";
|
|
50
|
+
case "put":
|
|
51
|
+
return "Update";
|
|
52
|
+
default:
|
|
53
|
+
return pascalCase(method);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function transformEntity({ path, method, base }) {
|
|
57
|
+
path = base ? path.replace(base, "") : path;
|
|
58
|
+
const words = path.split("/").filter(Boolean);
|
|
59
|
+
return words.reduce((entity, word, index) => {
|
|
60
|
+
if (word.includes("{")) {
|
|
61
|
+
return entity;
|
|
62
|
+
}
|
|
63
|
+
word = word.replace(/\.([a-z])/g, (_, p) => p.toUpperCase());
|
|
64
|
+
word = pluralize.singular(pascalCase(word));
|
|
65
|
+
if (method === "get" && index === words.length - 1) {
|
|
66
|
+
word = pluralize.plural(word);
|
|
67
|
+
}
|
|
68
|
+
return `${entity}${word}`;
|
|
69
|
+
}, "");
|
|
70
|
+
}
|
|
71
|
+
function transformFn({ verb, entity }) {
|
|
72
|
+
return `api${verb}${entity}`;
|
|
73
|
+
}
|
|
74
|
+
function transformType({ verb, entity }) {
|
|
75
|
+
return `Api${verb}${entity}`;
|
|
76
|
+
}
|
|
77
|
+
function transformTypeValue({ path, method }) {
|
|
78
|
+
return `paths['${path}']['${method}']`;
|
|
79
|
+
}
|
|
80
|
+
function transformTypeQuery({ type }) {
|
|
81
|
+
return `${type}Query`;
|
|
82
|
+
}
|
|
83
|
+
function transformTypeQueryValue({
|
|
84
|
+
type
|
|
85
|
+
}) {
|
|
86
|
+
return `${type}['parameters']['query']`;
|
|
87
|
+
}
|
|
88
|
+
function transformTypeRequestBody({
|
|
89
|
+
type
|
|
90
|
+
}) {
|
|
91
|
+
return `${type}RequestBody`;
|
|
92
|
+
}
|
|
93
|
+
function transformTypeRequestBodyValue({
|
|
94
|
+
type,
|
|
95
|
+
required,
|
|
96
|
+
requestContentType
|
|
97
|
+
}) {
|
|
98
|
+
return required ? `${type}['requestBody']['content']['${requestContentType}']` : `NonNullable<${type}['requestBody']>['content']['${requestContentType}'] | undefined`;
|
|
99
|
+
}
|
|
100
|
+
function transformTypeResponseBody({
|
|
101
|
+
type
|
|
102
|
+
}) {
|
|
103
|
+
return `${type}ResponseBody`;
|
|
104
|
+
}
|
|
105
|
+
function transformTypeResponseBodyValue({
|
|
106
|
+
type,
|
|
107
|
+
responseMetadataItems
|
|
108
|
+
}) {
|
|
109
|
+
return responseMetadataItems.map(({ status, responseContentType }) => `${type}['responses']['${status}']['content']['${responseContentType}']`).join(" | ");
|
|
110
|
+
}
|
|
111
|
+
function createTransformer() {
|
|
112
|
+
return {
|
|
113
|
+
moduleName: transformModuleName,
|
|
114
|
+
verb: transformVerb,
|
|
115
|
+
url: transformUrl,
|
|
116
|
+
entity: transformEntity,
|
|
117
|
+
fn: transformFn,
|
|
118
|
+
type: transformType,
|
|
119
|
+
typeValue: transformTypeValue,
|
|
120
|
+
typeQuery: transformTypeQuery,
|
|
121
|
+
typeQueryValue: transformTypeQueryValue,
|
|
122
|
+
typeRequestBody: transformTypeRequestBody,
|
|
123
|
+
typeRequestBodyValue: transformTypeRequestBodyValue,
|
|
124
|
+
typeResponseBody: transformTypeResponseBody,
|
|
125
|
+
typeResponseBodyValue: transformTypeResponseBodyValue
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/generate.ts
|
|
130
|
+
function transformPayloads(pathItems, options) {
|
|
131
|
+
const { transformer, path, base, validateStatus } = options;
|
|
132
|
+
return Object.entries(pathItems).filter(([key]) => SUPPORTED_HTTP_METHODS.includes(key)).reduce((payloads, [method, operation]) => {
|
|
133
|
+
const url = transformer.url({ path, base });
|
|
134
|
+
const args = { path, base, url, method, operation };
|
|
135
|
+
const entity = transformer.entity(args);
|
|
136
|
+
const verb = transformer.verb(args);
|
|
137
|
+
const requestContentType = operation.requestBody ? getRequestBodyContentType(operation.requestBody) : void 0;
|
|
138
|
+
const responseMetadataItems = getResponseMetadataItems(operation, validateStatus);
|
|
139
|
+
const fn = transformer.fn({ ...args, verb, entity });
|
|
140
|
+
const type = transformer.type({ ...args, verb, entity });
|
|
141
|
+
const typeValue = transformer.typeValue({ ...args, verb, entity });
|
|
142
|
+
const typeQuery = transformer.typeQuery({ ...args, type, verb, entity });
|
|
143
|
+
const typeQueryValue = hasQueryParameter(operation) ? transformer.typeQueryValue({ ...args, type, verb, entity }) : "undefined";
|
|
144
|
+
const typeRequestBody = transformer.typeRequestBody({ ...args, type, verb, entity });
|
|
145
|
+
const typeRequestBodyValue = operation.requestBody && requestContentType ? transformer.typeRequestBodyValue({
|
|
146
|
+
...args,
|
|
147
|
+
type,
|
|
148
|
+
verb,
|
|
149
|
+
entity,
|
|
150
|
+
required: isRequiredRequestBody(operation.requestBody),
|
|
151
|
+
requestContentType
|
|
152
|
+
}) : "undefined";
|
|
153
|
+
const typeResponseBody = transformer.typeResponseBody({ ...args, type, verb, entity });
|
|
154
|
+
const typeResponseBodyValue = responseMetadataItems.length > 0 ? transformer.typeResponseBodyValue({ ...args, type, verb, entity, responseMetadataItems }) : "undefined";
|
|
155
|
+
payloads.push({
|
|
156
|
+
fn,
|
|
157
|
+
url,
|
|
158
|
+
method,
|
|
159
|
+
verb,
|
|
160
|
+
entity,
|
|
161
|
+
requestContentType,
|
|
162
|
+
type,
|
|
163
|
+
typeValue,
|
|
164
|
+
typeQuery,
|
|
165
|
+
typeQueryValue,
|
|
166
|
+
typeRequestBody,
|
|
167
|
+
typeRequestBodyValue,
|
|
168
|
+
typeResponseBody,
|
|
169
|
+
typeResponseBodyValue
|
|
170
|
+
});
|
|
171
|
+
return payloads;
|
|
172
|
+
}, []);
|
|
173
|
+
}
|
|
174
|
+
function partitionApiModules(schema, options) {
|
|
175
|
+
const { base, transformer, validateStatus } = options;
|
|
176
|
+
const schemaPaths = schema.paths ?? {};
|
|
177
|
+
const schemaPathKeys = base ? Object.keys(schemaPaths).map((key) => key.replace(base, "")) : Object.keys(schemaPaths);
|
|
178
|
+
const keyToPaths = groupBy(schemaPathKeys, (key) => key.split("/")[1]);
|
|
179
|
+
const apiModules = Object.entries(keyToPaths).reduce((apiModules2, [name, paths]) => {
|
|
180
|
+
const payloads = paths.reduce((payloads2, path) => {
|
|
181
|
+
const pathItems = schemaPaths[path];
|
|
182
|
+
payloads2.push(
|
|
183
|
+
...transformPayloads(pathItems, { ...options, path: base ? base + path : path, transformer, validateStatus })
|
|
184
|
+
);
|
|
185
|
+
return payloads2;
|
|
186
|
+
}, []);
|
|
187
|
+
apiModules2.push({ name: transformer.moduleName({ name }), payloads });
|
|
188
|
+
return apiModules2;
|
|
189
|
+
}, []);
|
|
190
|
+
return apiModules;
|
|
191
|
+
}
|
|
192
|
+
function renderApiModules(apiModules, options) {
|
|
193
|
+
const { output, ts, typesOnly, overrides, preset } = options;
|
|
194
|
+
const templateFile = readTemplateFile(preset);
|
|
195
|
+
const typesFilename = options.typesFilename.replace(".ts", "");
|
|
196
|
+
return Promise.all(
|
|
197
|
+
apiModules.map(
|
|
198
|
+
(apiModule) => new Promise((promiseResolve) => {
|
|
199
|
+
const data = {
|
|
200
|
+
apiModule,
|
|
201
|
+
typesFilename,
|
|
202
|
+
ts,
|
|
203
|
+
typesOnly
|
|
204
|
+
};
|
|
205
|
+
prettier.format(ejs.render(templateFile, data), {
|
|
206
|
+
parser: "typescript",
|
|
207
|
+
semi: false,
|
|
208
|
+
singleQuote: true,
|
|
209
|
+
printWidth: 120
|
|
210
|
+
}).then((content) => {
|
|
211
|
+
const path = resolve(output, `${apiModule.name}.${ts ? "ts" : "js"}`);
|
|
212
|
+
const shouldSkip = (!overrides || isArray(overrides) && !overrides.includes(apiModule.name)) && fse.existsSync(path);
|
|
213
|
+
if (shouldSkip) {
|
|
214
|
+
logger.warn(`File already exists, skip: ${path}`);
|
|
215
|
+
promiseResolve(content);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
fse.outputFileSync(path, content);
|
|
219
|
+
logger.success(`Generated ${path}`);
|
|
220
|
+
promiseResolve(content);
|
|
221
|
+
});
|
|
222
|
+
})
|
|
223
|
+
)
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
async function generateTypes(schema, output, typesFilename) {
|
|
227
|
+
const ast = await openapiTS(schema);
|
|
228
|
+
const contents = astToString(ast);
|
|
229
|
+
const typesFilepath = resolve(CWD, output, typesFilename);
|
|
230
|
+
fse.outputFileSync(typesFilepath, contents);
|
|
231
|
+
logger.success(`Generated ${typesFilepath}`);
|
|
232
|
+
}
|
|
233
|
+
async function generate(userOptions = {}) {
|
|
234
|
+
const config = await getConfig();
|
|
235
|
+
const options = merge(config, userOptions);
|
|
236
|
+
const {
|
|
237
|
+
base,
|
|
238
|
+
ts = true,
|
|
239
|
+
typesOnly = false,
|
|
240
|
+
overrides = true,
|
|
241
|
+
preset = "axle",
|
|
242
|
+
input = "./schema.json",
|
|
243
|
+
output = "./src/apis/generated",
|
|
244
|
+
typesFilename = "_types.ts",
|
|
245
|
+
validateStatus = (status) => status >= 200 && status < 300,
|
|
246
|
+
transformer = {}
|
|
247
|
+
} = options;
|
|
248
|
+
const mergedTransformer = { ...createTransformer(), ...transformer };
|
|
249
|
+
const schema = await readSchema(input);
|
|
250
|
+
logger.info("Generating API modules...");
|
|
251
|
+
if (ts) {
|
|
252
|
+
await generateTypes(schema, output, typesFilename);
|
|
253
|
+
}
|
|
254
|
+
const apiModules = partitionApiModules(schema, {
|
|
255
|
+
base,
|
|
256
|
+
transformer: mergedTransformer,
|
|
257
|
+
validateStatus
|
|
258
|
+
});
|
|
259
|
+
await renderApiModules(apiModules, { output, typesFilename, ts, typesOnly, overrides, preset });
|
|
260
|
+
logger.success("Done");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export {
|
|
264
|
+
defineConfig,
|
|
265
|
+
getConfig,
|
|
266
|
+
transformModuleName,
|
|
267
|
+
transformUrl,
|
|
268
|
+
transformVerb,
|
|
269
|
+
transformEntity,
|
|
270
|
+
transformFn,
|
|
271
|
+
transformType,
|
|
272
|
+
transformTypeValue,
|
|
273
|
+
transformTypeQuery,
|
|
274
|
+
transformTypeQueryValue,
|
|
275
|
+
transformTypeRequestBody,
|
|
276
|
+
transformTypeRequestBodyValue,
|
|
277
|
+
transformTypeResponseBody,
|
|
278
|
+
transformTypeResponseBodyValue,
|
|
279
|
+
createTransformer,
|
|
280
|
+
transformPayloads,
|
|
281
|
+
partitionApiModules,
|
|
282
|
+
renderApiModules,
|
|
283
|
+
generateTypes,
|
|
284
|
+
generate
|
|
285
|
+
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
// node_modules/.pnpm/tsup@8.3.5_jiti@2.4.2_postcss@8.5.1_tsx@4.19.2_typescript@5.3.3_yaml@2.7.0/node_modules/tsup/assets/esm_shims.js
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var getFilename = () => fileURLToPath(import.meta.url);
|
|
5
|
+
var getDirname = () => path.dirname(getFilename());
|
|
6
|
+
var __dirname = /* @__PURE__ */ getDirname();
|
|
4
7
|
|
|
5
8
|
// src/constants.ts
|
|
6
9
|
import { resolve } from "path";
|
|
@@ -11,7 +14,10 @@ var SUPPORTED_HTTP_METHODS = ["get", "post", "put", "delete", "patch", "options"
|
|
|
11
14
|
|
|
12
15
|
// src/utils.ts
|
|
13
16
|
import { resolve as resolve2 } from "path";
|
|
17
|
+
import { createAxle } from "@varlet/axle";
|
|
14
18
|
import fse from "fs-extra";
|
|
19
|
+
import { tryParseJSON } from "rattail";
|
|
20
|
+
import { logger } from "rslog";
|
|
15
21
|
import swagger from "swagger2openapi";
|
|
16
22
|
import yaml from "yaml";
|
|
17
23
|
function createStatusCodesByStrategy(strategy) {
|
|
@@ -46,13 +52,29 @@ function createStatusCodesByStrategy(strategy) {
|
|
|
46
52
|
}[strategy];
|
|
47
53
|
}
|
|
48
54
|
async function readSchema(input) {
|
|
49
|
-
const
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
const swaggerOrOpenapiSchema = isYaml ? yaml.parse(content) : JSON.parse(content);
|
|
55
|
+
const content = await readSchemaContent(input);
|
|
56
|
+
const jsonSchema = tryParseJSON(content);
|
|
57
|
+
const swaggerOrOpenapiSchema = jsonSchema ? jsonSchema : yaml.parse(content);
|
|
53
58
|
const schema = swaggerOrOpenapiSchema.swagger ? (await swagger.convert(swaggerOrOpenapiSchema, {})).openapi : swaggerOrOpenapiSchema;
|
|
54
59
|
return schema;
|
|
55
60
|
}
|
|
61
|
+
async function readSchemaContent(input) {
|
|
62
|
+
if (isRemoteSchema(input)) {
|
|
63
|
+
try {
|
|
64
|
+
logger.info("Fetching remote schema...");
|
|
65
|
+
const { data } = await createAxle().get(input);
|
|
66
|
+
return JSON.stringify(data);
|
|
67
|
+
} catch {
|
|
68
|
+
throw new Error("Failed to fetch remote schema");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const path2 = resolve2(CWD, input);
|
|
72
|
+
const content = fse.readFileSync(path2, "utf-8");
|
|
73
|
+
return content;
|
|
74
|
+
}
|
|
75
|
+
function isRemoteSchema(path2) {
|
|
76
|
+
return path2.startsWith("http://") || path2.startsWith("https://");
|
|
77
|
+
}
|
|
56
78
|
function readTemplateFile(preset = "axle") {
|
|
57
79
|
if (fse.existsSync(CUSTOM_TEMPLATE_FILE)) {
|
|
58
80
|
return fse.readFileSync(CUSTOM_TEMPLATE_FILE, "utf-8");
|
|
@@ -68,18 +90,27 @@ function getCliVersion() {
|
|
|
68
90
|
function isRequiredRequestBody(value) {
|
|
69
91
|
return "required" in value && value.required === true;
|
|
70
92
|
}
|
|
71
|
-
function
|
|
93
|
+
function getRequestBodyContentType(value) {
|
|
94
|
+
if (!("content" in value)) {
|
|
95
|
+
return "";
|
|
96
|
+
}
|
|
97
|
+
return value.content["application/json"] ? "application/json" : value.content["application/x-www-form-urlencoded"] ? "application/x-www-form-urlencoded" : void 0;
|
|
98
|
+
}
|
|
99
|
+
function getResponseMetadataItems(operation, validateStatus) {
|
|
72
100
|
const responses = operation.responses ?? {};
|
|
73
101
|
const validStatusResults = Object.keys(responses).sort((a, b) => Number(a) - Number(b)).filter((key) => validateStatus(Number(key))).map(Number);
|
|
74
|
-
const
|
|
75
|
-
const content = operation.responses?.[status]?.content;
|
|
76
|
-
const
|
|
102
|
+
const metadataItems = validStatusResults.map((status) => {
|
|
103
|
+
const content = operation.responses?.[status]?.content ?? {};
|
|
104
|
+
const responseContentType = findResponseContentType(content);
|
|
77
105
|
return {
|
|
78
106
|
status,
|
|
79
|
-
|
|
107
|
+
responseContentType
|
|
80
108
|
};
|
|
81
|
-
}).filter((result) => result.
|
|
82
|
-
|
|
109
|
+
}).filter((result) => result.responseContentType);
|
|
110
|
+
function findResponseContentType(content) {
|
|
111
|
+
return content["application/json"] ? "application/json" : content["*/*"] ? "*/*" : void 0;
|
|
112
|
+
}
|
|
113
|
+
return metadataItems;
|
|
83
114
|
}
|
|
84
115
|
|
|
85
116
|
export {
|
|
@@ -87,9 +118,12 @@ export {
|
|
|
87
118
|
SUPPORTED_HTTP_METHODS,
|
|
88
119
|
createStatusCodesByStrategy,
|
|
89
120
|
readSchema,
|
|
121
|
+
readSchemaContent,
|
|
122
|
+
isRemoteSchema,
|
|
90
123
|
readTemplateFile,
|
|
91
124
|
hasQueryParameter,
|
|
92
125
|
getCliVersion,
|
|
93
126
|
isRequiredRequestBody,
|
|
94
|
-
|
|
127
|
+
getRequestBodyContentType,
|
|
128
|
+
getResponseMetadataItems
|
|
95
129
|
};
|