mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-16 17:46:45 +00:00
feat: Add release and lint scripts to node CLI (#18935)
This commit is contained in:
@@ -1,13 +1,297 @@
|
|||||||
# @n8n/create-node
|
# @n8n/create-node
|
||||||
|
|
||||||
Scaffold a new community n8n node
|
A powerful scaffolding tool to quickly create custom n8n community nodes with best practices built-in.
|
||||||
|
|
||||||
## Usage
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
Create a new n8n node in seconds:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm create @n8n/node
|
|
||||||
# or
|
|
||||||
pnpm create @n8n/node
|
pnpm create @n8n/node
|
||||||
# or
|
|
||||||
yarn create @n8n/node
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Follow the interactive prompts to configure your node, or specify options directly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm create @n8n/node my-awesome-node --template declarative/custom
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Command Line Options
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm create @n8n/node [NAME] [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `-f, --force` | Overwrite destination folder if it already exists |
|
||||||
|
| `--skip-install` | Skip automatic dependency installation |
|
||||||
|
| `--template <template>` | Specify which template to use |
|
||||||
|
|
||||||
|
### Available Templates
|
||||||
|
|
||||||
|
- **`declarative/custom`** - Start with a minimal declarative node structure
|
||||||
|
- **`declarative/github-issues`** - GitHub Issues integration example
|
||||||
|
- **`programmatic/example`** - Full programmatic node with advanced features
|
||||||
|
|
||||||
|
## 🎯 Interactive Setup
|
||||||
|
|
||||||
|
The CLI will guide you through setting up your node:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pnpm create @n8n/node
|
||||||
|
┌ @n8n/create-node
|
||||||
|
│
|
||||||
|
◇ What is your node called?
|
||||||
|
│ my-awesome-api-node
|
||||||
|
│
|
||||||
|
◇ What kind of node are you building?
|
||||||
|
│ HTTP API
|
||||||
|
│
|
||||||
|
◇ What template do you want to use?
|
||||||
|
│ Start from scratch
|
||||||
|
│
|
||||||
|
◇ What's the base URL of the API?
|
||||||
|
│ https://api.example.com/v1
|
||||||
|
│
|
||||||
|
◇ What type of authentication does your API use?
|
||||||
|
│ API Key
|
||||||
|
│
|
||||||
|
◇ Files copied ✓
|
||||||
|
│
|
||||||
|
◇ Dependencies installed ✓
|
||||||
|
│
|
||||||
|
◇ Next Steps ─────────────────────────────────────────────────────────────────────╮
|
||||||
|
│ │
|
||||||
|
│ cd ./my-awesome-api-node && pnpm run dev │
|
||||||
|
│ │
|
||||||
|
│ 📚 Documentation: https://docs.n8n.io/integrations/creating-nodes/ │
|
||||||
|
│ 💬 Community: https://community.n8n.io │
|
||||||
|
│ │
|
||||||
|
├──────────────────────────────────────────────────────────────────────────────────╯
|
||||||
|
│
|
||||||
|
└ Created ./my-awesome-api-node ✨
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ Development Workflow
|
||||||
|
|
||||||
|
### 1. Navigate to your project
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ./my-awesome-api-node
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Start development server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This command:
|
||||||
|
- Starts n8n in development mode on `http://localhost:5678`
|
||||||
|
- Enables hot reload for your node changes
|
||||||
|
- Automatically includes your node in the n8n instance
|
||||||
|
- Links your node to `~/.n8n-node-cli/.n8n/custom` for development
|
||||||
|
- Watches for file changes and rebuilds automatically
|
||||||
|
|
||||||
|
### 3. Test your node
|
||||||
|
|
||||||
|
- Open n8n at `http://localhost:5678`
|
||||||
|
- Create a new workflow
|
||||||
|
- Find your node in the node panel
|
||||||
|
- Test parameters and functionality in real-time
|
||||||
|
|
||||||
|
## 📦 Generated Project Commands
|
||||||
|
|
||||||
|
Your generated project comes with these convenient npm scripts:
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
# Runs: n8n-node dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
# Runs: n8n-node build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
```bash
|
||||||
|
pnpm lint
|
||||||
|
# Runs: n8n-node lint
|
||||||
|
|
||||||
|
pnpm lint:fix
|
||||||
|
# Runs: n8n-node lint --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing
|
||||||
|
```bash
|
||||||
|
pnpm run release
|
||||||
|
# Runs: n8n-node release
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📦 Build & Deploy
|
||||||
|
|
||||||
|
### Build for production
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
- Compiled TypeScript code
|
||||||
|
- Bundled node package
|
||||||
|
- Optimized assets and icons
|
||||||
|
- Ready-to-publish package
|
||||||
|
|
||||||
|
### Quality checks
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm lint
|
||||||
|
```
|
||||||
|
|
||||||
|
Validates:
|
||||||
|
- Code style and formatting
|
||||||
|
- n8n node conventions
|
||||||
|
- Common integration issues
|
||||||
|
- Cloud publication readiness
|
||||||
|
|
||||||
|
Fix issues automatically:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm lint:fix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publish your node
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run release
|
||||||
|
```
|
||||||
|
|
||||||
|
Runs [release-it](https://github.com/release-it/release-it) to handle the complete release process:
|
||||||
|
- Ensures working directory is clean
|
||||||
|
- Verifies you're on the main git branch
|
||||||
|
- Increments your package version
|
||||||
|
- Runs build and lint checks
|
||||||
|
- Updates changelog
|
||||||
|
- Creates git tag with version bump
|
||||||
|
- Creates GitHub release with changelog
|
||||||
|
- Publishes to npm
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
Your generated project includes:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-awesome-api-node/
|
||||||
|
├── src/
|
||||||
|
│ ├── nodes/
|
||||||
|
│ │ └── MyAwesomeApi/
|
||||||
|
│ │ ├── MyAwesomeApi.node.ts # Main node logic
|
||||||
|
│ │ └── MyAwesomeApi.node.json # Node metadata
|
||||||
|
│ └── credentials/
|
||||||
|
│ └── MyAwesomeApiAuth.credentials.ts
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
The CLI expects your project to follow this structure for proper building and development.
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
The CLI reads configuration from your `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "n8n-nodes-my-awesome-node",
|
||||||
|
"n8n": {
|
||||||
|
"n8nNodesApiVersion": 1,
|
||||||
|
"nodes": [
|
||||||
|
"dist/nodes/MyAwesomeApi/MyAwesomeApi.node.js"
|
||||||
|
],
|
||||||
|
"credentials": [
|
||||||
|
"dist/credentials/MyAwesomeApiAuth.credentials.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🎨 Node Types
|
||||||
|
|
||||||
|
Choose the right template for your use case:
|
||||||
|
|
||||||
|
| Template | Best For | Features |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| **Declarative** | REST APIs, simple integrations | JSON-based configuration, automatic UI generation |
|
||||||
|
| **Programmatic** | Complex logic, custom operations | Full TypeScript control, advanced error handling |
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**Node not appearing in n8n:**
|
||||||
|
```bash
|
||||||
|
# Clear n8n node cli cache and restart
|
||||||
|
rm -rf ~/.n8n-node-cli/.n8n/custom
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
**TypeScript errors:**
|
||||||
|
```bash
|
||||||
|
# Reinstall dependencies
|
||||||
|
rm -rf node_modules pnpm-lock.yaml
|
||||||
|
pnpm install
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build failures:**
|
||||||
|
```bash
|
||||||
|
# Check for linting issues first
|
||||||
|
pnpm lint --fix
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Development server issues:**
|
||||||
|
```bash
|
||||||
|
# Clear cache and restart development server
|
||||||
|
rm -rf ~/.n8n-node-cli/.n8n/custom
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔧 Advanced Usage
|
||||||
|
|
||||||
|
### Using External n8n Instance
|
||||||
|
|
||||||
|
If you prefer to use your own n8n installation:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev --external-n8n
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom User Folder
|
||||||
|
|
||||||
|
Specify a custom location for n8n user data:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm dev --custom-user-folder /path/to/custom/folder
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Resources
|
||||||
|
|
||||||
|
- **[Node Development Guide](https://docs.n8n.io/integrations/creating-nodes/)** - Complete documentation
|
||||||
|
- **[API Reference](https://docs.n8n.io/integrations/creating-nodes/build/reference/)** - Technical specifications
|
||||||
|
- **[Community Forum](https://community.n8n.io)** - Get help and share your nodes
|
||||||
|
- **[Node Examples](https://github.com/n8n-io/n8n/tree/master/packages/nodes-base/nodes)** - Official node implementations
|
||||||
|
- **[@n8n/node-cli](https://www.npmjs.com/package/@n8n/node-cli)** - The underlying CLI tool
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Found a bug or want to contribute? Check out the [n8n repository](https://github.com/n8n-io/n8n) and join our community!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy node building! 🎉**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@n8n/create-node",
|
"name": "@n8n/create-node",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "Official CLI to create new community nodes for n8n",
|
"description": "Official CLI to create new community nodes for n8n",
|
||||||
"bin": {
|
"bin": {
|
||||||
"create-n8n-node": "bin/create.js"
|
"create-n8n-node": "bin/create.js"
|
||||||
|
|||||||
@@ -1,47 +1,272 @@
|
|||||||
# @n8n/node-cli
|
# @n8n/node-cli
|
||||||
|
|
||||||
Official CLI for developing community nodes for [n8n](https://n8n.io).
|
Official CLI for developing community nodes for n8n.
|
||||||
|
|
||||||
## Features
|
## 🚀 Getting Started
|
||||||
|
|
||||||
- 🔧 Scaffold new n8n nodes
|
**To create a new node**, run:
|
||||||
- 💻 Develop n8n nodes with live preview
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Run directly via `npx`:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx n8n-node new
|
pnpm create @n8n/node
|
||||||
```
|
```
|
||||||
|
|
||||||
Or install globally:
|
This will generate a project with `pnpm` scripts that use this CLI under the hood.
|
||||||
|
|
||||||
|
## 📦 Generated Project Commands
|
||||||
|
|
||||||
|
After creating your node with `pnpm create @n8n/node`, you'll use these commands in your project:
|
||||||
|
|
||||||
|
### Development
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
# Runs: n8n-node dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
# Runs: n8n-node build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linting
|
||||||
|
```bash
|
||||||
|
pnpm lint
|
||||||
|
# Runs: n8n-node lint
|
||||||
|
|
||||||
|
pnpm lint:fix
|
||||||
|
# Runs: n8n-node lint --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
### Publishing
|
||||||
|
```bash
|
||||||
|
pnpm run release
|
||||||
|
# Runs: n8n-node release
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🛠️ CLI Reference
|
||||||
|
|
||||||
|
> **Note:** These commands are typically wrapped by `pnpm` scripts in generated projects.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install -g @n8n/node-cli
|
n8n-node [COMMAND] [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
#### `n8n-node new`
|
||||||
|
|
||||||
|
Create a new node project.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
n8n-node new [NAME] [OPTIONS]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flags:**
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `-f, --force` | Overwrite destination folder if it already exists |
|
||||||
|
| `--skip-install` | Skip installing dependencies |
|
||||||
|
| `--template <template>` | Choose template: `declarative/custom`, `declarative/github-issues`, `programmatic/example` |
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
n8n-node new
|
n8n-node new
|
||||||
|
n8n-node new n8n-nodes-my-app --skip-install
|
||||||
|
n8n-node new n8n-nodes-my-app --force
|
||||||
|
n8n-node new n8n-nodes-my-app --template declarative/custom
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
> **Note:** This command is used internally by `pnpm create @n8n/node` to provide the interactive scaffolding experience.
|
||||||
|
|
||||||
## Create a node
|
#### `n8n-node dev`
|
||||||
|
|
||||||
|
Run n8n with your node in development mode with hot reload.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
n8n-node new # Scaffold a new node
|
n8n-node dev [--external-n8n] [--custom-user-folder <value>]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build a node
|
**Flags:**
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--external-n8n` | Run n8n externally instead of in a subprocess |
|
||||||
|
| `--custom-user-folder <path>` | Folder to use to store user-specific n8n data (default: `~/.n8n-node-cli`) |
|
||||||
|
|
||||||
|
This command:
|
||||||
|
- Starts n8n on `http://localhost:5678` (unless using `--external-n8n`)
|
||||||
|
- Links your node to n8n's custom nodes directory (`~/.n8n-node-cli/.n8n/custom`)
|
||||||
|
- Rebuilds on file changes for live preview
|
||||||
|
- Watches for changes in your `src/` directory
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Standard development with built-in n8n
|
||||||
|
n8n-node dev
|
||||||
|
|
||||||
|
# Use external n8n instance
|
||||||
|
n8n-node dev --external-n8n
|
||||||
|
|
||||||
|
# Custom n8n extensions directory
|
||||||
|
n8n-node dev --custom-user-folder /home/user
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `n8n-node build`
|
||||||
|
|
||||||
|
Compile your node and prepare it for distribution.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
n8n-node build # Build your node; should be ran in the root of your custom node
|
n8n-node build
|
||||||
```
|
```
|
||||||
|
|
||||||
## Develop a node
|
**Flags:** None
|
||||||
|
|
||||||
|
Generates:
|
||||||
|
- Compiled TypeScript code
|
||||||
|
- Bundled node package
|
||||||
|
- Optimized assets and icons
|
||||||
|
- Ready-to-publish package in `dist/`
|
||||||
|
|
||||||
|
#### `n8n-node lint`
|
||||||
|
|
||||||
|
Lint the node in the current directory.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
n8n-node dev # Develop your node with hot reloading; should be ran in the root of your custom node
|
n8n-node lint [--fix]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Related
|
**Flags:**
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--fix` | Automatically fix problems |
|
||||||
|
|
||||||
`@n8n/create-node`: Lightweight wrapper to support `npm create @n8n/node`
|
**Examples:**
|
||||||
|
```bash
|
||||||
|
# Check for linting issues
|
||||||
|
n8n-node lint
|
||||||
|
|
||||||
|
# Automatically fix fixable issues
|
||||||
|
n8n-node lint --fix
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `n8n-node release`
|
||||||
|
|
||||||
|
Publish your community node package to npm.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
n8n-node release
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flags:** None
|
||||||
|
|
||||||
|
This command handles the complete release process using [release-it](https://github.com/release-it/release-it):
|
||||||
|
- Builds the node
|
||||||
|
- Runs linting checks
|
||||||
|
- Updates changelog
|
||||||
|
- Creates git tags
|
||||||
|
- Creates GitHub releases
|
||||||
|
- Publishes to npm
|
||||||
|
|
||||||
|
## 🔄 Development Workflow
|
||||||
|
|
||||||
|
The recommended workflow using the scaffolding tool:
|
||||||
|
|
||||||
|
1. **Create your node**:
|
||||||
|
```bash
|
||||||
|
pnpm create @n8n/node my-awesome-node
|
||||||
|
cd my-awesome-node
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Start development**:
|
||||||
|
```bash
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
- Starts n8n on `http://localhost:5678`
|
||||||
|
- Links your node automatically
|
||||||
|
- Rebuilds on file changes
|
||||||
|
|
||||||
|
3. **Test your node** at `http://localhost:5678`
|
||||||
|
|
||||||
|
4. **Lint your code**:
|
||||||
|
```bash
|
||||||
|
pnpm lint
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Build for production**:
|
||||||
|
```bash
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Publish**:
|
||||||
|
```bash
|
||||||
|
pnpm run release
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📁 Project Structure
|
||||||
|
|
||||||
|
The CLI expects your project to follow this structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
my-node/
|
||||||
|
├── src/
|
||||||
|
│ ├── nodes/
|
||||||
|
│ │ └── MyNode/
|
||||||
|
│ │ ├── MyNode.node.ts
|
||||||
|
│ │ └── MyNode.node.json
|
||||||
|
│ └── credentials/
|
||||||
|
├── package.json
|
||||||
|
└── tsconfig.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## ⚙️ Configuration
|
||||||
|
|
||||||
|
The CLI reads configuration from your `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "n8n-nodes-my-awesome-node",
|
||||||
|
"n8n": {
|
||||||
|
"n8nNodesApiVersion": 1,
|
||||||
|
"nodes": [
|
||||||
|
"dist/nodes/MyNode/MyNode.node.js"
|
||||||
|
],
|
||||||
|
"credentials": [
|
||||||
|
"dist/credentials/MyNodeAuth.credentials.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐛 Troubleshooting
|
||||||
|
|
||||||
|
### Development server issues
|
||||||
|
```bash
|
||||||
|
# Clear n8n custom nodes cache
|
||||||
|
rm -rf ~/.n8n-node-cli/.n8n/custom
|
||||||
|
|
||||||
|
# Restart development server
|
||||||
|
pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build failures
|
||||||
|
```bash
|
||||||
|
# Run linting first
|
||||||
|
pnpm lint
|
||||||
|
|
||||||
|
# Clean build
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Resources
|
||||||
|
|
||||||
|
- **[Creating Nodes Guide](https://docs.n8n.io/integrations/creating-nodes/)** - Complete documentation
|
||||||
|
- **[Node Development Reference](https://docs.n8n.io/integrations/creating-nodes/build/reference/)** - API specifications
|
||||||
|
- **[Community Forum](https://community.n8n.io)** - Get help and showcase your nodes
|
||||||
|
- **[@n8n/create-node](https://www.npmjs.com/package/@n8n/create-node)** - Recommended scaffolding tool
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
Found an issue? Contribute to the [n8n repository](https://github.com/n8n-io/n8n) on GitHub.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Happy node development! 🎉**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@n8n/node-cli",
|
"name": "@n8n/node-cli",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"description": "Official CLI for developing community nodes for n8n",
|
"description": "Official CLI for developing community nodes for n8n",
|
||||||
"bin": {
|
"bin": {
|
||||||
"n8n-node": "bin/n8n-node.mjs"
|
"n8n-node": "bin/n8n-node.mjs"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { cancel, intro, log, outro, spinner } from '@clack/prompts';
|
import { cancel, intro, log, outro, spinner } from '@clack/prompts';
|
||||||
import { Command } from '@oclif/core';
|
import { Command } from '@oclif/core';
|
||||||
import { spawn } from 'child_process';
|
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import { cp, mkdir } from 'node:fs/promises';
|
import { cp, mkdir } from 'node:fs/promises';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import picocolors from 'picocolors';
|
import picocolors from 'picocolors';
|
||||||
import { rimraf } from 'rimraf';
|
import { rimraf } from 'rimraf';
|
||||||
|
|
||||||
|
import { runCommand } from '../utils/child-process';
|
||||||
import { ensureN8nPackage } from '../utils/prompts';
|
import { ensureN8nPackage } from '../utils/prompts';
|
||||||
|
|
||||||
export default class Build extends Command {
|
export default class Build extends Command {
|
||||||
@@ -44,42 +44,17 @@ export default class Build extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function runTscBuild(): Promise<void> {
|
async function runTscBuild(): Promise<void> {
|
||||||
return await new Promise((resolve, reject) => {
|
return await runCommand('tsc', [], {
|
||||||
const child = spawn('tsc', [], {
|
context: 'local',
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
printOutput: ({ stdout, stderr }) => {
|
||||||
shell: true,
|
log.error(stdout.concat(stderr).toString());
|
||||||
});
|
},
|
||||||
|
|
||||||
let stderr = '';
|
|
||||||
let stdout = '';
|
|
||||||
|
|
||||||
child.stdout.on('data', (chunk: Buffer) => {
|
|
||||||
stdout += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
child.stderr.on('data', (chunk: Buffer) => {
|
|
||||||
stderr += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('error', (error) => {
|
|
||||||
log.error(`${stdout.trim()}\n${stderr.trim()}`);
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('close', (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
log.error(`${stdout.trim()}\n${stderr.trim()}`);
|
|
||||||
reject(new Error(`tsc exited with code ${code}`));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function copyStaticFiles() {
|
export async function copyStaticFiles() {
|
||||||
const staticFiles = glob.sync(['**/*.{png,svg}', '**/__schema__/**/*.json'], {
|
const staticFiles = glob.sync(['**/*.{png,svg}', '**/__schema__/**/*.json'], {
|
||||||
ignore: ['dist'],
|
ignore: ['dist', 'node_modules'],
|
||||||
});
|
});
|
||||||
|
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
|
import { intro, outro, spinner } from '@clack/prompts';
|
||||||
import { Command, Flags } from '@oclif/core';
|
import { Command, Flags } from '@oclif/core';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import picocolors from 'picocolors';
|
import picocolors from 'picocolors';
|
||||||
|
import { rimraf } from 'rimraf';
|
||||||
|
|
||||||
import { ensureFolder } from '../../utils/filesystem';
|
import { ensureFolder } from '../../utils/filesystem';
|
||||||
import { detectPackageManager } from '../../utils/package-manager';
|
import { detectPackageManager } from '../../utils/package-manager';
|
||||||
import { copyStaticFiles } from '../build';
|
|
||||||
import { commands, readPackageName } from './utils';
|
|
||||||
import { ensureN8nPackage, onCancel } from '../../utils/prompts';
|
import { ensureN8nPackage, onCancel } from '../../utils/prompts';
|
||||||
import { validateNodeName } from '../../utils/validation';
|
import { validateNodeName } from '../../utils/validation';
|
||||||
|
import { copyStaticFiles } from '../build';
|
||||||
|
import { commands, readPackageName } from './utils';
|
||||||
|
import { runCommand } from '../../utils/child-process';
|
||||||
|
|
||||||
export default class Dev extends Command {
|
export default class Dev extends Command {
|
||||||
static override description = 'Run n8n with the node and rebuild on changes for live preview';
|
static override description = 'Run n8n with the node and rebuild on changes for live preview';
|
||||||
@@ -21,58 +24,70 @@ export default class Dev extends Command {
|
|||||||
'external-n8n': Flags.boolean({
|
'external-n8n': Flags.boolean({
|
||||||
default: false,
|
default: false,
|
||||||
description:
|
description:
|
||||||
'By default n8n-node dev will run n8n in a sub process. Enable this option if you would like to run n8n elsewhere.',
|
'By default n8n-node dev will run n8n in a sub process. Enable this option if you would like to run n8n elsewhere. Make sure to set N8N_DEV_RELOAD to true in that case.',
|
||||||
}),
|
}),
|
||||||
'custom-nodes-dir': Flags.directory({
|
'custom-user-folder': Flags.directory({
|
||||||
default: path.join(os.homedir(), '.n8n/custom'),
|
default: path.join(os.homedir(), '.n8n-node-cli'),
|
||||||
description:
|
description:
|
||||||
'Where to link your custom node. By default it will link to ~/.n8n/custom. You probably want to enable this option if you run n8n with a custom N8N_CUSTOM_EXTENSIONS env variable.',
|
'Folder to use to store user-specific n8n data. By default it will use ~/.n8n-node-cli. You probably want to enable this option if you run n8n with a custom N8N_CUSTOM_EXTENSIONS env variable.',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
async run(): Promise<void> {
|
async run(): Promise<void> {
|
||||||
const { flags } = await this.parse(Dev);
|
const { flags } = await this.parse(Dev);
|
||||||
|
|
||||||
const packageManager = detectPackageManager() ?? 'npm';
|
const packageManager = (await detectPackageManager()) ?? 'npm';
|
||||||
const { isN8nInstalled, runCommand, runPersistentCommand } = commands();
|
const { runPersistentCommand } = commands();
|
||||||
|
|
||||||
|
intro(picocolors.inverse(' n8n-node dev '));
|
||||||
|
|
||||||
await ensureN8nPackage('n8n-node dev');
|
await ensureN8nPackage('n8n-node dev');
|
||||||
|
|
||||||
const installed = await isN8nInstalled();
|
|
||||||
if (!installed && !flags['external-n8n']) {
|
|
||||||
console.error(
|
|
||||||
'❌ n8n is not installed or not in PATH. Learn how to install n8n here: https://docs.n8n.io/hosting/installation/npm',
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
await copyStaticFiles();
|
await copyStaticFiles();
|
||||||
|
|
||||||
|
const linkingSpinner = spinner();
|
||||||
|
linkingSpinner.start('Linking custom node to n8n');
|
||||||
await runCommand(packageManager, ['link']);
|
await runCommand(packageManager, ['link']);
|
||||||
|
|
||||||
const customPath = flags['custom-nodes-dir'];
|
const n8nUserFolder = flags['custom-user-folder'];
|
||||||
|
const customNodesFolder = path.join(n8nUserFolder, '.n8n', 'custom');
|
||||||
|
|
||||||
await ensureFolder(customPath);
|
await ensureFolder(customNodesFolder);
|
||||||
|
|
||||||
const packageName = await readPackageName();
|
const packageName = await readPackageName();
|
||||||
const invalidNodeNameError = validateNodeName(packageName);
|
const invalidNodeNameError = validateNodeName(packageName);
|
||||||
|
|
||||||
if (invalidNodeNameError) return onCancel(invalidNodeNameError);
|
if (invalidNodeNameError) return onCancel(invalidNodeNameError);
|
||||||
|
|
||||||
await runCommand(packageManager, ['link', packageName], { cwd: customPath });
|
// Remove existing package.json to avoid conflicts
|
||||||
|
await rimraf(path.join(customNodesFolder, 'package.json'));
|
||||||
|
await runCommand(packageManager, ['link', packageName], {
|
||||||
|
cwd: customNodesFolder,
|
||||||
|
});
|
||||||
|
|
||||||
|
linkingSpinner.stop('Linked custom node to n8n');
|
||||||
|
|
||||||
|
outro('✓ Setup complete');
|
||||||
|
|
||||||
if (!flags['external-n8n']) {
|
if (!flags['external-n8n']) {
|
||||||
// Run n8n with hot reload enabled
|
// Run n8n with hot reload enabled
|
||||||
runPersistentCommand('n8n', [], {
|
runPersistentCommand('npx', ['-y', '--quiet', 'n8n'], {
|
||||||
cwd: customPath,
|
cwd: n8nUserFolder,
|
||||||
env: { N8N_DEV_RELOAD: 'true' },
|
env: {
|
||||||
|
...process.env,
|
||||||
|
N8N_DEV_RELOAD: 'true',
|
||||||
|
N8N_RUNNERS_ENABLED: 'true',
|
||||||
|
DB_SQLITE_POOL_SIZE: '10',
|
||||||
|
N8N_USER_FOLDER: n8nUserFolder,
|
||||||
|
N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS: 'false',
|
||||||
|
},
|
||||||
name: 'n8n',
|
name: 'n8n',
|
||||||
color: picocolors.green,
|
color: picocolors.green,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run `tsc --watch` in background
|
// Run `tsc --watch` in background
|
||||||
runPersistentCommand('tsc', ['--watch'], {
|
runPersistentCommand(packageManager, ['exec', '--', 'tsc', '--watch'], {
|
||||||
name: 'build',
|
name: 'build',
|
||||||
color: picocolors.cyan,
|
color: picocolors.cyan,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,34 +22,6 @@ export function commands() {
|
|||||||
process.on('SIGINT', () => cleanup('SIGINT'));
|
process.on('SIGINT', () => cleanup('SIGINT'));
|
||||||
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
||||||
|
|
||||||
async function runCommand(
|
|
||||||
cmd: string,
|
|
||||||
args: string[],
|
|
||||||
opts: {
|
|
||||||
cwd?: string;
|
|
||||||
env?: NodeJS.ProcessEnv;
|
|
||||||
} = {},
|
|
||||||
): Promise<void> {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
const child = spawn(cmd, args, {
|
|
||||||
cwd: opts.cwd,
|
|
||||||
env: { ...process.env, ...opts.env },
|
|
||||||
stdio: ['inherit', 'pipe', 'pipe'],
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('error', (error) => {
|
|
||||||
reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
child.on('close', (code) => {
|
|
||||||
if (code === 0) resolve();
|
|
||||||
else reject(new Error(`${cmd} exited with code ${code}`));
|
|
||||||
});
|
|
||||||
|
|
||||||
registerChild(child);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function runPersistentCommand(
|
function runPersistentCommand(
|
||||||
cmd: string,
|
cmd: string,
|
||||||
args: string[],
|
args: string[],
|
||||||
@@ -101,18 +73,7 @@ export function commands() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isN8nInstalled(): Promise<boolean> {
|
|
||||||
try {
|
|
||||||
await runCommand('n8n', ['--version'], {});
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isN8nInstalled,
|
|
||||||
runCommand,
|
|
||||||
runPersistentCommand,
|
runPersistentCommand,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
34
packages/@n8n/node-cli/src/commands/lint.ts
Normal file
34
packages/@n8n/node-cli/src/commands/lint.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Command, Flags } from '@oclif/core';
|
||||||
|
|
||||||
|
import { ChildProcessError, runCommand } from '../utils/child-process';
|
||||||
|
|
||||||
|
export default class Lint extends Command {
|
||||||
|
static override description = 'Lint the node in the current directory. Includes auto-fixing.';
|
||||||
|
static override examples = ['<%= config.bin %> <%= command.id %>'];
|
||||||
|
static override flags = {
|
||||||
|
fix: Flags.boolean({ description: 'Automatically fix problems', default: false }),
|
||||||
|
};
|
||||||
|
|
||||||
|
async run(): Promise<void> {
|
||||||
|
const { flags } = await this.parse(Lint);
|
||||||
|
|
||||||
|
const args = ['.'];
|
||||||
|
|
||||||
|
if (flags.fix) {
|
||||||
|
args.push('--fix');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCommand('eslint', args, { context: 'local', stdio: 'inherit' });
|
||||||
|
} catch (error: unknown) {
|
||||||
|
if (error instanceof ChildProcessError) {
|
||||||
|
if (error.signal) {
|
||||||
|
process.kill(process.pid, error.signal);
|
||||||
|
} else {
|
||||||
|
process.exit(error.code ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { confirm, intro, isCancel, note, outro, spinner } from '@clack/prompts';
|
import { confirm, intro, isCancel, log, note, outro, spinner } from '@clack/prompts';
|
||||||
import { Args, Command, Flags } from '@oclif/core';
|
import { Args, Command, Flags } from '@oclif/core';
|
||||||
import { camelCase } from 'change-case';
|
import { camelCase } from 'change-case';
|
||||||
import fs from 'node:fs/promises';
|
import fs from 'node:fs/promises';
|
||||||
@@ -9,9 +9,10 @@ import { declarativeTemplatePrompt, nodeNamePrompt, nodeTypePrompt } from './pro
|
|||||||
import { createIntro } from './utils';
|
import { createIntro } from './utils';
|
||||||
import type { TemplateData, TemplateWithRun } from '../../template/core';
|
import type { TemplateData, TemplateWithRun } from '../../template/core';
|
||||||
import { getTemplate, isTemplateName, isTemplateType, templates } from '../../template/templates';
|
import { getTemplate, isTemplateName, isTemplateType, templates } from '../../template/templates';
|
||||||
|
import { ChildProcessError, runCommand } from '../../utils/child-process';
|
||||||
import { delayAtLeast, folderExists } from '../../utils/filesystem';
|
import { delayAtLeast, folderExists } from '../../utils/filesystem';
|
||||||
import { tryReadGitUser } from '../../utils/git';
|
import { initGit, tryReadGitUser } from '../../utils/git';
|
||||||
import { detectPackageManager, installDependencies } from '../../utils/package-manager';
|
import { detectPackageManager } from '../../utils/package-manager';
|
||||||
import { onCancel } from '../../utils/prompts';
|
import { onCancel } from '../../utils/prompts';
|
||||||
import { validateNodeName } from '../../utils/validation';
|
import { validateNodeName } from '../../utils/validation';
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ export default class New extends Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const config = (await template.prompts?.()) ?? {};
|
const config = (await template.prompts?.()) ?? {};
|
||||||
const packageManager = detectPackageManager() ?? 'npm';
|
const packageManager = (await detectPackageManager()) ?? 'npm';
|
||||||
const templateData: TemplateData = {
|
const templateData: TemplateData = {
|
||||||
destinationPath: destination,
|
destinationPath: destination,
|
||||||
nodePackageName: nodeName,
|
nodePackageName: nodeName,
|
||||||
@@ -99,22 +100,59 @@ export default class New extends Command {
|
|||||||
await delayAtLeast(template.run(templateData), 1000);
|
await delayAtLeast(template.run(templateData), 1000);
|
||||||
copyingSpinner.stop('Files copied');
|
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']) {
|
if (!flags['skip-install']) {
|
||||||
const installingSpinner = spinner();
|
const installingSpinner = spinner();
|
||||||
installingSpinner.start('Installing dependencies');
|
installingSpinner.start('Installing dependencies');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await delayAtLeast(installDependencies({ dir: destination, packageManager }), 1000);
|
await delayAtLeast(
|
||||||
|
runCommand(packageManager, ['install'], {
|
||||||
|
cwd: destination,
|
||||||
|
printOutput: ({ stdout, stderr }) => {
|
||||||
|
log.error(stdout.concat(stderr).toString());
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
1000,
|
||||||
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
installingSpinner.stop('Could not install dependencies', 1);
|
if (error instanceof ChildProcessError) {
|
||||||
return process.exit(1);
|
installingSpinner.stop(
|
||||||
|
`Could not install dependencies: ${error.message}`,
|
||||||
|
error.code ?? undefined,
|
||||||
|
);
|
||||||
|
process.exit(error.code ?? 1);
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
installingSpinner.stop('Dependencies installed');
|
installingSpinner.stop('Dependencies installed');
|
||||||
}
|
}
|
||||||
|
|
||||||
note(
|
note(
|
||||||
`Need help? Check out the docs: https://docs.n8n.io/integrations/creating-nodes/build/${type}-style-node/`,
|
`cd ./${nodeName} && ${packageManager} run dev
|
||||||
|
|
||||||
|
📚 Documentation: https://docs.n8n.io/integrations/creating-nodes/build/${type}-style-node/
|
||||||
|
💬 Community: https://community.n8n.io`,
|
||||||
'Next Steps',
|
'Next Steps',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { detectPackageManager } from '../../utils/package-manager';
|
import { detectPackageManagerFromUserAgent } from '../../utils/package-manager';
|
||||||
|
|
||||||
export const createIntro = () => {
|
export const createIntro = () => {
|
||||||
const maybePackageManager = detectPackageManager();
|
const maybePackageManager = detectPackageManagerFromUserAgent();
|
||||||
const packageManager = maybePackageManager ?? 'npm';
|
const packageManager = maybePackageManager ?? 'npm';
|
||||||
return maybePackageManager ? ` ${packageManager} create @n8n/node ` : ' n8n-node new ';
|
return maybePackageManager ? ` ${packageManager} create @n8n/node ` : ' n8n-node new ';
|
||||||
};
|
};
|
||||||
|
|||||||
22
packages/@n8n/node-cli/src/commands/prerelease.ts
Normal file
22
packages/@n8n/node-cli/src/commands/prerelease.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Command } from '@oclif/core';
|
||||||
|
|
||||||
|
import { detectPackageManager } from '../utils/package-manager';
|
||||||
|
|
||||||
|
export default class Prerelease extends Command {
|
||||||
|
static override description =
|
||||||
|
'Only for internal use. Prevent npm publish, instead require npm run release';
|
||||||
|
static override examples = ['<%= config.bin %> <%= command.id %>'];
|
||||||
|
static override flags = {};
|
||||||
|
static override hidden = true;
|
||||||
|
|
||||||
|
async run(): Promise<void> {
|
||||||
|
await this.parse(Prerelease);
|
||||||
|
|
||||||
|
const packageManager = (await detectPackageManager()) ?? 'npm';
|
||||||
|
|
||||||
|
if (!process.env.RELEASE_MODE) {
|
||||||
|
console.log(`Run \`${packageManager} run release\` to publish the package`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
packages/@n8n/node-cli/src/commands/release.ts
Normal file
52
packages/@n8n/node-cli/src/commands/release.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Command } from '@oclif/core';
|
||||||
|
|
||||||
|
import { ChildProcessError, runCommand } from '../utils/child-process';
|
||||||
|
import { detectPackageManager } from '../utils/package-manager';
|
||||||
|
|
||||||
|
export default class Release extends Command {
|
||||||
|
static override description = 'Publish your community node package to npm';
|
||||||
|
static override examples = ['<%= config.bin %> <%= command.id %>'];
|
||||||
|
static override flags = {};
|
||||||
|
|
||||||
|
async run(): Promise<void> {
|
||||||
|
await this.parse(Release);
|
||||||
|
|
||||||
|
const pm = (await detectPackageManager()) ?? 'npm';
|
||||||
|
|
||||||
|
try {
|
||||||
|
await runCommand(
|
||||||
|
'release-it',
|
||||||
|
[
|
||||||
|
'-n',
|
||||||
|
'--git.requireBranch main',
|
||||||
|
'--git.requireCleanWorkingDir',
|
||||||
|
'--git.requireUpstream',
|
||||||
|
'--git.requireCommits',
|
||||||
|
'--git.commit',
|
||||||
|
'--git.tag',
|
||||||
|
'--git.push',
|
||||||
|
'--git.changelog="npx auto-changelog --stdout --unreleased --commit-limit false -u --hide-credit"',
|
||||||
|
'--github.release',
|
||||||
|
`--hooks.before:init="${pm} run lint && ${pm} run build"`,
|
||||||
|
'--hooks.after:bump="npx auto-changelog -p"',
|
||||||
|
],
|
||||||
|
{
|
||||||
|
stdio: 'inherit',
|
||||||
|
context: 'local',
|
||||||
|
env: {
|
||||||
|
RELEASE_MODE: 'true',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ChildProcessError) {
|
||||||
|
if (error.signal) {
|
||||||
|
process.kill(process.pid, error.signal);
|
||||||
|
} else {
|
||||||
|
process.exit(error.code ?? 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,15 @@
|
|||||||
import Build from './commands/build';
|
import Build from './commands/build';
|
||||||
import Dev from './commands/dev';
|
import Dev from './commands/dev';
|
||||||
|
import Lint from './commands/lint';
|
||||||
import New from './commands/new';
|
import New from './commands/new';
|
||||||
|
import Prerelease from './commands/prerelease';
|
||||||
|
import Release from './commands/release';
|
||||||
|
|
||||||
export const commands = {
|
export const commands = {
|
||||||
new: New,
|
new: New,
|
||||||
build: Build,
|
build: Build,
|
||||||
dev: Dev,
|
dev: Dev,
|
||||||
|
prerelease: Prerelease,
|
||||||
|
release: Release,
|
||||||
|
lint: Lint,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": ""
|
"url": ""
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "n8n-node build",
|
"build": "n8n-node build",
|
||||||
"build:watch": "tsc --watch",
|
"build:watch": "tsc --watch",
|
||||||
"dev": "n8n-node dev",
|
"dev": "n8n-node dev",
|
||||||
"lint": "eslint .",
|
"lint": "n8n-node lint",
|
||||||
"release": "release-it"
|
"lint:fix": "n8n-node lint --fix",
|
||||||
},
|
"release": "n8n-node release",
|
||||||
|
"prepublishOnly": "n8n-node prerelease"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
@@ -32,11 +34,6 @@
|
|||||||
"dist/nodes/Example/Example.node.js"
|
"dist/nodes/Example/Example.node.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"release-it": {
|
|
||||||
"hooks": {
|
|
||||||
"before:init": "{{packageManager.name}} run lint && {{packageManager.name}} run build"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@n8n/node-cli": "*",
|
"@n8n/node-cli": "*",
|
||||||
"eslint": "9.32.0",
|
"eslint": "9.32.0",
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": ""
|
"url": ""
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "n8n-node build",
|
"build": "n8n-node build",
|
||||||
"build:watch": "tsc --watch",
|
"build:watch": "tsc --watch",
|
||||||
"dev": "n8n-node dev",
|
"dev": "n8n-node dev",
|
||||||
"lint": "eslint .",
|
"lint": "n8n-node lint",
|
||||||
"release": "release-it"
|
"lint:fix": "n8n-node lint --fix",
|
||||||
},
|
"release": "n8n-node release",
|
||||||
|
"prepublishOnly": "n8n-node prerelease"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
@@ -35,11 +37,6 @@
|
|||||||
"dist/nodes/GithubIssues/GithubIssues.node.js"
|
"dist/nodes/GithubIssues/GithubIssues.node.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"release-it": {
|
|
||||||
"hooks": {
|
|
||||||
"before:init": "{{packageManager.name}} run lint && {{packageManager.name}} run build"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@n8n/node-cli": "*",
|
"@n8n/node-cli": "*",
|
||||||
"eslint": "9.32.0",
|
"eslint": "9.32.0",
|
||||||
|
|||||||
@@ -15,13 +15,15 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": ""
|
"url": ""
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "n8n-node build",
|
"build": "n8n-node build",
|
||||||
"build:watch": "tsc --watch",
|
"build:watch": "tsc --watch",
|
||||||
"dev": "n8n-node dev",
|
"dev": "n8n-node dev",
|
||||||
"lint": "eslint .",
|
"lint": "n8n-node lint",
|
||||||
"release": "release-it"
|
"lint:fix": "n8n-node lint --fix",
|
||||||
},
|
"release": "n8n-node release",
|
||||||
|
"prepublishOnly": "n8n-node prerelease"
|
||||||
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
],
|
],
|
||||||
@@ -32,11 +34,6 @@
|
|||||||
"dist/nodes/Example/Example.node.js"
|
"dist/nodes/Example/Example.node.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"release-it": {
|
|
||||||
"hooks": {
|
|
||||||
"before:init": "{{packageManager.name}} run lint && {{packageManager.name}} run build"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@n8n/node-cli": "*",
|
"@n8n/node-cli": "*",
|
||||||
"eslint": "9.32.0",
|
"eslint": "9.32.0",
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
dist
|
dist
|
||||||
|
node_modules
|
||||||
|
|||||||
82
packages/@n8n/node-cli/src/utils/child-process.ts
Normal file
82
packages/@n8n/node-cli/src/utils/child-process.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import { spawn, type SpawnOptions, type StdioOptions } from 'node:child_process';
|
||||||
|
|
||||||
|
import { detectPackageManager } from './package-manager';
|
||||||
|
|
||||||
|
export class ChildProcessError extends Error {
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
public code: number | null,
|
||||||
|
public signal: NodeJS.Signals | null,
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function runCommand(
|
||||||
|
cmd: string,
|
||||||
|
args: string[] = [],
|
||||||
|
opts: {
|
||||||
|
cwd?: string;
|
||||||
|
env?: NodeJS.ProcessEnv;
|
||||||
|
stdio?: StdioOptions;
|
||||||
|
context?: 'local' | 'global';
|
||||||
|
printOutput?: (options: { stdout: Buffer[]; stderr: Buffer[] }) => void;
|
||||||
|
} = {},
|
||||||
|
): Promise<void> {
|
||||||
|
const packageManager = (await detectPackageManager()) ?? 'npm';
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
const options: SpawnOptions = {
|
||||||
|
cwd: opts.cwd,
|
||||||
|
env: { ...process.env, ...opts.env },
|
||||||
|
stdio: opts.stdio ?? ['ignore', 'pipe', 'pipe'],
|
||||||
|
};
|
||||||
|
const child =
|
||||||
|
opts.context === 'local'
|
||||||
|
? spawn(packageManager, ['exec', '--', cmd, ...args], options)
|
||||||
|
: spawn(cmd, args, options);
|
||||||
|
|
||||||
|
const stdoutBuffers: Buffer[] = [];
|
||||||
|
const stderrBuffers: Buffer[] = [];
|
||||||
|
|
||||||
|
child.stdout?.on('data', (data: Buffer) => {
|
||||||
|
stdoutBuffers.push(data);
|
||||||
|
});
|
||||||
|
child.stderr?.on('data', (data: Buffer) => {
|
||||||
|
stderrBuffers.push(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
function printOutput() {
|
||||||
|
if (opts.printOutput) {
|
||||||
|
opts.printOutput({ stdout: stdoutBuffers, stderr: stderrBuffers });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const buffer of stdoutBuffers) {
|
||||||
|
process.stdout.write(buffer);
|
||||||
|
}
|
||||||
|
for (const buffer of stderrBuffers) {
|
||||||
|
process.stderr.write(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
child.on('error', (error) => {
|
||||||
|
printOutput();
|
||||||
|
reject(new ChildProcessError(error.message, null, null));
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('close', (code, signal) => {
|
||||||
|
if (code === 0) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
printOutput();
|
||||||
|
reject(
|
||||||
|
new ChildProcessError(
|
||||||
|
`${cmd} exited with code ${code}${signal ? ` (signal: ${signal})` : ''}`,
|
||||||
|
code,
|
||||||
|
signal,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
|
|
||||||
|
import { runCommand } from './child-process';
|
||||||
|
|
||||||
type GitUser = {
|
type GitUser = {
|
||||||
name?: string;
|
name?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
@@ -32,3 +34,7 @@ export function tryReadGitUser(): GitUser {
|
|||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function initGit(dir: string): Promise<void> {
|
||||||
|
await runCommand('git', ['init', '-b', 'main'], { cwd: dir });
|
||||||
|
}
|
||||||
|
|||||||
196
packages/@n8n/node-cli/src/utils/package-manager.test.ts
Normal file
196
packages/@n8n/node-cli/src/utils/package-manager.test.ts
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
import type { Stats } from 'node:fs';
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import { mock } from 'vitest-mock-extended';
|
||||||
|
|
||||||
|
import { detectPackageManager, detectPackageManagerFromUserAgent } from './package-manager';
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
vi.mock('node:child_process');
|
||||||
|
vi.mock('node:fs/promises');
|
||||||
|
vi.mock('@clack/prompts');
|
||||||
|
|
||||||
|
describe('package manager utils', () => {
|
||||||
|
const originalEnv = process.env;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
process.env = { ...originalEnv };
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
process.env = originalEnv;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('detectPackageManagerFromUserAgent', () => {
|
||||||
|
it('returns pnpm when user agent contains pnpm', () => {
|
||||||
|
process.env.npm_config_user_agent = 'pnpm/8.6.0 npm/? node/v18.16.0 darwin x64';
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe('pnpm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns yarn when user agent contains yarn', () => {
|
||||||
|
process.env.npm_config_user_agent = 'yarn/1.22.19 npm/? node/v18.16.0 darwin x64';
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe('yarn');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns npm when user agent contains npm', () => {
|
||||||
|
process.env.npm_config_user_agent = 'npm/9.5.1 node/v18.16.0 darwin x64 workspaces/false';
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe('npm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prioritizes pnpm over yarn and npm when multiple are present', () => {
|
||||||
|
process.env.npm_config_user_agent = 'pnpm/8.6.0 yarn/1.22.19 npm/9.5.1 node/v18.16.0';
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe('pnpm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prioritizes yarn over npm when both are present but no pnpm', () => {
|
||||||
|
process.env.npm_config_user_agent = 'yarn/1.22.19 npm/9.5.1 node/v18.16.0';
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe('yarn');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when npm_config_user_agent is not set', () => {
|
||||||
|
delete process.env.npm_config_user_agent;
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when user agent does not contain any package manager', () => {
|
||||||
|
process.env.npm_config_user_agent = 'node/v18.16.0 darwin x64';
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when user agent is empty string', () => {
|
||||||
|
process.env.npm_config_user_agent = '';
|
||||||
|
|
||||||
|
const result = detectPackageManagerFromUserAgent();
|
||||||
|
|
||||||
|
expect(result).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('detectPackageManager', () => {
|
||||||
|
it('returns package manager from user agent when available', async () => {
|
||||||
|
process.env.npm_config_user_agent = 'pnpm/8.6.0 npm/? node/v18.16.0';
|
||||||
|
|
||||||
|
const result = await detectPackageManager();
|
||||||
|
|
||||||
|
expect(result).toBe('pnpm');
|
||||||
|
expect(vi.mocked(fs).stat).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects npm from package-lock.json when user agent is not available', async () => {
|
||||||
|
delete process.env.npm_config_user_agent;
|
||||||
|
|
||||||
|
vi.mocked(fs).stat.mockImplementation(async (path) => {
|
||||||
|
if (path === 'package-lock.json') {
|
||||||
|
const stats = mock<Stats>();
|
||||||
|
stats.isFile.mockReturnValue(true);
|
||||||
|
return await Promise.resolve(stats);
|
||||||
|
}
|
||||||
|
throw new Error('File not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await detectPackageManager();
|
||||||
|
|
||||||
|
expect(result).toBe('npm');
|
||||||
|
expect(vi.mocked(fs).stat).toHaveBeenCalledWith('package-lock.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects yarn from yarn.lock when user agent is not available', async () => {
|
||||||
|
delete process.env.npm_config_user_agent;
|
||||||
|
|
||||||
|
vi.mocked(fs).stat.mockImplementation(async (path) => {
|
||||||
|
if (path === 'yarn.lock') {
|
||||||
|
const stats = mock<Stats>();
|
||||||
|
stats.isFile.mockReturnValue(true);
|
||||||
|
return await Promise.resolve(stats);
|
||||||
|
}
|
||||||
|
throw new Error('File not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await detectPackageManager();
|
||||||
|
|
||||||
|
expect(result).toBe('yarn');
|
||||||
|
expect(vi.mocked(fs).stat).toHaveBeenCalledWith('package-lock.json');
|
||||||
|
expect(vi.mocked(fs).stat).toHaveBeenCalledWith('yarn.lock');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('detects pnpm from pnpm-lock.yaml when user agent is not available', async () => {
|
||||||
|
delete process.env.npm_config_user_agent;
|
||||||
|
|
||||||
|
vi.mocked(fs).stat.mockImplementation(async (path) => {
|
||||||
|
if (path === 'pnpm-lock.yaml') {
|
||||||
|
const stats = mock<Stats>();
|
||||||
|
stats.isFile.mockReturnValue(true);
|
||||||
|
return await Promise.resolve(stats);
|
||||||
|
}
|
||||||
|
throw new Error('File not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await detectPackageManager();
|
||||||
|
|
||||||
|
expect(result).toBe('pnpm');
|
||||||
|
expect(vi.mocked(fs).stat).toHaveBeenCalledWith('package-lock.json');
|
||||||
|
expect(vi.mocked(fs).stat).toHaveBeenCalledWith('yarn.lock');
|
||||||
|
expect(vi.mocked(fs).stat).toHaveBeenCalledWith('pnpm-lock.yaml');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prioritizes npm lock file when multiple lock files exist', async () => {
|
||||||
|
delete process.env.npm_config_user_agent;
|
||||||
|
|
||||||
|
const stats = mock<Stats>();
|
||||||
|
stats.isFile.mockReturnValue(true);
|
||||||
|
vi.mocked(fs).stat.mockResolvedValue(stats);
|
||||||
|
|
||||||
|
const result = await detectPackageManager();
|
||||||
|
|
||||||
|
expect(result).toBe('npm');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no user agent and no lock files exist', async () => {
|
||||||
|
delete process.env.npm_config_user_agent;
|
||||||
|
vi.mocked(fs).stat.mockRejectedValue(new Error('File not found'));
|
||||||
|
|
||||||
|
const result = await detectPackageManager();
|
||||||
|
|
||||||
|
expect(result).toBe(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores directories that match lock file names', async () => {
|
||||||
|
delete process.env.npm_config_user_agent;
|
||||||
|
|
||||||
|
vi.mocked(fs).stat.mockImplementation(async (path) => {
|
||||||
|
if (path === 'package-lock.json') {
|
||||||
|
const stats = mock<Stats>();
|
||||||
|
stats.isFile.mockReturnValue(false);
|
||||||
|
return await Promise.resolve(stats);
|
||||||
|
}
|
||||||
|
throw new Error('File not found');
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await detectPackageManager();
|
||||||
|
|
||||||
|
expect(result).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,45 +1,45 @@
|
|||||||
import { log } from '@clack/prompts';
|
import fs from 'node:fs/promises';
|
||||||
import { spawn } from 'node:child_process';
|
|
||||||
import pico from 'picocolors';
|
|
||||||
|
|
||||||
type PackageManager = 'npm' | 'yarn' | 'pnpm';
|
type PackageManager = 'npm' | 'yarn' | 'pnpm';
|
||||||
|
|
||||||
export function detectPackageManager(): PackageManager | null {
|
export function detectPackageManagerFromUserAgent(): PackageManager | null {
|
||||||
if ('npm_config_user_agent' in process.env) {
|
if ('npm_config_user_agent' in process.env) {
|
||||||
const ua = process.env['npm_config_user_agent'] ?? '';
|
const ua = process.env['npm_config_user_agent'] ?? '';
|
||||||
if (ua.includes('pnpm')) return 'pnpm';
|
if (ua.includes('pnpm')) return 'pnpm';
|
||||||
if (ua.includes('yarn')) return 'yarn';
|
if (ua.includes('yarn')) return 'yarn';
|
||||||
if (ua.includes('npm')) return 'npm';
|
if (ua.includes('npm')) return 'npm';
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function installDependencies({
|
async function detectPackageManagerFromLockFiles(): Promise<PackageManager | null> {
|
||||||
dir,
|
const lockFiles: Record<PackageManager, string> = {
|
||||||
packageManager,
|
npm: 'package-lock.json',
|
||||||
}: { dir: string; packageManager: PackageManager }): Promise<void> {
|
yarn: 'yarn.lock',
|
||||||
return await new Promise((resolve, reject) => {
|
pnpm: 'pnpm-lock.yaml',
|
||||||
const child = spawn(packageManager, ['install'], {
|
};
|
||||||
cwd: dir,
|
|
||||||
stdio: ['ignore', 'pipe', 'pipe'],
|
|
||||||
shell: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const output: Buffer[] = [];
|
for (const [pm, lockFile] of Object.entries(lockFiles)) {
|
||||||
|
try {
|
||||||
child.stdout.on('data', (chunk: Buffer) => output.push(chunk));
|
const stats = await fs.stat(lockFile);
|
||||||
child.stderr.on('data', (chunk: Buffer) => output.push(chunk));
|
if (stats.isFile()) {
|
||||||
|
return pm as PackageManager;
|
||||||
child.on('close', (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
resolve();
|
|
||||||
} else {
|
|
||||||
const error = new Error(`${packageManager} install exited with code ${code}`);
|
|
||||||
log.error(`${pico.bold(pico.red(error.message))}
|
|
||||||
${output.map((item) => item.toString()).join('\n')}`);
|
|
||||||
reject(error);
|
|
||||||
}
|
}
|
||||||
});
|
} catch (e) {
|
||||||
});
|
// File does not exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function detectPackageManager(): Promise<PackageManager | null> {
|
||||||
|
// When used via package.json scripts or `npm/yarn/pnpm create`, we can detect the package manager via the user agent
|
||||||
|
const fromUserAgent = detectPackageManagerFromUserAgent();
|
||||||
|
if (fromUserAgent) return fromUserAgent;
|
||||||
|
|
||||||
|
// When used directly via `n8n-node` CLI, we can try to detect the package manager via the lock files
|
||||||
|
const fromLockFiles = await detectPackageManagerFromLockFiles();
|
||||||
|
if (fromLockFiles) return fromLockFiles;
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,7 +49,6 @@
|
|||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@n8n/typescript-config": "workspace:*",
|
"@n8n/typescript-config": "workspace:*",
|
||||||
"@parcel/watcher": "^2.5.1",
|
|
||||||
"@redocly/cli": "^1.28.5",
|
"@redocly/cli": "^1.28.5",
|
||||||
"@types/aws4": "^1.5.1",
|
"@types/aws4": "^1.5.1",
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
@@ -106,6 +105,7 @@
|
|||||||
"@n8n_io/ai-assistant-sdk": "catalog:",
|
"@n8n_io/ai-assistant-sdk": "catalog:",
|
||||||
"@n8n_io/license-sdk": "2.23.0",
|
"@n8n_io/license-sdk": "2.23.0",
|
||||||
"@rudderstack/rudder-sdk-node": "2.1.4",
|
"@rudderstack/rudder-sdk-node": "2.1.4",
|
||||||
|
"@parcel/watcher": "^2.5.1",
|
||||||
"@sentry/node": "catalog:",
|
"@sentry/node": "catalog:",
|
||||||
"aws4": "1.11.0",
|
"aws4": "1.11.0",
|
||||||
"axios": "catalog:",
|
"axios": "catalog:",
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -1504,6 +1504,9 @@ importers:
|
|||||||
'@n8n_io/license-sdk':
|
'@n8n_io/license-sdk':
|
||||||
specifier: 2.23.0
|
specifier: 2.23.0
|
||||||
version: 2.23.0
|
version: 2.23.0
|
||||||
|
'@parcel/watcher':
|
||||||
|
specifier: ^2.5.1
|
||||||
|
version: 2.5.1
|
||||||
'@rudderstack/rudder-sdk-node':
|
'@rudderstack/rudder-sdk-node':
|
||||||
specifier: 2.1.4
|
specifier: 2.1.4
|
||||||
version: 2.1.4(tslib@2.8.1)
|
version: 2.1.4(tslib@2.8.1)
|
||||||
@@ -1736,9 +1739,6 @@ importers:
|
|||||||
'@n8n/typescript-config':
|
'@n8n/typescript-config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../@n8n/typescript-config
|
version: link:../@n8n/typescript-config
|
||||||
'@parcel/watcher':
|
|
||||||
specifier: ^2.5.1
|
|
||||||
version: 2.5.1
|
|
||||||
'@redocly/cli':
|
'@redocly/cli':
|
||||||
specifier: ^1.28.5
|
specifier: ^1.28.5
|
||||||
version: 1.28.5(encoding@0.1.13)
|
version: 1.28.5(encoding@0.1.13)
|
||||||
|
|||||||
Reference in New Issue
Block a user