api-farmer 0.0.18 → 0.0.20

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 CHANGED
@@ -56,6 +56,16 @@ npx af
56
56
 
57
57
  Some simple usage examples can be found [here](fixtures)
58
58
 
59
+ ### Custom code generation templates
60
+
61
+ Create `api-farmer.ejs` in the project root, which will replace the `preset` template.
62
+ The template format can refer to the preset template listed below:
63
+
64
+ - [Axle](templates/axle.ejs)
65
+ - [Axios](templates/axios.ejs)
66
+
67
+ And see the bottom of the document for template variable definitions.
68
+
59
69
  ### Transformer API
60
70
 
61
71
  You can use the Transformer API to further define template variables, which will override the default transformation rules.
@@ -86,16 +96,6 @@ export default defineConfig({
86
96
  })
87
97
  ```
88
98
 
89
- ### Custom EJS Template
90
-
91
- Create `api-farmer.ejs` in the project root, which will replace the `preset` template.
92
- The template format can refer to the preset template listed below:
93
-
94
- - [Axle](templates/axle.ejs)
95
- - [Axios](templates/axios.ejs)
96
-
97
- See the bottom of the document for template variable definitions.
98
-
99
99
  ### Configuration Options
100
100
 
101
101
  ```ts
@@ -0,0 +1,280 @@
1
+ import {
2
+ CWD,
3
+ SUPPORTED_HTTP_METHODS,
4
+ getValidResponseMetadataItems,
5
+ hasQueryParameter,
6
+ isRequiredRequestBody,
7
+ readSchema,
8
+ readTemplateFile
9
+ } from "./chunk-NFK24PSA.js";
10
+
11
+ // src/generate.ts
12
+ import { resolve } from "path";
13
+ import ejs from "ejs";
14
+ import fse from "fs-extra";
15
+ import openapiTS, { astToString } from "openapi-typescript";
16
+ import prettier from "prettier";
17
+ import { groupBy, isArray, merge } from "rattail";
18
+ import { logger } from "rslog";
19
+
20
+ // src/config.ts
21
+ import { loadConfig } from "unconfig";
22
+ function defineConfig(config) {
23
+ return config;
24
+ }
25
+ async function getConfig() {
26
+ const { config } = await loadConfig({
27
+ sources: [
28
+ {
29
+ files: "api-farmer.config"
30
+ }
31
+ ]
32
+ });
33
+ return config ?? {};
34
+ }
35
+
36
+ // src/transformer.ts
37
+ import pluralize from "pluralize";
38
+ import { camelize, pascalCase } from "rattail";
39
+ function transformModuleName({ name }) {
40
+ return camelize(name);
41
+ }
42
+ function transformUrl({ path, base }) {
43
+ return (base ? path.replace(base, "") : path).replace(/{/g, ":").replace(/}/g, "");
44
+ }
45
+ function transformVerb({ method }) {
46
+ switch (method) {
47
+ case "post":
48
+ return "Create";
49
+ case "put":
50
+ return "Update";
51
+ default:
52
+ return pascalCase(method);
53
+ }
54
+ }
55
+ function transformEntity({ path, method, base }) {
56
+ path = base ? path.replace(base, "") : path;
57
+ const words = path.split("/").filter(Boolean);
58
+ return words.reduce((entity, word, index) => {
59
+ if (word.includes("{")) {
60
+ return entity;
61
+ }
62
+ word = word.replace(/\.([a-z])/g, (_, p) => p.toUpperCase());
63
+ word = pluralize.singular(pascalCase(word));
64
+ if (method === "get" && index === words.length - 1) {
65
+ word = pluralize.plural(word);
66
+ }
67
+ return `${entity}${word}`;
68
+ }, "");
69
+ }
70
+ function transformFn({ verb, entity }) {
71
+ return `api${verb}${entity}`;
72
+ }
73
+ function transformType({ verb, entity }) {
74
+ return `Api${verb}${entity}`;
75
+ }
76
+ function transformTypeValue({ path, method }) {
77
+ return `paths['${path}']['${method}']`;
78
+ }
79
+ function transformTypeQuery({ type }) {
80
+ return `${type}Query`;
81
+ }
82
+ function transformTypeQueryValue({
83
+ type
84
+ }) {
85
+ return `${type}['parameters']['query']`;
86
+ }
87
+ function transformTypeRequestBody({
88
+ type
89
+ }) {
90
+ return `${type}RequestBody`;
91
+ }
92
+ function transformTypeRequestBodyValue({
93
+ type,
94
+ required
95
+ }) {
96
+ return required ? `${type}['requestBody']['content']['application/json']` : `NonNullable<${type}['requestBody']>['content']['application/json'] | undefined`;
97
+ }
98
+ function transformTypeResponseBody({
99
+ type
100
+ }) {
101
+ return `${type}ResponseBody`;
102
+ }
103
+ function transformTypeResponseBodyValue({
104
+ type,
105
+ responseMetadataItems
106
+ }) {
107
+ return responseMetadataItems.map(({ status, mime }) => `${type}['responses']['${status}']['content']['${mime}']`).join(" | ");
108
+ }
109
+ function createTransformer() {
110
+ return {
111
+ moduleName: transformModuleName,
112
+ verb: transformVerb,
113
+ url: transformUrl,
114
+ entity: transformEntity,
115
+ fn: transformFn,
116
+ type: transformType,
117
+ typeValue: transformTypeValue,
118
+ typeQuery: transformTypeQuery,
119
+ typeQueryValue: transformTypeQueryValue,
120
+ typeRequestBody: transformTypeRequestBody,
121
+ typeRequestBodyValue: transformTypeRequestBodyValue,
122
+ typeResponseBody: transformTypeResponseBody,
123
+ typeResponseBodyValue: transformTypeResponseBodyValue
124
+ };
125
+ }
126
+
127
+ // src/generate.ts
128
+ function transformPayloads(pathItems, options) {
129
+ const { transformer, path, base, validateStatus } = options;
130
+ return Object.entries(pathItems).filter(([key]) => SUPPORTED_HTTP_METHODS.includes(key)).reduce((payloads, [method, operation]) => {
131
+ const url = transformer.url({ path, base });
132
+ const args = { path, base, url, method, operation };
133
+ const entity = transformer.entity(args);
134
+ const verb = transformer.verb(args);
135
+ const fn = transformer.fn({ ...args, verb, entity });
136
+ const type = transformer.type({ ...args, verb, entity });
137
+ const typeValue = transformer.typeValue({ ...args, verb, entity });
138
+ const typeQuery = transformer.typeQuery({ ...args, type, verb, entity });
139
+ const typeQueryValue = hasQueryParameter(operation) ? transformer.typeQueryValue({ ...args, type, verb, entity }) : "undefined";
140
+ const typeRequestBody = transformer.typeRequestBody({ ...args, type, verb, entity });
141
+ const typeRequestBodyValue = operation.requestBody ? transformer.typeRequestBodyValue({
142
+ ...args,
143
+ type,
144
+ verb,
145
+ entity,
146
+ required: isRequiredRequestBody(operation.requestBody)
147
+ }) : "undefined";
148
+ const typeResponseBody = transformer.typeResponseBody({ ...args, type, verb, entity });
149
+ const responseMetadataItems = getValidResponseMetadataItems(operation, validateStatus);
150
+ const typeResponseBodyValue = responseMetadataItems.length > 0 ? transformer.typeResponseBodyValue({ ...args, type, verb, entity, responseMetadataItems }) : "undefined";
151
+ payloads.push({
152
+ fn,
153
+ url,
154
+ method,
155
+ verb,
156
+ entity,
157
+ type,
158
+ typeValue,
159
+ typeQuery,
160
+ typeQueryValue,
161
+ typeRequestBody,
162
+ typeRequestBodyValue,
163
+ typeResponseBody,
164
+ typeResponseBodyValue
165
+ });
166
+ return payloads;
167
+ }, []);
168
+ }
169
+ function partitionApiModules(schema, options) {
170
+ const { base, transformer, validateStatus } = options;
171
+ const schemaPaths = schema.paths ?? {};
172
+ const schemaPathKeys = base ? Object.keys(schemaPaths).map((key) => key.replace(base, "")) : Object.keys(schemaPaths);
173
+ const keyToPaths = groupBy(schemaPathKeys, (key) => key.split("/")[1]);
174
+ const apiModules = Object.entries(keyToPaths).reduce((apiModules2, [name, paths]) => {
175
+ const payloads = paths.reduce((payloads2, path) => {
176
+ const pathItems = schemaPaths[path];
177
+ payloads2.push(
178
+ ...transformPayloads(pathItems, { ...options, path: base ? base + path : path, transformer, validateStatus })
179
+ );
180
+ return payloads2;
181
+ }, []);
182
+ apiModules2.push({ name: transformer.moduleName({ name }), payloads });
183
+ return apiModules2;
184
+ }, []);
185
+ return apiModules;
186
+ }
187
+ function renderApiModules(apiModules, options) {
188
+ const { output, ts, typesOnly, overrides, preset } = options;
189
+ const templateFile = readTemplateFile(preset);
190
+ const typesFilename = options.typesFilename.replace(".ts", "");
191
+ return Promise.all(
192
+ apiModules.map(
193
+ (apiModule) => new Promise((promiseResolve) => {
194
+ const data = {
195
+ apiModule,
196
+ typesFilename,
197
+ ts,
198
+ typesOnly
199
+ };
200
+ prettier.format(ejs.render(templateFile, data), {
201
+ parser: "typescript",
202
+ semi: false,
203
+ singleQuote: true,
204
+ printWidth: 120
205
+ }).then((content) => {
206
+ const path = resolve(output, `${apiModule.name}.${ts ? "ts" : "js"}`);
207
+ const shouldSkip = (!overrides || isArray(overrides) && !overrides.includes(apiModule.name)) && fse.existsSync(path);
208
+ if (shouldSkip) {
209
+ logger.warn(`File already exists, skip: ${path}`);
210
+ promiseResolve(content);
211
+ return;
212
+ }
213
+ fse.outputFileSync(path, content);
214
+ logger.success(`Generated ${path}`);
215
+ promiseResolve(content);
216
+ });
217
+ })
218
+ )
219
+ );
220
+ }
221
+ async function generateTypes(schema, output, typesFilename) {
222
+ const ast = await openapiTS(schema);
223
+ const contents = astToString(ast);
224
+ const typesFilepath = resolve(CWD, output, typesFilename);
225
+ fse.outputFileSync(typesFilepath, contents);
226
+ logger.success(`Generated ${typesFilepath}`);
227
+ }
228
+ async function generate(userOptions = {}) {
229
+ const config = await getConfig();
230
+ const options = merge(config, userOptions);
231
+ const {
232
+ base,
233
+ ts = true,
234
+ typesOnly = false,
235
+ overrides = true,
236
+ preset = "axle",
237
+ input = "./schema.json",
238
+ output = "./src/apis/generated",
239
+ typesFilename = "_types.ts",
240
+ validateStatus = (status) => status >= 200 && status < 300,
241
+ transformer = {}
242
+ } = options;
243
+ const mergedTransformer = { ...createTransformer(), ...transformer };
244
+ const schema = await readSchema(input);
245
+ logger.info("Generating API modules...");
246
+ if (ts) {
247
+ await generateTypes(schema, output, typesFilename);
248
+ }
249
+ const apiModules = partitionApiModules(schema, {
250
+ base,
251
+ transformer: mergedTransformer,
252
+ validateStatus
253
+ });
254
+ await renderApiModules(apiModules, { output, typesFilename, ts, typesOnly, overrides, preset });
255
+ logger.success("Done");
256
+ }
257
+
258
+ export {
259
+ defineConfig,
260
+ getConfig,
261
+ transformModuleName,
262
+ transformUrl,
263
+ transformVerb,
264
+ transformEntity,
265
+ transformFn,
266
+ transformType,
267
+ transformTypeValue,
268
+ transformTypeQuery,
269
+ transformTypeQueryValue,
270
+ transformTypeRequestBody,
271
+ transformTypeRequestBodyValue,
272
+ transformTypeResponseBody,
273
+ transformTypeResponseBodyValue,
274
+ createTransformer,
275
+ transformPayloads,
276
+ partitionApiModules,
277
+ renderApiModules,
278
+ generateTypes,
279
+ generate
280
+ };
@@ -1,6 +1,9 @@
1
- import {
2
- __dirname
3
- } from "./chunk-6OIOYGN7.js";
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";
@@ -47,8 +50,8 @@ function createStatusCodesByStrategy(strategy) {
47
50
  }
48
51
  async function readSchema(input) {
49
52
  const isYaml = input.endsWith(".yaml");
50
- const path = resolve2(CWD, input);
51
- const content = fse.readFileSync(path, "utf-8");
53
+ const path2 = resolve2(CWD, input);
54
+ const content = fse.readFileSync(path2, "utf-8");
52
55
  const swaggerOrOpenapiSchema = isYaml ? yaml.parse(content) : JSON.parse(content);
53
56
  const schema = swaggerOrOpenapiSchema.swagger ? (await swagger.convert(swaggerOrOpenapiSchema, {})).openapi : swaggerOrOpenapiSchema;
54
57
  return schema;