feat: add Mega community node MVP
This commit is contained in:
parent
2e9e5c61ed
commit
c13bd45efb
32 changed files with 477 additions and 1319 deletions
|
|
@ -1,4 +1,4 @@
|
|||
Copyright 2022 n8n
|
||||
Copyright 2026 Jesse Freitas
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
|
|
|
|||
270
README.md
270
README.md
|
|
@ -1,247 +1,77 @@
|
|||

|
||||
# n8n-nodes-mega
|
||||
|
||||
# n8n-nodes-starter
|
||||
Community node for [n8n](https://n8n.io) that creates conversations in Mega through its Chatwoot-compatible account-scoped API.
|
||||
|
||||
This starter repository helps you build custom integrations for [n8n](https://n8n.io). It includes example nodes, credentials, the node linter, and all the tooling you need to get started.
|
||||
## Features
|
||||
|
||||
## Quick Start
|
||||
- `Mega` regular node
|
||||
- `Conversation -> Create` operation
|
||||
- `Mega API` credential with `Base URL`, `API Access Token`, and `Mega Account ID`
|
||||
- Credential connection test using `GET /api/v1/profile`
|
||||
|
||||
> [!TIP]
|
||||
> **New to building n8n nodes?** The fastest way to get started is with `npm create @n8n/node`. This command scaffolds a complete node package for you using the [@n8n/node-cli](https://www.npmjs.com/package/@n8n/node-cli).
|
||||
## Requirements
|
||||
|
||||
**To create a new node package from scratch:**
|
||||
- Node.js 22+ is recommended by the current n8n node toolchain
|
||||
- npm
|
||||
|
||||
```bash
|
||||
npm create @n8n/node
|
||||
```
|
||||
|
||||
**Already using this starter? Start developing with:**
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This starts n8n with your nodes loaded and hot reload enabled.
|
||||
|
||||
## What's Included
|
||||
|
||||
This starter repository includes two example nodes to learn from:
|
||||
|
||||
- **[Example Node](nodes/Example/)** - A simple starter node that shows the basic structure with a custom `execute` method
|
||||
- **[GitHub Issues Node](nodes/GithubIssues/)** - A complete, production-ready example built using the **declarative style**:
|
||||
- **Low-code approach** - Define operations declaratively without writing request logic
|
||||
- Multiple resources (Issues, Comments)
|
||||
- Multiple operations (Get, Get All, Create)
|
||||
- Two authentication methods (OAuth2 and Personal Access Token)
|
||||
- List search functionality for dynamic dropdowns
|
||||
- Proper error handling and typing
|
||||
- Ideal for HTTP API-based integrations
|
||||
|
||||
> [!TIP]
|
||||
> The declarative/low-code style (used in GitHub Issues) is the recommended approach for building nodes that interact with HTTP APIs. It significantly reduces boilerplate code and handles requests automatically.
|
||||
|
||||
Browse these examples to understand both approaches, then modify them or create your own.
|
||||
|
||||
## Finding Inspiration
|
||||
|
||||
Looking for more examples? Check out these resources:
|
||||
|
||||
- **[npm Community Nodes](https://www.npmjs.com/search?q=keywords:n8n-community-node-package)** - Browse thousands of community-built nodes on npm using the `n8n-community-node-package` tag
|
||||
- **[n8n Built-in Nodes](https://github.com/n8n-io/n8n/tree/master/packages/nodes-base/nodes)** - Study the source code of n8n's official nodes for production-ready patterns and best practices
|
||||
- **[n8n Credentials](https://github.com/n8n-io/n8n/tree/master/packages/nodes-base/credentials)** - See how authentication is implemented for various services
|
||||
|
||||
These are excellent resources to understand how to structure your nodes, handle different API patterns, and implement advanced features.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, install the following on your development machine:
|
||||
|
||||
### Required
|
||||
|
||||
- **[Node.js](https://nodejs.org/)** (v22 or higher) and npm
|
||||
- Linux/Mac/WSL: Install via [nvm](https://github.com/nvm-sh/nvm)
|
||||
- Windows: Follow [Microsoft's NodeJS guide](https://learn.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-windows)
|
||||
- **[git](https://git-scm.com/downloads)**
|
||||
|
||||
### Recommended
|
||||
|
||||
- Follow n8n's [development environment setup guide](https://docs.n8n.io/integrations/creating-nodes/build/node-development-environment/)
|
||||
|
||||
> [!NOTE]
|
||||
> The `@n8n/node-cli` is included as a dev dependency and will be installed automatically when you run `npm install`. The CLI includes n8n for local development, so you don't need to install n8n globally.
|
||||
|
||||
## Getting Started with this Starter
|
||||
|
||||
Follow these steps to create your own n8n community node package:
|
||||
|
||||
### 1. Create Your Repository
|
||||
|
||||
[Generate a new repository](https://github.com/n8n-io/n8n-nodes-starter/generate) from this template, then clone it:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/<your-organization>/<your-repo-name>.git
|
||||
cd <your-repo-name>
|
||||
```
|
||||
|
||||
### 2. Install Dependencies
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
This installs all required dependencies including the `@n8n/node-cli`.
|
||||
|
||||
### 3. Explore the Examples
|
||||
|
||||
Browse the example nodes in [nodes/](nodes/) and [credentials/](credentials/) to understand the structure:
|
||||
|
||||
- Start with [nodes/Example/](nodes/Example/) for a basic node
|
||||
- Study [nodes/GithubIssues/](nodes/GithubIssues/) for a real-world implementation
|
||||
|
||||
### 4. Build Your Node
|
||||
|
||||
Edit the example nodes to fit your use case, or create new node files by copying the structure from [nodes/Example/](nodes/Example/).
|
||||
|
||||
> [!TIP]
|
||||
> If you want to scaffold a completely new node package, use `npm create @n8n/node` to start fresh with the CLI's interactive generator.
|
||||
|
||||
### 5. Configure Your Package
|
||||
|
||||
Update `package.json` with your details:
|
||||
|
||||
- `name` - Your package name (must start with `n8n-nodes-`)
|
||||
- `author` - Your name and email
|
||||
- `repository` - Your repository URL
|
||||
- `description` - What your node does
|
||||
|
||||
Make sure your node is registered in the `n8n.nodes` array.
|
||||
|
||||
### 6. Develop and Test Locally
|
||||
|
||||
Start n8n with your node loaded:
|
||||
To use it as a local community node during development:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
This command runs `n8n-node dev` which:
|
||||
To install the published package into n8n:
|
||||
|
||||
- Builds your node with watch mode
|
||||
- Starts n8n with your node available
|
||||
- Automatically rebuilds when you make changes
|
||||
- Opens n8n in your browser (usually http://localhost:5678)
|
||||
```bash
|
||||
npm install n8n-nodes-mega
|
||||
```
|
||||
|
||||
You can now test your node in n8n workflows!
|
||||
## Credentials
|
||||
|
||||
> [!NOTE]
|
||||
> Learn more about CLI commands in the [@n8n/node-cli documentation](https://www.npmjs.com/package/@n8n/node-cli).
|
||||
Create a `Mega API` credential in n8n with:
|
||||
|
||||
### 7. Lint Your Code
|
||||
- `Base URL`: your Mega or Chatwoot instance URL, for example `https://app.example.com`
|
||||
- `API Access Token`: application token sent in the `api_access_token` header
|
||||
- `Mega Account ID`: external Chatwoot account identifier used in account-scoped endpoints
|
||||
|
||||
Check for errors:
|
||||
The credential test calls `GET /api/v1/profile` to validate the token.
|
||||
|
||||
## Operation
|
||||
|
||||
### Conversation -> Create
|
||||
|
||||
Required fields:
|
||||
|
||||
- `Source ID`
|
||||
- `Inbox ID`
|
||||
- `Contact ID`
|
||||
|
||||
Optional fields:
|
||||
|
||||
- `Status`
|
||||
- `Assignee ID`
|
||||
- `Initial Message`
|
||||
- `Additional Attributes`
|
||||
- `Custom Attributes`
|
||||
|
||||
The node sends requests to:
|
||||
|
||||
```text
|
||||
POST /api/v1/accounts/{accountId}/conversations
|
||||
```
|
||||
|
||||
## Local validation
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
```
|
||||
|
||||
Auto-fix issues when possible:
|
||||
|
||||
```bash
|
||||
npm run lint:fix
|
||||
```
|
||||
|
||||
### 8. Build for Production
|
||||
|
||||
When ready to publish:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
This compiles your TypeScript code to the `dist/` folder.
|
||||
|
||||
### 9. Prepare for Publishing
|
||||
|
||||
Before publishing:
|
||||
|
||||
1. **Update documentation**: Replace this README with your node's documentation. Use [README_TEMPLATE.md](README_TEMPLATE.md) as a starting point.
|
||||
2. **Update the LICENSE**: Add your details to the [LICENSE](LICENSE.md) file.
|
||||
3. **Test thoroughly**: Ensure your node works in different scenarios.
|
||||
|
||||
### 10. Publish to npm
|
||||
|
||||
Publish your package to make it available to the n8n community:
|
||||
|
||||
```bash
|
||||
npm publish
|
||||
```
|
||||
|
||||
Learn more about [publishing to npm](https://docs.npmjs.com/packages-and-modules/contributing-packages-to-the-registry).
|
||||
|
||||
### 11. Submit for Verification (Optional)
|
||||
|
||||
Get your node verified for n8n Cloud:
|
||||
|
||||
1. Ensure your node meets the [requirements](https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/):
|
||||
- Uses MIT license ✅ (included in this starter)
|
||||
- No external package dependencies
|
||||
- Follows n8n's design guidelines
|
||||
- Passes quality and security review
|
||||
|
||||
2. Submit through the [n8n Creator Portal](https://creators.n8n.io/nodes)
|
||||
|
||||
**Benefits of verification:**
|
||||
|
||||
- Available directly in n8n Cloud
|
||||
- Discoverable in the n8n nodes panel
|
||||
- Verified badge for quality assurance
|
||||
- Increased visibility in the n8n community
|
||||
|
||||
## Available Scripts
|
||||
|
||||
This starter includes several npm scripts to streamline development:
|
||||
|
||||
| Script | Description |
|
||||
| --------------------- | ---------------------------------------------------------------- |
|
||||
| `npm run dev` | Start n8n with your node and watch for changes (runs `n8n-node dev`) |
|
||||
| `npm run build` | Compile TypeScript to JavaScript for production (runs `n8n-node build`) |
|
||||
| `npm run build:watch` | Build in watch mode (auto-rebuild on changes) |
|
||||
| `npm run lint` | Check your code for errors and style issues (runs `n8n-node lint`) |
|
||||
| `npm run lint:fix` | Automatically fix linting issues when possible (runs `n8n-node lint --fix`) |
|
||||
| `npm run release` | Create a new release (runs `n8n-node release`) |
|
||||
|
||||
> [!TIP]
|
||||
> These scripts use the [@n8n/node-cli](https://www.npmjs.com/package/@n8n/node-cli) under the hood. You can also run CLI commands directly, e.g., `npx n8n-node dev`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### My node doesn't appear in n8n
|
||||
|
||||
1. Make sure you ran `npm install` to install dependencies
|
||||
2. Check that your node is listed in `package.json` under `n8n.nodes`
|
||||
3. Restart the dev server with `npm run dev`
|
||||
4. Check the console for any error messages
|
||||
|
||||
### Linting errors
|
||||
|
||||
Run `npm run lint:fix` to automatically fix most common issues. For remaining errors, check the [n8n node development guidelines](https://docs.n8n.io/integrations/creating-nodes/).
|
||||
|
||||
### TypeScript errors
|
||||
|
||||
Make sure you're using Node.js v22 or higher and have run `npm install` to get all type definitions.
|
||||
|
||||
## Resources
|
||||
|
||||
- **[n8n Node Documentation](https://docs.n8n.io/integrations/creating-nodes/)** - Complete guide to building nodes
|
||||
- **[n8n Community Forum](https://community.n8n.io/)** - Get help and share your nodes
|
||||
- **[@n8n/node-cli Documentation](https://www.npmjs.com/package/@n8n/node-cli)** - CLI tool reference
|
||||
- **[n8n Creator Portal](https://creators.n8n.io/nodes)** - Submit your node for verification
|
||||
- **[Submit Community Nodes Guide](https://docs.n8n.io/integrations/creating-nodes/deploy/submit-community-nodes/)** - Verification requirements and process
|
||||
|
||||
## Contributing
|
||||
|
||||
Have suggestions for improving this starter? [Open an issue](https://github.com/n8n-io/n8n-nodes-starter/issues) or submit a pull request!
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/n8n-io/n8n-nodes-starter/blob/master/LICENSE.md)
|
||||
If you are using an older Node.js release, the current `@n8n/node-cli` may warn or fail. Upgrade to Node.js 22+ before publishing.
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
import type {
|
||||
IAuthenticateGeneric,
|
||||
Icon,
|
||||
ICredentialTestRequest,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class GithubIssuesApi implements ICredentialType {
|
||||
name = 'githubIssuesApi';
|
||||
|
||||
displayName = 'GitHub Issues API';
|
||||
|
||||
icon: Icon = { light: 'file:../icons/github.svg', dark: 'file:../icons/github.dark.svg' };
|
||||
|
||||
documentationUrl =
|
||||
'https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#deleting-a-personal-access-token';
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Access Token',
|
||||
name: 'accessToken',
|
||||
type: 'string',
|
||||
typeOptions: { password: true },
|
||||
default: '',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate: IAuthenticateGeneric = {
|
||||
type: 'generic',
|
||||
properties: {
|
||||
headers: {
|
||||
Authorization: '=token {{$credentials?.accessToken}}',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test: ICredentialTestRequest = {
|
||||
request: {
|
||||
baseURL: 'https://api.github.com',
|
||||
url: '/user',
|
||||
method: 'GET',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,54 +0,0 @@
|
|||
import type { Icon, ICredentialType, INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export class GithubIssuesOAuth2Api implements ICredentialType {
|
||||
name = 'githubIssuesOAuth2Api';
|
||||
|
||||
extends = ['oAuth2Api'];
|
||||
|
||||
displayName = 'GitHub Issues OAuth2 API';
|
||||
|
||||
icon: Icon = { light: 'file:../icons/github.svg', dark: 'file:../icons/github.dark.svg' };
|
||||
|
||||
documentationUrl = 'https://docs.github.com/en/apps/oauth-apps';
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Grant Type',
|
||||
name: 'grantType',
|
||||
type: 'hidden',
|
||||
default: 'authorizationCode',
|
||||
},
|
||||
{
|
||||
displayName: 'Authorization URL',
|
||||
name: 'authUrl',
|
||||
type: 'hidden',
|
||||
default: 'https://github.com/login/oauth/authorize',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Access Token URL',
|
||||
name: 'accessTokenUrl',
|
||||
type: 'hidden',
|
||||
default: 'https://github.com/login/oauth/access_token',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
displayName: 'Scope',
|
||||
name: 'scope',
|
||||
type: 'hidden',
|
||||
default: 'repo',
|
||||
},
|
||||
{
|
||||
displayName: 'Auth URI Query Parameters',
|
||||
name: 'authQueryParameters',
|
||||
type: 'hidden',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'hidden',
|
||||
default: 'header',
|
||||
},
|
||||
];
|
||||
}
|
||||
63
credentials/MegaApi.credentials.ts
Normal file
63
credentials/MegaApi.credentials.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import type {
|
||||
IAuthenticateGeneric,
|
||||
Icon,
|
||||
ICredentialTestRequest,
|
||||
ICredentialType,
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export class MegaApi implements ICredentialType {
|
||||
name = 'megaApi';
|
||||
|
||||
displayName = 'Mega API';
|
||||
|
||||
documentationUrl = 'https://developers.chatwoot.com/api-reference/profile/get-user-profile';
|
||||
|
||||
icon: Icon = { light: 'file:../icons/mega.svg', dark: 'file:../icons/mega.dark.svg' };
|
||||
|
||||
properties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Base URL',
|
||||
name: 'baseUrl',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'https://app.example.com',
|
||||
required: true,
|
||||
description: 'Base URL of the Mega or Chatwoot instance',
|
||||
},
|
||||
{
|
||||
displayName: 'API Access Token',
|
||||
name: 'apiAccessToken',
|
||||
type: 'string',
|
||||
typeOptions: { password: true },
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Application token sent in the api_access_token header',
|
||||
},
|
||||
{
|
||||
displayName: 'Mega Account ID',
|
||||
name: 'accountId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'External Chatwoot account identifier used in account-scoped routes',
|
||||
},
|
||||
];
|
||||
|
||||
authenticate: IAuthenticateGeneric = {
|
||||
type: 'generic',
|
||||
properties: {
|
||||
headers: {
|
||||
api_access_token: '={{$credentials.apiAccessToken}}',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
test: ICredentialTestRequest = {
|
||||
request: {
|
||||
baseURL: '={{$credentials.baseUrl}}',
|
||||
url: '/api/v1/profile',
|
||||
method: 'GET',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0165 0C8.94791 0 0 9.01388 0 20.1653C0 29.0792 5.73324 36.6246 13.6868 39.2952C14.6812 39.496 15.0454 38.8613 15.0454 38.3274C15.0454 37.8599 15.0126 36.2575 15.0126 34.5879C9.4445 35.79 8.28498 32.1841 8.28498 32.1841C7.39015 29.847 6.06429 29.2463 6.06429 29.2463C4.24185 28.011 6.19704 28.011 6.19704 28.011C8.21861 28.1446 9.27938 30.081 9.27938 30.081C11.0686 33.1522 13.9518 32.2844 15.1118 31.7502C15.2773 30.4481 15.8079 29.5467 16.3713 29.046C11.9303 28.5785 7.25781 26.8425 7.25781 19.0967C7.25781 16.8932 8.05267 15.0905 9.31216 13.6884C9.11344 13.1877 8.41732 11.1174 9.51128 8.34644C9.51128 8.34644 11.2014 7.81217 15.0122 10.4164C16.6438 9.97495 18.3263 9.7504 20.0165 9.74851C21.7067 9.74851 23.4295 9.98246 25.0205 10.4164C28.8317 7.81217 30.5218 8.34644 30.5218 8.34644C31.6158 11.1174 30.9192 13.1877 30.7205 13.6884C32.0132 15.0905 32.7753 16.8932 32.7753 19.0967C32.7753 26.8425 28.1028 28.5449 23.6287 29.046C24.358 29.6802 24.9873 30.882 24.9873 32.7851C24.9873 35.4893 24.9545 37.6596 24.9545 38.327C24.9545 38.8613 25.3192 39.496 26.3132 39.2956C34.2667 36.6242 39.9999 29.0792 39.9999 20.1653C40.0327 9.01388 31.052 0 20.0165 0Z" fill="white"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
|
|
@ -1,3 +0,0 @@
|
|||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0165 0C8.94791 0 0 9.01388 0 20.1653C0 29.0792 5.73324 36.6246 13.6868 39.2952C14.6812 39.496 15.0454 38.8613 15.0454 38.3274C15.0454 37.8599 15.0126 36.2575 15.0126 34.5879C9.4445 35.79 8.28498 32.1841 8.28498 32.1841C7.39015 29.847 6.06429 29.2463 6.06429 29.2463C4.24185 28.011 6.19704 28.011 6.19704 28.011C8.21861 28.1446 9.27938 30.081 9.27938 30.081C11.0686 33.1522 13.9518 32.2844 15.1118 31.7502C15.2773 30.4481 15.8079 29.5467 16.3713 29.046C11.9303 28.5785 7.25781 26.8425 7.25781 19.0967C7.25781 16.8932 8.05267 15.0905 9.31216 13.6884C9.11344 13.1877 8.41732 11.1174 9.51128 8.34644C9.51128 8.34644 11.2014 7.81217 15.0122 10.4164C16.6438 9.97495 18.3263 9.7504 20.0165 9.74851C21.7067 9.74851 23.4295 9.98246 25.0205 10.4164C28.8317 7.81217 30.5218 8.34644 30.5218 8.34644C31.6158 11.1174 30.9192 13.1877 30.7205 13.6884C32.0132 15.0905 32.7753 16.8932 32.7753 19.0967C32.7753 26.8425 28.1028 28.5449 23.6287 29.046C24.358 29.6802 24.9873 30.882 24.9873 32.7851C24.9873 35.4893 24.9545 37.6596 24.9545 38.327C24.9545 38.8613 25.3192 39.496 26.3132 39.2956C34.2667 36.6242 39.9999 29.0792 39.9999 20.1653C40.0327 9.01388 31.052 0 20.0165 0Z" fill="#24292F"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB |
5
icons/mega.dark.svg
Normal file
5
icons/mega.dark.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title">
|
||||
<title>Mega</title>
|
||||
<rect width="64" height="64" rx="14" fill="#F8FAFC"/>
|
||||
<path d="M16 46V18h7.04L32 33.1 40.96 18H48v28h-6.18V28.84L32 44.5l-9.82-15.66V46H16Z" fill="#0F172A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 287 B |
5
icons/mega.svg
Normal file
5
icons/mega.svg
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-labelledby="title">
|
||||
<title>Mega</title>
|
||||
<rect width="64" height="64" rx="14" fill="#0F172A"/>
|
||||
<path d="M16 46V18h7.04L32 33.1 40.96 18H48v28h-6.18V28.84L32 44.5l-9.82-15.66V46H16Z" fill="#F8FAFC"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 287 B |
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"node": "n8n-nodes-example",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
import type {
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
export class Example implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Example',
|
||||
name: 'example',
|
||||
icon: { light: 'file:example.svg', dark: 'file:example.dark.svg' },
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
description: 'Basic Example Node',
|
||||
defaults: {
|
||||
name: 'Example',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
usableAsTool: true,
|
||||
properties: [
|
||||
// Node properties which the user gets displayed and
|
||||
// can change on the node.
|
||||
{
|
||||
displayName: 'My String',
|
||||
name: 'myString',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: 'Placeholder value',
|
||||
description: 'The description text',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// The function below is responsible for actually doing whatever this node
|
||||
// is supposed to do. In this case, we're just appending the `myString` property
|
||||
// with whatever the user has entered.
|
||||
// You can make async calls and use `await`.
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
|
||||
let item: INodeExecutionData;
|
||||
let myString: string;
|
||||
|
||||
// Iterates over all input items and add the key "myString" with the
|
||||
// value the parameter "myString" resolves to.
|
||||
// (This could be a different value for each item in case it contains an expression)
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
try {
|
||||
myString = this.getNodeParameter('myString', itemIndex, '') as string;
|
||||
item = items[itemIndex];
|
||||
|
||||
item.json.myString = myString;
|
||||
} catch (error) {
|
||||
// This node should never fail but we want to showcase how
|
||||
// to handle errors.
|
||||
if (this.continueOnFail()) {
|
||||
items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
|
||||
} else {
|
||||
// Adding `itemIndex` allows other workflows to handle this error
|
||||
if (error.context) {
|
||||
// If the error thrown already contains the context property,
|
||||
// only append the itemIndex
|
||||
error.context.itemIndex = itemIndex;
|
||||
throw error;
|
||||
}
|
||||
throw new NodeOperationError(this.getNode(), error, {
|
||||
itemIndex,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [items];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
<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>
|
||||
|
Before Width: | Height: | Size: 698 B |
|
|
@ -1,13 +0,0 @@
|
|||
<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>
|
||||
|
Before Width: | Height: | Size: 696 B |
|
|
@ -1,18 +0,0 @@
|
|||
{
|
||||
"node": "n8n-nodes-github-issues",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
import { NodeConnectionTypes, type INodeType, type INodeTypeDescription } from 'n8n-workflow';
|
||||
import { issueDescription } from './resources/issue';
|
||||
import { issueCommentDescription } from './resources/issueComment';
|
||||
import { getRepositories } from './listSearch/getRepositories';
|
||||
import { getUsers } from './listSearch/getUsers';
|
||||
import { getIssues } from './listSearch/getIssues';
|
||||
|
||||
export class GithubIssues implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'GitHub Issues',
|
||||
name: 'githubIssues',
|
||||
icon: { light: 'file:../../icons/github.svg', dark: 'file:../../icons/github.dark.svg' },
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume issues from the GitHub API',
|
||||
defaults: {
|
||||
name: 'GitHub Issues',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'githubIssuesApi',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['accessToken'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'githubIssuesOAuth2Api',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
authentication: ['oAuth2'],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
requestDefaults: {
|
||||
baseURL: 'https://api.github.com',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Authentication',
|
||||
name: 'authentication',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'Access Token',
|
||||
value: 'accessToken',
|
||||
},
|
||||
{
|
||||
name: 'OAuth2',
|
||||
value: 'oAuth2',
|
||||
},
|
||||
],
|
||||
default: 'accessToken',
|
||||
},
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Issue',
|
||||
value: 'issue',
|
||||
},
|
||||
{
|
||||
name: 'Issue Comment',
|
||||
value: 'issueComment',
|
||||
},
|
||||
],
|
||||
default: 'issue',
|
||||
},
|
||||
...issueDescription,
|
||||
...issueCommentDescription,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
listSearch: {
|
||||
getRepositories,
|
||||
getUsers,
|
||||
getIssues,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import type {
|
||||
ILoadOptionsFunctions,
|
||||
INodeListSearchResult,
|
||||
INodeListSearchItems,
|
||||
} from 'n8n-workflow';
|
||||
import { githubApiRequest } from '../shared/transport';
|
||||
|
||||
type IssueSearchItem = {
|
||||
number: number;
|
||||
title: string;
|
||||
html_url: string;
|
||||
};
|
||||
|
||||
type IssueSearchResponse = {
|
||||
items: IssueSearchItem[];
|
||||
total_count: number;
|
||||
};
|
||||
|
||||
export async function getIssues(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const page = paginationToken ? +paginationToken : 1;
|
||||
const per_page = 100;
|
||||
|
||||
let responseData: IssueSearchResponse = {
|
||||
items: [],
|
||||
total_count: 0,
|
||||
};
|
||||
const owner = this.getNodeParameter('owner', '', { extractValue: true });
|
||||
const repository = this.getNodeParameter('repository', '', { extractValue: true });
|
||||
const filters = [filter, `repo:${owner}/${repository}`];
|
||||
|
||||
responseData = await githubApiRequest.call(this, 'GET', '/search/issues', {
|
||||
q: filters.filter(Boolean).join(' '),
|
||||
page,
|
||||
per_page,
|
||||
});
|
||||
|
||||
const results: INodeListSearchItems[] = responseData.items.map((item: IssueSearchItem) => ({
|
||||
name: item.title,
|
||||
value: item.number,
|
||||
url: item.html_url,
|
||||
}));
|
||||
|
||||
const nextPaginationToken = page * per_page < responseData.total_count ? page + 1 : undefined;
|
||||
return { results, paginationToken: nextPaginationToken };
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
import type {
|
||||
ILoadOptionsFunctions,
|
||||
INodeListSearchItems,
|
||||
INodeListSearchResult,
|
||||
} from 'n8n-workflow';
|
||||
import { githubApiRequest } from '../shared/transport';
|
||||
|
||||
type RepositorySearchItem = {
|
||||
name: string;
|
||||
html_url: string;
|
||||
};
|
||||
|
||||
type RepositorySearchResponse = {
|
||||
items: RepositorySearchItem[];
|
||||
total_count: number;
|
||||
};
|
||||
|
||||
export async function getRepositories(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const owner = this.getCurrentNodeParameter('owner', { extractValue: true });
|
||||
const page = paginationToken ? +paginationToken : 1;
|
||||
const per_page = 100;
|
||||
const q = `${filter ?? ''} user:${owner} fork:true`;
|
||||
let responseData: RepositorySearchResponse = {
|
||||
items: [],
|
||||
total_count: 0,
|
||||
};
|
||||
|
||||
try {
|
||||
responseData = await githubApiRequest.call(this, 'GET', '/search/repositories', {
|
||||
q,
|
||||
page,
|
||||
per_page,
|
||||
});
|
||||
} catch {
|
||||
// will fail if the owner does not have any repositories
|
||||
}
|
||||
|
||||
const results: INodeListSearchItems[] = responseData.items.map((item: RepositorySearchItem) => ({
|
||||
name: item.name,
|
||||
value: item.name,
|
||||
url: item.html_url,
|
||||
}));
|
||||
|
||||
const nextPaginationToken = page * per_page < responseData.total_count ? page + 1 : undefined;
|
||||
return { results, paginationToken: nextPaginationToken };
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
import type {
|
||||
ILoadOptionsFunctions,
|
||||
INodeListSearchResult,
|
||||
INodeListSearchItems,
|
||||
} from 'n8n-workflow';
|
||||
import { githubApiRequest } from '../shared/transport';
|
||||
|
||||
type UserSearchItem = {
|
||||
login: string;
|
||||
html_url: string;
|
||||
};
|
||||
|
||||
type UserSearchResponse = {
|
||||
items: UserSearchItem[];
|
||||
total_count: number;
|
||||
};
|
||||
|
||||
export async function getUsers(
|
||||
this: ILoadOptionsFunctions,
|
||||
filter?: string,
|
||||
paginationToken?: string,
|
||||
): Promise<INodeListSearchResult> {
|
||||
const page = paginationToken ? +paginationToken : 1;
|
||||
const per_page = 100;
|
||||
|
||||
let responseData: UserSearchResponse = {
|
||||
items: [],
|
||||
total_count: 0,
|
||||
};
|
||||
|
||||
try {
|
||||
responseData = await githubApiRequest.call(this, 'GET', '/search/users', {
|
||||
q: filter,
|
||||
page,
|
||||
per_page,
|
||||
});
|
||||
} catch {
|
||||
// will fail if the owner does not have any users
|
||||
}
|
||||
|
||||
const results: INodeListSearchItems[] = responseData.items.map((item: UserSearchItem) => ({
|
||||
name: item.login,
|
||||
value: item.login,
|
||||
url: item.html_url,
|
||||
}));
|
||||
|
||||
const nextPaginationToken = page * per_page < responseData.total_count ? page + 1 : undefined;
|
||||
return { results, paginationToken: nextPaginationToken };
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
const showOnlyForIssueCreate = {
|
||||
operation: ['create'],
|
||||
resource: ['issue'],
|
||||
};
|
||||
|
||||
export const issueCreateDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Title',
|
||||
name: 'title',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueCreate,
|
||||
},
|
||||
description: 'The title of the issue',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'title',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Body',
|
||||
name: 'body',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 5,
|
||||
},
|
||||
default: '',
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueCreate,
|
||||
},
|
||||
description: 'The body of the issue',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'body',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Labels',
|
||||
name: 'labels',
|
||||
type: 'collection',
|
||||
typeOptions: {
|
||||
multipleValues: true,
|
||||
multipleValueButtonText: 'Add Label',
|
||||
},
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueCreate,
|
||||
},
|
||||
default: { label: '' },
|
||||
options: [
|
||||
{
|
||||
displayName: 'Label',
|
||||
name: 'label',
|
||||
type: 'string',
|
||||
default: '',
|
||||
description: 'Label to add to issue',
|
||||
},
|
||||
],
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
property: 'labels',
|
||||
value: '={{$value.map((data) => data.label)}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { issueSelect } from '../../shared/descriptions';
|
||||
|
||||
const showOnlyForIssueGet = {
|
||||
operation: ['get'],
|
||||
resource: ['issue'],
|
||||
};
|
||||
|
||||
export const issueGetDescription: INodeProperties[] = [
|
||||
{
|
||||
...issueSelect,
|
||||
displayOptions: { show: showOnlyForIssueGet },
|
||||
},
|
||||
];
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { parseLinkHeader } from '../../shared/utils';
|
||||
|
||||
const showOnlyForIssueGetMany = {
|
||||
operation: ['getAll'],
|
||||
resource: ['issue'],
|
||||
};
|
||||
|
||||
export const issueGetManyDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
...showOnlyForIssueGetMany,
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
routing: {
|
||||
send: {
|
||||
type: 'query',
|
||||
property: 'per_page',
|
||||
},
|
||||
output: {
|
||||
maxResults: '={{$value}}',
|
||||
},
|
||||
},
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueGetMany,
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
routing: {
|
||||
send: {
|
||||
paginate: '={{ $value }}',
|
||||
type: 'query',
|
||||
property: 'per_page',
|
||||
value: '100',
|
||||
},
|
||||
operations: {
|
||||
pagination: {
|
||||
type: 'generic',
|
||||
properties: {
|
||||
continue: `={{ !!(${parseLinkHeader.toString()})($response.headers?.link).next }}`,
|
||||
request: {
|
||||
url: `={{ (${parseLinkHeader.toString()})($response.headers?.link)?.next ?? $request.url }}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Filters',
|
||||
name: 'filters',
|
||||
type: 'collection',
|
||||
typeOptions: {
|
||||
multipleValueButtonText: 'Add Filter',
|
||||
},
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueGetMany,
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Updated Since',
|
||||
name: 'since',
|
||||
type: 'dateTime',
|
||||
default: '',
|
||||
description: 'Return only issues updated at or after this time',
|
||||
routing: {
|
||||
request: {
|
||||
qs: {
|
||||
since: '={{$value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'State',
|
||||
name: 'state',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'All',
|
||||
value: 'all',
|
||||
description: 'Returns issues with any state',
|
||||
},
|
||||
{
|
||||
name: 'Closed',
|
||||
value: 'closed',
|
||||
description: 'Return issues with "closed" state',
|
||||
},
|
||||
{
|
||||
name: 'Open',
|
||||
value: 'open',
|
||||
description: 'Return issues with "open" state',
|
||||
},
|
||||
],
|
||||
default: 'open',
|
||||
description: 'The issue state to filter on',
|
||||
routing: {
|
||||
request: {
|
||||
qs: {
|
||||
state: '={{$value}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { repoNameSelect, repoOwnerSelect } from '../../shared/descriptions';
|
||||
import { issueGetManyDescription } from './getAll';
|
||||
import { issueGetDescription } from './get';
|
||||
import { issueCreateDescription } from './create';
|
||||
|
||||
const showOnlyForIssues = {
|
||||
resource: ['issue'],
|
||||
};
|
||||
|
||||
export const issueDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: showOnlyForIssues,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
action: 'Get issues in a repository',
|
||||
description: 'Get many issues in a repository',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
action: 'Get an issue',
|
||||
description: 'Get the data of a single issue',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues/{{$parameter.issue}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
action: 'Create a new issue',
|
||||
description: 'Create a new issue',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'POST',
|
||||
url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
{
|
||||
...repoOwnerSelect,
|
||||
displayOptions: {
|
||||
show: showOnlyForIssues,
|
||||
},
|
||||
},
|
||||
{
|
||||
...repoNameSelect,
|
||||
displayOptions: {
|
||||
show: showOnlyForIssues,
|
||||
},
|
||||
},
|
||||
...issueGetManyDescription,
|
||||
...issueGetDescription,
|
||||
...issueCreateDescription,
|
||||
];
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { parseLinkHeader } from '../../shared/utils';
|
||||
|
||||
const showOnlyForIssueCommentGetMany = {
|
||||
operation: ['getAll'],
|
||||
resource: ['issueComment'],
|
||||
};
|
||||
|
||||
export const issueCommentGetManyDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
...showOnlyForIssueCommentGetMany,
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 100,
|
||||
},
|
||||
default: 50,
|
||||
routing: {
|
||||
send: {
|
||||
type: 'query',
|
||||
property: 'per_page',
|
||||
},
|
||||
output: {
|
||||
maxResults: '={{$value}}',
|
||||
},
|
||||
},
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueCommentGetMany,
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
routing: {
|
||||
send: {
|
||||
paginate: '={{ $value }}',
|
||||
type: 'query',
|
||||
property: 'per_page',
|
||||
value: '100',
|
||||
},
|
||||
operations: {
|
||||
pagination: {
|
||||
type: 'generic',
|
||||
properties: {
|
||||
continue: `={{ !!(${parseLinkHeader.toString()})($response.headers?.link).next }}`,
|
||||
request: {
|
||||
url: `={{ (${parseLinkHeader.toString()})($response.headers?.link)?.next ?? $request.url }}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
import { repoNameSelect, repoOwnerSelect } from '../../shared/descriptions';
|
||||
import { issueCommentGetManyDescription } from './getAll';
|
||||
|
||||
const showOnlyForIssueComments = {
|
||||
resource: ['issueComment'],
|
||||
};
|
||||
|
||||
export const issueCommentDescription: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueComments,
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
action: 'Get issue comments',
|
||||
description: 'Get issue comments',
|
||||
routing: {
|
||||
request: {
|
||||
method: 'GET',
|
||||
url: '=/repos/{{$parameter.owner}}/{{$parameter.repository}}/issues/comments',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
default: 'getAll',
|
||||
},
|
||||
{
|
||||
...repoOwnerSelect,
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueComments,
|
||||
},
|
||||
},
|
||||
{
|
||||
...repoNameSelect,
|
||||
displayOptions: {
|
||||
show: showOnlyForIssueComments,
|
||||
},
|
||||
},
|
||||
...issueCommentGetManyDescription,
|
||||
];
|
||||
|
|
@ -1,151 +0,0 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const repoOwnerSelect: INodeProperties = {
|
||||
displayName: 'Repository Owner',
|
||||
name: 'owner',
|
||||
type: 'resourceLocator',
|
||||
default: { mode: 'list', value: '' },
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'Repository Owner',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'Select an owner...',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getUsers',
|
||||
searchable: true,
|
||||
searchFilterRequired: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Link',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://github.com/n8n-io',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https:\\/\\/github.com\\/([-_0-9a-zA-Z]+)',
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https:\\/\\/github.com\\/([-_0-9a-zA-Z]+)(?:.*)',
|
||||
errorMessage: 'Not a valid GitHub URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. n8n-io',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[-_a-zA-Z0-9]+',
|
||||
errorMessage: 'Not a valid GitHub Owner Name',
|
||||
},
|
||||
},
|
||||
],
|
||||
url: '=https://github.com/{{$value}}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const repoNameSelect: INodeProperties = {
|
||||
displayName: 'Repository Name',
|
||||
name: 'repository',
|
||||
type: 'resourceLocator',
|
||||
default: {
|
||||
mode: 'list',
|
||||
value: '',
|
||||
},
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'Repository Name',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'Select an Repository...',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getRepositories',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Link',
|
||||
name: 'url',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. https://github.com/n8n-io/n8n',
|
||||
extractValue: {
|
||||
type: 'regex',
|
||||
regex: 'https:\\/\\/github.com\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)',
|
||||
},
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: 'https:\\/\\/github.com\\/(?:[-_0-9a-zA-Z]+)\\/([-_.0-9a-zA-Z]+)(?:.*)',
|
||||
errorMessage: 'Not a valid GitHub Repository URL',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
displayName: 'By Name',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. n8n',
|
||||
validation: [
|
||||
{
|
||||
type: 'regex',
|
||||
properties: {
|
||||
regex: '[-_.0-9a-zA-Z]+',
|
||||
errorMessage: 'Not a valid GitHub Repository Name',
|
||||
},
|
||||
},
|
||||
],
|
||||
url: '=https://github.com/{{$parameter["owner"]}}/{{$value}}',
|
||||
},
|
||||
],
|
||||
displayOptions: {
|
||||
hide: {
|
||||
resource: ['user', 'organization'],
|
||||
operation: ['getRepositories'],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const issueSelect: INodeProperties = {
|
||||
displayName: 'Issue',
|
||||
name: 'issue',
|
||||
type: 'resourceLocator',
|
||||
default: {
|
||||
mode: 'list',
|
||||
value: '',
|
||||
},
|
||||
required: true,
|
||||
modes: [
|
||||
{
|
||||
displayName: 'Issue',
|
||||
name: 'list',
|
||||
type: 'list',
|
||||
placeholder: 'Select an Issue...',
|
||||
typeOptions: {
|
||||
searchListMethod: 'getIssues',
|
||||
searchable: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'By ID',
|
||||
name: 'name',
|
||||
type: 'string',
|
||||
placeholder: 'e.g. 123',
|
||||
url: '=https://github.com/{{$parameter.owner}}/{{$parameter.repository}}/issues/{{$value}}',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import type {
|
||||
IHookFunctions,
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IHttpRequestMethods,
|
||||
IDataObject,
|
||||
IHttpRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export async function githubApiRequest(
|
||||
this: IHookFunctions | IExecuteFunctions | IExecuteSingleFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
resource: string,
|
||||
qs: IDataObject = {},
|
||||
body: IDataObject | undefined = undefined,
|
||||
) {
|
||||
const authenticationMethod = this.getNodeParameter('authentication', 0);
|
||||
|
||||
const options: IHttpRequestOptions = {
|
||||
method: method,
|
||||
qs,
|
||||
body,
|
||||
url: `https://api.github.com${resource}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
const credentialType =
|
||||
authenticationMethod === 'accessToken' ? 'githubIssuesApi' : 'githubIssuesOAuth2Api';
|
||||
|
||||
return this.helpers.httpRequestWithAuthentication.call(this, credentialType, options);
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
export function parseLinkHeader(header?: string): { [rel: string]: string } {
|
||||
const links: { [rel: string]: string } = {};
|
||||
|
||||
for (const part of header?.split(',') ?? []) {
|
||||
const section = part.trim();
|
||||
const match = section.match(/^<([^>]+)>\s*;\s*rel="?([^"]+)"?/);
|
||||
if (match) {
|
||||
const [, url, rel] = match;
|
||||
links[rel] = url;
|
||||
}
|
||||
}
|
||||
|
||||
return links;
|
||||
}
|
||||
18
nodes/Mega/Mega.node.json
Normal file
18
nodes/Mega/Mega.node.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"node": "n8n-nodes-mega.mega",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Communication"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file#credentials"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://github.com/jessefreitas/n8n_community_mega?tab=readme-ov-file"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
283
nodes/Mega/Mega.node.ts
Normal file
283
nodes/Mega/Mega.node.ts
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
INodeProperties,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes, NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
import { megaApiRequest } from './shared/transport';
|
||||
|
||||
const conversationCreateProperties: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Source ID',
|
||||
name: 'sourceId',
|
||||
type: 'string',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'Source identifier of the contact inbox used to create the conversation',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Inbox ID',
|
||||
name: 'inboxId',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberPrecision: 0,
|
||||
},
|
||||
default: 0,
|
||||
required: true,
|
||||
description: 'Inbox identifier that will own the conversation',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Contact ID',
|
||||
name: 'contactId',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberPrecision: 0,
|
||||
},
|
||||
default: 0,
|
||||
required: true,
|
||||
description: 'Contact identifier linked to the new conversation',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Status',
|
||||
name: 'status',
|
||||
type: 'options',
|
||||
options: [
|
||||
{ name: 'Open', value: 'open' },
|
||||
{ name: 'Pending', value: 'pending' },
|
||||
{ name: 'Resolved', value: 'resolved' },
|
||||
{ name: 'Snoozed', value: 'snoozed' },
|
||||
],
|
||||
default: 'open',
|
||||
description: 'Initial status for the created conversation',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Assignee ID',
|
||||
name: 'assigneeId',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
numberPrecision: 0,
|
||||
},
|
||||
default: 0,
|
||||
description: 'Optional assignee identifier',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Initial Message',
|
||||
name: 'messageContent',
|
||||
type: 'string',
|
||||
typeOptions: {
|
||||
rows: 4,
|
||||
},
|
||||
default: '',
|
||||
description: 'Optional first message sent together with the conversation',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Attributes',
|
||||
name: 'additionalAttributes',
|
||||
type: 'json',
|
||||
default: '{}',
|
||||
description: 'Optional JSON object sent as additional_attributes',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
displayName: 'Custom Attributes',
|
||||
name: 'customAttributes',
|
||||
type: 'json',
|
||||
default: '{}',
|
||||
description: 'Optional JSON object sent as custom_attributes',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export class Mega implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Mega',
|
||||
name: 'mega',
|
||||
icon: { light: 'file:../../icons/mega.svg', dark: 'file:../../icons/mega.dark.svg' },
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Create conversations in Mega through the Chatwoot-compatible API',
|
||||
defaults: {
|
||||
name: 'Mega',
|
||||
},
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
usableAsTool: true,
|
||||
credentials: [
|
||||
{
|
||||
name: 'megaApi',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Conversation',
|
||||
value: 'conversation',
|
||||
},
|
||||
],
|
||||
default: 'conversation',
|
||||
},
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Create',
|
||||
value: 'create',
|
||||
description: 'Create a conversation',
|
||||
action: 'Create a conversation',
|
||||
},
|
||||
],
|
||||
default: 'create',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['conversation'],
|
||||
},
|
||||
},
|
||||
},
|
||||
...conversationCreateProperties,
|
||||
],
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const credentials = await this.getCredentials('megaApi');
|
||||
const accountId = credentials.accountId as string;
|
||||
|
||||
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
|
||||
try {
|
||||
const resource = this.getNodeParameter('resource', itemIndex) as string;
|
||||
const operation = this.getNodeParameter('operation', itemIndex) as string;
|
||||
|
||||
if (resource !== 'conversation' || operation !== 'create') {
|
||||
throw new NodeOperationError(this.getNode(), 'Unsupported operation', { itemIndex });
|
||||
}
|
||||
|
||||
const body: IDataObject = {
|
||||
source_id: this.getNodeParameter('sourceId', itemIndex) as string,
|
||||
inbox_id: this.getNodeParameter('inboxId', itemIndex) as number,
|
||||
contact_id: this.getNodeParameter('contactId', itemIndex) as number,
|
||||
status: this.getNodeParameter('status', itemIndex) as string,
|
||||
};
|
||||
|
||||
const assigneeId = this.getNodeParameter('assigneeId', itemIndex) as number;
|
||||
const messageContent = this.getNodeParameter('messageContent', itemIndex) as string;
|
||||
const additionalAttributes = this.getNodeParameter(
|
||||
'additionalAttributes',
|
||||
itemIndex,
|
||||
{},
|
||||
) as IDataObject;
|
||||
const customAttributes = this.getNodeParameter(
|
||||
'customAttributes',
|
||||
itemIndex,
|
||||
{},
|
||||
) as IDataObject;
|
||||
|
||||
if (assigneeId > 0) {
|
||||
body.assignee_id = assigneeId;
|
||||
}
|
||||
|
||||
if (messageContent.trim()) {
|
||||
body.message = {
|
||||
content: messageContent,
|
||||
};
|
||||
}
|
||||
|
||||
if (Object.keys(additionalAttributes).length > 0) {
|
||||
body.additional_attributes = additionalAttributes;
|
||||
}
|
||||
|
||||
if (Object.keys(customAttributes).length > 0) {
|
||||
body.custom_attributes = customAttributes;
|
||||
}
|
||||
|
||||
const response = await megaApiRequest.call(
|
||||
this,
|
||||
'POST',
|
||||
`/api/v1/accounts/${accountId}/conversations`,
|
||||
body,
|
||||
);
|
||||
|
||||
returnData.push({
|
||||
json: response as IDataObject,
|
||||
pairedItem: itemIndex,
|
||||
});
|
||||
} catch (error) {
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({
|
||||
json: { error: (error as Error).message },
|
||||
pairedItem: itemIndex,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new NodeOperationError(this.getNode(), error as Error, {
|
||||
itemIndex,
|
||||
description:
|
||||
'Mega API request failed. Confirm the base URL, API access token, account ID, and payload fields.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
38
nodes/Mega/shared/transport.ts
Normal file
38
nodes/Mega/shared/transport.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
IExecuteSingleFunctions,
|
||||
IHookFunctions,
|
||||
IHttpRequestMethods,
|
||||
IHttpRequestOptions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
type RequestContext =
|
||||
| IExecuteFunctions
|
||||
| IExecuteSingleFunctions
|
||||
| IHookFunctions
|
||||
| ILoadOptionsFunctions;
|
||||
|
||||
function normalizeBaseUrl(baseUrl: string): string {
|
||||
return baseUrl.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
export async function megaApiRequest(
|
||||
this: RequestContext,
|
||||
method: IHttpRequestMethods,
|
||||
route: string,
|
||||
body?: IDataObject,
|
||||
) {
|
||||
const credentials = await this.getCredentials('megaApi');
|
||||
const baseUrl = normalizeBaseUrl(credentials.baseUrl as string);
|
||||
|
||||
const options: IHttpRequestOptions = {
|
||||
method,
|
||||
url: `${baseUrl}${route}`,
|
||||
body,
|
||||
json: true,
|
||||
};
|
||||
|
||||
return this.helpers.httpRequestWithAuthentication.call(this, 'megaApi', options);
|
||||
}
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "n8n-nodes-<...>",
|
||||
"name": "n8n-nodes-mega",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "n8n-nodes-<...>",
|
||||
"name": "n8n-nodes-mega",
|
||||
"version": "0.1.0",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
|
|
|
|||
23
package.json
23
package.json
|
|
@ -1,19 +1,22 @@
|
|||
{
|
||||
"name": "n8n-nodes-<...>",
|
||||
"name": "n8n-nodes-mega",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"description": "n8n community node for creating Mega conversations via the Chatwoot-compatible API",
|
||||
"license": "MIT",
|
||||
"homepage": "",
|
||||
"homepage": "https://github.com/jessefreitas/n8n_community_mega",
|
||||
"keywords": [
|
||||
"n8n-community-node-package"
|
||||
"n8n-community-node-package",
|
||||
"n8n",
|
||||
"mega",
|
||||
"chatwoot"
|
||||
],
|
||||
"author": {
|
||||
"name": "",
|
||||
"email": ""
|
||||
"name": "Jesse Freitas",
|
||||
"email": "jesse.freitas@omniforge.com.br"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/<...>/n8n-nodes-<...>.git"
|
||||
"url": "https://github.com/jessefreitas/n8n_community_mega.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "n8n-node build",
|
||||
|
|
@ -31,12 +34,10 @@
|
|||
"n8nNodesApiVersion": 1,
|
||||
"strict": true,
|
||||
"credentials": [
|
||||
"dist/credentials/GithubIssuesApi.credentials.js",
|
||||
"dist/credentials/GithubIssuesOAuth2Api.credentials.js"
|
||||
"dist/credentials/MegaApi.credentials.js"
|
||||
],
|
||||
"nodes": [
|
||||
"dist/nodes/GithubIssues/GithubIssues.node.js",
|
||||
"dist/nodes/Example/Example.node.js"
|
||||
"dist/nodes/Mega/Mega.node.js"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue