@19americano/n8n-nodes-google-news-decoder 1.0.0
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/LICENSE +21 -0
- package/dist/nodes/GoogleNewsDecode/GoogleNewsDecode.node.d.ts +5 -0
- package/dist/nodes/GoogleNewsDecode/GoogleNewsDecode.node.js +117 -0
- package/dist/nodes/GoogleNewsDecode/GoogleNewsDecode.node.js.map +1 -0
- package/dist/nodes/GoogleNewsDecode/GoogleNewsDecode.node.json +14 -0
- package/dist/nodes/GoogleNewsDecode/googleNewsDecode.svg +7 -0
- package/dist/nodes/GoogleNewsDecode/utils/decoder.d.ts +37 -0
- package/dist/nodes/GoogleNewsDecode/utils/decoder.js +162 -0
- package/dist/nodes/GoogleNewsDecode/utils/decoder.js.map +1 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { IExecuteFunctions, INodeExecutionData, INodeType, INodeTypeDescription } from 'n8n-workflow';
|
|
2
|
+
export declare class GoogleNewsDecode implements INodeType {
|
|
3
|
+
description: INodeTypeDescription;
|
|
4
|
+
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
|
|
5
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GoogleNewsDecode = void 0;
|
|
4
|
+
const decoder_1 = require("./utils/decoder");
|
|
5
|
+
class GoogleNewsDecode {
|
|
6
|
+
description = {
|
|
7
|
+
displayName: 'Google News Decode',
|
|
8
|
+
name: 'googleNewsDecode',
|
|
9
|
+
icon: 'file:googleNewsDecode.svg',
|
|
10
|
+
group: ['transform'],
|
|
11
|
+
version: 1,
|
|
12
|
+
subtitle: 'Decode Google News URLs',
|
|
13
|
+
description: 'Decode Google News RSS article URLs to their original source URLs',
|
|
14
|
+
defaults: {
|
|
15
|
+
name: 'Google News Decode',
|
|
16
|
+
},
|
|
17
|
+
inputs: ['main'],
|
|
18
|
+
outputs: ['main'],
|
|
19
|
+
properties: [
|
|
20
|
+
{
|
|
21
|
+
displayName: 'URL Field',
|
|
22
|
+
name: 'urlField',
|
|
23
|
+
type: 'string',
|
|
24
|
+
default: 'link',
|
|
25
|
+
required: true,
|
|
26
|
+
description: 'The name of the input field containing the Google News URL',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
displayName: 'Output Field',
|
|
30
|
+
name: 'outputField',
|
|
31
|
+
type: 'string',
|
|
32
|
+
default: 'decodedUrl',
|
|
33
|
+
description: 'The name of the output field to store the decoded URL',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
displayName: 'Request Delay (ms)',
|
|
37
|
+
name: 'delay',
|
|
38
|
+
type: 'number',
|
|
39
|
+
default: 0,
|
|
40
|
+
description: 'Delay between decoding requests in milliseconds (helps avoid rate limiting when processing many URLs)',
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
async execute() {
|
|
45
|
+
const items = this.getInputData();
|
|
46
|
+
const returnData = [];
|
|
47
|
+
for (let i = 0; i < items.length; i++) {
|
|
48
|
+
try {
|
|
49
|
+
const urlField = this.getNodeParameter('urlField', i);
|
|
50
|
+
const outputField = this.getNodeParameter('outputField', i);
|
|
51
|
+
const delay = this.getNodeParameter('delay', i, 0);
|
|
52
|
+
const googleNewsUrl = items[i].json[urlField];
|
|
53
|
+
if (!googleNewsUrl) {
|
|
54
|
+
throw new Error(`Field "${urlField}" is empty or missing in item ${i}`);
|
|
55
|
+
}
|
|
56
|
+
const result = await (0, decoder_1.decodeGoogleNewsUrl)(googleNewsUrl, async (options) => {
|
|
57
|
+
const response = await this.helpers.httpRequest({
|
|
58
|
+
method: options.method,
|
|
59
|
+
url: options.url,
|
|
60
|
+
headers: options.headers,
|
|
61
|
+
body: options.body,
|
|
62
|
+
returnFullResponse: false,
|
|
63
|
+
});
|
|
64
|
+
return typeof response === 'string'
|
|
65
|
+
? response
|
|
66
|
+
: JSON.stringify(response);
|
|
67
|
+
});
|
|
68
|
+
if (result.status && result.decodedUrl) {
|
|
69
|
+
returnData.push({
|
|
70
|
+
json: {
|
|
71
|
+
...items[i].json,
|
|
72
|
+
[outputField]: result.decodedUrl,
|
|
73
|
+
_decodeMethod: result.method,
|
|
74
|
+
},
|
|
75
|
+
pairedItem: { item: i },
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
if (this.continueOnFail()) {
|
|
80
|
+
returnData.push({
|
|
81
|
+
json: {
|
|
82
|
+
...items[i].json,
|
|
83
|
+
[outputField]: null,
|
|
84
|
+
_decodeError: result.message,
|
|
85
|
+
},
|
|
86
|
+
pairedItem: { item: i },
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
throw new Error(result.message || 'Failed to decode URL');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Apply delay between requests if configured
|
|
94
|
+
if (delay > 0 && i < items.length - 1) {
|
|
95
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
if (this.continueOnFail()) {
|
|
100
|
+
returnData.push({
|
|
101
|
+
json: {
|
|
102
|
+
...items[i].json,
|
|
103
|
+
_decodeError: error.message,
|
|
104
|
+
},
|
|
105
|
+
pairedItem: { item: i },
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return [returnData];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
exports.GoogleNewsDecode = GoogleNewsDecode;
|
|
117
|
+
//# sourceMappingURL=GoogleNewsDecode.node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GoogleNewsDecode.node.js","sourceRoot":"","sources":["../../../nodes/GoogleNewsDecode/GoogleNewsDecode.node.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"node": "n8n-nodes-google-news-decoder.googleNewsDecode",
|
|
3
|
+
"nodeVersion": "1.0",
|
|
4
|
+
"codexVersion": "1.0",
|
|
5
|
+
"categories": ["Data & Storage"],
|
|
6
|
+
"resources": {
|
|
7
|
+
"primaryDocumentation": [
|
|
8
|
+
{
|
|
9
|
+
"url": "https://github.com/n8n-nodes-google-news-decoder"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"alias": ["google news", "rss", "decode", "url", "news", "article"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48" height="48">
|
|
2
|
+
<path fill="#4285F4" d="M39 12H9c-1.7 0-3 1.3-3 3v18c0 1.7 1.3 3 3 3h30c1.7 0 3-1.3 3-3V15c0-1.7-1.3-3-3-3z"/>
|
|
3
|
+
<path fill="#fff" d="M14 20h20v2H14zm0 4h16v2H14zm0 4h12v2H14z"/>
|
|
4
|
+
<circle fill="#FBBC05" cx="24" cy="14" r="4"/>
|
|
5
|
+
<path fill="#EA4335" d="M24 10c-2.2 0-4 1.8-4 4h2c0-1.1.9-2 2-2s2 .9 2 2h2c0-2.2-1.8-4-4-4z"/>
|
|
6
|
+
<path fill="#34A853" d="M20 14c0 2.2 1.8 4 4 4s4-1.8 4-4h-2c0 1.1-.9 2-2 2s-2-.9-2-2h-2z"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface DecodeResult {
|
|
2
|
+
status: boolean;
|
|
3
|
+
decodedUrl?: string;
|
|
4
|
+
method?: 'base64' | 'api';
|
|
5
|
+
message?: string;
|
|
6
|
+
}
|
|
7
|
+
export type HttpRequestFn = (options: {
|
|
8
|
+
method: string;
|
|
9
|
+
url: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: string;
|
|
12
|
+
}) => Promise<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Extract the base64-encoded article ID from a Google News URL.
|
|
15
|
+
*/
|
|
16
|
+
export declare function extractBase64FromUrl(sourceUrl: string): string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Try to decode the URL directly from the base64 payload (old format).
|
|
19
|
+
* Old-format Google News URLs embed the article URL directly in protobuf field 2.
|
|
20
|
+
*/
|
|
21
|
+
export declare function tryDirectDecode(base64Str: string): string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Decode a Google News URL using Google's batchexecute API.
|
|
24
|
+
* This is the primary method that works for both old and new format URLs.
|
|
25
|
+
*
|
|
26
|
+
* Steps:
|
|
27
|
+
* 1. Fetch the article page to get signing parameters (signature + timestamp)
|
|
28
|
+
* 2. POST to Google's batchexecute API with those params to get the real URL
|
|
29
|
+
*/
|
|
30
|
+
export declare function decodeViaApi(base64Str: string, httpRequest: HttpRequestFn): Promise<string | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Decode a Google News URL to its original article URL.
|
|
33
|
+
*
|
|
34
|
+
* Tries direct base64 decoding first (old format), then falls back to
|
|
35
|
+
* Google's batchexecute API (new format).
|
|
36
|
+
*/
|
|
37
|
+
export declare function decodeGoogleNewsUrl(sourceUrl: string, httpRequest: HttpRequestFn): Promise<DecodeResult>;
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.decodeGoogleNewsUrl = exports.decodeViaApi = exports.tryDirectDecode = exports.extractBase64FromUrl = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Extract the base64-encoded article ID from a Google News URL.
|
|
6
|
+
*/
|
|
7
|
+
function extractBase64FromUrl(sourceUrl) {
|
|
8
|
+
try {
|
|
9
|
+
const url = new URL(sourceUrl);
|
|
10
|
+
if (url.hostname !== 'news.google.com')
|
|
11
|
+
return null;
|
|
12
|
+
const pathParts = url.pathname.split('/');
|
|
13
|
+
const articleIndex = pathParts.findIndex((p) => p === 'articles' || p === 'read');
|
|
14
|
+
if (articleIndex === -1 || articleIndex + 1 >= pathParts.length)
|
|
15
|
+
return null;
|
|
16
|
+
return pathParts[articleIndex + 1];
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.extractBase64FromUrl = extractBase64FromUrl;
|
|
23
|
+
/**
|
|
24
|
+
* Try to decode the URL directly from the base64 payload (old format).
|
|
25
|
+
* Old-format Google News URLs embed the article URL directly in protobuf field 2.
|
|
26
|
+
*/
|
|
27
|
+
function tryDirectDecode(base64Str) {
|
|
28
|
+
try {
|
|
29
|
+
const buf = Buffer.from(base64Str, 'base64');
|
|
30
|
+
const str = buf.toString('utf8');
|
|
31
|
+
// Look for an http URL in the decoded bytes
|
|
32
|
+
const httpIdx = str.indexOf('http');
|
|
33
|
+
if (httpIdx === -1)
|
|
34
|
+
return null;
|
|
35
|
+
// Extract the URL - it runs until we hit a non-URL character
|
|
36
|
+
const urlCandidate = str.substring(httpIdx);
|
|
37
|
+
// URL ends at first control char or non-printable
|
|
38
|
+
const match = urlCandidate.match(/^https?:\/\/[^\x00-\x1f\x7f]+/);
|
|
39
|
+
if (!match)
|
|
40
|
+
return null;
|
|
41
|
+
// Validate it's a real URL
|
|
42
|
+
try {
|
|
43
|
+
new URL(match[0]);
|
|
44
|
+
return match[0];
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.tryDirectDecode = tryDirectDecode;
|
|
55
|
+
/**
|
|
56
|
+
* Extract signature and timestamp from Google News article page HTML.
|
|
57
|
+
*/
|
|
58
|
+
function extractDecodingParams(html) {
|
|
59
|
+
// Look for data-n-a-sg and data-n-a-ts attributes
|
|
60
|
+
const sigMatch = html.match(/data-n-a-sg="([^"]+)"/);
|
|
61
|
+
const tsMatch = html.match(/data-n-a-ts="([^"]+)"/);
|
|
62
|
+
if (!sigMatch || !tsMatch)
|
|
63
|
+
return null;
|
|
64
|
+
return {
|
|
65
|
+
signature: sigMatch[1],
|
|
66
|
+
timestamp: tsMatch[1],
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Decode a Google News URL using Google's batchexecute API.
|
|
71
|
+
* This is the primary method that works for both old and new format URLs.
|
|
72
|
+
*
|
|
73
|
+
* Steps:
|
|
74
|
+
* 1. Fetch the article page to get signing parameters (signature + timestamp)
|
|
75
|
+
* 2. POST to Google's batchexecute API with those params to get the real URL
|
|
76
|
+
*/
|
|
77
|
+
async function decodeViaApi(base64Str, httpRequest) {
|
|
78
|
+
// Step 1: Fetch the article page to get data-n-a-sg and data-n-a-ts
|
|
79
|
+
let params = null;
|
|
80
|
+
// Try /articles/ first, then /rss/articles/
|
|
81
|
+
for (const prefix of ['articles', 'rss/articles']) {
|
|
82
|
+
try {
|
|
83
|
+
const html = await httpRequest({
|
|
84
|
+
method: 'GET',
|
|
85
|
+
url: `https://news.google.com/${prefix}/${base64Str}`,
|
|
86
|
+
headers: {
|
|
87
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
params = extractDecodingParams(html);
|
|
91
|
+
if (params)
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!params)
|
|
99
|
+
return null;
|
|
100
|
+
// Step 2: Call batchexecute API
|
|
101
|
+
const payload = [
|
|
102
|
+
'Fbv4je',
|
|
103
|
+
`["garturlreq",[["X","X",["X","X"],null,null,1,1,"US:en",null,1,null,null,null,null,null,0,1],"X","X",1,[1,1,1],1,1,null,0,0,null,0],"${base64Str}",${params.timestamp},"${params.signature}"]`,
|
|
104
|
+
];
|
|
105
|
+
const body = `f.req=${encodeURIComponent(JSON.stringify([[payload]]))}`;
|
|
106
|
+
const response = await httpRequest({
|
|
107
|
+
method: 'POST',
|
|
108
|
+
url: 'https://news.google.com/_/DotsSplashUi/data/batchexecute',
|
|
109
|
+
headers: {
|
|
110
|
+
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
|
|
111
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36',
|
|
112
|
+
},
|
|
113
|
+
body,
|
|
114
|
+
});
|
|
115
|
+
// Parse the response - format is two sections separated by \n\n
|
|
116
|
+
const sections = response.split('\n\n');
|
|
117
|
+
if (sections.length < 2)
|
|
118
|
+
return null;
|
|
119
|
+
const data = JSON.parse(sections[1]);
|
|
120
|
+
const decodedUrl = JSON.parse(data[0][2])[1];
|
|
121
|
+
if (typeof decodedUrl === 'string' && decodedUrl.startsWith('http')) {
|
|
122
|
+
return decodedUrl;
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
exports.decodeViaApi = decodeViaApi;
|
|
127
|
+
/**
|
|
128
|
+
* Decode a Google News URL to its original article URL.
|
|
129
|
+
*
|
|
130
|
+
* Tries direct base64 decoding first (old format), then falls back to
|
|
131
|
+
* Google's batchexecute API (new format).
|
|
132
|
+
*/
|
|
133
|
+
async function decodeGoogleNewsUrl(sourceUrl, httpRequest) {
|
|
134
|
+
const base64Str = extractBase64FromUrl(sourceUrl);
|
|
135
|
+
if (!base64Str) {
|
|
136
|
+
return { status: false, message: 'Invalid Google News URL format.' };
|
|
137
|
+
}
|
|
138
|
+
// Try direct base64 decode first (fast, no network)
|
|
139
|
+
const directUrl = tryDirectDecode(base64Str);
|
|
140
|
+
if (directUrl) {
|
|
141
|
+
return { status: true, decodedUrl: directUrl, method: 'base64' };
|
|
142
|
+
}
|
|
143
|
+
// Fall back to API-based decoding
|
|
144
|
+
try {
|
|
145
|
+
const apiUrl = await decodeViaApi(base64Str, httpRequest);
|
|
146
|
+
if (apiUrl) {
|
|
147
|
+
return { status: true, decodedUrl: apiUrl, method: 'api' };
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
status: false,
|
|
151
|
+
message: 'Failed to decode URL via API. Could not extract parameters from Google News page.',
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
return {
|
|
156
|
+
status: false,
|
|
157
|
+
message: `API decode error: ${error.message}`,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
exports.decodeGoogleNewsUrl = decodeGoogleNewsUrl;
|
|
162
|
+
//# sourceMappingURL=decoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decoder.js","sourceRoot":"","sources":["../../../../nodes/GoogleNewsDecode/utils/decoder.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@19americano/n8n-nodes-google-news-decoder",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "n8n community node to decode Google News RSS article URLs to original source URLs",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"n8n-community-node-package",
|
|
7
|
+
"google-news",
|
|
8
|
+
"url-decoder",
|
|
9
|
+
"rss"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"main": "index.js",
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc && copyfiles nodes/GoogleNewsDecode/googleNewsDecode.svg nodes/GoogleNewsDecode/GoogleNewsDecode.node.json dist/",
|
|
18
|
+
"dev": "tsc --watch",
|
|
19
|
+
"test": "jest",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"n8n": {
|
|
23
|
+
"n8nNodesApiVersion": 1,
|
|
24
|
+
"credentials": [],
|
|
25
|
+
"nodes": [
|
|
26
|
+
"dist/nodes/GoogleNewsDecode/GoogleNewsDecode.node.js"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"n8n-workflow": "*"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@types/node": "^20.0.0",
|
|
34
|
+
"copyfiles": "^2.4.1",
|
|
35
|
+
"jest": "^29.7.0",
|
|
36
|
+
"n8n-workflow": "^1.0.0",
|
|
37
|
+
"ts-jest": "^29.1.0",
|
|
38
|
+
"typescript": "~5.4.0",
|
|
39
|
+
"@types/jest": "^29.5.0"
|
|
40
|
+
}
|
|
41
|
+
}
|