Files
n8n-nodes-wibu/gen.txt2
bipproduction 4fcb80df3d tambahan v2
2025-11-07 10:41:05 +08:00

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();