n8n-nodes-bun 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +315 -0
- package/dist/nodes/BunCode/BunCode.node.d.ts +5 -0
- package/dist/nodes/BunCode/BunCode.node.js +99 -0
- package/dist/nodes/BunCode/BunCode.node.js.map +1 -0
- package/dist/nodes/BunCode/bunCode.svg +22 -0
- package/dist/nodes/BunCode/runBunCode.d.ts +2 -0
- package/dist/nodes/BunCode/runBunCode.js +143 -0
- package/dist/nodes/BunCode/runBunCode.js.map +1 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
# n8n-nodes-bun
|
|
2
|
+
|
|
3
|
+
An [n8n](https://n8n.io) community node that lets you execute **TypeScript and JavaScript code using the [Bun](https://bun.sh) runtime** — with full system access, native TypeScript support, and all Bun APIs available.
|
|
4
|
+
|
|
5
|
+
Unlike the built-in Code node (which runs in a sandboxed VM), this node spawns a real Bun process. Your code can use the filesystem, network, Bun-specific APIs, npm packages, and anything else Bun supports.
|
|
6
|
+
|
|
7
|
+
 
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Native TypeScript** — Bun runs `.ts` files directly, no compilation step
|
|
12
|
+
- **Full Bun runtime** — access `Bun.file()`, `Bun.serve()`, `Bun.sleep()`, `fetch()`, and all Bun APIs
|
|
13
|
+
- **No sandbox** — code executes with full system access (read/write files, spawn processes, network calls)
|
|
14
|
+
- **Two execution modes** — "Run Once for All Items" and "Run Once for Each Item", matching the standard Code node
|
|
15
|
+
- **n8n-compatible helpers** — familiar `$input`, `$json`, `items` variables
|
|
16
|
+
- **Error handling** — supports n8n's "Continue on Fail" setting
|
|
17
|
+
- **60-second timeout** — prevents runaway scripts
|
|
18
|
+
|
|
19
|
+
## Prerequisites
|
|
20
|
+
|
|
21
|
+
- **n8n** — self-hosted instance (v1.0+)
|
|
22
|
+
- **Bun** — installed on the same machine running n8n
|
|
23
|
+
|
|
24
|
+
Install Bun:
|
|
25
|
+
```bash
|
|
26
|
+
curl -fsSL https://bun.sh/install | bash
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Verify it's available:
|
|
30
|
+
```bash
|
|
31
|
+
bun --version
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
### In n8n (Community Nodes)
|
|
37
|
+
|
|
38
|
+
1. Go to **Settings > Community Nodes**
|
|
39
|
+
2. Enter `n8n-nodes-bun`
|
|
40
|
+
3. Click **Install**
|
|
41
|
+
4. Restart n8n
|
|
42
|
+
|
|
43
|
+
### Manual / Docker
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# From your n8n installation directory
|
|
47
|
+
npm install n8n-nodes-bun
|
|
48
|
+
|
|
49
|
+
# Restart n8n
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
For Docker, add to your Dockerfile:
|
|
53
|
+
```dockerfile
|
|
54
|
+
RUN npm install n8n-nodes-bun
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
> Make sure Bun is also installed in your Docker image. See [Bun Docker docs](https://bun.sh/guides/ecosystem/docker).
|
|
58
|
+
|
|
59
|
+
### From source
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone https://github.com/borgius/n8n-nodes-bun.git
|
|
63
|
+
cd n8n-nodes-bun
|
|
64
|
+
npm install
|
|
65
|
+
npm run build
|
|
66
|
+
|
|
67
|
+
# Link into your n8n installation
|
|
68
|
+
npm link
|
|
69
|
+
cd /path/to/n8n
|
|
70
|
+
npm link n8n-nodes-bun
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Usage
|
|
74
|
+
|
|
75
|
+
After installation, search for **"Bun Code"** in the n8n node panel.
|
|
76
|
+
|
|
77
|
+
### Execution Modes
|
|
78
|
+
|
|
79
|
+
#### Run Once for All Items
|
|
80
|
+
|
|
81
|
+
Your code runs a single time and receives all input items at once. Return an array of items.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// Access all input items
|
|
85
|
+
const items = $input.all();
|
|
86
|
+
|
|
87
|
+
// Transform each item
|
|
88
|
+
return items.map(item => ({
|
|
89
|
+
json: {
|
|
90
|
+
...item.json,
|
|
91
|
+
processed: true,
|
|
92
|
+
timestamp: Date.now(),
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Run Once for Each Item
|
|
98
|
+
|
|
99
|
+
Your code runs separately for each input item. Return a single item.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
// Access the current item
|
|
103
|
+
const item = $input.item;
|
|
104
|
+
|
|
105
|
+
// Transform it
|
|
106
|
+
return {
|
|
107
|
+
json: {
|
|
108
|
+
...item.json,
|
|
109
|
+
uppercased: item.json.name.toUpperCase(),
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Available Variables
|
|
115
|
+
|
|
116
|
+
| Variable | Mode | Description |
|
|
117
|
+
|----------|------|-------------|
|
|
118
|
+
| `$input.all()` | Both | Returns all input items as an array |
|
|
119
|
+
| `$input.first()` | Both | Returns the first input item |
|
|
120
|
+
| `$input.last()` | Both | Returns the last input item |
|
|
121
|
+
| `$input.item` | Each Item | The current item being processed |
|
|
122
|
+
| `items` | All Items | Alias for `$input.all()` — all input items |
|
|
123
|
+
| `item` | Each Item | Alias for `$input.item` — current item |
|
|
124
|
+
| `$json` | Both | Shortcut to the `.json` property of the first/current item |
|
|
125
|
+
|
|
126
|
+
### Item Format
|
|
127
|
+
|
|
128
|
+
Each item follows the n8n data structure:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
{
|
|
132
|
+
json: {
|
|
133
|
+
// your data here
|
|
134
|
+
key: "value",
|
|
135
|
+
count: 42,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
When returning data, you can return either:
|
|
141
|
+
- **Proper n8n items** — objects with a `json` property: `{ json: { ... } }`
|
|
142
|
+
- **Plain objects** — automatically wrapped in `{ json: ... }` for you
|
|
143
|
+
- **Primitives** — wrapped as `{ json: { data: value } }`
|
|
144
|
+
|
|
145
|
+
## Examples
|
|
146
|
+
|
|
147
|
+
### Use TypeScript features
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
interface User {
|
|
151
|
+
name: string;
|
|
152
|
+
email: string;
|
|
153
|
+
age: number;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const users: User[] = $input.all().map(item => item.json as User);
|
|
157
|
+
|
|
158
|
+
const adults = users.filter(u => u.age >= 18);
|
|
159
|
+
|
|
160
|
+
return adults.map(u => ({
|
|
161
|
+
json: { name: u.name, email: u.email, isAdult: true }
|
|
162
|
+
}));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Read a file from disk
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
import { readFileSync } from 'fs';
|
|
169
|
+
|
|
170
|
+
const config = JSON.parse(readFileSync('/etc/myapp/config.json', 'utf-8'));
|
|
171
|
+
|
|
172
|
+
return [{ json: config }];
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Use Bun APIs
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// Read a file with Bun's fast API
|
|
179
|
+
const file = Bun.file('/tmp/data.csv');
|
|
180
|
+
const text = await file.text();
|
|
181
|
+
|
|
182
|
+
const rows = text.split('\n').map(line => {
|
|
183
|
+
const [name, value] = line.split(',');
|
|
184
|
+
return { json: { name, value } };
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
return rows;
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Make HTTP requests
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
const response = await fetch('https://api.example.com/data', {
|
|
194
|
+
headers: { 'Authorization': 'Bearer my-token' },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
const data = await response.json();
|
|
198
|
+
|
|
199
|
+
return Array.isArray(data)
|
|
200
|
+
? data.map(item => ({ json: item }))
|
|
201
|
+
: [{ json: data }];
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Hash passwords with Bun
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
const items = $input.all();
|
|
208
|
+
|
|
209
|
+
return items.map(item => ({
|
|
210
|
+
json: {
|
|
211
|
+
...item.json,
|
|
212
|
+
passwordHash: Bun.hash(item.json.password as string).toString(),
|
|
213
|
+
}
|
|
214
|
+
}));
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Run a shell command
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
const proc = Bun.spawn(['ls', '-la', '/tmp']);
|
|
221
|
+
const output = await new Response(proc.stdout).text();
|
|
222
|
+
|
|
223
|
+
return [{
|
|
224
|
+
json: {
|
|
225
|
+
files: output.split('\n').filter(Boolean),
|
|
226
|
+
}
|
|
227
|
+
}];
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Process items with async operations
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
const items = $input.all();
|
|
234
|
+
|
|
235
|
+
const results = await Promise.all(
|
|
236
|
+
items.map(async (item) => {
|
|
237
|
+
const resp = await fetch(`https://api.example.com/enrich/${item.json.id}`);
|
|
238
|
+
const extra = await resp.json();
|
|
239
|
+
return {
|
|
240
|
+
json: { ...item.json, ...extra },
|
|
241
|
+
};
|
|
242
|
+
})
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
return results;
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## How It Works
|
|
249
|
+
|
|
250
|
+
1. Your code is wrapped in a template that provides the `$input`, `$json`, `items` helpers
|
|
251
|
+
2. The template + your code are written to a temporary `.ts` file
|
|
252
|
+
3. Input items are serialized to a temporary JSON file
|
|
253
|
+
4. `bun run script.ts` is spawned as a child process
|
|
254
|
+
5. Your code reads input, executes, and writes output to another temp JSON file
|
|
255
|
+
6. The node reads the output and passes it downstream in n8n
|
|
256
|
+
7. All temp files are cleaned up
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
n8n node Bun child process
|
|
260
|
+
┌─────────────┐ ┌─────────────────┐
|
|
261
|
+
│ Serialize │──input.json───> │ Read input │
|
|
262
|
+
│ input items │ │ Provide helpers │
|
|
263
|
+
│ │ │ Execute user code│
|
|
264
|
+
│ Parse output │<─output.json── │ Write result │
|
|
265
|
+
│ items │ │ │
|
|
266
|
+
└─────────────┘ └─────────────────┘
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Security Considerations
|
|
270
|
+
|
|
271
|
+
This node executes code **without any sandbox**. The Bun process has full access to:
|
|
272
|
+
|
|
273
|
+
- The filesystem (read/write any file the n8n process user can access)
|
|
274
|
+
- The network (make any outbound connections)
|
|
275
|
+
- Environment variables
|
|
276
|
+
- Child process spawning
|
|
277
|
+
- All Bun and Node.js APIs
|
|
278
|
+
|
|
279
|
+
**Only install this node on n8n instances where you trust all workflow editors.** It is not suitable for multi-tenant environments where untrusted users can create workflows.
|
|
280
|
+
|
|
281
|
+
## Limitations
|
|
282
|
+
|
|
283
|
+
- **Binary data** — n8n binary attachments are not passed through to the Bun process. Work with `json` data or read files directly.
|
|
284
|
+
- **n8n context** — advanced n8n helpers like `$getWorkflowStaticData`, `helpers.httpRequestWithAuthentication`, and credential access are not available inside the Bun process. Use `fetch()` or Bun APIs directly.
|
|
285
|
+
- **Console output** — `console.log()` output from your code goes to the Bun process stderr/stdout and is not displayed in the n8n UI.
|
|
286
|
+
- **Execution timeout** — scripts are killed after 60 seconds.
|
|
287
|
+
- **Requires Bun installed** — the `bun` binary must be in the system PATH on the machine running n8n.
|
|
288
|
+
|
|
289
|
+
## Development
|
|
290
|
+
|
|
291
|
+
```bash
|
|
292
|
+
git clone https://github.com/borgius/n8n-nodes-bun.git
|
|
293
|
+
cd n8n-nodes-bun
|
|
294
|
+
npm install
|
|
295
|
+
npm run build # compile TypeScript + copy assets
|
|
296
|
+
npm run dev # watch mode (tsc --watch)
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Project structure
|
|
300
|
+
|
|
301
|
+
```
|
|
302
|
+
n8n-nodes-bun/
|
|
303
|
+
├── package.json # n8n community node registration
|
|
304
|
+
├── tsconfig.json
|
|
305
|
+
├── nodes/
|
|
306
|
+
│ └── BunCode/
|
|
307
|
+
│ ├── BunCode.node.ts # Node class (UI config + execute)
|
|
308
|
+
│ ├── runBunCode.ts # Bun execution engine
|
|
309
|
+
│ └── bunCode.svg # Node icon
|
|
310
|
+
└── dist/ # Build output (published to npm)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type IExecuteFunctions, type INodeExecutionData, type INodeType, type INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class BunCode implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BunCode = void 0;
|
|
4
|
+
const n8n_workflow_1 = require("n8n-workflow");
|
|
5
|
+
const runBunCode_1 = require("./runBunCode");
|
|
6
|
+
class BunCode {
|
|
7
|
+
description = {
|
|
8
|
+
displayName: 'Bun Code',
|
|
9
|
+
name: 'bunCode',
|
|
10
|
+
icon: 'file:bunCode.svg',
|
|
11
|
+
group: ['transform'],
|
|
12
|
+
version: 1,
|
|
13
|
+
description: 'Run TypeScript/JavaScript code using Bun runtime',
|
|
14
|
+
defaults: {
|
|
15
|
+
name: 'Bun Code',
|
|
16
|
+
},
|
|
17
|
+
inputs: [n8n_workflow_1.NodeConnectionTypes.Main],
|
|
18
|
+
outputs: [n8n_workflow_1.NodeConnectionTypes.Main],
|
|
19
|
+
parameterPane: 'wide',
|
|
20
|
+
properties: [
|
|
21
|
+
{
|
|
22
|
+
displayName: 'Mode',
|
|
23
|
+
name: 'mode',
|
|
24
|
+
type: 'options',
|
|
25
|
+
noDataExpression: true,
|
|
26
|
+
options: [
|
|
27
|
+
{
|
|
28
|
+
name: 'Run Once for All Items',
|
|
29
|
+
value: 'runOnceForAllItems',
|
|
30
|
+
description: 'Run this code only once, no matter how many input items there are',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'Run Once for Each Item',
|
|
34
|
+
value: 'runOnceForEachItem',
|
|
35
|
+
description: 'Run this code as many times as there are input items',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
default: 'runOnceForAllItems',
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
displayName: 'TypeScript/JavaScript Code',
|
|
42
|
+
name: 'code',
|
|
43
|
+
type: 'string',
|
|
44
|
+
typeOptions: {
|
|
45
|
+
editor: 'codeNodeEditor',
|
|
46
|
+
editorLanguage: 'javaScript',
|
|
47
|
+
},
|
|
48
|
+
default: '// Access input items with $input.all()\nconst items = $input.all();\n\nreturn items;',
|
|
49
|
+
noDataExpression: true,
|
|
50
|
+
description: 'TypeScript or JavaScript code to execute with Bun. Full Bun API available.',
|
|
51
|
+
displayOptions: {
|
|
52
|
+
show: {
|
|
53
|
+
mode: ['runOnceForAllItems'],
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
displayName: 'TypeScript/JavaScript Code',
|
|
59
|
+
name: 'code',
|
|
60
|
+
type: 'string',
|
|
61
|
+
typeOptions: {
|
|
62
|
+
editor: 'codeNodeEditor',
|
|
63
|
+
editorLanguage: 'javaScript',
|
|
64
|
+
},
|
|
65
|
+
default: '// Access current item with $input.item\nconst item = $input.item;\n\nreturn item;',
|
|
66
|
+
noDataExpression: true,
|
|
67
|
+
description: 'TypeScript or JavaScript code to execute with Bun. Full Bun API available.',
|
|
68
|
+
displayOptions: {
|
|
69
|
+
show: {
|
|
70
|
+
mode: ['runOnceForEachItem'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
displayName: 'Bun natively supports TypeScript, top-level await, and fast built-in APIs. No sandbox — code runs with full system access. <a href="https://bun.sh/docs" target="_blank">Bun docs</a>',
|
|
76
|
+
name: 'notice',
|
|
77
|
+
type: 'notice',
|
|
78
|
+
default: '',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
};
|
|
82
|
+
async execute() {
|
|
83
|
+
const mode = this.getNodeParameter('mode', 0);
|
|
84
|
+
const code = this.getNodeParameter('code', 0);
|
|
85
|
+
const inputItems = this.getInputData();
|
|
86
|
+
try {
|
|
87
|
+
const result = await (0, runBunCode_1.runBunCode)(code, inputItems, mode);
|
|
88
|
+
return [result];
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (this.continueOnFail()) {
|
|
92
|
+
return [[{ json: { error: error.message } }]];
|
|
93
|
+
}
|
|
94
|
+
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.BunCode = BunCode;
|
|
99
|
+
//# sourceMappingURL=BunCode.node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BunCode.node.js","sourceRoot":"","sources":["../../../nodes/BunCode/BunCode.node.ts"],"names":[],"mappings":";;;AAAA,+CAOsB;AAEtB,6CAA0C;AAE1C,MAAa,OAAO;IACnB,WAAW,GAAyB;QACnC,WAAW,EAAE,UAAU;QACvB,IAAI,EAAE,SAAS;QACf,IAAI,EAAE,kBAAkB;QACxB,KAAK,EAAE,CAAC,WAAW,CAAC;QACpB,OAAO,EAAE,CAAC;QACV,WAAW,EAAE,kDAAkD;QAC/D,QAAQ,EAAE;YACT,IAAI,EAAE,UAAU;SAChB;QACD,MAAM,EAAE,CAAC,kCAAmB,CAAC,IAAI,CAAC;QAClC,OAAO,EAAE,CAAC,kCAAmB,CAAC,IAAI,CAAC;QACnC,aAAa,EAAE,MAAM;QACrB,UAAU,EAAE;YACX;gBACC,WAAW,EAAE,MAAM;gBACnB,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,SAAS;gBACf,gBAAgB,EAAE,IAAI;gBACtB,OAAO,EAAE;oBACR;wBACC,IAAI,EAAE,wBAAwB;wBAC9B,KAAK,EAAE,oBAAoB;wBAC3B,WAAW,EACV,mEAAmE;qBACpE;oBACD;wBACC,IAAI,EAAE,wBAAwB;wBAC9B,KAAK,EAAE,oBAAoB;wBAC3B,WAAW,EACV,sDAAsD;qBACvD;iBACD;gBACD,OAAO,EAAE,oBAAoB;aAC7B;YACD;gBACC,WAAW,EAAE,4BAA4B;gBACzC,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE;oBACZ,MAAM,EAAE,gBAAgB;oBACxB,cAAc,EAAE,YAAY;iBAC5B;gBACD,OAAO,EACN,uFAAuF;gBACxF,gBAAgB,EAAE,IAAI;gBACtB,WAAW,EACV,4EAA4E;gBAC7E,cAAc,EAAE;oBACf,IAAI,EAAE;wBACL,IAAI,EAAE,CAAC,oBAAoB,CAAC;qBAC5B;iBACD;aACD;YACD;gBACC,WAAW,EAAE,4BAA4B;gBACzC,IAAI,EAAE,MAAM;gBACZ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE;oBACZ,MAAM,EAAE,gBAAgB;oBACxB,cAAc,EAAE,YAAY;iBAC5B;gBACD,OAAO,EACN,oFAAoF;gBACrF,gBAAgB,EAAE,IAAI;gBACtB,WAAW,EACV,4EAA4E;gBAC7E,cAAc,EAAE;oBACf,IAAI,EAAE;wBACL,IAAI,EAAE,CAAC,oBAAoB,CAAC;qBAC5B;iBACD;aACD;YACD;gBACC,WAAW,EACV,uLAAuL;gBACxL,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,EAAE;aACX;SACD;KACD,CAAC;IAEF,KAAK,CAAC,OAAO;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAW,CAAC;QACxD,MAAM,IAAI,GAAG,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE,CAAC,CAAW,CAAC;QACxD,MAAM,UAAU,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QAEvC,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,IAAA,uBAAU,EAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,CAAC;YACxD,OAAO,CAAC,MAAM,CAAC,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC;gBAC3B,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,KAAK,EAAG,KAAe,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;YAC1D,CAAC;YACD,MAAM,IAAI,iCAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,KAAc,CAAC,CAAC;QAC9D,CAAC;IACF,CAAC;CACD;AAnGD,0BAmGC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
|
2
|
+
<rect width="80" height="80" rx="16" fill="#fbf0df"/>
|
|
3
|
+
<g transform="translate(40,44)">
|
|
4
|
+
<!-- bun body -->
|
|
5
|
+
<ellipse cx="0" cy="2" rx="22" ry="18" fill="#fbbd6a"/>
|
|
6
|
+
<!-- bun top lines -->
|
|
7
|
+
<path d="M-8,-14 Q-6,-22 -2,-14" stroke="#c48a3f" stroke-width="2" fill="none" stroke-linecap="round"/>
|
|
8
|
+
<path d="M0,-16 Q2,-24 4,-16" stroke="#c48a3f" stroke-width="2" fill="none" stroke-linecap="round"/>
|
|
9
|
+
<path d="M6,-14 Q8,-22 12,-14" stroke="#c48a3f" stroke-width="2" fill="none" stroke-linecap="round"/>
|
|
10
|
+
<!-- face -->
|
|
11
|
+
<circle cx="-8" cy="2" r="2.5" fill="#3d2b1f"/>
|
|
12
|
+
<circle cx="8" cy="2" r="2.5" fill="#3d2b1f"/>
|
|
13
|
+
<!-- rosy cheeks -->
|
|
14
|
+
<ellipse cx="-14" cy="6" rx="3" ry="2" fill="#f4a0a0" opacity="0.5"/>
|
|
15
|
+
<ellipse cx="14" cy="6" rx="3" ry="2" fill="#f4a0a0" opacity="0.5"/>
|
|
16
|
+
<!-- smile -->
|
|
17
|
+
<path d="M-4,7 Q0,12 4,7" stroke="#3d2b1f" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
|
18
|
+
</g>
|
|
19
|
+
<!-- TS badge -->
|
|
20
|
+
<rect x="52" y="4" width="24" height="16" rx="4" fill="#3178c6"/>
|
|
21
|
+
<text x="64" y="15.5" text-anchor="middle" font-family="Arial,sans-serif" font-weight="bold" font-size="10" fill="white">TS</text>
|
|
22
|
+
</svg>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runBunCode = runBunCode;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const promises_1 = require("fs/promises");
|
|
6
|
+
const path_1 = require("path");
|
|
7
|
+
const os_1 = require("os");
|
|
8
|
+
function buildAllItemsTemplate(userCode, inputPath, outputPath) {
|
|
9
|
+
return `
|
|
10
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
11
|
+
|
|
12
|
+
const __inputData: any[] = JSON.parse(readFileSync(${JSON.stringify(inputPath)}, 'utf-8'));
|
|
13
|
+
|
|
14
|
+
const $input = {
|
|
15
|
+
all: () => __inputData,
|
|
16
|
+
first: () => __inputData[0] ?? null,
|
|
17
|
+
last: () => __inputData[__inputData.length - 1] ?? null,
|
|
18
|
+
item: __inputData[0] ?? null,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const items = __inputData;
|
|
22
|
+
const $json = __inputData[0]?.json ?? {};
|
|
23
|
+
|
|
24
|
+
async function __userCode() {
|
|
25
|
+
${userCode}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const __result = await __userCode();
|
|
29
|
+
|
|
30
|
+
let __output: any[];
|
|
31
|
+
if (Array.isArray(__result)) {
|
|
32
|
+
__output = __result.map((item: any) => {
|
|
33
|
+
if (item && typeof item === 'object' && 'json' in item) return item;
|
|
34
|
+
return { json: typeof item === 'object' && item !== null ? item : { data: item } };
|
|
35
|
+
});
|
|
36
|
+
} else if (__result && typeof __result === 'object' && 'json' in __result) {
|
|
37
|
+
__output = [__result];
|
|
38
|
+
} else if (__result !== null && __result !== undefined) {
|
|
39
|
+
__output = [{ json: typeof __result === 'object' ? __result : { data: __result } }];
|
|
40
|
+
} else {
|
|
41
|
+
__output = [];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
writeFileSync(${JSON.stringify(outputPath)}, JSON.stringify(__output));
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
function buildEachItemTemplate(userCode, inputPath, outputPath) {
|
|
48
|
+
return `
|
|
49
|
+
import { readFileSync, writeFileSync } from 'fs';
|
|
50
|
+
|
|
51
|
+
const __inputData: any[] = JSON.parse(readFileSync(${JSON.stringify(inputPath)}, 'utf-8'));
|
|
52
|
+
const __results: any[] = [];
|
|
53
|
+
|
|
54
|
+
for (let __idx = 0; __idx < __inputData.length; __idx++) {
|
|
55
|
+
const $input = {
|
|
56
|
+
item: __inputData[__idx],
|
|
57
|
+
all: () => __inputData,
|
|
58
|
+
first: () => __inputData[0] ?? null,
|
|
59
|
+
last: () => __inputData[__inputData.length - 1] ?? null,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const item = __inputData[__idx];
|
|
63
|
+
const $json = __inputData[__idx].json ?? {};
|
|
64
|
+
|
|
65
|
+
const __runUserCode = async () => {
|
|
66
|
+
${userCode}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const __itemResult = await __runUserCode();
|
|
70
|
+
|
|
71
|
+
if (__itemResult !== null && __itemResult !== undefined) {
|
|
72
|
+
let __normalized: any;
|
|
73
|
+
if (typeof __itemResult === 'object' && 'json' in __itemResult) {
|
|
74
|
+
__normalized = __itemResult;
|
|
75
|
+
} else if (typeof __itemResult === 'object' && __itemResult !== null) {
|
|
76
|
+
__normalized = { json: __itemResult };
|
|
77
|
+
} else {
|
|
78
|
+
__normalized = { json: { data: __itemResult } };
|
|
79
|
+
}
|
|
80
|
+
__normalized.pairedItem = { item: __idx };
|
|
81
|
+
__results.push(__normalized);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
writeFileSync(${JSON.stringify(outputPath)}, JSON.stringify(__results));
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
function executeBun(scriptPath) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
const proc = (0, child_process_1.spawn)('bun', ['run', scriptPath], {
|
|
91
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
92
|
+
timeout: 60_000,
|
|
93
|
+
});
|
|
94
|
+
let stdout = '';
|
|
95
|
+
let stderr = '';
|
|
96
|
+
proc.stdout.on('data', (data) => {
|
|
97
|
+
stdout += data.toString();
|
|
98
|
+
});
|
|
99
|
+
proc.stderr.on('data', (data) => {
|
|
100
|
+
stderr += data.toString();
|
|
101
|
+
});
|
|
102
|
+
proc.on('error', (err) => {
|
|
103
|
+
if (err.code === 'ENOENT') {
|
|
104
|
+
reject(new Error('Bun runtime not found. Please install Bun: https://bun.sh'));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
reject(err);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
proc.on('close', (exitCode) => {
|
|
111
|
+
resolve({ stdout, stderr, exitCode: exitCode ?? 1 });
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
async function runBunCode(userCode, inputItems, mode) {
|
|
116
|
+
const tempDir = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), 'n8n-bun-'));
|
|
117
|
+
const inputPath = (0, path_1.join)(tempDir, 'input.json');
|
|
118
|
+
const outputPath = (0, path_1.join)(tempDir, 'output.json');
|
|
119
|
+
const scriptPath = (0, path_1.join)(tempDir, 'script.ts');
|
|
120
|
+
try {
|
|
121
|
+
await (0, promises_1.writeFile)(inputPath, JSON.stringify(inputItems));
|
|
122
|
+
const template = mode === 'runOnceForAllItems'
|
|
123
|
+
? buildAllItemsTemplate(userCode, inputPath, outputPath)
|
|
124
|
+
: buildEachItemTemplate(userCode, inputPath, outputPath);
|
|
125
|
+
await (0, promises_1.writeFile)(scriptPath, template);
|
|
126
|
+
const { stderr, exitCode } = await executeBun(scriptPath);
|
|
127
|
+
if (exitCode !== 0) {
|
|
128
|
+
throw new Error(`Bun execution failed (exit code ${exitCode}):\n${stderr}`);
|
|
129
|
+
}
|
|
130
|
+
let outputRaw;
|
|
131
|
+
try {
|
|
132
|
+
outputRaw = await (0, promises_1.readFile)(outputPath, 'utf-8');
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
throw new Error(`Code did not produce output. Make sure your code returns data.\n${stderr}`);
|
|
136
|
+
}
|
|
137
|
+
return JSON.parse(outputRaw);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await (0, promises_1.rm)(tempDir, { recursive: true, force: true }).catch(() => { });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=runBunCode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runBunCode.js","sourceRoot":"","sources":["../../../nodes/BunCode/runBunCode.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "n8n-nodes-bun",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "n8n community node that executes TypeScript/JavaScript code using Bun runtime",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"n8n",
|
|
8
|
+
"bun",
|
|
9
|
+
"typescript",
|
|
10
|
+
"code"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"main": "dist/nodes/BunCode/BunCode.node.js",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc && copyfiles \"nodes/**/*.svg\" dist/",
|
|
19
|
+
"dev": "tsc --watch",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
21
|
+
"release": "npm version patch && npm run build && npm publish",
|
|
22
|
+
"release:minor": "npm version minor && npm run build && npm publish",
|
|
23
|
+
"release:major": "npm version major && npm run build && npm publish"
|
|
24
|
+
},
|
|
25
|
+
"n8n": {
|
|
26
|
+
"n8nNodesApiVersion": 1,
|
|
27
|
+
"nodes": [
|
|
28
|
+
"dist/nodes/BunCode/BunCode.node.js"
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"n8n-workflow": "*"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^25.3.0",
|
|
36
|
+
"copyfiles": "^2.4.1",
|
|
37
|
+
"n8n-workflow": "*",
|
|
38
|
+
"typescript": "~5.7.0"
|
|
39
|
+
}
|
|
40
|
+
}
|