From 79c6b60fcb5ea9a3e22a8aa0fa516f270239df69 Mon Sep 17 00:00:00 2001 From: Elias Meire Date: Thu, 31 Jul 2025 00:09:49 +0200 Subject: [PATCH] feat: Add @n8n/node-cli package with an empty create command (#17620) --- packages/@n8n/create-node/README.md | 13 ++++ packages/@n8n/create-node/bin/create.js | 15 ++++ packages/@n8n/create-node/package.json | 25 +++++++ packages/@n8n/create-node/tsconfig.json | 11 +++ packages/@n8n/node-cli/README.md | 35 ++++++++++ packages/@n8n/node-cli/bin/n8n-node.js | 5 ++ packages/@n8n/node-cli/eslint.config.mjs | 7 ++ packages/@n8n/node-cli/package.json | 46 ++++++++++++ .../@n8n/node-cli/src/commands/create.test.ts | 8 +++ packages/@n8n/node-cli/src/commands/create.ts | 17 +++++ packages/@n8n/node-cli/tsconfig.json | 12 ++++ packages/@n8n/node-cli/vite.config.ts | 4 ++ pnpm-lock.yaml | 70 +++++++++++++++++++ 13 files changed, 268 insertions(+) create mode 100644 packages/@n8n/create-node/README.md create mode 100755 packages/@n8n/create-node/bin/create.js create mode 100644 packages/@n8n/create-node/package.json create mode 100644 packages/@n8n/create-node/tsconfig.json create mode 100644 packages/@n8n/node-cli/README.md create mode 100755 packages/@n8n/node-cli/bin/n8n-node.js create mode 100644 packages/@n8n/node-cli/eslint.config.mjs create mode 100644 packages/@n8n/node-cli/package.json create mode 100644 packages/@n8n/node-cli/src/commands/create.test.ts create mode 100644 packages/@n8n/node-cli/src/commands/create.ts create mode 100644 packages/@n8n/node-cli/tsconfig.json create mode 100644 packages/@n8n/node-cli/vite.config.ts diff --git a/packages/@n8n/create-node/README.md b/packages/@n8n/create-node/README.md new file mode 100644 index 0000000000..f2918e668f --- /dev/null +++ b/packages/@n8n/create-node/README.md @@ -0,0 +1,13 @@ +# @n8n/create-node + +Scaffold a new community n8n node + +## Usage + +```bash +npm create @n8n/node +# or +pnpm create @n8n/node +# or +yarn create @n8n/node +``` diff --git a/packages/@n8n/create-node/bin/create.js b/packages/@n8n/create-node/bin/create.js new file mode 100755 index 0000000000..51e8287e75 --- /dev/null +++ b/packages/@n8n/create-node/bin/create.js @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +import { spawnSync } from 'node:child_process'; +import { createRequire } from 'node:module'; +import path from 'node:path'; + +const require = createRequire(import.meta.url); + +const cliBin = require.resolve('@n8n/node-cli/bin/n8n-node.js'); + +const result = spawnSync('node', [cliBin, 'create', ...process.argv.slice(2)], { + stdio: 'inherit', +}); + +process.exit(result.status ?? 1); diff --git a/packages/@n8n/create-node/package.json b/packages/@n8n/create-node/package.json new file mode 100644 index 0000000000..76247e964b --- /dev/null +++ b/packages/@n8n/create-node/package.json @@ -0,0 +1,25 @@ +{ + "private": true, + "type": "module", + "name": "@n8n/create-node", + "version": "0.1.0", + "description": "Official CLI to create new community nodes for n8n", + "bin": { + "create-n8n-node": "./bin/create.js" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "publish:dry": "pnpm run build && pnpm pub --dry-run", + "start": "./bin/create.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/n8n-io/n8n" + }, + "dependencies": { + "@n8n/node-cli": "workspace:*" + } +} diff --git a/packages/@n8n/create-node/tsconfig.json b/packages/@n8n/create-node/tsconfig.json new file mode 100644 index 0000000000..1fc9b18700 --- /dev/null +++ b/packages/@n8n/create-node/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@n8n/typescript-config/modern/tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "types": ["vite/client", "vitest/globals"], + "isolatedModules": true + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/@n8n/node-cli/README.md b/packages/@n8n/node-cli/README.md new file mode 100644 index 0000000000..7bfce9cfcb --- /dev/null +++ b/packages/@n8n/node-cli/README.md @@ -0,0 +1,35 @@ +# @n8n/node-cli + +Official CLI for developing community nodes for [n8n](https://n8n.io). + +## Features + +- 🔧 Scaffold new nodes +- More coming soon + +## Installation + +Run directly via `npx`: + +```bash +npx n8n-node create +``` + +Or install globally: + +```bash +npm install -g @n8n/node-cli +n8n-node create +``` + +## Commands + +## Create a node + +```bash +n8n-node create # Scaffold a new node +``` + +## Related + +`@n8n/create-node`: Lightweight wrapper to support `npm create @n8n/node` diff --git a/packages/@n8n/node-cli/bin/n8n-node.js b/packages/@n8n/node-cli/bin/n8n-node.js new file mode 100755 index 0000000000..176d2af58c --- /dev/null +++ b/packages/@n8n/node-cli/bin/n8n-node.js @@ -0,0 +1,5 @@ +#!/usr/bin/env node + +import { execute } from '@oclif/core'; + +await execute({ dir: import.meta.url }); diff --git a/packages/@n8n/node-cli/eslint.config.mjs b/packages/@n8n/node-cli/eslint.config.mjs new file mode 100644 index 0000000000..378bbb0b8f --- /dev/null +++ b/packages/@n8n/node-cli/eslint.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'eslint/config'; +import { nodeConfig } from '@n8n/eslint-config/node'; + +export default defineConfig(nodeConfig, { + files: ['./src/commands/*.ts'], + rules: { 'import-x/no-default-export': 'off' }, +}); diff --git a/packages/@n8n/node-cli/package.json b/packages/@n8n/node-cli/package.json new file mode 100644 index 0000000000..647fb474b0 --- /dev/null +++ b/packages/@n8n/node-cli/package.json @@ -0,0 +1,46 @@ +{ + "private": true, + "type": "module", + "name": "@n8n/node-cli", + "version": "0.1.0", + "description": "Official CLI for developing community nodes for n8n", + "bin": { + "n8n-node": "./bin/n8n-node.js" + }, + "files": [ + "bin", + "dist" + ], + "scripts": { + "clean": "rimraf dist .turbo", + "typecheck": "tsc --noEmit", + "dev": "tsc -w", + "format": "biome format --write src", + "format:check": "biome ci src", + "lint": "eslint src --quiet", + "lintfix": "eslint src --fix", + "build": "tsc", + "publish:dry": "pnpm run build && pnpm pub --dry-run", + "test": "vitest run", + "test:dev": "vitest --silent=false", + "start": "./bin/n8n-node.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/n8n-io/n8n" + }, + "oclif": { + "bin": "n8n-node", + "commands": "./dist/commands", + "topicSeparator": " " + }, + "dependencies": { + "@oclif/core": "^4.5.2", + "prompts": "^2.4.2" + }, + "devDependencies": { + "@n8n/typescript-config": "workspace:*", + "@n8n/vitest-config": "workspace:*", + "@oclif/test": "^4.1.13" + } +} diff --git a/packages/@n8n/node-cli/src/commands/create.test.ts b/packages/@n8n/node-cli/src/commands/create.test.ts new file mode 100644 index 0000000000..0a68c80120 --- /dev/null +++ b/packages/@n8n/node-cli/src/commands/create.test.ts @@ -0,0 +1,8 @@ +import { runCommand } from '@oclif/test'; + +describe('n8n-node create', () => { + it('should print correct output', async () => { + const { stdout } = await runCommand('create -f', { root: import.meta.dirname }); + expect(stdout).toEqual('hello from commands/create.ts (force=true)\n'); + }); +}); diff --git a/packages/@n8n/node-cli/src/commands/create.ts b/packages/@n8n/node-cli/src/commands/create.ts new file mode 100644 index 0000000000..64710cc077 --- /dev/null +++ b/packages/@n8n/node-cli/src/commands/create.ts @@ -0,0 +1,17 @@ +import { Command, Flags } from '@oclif/core'; + +export default class Create extends Command { + static override description = 'Create a new n8n community node'; + static override examples = ['<%= config.bin %> <%= command.id %>']; + static override flags = { + // flag with no value (-f, --force) + force: Flags.boolean({ char: 'f' }), + }; + + async run(): Promise { + const { flags } = await this.parse(Create); + + const force = flags.force; + this.log(`hello from commands/create.ts (force=${force})`); + } +} diff --git a/packages/@n8n/node-cli/tsconfig.json b/packages/@n8n/node-cli/tsconfig.json new file mode 100644 index 0000000000..afd42eafbf --- /dev/null +++ b/packages/@n8n/node-cli/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "@n8n/typescript-config/modern/tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "src", + "outDir": "dist", + "types": ["vite/client", "vitest/globals"], + "isolatedModules": true + }, + "include": ["src/**/*.ts"], + "exclude": ["src/**/*.test.ts"] +} diff --git a/packages/@n8n/node-cli/vite.config.ts b/packages/@n8n/node-cli/vite.config.ts new file mode 100644 index 0000000000..90ab02b077 --- /dev/null +++ b/packages/@n8n/node-cli/vite.config.ts @@ -0,0 +1,4 @@ +import { vitestConfig } from '@n8n/vitest-config/node'; +import { defineConfig, mergeConfig } from 'vitest/config'; + +export default defineConfig({ test: { globals: true, disableConsoleIntercept: true } }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c0b399cb23..ff20f83e15 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -635,6 +635,12 @@ importers: specifier: workspace:* version: link:../typescript-config + packages/@n8n/create-node: + dependencies: + '@n8n/node-cli': + specifier: workspace:* + version: link:../node-cli + packages/@n8n/db: dependencies: '@n8n/api-types': @@ -912,6 +918,25 @@ importers: specifier: 'catalog:' version: 3.25.67 + packages/@n8n/node-cli: + dependencies: + '@oclif/core': + specifier: ^4.5.2 + version: 4.5.2 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + devDependencies: + '@n8n/typescript-config': + specifier: workspace:* + version: link:../typescript-config + '@n8n/vitest-config': + specifier: workspace:* + version: link:../vitest-config + '@oclif/test': + specifier: ^4.1.13 + version: 4.1.13(@oclif/core@4.5.2) + packages/@n8n/nodes-langchain: dependencies: '@aws-sdk/client-sso-oidc': @@ -5734,6 +5759,16 @@ packages: resolution: {integrity: sha512-sU4Dx+RXCWAkrMw8tQFYAL6VfcHYKLPxVC9iKfgTXr4aDhcCssDwrbgpx0Di1dnNxvQlDGUhuCEInZuIY/nNfw==} engines: {node: '>=18.0.0'} + '@oclif/core@4.5.2': + resolution: {integrity: sha512-eQcKyrEcDYeZJKu4vUWiu0ii/1Gfev6GF4FsLSgNez5/+aQyAUCjg3ZWlurf491WiYZTXCWyKAxyPWk8DKv2MA==} + engines: {node: '>=18.0.0'} + + '@oclif/test@4.1.13': + resolution: {integrity: sha512-pulrTiJRhoAKizFf6y5WeHvM2JyoRiZKV0H8qqYEoE0UHDKqInNmfGJyp8Ip6lTVQeMv1U8YCAXOS/HiWPVWeg==} + engines: {node: '>=18.0.0'} + peerDependencies: + '@oclif/core': '>= 3.0.0' + '@one-ini/wasm@0.1.1': resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} @@ -8047,6 +8082,10 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + ansis@3.17.0: + resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==} + engines: {node: '>=14'} + ansis@3.2.0: resolution: {integrity: sha512-Yk3BkHH9U7oPyCN3gL5Tc7CpahG/+UFv/6UG03C311Vy9lzRmA5uoxDTpU9CO3rGHL6KzJz/pdDeXZCZ5Mu/Sg==} engines: {node: '>=15'} @@ -19843,6 +19882,35 @@ snapshots: wordwrap: 1.0.0 wrap-ansi: 7.0.0 + '@oclif/core@4.5.2': + dependencies: + ansi-escapes: 4.3.2 + ansis: 3.17.0 + clean-stack: 3.0.1 + cli-spinners: 2.9.2 + debug: 4.4.1(supports-color@8.1.1) + ejs: 3.1.10 + get-package-type: 0.1.0 + indent-string: 4.0.0 + is-wsl: 2.2.0 + lilconfig: 3.1.3 + minimatch: 9.0.5 + semver: 7.7.2 + string-width: 4.2.3 + supports-color: 8.1.1 + tinyglobby: 0.2.14 + widest-line: 3.1.0 + wordwrap: 1.0.0 + wrap-ansi: 7.0.0 + + '@oclif/test@4.1.13(@oclif/core@4.5.2)': + dependencies: + '@oclif/core': 4.5.2 + ansis: 3.17.0 + debug: 4.4.1(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + '@one-ini/wasm@0.1.1': {} '@open-draft/deferred-promise@2.2.0': {} @@ -22732,6 +22800,8 @@ snapshots: ansi-styles@6.2.1: {} + ansis@3.17.0: {} + ansis@3.2.0: {} any-promise@1.3.0: {}