prisma-swagger-autogen 1.0.8 → 1.0.11
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/dist/bin.cjs +2 -0
- package/dist/cli.cjs +332 -0
- package/package.json +3 -2
- package/scripts/make-bin.cjs +14 -0
- package/dist/cli.js +0 -198
package/dist/bin.cjs
ADDED
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_node_fs = __toESM(require("fs"), 1);
|
|
28
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
29
|
+
var import_node_module = require("module");
|
|
30
|
+
var import_glob = require("glob");
|
|
31
|
+
var DEFAULT_CONFIG = {
|
|
32
|
+
projectRoot: process.cwd(),
|
|
33
|
+
controllersGlob: "./**/controllers/**/*.ts",
|
|
34
|
+
outFile: "./swagger.config.js",
|
|
35
|
+
openapiOut: "./**/openapi.json",
|
|
36
|
+
serviceTitle: "Microservice Swagger Docs",
|
|
37
|
+
serverUrl: "http://localhost:3000",
|
|
38
|
+
securitySchemeName: "keycloakOAuth",
|
|
39
|
+
oauth: {
|
|
40
|
+
tokenUrl: "http://localhost:8080/realms/master/protocol/openid-connect/token",
|
|
41
|
+
refreshUrl: "http://localhost:8080/realms/master/protocol/openid-connect/refresh",
|
|
42
|
+
scopes: { openid: "openid" }
|
|
43
|
+
},
|
|
44
|
+
omitFieldsInWriteDtos: /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "v"])
|
|
45
|
+
};
|
|
46
|
+
function ensurePosix(p) {
|
|
47
|
+
return p.split(import_node_path.default.sep).join(import_node_path.default.posix.sep);
|
|
48
|
+
}
|
|
49
|
+
function pluralize(name) {
|
|
50
|
+
if (name.endsWith("s")) return `${name}es`;
|
|
51
|
+
return `${name}s`;
|
|
52
|
+
}
|
|
53
|
+
function getRequire() {
|
|
54
|
+
return (0, import_node_module.createRequire)(typeof __filename !== "undefined" ? __filename : process.cwd() + "/");
|
|
55
|
+
}
|
|
56
|
+
function parseJsonObject(value, flagName) {
|
|
57
|
+
try {
|
|
58
|
+
const parsed = JSON.parse(value);
|
|
59
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
60
|
+
throw new Error("must be a JSON object");
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
throw new Error(`Invalid JSON for ${flagName}: ${e?.message ?? String(e)}`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function parseCsv(value) {
|
|
68
|
+
return value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
69
|
+
}
|
|
70
|
+
function readFlagValue(args, flag) {
|
|
71
|
+
const i = args.findIndex((a) => a === flag);
|
|
72
|
+
if (i < 0) return void 0;
|
|
73
|
+
const v = args[i + 1];
|
|
74
|
+
if (!v || v.startsWith("--")) return void 0;
|
|
75
|
+
return v;
|
|
76
|
+
}
|
|
77
|
+
function hasFlag(args, flag) {
|
|
78
|
+
return args.includes(flag);
|
|
79
|
+
}
|
|
80
|
+
function parseArgs(args) {
|
|
81
|
+
if (hasFlag(args, "--help") || hasFlag(args, "-h")) {
|
|
82
|
+
const help = `
|
|
83
|
+
prisma-swagger-autogen
|
|
84
|
+
|
|
85
|
+
Usage:
|
|
86
|
+
prisma-swagger-autogen [options]
|
|
87
|
+
|
|
88
|
+
Options:
|
|
89
|
+
--schema <path> Prisma schema path (default: ./prisma/schema.prisma)
|
|
90
|
+
|
|
91
|
+
--projectRoot <path> Project root for resolving outFile/openapiOut (default: cwd)
|
|
92
|
+
--controllersGlob <glob> Glob for controller files (default: ./**/controllers/**/*.ts)
|
|
93
|
+
--outFile <path> Path to write swagger.config.js (default: ./swagger.config.js)
|
|
94
|
+
--openapiOut <path> Path swagger-autogen writes OpenAPI JSON (default: ./openapi.json)
|
|
95
|
+
|
|
96
|
+
--serviceTitle <string> OpenAPI title
|
|
97
|
+
--serverUrl <url> OpenAPI server url
|
|
98
|
+
--securitySchemeName <string> Security scheme name
|
|
99
|
+
|
|
100
|
+
--oauthTokenUrl <url> OAuth2 tokenUrl
|
|
101
|
+
--oauthRefreshUrl <url> OAuth2 refreshUrl
|
|
102
|
+
--oauthScopes <json> OAuth2 scopes as JSON object, e.g. {"openid":"openid scope"}
|
|
103
|
+
|
|
104
|
+
--omitFields <csv> Comma-separated fields to omit in write DTOs (default: id,createdAt,updatedAt,v)
|
|
105
|
+
|
|
106
|
+
Examples:
|
|
107
|
+
prisma-swagger-autogen
|
|
108
|
+
prisma-swagger-autogen --schema ./prisma/schema.prisma
|
|
109
|
+
prisma-swagger-autogen --controllersGlob "./src/api/**/*.ts" --outFile ./swagger.config.js
|
|
110
|
+
prisma-swagger-autogen --oauthScopes '{"openid":"openid scope","profile":"profile"}'
|
|
111
|
+
`.trim();
|
|
112
|
+
process.stdout.write(help + "\n");
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
const schemaPath = readFlagValue(args, "--schema");
|
|
116
|
+
const projectRoot = readFlagValue(args, "--projectRoot");
|
|
117
|
+
const controllersGlob = readFlagValue(args, "--controllersGlob");
|
|
118
|
+
const outFile = readFlagValue(args, "--outFile");
|
|
119
|
+
const openapiOut = readFlagValue(args, "--openapiOut");
|
|
120
|
+
const serviceTitle = readFlagValue(args, "--serviceTitle");
|
|
121
|
+
const serverUrl = readFlagValue(args, "--serverUrl");
|
|
122
|
+
const securitySchemeName = readFlagValue(args, "--securitySchemeName");
|
|
123
|
+
const oauthTokenUrl = readFlagValue(args, "--oauthTokenUrl");
|
|
124
|
+
const oauthRefreshUrl = readFlagValue(args, "--oauthRefreshUrl");
|
|
125
|
+
const oauthScopesRaw = readFlagValue(args, "--oauthScopes");
|
|
126
|
+
const omitFieldsRaw = readFlagValue(args, "--omitFields");
|
|
127
|
+
const oauthScopes = oauthScopesRaw ? parseJsonObject(oauthScopesRaw, "--oauthScopes") : void 0;
|
|
128
|
+
const omitFieldsInWriteDtos = omitFieldsRaw ? parseCsv(omitFieldsRaw) : void 0;
|
|
129
|
+
return {
|
|
130
|
+
schemaPath,
|
|
131
|
+
projectRoot,
|
|
132
|
+
controllersGlob,
|
|
133
|
+
outFile,
|
|
134
|
+
openapiOut,
|
|
135
|
+
serviceTitle,
|
|
136
|
+
serverUrl,
|
|
137
|
+
securitySchemeName,
|
|
138
|
+
oauthTokenUrl,
|
|
139
|
+
oauthRefreshUrl,
|
|
140
|
+
oauthScopes,
|
|
141
|
+
omitFieldsInWriteDtos
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function mergeConfig(base, parsed) {
|
|
145
|
+
const cfg = {
|
|
146
|
+
...base,
|
|
147
|
+
projectRoot: parsed.projectRoot ? import_node_path.default.resolve(process.cwd(), parsed.projectRoot) : base.projectRoot,
|
|
148
|
+
controllersGlob: parsed.controllersGlob ?? base.controllersGlob,
|
|
149
|
+
outFile: parsed.outFile ?? base.outFile,
|
|
150
|
+
openapiOut: parsed.openapiOut ?? base.openapiOut,
|
|
151
|
+
serviceTitle: parsed.serviceTitle ?? base.serviceTitle,
|
|
152
|
+
serverUrl: parsed.serverUrl ?? base.serverUrl,
|
|
153
|
+
securitySchemeName: parsed.securitySchemeName ?? base.securitySchemeName,
|
|
154
|
+
oauth: {
|
|
155
|
+
tokenUrl: parsed.oauthTokenUrl ?? base.oauth.tokenUrl,
|
|
156
|
+
refreshUrl: parsed.oauthRefreshUrl ?? base.oauth.refreshUrl,
|
|
157
|
+
scopes: parsed.oauthScopes ?? base.oauth.scopes
|
|
158
|
+
},
|
|
159
|
+
omitFieldsInWriteDtos: parsed.omitFieldsInWriteDtos ? new Set(parsed.omitFieldsInWriteDtos) : base.omitFieldsInWriteDtos
|
|
160
|
+
};
|
|
161
|
+
cfg.controllersGlob = ensurePosix(cfg.controllersGlob);
|
|
162
|
+
cfg.outFile = ensurePosix(cfg.outFile);
|
|
163
|
+
cfg.openapiOut = ensurePosix(cfg.openapiOut);
|
|
164
|
+
return cfg;
|
|
165
|
+
}
|
|
166
|
+
async function loadDmmfFromProject(schemaPath) {
|
|
167
|
+
const resolvedSchemaPath = schemaPath ? import_node_path.default.resolve(process.cwd(), schemaPath) : import_node_path.default.resolve(process.cwd(), "prisma/schema.prisma");
|
|
168
|
+
if (!import_node_fs.default.existsSync(resolvedSchemaPath)) {
|
|
169
|
+
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
170
|
+
}
|
|
171
|
+
const datamodel = import_node_fs.default.readFileSync(resolvedSchemaPath, "utf8");
|
|
172
|
+
const require2 = getRequire();
|
|
173
|
+
const internals = require2("@prisma/internals");
|
|
174
|
+
if (typeof internals.getDMMF !== "function") {
|
|
175
|
+
throw new Error(`@prisma/internals.getDMMF not available`);
|
|
176
|
+
}
|
|
177
|
+
const dmmf = await internals.getDMMF({ datamodel });
|
|
178
|
+
if (!dmmf || !dmmf.datamodel || !Array.isArray(dmmf.datamodel.models) || !Array.isArray(dmmf.datamodel.enums)) {
|
|
179
|
+
throw new Error(`Unexpected DMMF shape returned by @prisma/internals.getDMMF`);
|
|
180
|
+
}
|
|
181
|
+
return dmmf;
|
|
182
|
+
}
|
|
183
|
+
function scalarToSchema(scalar) {
|
|
184
|
+
switch (scalar) {
|
|
185
|
+
case "String":
|
|
186
|
+
return { type: "string" };
|
|
187
|
+
case "Boolean":
|
|
188
|
+
return { type: "boolean" };
|
|
189
|
+
case "Int":
|
|
190
|
+
return { type: "integer" };
|
|
191
|
+
case "BigInt":
|
|
192
|
+
return { type: "integer", format: "int64" };
|
|
193
|
+
case "Float":
|
|
194
|
+
return { type: "number" };
|
|
195
|
+
case "Decimal":
|
|
196
|
+
return { type: "number" };
|
|
197
|
+
case "DateTime":
|
|
198
|
+
return { type: "string", format: "date-time" };
|
|
199
|
+
case "Json":
|
|
200
|
+
return { type: "object" };
|
|
201
|
+
case "Bytes":
|
|
202
|
+
return { type: "string", format: "byte" };
|
|
203
|
+
default:
|
|
204
|
+
return { type: "string" };
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function fieldSchema(field, getRefName) {
|
|
208
|
+
if (field.kind === "scalar") {
|
|
209
|
+
const base = scalarToSchema(field.type);
|
|
210
|
+
if (field.isList) return { type: "array", items: base };
|
|
211
|
+
return base;
|
|
212
|
+
}
|
|
213
|
+
if (field.kind === "enum") {
|
|
214
|
+
const base = { $ref: `#/components/schemas/${field.type}` };
|
|
215
|
+
if (field.isList) return { type: "array", items: base };
|
|
216
|
+
return base;
|
|
217
|
+
}
|
|
218
|
+
if (field.kind === "object") {
|
|
219
|
+
const ref = { $ref: `#/components/schemas/${getRefName(String(field.type))}` };
|
|
220
|
+
if (field.isList) return { type: "array", items: ref };
|
|
221
|
+
return ref;
|
|
222
|
+
}
|
|
223
|
+
return { type: "object" };
|
|
224
|
+
}
|
|
225
|
+
function modelToGetSchema(model, getRefName) {
|
|
226
|
+
const properties = {};
|
|
227
|
+
const required = [];
|
|
228
|
+
for (const f of model.fields) {
|
|
229
|
+
properties[f.name] = fieldSchema(f, getRefName);
|
|
230
|
+
if (f.isRequired) required.push(f.name);
|
|
231
|
+
}
|
|
232
|
+
const schema = { type: "object", properties };
|
|
233
|
+
if (required.length) schema.required = required;
|
|
234
|
+
return schema;
|
|
235
|
+
}
|
|
236
|
+
function stripWriteFields(model, getSchema, omit) {
|
|
237
|
+
const schema = JSON.parse(JSON.stringify(getSchema));
|
|
238
|
+
if (!schema.properties) return schema;
|
|
239
|
+
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
240
|
+
for (const key of Object.keys(schema.properties)) {
|
|
241
|
+
if (omit.has(key) || relationFieldNames.has(key)) delete schema.properties[key];
|
|
242
|
+
}
|
|
243
|
+
if (Array.isArray(schema.required)) {
|
|
244
|
+
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
245
|
+
if (schema.required.length === 0) delete schema.required;
|
|
246
|
+
}
|
|
247
|
+
return schema;
|
|
248
|
+
}
|
|
249
|
+
function makeAllOptional(schema) {
|
|
250
|
+
const s = JSON.parse(JSON.stringify(schema));
|
|
251
|
+
delete s.required;
|
|
252
|
+
return s;
|
|
253
|
+
}
|
|
254
|
+
function listResponseSchema(itemRef) {
|
|
255
|
+
return {
|
|
256
|
+
type: "object",
|
|
257
|
+
properties: {
|
|
258
|
+
count: { type: "number" },
|
|
259
|
+
hasPreviousPage: { type: "boolean" },
|
|
260
|
+
hasNextPage: { type: "boolean" },
|
|
261
|
+
pageNumber: { type: "number" },
|
|
262
|
+
pageSize: { type: "number" },
|
|
263
|
+
totalPages: { type: "number" },
|
|
264
|
+
items: { type: "array", items: { $ref: itemRef } }
|
|
265
|
+
},
|
|
266
|
+
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async function buildSchemasFromPrismaDmmf(cfg, schemaPath) {
|
|
270
|
+
const dmmf = await loadDmmfFromProject(schemaPath);
|
|
271
|
+
const schemas = {};
|
|
272
|
+
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
273
|
+
for (const e of dmmf.datamodel.enums) {
|
|
274
|
+
schemas[e.name] = { type: "string", enum: e.values.map((v) => v.name) };
|
|
275
|
+
}
|
|
276
|
+
for (const model of dmmf.datamodel.models) {
|
|
277
|
+
const getName = `Get${model.name}Response`;
|
|
278
|
+
const postName = `Post${model.name}Request`;
|
|
279
|
+
const putName = `Put${model.name}Request`;
|
|
280
|
+
const listName = `List${pluralize(model.name)}Response`;
|
|
281
|
+
const getSchema = modelToGetSchema(model, getRefName);
|
|
282
|
+
const postSchema = stripWriteFields(model, getSchema, cfg.omitFieldsInWriteDtos);
|
|
283
|
+
const putSchema = makeAllOptional(postSchema);
|
|
284
|
+
schemas[getName] = getSchema;
|
|
285
|
+
schemas[postName] = postSchema;
|
|
286
|
+
schemas[putName] = putSchema;
|
|
287
|
+
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
288
|
+
}
|
|
289
|
+
return schemas;
|
|
290
|
+
}
|
|
291
|
+
function generateSwaggerConfigJs(cfg, schemas) {
|
|
292
|
+
const routes = (0, import_glob.globSync)(cfg.controllersGlob, { nodir: true }).map((p) => ensurePosix(p));
|
|
293
|
+
const docs = {
|
|
294
|
+
info: { title: cfg.serviceTitle },
|
|
295
|
+
servers: [{ url: cfg.serverUrl }],
|
|
296
|
+
components: {
|
|
297
|
+
schemas,
|
|
298
|
+
securitySchemes: {
|
|
299
|
+
[cfg.securitySchemeName]: {
|
|
300
|
+
type: "oauth2",
|
|
301
|
+
description: "This API uses OAuth2 with the password flow.",
|
|
302
|
+
flows: {
|
|
303
|
+
password: {
|
|
304
|
+
tokenUrl: cfg.oauth.tokenUrl,
|
|
305
|
+
refreshUrl: cfg.oauth.refreshUrl,
|
|
306
|
+
scopes: cfg.oauth.scopes
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
security: [{ [cfg.securitySchemeName]: ["openid"] }]
|
|
313
|
+
};
|
|
314
|
+
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
315
|
+
const docs = ${JSON.stringify(docs, null, 2)};
|
|
316
|
+
const routes = ${JSON.stringify(routes, null, 2)};
|
|
317
|
+
swaggerAutogen('${ensurePosix(cfg.openapiOut)}', routes, docs);`;
|
|
318
|
+
const outPath = import_node_path.default.resolve(cfg.projectRoot, cfg.outFile);
|
|
319
|
+
import_node_fs.default.writeFileSync(outPath, fileContent, "utf8");
|
|
320
|
+
}
|
|
321
|
+
async function run(args = []) {
|
|
322
|
+
const parsed = parseArgs(args);
|
|
323
|
+
const cfg = mergeConfig(DEFAULT_CONFIG, parsed);
|
|
324
|
+
const schemas = await buildSchemasFromPrismaDmmf(cfg, parsed.schemaPath);
|
|
325
|
+
generateSwaggerConfigJs(cfg, schemas);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// src/cli.ts
|
|
329
|
+
void run(process.argv.slice(2)).catch((e) => {
|
|
330
|
+
console.error(e);
|
|
331
|
+
process.exitCode = 1;
|
|
332
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-swagger-autogen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.11",
|
|
4
4
|
"description": "Generate swagger-autogen config + Prisma DMMF schemas and fix swagger-autogen output.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Joyce Marvin Rafflenbeul",
|
|
@@ -35,11 +35,12 @@
|
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist",
|
|
38
|
+
"scripts/make-bin.cjs",
|
|
38
39
|
"README.md",
|
|
39
40
|
"LICENSE"
|
|
40
41
|
],
|
|
41
42
|
"scripts": {
|
|
42
|
-
"build": "tsup src/index.ts --format esm,cjs --dts --out-dir dist && tsup src/cli.ts --format
|
|
43
|
+
"build": "tsup src/index.ts --format esm,cjs --dts --out-dir dist --tsconfig tsconfig.json && tsup src/cli.ts --format cjs --out-dir dist --no-dts --tsconfig tsconfig.json && node scripts/make-bin.cjs",
|
|
43
44
|
"prepublishOnly": "npm run build"
|
|
44
45
|
},
|
|
45
46
|
"dependencies": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
const distDir = path.resolve(__dirname, "../dist");
|
|
5
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
6
|
+
|
|
7
|
+
const out = path.join(distDir, "bin.cjs");
|
|
8
|
+
|
|
9
|
+
const content = `#!/usr/bin/env node
|
|
10
|
+
require("./cli.cjs");
|
|
11
|
+
`;
|
|
12
|
+
|
|
13
|
+
fs.writeFileSync(out, content, "utf8");
|
|
14
|
+
try { fs.chmodSync(out, 0o755); } catch {}
|
package/dist/cli.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { createRequire } from "module";
|
|
5
|
-
import { globSync } from "glob";
|
|
6
|
-
var CONFIG = {
|
|
7
|
-
projectRoot: process.cwd(),
|
|
8
|
-
controllersGlob: "./src/web/api/controllers/**/*.ts",
|
|
9
|
-
outFile: "./swagger.config.js",
|
|
10
|
-
openapiOut: "./src/web/api/openapi.json",
|
|
11
|
-
serviceTitle: "Prescription Service",
|
|
12
|
-
serverUrl: "http://localhost:3008",
|
|
13
|
-
securitySchemeName: "keycloakOAuth",
|
|
14
|
-
oauth: {
|
|
15
|
-
tokenUrl: "http://auth.localhost/realms/haemo/protocol/openid-connect/token",
|
|
16
|
-
refreshUrl: "http://auth.localhost/realms/haemo/protocol/openid-connect/refresh",
|
|
17
|
-
scopes: { openid: "openid scope" }
|
|
18
|
-
},
|
|
19
|
-
omitFieldsInWriteDtos: /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "v"])
|
|
20
|
-
};
|
|
21
|
-
function ensurePosix(p) {
|
|
22
|
-
return p.split(path.sep).join(path.posix.sep);
|
|
23
|
-
}
|
|
24
|
-
function pluralize(name) {
|
|
25
|
-
if (name.endsWith("s")) return `${name}es`;
|
|
26
|
-
return `${name}s`;
|
|
27
|
-
}
|
|
28
|
-
function getRequire() {
|
|
29
|
-
return createRequire(typeof __filename !== "undefined" ? __filename : process.cwd() + "/");
|
|
30
|
-
}
|
|
31
|
-
async function loadDmmfFromProject(schemaPath) {
|
|
32
|
-
const resolvedSchemaPath = schemaPath ? path.resolve(process.cwd(), schemaPath) : path.resolve(process.cwd(), "prisma/schema.prisma");
|
|
33
|
-
if (!fs.existsSync(resolvedSchemaPath)) {
|
|
34
|
-
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
35
|
-
}
|
|
36
|
-
const datamodel = fs.readFileSync(resolvedSchemaPath, "utf8");
|
|
37
|
-
const require2 = getRequire();
|
|
38
|
-
const internals = require2("@prisma/internals");
|
|
39
|
-
if (typeof internals.getDMMF !== "function") {
|
|
40
|
-
throw new Error(`@prisma/internals.getDMMF not available`);
|
|
41
|
-
}
|
|
42
|
-
const dmmf = await internals.getDMMF({ datamodel });
|
|
43
|
-
if (!dmmf || !dmmf.datamodel || !Array.isArray(dmmf.datamodel.models) || !Array.isArray(dmmf.datamodel.enums)) {
|
|
44
|
-
throw new Error(`Unexpected DMMF shape returned by @prisma/internals.getDMMF`);
|
|
45
|
-
}
|
|
46
|
-
return dmmf;
|
|
47
|
-
}
|
|
48
|
-
function scalarToSchema(scalar) {
|
|
49
|
-
switch (scalar) {
|
|
50
|
-
case "String":
|
|
51
|
-
return { type: "string" };
|
|
52
|
-
case "Boolean":
|
|
53
|
-
return { type: "boolean" };
|
|
54
|
-
case "Int":
|
|
55
|
-
return { type: "integer" };
|
|
56
|
-
case "BigInt":
|
|
57
|
-
return { type: "integer", format: "int64" };
|
|
58
|
-
case "Float":
|
|
59
|
-
return { type: "number" };
|
|
60
|
-
case "Decimal":
|
|
61
|
-
return { type: "number" };
|
|
62
|
-
case "DateTime":
|
|
63
|
-
return { type: "string", format: "date-time" };
|
|
64
|
-
case "Json":
|
|
65
|
-
return { type: "object" };
|
|
66
|
-
case "Bytes":
|
|
67
|
-
return { type: "string", format: "byte" };
|
|
68
|
-
default:
|
|
69
|
-
return { type: "string" };
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
function fieldSchema(field, getRefName) {
|
|
73
|
-
if (field.kind === "scalar") {
|
|
74
|
-
const base = scalarToSchema(field.type);
|
|
75
|
-
if (field.isList) return { type: "array", items: base };
|
|
76
|
-
return base;
|
|
77
|
-
}
|
|
78
|
-
if (field.kind === "enum") {
|
|
79
|
-
const base = { $ref: `#/components/schemas/${field.type}` };
|
|
80
|
-
if (field.isList) return { type: "array", items: base };
|
|
81
|
-
return base;
|
|
82
|
-
}
|
|
83
|
-
if (field.kind === "object") {
|
|
84
|
-
const ref = { $ref: `#/components/schemas/${getRefName(String(field.type))}` };
|
|
85
|
-
if (field.isList) return { type: "array", items: ref };
|
|
86
|
-
return ref;
|
|
87
|
-
}
|
|
88
|
-
return { type: "object" };
|
|
89
|
-
}
|
|
90
|
-
function modelToGetSchema(model, getRefName) {
|
|
91
|
-
const properties = {};
|
|
92
|
-
const required = [];
|
|
93
|
-
for (const f of model.fields) {
|
|
94
|
-
properties[f.name] = fieldSchema(f, getRefName);
|
|
95
|
-
if (f.isRequired) required.push(f.name);
|
|
96
|
-
}
|
|
97
|
-
const schema = { type: "object", properties };
|
|
98
|
-
if (required.length) schema.required = required;
|
|
99
|
-
return schema;
|
|
100
|
-
}
|
|
101
|
-
function stripWriteFields(model, getSchema, omit) {
|
|
102
|
-
const schema = JSON.parse(JSON.stringify(getSchema));
|
|
103
|
-
if (!schema.properties) return schema;
|
|
104
|
-
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
105
|
-
for (const key of Object.keys(schema.properties)) {
|
|
106
|
-
if (omit.has(key) || relationFieldNames.has(key)) delete schema.properties[key];
|
|
107
|
-
}
|
|
108
|
-
if (Array.isArray(schema.required)) {
|
|
109
|
-
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
110
|
-
if (schema.required.length === 0) delete schema.required;
|
|
111
|
-
}
|
|
112
|
-
return schema;
|
|
113
|
-
}
|
|
114
|
-
function makeAllOptional(schema) {
|
|
115
|
-
const s = JSON.parse(JSON.stringify(schema));
|
|
116
|
-
delete s.required;
|
|
117
|
-
return s;
|
|
118
|
-
}
|
|
119
|
-
function listResponseSchema(itemRef) {
|
|
120
|
-
return {
|
|
121
|
-
type: "object",
|
|
122
|
-
properties: {
|
|
123
|
-
count: { type: "number" },
|
|
124
|
-
hasPreviousPage: { type: "boolean" },
|
|
125
|
-
hasNextPage: { type: "boolean" },
|
|
126
|
-
pageNumber: { type: "number" },
|
|
127
|
-
pageSize: { type: "number" },
|
|
128
|
-
totalPages: { type: "number" },
|
|
129
|
-
items: { type: "array", items: { $ref: itemRef } }
|
|
130
|
-
},
|
|
131
|
-
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
async function buildSchemasFromPrismaDmmf(schemaPath) {
|
|
135
|
-
const dmmf = await loadDmmfFromProject(schemaPath);
|
|
136
|
-
const schemas = {};
|
|
137
|
-
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
138
|
-
for (const e of dmmf.datamodel.enums) {
|
|
139
|
-
schemas[e.name] = { type: "string", enum: e.values.map((v) => v.name) };
|
|
140
|
-
}
|
|
141
|
-
for (const model of dmmf.datamodel.models) {
|
|
142
|
-
const getName = `Get${model.name}Response`;
|
|
143
|
-
const postName = `Post${model.name}Request`;
|
|
144
|
-
const putName = `Put${model.name}Request`;
|
|
145
|
-
const listName = `List${pluralize(model.name)}Response`;
|
|
146
|
-
const getSchema = modelToGetSchema(model, getRefName);
|
|
147
|
-
const postSchema = stripWriteFields(model, getSchema, CONFIG.omitFieldsInWriteDtos);
|
|
148
|
-
const putSchema = makeAllOptional(postSchema);
|
|
149
|
-
schemas[getName] = getSchema;
|
|
150
|
-
schemas[postName] = postSchema;
|
|
151
|
-
schemas[putName] = putSchema;
|
|
152
|
-
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
153
|
-
}
|
|
154
|
-
return schemas;
|
|
155
|
-
}
|
|
156
|
-
function generateSwaggerConfigJs(schemas) {
|
|
157
|
-
const routes = globSync(CONFIG.controllersGlob, { nodir: true }).map((p) => ensurePosix(p));
|
|
158
|
-
const docs = {
|
|
159
|
-
info: { title: CONFIG.serviceTitle },
|
|
160
|
-
servers: [{ url: CONFIG.serverUrl }],
|
|
161
|
-
components: {
|
|
162
|
-
schemas,
|
|
163
|
-
securitySchemes: {
|
|
164
|
-
[CONFIG.securitySchemeName]: {
|
|
165
|
-
type: "oauth2",
|
|
166
|
-
description: "This API uses OAuth2 with the password flow.",
|
|
167
|
-
flows: {
|
|
168
|
-
password: {
|
|
169
|
-
tokenUrl: CONFIG.oauth.tokenUrl,
|
|
170
|
-
refreshUrl: CONFIG.oauth.refreshUrl,
|
|
171
|
-
scopes: CONFIG.oauth.scopes
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
security: [{ [CONFIG.securitySchemeName]: ["openid"] }]
|
|
178
|
-
};
|
|
179
|
-
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
180
|
-
const docs = ${JSON.stringify(docs, null, 2)};
|
|
181
|
-
const routes = ${JSON.stringify(routes, null, 2)};
|
|
182
|
-
swaggerAutogen('${ensurePosix(CONFIG.openapiOut)}', routes, docs);`;
|
|
183
|
-
fs.writeFileSync(path.resolve(CONFIG.projectRoot, CONFIG.outFile), fileContent, "utf8");
|
|
184
|
-
}
|
|
185
|
-
async function run(args = []) {
|
|
186
|
-
const schemaFlagIndex = args.findIndex((a) => a === "--schema");
|
|
187
|
-
const schemaPath = schemaFlagIndex >= 0 ? args[schemaFlagIndex + 1] : void 0;
|
|
188
|
-
const schemas = await buildSchemasFromPrismaDmmf(schemaPath);
|
|
189
|
-
generateSwaggerConfigJs(schemas);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// src/cli.ts
|
|
193
|
-
void run(process.argv.slice(2)).catch((e) => {
|
|
194
|
-
console.error(e);
|
|
195
|
-
process.exitCode = 1;
|
|
196
|
-
});
|
|
197
|
-
ss.exitCode = 1;
|
|
198
|
-
});
|