264 lines
7.6 KiB
Plaintext
264 lines
7.6 KiB
Plaintext
import fs from "fs";
|
|
import path from "path";
|
|
|
|
const NAMESPACE = "wibu";
|
|
|
|
// ---------------------------
|
|
// Helper Functions
|
|
// ---------------------------
|
|
|
|
function pascalCase(str: string) {
|
|
return str
|
|
.replace(/[^a-zA-Z0-9]+(.)/g, (_, chr) => chr.toUpperCase())
|
|
.replace(/^\w/, (c) => c.toUpperCase());
|
|
}
|
|
|
|
function buildPathWithParams(url: string) {
|
|
return url.replace(/{(.*?)}/g, (match, p1) => `\${encodeURIComponent(params.${p1})}`);
|
|
}
|
|
|
|
function detectRequestBody(requestBody: any) {
|
|
if (!requestBody || !requestBody.content) return null;
|
|
if (requestBody.content["application/json"]) return "json";
|
|
if (requestBody.content["application/x-www-form-urlencoded"]) return "form";
|
|
return null;
|
|
}
|
|
|
|
function buildProperties(parameters: any[], requestBodyType: string | null) {
|
|
const props: any[] = [];
|
|
|
|
for (const p of parameters || []) {
|
|
props.push({
|
|
displayName: `${pascalCase(p.in)} - ${p.name}`,
|
|
name: `${p.in}_${p.name}`,
|
|
type: "string",
|
|
default: "",
|
|
});
|
|
}
|
|
|
|
if (requestBodyType === "json") {
|
|
props.push({
|
|
displayName: "JSON Body",
|
|
name: "bodyJson",
|
|
type: "json",
|
|
default: {},
|
|
});
|
|
}
|
|
|
|
return props;
|
|
}
|
|
|
|
// ---------------------------
|
|
// Main Node Generator
|
|
// ---------------------------
|
|
|
|
function generateNode(tag: string, operations: any[], outDir: string) {
|
|
// Namespace folder: src/nodes/<namespace>
|
|
const baseFolder = path.join(outDir, NAMESPACE);
|
|
if (!fs.existsSync(baseFolder)) fs.mkdirSync(baseFolder, { recursive: true });
|
|
|
|
// Subfolder per-tag: src/nodes/<namespace>/<tag>
|
|
const folder = path.join(baseFolder, tag);
|
|
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
|
|
const className = `${pascalCase(tag)}Api`;
|
|
|
|
// Create switch-case
|
|
const switchCases = operations
|
|
.map((op) => {
|
|
const requestBodyType = detectRequestBody(op.requestBody);
|
|
|
|
return `
|
|
case "${op.operationId}": {
|
|
const params = this.getNodeParameter("params", i, {}) as any;
|
|
const query = this.getNodeParameter("query", i, {}) as any;
|
|
const body = this.getNodeParameter("body", i, {}) as any;
|
|
|
|
const url = \`${buildPathWithParams(op.path)}\`;
|
|
|
|
const qs = query;
|
|
const options: any = {
|
|
method: "${op.method.toUpperCase()}",
|
|
uri: url,
|
|
qs,
|
|
json: true,
|
|
};
|
|
|
|
${requestBodyType === "json" ? `options.body = body.bodyJson;` : ""}
|
|
|
|
const response = await this.helpers.request(options);
|
|
returnData.push(response);
|
|
break;
|
|
}
|
|
`;
|
|
})
|
|
.join("\n");
|
|
|
|
// Build dynamic properties
|
|
const allProps = operations.flatMap((op: any) => {
|
|
const body = detectRequestBody(op.requestBody);
|
|
return buildProperties(op.parameters, body);
|
|
});
|
|
|
|
const nodeContent = `
|
|
import { INodeType, INodeTypeDescription, IExecuteFunctions } from "n8n-workflow";
|
|
|
|
export class ${className} implements INodeType {
|
|
|
|
description: INodeTypeDescription = {
|
|
displayName: "${NAMESPACE} - ${pascalCase(tag)}",
|
|
name: "${pascalCase(tag)}",
|
|
group: ["transform"],
|
|
version: 1,
|
|
description: "Auto-generated from OpenAPI",
|
|
defaults: {
|
|
name: "${NAMESPACE} - ${pascalCase(tag)}",
|
|
},
|
|
icon: "file:../../../icon.svg",
|
|
inputs: ["main"],
|
|
outputs: ["main"],
|
|
properties: [
|
|
{
|
|
displayName: "Operation",
|
|
name: "operation",
|
|
type: "options",
|
|
options: [
|
|
${operations
|
|
.map(
|
|
(o) => `
|
|
{
|
|
name: "${o.summary || o.operationId}",
|
|
value: "${o.operationId}"
|
|
}
|
|
`
|
|
)
|
|
.join(",")}
|
|
],
|
|
default: "${operations[0].operationId}",
|
|
},
|
|
{
|
|
displayName: "Parameters",
|
|
name: "params",
|
|
type: "collection",
|
|
placeholder: "Add Parameter",
|
|
default: {},
|
|
options: [
|
|
${allProps
|
|
.map(
|
|
(p) => `
|
|
{
|
|
displayName: "${p.displayName}",
|
|
name: "${p.name}",
|
|
type: "string",
|
|
default: "",
|
|
}
|
|
`
|
|
)
|
|
.join(",")}
|
|
]
|
|
},
|
|
{
|
|
displayName: "Query",
|
|
name: "query",
|
|
type: "collection",
|
|
placeholder: "Add Query",
|
|
default: {},
|
|
options: []
|
|
},
|
|
{
|
|
displayName: "Body",
|
|
name: "body",
|
|
type: "collection",
|
|
default: {},
|
|
options: [
|
|
{
|
|
displayName: "JSON Body",
|
|
name: "bodyJson",
|
|
type: "json",
|
|
default: {}
|
|
}
|
|
]
|
|
}
|
|
]
|
|
};
|
|
|
|
async execute(this: IExecuteFunctions) {
|
|
const returnData: any[] = [];
|
|
const items = this.getInputData();
|
|
|
|
for (let i = 0; i < items.length; i++) {
|
|
const operation = this.getNodeParameter("operation", i) as string;
|
|
|
|
switch (operation) {
|
|
${switchCases}
|
|
default:
|
|
throw new Error("Operation not implemented: " + operation);
|
|
}
|
|
}
|
|
|
|
return [returnData];
|
|
}
|
|
}
|
|
`;
|
|
|
|
const file = path.join(folder, `${className}.node.ts`);
|
|
fs.writeFileSync(file, nodeContent);
|
|
|
|
return className;
|
|
}
|
|
|
|
// ---------------------------
|
|
// Main Program
|
|
// ---------------------------
|
|
|
|
export function main() {
|
|
const projectRoot = process.cwd();
|
|
const openapiPath = path.join(projectRoot, "openapi.json");
|
|
const outDir = path.join(projectRoot, "src", "nodes");
|
|
|
|
if (!fs.existsSync(openapiPath)) {
|
|
console.error("❌ openapi.json not found");
|
|
process.exit(1);
|
|
}
|
|
|
|
const openapi = JSON.parse(fs.readFileSync(openapiPath, "utf8"));
|
|
|
|
const tagsMap: any = {};
|
|
|
|
// Group by tags
|
|
for (const pathKey of Object.keys(openapi.paths)) {
|
|
const pathObj = openapi.paths[pathKey];
|
|
|
|
for (const method of Object.keys(pathObj)) {
|
|
const op = pathObj[method];
|
|
if (!op.tags) continue;
|
|
|
|
const item = {
|
|
path: pathKey,
|
|
method,
|
|
...op,
|
|
};
|
|
|
|
for (const t of op.tags) {
|
|
tagsMap[t] = tagsMap[t] || [];
|
|
tagsMap[t].push(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate index.ts exports
|
|
const indexExports: string[] = [];
|
|
|
|
for (const tag of Object.keys(tagsMap)) {
|
|
const className = generateNode(tag, tagsMap[tag], outDir);
|
|
indexExports.push(`export * from "./nodes/${NAMESPACE}/${tag}/${className}.node";`);
|
|
}
|
|
|
|
const indexPath = path.join(projectRoot, "src", "index.ts");
|
|
fs.writeFileSync(indexPath, indexExports.join("\n"));
|
|
|
|
console.log("✅ Generation complete");
|
|
}
|
|
|
|
main();
|