init
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
const collectionPath = process.argv[2] || "postman/collection.json";
|
||||
const outputPath = "open-api/schema.json";
|
||||
|
||||
// ── Schema inference from JSON examples ─────────────────────────────
|
||||
|
||||
function inferSchema(value) {
|
||||
if (value === null || value === undefined) {
|
||||
return { type: "string", nullable: true };
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return { type: "boolean" };
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return Number.isInteger(value) ? { type: "integer" } : { type: "number" };
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
if (/^\d{4}-\d{2}-\d{2}T/.test(value)) {
|
||||
return { type: "string", format: "date-time" };
|
||||
}
|
||||
return { type: "string" };
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
return { type: "array", items: {} };
|
||||
}
|
||||
return { type: "array", items: inferSchema(value[0]) };
|
||||
}
|
||||
if (typeof value === "object") {
|
||||
const properties = {};
|
||||
for (const [key, val] of Object.entries(value)) {
|
||||
properties[key] = inferSchema(val);
|
||||
}
|
||||
return { type: "object", properties };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// ── Path helpers ────────────────────────────────────────────────────
|
||||
|
||||
function extractPath(url) {
|
||||
const parts = url.path || [];
|
||||
const raw = "/" + parts.join("/");
|
||||
return raw.replace(/\{\{(\w+)\}\}/g, "{$1}");
|
||||
}
|
||||
|
||||
function extractPathParams(apiPath) {
|
||||
const params = [];
|
||||
const re = /\{(\w+)\}/g;
|
||||
let m;
|
||||
while ((m = re.exec(apiPath)) !== null) {
|
||||
params.push({
|
||||
name: m[1],
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: { type: "string" },
|
||||
});
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
// ── Request body ────────────────────────────────────────────────────
|
||||
|
||||
function buildRequestBody(body) {
|
||||
if (!body) return undefined;
|
||||
|
||||
if (body.mode === "raw" && body.raw) {
|
||||
try {
|
||||
const parsed = JSON.parse(body.raw);
|
||||
return {
|
||||
required: true,
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: inferSchema(parsed),
|
||||
example: parsed,
|
||||
},
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
content: {
|
||||
"text/plain": { schema: { type: "string" } },
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (body.mode === "formdata" && body.formdata) {
|
||||
const properties = {};
|
||||
for (const field of body.formdata) {
|
||||
properties[field.key] =
|
||||
field.type === "file"
|
||||
? { type: "string", format: "binary" }
|
||||
: { type: "string" };
|
||||
}
|
||||
return {
|
||||
content: {
|
||||
"multipart/form-data": {
|
||||
schema: { type: "object", properties },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// ── Response schemas ────────────────────────────────────────────────
|
||||
|
||||
function buildResponses(responses) {
|
||||
const out = {};
|
||||
|
||||
if (!responses || responses.length === 0) {
|
||||
out["200"] = { description: "OK" };
|
||||
return out;
|
||||
}
|
||||
|
||||
for (const resp of responses) {
|
||||
const code = String(resp.code || 200);
|
||||
const desc = resp.status || "OK";
|
||||
const entry = { description: desc };
|
||||
|
||||
if (resp.body) {
|
||||
try {
|
||||
const parsed = JSON.parse(resp.body);
|
||||
entry.content = {
|
||||
"application/json": {
|
||||
schema: inferSchema(parsed),
|
||||
example: parsed,
|
||||
},
|
||||
};
|
||||
} catch {
|
||||
entry.content = {
|
||||
"text/plain": { schema: { type: "string" } },
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
out[code] = entry;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// ── Tree walker ─────────────────────────────────────────────────────
|
||||
|
||||
function processItem(item, tag, paths) {
|
||||
const req = item.request;
|
||||
if (!req) return;
|
||||
|
||||
const method = req.method.toLowerCase();
|
||||
const apiPath = extractPath(req.url);
|
||||
const pathParams = extractPathParams(apiPath);
|
||||
|
||||
if (!paths[apiPath]) paths[apiPath] = {};
|
||||
|
||||
const operation = {
|
||||
tags: [tag],
|
||||
summary: item.name,
|
||||
};
|
||||
|
||||
const reqBody = buildRequestBody(req.body);
|
||||
if (reqBody) operation.requestBody = reqBody;
|
||||
|
||||
if (pathParams.length > 0) operation.parameters = pathParams;
|
||||
|
||||
operation.responses = buildResponses(item.response);
|
||||
|
||||
paths[apiPath][method] = operation;
|
||||
}
|
||||
|
||||
function walkFolder(folder, paths, tag) {
|
||||
const currentTag = folder.name || tag;
|
||||
if (!folder.item) return;
|
||||
|
||||
for (const child of folder.item) {
|
||||
if (child.item) {
|
||||
walkFolder(child, paths, currentTag);
|
||||
} else {
|
||||
processItem(child, currentTag, paths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Main ────────────────────────────────────────────────────────────
|
||||
|
||||
function main() {
|
||||
const collection = JSON.parse(fs.readFileSync(collectionPath, "utf-8"));
|
||||
|
||||
const tags = new Set();
|
||||
const paths = {};
|
||||
|
||||
for (const folder of collection.item) {
|
||||
tags.add(folder.name);
|
||||
walkFolder(folder, paths, folder.name);
|
||||
}
|
||||
|
||||
const spec = {
|
||||
openapi: "3.0.0",
|
||||
info: {
|
||||
title: collection.info.name || "API",
|
||||
description: collection.info.description || "",
|
||||
version: "1.0.0",
|
||||
},
|
||||
servers: [{ url: "http://{{base_url}}" }],
|
||||
components: {
|
||||
securitySchemes: {
|
||||
bearerAuth: { type: "http", scheme: "bearer" },
|
||||
},
|
||||
},
|
||||
security: [{ bearerAuth: [] }],
|
||||
tags: Array.from(tags).map((name) => ({ name })),
|
||||
paths,
|
||||
};
|
||||
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
fs.writeFileSync(outputPath, JSON.stringify(spec, null, 2));
|
||||
console.log(`OpenAPI schema written to ${outputPath}`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -0,0 +1,13 @@
|
||||
const { execSync } = require("child_process");
|
||||
|
||||
const schemaSource = process.argv[2] || "open-api/schema.json";
|
||||
const outputPath = "types/index.ts";
|
||||
|
||||
try {
|
||||
execSync(`npx openapi-typescript ${schemaSource} -o ${outputPath}`, {
|
||||
stdio: "inherit",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to generate TypeScript types from OpenAPI schema.");
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user