prisma-swagger-autogen 1.0.2 → 1.0.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/dist/chunk-NLFDSHIX.js +193 -0
- package/dist/cli.cjs +36 -183
- package/dist/cli.d.cts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +2 -3
- package/dist/index.cjs +35 -181
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +5 -2
- package/dist/chunk-B6OADLRN.js +0 -339
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
const base = typeof __filename !== "undefined" ? __filename : import.meta.url;
|
|
30
|
+
return createRequire(base);
|
|
31
|
+
}
|
|
32
|
+
function loadDmmfFromProject(schemaPath) {
|
|
33
|
+
const resolvedSchemaPath = schemaPath ? path.resolve(process.cwd(), schemaPath) : path.resolve(process.cwd(), "prisma/schema.prisma");
|
|
34
|
+
if (!fs.existsSync(resolvedSchemaPath)) {
|
|
35
|
+
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
36
|
+
}
|
|
37
|
+
const datamodel = fs.readFileSync(resolvedSchemaPath, "utf8");
|
|
38
|
+
const require2 = getRequire();
|
|
39
|
+
const internals = require2("@prisma/internals");
|
|
40
|
+
if (typeof internals.getDMMF !== "function") {
|
|
41
|
+
throw new Error(`@prisma/internals.getDMMF not available`);
|
|
42
|
+
}
|
|
43
|
+
return internals.getDMMF({ datamodel });
|
|
44
|
+
}
|
|
45
|
+
function scalarToSchema(scalar) {
|
|
46
|
+
switch (scalar) {
|
|
47
|
+
case "String":
|
|
48
|
+
return { type: "string" };
|
|
49
|
+
case "Boolean":
|
|
50
|
+
return { type: "boolean" };
|
|
51
|
+
case "Int":
|
|
52
|
+
return { type: "integer" };
|
|
53
|
+
case "BigInt":
|
|
54
|
+
return { type: "integer", format: "int64" };
|
|
55
|
+
case "Float":
|
|
56
|
+
return { type: "number" };
|
|
57
|
+
case "Decimal":
|
|
58
|
+
return { type: "number" };
|
|
59
|
+
case "DateTime":
|
|
60
|
+
return { type: "string", format: "date-time" };
|
|
61
|
+
case "Json":
|
|
62
|
+
return { type: "object" };
|
|
63
|
+
case "Bytes":
|
|
64
|
+
return { type: "string", format: "byte" };
|
|
65
|
+
default:
|
|
66
|
+
return { type: "string" };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function fieldSchema(field, getRefName) {
|
|
70
|
+
if (field.kind === "scalar") {
|
|
71
|
+
const base = scalarToSchema(field.type);
|
|
72
|
+
if (field.isList) return { type: "array", items: base };
|
|
73
|
+
return base;
|
|
74
|
+
}
|
|
75
|
+
if (field.kind === "enum") {
|
|
76
|
+
const base = { $ref: `#/components/schemas/${field.type}` };
|
|
77
|
+
if (field.isList) return { type: "array", items: base };
|
|
78
|
+
return base;
|
|
79
|
+
}
|
|
80
|
+
if (field.kind === "object") {
|
|
81
|
+
const ref = { $ref: `#/components/schemas/${getRefName(String(field.type))}` };
|
|
82
|
+
if (field.isList) return { type: "array", items: ref };
|
|
83
|
+
return ref;
|
|
84
|
+
}
|
|
85
|
+
return { type: "object" };
|
|
86
|
+
}
|
|
87
|
+
function modelToGetSchema(model, getRefName) {
|
|
88
|
+
const properties = {};
|
|
89
|
+
const required = [];
|
|
90
|
+
for (const f of model.fields) {
|
|
91
|
+
properties[f.name] = fieldSchema(f, getRefName);
|
|
92
|
+
if (f.isRequired) required.push(f.name);
|
|
93
|
+
}
|
|
94
|
+
const schema = { type: "object", properties };
|
|
95
|
+
if (required.length) schema.required = required;
|
|
96
|
+
return schema;
|
|
97
|
+
}
|
|
98
|
+
function stripWriteFields(model, getSchema, omit) {
|
|
99
|
+
const schema = JSON.parse(JSON.stringify(getSchema));
|
|
100
|
+
if (!schema.properties) return schema;
|
|
101
|
+
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
102
|
+
for (const key of Object.keys(schema.properties)) {
|
|
103
|
+
if (omit.has(key) || relationFieldNames.has(key)) {
|
|
104
|
+
delete schema.properties[key];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (Array.isArray(schema.required)) {
|
|
108
|
+
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
109
|
+
if (schema.required.length === 0) delete schema.required;
|
|
110
|
+
}
|
|
111
|
+
return schema;
|
|
112
|
+
}
|
|
113
|
+
function makeAllOptional(schema) {
|
|
114
|
+
const s = JSON.parse(JSON.stringify(schema));
|
|
115
|
+
delete s.required;
|
|
116
|
+
return s;
|
|
117
|
+
}
|
|
118
|
+
function listResponseSchema(itemRef) {
|
|
119
|
+
return {
|
|
120
|
+
type: "object",
|
|
121
|
+
properties: {
|
|
122
|
+
count: { type: "number" },
|
|
123
|
+
hasPreviousPage: { type: "boolean" },
|
|
124
|
+
hasNextPage: { type: "boolean" },
|
|
125
|
+
pageNumber: { type: "number" },
|
|
126
|
+
pageSize: { type: "number" },
|
|
127
|
+
totalPages: { type: "number" },
|
|
128
|
+
items: { type: "array", items: { $ref: itemRef } }
|
|
129
|
+
},
|
|
130
|
+
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
function buildSchemasFromPrismaDmmf(schemaPath) {
|
|
134
|
+
const dmmf = loadDmmfFromProject(schemaPath);
|
|
135
|
+
const schemas = {};
|
|
136
|
+
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
137
|
+
for (const e of dmmf.datamodel.enums) {
|
|
138
|
+
schemas[e.name] = { type: "string", enum: e.values.map((v) => v.name) };
|
|
139
|
+
}
|
|
140
|
+
for (const model of dmmf.datamodel.models) {
|
|
141
|
+
const getName = `Get${model.name}Response`;
|
|
142
|
+
const postName = `Post${model.name}Request`;
|
|
143
|
+
const putName = `Put${model.name}Request`;
|
|
144
|
+
const listName = `List${pluralize(model.name)}Response`;
|
|
145
|
+
const getSchema = modelToGetSchema(model, getRefName);
|
|
146
|
+
const postSchema = stripWriteFields(model, getSchema, CONFIG.omitFieldsInWriteDtos);
|
|
147
|
+
const putSchema = makeAllOptional(postSchema);
|
|
148
|
+
schemas[getName] = getSchema;
|
|
149
|
+
schemas[postName] = postSchema;
|
|
150
|
+
schemas[putName] = putSchema;
|
|
151
|
+
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
152
|
+
}
|
|
153
|
+
return schemas;
|
|
154
|
+
}
|
|
155
|
+
function generateSwaggerConfigJs(schemas) {
|
|
156
|
+
const routes = globSync(CONFIG.controllersGlob, { nodir: true }).map((p) => ensurePosix(p));
|
|
157
|
+
const docs = {
|
|
158
|
+
info: { title: CONFIG.serviceTitle },
|
|
159
|
+
servers: [{ url: CONFIG.serverUrl }],
|
|
160
|
+
components: {
|
|
161
|
+
schemas,
|
|
162
|
+
securitySchemes: {
|
|
163
|
+
[CONFIG.securitySchemeName]: {
|
|
164
|
+
type: "oauth2",
|
|
165
|
+
description: "This API uses OAuth2 with the password flow.",
|
|
166
|
+
flows: {
|
|
167
|
+
password: {
|
|
168
|
+
tokenUrl: CONFIG.oauth.tokenUrl,
|
|
169
|
+
refreshUrl: CONFIG.oauth.refreshUrl,
|
|
170
|
+
scopes: CONFIG.oauth.scopes
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
security: [{ [CONFIG.securitySchemeName]: ["openid"] }]
|
|
177
|
+
};
|
|
178
|
+
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
179
|
+
const docs = ${JSON.stringify(docs, null, 2)};
|
|
180
|
+
const routes = ${JSON.stringify(routes, null, 2)};
|
|
181
|
+
swaggerAutogen('${ensurePosix(CONFIG.openapiOut)}', routes, docs);`;
|
|
182
|
+
fs.writeFileSync(path.resolve(CONFIG.projectRoot, CONFIG.outFile), fileContent, "utf8");
|
|
183
|
+
}
|
|
184
|
+
async function run(args = []) {
|
|
185
|
+
const schemaFlagIndex = args.findIndex((a) => a === "--schema");
|
|
186
|
+
const schemaPath = schemaFlagIndex >= 0 ? args[schemaFlagIndex + 1] : void 0;
|
|
187
|
+
const schemas = buildSchemasFromPrismaDmmf(schemaPath);
|
|
188
|
+
generateSwaggerConfigJs(schemas);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
export {
|
|
192
|
+
run
|
|
193
|
+
};
|
package/dist/cli.cjs
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
"use strict";
|
|
3
2
|
var __create = Object.create;
|
|
4
3
|
var __defProp = Object.defineProperty;
|
|
@@ -26,8 +25,8 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
25
|
// src/index.ts
|
|
27
26
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
28
27
|
var import_node_path = __toESM(require("path"), 1);
|
|
29
|
-
var import_glob = require("glob");
|
|
30
28
|
var import_node_module = require("module");
|
|
29
|
+
var import_glob = require("glob");
|
|
31
30
|
var import_meta = {};
|
|
32
31
|
var CONFIG = {
|
|
33
32
|
projectRoot: process.cwd(),
|
|
@@ -51,6 +50,23 @@ function pluralize(name) {
|
|
|
51
50
|
if (name.endsWith("s")) return `${name}es`;
|
|
52
51
|
return `${name}s`;
|
|
53
52
|
}
|
|
53
|
+
function getRequire() {
|
|
54
|
+
const base = typeof __filename !== "undefined" ? __filename : import_meta.url;
|
|
55
|
+
return (0, import_node_module.createRequire)(base);
|
|
56
|
+
}
|
|
57
|
+
function loadDmmfFromProject(schemaPath) {
|
|
58
|
+
const resolvedSchemaPath = schemaPath ? import_node_path.default.resolve(process.cwd(), schemaPath) : import_node_path.default.resolve(process.cwd(), "prisma/schema.prisma");
|
|
59
|
+
if (!import_node_fs.default.existsSync(resolvedSchemaPath)) {
|
|
60
|
+
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
61
|
+
}
|
|
62
|
+
const datamodel = import_node_fs.default.readFileSync(resolvedSchemaPath, "utf8");
|
|
63
|
+
const require2 = getRequire();
|
|
64
|
+
const internals = require2("@prisma/internals");
|
|
65
|
+
if (typeof internals.getDMMF !== "function") {
|
|
66
|
+
throw new Error(`@prisma/internals.getDMMF not available`);
|
|
67
|
+
}
|
|
68
|
+
return internals.getDMMF({ datamodel });
|
|
69
|
+
}
|
|
54
70
|
function scalarToSchema(scalar) {
|
|
55
71
|
switch (scalar) {
|
|
56
72
|
case "String":
|
|
@@ -109,7 +125,9 @@ function stripWriteFields(model, getSchema, omit) {
|
|
|
109
125
|
if (!schema.properties) return schema;
|
|
110
126
|
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
111
127
|
for (const key of Object.keys(schema.properties)) {
|
|
112
|
-
if (omit.has(key) || relationFieldNames.has(key))
|
|
128
|
+
if (omit.has(key) || relationFieldNames.has(key)) {
|
|
129
|
+
delete schema.properties[key];
|
|
130
|
+
}
|
|
113
131
|
}
|
|
114
132
|
if (Array.isArray(schema.required)) {
|
|
115
133
|
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
@@ -126,100 +144,19 @@ function listResponseSchema(itemRef) {
|
|
|
126
144
|
return {
|
|
127
145
|
type: "object",
|
|
128
146
|
properties: {
|
|
129
|
-
count: { type: "number"
|
|
130
|
-
hasPreviousPage: { type: "boolean"
|
|
131
|
-
hasNextPage: { type: "boolean"
|
|
132
|
-
pageNumber: { type: "number"
|
|
133
|
-
pageSize: { type: "number"
|
|
134
|
-
totalPages: { type: "number"
|
|
147
|
+
count: { type: "number" },
|
|
148
|
+
hasPreviousPage: { type: "boolean" },
|
|
149
|
+
hasNextPage: { type: "boolean" },
|
|
150
|
+
pageNumber: { type: "number" },
|
|
151
|
+
pageSize: { type: "number" },
|
|
152
|
+
totalPages: { type: "number" },
|
|
135
153
|
items: { type: "array", items: { $ref: itemRef } }
|
|
136
154
|
},
|
|
137
155
|
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
138
156
|
};
|
|
139
157
|
}
|
|
140
|
-
function
|
|
141
|
-
|
|
142
|
-
switch (type) {
|
|
143
|
-
case "string":
|
|
144
|
-
return "string";
|
|
145
|
-
case "integer":
|
|
146
|
-
return 0;
|
|
147
|
-
case "number":
|
|
148
|
-
return 0;
|
|
149
|
-
case "boolean":
|
|
150
|
-
return true;
|
|
151
|
-
case "object":
|
|
152
|
-
return {};
|
|
153
|
-
default:
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
function buildExampleFromSchema(schema, components, depth = 0) {
|
|
158
|
-
if (depth > 2) return void 0;
|
|
159
|
-
if (schema.$ref) {
|
|
160
|
-
const name = String(schema.$ref).split("/").pop() || "";
|
|
161
|
-
const target = components[name];
|
|
162
|
-
if (!target) return void 0;
|
|
163
|
-
return buildExampleFromSchema(target, components, depth + 1);
|
|
164
|
-
}
|
|
165
|
-
if (Array.isArray(schema.allOf) && schema.allOf.length) {
|
|
166
|
-
const merged = {};
|
|
167
|
-
for (const part of schema.allOf) {
|
|
168
|
-
const ex = buildExampleFromSchema(part, components, depth + 1);
|
|
169
|
-
if (ex && typeof ex === "object" && !Array.isArray(ex)) Object.assign(merged, ex);
|
|
170
|
-
}
|
|
171
|
-
return Object.keys(merged).length ? merged : void 0;
|
|
172
|
-
}
|
|
173
|
-
if (schema.type === "array" && schema.items) {
|
|
174
|
-
const item = buildExampleFromSchema(schema.items, components, depth + 1);
|
|
175
|
-
return item === void 0 ? [] : [item];
|
|
176
|
-
}
|
|
177
|
-
if (schema.type === "object" && schema.properties) {
|
|
178
|
-
const obj = {};
|
|
179
|
-
for (const [k, v] of Object.entries(schema.properties)) {
|
|
180
|
-
const ex = buildExampleFromSchema(v, components, depth + 1);
|
|
181
|
-
if (ex !== void 0) obj[k] = ex;
|
|
182
|
-
}
|
|
183
|
-
return obj;
|
|
184
|
-
}
|
|
185
|
-
if (Array.isArray(schema.enum) && schema.enum.length) return schema.enum[0];
|
|
186
|
-
if (typeof schema.type === "string") return exampleForScalarType(schema.type, schema.format);
|
|
187
|
-
return void 0;
|
|
188
|
-
}
|
|
189
|
-
function attachExample(schema, components) {
|
|
190
|
-
const s = JSON.parse(JSON.stringify(schema));
|
|
191
|
-
if (s.example === void 0) {
|
|
192
|
-
const ex = buildExampleFromSchema(s, components);
|
|
193
|
-
if (ex !== void 0) s.example = ex;
|
|
194
|
-
}
|
|
195
|
-
return s;
|
|
196
|
-
}
|
|
197
|
-
function loadDmmfFromProject() {
|
|
198
|
-
const schemaPath = import_node_path.default.resolve(process.cwd(), "prisma/schema.prisma");
|
|
199
|
-
if (!import_node_fs.default.existsSync(schemaPath)) {
|
|
200
|
-
throw new Error(`Prisma schema not found at: ${schemaPath}`);
|
|
201
|
-
}
|
|
202
|
-
const datamodel = import_node_fs.default.readFileSync(schemaPath, "utf8");
|
|
203
|
-
const require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
204
|
-
const tryLoad = (id) => {
|
|
205
|
-
try {
|
|
206
|
-
return require2(id);
|
|
207
|
-
} catch {
|
|
208
|
-
return null;
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
const runtime = tryLoad("@prisma/client/runtime/library") ?? tryLoad("@prisma/client/runtime");
|
|
212
|
-
if (!runtime || typeof runtime.getDMMF !== "function") {
|
|
213
|
-
throw new Error(
|
|
214
|
-
`Unable to load Prisma runtime getDMMF(). Ensure @prisma/client is installed in the target project.
|
|
215
|
-
Tried: @prisma/client/runtime/library and @prisma/client/runtime`
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
const dmmf = runtime.getDMMF({ datamodel });
|
|
219
|
-
if (!dmmf?.datamodel?.models) throw new Error("Failed to load Prisma DMMF (unexpected structure).");
|
|
220
|
-
return dmmf;
|
|
221
|
-
}
|
|
222
|
-
function buildSchemasFromDmmf(dmmf) {
|
|
158
|
+
function buildSchemasFromPrismaDmmf(schemaPath) {
|
|
159
|
+
const dmmf = loadDmmfFromProject(schemaPath);
|
|
223
160
|
const schemas = {};
|
|
224
161
|
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
225
162
|
for (const e of dmmf.datamodel.enums) {
|
|
@@ -238,45 +175,6 @@ function buildSchemasFromDmmf(dmmf) {
|
|
|
238
175
|
schemas[putName] = putSchema;
|
|
239
176
|
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
240
177
|
}
|
|
241
|
-
schemas["ExceptionResponse"] = {
|
|
242
|
-
type: "object",
|
|
243
|
-
properties: {
|
|
244
|
-
detail: { type: "string" },
|
|
245
|
-
errors: { type: "array", items: { type: "string" } },
|
|
246
|
-
status: { type: "number" },
|
|
247
|
-
title: { type: "string" },
|
|
248
|
-
type: { type: "string" }
|
|
249
|
-
},
|
|
250
|
-
required: ["status", "title", "type"]
|
|
251
|
-
};
|
|
252
|
-
schemas["BadRequestResponse"] = {
|
|
253
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
254
|
-
example: {
|
|
255
|
-
status: 400,
|
|
256
|
-
title: "The request was invalid",
|
|
257
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1"
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
schemas["NotFoundResponse"] = {
|
|
261
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
262
|
-
example: {
|
|
263
|
-
status: 404,
|
|
264
|
-
title: "The specified resource was not found",
|
|
265
|
-
type: "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4"
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
schemas["InternalErrorResponse"] = {
|
|
269
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
270
|
-
example: {
|
|
271
|
-
status: 500,
|
|
272
|
-
title: "An error occurred while processing your request",
|
|
273
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.6.1"
|
|
274
|
-
}
|
|
275
|
-
};
|
|
276
|
-
for (const name of Object.keys(schemas)) {
|
|
277
|
-
if (name.startsWith("Post") && name.endsWith("Request")) schemas[name] = attachExample(schemas[name], schemas);
|
|
278
|
-
if (name.startsWith("Put") && name.endsWith("Request")) schemas[name] = attachExample(schemas[name], schemas);
|
|
279
|
-
}
|
|
280
178
|
return schemas;
|
|
281
179
|
}
|
|
282
180
|
function generateSwaggerConfigJs(schemas) {
|
|
@@ -303,65 +201,20 @@ function generateSwaggerConfigJs(schemas) {
|
|
|
303
201
|
security: [{ [CONFIG.securitySchemeName]: ["openid"] }]
|
|
304
202
|
};
|
|
305
203
|
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
306
|
-
const fs = require('fs');
|
|
307
|
-
const path = require('node:path');
|
|
308
|
-
|
|
309
|
-
function isPlainObject(v){return v!==null && typeof v==='object' && !Array.isArray(v);}
|
|
310
|
-
|
|
311
|
-
function normalizeSchema(schema){
|
|
312
|
-
if(!isPlainObject(schema)) return schema;
|
|
313
|
-
if(schema.$ref) return schema;
|
|
314
|
-
|
|
315
|
-
if(isPlainObject(schema.type) && typeof schema.type.example === 'string') schema.type = schema.type.example;
|
|
316
|
-
if(isPlainObject(schema.format) && typeof schema.format.example === 'string') schema.format = schema.format.example;
|
|
317
|
-
if(isPlainObject(schema.required) && Array.isArray(schema.required.example)) schema.required = schema.required.example;
|
|
318
|
-
if(isPlainObject(schema.enum) && Array.isArray(schema.enum.example)) schema.enum = schema.enum.example;
|
|
319
|
-
|
|
320
|
-
if(Array.isArray(schema.allOf)) schema.allOf = schema.allOf.map(normalizeSchema);
|
|
321
|
-
if(isPlainObject(schema.items)) schema.items = normalizeSchema(schema.items);
|
|
322
|
-
if(isPlainObject(schema.additionalProperties)) schema.additionalProperties = normalizeSchema(schema.additionalProperties);
|
|
323
|
-
|
|
324
|
-
if(isPlainObject(schema.properties)){
|
|
325
|
-
for(const k of Object.keys(schema.properties)){
|
|
326
|
-
schema.properties[k] = normalizeSchema(schema.properties[k]);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return schema;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
function fixOpenApiFile(openapiPath){
|
|
334
|
-
const abs = path.resolve(process.cwd(), openapiPath);
|
|
335
|
-
if(!fs.existsSync(abs)) return;
|
|
336
|
-
const doc = JSON.parse(fs.readFileSync(abs,'utf8'));
|
|
337
|
-
|
|
338
|
-
if(doc && doc.components && isPlainObject(doc.components.schemas)){
|
|
339
|
-
for(const name of Object.keys(doc.components.schemas)){
|
|
340
|
-
doc.components.schemas[name] = normalizeSchema(doc.components.schemas[name]);
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
fs.writeFileSync(abs, JSON.stringify(doc,null,2),'utf8');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
204
|
const docs = ${JSON.stringify(docs, null, 2)};
|
|
348
205
|
const routes = ${JSON.stringify(routes, null, 2)};
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
.then(() => fixOpenApiFile('${ensurePosix(CONFIG.openapiOut)}'))
|
|
352
|
-
.catch((e) => { console.error(e); process.exitCode = 1; });
|
|
353
|
-
`;
|
|
354
|
-
const outPath = import_node_path.default.resolve(CONFIG.projectRoot, CONFIG.outFile);
|
|
355
|
-
import_node_fs.default.writeFileSync(outPath, fileContent, "utf8");
|
|
206
|
+
swaggerAutogen('${ensurePosix(CONFIG.openapiOut)}', routes, docs);`;
|
|
207
|
+
import_node_fs.default.writeFileSync(import_node_path.default.resolve(CONFIG.projectRoot, CONFIG.outFile), fileContent, "utf8");
|
|
356
208
|
}
|
|
357
|
-
async function run(
|
|
358
|
-
const
|
|
359
|
-
const
|
|
209
|
+
async function run(args = []) {
|
|
210
|
+
const schemaFlagIndex = args.findIndex((a) => a === "--schema");
|
|
211
|
+
const schemaPath = schemaFlagIndex >= 0 ? args[schemaFlagIndex + 1] : void 0;
|
|
212
|
+
const schemas = buildSchemasFromPrismaDmmf(schemaPath);
|
|
360
213
|
generateSwaggerConfigJs(schemas);
|
|
361
214
|
}
|
|
362
215
|
|
|
363
216
|
// src/cli.ts
|
|
364
|
-
run(process.argv.slice(2)).catch((e) => {
|
|
217
|
+
void run(process.argv.slice(2)).catch((e) => {
|
|
365
218
|
console.error(e);
|
|
366
219
|
process.exitCode = 1;
|
|
367
220
|
});
|
package/dist/cli.d.cts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
export { }
|
package/dist/cli.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
export { }
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
import {
|
|
3
2
|
run
|
|
4
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-NLFDSHIX.js";
|
|
5
4
|
|
|
6
5
|
// src/cli.ts
|
|
7
|
-
run(process.argv.slice(2)).catch((e) => {
|
|
6
|
+
void run(process.argv.slice(2)).catch((e) => {
|
|
8
7
|
console.error(e);
|
|
9
8
|
process.exitCode = 1;
|
|
10
9
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -35,8 +35,8 @@ __export(index_exports, {
|
|
|
35
35
|
module.exports = __toCommonJS(index_exports);
|
|
36
36
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
37
37
|
var import_node_path = __toESM(require("path"), 1);
|
|
38
|
-
var import_glob = require("glob");
|
|
39
38
|
var import_node_module = require("module");
|
|
39
|
+
var import_glob = require("glob");
|
|
40
40
|
var import_meta = {};
|
|
41
41
|
var CONFIG = {
|
|
42
42
|
projectRoot: process.cwd(),
|
|
@@ -60,6 +60,23 @@ function pluralize(name) {
|
|
|
60
60
|
if (name.endsWith("s")) return `${name}es`;
|
|
61
61
|
return `${name}s`;
|
|
62
62
|
}
|
|
63
|
+
function getRequire() {
|
|
64
|
+
const base = typeof __filename !== "undefined" ? __filename : import_meta.url;
|
|
65
|
+
return (0, import_node_module.createRequire)(base);
|
|
66
|
+
}
|
|
67
|
+
function loadDmmfFromProject(schemaPath) {
|
|
68
|
+
const resolvedSchemaPath = schemaPath ? import_node_path.default.resolve(process.cwd(), schemaPath) : import_node_path.default.resolve(process.cwd(), "prisma/schema.prisma");
|
|
69
|
+
if (!import_node_fs.default.existsSync(resolvedSchemaPath)) {
|
|
70
|
+
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
71
|
+
}
|
|
72
|
+
const datamodel = import_node_fs.default.readFileSync(resolvedSchemaPath, "utf8");
|
|
73
|
+
const require2 = getRequire();
|
|
74
|
+
const internals = require2("@prisma/internals");
|
|
75
|
+
if (typeof internals.getDMMF !== "function") {
|
|
76
|
+
throw new Error(`@prisma/internals.getDMMF not available`);
|
|
77
|
+
}
|
|
78
|
+
return internals.getDMMF({ datamodel });
|
|
79
|
+
}
|
|
63
80
|
function scalarToSchema(scalar) {
|
|
64
81
|
switch (scalar) {
|
|
65
82
|
case "String":
|
|
@@ -118,7 +135,9 @@ function stripWriteFields(model, getSchema, omit) {
|
|
|
118
135
|
if (!schema.properties) return schema;
|
|
119
136
|
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
120
137
|
for (const key of Object.keys(schema.properties)) {
|
|
121
|
-
if (omit.has(key) || relationFieldNames.has(key))
|
|
138
|
+
if (omit.has(key) || relationFieldNames.has(key)) {
|
|
139
|
+
delete schema.properties[key];
|
|
140
|
+
}
|
|
122
141
|
}
|
|
123
142
|
if (Array.isArray(schema.required)) {
|
|
124
143
|
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
@@ -135,100 +154,19 @@ function listResponseSchema(itemRef) {
|
|
|
135
154
|
return {
|
|
136
155
|
type: "object",
|
|
137
156
|
properties: {
|
|
138
|
-
count: { type: "number"
|
|
139
|
-
hasPreviousPage: { type: "boolean"
|
|
140
|
-
hasNextPage: { type: "boolean"
|
|
141
|
-
pageNumber: { type: "number"
|
|
142
|
-
pageSize: { type: "number"
|
|
143
|
-
totalPages: { type: "number"
|
|
157
|
+
count: { type: "number" },
|
|
158
|
+
hasPreviousPage: { type: "boolean" },
|
|
159
|
+
hasNextPage: { type: "boolean" },
|
|
160
|
+
pageNumber: { type: "number" },
|
|
161
|
+
pageSize: { type: "number" },
|
|
162
|
+
totalPages: { type: "number" },
|
|
144
163
|
items: { type: "array", items: { $ref: itemRef } }
|
|
145
164
|
},
|
|
146
165
|
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
147
166
|
};
|
|
148
167
|
}
|
|
149
|
-
function
|
|
150
|
-
|
|
151
|
-
switch (type) {
|
|
152
|
-
case "string":
|
|
153
|
-
return "string";
|
|
154
|
-
case "integer":
|
|
155
|
-
return 0;
|
|
156
|
-
case "number":
|
|
157
|
-
return 0;
|
|
158
|
-
case "boolean":
|
|
159
|
-
return true;
|
|
160
|
-
case "object":
|
|
161
|
-
return {};
|
|
162
|
-
default:
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
function buildExampleFromSchema(schema, components, depth = 0) {
|
|
167
|
-
if (depth > 2) return void 0;
|
|
168
|
-
if (schema.$ref) {
|
|
169
|
-
const name = String(schema.$ref).split("/").pop() || "";
|
|
170
|
-
const target = components[name];
|
|
171
|
-
if (!target) return void 0;
|
|
172
|
-
return buildExampleFromSchema(target, components, depth + 1);
|
|
173
|
-
}
|
|
174
|
-
if (Array.isArray(schema.allOf) && schema.allOf.length) {
|
|
175
|
-
const merged = {};
|
|
176
|
-
for (const part of schema.allOf) {
|
|
177
|
-
const ex = buildExampleFromSchema(part, components, depth + 1);
|
|
178
|
-
if (ex && typeof ex === "object" && !Array.isArray(ex)) Object.assign(merged, ex);
|
|
179
|
-
}
|
|
180
|
-
return Object.keys(merged).length ? merged : void 0;
|
|
181
|
-
}
|
|
182
|
-
if (schema.type === "array" && schema.items) {
|
|
183
|
-
const item = buildExampleFromSchema(schema.items, components, depth + 1);
|
|
184
|
-
return item === void 0 ? [] : [item];
|
|
185
|
-
}
|
|
186
|
-
if (schema.type === "object" && schema.properties) {
|
|
187
|
-
const obj = {};
|
|
188
|
-
for (const [k, v] of Object.entries(schema.properties)) {
|
|
189
|
-
const ex = buildExampleFromSchema(v, components, depth + 1);
|
|
190
|
-
if (ex !== void 0) obj[k] = ex;
|
|
191
|
-
}
|
|
192
|
-
return obj;
|
|
193
|
-
}
|
|
194
|
-
if (Array.isArray(schema.enum) && schema.enum.length) return schema.enum[0];
|
|
195
|
-
if (typeof schema.type === "string") return exampleForScalarType(schema.type, schema.format);
|
|
196
|
-
return void 0;
|
|
197
|
-
}
|
|
198
|
-
function attachExample(schema, components) {
|
|
199
|
-
const s = JSON.parse(JSON.stringify(schema));
|
|
200
|
-
if (s.example === void 0) {
|
|
201
|
-
const ex = buildExampleFromSchema(s, components);
|
|
202
|
-
if (ex !== void 0) s.example = ex;
|
|
203
|
-
}
|
|
204
|
-
return s;
|
|
205
|
-
}
|
|
206
|
-
function loadDmmfFromProject() {
|
|
207
|
-
const schemaPath = import_node_path.default.resolve(process.cwd(), "prisma/schema.prisma");
|
|
208
|
-
if (!import_node_fs.default.existsSync(schemaPath)) {
|
|
209
|
-
throw new Error(`Prisma schema not found at: ${schemaPath}`);
|
|
210
|
-
}
|
|
211
|
-
const datamodel = import_node_fs.default.readFileSync(schemaPath, "utf8");
|
|
212
|
-
const require2 = (0, import_node_module.createRequire)(import_meta.url);
|
|
213
|
-
const tryLoad = (id) => {
|
|
214
|
-
try {
|
|
215
|
-
return require2(id);
|
|
216
|
-
} catch {
|
|
217
|
-
return null;
|
|
218
|
-
}
|
|
219
|
-
};
|
|
220
|
-
const runtime = tryLoad("@prisma/client/runtime/library") ?? tryLoad("@prisma/client/runtime");
|
|
221
|
-
if (!runtime || typeof runtime.getDMMF !== "function") {
|
|
222
|
-
throw new Error(
|
|
223
|
-
`Unable to load Prisma runtime getDMMF(). Ensure @prisma/client is installed in the target project.
|
|
224
|
-
Tried: @prisma/client/runtime/library and @prisma/client/runtime`
|
|
225
|
-
);
|
|
226
|
-
}
|
|
227
|
-
const dmmf = runtime.getDMMF({ datamodel });
|
|
228
|
-
if (!dmmf?.datamodel?.models) throw new Error("Failed to load Prisma DMMF (unexpected structure).");
|
|
229
|
-
return dmmf;
|
|
230
|
-
}
|
|
231
|
-
function buildSchemasFromDmmf(dmmf) {
|
|
168
|
+
function buildSchemasFromPrismaDmmf(schemaPath) {
|
|
169
|
+
const dmmf = loadDmmfFromProject(schemaPath);
|
|
232
170
|
const schemas = {};
|
|
233
171
|
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
234
172
|
for (const e of dmmf.datamodel.enums) {
|
|
@@ -247,45 +185,6 @@ function buildSchemasFromDmmf(dmmf) {
|
|
|
247
185
|
schemas[putName] = putSchema;
|
|
248
186
|
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
249
187
|
}
|
|
250
|
-
schemas["ExceptionResponse"] = {
|
|
251
|
-
type: "object",
|
|
252
|
-
properties: {
|
|
253
|
-
detail: { type: "string" },
|
|
254
|
-
errors: { type: "array", items: { type: "string" } },
|
|
255
|
-
status: { type: "number" },
|
|
256
|
-
title: { type: "string" },
|
|
257
|
-
type: { type: "string" }
|
|
258
|
-
},
|
|
259
|
-
required: ["status", "title", "type"]
|
|
260
|
-
};
|
|
261
|
-
schemas["BadRequestResponse"] = {
|
|
262
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
263
|
-
example: {
|
|
264
|
-
status: 400,
|
|
265
|
-
title: "The request was invalid",
|
|
266
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1"
|
|
267
|
-
}
|
|
268
|
-
};
|
|
269
|
-
schemas["NotFoundResponse"] = {
|
|
270
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
271
|
-
example: {
|
|
272
|
-
status: 404,
|
|
273
|
-
title: "The specified resource was not found",
|
|
274
|
-
type: "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4"
|
|
275
|
-
}
|
|
276
|
-
};
|
|
277
|
-
schemas["InternalErrorResponse"] = {
|
|
278
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
279
|
-
example: {
|
|
280
|
-
status: 500,
|
|
281
|
-
title: "An error occurred while processing your request",
|
|
282
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.6.1"
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
for (const name of Object.keys(schemas)) {
|
|
286
|
-
if (name.startsWith("Post") && name.endsWith("Request")) schemas[name] = attachExample(schemas[name], schemas);
|
|
287
|
-
if (name.startsWith("Put") && name.endsWith("Request")) schemas[name] = attachExample(schemas[name], schemas);
|
|
288
|
-
}
|
|
289
188
|
return schemas;
|
|
290
189
|
}
|
|
291
190
|
function generateSwaggerConfigJs(schemas) {
|
|
@@ -312,60 +211,15 @@ function generateSwaggerConfigJs(schemas) {
|
|
|
312
211
|
security: [{ [CONFIG.securitySchemeName]: ["openid"] }]
|
|
313
212
|
};
|
|
314
213
|
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
315
|
-
const fs = require('fs');
|
|
316
|
-
const path = require('node:path');
|
|
317
|
-
|
|
318
|
-
function isPlainObject(v){return v!==null && typeof v==='object' && !Array.isArray(v);}
|
|
319
|
-
|
|
320
|
-
function normalizeSchema(schema){
|
|
321
|
-
if(!isPlainObject(schema)) return schema;
|
|
322
|
-
if(schema.$ref) return schema;
|
|
323
|
-
|
|
324
|
-
if(isPlainObject(schema.type) && typeof schema.type.example === 'string') schema.type = schema.type.example;
|
|
325
|
-
if(isPlainObject(schema.format) && typeof schema.format.example === 'string') schema.format = schema.format.example;
|
|
326
|
-
if(isPlainObject(schema.required) && Array.isArray(schema.required.example)) schema.required = schema.required.example;
|
|
327
|
-
if(isPlainObject(schema.enum) && Array.isArray(schema.enum.example)) schema.enum = schema.enum.example;
|
|
328
|
-
|
|
329
|
-
if(Array.isArray(schema.allOf)) schema.allOf = schema.allOf.map(normalizeSchema);
|
|
330
|
-
if(isPlainObject(schema.items)) schema.items = normalizeSchema(schema.items);
|
|
331
|
-
if(isPlainObject(schema.additionalProperties)) schema.additionalProperties = normalizeSchema(schema.additionalProperties);
|
|
332
|
-
|
|
333
|
-
if(isPlainObject(schema.properties)){
|
|
334
|
-
for(const k of Object.keys(schema.properties)){
|
|
335
|
-
schema.properties[k] = normalizeSchema(schema.properties[k]);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return schema;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
function fixOpenApiFile(openapiPath){
|
|
343
|
-
const abs = path.resolve(process.cwd(), openapiPath);
|
|
344
|
-
if(!fs.existsSync(abs)) return;
|
|
345
|
-
const doc = JSON.parse(fs.readFileSync(abs,'utf8'));
|
|
346
|
-
|
|
347
|
-
if(doc && doc.components && isPlainObject(doc.components.schemas)){
|
|
348
|
-
for(const name of Object.keys(doc.components.schemas)){
|
|
349
|
-
doc.components.schemas[name] = normalizeSchema(doc.components.schemas[name]);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
fs.writeFileSync(abs, JSON.stringify(doc,null,2),'utf8');
|
|
354
|
-
}
|
|
355
|
-
|
|
356
214
|
const docs = ${JSON.stringify(docs, null, 2)};
|
|
357
215
|
const routes = ${JSON.stringify(routes, null, 2)};
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
.then(() => fixOpenApiFile('${ensurePosix(CONFIG.openapiOut)}'))
|
|
361
|
-
.catch((e) => { console.error(e); process.exitCode = 1; });
|
|
362
|
-
`;
|
|
363
|
-
const outPath = import_node_path.default.resolve(CONFIG.projectRoot, CONFIG.outFile);
|
|
364
|
-
import_node_fs.default.writeFileSync(outPath, fileContent, "utf8");
|
|
216
|
+
swaggerAutogen('${ensurePosix(CONFIG.openapiOut)}', routes, docs);`;
|
|
217
|
+
import_node_fs.default.writeFileSync(import_node_path.default.resolve(CONFIG.projectRoot, CONFIG.outFile), fileContent, "utf8");
|
|
365
218
|
}
|
|
366
|
-
async function run(
|
|
367
|
-
const
|
|
368
|
-
const
|
|
219
|
+
async function run(args = []) {
|
|
220
|
+
const schemaFlagIndex = args.findIndex((a) => a === "--schema");
|
|
221
|
+
const schemaPath = schemaFlagIndex >= 0 ? args[schemaFlagIndex + 1] : void 0;
|
|
222
|
+
const schemas = buildSchemasFromPrismaDmmf(schemaPath);
|
|
369
223
|
generateSwaggerConfigJs(schemas);
|
|
370
224
|
}
|
|
371
225
|
// Annotate the CommonJS export names for ESM import in node:
|
package/dist/index.d.cts
CHANGED
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-swagger-autogen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
}
|
|
32
32
|
},
|
|
33
33
|
"bin": {
|
|
34
|
-
"prisma-swagger-autogen": "./dist/cli.
|
|
34
|
+
"prisma-swagger-autogen": "./dist/cli.cjs"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist",
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts --out-dir dist --tsconfig tsconfig.json",
|
|
43
43
|
"prepublishOnly": "npm run build"
|
|
44
44
|
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@prisma/internals": "^6.0.0"
|
|
47
|
+
},
|
|
45
48
|
"peerDependencies": {
|
|
46
49
|
"@prisma/client": ">=4",
|
|
47
50
|
"glob": ">=10",
|
package/dist/chunk-B6OADLRN.js
DELETED
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
// src/index.ts
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import { globSync } from "glob";
|
|
5
|
-
import { createRequire } from "module";
|
|
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 scalarToSchema(scalar) {
|
|
29
|
-
switch (scalar) {
|
|
30
|
-
case "String":
|
|
31
|
-
return { type: "string" };
|
|
32
|
-
case "Boolean":
|
|
33
|
-
return { type: "boolean" };
|
|
34
|
-
case "Int":
|
|
35
|
-
return { type: "integer" };
|
|
36
|
-
case "BigInt":
|
|
37
|
-
return { type: "integer", format: "int64" };
|
|
38
|
-
case "Float":
|
|
39
|
-
return { type: "number" };
|
|
40
|
-
case "Decimal":
|
|
41
|
-
return { type: "number" };
|
|
42
|
-
case "DateTime":
|
|
43
|
-
return { type: "string", format: "date-time" };
|
|
44
|
-
case "Json":
|
|
45
|
-
return { type: "object" };
|
|
46
|
-
case "Bytes":
|
|
47
|
-
return { type: "string", format: "byte" };
|
|
48
|
-
default:
|
|
49
|
-
return { type: "string" };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function fieldSchema(field, getRefName) {
|
|
53
|
-
if (field.kind === "scalar") {
|
|
54
|
-
const base = scalarToSchema(field.type);
|
|
55
|
-
if (field.isList) return { type: "array", items: base };
|
|
56
|
-
return base;
|
|
57
|
-
}
|
|
58
|
-
if (field.kind === "enum") {
|
|
59
|
-
const base = { $ref: `#/components/schemas/${field.type}` };
|
|
60
|
-
if (field.isList) return { type: "array", items: base };
|
|
61
|
-
return base;
|
|
62
|
-
}
|
|
63
|
-
if (field.kind === "object") {
|
|
64
|
-
const ref = { $ref: `#/components/schemas/${getRefName(String(field.type))}` };
|
|
65
|
-
if (field.isList) return { type: "array", items: ref };
|
|
66
|
-
return ref;
|
|
67
|
-
}
|
|
68
|
-
return { type: "object" };
|
|
69
|
-
}
|
|
70
|
-
function modelToGetSchema(model, getRefName) {
|
|
71
|
-
const properties = {};
|
|
72
|
-
const required = [];
|
|
73
|
-
for (const f of model.fields) {
|
|
74
|
-
properties[f.name] = fieldSchema(f, getRefName);
|
|
75
|
-
if (f.isRequired) required.push(f.name);
|
|
76
|
-
}
|
|
77
|
-
const schema = { type: "object", properties };
|
|
78
|
-
if (required.length) schema.required = required;
|
|
79
|
-
return schema;
|
|
80
|
-
}
|
|
81
|
-
function stripWriteFields(model, getSchema, omit) {
|
|
82
|
-
const schema = JSON.parse(JSON.stringify(getSchema));
|
|
83
|
-
if (!schema.properties) return schema;
|
|
84
|
-
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
85
|
-
for (const key of Object.keys(schema.properties)) {
|
|
86
|
-
if (omit.has(key) || relationFieldNames.has(key)) delete schema.properties[key];
|
|
87
|
-
}
|
|
88
|
-
if (Array.isArray(schema.required)) {
|
|
89
|
-
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
90
|
-
if (schema.required.length === 0) delete schema.required;
|
|
91
|
-
}
|
|
92
|
-
return schema;
|
|
93
|
-
}
|
|
94
|
-
function makeAllOptional(schema) {
|
|
95
|
-
const s = JSON.parse(JSON.stringify(schema));
|
|
96
|
-
delete s.required;
|
|
97
|
-
return s;
|
|
98
|
-
}
|
|
99
|
-
function listResponseSchema(itemRef) {
|
|
100
|
-
return {
|
|
101
|
-
type: "object",
|
|
102
|
-
properties: {
|
|
103
|
-
count: { type: "number", example: 3 },
|
|
104
|
-
hasPreviousPage: { type: "boolean", example: false },
|
|
105
|
-
hasNextPage: { type: "boolean", example: true },
|
|
106
|
-
pageNumber: { type: "number", example: 1 },
|
|
107
|
-
pageSize: { type: "number", example: 10 },
|
|
108
|
-
totalPages: { type: "number", example: 1 },
|
|
109
|
-
items: { type: "array", items: { $ref: itemRef } }
|
|
110
|
-
},
|
|
111
|
-
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
function exampleForScalarType(type, format) {
|
|
115
|
-
if (type === "string" && format === "date-time") return (/* @__PURE__ */ new Date(0)).toISOString();
|
|
116
|
-
switch (type) {
|
|
117
|
-
case "string":
|
|
118
|
-
return "string";
|
|
119
|
-
case "integer":
|
|
120
|
-
return 0;
|
|
121
|
-
case "number":
|
|
122
|
-
return 0;
|
|
123
|
-
case "boolean":
|
|
124
|
-
return true;
|
|
125
|
-
case "object":
|
|
126
|
-
return {};
|
|
127
|
-
default:
|
|
128
|
-
return null;
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
function buildExampleFromSchema(schema, components, depth = 0) {
|
|
132
|
-
if (depth > 2) return void 0;
|
|
133
|
-
if (schema.$ref) {
|
|
134
|
-
const name = String(schema.$ref).split("/").pop() || "";
|
|
135
|
-
const target = components[name];
|
|
136
|
-
if (!target) return void 0;
|
|
137
|
-
return buildExampleFromSchema(target, components, depth + 1);
|
|
138
|
-
}
|
|
139
|
-
if (Array.isArray(schema.allOf) && schema.allOf.length) {
|
|
140
|
-
const merged = {};
|
|
141
|
-
for (const part of schema.allOf) {
|
|
142
|
-
const ex = buildExampleFromSchema(part, components, depth + 1);
|
|
143
|
-
if (ex && typeof ex === "object" && !Array.isArray(ex)) Object.assign(merged, ex);
|
|
144
|
-
}
|
|
145
|
-
return Object.keys(merged).length ? merged : void 0;
|
|
146
|
-
}
|
|
147
|
-
if (schema.type === "array" && schema.items) {
|
|
148
|
-
const item = buildExampleFromSchema(schema.items, components, depth + 1);
|
|
149
|
-
return item === void 0 ? [] : [item];
|
|
150
|
-
}
|
|
151
|
-
if (schema.type === "object" && schema.properties) {
|
|
152
|
-
const obj = {};
|
|
153
|
-
for (const [k, v] of Object.entries(schema.properties)) {
|
|
154
|
-
const ex = buildExampleFromSchema(v, components, depth + 1);
|
|
155
|
-
if (ex !== void 0) obj[k] = ex;
|
|
156
|
-
}
|
|
157
|
-
return obj;
|
|
158
|
-
}
|
|
159
|
-
if (Array.isArray(schema.enum) && schema.enum.length) return schema.enum[0];
|
|
160
|
-
if (typeof schema.type === "string") return exampleForScalarType(schema.type, schema.format);
|
|
161
|
-
return void 0;
|
|
162
|
-
}
|
|
163
|
-
function attachExample(schema, components) {
|
|
164
|
-
const s = JSON.parse(JSON.stringify(schema));
|
|
165
|
-
if (s.example === void 0) {
|
|
166
|
-
const ex = buildExampleFromSchema(s, components);
|
|
167
|
-
if (ex !== void 0) s.example = ex;
|
|
168
|
-
}
|
|
169
|
-
return s;
|
|
170
|
-
}
|
|
171
|
-
function loadDmmfFromProject() {
|
|
172
|
-
const schemaPath = path.resolve(process.cwd(), "prisma/schema.prisma");
|
|
173
|
-
if (!fs.existsSync(schemaPath)) {
|
|
174
|
-
throw new Error(`Prisma schema not found at: ${schemaPath}`);
|
|
175
|
-
}
|
|
176
|
-
const datamodel = fs.readFileSync(schemaPath, "utf8");
|
|
177
|
-
const require2 = createRequire(import.meta.url);
|
|
178
|
-
const tryLoad = (id) => {
|
|
179
|
-
try {
|
|
180
|
-
return require2(id);
|
|
181
|
-
} catch {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const runtime = tryLoad("@prisma/client/runtime/library") ?? tryLoad("@prisma/client/runtime");
|
|
186
|
-
if (!runtime || typeof runtime.getDMMF !== "function") {
|
|
187
|
-
throw new Error(
|
|
188
|
-
`Unable to load Prisma runtime getDMMF(). Ensure @prisma/client is installed in the target project.
|
|
189
|
-
Tried: @prisma/client/runtime/library and @prisma/client/runtime`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
const dmmf = runtime.getDMMF({ datamodel });
|
|
193
|
-
if (!dmmf?.datamodel?.models) throw new Error("Failed to load Prisma DMMF (unexpected structure).");
|
|
194
|
-
return dmmf;
|
|
195
|
-
}
|
|
196
|
-
function buildSchemasFromDmmf(dmmf) {
|
|
197
|
-
const schemas = {};
|
|
198
|
-
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
199
|
-
for (const e of dmmf.datamodel.enums) {
|
|
200
|
-
schemas[e.name] = { type: "string", enum: e.values.map((v) => v.name) };
|
|
201
|
-
}
|
|
202
|
-
for (const model of dmmf.datamodel.models) {
|
|
203
|
-
const getName = `Get${model.name}Response`;
|
|
204
|
-
const postName = `Post${model.name}Request`;
|
|
205
|
-
const putName = `Put${model.name}Request`;
|
|
206
|
-
const listName = `List${pluralize(model.name)}Response`;
|
|
207
|
-
const getSchema = modelToGetSchema(model, getRefName);
|
|
208
|
-
const postSchema = stripWriteFields(model, getSchema, CONFIG.omitFieldsInWriteDtos);
|
|
209
|
-
const putSchema = makeAllOptional(postSchema);
|
|
210
|
-
schemas[getName] = getSchema;
|
|
211
|
-
schemas[postName] = postSchema;
|
|
212
|
-
schemas[putName] = putSchema;
|
|
213
|
-
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
214
|
-
}
|
|
215
|
-
schemas["ExceptionResponse"] = {
|
|
216
|
-
type: "object",
|
|
217
|
-
properties: {
|
|
218
|
-
detail: { type: "string" },
|
|
219
|
-
errors: { type: "array", items: { type: "string" } },
|
|
220
|
-
status: { type: "number" },
|
|
221
|
-
title: { type: "string" },
|
|
222
|
-
type: { type: "string" }
|
|
223
|
-
},
|
|
224
|
-
required: ["status", "title", "type"]
|
|
225
|
-
};
|
|
226
|
-
schemas["BadRequestResponse"] = {
|
|
227
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
228
|
-
example: {
|
|
229
|
-
status: 400,
|
|
230
|
-
title: "The request was invalid",
|
|
231
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.5.1"
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
schemas["NotFoundResponse"] = {
|
|
235
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
236
|
-
example: {
|
|
237
|
-
status: 404,
|
|
238
|
-
title: "The specified resource was not found",
|
|
239
|
-
type: "https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4"
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
schemas["InternalErrorResponse"] = {
|
|
243
|
-
allOf: [{ $ref: "#/components/schemas/ExceptionResponse" }],
|
|
244
|
-
example: {
|
|
245
|
-
status: 500,
|
|
246
|
-
title: "An error occurred while processing your request",
|
|
247
|
-
type: "https://tools.ietf.org/html/rfc7231#section-6.6.1"
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
for (const name of Object.keys(schemas)) {
|
|
251
|
-
if (name.startsWith("Post") && name.endsWith("Request")) schemas[name] = attachExample(schemas[name], schemas);
|
|
252
|
-
if (name.startsWith("Put") && name.endsWith("Request")) schemas[name] = attachExample(schemas[name], schemas);
|
|
253
|
-
}
|
|
254
|
-
return schemas;
|
|
255
|
-
}
|
|
256
|
-
function generateSwaggerConfigJs(schemas) {
|
|
257
|
-
const routes = globSync(CONFIG.controllersGlob, { nodir: true }).map((p) => ensurePosix(p));
|
|
258
|
-
const docs = {
|
|
259
|
-
info: { title: CONFIG.serviceTitle },
|
|
260
|
-
servers: [{ url: CONFIG.serverUrl }],
|
|
261
|
-
components: {
|
|
262
|
-
schemas,
|
|
263
|
-
securitySchemes: {
|
|
264
|
-
[CONFIG.securitySchemeName]: {
|
|
265
|
-
type: "oauth2",
|
|
266
|
-
description: "This API uses OAuth2 with the password flow.",
|
|
267
|
-
flows: {
|
|
268
|
-
password: {
|
|
269
|
-
tokenUrl: CONFIG.oauth.tokenUrl,
|
|
270
|
-
refreshUrl: CONFIG.oauth.refreshUrl,
|
|
271
|
-
scopes: CONFIG.oauth.scopes
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
},
|
|
277
|
-
security: [{ [CONFIG.securitySchemeName]: ["openid"] }]
|
|
278
|
-
};
|
|
279
|
-
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
280
|
-
const fs = require('fs');
|
|
281
|
-
const path = require('node:path');
|
|
282
|
-
|
|
283
|
-
function isPlainObject(v){return v!==null && typeof v==='object' && !Array.isArray(v);}
|
|
284
|
-
|
|
285
|
-
function normalizeSchema(schema){
|
|
286
|
-
if(!isPlainObject(schema)) return schema;
|
|
287
|
-
if(schema.$ref) return schema;
|
|
288
|
-
|
|
289
|
-
if(isPlainObject(schema.type) && typeof schema.type.example === 'string') schema.type = schema.type.example;
|
|
290
|
-
if(isPlainObject(schema.format) && typeof schema.format.example === 'string') schema.format = schema.format.example;
|
|
291
|
-
if(isPlainObject(schema.required) && Array.isArray(schema.required.example)) schema.required = schema.required.example;
|
|
292
|
-
if(isPlainObject(schema.enum) && Array.isArray(schema.enum.example)) schema.enum = schema.enum.example;
|
|
293
|
-
|
|
294
|
-
if(Array.isArray(schema.allOf)) schema.allOf = schema.allOf.map(normalizeSchema);
|
|
295
|
-
if(isPlainObject(schema.items)) schema.items = normalizeSchema(schema.items);
|
|
296
|
-
if(isPlainObject(schema.additionalProperties)) schema.additionalProperties = normalizeSchema(schema.additionalProperties);
|
|
297
|
-
|
|
298
|
-
if(isPlainObject(schema.properties)){
|
|
299
|
-
for(const k of Object.keys(schema.properties)){
|
|
300
|
-
schema.properties[k] = normalizeSchema(schema.properties[k]);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return schema;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
function fixOpenApiFile(openapiPath){
|
|
308
|
-
const abs = path.resolve(process.cwd(), openapiPath);
|
|
309
|
-
if(!fs.existsSync(abs)) return;
|
|
310
|
-
const doc = JSON.parse(fs.readFileSync(abs,'utf8'));
|
|
311
|
-
|
|
312
|
-
if(doc && doc.components && isPlainObject(doc.components.schemas)){
|
|
313
|
-
for(const name of Object.keys(doc.components.schemas)){
|
|
314
|
-
doc.components.schemas[name] = normalizeSchema(doc.components.schemas[name]);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
fs.writeFileSync(abs, JSON.stringify(doc,null,2),'utf8');
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const docs = ${JSON.stringify(docs, null, 2)};
|
|
322
|
-
const routes = ${JSON.stringify(routes, null, 2)};
|
|
323
|
-
|
|
324
|
-
swaggerAutogen('${ensurePosix(CONFIG.openapiOut)}', routes, docs)
|
|
325
|
-
.then(() => fixOpenApiFile('${ensurePosix(CONFIG.openapiOut)}'))
|
|
326
|
-
.catch((e) => { console.error(e); process.exitCode = 1; });
|
|
327
|
-
`;
|
|
328
|
-
const outPath = path.resolve(CONFIG.projectRoot, CONFIG.outFile);
|
|
329
|
-
fs.writeFileSync(outPath, fileContent, "utf8");
|
|
330
|
-
}
|
|
331
|
-
async function run(_args) {
|
|
332
|
-
const dmmf = loadDmmfFromProject();
|
|
333
|
-
const schemas = buildSchemasFromDmmf(dmmf);
|
|
334
|
-
generateSwaggerConfigJs(schemas);
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
export {
|
|
338
|
-
run
|
|
339
|
-
};
|