api-farmer 0.1.3 → 0.1.4

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
@@ -155,7 +155,7 @@ export interface Config {
155
155
  uncountableNouns?: string[]
156
156
  /**
157
157
  * Whether to clean the output directory before generating.
158
- * @default false
158
+ * @default true
159
159
  */
160
160
  clean?: boolean
161
161
  /**
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-C4lplT2F.mjs").then((n) => n.r);
9
+ return generate();
10
+ });
11
+ program.parse();
12
+ //#endregion
13
+ export {};
@@ -0,0 +1,320 @@
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 } 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
+ generate: () => generate,
116
+ generateTypes: () => generateTypes,
117
+ partitionApiModules: () => partitionApiModules,
118
+ renderApiModules: () => renderApiModules,
119
+ transformPayloads: () => transformPayloads
120
+ });
121
+ function transformPayloads(pathItems, options) {
122
+ const { transformer, path, fullPath, base, uncountableNouns, validateStatus, excludeDeprecated } = options;
123
+ return Object.entries(pathItems).filter(([key]) => SUPPORTED_HTTP_METHODS.includes(key)).filter(([, operation]) => !(excludeDeprecated && operation.deprecated)).reduce((payloads, [method, operation]) => {
124
+ const url = transformer.url({
125
+ path,
126
+ base,
127
+ fullPath
128
+ });
129
+ const args = {
130
+ path,
131
+ base,
132
+ fullPath,
133
+ url,
134
+ method,
135
+ uncountableNouns,
136
+ operation
137
+ };
138
+ const entity = transformer.entity(args);
139
+ const verb = transformer.verb(args);
140
+ const comment = transformer.comment({
141
+ ...args,
142
+ ...operation
143
+ });
144
+ const requestContentType = operation.requestBody ? getRequestBodyContentType(operation.requestBody) : void 0;
145
+ const responseMetadataItems = getResponseMetadataItems(operation, validateStatus);
146
+ const fn = transformer.fn({
147
+ ...args,
148
+ verb,
149
+ entity
150
+ });
151
+ const type = transformer.type({
152
+ ...args,
153
+ verb,
154
+ entity
155
+ });
156
+ const typeValue = transformer.typeValue({
157
+ ...args,
158
+ verb,
159
+ entity
160
+ });
161
+ const typeQuery = transformer.typeQuery({
162
+ ...args,
163
+ type,
164
+ verb,
165
+ entity
166
+ });
167
+ const typeQueryValue = transformer.typeQueryValue({
168
+ ...args,
169
+ type,
170
+ verb,
171
+ entity
172
+ });
173
+ const typeRequestBody = transformer.typeRequestBody({
174
+ ...args,
175
+ type,
176
+ verb,
177
+ entity
178
+ });
179
+ const typeRequestBodyValue = operation.requestBody && requestContentType ? transformer.typeRequestBodyValue({
180
+ ...args,
181
+ type,
182
+ verb,
183
+ entity,
184
+ required: isRequiredRequestBody(operation.requestBody),
185
+ requestContentType
186
+ }) : "undefined";
187
+ const typeResponseBody = transformer.typeResponseBody({
188
+ ...args,
189
+ type,
190
+ verb,
191
+ entity
192
+ });
193
+ const typeResponseBodyValue = responseMetadataItems.length > 0 ? transformer.typeResponseBodyValue({
194
+ ...args,
195
+ type,
196
+ verb,
197
+ entity,
198
+ responseMetadataItems
199
+ }) : "undefined";
200
+ payloads.push({
201
+ comment,
202
+ fn,
203
+ url,
204
+ method,
205
+ verb,
206
+ entity,
207
+ requestContentType,
208
+ type,
209
+ typeValue,
210
+ typeQuery,
211
+ typeQueryValue,
212
+ typeRequestBody,
213
+ typeRequestBodyValue,
214
+ typeResponseBody,
215
+ typeResponseBodyValue
216
+ });
217
+ return payloads;
218
+ }, []);
219
+ }
220
+ function partitionApiModules(schema, options) {
221
+ const { base, transformer, uncountableNouns, validateStatus, excludeDeprecated } = options;
222
+ const schemaPaths = schema.paths ?? {};
223
+ const keyToPaths = groupBy(base ? Object.keys(schemaPaths).map((key) => key.replace(base, "")) : Object.keys(schemaPaths), (key) => key.split("/")[1]);
224
+ return Object.entries(keyToPaths).reduce((apiModules, [name, paths]) => {
225
+ const payloads = paths.reduce((payloads, path) => {
226
+ const fullPath = base ? base + path : path;
227
+ const pathItems = schemaPaths[fullPath];
228
+ payloads.push(...transformPayloads(pathItems, {
229
+ ...options,
230
+ path,
231
+ fullPath,
232
+ transformer,
233
+ uncountableNouns,
234
+ validateStatus,
235
+ excludeDeprecated
236
+ }));
237
+ return payloads;
238
+ }, []);
239
+ apiModules.push({
240
+ name: transformer.moduleName({ name }),
241
+ payloads
242
+ });
243
+ return apiModules;
244
+ }, []);
245
+ }
246
+ function renderApiModules(apiModules, options) {
247
+ const { output, ts, typesOnly, overrides, preset } = options;
248
+ const templateFile = readTemplateFile(preset);
249
+ const typesFilename = options.typesFilename.replace(".ts", "");
250
+ return Promise.all(apiModules.map((apiModule) => new Promise((promiseResolve) => {
251
+ const data = {
252
+ apiModule,
253
+ typesFilename,
254
+ ts,
255
+ typesOnly
256
+ };
257
+ prettier.format(ejs.render(templateFile, data), {
258
+ parser: "typescript",
259
+ semi: false,
260
+ singleQuote: true,
261
+ printWidth: 120
262
+ }).then((content) => {
263
+ const path = resolve(output, `${apiModule.name}.${ts ? "ts" : "js"}`);
264
+ if ((!overrides || isArray(overrides) && !overrides.includes(apiModule.name)) && fse.existsSync(path)) {
265
+ logger.warn(`File already exists, skip: ${path}`);
266
+ promiseResolve(content);
267
+ return;
268
+ }
269
+ fse.outputFileSync(path, content);
270
+ logger.success(`Generated ${path}`);
271
+ promiseResolve(content);
272
+ });
273
+ })));
274
+ }
275
+ async function generateTypes(schema, output, typesFilename, openapiTsOptions) {
276
+ const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Blob"));
277
+ const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
278
+ const contents = astToString(await openapiTS(schema, {
279
+ defaultNonNullable: false,
280
+ transform(schemaObject) {
281
+ if (schemaObject.format === "binary") return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
282
+ },
283
+ ...openapiTsOptions
284
+ }));
285
+ const typesFilepath = resolve(CWD, output, typesFilename);
286
+ fse.outputFileSync(typesFilepath, contents);
287
+ logger.success(`Generated ${typesFilepath}`);
288
+ }
289
+ async function generate(userOptions = {}) {
290
+ 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);
291
+ const mergedTransformer = {
292
+ ...createTransformer(),
293
+ ...transformer
294
+ };
295
+ const schema = await readSchema(input);
296
+ if (clean) {
297
+ fse.removeSync(resolve(CWD, output));
298
+ logger.info(`Cleaned output directory: ${resolve(CWD, output)}`);
299
+ }
300
+ logger.info("Generating API modules...");
301
+ if (openapiTsOptions.excludeDeprecated === void 0) openapiTsOptions.excludeDeprecated = excludeDeprecated;
302
+ if (ts) await generateTypes(schema, output, typesFilename, openapiTsOptions);
303
+ await renderApiModules(partitionApiModules(schema, {
304
+ base,
305
+ uncountableNouns,
306
+ transformer: mergedTransformer,
307
+ validateStatus,
308
+ excludeDeprecated
309
+ }), {
310
+ output,
311
+ typesFilename,
312
+ ts,
313
+ typesOnly,
314
+ overrides,
315
+ preset
316
+ });
317
+ logger.success("Done");
318
+ }
319
+ //#endregion
320
+ export { transformVerb as C, transformUrl as S, transformTypeRequestBody as _, renderApiModules as a, transformTypeResponseBodyValue as b, getConfig as c, transformEntity as d, transformFn as f, transformTypeQueryValue as g, transformTypeQuery as h, partitionApiModules as i, createTransformer as l, transformType as m, generateTypes as n, transformPayloads as o, transformModuleName as p, generate_exports as r, defineConfig as s, generate as t, transformComment as u, transformTypeRequestBodyValue as v, transformTypeValue as x, transformTypeResponseBody as y };