tambahan baru

This commit is contained in:
bipproduction
2025-11-10 10:17:51 +08:00
commit 9ff2aa0fec
17 changed files with 5267 additions and 0 deletions

34
.gitignore vendored Normal file
View File

@@ -0,0 +1,34 @@
# dependencies (bun install)
node_modules
# output
out
dist
*.tgz
# code coverage
coverage
*.lcov
# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# caches
.eslintcache
.cache
*.tsbuildinfo
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
# n8n-generator
To install dependencies:
```bash
bun install
```
To run:
```bash
bun run index.ts
```
This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.

View File

@@ -0,0 +1,27 @@
import { ICredentialType, INodeProperties } from "n8n-workflow";
export class WibuApi implements ICredentialType {
name = "wibuApi";
displayName = "Wibu API (Bearer Token)";
properties: INodeProperties[] = [
{
displayName: "Base URL",
name: "baseUrl",
type: "string",
default: "",
placeholder: "https://api.example.com",
description: "Masukkan URL dasar API tanpa garis miring di akhir",
required: true,
},
{
displayName: "Bearer Token",
name: "token",
type: "string",
default: "",
typeOptions: { password: true },
description: "Masukkan token autentikasi Bearer (tanpa 'Bearer ' di depannya)",
required: true,
},
];
}

35
assets/icon.svg Normal file
View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 48 48" id="happy">
<symbol id="New_Symbol_14" viewBox="-6.5 -6.5 13 13">
<path fill="#FFD4C3" stroke="#504B46" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M0-6c2.2 0 4.1 1.5 4.7 3.5C6.3-2.5 6.4 0 5 0v1c0 2.8-2.2 5-5 5s-5-2.2-5-5V0c-1.4 0-1.3-2.5.2-2.5C-4.1-4.5-2.2-6 0-6z" style="fill:#ffd4c3;stroke:#504b46;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<circle cx="-1.6" cy="-.1" r=".1" style="fill:#ffc258"></circle>
<path d="M-1.6.5c-.3 0-.6-.3-.6-.6s.2-.7.6-.7c.3 0 .6.3.6.7s-.3.6-.6.6z" style="fill:#4f4b45"></path>
<circle cx="1.6" cy="-.1" r=".1" style="fill:#ffc258"></circle>
<path d="M1.6.5C1.3.5 1 .2 1-.1s.3-.6.6-.6.6.3.6.6-.2.6-.6.6z" style="fill:#4f4b45"></path>
<circle cx="-3" cy="-1.5" r=".5" style="fill:#fabfa5"></circle>
<circle cx="3" cy="-1.5" r=".5" style="fill:#fabfa5"></circle>
<path fill="none" stroke="#504B46" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M-1.2-3c.8-.5 1.7-.5 2.5 0" style="fill:none;stroke:#504b46;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
</symbol>
<g id="Icons">
<g id="XMLID_2_">
<circle id="XMLID_7676_" cx="20.8" cy="21.5" r="20" fill="#FFE500" style="fill:#ffe500"></circle>
<path id="XMLID_7673_" fill="#EBCB00" d="M20.8 1.5c-11 0-20 9-20 20s9 20 20 20 20-9 20-20-9-20-20-20zm0 37c-10.1 0-18.2-8.2-18.2-18.2C2.5 10.1 10.7 2 20.8 2S39 10.1 39 20.2s-8.2 18.3-18.2 18.3z" style="fill:#ebcb00"></path>
<ellipse id="XMLID_7672_" cx="20.8" cy="5.5" fill="#FFF48C" rx="6" ry="1.5" style="fill:#fff48c"></ellipse>
<ellipse id="XMLID_7671_" cx="20.8" cy="45" fill="#45413C" opacity=".15" rx="16" ry="1.5" style="opacity:.15;fill:#45413c"></ellipse>
<circle id="XMLID_7670_" cx="20.8" cy="21.5" r="20" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></circle>
<ellipse id="XMLID_7669_" cx="32.8" cy="26.5" rx="2.5" ry="1.5" style="fill:#ffaa54"></ellipse>
<ellipse id="XMLID_7668_" cx="8.7" cy="26.5" rx="2.5" ry="1.5" style="fill:#ffaa54"></ellipse>
<path id="XMLID_7667_" d="M6.8 22.7c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7666_" d="M31.2 22.7c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7665_" d="m42.4 30.6 4.5-1.5c.2-.1.3-.2.3-.4s-.1-.3-.3-.4l-3.7-1.2c-.5-.2-.9-.6-1.1-1.1l-1.2-3.7c-.1-.2-.2-.3-.4-.3s-.3.1-.4.3L38.9 26c-.2.5-.6.9-1.1 1.1L34 28.3c-.2.1-.3.2-.3.4s.1.3.3.4l3.7 1.2c.5.2.9.6 1.1 1.1l1.2 3.7c.1.2.2.3.4.3s.3-.1.4-.3l1.6-4.5z" style="stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;fill:#fff"></path>
<g id="XMLID_7658_">
<path id="XMLID_7664_" d="M20.8 36.5c9.2 0 10.4-3.8 9.9-7-.3-1.9-2.5-3.1-4.6-2.4-1.5.5-3.4.8-5.3.8s-3.8-.3-5.3-.8c-2-.7-4.2.5-4.6 2.4-.6 3.2.6 7 9.9 7z" style="stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;fill:#fff"></path>
<g id="XMLID_7659_">
<path id="XMLID_7663_" d="M30.7 30.2c-1 .5-2.5 1-4.6 1.4-1.5.2-3.2.4-5.4.4s-3.9-.2-5.4-.4c-2.1-.4-3.6-.9-4.6-1.4" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7662_" d="m16 27.4-.6 4.2-.7 4.1" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7661_" d="M20.8 36.5V28" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7660_" d="m25.5 27.4.6 4.2.7 4.1" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

24
assets/package.txt Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "n8n-nodes-jenna-mcp",
"version": "1.0.41",
"main": "dist/index.js",
"keywords": [
"n8n",
"n8n-nodes"
],
"author": {
"name": "makuro",
"phone": "6289697338821"
},
"license": "ISC",
"description": "",
"n8n": {
"nodes": [
"nodes/JennaMcp.node.js"
],
"n8nNodesApiVersion": 1,
"credentials": [
"credentials/WibuApi.credentials.js"
]
}
}

1039
bun.lock Normal file

File diff suppressed because it is too large Load Diff

356
gen.ts Normal file
View File

@@ -0,0 +1,356 @@
// gen-single-file.ts
import fs from 'fs/promises';
import path from 'path';
import prettier from 'prettier';
import _ from 'lodash';
import { execSync } from 'child_process'
const NAMESPACE = 'jenna-mcp';
const OPENAPI_URL = 'https://cld-dkr-prod-jenna-mcp.wibudev.com/docs/json';
const namespaceCase = _.startCase(_.camelCase(NAMESPACE)).replace(/ /g, '');
const OUT_DIR = path.join('src', 'nodes');
const OUT_FILE = path.join(OUT_DIR, `${namespaceCase}.node.ts`);
const CREDENTIAL_NAME = 'wibuApi';
interface OpenAPI {
paths: Record<string, any>;
components?: any;
tags?: { name: string }[];
}
// helpers
const safe = (s: string) => s.replace(/[^a-zA-Z0-9]/g, '_');
// load OpenAPI
async function loadOpenAPI(): Promise<OpenAPI> {
const url = OPENAPI_URL
const res = await fetch(url);
if (!res.ok) throw new Error(`Failed to fetch OpenAPI: ${res.status} ${res.statusText}`);
return res.json() as Promise<OpenAPI>;
}
// convert operation to value
function operationValue(tag: string, operationId: string) {
return `${safe(tag)}_${safe(operationId)}`;
}
// build properties for dropdown + dynamic inputs
function buildPropertiesBlock(ops: Array<any>) {
const options = ops.map((op) => {
const value = operationValue(op.tag, op.operationId);
const label = `${NAMESPACE} ${op.tag} ${op.operationId}`;
return `{ name: '${label}', value: '${value}', description: ${JSON.stringify(
op.summary || op.description || '',
)}, action: '${label}' }`;
});
const dropdown = `
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
${options.join(',\n ')}
],
default: '${operationValue(ops[0].tag, ops[0].operationId)}',
description: 'Pilih endpoint yang akan dipanggil'
}
`;
const dynamicProps: string[] = [];
for (const op of ops) {
const value = operationValue(op.tag, op.operationId);
// Query fields
for (const name of op.query ?? []) {
dynamicProps.push(`
{
displayName: 'Query ${name}',
name: 'query_${name}',
type: 'string',
default: '',
placeholder: '${name}',
description: '${name}',
displayOptions: { show: { operation: ['${value}'] } }
}`);
}
// Body fields (required only)
const bodyRequired = op.body?.required ?? [];
const bodySchema = op.body?.schema ?? {};
for (const name of bodyRequired) {
const schema = bodySchema[name] ?? {};
let type = 'string';
if (schema.type === 'number' || schema.type === 'integer') type = 'number';
if (schema.type === 'boolean') type = 'boolean';
const defVal =
type === 'string' ? "''" : type === 'number' ? '0' : type === 'boolean' ? 'false' : "''";
dynamicProps.push(`
{
displayName: 'Body ${name}',
name: 'body_${name}',
type: '${type}',
default: ${defVal},
placeholder: '${name}',
description: '${schema?.description ?? name}',
displayOptions: { show: { operation: ['${value}'] } }
}`);
}
}
return `[
${dropdown},
${dynamicProps.join(',\n ')}
]`;
}
// build execute switch
function buildExecuteSwitch(ops: Array<any>) {
const cases: string[] = [];
for (const op of ops) {
const val = operationValue(op.tag, op.operationId);
const method = (op.method || 'get').toLowerCase();
const url = op.path;
const q = op.query ?? [];
const bodyReq = op.body?.required ?? [];
const qLines =
q
.map(
(name: string) =>
`const query_${_.snakeCase(name)} = this.getNodeParameter('query_${_.snakeCase(name)}', i, '') as string;`,
)
.join('\n ') || '';
const bodyLines =
bodyReq
.map(
(name: string) =>
`const body_${_.snakeCase(name)} = this.getNodeParameter('body_${_.snakeCase(name)}', i, '') as any;`,
)
.join('\n ') || '';
const bodyObject =
bodyReq.length > 0
? `const body = { ${bodyReq.map((n: string) => `${_.snakeCase(n)}: body_${_.snakeCase(n)}`).join(', ')} };`
: 'const body = undefined;';
const paramsObj =
q.length > 0 ? `params: { ${q.map((n: string) => `${_.snakeCase(n)}: query_${_.snakeCase(n)}`).join(', ')} },` : '';
const dataLine = method === 'get' ? '' : 'data: body,';
cases.push(`
case '${val}': {
${qLines}
${bodyLines}
${bodyObject}
url = baseUrl + '${url}';
method = '${method}';
axiosConfig = {
headers: finalHeaders,
${paramsObj}
${dataLine}
};
break;
}
`);
}
return `
switch (operation) {
${cases.join('\n')}
default:
throw new Error('Unknown operation: ' + operation);
}
`;
}
// top-level
function generateNodeFile(ops: Array<any>) {
const propertiesBlock = buildPropertiesBlock(ops);
const executeSwitch = buildExecuteSwitch(ops);
return `import type { INodeType, INodeTypeDescription, IExecuteFunctions } from 'n8n-workflow';
import axios from 'axios';
export class ${namespaceCase} implements INodeType {
description: INodeTypeDescription = {
displayName: '${namespaceCase}',
name: '${namespaceCase}',
icon: 'file:icon.svg',
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"]}}',
description: 'Universal node generated from OpenAPI - satu node memuat semua endpoint',
defaults: { name: '${namespaceCase}' },
inputs: ['main'],
outputs: ['main'],
credentials: [
{ name: '${CREDENTIAL_NAME}', required: true }
],
properties: ${propertiesBlock}
};
async execute(this: IExecuteFunctions) {
const items = this.getInputData();
const returnData: any[] = [];
const creds = await this.getCredentials('${CREDENTIAL_NAME}') as any;
const baseUrlRaw = creds?.baseUrl ?? '';
const apiKeyRaw = creds?.token ?? '';
const baseUrl = String(baseUrlRaw || '').replace(/\\/$/, '');
const apiKey = String(apiKeyRaw || '').trim().replace(/^Bearer\\s+/i, '');
if (!baseUrl) throw new Error('Base URL tidak ditemukan');
if (!apiKey) throw new Error('Token tidak ditemukan');
for (let i = 0; i < items.length; i++) {
const operation = this.getNodeParameter('operation', i) as string;
let url = '';
let method: any = 'get';
let axiosConfig: any = {};
const finalHeaders: any = { Authorization: \`Bearer \${apiKey}\` };
${executeSwitch}
try {
const response = await axios({ method, url, ...axiosConfig });
returnData.push(response.data);
} catch (err: any) {
returnData.push({
error: true,
message: err.message,
status: err.response?.status,
data: err.response?.data,
});
}
}
return [this.helpers.returnJsonArray(returnData)];
}
}
`;
}
// main
async function run() {
console.log('💡 Loading OpenAPI...');
const api = await loadOpenAPI();
const ops: Array<any> = [];
for (const pathStr of Object.keys(api.paths || {})) {
const pathObj = api.paths[pathStr];
for (const method of Object.keys(pathObj)) {
const operation = pathObj[method];
const tags = operation.tags?.length ? operation.tags : ['default'];
console.log("✅", _.upperCase(method).padEnd(7), pathStr);
const operationId = operation.operationId || `${method}_${safe(pathStr)}`;
const query = (operation.parameters ?? [])
.filter((p: any) => p.in === 'query')
.map((p: any) => p.name);
const requestBody =
operation.requestBody?.content?.['application/json']?.schema ??
operation.requestBody?.content?.['multipart/form-data']?.schema ??
null;
const bodyRequired = requestBody?.required ?? [];
const bodyProps = requestBody?.properties ?? {};
for (const tag of tags) {
ops.push({
tag,
path: pathStr,
method,
operationId,
summary: operation.summary || '',
description: operation.description || '',
query,
body: {
required: bodyRequired,
schema: bodyProps,
},
});
}
}
}
if (ops.length === 0) throw new Error('No operations found');
console.log('💡 Creating output directory...');
await fs.mkdir(OUT_DIR, { recursive: true }).catch(() => {});
console.log('💡 Creating credentials directory...');
await fs.mkdir(`src/credentials`, { recursive: true }).catch(() => {});
const raw = generateNodeFile(ops);
// ✅ PRETTIER SAFE-FORMAT
let formatted: string;
try {
console.log('💡 Formatting with Prettier...');
const conf = await prettier.resolveConfig(process.cwd()).catch(() => null);
formatted = await prettier.format(raw, {
...(conf || {}),
parser: 'typescript',
});
} catch (err) {
console.warn('⚠️ Prettier gagal → output raw digunakan.');
formatted = raw;
}
console.log('✅ Generated single node file:', OUT_FILE);
await fs.writeFile(OUT_FILE, formatted, 'utf-8');
// credential
console.log('💡 Copying credentials...');
await fs.copyFile("assets/WibuApi.credentials.txt", `src/credentials/WibuApi.credentials.ts`)
console.log('💡 Compiling TypeScript...');
execSync('rm -rf dist && npx tsc')
const packageText = await fs.readFile("assets/package.txt", 'utf-8')
const packageJson = JSON.parse(packageText)
const version = packageJson.version.split(".")
version[2] = (Number(version[2]) + 1).toString()
packageJson.version = version.join(".")
console.log("💡 version", packageJson.version)
packageJson.name = `n8n-nodes-${_.kebabCase(namespaceCase)}`
packageJson.n8n.nodes = [`nodes/${namespaceCase}.node.js`]
packageJson.n8n.credentials = [`credentials/WibuApi.credentials.js`]
console.log('💡 Updating package.json...')
await fs.writeFile("dist/package.json", JSON.stringify(packageJson, null, 2))
await fs.writeFile("assets/package.txt", JSON.stringify(packageJson, null, 2))
await fs.copyFile("assets/icon.svg", "dist/nodes/icon.svg")
console.log('💡 Renaming dist to package name...')
await fs.rmdir(packageJson.name, { recursive: true }).catch(() => {})
await fs.rename("dist", packageJson.name).catch((e) => {throw new Error("gagal rename dist")})
console.log('💡 Publishing...')
execSync(`cd ${packageJson.name} && bun publish`)
console.log('✅ Generated single node file:', OUT_FILE);
}
run().catch((err) => {
console.error('❌ Generator failed:', err);
process.exit(1);
});

1
index.ts Normal file
View File

@@ -0,0 +1 @@
console.log("apa kabar")

View File

@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WibuApi = void 0;
class WibuApi {
constructor() {
this.name = "wibuApi";
this.displayName = "Wibu API (Bearer Token)";
this.properties = [
{
displayName: "Base URL",
name: "baseUrl",
type: "string",
default: "",
placeholder: "https://api.example.com",
description: "Masukkan URL dasar API tanpa garis miring di akhir",
required: true,
},
{
displayName: "Bearer Token",
name: "token",
type: "string",
default: "",
typeOptions: { password: true },
description: "Masukkan token autentikasi Bearer (tanpa 'Bearer ' di depannya)",
required: true,
},
];
}
}
exports.WibuApi = WibuApi;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,35 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 48 48" id="happy">
<symbol id="New_Symbol_14" viewBox="-6.5 -6.5 13 13">
<path fill="#FFD4C3" stroke="#504B46" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M0-6c2.2 0 4.1 1.5 4.7 3.5C6.3-2.5 6.4 0 5 0v1c0 2.8-2.2 5-5 5s-5-2.2-5-5V0c-1.4 0-1.3-2.5.2-2.5C-4.1-4.5-2.2-6 0-6z" style="fill:#ffd4c3;stroke:#504b46;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<circle cx="-1.6" cy="-.1" r=".1" style="fill:#ffc258"></circle>
<path d="M-1.6.5c-.3 0-.6-.3-.6-.6s.2-.7.6-.7c.3 0 .6.3.6.7s-.3.6-.6.6z" style="fill:#4f4b45"></path>
<circle cx="1.6" cy="-.1" r=".1" style="fill:#ffc258"></circle>
<path d="M1.6.5C1.3.5 1 .2 1-.1s.3-.6.6-.6.6.3.6.6-.2.6-.6.6z" style="fill:#4f4b45"></path>
<circle cx="-3" cy="-1.5" r=".5" style="fill:#fabfa5"></circle>
<circle cx="3" cy="-1.5" r=".5" style="fill:#fabfa5"></circle>
<path fill="none" stroke="#504B46" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="M-1.2-3c.8-.5 1.7-.5 2.5 0" style="fill:none;stroke:#504b46;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
</symbol>
<g id="Icons">
<g id="XMLID_2_">
<circle id="XMLID_7676_" cx="20.8" cy="21.5" r="20" fill="#FFE500" style="fill:#ffe500"></circle>
<path id="XMLID_7673_" fill="#EBCB00" d="M20.8 1.5c-11 0-20 9-20 20s9 20 20 20 20-9 20-20-9-20-20-20zm0 37c-10.1 0-18.2-8.2-18.2-18.2C2.5 10.1 10.7 2 20.8 2S39 10.1 39 20.2s-8.2 18.3-18.2 18.3z" style="fill:#ebcb00"></path>
<ellipse id="XMLID_7672_" cx="20.8" cy="5.5" fill="#FFF48C" rx="6" ry="1.5" style="fill:#fff48c"></ellipse>
<ellipse id="XMLID_7671_" cx="20.8" cy="45" fill="#45413C" opacity=".15" rx="16" ry="1.5" style="opacity:.15;fill:#45413c"></ellipse>
<circle id="XMLID_7670_" cx="20.8" cy="21.5" r="20" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></circle>
<ellipse id="XMLID_7669_" cx="32.8" cy="26.5" rx="2.5" ry="1.5" style="fill:#ffaa54"></ellipse>
<ellipse id="XMLID_7668_" cx="8.7" cy="26.5" rx="2.5" ry="1.5" style="fill:#ffaa54"></ellipse>
<path id="XMLID_7667_" d="M6.8 22.7c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7666_" d="M31.2 22.7c0-1 .8-1.8 1.8-1.8s1.8.8 1.8 1.8" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7665_" d="m42.4 30.6 4.5-1.5c.2-.1.3-.2.3-.4s-.1-.3-.3-.4l-3.7-1.2c-.5-.2-.9-.6-1.1-1.1l-1.2-3.7c-.1-.2-.2-.3-.4-.3s-.3.1-.4.3L38.9 26c-.2.5-.6.9-1.1 1.1L34 28.3c-.2.1-.3.2-.3.4s.1.3.3.4l3.7 1.2c.5.2.9.6 1.1 1.1l1.2 3.7c.1.2.2.3.4.3s.3-.1.4-.3l1.6-4.5z" style="stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;fill:#fff"></path>
<g id="XMLID_7658_">
<path id="XMLID_7664_" d="M20.8 36.5c9.2 0 10.4-3.8 9.9-7-.3-1.9-2.5-3.1-4.6-2.4-1.5.5-3.4.8-5.3.8s-3.8-.3-5.3-.8c-2-.7-4.2.5-4.6 2.4-.6 3.2.6 7 9.9 7z" style="stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10;fill:#fff"></path>
<g id="XMLID_7659_">
<path id="XMLID_7663_" d="M30.7 30.2c-1 .5-2.5 1-4.6 1.4-1.5.2-3.2.4-5.4.4s-3.9-.2-5.4-.4c-2.1-.4-3.6-.9-4.6-1.4" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7662_" d="m16 27.4-.6 4.2-.7 4.1" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7661_" d="M20.8 36.5V28" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
<path id="XMLID_7660_" d="m25.5 27.4.6 4.2.7 4.1" style="fill:none;stroke:#45413c;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:10"></path>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,24 @@
{
"name": "n8n-nodes-jenna-mcp",
"version": "1.0.41",
"main": "dist/index.js",
"keywords": [
"n8n",
"n8n-nodes"
],
"author": {
"name": "makuro",
"phone": "6289697338821"
},
"license": "ISC",
"description": "",
"n8n": {
"nodes": [
"nodes/JennaMcp.node.js"
],
"n8nNodesApiVersion": 1,
"credentials": [
"credentials/WibuApi.credentials.js"
]
}
}

31
package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "n8n-generator",
"module": "index.ts",
"type": "module",
"private": true,
"scripts": {
"build": "rm -rf dist && tsc && cp assets/icon.svg dist/nodes/icon.svg",
"start": "bun run dist/index.js",
"gen": "bun gen.ts"
},
"dependencies": {
"express": "^5.1.0",
"lodash": "^4.17.21",
"n8n-core": "^1.117.1",
"n8n-workflow": "^1.116.0",
"nock": "^14.0.10",
"ssh2": "^1.17.0"
},
"devDependencies": {
"@types/bun": "latest",
"@types/lodash": "^4.17.20",
"@types/express": "^5.0.5",
"@types/node": "^24.10.0",
"@types/ssh2": "^1.15.5",
"prettier": "^3.6.2",
"ts-node": "^10.9.2"
},
"peerDependencies": {
"typescript": "^5"
}
}

View File

@@ -0,0 +1,27 @@
import { ICredentialType, INodeProperties } from "n8n-workflow";
export class WibuApi implements ICredentialType {
name = "wibuApi";
displayName = "Wibu API (Bearer Token)";
properties: INodeProperties[] = [
{
displayName: "Base URL",
name: "baseUrl",
type: "string",
default: "",
placeholder: "https://api.example.com",
description: "Masukkan URL dasar API tanpa garis miring di akhir",
required: true,
},
{
displayName: "Bearer Token",
name: "token",
type: "string",
default: "",
typeOptions: { password: true },
description: "Masukkan token autentikasi Bearer (tanpa 'Bearer ' di depannya)",
required: true,
},
];
}

1928
src/nodes/JennaMcp.node.ts Normal file

File diff suppressed because it is too large Load Diff

14
tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"lib": ["es2021", "dom"],
"module": "commonjs",
"target": "es2017",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true
},
"include": ["src/**/*.ts", "src/nodes/**/*.svg"]
}

3
x.sh Normal file
View File

@@ -0,0 +1,3 @@
git remote add origin https://wibugit.wibudev.com/wibu/n8n-generator.git
git remote set-url --add --push origin https://wibugit.wibudev.com/wibu/n8n-generator.git
git remote set-url --add --push origin https://github.com/bipproduction/n8n-generator.git