prisma-swagger-autogen 1.0.4 → 1.0.6
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/chunk-LHCOAEWQ.cjs +193 -0
- package/dist/cli.cjs +1 -0
- package/dist/cli.js +191 -3
- package/dist/index.cjs +1 -0
- package/dist/index.js +191 -3
- package/package.json +6 -6
package/dist/bin.cjs
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }// src/index.ts
|
|
2
|
+
var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);
|
|
3
|
+
var _path = require('path'); var _path2 = _interopRequireDefault(_path);
|
|
4
|
+
var _module = require('module');
|
|
5
|
+
var _glob = require('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(_path2.default.sep).join(_path2.default.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 _module.createRequire.call(void 0, base);
|
|
31
|
+
}
|
|
32
|
+
function loadDmmfFromProject(schemaPath) {
|
|
33
|
+
const resolvedSchemaPath = schemaPath ? _path2.default.resolve(process.cwd(), schemaPath) : _path2.default.resolve(process.cwd(), "prisma/schema.prisma");
|
|
34
|
+
if (!_fs2.default.existsSync(resolvedSchemaPath)) {
|
|
35
|
+
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
36
|
+
}
|
|
37
|
+
const datamodel = _fs2.default.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 = _glob.globSync.call(void 0, 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
|
+
_fs2.default.writeFileSync(_path2.default.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
|
+
|
|
192
|
+
|
|
193
|
+
exports.run = run;
|
package/dist/cli.cjs
CHANGED
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,194 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import { globSync } from "glob";
|
|
8
|
+
var CONFIG = {
|
|
9
|
+
projectRoot: process.cwd(),
|
|
10
|
+
controllersGlob: "./src/web/api/controllers/**/*.ts",
|
|
11
|
+
outFile: "./swagger.config.js",
|
|
12
|
+
openapiOut: "./src/web/api/openapi.json",
|
|
13
|
+
serviceTitle: "Prescription Service",
|
|
14
|
+
serverUrl: "http://localhost:3008",
|
|
15
|
+
securitySchemeName: "keycloakOAuth",
|
|
16
|
+
oauth: {
|
|
17
|
+
tokenUrl: "http://auth.localhost/realms/haemo/protocol/openid-connect/token",
|
|
18
|
+
refreshUrl: "http://auth.localhost/realms/haemo/protocol/openid-connect/refresh",
|
|
19
|
+
scopes: { openid: "openid scope" }
|
|
20
|
+
},
|
|
21
|
+
omitFieldsInWriteDtos: /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "v"])
|
|
22
|
+
};
|
|
23
|
+
function ensurePosix(p) {
|
|
24
|
+
return p.split(path.sep).join(path.posix.sep);
|
|
25
|
+
}
|
|
26
|
+
function pluralize(name) {
|
|
27
|
+
if (name.endsWith("s")) return `${name}es`;
|
|
28
|
+
return `${name}s`;
|
|
29
|
+
}
|
|
30
|
+
function getRequire() {
|
|
31
|
+
const base = typeof __filename !== "undefined" ? __filename : import.meta.url;
|
|
32
|
+
return createRequire(base);
|
|
33
|
+
}
|
|
34
|
+
function loadDmmfFromProject(schemaPath) {
|
|
35
|
+
const resolvedSchemaPath = schemaPath ? path.resolve(process.cwd(), schemaPath) : path.resolve(process.cwd(), "prisma/schema.prisma");
|
|
36
|
+
if (!fs.existsSync(resolvedSchemaPath)) {
|
|
37
|
+
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
38
|
+
}
|
|
39
|
+
const datamodel = fs.readFileSync(resolvedSchemaPath, "utf8");
|
|
40
|
+
const require2 = getRequire();
|
|
41
|
+
const internals = require2("@prisma/internals");
|
|
42
|
+
if (typeof internals.getDMMF !== "function") {
|
|
43
|
+
throw new Error(`@prisma/internals.getDMMF not available`);
|
|
44
|
+
}
|
|
45
|
+
return internals.getDMMF({ datamodel });
|
|
46
|
+
}
|
|
47
|
+
function scalarToSchema(scalar) {
|
|
48
|
+
switch (scalar) {
|
|
49
|
+
case "String":
|
|
50
|
+
return { type: "string" };
|
|
51
|
+
case "Boolean":
|
|
52
|
+
return { type: "boolean" };
|
|
53
|
+
case "Int":
|
|
54
|
+
return { type: "integer" };
|
|
55
|
+
case "BigInt":
|
|
56
|
+
return { type: "integer", format: "int64" };
|
|
57
|
+
case "Float":
|
|
58
|
+
return { type: "number" };
|
|
59
|
+
case "Decimal":
|
|
60
|
+
return { type: "number" };
|
|
61
|
+
case "DateTime":
|
|
62
|
+
return { type: "string", format: "date-time" };
|
|
63
|
+
case "Json":
|
|
64
|
+
return { type: "object" };
|
|
65
|
+
case "Bytes":
|
|
66
|
+
return { type: "string", format: "byte" };
|
|
67
|
+
default:
|
|
68
|
+
return { type: "string" };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function fieldSchema(field, getRefName) {
|
|
72
|
+
if (field.kind === "scalar") {
|
|
73
|
+
const base = scalarToSchema(field.type);
|
|
74
|
+
if (field.isList) return { type: "array", items: base };
|
|
75
|
+
return base;
|
|
76
|
+
}
|
|
77
|
+
if (field.kind === "enum") {
|
|
78
|
+
const base = { $ref: `#/components/schemas/${field.type}` };
|
|
79
|
+
if (field.isList) return { type: "array", items: base };
|
|
80
|
+
return base;
|
|
81
|
+
}
|
|
82
|
+
if (field.kind === "object") {
|
|
83
|
+
const ref = { $ref: `#/components/schemas/${getRefName(String(field.type))}` };
|
|
84
|
+
if (field.isList) return { type: "array", items: ref };
|
|
85
|
+
return ref;
|
|
86
|
+
}
|
|
87
|
+
return { type: "object" };
|
|
88
|
+
}
|
|
89
|
+
function modelToGetSchema(model, getRefName) {
|
|
90
|
+
const properties = {};
|
|
91
|
+
const required = [];
|
|
92
|
+
for (const f of model.fields) {
|
|
93
|
+
properties[f.name] = fieldSchema(f, getRefName);
|
|
94
|
+
if (f.isRequired) required.push(f.name);
|
|
95
|
+
}
|
|
96
|
+
const schema = { type: "object", properties };
|
|
97
|
+
if (required.length) schema.required = required;
|
|
98
|
+
return schema;
|
|
99
|
+
}
|
|
100
|
+
function stripWriteFields(model, getSchema, omit) {
|
|
101
|
+
const schema = JSON.parse(JSON.stringify(getSchema));
|
|
102
|
+
if (!schema.properties) return schema;
|
|
103
|
+
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
104
|
+
for (const key of Object.keys(schema.properties)) {
|
|
105
|
+
if (omit.has(key) || relationFieldNames.has(key)) {
|
|
106
|
+
delete schema.properties[key];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(schema.required)) {
|
|
110
|
+
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
111
|
+
if (schema.required.length === 0) delete schema.required;
|
|
112
|
+
}
|
|
113
|
+
return schema;
|
|
114
|
+
}
|
|
115
|
+
function makeAllOptional(schema) {
|
|
116
|
+
const s = JSON.parse(JSON.stringify(schema));
|
|
117
|
+
delete s.required;
|
|
118
|
+
return s;
|
|
119
|
+
}
|
|
120
|
+
function listResponseSchema(itemRef) {
|
|
121
|
+
return {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
count: { type: "number" },
|
|
125
|
+
hasPreviousPage: { type: "boolean" },
|
|
126
|
+
hasNextPage: { type: "boolean" },
|
|
127
|
+
pageNumber: { type: "number" },
|
|
128
|
+
pageSize: { type: "number" },
|
|
129
|
+
totalPages: { type: "number" },
|
|
130
|
+
items: { type: "array", items: { $ref: itemRef } }
|
|
131
|
+
},
|
|
132
|
+
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function buildSchemasFromPrismaDmmf(schemaPath) {
|
|
136
|
+
const dmmf = loadDmmfFromProject(schemaPath);
|
|
137
|
+
const schemas = {};
|
|
138
|
+
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
139
|
+
for (const e of dmmf.datamodel.enums) {
|
|
140
|
+
schemas[e.name] = { type: "string", enum: e.values.map((v) => v.name) };
|
|
141
|
+
}
|
|
142
|
+
for (const model of dmmf.datamodel.models) {
|
|
143
|
+
const getName = `Get${model.name}Response`;
|
|
144
|
+
const postName = `Post${model.name}Request`;
|
|
145
|
+
const putName = `Put${model.name}Request`;
|
|
146
|
+
const listName = `List${pluralize(model.name)}Response`;
|
|
147
|
+
const getSchema = modelToGetSchema(model, getRefName);
|
|
148
|
+
const postSchema = stripWriteFields(model, getSchema, CONFIG.omitFieldsInWriteDtos);
|
|
149
|
+
const putSchema = makeAllOptional(postSchema);
|
|
150
|
+
schemas[getName] = getSchema;
|
|
151
|
+
schemas[postName] = postSchema;
|
|
152
|
+
schemas[putName] = putSchema;
|
|
153
|
+
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
154
|
+
}
|
|
155
|
+
return schemas;
|
|
156
|
+
}
|
|
157
|
+
function generateSwaggerConfigJs(schemas) {
|
|
158
|
+
const routes = globSync(CONFIG.controllersGlob, { nodir: true }).map((p) => ensurePosix(p));
|
|
159
|
+
const docs = {
|
|
160
|
+
info: { title: CONFIG.serviceTitle },
|
|
161
|
+
servers: [{ url: CONFIG.serverUrl }],
|
|
162
|
+
components: {
|
|
163
|
+
schemas,
|
|
164
|
+
securitySchemes: {
|
|
165
|
+
[CONFIG.securitySchemeName]: {
|
|
166
|
+
type: "oauth2",
|
|
167
|
+
description: "This API uses OAuth2 with the password flow.",
|
|
168
|
+
flows: {
|
|
169
|
+
password: {
|
|
170
|
+
tokenUrl: CONFIG.oauth.tokenUrl,
|
|
171
|
+
refreshUrl: CONFIG.oauth.refreshUrl,
|
|
172
|
+
scopes: CONFIG.oauth.scopes
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
security: [{ [CONFIG.securitySchemeName]: ["openid"] }]
|
|
179
|
+
};
|
|
180
|
+
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
181
|
+
const docs = ${JSON.stringify(docs, null, 2)};
|
|
182
|
+
const routes = ${JSON.stringify(routes, null, 2)};
|
|
183
|
+
swaggerAutogen('${ensurePosix(CONFIG.openapiOut)}', routes, docs);`;
|
|
184
|
+
fs.writeFileSync(path.resolve(CONFIG.projectRoot, CONFIG.outFile), fileContent, "utf8");
|
|
185
|
+
}
|
|
186
|
+
async function run(args = []) {
|
|
187
|
+
const schemaFlagIndex = args.findIndex((a) => a === "--schema");
|
|
188
|
+
const schemaPath = schemaFlagIndex >= 0 ? args[schemaFlagIndex + 1] : void 0;
|
|
189
|
+
const schemas = buildSchemasFromPrismaDmmf(schemaPath);
|
|
190
|
+
generateSwaggerConfigJs(schemas);
|
|
191
|
+
}
|
|
4
192
|
|
|
5
193
|
// src/cli.ts
|
|
6
194
|
void run(process.argv.slice(2)).catch((e) => {
|
package/dist/index.cjs
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,194 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { createRequire } from "module";
|
|
7
|
+
import { globSync } from "glob";
|
|
8
|
+
var CONFIG = {
|
|
9
|
+
projectRoot: process.cwd(),
|
|
10
|
+
controllersGlob: "./src/web/api/controllers/**/*.ts",
|
|
11
|
+
outFile: "./swagger.config.js",
|
|
12
|
+
openapiOut: "./src/web/api/openapi.json",
|
|
13
|
+
serviceTitle: "Prescription Service",
|
|
14
|
+
serverUrl: "http://localhost:3008",
|
|
15
|
+
securitySchemeName: "keycloakOAuth",
|
|
16
|
+
oauth: {
|
|
17
|
+
tokenUrl: "http://auth.localhost/realms/haemo/protocol/openid-connect/token",
|
|
18
|
+
refreshUrl: "http://auth.localhost/realms/haemo/protocol/openid-connect/refresh",
|
|
19
|
+
scopes: { openid: "openid scope" }
|
|
20
|
+
},
|
|
21
|
+
omitFieldsInWriteDtos: /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "v"])
|
|
22
|
+
};
|
|
23
|
+
function ensurePosix(p) {
|
|
24
|
+
return p.split(path.sep).join(path.posix.sep);
|
|
25
|
+
}
|
|
26
|
+
function pluralize(name) {
|
|
27
|
+
if (name.endsWith("s")) return `${name}es`;
|
|
28
|
+
return `${name}s`;
|
|
29
|
+
}
|
|
30
|
+
function getRequire() {
|
|
31
|
+
const base = typeof __filename !== "undefined" ? __filename : import.meta.url;
|
|
32
|
+
return createRequire(base);
|
|
33
|
+
}
|
|
34
|
+
function loadDmmfFromProject(schemaPath) {
|
|
35
|
+
const resolvedSchemaPath = schemaPath ? path.resolve(process.cwd(), schemaPath) : path.resolve(process.cwd(), "prisma/schema.prisma");
|
|
36
|
+
if (!fs.existsSync(resolvedSchemaPath)) {
|
|
37
|
+
throw new Error(`Prisma schema not found at ${resolvedSchemaPath}`);
|
|
38
|
+
}
|
|
39
|
+
const datamodel = fs.readFileSync(resolvedSchemaPath, "utf8");
|
|
40
|
+
const require2 = getRequire();
|
|
41
|
+
const internals = require2("@prisma/internals");
|
|
42
|
+
if (typeof internals.getDMMF !== "function") {
|
|
43
|
+
throw new Error(`@prisma/internals.getDMMF not available`);
|
|
44
|
+
}
|
|
45
|
+
return internals.getDMMF({ datamodel });
|
|
46
|
+
}
|
|
47
|
+
function scalarToSchema(scalar) {
|
|
48
|
+
switch (scalar) {
|
|
49
|
+
case "String":
|
|
50
|
+
return { type: "string" };
|
|
51
|
+
case "Boolean":
|
|
52
|
+
return { type: "boolean" };
|
|
53
|
+
case "Int":
|
|
54
|
+
return { type: "integer" };
|
|
55
|
+
case "BigInt":
|
|
56
|
+
return { type: "integer", format: "int64" };
|
|
57
|
+
case "Float":
|
|
58
|
+
return { type: "number" };
|
|
59
|
+
case "Decimal":
|
|
60
|
+
return { type: "number" };
|
|
61
|
+
case "DateTime":
|
|
62
|
+
return { type: "string", format: "date-time" };
|
|
63
|
+
case "Json":
|
|
64
|
+
return { type: "object" };
|
|
65
|
+
case "Bytes":
|
|
66
|
+
return { type: "string", format: "byte" };
|
|
67
|
+
default:
|
|
68
|
+
return { type: "string" };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function fieldSchema(field, getRefName) {
|
|
72
|
+
if (field.kind === "scalar") {
|
|
73
|
+
const base = scalarToSchema(field.type);
|
|
74
|
+
if (field.isList) return { type: "array", items: base };
|
|
75
|
+
return base;
|
|
76
|
+
}
|
|
77
|
+
if (field.kind === "enum") {
|
|
78
|
+
const base = { $ref: `#/components/schemas/${field.type}` };
|
|
79
|
+
if (field.isList) return { type: "array", items: base };
|
|
80
|
+
return base;
|
|
81
|
+
}
|
|
82
|
+
if (field.kind === "object") {
|
|
83
|
+
const ref = { $ref: `#/components/schemas/${getRefName(String(field.type))}` };
|
|
84
|
+
if (field.isList) return { type: "array", items: ref };
|
|
85
|
+
return ref;
|
|
86
|
+
}
|
|
87
|
+
return { type: "object" };
|
|
88
|
+
}
|
|
89
|
+
function modelToGetSchema(model, getRefName) {
|
|
90
|
+
const properties = {};
|
|
91
|
+
const required = [];
|
|
92
|
+
for (const f of model.fields) {
|
|
93
|
+
properties[f.name] = fieldSchema(f, getRefName);
|
|
94
|
+
if (f.isRequired) required.push(f.name);
|
|
95
|
+
}
|
|
96
|
+
const schema = { type: "object", properties };
|
|
97
|
+
if (required.length) schema.required = required;
|
|
98
|
+
return schema;
|
|
99
|
+
}
|
|
100
|
+
function stripWriteFields(model, getSchema, omit) {
|
|
101
|
+
const schema = JSON.parse(JSON.stringify(getSchema));
|
|
102
|
+
if (!schema.properties) return schema;
|
|
103
|
+
const relationFieldNames = new Set(model.fields.filter((f) => f.kind === "object").map((f) => f.name));
|
|
104
|
+
for (const key of Object.keys(schema.properties)) {
|
|
105
|
+
if (omit.has(key) || relationFieldNames.has(key)) {
|
|
106
|
+
delete schema.properties[key];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (Array.isArray(schema.required)) {
|
|
110
|
+
schema.required = schema.required.filter((k) => !omit.has(k) && !relationFieldNames.has(k));
|
|
111
|
+
if (schema.required.length === 0) delete schema.required;
|
|
112
|
+
}
|
|
113
|
+
return schema;
|
|
114
|
+
}
|
|
115
|
+
function makeAllOptional(schema) {
|
|
116
|
+
const s = JSON.parse(JSON.stringify(schema));
|
|
117
|
+
delete s.required;
|
|
118
|
+
return s;
|
|
119
|
+
}
|
|
120
|
+
function listResponseSchema(itemRef) {
|
|
121
|
+
return {
|
|
122
|
+
type: "object",
|
|
123
|
+
properties: {
|
|
124
|
+
count: { type: "number" },
|
|
125
|
+
hasPreviousPage: { type: "boolean" },
|
|
126
|
+
hasNextPage: { type: "boolean" },
|
|
127
|
+
pageNumber: { type: "number" },
|
|
128
|
+
pageSize: { type: "number" },
|
|
129
|
+
totalPages: { type: "number" },
|
|
130
|
+
items: { type: "array", items: { $ref: itemRef } }
|
|
131
|
+
},
|
|
132
|
+
required: ["count", "hasPreviousPage", "hasNextPage", "pageNumber", "pageSize", "totalPages", "items"]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
function buildSchemasFromPrismaDmmf(schemaPath) {
|
|
136
|
+
const dmmf = loadDmmfFromProject(schemaPath);
|
|
137
|
+
const schemas = {};
|
|
138
|
+
const getRefName = (modelName) => `Get${modelName}Response`;
|
|
139
|
+
for (const e of dmmf.datamodel.enums) {
|
|
140
|
+
schemas[e.name] = { type: "string", enum: e.values.map((v) => v.name) };
|
|
141
|
+
}
|
|
142
|
+
for (const model of dmmf.datamodel.models) {
|
|
143
|
+
const getName = `Get${model.name}Response`;
|
|
144
|
+
const postName = `Post${model.name}Request`;
|
|
145
|
+
const putName = `Put${model.name}Request`;
|
|
146
|
+
const listName = `List${pluralize(model.name)}Response`;
|
|
147
|
+
const getSchema = modelToGetSchema(model, getRefName);
|
|
148
|
+
const postSchema = stripWriteFields(model, getSchema, CONFIG.omitFieldsInWriteDtos);
|
|
149
|
+
const putSchema = makeAllOptional(postSchema);
|
|
150
|
+
schemas[getName] = getSchema;
|
|
151
|
+
schemas[postName] = postSchema;
|
|
152
|
+
schemas[putName] = putSchema;
|
|
153
|
+
schemas[listName] = listResponseSchema(`#/components/schemas/${getName}`);
|
|
154
|
+
}
|
|
155
|
+
return schemas;
|
|
156
|
+
}
|
|
157
|
+
function generateSwaggerConfigJs(schemas) {
|
|
158
|
+
const routes = globSync(CONFIG.controllersGlob, { nodir: true }).map((p) => ensurePosix(p));
|
|
159
|
+
const docs = {
|
|
160
|
+
info: { title: CONFIG.serviceTitle },
|
|
161
|
+
servers: [{ url: CONFIG.serverUrl }],
|
|
162
|
+
components: {
|
|
163
|
+
schemas,
|
|
164
|
+
securitySchemes: {
|
|
165
|
+
[CONFIG.securitySchemeName]: {
|
|
166
|
+
type: "oauth2",
|
|
167
|
+
description: "This API uses OAuth2 with the password flow.",
|
|
168
|
+
flows: {
|
|
169
|
+
password: {
|
|
170
|
+
tokenUrl: CONFIG.oauth.tokenUrl,
|
|
171
|
+
refreshUrl: CONFIG.oauth.refreshUrl,
|
|
172
|
+
scopes: CONFIG.oauth.scopes
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
security: [{ [CONFIG.securitySchemeName]: ["openid"] }]
|
|
179
|
+
};
|
|
180
|
+
const fileContent = `const swaggerAutogen = require('swagger-autogen')();
|
|
181
|
+
const docs = ${JSON.stringify(docs, null, 2)};
|
|
182
|
+
const routes = ${JSON.stringify(routes, null, 2)};
|
|
183
|
+
swaggerAutogen('${ensurePosix(CONFIG.openapiOut)}', routes, docs);`;
|
|
184
|
+
fs.writeFileSync(path.resolve(CONFIG.projectRoot, CONFIG.outFile), fileContent, "utf8");
|
|
185
|
+
}
|
|
186
|
+
async function run(args = []) {
|
|
187
|
+
const schemaFlagIndex = args.findIndex((a) => a === "--schema");
|
|
188
|
+
const schemaPath = schemaFlagIndex >= 0 ? args[schemaFlagIndex + 1] : void 0;
|
|
189
|
+
const schemas = buildSchemasFromPrismaDmmf(schemaPath);
|
|
190
|
+
generateSwaggerConfigJs(schemas);
|
|
191
|
+
}
|
|
4
192
|
export {
|
|
5
193
|
run
|
|
6
194
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "prisma-swagger-autogen",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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": "
|
|
34
|
+
"prisma-swagger-autogen": "dist/bin.cjs"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"dist",
|
|
@@ -39,20 +39,20 @@
|
|
|
39
39
|
"LICENSE"
|
|
40
40
|
],
|
|
41
41
|
"scripts": {
|
|
42
|
-
"build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts --out-dir dist --tsconfig tsconfig.json",
|
|
42
|
+
"build": "tsup src/index.ts src/cli.ts --format esm,cjs --dts --out-dir dist --tsconfig tsconfig.json && node scripts/make-bin.cjs && chmod +x dist/bin.cjs",
|
|
43
43
|
"prepublishOnly": "npm run build"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@prisma/internals": "^6.0.0"
|
|
46
|
+
"@prisma/internals": "^6.0.0",
|
|
47
|
+
"glob": "^10.3.10"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
50
|
"@prisma/client": ">=4",
|
|
50
|
-
"glob": ">=10",
|
|
51
51
|
"swagger-autogen": ">=2"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@types/node": "^25.1.0",
|
|
55
|
-
"tsup": "^8.
|
|
55
|
+
"tsup": "^8.5.1",
|
|
56
56
|
"typescript": "^5.0.0"
|
|
57
57
|
}
|
|
58
58
|
}
|