Files
n8n/packages/@n8n/node-cli/src/commands/new/index.ts
T
alighasami 3d5eaf9445
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
first commit
2026-03-17 16:22:57 +03:30

192 lines
6.1 KiB
TypeScript

import { confirm, intro, isCancel, log, note, outro, spinner } from '@clack/prompts';
import { Args, Command, Flags } from '@oclif/core';
import { camelCase } from 'change-case';
import fs from 'node:fs/promises';
import path from 'node:path';
import {
chatModelTypePrompt,
declarativeTemplatePrompt,
nodeNamePrompt,
nodeTypePrompt,
programmaticNodeTypePrompt,
} from './prompts';
import { createIntro } from './utils';
import type { TemplateData, TemplateWithRun } from '../../template/core';
import { getTemplate, isTemplateName, isTemplateType, templates } from '../../template/templates';
import { ChildProcessError, runCommand } from '../../utils/child-process';
import { delayAtLeast, folderExists } from '../../utils/filesystem';
import { initGit, tryReadGitUser } from '../../utils/git';
import { detectPackageManager } from '../../utils/package-manager';
import { onCancel } from '../../utils/prompts';
import { validateNodeName } from '../../utils/validation';
export default class New extends Command {
static override description = 'Create a starter community node in a new directory';
static override examples = [
'<%= config.bin %> <%= command.id %>',
'<%= config.bin %> <%= command.id %> n8n-nodes-my-app --skip-install',
'<%= config.bin %> <%= command.id %> n8n-nodes-my-app --force',
'<%= config.bin %> <%= command.id %> n8n-nodes-my-app --template declarative/custom',
];
static override args = {
name: Args.string({ name: 'Name' }),
};
static override flags = {
force: Flags.boolean({
char: 'f',
description: 'Overwrite destination folder if it already exists',
}),
'skip-install': Flags.boolean({ description: 'Skip installing dependencies' }),
template: Flags.string({
options: [
'declarative/github-issues',
'declarative/custom',
'programmatic/example',
'programmatic/openai-chat-model',
'programmatic/custom-chat-model',
'programmatic/custom-chat-model-example',
'programmatic/custom-chat-memory',
] as const,
}),
};
async run(): Promise<void> {
const { flags, args } = await this.parse(New);
const [typeFlag, templateFlag] = flags.template?.split('/') ?? [];
intro(await createIntro());
const nodeName = args.name ?? (await nodeNamePrompt());
const invalidNodeNameError = validateNodeName(nodeName);
if (invalidNodeNameError) return onCancel(invalidNodeNameError);
const destination = path.resolve(process.cwd(), nodeName);
let overwrite = false;
if (await folderExists(destination)) {
if (!flags.force) {
const shouldOverwrite = await confirm({
message: `./${nodeName} already exists, do you want to overwrite?`,
});
if (isCancel(shouldOverwrite) || !shouldOverwrite) return onCancel();
}
overwrite = true;
}
const type = typeFlag ?? (await nodeTypePrompt());
if (!isTemplateType(type)) {
return onCancel(`Invalid template type: ${type}`);
}
let template: TemplateWithRun = templates.programmatic.example;
if (templateFlag) {
const name = camelCase(templateFlag);
if (!isTemplateName(type, name)) {
return onCancel(`Invalid template name: ${name} for type: ${type}`);
}
template = getTemplate(type, name);
} else if (type === 'declarative') {
const chosenTemplate = await declarativeTemplatePrompt();
template = getTemplate('declarative', chosenTemplate) as TemplateWithRun;
} else if (type === 'programmatic') {
const programmaticNodeType = await programmaticNodeTypePrompt();
if (programmaticNodeType === 'basic') {
template = templates.programmatic.example;
} else if (programmaticNodeType === 'chatModel') {
const chatModelType = await chatModelTypePrompt();
if (chatModelType === 'openaiCompatible') {
template = templates.programmatic.openaiChatModel as TemplateWithRun;
} else if (chatModelType === 'custom') {
template = templates.programmatic.customChatModel as TemplateWithRun;
} else if (chatModelType === 'customExample') {
template = templates.programmatic.customChatModelExample as TemplateWithRun;
}
} else if (programmaticNodeType === 'chatMemory') {
template = templates.programmatic.customChatMemory as TemplateWithRun;
}
}
const config = (await template.prompts?.()) ?? {};
const packageManager = (await detectPackageManager()) ?? 'npm';
const templateData: TemplateData = {
destinationPath: destination,
nodePackageName: nodeName,
config,
user: tryReadGitUser(),
packageManager: {
name: packageManager,
installCommand: packageManager === 'npm' ? 'ci' : 'install',
},
};
const copyingSpinner = spinner();
copyingSpinner.start('Copying files');
if (overwrite) {
await fs.rm(destination, { recursive: true, force: true });
}
await delayAtLeast(template.run(templateData), 1000);
copyingSpinner.stop('Files copied');
const gitSpinner = spinner();
gitSpinner.start('Initializing git repository');
try {
await initGit(destination);
gitSpinner.stop('Git repository initialized');
} catch (error: unknown) {
if (error instanceof ChildProcessError) {
gitSpinner.stop(
`Could not initialize git repository: ${error.message}`,
error.code ?? undefined,
);
process.exit(error.code ?? 1);
} else {
throw error;
}
}
if (!flags['skip-install']) {
const installingSpinner = spinner();
installingSpinner.start('Installing dependencies');
try {
await delayAtLeast(
runCommand(packageManager, ['install'], {
cwd: destination,
printOutput: ({ stdout, stderr }) => {
log.error(stdout.concat(stderr).toString());
},
}),
1000,
);
} catch (error: unknown) {
if (error instanceof ChildProcessError) {
installingSpinner.stop(
`Could not install dependencies: ${error.message}`,
error.code ?? undefined,
);
process.exit(error.code ?? 1);
} else {
throw error;
}
}
installingSpinner.stop('Dependencies installed');
}
note(
`cd ./${nodeName} && ${packageManager} run dev
📚 Documentation: https://docs.n8n.io/integrations/creating-nodes/build/${type}-style-node/
💬 Community: https://community.n8n.io`,
'Next Steps',
);
outro(`Created ./${nodeName} ✨`);
}
}