first commit

This commit is contained in:
2025-10-26 23:10:15 +08:00
commit 8f0345b7be
14961 changed files with 2356381 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
export declare function updateNodeAst({ nodePath, className, baseUrl, }: {
nodePath: string;
className: string;
baseUrl: string;
}): import("ts-morph").SourceFile;
export declare function updateCredentialAst({ repoName, baseUrl, credentialPath, credentialName, credentialDisplayName, credentialClassName, }: {
repoName: string;
credentialPath: string;
credentialName: string;
credentialDisplayName: string;
credentialClassName: string;
baseUrl: string;
}): import("ts-morph").SourceFile;
export declare function addCredentialToNode({ nodePath, credentialName, }: {
nodePath: string;
credentialName: string;
}): import("ts-morph").SourceFile;

View File

@@ -0,0 +1,110 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateNodeAst = updateNodeAst;
exports.updateCredentialAst = updateCredentialAst;
exports.addCredentialToNode = addCredentialToNode;
const change_case_1 = require("change-case");
const ts_morph_1 = require("ts-morph");
const ast_1 = require("../../../../utils/ast");
function updateNodeAst({ nodePath, className, baseUrl, }) {
const sourceFile = (0, ast_1.loadSingleSourceFile)(nodePath);
const classDecl = sourceFile.getClasses()[0];
classDecl.rename(className);
const nodeDescriptionObj = classDecl
.getPropertyOrThrow('description')
.getInitializerIfKindOrThrow(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
(0, ast_1.updateStringProperty)({
obj: nodeDescriptionObj,
key: 'displayName',
value: (0, change_case_1.capitalCase)(className),
});
(0, ast_1.updateStringProperty)({
obj: nodeDescriptionObj,
key: 'name',
value: (0, change_case_1.camelCase)(className),
});
(0, ast_1.updateStringProperty)({
obj: nodeDescriptionObj,
key: 'description',
value: `Interact with the ${(0, change_case_1.capitalCase)(className)} API`,
});
const icon = (0, ast_1.getChildObjectLiteral)({ obj: nodeDescriptionObj, key: 'icon' });
(0, ast_1.updateStringProperty)({
obj: icon,
key: 'light',
value: `file:${(0, change_case_1.camelCase)(className)}.svg`,
});
(0, ast_1.updateStringProperty)({
obj: icon,
key: 'dark',
value: `file:${(0, change_case_1.camelCase)(className)}.dark.svg`,
});
const requestDefaults = (0, ast_1.getChildObjectLiteral)({
obj: nodeDescriptionObj,
key: 'requestDefaults',
});
(0, ast_1.updateStringProperty)({
obj: requestDefaults,
key: 'baseURL',
value: baseUrl,
});
const defaults = (0, ast_1.getChildObjectLiteral)({
obj: nodeDescriptionObj,
key: 'defaults',
});
(0, ast_1.updateStringProperty)({ obj: defaults, key: 'name', value: (0, change_case_1.capitalCase)(className) });
return sourceFile;
}
function updateCredentialAst({ repoName, baseUrl, credentialPath, credentialName, credentialDisplayName, credentialClassName, }) {
const sourceFile = (0, ast_1.loadSingleSourceFile)(credentialPath);
const classDecl = sourceFile.getClasses()[0];
classDecl.rename(credentialClassName);
(0, ast_1.updateStringProperty)({
obj: classDecl,
key: 'displayName',
value: credentialDisplayName,
});
(0, ast_1.updateStringProperty)({
obj: classDecl,
key: 'name',
value: credentialName,
});
const docUrlProp = classDecl.getProperty('documentationUrl');
if (docUrlProp) {
const initializer = docUrlProp.getInitializerIfKindOrThrow(ts_morph_1.SyntaxKind.StringLiteral);
const newUrl = initializer.getLiteralText().replace('/repo', `/${repoName}`);
initializer.setLiteralValue(newUrl);
}
const testProperty = classDecl.getProperty('test');
if (testProperty) {
const testRequest = testProperty
.getInitializerIfKindOrThrow(ts_morph_1.SyntaxKind.ObjectLiteralExpression)
.getPropertyOrThrow('request')
.asKindOrThrow(ts_morph_1.SyntaxKind.PropertyAssignment)
.getInitializerIfKindOrThrow(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
(0, ast_1.updateStringProperty)({
obj: testRequest,
key: 'baseURL',
value: baseUrl,
});
}
return sourceFile;
}
function addCredentialToNode({ nodePath, credentialName, }) {
const sourceFile = (0, ast_1.loadSingleSourceFile)(nodePath);
const classDecl = sourceFile.getClasses()[0];
const descriptionProp = classDecl
.getPropertyOrThrow('description')
.getInitializerIfKindOrThrow(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
const credentialsProp = descriptionProp.getPropertyOrThrow('credentials');
if (credentialsProp.getKind() === ts_morph_1.SyntaxKind.PropertyAssignment) {
const initializer = credentialsProp.getFirstDescendantByKindOrThrow(ts_morph_1.SyntaxKind.ArrayLiteralExpression);
const credentialObject = ts_morph_1.ts.factory.createObjectLiteralExpression([
ts_morph_1.ts.factory.createPropertyAssignment(ts_morph_1.ts.factory.createIdentifier('name'), ts_morph_1.ts.factory.createStringLiteral(credentialName, true)),
ts_morph_1.ts.factory.createPropertyAssignment(ts_morph_1.ts.factory.createIdentifier('required'), ts_morph_1.ts.factory.createTrue()),
]);
initializer.addElement((0, ts_morph_1.printNode)(credentialObject));
}
return sourceFile;
}
//# sourceMappingURL=ast.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../../../../../src/template/templates/declarative/custom/ast.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,161 @@
import { camelCase, capitalCase } from 'change-case';
import { ts, SyntaxKind, printNode } from 'ts-morph';
import {
getChildObjectLiteral,
loadSingleSourceFile,
updateStringProperty,
} from '../../../../utils/ast';
export function updateNodeAst({
nodePath,
className,
baseUrl,
}: { nodePath: string; className: string; baseUrl: string }) {
const sourceFile = loadSingleSourceFile(nodePath);
const classDecl = sourceFile.getClasses()[0];
classDecl.rename(className);
const nodeDescriptionObj = classDecl
.getPropertyOrThrow('description')
.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
updateStringProperty({
obj: nodeDescriptionObj,
key: 'displayName',
value: capitalCase(className),
});
updateStringProperty({
obj: nodeDescriptionObj,
key: 'name',
value: camelCase(className),
});
updateStringProperty({
obj: nodeDescriptionObj,
key: 'description',
value: `Interact with the ${capitalCase(className)} API`,
});
const icon = getChildObjectLiteral({ obj: nodeDescriptionObj, key: 'icon' });
updateStringProperty({
obj: icon,
key: 'light',
value: `file:${camelCase(className)}.svg`,
});
updateStringProperty({
obj: icon,
key: 'dark',
value: `file:${camelCase(className)}.dark.svg`,
});
const requestDefaults = getChildObjectLiteral({
obj: nodeDescriptionObj,
key: 'requestDefaults',
});
updateStringProperty({
obj: requestDefaults,
key: 'baseURL',
value: baseUrl,
});
const defaults = getChildObjectLiteral({
obj: nodeDescriptionObj,
key: 'defaults',
});
updateStringProperty({ obj: defaults, key: 'name', value: capitalCase(className) });
return sourceFile;
}
export function updateCredentialAst({
repoName,
baseUrl,
credentialPath,
credentialName,
credentialDisplayName,
credentialClassName,
}: {
repoName: string;
credentialPath: string;
credentialName: string;
credentialDisplayName: string;
credentialClassName: string;
baseUrl: string;
}) {
const sourceFile = loadSingleSourceFile(credentialPath);
const classDecl = sourceFile.getClasses()[0];
classDecl.rename(credentialClassName);
updateStringProperty({
obj: classDecl,
key: 'displayName',
value: credentialDisplayName,
});
updateStringProperty({
obj: classDecl,
key: 'name',
value: credentialName,
});
const docUrlProp = classDecl.getProperty('documentationUrl');
if (docUrlProp) {
const initializer = docUrlProp.getInitializerIfKindOrThrow(SyntaxKind.StringLiteral);
const newUrl = initializer.getLiteralText().replace('/repo', `/${repoName}`);
initializer.setLiteralValue(newUrl);
}
const testProperty = classDecl.getProperty('test');
if (testProperty) {
const testRequest = testProperty
.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression)
.getPropertyOrThrow('request')
.asKindOrThrow(SyntaxKind.PropertyAssignment)
.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
updateStringProperty({
obj: testRequest,
key: 'baseURL',
value: baseUrl,
});
}
return sourceFile;
}
export function addCredentialToNode({
nodePath,
credentialName,
}: { nodePath: string; credentialName: string }) {
const sourceFile = loadSingleSourceFile(nodePath);
const classDecl = sourceFile.getClasses()[0];
const descriptionProp = classDecl
.getPropertyOrThrow('description')
.getInitializerIfKindOrThrow(SyntaxKind.ObjectLiteralExpression);
const credentialsProp = descriptionProp.getPropertyOrThrow('credentials');
if (credentialsProp.getKind() === SyntaxKind.PropertyAssignment) {
const initializer = credentialsProp.getFirstDescendantByKindOrThrow(
SyntaxKind.ArrayLiteralExpression,
);
const credentialObject = ts.factory.createObjectLiteralExpression([
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier('name'),
ts.factory.createStringLiteral(credentialName, true),
),
ts.factory.createPropertyAssignment(
ts.factory.createIdentifier('required'),
ts.factory.createTrue(),
),
]);
initializer.addElement(printNode(credentialObject));
}
return sourceFile;
}

View File

@@ -0,0 +1,3 @@
export declare const credentialTypePrompt: () => Promise<"custom" | "apiKey" | "bearer" | "basicAuth" | "none" | "oauth2">;
export declare const baseUrlPrompt: () => Promise<string>;
export declare const oauthFlowPrompt: () => Promise<"clientCredentials" | "authorizationCode">;

View File

@@ -0,0 +1,80 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.oauthFlowPrompt = exports.baseUrlPrompt = exports.credentialTypePrompt = void 0;
const prompts_1 = require("@clack/prompts");
const prompts_2 = require("../../../../utils/prompts");
const credentialTypePrompt = async () => await (0, prompts_2.withCancelHandler)((0, prompts_1.select)({
message: 'What type of authentication does your API use?',
options: [
{
label: 'API Key',
value: 'apiKey',
hint: 'Send a secret key via headers, query, or body',
},
{
label: 'Bearer Token',
value: 'bearer',
hint: 'Send a token via Authorization header (Authorization: Bearer <token>)',
},
{
label: 'OAuth2',
value: 'oauth2',
hint: 'Use an OAuth 2.0 flow to obtain access tokens on behalf of a user or app',
},
{
label: 'Basic Auth',
value: 'basicAuth',
hint: 'Send username and password encoded in base64 via the Authorization header',
},
{
label: 'Custom',
value: 'custom',
hint: 'Create your own credential logic; an empty credential class will be scaffolded for you',
},
{
label: 'None',
value: 'none',
hint: 'No authentication; no credential class will be generated',
},
],
initialValue: 'apiKey',
}));
exports.credentialTypePrompt = credentialTypePrompt;
const baseUrlPrompt = async () => await (0, prompts_2.withCancelHandler)((0, prompts_1.text)({
message: "What's the base URL of the API?",
placeholder: 'https://api.example.com/v2',
defaultValue: 'https://api.example.com/v2',
validate: (value) => {
if (!value)
return;
if (!value.startsWith('https://') && !value.startsWith('http://')) {
return 'Base URL must start with http(s)://';
}
try {
new URL(value);
}
catch (error) {
return 'Must be a valid URL';
}
return;
},
}));
exports.baseUrlPrompt = baseUrlPrompt;
const oauthFlowPrompt = async () => await (0, prompts_2.withCancelHandler)((0, prompts_1.select)({
message: 'What OAuth2 flow does your API use?',
options: [
{
label: 'Authorization code',
value: 'authorizationCode',
hint: 'Users log in and approve access (use this if unsure)',
},
{
label: 'Client credentials',
value: 'clientCredentials',
hint: 'Server-to-server auth without user interaction',
},
],
initialValue: 'authorizationCode',
}));
exports.oauthFlowPrompt = oauthFlowPrompt;
//# sourceMappingURL=prompts.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../../../src/template/templates/declarative/custom/prompts.ts"],"names":[],"mappings":";;;AAAA,4CAA8C;AAG9C,uDAA8D;AAEvD,MAAM,oBAAoB,GAAG,KAAK,IAAI,EAAE,CAC9C,MAAM,IAAA,2BAAiB,EACtB,IAAA,gBAAM,EAAiB;IACtB,OAAO,EAAE,gDAAgD;IACzD,OAAO,EAAE;QACR;YACC,KAAK,EAAE,SAAS;YAChB,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,+CAA+C;SACrD;QACD;YACC,KAAK,EAAE,cAAc;YACrB,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,uEAAuE;SAC7E;QACD;YACC,KAAK,EAAE,QAAQ;YACf,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,0EAA0E;SAChF;QACD;YACC,KAAK,EAAE,YAAY;YACnB,KAAK,EAAE,WAAW;YAClB,IAAI,EAAE,2EAA2E;SACjF;QACD;YACC,KAAK,EAAE,QAAQ;YACf,KAAK,EAAE,QAAQ;YACf,IAAI,EAAE,wFAAwF;SAC9F;QACD;YACC,KAAK,EAAE,MAAM;YACb,KAAK,EAAE,MAAM;YACb,IAAI,EAAE,0DAA0D;SAChE;KACD;IACD,YAAY,EAAE,QAAQ;CACtB,CAAC,CACF,CAAC;AAtCU,QAAA,oBAAoB,wBAsC9B;AAEI,MAAM,aAAa,GAAG,KAAK,IAAI,EAAE,CACvC,MAAM,IAAA,2BAAiB,EACtB,IAAA,cAAI,EAAC;IACJ,OAAO,EAAE,iCAAiC;IAC1C,WAAW,EAAE,4BAA4B;IACzC,YAAY,EAAE,4BAA4B;IAC1C,QAAQ,EAAE,CAAC,KAAK,EAAE,EAAE;QACnB,IAAI,CAAC,KAAK;YAAE,OAAO;QAEnB,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACnE,OAAO,qCAAqC,CAAC;QAC9C,CAAC;QAED,IAAI,CAAC;YACJ,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,qBAAqB,CAAC;QAC9B,CAAC;QACD,OAAO;IACR,CAAC;CACD,CAAC,CACF,CAAC;AArBU,QAAA,aAAa,iBAqBvB;AAEI,MAAM,eAAe,GAAG,KAAK,IAAI,EAAE,CACzC,MAAM,IAAA,2BAAiB,EACtB,IAAA,gBAAM,EAA4C;IACjD,OAAO,EAAE,qCAAqC;IAC9C,OAAO,EAAE;QACR;YACC,KAAK,EAAE,oBAAoB;YAC3B,KAAK,EAAE,mBAAmB;YAC1B,IAAI,EAAE,sDAAsD;SAC5D;QACD;YACC,KAAK,EAAE,oBAAoB;YAC3B,KAAK,EAAE,mBAAmB;YAC1B,IAAI,EAAE,gDAAgD;SACtD;KACD;IACD,YAAY,EAAE,mBAAmB;CACjC,CAAC,CACF,CAAC;AAlBU,QAAA,eAAe,mBAkBzB"}

View File

@@ -0,0 +1,87 @@
import { select, text } from '@clack/prompts';
import type { CredentialType } from './types';
import { withCancelHandler } from '../../../../utils/prompts';
export const credentialTypePrompt = async () =>
await withCancelHandler(
select<CredentialType>({
message: 'What type of authentication does your API use?',
options: [
{
label: 'API Key',
value: 'apiKey',
hint: 'Send a secret key via headers, query, or body',
},
{
label: 'Bearer Token',
value: 'bearer',
hint: 'Send a token via Authorization header (Authorization: Bearer <token>)',
},
{
label: 'OAuth2',
value: 'oauth2',
hint: 'Use an OAuth 2.0 flow to obtain access tokens on behalf of a user or app',
},
{
label: 'Basic Auth',
value: 'basicAuth',
hint: 'Send username and password encoded in base64 via the Authorization header',
},
{
label: 'Custom',
value: 'custom',
hint: 'Create your own credential logic; an empty credential class will be scaffolded for you',
},
{
label: 'None',
value: 'none',
hint: 'No authentication; no credential class will be generated',
},
],
initialValue: 'apiKey',
}),
);
export const baseUrlPrompt = async () =>
await withCancelHandler(
text({
message: "What's the base URL of the API?",
placeholder: 'https://api.example.com/v2',
defaultValue: 'https://api.example.com/v2',
validate: (value) => {
if (!value) return;
if (!value.startsWith('https://') && !value.startsWith('http://')) {
return 'Base URL must start with http(s)://';
}
try {
new URL(value);
} catch (error) {
return 'Must be a valid URL';
}
return;
},
}),
);
export const oauthFlowPrompt = async () =>
await withCancelHandler(
select<'clientCredentials' | 'authorizationCode'>({
message: 'What OAuth2 flow does your API use?',
options: [
{
label: 'Authorization code',
value: 'authorizationCode',
hint: 'Users log in and approve access (use this if unsure)',
},
{
label: 'Client credentials',
value: 'clientCredentials',
hint: 'Server-to-server auth without user interaction',
},
],
initialValue: 'authorizationCode',
}),
);

View File

@@ -0,0 +1,2 @@
import type { CustomTemplateConfig } from './types';
export declare const customTemplate: import("../../../core").TemplateWithRun<CustomTemplateConfig>;

View File

@@ -0,0 +1,83 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.customTemplate = void 0;
const change_case_1 = require("change-case");
const node_path_1 = __importDefault(require("node:path"));
const ast_1 = require("./ast");
const prompts_1 = require("./prompts");
const filesystem_1 = require("../../../../utils/filesystem");
const package_1 = require("../../../../utils/package");
const core_1 = require("../../../core");
exports.customTemplate = (0, core_1.createTemplate)({
name: 'Start from scratch',
description: 'Blank template with guided setup',
path: node_path_1.default.join(__dirname, 'template'),
prompts: async () => {
const baseUrl = await (0, prompts_1.baseUrlPrompt)();
const credentialType = await (0, prompts_1.credentialTypePrompt)();
if (credentialType === 'oauth2') {
const flow = await (0, prompts_1.oauthFlowPrompt)();
return { credentialType, baseUrl, flow };
}
return { credentialType, baseUrl };
},
run: async (data) => {
await renameNode(data, 'Example');
await addCredential(data);
},
});
async function renameNode(data, oldNodeName) {
const { config, nodePackageName: nodeName, destinationPath } = data;
const newClassName = (0, change_case_1.pascalCase)(nodeName.replace('n8n-nodes-', ''));
const oldNodeDir = node_path_1.default.resolve(destinationPath, `nodes/${oldNodeName}`);
await (0, filesystem_1.renameFilesInDirectory)(oldNodeDir, oldNodeName, newClassName);
const newNodeDir = await (0, filesystem_1.renameDirectory)(oldNodeDir, newClassName);
const newNodePath = node_path_1.default.resolve(newNodeDir, `${newClassName}.node.ts`);
const newNodeAst = (0, ast_1.updateNodeAst)({
nodePath: newNodePath,
baseUrl: config.baseUrl,
className: newClassName,
});
await (0, filesystem_1.writeFileSafe)(newNodePath, newNodeAst.getFullText());
const nodes = [`dist/nodes/${newClassName}/${newClassName}.node.js`];
await (0, package_1.setNodesPackageJson)(destinationPath, nodes);
}
async function addCredential(data) {
const { config, destinationPath, nodePackageName } = data;
if (config.credentialType === 'none')
return;
const credentialTemplateName = config.credentialType === 'oauth2'
? config.credentialType + (0, change_case_1.pascalCase)(config.flow)
: config.credentialType;
const credentialTemplatePath = node_path_1.default.resolve(__dirname, `../../shared/credentials/${credentialTemplateName}.credentials.ts`);
const nodeName = nodePackageName.replace('n8n-nodes', '');
const repoName = nodeName;
const { baseUrl, credentialType } = config;
const credentialClassName = config.credentialType === 'oauth2'
? (0, change_case_1.pascalCase)(`${nodeName}-OAuth2-api`)
: (0, change_case_1.pascalCase)(`${nodeName}-api`);
const credentialName = (0, change_case_1.camelCase)(`${nodeName}${credentialType === 'oauth2' ? 'OAuth2Api' : 'Api'}`);
const credentialDisplayName = `${(0, change_case_1.capitalCase)(nodeName)} ${credentialType === 'oauth2' ? 'OAuth2 API' : 'API'}`;
const updatedCredentialAst = (0, ast_1.updateCredentialAst)({
repoName,
baseUrl,
credentialName,
credentialDisplayName,
credentialClassName,
credentialPath: credentialTemplatePath,
});
await (0, filesystem_1.writeFileSafe)(node_path_1.default.resolve(destinationPath, `credentials/${credentialClassName}.credentials.ts`), updatedCredentialAst.getFullText());
await (0, package_1.addCredentialPackageJson)(destinationPath, `dist/credentials/${credentialClassName}.credentials.js`);
for (const nodePath of await (0, package_1.getPackageJsonNodes)(destinationPath)) {
const srcNodePath = node_path_1.default.resolve(destinationPath, nodePath.replace(/.js$/, '.ts').replace(/^dist\//, ''));
const updatedNodeAst = (0, ast_1.addCredentialToNode)({
nodePath: srcNodePath,
credentialName,
});
await (0, filesystem_1.writeFileSafe)(srcNodePath, updatedNodeAst.getFullText());
}
}
//# sourceMappingURL=template.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"template.js","sourceRoot":"","sources":["../../../../../src/template/templates/declarative/custom/template.ts"],"names":[],"mappings":"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"}

View File

@@ -0,0 +1,121 @@
import { camelCase, capitalCase, pascalCase } from 'change-case';
import path from 'node:path';
import { addCredentialToNode, updateCredentialAst, updateNodeAst } from './ast';
import { baseUrlPrompt, credentialTypePrompt, oauthFlowPrompt } from './prompts';
import type { CustomTemplateConfig } from './types';
import {
renameDirectory,
renameFilesInDirectory,
writeFileSafe,
} from '../../../../utils/filesystem';
import {
setNodesPackageJson,
addCredentialPackageJson,
getPackageJsonNodes,
} from '../../../../utils/package';
import { createTemplate, type TemplateData } from '../../../core';
export const customTemplate = createTemplate({
name: 'Start from scratch',
description: 'Blank template with guided setup',
path: path.join(__dirname, 'template'),
prompts: async (): Promise<CustomTemplateConfig> => {
const baseUrl = await baseUrlPrompt();
const credentialType = await credentialTypePrompt();
if (credentialType === 'oauth2') {
const flow = await oauthFlowPrompt();
return { credentialType, baseUrl, flow };
}
return { credentialType, baseUrl };
},
run: async (data) => {
await renameNode(data, 'Example');
await addCredential(data);
},
});
async function renameNode(data: TemplateData<CustomTemplateConfig>, oldNodeName: string) {
const { config, nodePackageName: nodeName, destinationPath } = data;
const newClassName = pascalCase(nodeName.replace('n8n-nodes-', ''));
const oldNodeDir = path.resolve(destinationPath, `nodes/${oldNodeName}`);
await renameFilesInDirectory(oldNodeDir, oldNodeName, newClassName);
const newNodeDir = await renameDirectory(oldNodeDir, newClassName);
const newNodePath = path.resolve(newNodeDir, `${newClassName}.node.ts`);
const newNodeAst = updateNodeAst({
nodePath: newNodePath,
baseUrl: config.baseUrl,
className: newClassName,
});
await writeFileSafe(newNodePath, newNodeAst.getFullText());
const nodes = [`dist/nodes/${newClassName}/${newClassName}.node.js`];
await setNodesPackageJson(destinationPath, nodes);
}
async function addCredential(data: TemplateData<CustomTemplateConfig>) {
const { config, destinationPath, nodePackageName } = data;
if (config.credentialType === 'none') return;
const credentialTemplateName =
config.credentialType === 'oauth2'
? config.credentialType + pascalCase(config.flow)
: config.credentialType;
const credentialTemplatePath = path.resolve(
__dirname,
`../../shared/credentials/${credentialTemplateName}.credentials.ts`,
);
const nodeName = nodePackageName.replace('n8n-nodes', '');
const repoName = nodeName;
const { baseUrl, credentialType } = config;
const credentialClassName =
config.credentialType === 'oauth2'
? pascalCase(`${nodeName}-OAuth2-api`)
: pascalCase(`${nodeName}-api`);
const credentialName = camelCase(
`${nodeName}${credentialType === 'oauth2' ? 'OAuth2Api' : 'Api'}`,
);
const credentialDisplayName = `${capitalCase(nodeName)} ${
credentialType === 'oauth2' ? 'OAuth2 API' : 'API'
}`;
const updatedCredentialAst = updateCredentialAst({
repoName,
baseUrl,
credentialName,
credentialDisplayName,
credentialClassName,
credentialPath: credentialTemplatePath,
});
await writeFileSafe(
path.resolve(destinationPath, `credentials/${credentialClassName}.credentials.ts`),
updatedCredentialAst.getFullText(),
);
await addCredentialPackageJson(
destinationPath,
`dist/credentials/${credentialClassName}.credentials.js`,
);
for (const nodePath of await getPackageJsonNodes(destinationPath)) {
const srcNodePath = path.resolve(
destinationPath,
nodePath.replace(/.js$/, '.ts').replace(/^dist\//, ''),
);
const updatedNodeAst = addCredentialToNode({
nodePath: srcNodePath,
credentialName,
});
await writeFileSafe(srcNodePath, updatedNodeAst.getFullText());
}
}

View File

@@ -0,0 +1,46 @@
# {{nodePackageName}}
This is an n8n community node. It lets you use _app/service name_ in your n8n workflows.
_App/service name_ is _one or two sentences describing the service this node integrates with_.
[n8n](https://n8n.io/) is a [fair-code licensed](https://docs.n8n.io/sustainable-use-license/) workflow automation platform.
[Installation](#installation)
[Operations](#operations)
[Credentials](#credentials)
[Compatibility](#compatibility)
[Usage](#usage)
[Resources](#resources)
[Version history](#version-history)
## Installation
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
## Operations
_List the operations supported by your node._
## Credentials
_If users need to authenticate with the app/service, provide details here. You should include prerequisites (such as signing up with the service), available authentication methods, and how to set them up._
## Compatibility
_State the minimum n8n version, as well as which versions you test against. You can also include any known version incompatibility issues._
## Usage
_This is an optional section. Use it to help users with any difficult or confusing aspects of the node._
_By the time users are looking for community nodes, they probably already know n8n basics. But if you expect new users, you can link to the [Try it out](https://docs.n8n.io/try-it-out/) documentation to help them get started._
## Resources
* [n8n community nodes documentation](https://docs.n8n.io/integrations/#community-nodes)
* _Link to app/service documentation._
## Version history
_This is another optional section. If your node has multiple versions, include a short description of available versions and what changed, as well as any compatibility impact._

View File

@@ -0,0 +1,18 @@
{
"node": "{{nodePackageName}}",
"nodeVersion": "1.0",
"codexVersion": "1.0",
"categories": ["Development", "Developer Tools"],
"resources": {
"credentialDocumentation": [
{
"url": "https://github.com/org/repo?tab=readme-ov-file#credentials"
}
],
"primaryDocumentation": [
{
"url": "https://github.com/org/repo?tab=readme-ov-file"
}
]
}
}

View File

@@ -0,0 +1,50 @@
import { NodeConnectionType, type INodeType, type INodeTypeDescription } from 'n8n-workflow';
import { userDescription } from './resources/user';
import { companyDescription } from './resources/company';
export class Example implements INodeType {
description: INodeTypeDescription = {
displayName: 'Example',
name: 'example',
icon: { light: 'file:example.svg', dark: 'file:example.dark.svg' },
group: ['transform'],
version: 1,
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
description: 'Interact with the Example API',
defaults: {
name: 'Example',
},
usableAsTool: true,
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
credentials: [],
requestDefaults: {
baseURL: 'https://api.example.com',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
},
properties: [
{
displayName: 'Resource',
name: 'resource',
type: 'options',
noDataExpression: true,
options: [
{
name: 'User',
value: 'user',
},
{
name: 'Company',
value: 'company',
},
],
default: 'user',
},
...userDescription,
...companyDescription,
],
};
}

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="aquamarine"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cpu">
<rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect>
<rect x="9" y="9" width="6" height="6"></rect>
<line x1="9" y1="1" x2="9" y2="4"></line>
<line x1="15" y1="1" x2="15" y2="4"></line>
<line x1="9" y1="20" x2="9" y2="23"></line>
<line x1="15" y1="20" x2="15" y2="23"></line>
<line x1="20" y1="9" x2="23" y2="9"></line>
<line x1="20" y1="14" x2="23" y2="14"></line>
<line x1="1" y1="9" x2="4" y2="9"></line>
<line x1="1" y1="14" x2="4" y2="14"></line>
</svg>

After

Width:  |  Height:  |  Size: 698 B

View File

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="darkblue"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-cpu">
<rect x="4" y="4" width="16" height="16" rx="2" ry="2"></rect>
<rect x="9" y="9" width="6" height="6"></rect>
<line x1="9" y1="1" x2="9" y2="4"></line>
<line x1="15" y1="1" x2="15" y2="4"></line>
<line x1="9" y1="20" x2="9" y2="23"></line>
<line x1="15" y1="20" x2="15" y2="23"></line>
<line x1="20" y1="9" x2="23" y2="9"></line>
<line x1="20" y1="14" x2="23" y2="14"></line>
<line x1="1" y1="9" x2="4" y2="9"></line>
<line x1="1" y1="14" x2="4" y2="14"></line>
</svg>

After

Width:  |  Height:  |  Size: 696 B

View File

@@ -0,0 +1,61 @@
import type { INodeProperties } from 'n8n-workflow';
const showOnlyForCompanyGetMany = {
operation: ['getAll'],
resource: ['company'],
};
export const companyGetManyDescription: INodeProperties[] = [
{
displayName: 'Limit',
name: 'limit',
type: 'number',
displayOptions: {
show: {
...showOnlyForCompanyGetMany,
returnAll: [false],
},
},
typeOptions: {
minValue: 1,
maxValue: 100,
},
default: 50,
routing: {
send: {
type: 'query',
property: 'limit',
},
output: {
maxResults: '={{$value}}',
},
},
description: 'Max number of results to return',
},
{
displayName: 'Return All',
name: 'returnAll',
type: 'boolean',
displayOptions: {
show: showOnlyForCompanyGetMany,
},
default: false,
description: 'Whether to return all results or only up to a given limit',
routing: {
send: {
paginate: '={{ $value }}',
},
operations: {
pagination: {
type: 'offset',
properties: {
limitParameter: 'limit',
offsetParameter: 'offset',
pageSize: 100,
type: 'query',
},
},
},
},
},
];

View File

@@ -0,0 +1,34 @@
import type { INodeProperties } from 'n8n-workflow';
import { companyGetManyDescription } from './getAll';
const showOnlyForCompanies = {
resource: ['company'],
};
export const companyDescription: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: showOnlyForCompanies,
},
options: [
{
name: 'Get Many',
value: 'getAll',
action: 'Get companies',
description: 'Get companies',
routing: {
request: {
method: 'GET',
url: '/companies',
},
},
},
],
default: 'getAll',
},
...companyGetManyDescription,
];

View File

@@ -0,0 +1,26 @@
import type { INodeProperties } from 'n8n-workflow';
const showOnlyForUserCreate = {
operation: ['create'],
resource: ['user'],
};
export const userCreateDescription: INodeProperties[] = [
{
displayName: 'Name',
name: 'name',
type: 'string',
default: '',
required: true,
displayOptions: {
show: showOnlyForUserCreate,
},
description: 'The name of the user',
routing: {
send: {
type: 'body',
property: 'name',
},
},
},
];

View File

@@ -0,0 +1,17 @@
import type { INodeProperties } from 'n8n-workflow';
const showOnlyForUserGet = {
operation: ['get'],
resource: ['user'],
};
export const userGetDescription: INodeProperties[] = [
{
displayName: 'User ID',
name: 'userId',
type: 'string',
displayOptions: { show: showOnlyForUserGet },
default: '',
description: "The user's ID to retrieve",
},
];

View File

@@ -0,0 +1,60 @@
import type { INodeProperties } from 'n8n-workflow';
import { userCreateDescription } from './create';
import { userGetDescription } from './get';
const showOnlyForUsers = {
resource: ['user'],
};
export const userDescription: INodeProperties[] = [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
displayOptions: {
show: showOnlyForUsers,
},
options: [
{
name: 'Get Many',
value: 'getAll',
action: 'Get users',
description: 'Get many users',
routing: {
request: {
method: 'GET',
url: '/users',
},
},
},
{
name: 'Get',
value: 'get',
action: 'Get a user',
description: 'Get the data of a single user',
routing: {
request: {
method: 'GET',
url: '=/users/{{$parameter.userId}}',
},
},
},
{
name: 'Create',
value: 'create',
action: 'Create a new user',
description: 'Create a new user',
routing: {
request: {
method: 'POST',
url: '/users',
},
},
},
],
default: 'getAll',
},
...userGetDescription,
...userCreateDescription,
];

View File

@@ -0,0 +1,48 @@
{
"name": "{{nodePackageName}}",
"version": "0.1.0",
"description": "",
"license": "MIT",
"homepage": "",
"keywords": [
"n8n-community-node-package"
],
"author": {
"name": "{{user.name}}",
"email": "{{user.email}}"
},
"repository": {
"type": "git",
"url": "https://github.com/<...>/n8n-nodes-<...>.git"
},
"scripts": {
"build": "n8n-node build",
"build:watch": "tsc --watch",
"dev": "n8n-node dev",
"lint": "n8n-node lint",
"lint:fix": "n8n-node lint --fix",
"release": "n8n-node release",
"prepublishOnly": "n8n-node prerelease"
},
"files": [
"dist"
],
"n8n": {
"n8nNodesApiVersion": 1,
"strict": true,
"credentials": [],
"nodes": [
"dist/nodes/Example/Example.node.js"
]
},
"devDependencies": {
"@n8n/node-cli": "*",
"eslint": "9.32.0",
"prettier": "3.6.2",
"release-it": "^19.0.4",
"typescript": "5.9.2"
},
"peerDependencies": {
"n8n-workflow": "*"
}
}

View File

@@ -0,0 +1,25 @@
{
"compilerOptions": {
"strict": true,
"module": "commonjs",
"moduleResolution": "node",
"target": "es2019",
"lib": ["es2019", "es2020", "es2022.error"],
"removeComments": true,
"useUnknownInCatchVariables": false,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"strictNullChecks": true,
"preserveConstEnums": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"incremental": true,
"declaration": true,
"sourceMap": true,
"skipLibCheck": true,
"outDir": "./dist/"
},
"include": ["credentials/**/*", "nodes/**/*", "nodes/**/*.json", "package.json"]
}

View File

@@ -0,0 +1,9 @@
export type CustomTemplateConfig = {
credentialType: 'apiKey' | 'bearer' | 'basicAuth' | 'custom' | 'none';
baseUrl: string;
} | {
credentialType: 'oauth2';
baseUrl: string;
flow: string;
};
export type CredentialType = CustomTemplateConfig['credentialType'];

View File

@@ -0,0 +1,3 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=types.js.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../../src/template/templates/declarative/custom/types.ts"],"names":[],"mappings":""}

View File

@@ -0,0 +1,8 @@
export type CustomTemplateConfig =
| {
credentialType: 'apiKey' | 'bearer' | 'basicAuth' | 'custom' | 'none';
baseUrl: string;
}
| { credentialType: 'oauth2'; baseUrl: string; flow: string };
export type CredentialType = CustomTemplateConfig['credentialType'];