mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-12 16:10:30 +02:00
Compare commits
785 Commits
n8n@2.17.1
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22f2e34fe6 | ||
|
|
d06110ba9d | ||
|
|
0ce820de73 | ||
|
|
f0649e0a3d | ||
|
|
28df864aab | ||
|
|
27d72acae5 | ||
|
|
cd0519f360 | ||
|
|
c158771d5f | ||
|
|
c0be06f9ff | ||
|
|
d5d51731d2 | ||
|
|
fb78047d9a | ||
|
|
b3760c776f | ||
|
|
d2e5db258c | ||
|
|
744bb92c2f | ||
|
|
d06bbe4f32 | ||
|
|
54d62bb4a1 | ||
|
|
a60ef7dbb5 | ||
|
|
111d403aa7 | ||
|
|
5059ce7e3d | ||
|
|
b445221c6a | ||
|
|
dc7dcaf1b1 | ||
|
|
ab8475b4cf | ||
|
|
980f3c8461 | ||
|
|
2b7e313430 | ||
|
|
0bde73c42f | ||
|
|
1e685062c3 | ||
|
|
df6e39bddf | ||
|
|
3297536011 | ||
|
|
95cf41c37c | ||
|
|
74fb4110c4 | ||
|
|
61be42c7bb | ||
|
|
b5bafc861e | ||
|
|
3dd134ab3c | ||
|
|
e98c1e5fe6 | ||
|
|
ae81d1bac0 | ||
|
|
cb019eb253 | ||
|
|
8b0a3ae3d3 | ||
|
|
7fdd98aa72 | ||
|
|
133a5aa0ad | ||
|
|
da41470311 | ||
|
|
94e403300b | ||
|
|
267fe49d51 | ||
|
|
e968723808 | ||
|
|
bb73952fcc | ||
|
|
9072ee3beb | ||
|
|
1749801508 | ||
|
|
a8aa95551e | ||
|
|
c75a45ba15 | ||
|
|
0a761355c4 | ||
|
|
bad43d0c81 | ||
|
|
b168523254 | ||
|
|
3df6611fb3 | ||
|
|
2e046d5b7f | ||
|
|
0494f24967 | ||
|
|
e8827cd6e8 | ||
|
|
b64a84159d | ||
|
|
5bf5f03453 | ||
|
|
3123f2551b | ||
|
|
127544ae5b | ||
|
|
ea98243c2b | ||
|
|
2e21c5fcf8 | ||
|
|
7635131bd3 | ||
|
|
0d571c05e4 | ||
|
|
6f9b99a3cf | ||
|
|
0feec2fea6 | ||
|
|
e3e70d6068 | ||
|
|
410b75c3d0 | ||
|
|
75646c4527 | ||
|
|
d0367a00e8 | ||
|
|
7094b48c94 | ||
|
|
582b6ae9ea | ||
|
|
26beabb445 | ||
|
|
96b018d356 | ||
|
|
d5d290d706 | ||
|
|
40ffbfa5ab | ||
|
|
94d91e13bf | ||
|
|
515ae7ced4 | ||
|
|
52a4bcb23a | ||
|
|
be4ef22533 | ||
|
|
f4e8088cb8 | ||
|
|
174f0f805e | ||
|
|
c94a403682 | ||
|
|
a30772c933 | ||
|
|
86170674b7 | ||
|
|
1a22c76270 | ||
|
|
7c1a77154c | ||
|
|
f63567b1ec | ||
|
|
dd7555d277 | ||
|
|
3bf5d4ac91 | ||
|
|
3a33a448b0 | ||
|
|
60e23e10e0 | ||
|
|
5e88748334 | ||
|
|
fbf89bde11 | ||
|
|
3702ff8eb3 | ||
|
|
d3a3441be2 | ||
|
|
8171cf0b32 | ||
|
|
523fd85e45 | ||
|
|
bda1782de6 | ||
|
|
1e8f89bd5a | ||
|
|
f709e53824 | ||
|
|
f87094cf6e | ||
|
|
cd5b2b3762 | ||
|
|
8a6e779c6d | ||
|
|
277431b88b | ||
|
|
9931c4d055 | ||
|
|
72eca2f398 | ||
|
|
7e6bca1f13 | ||
|
|
e15c091c75 | ||
|
|
ecf96ad30c | ||
|
|
8116e0a485 | ||
|
|
2ece58eee5 | ||
|
|
0cc163b7dc | ||
|
|
ceaebc6cbe | ||
|
|
afe119be14 | ||
|
|
cfec60de6a | ||
|
|
2b2fa0aaa3 | ||
|
|
4b89faa707 | ||
|
|
910822fb09 | ||
|
|
b3a806125b | ||
|
|
149bdebf37 | ||
|
|
33c3598e66 | ||
|
|
7c57843cf6 | ||
|
|
6f4f0a0303 | ||
|
|
e7b353cabc | ||
|
|
478d4998a8 | ||
|
|
5cbd2dd1e9 | ||
|
|
d318bc1e33 | ||
|
|
6b893b45a0 | ||
|
|
75ed71c001 | ||
|
|
73dae68663 | ||
|
|
820128196c | ||
|
|
8e0f37d100 | ||
|
|
75053fec93 | ||
|
|
ffcf63691f | ||
|
|
730c3e12a5 | ||
|
|
e6b37ef06d | ||
|
|
43438f0361 | ||
|
|
9014baea7e | ||
|
|
ca33060e0b | ||
|
|
1a270f2f35 | ||
|
|
ba5b3d13b1 | ||
|
|
9f92005938 | ||
|
|
f7c7acc244 | ||
|
|
f871d44cab | ||
|
|
01300e9b9b | ||
|
|
972d8d4ec7 | ||
|
|
30d9a168bc | ||
|
|
dab3653f80 | ||
|
|
8573197aef | ||
|
|
0edcdcfe85 | ||
|
|
a316742c92 | ||
|
|
ad0a6e9d46 | ||
|
|
db0097c57f | ||
|
|
5c7921f71c | ||
|
|
15105610f6 | ||
|
|
8474f1e6f3 | ||
|
|
5abcae686c | ||
|
|
1cb7c591b3 | ||
|
|
ebafde7f85 | ||
|
|
8f1f42d180 | ||
|
|
2dbf02e63e | ||
|
|
7fdc7788d5 | ||
|
|
acc9643811 | ||
|
|
29a864ca9b | ||
|
|
e71afedfab | ||
|
|
6232de4d47 | ||
|
|
273db4be75 | ||
|
|
8dd6d12918 | ||
|
|
be90f9f873 | ||
|
|
5e3aa1a726 | ||
|
|
55df7cbd06 | ||
|
|
9b3b29b505 | ||
|
|
4e2865206c | ||
|
|
68560fbb9a | ||
|
|
34f2107071 | ||
|
|
ac993e8859 | ||
|
|
4b67c31896 | ||
|
|
9255311491 | ||
|
|
d247f61096 | ||
|
|
3276edce10 | ||
|
|
e929f9fbe7 | ||
|
|
a772016e36 | ||
|
|
eaadf190b8 | ||
|
|
3c97c49d63 | ||
|
|
12b7cc6739 | ||
|
|
1faa3b1f2a | ||
|
|
9d3fb2ba26 | ||
|
|
2164afc5df | ||
|
|
dd812c5010 | ||
|
|
ae57e606b4 | ||
|
|
1e52b14b99 | ||
|
|
31f577a39f | ||
|
|
64079ad98c | ||
|
|
6b1061386e | ||
|
|
bc8d196931 | ||
|
|
d6cc3bedd1 | ||
|
|
60a51229e0 | ||
|
|
04e9b258a8 | ||
|
|
f42be9030e | ||
|
|
de3a98f58f | ||
|
|
8e30d78939 | ||
|
|
81a621e3d8 | ||
|
|
35931319b5 | ||
|
|
49e7b056b4 | ||
|
|
188ee6d704 | ||
|
|
b6cc694ef5 | ||
|
|
ed9471a532 | ||
|
|
bec74aeb4f | ||
|
|
d6bae35e8f | ||
|
|
a3ae1d8556 | ||
|
|
8b54333739 | ||
|
|
d63e1ae84e | ||
|
|
4dce41f795 | ||
|
|
4d5bafc146 | ||
|
|
b6127d8722 | ||
|
|
e99e6afb49 | ||
|
|
ff41613533 | ||
|
|
9afbe13b81 | ||
|
|
08a36d7515 | ||
|
|
f3a21e14a1 | ||
|
|
8aea190659 | ||
|
|
d4e9705749 | ||
|
|
701f9a4627 | ||
|
|
46d52ffc7e | ||
|
|
80c8a6c2fd | ||
|
|
61c8895f63 | ||
|
|
f2764f04c0 | ||
|
|
869dc32c15 | ||
|
|
a33a89a215 | ||
|
|
bbe3e2d148 | ||
|
|
5b01cba8b2 | ||
|
|
2714f00121 | ||
|
|
ee847d1624 | ||
|
|
b6ee2b93ed | ||
|
|
07f6de6ba0 | ||
|
|
57ae85785d | ||
|
|
34b92b1623 | ||
|
|
50e8218ce8 | ||
|
|
c4056b255e | ||
|
|
5af9d0729f | ||
|
|
82354742d3 | ||
|
|
4d9e624b41 | ||
|
|
283071e611 | ||
|
|
e2576ca25b | ||
|
|
4c369e83f2 | ||
|
|
bd7eeb7bc8 | ||
|
|
3a967fc041 | ||
|
|
ed12bcb58e | ||
|
|
bfc7775ab3 | ||
|
|
1ca4dd3fa5 | ||
|
|
e3ff671448 | ||
|
|
0cafc717a2 | ||
|
|
fba873c37e | ||
|
|
c742a85b3b | ||
|
|
67f621519e | ||
|
|
d2e1eb30f1 | ||
|
|
9c4ac76255 | ||
|
|
a7864762ca | ||
|
|
d5af542f25 | ||
|
|
6ace86e0eb | ||
|
|
78aa0e70f2 | ||
|
|
a408257ebe | ||
|
|
ec514da099 | ||
|
|
0697562ac9 | ||
|
|
dc749e0423 | ||
|
|
804f51cf0d | ||
|
|
74c256c1c1 | ||
|
|
b970d259c4 | ||
|
|
9ab58df394 | ||
|
|
4e0f8b5018 | ||
|
|
c6c6f8ff38 | ||
|
|
8aace75535 | ||
|
|
0f7776e972 | ||
|
|
34c49b9c23 | ||
|
|
c724dace38 | ||
|
|
c6cbc49016 | ||
|
|
7c0d3ccb39 | ||
|
|
f401f9101d | ||
|
|
a65e181a22 | ||
|
|
b41f1a06ab | ||
|
|
17b1206790 | ||
|
|
b72bd1987c | ||
|
|
4b9e975ca0 | ||
|
|
ad7cdcc04f | ||
|
|
568e5a24bf | ||
|
|
96fabbafad | ||
|
|
63d59d48c5 | ||
|
|
dad423155f | ||
|
|
dc6bd68de3 | ||
|
|
1d9548c81f | ||
|
|
f69aea3899 | ||
|
|
fdceec21b9 | ||
|
|
4c76aa1467 | ||
|
|
e35042999f | ||
|
|
45effb8959 | ||
|
|
2259f32de8 | ||
|
|
d422d2bafb | ||
|
|
62ddc5c443 | ||
|
|
9fda7332c4 | ||
|
|
f775604c25 | ||
|
|
c28d501ba1 | ||
|
|
418f1f2edb | ||
|
|
dc52bbd532 | ||
|
|
be22095646 | ||
|
|
cf8887f9ea | ||
|
|
baf5bb8e91 | ||
|
|
a2afc47c22 | ||
|
|
595aae498c | ||
|
|
9decb1e2a9 | ||
|
|
b4d898e4ae | ||
|
|
07b53430f9 | ||
|
|
8c0faa27c4 | ||
|
|
34d7a02df7 | ||
|
|
45c18fb09c | ||
|
|
88b3a0b3c6 | ||
|
|
6bca1fa26f | ||
|
|
94bf3db438 | ||
|
|
243f665e60 | ||
|
|
86f47ee6dc | ||
|
|
bdf06fa8dd | ||
|
|
e17b6864be | ||
|
|
56412bcce2 | ||
|
|
9b00ccbfd1 | ||
|
|
ee7260c495 | ||
|
|
221c7f7410 | ||
|
|
ef3c3e0f80 | ||
|
|
6698c42e4e | ||
|
|
bd130a071f | ||
|
|
b97ca36a99 | ||
|
|
90d875ce3e | ||
|
|
a7ef7416b1 | ||
|
|
473d49c9b1 | ||
|
|
12275c86d9 | ||
|
|
c04ea7fae9 | ||
|
|
625ed5e95a | ||
|
|
661f9908bc | ||
|
|
44579d6d3a | ||
|
|
cdfa7fe4da | ||
|
|
f77dfd1a11 | ||
|
|
6175fd6f7b | ||
|
|
896461bee3 | ||
|
|
83250c1710 | ||
|
|
d18f183b21 | ||
|
|
8b7b4f575d | ||
|
|
e7f3e6f771 | ||
|
|
5799481d1c | ||
|
|
656f9c2d7f | ||
|
|
5a56459129 | ||
|
|
b2ac67f154 | ||
|
|
83c400e8d4 | ||
|
|
139b803dae | ||
|
|
4fd68bfc99 | ||
|
|
0dbe6c533e | ||
|
|
74d55b9c68 | ||
|
|
ef56501d47 | ||
|
|
ab16e197a7 | ||
|
|
2a0e2fb47a | ||
|
|
5f93b48e79 | ||
|
|
484cb2efba | ||
|
|
3791db782b | ||
|
|
0e07dedc08 | ||
|
|
334ce39f65 | ||
|
|
40da23f688 | ||
|
|
9a91c83a27 | ||
|
|
594c60b497 | ||
|
|
e075f859f9 | ||
|
|
0a80722dcb | ||
|
|
388cd79908 | ||
|
|
84ac8110f8 | ||
|
|
c4bb5ae8df | ||
|
|
4358f1d51c | ||
|
|
1516ec7c06 | ||
|
|
898ba5ae25 | ||
|
|
d9d1e7c44a | ||
|
|
54d9286d92 | ||
|
|
794334cd79 | ||
|
|
758f89c9ef | ||
|
|
5361257a80 | ||
|
|
39a9ac2a14 | ||
|
|
16d1461858 | ||
|
|
bc315d087f | ||
|
|
4ea1153dfb | ||
|
|
ec2e2f11dc | ||
|
|
9576ab907c | ||
|
|
05e10e2680 | ||
|
|
1c8f4ec67b | ||
|
|
308d0b42b3 | ||
|
|
ecd0ba8eba | ||
|
|
9cb160585c | ||
|
|
a273a9d3f4 | ||
|
|
d92ec168aa | ||
|
|
47a6658b2d | ||
|
|
b8b75719ba | ||
|
|
7277566c64 | ||
|
|
bfc3f88a8b | ||
|
|
32dd7433b7 | ||
|
|
f5132b9e9a | ||
|
|
a4806ce068 | ||
|
|
98e24baf64 | ||
|
|
ca5320a7ed | ||
|
|
569f94bb82 | ||
|
|
fb65c6155e | ||
|
|
328f4b8b96 | ||
|
|
e8a79d3f5c | ||
|
|
b9a8b578c6 | ||
|
|
4ae0322ef2 | ||
|
|
2beb0062a5 | ||
|
|
d461ec3e9b | ||
|
|
ded94a5124 | ||
|
|
c2749768aa | ||
|
|
ad359b5e2c | ||
|
|
0d907d6794 | ||
|
|
e90397627d | ||
|
|
c65fa28e1c | ||
|
|
39154b9037 | ||
|
|
e04f027b5d | ||
|
|
aa0daf9fb6 | ||
|
|
7722023abd | ||
|
|
8551b1b90c | ||
|
|
3f350a8577 | ||
|
|
16a36186f2 | ||
|
|
7bd3532f07 | ||
|
|
d6618f3c68 | ||
|
|
d332fe9c84 | ||
|
|
47ad39777f | ||
|
|
258b9703c5 | ||
|
|
632604bbe3 | ||
|
|
6ec8144914 | ||
|
|
eb053180b0 | ||
|
|
2171e9461f | ||
|
|
a70a31411d | ||
|
|
8f16999516 | ||
|
|
10f619f6d6 | ||
|
|
95a626350a | ||
|
|
13029e6d03 | ||
|
|
3a468c2b73 | ||
|
|
4cf26bb70b | ||
|
|
ffef9c9c48 | ||
|
|
e503587854 | ||
|
|
3409a67dfe | ||
|
|
c7a0f3eb39 | ||
|
|
b6fcabc025 | ||
|
|
fadd81c519 | ||
|
|
a7d7352c9b | ||
|
|
98ec56ad77 | ||
|
|
048e01e060 | ||
|
|
58d79832bc | ||
|
|
6daccce97c | ||
|
|
4deb356a51 | ||
|
|
0aabee0a99 | ||
|
|
65e55a88c1 | ||
|
|
632ae67de3 | ||
|
|
90843cf4ba | ||
|
|
dc33223d3b | ||
|
|
0a89814220 | ||
|
|
e028204c56 | ||
|
|
2d721d29f3 | ||
|
|
370b281216 | ||
|
|
553976d065 | ||
|
|
b71a31d2a9 | ||
|
|
acd18ef9e9 | ||
|
|
97bdbd055a | ||
|
|
3a9c922bf4 | ||
|
|
5f2c32ad6e | ||
|
|
b4373715f3 | ||
|
|
23170e436a | ||
|
|
29cdd011b0 | ||
|
|
19b2388aca | ||
|
|
cf5cdbfbcf | ||
|
|
20d9215736 | ||
|
|
3637287ee1 | ||
|
|
bb0a550039 | ||
|
|
641d492d56 | ||
|
|
0eb30c6ca7 | ||
|
|
ff6d65e22a | ||
|
|
905f3eb4be | ||
|
|
b2b1370e5d | ||
|
|
e4004fa26a | ||
|
|
f6582783ea | ||
|
|
250b718936 | ||
|
|
1256da91f0 | ||
|
|
2b0add185f | ||
|
|
b44e9d1207 | ||
|
|
d2d7dfc276 | ||
|
|
b37044e124 | ||
|
|
5cef66269e | ||
|
|
494a855d34 | ||
|
|
b1ddbf26b0 | ||
|
|
1d325a20ca | ||
|
|
b0ec5cdb69 | ||
|
|
1e77f7146d | ||
|
|
67af2e177d | ||
|
|
380866838e | ||
|
|
35449aaa7b | ||
|
|
9599fb9d54 | ||
|
|
f27bbc9022 | ||
|
|
bc5e490f68 | ||
|
|
5dda3b2142 | ||
|
|
0d16dd5dfb | ||
|
|
481fbdf186 | ||
|
|
875d9c980c | ||
|
|
e183ae4434 | ||
|
|
47bb91e9b4 | ||
|
|
f3efcf8a54 | ||
|
|
147bd8c84f | ||
|
|
a9e5f0ea48 | ||
|
|
11ebf61471 | ||
|
|
9345d132fa | ||
|
|
b143830f24 | ||
|
|
abd6eacce8 | ||
|
|
80cb82e9c5 | ||
|
|
c1769268ef | ||
|
|
e1295187f1 | ||
|
|
afe4298427 | ||
|
|
af3841f629 | ||
|
|
0f4c5b396d | ||
|
|
3a8239e52b | ||
|
|
7f54ff4acf | ||
|
|
cc2d4da0c9 | ||
|
|
e10509f114 | ||
|
|
39dcab4b47 | ||
|
|
fda3d5cf8d | ||
|
|
3c54cda575 | ||
|
|
bb25d4cc38 | ||
|
|
089d6bda0e | ||
|
|
7fd0b3761d | ||
|
|
ef944b4a52 | ||
|
|
e77eb1870b | ||
|
|
0f7a048b37 | ||
|
|
0c5d82ad2c | ||
|
|
540faa7c1d | ||
|
|
9ea8533873 | ||
|
|
f9035b4c1b | ||
|
|
330211c5ae | ||
|
|
390b963e29 | ||
|
|
781b6b5392 | ||
|
|
9e694f5f41 | ||
|
|
81dbb16043 | ||
|
|
c961849226 | ||
|
|
ccbd914594 | ||
|
|
67bd8de90f | ||
|
|
68c289f76f | ||
|
|
20d8e90c95 | ||
|
|
8b33424d0f | ||
|
|
6650068f00 | ||
|
|
6d1989449d | ||
|
|
b649eea01d | ||
|
|
222fce3364 | ||
|
|
720fa753cd | ||
|
|
5af4e1cc19 | ||
|
|
f931b17a93 | ||
|
|
71b2b88eb8 | ||
|
|
9415f441d9 | ||
|
|
3a165d4843 | ||
|
|
a2ff55c1f9 | ||
|
|
af25a0e7eb | ||
|
|
9997203667 | ||
|
|
89e9117d39 | ||
|
|
7a64bd23d7 | ||
|
|
09d4756ef3 | ||
|
|
c662862d7f | ||
|
|
7e3a2f6885 | ||
|
|
f2aa0160af | ||
|
|
886f22ee6d | ||
|
|
76a558f550 | ||
|
|
13dbcf9bbb | ||
|
|
fd000edf1c | ||
|
|
9abaea8518 | ||
|
|
c55b95ff06 | ||
|
|
a757ddd4a8 | ||
|
|
16e5f9572f | ||
|
|
639e1dab1c | ||
|
|
3f9464f7b7 | ||
|
|
d8a951dee8 | ||
|
|
5fa1f8f7d9 | ||
|
|
1c52b0a448 | ||
|
|
4352749ec9 | ||
|
|
e25a78e346 | ||
|
|
bb6a5e47b0 | ||
|
|
a2aa69dbd7 | ||
|
|
1eea62629a | ||
|
|
bce5c9a103 | ||
|
|
0c021116bc | ||
|
|
a31d003c05 | ||
|
|
7a47fddd90 | ||
|
|
516d181b10 | ||
|
|
c4ff0188db | ||
|
|
5ee534c3b8 | ||
|
|
828d0ee906 | ||
|
|
1b02a0dbf4 | ||
|
|
e62e44f3f8 | ||
|
|
46739a5bc4 | ||
|
|
7d6f777808 | ||
|
|
4ce186a14d | ||
|
|
7be9f65399 | ||
|
|
d58e67e76e | ||
|
|
41424b3e99 | ||
|
|
50dab4663a | ||
|
|
ff9d7d6756 | ||
|
|
6e131fb206 | ||
|
|
d49049dd3f | ||
|
|
c8bdd26100 | ||
|
|
b9c4618270 | ||
|
|
571388924d | ||
|
|
b6eabb4ce5 | ||
|
|
2c60f42fd0 | ||
|
|
2bd8b47207 | ||
|
|
f2f76d0ebd | ||
|
|
c40599b82e | ||
|
|
c9aa5fca84 | ||
|
|
fd124420f1 | ||
|
|
8630845cbd | ||
|
|
d8f742baa6 | ||
|
|
d3ef7e3e0f | ||
|
|
767b3abea1 | ||
|
|
a1d733bb9a | ||
|
|
dafa89988c | ||
|
|
02953693a6 | ||
|
|
714981eea3 | ||
|
|
b453d12a93 | ||
|
|
852d0825d7 | ||
|
|
c0d56f965b | ||
|
|
86a6d1b1fa | ||
|
|
08949a1ac9 | ||
|
|
0e838deab2 | ||
|
|
51d35f4709 | ||
|
|
29f0677945 | ||
|
|
85b7796434 | ||
|
|
345ce7f0dc | ||
|
|
a817cbc1c0 | ||
|
|
10dbf32596 | ||
|
|
4869e0a463 | ||
|
|
3bd7a2847c | ||
|
|
9494f41c34 | ||
|
|
713c4981b7 | ||
|
|
6db02fe928 | ||
|
|
a88f847708 | ||
|
|
7d74c1f04b | ||
|
|
b1ca129496 | ||
|
|
8e49800421 | ||
|
|
782b2d18b2 | ||
|
|
76358a60be | ||
|
|
86ceb68a05 | ||
|
|
2d624a521e | ||
|
|
ba2c5488c7 | ||
|
|
d1c7b31237 | ||
|
|
26ecadcf94 | ||
|
|
45b5b9e383 | ||
|
|
cb9882ce9c | ||
|
|
6592ed8047 | ||
|
|
92f1dac835 | ||
|
|
a88ee76553 | ||
|
|
b444a95e11 | ||
|
|
5e8002ab28 | ||
|
|
c012b52ac2 | ||
|
|
fc5424477d | ||
|
|
cb1244c041 | ||
|
|
6336f0a447 | ||
|
|
9ea2ef1840 | ||
|
|
5e111975d4 | ||
|
|
87163163e6 | ||
|
|
95c155859e | ||
|
|
575c34eae1 | ||
|
|
0d98d29ae4 | ||
|
|
9a65549575 | ||
|
|
dd6c28c6d1 | ||
|
|
d14f2546a1 | ||
|
|
d179f667c0 | ||
|
|
5b376cb12d | ||
|
|
6cfa0ed559 | ||
|
|
107c48f65c | ||
|
|
1b13d325f1 | ||
|
|
db83a95522 | ||
|
|
b42c890c5e | ||
|
|
3b15e470b5 | ||
|
|
bef528cb21 | ||
|
|
0b8fae6c5a | ||
|
|
560f300716 | ||
|
|
73d93d4edf | ||
|
|
9f71e12e5f | ||
|
|
9dd3e59acb | ||
|
|
657bdf136f | ||
|
|
2d0b231e31 | ||
|
|
c17f5b61fe | ||
|
|
db1eb91940 | ||
|
|
a3292b738a | ||
|
|
82ee4a9fce | ||
|
|
d608889e88 | ||
|
|
a39618a889 | ||
|
|
bfee79dc21 | ||
|
|
3e724303c5 | ||
|
|
19aadf19f7 | ||
|
|
7b3696f3f7 | ||
|
|
35f9bed4de | ||
|
|
b1c52dad58 | ||
|
|
d037fd4647 | ||
|
|
0fc2d90b52 | ||
|
|
b2fdcf16c0 | ||
|
|
73659cb3e7 | ||
|
|
4070930e4c | ||
|
|
e848230947 | ||
|
|
7094395cef | ||
|
|
f1dab3e295 | ||
|
|
ff950e5840 | ||
|
|
77d27bc826 | ||
|
|
25e07cab5a | ||
|
|
8c3e692174 | ||
|
|
ef4bfbfe94 | ||
|
|
51bc71e897 | ||
|
|
3b248eedc2 | ||
|
|
21317b8945 | ||
|
|
46aa46d996 | ||
|
|
5c9a732af4 | ||
|
|
cff2852332 | ||
|
|
465478a829 | ||
|
|
d17211342e | ||
|
|
bb96d2e50a | ||
|
|
c97c3b4d12 | ||
|
|
fb2bc1ca5f | ||
|
|
04860d5cd7 | ||
|
|
c6534fa0b3 | ||
|
|
bb9bec3ba4 | ||
|
|
56f36a6d19 | ||
|
|
e4fc753967 | ||
|
|
1ecc290107 | ||
|
|
6bb271d83c | ||
|
|
d012346c77 | ||
|
|
6739856aa3 | ||
|
|
b3e56437c8 | ||
|
|
e5aaeb53a9 | ||
|
|
8b105cc0cf | ||
|
|
34430aedb1 | ||
|
|
30128c9254 | ||
|
|
e20f8e91ce | ||
|
|
f216fda511 | ||
|
|
5368851506 | ||
|
|
80de266be4 | ||
|
|
57af37fc61 | ||
|
|
229256ee7c | ||
|
|
bb7d137cf7 | ||
|
|
62dc073b3d | ||
|
|
3f57f1cc19 | ||
|
|
819e707a61 | ||
|
|
04d57c5fd6 | ||
|
|
bd927d9350 | ||
|
|
1042350f4e | ||
|
|
f54608e6e4 | ||
|
|
9c97931ca0 | ||
|
|
ac41112731 | ||
|
|
2959b4dc2a | ||
|
|
36261fbe7a | ||
|
|
e849041c11 | ||
|
|
fa3299d042 | ||
|
|
24015b3449 | ||
|
|
59edd6ae54 | ||
|
|
ca871cc10a | ||
|
|
39189c3985 | ||
|
|
9ef55ca4f9 | ||
|
|
90a3f460f1 | ||
|
|
00b0558c2b | ||
|
|
094a5b403e | ||
|
|
c9cab112f9 | ||
|
|
dcbc3f14bd | ||
|
|
69a62e0906 | ||
|
|
357fb7210a | ||
|
|
98b833a07d | ||
|
|
b1a075f760 | ||
|
|
d6fbe5f847 | ||
|
|
d496f6f1bd | ||
|
|
bd9713bd67 | ||
|
|
9078bb2306 | ||
|
|
433370dc2f | ||
|
|
bbc3230dcf | ||
|
|
3c850f2711 | ||
|
|
b48aeef1f2 | ||
|
|
e8360a497d | ||
|
|
5f8ab01f9b | ||
|
|
9a22fe5a25 | ||
|
|
ca71d89d88 | ||
|
|
550409923a | ||
|
|
60503b60b1 | ||
|
|
df5855d4c6 | ||
|
|
1108467f44 | ||
|
|
39c6217109 | ||
|
|
6217d08ce9 |
|
|
@ -1,62 +0,0 @@
|
|||
# Design System Style Review Rules
|
||||
|
||||
Use these rules when reviewing CSS/SCSS/Vue style changes, especially in
|
||||
`packages/frontend/` and `packages/frontend/@n8n/design-system/`.
|
||||
|
||||
## 1) Token source priority
|
||||
|
||||
Prefer this order when choosing visual values:
|
||||
|
||||
1. Semantic tokens from
|
||||
`packages/frontend/@n8n/design-system/src/css/_tokens.scss`
|
||||
2. Primitives from
|
||||
`packages/frontend/@n8n/design-system/src/css/_primitives.scss`
|
||||
3. Hard-coded values only when no suitable token exists
|
||||
|
||||
If no token exists, request a short rationale in the PR.
|
||||
|
||||
## 2) Hard-coded visual values
|
||||
|
||||
Flag hard-coded visual values and suggest token alternatives. This includes:
|
||||
|
||||
- Colors (`#fff`, `rgb()`, `hsl()`, `oklch()`)
|
||||
- Spacing and sizing (`px`, `rem`, numeric layout constants in styles)
|
||||
- Radius, border widths/styles, and shadows
|
||||
- Typography values (font size, weight, line-height)
|
||||
- Motion values (durations and easing like `cubic-bezier(...)`)
|
||||
|
||||
Severity: strong warning (expected migration to tokens/primitives when possible).
|
||||
|
||||
## 3) Legacy token usage
|
||||
|
||||
In `_tokens.scss`, the compatibility section labeled
|
||||
"Legacy tokens (kept for compatibility)" is considered legacy usage.
|
||||
|
||||
When new or modified code uses these legacy token families, flag it as a
|
||||
migration opportunity and recommend semantic token usage where available.
|
||||
|
||||
Severity: strong warning (discourage new usage, allow compatibility paths).
|
||||
|
||||
## 4) Deprecated style and component surfaces
|
||||
|
||||
Flag new usage of deprecated/legacy style surfaces in design-system components,
|
||||
for example:
|
||||
|
||||
- `Button.legacy.scss` and legacy button override classes
|
||||
- Legacy button variants/types (for example `highlight`, `highlight-fill`)
|
||||
- Legacy component variants that exist for compatibility (for example legacy
|
||||
tabs variant)
|
||||
|
||||
Severity: strong warning (prefer modern semantic variants/components).
|
||||
|
||||
## 5) Token substitution changes
|
||||
|
||||
If a PR changes one token reference to another (for example
|
||||
`--text-color` -> `--text-color--subtle`), flag it as a soft warning.
|
||||
|
||||
Ask for intent in the PR description/comment:
|
||||
|
||||
- Intentional design adjustment, or
|
||||
- Potential accidental visual regression
|
||||
|
||||
Do not treat token substitution as a hard failure by default.
|
||||
1
.agents/skills
Symbolic link
1
.agents/skills
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../.claude/plugins/n8n/skills
|
||||
150
.claude/plugins/n8n/skills/community-pr-review/SKILL.md
Normal file
150
.claude/plugins/n8n/skills/community-pr-review/SKILL.md
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
---
|
||||
description: >-
|
||||
Checks if a community pull request is ready for human review. Verifies CLA
|
||||
signature, PR title format, description completeness, test coverage, and
|
||||
cubic-dev-ai issues. Use when given a PR number or branch name to review,
|
||||
or when the user says /community-pr-review, /pr-review, or asks to check if
|
||||
a PR is ready for review.
|
||||
allowed-tools: Bash(gh:*), Bash(git:*), Read, Glob, Grep
|
||||
---
|
||||
|
||||
# Community PR Review
|
||||
|
||||
Given a PR number or branch name, determine whether it is ready for human review.
|
||||
|
||||
## Steps
|
||||
|
||||
### 1. Resolve the PR
|
||||
|
||||
If given a branch name, find the PR number first:
|
||||
```bash
|
||||
gh pr view <branch> --repo n8n-io/n8n --json number --jq .number
|
||||
```
|
||||
|
||||
### 2. Fetch PR data
|
||||
|
||||
```bash
|
||||
gh pr view <number> --repo n8n-io/n8n \
|
||||
--json number,title,body,author,headRefName,headRefOid,files,isDraft,state
|
||||
```
|
||||
|
||||
Fetch in parallel:
|
||||
|
||||
```bash
|
||||
# CLA commit status (primary signal) — statuses are newest-first; use the first returned entry
|
||||
gh api --paginate "repos/n8n-io/n8n/commits/<headRefOid>/statuses" \
|
||||
--jq '[.[] | select(.context == "license/cla") | {state, description}] | first'
|
||||
|
||||
# CLAassistant issue comment (fallback when no commit status) — use the last returned entry
|
||||
gh api --paginate "repos/n8n-io/n8n/issues/<number>/comments" \
|
||||
--jq '[.[] | select(.user.login == "CLAassistant") | .body] | last'
|
||||
|
||||
# cubic-dev-ai PR review comments (streamed so results concatenate cleanly across pages)
|
||||
gh api --paginate "repos/n8n-io/n8n/pulls/<number>/comments" \
|
||||
--jq '.[] | select(.user.login == "cubic-dev-ai[bot]") | {body: .body, path: .path}'
|
||||
```
|
||||
|
||||
### 3. Run the five checks
|
||||
|
||||
#### A. CLA signed
|
||||
|
||||
Check the `license/cla` commit status first; fall back to the CLAassistant comment if no status exists.
|
||||
|
||||
**Commit status** (`context == "license/cla"`):
|
||||
- `state: "success"` → ✅ signed
|
||||
- `state: "failure"` or `state: "error"` → ❌ not signed
|
||||
- `state: "pending"` → ⏳ pending
|
||||
- Not present → fall back to comment
|
||||
|
||||
**CLAassistant issue comment** (fallback):
|
||||
- Body contains `"All committers have signed the CLA."` → ✅ signed
|
||||
- Body contains `"not signed"` or a link to sign → ❌ not signed
|
||||
- No comment → ❌ treat as not signed
|
||||
|
||||
#### B. PR title format
|
||||
|
||||
For all types except `revert`, the title must match:
|
||||
```
|
||||
^(feat|fix|perf|test|docs|refactor|build|ci|chore)(\([a-zA-Z0-9 ]+( Node)?\))?!?: [A-Z].+[^.]$
|
||||
```
|
||||
|
||||
For `revert` titles, the summary is the original commit header (which starts with a lowercase type), so capitalization is not enforced:
|
||||
```
|
||||
^revert(\([a-zA-Z0-9 ]+( Node)?\))?!?: .+[^.]$
|
||||
```
|
||||
|
||||
- Type must be one of: `feat fix perf test docs refactor build ci chore revert`
|
||||
- Scope is optional, in parentheses e.g. `(editor)` or `(Slack Node)`
|
||||
- Breaking changes: `!` before the colon
|
||||
- Summary: starts with capital letter (lowercase allowed for `revert:`), no trailing period
|
||||
- No Linear ticket IDs in the title (e.g. `N8N-1234`)
|
||||
|
||||
#### C. PR description completeness
|
||||
|
||||
1. **Summary** (`## Summary`) — must have non-empty content below the heading (not just the HTML comment).
|
||||
2. **Related tickets** (`## Related Linear tickets, Github issues, and Community forum posts`) — acceptable content: a URL (`http`), a GitHub closing keyword (`closes #N`, `fixes #N`, `resolves #N`, etc.), or empty. Only flag if the section heading is missing entirely.
|
||||
3. **Checklist** (`## Review / Merge checklist`) — all four items must be present. Unchecked checkboxes are expected for community PRs; do **not** flag them as missing.
|
||||
|
||||
#### D. Tests
|
||||
|
||||
Skip this check if the PR type (from the title) is `docs`, `ci`, `chore`, or `build`.
|
||||
|
||||
Otherwise:
|
||||
1. Identify source files changed: non-test files under `packages/` from the `files` list.
|
||||
2. If there are source file changes, check out the PR in a temporary worktree:
|
||||
|
||||
```bash
|
||||
git fetch origin pull/<number>/head:pr/<number>
|
||||
git worktree add /tmp/pr-<number>-review pr/<number>
|
||||
```
|
||||
|
||||
3. Read the changed source files from the worktree to understand whether the changes introduce logic that warrants tests (new functions, bug fixes, behaviour changes, data transformations). Pure config changes, type-only changes, and trivial renames do not require tests.
|
||||
4. Look for matching test files (`*.test.ts`, `*.spec.ts`, files inside `__tests__/`) among the changed files.
|
||||
5. **Always clean up the worktree**, even if a previous check failed:
|
||||
|
||||
```bash
|
||||
git worktree remove /tmp/pr-<number>-review --force
|
||||
git branch -D pr/<number>
|
||||
```
|
||||
|
||||
Report:
|
||||
- ✅ Tests present, or change does not require tests
|
||||
- ❌ Source logic changed but no test files found
|
||||
|
||||
#### E. cubic-dev-ai issues
|
||||
|
||||
Review the PR review comments fetched in step 2. `cubic-dev-ai[bot]` leaves comments for every issue it finds.
|
||||
|
||||
- No comments from `cubic-dev-ai[bot]`, or every comment explicitly states no issues were found → ✅
|
||||
- Any other comment → ❌ report the total count and priority breakdown (e.g. "3 issues: 1× P1, 1× P2, 1× P3")
|
||||
|
||||
### 4. Output
|
||||
|
||||
Always output valid JSON in this exact shape:
|
||||
|
||||
```json
|
||||
{
|
||||
"readyForReview": <true if all passing checks allow merge, false otherwise>,
|
||||
"messageForUser": "<Human-readable summary of what needs to change, written as if posted directly to the PR contributor. 'N/A' if nothing is needed.>",
|
||||
"checks": {
|
||||
"CLA": <true if signed, false if not signed or pending>,
|
||||
"Title": <true if title matches convention, false otherwise>,
|
||||
"Description": <true if all three template sections are complete, false otherwise>,
|
||||
"TestsNeeded": <true if the code changes require tests, false if not applicable>,
|
||||
"TestsIncluded": <true if test files are present in the PR, false otherwise>,
|
||||
"CubicIssues": <true if cubic-dev-ai raised issues, false if no issues>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`readyForReview` is `true` only when: `CLA`, `Title`, and `Description` are all `true`; `CubicIssues` is `false`; and either `TestsNeeded` is `false` or `TestsIncluded` is `true`.
|
||||
|
||||
`messageForUser` should be a short, friendly message directed at the contributor listing exactly what they need to address. If `readyForReview` is `true`, set it to `"N/A"`.
|
||||
|
||||
Output nothing other than the JSON block.
|
||||
|
||||
## Notes
|
||||
|
||||
- Draft PRs — report all findings but note the PR is a draft.
|
||||
- If the PR is already merged or closed, say so and skip the checks.
|
||||
- Always remove the worktree even if earlier checks failed.
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:content-design
|
||||
description: >
|
||||
Product content designer for UI copy. Use when writing, reviewing, or auditing
|
||||
user-facing text: button labels, error messages, tooltips, empty states, modal copy,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:conventions
|
||||
description: Quick reference for n8n patterns. Full docs /AGENTS.md
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:create-community-node-lint-rule
|
||||
description: >-
|
||||
Create new ESLint rules for the @n8n/eslint-plugin-community-nodes package.
|
||||
Use when adding a lint rule, creating a community node lint, or working on
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:create-issue
|
||||
description: Create Linear tickets or GitHub issues following n8n conventions. Use when the user asks to create a ticket, file a bug, open an issue, or says /create-issue.
|
||||
argument-hint: "[linear|github] <description of the issue>"
|
||||
compatibility:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:create-pr
|
||||
description: Creates GitHub pull requests with properly formatted titles that pass the check-pr-title CI validation. Use when creating PRs, submitting changes for review, or when the user says /pr or asks to create a pull request.
|
||||
allowed-tools: Bash(git:*), Bash(gh:*), Read, Grep, Glob
|
||||
---
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:create-skill
|
||||
description: >-
|
||||
Guides users through creating effective Agent Skills. Use when you want to
|
||||
create, write, or author a new skill, or asks about skill structure, best
|
||||
|
|
|
|||
33
.claude/plugins/n8n/skills/design-system/SKILL.md
Normal file
33
.claude/plugins/n8n/skills/design-system/SKILL.md
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
name: n8n:design-system
|
||||
description: Guidelines on using Design System styles and components. Use when working on .vue files in packages/frontend. Triggers for tasks that include component architecture, styling, UI changes, or feature work.
|
||||
---
|
||||
|
||||
# Design System
|
||||
|
||||
Comprehensive guide for building, styling, and using components in the frontend.
|
||||
|
||||
## When to Apply
|
||||
Reference these guidelines when:
|
||||
- Working on `.{vue|css|scss}` files in `packages/frontend`
|
||||
- Adding new components to `packages/frontend/@n8n/design-system`
|
||||
- Refactoring styles for Vue components
|
||||
- Implementing new UI components or features
|
||||
- Reviewing changes to UI
|
||||
|
||||
## Rules
|
||||
- Follow guidelines in `packages/frontend/@n8n/design-system/src/styleguide/*.mdx`
|
||||
- ALWAYS use CSS variables for styles from `packages/frontend/@n8n/design-system/src/css/_tokens.scss` or `packages/frontend/@n8n/design-system/src/css/_primtivies.scss`. Use hard-coded values only when no suitable tokens.
|
||||
- ALWAYS prefer using existing components from `packages/frontend/@n8n/design-system/src/components`. Prefer components that aren't marked `@deprecated`.
|
||||
- Use `light-dark()` when alternating colors for ligh/dark mode
|
||||
- When working with animations or transitions, ALWAYS prefer using mixins from `packages/frontend/@n8n/design-system/src/css/mixins/motion.scss`
|
||||
- When reviewing animations, follow the guides in `rules/web-animation-guidelines.md`
|
||||
- When reviewing UI changes or adding new components, follow `rules/web-interface-guidelines.md`
|
||||
|
||||
## Examples
|
||||
- "Add a modal dialog for confirming workflow deletion" → Use `N8nDialog`
|
||||
- "Add a dropdown to select workflow status" → Use `N8nDropdown` or `N8nSelect`
|
||||
- "Add button with + icon to add new tiem" → Wrap `N8nButton` with `iconOnly` prop with `N8nTooltip` and wrap in `N8nTooltip`. Use `N8nIcon` and proper aria-label.
|
||||
- "Add a destructive action button" → use `N8nButton` with `variant="destructive"`
|
||||
- "Make background color white/black" → Use `var(--background--surface)` for white on light mode and "black" on dark mode
|
||||
- "Animate the title in gracefully" -> Use `fade-in-up` mixin from `motion.scss` with `var(--duration--base)`
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# Web Motion Guidelines
|
||||
Design and implement web animations that feel natural and purposeful
|
||||
|
||||
## Timing and Duration
|
||||
|
||||
## Duration Guidelines
|
||||
|
||||
| Element Type | Duration |
|
||||
| --------------------------------- | --------- |
|
||||
| Micro-interactions | 100-150ms |
|
||||
| Standard UI (tooltips, dropdowns) | 150-250ms |
|
||||
| Modals, drawers | 200-300ms |
|
||||
|
||||
**Rules:**
|
||||
|
||||
- UI animations should stay under 300ms
|
||||
- Larger elements animate slower than smaller ones
|
||||
- Exit animations can be ~20% faster than entrance
|
||||
- Match duration to distance - longer travel = longer duration
|
||||
|
||||
### The Frequency
|
||||
|
||||
Determine how often users will see the animation:
|
||||
|
||||
- **100+ times/day** → No animation (or drastically reduced)
|
||||
- **Occasional use** → Standard animation
|
||||
- **Rare/first-time** → Can be more special
|
||||
|
||||
**Example:** Raycast never animates because users open it hundreds of times a day.
|
||||
|
||||
## When to Animate
|
||||
|
||||
**Do animate:**
|
||||
|
||||
- Enter/exit transitions for spatial consistency
|
||||
- State changes that benefit from visual continuity
|
||||
- Responses to user actions (feedback)
|
||||
- Rarely-used interactions where delight adds value
|
||||
|
||||
**Don't animate:**
|
||||
|
||||
- Keyboard-initiated actions
|
||||
- Hover effects on frequently-used elements
|
||||
- Anything users interact with 100+ times daily
|
||||
- When speed matters more than smoothness
|
||||
|
||||
## Performance
|
||||
|
||||
Prefer animating `transform` and `opacity`. These skip layout and paint stages, running entirely on the GPU.
|
||||
|
||||
**Avoid animating:**
|
||||
|
||||
- `padding`, `margin`, `height`, `width` (trigger layout)
|
||||
- `blur` filters above 20px (expensive, especially Safari)
|
||||
- CSS variables in deep component trees
|
||||
|
||||
### Optimization Techniques
|
||||
|
||||
```css
|
||||
/* Force GPU acceleration */
|
||||
.animated-element {
|
||||
will-change: transform;
|
||||
}
|
||||
```
|
||||
|
||||
## Practical Tips
|
||||
|
||||
Quick reference for common scenarios. See [PRACTICAL-TIPS.md](PRACTICAL-TIPS.md) for detailed implementations.
|
||||
|
||||
| Scenario | Solution |
|
||||
| ------------------------------- | ----------------------------------------------- |
|
||||
| Make buttons feel responsive | Add `transform: scale(0.97)` on `:active` |
|
||||
| Element appears from nowhere | Start from `scale(0.95)`, not `scale(0)` |
|
||||
| Shaky/jittery animations | Add `will-change: transform` |
|
||||
| Hover causes flicker | Animate child element, not parent |
|
||||
| Popover scales from wrong point | Set `transform-origin` to trigger location |
|
||||
| Sequential tooltips feel slow | Skip delay/animation after first tooltip |
|
||||
| Small buttons hard to tap | Use 44px minimum hit area (pseudo-element) |
|
||||
| Something still feels off | Add subtle blur (under 20px) to mask it |
|
||||
| Hover triggers on mobile | Use `@media (hover: hover) and (pointer: fine)` |
|
||||
|
||||
## Easing Decision Flowchart
|
||||
|
||||
Is the element entering or exiting the viewport?
|
||||
├── Yes → ease-out
|
||||
└── No
|
||||
├── Is it moving/morphing on screen?
|
||||
│ └── Yes → ease-in-out
|
||||
└── Is it a hover change?
|
||||
├── Yes → ease
|
||||
└── Is it constant motion?
|
||||
├── Yes → linear
|
||||
└── Default → ease-out
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
# Web Interface Guidelines
|
||||
<!-- credit to https://github.com/raunofreiberg/interfaces -->
|
||||
This document outlines a non-exhaustive list of details that make a good (web) interface. It is a living document, periodically updated based on learnings. Some of these may be subjective, but most apply to all websites.
|
||||
|
||||
The [WAI-ARIA](https://www.w3.org/TR/wai-aria-1.1/) spec is deliberately not duplicated in this document. However, some accessibility guidelines may be pointed out. Contributions are welcome. Edit [this file](https://github.com/raunofreiberg/interfaces/blob/main/README.md) and submit a pull request.
|
||||
|
||||
## Interactivity
|
||||
|
||||
- Clicking the input label should focus the input field
|
||||
- Inputs should be wrapped with a `<form>` to submit by pressing Enter
|
||||
- Inputs should have an appropriate `type` like `password`, `email`, etc
|
||||
- Inputs should disable `spellcheck` and `autocomplete` attributes most of the time
|
||||
- Inputs should leverage HTML form validation by using the `required` attribute when appropriate
|
||||
- Input prefix and suffix decorations, such as icons, should be absolutely positioned on top of the text input with padding, not next to it, and trigger focus on the input
|
||||
- Toggles should immediately take effect, not require confirmation
|
||||
- Buttons should be disabled after submission to avoid duplicate network requests
|
||||
- Interactive elements should disable `user-select` for inner content
|
||||
- Decorative elements (glows, gradients) should disable `pointer-events` to not hijack events
|
||||
- Interactive elements in a vertical or horizontal list should have no dead areas between each element, instead, increase their `padding`
|
||||
|
||||
## Typography
|
||||
|
||||
- Fonts should have `-webkit-font-smoothing: antialiased` applied for better legibility
|
||||
- Fonts should have `text-rendering: optimizeLegibility` applied for better legibility
|
||||
- Fonts should be subset based on the content, alphabet or relevant language(s)
|
||||
- Font weight should not change on hover or selected state to prevent layout shift
|
||||
- Font weights below 400 should not be used
|
||||
- Medium sized headings generally look best with a font weight between 500-600
|
||||
- Adjust values fluidly by using CSS [`clamp()`](https://developer.mozilla.org/en-US/docs/Web/CSS/clamp), e.g. `clamp(48px, 5vw, 72px)` for the `font-size` of a heading
|
||||
- Where available, tabular figures should be applied with `font-variant-numeric: tabular-nums`, particularly in tables or when layout shifts are undesirable, like in timers
|
||||
- Prevent text resizing unexpectedly in landscape mode on iOS with `-webkit-text-size-adjust: 100%`
|
||||
|
||||
|
||||
## Motion
|
||||
|
||||
- Switching themes should not trigger transitions and animations on elements [^1]
|
||||
- Animation duration should not be more than 200ms for interactions to feel immediate
|
||||
- Animation values should be proportional to the trigger size:
|
||||
- Don't animate dialog scale in from 0 → 1, fade opacity and scale from ~0.8
|
||||
- Don't scale buttons on press from 1 → 0.8, but ~0.96, ~0.9, or so
|
||||
- Actions that are frequent and low in novelty should avoid extraneous animations: [^2]
|
||||
- Opening a right click menu
|
||||
- Deleting or adding items from a list
|
||||
- Hovering trivial buttons
|
||||
- Looping animations should pause when not visible on the screen to offload CPU and GPU usage
|
||||
- Use `scroll-behavior: smooth` for navigating to in-page anchors, with an appropriate offset
|
||||
|
||||
## Touch
|
||||
|
||||
- Hover states should not be visible on touch press, use `@media (hover: hover)` [^3]
|
||||
- Font size for inputs should not be smaller than 16px to prevent iOS zooming on focus
|
||||
- Inputs should not auto focus on touch devices as it will open the keyboard and cover the screen
|
||||
- Apply `muted` and `playsinline` to `<video />` tags to auto play on iOS
|
||||
- Disable `touch-action` for custom components that implement pan and zoom gestures to prevent interference from native behavior like zooming and scrolling
|
||||
- Disable the default iOS tap highlight with `-webkit-tap-highlight-color: rgba(0,0,0,0)`, but always replace it with an appropriate alternative
|
||||
|
||||
## Optimizations
|
||||
|
||||
- Large `blur()` values for `filter` and `backdrop-filter` may be slow
|
||||
- Scaling and blurring filled rectangles will cause banding, use radial gradients instead
|
||||
- Sparingly enable GPU rendering with `transform: translateZ(0)` for unperformant animations
|
||||
- Toggle `will-change` on unperformant scroll animations for the duration of the animation [^4]
|
||||
- Auto-playing too many videos on iOS will choke the device, pause or even unmount off-screen videos
|
||||
- Bypass React's render lifecycle with refs for real-time values that can commit to the DOM directly [^5]
|
||||
- [Detect and adapt](https://github.com/GoogleChromeLabs/react-adaptive-hooks) to the hardware and network capabilities of the user's device
|
||||
|
||||
## Accessibility
|
||||
|
||||
- Disabled buttons should not have tooltips, they are not accessible [^6]
|
||||
- Focusable elements in a sequential list should be navigable with <kbd>↑</kbd> <kbd>↓</kbd>
|
||||
- Focusable elements in a sequential list should be deletable with <kbd>⌘</kbd> <kbd>Backspace</kbd>
|
||||
- To open immediately on press, dropdown menus should trigger on `mousedown`, not `click`
|
||||
- Use a svg favicon with a style tag that adheres to the system theme based on `prefers-color-scheme`
|
||||
- Icon only interactive elements should define an explicit `aria-label`
|
||||
- Tooltips triggered by hover should not contain interactive content
|
||||
- Images should always be rendered with `<img>` for screen readers and ease of copying from the right click menu
|
||||
- Illustrations built with HTML should have an explicit `aria-label` instead of announcing the raw DOM tree to people using screen readers
|
||||
- Gradient text should unset the gradient on `::selection` state
|
||||
- When using nested menus, use a "prediction cone" to prevent the pointer from accidentally closing the menu when moving across other elements.
|
||||
|
||||
|
||||
## Design
|
||||
|
||||
- Optimistically update data locally and roll back on server error with feedback
|
||||
- Authentication redirects should happen on the server before the client loads to avoid janky URL changes
|
||||
- Style the document selection state with `::selection`
|
||||
- Display feedback relative to its trigger:
|
||||
- Show a temporary inline checkmark on a successful copy, not a notification
|
||||
- Highlight the relevant input(s) on form error(s)
|
||||
- Empty states should prompt to create a new item, with optional templates
|
||||
|
||||
[^1]: Switching between dark mode or light mode will trigger transitions on elements that are meant for explicit interactions like hover. We can [disable transitions temporarily](https://paco.me/writing/disable-theme-transitions) to prevent this. For Next.js, use [next-themes](https://github.com/pacocoursey/next-themes) which prevents transitions out of the box.
|
||||
[^2]: This is a matter of taste but some interactions just feel better with no motion. For example, the native macOS right click menu only animates out, not in, due to the frequent usage of it.
|
||||
[^3]: Most touch devices on press will temporarily flash the hover state, unless explicitly only defined for pointer devices with [`@media (hover: hover)`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/hover).
|
||||
[^4]: Use [`will-change`](https://developer.mozilla.org/en-US/docs/Web/CSS/will-change) as a last resort to improve performance. Pre-emptively throwing it on elements for better performance may have the opposite effect.
|
||||
[^5]: This might be controversial but sometimes it can be beneficial to manipulate the DOM directly. For example, instead of relying on React re-rendering on every wheel event, we can track the delta in a ref and update relevant elements directly in the callback.
|
||||
[^6]: Disabled buttons do not appear in tab order in the DOM so the tooltip will never be announced for keyboard users and they won't know why the button is disabled.
|
||||
[^7]: As of 2023, Safari will not take the border radius of an element into account when defining custom outline styles. [Safari 16.4](https://developer.apple.com/documentation/safari-release-notes/safari-16_4-release-notes) has added support for `outline` following the curve of border radius. However, keep in mind that not everyone updates their OS immediately.
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
name: n8n:linear-issue
|
||||
description: Fetch and analyze Linear issue with all related context. Use when starting work on a Linear ticket, analyzing issues, or gathering context about a Linear issue.
|
||||
disable-model-invocation: true
|
||||
argument-hint: "[issue-id]"
|
||||
compatibility:
|
||||
requires:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:loom-transcript
|
||||
description: Fetch and display the full transcript from a Loom video URL. Use when the user wants to get or read a Loom transcript.
|
||||
argument-hint: [loom-url]
|
||||
---
|
||||
|
|
@ -101,4 +102,4 @@ Format and present the full transcript to the user:
|
|||
|
||||
- No authentication or cookies are required — Loom's transcript API is publicly accessible.
|
||||
- Only English transcripts are available through this API.
|
||||
- Transcripts are auto-generated and may contain minor errors.
|
||||
- Transcripts are auto-generated and may contain minor errors.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:node-add-oauth
|
||||
description: Add OAuth2 credential support to an existing n8n node — creates the credential file, updates the node, adds tests, and keeps the CLI constant in sync. Use when the user says /node-add-oauth.
|
||||
argument-hint: "[node-name] [optional: custom-scopes flag or scope list]"
|
||||
---
|
||||
|
|
|
|||
139
.claude/plugins/n8n/skills/protect-endpoints/SKILL.md
Normal file
139
.claude/plugins/n8n/skills/protect-endpoints/SKILL.md
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
---
|
||||
name: n8n:protect-endpoints
|
||||
description: Applies n8n's RBAC scope decorators to REST endpoints. Use when creating a new @RestController, adding any @Get/@Post/@Put/@Patch/@Delete route to an existing controller, or reviewing endpoint authorization. Every authenticated endpoint must be gated by @ProjectScope or @GlobalScope.
|
||||
---
|
||||
|
||||
# Protect REST endpoints with RBAC
|
||||
|
||||
**Rule:** every authenticated route on a `@RestController` MUST carry an access-scope decorator. If you add a route without one, the IDOR/permission bypass is on you.
|
||||
|
||||
## Decision
|
||||
|
||||
```
|
||||
URL has :projectId → @ProjectScope('<resource>:<op>')
|
||||
URL has no project → @GlobalScope('<resource>:<op>')
|
||||
skipAuth: true → no decorator + comment explaining alternate auth
|
||||
```
|
||||
|
||||
`@ProjectScope` succeeds if the user has the scope **globally OR in the project named in the URL**. `@GlobalScope` ignores project relations entirely.
|
||||
|
||||
Both decorators come from `@n8n/decorators`. The middleware lives in `packages/cli/src/controller.registry.ts` (`createScopedMiddleware`) and resolves access via `userHasScopes` in `packages/cli/src/permissions.ee/check-access.ts`.
|
||||
|
||||
## Apply the decorator
|
||||
|
||||
```ts
|
||||
import { Get, Post, ProjectScope, RestController } from '@n8n/decorators';
|
||||
|
||||
@RestController('/projects/:projectId/widgets')
|
||||
export class WidgetsController {
|
||||
@Post('/')
|
||||
@ProjectScope('widget:create') // create
|
||||
async create(...) { ... }
|
||||
|
||||
@Get('/:widgetId')
|
||||
@ProjectScope('widget:read') // read one
|
||||
async get(...) { ... }
|
||||
|
||||
@Get('/')
|
||||
@ProjectScope('widget:list') // list
|
||||
async list(...) { ... }
|
||||
|
||||
@Patch('/:widgetId')
|
||||
@ProjectScope('widget:update') // update
|
||||
async update(...) { ... }
|
||||
|
||||
@Delete('/:widgetId')
|
||||
@ProjectScope('widget:delete') // delete
|
||||
async delete(...) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Conventions:
|
||||
- One decorator per route, placed directly under the HTTP-method decorator.
|
||||
- Use the most specific scope that fits. Reuse `*:update` for state-changing actions like `publish`/`unpublish`/`build` unless the resource needs to gate them separately (see `workflow:publish` for the precedent).
|
||||
- Routes without `:projectId` and not global-only operations are usually a design smell — flag it.
|
||||
|
||||
## When the scope doesn't exist yet
|
||||
|
||||
Add the resource and ops in `packages/@n8n/permissions/`:
|
||||
|
||||
1. **`src/constants.ee.ts`** — add to `RESOURCES` (alphabetical):
|
||||
```ts
|
||||
widget: [...DEFAULT_OPERATIONS, 'execute'] as const,
|
||||
```
|
||||
The `Scope` union (`<resource>:<op>` template-literal type) auto-derives.
|
||||
2. **`src/scope-information.ts`** — add a display name + description per scope.
|
||||
3. **`src/roles/scopes/project-scopes.ee.ts`** — add to project roles. Match the `workflow` precedent unless product says otherwise:
|
||||
- `REGULAR_PROJECT_ADMIN_SCOPES`, `PERSONAL_PROJECT_OWNER_SCOPES`, `PROJECT_EDITOR_SCOPES` → all CRUDL+execute scopes.
|
||||
- `PROJECT_VIEWER_SCOPES` → read/list/execute only.
|
||||
- `PROJECT_CHAT_USER_SCOPES` → execute only (if applicable).
|
||||
4. **`src/roles/scopes/global-scopes.ee.ts`** — add to `GLOBAL_OWNER_SCOPES` (admin inherits via `concat()`). Do **not** add to member/chat-user globals — they get scopes via project relations.
|
||||
5. **Personal-space publishing**: if you add a `<resource>:publish` scope, also append it to `PERSONAL_SPACE_PUBLISHING_SETTING.scopes` in `constants.ee.ts` so personal-owner gating matches `workflow:publish`.
|
||||
6. **Frontend wiring** — three files in the editor; skipping any of them means the new scopes will not appear in the project-role configuration UI:
|
||||
- `packages/frontend/editor-ui/src/app/stores/rbac.store.ts` — add `<resource>: {}` to `scopesByResourceId` (typecheck will fail otherwise).
|
||||
- `packages/frontend/editor-ui/src/features/project-roles/projectRoleScopes.ts` — add the resource to `UI_OPERATIONS` (operations to render in the permissions matrix, in display order) **and** to `SCOPE_TYPES` (the order the resource group appears on the page).
|
||||
- `packages/frontend/@n8n/i18n/src/locales/en.json` — add `projectRoles.<resource>:<op>` (column label) and `projectRoles.<resource>:<op>.tooltip` (hover description) for every op, plus `projectRoles.type.<resource>` (the group header).
|
||||
7. **Snapshot** — update `packages/@n8n/permissions/src/__tests__/__snapshots__/scope-information.test.ts.snap` to include the new `<resource>:*` entries.
|
||||
|
||||
No DB migration needed — `AuthRolesService.init()` syncs scopes/roles on every startup. Custom team roles created in the UI are **not** auto-updated; mention this in the PR description.
|
||||
|
||||
## Public / unauthenticated routes
|
||||
|
||||
`{ skipAuth: true }` skips the auth middleware → `req.user` is undefined → adding `@ProjectScope` would 401 every call. Public routes (third-party webhooks, signed callbacks) must:
|
||||
|
||||
1. **Omit the scope decorator.**
|
||||
2. Authenticate via signature/HMAC verification inside the handler (or another route-specific mechanism).
|
||||
3. Carry a comment explaining why no scope is applied, so the next reviewer doesn't try to "fix" it.
|
||||
|
||||
Example:
|
||||
```ts
|
||||
// Third-party webhook callback: do not add @ProjectScope. Auth happens
|
||||
// via per-platform signature verification inside webhookHandler, and
|
||||
// :projectId is unused in the (agentId, platform) lookup.
|
||||
@Post('/:agentId/webhooks/:platform', { skipAuth: true, allowBots: true })
|
||||
async handleWebhook(...) { ... }
|
||||
```
|
||||
|
||||
## Verify with a route-metadata test
|
||||
|
||||
Add a regression test that fails when a future route is added without a scope. Iterate every route on the controller via `ControllerRegistryMetadata` and assert the gate.
|
||||
|
||||
```ts
|
||||
import { ControllerRegistryMetadata } from '@n8n/decorators';
|
||||
import { Container } from '@n8n/di';
|
||||
import { WidgetsController } from '../widgets.controller';
|
||||
|
||||
const UNAUTHENTICATED_HANDLERS = new Set<string>(); // add public handler names here
|
||||
|
||||
const metadata = Container.get(ControllerRegistryMetadata).getControllerMetadata(
|
||||
WidgetsController as never,
|
||||
);
|
||||
const routeCases = Array.from(metadata.routes.entries()).map(([handlerName, route]) => ({
|
||||
handlerName, route,
|
||||
}));
|
||||
|
||||
describe('WidgetsController route access scopes', () => {
|
||||
it.each(routeCases)(
|
||||
'$handlerName is gated by a project-scoped widget:* check',
|
||||
({ handlerName, route }) => {
|
||||
if (UNAUTHENTICATED_HANDLERS.has(handlerName)) {
|
||||
expect(route.accessScope).toBeUndefined();
|
||||
expect(route.skipAuth).toBe(true);
|
||||
return;
|
||||
}
|
||||
expect(route.accessScope).toBeDefined();
|
||||
expect(route.accessScope?.globalOnly).toBe(false);
|
||||
expect(route.accessScope?.scope.startsWith('widget:')).toBe(true);
|
||||
},
|
||||
);
|
||||
});
|
||||
```
|
||||
|
||||
## Defense in depth (still required)
|
||||
|
||||
Decorator alone is not enough when handlers leak data via downstream calls. Service/repository methods should still **filter by `projectId`** (or user-scoped helpers like `findByUser`). The decorator gates *who can call this URL*; the service gates *what they can read*. Both, always.
|
||||
|
||||
## Reference patterns
|
||||
|
||||
- Project-scoped CRUD: `packages/cli/src/workflows/workflows.controller.ts`, `packages/cli/src/credentials/credentials.controller.ts`, `packages/cli/src/modules/data-table/data-table.controller.ts`.
|
||||
- Mixed global + project: `packages/cli/src/controllers/project.controller.ts`.
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:reproduce-bug
|
||||
description: Reproduce a bug from a Linear ticket with a failing test. Expects the full ticket context (title, description, comments) to be provided as input.
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:setup-mcps
|
||||
description: >-
|
||||
Configure MCP servers for n8n development. Use when the user says /setup-mcps
|
||||
or asks to set up MCP servers for n8n.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
name: n8n:spec-driven-development
|
||||
description: Keeps implementation and specs in sync. Use when working on a feature that has a spec in .claude/specs/, when the user says /spec, or when starting implementation of a documented feature. Also use when the user asks to verify implementation against a spec or update a spec after changes.
|
||||
---
|
||||
|
||||
|
|
|
|||
1
.claude/skills
Symbolic link
1
.claude/skills
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
plugins/n8n/skills
|
||||
545
.code-health-baseline.json
Normal file
545
.code-health-baseline.json
Normal file
|
|
@ -0,0 +1,545 @@
|
|||
{
|
||||
"version": 1,
|
||||
"generated": "2026-05-12T09:37:31.489Z",
|
||||
"totalViolations": 82,
|
||||
"violations": {
|
||||
"packages/@n8n/ai-workflow-builder.ee/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 73,
|
||||
"message": "langsmith@^0.4.6 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
|
||||
"hash": "6ee5e003d795"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 61,
|
||||
"message": "@mozilla/readability appears in 5 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "d2120f012c93"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 70,
|
||||
"message": "csv-parse appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "94f80b083b76"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 71,
|
||||
"message": "jsdom appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "9c770d66baf2"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 77,
|
||||
"message": "turndown appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "85c311d87491"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 83,
|
||||
"message": "@types/turndown appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "407c8d1b3428"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/json-schema-to-zod/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 63,
|
||||
"message": "zod@^3.25.76 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
|
||||
"hash": "0e18482e8781"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/nodes-langchain/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 292,
|
||||
"message": "openai@^6.34.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
|
||||
"hash": "3c1f53f0afe3"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 299,
|
||||
"message": "tmp-promise appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "88d67e2ef747"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 259,
|
||||
"message": "@mozilla/readability appears in 5 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "69d6fa7e46f9"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 274,
|
||||
"message": "cheerio appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "8cd029bb871e"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 284,
|
||||
"message": "jsdom appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "26f20ebea4b1"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 289,
|
||||
"message": "mongodb appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "46cb48884e22"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 293,
|
||||
"message": "pdf-parse appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "0c7d44a9c2e4"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/tournament/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 44,
|
||||
"message": "@types/node@^18.13.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
|
||||
"hash": "6368b5d3b924"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 52,
|
||||
"message": "typescript@^5.0.0 should use \"catalog:\" (exists in pnpm-workspace.yaml)",
|
||||
"hash": "f668021a144e"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 55,
|
||||
"message": "ast-types appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "27edcbb2b4f8"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 56,
|
||||
"message": "esprima-next appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "75058f9a4d30"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 57,
|
||||
"message": "recast appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "5f2b50fef19d"
|
||||
}
|
||||
],
|
||||
"packages/frontend/@n8n/chat/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 56,
|
||||
"message": "unplugin-icons@^0.19.0 should use \"catalog:frontend\" (exists in pnpm-workspace.yaml [frontend])",
|
||||
"hash": "a0d24d761026"
|
||||
}
|
||||
],
|
||||
"packages/frontend/@n8n/design-system/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 73,
|
||||
"message": "@vueuse/core@* should use \"catalog:frontend\" (exists in pnpm-workspace.yaml [frontend])",
|
||||
"hash": "237e9d17c4ba"
|
||||
}
|
||||
],
|
||||
"packages/cli/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 98,
|
||||
"message": "@ai-sdk/anthropic appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "1e3686e1923b"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 139,
|
||||
"message": "@opentelemetry/sdk-trace-base appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "1cf7f6bcf5d1"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 140,
|
||||
"message": "@opentelemetry/sdk-trace-node appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "a3dad0b8dc21"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 150,
|
||||
"message": "change-case appears in 5 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "949e802528f7"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 202,
|
||||
"message": "prettier appears in 3 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "3cab98902302"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 209,
|
||||
"message": "semver appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "5b7e9b03fb10"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 217,
|
||||
"message": "undici appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "91c29775e961"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 220,
|
||||
"message": "ws appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "cd07242e8163"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 75,
|
||||
"message": "@types/psl appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "6e62e0076b0a"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/agents/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 28,
|
||||
"message": "@ai-sdk/anthropic appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "b58f03d0d5c1"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 50,
|
||||
"message": "@opentelemetry/sdk-trace-base appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "c5c495ac3508"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 51,
|
||||
"message": "@opentelemetry/sdk-trace-node appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "a77ced903cdf"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/instance-ai/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 80,
|
||||
"message": "@ai-sdk/anthropic appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "5b2153508e47"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 86,
|
||||
"message": "@types/psl appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "56dabb51b433"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 56,
|
||||
"message": "@mozilla/readability appears in 5 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "8fa6b9a8fc91"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 64,
|
||||
"message": "csv-parse appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "8f082fc2e8b6"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 71,
|
||||
"message": "turndown appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "9a9d97065952"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 87,
|
||||
"message": "@types/turndown appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "12e346c47b39"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 50,
|
||||
"message": "@joplin/turndown-plugin-gfm appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "a3cf1504b5c2"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 68,
|
||||
"message": "pdf-parse appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "283fa9114c03"
|
||||
}
|
||||
],
|
||||
"packages/node-dev/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 47,
|
||||
"message": "change-case appears in 5 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "6988b9f58c92"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 46,
|
||||
"message": "@oclif/core appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "da9b64834300"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 53,
|
||||
"message": "tmp-promise appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "632a744e397e"
|
||||
}
|
||||
],
|
||||
"packages/nodes-base/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 911,
|
||||
"message": "change-case appears in 5 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "2d1fab7a5b05"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 961,
|
||||
"message": "semver appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "2daf37aa14e4"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 966,
|
||||
"message": "tmp-promise appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "3f93c404ae9c"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 900,
|
||||
"message": "@mozilla/readability appears in 5 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "ca4ac788adc6"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 912,
|
||||
"message": "cheerio appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "1a1b5bbc50c9"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 915,
|
||||
"message": "csv-parse appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "781db4a1e068"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 917,
|
||||
"message": "eventsource appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "9795e6c6d9e9"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 930,
|
||||
"message": "jsdom appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "02341f2b5e3e"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 941,
|
||||
"message": "mongodb appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "f688907d087a"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 892,
|
||||
"message": "eslint-plugin-n8n-nodes-base appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "ac254baa61f9"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/node-cli/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 52,
|
||||
"message": "change-case appears in 5 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "da74ed210d07"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 59,
|
||||
"message": "prettier appears in 3 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "188baf266f61"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 51,
|
||||
"message": "@oclif/core appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "9711a9b00bf9"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 55,
|
||||
"message": "eslint-plugin-n8n-nodes-base appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "6a9e12780943"
|
||||
}
|
||||
],
|
||||
"packages/frontend/editor-ui/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 69,
|
||||
"message": "change-case appears in 5 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "bd9a2eeb072b"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 90,
|
||||
"message": "prettier appears in 3 packages with 3 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "9e9c7ec09a0b"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 92,
|
||||
"message": "semver appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "d8c606e42c92"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 77,
|
||||
"message": "esprima-next appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "62156c2613b2"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/scan-community-package/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 20,
|
||||
"message": "semver appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "ac0e4301d694"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/ai-utilities/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 69,
|
||||
"message": "undici appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "c14cd05614e8"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 65,
|
||||
"message": "tmp-promise appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "884a45bdbcf2"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 72,
|
||||
"message": "n8n-workflow appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "ea4fbfff30ba"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/mcp-browser/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 36,
|
||||
"message": "ws appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "9650c1b55f3c"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 28,
|
||||
"message": "@mozilla/readability appears in 5 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "0c97891a24f4"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 30,
|
||||
"message": "jsdom appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "8466b03b1044"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 35,
|
||||
"message": "turndown appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "f23a9d3d7aa2"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 42,
|
||||
"message": "@types/turndown appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "3f9e46e56803"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 26,
|
||||
"message": "@joplin/turndown-plugin-gfm appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "743e3a7dbb32"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/task-runner/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 50,
|
||||
"message": "ws appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "51cb5069f382"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/benchmark/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 36,
|
||||
"message": "@oclif/core appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "67f9d81d9528"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/cli/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 74,
|
||||
"message": "@oclif/core appears in 4 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "733c3960022e"
|
||||
}
|
||||
],
|
||||
"packages/workflow/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 58,
|
||||
"message": "ast-types appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "1c7d7cf0b0fe"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 60,
|
||||
"message": "esprima-next appears in 3 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "627a716b5d23"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 68,
|
||||
"message": "recast appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "b660317b5f6f"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/computer-use/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 47,
|
||||
"message": "eventsource appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "f50c1eee2ed6"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/eslint-plugin-community-nodes/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 47,
|
||||
"message": "n8n-workflow appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "c5830b76ff8e"
|
||||
}
|
||||
],
|
||||
"packages/@n8n/stylelint-config/package.json": [
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 29,
|
||||
"message": "stylelint appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "955f3fe044c7"
|
||||
},
|
||||
{
|
||||
"rule": "catalog-violations",
|
||||
"line": 45,
|
||||
"message": "stylelint appears in 2 packages with 2 different versions — add to pnpm-workspace.yaml catalog",
|
||||
"hash": "955f3fe044c7"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -38,3 +38,4 @@
|
|||
!packages/@n8n/benchmark/**
|
||||
!packages/@n8n/typescript-config
|
||||
!packages/@n8n/typescript-config/**
|
||||
|
||||
|
|
|
|||
9
.github/CODEOWNERS
vendored
9
.github/CODEOWNERS
vendored
|
|
@ -1,6 +1,5 @@
|
|||
packages/@n8n/db/src/migrations/ @n8n-io/migrations-review
|
||||
.github/workflows @n8n-io/ci-admins
|
||||
.github/scripts @n8n-io/ci-admins
|
||||
.github/actions @n8n-io/ci-admins
|
||||
.github/poutine-rules @n8n-io/ci-admins
|
||||
|
||||
.github/workflows @n8n-io/qa-dx
|
||||
.github/scripts @n8n-io/qa-dx
|
||||
.github/actions @n8n-io/qa-dx
|
||||
.github/poutine-rules @n8n-io/qa-dx
|
||||
|
|
|
|||
232
.github/OWNERS
vendored
Normal file
232
.github/OWNERS
vendored
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
# n8n CODEOWNERS
|
||||
#
|
||||
# Last-match-wins: specific rules MUST come AFTER general rules.
|
||||
|
||||
# Default catch-all (ensures every file gets at least one reviewer)
|
||||
* @n8n-io/catalysts
|
||||
|
||||
# Catalysts
|
||||
|
||||
packages/core/ @n8n-io/catalysts
|
||||
packages/workflow/ @n8n-io/catalysts
|
||||
packages/@n8n/config/ @n8n-io/catalysts
|
||||
packages/@n8n/backend-common/ @n8n-io/catalysts
|
||||
packages/@n8n/backend-test-utils/ @n8n-io/catalysts
|
||||
packages/@n8n/di/ @n8n-io/catalysts
|
||||
packages/@n8n/errors/ @n8n-io/catalysts
|
||||
packages/@n8n/constants/ @n8n-io/catalysts
|
||||
packages/@n8n/utils/ @n8n-io/catalysts
|
||||
packages/@n8n/api-types/ @n8n-io/catalysts
|
||||
packages/@n8n/workflow-sdk/ @n8n-io/instance-ai
|
||||
packages/@n8n/task-runner/ @n8n-io/catalysts
|
||||
packages/@n8n/task-runner-python/ @n8n-io/catalysts
|
||||
packages/@n8n/expression-runtime/ @n8n-io/catalysts
|
||||
packages/@n8n/db/ @n8n-io/catalysts
|
||||
packages/@n8n/json-schema-to-zod/ @n8n-io/catalysts
|
||||
packages/@n8n/crdt/ @n8n-io/catalysts
|
||||
packages/@n8n/extension-sdk/ @n8n-io/catalysts
|
||||
packages/@n8n/eslint-config/ @n8n-io/qa-dx
|
||||
packages/@n8n/typescript-config/ @n8n-io/qa-dx
|
||||
|
||||
packages/@n8n/db/src/migrations/ @n8n-io/migrations-review
|
||||
|
||||
# Top-level paths
|
||||
scripts/ @n8n-io/qa-dx
|
||||
patches/ @n8n-io/qa-dx
|
||||
assets/ @n8n-io/adore
|
||||
security/ @n8n-io/qa-dx
|
||||
|
||||
# @n8n/cli
|
||||
packages/@n8n/cli/ @n8n-io/adore
|
||||
packages/@n8n/cli/src/commands/credential/ @n8n-io/iam
|
||||
packages/@n8n/cli/src/commands/user/ @n8n-io/iam
|
||||
packages/@n8n/cli/src/commands/data-table/ @n8n-io/adore
|
||||
packages/@n8n/cli/src/commands/tag/ @n8n-io/adore
|
||||
packages/@n8n/cli/src/commands/project/ @n8n-io/ligo
|
||||
packages/@n8n/cli/src/commands/source-control/ @n8n-io/ligo
|
||||
packages/@n8n/cli/src/commands/variable/ @n8n-io/ligo
|
||||
packages/@n8n/cli/src/commands/skill/ @n8n-io/ai
|
||||
|
||||
# packages/cli
|
||||
packages/cli/ @n8n-io/catalysts
|
||||
packages/cli/src/scaling/ @n8n-io/catalysts
|
||||
packages/cli/src/concurrency/ @n8n-io/catalysts
|
||||
packages/cli/src/execution-lifecycle/ @n8n-io/catalysts
|
||||
packages/cli/src/executions/ @n8n-io/catalysts
|
||||
packages/cli/src/task-runners/ @n8n-io/catalysts
|
||||
packages/cli/src/webhooks/ @n8n-io/catalysts
|
||||
packages/cli/src/push/ @n8n-io/catalysts
|
||||
packages/cli/src/commands/ @n8n-io/catalysts
|
||||
packages/cli/src/config/ @n8n-io/catalysts
|
||||
packages/cli/src/eventbus/ @n8n-io/catalysts
|
||||
packages/cli/src/events/ @n8n-io/catalysts
|
||||
packages/cli/src/security-audit/ @n8n-io/catalysts
|
||||
packages/cli/src/modules/workflow-index/ @n8n-io/catalysts
|
||||
packages/cli/src/modules/breaking-changes/ @n8n-io/catalysts
|
||||
packages/cli/src/modules/otel/ @n8n-io/ligo
|
||||
|
||||
packages/cli/src/auth/ @n8n-io/iam
|
||||
packages/cli/src/credentials/ @n8n-io/iam
|
||||
packages/cli/src/mfa/ @n8n-io/iam
|
||||
packages/cli/src/oauth/ @n8n-io/iam
|
||||
packages/cli/src/permissions.ee/ @n8n-io/iam
|
||||
packages/cli/src/sso.ee/ @n8n-io/iam
|
||||
packages/cli/src/user-management/ @n8n-io/iam
|
||||
packages/cli/src/license/ @n8n-io/iam
|
||||
packages/cli/src/modules/ldap.ee/ @n8n-io/iam
|
||||
packages/cli/src/modules/log-streaming.ee/ @n8n-io/iam
|
||||
packages/cli/src/modules/sso-oidc/ @n8n-io/iam
|
||||
packages/cli/src/modules/sso-saml/ @n8n-io/iam
|
||||
packages/cli/src/modules/provisioning.ee/ @n8n-io/iam
|
||||
packages/cli/src/modules/dynamic-credentials.ee/ @n8n-io/iam
|
||||
packages/cli/src/modules/redaction/ @n8n-io/iam
|
||||
packages/cli/src/modules/instance-registry/ @n8n-io/iam
|
||||
packages/cli/src/modules/token-exchange/ @n8n-io/iam
|
||||
|
||||
packages/cli/src/environments.ee/ @n8n-io/ligo
|
||||
packages/cli/src/public-api/ @n8n-io/ligo
|
||||
packages/cli/src/modules/source-control.ee/ @n8n-io/ligo
|
||||
packages/cli/src/modules/external-secrets.ee/ @n8n-io/ligo
|
||||
packages/cli/src/modules/insights/ @n8n-io/ligo
|
||||
|
||||
packages/cli/src/collaboration/ @n8n-io/catalysts
|
||||
packages/cli/src/binary-data/ @n8n-io/catalysts
|
||||
packages/cli/src/posthog/ @n8n-io/adore
|
||||
packages/cli/src/modules/data-table/ @n8n-io/adore
|
||||
|
||||
packages/cli/src/evaluation.ee/ @n8n-io/ai
|
||||
packages/cli/src/chat/ @n8n-io/ai
|
||||
packages/cli/src/tool-generation/ @n8n-io/ai
|
||||
packages/cli/src/modules/workflow-builder/ @n8n-io/ai
|
||||
packages/cli/src/modules/mcp/ @n8n-io/ai
|
||||
packages/cli/src/modules/quick-connect/ @n8n-io/ai
|
||||
packages/cli/src/modules/chat-hub/ @n8n-io/ai
|
||||
packages/cli/src/modules/instance-ai/ @n8n-io/instance-ai
|
||||
|
||||
packages/cli/src/modules/community-packages/ @n8n-io/nodes
|
||||
|
||||
# CLI controllers
|
||||
packages/cli/src/controllers/auth.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/invitation.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/me.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/mfa.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/owner.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/password-reset.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/role.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/users.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/user-settings.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/api-keys.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/security-settings.controller.ts @n8n-io/iam
|
||||
packages/cli/src/controllers/oauth/ @n8n-io/iam
|
||||
packages/cli/src/controllers/ai.controller.ts @n8n-io/ai
|
||||
packages/cli/src/controllers/annotation-tags.controller.ee.ts @n8n-io/ai
|
||||
packages/cli/src/controllers/cta.controller.ts @n8n-io/adore
|
||||
packages/cli/src/controllers/folder.controller.ts @n8n-io/adore
|
||||
packages/cli/src/controllers/tags.controller.ts @n8n-io/adore
|
||||
packages/cli/src/controllers/binary-data.controller.ts @n8n-io/adore
|
||||
packages/cli/src/controllers/dynamic-templates.controller.ts @n8n-io/adore
|
||||
packages/cli/src/controllers/posthog.controller.ts @n8n-io/adore
|
||||
packages/cli/src/controllers/translation.controller.ts @n8n-io/adore
|
||||
packages/cli/src/controllers/project.controller.ts @n8n-io/ligo
|
||||
packages/cli/src/controllers/workflow-statistics.controller.ts @n8n-io/ligo
|
||||
packages/cli/src/controllers/node-types.controller.ts @n8n-io/nodes
|
||||
packages/cli/src/controllers/dynamic-node-parameters.controller.ts @n8n-io/nodes
|
||||
packages/cli/src/controllers/e2e.controller.ts @n8n-io/qa-dx
|
||||
|
||||
# CLI services
|
||||
packages/cli/src/services/jwt.service.ts @n8n-io/iam
|
||||
packages/cli/src/services/user.service.ts @n8n-io/iam
|
||||
packages/cli/src/services/role.service.ts @n8n-io/iam
|
||||
packages/cli/src/services/role-cache.service.ts @n8n-io/iam
|
||||
packages/cli/src/services/password.utility.ts @n8n-io/iam
|
||||
packages/cli/src/services/public-api-key.service.ts @n8n-io/iam
|
||||
packages/cli/src/services/security-settings.service.ts @n8n-io/iam
|
||||
packages/cli/src/services/ssrf/ @n8n-io/catalysts
|
||||
packages/cli/src/services/static-auth-service.ts @n8n-io/iam
|
||||
packages/cli/src/services/access.service.ts @n8n-io/iam
|
||||
packages/cli/src/services/ai.service.ts @n8n-io/ai
|
||||
packages/cli/src/services/ai-usage.service.ts @n8n-io/ai
|
||||
packages/cli/src/services/ai-workflow-builder.service.ts @n8n-io/ai
|
||||
packages/cli/src/services/annotation-tag.service.ee.ts @n8n-io/ai
|
||||
packages/cli/src/services/folder.service.ts @n8n-io/adore
|
||||
packages/cli/src/services/tag.service.ts @n8n-io/adore
|
||||
packages/cli/src/services/cta.service.ts @n8n-io/adore
|
||||
packages/cli/src/services/dynamic-templates.service.ts @n8n-io/adore
|
||||
packages/cli/src/services/frontend.service.ts @n8n-io/adore
|
||||
packages/cli/src/services/banner.service.ts @n8n-io/adore
|
||||
packages/cli/src/services/project.service.ee.ts @n8n-io/ligo
|
||||
packages/cli/src/services/workflow-statistics.service.ts @n8n-io/ligo
|
||||
packages/cli/src/services/export.service.ts @n8n-io/ligo
|
||||
packages/cli/src/services/import.service.ts @n8n-io/ligo
|
||||
packages/cli/src/services/ownership.service.ts @n8n-io/ligo
|
||||
packages/cli/src/services/dynamic-node-parameters.service.ts @n8n-io/nodes
|
||||
|
||||
# Adore
|
||||
|
||||
packages/frontend/editor-ui/ @n8n-io/frontend
|
||||
packages/frontend/editor-ui/src/features/ai/ @n8n-io/ai
|
||||
packages/frontend/editor-ui/src/features/credentials/ @n8n-io/iam
|
||||
packages/frontend/editor-ui/src/features/execution/ @n8n-io/ligo
|
||||
packages/frontend/editor-ui/src/features/project-roles/ @n8n-io/iam
|
||||
packages/frontend/editor-ui/src/features/integrations/ @n8n-io/nodes
|
||||
|
||||
packages/frontend/@n8n/design-system/ @n8n-io/design
|
||||
packages/frontend/@n8n/stores/ @n8n-io/frontend
|
||||
packages/frontend/@n8n/composables/ @n8n-io/frontend
|
||||
packages/frontend/@n8n/rest-api-client/ @n8n-io/frontend
|
||||
packages/frontend/@n8n/storybook/ @n8n-io/design
|
||||
packages/frontend/@n8n/i18n/ @n8n-io/frontend
|
||||
packages/@n8n/stylelint-config/ @n8n-io/qa-dx
|
||||
|
||||
# AI
|
||||
|
||||
packages/@n8n/instance-ai/ @n8n-io/instance-ai
|
||||
packages/@n8n/nodes-langchain/ @n8n-io/ai
|
||||
packages/@n8n/ai-utilities/ @n8n-io/ai
|
||||
packages/@n8n/ai-node-sdk/ @n8n-io/ai
|
||||
packages/@n8n/ai-workflow-builder.ee/ @n8n-io/ai
|
||||
packages/@n8n/agents/ @n8n-io/ai
|
||||
packages/frontend/@n8n/chat/ @n8n-io/ai
|
||||
|
||||
# Chat
|
||||
|
||||
packages/@n8n/chat-hub/ @n8n-io/ai
|
||||
|
||||
# Nodes
|
||||
|
||||
packages/@n8n/codemirror-lang/ @n8n-io/nodes
|
||||
packages/@n8n/codemirror-lang-html/ @n8n-io/nodes
|
||||
packages/@n8n/codemirror-lang-sql/ @n8n-io/nodes
|
||||
packages/nodes-base/ @n8n-io/nodes
|
||||
packages/@n8n/decorators/ @n8n-io/catalysts
|
||||
packages/node-dev/ @n8n-io/nodes
|
||||
packages/@n8n/create-node/ @n8n-io/nodes
|
||||
packages/@n8n/node-cli/ @n8n-io/nodes
|
||||
packages/@n8n/imap/ @n8n-io/iam
|
||||
packages/@n8n/syslog-client/ @n8n-io/iam
|
||||
packages/@n8n/scan-community-package/ @n8n-io/nodes
|
||||
packages/@n8n/eslint-plugin-community-nodes/ @n8n-io/nodes
|
||||
packages/@n8n/computer-use/ @n8n-io/nodes
|
||||
packages/@n8n/local-gateway/ @n8n-io/nodes
|
||||
packages/@n8n/mcp-browser/ @n8n-io/nodes
|
||||
packages/@n8n/mcp-browser-extension/ @n8n-io/nodes
|
||||
|
||||
# IAM
|
||||
|
||||
packages/@n8n/permissions/ @n8n-io/iam
|
||||
packages/@n8n/client-oauth2/ @n8n-io/iam
|
||||
|
||||
# LiGo
|
||||
|
||||
packages/extensions/insights/ @n8n-io/ligo
|
||||
|
||||
# CI/CD
|
||||
|
||||
.github/ @n8n-io/qa-dx
|
||||
docker/ @n8n-io/qa-dx
|
||||
|
||||
# QA
|
||||
|
||||
packages/testing/ @n8n-io/qa-dx
|
||||
packages/@n8n/benchmark/ @n8n-io/qa-dx
|
||||
packages/@n8n/vitest-config/ @n8n-io/qa-dx
|
||||
12
.github/WORKFLOWS.md
vendored
12
.github/WORKFLOWS.md
vendored
|
|
@ -242,8 +242,7 @@ CALLER REUSABLE WORKFLOW
|
|||
ci-pull-requests.yml
|
||||
├──────────────────────────▶ test-unit-reusable.yml
|
||||
├──────────────────────────▶ test-linting-reusable.yml
|
||||
├──────────────────────────▶ test-e2e-ci-reusable.yml
|
||||
│ └──────────▶ test-e2e-reusable.yml
|
||||
├──────────────────────────▶ test-e2e-reusable.yml
|
||||
└──────────────────────────▶ sec-ci-reusable.yml
|
||||
└──────────▶ sec-poutine-reusable.yml
|
||||
|
||||
|
|
@ -260,8 +259,7 @@ test-workflows-nightly.yml
|
|||
└──────────────────────────▶ test-workflows-callable.yml
|
||||
|
||||
test-e2e-vm-expressions-nightly.yml
|
||||
└──────────────────────────▶ test-e2e-ci-reusable.yml
|
||||
└──────────▶ test-e2e-reusable.yml
|
||||
└──────────────────────────▶ test-e2e-reusable.yml
|
||||
|
||||
PR Comment Dispatchers (triggered by /command in PR comments):
|
||||
test-workflows-pr-comment.yml
|
||||
|
|
@ -421,8 +419,6 @@ Workflows with `workflow_call` trigger:
|
|||
| `test-unit-reusable.yml` | `ref`, `nodeVersion`, `collectCoverage` | Unit tests |
|
||||
| `test-linting-reusable.yml` | `ref`, `nodeVersion` | ESLint |
|
||||
| `test-e2e-reusable.yml` | `branch`, `test-mode`, `shards`, `runner` | Core E2E executor |
|
||||
| `test-e2e-ci-reusable.yml` | `branch` | E2E orchestrator |
|
||||
| `test-e2e-docker-pull-reusable.yml`| `branch`, `n8n_version` | E2E with pulled image |
|
||||
| `test-workflows-callable.yml` | `git_ref`, `compare_schemas` | Workflow tests |
|
||||
| `docker-build-push.yml` | `n8n_version`, `release_type`, `push_enabled` | Docker build |
|
||||
| `sec-ci-reusable.yml` | `ref` | Security orchestrator |
|
||||
|
|
@ -491,7 +487,7 @@ Team ownership mappings in `CODEOWNERS`:
|
|||
| `ubuntu-latest` | 2 | Simple jobs, fork PR E2E |
|
||||
| `blacksmith-2vcpu-ubuntu-2204` | 2 | Standard builds, E2E shards |
|
||||
| `blacksmith-4vcpu-ubuntu-2204` | 4 | Unit tests, typecheck, lint |
|
||||
| `blacksmith-8vcpu-ubuntu-2204` | 8 | E2E coverage (weekly) |
|
||||
| `blacksmith-8vcpu-ubuntu-2204` | 8 | Heavy parallel workloads |
|
||||
| `blacksmith-4vcpu-ubuntu-2204-arm` | 4 | ARM64 Docker builds |
|
||||
|
||||
### Selection Guidelines
|
||||
|
|
@ -504,7 +500,7 @@ Team ownership mappings in `CODEOWNERS`:
|
|||
|
||||
**`blacksmith-4vcpu-ubuntu-2204`** - Unit tests (parallelized), linting (parallel file processing), typechecking (CPU-intensive), E2E test shards
|
||||
|
||||
**`blacksmith-8vcpu-ubuntu-2204`** - Heavy parallel workloads, full E2E coverage runs
|
||||
**`blacksmith-8vcpu-ubuntu-2204`** - Heavy parallel workloads
|
||||
|
||||
### Runner Provider Toggle
|
||||
|
||||
|
|
|
|||
51
.github/actions/build-n8n-docker/action.yml
vendored
Normal file
51
.github/actions/build-n8n-docker/action.yml
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Builds the n8n and runners CI test images (n8nio/n8n:local +
|
||||
# n8nio/runners:local) and caches the tarball under a SHA-derived key so
|
||||
# downstream jobs in the same workflow run can restore it via
|
||||
# load-n8n-docker.
|
||||
#
|
||||
# Cache-aware: if the tarball for this SHA is already cached (e.g. a parent
|
||||
# workflow ran prepare-docker earlier), the build step is skipped and this
|
||||
# action becomes a no-op.
|
||||
|
||||
name: 'Build n8n CI Docker image'
|
||||
description: 'Builds n8nio/n8n + n8nio/runners test images and publishes them as a SHA-keyed cache tarball for downstream shards.'
|
||||
|
||||
inputs:
|
||||
build-variant:
|
||||
description: 'standard or coverage. Coverage uses build:docker:coverage.'
|
||||
required: false
|
||||
default: 'standard'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Check cache for existing image
|
||||
id: cache-check
|
||||
uses: actions/cache/restore@640a1c2554105b57832a23eea0b4672fc7a790d5 # v4.2.3
|
||||
with:
|
||||
key: n8n-docker-image-${{ github.sha }}
|
||||
path: /tmp/n8n-image.tar.zst
|
||||
lookup-only: true
|
||||
|
||||
- name: Build Docker image
|
||||
if: steps.cache-check.outputs.cache-hit != 'true'
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: ${{ inputs.build-variant == 'coverage' && 'pnpm build:docker:coverage' || 'pnpm build:docker' }}
|
||||
enable-docker-cache: true
|
||||
env:
|
||||
INCLUDE_TEST_CONTROLLER: 'true'
|
||||
|
||||
- name: Save image tarball
|
||||
if: steps.cache-check.outputs.cache-hit != 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
docker save n8nio/n8n:local n8nio/runners:local | zstd -T0 -3 -o /tmp/n8n-image.tar.zst
|
||||
ls -lh /tmp/n8n-image.tar.zst
|
||||
|
||||
- name: Publish image tarball to cache
|
||||
if: steps.cache-check.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@640a1c2554105b57832a23eea0b4672fc7a790d5 # v4.2.3
|
||||
with:
|
||||
key: n8n-docker-image-${{ github.sha }}
|
||||
path: /tmp/n8n-image.tar.zst
|
||||
|
|
@ -1,6 +1,10 @@
|
|||
import { describe, it } from 'node:test';
|
||||
import { describe, it, before, after } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { matchGlob, parseFilters, evaluateFilter, runValidate } from '../ci-filter.mjs';
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { matchGlob, parseFilters, evaluateFilter, runValidate, getChangedFiles, getMergeBase } from '../ci-filter.mjs';
|
||||
|
||||
// --- matchGlob ---
|
||||
|
||||
|
|
@ -172,6 +176,70 @@ describe('evaluateFilter', () => {
|
|||
});
|
||||
});
|
||||
|
||||
// --- getChangedFiles + getMergeBase (integration, exercises real git) ---
|
||||
|
||||
describe('getChangedFiles', () => {
|
||||
const repoDir = mkdtempSync(join(tmpdir(), 'ci-filter-'));
|
||||
const remoteDir = mkdtempSync(join(tmpdir(), 'ci-filter-remote-'));
|
||||
const originalCwd = process.cwd();
|
||||
const git = (args: string[], cwd: string = repoDir) =>
|
||||
execFileSync('git', args, { cwd, stdio: 'pipe' }).toString().trim();
|
||||
|
||||
before(() => {
|
||||
// Bare remote so the action's `git fetch origin <ref>` works
|
||||
execFileSync('git', ['init', '--bare', '-b', 'main', remoteDir], { stdio: 'pipe' });
|
||||
git(['init', '-b', 'main'], repoDir);
|
||||
git(['config', 'user.email', 'test@test.local']);
|
||||
git(['config', 'user.name', 'test']);
|
||||
git(['remote', 'add', 'origin', remoteDir]);
|
||||
|
||||
// Common ancestor commit
|
||||
writeFileSync(join(repoDir, 'shared.ts'), 'shared\n');
|
||||
git(['add', '.']);
|
||||
git(['commit', '-m', 'root']);
|
||||
git(['push', 'origin', 'main']);
|
||||
|
||||
// PR branches off main, adds a file
|
||||
git(['checkout', '-b', 'pr-branch']);
|
||||
writeFileSync(join(repoDir, 'pr-only.ts'), 'pr\n');
|
||||
git(['add', '.']);
|
||||
git(['commit', '-m', 'PR change']);
|
||||
|
||||
// Master drifts forward, modifying shared.ts (the pre-fix bug surface)
|
||||
git(['checkout', 'main']);
|
||||
writeFileSync(join(repoDir, 'shared.ts'), 'shared\ndrift-from-master\n');
|
||||
git(['commit', '-am', 'master moves']);
|
||||
git(['push', 'origin', 'main']);
|
||||
|
||||
// Sit on the PR branch as if running CI
|
||||
git(['checkout', 'pr-branch']);
|
||||
process.chdir(repoDir);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
process.chdir(originalCwd);
|
||||
rmSync(repoDir, { recursive: true, force: true });
|
||||
rmSync(remoteDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
it('returns only PR-introduced files (master drift does not pollute)', () => {
|
||||
const changed = getChangedFiles('main');
|
||||
assert.deepEqual(changed, ['pr-only.ts']);
|
||||
});
|
||||
|
||||
it('getMergeBase returns the common ancestor commit', () => {
|
||||
const mergeBase = getMergeBase();
|
||||
assert.match(mergeBase, /^[a-f0-9]{40}$/);
|
||||
const expected = git(['merge-base', 'FETCH_HEAD', 'HEAD']);
|
||||
assert.equal(mergeBase, expected);
|
||||
});
|
||||
|
||||
it('rejects unsafe base refs', () => {
|
||||
assert.throws(() => getChangedFiles('main; rm -rf /'), /Unsafe/);
|
||||
assert.throws(() => getChangedFiles('main$evil'), /Unsafe/);
|
||||
});
|
||||
});
|
||||
|
||||
// --- runValidate ---
|
||||
|
||||
describe('runValidate', () => {
|
||||
|
|
|
|||
9
.github/actions/ci-filter/action.yml
vendored
9
.github/actions/ci-filter/action.yml
vendored
|
|
@ -24,6 +24,15 @@ outputs:
|
|||
results:
|
||||
description: 'JSON object: { "filter-name": true/false }'
|
||||
value: ${{ steps.run.outputs.results }}
|
||||
changed-files:
|
||||
description: 'Newline-separated list of changed files (filter mode only)'
|
||||
value: ${{ steps.run.outputs.changed-files }}
|
||||
base-ref:
|
||||
description: 'Resolved base ref used for the diff (filter mode only)'
|
||||
value: ${{ steps.run.outputs.base-ref }}
|
||||
merge-base:
|
||||
description: 'Merge-base SHA between FETCH_HEAD and HEAD (filter mode only)'
|
||||
value: ${{ steps.run.outputs.merge-base }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
|
|
|
|||
25
.github/actions/ci-filter/ci-filter.mjs
vendored
25
.github/actions/ci-filter/ci-filter.mjs
vendored
|
|
@ -98,14 +98,30 @@ export function getChangedFiles(baseRef) {
|
|||
if (!SAFE_REF.test(baseRef)) {
|
||||
throw new Error(`Unsafe base ref: "${baseRef}"`);
|
||||
}
|
||||
execSync(`git fetch --depth=1 origin ${baseRef}`, { stdio: 'pipe' });
|
||||
const output = execSync('git diff --name-only FETCH_HEAD HEAD', { encoding: 'utf-8' });
|
||||
// Deepen the fetch so the merge base is reachable from this shallow clone.
|
||||
// A 2-dot diff (FETCH_HEAD HEAD) reports anything that differs in either
|
||||
// direction, so files added to base-branch after the PR diverged show up as
|
||||
// "changed" — spuriously triggering path-filtered jobs. The merge base
|
||||
// scopes the diff to PR-only changes.
|
||||
execSync(`git fetch --no-tags --prune --deepen=200 origin ${baseRef}`, { stdio: 'pipe' });
|
||||
const output = execSync('git diff --name-only --merge-base FETCH_HEAD HEAD', {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
return output
|
||||
.split('\n')
|
||||
.map((f) => f.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the merge-base SHA between FETCH_HEAD and HEAD.
|
||||
* Used to give downstream tools (e.g. janitor's AST diff) a stable, PR-only
|
||||
* comparison point that doesn't drift when the base branch moves forward.
|
||||
*/
|
||||
export function getMergeBase() {
|
||||
return execSync('git merge-base FETCH_HEAD HEAD', { encoding: 'utf-8' }).trim();
|
||||
}
|
||||
|
||||
// --- Filter evaluation ---
|
||||
|
||||
/**
|
||||
|
|
@ -155,7 +171,9 @@ export function runFilter() {
|
|||
|
||||
const filters = parseFilters(filtersInput);
|
||||
const changedFiles = getChangedFiles(baseRef);
|
||||
const mergeBase = getMergeBase();
|
||||
|
||||
console.log(`Merge base: ${mergeBase}`);
|
||||
console.log(`Changed files (${changedFiles.length}):`);
|
||||
for (const f of changedFiles) {
|
||||
console.log(` ${f}`);
|
||||
|
|
@ -170,6 +188,9 @@ export function runFilter() {
|
|||
}
|
||||
|
||||
setOutput('results', JSON.stringify(results));
|
||||
setOutput('changed-files', changedFiles.join('\n'));
|
||||
setOutput('base-ref', baseRef);
|
||||
setOutput('merge-base', mergeBase);
|
||||
}
|
||||
|
||||
// --- Mode: validate ---
|
||||
|
|
|
|||
11
.github/actions/docker-registry-login/action.yml
vendored
11
.github/actions/docker-registry-login/action.yml
vendored
|
|
@ -39,10 +39,13 @@ runs:
|
|||
|
||||
- name: Login to DockerHub
|
||||
if: inputs.login-dockerhub == 'true'
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
username: ${{ inputs.dockerhub-username }}
|
||||
password: ${{ inputs.dockerhub-password }}
|
||||
shell: bash
|
||||
env:
|
||||
DOCKER_USER: ${{ inputs.dockerhub-username }}
|
||||
DOCKER_PASS: ${{ inputs.dockerhub-password }}
|
||||
run: |
|
||||
node .github/scripts/retry.mjs --attempts 3 --delay 10 \
|
||||
'echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin'
|
||||
|
||||
- name: Login to DHI Registry
|
||||
if: inputs.login-dhi == 'true'
|
||||
|
|
|
|||
23
.github/actions/load-n8n-docker/action.yml
vendored
Normal file
23
.github/actions/load-n8n-docker/action.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Restores the n8n + runners image tarball (produced by build-n8n-docker
|
||||
# under the SHA-derived cache key) and loads both images into the local
|
||||
# docker daemon.
|
||||
#
|
||||
# After this action runs, `n8nio/n8n:local` and `n8nio/runners:local` are
|
||||
# present on the runner.
|
||||
|
||||
name: 'Load n8n Docker images from cache'
|
||||
description: 'Restores the zstd-compressed n8n + runners image tarball from the SHA-keyed GHA cache and loads both images into the local docker daemon.'
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: Restore image tarball from cache
|
||||
uses: actions/cache/restore@640a1c2554105b57832a23eea0b4672fc7a790d5 # v4.2.3
|
||||
with:
|
||||
key: n8n-docker-image-${{ github.sha }}
|
||||
path: /tmp/n8n-image.tar.zst
|
||||
fail-on-cache-miss: true
|
||||
|
||||
- name: Load n8n and runners images into docker
|
||||
shell: bash
|
||||
run: zstd -d -c /tmp/n8n-image.tar.zst | docker load
|
||||
31
.github/actions/setup-nodejs/action.yml
vendored
31
.github/actions/setup-nodejs/action.yml
vendored
|
|
@ -45,13 +45,19 @@ runs:
|
|||
mkdir -p "$PNPM_STORE_PATH"
|
||||
fi
|
||||
|
||||
- name: Install Aikido SafeChain
|
||||
if: runner.os != 'Windows'
|
||||
- name: Configure SafeChain
|
||||
shell: bash
|
||||
run: |
|
||||
VERSION="1.4.1"
|
||||
EXPECTED_SHA256="628235987175072a4255aa3f5f0128f31795b63970f1970ae8a04d07bf8527b0"
|
||||
node .github/scripts/retry.mjs --attempts 3 --delay 10 \
|
||||
"curl -fsSL -o install-safe-chain.sh https://github.com/AikidoSec/safe-chain/releases/download/${VERSION}/install-safe-chain.sh"
|
||||
# SafeChain only reads configs from this directory https://github.com/AikidoSec/safe-chain#configuration-options-1
|
||||
mkdir -p "$HOME/.safe-chain"
|
||||
cp "${{ github.action_path }}/safe-chain.config.json" "$HOME/.safe-chain/config.json"
|
||||
|
||||
- name: Install Aikido SafeChain
|
||||
run: |
|
||||
VERSION="1.5.1"
|
||||
EXPECTED_SHA256="7c910fff717649c86cc8ca960e6c054d3734da2d660050e3bcfc54029e3b485b"
|
||||
node .github/scripts/retry.mjs --attempts 3 --delay 10 -- \
|
||||
curl -fsSL -o install-safe-chain.sh "https://github.com/AikidoSec/safe-chain/releases/download/${VERSION}/install-safe-chain.sh"
|
||||
echo "${EXPECTED_SHA256} install-safe-chain.sh" | sha256sum -c -
|
||||
sh install-safe-chain.sh --ci
|
||||
rm install-safe-chain.sh
|
||||
|
|
@ -59,13 +65,10 @@ runs:
|
|||
|
||||
- name: Install Dependencies
|
||||
if: ${{ inputs.install-command != '' }}
|
||||
env:
|
||||
INSTALL_COMMAND: ${{ inputs.install-command }}
|
||||
run: |
|
||||
${{ inputs.install-command }}
|
||||
shell: bash
|
||||
|
||||
- name: Disable safe-chain
|
||||
if: runner.os != 'Windows'
|
||||
run: safe-chain teardown
|
||||
$INSTALL_COMMAND
|
||||
shell: bash
|
||||
|
||||
- name: Configure Turborepo Cache
|
||||
|
|
@ -81,8 +84,10 @@ runs:
|
|||
|
||||
- name: Build Project
|
||||
if: ${{ inputs.build-command != '' }}
|
||||
env:
|
||||
BUILD_COMMAND: ${{ inputs.build-command }}
|
||||
run: |
|
||||
${{ inputs.build-command }} --summarize
|
||||
$BUILD_COMMAND --summarize
|
||||
node .github/scripts/send-build-stats.mjs || true
|
||||
node .github/scripts/send-docker-stats.mjs || true
|
||||
shell: bash
|
||||
|
|
|
|||
16
.github/actions/setup-nodejs/safe-chain.config.json
vendored
Normal file
16
.github/actions/setup-nodejs/safe-chain.config.json
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"npm": {
|
||||
"minimumPackageAgeExclusions": [
|
||||
"@n8n/*",
|
||||
"@n8n_io/*",
|
||||
"n8n",
|
||||
"n8n-containers",
|
||||
"n8n-core",
|
||||
"n8n-editor-ui",
|
||||
"n8n-node-dev",
|
||||
"n8n-nodes-base",
|
||||
"n8n-playwright",
|
||||
"n8n-workflow"
|
||||
]
|
||||
}
|
||||
}
|
||||
333
.github/scripts/bump-versions.mjs
vendored
333
.github/scripts/bump-versions.mjs
vendored
|
|
@ -1,4 +1,5 @@
|
|||
import semver from 'semver';
|
||||
import { parse } from 'yaml';
|
||||
import { writeFile, readFile } from 'fs/promises';
|
||||
import { resolve } from 'path';
|
||||
import child_process from 'child_process';
|
||||
|
|
@ -7,14 +8,19 @@ import assert from 'assert';
|
|||
|
||||
const exec = promisify(child_process.exec);
|
||||
|
||||
function generateExperimentalVersion(currentVersion) {
|
||||
/**
|
||||
* @param {string | semver.SemVer} currentVersion
|
||||
*/
|
||||
export function generateExperimentalVersion(currentVersion) {
|
||||
const parsed = semver.parse(currentVersion);
|
||||
if (!parsed) throw new Error(`Invalid version: ${currentVersion}`);
|
||||
|
||||
// Check if it's already an experimental version
|
||||
if (parsed.prerelease.length > 0 && parsed.prerelease[0] === 'exp') {
|
||||
const minor = parsed.prerelease[1] || 0;
|
||||
const minorInt = typeof minor === 'string' ? parseInt(minor) : minor;
|
||||
// Increment the experimental minor version
|
||||
const expMinor = (parsed.prerelease[1] || 0) + 1;
|
||||
const expMinor = minorInt + 1;
|
||||
return `${parsed.major}.${parsed.minor}.${parsed.patch}-exp.${expMinor}`;
|
||||
}
|
||||
|
||||
|
|
@ -22,96 +28,267 @@ function generateExperimentalVersion(currentVersion) {
|
|||
return `${parsed.major}.${parsed.minor}.${parsed.patch}-exp.0`;
|
||||
}
|
||||
|
||||
const rootDir = process.cwd();
|
||||
const releaseType = process.env.RELEASE_TYPE;
|
||||
assert.match(releaseType, /^(patch|minor|major|experimental|premajor)$/, 'Invalid RELEASE_TYPE');
|
||||
|
||||
// TODO: if releaseType is `auto` determine release type based on the changelog
|
||||
|
||||
const lastTag = (await exec('git describe --tags --match "n8n@*" --abbrev=0')).stdout.trim();
|
||||
const packages = JSON.parse(
|
||||
(
|
||||
await exec(
|
||||
`pnpm ls -r --only-projects --json | jq -r '[.[] | { name: .name, version: .version, path: .path, private: .private}]'`,
|
||||
)
|
||||
).stdout,
|
||||
);
|
||||
|
||||
const packageMap = {};
|
||||
for (let { name, path, version, private: isPrivate } of packages) {
|
||||
if (isPrivate && path !== rootDir) continue;
|
||||
if (path === rootDir) name = 'monorepo-root';
|
||||
|
||||
const isDirty = await exec(`git diff --quiet HEAD ${lastTag} -- ${path}`)
|
||||
.then(() => false)
|
||||
.catch((error) => true);
|
||||
|
||||
packageMap[name] = { path, isDirty, version };
|
||||
/**
|
||||
* @param {{ pnpm?: { overrides?: Record<string, string> }, overrides?: Record<string, string> }} pkg
|
||||
* @returns {Record<string, string>}
|
||||
*/
|
||||
export function getOverrides(pkg) {
|
||||
return { ...pkg.pnpm?.overrides, ...pkg.overrides };
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
Object.values(packageMap).some(({ isDirty }) => isDirty),
|
||||
'No changes found since the last release',
|
||||
);
|
||||
|
||||
// Propagate isDirty transitively: if a package's dependency will be bumped,
|
||||
// that package also needs a bump (e.g. design-system → editor-ui → cli).
|
||||
|
||||
const depsByPackage = {};
|
||||
for (const packageName in packageMap) {
|
||||
const packageFile = resolve(packageMap[packageName].path, 'package.json');
|
||||
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
|
||||
depsByPackage[packageName] = Object.keys(packageJson.dependencies || {});
|
||||
/**
|
||||
* @param {string} content
|
||||
* @returns {Record<string, unknown>}
|
||||
*/
|
||||
export function parseWorkspaceYaml(content) {
|
||||
try {
|
||||
return /** @type {Record<string, unknown>} */ (parse(content) ?? {});
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const packageName in packageMap) {
|
||||
/**
|
||||
* @param {Record<string, unknown>} ws
|
||||
* @returns {Map<string, Record<string, string>>}
|
||||
*/
|
||||
export function getCatalogs(ws) {
|
||||
const result = new Map();
|
||||
if (ws.catalog) {
|
||||
result.set('default', /** @type {Record<string,string>} */ (ws.catalog));
|
||||
}
|
||||
|
||||
for (const [name, entries] of Object.entries(ws.catalogs ?? {})) {
|
||||
result.set(name, entries);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, string>} currentOverrides
|
||||
* @param {Record<string, string>} previousOverrides
|
||||
* @returns {Set<string>}
|
||||
*/
|
||||
export function computeChangedOverrides(currentOverrides, previousOverrides) {
|
||||
return new Set(
|
||||
Object.keys({ ...currentOverrides, ...previousOverrides }).filter(
|
||||
(k) => currentOverrides[k] !== previousOverrides[k],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Map<string, Record<string, string>>} currentCatalogs
|
||||
* @param {Map<string, Record<string, string>>} previousCatalogs
|
||||
* @returns {Map<string, Set<string>>}
|
||||
*/
|
||||
export function computeChangedCatalogEntries(currentCatalogs, previousCatalogs) {
|
||||
const changedCatalogEntries = new Map();
|
||||
for (const catalogName of new Set([...currentCatalogs.keys(), ...previousCatalogs.keys()])) {
|
||||
const current = currentCatalogs.get(catalogName) ?? {};
|
||||
const previous = previousCatalogs.get(catalogName) ?? {};
|
||||
const changedDeps = new Set(
|
||||
Object.keys({ ...current, ...previous }).filter((dep) => current[dep] !== previous[dep]),
|
||||
);
|
||||
if (changedDeps.size > 0) {
|
||||
changedCatalogEntries.set(catalogName, changedDeps);
|
||||
}
|
||||
}
|
||||
return changedCatalogEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark packages as dirty if any dep had a root-level override or catalog version change.
|
||||
* Mutates packageMap in place.
|
||||
*
|
||||
* @param {Record<string, { isDirty: boolean }>} packageMap
|
||||
* @param {Record<string, Record<string, string>>} depsByPackage
|
||||
* @param {Set<string>} changedOverrides
|
||||
* @param {Map<string, Set<string>>} changedCatalogEntries
|
||||
*/
|
||||
export function markDirtyByRootChanges(
|
||||
packageMap,
|
||||
depsByPackage,
|
||||
changedOverrides,
|
||||
changedCatalogEntries,
|
||||
) {
|
||||
for (const [packageName, deps] of Object.entries(depsByPackage)) {
|
||||
if (packageMap[packageName].isDirty) continue;
|
||||
if (depsByPackage[packageName].some((dep) => packageMap[dep]?.isDirty)) {
|
||||
packageMap[packageName].isDirty = true;
|
||||
changed = true;
|
||||
for (const [dep, specifier] of Object.entries(deps)) {
|
||||
if (changedOverrides.has(dep)) {
|
||||
packageMap[packageName].isDirty = true;
|
||||
break;
|
||||
}
|
||||
if (typeof specifier === 'string' && specifier.startsWith('catalog:')) {
|
||||
const catalogName = specifier === 'catalog:' ? 'default' : specifier.slice(8);
|
||||
if (changedCatalogEntries.get(catalogName)?.has(dep)) {
|
||||
packageMap[packageName].isDirty = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the monorepo version up to date with the released version
|
||||
packageMap['monorepo-root'].version = packageMap['n8n'].version;
|
||||
/**
|
||||
* Propagate isDirty transitively: if a package's dependency will be bumped,
|
||||
* that package also needs a bump. Mutates packageMap in place.
|
||||
*
|
||||
* @param {Record<string, { isDirty: boolean }>} packageMap
|
||||
* @param {Record<string, Record<string, string>>} depsByPackage
|
||||
*/
|
||||
export function propagateDirtyTransitively(packageMap, depsByPackage) {
|
||||
let changed = true;
|
||||
while (changed) {
|
||||
changed = false;
|
||||
for (const packageName in packageMap) {
|
||||
if (packageMap[packageName].isDirty) continue;
|
||||
if (Object.keys(depsByPackage[packageName]).some((dep) => packageMap[dep]?.isDirty)) {
|
||||
packageMap[packageName].isDirty = true;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const packageName in packageMap) {
|
||||
const { path, version, isDirty } = packageMap[packageName];
|
||||
const packageFile = resolve(path, 'package.json');
|
||||
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
|
||||
|
||||
const dependencyIsDirty = Object.keys(packageJson.dependencies || {}).some(
|
||||
(dependencyName) => packageMap[dependencyName]?.isDirty,
|
||||
);
|
||||
|
||||
let newVersion = version;
|
||||
|
||||
if (isDirty || dependencyIsDirty) {
|
||||
switch (releaseType) {
|
||||
case 'experimental':
|
||||
newVersion = generateExperimentalVersion(version);
|
||||
break;
|
||||
case 'premajor':
|
||||
newVersion = semver.inc(
|
||||
/**
|
||||
* @param {string} version
|
||||
* @param {import('semver').ReleaseType | 'experimental'} releaseType
|
||||
* @returns {string}
|
||||
*/
|
||||
export function computeNewVersion(version, releaseType) {
|
||||
switch (releaseType) {
|
||||
case 'experimental':
|
||||
return generateExperimentalVersion(version);
|
||||
case 'premajor':
|
||||
return /** @type {string} */ (
|
||||
semver.inc(
|
||||
version,
|
||||
version.includes('-rc.') ? 'prerelease' : 'premajor',
|
||||
undefined,
|
||||
'rc',
|
||||
);
|
||||
break;
|
||||
default:
|
||||
newVersion = semver.inc(version, releaseType);
|
||||
break;
|
||||
}
|
||||
)
|
||||
);
|
||||
default:
|
||||
return /** @type {string} */ (semver.inc(version, releaseType));
|
||||
}
|
||||
|
||||
packageJson.version = packageMap[packageName].nextVersion = newVersion;
|
||||
|
||||
await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n');
|
||||
}
|
||||
|
||||
console.log(packageMap['n8n'].nextVersion);
|
||||
async function bumpVersions() {
|
||||
const rootDir = process.cwd();
|
||||
|
||||
const releaseType = /** @type { import('semver').ReleaseType | "experimental" } */ (
|
||||
process.env.RELEASE_TYPE
|
||||
);
|
||||
assert.match(releaseType, /^(patch|minor|major|experimental|premajor)$/, 'Invalid RELEASE_TYPE');
|
||||
|
||||
// TODO: if releaseType is `auto` determine release type based on the changelog
|
||||
|
||||
const lastTag = (await exec('git describe --tags --match "n8n@*" --abbrev=0')).stdout.trim();
|
||||
const packages = JSON.parse(
|
||||
(
|
||||
await exec(
|
||||
`pnpm ls -r --only-projects --json | jq -r '[.[] | { name: .name, version: .version, path: .path, private: .private}]'`,
|
||||
)
|
||||
).stdout,
|
||||
);
|
||||
|
||||
/** @type {Record<string, { path: string, isDirty: boolean, version: string, nextVersion?: string }>} */
|
||||
const packageMap = {};
|
||||
for (let { name, path, version, private: isPrivate } of packages) {
|
||||
if (isPrivate && path !== rootDir) {
|
||||
continue;
|
||||
}
|
||||
if (path === rootDir) {
|
||||
name = 'monorepo-root';
|
||||
}
|
||||
|
||||
const isDirty = await exec(`git diff --quiet HEAD ${lastTag} -- ${path}`)
|
||||
.then(() => false)
|
||||
.catch(() => true);
|
||||
|
||||
packageMap[name] = { path, isDirty, version };
|
||||
}
|
||||
|
||||
assert.ok(
|
||||
Object.values(packageMap).some(({ isDirty }) => isDirty),
|
||||
'No changes found since the last release',
|
||||
);
|
||||
|
||||
// Propagate isDirty transitively: if a package's dependency will be bumped,
|
||||
// that package also needs a bump (e.g. design-system → editor-ui → cli).
|
||||
|
||||
// Detect root-level changes that affect resolved dep versions without touching individual
|
||||
// package.json files: pnpm.overrides (applies to all specifiers)
|
||||
// and pnpm-workspace.yaml catalog entries (applies only to deps using a "catalog:…" specifier).
|
||||
|
||||
const rootPkgJson = JSON.parse(await readFile(resolve(rootDir, 'package.json'), 'utf-8'));
|
||||
const rootPkgJsonAtTag = await exec(`git show ${lastTag}:package.json`)
|
||||
.then(({ stdout }) => JSON.parse(stdout))
|
||||
.catch(() => ({}));
|
||||
|
||||
const changedOverrides = computeChangedOverrides(
|
||||
getOverrides(rootPkgJson),
|
||||
getOverrides(rootPkgJsonAtTag),
|
||||
);
|
||||
|
||||
const workspaceYaml = parseWorkspaceYaml(
|
||||
await readFile(resolve(rootDir, 'pnpm-workspace.yaml'), 'utf-8').catch(() => ''),
|
||||
);
|
||||
const workspaceYamlAtTag = parseWorkspaceYaml(
|
||||
await exec(`git show ${lastTag}:pnpm-workspace.yaml`)
|
||||
.then(({ stdout }) => stdout)
|
||||
.catch(() => ''),
|
||||
);
|
||||
const changedCatalogEntries = computeChangedCatalogEntries(
|
||||
getCatalogs(workspaceYaml),
|
||||
getCatalogs(workspaceYamlAtTag),
|
||||
);
|
||||
|
||||
// Store full dep objects (with specifiers) so we can inspect "catalog:…" values below.
|
||||
/** @type {Record<string, Record<string, string>>} */
|
||||
const depsByPackage = {};
|
||||
for (const packageName in packageMap) {
|
||||
const packageFile = resolve(packageMap[packageName].path, 'package.json');
|
||||
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
|
||||
depsByPackage[packageName] = /** @type {Record<string,string>} */ (
|
||||
packageJson.dependencies ?? {}
|
||||
);
|
||||
}
|
||||
|
||||
// Mark packages dirty if any dep had a root-level override or catalog version change.
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, changedOverrides, changedCatalogEntries);
|
||||
|
||||
propagateDirtyTransitively(packageMap, depsByPackage);
|
||||
|
||||
// Keep the monorepo version up to date with the released version
|
||||
packageMap['monorepo-root'].version = packageMap['n8n'].version;
|
||||
|
||||
for (const packageName in packageMap) {
|
||||
const { path, version, isDirty } = packageMap[packageName];
|
||||
const packageFile = resolve(path, 'package.json');
|
||||
const packageJson = JSON.parse(await readFile(packageFile, 'utf-8'));
|
||||
|
||||
const dependencyIsDirty = Object.keys(packageJson.dependencies || {}).some(
|
||||
(dependencyName) => packageMap[dependencyName]?.isDirty,
|
||||
);
|
||||
|
||||
let newVersion = version;
|
||||
|
||||
if (isDirty || dependencyIsDirty) {
|
||||
newVersion = computeNewVersion(version, releaseType);
|
||||
}
|
||||
|
||||
packageJson.version = packageMap[packageName].nextVersion = newVersion;
|
||||
|
||||
await writeFile(packageFile, JSON.stringify(packageJson, null, 2) + '\n');
|
||||
}
|
||||
|
||||
console.log(packageMap['n8n'].nextVersion);
|
||||
}
|
||||
|
||||
// only run when executed directly, not when imported by tests
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
bumpVersions();
|
||||
}
|
||||
|
|
|
|||
380
.github/scripts/bump-versions.test.mjs
vendored
Normal file
380
.github/scripts/bump-versions.test.mjs
vendored
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
/**
|
||||
* Run these tests with:
|
||||
*
|
||||
* node --test ./.github/scripts/bump-versions.test.mjs
|
||||
*/
|
||||
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
generateExperimentalVersion,
|
||||
getOverrides,
|
||||
parseWorkspaceYaml,
|
||||
getCatalogs,
|
||||
computeChangedOverrides,
|
||||
computeChangedCatalogEntries,
|
||||
markDirtyByRootChanges,
|
||||
propagateDirtyTransitively,
|
||||
computeNewVersion,
|
||||
} from './bump-versions.mjs';
|
||||
|
||||
describe('generateExperimentalVersion', () => {
|
||||
it('creates -exp.0 from a stable version', () => {
|
||||
assert.equal(generateExperimentalVersion('1.2.3'), '1.2.3-exp.0');
|
||||
});
|
||||
|
||||
it('increments exp minor when already at exp.0', () => {
|
||||
assert.equal(generateExperimentalVersion('1.2.3-exp.0'), '1.2.3-exp.1');
|
||||
});
|
||||
|
||||
it('increments exp minor when already at exp.5', () => {
|
||||
assert.equal(generateExperimentalVersion('1.2.3-exp.5'), '1.2.3-exp.6');
|
||||
});
|
||||
|
||||
it('creates -exp.0 from a version with a different pre-release tag', () => {
|
||||
assert.equal(generateExperimentalVersion('1.2.3-beta.1'), '1.2.3-exp.0');
|
||||
});
|
||||
|
||||
it('handles multi-digit version numbers', () => {
|
||||
assert.equal(generateExperimentalVersion('10.20.30'), '10.20.30-exp.0');
|
||||
});
|
||||
|
||||
it('throws on an invalid version string', () => {
|
||||
assert.throws(() => generateExperimentalVersion('not-a-version'), /Invalid version/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOverrides', () => {
|
||||
it('returns empty object when no overrides exist', () => {
|
||||
assert.deepEqual(getOverrides({}), {});
|
||||
});
|
||||
|
||||
it('returns pnpm.overrides when only pnpm.overrides is set', () => {
|
||||
assert.deepEqual(getOverrides({ pnpm: { overrides: { lodash: '^4.0.0' } } }), {
|
||||
lodash: '^4.0.0',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns overrides when only top-level overrides is set', () => {
|
||||
assert.deepEqual(getOverrides({ overrides: { lodash: '^4.0.0' } }), { lodash: '^4.0.0' });
|
||||
});
|
||||
|
||||
it('merges both fields with top-level overrides taking precedence for the same key', () => {
|
||||
assert.deepEqual(
|
||||
getOverrides({
|
||||
pnpm: { overrides: { lodash: '^3.0.0', underscore: '^1.0.0' } },
|
||||
overrides: { lodash: '^4.0.0' },
|
||||
}),
|
||||
{ lodash: '^4.0.0', underscore: '^1.0.0' },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseWorkspaceYaml', () => {
|
||||
it('parses valid YAML into an object', () => {
|
||||
assert.deepEqual(parseWorkspaceYaml('catalog:\n lodash: "^4.0.0"'), {
|
||||
catalog: { lodash: '^4.0.0' },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns empty object for an empty string', () => {
|
||||
assert.deepEqual(parseWorkspaceYaml(''), {});
|
||||
});
|
||||
|
||||
it('returns empty object for invalid YAML', () => {
|
||||
assert.deepEqual(parseWorkspaceYaml(': - invalid: [yaml}'), {});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCatalogs', () => {
|
||||
it('returns empty map when no catalog or catalogs field exists', () => {
|
||||
assert.equal(getCatalogs({}).size, 0);
|
||||
});
|
||||
|
||||
it('returns a "default" entry for the top-level catalog field', () => {
|
||||
const result = getCatalogs({ catalog: { lodash: '^4.0.0' } });
|
||||
assert.equal(result.size, 1);
|
||||
assert.deepEqual(result.get('default'), { lodash: '^4.0.0' });
|
||||
});
|
||||
|
||||
it('returns named entries from the catalogs field', () => {
|
||||
const result = getCatalogs({ catalogs: { react18: { react: '^18.0.0' } } });
|
||||
assert.equal(result.size, 1);
|
||||
assert.deepEqual(result.get('react18'), { react: '^18.0.0' });
|
||||
});
|
||||
|
||||
it('returns both default and named catalog entries when both fields are present', () => {
|
||||
const result = getCatalogs({
|
||||
catalog: { lodash: '^4.0.0' },
|
||||
catalogs: { react18: { react: '^18.0.0' } },
|
||||
});
|
||||
assert.equal(result.size, 2);
|
||||
assert.deepEqual(result.get('default'), { lodash: '^4.0.0' });
|
||||
assert.deepEqual(result.get('react18'), { react: '^18.0.0' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeChangedOverrides', () => {
|
||||
it('returns empty set when nothing changed', () => {
|
||||
assert.equal(computeChangedOverrides({ lodash: '^4' }, { lodash: '^4' }).size, 0);
|
||||
});
|
||||
|
||||
it('detects an added override', () => {
|
||||
const result = computeChangedOverrides({ lodash: '^4' }, {});
|
||||
assert.ok(result.has('lodash'));
|
||||
});
|
||||
|
||||
it('detects a removed override', () => {
|
||||
const result = computeChangedOverrides({}, { lodash: '^4' });
|
||||
assert.ok(result.has('lodash'));
|
||||
});
|
||||
|
||||
it('detects a changed override value', () => {
|
||||
const result = computeChangedOverrides({ lodash: '^4' }, { lodash: '^3' });
|
||||
assert.ok(result.has('lodash'));
|
||||
});
|
||||
|
||||
it('does not include unchanged overrides', () => {
|
||||
const result = computeChangedOverrides(
|
||||
{ lodash: '^4', underscore: '^1' },
|
||||
{ lodash: '^4', underscore: '^1' },
|
||||
);
|
||||
assert.equal(result.size, 0);
|
||||
});
|
||||
|
||||
it('handles mixed changed and unchanged overrides', () => {
|
||||
const result = computeChangedOverrides(
|
||||
{ lodash: '^4', underscore: '^2' },
|
||||
{ lodash: '^4', underscore: '^1' },
|
||||
);
|
||||
assert.equal(result.size, 1);
|
||||
assert.ok(result.has('underscore'));
|
||||
assert.ok(!result.has('lodash'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeChangedCatalogEntries', () => {
|
||||
it('returns empty map when nothing changed', () => {
|
||||
const current = new Map([['default', { lodash: '^4' }]]);
|
||||
const previous = new Map([['default', { lodash: '^4' }]]);
|
||||
assert.equal(computeChangedCatalogEntries(current, previous).size, 0);
|
||||
});
|
||||
|
||||
it('detects an added dep in a catalog', () => {
|
||||
const current = new Map([['default', { lodash: '^4' }]]);
|
||||
const previous = new Map([['default', {}]]);
|
||||
const result = computeChangedCatalogEntries(current, previous);
|
||||
assert.ok(result.get('default')?.has('lodash'));
|
||||
});
|
||||
|
||||
it('detects a removed dep from a catalog', () => {
|
||||
const current = new Map([['default', {}]]);
|
||||
const previous = new Map([['default', { lodash: '^4' }]]);
|
||||
const result = computeChangedCatalogEntries(current, previous);
|
||||
assert.ok(result.get('default')?.has('lodash'));
|
||||
});
|
||||
|
||||
it('detects a changed dep version in a catalog', () => {
|
||||
const current = new Map([['default', { lodash: '^4' }]]);
|
||||
const previous = new Map([['default', { lodash: '^3' }]]);
|
||||
const result = computeChangedCatalogEntries(current, previous);
|
||||
assert.ok(result.get('default')?.has('lodash'));
|
||||
});
|
||||
|
||||
it('detects changes in a named catalog', () => {
|
||||
const current = new Map([['react18', { react: '^18' }]]);
|
||||
const previous = new Map([['react18', { react: '^17' }]]);
|
||||
const result = computeChangedCatalogEntries(current, previous);
|
||||
assert.ok(result.get('react18')?.has('react'));
|
||||
});
|
||||
|
||||
it('detects a newly added catalog', () => {
|
||||
const current = new Map([['newCatalog', { lodash: '^4' }]]);
|
||||
const previous = new Map();
|
||||
const result = computeChangedCatalogEntries(current, previous);
|
||||
assert.ok(result.get('newCatalog')?.has('lodash'));
|
||||
});
|
||||
|
||||
it('detects a removed catalog', () => {
|
||||
const current = new Map();
|
||||
const previous = new Map([['oldCatalog', { lodash: '^4' }]]);
|
||||
const result = computeChangedCatalogEntries(current, previous);
|
||||
assert.ok(result.get('oldCatalog')?.has('lodash'));
|
||||
});
|
||||
|
||||
it('does not include a catalog that has no changed entries', () => {
|
||||
const current = new Map([
|
||||
['default', { lodash: '^4' }],
|
||||
['react18', { react: '^18' }],
|
||||
]);
|
||||
const previous = new Map([
|
||||
['default', { lodash: '^3' }],
|
||||
['react18', { react: '^18' }],
|
||||
]);
|
||||
const result = computeChangedCatalogEntries(current, previous);
|
||||
assert.ok(result.has('default'));
|
||||
assert.ok(!result.has('react18'));
|
||||
});
|
||||
});
|
||||
|
||||
describe('markDirtyByRootChanges', () => {
|
||||
it('marks a package dirty when its dep appears in changedOverrides', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: false } };
|
||||
const depsByPackage = { 'pkg-a': { lodash: '^4' } };
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, new Set(['lodash']), new Map());
|
||||
assert.ok(packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('skips already-dirty packages', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: true } };
|
||||
// No deps, but package is already dirty — should not throw or change state
|
||||
const depsByPackage = { 'pkg-a': {} };
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, new Set(['lodash']), new Map());
|
||||
assert.ok(packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('marks a package dirty when its dep uses "catalog:" (default catalog) and that entry changed', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: false } };
|
||||
const depsByPackage = { 'pkg-a': { lodash: 'catalog:' } };
|
||||
const changedCatalogEntries = new Map([['default', new Set(['lodash'])]]);
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, new Set(), changedCatalogEntries);
|
||||
assert.ok(packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('marks a package dirty when its dep uses "catalog:<name>" and that named catalog entry changed', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: false } };
|
||||
const depsByPackage = { 'pkg-a': { react: 'catalog:react18' } };
|
||||
const changedCatalogEntries = new Map([['react18', new Set(['react'])]]);
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, new Set(), changedCatalogEntries);
|
||||
assert.ok(packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('does not mark a package dirty when none of its deps changed', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: false } };
|
||||
const depsByPackage = { 'pkg-a': { lodash: '^4' } };
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, new Set(['underscore']), new Map());
|
||||
assert.ok(!packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('does not mark a package dirty when a catalog: dep is in a catalog with no changes', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: false } };
|
||||
const depsByPackage = { 'pkg-a': { lodash: 'catalog:' } };
|
||||
const changedCatalogEntries = new Map([['default', new Set(['underscore'])]]);
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, new Set(), changedCatalogEntries);
|
||||
assert.ok(!packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('does not mark a package dirty when a catalog: dep is in a different catalog than the one that changed', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: false } };
|
||||
const depsByPackage = { 'pkg-a': { react: 'catalog:react18' } };
|
||||
const changedCatalogEntries = new Map([['default', new Set(['react'])]]);
|
||||
markDirtyByRootChanges(packageMap, depsByPackage, new Set(), changedCatalogEntries);
|
||||
assert.ok(!packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('propagateDirtyTransitively', () => {
|
||||
it('does nothing when no packages are dirty', () => {
|
||||
const packageMap = {
|
||||
'pkg-a': { isDirty: false },
|
||||
'pkg-b': { isDirty: false },
|
||||
};
|
||||
const depsByPackage = {
|
||||
'pkg-a': { 'pkg-b': 'workspace:*' },
|
||||
'pkg-b': {},
|
||||
};
|
||||
propagateDirtyTransitively(packageMap, depsByPackage);
|
||||
assert.ok(!packageMap['pkg-a'].isDirty);
|
||||
assert.ok(!packageMap['pkg-b'].isDirty);
|
||||
});
|
||||
|
||||
it('propagates dirty state one level up the dependency chain', () => {
|
||||
const packageMap = {
|
||||
'pkg-a': { isDirty: false },
|
||||
'pkg-b': { isDirty: true },
|
||||
};
|
||||
const depsByPackage = {
|
||||
'pkg-a': { 'pkg-b': 'workspace:*' },
|
||||
'pkg-b': {},
|
||||
};
|
||||
propagateDirtyTransitively(packageMap, depsByPackage);
|
||||
assert.ok(packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('propagates dirty state through multiple levels', () => {
|
||||
const packageMap = {
|
||||
'pkg-a': { isDirty: false },
|
||||
'pkg-b': { isDirty: false },
|
||||
'pkg-c': { isDirty: true },
|
||||
};
|
||||
const depsByPackage = {
|
||||
'pkg-a': { 'pkg-b': 'workspace:*' },
|
||||
'pkg-b': { 'pkg-c': 'workspace:*' },
|
||||
'pkg-c': {},
|
||||
};
|
||||
propagateDirtyTransitively(packageMap, depsByPackage);
|
||||
assert.ok(packageMap['pkg-b'].isDirty, 'pkg-b should be dirty (depends on dirty pkg-c)');
|
||||
assert.ok(packageMap['pkg-a'].isDirty, 'pkg-a should be dirty (depends on dirty pkg-b)');
|
||||
});
|
||||
|
||||
it('does not mark packages dirty when their deps are external (not in packageMap)', () => {
|
||||
const packageMap = { 'pkg-a': { isDirty: false } };
|
||||
const depsByPackage = { 'pkg-a': { lodash: '^4' } };
|
||||
propagateDirtyTransitively(packageMap, depsByPackage);
|
||||
assert.ok(!packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
|
||||
it('handles diamond dependency graphs without infinite loops', () => {
|
||||
// pkg-a depends on pkg-b and pkg-c; both depend on pkg-d (dirty)
|
||||
const packageMap = {
|
||||
'pkg-a': { isDirty: false },
|
||||
'pkg-b': { isDirty: false },
|
||||
'pkg-c': { isDirty: false },
|
||||
'pkg-d': { isDirty: true },
|
||||
};
|
||||
const depsByPackage = {
|
||||
'pkg-a': { 'pkg-b': 'workspace:*', 'pkg-c': 'workspace:*' },
|
||||
'pkg-b': { 'pkg-d': 'workspace:*' },
|
||||
'pkg-c': { 'pkg-d': 'workspace:*' },
|
||||
'pkg-d': {},
|
||||
};
|
||||
propagateDirtyTransitively(packageMap, depsByPackage);
|
||||
assert.ok(packageMap['pkg-b'].isDirty);
|
||||
assert.ok(packageMap['pkg-c'].isDirty);
|
||||
assert.ok(packageMap['pkg-a'].isDirty);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computeNewVersion', () => {
|
||||
it('increments patch version', () => {
|
||||
assert.equal(computeNewVersion('1.2.3', 'patch'), '1.2.4');
|
||||
});
|
||||
|
||||
it('increments minor version (resets patch)', () => {
|
||||
assert.equal(computeNewVersion('1.2.3', 'minor'), '1.3.0');
|
||||
});
|
||||
|
||||
it('increments major version (resets minor and patch)', () => {
|
||||
assert.equal(computeNewVersion('1.2.3', 'major'), '2.0.0');
|
||||
});
|
||||
|
||||
it('creates -exp.0 from a stable version for experimental', () => {
|
||||
assert.equal(computeNewVersion('1.2.3', 'experimental'), '1.2.3-exp.0');
|
||||
});
|
||||
|
||||
it('increments exp minor for experimental when already an exp version', () => {
|
||||
assert.equal(computeNewVersion('1.2.3-exp.0', 'experimental'), '1.2.3-exp.1');
|
||||
});
|
||||
|
||||
it('creates a premajor rc version from a stable version', () => {
|
||||
assert.equal(computeNewVersion('1.2.3', 'premajor'), '2.0.0-rc.0');
|
||||
});
|
||||
|
||||
it('increments the rc prerelease number for premajor when already an rc version', () => {
|
||||
assert.equal(computeNewVersion('2.0.0-rc.0', 'premajor'), '2.0.0-rc.1');
|
||||
});
|
||||
|
||||
it('increments rc correctly across multiple premajor calls', () => {
|
||||
assert.equal(computeNewVersion('2.0.0-rc.4', 'premajor'), '2.0.0-rc.5');
|
||||
});
|
||||
});
|
||||
114
.github/scripts/cla/check-signatures.mjs
vendored
Normal file
114
.github/scripts/cla/check-signatures.mjs
vendored
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// Invoked from .github/workflows/ci-cla-check.yml via actions/github-script.
|
||||
//
|
||||
// Collects unique commit authors for the PR (or for the commits a merge
|
||||
// queue is about to land) and asks the n8n CLA service whether each one
|
||||
// has signed. Surfaces three buckets to subsequent steps:
|
||||
// - signed : verified contributors
|
||||
// - unsigned : verified non-contributors (block the merge)
|
||||
// - errored : CLA lookup failed (block the merge — fail-closed so we
|
||||
// never green-light an unverified contribution)
|
||||
//
|
||||
// Commits whose author email is not linked to a GitHub account can't be
|
||||
// looked up by login; they're surfaced separately as `unlinked`.
|
||||
|
||||
/**
|
||||
* @typedef { InstanceType<typeof import("@actions/github/lib/utils").GitHub> } GitHubInstance
|
||||
* @typedef { import("@actions/github/lib/context").Context } Context
|
||||
* @typedef { typeof import("@actions/core") } Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {{ github: GitHubInstance, context: Context, core: Core }} params
|
||||
*/
|
||||
export default async function checkSignatures ({ github, context, core }) {
|
||||
const { owner, repo } = context.repo;
|
||||
const prNumber = process.env.PR_NUMBER;
|
||||
const headSha = process.env.HEAD_SHA;
|
||||
const baseSha = process.env.BASE_SHA;
|
||||
const isMergeGroup = process.env.IS_MERGE_GROUP === 'true';
|
||||
|
||||
/** @type {Set<string>} */
|
||||
const authors = new Set();
|
||||
/** @type {Array<{sha: string, name: string, email: string}>} */
|
||||
const unlinkedCommits = [];
|
||||
|
||||
/**
|
||||
* @param {Array<any>} commits
|
||||
*/
|
||||
const collect = (commits) => {
|
||||
for (const c of commits) {
|
||||
// Bot-authored commits don't need a CLA; skip before the linked/unlinked split
|
||||
// so they don't fall through to `unlinkedCommits` and fail `all_signed`.
|
||||
if (c.author && c.author.type === 'Bot') continue;
|
||||
|
||||
if (c.author && c.author.login) {
|
||||
authors.add(c.author.login);
|
||||
} else if (c.commit && c.commit.author) {
|
||||
unlinkedCommits.push({
|
||||
sha: c.sha,
|
||||
name: c.commit.author.name,
|
||||
email: c.commit.author.email,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (isMergeGroup) {
|
||||
const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({
|
||||
owner,
|
||||
repo,
|
||||
basehead: `${baseSha}...${headSha}`,
|
||||
});
|
||||
collect(comparison.commits || []);
|
||||
} else if (prNumber) {
|
||||
const commits = await github.paginate(github.rest.pulls.listCommits, {
|
||||
owner,
|
||||
repo,
|
||||
pull_number: Number(prNumber),
|
||||
per_page: 100,
|
||||
});
|
||||
collect(commits);
|
||||
}
|
||||
|
||||
const loginList = [...authors];
|
||||
core.info(`Contributors to check: ${loginList.join(', ') || '(none)'}`);
|
||||
if (unlinkedCommits.length > 0) {
|
||||
core.warning(
|
||||
`${unlinkedCommits.length} commit(s) have an author email not linked to a GitHub account ` +
|
||||
'and cannot be verified against the CLA service.',
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {string[]} */
|
||||
const signed = [];
|
||||
/** @type {string[]} */
|
||||
const unsigned = [];
|
||||
/** @type {string[]} */
|
||||
const errored = [];
|
||||
|
||||
for (const login of loginList) {
|
||||
const url = `${process.env.CLA_API}?checkContributor=${encodeURIComponent(login)}`;
|
||||
try {
|
||||
const res = await fetch(url);
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
if (data && data.isContributor === true) {
|
||||
signed.push(login);
|
||||
} else {
|
||||
unsigned.push(login);
|
||||
}
|
||||
} catch (e) {
|
||||
core.warning(`CLA lookup failed for @${login}: ${e instanceof Error ? e.message : String(e)}`);
|
||||
errored.push(login);
|
||||
}
|
||||
}
|
||||
|
||||
const blocking = [...unsigned, ...errored];
|
||||
const allSigned = blocking.length === 0 && unlinkedCommits.length === 0;
|
||||
|
||||
core.setOutput('signed', signed.join(','));
|
||||
core.setOutput('unsigned', unsigned.join(','));
|
||||
core.setOutput('errored', errored.join(','));
|
||||
core.setOutput('unlinked', JSON.stringify(unlinkedCommits));
|
||||
core.setOutput('all_signed', String(allSigned));
|
||||
}
|
||||
83
.github/scripts/cla/manage-label.mjs
vendored
Normal file
83
.github/scripts/cla/manage-label.mjs
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
// Invoked from .github/workflows/ci-cla-check.yml via actions/github-script.
|
||||
//
|
||||
// Adds the `cla-signed` label when every contributor has signed, and
|
||||
// removes it otherwise. Idempotent: re-runs safely without duplicating
|
||||
// the label or erroring if it's already in the desired state. Creates
|
||||
// the label on first use so the workflow is self-contained.
|
||||
|
||||
/**
|
||||
* @typedef { InstanceType<typeof import("@actions/github/lib/utils").GitHub> } GitHubInstance
|
||||
* @typedef { import("@actions/github/lib/context").Context } Context
|
||||
* @typedef { typeof import("@actions/core") } Core
|
||||
*/
|
||||
|
||||
const LABEL_NAME = 'cla-signed';
|
||||
const LABEL_COLOR = '0e8a16'; // GitHub's standard green
|
||||
const LABEL_DESCRIPTION = 'All contributors on this PR have signed the CLA';
|
||||
|
||||
/**
|
||||
* @param {{ github: GitHubInstance, context: Context, core: Core }} params
|
||||
*/
|
||||
export default async function manageClaLabel({ github, context, core }) {
|
||||
const { owner, repo } = context.repo;
|
||||
const issue_number = Number(process.env.PR_NUMBER);
|
||||
const allSigned = process.env.ALL_SIGNED === 'true';
|
||||
|
||||
if (allSigned) {
|
||||
// Make sure the label exists before trying to apply it — addLabels
|
||||
// errors if the label is missing from the repo.
|
||||
try {
|
||||
await github.rest.issues.getLabel({ owner, repo, name: LABEL_NAME });
|
||||
} catch (e) {
|
||||
if (errorStatus(e) === 404) {
|
||||
try {
|
||||
await github.rest.issues.createLabel({
|
||||
owner,
|
||||
repo,
|
||||
name: LABEL_NAME,
|
||||
color: LABEL_COLOR,
|
||||
description: LABEL_DESCRIPTION,
|
||||
});
|
||||
} catch (createErr) {
|
||||
// 422 = race with a parallel run that just created it. Fine.
|
||||
if (errorStatus(createErr) !== 422) throw createErr;
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
labels: [LABEL_NAME],
|
||||
});
|
||||
core.info(`Applied "${LABEL_NAME}" label to PR #${issue_number}`);
|
||||
} else {
|
||||
// 404 just means the label wasn't on the PR — nothing to undo.
|
||||
try {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
name: LABEL_NAME,
|
||||
});
|
||||
core.info(`Removed "${LABEL_NAME}" label from PR #${issue_number}`);
|
||||
} catch (e) {
|
||||
if (errorStatus(e) !== 404) throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Octokit's request errors carry an HTTP `status` field, but TypeScript
|
||||
* sees catch parameters as `unknown`. This guard narrows safely.
|
||||
* @param {unknown} e
|
||||
* @returns {number | undefined}
|
||||
*/
|
||||
function errorStatus(e) {
|
||||
return typeof e === 'object' && e !== null && 'status' in e && typeof e.status === 'number'
|
||||
? e.status
|
||||
: undefined;
|
||||
}
|
||||
66
.github/scripts/cla/post-final-status.mjs
vendored
Normal file
66
.github/scripts/cla/post-final-status.mjs
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Invoked from .github/workflows/ci-cla-check.yml via actions/github-script.
|
||||
//
|
||||
// Translates the buckets emitted by check-signatures.mjs into a single
|
||||
// commit status on the head SHA. The status `context` name is what a
|
||||
// repository ruleset gates on; description and target_url are best-effort
|
||||
// human signals.
|
||||
//
|
||||
// State mapping:
|
||||
// - success: every contributor is signed and every commit author is linked
|
||||
// - error : only failures were API lookup errors (transient)
|
||||
// - failure: at least one contributor is verified unsigned, or commits
|
||||
// have author emails not linked to a GitHub account
|
||||
|
||||
/**
|
||||
* @typedef { InstanceType<typeof import("@actions/github/lib/utils").GitHub> } GitHubInstance
|
||||
* @typedef { import("@actions/github/lib/context").Context } Context
|
||||
* @typedef { typeof import("@actions/core") } Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {{ github: GitHubInstance, context: Context, core: Core }} params
|
||||
*/
|
||||
export default async function postFinalClaStatus({ github, context }) {
|
||||
const allSigned = process.env.ALL_SIGNED === 'true';
|
||||
const unsigned = (process.env.UNSIGNED ?? '').split(',').filter(Boolean);
|
||||
const errored = (process.env.ERRORED ?? '').split(',').filter(Boolean);
|
||||
const unlinked = JSON.parse(process.env.UNLINKED || '[]');
|
||||
|
||||
/** @type {'success' | 'failure' | 'error' | 'pending'} */
|
||||
let state;
|
||||
let description;
|
||||
if (allSigned) {
|
||||
state = 'success';
|
||||
description = 'All contributors have signed the CLA';
|
||||
} else if (errored.length > 0 && unsigned.length === 0 && unlinked.length === 0) {
|
||||
state = 'error';
|
||||
description = `Could not verify: ${errored.join(', ')}`;
|
||||
} else {
|
||||
state = 'failure';
|
||||
const parts = [];
|
||||
if (unsigned.length > 0) parts.push(`unsigned: ${unsigned.join(', ')}`);
|
||||
if (errored.length > 0) parts.push(`errored: ${errored.join(', ')}`);
|
||||
if (unlinked.length > 0) parts.push(`${unlinked.length} unlinked commit(s)`);
|
||||
description = parts.join(' | ');
|
||||
}
|
||||
|
||||
// GitHub commit status description is capped at 140 chars.
|
||||
if (description.length > 140) {
|
||||
description = description.slice(0, 137) + '…';
|
||||
}
|
||||
|
||||
const prNumber = process.env.PR_NUMBER;
|
||||
const target_url = prNumber
|
||||
? `${context.payload.repository?.html_url}/pull/${prNumber}`
|
||||
: process.env.CLA_SIGN_URL;
|
||||
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: /** @type {string} */ (process.env.HEAD_SHA),
|
||||
state,
|
||||
context: /** @type {string} */ (process.env.STATUS_CONTEXT),
|
||||
description,
|
||||
target_url,
|
||||
});
|
||||
}
|
||||
76
.github/scripts/cla/resolve-context.mjs
vendored
Normal file
76
.github/scripts/cla/resolve-context.mjs
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
// Invoked from .github/workflows/ci-cla-check.yml via actions/github-script.
|
||||
//
|
||||
// Reads the triggering event (pull_request_target, issue_comment, or
|
||||
// merge_group) and emits the head/base SHA and PR number that the rest of
|
||||
// the workflow needs. For /cla-check comments, also leaves an "eyes"
|
||||
// reaction so the commenter sees we picked it up.
|
||||
|
||||
/**
|
||||
* @typedef { InstanceType<typeof import("@actions/github/lib/utils").GitHub> } GitHubInstance
|
||||
* @typedef { import("@actions/github/lib/context").Context } Context
|
||||
* @typedef { typeof import("@actions/core") } Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {{ github: GitHubInstance, context: Context, core: Core }} params
|
||||
*/
|
||||
export default async function resolveClaContext({ github, context, core }) {
|
||||
const { owner, repo } = context.repo;
|
||||
const event = context.eventName;
|
||||
|
||||
let prNumber = '';
|
||||
let headSha = '';
|
||||
let baseSha = '';
|
||||
let isMergeGroup = false;
|
||||
|
||||
if (event === 'pull_request_target' && context.payload.pull_request) {
|
||||
const pr = context.payload.pull_request;
|
||||
prNumber = String(pr.number);
|
||||
headSha = pr.head.sha;
|
||||
baseSha = pr.base.sha;
|
||||
} else if (event === 'issue_comment' && context.payload.issue) {
|
||||
prNumber = String(context.payload.issue.number);
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: Number(prNumber),
|
||||
});
|
||||
headSha = pr.head.sha;
|
||||
baseSha = pr.base.sha;
|
||||
|
||||
// Acknowledge the command so the commenter sees we received it.
|
||||
try {
|
||||
await github.rest.reactions.createForIssueComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: context.payload.comment?.id || -1,
|
||||
content: 'eyes',
|
||||
});
|
||||
} catch (e) {
|
||||
core.info(`Could not react to comment: ${e instanceof Error ? e.message : String(e)}`);
|
||||
}
|
||||
} else if (event === 'merge_group') {
|
||||
isMergeGroup = true;
|
||||
headSha = context.payload.merge_group.head_sha;
|
||||
baseSha = context.payload.merge_group.base_sha;
|
||||
} else if (event === 'workflow_dispatch') {
|
||||
const input = context.payload.inputs?.pr_number;
|
||||
if (!input) {
|
||||
core.setFailed('workflow_dispatch requires the pr_number input');
|
||||
return;
|
||||
}
|
||||
prNumber = String(input);
|
||||
const { data: pr } = await github.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: Number(prNumber),
|
||||
});
|
||||
headSha = pr.head.sha;
|
||||
baseSha = pr.base.sha;
|
||||
}
|
||||
|
||||
core.setOutput('pr_number', prNumber);
|
||||
core.setOutput('head_sha', headSha);
|
||||
core.setOutput('base_sha', baseSha);
|
||||
core.setOutput('is_merge_group', String(isMergeGroup));
|
||||
}
|
||||
104
.github/scripts/cla/update-pr-comment.mjs
vendored
Normal file
104
.github/scripts/cla/update-pr-comment.mjs
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
// Invoked from .github/workflows/ci-cla-check.yml via actions/github-script.
|
||||
//
|
||||
// Maintains a single CLA comment per PR, keyed by an HTML marker so the
|
||||
// same comment is edited in place across re-runs instead of spammed.
|
||||
// A clean PR that has never been flagged gets no comment at all — only
|
||||
// PRs that needed a nudge get the eventual "thanks" follow-up.
|
||||
|
||||
/**
|
||||
* @typedef { InstanceType<typeof import("@actions/github/lib/utils").GitHub> } GitHubInstance
|
||||
* @typedef { import("@actions/github/lib/context").Context } Context
|
||||
* @typedef { typeof import("@actions/core") } Core
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {{ github: GitHubInstance, context: Context, core: Core }} params
|
||||
*/
|
||||
export default async function updatePRComment({ github, context }) {
|
||||
const { owner, repo } = context.repo;
|
||||
const issue_number = Number(process.env.PR_NUMBER);
|
||||
const allSigned = process.env.ALL_SIGNED === 'true';
|
||||
const unsigned = (process.env.UNSIGNED ?? '').split(',').filter(Boolean);
|
||||
const errored = (process.env.ERRORED ?? '').split(',').filter(Boolean);
|
||||
const unlinked = JSON.parse(process.env.UNLINKED || '[]');
|
||||
const MARKER = /** @type {string} */ (process.env.COMMENT_MARKER);
|
||||
|
||||
const comments = await github.paginate(github.rest.issues.listComments, {
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
per_page: 100,
|
||||
});
|
||||
// Only adopt the comment as ours if it's bot-authored — otherwise a user
|
||||
// who copies our marker into their own comment would either hijack the
|
||||
// thread or make updateComment 403 with insufficient permissions.
|
||||
const existing = comments.find(
|
||||
(c) => c.body && c.body.includes(MARKER) && c.user && c.user.type === 'Bot',
|
||||
);
|
||||
|
||||
let body;
|
||||
if (allSigned) {
|
||||
// Only leave a "thanks" trail if we already nudged once. Avoids
|
||||
// pinging every clean PR with a CLA comment.
|
||||
if (!existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
body = [
|
||||
MARKER,
|
||||
'✅ **CLA Check passed.** All contributors on this PR have signed the n8n CLA — thank you!',
|
||||
].join('\n');
|
||||
} else {
|
||||
const lines = [MARKER, '## CLA signatures required', ''];
|
||||
lines.push(`Thank you for your submission! We really appreciate it.
|
||||
Like many open source projects, we ask that you sign our [Contributor License Agreement](${process.env.CLA_SIGN_URL}) before we can accept your contribution.`);
|
||||
lines.push('');
|
||||
|
||||
if (unsigned.length > 0) {
|
||||
lines.push('**Contributors who still need to sign:**');
|
||||
for (const u of unsigned) {
|
||||
lines.push(`- @${u}`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
if (errored.length > 0) {
|
||||
lines.push('**Could not verify (will retry on next push):**');
|
||||
for (const u of errored) {
|
||||
lines.push(`- @${u}`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
if (unlinked.length > 0) {
|
||||
lines.push('**Commits authored by an email not linked to a GitHub account:**');
|
||||
for (const c of unlinked) {
|
||||
lines.push(`- \`${c.sha.slice(0, 7)}\` — ${c.name} <${c.email}>`);
|
||||
}
|
||||
lines.push('');
|
||||
lines.push(
|
||||
'Add the email to your GitHub account ' +
|
||||
'([instructions](https://docs.github.com/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/adding-an-email-address-to-your-github-account)) ' +
|
||||
'or amend the commits to use a linked email, then push again.',
|
||||
);
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
lines.push('Once signed, comment `/cla-check` on this PR to re-run verification.');
|
||||
body = lines.join('\n');
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
await github.rest.issues.updateComment({
|
||||
owner,
|
||||
repo,
|
||||
comment_id: existing.id,
|
||||
body,
|
||||
});
|
||||
} else {
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number,
|
||||
body,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,13 @@ let prompt;
|
|||
|
||||
if (useRaw) {
|
||||
prompt = task;
|
||||
} else if (task.startsWith('/')) {
|
||||
// Task is a skill invocation (e.g. "/n8n:linear-issue CAT-2820").
|
||||
// Wrap it so the model invokes the Skill tool instead of implementing code.
|
||||
prompt = `# Skill Invocation
|
||||
Invoke the following skill using the Skill tool and follow its instructions.
|
||||
|
||||
${task}`;
|
||||
} else {
|
||||
// List available templates so Claude knows what exists (reads them if needed)
|
||||
const templateDir = '.github/claude-templates';
|
||||
|
|
|
|||
3
.github/scripts/package.json
vendored
3
.github/scripts/package.json
vendored
|
|
@ -11,7 +11,8 @@
|
|||
"glob": "13.0.6",
|
||||
"minimatch": "10.2.4",
|
||||
"semver": "7.7.4",
|
||||
"tempfile": "6.0.1"
|
||||
"tempfile": "6.0.1",
|
||||
"yaml": "^2.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"conventional-changelog-angular": "8.3.0"
|
||||
|
|
|
|||
10
.github/scripts/pnpm-lock.yaml
vendored
10
.github/scripts/pnpm-lock.yaml
vendored
|
|
@ -32,6 +32,9 @@ importers:
|
|||
tempfile:
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1
|
||||
yaml:
|
||||
specifier: ^2.8.3
|
||||
version: 2.8.3
|
||||
devDependencies:
|
||||
conventional-changelog-angular:
|
||||
specifier: 8.3.0
|
||||
|
|
@ -292,6 +295,11 @@ packages:
|
|||
wordwrap@1.0.0:
|
||||
resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
|
||||
|
||||
yaml@2.8.3:
|
||||
resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==}
|
||||
engines: {node: '>= 14.6'}
|
||||
hasBin: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@actions/github@9.0.0':
|
||||
|
|
@ -540,3 +548,5 @@ snapshots:
|
|||
walk-up-path@4.0.0: {}
|
||||
|
||||
wordwrap@1.0.0: {}
|
||||
|
||||
yaml@2.8.3: {}
|
||||
|
|
|
|||
25
.github/scripts/pnpm-utils.mjs
vendored
Normal file
25
.github/scripts/pnpm-utils.mjs
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import child_process from 'child_process';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
const exec = promisify(child_process.exec);
|
||||
|
||||
/**
|
||||
* @typedef PnpmPackage
|
||||
* @property { string } name
|
||||
* @property { string } version
|
||||
* @property { string } path
|
||||
* @property { boolean } private
|
||||
* */
|
||||
|
||||
/**
|
||||
* @returns { Promise<PnpmPackage[]> }
|
||||
* */
|
||||
export async function getMonorepoProjects() {
|
||||
return JSON.parse(
|
||||
(
|
||||
await exec(
|
||||
`pnpm ls -r --only-projects --json | jq -r '[.[] | { name: .name, version: .version, path: .path, private: .private}]'`,
|
||||
)
|
||||
).stdout,
|
||||
);
|
||||
}
|
||||
2
.github/scripts/quality/check-pr-size.mjs
vendored
2
.github/scripts/quality/check-pr-size.mjs
vendored
|
|
@ -40,6 +40,8 @@ export const EXCLUDE_PATTERNS = [
|
|||
'packages/testing/**',
|
||||
// Lock file (can produce massive diffs on dependency changes)
|
||||
'pnpm-lock.yaml',
|
||||
'**/*.md',
|
||||
'**/*.mdx'
|
||||
];
|
||||
|
||||
const BOT_MARKER = '<!-- pr-size-check -->';
|
||||
|
|
|
|||
|
|
@ -203,4 +203,13 @@ describe('countFilteredAdditions', () => {
|
|||
];
|
||||
assert.equal(countFilteredAdditions(files, EXCLUDE_PATTERNS), 50);
|
||||
});
|
||||
|
||||
it('applies EXCLUDE_PATTERNS to markdown files', () => {
|
||||
const files = [
|
||||
{ filename: 'packages/cli/src/service.ts', additions: 50 },
|
||||
{ filename: 'packages/cli/AGENTS.md', additions: 100 },
|
||||
{ filename: 'packages/frontend/STORIES.mdx', additions: 100 },
|
||||
];
|
||||
assert.equal(countFilteredAdditions(files, EXCLUDE_PATTERNS), 50);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
45
.github/scripts/retry.mjs
vendored
45
.github/scripts/retry.mjs
vendored
|
|
@ -2,16 +2,18 @@
|
|||
/**
|
||||
* Retry a shell command with configurable attempts and delay.
|
||||
*
|
||||
* Usage: node retry.mjs [--attempts N] [--delay N] '<command>'
|
||||
* Usage (safe): node retry.mjs [--attempts N] [--delay N] -- <cmd> [args...]
|
||||
* Usage (legacy): node retry.mjs [--attempts N] [--delay N] '<shell command>'
|
||||
*
|
||||
* Options:
|
||||
* --attempts N Maximum number of attempts (default: 4)
|
||||
* --delay N Seconds to wait between retries (default: 15)
|
||||
*
|
||||
* The command is executed via shell, so pipes and env-var expansion work.
|
||||
* The -- form passes args directly to the process (no shell, safe for untrusted input).
|
||||
* The legacy form executes via shell, so pipes and env-var expansion work but injection is possible.
|
||||
* Exits 0 on first success, 1 if all attempts fail.
|
||||
*/
|
||||
import { execSync } from 'node:child_process';
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
|
|
@ -29,23 +31,40 @@ function getFlag(name, defaultValue) {
|
|||
const attempts = getFlag('attempts', 4);
|
||||
const delay = getFlag('delay', 15);
|
||||
|
||||
// Command is the last positional arg (skip flags and their values)
|
||||
const command = args
|
||||
.filter((a, i) => {
|
||||
if (a.startsWith('--')) return false;
|
||||
if (i > 0 && args[i - 1].startsWith('--')) return false;
|
||||
return true;
|
||||
})
|
||||
.pop();
|
||||
// Preferred form: -- cmd arg1 arg2 ... (no shell, safe for untrusted input)
|
||||
// Legacy form: '<shell command string>' (uses shell; kept for backwards compat)
|
||||
const separatorIndex = args.indexOf('--');
|
||||
|
||||
let command;
|
||||
let commandArgs = [];
|
||||
|
||||
const isSafeRetry = separatorIndex !== -1;
|
||||
|
||||
if (isSafeRetry) {
|
||||
[command, ...commandArgs] = args.slice(separatorIndex + 1);
|
||||
} else {
|
||||
command = args
|
||||
.filter((a, i) => {
|
||||
if (a.startsWith('--')) return false;
|
||||
if (i > 0 && args[i - 1].startsWith('--')) return false;
|
||||
return true;
|
||||
})
|
||||
.pop();
|
||||
}
|
||||
|
||||
if (!command) {
|
||||
console.error("Usage: node retry.mjs [--attempts N] [--delay N] '<command>'");
|
||||
console.error('Usage: node retry.mjs [--attempts N] [--delay N] -- <cmd> [args...]');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
for (let i = 1; i <= attempts; i++) {
|
||||
try {
|
||||
execSync(command, { shell: true, stdio: 'inherit' });
|
||||
if (isSafeRetry) {
|
||||
const result = spawnSync(command, commandArgs, { stdio: 'inherit' });
|
||||
if (result.status !== 0) throw new Error(`Exit code ${result.status}`);
|
||||
} else {
|
||||
execSync(command, { shell: true, stdio: 'inherit' });
|
||||
}
|
||||
process.exit(0);
|
||||
} catch {
|
||||
if (i < attempts) {
|
||||
|
|
|
|||
72
.github/scripts/set-latest-for-monorepo-packages.mjs
vendored
Normal file
72
.github/scripts/set-latest-for-monorepo-packages.mjs
vendored
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import { getMonorepoProjects } from './pnpm-utils.mjs';
|
||||
|
||||
const NPM_REGISTRY = 'https://registry.npmjs.org';
|
||||
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {string} version
|
||||
* @param {string} tag
|
||||
* @param {string} token
|
||||
*/
|
||||
async function setDistTag(name, version, tag, token) {
|
||||
// Scoped package names need both @ and / encoded (e.g. @n8n/foo → %40n8n%2ffoo)
|
||||
const encodedName = encodeURIComponent(name);
|
||||
const url = `${NPM_REGISTRY}/-/package/${encodedName}/dist-tags/${tag}`;
|
||||
|
||||
return fetch(url, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(version),
|
||||
});
|
||||
}
|
||||
|
||||
async function setLatestForMonorepoPackages() {
|
||||
const token = process.env.NPM_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error('NPM_TOKEN environment variable is required');
|
||||
}
|
||||
|
||||
const packages = await getMonorepoProjects();
|
||||
|
||||
const publishedPackages = packages //
|
||||
.filter((pkg) => !pkg.private)
|
||||
.filter((pkg) => pkg.name.startsWith('@n8n/'))
|
||||
.filter((pkg) => pkg.version);
|
||||
|
||||
const failures = [];
|
||||
|
||||
for (const pkg of publishedPackages) {
|
||||
const versionName = `${pkg.name}@${pkg.version}`;
|
||||
|
||||
try {
|
||||
const res = await setDistTag(pkg.name, pkg.version, 'latest', token);
|
||||
|
||||
if (res.ok) {
|
||||
console.log(`Set ${versionName} as latest`);
|
||||
} else {
|
||||
const body = await res.text().catch(() => '');
|
||||
console.error(`Failed to set ${versionName} as latest: HTTP ${res.status} ${body}`);
|
||||
failures.push(versionName);
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
console.error(`Failed to set ${versionName} as latest: ${message}`);
|
||||
failures.push(versionName);
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.length > 0) {
|
||||
throw new Error(`Failed to update dist-tags for: ${failures.join(', ')}`);
|
||||
}
|
||||
}
|
||||
|
||||
// only run when executed directly, not when imported by tests
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
setLatestForMonorepoPackages().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
1280
.github/test-metrics/playwright.json
vendored
1280
.github/test-metrics/playwright.json
vendored
File diff suppressed because it is too large
Load Diff
45
.github/test-metrics/quarantine.json
vendored
Normal file
45
.github/test-metrics/quarantine.json
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"updatedAt": "2026-05-11T14:16:56.139Z",
|
||||
"source": "currents",
|
||||
"projectId": "LRxcNt",
|
||||
"quarantined": [
|
||||
"Canvas Actions > Node hover actions > should execute node",
|
||||
"Chat user role @capability:proxy > use chat as chat user @auth:chat",
|
||||
"Code node > Code editor > should execute the placeholder successfully in both modes",
|
||||
"Data Mapping > maps expressions to updated fields correctly @fixme",
|
||||
"Data pinning > Advanced pinning scenarios > should be able to reference paired items in node before pinned data",
|
||||
"Debug mode > should enter debug mode for failed executions",
|
||||
"HITL for Tools @capability:proxy > should add a HITL tool node and run it",
|
||||
"Inject previous execution > can map keys from previous execution",
|
||||
"Instance AI remediation guard @capability:proxy > should preserve a submitted workflow when mocked credential verification needs setup",
|
||||
"Instance AI sidebar @capability:proxy > should delete thread via action menu",
|
||||
"Instance AI workflow setup actions @capability:proxy > should apply parameter and credential edits and persist them to the workflow",
|
||||
"Instance AI workflow setup actions @capability:proxy > should partially apply completed cards when Later is clicked on the last step",
|
||||
"Loads template setup modal correctly",
|
||||
"NDV Data Display > Schema View > should not display pagination for schema",
|
||||
"Settings @capability:proxy > set global credentials for a provider",
|
||||
"Tools usage @capability:proxy > use web search tool in conversation",
|
||||
"can configure, connect, and sync secrets from LocalStack",
|
||||
"can create a connection pointing to LocalStack",
|
||||
"manage workflow agents @auth:admin",
|
||||
"maps expressions to updated fields correctly @fixme",
|
||||
"sharing workflow agent with project chat user",
|
||||
"should add switch node and test connections",
|
||||
"should allow re-running workflow after initial execution",
|
||||
"should be able to reference paired items in node before pinned data",
|
||||
"should clear required-parameter issue indicator when the field is filled",
|
||||
"should execute node",
|
||||
"should filter executions by status and show filter badge",
|
||||
"should maintain zoom functionality after switching between Editor and Workflow history and Workflow list",
|
||||
"should not send workflow context if nothing changed",
|
||||
"should open executions tab",
|
||||
"should populate logs as manual execution progresses",
|
||||
"should preserve resource mapper values when navigating between connected nodes via floating nodes",
|
||||
"should render runItems for sub-nodes and allow switching between them",
|
||||
"should reset filter and remove badge",
|
||||
"should retrieve list options when other params throw errors",
|
||||
"should save template id with the workflow",
|
||||
"should send proper payload for node rerun",
|
||||
"use web search tool in conversation"
|
||||
]
|
||||
}
|
||||
184
.github/workflows/ci-cla-check.yml
vendored
Normal file
184
.github/workflows/ci-cla-check.yml
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
name: 'CI: CLA Check'
|
||||
|
||||
# In-house replacement for the GitHub App "CLA Bot".
|
||||
#
|
||||
# Triggers
|
||||
# - pull_request_target (opened/synchronize/reopened): re-checks signatures
|
||||
# whenever a PR is opened or new commits are pushed.
|
||||
# - issue_comment (`/cla-check` on a PR): manual re-check after a contributor
|
||||
# signs the CLA, without needing a push.
|
||||
# - merge_group: re-checks at merge-queue time so a ruleset can hard-block
|
||||
# unsigned merges even if the PR check went stale.
|
||||
#
|
||||
# Output
|
||||
# - A commit status named "CLA Check" on the head SHA. Add this name to a
|
||||
# ruleset's required-checks list to gate merges on it.
|
||||
# - A single, edited-in-place PR comment listing unsigned contributors.
|
||||
#
|
||||
# Implementation
|
||||
# The heavy lifting lives in .github/scripts/cla/*.mjs. Each step below
|
||||
# loads its corresponding module and invokes its default export.
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
merge_group:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pr_number:
|
||||
description: 'Pull request number to re-verify'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
issues: write
|
||||
statuses: write
|
||||
|
||||
concurrency:
|
||||
group: cla-check-${{ github.event.pull_request.number || github.event.issue.number || github.event.merge_group.head_sha || github.event.inputs.pr_number || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
STATUS_CONTEXT: 'CLA Check'
|
||||
CLA_API: 'https://cla-bot-prod.users.n8n.cloud/webhook/cla/check'
|
||||
CLA_SIGN_URL: 'https://cla-bot-prod.users.n8n.cloud/webhook/cla'
|
||||
COMMENT_MARKER: '<!-- n8n-cla-check -->'
|
||||
|
||||
jobs:
|
||||
cla-check:
|
||||
name: Verify CLA signatures
|
||||
# Skip issue_comment unless it's on a PR and the body starts with /cla-check.
|
||||
if: >-
|
||||
github.event_name != 'issue_comment' ||
|
||||
(github.event.issue.pull_request != null &&
|
||||
startsWith(github.event.comment.body, '/cla-check'))
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Generate GitHub App Token
|
||||
id: generate-token
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
with:
|
||||
app-id: ${{ secrets.N8N_ASSISTANT_APP_ID }}
|
||||
private-key: ${{ secrets.N8N_ASSISTANT_PRIVATE_KEY }}
|
||||
|
||||
- name: Checkout CLA scripts
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: .github/scripts/cla
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Resolve PR context
|
||||
id: context
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const mod = await import('${{ github.workspace }}/.github/scripts/cla/resolve-context.mjs');
|
||||
await mod.default({ github, context, core });
|
||||
|
||||
- name: Post pending commit status
|
||||
if: steps.context.outputs.head_sha != ''
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
HEAD_SHA: ${{ steps.context.outputs.head_sha }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: process.env.HEAD_SHA,
|
||||
state: 'pending',
|
||||
context: process.env.STATUS_CONTEXT,
|
||||
description: 'Verifying CLA signatures…',
|
||||
});
|
||||
|
||||
- name: Check CLA signatures
|
||||
id: check
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
|
||||
HEAD_SHA: ${{ steps.context.outputs.head_sha }}
|
||||
BASE_SHA: ${{ steps.context.outputs.base_sha }}
|
||||
IS_MERGE_GROUP: ${{ steps.context.outputs.is_merge_group }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const mod = await import('${{ github.workspace }}/.github/scripts/cla/check-signatures.mjs');
|
||||
await mod.default({ github, context, core });
|
||||
|
||||
- name: Post final commit status
|
||||
if: always() && steps.context.outputs.head_sha != ''
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
HEAD_SHA: ${{ steps.context.outputs.head_sha }}
|
||||
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
|
||||
ALL_SIGNED: ${{ steps.check.outputs.all_signed }}
|
||||
UNSIGNED: ${{ steps.check.outputs.unsigned }}
|
||||
ERRORED: ${{ steps.check.outputs.errored }}
|
||||
UNLINKED: ${{ steps.check.outputs.unlinked }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const mod = await import('${{ github.workspace }}/.github/scripts/cla/post-final-status.mjs');
|
||||
await mod.default({ github, context, core });
|
||||
|
||||
- name: Update PR comment
|
||||
# Don't comment from merge_group (no PR context) or when the check
|
||||
# failed to produce a result.
|
||||
if: >-
|
||||
always() &&
|
||||
steps.context.outputs.pr_number != '' &&
|
||||
steps.check.outputs.all_signed != ''
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
|
||||
ALL_SIGNED: ${{ steps.check.outputs.all_signed }}
|
||||
UNSIGNED: ${{ steps.check.outputs.unsigned }}
|
||||
ERRORED: ${{ steps.check.outputs.errored }}
|
||||
UNLINKED: ${{ steps.check.outputs.unlinked }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const mod = await import('${{ github.workspace }}/.github/scripts/cla/update-pr-comment.mjs');
|
||||
await mod.default({ github, context, core });
|
||||
|
||||
- name: Manage cla-signed label
|
||||
# Skip on merge_group (no PR) and when the check produced no result.
|
||||
if: >-
|
||||
always() &&
|
||||
steps.context.outputs.pr_number != '' &&
|
||||
steps.check.outputs.all_signed != ''
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
PR_NUMBER: ${{ steps.context.outputs.pr_number }}
|
||||
ALL_SIGNED: ${{ steps.check.outputs.all_signed }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
const mod = await import('${{ github.workspace }}/.github/scripts/cla/manage-label.mjs');
|
||||
await mod.default({ github, context, core });
|
||||
|
||||
- name: React to /cla-check comment
|
||||
if: always() && github.event_name == 'issue_comment' && steps.check.outputs.all_signed != ''
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
env:
|
||||
ALL_SIGNED: ${{ steps.check.outputs.all_signed }}
|
||||
with:
|
||||
github-token: ${{ steps.generate-token.outputs.token }}
|
||||
script: |
|
||||
try {
|
||||
await github.rest.reactions.createForIssueComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
comment_id: context.payload.comment.id,
|
||||
content: process.env.ALL_SIGNED === 'true' ? '+1' : '-1',
|
||||
});
|
||||
} catch (e) {
|
||||
core.info(`Could not react to comment: ${e.message}`);
|
||||
}
|
||||
23
.github/workflows/ci-codeowners-validation.yml
vendored
Normal file
23
.github/workflows/ci-codeowners-validation.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# .github/workflows/ci-codeowners-validation.yml
|
||||
name: "CI: Validate CODEOWNERS"
|
||||
|
||||
# Only run when CODEOWNERS or packages change
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/CODEOWNERS"
|
||||
- "packages/**"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- uses: mszostok/codeowners-validator@7f3f5e28c6d7b8dfae5731e54ce2272ca384592f #v0.7.4
|
||||
with:
|
||||
# Start with safe checks only. Add "owners" and
|
||||
# experimental_checks: "notowned" once the file has settled
|
||||
# and skip patterns are configured.
|
||||
checks: "files,duppatterns,syntax"
|
||||
github_access_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
88
.github/workflows/ci-pr-quality.yml
vendored
88
.github/workflows/ci-pr-quality.yml
vendored
|
|
@ -1,6 +1,7 @@
|
|||
name: 'CI: PR Quality Checks'
|
||||
|
||||
on:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
|
|
@ -46,10 +47,14 @@ jobs:
|
|||
name: Ownership Acknowledgement
|
||||
# Checks that the author has acknowledged the ownership of their code
|
||||
# by checking the checkbox in the PR summary.
|
||||
# Skipped for bot-authored PRs (Dependabot, Renovate, github-actions, Aikido, etc.).
|
||||
# The required aggregator `required-pr-quality-checks` treats skipped as success.
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'automation:backport') &&
|
||||
!contains(github.event.pull_request.title, '(backport to')
|
||||
!contains(github.event.pull_request.title, '(backport to') &&
|
||||
github.event.pull_request.user.type != 'Bot'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
|
|
@ -73,11 +78,15 @@ jobs:
|
|||
check-pr-size:
|
||||
name: PR Size Limit
|
||||
# Checks that the PR size doesn't exceed the limit (currently 1000 lines)
|
||||
# Allows for override via '/size-limit-override' comment
|
||||
# Allows for override via '/size-limit-override' comment.
|
||||
# Skipped for bot-authored PRs — dep bumps from Dependabot/Renovate/Aikido
|
||||
# routinely exceed the size limit and shouldn't be gated on it.
|
||||
if: |
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
!contains(github.event.pull_request.labels.*.name, 'automation:backport') &&
|
||||
!contains(github.event.pull_request.title, '(backport to')
|
||||
!contains(github.event.pull_request.title, '(backport to') &&
|
||||
github.event.pull_request.user.type != 'Bot'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
|
|
@ -97,3 +106,76 @@ jobs:
|
|||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: node .github/scripts/quality/check-pr-size.mjs
|
||||
|
||||
changes:
|
||||
name: Detect Changes
|
||||
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
janitor: ${{ fromJSON(steps.filter.outputs.results).janitor == true }}
|
||||
code-health: ${{ fromJSON(steps.filter.outputs.results)['code-health'] == true }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Detect changed paths
|
||||
id: filter
|
||||
uses: ./.github/actions/ci-filter
|
||||
with:
|
||||
mode: filter
|
||||
filters: |
|
||||
janitor:
|
||||
packages/testing/playwright/**
|
||||
packages/testing/janitor/**
|
||||
code-health:
|
||||
**/package.json
|
||||
pnpm-workspace.yaml
|
||||
.code-health-baseline.json
|
||||
packages/testing/code-health/**
|
||||
|
||||
check-static-analysis:
|
||||
name: Static Analysis
|
||||
needs: changes
|
||||
if: |
|
||||
github.event_name == 'merge_group' ||
|
||||
needs.changes.outputs.code-health == 'true' ||
|
||||
needs.changes.outputs.janitor == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: pnpm turbo run build --filter=@n8n/code-health --filter=@n8n/playwright-janitor
|
||||
|
||||
- name: Run code-health
|
||||
if: github.event_name == 'merge_group' || needs.changes.outputs.code-health == 'true'
|
||||
run: pnpm --filter=@n8n/code-health check
|
||||
|
||||
- name: Run janitor
|
||||
if: ${{ !cancelled() && (github.event_name == 'merge_group' || needs.changes.outputs.janitor == 'true') }}
|
||||
run: pnpm --filter=n8n-playwright janitor
|
||||
|
||||
required-pr-quality-checks:
|
||||
name: Required PR Quality Checks
|
||||
needs: [check-ownership-checkbox, check-pr-size, check-static-analysis]
|
||||
if: always()
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: .github/actions/ci-filter
|
||||
sparse-checkout-cone-mode: false
|
||||
- name: Validate required checks
|
||||
uses: ./.github/actions/ci-filter
|
||||
with:
|
||||
mode: validate
|
||||
job-results: ${{ toJSON(needs) }}
|
||||
|
||||
|
|
|
|||
9
.github/workflows/ci-pull-request-review.yml
vendored
9
.github/workflows/ci-pull-request-review.yml
vendored
|
|
@ -41,7 +41,12 @@ jobs:
|
|||
chromatic:
|
||||
name: Chromatic
|
||||
needs: filter
|
||||
if: needs.filter.outputs.design_system == 'true'
|
||||
# Skip on fork PRs — they don't have access to the Chromatic secret.
|
||||
# This job is intentionally not in `required-review-checks` needs, so it
|
||||
# is non-blocking and won't gate merging.
|
||||
if: >-
|
||||
needs.filter.outputs.design_system == 'true' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
uses: ./.github/workflows/test-visual-chromatic.yml
|
||||
with:
|
||||
ref: ${{ needs.filter.outputs.commit_sha }}
|
||||
|
|
@ -51,7 +56,7 @@ jobs:
|
|||
# PRs cannot be merged unless this job passes.
|
||||
required-review-checks:
|
||||
name: Required Review Checks
|
||||
needs: [filter, chromatic]
|
||||
needs: [filter]
|
||||
if: always()
|
||||
runs-on: ubuntu-slim
|
||||
steps:
|
||||
|
|
|
|||
140
.github/workflows/ci-pull-requests.yml
vendored
140
.github/workflows/ci-pull-requests.yml
vendored
|
|
@ -22,12 +22,17 @@ jobs:
|
|||
ci: ${{ fromJSON(steps.ci-filter.outputs.results).ci == true }}
|
||||
unit: ${{ fromJSON(steps.ci-filter.outputs.results).unit == true }}
|
||||
e2e: ${{ fromJSON(steps.ci-filter.outputs.results).e2e == true }}
|
||||
dev_server_smoke: ${{ fromJSON(steps.ci-filter.outputs.results)['dev-server-smoke'] == true }}
|
||||
workflows: ${{ fromJSON(steps.ci-filter.outputs.results).workflows == true }}
|
||||
workflow_scripts: ${{ fromJSON(steps.ci-filter.outputs.results)['workflow-scripts'] == true }}
|
||||
db: ${{ fromJSON(steps.ci-filter.outputs.results).db == true }}
|
||||
performance: ${{ fromJSON(steps.ci-filter.outputs.results).performance == true }}
|
||||
e2e_performance: ${{ fromJSON(steps.ci-filter.outputs.results)['e2e-performance'] == true }}
|
||||
instance_ai_workflow_eval: ${{ fromJSON(steps.ci-filter.outputs.results)['instance-ai-workflow-eval'] == true }}
|
||||
commit_sha: ${{ steps.commit-sha.outputs.sha }}
|
||||
merge_base: ${{ steps.ci-filter.outputs.merge-base }}
|
||||
matrix: ${{ steps.generate-matrix.outputs.matrix }}
|
||||
skip_tests: ${{ steps.generate-matrix.outputs.skip-tests }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
|
|
@ -55,20 +60,38 @@ jobs:
|
|||
!.github/**
|
||||
e2e:
|
||||
.github/workflows/test-e2e-*.yml
|
||||
.github/scripts/cleanup-ghcr-images.mjs
|
||||
.github/workflows/prepare-docker-reusable.yml
|
||||
.github/actions/build-n8n-docker/**
|
||||
.github/actions/load-n8n-docker/**
|
||||
packages/testing/playwright/**
|
||||
packages/testing/containers/**
|
||||
dev-server-smoke:
|
||||
packages/frontend/editor-ui/vite.config.mts
|
||||
pnpm-workspace.yaml
|
||||
packages/@n8n/*/package.json
|
||||
packages/testing/playwright/tests/dev-server-smoke/**
|
||||
packages/testing/playwright/playwright.config.ts
|
||||
packages/testing/playwright/playwright-projects.ts
|
||||
packages/testing/playwright/package.json
|
||||
.github/workflows/test-dev-server-smoke-reusable.yml
|
||||
workflows: .github/**
|
||||
workflow-scripts: .github/scripts/**
|
||||
performance:
|
||||
packages/testing/performance/**
|
||||
packages/workflow/src/**
|
||||
packages/@n8n/expression-runtime/src/**
|
||||
.github/workflows/test-bench-reusable.yml
|
||||
e2e-performance:
|
||||
packages/testing/playwright/tests/performance/**
|
||||
packages/testing/playwright/utils/performance-helper.ts
|
||||
packages/testing/containers/**
|
||||
.github/workflows/test-e2e-performance-reusable.yml
|
||||
instance-ai-workflow-eval:
|
||||
packages/@n8n/instance-ai/src/**
|
||||
packages/@n8n/instance-ai/evaluations/**
|
||||
packages/cli/src/modules/instance-ai/**
|
||||
packages/core/src/execution-engine/eval-mock-helpers.ts
|
||||
.github/workflows/test-evals-instance-ai*.yml
|
||||
db:
|
||||
packages/cli/src/databases/**
|
||||
packages/cli/src/modules/*/database/**
|
||||
|
|
@ -83,13 +106,27 @@ jobs:
|
|||
.github/workflows/test-db-reusable.yml
|
||||
|
||||
- name: Setup and Build
|
||||
if: fromJSON(steps.ci-filter.outputs.results).ci
|
||||
if: fromJSON(steps.ci-filter.outputs.results).ci || fromJSON(steps.ci-filter.outputs.results).e2e
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: ${{ fromJSON(steps.ci-filter.outputs.results).ci && 'pnpm build' || 'pnpm turbo run build --filter=@n8n/playwright-janitor' }}
|
||||
|
||||
- name: Run format check
|
||||
if: fromJSON(steps.ci-filter.outputs.results).ci
|
||||
run: pnpm format:check
|
||||
|
||||
- name: Generate shard matrix
|
||||
id: generate-matrix
|
||||
if: fromJSON(steps.ci-filter.outputs.results).ci || fromJSON(steps.ci-filter.outputs.results).e2e
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.ci-filter.outputs.changed-files }}
|
||||
MERGE_BASE: ${{ steps.ci-filter.outputs.merge-base }}
|
||||
run: |
|
||||
FILES_CSV=$(echo "$CHANGED_FILES" | tr '\n' ',' | sed 's/,$//')
|
||||
MATRIX=$(node packages/testing/playwright/scripts/distribute-tests.mjs --matrix 16 --orchestrate --impact "--files=$FILES_CSV" "--base=$MERGE_BASE")
|
||||
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
|
||||
echo "skip-tests=$(node -e "process.stdout.write(JSON.parse(process.argv[1])[0]?.skip === true ? 'true' : 'false')" "$MATRIX")" >> "$GITHUB_OUTPUT"
|
||||
|
||||
unit-test:
|
||||
name: Unit tests
|
||||
if: needs.install-and-build.outputs.unit == 'true'
|
||||
|
|
@ -141,14 +178,73 @@ jobs:
|
|||
run: |
|
||||
pnpm -r pack --dry-run
|
||||
|
||||
e2e-tests:
|
||||
name: E2E Tests
|
||||
# Seeds the SHA-keyed Docker image cache once so that downstream e2e jobs
|
||||
# (each of which invokes prepare-docker internally) short-circuit to a
|
||||
# cache hit instead of racing to rebuild.
|
||||
prepare-docker:
|
||||
name: Prepare Docker
|
||||
needs: install-and-build
|
||||
if: (needs.install-and-build.outputs.ci == 'true' || needs.install-and-build.outputs.e2e == 'true') && github.repository == 'n8n-io/n8n' && github.event_name != 'merge_group'
|
||||
uses: ./.github/workflows/test-e2e-ci-reusable.yml
|
||||
if: >-
|
||||
github.repository == 'n8n-io/n8n' &&
|
||||
github.event_name != 'merge_group' &&
|
||||
(needs.install-and-build.outputs.ci == 'true'
|
||||
|| needs.install-and-build.outputs.e2e == 'true'
|
||||
|| needs.install-and-build.outputs.e2e_performance == 'true')
|
||||
uses: ./.github/workflows/prepare-docker-reusable.yml
|
||||
with:
|
||||
branch: ${{ needs.install-and-build.outputs.commit_sha }}
|
||||
playwright-only: ${{ needs.install-and-build.outputs.e2e == 'true' && needs.install-and-build.outputs.unit == 'false' }}
|
||||
secrets: inherit
|
||||
|
||||
# Internal-only 1-spec fail-fast sanity check on sqlite.
|
||||
sqlite-sanity:
|
||||
name: 'SQLite: Sanity Check'
|
||||
needs: [install-and-build, prepare-docker]
|
||||
if: >-
|
||||
needs.prepare-docker.result == 'success' &&
|
||||
(needs.install-and-build.outputs.ci == 'true' || needs.install-and-build.outputs.e2e == 'true') &&
|
||||
github.repository == 'n8n-io/n8n' &&
|
||||
github.event_name != 'merge_group' &&
|
||||
github.event.pull_request.head.repo.fork != true
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
branch: ${{ needs.install-and-build.outputs.commit_sha }}
|
||||
test-mode: docker-artifact
|
||||
test-command: pnpm --filter=n8n-playwright test:container:sqlite:e2e tests/e2e/building-blocks/workflow-entry-points.spec.ts
|
||||
workers: '1'
|
||||
artifact-prefix: sanity
|
||||
secrets: inherit
|
||||
|
||||
# Full e2e run. Internal PRs run multi-main (postgres + redis + caddy + 2 mains + 1 worker).
|
||||
# Fork PRs run sqlite-only and skip @licensed tests (no enterprise license secrets on forks).
|
||||
e2e:
|
||||
name: E2E
|
||||
needs: [install-and-build, prepare-docker]
|
||||
if: >-
|
||||
needs.prepare-docker.result == 'success' &&
|
||||
(needs.install-and-build.outputs.ci == 'true' || needs.install-and-build.outputs.e2e == 'true') &&
|
||||
needs.install-and-build.outputs.skip_tests != 'true' &&
|
||||
github.event_name != 'merge_group'
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
branch: ${{ needs.install-and-build.outputs.commit_sha }}
|
||||
test-mode: docker-artifact
|
||||
test-command: ${{ github.event.pull_request.head.repo.fork == true && 'pnpm --filter=n8n-playwright test:container:sqlite:e2e --grep-invert=@licensed' || 'pnpm --filter=n8n-playwright test:container:multi-main:e2e' }}
|
||||
workers: '1'
|
||||
pre-generated-matrix: ${{ needs.install-and-build.outputs.matrix }}
|
||||
artifact-prefix: e2e
|
||||
secrets: inherit
|
||||
|
||||
# Boots the editor-ui against the Vite dev server and fails on any console
|
||||
# or page error during load. Catches regressions in dev-mode module
|
||||
# resolution (missing Vite alias, broken workspace package interop) that
|
||||
# the production-bundle e2e job bundles around.
|
||||
dev-server-smoke:
|
||||
name: Dev-server boot smoke
|
||||
needs: install-and-build
|
||||
if: needs.install-and-build.outputs.dev_server_smoke == 'true' && github.event_name != 'merge_group'
|
||||
uses: ./.github/workflows/test-dev-server-smoke-reusable.yml
|
||||
with:
|
||||
ref: ${{ needs.install-and-build.outputs.commit_sha }}
|
||||
secrets: inherit
|
||||
|
||||
db-tests:
|
||||
|
|
@ -169,11 +265,12 @@ jobs:
|
|||
|
||||
e2e-performance:
|
||||
name: E2E Performance
|
||||
needs: install-and-build
|
||||
needs: [install-and-build, prepare-docker]
|
||||
# Performance is internal-only (license secrets required, not available on forks).
|
||||
if: >-
|
||||
(needs.install-and-build.outputs.ci == 'true' || needs.install-and-build.outputs.e2e_performance == 'true') &&
|
||||
github.event_name == 'pull_request' &&
|
||||
github.repository == 'n8n-io/n8n'
|
||||
needs.prepare-docker.result == 'success' &&
|
||||
needs.install-and-build.outputs.e2e_performance == 'true' &&
|
||||
github.event.pull_request.head.repo.fork != true
|
||||
uses: ./.github/workflows/test-e2e-performance-reusable.yml
|
||||
secrets: inherit
|
||||
|
||||
|
|
@ -195,6 +292,23 @@ jobs:
|
|||
ref: ${{ needs.install-and-build.outputs.commit_sha }}
|
||||
secrets: inherit
|
||||
|
||||
# Depends on prepare-docker so the eval workflow can load the SHA-keyed image cache.
|
||||
# prepare-docker may be skipped (its filter excludes .github/**); the eval falls back to a local build.
|
||||
instance-ai-workflow-evals:
|
||||
name: Instance AI Workflow Evals
|
||||
needs: [install-and-build, prepare-docker]
|
||||
if: >-
|
||||
!cancelled() &&
|
||||
needs.install-and-build.result == 'success' &&
|
||||
(needs.prepare-docker.result == 'success' || needs.prepare-docker.result == 'skipped') &&
|
||||
needs.install-and-build.outputs.instance_ai_workflow_eval == 'true' &&
|
||||
github.repository == 'n8n-io/n8n' &&
|
||||
(github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
|
||||
uses: ./.github/workflows/test-evals-instance-ai.yml
|
||||
with:
|
||||
branch: ${{ needs.install-and-build.outputs.commit_sha }}
|
||||
secrets: inherit
|
||||
|
||||
# This job is required by GitHub branch protection rules.
|
||||
# PRs cannot be merged unless this job passes.
|
||||
required-checks:
|
||||
|
|
@ -206,7 +320,9 @@ jobs:
|
|||
typecheck,
|
||||
lint,
|
||||
check-packaging,
|
||||
e2e-tests,
|
||||
sqlite-sanity,
|
||||
e2e,
|
||||
dev-server-smoke,
|
||||
db-tests,
|
||||
performance,
|
||||
security-checks,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- 1.x
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
|
@ -46,7 +47,7 @@ jobs:
|
|||
`${marker}\n` +
|
||||
`🚫 **Merge blocked**: PRs into \`${base}\` are only allowed from branches named \`bundle/*\`.\n\n` +
|
||||
`Current source branch: \`${head}\`\n\n` +
|
||||
`Merge your developments into a bundle branch instead of directly merging to master.`;
|
||||
`Merge your developments into a bundle branch instead of directly merging to master or 1.x.`;
|
||||
|
||||
// Find an existing marker comment (to update instead of spamming)
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
|
|
@ -79,7 +80,7 @@ jobs:
|
|||
env:
|
||||
HEAD_REF: ${{ github.head_ref }}
|
||||
run: |
|
||||
echo "::error::You can only merge to master from a bundle/* branch. Got '$HEAD_REF'."
|
||||
echo "::error::You can only merge to master and 1.x from a bundle/* branch. Got '$HEAD_REF'."
|
||||
exit 1
|
||||
|
||||
- name: Allowed
|
||||
|
|
|
|||
10
.github/workflows/docker-build-push.yml
vendored
10
.github/workflows/docker-build-push.yml
vendored
|
|
@ -58,14 +58,18 @@ jobs:
|
|||
|
||||
- name: Determine build context
|
||||
id: context
|
||||
env:
|
||||
N8N_VERSION: ${{ inputs.n8n_version }}
|
||||
RELEASE_TYPE: ${{ inputs.release_type }}
|
||||
PUSH_ENABLED: ${{ inputs.push_enabled }}
|
||||
run: |
|
||||
node .github/scripts/docker/docker-config.mjs \
|
||||
--event "${{ github.event_name }}" \
|
||||
--pr "${{ github.event.pull_request.number }}" \
|
||||
--branch "${{ github.ref_name }}" \
|
||||
--version "${{ inputs.n8n_version }}" \
|
||||
--release-type "${{ inputs.release_type }}" \
|
||||
--push-enabled "${{ inputs.push_enabled }}"
|
||||
--version "$N8N_VERSION" \
|
||||
--release-type "$RELEASE_TYPE" \
|
||||
--push-enabled "$PUSH_ENABLED"
|
||||
|
||||
build-and-push-docker:
|
||||
name: Build App, then Build and Push Docker Image (${{ matrix.platform }})
|
||||
|
|
|
|||
49
.github/workflows/prepare-docker-reusable.yml
vendored
Normal file
49
.github/workflows/prepare-docker-reusable.yml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
name: 'Prepare n8n Docker (reusable)'
|
||||
|
||||
# Reusable workflow that ensures the n8n + runners CI test images for the
|
||||
# current commit SHA are present in the GHA cache. Cache-aware: if another
|
||||
# job in the same run already populated `n8n-docker-image-<sha>`, this
|
||||
# becomes a no-op.
|
||||
#
|
||||
# Downstream jobs restore the same SHA-keyed cache via load-n8n-docker.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
build-variant:
|
||||
description: 'standard or coverage'
|
||||
required: false
|
||||
default: 'standard'
|
||||
type: string
|
||||
runner:
|
||||
description: 'Runner for the build.'
|
||||
required: false
|
||||
default: 'blacksmith-4vcpu-ubuntu-2204'
|
||||
type: string
|
||||
branch:
|
||||
description: 'Git ref to check out.'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: 'Build & publish image'
|
||||
runs-on: ${{ inputs.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.branch || github.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Build and publish image
|
||||
uses: ./.github/actions/build-n8n-docker
|
||||
with:
|
||||
build-variant: ${{ inputs.build-variant }}
|
||||
env:
|
||||
QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }}
|
||||
QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }}
|
||||
QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }}
|
||||
43
.github/workflows/release-build-daytona-snapshot.yml
vendored
Normal file
43
.github/workflows/release-build-daytona-snapshot.yml
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
name: 'Release: Build Daytona snapshot'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
n8n_version:
|
||||
description: 'n8n version to build the Daytona snapshot for'
|
||||
required: true
|
||||
type: string
|
||||
secrets:
|
||||
DAYTONA_API_KEY:
|
||||
required: true
|
||||
DAYTONA_API_URL:
|
||||
required: false
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
n8n_version:
|
||||
description: 'n8n version to build the Daytona snapshot for (e.g. 1.123.0)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-snapshot:
|
||||
name: Build versioned Daytona snapshot
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Node.js and build
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
|
||||
- name: Build versioned Daytona snapshot
|
||||
env:
|
||||
N8N_VERSION: ${{ inputs.n8n_version }}
|
||||
DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
|
||||
DAYTONA_API_URL: ${{ secrets.DAYTONA_API_URL }}
|
||||
run: node packages/@n8n/instance-ai/scripts/build-snapshot.cjs --version "$N8N_VERSION"
|
||||
|
|
@ -3,7 +3,7 @@ name: 'Release: Create Minor Release PR'
|
|||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: 0 13 * * 1 # 2pm CET (UTC+1), Monday
|
||||
- cron: 0 8 * * 2 # 9am CET (UTC+1), Tuesday
|
||||
|
||||
jobs:
|
||||
create-release-pr:
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
echo "Package '$PACKAGE_NAME' does not exist on NPM yet. Proceeding with publish."
|
||||
|
||||
- name: Configure NPM token
|
||||
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_INITIAL_PUBLISH_TOKEN }}" > ~/.npmrc
|
||||
run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_DIST_TAG_AND_INITIAL_PUBLISH_TOKEN }}" > ~/.npmrc
|
||||
|
||||
- name: Publish package
|
||||
working-directory: ${{ github.event.inputs.package-path }}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@ jobs:
|
|||
uses: ./.github/workflows/util-ensure-release-candidate-branches.yml
|
||||
secrets: inherit
|
||||
|
||||
ensure-correct-latest-version-on-npm:
|
||||
name: Ensure correct latest version on npm
|
||||
if: |
|
||||
inputs.bump == 'minor' ||
|
||||
inputs.track == 'stable'
|
||||
uses: ./.github/workflows/release-set-stable-npm-packages-to-latest.yml
|
||||
secrets: inherit
|
||||
|
||||
populate-cloud-with-releases:
|
||||
name: 'Populate cloud database with releases'
|
||||
uses: ./.github/workflows/release-populate-cloud-with-releases.yml
|
||||
|
|
|
|||
27
.github/workflows/release-publish.yml
vendored
27
.github/workflows/release-publish.yml
vendored
|
|
@ -76,15 +76,13 @@ jobs:
|
|||
cp README.md packages/cli/README.md
|
||||
sed -i "s/default: 'dev'/default: '${{ needs.determine-version-info.outputs.release_type }}'/g" packages/cli/dist/config/schema.js
|
||||
|
||||
- name: Publish n8n to NPM with rc tag
|
||||
env:
|
||||
PUBLISH_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
run: pnpm --filter n8n publish --publish-branch "$PUBLISH_BRANCH" --access public --tag rc --no-git-checks
|
||||
|
||||
# Publishing via `pnpm publish -r` is idempotent, as it checks if the package exists
|
||||
# and only publishes if it doesn't. This is why we do the sub-packages before the main n8n package.
|
||||
# So if anything goes wrong, we can easily re-try the run instead of abandoning the release.
|
||||
- name: Publish other packages to NPM
|
||||
env:
|
||||
PUBLISH_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
PUBLISH_TAG: ${{ needs.determine-version-info.outputs.track == 'stable' && 'latest' || needs.determine-version-info.outputs.track }}
|
||||
PUBLISH_TAG: ${{ needs.determine-version-info.outputs.track }}
|
||||
run: |
|
||||
# Prefix version-like track names (e.g. "1", "v1") to avoid npm rejecting them as semver ranges
|
||||
if [[ "$PUBLISH_TAG" =~ ^v?[0-9] ]]; then
|
||||
|
|
@ -92,6 +90,12 @@ jobs:
|
|||
fi
|
||||
pnpm publish -r --filter '!n8n' --publish-branch "$PUBLISH_BRANCH" --access public --tag "$PUBLISH_TAG" --no-git-checks
|
||||
|
||||
# If we don't use the --tag rc, all releases will default to "latest".
|
||||
- name: Publish n8n to NPM with rc tag
|
||||
env:
|
||||
PUBLISH_BRANCH: ${{ github.event.pull_request.base.ref }}
|
||||
run: pnpm --filter n8n publish --publish-branch "$PUBLISH_BRANCH" --access public --tag rc --no-git-checks
|
||||
|
||||
- name: Cleanup rc tag
|
||||
run: npm dist-tag rm n8n rc
|
||||
continue-on-error: true
|
||||
|
|
@ -105,6 +109,15 @@ jobs:
|
|||
release_type: ${{ needs.determine-version-info.outputs.release_type }}
|
||||
secrets: inherit
|
||||
|
||||
build-daytona-snapshot:
|
||||
name: Build Daytona snapshot
|
||||
needs: [determine-version-info, publish-to-npm]
|
||||
if: github.event.pull_request.merged == true
|
||||
uses: ./.github/workflows/release-build-daytona-snapshot.yml
|
||||
with:
|
||||
n8n_version: ${{ needs.determine-version-info.outputs.version }}
|
||||
secrets: inherit
|
||||
|
||||
create-github-release:
|
||||
name: Create GitHub Release
|
||||
needs: [determine-version-info, publish-to-npm, publish-to-docker-hub]
|
||||
|
|
@ -183,11 +196,13 @@ jobs:
|
|||
create-github-release,
|
||||
move-track-tag,
|
||||
promote-stable-tag,
|
||||
build-daytona-snapshot,
|
||||
]
|
||||
if: |
|
||||
always() &&
|
||||
needs.publish-to-npm.result == 'success' &&
|
||||
needs.create-github-release.result == 'success' &&
|
||||
needs.build-daytona-snapshot.result == 'success' &&
|
||||
(needs.move-track-tag.result == 'success' || needs.move-track-tag.result == 'skipped') &&
|
||||
(needs.promote-stable-tag.result == 'success' || needs.promote-stable-tag.result == 'skipped')
|
||||
uses: ./.github/workflows/release-publish-post-release.yml
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ name: 'Release: Schedule Patch Release PRs'
|
|||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 8 * * 2-5' # 9am CET (UTC+1), Tuesday–Friday
|
||||
- cron: '0 8 * * 3-5,1' # 9am CET (UTC+1), Wednesday - Friday and Monday. (Minor release on tuesday)
|
||||
|
||||
jobs:
|
||||
create-patch-prs:
|
||||
|
|
|
|||
32
.github/workflows/release-set-stable-npm-packages-to-latest.yml
vendored
Normal file
32
.github/workflows/release-set-stable-npm-packages-to-latest.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: 'Release: Set stable npm packages to latest'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
promote-github-releases:
|
||||
name: Promote current stable releases as latest
|
||||
runs-on: ubuntu-slim
|
||||
environment: release
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: refs/tags/stable
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup NodeJS
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: ''
|
||||
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
|
||||
|
||||
- name: Set npm packages to latest
|
||||
run: node ./.github/scripts/set-latest-for-monorepo-packages.mjs
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_DIST_TAG_AND_INITIAL_PUBLISH_TOKEN }}
|
||||
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
output-file: sbom-source.cdx.json
|
||||
|
||||
- name: Attest SBOM for source release
|
||||
uses: actions/attest-sbom@07e74fc4e78d1aad915e867f9a094073a9f71527 # v4.0.0
|
||||
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
|
||||
with:
|
||||
subject-path: './package.json'
|
||||
sbom-path: 'sbom-source.cdx.json'
|
||||
|
|
@ -64,9 +64,10 @@ jobs:
|
|||
- name: Attach SBOM and VEX files to release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_TAG_REF: ${{ inputs.release_tag_ref }}
|
||||
run: |
|
||||
# Upload SBOM and VEX files to the existing release
|
||||
gh release upload "${{ inputs.release_tag_ref }}" \
|
||||
gh release upload "$RELEASE_TAG_REF" \
|
||||
sbom-source.cdx.json \
|
||||
security/vex.openvex.json \
|
||||
--clobber
|
||||
|
|
|
|||
14
.github/workflows/sec-sync-public-to-private.yml
vendored
14
.github/workflows/sec-sync-public-to-private.yml
vendored
|
|
@ -40,6 +40,9 @@ jobs:
|
|||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Sync master from public
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
FORCE: ${{ inputs.force }}
|
||||
run: |
|
||||
git fetch https://github.com/n8n-io/n8n.git master:public-master
|
||||
|
||||
|
|
@ -47,10 +50,10 @@ jobs:
|
|||
AHEAD_COUNT=$(git rev-list public-master..HEAD --pretty=oneline --grep="chore: Bundle" --invert-grep --count)
|
||||
|
||||
if [ "$AHEAD_COUNT" -gt 0 ]; then
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
if [ "$EVENT_NAME" = "schedule" ]; then
|
||||
echo "Private is $AHEAD_COUNT commit(s) ahead of public, skipping scheduled sync"
|
||||
exit 0
|
||||
elif [ "${{ inputs.force }}" != "true" ]; then
|
||||
elif [ "$FORCE" != "true" ]; then
|
||||
echo "Private is $AHEAD_COUNT commit(s) ahead of public, skipping (force not enabled)"
|
||||
exit 0
|
||||
else
|
||||
|
|
@ -62,6 +65,9 @@ jobs:
|
|||
git push origin master --force-with-lease
|
||||
|
||||
- name: Sync 1.x from public
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
FORCE: ${{ inputs.force }}
|
||||
run: |
|
||||
git fetch https://github.com/n8n-io/n8n.git 1.x:public-1.x
|
||||
git checkout 1.x
|
||||
|
|
@ -70,10 +76,10 @@ jobs:
|
|||
AHEAD_COUNT=$(git rev-list public-1.x..HEAD --pretty=oneline --grep="chore: Bundle" --invert-grep --count)
|
||||
|
||||
if [ "$AHEAD_COUNT" -gt 0 ]; then
|
||||
if [ "${{ github.event_name }}" = "schedule" ]; then
|
||||
if [ "$EVENT_NAME" = "schedule" ]; then
|
||||
echo "Private 1.x is $AHEAD_COUNT commit(s) ahead of public, skipping scheduled sync"
|
||||
exit 0
|
||||
elif [ "${{ inputs.force }}" != "true" ]; then
|
||||
elif [ "$FORCE" != "true" ]; then
|
||||
echo "Private 1.x is $AHEAD_COUNT commit(s) ahead of public, skipping (force not enabled)"
|
||||
exit 0
|
||||
else
|
||||
|
|
|
|||
|
|
@ -40,7 +40,9 @@ jobs:
|
|||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Pull Docker image with retry
|
||||
run: node .github/scripts/retry.mjs --attempts 4 --delay 15 'docker pull "${{ inputs.image_ref }}"'
|
||||
env:
|
||||
IMAGE_REF: ${{ inputs.image_ref }}
|
||||
run: node .github/scripts/retry.mjs --attempts 4 --delay 15 -- docker pull "$IMAGE_REF"
|
||||
|
||||
- name: Run Trivy vulnerability scanner
|
||||
uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # v0.34.1
|
||||
|
|
@ -90,11 +92,13 @@ jobs:
|
|||
|
||||
- name: Generate GitHub Job Summary
|
||||
if: always()
|
||||
env:
|
||||
IMAGE_REF: ${{ inputs.image_ref }}
|
||||
run: |
|
||||
{
|
||||
echo "# 🛡️ Trivy Security Scan Results"
|
||||
echo ""
|
||||
echo "**Image:** \`${{ inputs.image_ref }}\`"
|
||||
echo "**Image:** \`$IMAGE_REF\`"
|
||||
echo "**Scan Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
|
||||
echo ""
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
|
@ -125,7 +129,7 @@ jobs:
|
|||
|
||||
{
|
||||
# Generate detailed vulnerability table
|
||||
jq -r --arg image_ref "${{ inputs.image_ref }}" '
|
||||
jq -r --arg image_ref "$IMAGE_REF" '
|
||||
# Collect all vulnerabilities
|
||||
[.Results[] | select(.Vulnerabilities != null) | .Vulnerabilities[]] |
|
||||
# Group by CVE ID to avoid duplicates
|
||||
|
|
@ -165,8 +169,10 @@ jobs:
|
|||
- name: Generate Slack Blocks JSON
|
||||
if: steps.process_results.outputs.vulnerabilities_found == 'true'
|
||||
id: generate_blocks
|
||||
env:
|
||||
IMAGE_REF: ${{ inputs.image_ref }}
|
||||
run: |
|
||||
BLOCKS_JSON=$(jq -c --arg image_ref "${{ inputs.image_ref }}" \
|
||||
BLOCKS_JSON=$(jq -c --arg image_ref "$IMAGE_REF" \
|
||||
--arg repo_url "${{ github.server_url }}/${{ github.repository }}" \
|
||||
--arg repo_name "${{ github.repository }}" \
|
||||
--arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
|
||||
|
|
|
|||
49
.github/workflows/test-dev-server-smoke-reusable.yml
vendored
Normal file
49
.github/workflows/test-dev-server-smoke-reusable.yml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
name: 'Test: Dev-server boot smoke'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ref:
|
||||
description: 'Git ref to test'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
NODE_OPTIONS: '--max-old-space-size=6144'
|
||||
PLAYWRIGHT_BROWSERS_PATH: packages/testing/playwright/.playwright-browsers
|
||||
|
||||
jobs:
|
||||
smoke:
|
||||
name: Dev-server smoke
|
||||
runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-4vcpu-ubuntu-2204' }}
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ inputs.ref }}
|
||||
|
||||
- name: Setup and Build
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
|
||||
- name: Install Browsers
|
||||
run: pnpm turbo run install-browsers --filter=n8n-playwright
|
||||
|
||||
- name: Run dev-server smoke spec
|
||||
# Run from repo root so PLAYWRIGHT_BROWSERS_PATH (relative) resolves
|
||||
# correctly. cd-ing into the playwright package double-nests it.
|
||||
run: pnpm --filter=n8n-playwright test:dev-server-smoke --reporter=list
|
||||
|
||||
- name: Upload Failure Artifacts
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: dev-server-smoke-report
|
||||
path: |
|
||||
packages/testing/playwright/test-results/
|
||||
packages/testing/playwright/playwright-report/
|
||||
retention-days: 7
|
||||
176
.github/workflows/test-e2e-ci-reusable.yml
vendored
176
.github/workflows/test-e2e-ci-reusable.yml
vendored
|
|
@ -1,176 +0,0 @@
|
|||
name: 'Test: E2E CI'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'GitHub branch/ref to test'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
playwright-only:
|
||||
description: 'Only Playwright files changed — run impacted tests only'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
n8n-env:
|
||||
description: 'JSON string of n8n env vars to inject into test containers, e.g. {"N8N_EXPRESSION_ENGINE":"vm"}'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'GitHub branch/ref to test'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
playwright-only:
|
||||
description: 'Only Playwright files changed — run impacted tests only'
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
n8n-env:
|
||||
description: 'JSON string of n8n env vars to inject into test containers (e.g. {"N8N_EXPRESSION_ENGINE":"vm"})'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
|
||||
env:
|
||||
DOCKER_IMAGE: ghcr.io/${{ github.repository }}:ci-${{ github.run_id }}
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
name: 'Prepare E2E'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
outputs:
|
||||
matrix: ${{ steps.generate-matrix.outputs.matrix }}
|
||||
skip-tests: ${{ steps.generate-matrix.outputs.skip-tests }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.branch || github.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push to GHCR
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: 'pnpm build:docker'
|
||||
enable-docker-cache: true
|
||||
env:
|
||||
INCLUDE_TEST_CONTROLLER: 'true'
|
||||
IMAGE_BASE_NAME: ghcr.io/${{ github.repository }}
|
||||
IMAGE_TAG: ci-${{ github.run_id }}
|
||||
RUNNERS_IMAGE_BASE_NAME: ghcr.io/${{ github.repository_owner }}/runners
|
||||
QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }}
|
||||
QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }}
|
||||
QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }}
|
||||
|
||||
- name: Get changed files for impact analysis
|
||||
if: ${{ inputs.playwright-only }}
|
||||
id: changed-files
|
||||
run: |
|
||||
git fetch --depth=1 origin ${{ github.event.pull_request.base.ref || 'master' }}
|
||||
echo "list=$(git diff --name-only FETCH_HEAD HEAD | tr '\n' ',' | sed 's/,$//')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate shard matrix
|
||||
id: generate-matrix
|
||||
env:
|
||||
CHANGED_FILES: ${{ steps.changed-files.outputs.list }}
|
||||
run: |
|
||||
ARGS=(--matrix 16 --orchestrate)
|
||||
if [[ "${{ inputs.playwright-only }}" == "true" ]]; then
|
||||
ARGS+=(--impact "--files=$CHANGED_FILES" "--base=FETCH_HEAD")
|
||||
fi
|
||||
MATRIX=$(node packages/testing/playwright/scripts/distribute-tests.mjs "${ARGS[@]}")
|
||||
echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
|
||||
echo "skip-tests=$(node -e "process.stdout.write(JSON.parse(process.argv[1])[0]?.skip === true ? 'true' : 'false')" "$MATRIX")" >> "$GITHUB_OUTPUT"
|
||||
|
||||
sqlite-sanity:
|
||||
needs: [prepare]
|
||||
name: 'SQLite: Sanity Check'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork }}
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
branch: ${{ inputs.branch }}
|
||||
test-mode: docker-pull
|
||||
docker-image: ghcr.io/${{ github.repository }}:ci-${{ github.run_id }}
|
||||
test-command: pnpm --filter=n8n-playwright test:container:sqlite:e2e tests/e2e/building-blocks/workflow-entry-points.spec.ts
|
||||
shards: 1
|
||||
runner: blacksmith-2vcpu-ubuntu-2204
|
||||
workers: '1'
|
||||
pre-generated-matrix: '[{"shard":1,"images":""}]'
|
||||
n8n-env: ${{ inputs.n8n-env }}
|
||||
|
||||
# Multi-main: postgres + redis + caddy + 2 mains + 1 worker
|
||||
# Only runs for internal PRs (not community/fork PRs)
|
||||
# Pulls pre-built Docker image from GHCR
|
||||
multi-main-e2e:
|
||||
needs: [prepare]
|
||||
name: 'Multi-Main: E2E'
|
||||
if: ${{ !github.event.pull_request.head.repo.fork && needs.prepare.outputs.skip-tests != 'true' }}
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
branch: ${{ inputs.branch }}
|
||||
test-mode: docker-pull
|
||||
docker-image: ghcr.io/${{ github.repository }}:ci-${{ github.run_id }}
|
||||
test-command: pnpm --filter=n8n-playwright test:container:multi-main:e2e
|
||||
shards: 16
|
||||
runner: blacksmith-2vcpu-ubuntu-2204
|
||||
workers: '1'
|
||||
use-custom-orchestration: true
|
||||
pre-generated-matrix: ${{ needs.prepare.outputs.matrix }}
|
||||
n8n-env: ${{ inputs.n8n-env }}
|
||||
secrets: inherit
|
||||
|
||||
# Community PR tests: Local mode with SQLite (no container building, no secrets required)
|
||||
# Runs on GitHub-hosted runners without Currents reporting
|
||||
community-e2e:
|
||||
name: 'Community: E2E'
|
||||
if: ${{ github.event.pull_request.head.repo.fork }}
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
branch: ${{ inputs.branch }}
|
||||
test-mode: local
|
||||
test-command: pnpm --filter=n8n-playwright test:local
|
||||
shards: 7
|
||||
runner: ubuntu-latest
|
||||
workers: '1'
|
||||
upload-failure-artifacts: true
|
||||
|
||||
# Cleanup ephemeral Docker image from GHCR after tests complete
|
||||
# Local runner cleanup is handled by each test shard in test-e2e-reusable.yml
|
||||
cleanup-ghcr:
|
||||
name: 'Cleanup GHCR Image'
|
||||
needs: [prepare, multi-main-e2e, sqlite-sanity]
|
||||
if: ${{ !failure() && !cancelled() && !github.event.pull_request.head.repo.fork }}
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2204
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
sparse-checkout: .github/scripts
|
||||
sparse-checkout-cone-mode: false
|
||||
|
||||
- name: Delete images from GHCR
|
||||
continue-on-error: true
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GHCR_ORG: ${{ github.repository_owner }}
|
||||
GHCR_REPO: ${{ github.event.repository.name }}
|
||||
run: node .github/scripts/cleanup-ghcr-images.mjs --tag ci-${{ github.run_id }}
|
||||
72
.github/workflows/test-e2e-coverage-weekly.yml
vendored
72
.github/workflows/test-e2e-coverage-weekly.yml
vendored
|
|
@ -5,48 +5,58 @@ on:
|
|||
- cron: '0 2 * * 1' # Every Monday at 2 AM
|
||||
workflow_dispatch: # Allow manual triggering
|
||||
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=16384
|
||||
PLAYWRIGHT_WORKERS: 4
|
||||
PLAYWRIGHT_BROWSERS_PATH: packages/testing/playwright/.playwright-browsers
|
||||
|
||||
jobs:
|
||||
coverage:
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2204
|
||||
name: Coverage Tests
|
||||
prepare-docker:
|
||||
name: Prepare Docker (coverage)
|
||||
uses: ./.github/workflows/prepare-docker-reusable.yml
|
||||
with:
|
||||
build-variant: coverage
|
||||
runner: blacksmith-8vcpu-ubuntu-2204
|
||||
secrets: inherit
|
||||
|
||||
e2e:
|
||||
name: E2E (coverage)
|
||||
needs: prepare-docker
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
test-mode: docker-artifact
|
||||
test-command: pnpm --filter=n8n-playwright test:container:coverage
|
||||
workers: '1'
|
||||
runner: blacksmith-4vcpu-ubuntu-2204
|
||||
timeout-minutes: 45
|
||||
pre-generated-matrix: '[{"shard":1,"images":""},{"shard":2,"images":""},{"shard":3,"images":""},{"shard":4,"images":""}]'
|
||||
artifact-prefix: coverage
|
||||
secrets: inherit
|
||||
|
||||
aggregate:
|
||||
name: Aggregate Coverage
|
||||
needs: e2e
|
||||
if: always() && needs.e2e.result != 'skipped' && needs.e2e.result != 'cancelled'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
env:
|
||||
INCLUDE_TEST_CONTROLLER: 'true'
|
||||
|
||||
- name: Build Docker Image with Coverage
|
||||
run: pnpm build:docker:coverage
|
||||
env:
|
||||
INCLUDE_TEST_CONTROLLER: 'true'
|
||||
- name: Download shard artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
|
||||
with:
|
||||
pattern: coverage-shard-*
|
||||
path: /tmp/shards/
|
||||
|
||||
- name: Install Browsers
|
||||
run: pnpm turbo run install-browsers --filter=n8n-playwright
|
||||
|
||||
- name: Run Container Coverage Tests
|
||||
id: coverage-tests
|
||||
- name: Collect coverage JSON
|
||||
shell: bash
|
||||
run: |
|
||||
pnpm --filter n8n-playwright test:container:sqlite \
|
||||
--workers=${{ env.PLAYWRIGHT_WORKERS }}
|
||||
env:
|
||||
BUILD_WITH_COVERAGE: 'true'
|
||||
CURRENTS_RECORD_KEY: ${{ secrets.CURRENTS_RECORD_KEY }}
|
||||
CURRENTS_PROJECT_ID: 'LRxcNt'
|
||||
QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }}
|
||||
QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }}
|
||||
QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }}
|
||||
mkdir -p packages/testing/playwright/.nyc_output/coverage
|
||||
found=$(find /tmp/shards -path '*/.nyc_output/coverage/*.json' 2>/dev/null | wc -l)
|
||||
echo "Found $found coverage JSON files across shards"
|
||||
find /tmp/shards -path '*/.nyc_output/coverage/*.json' \
|
||||
-exec cp {} packages/testing/playwright/.nyc_output/coverage/ \;
|
||||
ls -la packages/testing/playwright/.nyc_output/coverage/ || true
|
||||
|
||||
- name: Generate Coverage Report
|
||||
if: always() && steps.coverage-tests.outcome != 'skipped'
|
||||
run: pnpm --filter n8n-playwright coverage:report
|
||||
|
||||
- name: Upload Coverage Report Artifact
|
||||
|
|
@ -68,7 +78,7 @@ jobs:
|
|||
fail_ci_if_error: false
|
||||
|
||||
- name: Analyse Coverage Gaps
|
||||
if: always() && steps.coverage-tests.outcome != 'skipped'
|
||||
if: always()
|
||||
env:
|
||||
CODECOV_API_TOKEN: ${{ secrets.CODECOV_API_TOKEN }}
|
||||
run: |
|
||||
|
|
@ -76,7 +86,7 @@ jobs:
|
|||
--md --top=15 --out-json=coverage-gaps.json >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload Coverage Gap Report
|
||||
if: always() && steps.coverage-tests.outcome != 'skipped'
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: coverage-gap-report
|
||||
|
|
|
|||
|
|
@ -1,38 +0,0 @@
|
|||
name: 'Test: E2E Docker Pull'
|
||||
# This workflow is used to run Playwright tests in a Docker container pulled from the registry
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
shards:
|
||||
description: 'Shards for parallel execution'
|
||||
required: false
|
||||
default: 1
|
||||
type: number
|
||||
image:
|
||||
description: 'Image to use'
|
||||
required: false
|
||||
default: 'n8nio/n8n:nightly'
|
||||
type: string
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
shards:
|
||||
description: 'Shards for parallel execution'
|
||||
required: false
|
||||
default: 1
|
||||
type: number
|
||||
image:
|
||||
description: 'Image to use'
|
||||
required: false
|
||||
default: 'n8nio/n8n:nightly'
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
test-mode: docker-pull
|
||||
shards: ${{ inputs.shards }}
|
||||
docker-image: ${{ inputs.image }}
|
||||
test-command: pnpm --filter=n8n-playwright test:container:standard
|
||||
secrets: inherit
|
||||
|
|
@ -17,23 +17,26 @@ concurrency:
|
|||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
prepare-docker:
|
||||
uses: ./.github/workflows/prepare-docker-reusable.yml
|
||||
secrets: inherit
|
||||
|
||||
benchmark:
|
||||
name: ${{ matrix.profile }}
|
||||
needs: [prepare-docker]
|
||||
name: benchmarking
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- profile: benchmark-direct
|
||||
runner: blacksmith-4vcpu-ubuntu-2204
|
||||
- profile: benchmark-queue
|
||||
runner: blacksmith-8vcpu-ubuntu-2204
|
||||
- profile: benchmark-queue-tuned
|
||||
runner: blacksmith-8vcpu-ubuntu-2204
|
||||
- runner: blacksmith-8vcpu-ubuntu-2204
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
test-mode: docker-build
|
||||
test-command: pnpm --filter=n8n-playwright test:all --project='${{ matrix.profile }}:infrastructure' --workers=1
|
||||
shards: 1
|
||||
test-mode: docker-artifact
|
||||
# Runs the full benchmark suite. Each spec brings its own container via
|
||||
# `test.use({ capability })`, so workers must be 1 (one container at a time).
|
||||
test-command: 'pnpm --filter=n8n-playwright test:benchmark'
|
||||
workers: '1'
|
||||
runner: ${{ matrix.runner }}
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 120
|
||||
artifact-prefix: benchmark
|
||||
secrets: inherit
|
||||
|
|
|
|||
|
|
@ -7,11 +7,17 @@ on:
|
|||
- cron: '0 0 * * *' # Runs daily at midnight
|
||||
|
||||
jobs:
|
||||
# Cache-aware: no-op if an ancestor already populated the SHA-keyed cache.
|
||||
prepare-docker:
|
||||
uses: ./.github/workflows/prepare-docker-reusable.yml
|
||||
secrets: inherit
|
||||
|
||||
build-and-test-performance:
|
||||
needs: [prepare-docker]
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
test-mode: docker-build
|
||||
test-mode: docker-artifact
|
||||
test-command: pnpm --filter=n8n-playwright test:performance
|
||||
shards: 1
|
||||
currents-project-id: 'O9BJaN'
|
||||
artifact-prefix: performance
|
||||
secrets: inherit
|
||||
|
|
|
|||
100
.github/workflows/test-e2e-reusable.yml
vendored
100
.github/workflows/test-e2e-reusable.yml
vendored
|
|
@ -8,7 +8,7 @@ on:
|
|||
required: false
|
||||
type: string
|
||||
test-mode:
|
||||
description: 'Test mode: local (pnpm start from local), docker-build, or docker-pull'
|
||||
description: 'Test mode: local or docker-artifact'
|
||||
required: false
|
||||
default: 'local'
|
||||
type: string
|
||||
|
|
@ -17,16 +17,6 @@ on:
|
|||
required: false
|
||||
default: 'pnpm --filter=n8n-playwright test:local'
|
||||
type: string
|
||||
shards:
|
||||
description: 'Number of parallel shards'
|
||||
required: false
|
||||
default: 8
|
||||
type: number
|
||||
docker-image:
|
||||
description: 'Docker image to use (for docker-pull mode). The runners image is derived automatically from the n8n image.'
|
||||
required: false
|
||||
default: 'n8nio/n8n:nightly'
|
||||
type: string
|
||||
workers:
|
||||
description: 'Number of parallel workers'
|
||||
required: false
|
||||
|
|
@ -37,80 +27,53 @@ on:
|
|||
required: false
|
||||
default: 'blacksmith-2vcpu-ubuntu-2204'
|
||||
type: string
|
||||
use-custom-orchestration:
|
||||
description: 'Use duration-based custom orchestration instead of Playwright sharding'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
timeout-minutes:
|
||||
description: 'Job timeout in minutes'
|
||||
required: false
|
||||
default: 30
|
||||
type: number
|
||||
upload-failure-artifacts:
|
||||
description: 'Upload test failure artifacts (screenshots, traces, videos). Enable for community PRs without Currents access.'
|
||||
required: false
|
||||
default: false
|
||||
type: boolean
|
||||
currents-project-id:
|
||||
description: 'Currents project ID for reporting'
|
||||
required: false
|
||||
default: 'LRxcNt'
|
||||
type: string
|
||||
pre-generated-matrix:
|
||||
description: 'Pre-generated shard matrix JSON (skips matrix job if provided)'
|
||||
description: 'Pre-generated shard matrix JSON. Defaults to 1 shard; multi-shard callers pass their own.'
|
||||
required: false
|
||||
default: ''
|
||||
default: '[{"shard":1,"images":""}]'
|
||||
type: string
|
||||
n8n-env:
|
||||
description: 'JSON string of n8n env vars to inject into test containers, e.g. {"N8N_EXPRESSION_ENGINE":"vm"}'
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
artifact-prefix:
|
||||
description: 'Prefix for uploaded shard artifacts'
|
||||
required: false
|
||||
default: 'e2e'
|
||||
type: string
|
||||
|
||||
env:
|
||||
NODE_OPTIONS: ${{ contains(inputs.runner, '2vcpu') && '--max-old-space-size=6144' || '' }}
|
||||
PLAYWRIGHT_WORKERS: ${{ inputs.workers != '' && inputs.workers || '2' }}
|
||||
# Browser cache location - must match install-browsers script
|
||||
PLAYWRIGHT_BROWSERS_PATH: packages/testing/playwright/.playwright-browsers
|
||||
TEST_IMAGE_N8N: ${{ inputs.test-mode == 'docker-build' && 'n8nio/n8n:local' || inputs.docker-image }}
|
||||
# docker-artifact loads the image locally as n8nio/n8n:local; unused in local mode.
|
||||
TEST_IMAGE_N8N: 'n8nio/n8n:local'
|
||||
N8N_SKIP_LICENSES: 'true'
|
||||
CURRENTS_CI_BUILD_ID: ${{ github.repository }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
CURRENTS_PROJECT_ID: ${{ inputs.currents-project-id }}
|
||||
|
||||
jobs:
|
||||
matrix:
|
||||
if: ${{ inputs.pre-generated-matrix == '' }}
|
||||
runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-2vcpu-ubuntu-2204' }}
|
||||
outputs:
|
||||
matrix: ${{ steps.generate.outputs.matrix }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.branch || github.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: ''
|
||||
|
||||
- name: Generate shard matrix
|
||||
id: generate
|
||||
run: echo "matrix=$(node packages/testing/playwright/scripts/distribute-tests.mjs --matrix ${{ inputs.shards }} ${{ inputs.use-custom-orchestration && '--orchestrate' || '' }})" >> "$GITHUB_OUTPUT"
|
||||
|
||||
test:
|
||||
needs: matrix
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || inputs.runner }}
|
||||
timeout-minutes: ${{ inputs.timeout-minutes }}
|
||||
permissions:
|
||||
packages: read
|
||||
contents: read
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include: ${{ fromJSON(inputs.pre-generated-matrix || needs.matrix.outputs.matrix) }}
|
||||
include: ${{ fromJSON(inputs.pre-generated-matrix) }}
|
||||
name: Shard ${{ matrix.shard }}
|
||||
|
||||
steps:
|
||||
|
|
@ -122,14 +85,7 @@ jobs:
|
|||
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
# docker-build: build app + docker image locally
|
||||
# docker-pull: no build needed, image is pre-built
|
||||
# local: build app for local server
|
||||
build-command: ${{ inputs.test-mode == 'docker-build' && 'pnpm build:docker' || 'pnpm build' }}
|
||||
enable-docker-cache: ${{ inputs.test-mode == 'docker-build' }}
|
||||
env:
|
||||
INCLUDE_TEST_CONTROLLER: ${{ inputs.test-mode == 'docker-build' && 'true' || '' }}
|
||||
QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }}
|
||||
QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }}
|
||||
QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }}
|
||||
|
|
@ -137,18 +93,25 @@ jobs:
|
|||
- name: Install Browsers
|
||||
run: pnpm turbo run install-browsers --filter=n8n-playwright
|
||||
|
||||
- name: Login to GHCR
|
||||
if: ${{ inputs.test-mode == 'docker-pull' }}
|
||||
uses: ./.github/actions/docker-registry-login
|
||||
- name: Load n8n image from cache
|
||||
if: ${{ inputs.test-mode == 'docker-artifact' }}
|
||||
uses: ./.github/actions/load-n8n-docker
|
||||
|
||||
- name: Pre-pull Test Container Images
|
||||
if: ${{ !contains(inputs.test-command, 'test:local') }}
|
||||
run: npx tsx packages/testing/containers/pull-test-images.ts ${{ matrix.images }} || true
|
||||
|
||||
- name: Run Tests
|
||||
# Uses pre-distributed specs if orchestration enabled, otherwise falls back to Playwright sharding
|
||||
run: ${{ inputs.test-command }} --workers=${{ env.PLAYWRIGHT_WORKERS }} ${{ matrix.specs || format('--shard={0}/{1}', matrix.shard, strategy.job-total) }}
|
||||
run: |
|
||||
# shellcheck disable=SC2086
|
||||
$TEST_COMMAND --workers="$WORKERS" $SHARD_ARGS
|
||||
env:
|
||||
# Protect args from template injections
|
||||
TEST_COMMAND: ${{ inputs.test-command }}
|
||||
# Uses pre-distributed specs if orchestration enabled, otherwise falls back to Playwright sharding
|
||||
WORKERS: ${{ env.PLAYWRIGHT_WORKERS }}
|
||||
SHARD_ARGS: ${{ matrix.specs || format('--shard={0}/{1}', matrix.shard, strategy.job-total) }}
|
||||
# Args for actual test command runner
|
||||
CURRENTS_RECORD_KEY: ${{ secrets.CURRENTS_RECORD_KEY }}
|
||||
QA_METRICS_WEBHOOK_URL: ${{ secrets.QA_METRICS_WEBHOOK_URL }}
|
||||
QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }}
|
||||
|
|
@ -158,22 +121,17 @@ jobs:
|
|||
N8N_ENCRYPTION_KEY: ${{ secrets.N8N_ENCRYPTION_KEY }}
|
||||
N8N_TEST_ENV: ${{ inputs.n8n-env }}
|
||||
|
||||
- name: Upload Failure Artifacts
|
||||
if: ${{ failure() && inputs.upload-failure-artifacts }}
|
||||
- name: Upload Shard Artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: playwright-report-shard-${{ matrix.shard }}
|
||||
name: ${{ inputs.artifact-prefix }}-shard-${{ matrix.shard }}
|
||||
path: |
|
||||
packages/testing/playwright/test-results/
|
||||
packages/testing/playwright/playwright-report/
|
||||
retention-days: 7
|
||||
|
||||
- name: Cleanup cached CI images
|
||||
if: ${{ inputs.test-mode == 'docker-pull' }}
|
||||
continue-on-error: true
|
||||
run: |
|
||||
docker images --format '{{.Repository}}:{{.Tag}}' | grep -E 'ghcr\.io/n8n-io/(n8n|runners):(ci|pr)-' | xargs -r docker rmi || true
|
||||
docker system prune -f || true
|
||||
packages/testing/playwright/.nyc_output/
|
||||
retention-days: 1
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Cancel Currents run if workflow is cancelled
|
||||
if: ${{ cancelled() }}
|
||||
|
|
|
|||
|
|
@ -12,17 +12,30 @@ on:
|
|||
default: 'master'
|
||||
|
||||
jobs:
|
||||
e2e-vm-expressions:
|
||||
uses: ./.github/workflows/test-e2e-ci-reusable.yml
|
||||
prepare-docker:
|
||||
uses: ./.github/workflows/prepare-docker-reusable.yml
|
||||
with:
|
||||
branch: ${{ github.event_name == 'schedule' && 'master' || inputs.branch }}
|
||||
secrets: inherit
|
||||
|
||||
e2e:
|
||||
name: E2E
|
||||
needs: [prepare-docker]
|
||||
uses: ./.github/workflows/test-e2e-reusable.yml
|
||||
with:
|
||||
branch: ${{ github.event_name == 'schedule' && 'master' || inputs.branch }}
|
||||
test-mode: docker-artifact
|
||||
test-command: pnpm --filter=n8n-playwright test:container:multi-main:e2e
|
||||
workers: '1'
|
||||
pre-generated-matrix: '[{"shard":1},{"shard":2},{"shard":3},{"shard":4},{"shard":5},{"shard":6},{"shard":7},{"shard":8},{"shard":9},{"shard":10},{"shard":11},{"shard":12},{"shard":13},{"shard":14},{"shard":15},{"shard":16}]'
|
||||
n8n-env: '{"N8N_EXPRESSION_ENGINE":"vm"}'
|
||||
artifact-prefix: vm-expressions
|
||||
secrets: inherit
|
||||
|
||||
notify-on-failure:
|
||||
name: Notify Slack on failure
|
||||
runs-on: ubuntu-slim
|
||||
needs: [e2e-vm-expressions]
|
||||
needs: [e2e]
|
||||
if: failure() && github.event_name == 'schedule'
|
||||
steps:
|
||||
- uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
|
||||
|
|
|
|||
30
.github/workflows/test-evals-ai-reusable.yml
vendored
30
.github/workflows/test-evals-ai-reusable.yml
vendored
|
|
@ -48,15 +48,16 @@ jobs:
|
|||
|
||||
- name: Generate experiment name
|
||||
id: experiment
|
||||
env:
|
||||
PREFIX: ${{ inputs.experiment_name_prefix }}
|
||||
BRANCH: ${{ inputs.branch }}
|
||||
run: |
|
||||
DATE=$(date +%Y_%m_%d)
|
||||
PREFIX="${{ inputs.experiment_name_prefix }}"
|
||||
|
||||
if [ -n "$PREFIX" ]; then
|
||||
NAME="${PREFIX}_${DATE}"
|
||||
else
|
||||
# Extract ticket ID from branch name (e.g., AI-1234 from ai-1234-feature-name)
|
||||
BRANCH="${{ inputs.branch }}"
|
||||
TICKET=$(echo "$BRANCH" | grep -oE '^[Aa][Ii]-[0-9]+' | tr '[:lower:]' '[:upper:]' || true)
|
||||
if [ -n "$TICKET" ]; then
|
||||
NAME="${TICKET}_${DATE}"
|
||||
|
|
@ -95,14 +96,23 @@ jobs:
|
|||
|
||||
- name: Run Evaluations
|
||||
working-directory: packages/@n8n/ai-workflow-builder.ee/evaluations
|
||||
env:
|
||||
SUITE: ${{ inputs.suite }}
|
||||
DATASET: ${{ inputs.dataset }}
|
||||
REPETITIONS: ${{ inputs.repetitions }}
|
||||
JUDGES: ${{ inputs.judges }}
|
||||
CONCURRENCY: ${{ inputs.concurrency }}
|
||||
EXPERIMENT_NAME: ${{ steps.experiment.outputs.name }}
|
||||
WEBHOOK_URL_ARG: ${{ secrets.EVALS_WEBHOOK_URL && format('--webhook-url={0}', secrets.EVALS_WEBHOOK_URL) || '' }}
|
||||
WEBHOOK_SECRET_ARG: ${{ secrets.EVALS_WEBHOOK_SECRET && format('--webhook-secret={0}', secrets.EVALS_WEBHOOK_SECRET) || '' }}
|
||||
run: |
|
||||
pnpm eval \
|
||||
--suite "${{ inputs.suite }}" \
|
||||
--suite "$SUITE" \
|
||||
--backend langsmith \
|
||||
--dataset "${{ inputs.dataset }}" \
|
||||
--repetitions ${{ inputs.repetitions }} \
|
||||
--judges ${{ inputs.judges }} \
|
||||
--concurrency ${{ inputs.concurrency }} \
|
||||
--name "${{ steps.experiment.outputs.name }}" \
|
||||
${{ secrets.EVALS_WEBHOOK_URL && format('--webhook-url "{0}"', secrets.EVALS_WEBHOOK_URL) || '' }} \
|
||||
${{ secrets.EVALS_WEBHOOK_SECRET && format('--webhook-secret "{0}"', secrets.EVALS_WEBHOOK_SECRET) || '' }}
|
||||
--dataset "$DATASET" \
|
||||
--repetitions "$REPETITIONS" \
|
||||
--judges "$JUDGES" \
|
||||
--concurrency "$CONCURRENCY" \
|
||||
--name "$EXPERIMENT_NAME" \
|
||||
"$WEBHOOK_URL_ARG" \
|
||||
"$WEBHOOK_SECRET_ARG"
|
||||
|
|
|
|||
281
.github/workflows/test-evals-instance-ai.yml
vendored
Normal file
281
.github/workflows/test-evals-instance-ai.yml
vendored
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
name: 'Test: Instance AI Exec Evals'
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'GitHub branch to test'
|
||||
required: false
|
||||
type: string
|
||||
default: 'master'
|
||||
filter:
|
||||
description: 'Filter test cases by name (e.g. "contact-form")'
|
||||
required: false
|
||||
type: string
|
||||
default: ''
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'GitHub branch to test'
|
||||
required: false
|
||||
default: 'master'
|
||||
filter:
|
||||
description: 'Filter test cases by name (e.g. "contact-form")'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
run-evals:
|
||||
name: 'Run Evals'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
timeout-minutes: 45
|
||||
env:
|
||||
# Each port hosts an independent n8n container. The eval CLI's
|
||||
# work-stealing allocator dispatches builds across them, capped per-lane.
|
||||
# 9 lanes on 4vcpu — builds are LLM-bound so CPU headroom is sufficient;
|
||||
# bump back to 8vcpu if contention shows up.
|
||||
LANE_PORTS: '5678,5679,5680,5681,5682,5683,5684,5685,5686'
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
ref: ${{ inputs.branch || github.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup Environment
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: 'pnpm build'
|
||||
|
||||
# Cache populated by prepare-docker; fallback covers PRs that only touch this workflow file.
|
||||
- name: Load n8n Docker image
|
||||
id: load-image
|
||||
continue-on-error: true
|
||||
uses: ./.github/actions/load-n8n-docker
|
||||
|
||||
- name: Build Docker image (fallback on cache miss)
|
||||
if: steps.load-image.outcome == 'failure'
|
||||
run: pnpm build:docker
|
||||
env:
|
||||
INCLUDE_TEST_CONTROLLER: 'true'
|
||||
|
||||
- name: Start n8n containers
|
||||
env:
|
||||
EVALS_ANTHROPIC_KEY: ${{ secrets.EVALS_ANTHROPIC_KEY }}
|
||||
N8N_LICENSE_ACTIVATION_KEY: ${{ secrets.N8N_LICENSE_ACTIVATION_KEY }}
|
||||
N8N_LICENSE_CERT: ${{ secrets.N8N_LICENSE_CERT }}
|
||||
N8N_ENCRYPTION_KEY: ${{ secrets.N8N_ENCRYPTION_KEY }}
|
||||
DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
|
||||
run: |
|
||||
IFS=',' read -ra PORTS <<< "$LANE_PORTS"
|
||||
for i in "${!PORTS[@]}"; do
|
||||
port="${PORTS[$i]}"
|
||||
docker run -d --name "n8n-eval-$((i+1))" \
|
||||
-e E2E_TESTS=true \
|
||||
-e N8N_ENABLED_MODULES=instance-ai \
|
||||
-e N8N_AI_ENABLED=true \
|
||||
-e N8N_INSTANCE_AI_MODEL_API_KEY="$EVALS_ANTHROPIC_KEY" \
|
||||
-e N8N_AI_ASSISTANT_BASE_URL="" \
|
||||
-e N8N_INSTANCE_AI_SANDBOX_ENABLED=true \
|
||||
-e N8N_INSTANCE_AI_SANDBOX_PROVIDER=daytona \
|
||||
-e DAYTONA_API_URL=https://app.daytona.io/api \
|
||||
-e DAYTONA_API_KEY="$DAYTONA_API_KEY" \
|
||||
-e N8N_LICENSE_ACTIVATION_KEY="$N8N_LICENSE_ACTIVATION_KEY" \
|
||||
-e N8N_LICENSE_CERT="$N8N_LICENSE_CERT" \
|
||||
-e N8N_ENCRYPTION_KEY="$N8N_ENCRYPTION_KEY" \
|
||||
-p "$port:5678" \
|
||||
n8nio/n8n:local
|
||||
done
|
||||
# 120s budget per port: containers booting in parallel on a shared
|
||||
# 4vcpu runner contend for CPU/disk during n8n's startup (DB migrations,
|
||||
# license init), so each takes longer than a solo boot.
|
||||
for port in "${PORTS[@]}"; do
|
||||
ready=false
|
||||
for i in $(seq 1 120); do
|
||||
if curl -s "http://localhost:$port/healthz/readiness" -o /dev/null -w "%{http_code}" | grep -q 200; then
|
||||
echo "n8n on port $port ready after ${i}s"
|
||||
ready=true
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$ready" != "true" ]; then
|
||||
echo "::error::n8n on port $port failed to start within 120s"
|
||||
for n in $(docker ps -aq --filter "name=n8n-eval-"); do
|
||||
echo "Logs for $n:"
|
||||
docker logs "$n" --tail 30 || true
|
||||
done
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Create test users
|
||||
run: |
|
||||
IFS=',' read -ra PORTS <<< "$LANE_PORTS"
|
||||
for port in "${PORTS[@]}"; do
|
||||
curl -sf -X POST "http://localhost:$port/rest/e2e/reset" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"owner":{"email":"nathan@n8n.io","password":"PlaywrightTest123","firstName":"Eval","lastName":"Owner"},
|
||||
"admin":{"email":"admin@n8n.io","password":"PlaywrightTest123","firstName":"Admin","lastName":"User"},
|
||||
"members":[],
|
||||
"chat":{"email":"chat@n8n.io","password":"PlaywrightTest123","firstName":"Chat","lastName":"User"}
|
||||
}'
|
||||
done
|
||||
|
||||
# Belt-and-suspenders: env vars set sandbox config but persisted admin
|
||||
# settings can override. Per-lane assertion catches env-injection hiccups
|
||||
# or unexpected DB-side state. A single misconfigured lane would
|
||||
# silently route some builds through tool mode and pollute results.
|
||||
- name: Assert sandbox is enabled on every lane
|
||||
run: |
|
||||
IFS=',' read -ra PORTS <<< "$LANE_PORTS"
|
||||
bad=0
|
||||
for i in "${!PORTS[@]}"; do
|
||||
port="${PORTS[$i]}"
|
||||
lane="$((i+1))"
|
||||
curl -sf -X POST "http://localhost:$port/rest/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"emailOrLdapLoginId":"nathan@n8n.io","password":"PlaywrightTest123"}' \
|
||||
-c "/tmp/cookies-$port.txt" -o /dev/null
|
||||
cfg=$(curl -sf -b "/tmp/cookies-$port.txt" \
|
||||
"http://localhost:$port/rest/instance-ai/settings" \
|
||||
| jq -r '.data | "\(.sandboxEnabled) \(.sandboxProvider)"')
|
||||
if [ "$cfg" != "true daytona" ]; then
|
||||
echo "::error::lane $lane (port $port): expected 'true daytona', got '$cfg'"
|
||||
bad=$((bad+1))
|
||||
else
|
||||
echo " lane $lane: sandboxEnabled=true sandboxProvider=daytona ok"
|
||||
fi
|
||||
done
|
||||
if [ "$bad" -gt 0 ]; then
|
||||
echo "::error::$bad lane(s) misconfigured - eval would mix sandbox + tool-mode builds"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Run Instance AI Evals
|
||||
continue-on-error: true
|
||||
working-directory: packages/@n8n/instance-ai
|
||||
env:
|
||||
N8N_INSTANCE_AI_MODEL_API_KEY: ${{ secrets.EVALS_ANTHROPIC_KEY }}
|
||||
LANGSMITH_TRACING: 'true'
|
||||
LANGSMITH_ENDPOINT: ${{ secrets.EVALS_LANGSMITH_ENDPOINT }}
|
||||
LANGSMITH_API_KEY: ${{ secrets.EVALS_LANGSMITH_API_KEY }}
|
||||
LANGSMITH_REVISION_ID: ${{ github.sha }}
|
||||
LANGSMITH_BRANCH: ${{ github.head_ref || github.ref_name }}
|
||||
run: |
|
||||
IFS=',' read -ra PORTS <<< "$LANE_PORTS"
|
||||
URLS=()
|
||||
for port in "${PORTS[@]}"; do
|
||||
URLS+=("http://localhost:$port")
|
||||
done
|
||||
BASE_URLS=$(IFS=,; printf '%s' "${URLS[*]}")
|
||||
pnpm eval:instance-ai \
|
||||
--base-url "$BASE_URLS" \
|
||||
--concurrency 32 \
|
||||
--verbose \
|
||||
--iterations 5 \
|
||||
${{ inputs.filter && format('--filter "{0}"', inputs.filter) || '' }}
|
||||
|
||||
# Captures sandbox/builder/Daytona signals that surface during the eval
|
||||
# (after migrations finish). Two layers of secret-leak defense:
|
||||
#
|
||||
# 1. Filter to specific diagnostic patterns — never tail raw output.
|
||||
# The grep allowlist scopes the log surface to lines we care
|
||||
# about for debugging (sandbox lifecycle, builder, errors).
|
||||
#
|
||||
# 2. Re-register secrets via ::add-mask:: so any line that does
|
||||
# match the allowlist has the secret values replaced with ***
|
||||
# before reaching the GH Actions log. GitHub auto-masks
|
||||
# ${{ secrets.X }} references, but the masking is fragile
|
||||
# against transformed or split values; explicit registration
|
||||
# reinforces it.
|
||||
#
|
||||
# Runs even on eval failure so we have the post-mortem regardless.
|
||||
- name: Capture n8n container logs (debug)
|
||||
if: ${{ always() }}
|
||||
env:
|
||||
EVALS_ANTHROPIC_KEY: ${{ secrets.EVALS_ANTHROPIC_KEY }}
|
||||
DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
|
||||
N8N_LICENSE_ACTIVATION_KEY: ${{ secrets.N8N_LICENSE_ACTIVATION_KEY }}
|
||||
N8N_LICENSE_CERT: ${{ secrets.N8N_LICENSE_CERT }}
|
||||
N8N_ENCRYPTION_KEY: ${{ secrets.N8N_ENCRYPTION_KEY }}
|
||||
run: |
|
||||
# Layer 2 — defense in depth: explicitly mask each secret's value.
|
||||
# ::add-mask:: is a single-line workflow command. Multi-line secrets
|
||||
# (e.g. N8N_LICENSE_CERT is PEM-encoded) must be masked one line at
|
||||
# a time, otherwise only the first line is registered.
|
||||
for v in "$EVALS_ANTHROPIC_KEY" "$DAYTONA_API_KEY" \
|
||||
"$N8N_LICENSE_ACTIVATION_KEY" "$N8N_LICENSE_CERT" \
|
||||
"$N8N_ENCRYPTION_KEY"; do
|
||||
[ -z "$v" ] && continue
|
||||
while IFS= read -r line; do
|
||||
[ -n "$line" ] && echo "::add-mask::$line"
|
||||
done <<< "$v"
|
||||
done
|
||||
|
||||
# Layer 1 — accuracy filter: only surface diagnostic signals.
|
||||
# `tail -100` after the filter so we get the LATEST matching lines
|
||||
# (post-eval failure signal), not the earliest startup-time ones.
|
||||
SIGNALS='sandbox|builder|daytona|instance.?ai|error|warn|reject|exception|fail'
|
||||
for c in $(docker ps -aq --filter "name=n8n-eval-"); do
|
||||
name=$(docker inspect --format '{{.Name}}' "$c" | sed 's|^/||')
|
||||
echo ""
|
||||
echo "============================================================"
|
||||
echo "=== $name (filtered diagnostic signals, last 100 lines) ==="
|
||||
echo "============================================================"
|
||||
docker logs "$c" 2>&1 \
|
||||
| grep -ivE 'migration' \
|
||||
| grep -iE "$SIGNALS" \
|
||||
| tail -100 \
|
||||
|| true
|
||||
done
|
||||
|
||||
- name: Stop n8n containers
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
mapfile -t ids < <(docker ps -aq --filter "name=n8n-eval-")
|
||||
if [ "${#ids[@]}" -gt 0 ]; then
|
||||
docker stop "${ids[@]}" 2>/dev/null || true
|
||||
docker rm "${ids[@]}" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
- name: Post eval results to PR
|
||||
if: ${{ always() && github.event_name == 'pull_request' }}
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
# The eval CLI writes the full PR comment as eval-pr-comment.md
|
||||
# (see comparison/format.ts:formatComparisonMarkdown). It includes
|
||||
# the alert, aggregate, comparison sections, per-test-case results
|
||||
# collapsed, and failure details collapsed. CI just relays it.
|
||||
COMMENT_FILE="packages/@n8n/instance-ai/eval-pr-comment.md"
|
||||
if [ ! -f "$COMMENT_FILE" ]; then
|
||||
echo "No PR comment file found (eval likely cancelled before writing results)"
|
||||
exit 0
|
||||
fi
|
||||
cp "$COMMENT_FILE" /tmp/eval-comment.md
|
||||
|
||||
# Find and update existing eval comment, or create new one
|
||||
COMMENT_ID=$(gh api "repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments" \
|
||||
--jq '.[] | select(.body | startswith("### Instance AI Workflow Eval")) | .id' | tail -1)
|
||||
|
||||
if [ -n "$COMMENT_ID" ]; then
|
||||
gh api "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" -X PATCH -F body=@/tmp/eval-comment.md
|
||||
else
|
||||
gh pr comment "${{ github.event.pull_request.number }}" --body-file /tmp/eval-comment.md
|
||||
fi
|
||||
|
||||
- name: Upload Results
|
||||
if: ${{ always() }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: instance-ai-workflow-eval-results
|
||||
path: |
|
||||
packages/@n8n/instance-ai/eval-results.json
|
||||
packages/@n8n/instance-ai/.data/workflow-eval-report.html
|
||||
retention-days: 14
|
||||
2
.github/workflows/test-linting-reusable.yml
vendored
2
.github/workflows/test-linting-reusable.yml
vendored
|
|
@ -29,5 +29,5 @@ jobs:
|
|||
- name: Build and Test
|
||||
uses: ./.github/actions/setup-nodejs
|
||||
with:
|
||||
build-command: pnpm lint
|
||||
build-command: pnpm lint:ci
|
||||
node-version: ${{ inputs.nodeVersion }}
|
||||
|
|
|
|||
2
.github/workflows/test-visual-chromatic.yml
vendored
2
.github/workflows/test-visual-chromatic.yml
vendored
|
|
@ -34,4 +34,4 @@ jobs:
|
|||
skip: 'release/**'
|
||||
onlyChanged: true
|
||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||
exitZeroOnChanges: false
|
||||
exitZeroOnChanges: true
|
||||
|
|
|
|||
3
.github/workflows/test-visual-storybook.yml
vendored
3
.github/workflows/test-visual-storybook.yml
vendored
|
|
@ -20,7 +20,8 @@ jobs:
|
|||
cloudflare:
|
||||
name: Cloudflare Pages
|
||||
if: |
|
||||
!contains(github.event.pull_request.labels.*.name, 'community')
|
||||
!contains(github.event.pull_request.labels.*.name, 'community') &&
|
||||
github.repository == 'n8n-io/n8n'
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2204
|
||||
permissions:
|
||||
contents: read
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ on:
|
|||
description: 'Encryption key for n8n operations.'
|
||||
required: true
|
||||
CURRENTS_RECORD_KEY:
|
||||
description: 'Currents record key for uploading test results.'
|
||||
required: true
|
||||
description: 'Currents record key for test reporting. Empty on fork PRs.'
|
||||
required: false
|
||||
|
||||
env:
|
||||
NODE_OPTIONS: --max-old-space-size=3072
|
||||
|
|
|
|||
|
|
@ -30,16 +30,20 @@ jobs:
|
|||
- name: Approve PR (as the App)
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
PR_NUMBER: ${{ inputs.pull-request-number }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
gh pr review "${{ inputs.pull-request-number }}" \
|
||||
gh pr review "$PR_NUMBER" \
|
||||
--approve \
|
||||
--repo "${{ github.repository }}"
|
||||
--repo "$REPOSITORY"
|
||||
|
||||
- name: Enable auto-merge (merge when checks pass)
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
PR_NUMBER: ${{ inputs.pull-request-number }}
|
||||
REPOSITORY: ${{ github.repository }}
|
||||
run: |
|
||||
gh pr merge "${{ inputs.pull-request-number }}" \
|
||||
gh pr merge "$PR_NUMBER" \
|
||||
--auto \
|
||||
--squash \
|
||||
--repo "${{ github.repository }}"
|
||||
--repo "$REPOSITORY"
|
||||
|
|
|
|||
6
.github/workflows/util-claude-task.yml
vendored
6
.github/workflows/util-claude-task.yml
vendored
|
|
@ -131,6 +131,8 @@ jobs:
|
|||
prompt: ${{ env.CLAUDE_PROMPT }}
|
||||
show_full_output: ${{ inputs.suppress_output != true }}
|
||||
display_report: false
|
||||
plugin_marketplaces: "./.claude/plugins/n8n"
|
||||
plugins: "n8n@n8n"
|
||||
settings: |
|
||||
{
|
||||
"permissions": {
|
||||
|
|
@ -167,8 +169,10 @@ jobs:
|
|||
|
||||
- name: Push branch
|
||||
if: always()
|
||||
env:
|
||||
REF: ${{ inputs.ref }}
|
||||
run: |
|
||||
if ! git diff --quiet "${{ inputs.ref }}" 2>/dev/null; then
|
||||
if ! git diff --quiet "$REF" 2>/dev/null; then
|
||||
git push -u origin "$BRANCH_NAME"
|
||||
echo "::notice::Changes pushed to branch $BRANCH_NAME"
|
||||
else
|
||||
|
|
|
|||
|
|
@ -31,4 +31,6 @@ jobs:
|
|||
install-command: pnpm install --frozen-lockfile --dir ./.github/scripts --ignore-workspace
|
||||
|
||||
- name: Ensure release-candidate branches
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
run: node ./.github/scripts/ensure-release-candidate-branches.mjs
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ jobs:
|
|||
name: Post Metrics Comment
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
!github.event.pull_request.head.repo.fork
|
||||
!github.event.pull_request.head.repo.fork &&
|
||||
github.repository == 'n8n-io/n8n'
|
||||
runs-on: ubuntu-slim
|
||||
continue-on-error: true
|
||||
permissions:
|
||||
|
|
@ -34,7 +35,9 @@ jobs:
|
|||
QA_METRICS_WEBHOOK_USER: ${{ secrets.QA_METRICS_WEBHOOK_USER }}
|
||||
QA_METRICS_WEBHOOK_PASSWORD: ${{ secrets.QA_METRICS_WEBHOOK_PASSWORD }}
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
METRICS: ${{ inputs.metrics }}
|
||||
BASELINE_DAYS: ${{ inputs.baseline-days }}
|
||||
run: |
|
||||
node .github/scripts/post-qa-metrics-comment.mjs \
|
||||
--metrics "${{ inputs.metrics }}" \
|
||||
--baseline-days "${{ inputs.baseline-days }}"
|
||||
--metrics "$METRICS" \
|
||||
--baseline-days "$BASELINE_DAYS"
|
||||
|
|
|
|||
7
.gitignore
vendored
7
.gitignore
vendored
|
|
@ -25,6 +25,7 @@ packages/**/.turbo
|
|||
*.swp
|
||||
CHANGELOG-*.md
|
||||
*.mdx
|
||||
!packages/frontend/@n8n/design-system/**/*.mdx
|
||||
build-storybook.log
|
||||
build.log
|
||||
*.junit.xml
|
||||
|
|
@ -33,6 +34,10 @@ test-results.json
|
|||
*.0x
|
||||
packages/testing/playwright/playwright-report
|
||||
packages/testing/playwright/test-results
|
||||
packages/testing/playwright/eval-results.json
|
||||
packages/@n8n/instance-ai/eval-results.json
|
||||
packages/@n8n/instance-ai/.eval-output/
|
||||
packages/@n8n/instance-ai/eval-pr-comment.md
|
||||
packages/testing/playwright/.playwright-browsers
|
||||
packages/testing/playwright/.playwright-cli
|
||||
test-results/
|
||||
|
|
@ -57,6 +62,8 @@ packages/cli/src/commands/export/outputs
|
|||
.data/
|
||||
.claude/settings.local.json
|
||||
.claude/plans/
|
||||
.claude/worktrees/
|
||||
.claude/specs/
|
||||
.cursor/plans/
|
||||
.superset
|
||||
.conductor
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ The monorepo is organized into these key packages:
|
|||
- **`packages/@n8n/i18n`**: Internationalization for UI text
|
||||
- **`packages/nodes-base`**: Built-in nodes for integrations
|
||||
- **`packages/@n8n/nodes-langchain`**: AI/LangChain nodes
|
||||
- **`packages/@n8n/instance-ai`**: "AI Assistant" in the UI, "Instance AI" in code — AI assistant backend. See its `CLAUDE.md` for architecture docs.
|
||||
- **`@n8n/design-system`**: Vue component library for UI consistency
|
||||
- **`@n8n/config`**: Centralized configuration management
|
||||
|
||||
|
|
@ -145,14 +146,11 @@ const children = getChildNodes(workflow.connections, 'NodeName', 'main', 1);
|
|||
- Import from appropriate error classes in each package
|
||||
|
||||
### Frontend Development
|
||||
- Refer to `packages/frontend/AGENTS.md`
|
||||
- **All UI text must use i18n** - add translations to `@n8n/i18n` package
|
||||
- **Use CSS variables directly** - never hardcode spacing as px values
|
||||
- **data-testid must be a single value** (no spaces or multiple values)
|
||||
- For style changes and design-system updates, follow
|
||||
`.agents/design-system-style-rules.md`
|
||||
|
||||
When implementing CSS, refer to @packages/frontend/CLAUDE.md for guidelines on
|
||||
CSS variables and styling conventions.
|
||||
- Always use `design-system-rules` skill in reviews
|
||||
|
||||
### Testing Guidelines
|
||||
- **Always work from within the package directory** when running tests
|
||||
|
|
|
|||
482
CHANGELOG.md
482
CHANGELOG.md
|
|
@ -1,3 +1,485 @@
|
|||
# [2.21.0](https://github.com/n8n-io/n8n/compare/n8n@2.20.0...n8n@2.21.0) (2026-05-12)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add warning to Computer Use install modal ([#30094](https://github.com/n8n-io/n8n/issues/30094)) ([ecf96ad](https://github.com/n8n-io/n8n/commit/ecf96ad30c8d29641db07cd78885ea28aff26199))
|
||||
* **ai-builder:** Allow restoring archived workflows from Instance AI ([#29813](https://github.com/n8n-io/n8n/issues/29813)) ([a33a89a](https://github.com/n8n-io/n8n/commit/a33a89a215d6cef39895858bf36c00c15abfdd9d))
|
||||
* **ai-builder:** Preserve collected planning context ([#29916](https://github.com/n8n-io/n8n/issues/29916)) ([5e3aa1a](https://github.com/n8n-io/n8n/commit/5e3aa1a726e903387344d3a4ed51e97811e4ff02))
|
||||
* **ai-builder:** Resolve HitlTool variants to base node in get_node_types ([#29731](https://github.com/n8n-io/n8n/issues/29731)) ([ed9471a](https://github.com/n8n-io/n8n/commit/ed9471a5321747bbca003bee7d6a37d54bb79cb2))
|
||||
* **Airtable Node:** Fix typecast option dropping attachment field updates ([#29556](https://github.com/n8n-io/n8n/issues/29556)) ([0cafc71](https://github.com/n8n-io/n8n/commit/0cafc717a274053f698e988d6f44a27a8b936e83))
|
||||
* Align undici override across major versions ([#30028](https://github.com/n8n-io/n8n/issues/30028)) ([6b893b4](https://github.com/n8n-io/n8n/commit/6b893b45a0d05dfb08ea7b732f775c28b6ccf801))
|
||||
* **Calendly Trigger Node:** Use API v2 for webhook subscriptions ([#29771](https://github.com/n8n-io/n8n/issues/29771)) ([0edcdcf](https://github.com/n8n-io/n8n/commit/0edcdcfe8529b6296f1a1f0d8b8af3841a14a466))
|
||||
* **core:** Activate agent chat integrations on every main ([#30029](https://github.com/n8n-io/n8n/issues/30029)) ([6f4f0a0](https://github.com/n8n-io/n8n/commit/6f4f0a0303e1f0f0cd57a5b0dab08347010b7241))
|
||||
* **core:** Add configurable retries and error details to S3 ([#28309](https://github.com/n8n-io/n8n/issues/28309)) ([e2576ca](https://github.com/n8n-io/n8n/commit/e2576ca25bc973b315bdcbff1a1b2d3309bc647d))
|
||||
* **core:** Add ESLint rule to prevent error instances in toThrow assertions ([#29889](https://github.com/n8n-io/n8n/issues/29889)) ([75ed71c](https://github.com/n8n-io/n8n/commit/75ed71c00142e8bbdfb851691d5fc3de3cfada36))
|
||||
* **core:** Add liveness timeouts for Instance AI ([#30145](https://github.com/n8n-io/n8n/issues/30145)) ([52a4bcb](https://github.com/n8n-io/n8n/commit/52a4bcb23a9398b1327acd0ec39df7a9e00b48b6))
|
||||
* **core:** Add support for context establishment hooks in webhook mode ([#29893](https://github.com/n8n-io/n8n/issues/29893)) ([04e9b25](https://github.com/n8n-io/n8n/commit/04e9b258a887c07b62774f09e3921932038a3984))
|
||||
* **core:** Add workflow structure validation ([#29699](https://github.com/n8n-io/n8n/issues/29699)) ([bec74ae](https://github.com/n8n-io/n8n/commit/bec74aeb4fda198853b3ea82ed135a1db3ba4988))
|
||||
* **core:** Advance Postgres IDENTITY sequences after entity import ([#29762](https://github.com/n8n-io/n8n/issues/29762)) ([ca33060](https://github.com/n8n-io/n8n/commit/ca33060e0bd30c6d077f8dd18ca8492d50c06a92))
|
||||
* **core:** Agent sessions correctly quoting columns in queries for Postgres ([#29999](https://github.com/n8n-io/n8n/issues/29999)) ([9f92005](https://github.com/n8n-io/n8n/commit/9f92005938a1b481b89558b4e82a198da6ec4e8c))
|
||||
* **core:** Agents called from workflows use the workflows owner/user ID for calling further workflows through the agent ([#30242](https://github.com/n8n-io/n8n/issues/30242)) ([9072ee3](https://github.com/n8n-io/n8n/commit/9072ee3beb1789f34008cb0f85f361dcac8cae26))
|
||||
* **core:** Allow GIT_SSH_COMMAND in simple-git after 3.36.0 upgrade ([#29894](https://github.com/n8n-io/n8n/issues/29894)) ([f42be90](https://github.com/n8n-io/n8n/commit/f42be9030e7f549da5ed6dc3902d058c2ebbadcb))
|
||||
* **core:** Allow profile edits when SSO is no longer active ([#29765](https://github.com/n8n-io/n8n/issues/29765)) ([2714f00](https://github.com/n8n-io/n8n/commit/2714f001218d1323233c1920c94ed02a5ce8dcf1))
|
||||
* **core:** Allow same-domain redirects in instance-ai web research (TRUST-73) ([#30107](https://github.com/n8n-io/n8n/issues/30107)) ([3123f25](https://github.com/n8n-io/n8n/commit/3123f2551be75fb282628b9106b060975fb983fc))
|
||||
* **core:** Always create instance-ai sandbox workspace dirs (TRUST-79) ([#30106](https://github.com/n8n-io/n8n/issues/30106)) ([5e88748](https://github.com/n8n-io/n8n/commit/5e887483344daad5e11bee97d3315a9b2b38d0c9))
|
||||
* **core:** Avoid MCP get_execution hang on circular references ([#30051](https://github.com/n8n-io/n8n/issues/30051)) ([60e23e1](https://github.com/n8n-io/n8n/commit/60e23e10e01f20f73fb1c61d74b5ca44a4c677f6))
|
||||
* **core:** Check npm provenance in community package scanner ([#29667](https://github.com/n8n-io/n8n/issues/29667)) ([804f51c](https://github.com/n8n-io/n8n/commit/804f51cf0d8411b4d4df6f593fdea787b97fad51))
|
||||
* **core:** Clarify 0-based indexing in workflow SDK prompts and JSDoc ([#29734](https://github.com/n8n-io/n8n/issues/29734)) ([fba873c](https://github.com/n8n-io/n8n/commit/fba873c37e76f01d28443c5276b2d92bd333602a))
|
||||
* **core:** Clarify agent builder prompt guidance ([#30127](https://github.com/n8n-io/n8n/issues/30127)) ([75646c4](https://github.com/n8n-io/n8n/commit/75646c45271831bf8d03653baf024d201d5fae6d))
|
||||
* **core:** Defer credential setup during workflow builds ([#30181](https://github.com/n8n-io/n8n/issues/30181)) ([bb73952](https://github.com/n8n-io/n8n/commit/bb73952fcc9aff4eed0af6bb99fb10f65d48df3d))
|
||||
* **core:** Emit missing auth audit events for OIDC and SSO-restricted login ([#29856](https://github.com/n8n-io/n8n/issues/29856)) ([dd812c5](https://github.com/n8n-io/n8n/commit/dd812c5010ca28ca38c238bfa8c57fe39ac816d5))
|
||||
* **core:** Export boolean CSV values as true/false for Data Tables ([#30007](https://github.com/n8n-io/n8n/issues/30007)) ([94d91e1](https://github.com/n8n-io/n8n/commit/94d91e13bfcaf360099a0a3816b0025502b145f4))
|
||||
* **core:** Filter WaitTracker to only poll waiting executions ([#29898](https://github.com/n8n-io/n8n/issues/29898)) ([5c7921f](https://github.com/n8n-io/n8n/commit/5c7921f71c95d97f6730e6b28b06947b1cfbaa23))
|
||||
* **core:** Fix duplicate task request on runner defer ([#28315](https://github.com/n8n-io/n8n/issues/28315)) ([80c8a6c](https://github.com/n8n-io/n8n/commit/80c8a6c2fdc97624c9b4b3e97b8ff20aca641552))
|
||||
* **core:** Harden axios error handling against non-string error stack ([#29100](https://github.com/n8n-io/n8n/issues/29100)) ([2dbf02e](https://github.com/n8n-io/n8n/commit/2dbf02e63e5ddee8d9e4a94f2ad3cd1f5321f2a7))
|
||||
* **core:** Improve AI chat file upload handling and error states ([#29701](https://github.com/n8n-io/n8n/issues/29701)) ([afe119b](https://github.com/n8n-io/n8n/commit/afe119be1409ac2cb198f7a41dc12ed25f5cf106))
|
||||
* **core:** Improve documentation usage in mcp tools ([#30210](https://github.com/n8n-io/n8n/issues/30210)) ([e8827cd](https://github.com/n8n-io/n8n/commit/e8827cd6e8ff3eb03ceab6965574bacf10c719d0))
|
||||
* **core:** Initialise encryption key proxy on worker and webhook instances ([#29912](https://github.com/n8n-io/n8n/issues/29912)) ([ae57e60](https://github.com/n8n-io/n8n/commit/ae57e606b4f5cf691bceb01489e5991cf31911ef))
|
||||
* **core:** Inline AI_NODE_SDK_VERSION to save memory by not loading @n8n/ai-utilities on boot ([#30113](https://github.com/n8n-io/n8n/issues/30113)) ([f709e53](https://github.com/n8n-io/n8n/commit/f709e5382448926e15e36571aa9fd32db238e36d))
|
||||
* **core:** Persist agent chat draft across modes and hide unfinished tool-approval toggle ([#30123](https://github.com/n8n-io/n8n/issues/30123)) ([7094b48](https://github.com/n8n-io/n8n/commit/7094b48c9444024af6c14b72b49b47b555db52ef))
|
||||
* **core:** Preserve node positions on AI workflow updates ([#29850](https://github.com/n8n-io/n8n/issues/29850)) ([f2764f0](https://github.com/n8n-io/n8n/commit/f2764f04c0e663268fe40737c55c8c1a0f33173b))
|
||||
* **core:** Prevent proxy layer accumulation in ObservableObject ([#30129](https://github.com/n8n-io/n8n/issues/30129)) ([0a76135](https://github.com/n8n-io/n8n/commit/0a761355c4836433c379ee8933c0198621879ae0))
|
||||
* **core:** Propagate waitTill from worker to main in scaling mode ([#30099](https://github.com/n8n-io/n8n/issues/30099)) ([3702ff8](https://github.com/n8n-io/n8n/commit/3702ff8eb31547d51e3b56b484bf6a731296f9cf))
|
||||
* **core:** Scope credential resolution ([#30156](https://github.com/n8n-io/n8n/issues/30156)) ([174f0f8](https://github.com/n8n-io/n8n/commit/174f0f805e0d5715d2d80e5c0282a94b79e9a390))
|
||||
* **core:** Simple-git update broke https connection ([#29998](https://github.com/n8n-io/n8n/issues/29998)) ([01300e9](https://github.com/n8n-io/n8n/commit/01300e9b9b7e0f80f1852c5e1e4b3df9a42404c4))
|
||||
* **core:** Simplify Slack redirect URL verification process for agents ([#30033](https://github.com/n8n-io/n8n/issues/30033)) ([8201281](https://github.com/n8n-io/n8n/commit/820128196cf550ab8cf371fbebb3457b9fd35d22))
|
||||
* **core:** Skip disabled tool nodes when mapping AI Agent tool sources ([#29460](https://github.com/n8n-io/n8n/issues/29460)) ([bd7eeb7](https://github.com/n8n-io/n8n/commit/bd7eeb7bc89032b9a0db467cb53f37bfef71647e))
|
||||
* **core:** Skip unknown fixedCollection keys instead of throwing ([#29689](https://github.com/n8n-io/n8n/issues/29689)) ([a30772c](https://github.com/n8n-io/n8n/commit/a30772c933544d06b560a3c66ec69cd4f7b8574f))
|
||||
* **core:** Stop applying node-defined sensitive output fields to runtime data ([#30198](https://github.com/n8n-io/n8n/issues/30198)) ([f4e8088](https://github.com/n8n-io/n8n/commit/f4e8088cb8df24443eec0482e2c58346c1e30016))
|
||||
* **core:** Stop logging password reset token values ([#29405](https://github.com/n8n-io/n8n/issues/29405)) ([bc8d196](https://github.com/n8n-io/n8n/commit/bc8d196931b35118ca6078a5845e8549bbba7e6b))
|
||||
* **core:** Support type filters on global credential lookups ([#30002](https://github.com/n8n-io/n8n/issues/30002)) ([8e0f37d](https://github.com/n8n-io/n8n/commit/8e0f37d100b45d4105ca168bb8f62ec2c1328cf2))
|
||||
* **core:** Throw on bare OutputSelector passed to .add()/.to() ([#29736](https://github.com/n8n-io/n8n/issues/29736)) ([60a5122](https://github.com/n8n-io/n8n/commit/60a51229e0db92a00788eb12586ea6376276645d))
|
||||
* **core:** Validate AI builder credential IDs before save ([#30070](https://github.com/n8n-io/n8n/issues/30070)) ([ceaebc6](https://github.com/n8n-io/n8n/commit/ceaebc6cbe7cde2269aee4be6966d021f136f9c6))
|
||||
* Correct connect.html path in browser extension ([#29714](https://github.com/n8n-io/n8n/issues/29714)) ([9b3b29b](https://github.com/n8n-io/n8n/commit/9b3b29b5058da42ec736c14cc8af5726b2a64e4b))
|
||||
* **EditImage Node:** Fix composite operation failing with stream empty buffer ([#30088](https://github.com/n8n-io/n8n/issues/30088)) ([0cc163b](https://github.com/n8n-io/n8n/commit/0cc163b7dcccbfa68c065faa466b2b50f21c4a97))
|
||||
* **editor:** Add expand/collapse to chat panel in Agents ([#30069](https://github.com/n8n-io/n8n/issues/30069)) ([f87094c](https://github.com/n8n-io/n8n/commit/f87094cf6e5efe7c89ef16c4253525091479b356))
|
||||
* **editor:** Disable chat during interactive agent choices ([#30111](https://github.com/n8n-io/n8n/issues/30111)) ([8171cf0](https://github.com/n8n-io/n8n/commit/8171cf0b32ee5aa74dd240bb8f99a3250e428217))
|
||||
* **editor:** Fix Agents styling issues from merge regression ([#30032](https://github.com/n8n-io/n8n/issues/30032)) ([478d499](https://github.com/n8n-io/n8n/commit/478d4998a8055a3d5f81b93120d67282546f125a))
|
||||
* **editor:** Fix collapse/expand for Chat sidebar ([#29378](https://github.com/n8n-io/n8n/issues/29378)) ([ee847d1](https://github.com/n8n-io/n8n/commit/ee847d1624636914323b8b06f145ae811101528f))
|
||||
* **editor:** Improve sidebar new resource menu UX ([#29597](https://github.com/n8n-io/n8n/issues/29597)) ([d5af542](https://github.com/n8n-io/n8n/commit/d5af542f254ba4846f3f393404e24bc5ec998283))
|
||||
* **editor:** Make sure trimmed placeholder never reaches backend ([#29842](https://github.com/n8n-io/n8n/issues/29842)) ([f7c7acc](https://github.com/n8n-io/n8n/commit/f7c7acc2441481235d81a38ea14ed637546d3b40))
|
||||
* **editor:** Match input height with mode selector in resource locator ([#30075](https://github.com/n8n-io/n8n/issues/30075)) ([277431b](https://github.com/n8n-io/n8n/commit/277431b88b195d92a32e35a7df7f8df907d9cb44))
|
||||
* **editor:** Polish encryption keys settings page ([#30008](https://github.com/n8n-io/n8n/issues/30008)) ([5cbd2dd](https://github.com/n8n-io/n8n/commit/5cbd2dd1e9a66cb1d00d89191395f2b417c7a08b))
|
||||
* **editor:** Preserve decimal suffix when duplicating a node ([#29541](https://github.com/n8n-io/n8n/issues/29541)) ([08a36d7](https://github.com/n8n-io/n8n/commit/08a36d7515eda29acd6c5e03f7968d4896465b3d))
|
||||
* **editor:** Refresh node icon when diff sidebar selection changes ([#29816](https://github.com/n8n-io/n8n/issues/29816)) ([ff41613](https://github.com/n8n-io/n8n/commit/ff41613533980f8f2a0ff7baef5fd2a63d981636))
|
||||
* **editor:** Rename canvas header dropdown action to Description ([#29719](https://github.com/n8n-io/n8n/issues/29719)) ([49e7b05](https://github.com/n8n-io/n8n/commit/49e7b056b4a21b6341ce1811a597476d37dfa42f))
|
||||
* **editor:** Rename encryption keys "Type" column to "Status" ([#29966](https://github.com/n8n-io/n8n/issues/29966)) ([e71afed](https://github.com/n8n-io/n8n/commit/e71afedfab84b3b7b88fe9c4e2a36cd31ac6206b))
|
||||
* **editor:** Render tooltips above popovers ([#29997](https://github.com/n8n-io/n8n/issues/29997)) ([ba5b3d1](https://github.com/n8n-io/n8n/commit/ba5b3d13b116d8e055fe3a4dce1b5349545ff540))
|
||||
* **editor:** Resolve expressions in 'Go to Sub-workflow' navigation ([#29843](https://github.com/n8n-io/n8n/issues/29843)) ([d6bae35](https://github.com/n8n-io/n8n/commit/d6bae35e8f8f0399cd722606d911ae2c67b60431))
|
||||
* Fix 15 security issues in fast-xml-builder, basic-ftp, fast-uri and 5 more ([#30169](https://github.com/n8n-io/n8n/issues/30169)) ([267fe49](https://github.com/n8n-io/n8n/commit/267fe49d51b7b8bcc80489b0f9f1a585986bc525))
|
||||
* **Git Node:** Restore Clone and other operations on simple-git 3.36+ ([#30223](https://github.com/n8n-io/n8n/issues/30223)) ([a8aa955](https://github.com/n8n-io/n8n/commit/a8aa95551e5950fd1920c2cce21cd2739b464266))
|
||||
* **Google Chat Node:** Clarify message resource name field ([#29964](https://github.com/n8n-io/n8n/issues/29964)) ([55df7cb](https://github.com/n8n-io/n8n/commit/55df7cbd0619e483e7e02207bc5084c715dcb53a))
|
||||
* **Google Sheets Node:** Reduce duplicate API calls in append operation to avoid quota limits ([#29444](https://github.com/n8n-io/n8n/issues/29444)) ([d63e1ae](https://github.com/n8n-io/n8n/commit/d63e1ae84e767df33c1fc394f646e8ca093aa4a3))
|
||||
* Handle IMAP fetch errors to prevent instance crash and stuck workflows ([#29469](https://github.com/n8n-io/n8n/issues/29469)) ([46d52ff](https://github.com/n8n-io/n8n/commit/46d52ffc7e719f17db56c433ee97a0b48861ba36))
|
||||
* **HTTP Request Node:** Validate URL type in older node versions ([#29886](https://github.com/n8n-io/n8n/issues/29886)) ([29a864c](https://github.com/n8n-io/n8n/commit/29a864ca9bcd88e82cf5f998c9ea36d2f81a5dee))
|
||||
* **MongoDB Node:** Resolve collection parameter per item in write operations ([#29956](https://github.com/n8n-io/n8n/issues/29956)) ([582b6ae](https://github.com/n8n-io/n8n/commit/582b6ae9eaaef6a616233e9bd4eda7230c36eb0a))
|
||||
* **Notion Node:** Paginate Get Many operations beyond 100-item API cap ([#29690](https://github.com/n8n-io/n8n/issues/29690)) ([d318bc1](https://github.com/n8n-io/n8n/commit/d318bc1e330eeb92d84bc35a2ad9cf6931eccfdf))
|
||||
* **Notion Node:** Serialize staticData as ISO string in NotionTrigger ([#29688](https://github.com/n8n-io/n8n/issues/29688)) ([d2e1eb3](https://github.com/n8n-io/n8n/commit/d2e1eb30f15c1e2380b815f4d1f62b2b98b23e9a))
|
||||
* **Notion Node:** Update UI URLs from notion.so to notion.com ahead of domain migration ([#29861](https://github.com/n8n-io/n8n/issues/29861)) ([3593131](https://github.com/n8n-io/n8n/commit/35931319b5b987b7cdd7104accea407fd5390582))
|
||||
* **Oracle DB Node:** Handle the test failures ([#28341](https://github.com/n8n-io/n8n/issues/28341)) ([0697562](https://github.com/n8n-io/n8n/commit/0697562ac9f1507ca0230d02f462889259a5bdcf))
|
||||
* Restore broken stdlib calls in Python Code node ([#29776](https://github.com/n8n-io/n8n/issues/29776)) ([a786476](https://github.com/n8n-io/n8n/commit/a7864762ca656c8e636df1ea33750dff604b60ab))
|
||||
* **RSS Feed Read Node:** Respect proxy settings ([#30059](https://github.com/n8n-io/n8n/issues/30059)) ([2e046d5](https://github.com/n8n-io/n8n/commit/2e046d5b7f2ec4a6fbf00107ee088239f87ce8c5))
|
||||
* **Salesforce Node:** Fix trigger not firing on repeated record updates ([#29107](https://github.com/n8n-io/n8n/issues/29107)) ([f871d44](https://github.com/n8n-io/n8n/commit/f871d44cabc95fb102af8ba1a9e5d2e314205297))
|
||||
* **Schedule Node:** Fix hourly intervals that don't divide evenly into 24h ([#29778](https://github.com/n8n-io/n8n/issues/29778)) ([1a22c76](https://github.com/n8n-io/n8n/commit/1a22c762703bed75a18de868a7bfb7c60eacc516))
|
||||
* **Snowflake Node:** Fix issue with Insert and Update operations not working ([#29339](https://github.com/n8n-io/n8n/issues/29339)) ([4c369e8](https://github.com/n8n-io/n8n/commit/4c369e83f26450395a5a28b6c39a04b2c7650f1f))
|
||||
* **Supabase Node:** Don't display RPCs in an RLC for the table ([#28146](https://github.com/n8n-io/n8n/issues/28146)) ([78aa0e7](https://github.com/n8n-io/n8n/commit/78aa0e70f21df2533a494c02a3e35ca3ab6ca7b0))
|
||||
* **Wait Node:** Resolve expressions inside Custom HTML form fields ([#30060](https://github.com/n8n-io/n8n/issues/30060)) ([7c1a771](https://github.com/n8n-io/n8n/commit/7c1a77154ccf1a5f2a11da3cdf0949b2883c85fb))
|
||||
* **YouTube Node:** Fix misspelled "unlisted" privacy status value in Video Update operation ([#30203](https://github.com/n8n-io/n8n/issues/30203)) ([96b018d](https://github.com/n8n-io/n8n/commit/96b018d3569623e1696a28981b24120a3ceb46d0))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **Acuity Scheduling Trigger Node:** Add webhook request verification ([#29261](https://github.com/n8n-io/n8n/issues/29261)) ([da41470](https://github.com/n8n-io/n8n/commit/da41470311a03a15beb5d7361c0385b7dd9acc12))
|
||||
* Add fully dynamic disclaimer to Quick Connect offer ([#29852](https://github.com/n8n-io/n8n/issues/29852)) ([b6127d8](https://github.com/n8n-io/n8n/commit/b6127d8722ff1bddd9eb5786a6cbd90ce2f98ac1))
|
||||
* **ai-builder:** Add per-PR eval regression detection vs LangSmith baseline ([#29456](https://github.com/n8n-io/n8n/issues/29456)) ([bbe3e2d](https://github.com/n8n-io/n8n/commit/bbe3e2d1487e06df1e58057ec8c47edb5ad19aa7))
|
||||
* **ai-builder:** Guarantee user-visible output on terminal states ([#29636](https://github.com/n8n-io/n8n/issues/29636)) ([4d9e624](https://github.com/n8n-io/n8n/commit/4d9e624b4113d06a4cc7a632aed357806349abcb))
|
||||
* **Asana Trigger Node:** Add webhook request verification ([#29258](https://github.com/n8n-io/n8n/issues/29258)) ([94e4033](https://github.com/n8n-io/n8n/commit/94e403300b44d2f25f4d88dd3d9d1300adfea3bc))
|
||||
* **Cal Trigger Node:** Add webhook request verification ([#29484](https://github.com/n8n-io/n8n/issues/29484)) ([3276edc](https://github.com/n8n-io/n8n/commit/3276edce10dfc7e59aa12e43fd7fc566f91723c4))
|
||||
* **Calendly Trigger Node:** Add webhook request verification ([#29482](https://github.com/n8n-io/n8n/issues/29482)) ([e929f9f](https://github.com/n8n-io/n8n/commit/e929f9fbe751742da7f27658ded1ff0101af19d2))
|
||||
* **core:** Accept merge.input(n) inside ifElse/switch branch targets in workflow-sdk ([#29716](https://github.com/n8n-io/n8n/issues/29716)) ([34f2107](https://github.com/n8n-io/n8n/commit/34f2107071478591a1c98b65576262c40408a157))
|
||||
* **core:** Add flag to import workflow cli to activate workflow on import ([#29770](https://github.com/n8n-io/n8n/issues/29770)) ([283071e](https://github.com/n8n-io/n8n/commit/283071e6114fd8e8b5063e1ba38daf158bd762d2))
|
||||
* **core:** Add IP rate limiting to dynamic credential authentication endpoints ([#30199](https://github.com/n8n-io/n8n/issues/30199)) ([515ae7c](https://github.com/n8n-io/n8n/commit/515ae7ced4b109880306788cb16977c15de92279))
|
||||
* **core:** Add MCP tool to list credentials ([#29438](https://github.com/n8n-io/n8n/issues/29438)) ([d6cc3be](https://github.com/n8n-io/n8n/commit/d6cc3bedd1c4e7a2849eb5cf2acf538fb3a8f3da))
|
||||
* **core:** Add multi-config evaluations backend ([#29784](https://github.com/n8n-io/n8n/issues/29784)) ([8116e0a](https://github.com/n8n-io/n8n/commit/8116e0a4858044712e45c078e06e0a36103d141c))
|
||||
* **core:** Add n8n-object-validation ESLint rule for community nodes ([#29698](https://github.com/n8n-io/n8n/issues/29698)) ([701f9a4](https://github.com/n8n-io/n8n/commit/701f9a462773c204a6dc8bd15c533f9c07cd6e08))
|
||||
* **core:** Add no-template-placeholders ESLint rule for community nodes ([#29796](https://github.com/n8n-io/n8n/issues/29796)) ([c4056b2](https://github.com/n8n-io/n8n/commit/c4056b255edd4420fde6cb5e1028b61f10b2bcf7))
|
||||
* **core:** Add observational memory storage foundation ([#29814](https://github.com/n8n-io/n8n/issues/29814)) ([be4ef22](https://github.com/n8n-io/n8n/commit/be4ef225336166937a8847c2f2615bfd29e40765))
|
||||
* **core:** Define community packages with environment variables ([#29961](https://github.com/n8n-io/n8n/issues/29961)) ([730c3e1](https://github.com/n8n-io/n8n/commit/730c3e12a55a38cdbe9090eabef508cd56d67a9e))
|
||||
* **core:** Generate service-specific OAuth2 credentials for dedicated MCP tools ([#29884](https://github.com/n8n-io/n8n/issues/29884)) ([8617067](https://github.com/n8n-io/n8n/commit/86170674b72acc16d781eafd08cd762c55a7672f))
|
||||
* **core:** Server-side pagination, sorting, and filtering for encryption keys ([#29708](https://github.com/n8n-io/n8n/issues/29708)) ([9afbe13](https://github.com/n8n-io/n8n/commit/9afbe13b81f00f0ea7730541b4909e31b1080249))
|
||||
* **core:** Transform MCP server configs into dedicated MCP tools ([#29493](https://github.com/n8n-io/n8n/issues/29493)) ([4dce41f](https://github.com/n8n-io/n8n/commit/4dce41f79573f864fde16df622c028134d743f03))
|
||||
* **core:** Use McpManagerClient and enforce whether MCP server connections are allowed ([#29694](https://github.com/n8n-io/n8n/issues/29694)) ([8235474](https://github.com/n8n-io/n8n/commit/82354742d348850d8cb6efc6ffe490c53ff0a8a0))
|
||||
* **Customer.io Trigger Node:** Add webhook request verification ([#29480](https://github.com/n8n-io/n8n/issues/29480)) ([a772016](https://github.com/n8n-io/n8n/commit/a772016e36a87d1fbbacbee59ebcd80dbe3b9150))
|
||||
* **editor:** Add envFeatureFlag and copyButton property options ([#29733](https://github.com/n8n-io/n8n/issues/29733)) ([75053fe](https://github.com/n8n-io/n8n/commit/75053fec9373076abfba3db01a967f54f8274e83))
|
||||
* **editor:** Cap eval concurrency slider at admin-set limit ([#29807](https://github.com/n8n-io/n8n/issues/29807)) ([6232de4](https://github.com/n8n-io/n8n/commit/6232de4d477ffa56e0082d87a5b63d1c9ef00d4c))
|
||||
* **editor:** Eval run detail loading + error states (TRUST-70 follow-up) ([#29817](https://github.com/n8n-io/n8n/issues/29817)) ([6f9b99a](https://github.com/n8n-io/n8n/commit/6f9b99a3cf1207ece10a6bd6239a5005c6a10540))
|
||||
* **editor:** Redesign evaluation run detail page ([#29592](https://github.com/n8n-io/n8n/issues/29592)) ([9014bae](https://github.com/n8n-io/n8n/commit/9014baea7ea952aaf782c53bce03d3a8f0ae5ddf))
|
||||
* **editor:** Show locked state and permission notice on data redaction workflow settings ([#30022](https://github.com/n8n-io/n8n/issues/30022)) ([7635131](https://github.com/n8n-io/n8n/commit/7635131bd396252f51d29e7407099eafa92a304f))
|
||||
* **Figma Trigger Node:** Add OAuth2 authentication support ([#30079](https://github.com/n8n-io/n8n/issues/30079)) ([e3e70d6](https://github.com/n8n-io/n8n/commit/e3e70d6068a3d543b29b1bd24682101ecb2e641f))
|
||||
* **Figma Trigger Node:** Add webhook request verification ([#29262](https://github.com/n8n-io/n8n/issues/29262)) ([910822f](https://github.com/n8n-io/n8n/commit/910822fb0951f6ead55fc000e7743a8ee13e82e9))
|
||||
* **Formstack Trigger Node:** Add webhook request verification ([#29495](https://github.com/n8n-io/n8n/issues/29495)) ([4e28652](https://github.com/n8n-io/n8n/commit/4e2865206c72833d9fe585ed941ecc83c1bec699))
|
||||
* **GitLab Trigger Node:** Add webhook request verification ([#29260](https://github.com/n8n-io/n8n/issues/29260)) ([fbf89bd](https://github.com/n8n-io/n8n/commit/fbf89bde1164a19365fe4418405ddec7108543d9))
|
||||
* **Jira Node:** Add OAuth2 (3LO) support ([#29414](https://github.com/n8n-io/n8n/issues/29414)) ([4d5bafc](https://github.com/n8n-io/n8n/commit/4d5bafc146125fa22d05cf924c5e68bc51263722))
|
||||
* **MailerLite Trigger Node:** Add webhook request verification ([#29491](https://github.com/n8n-io/n8n/issues/29491)) ([12b7cc6](https://github.com/n8n-io/n8n/commit/12b7cc67395bf1991235ae0f00739d9f2803cb9c))
|
||||
* **Mautic Trigger Node:** Add webhook request verification ([#29658](https://github.com/n8n-io/n8n/issues/29658)) ([eaadf19](https://github.com/n8n-io/n8n/commit/eaadf190b89f21f74bc3a25b16803576f91e9618))
|
||||
* **Microsoft Outlook Node:** Add location and attendees fields to calendar events ([#29844](https://github.com/n8n-io/n8n/issues/29844)) ([2e21c5f](https://github.com/n8n-io/n8n/commit/2e21c5fcf83a2fc86659c7464b2bc6672230389f))
|
||||
* **Microsoft Outlook Node:** Add support for recurring event instances ([#29802](https://github.com/n8n-io/n8n/issues/29802)) ([dab3653](https://github.com/n8n-io/n8n/commit/dab3653f8016b7f9187559658ea6ef58220df2d1))
|
||||
* **Onfleet Trigger Node:** Add webhook request verification ([#29485](https://github.com/n8n-io/n8n/issues/29485)) ([133a5aa](https://github.com/n8n-io/n8n/commit/133a5aa0adae69f86f1603bd9ad85c852c0ccdf5))
|
||||
* **Strava Node:** Allow custom OAuth2 scopes ([#29972](https://github.com/n8n-io/n8n/issues/29972)) ([5abcae6](https://github.com/n8n-io/n8n/commit/5abcae686cf1b64e06bbbd6f62b6871bc4feec56))
|
||||
* **Taiga Trigger Node:** Add webhook request verification ([#29487](https://github.com/n8n-io/n8n/issues/29487)) ([3c97c49](https://github.com/n8n-io/n8n/commit/3c97c49d63c824c2a3b4284beecf8957c44c1c16))
|
||||
* **Trello Trigger Node:** Add webhook request verification ([#29252](https://github.com/n8n-io/n8n/issues/29252)) ([8f1f42d](https://github.com/n8n-io/n8n/commit/8f1f42d18056ba51e450ba90ba3be65cbf9745aa))
|
||||
* **Twilio Trigger Node:** Add webhook request verification ([#29259](https://github.com/n8n-io/n8n/issues/29259)) ([acc9643](https://github.com/n8n-io/n8n/commit/acc964381189aaacbeb584a16c0155ba6f96ffa1))
|
||||
|
||||
|
||||
# [2.20.0](https://github.com/n8n-io/n8n/compare/n8n@2.19.0...n8n@2.20.0) (2026-05-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ai-builder:** Add boundaries on the workflow builder remediation loops ([#29430](https://github.com/n8n-io/n8n/issues/29430)) ([2259f32](https://github.com/n8n-io/n8n/commit/2259f32de88c103b088b450bf46990ad2e939942))
|
||||
* **ai-builder:** Allow skipping final ask-user question ([#29563](https://github.com/n8n-io/n8n/issues/29563)) ([661f990](https://github.com/n8n-io/n8n/commit/661f9908bce51076811c76c854f165f4c5acaccf))
|
||||
* **ai-builder:** Filter LangSmith eval dataset by local file slugs ([#29507](https://github.com/n8n-io/n8n/issues/29507)) ([54d9286](https://github.com/n8n-io/n8n/commit/54d9286d922e0cad17d5c5de10a052d653c1591b))
|
||||
* **ai-builder:** Handle properties with contradicting displayOptions as OR alternatives instead of AND ([#29500](https://github.com/n8n-io/n8n/issues/29500)) ([84ac811](https://github.com/n8n-io/n8n/commit/84ac8110f8d70dd653b4d40cb63259522731b0d0))
|
||||
* **ai-builder:** Stop builder from adding auth to inbound trigger nodes by default ([#29648](https://github.com/n8n-io/n8n/issues/29648)) ([c28d501](https://github.com/n8n-io/n8n/commit/c28d501ba1630861fa0993d0d85f08efb635a5a4))
|
||||
* Allow 5-field cron expressions with step values in polling nodes ([#29447](https://github.com/n8n-io/n8n/issues/29447)) ([d18f183](https://github.com/n8n-io/n8n/commit/d18f183b211416d5b74cfdc2e740b9c663ede134))
|
||||
* **Anthropic Chat Model Node:** Add adaptive thinking mode for Claude Opus 4.7+ ([#29467](https://github.com/n8n-io/n8n/issues/29467)) ([90d875c](https://github.com/n8n-io/n8n/commit/90d875ce3e5a2a004a5a3d8f28ac4e9820b109f4))
|
||||
* **Compare Datasets Node:** Preserve falsy values in mix mode except fields ([#29666](https://github.com/n8n-io/n8n/issues/29666)) ([62ddc5c](https://github.com/n8n-io/n8n/commit/62ddc5c443273559c286a1d2eb19efdca345ac9a))
|
||||
* **core:** Accept placeholder() inside node credentials slot ([#29691](https://github.com/n8n-io/n8n/issues/29691)) ([dc6bd68](https://github.com/n8n-io/n8n/commit/dc6bd68de3b419fb1e23806781bbc125b621ed8a))
|
||||
* **core:** Acquire expression isolate for dynamic node parameter requests ([#29671](https://github.com/n8n-io/n8n/issues/29671)) ([418f1f2](https://github.com/n8n-io/n8n/commit/418f1f2edb6abfebe1085b8c3b5c1b22530f1a5c))
|
||||
* **core:** Add file path validation to localFile source ([#29464](https://github.com/n8n-io/n8n/issues/29464)) ([7277566](https://github.com/n8n-io/n8n/commit/7277566c64c36f5e43c17a2e620da2408ab1dcb7))
|
||||
* **core:** Add GET handler to MCP endpoint for Streamable HTTP spec compliance ([#28787](https://github.com/n8n-io/n8n/issues/28787)) ([4ae0322](https://github.com/n8n-io/n8n/commit/4ae0322ef246348892000d0539904e56c122d204))
|
||||
* **core:** Add timeout to external secrets provider refresh ([#29679](https://github.com/n8n-io/n8n/issues/29679)) ([e350429](https://github.com/n8n-io/n8n/commit/e35042999f7d477ed1da59f43ef03605763ac2bf))
|
||||
* **core:** Apply credential allowed domains in declarative node requests ([#29082](https://github.com/n8n-io/n8n/issues/29082)) ([8551b1b](https://github.com/n8n-io/n8n/commit/8551b1b90ce16b31a017bd07177694ef39ad226d))
|
||||
* **core:** Correct LDAP search filter construction ([#29388](https://github.com/n8n-io/n8n/issues/29388)) ([32dd743](https://github.com/n8n-io/n8n/commit/32dd7433b7ef168161e32c20939859060da9827c))
|
||||
* **core:** Fix code node executions hanging when idle timer overlaps with task acceptance ([#29239](https://github.com/n8n-io/n8n/issues/29239)) ([7bd3532](https://github.com/n8n-io/n8n/commit/7bd3532f07c151568634e84f3ae24f38ab8e60e4))
|
||||
* **core:** Fix MCP OAuth discovery URL construction and grant type selection ([#27283](https://github.com/n8n-io/n8n/issues/27283)) ([d92ec16](https://github.com/n8n-io/n8n/commit/d92ec168aa5f984513874e2978f73d8f2cbdc80e))
|
||||
* **core:** Force saving executions when instance AI executes WFs ([#29515](https://github.com/n8n-io/n8n/issues/29515)) ([ef56501](https://github.com/n8n-io/n8n/commit/ef56501d4729b5b508a4c5e60263d10a8fc9db76))
|
||||
* **core:** Gate Instance AI edits to pre-existing workflows ([#29501](https://github.com/n8n-io/n8n/issues/29501)) ([6175fd6](https://github.com/n8n-io/n8n/commit/6175fd6f7b56ead0176938657085b763c1204681))
|
||||
* **core:** Generate array types for properties with multipleValues ([#29410](https://github.com/n8n-io/n8n/issues/29410)) ([fb65c61](https://github.com/n8n-io/n8n/commit/fb65c6155ee9ae5b11a2c409f35e98c206aaf164))
|
||||
* **core:** Handle missing runData during execution recovery ([#29513](https://github.com/n8n-io/n8n/issues/29513)) ([8b7b4f5](https://github.com/n8n-io/n8n/commit/8b7b4f575d9d9b5b02a8ddf67aaff6b3d5279d78))
|
||||
* **core:** Harden Set node workflow SDK contract ([#29568](https://github.com/n8n-io/n8n/issues/29568)) ([625ed5e](https://github.com/n8n-io/n8n/commit/625ed5e95a90f30e07e88253515713056e406f5b))
|
||||
* **core:** Include stack trace in error logs for non-ApplicationError errors ([#29496](https://github.com/n8n-io/n8n/issues/29496)) ([16d1461](https://github.com/n8n-io/n8n/commit/16d1461858107697eac399039c834c7296fe8868))
|
||||
* **core:** Increase default task runner grant token TTL to 30s ([#29443](https://github.com/n8n-io/n8n/issues/29443)) ([328f4b8](https://github.com/n8n-io/n8n/commit/328f4b8b964d587763bf14b1980916046878f0f0))
|
||||
* **core:** Isolate expressions on chat resumption and test webhook deactivation ([#29703](https://github.com/n8n-io/n8n/issues/29703)) ([568e5a2](https://github.com/n8n-io/n8n/commit/568e5a24bf8f4e73d0b134dbac1631535bba10a7))
|
||||
* **core:** Make MCP client registration cap tunable and surface a proper limit error ([#29429](https://github.com/n8n-io/n8n/issues/29429)) ([dad4231](https://github.com/n8n-io/n8n/commit/dad423155f1ee105e3ed1eab0b65a8d8bc2ee3a3))
|
||||
* **core:** Make task runner grant token TTL configurable ([#29357](https://github.com/n8n-io/n8n/issues/29357)) ([3f350a8](https://github.com/n8n-io/n8n/commit/3f350a85770680895be5723803ef51453476fed2))
|
||||
* **core:** Pass nodeTypesProvider to validate workflows fully at instance AI ([#29333](https://github.com/n8n-io/n8n/issues/29333)) ([388cd79](https://github.com/n8n-io/n8n/commit/388cd79908418d558fff36f938969cdc79fc60c2))
|
||||
* **core:** Persist execution context before writing to db ([#28973](https://github.com/n8n-io/n8n/issues/28973)) ([c4bb5ae](https://github.com/n8n-io/n8n/commit/c4bb5ae8df8e7de4c7b919a82d3cf2f492edcc5b))
|
||||
* **core:** Recreate data table backing tables on entity import ([#29454](https://github.com/n8n-io/n8n/issues/29454)) ([6bca1fa](https://github.com/n8n-io/n8n/commit/6bca1fa26f0d1a23c8c7e175dc6ae590eeb2036e))
|
||||
* **core:** Reject empty webhookMethods in community lint rule ([#29474](https://github.com/n8n-io/n8n/issues/29474)) ([34d7a02](https://github.com/n8n-io/n8n/commit/34d7a02df73f233ef55fc78e3ea8167bc2b32a1f))
|
||||
* **core:** Reset Redis retry counter on successful reconnect ([#29377](https://github.com/n8n-io/n8n/issues/29377)) ([7722023](https://github.com/n8n-io/n8n/commit/7722023abd8ffb2f96a7dbec0ba51e4d7454ea05))
|
||||
* **core:** Respect global admin scope when listing favorites ([#29472](https://github.com/n8n-io/n8n/issues/29472)) ([d9d1e7c](https://github.com/n8n-io/n8n/commit/d9d1e7c44a1bcf074cdbec234b0d8d4ddb8d7d5e))
|
||||
* **core:** Restore peer project discovery in share dropdowns ([#29537](https://github.com/n8n-io/n8n/issues/29537)) ([2a0e2fb](https://github.com/n8n-io/n8n/commit/2a0e2fb47ae1d82cd2354db8c2013ea46f24f21e))
|
||||
* **core:** Round fractional time saved values before inserting into insights BIGINT column ([#29553](https://github.com/n8n-io/n8n/issues/29553)) ([74d55b9](https://github.com/n8n-io/n8n/commit/74d55b9c681273ae79fbaf39693bd3b37d83b66a))
|
||||
* **core:** Show AI Builder draft workflows in workflow list ([#29670](https://github.com/n8n-io/n8n/issues/29670)) ([dc52bbd](https://github.com/n8n-io/n8n/commit/dc52bbd5329a27245a5fe2a1da45d9e8efe6a549))
|
||||
* **core:** Use editor base URL for workflow and execution links ([#23630](https://github.com/n8n-io/n8n/issues/23630)) ([896461b](https://github.com/n8n-io/n8n/commit/896461bee3c356e66b282763cd31427a137ebd62))
|
||||
* **core:** Validate workflow import URL requests ([#29178](https://github.com/n8n-io/n8n/issues/29178)) ([ecd0ba8](https://github.com/n8n-io/n8n/commit/ecd0ba8ebabc99055441290d543f0bd87a33df31))
|
||||
* **core:** Wire EncryptionKeyProxy provider on bootstrap ([#29581](https://github.com/n8n-io/n8n/issues/29581)) ([ee7260c](https://github.com/n8n-io/n8n/commit/ee7260c4959b0dff8636606aebdac10eddd76e36))
|
||||
* **DeepL Node:** Update credentials to use header-based authentication ([#24614](https://github.com/n8n-io/n8n/issues/24614)) ([b72bd19](https://github.com/n8n-io/n8n/commit/b72bd1987c33b15cd658d2a038b9763c6fb83b55))
|
||||
* Drop template search tools from builder ([#29573](https://github.com/n8n-io/n8n/issues/29573)) ([9b00ccb](https://github.com/n8n-io/n8n/commit/9b00ccbfd1cfb123533397126123f5d2ad34071f))
|
||||
* **editor:** Add proper bg color for hover state with color-mix() ([#29590](https://github.com/n8n-io/n8n/issues/29590)) ([6698c42](https://github.com/n8n-io/n8n/commit/6698c42e4ed4706825f5d2e3bac39641e261f153))
|
||||
* **editor:** Align message box button radius with N8nButton ([#29397](https://github.com/n8n-io/n8n/issues/29397)) ([bc315d0](https://github.com/n8n-io/n8n/commit/bc315d087fd772218b2f3caa047c86493c048f27))
|
||||
* **editor:** Fix OAuth2 credential showing "Needs first setup" after connecting ([#29617](https://github.com/n8n-io/n8n/issues/29617)) ([243f665](https://github.com/n8n-io/n8n/commit/243f665e60bff1c2531977c3f860aa7589a321e9))
|
||||
* **editor:** Fix sub-workflow folder placement and connection loss ([#28770](https://github.com/n8n-io/n8n/issues/28770)) ([44579d6](https://github.com/n8n-io/n8n/commit/44579d6d3ae59a1f4eedf9a0b49cecb006053072))
|
||||
* **editor:** Ignore paste events on read-only canvas ([#29673](https://github.com/n8n-io/n8n/issues/29673)) ([34c49b9](https://github.com/n8n-io/n8n/commit/34c49b9c238de5d5ee0b9421918435c4582eb13a))
|
||||
* **editor:** Keep publish actions menu enabled for published workflows ([#29396](https://github.com/n8n-io/n8n/issues/29396)) ([c65fa28](https://github.com/n8n-io/n8n/commit/c65fa28e1caac5a49e6a5e82d3354ed631be0df4))
|
||||
* **editor:** Load more executions on tall screens ([#29407](https://github.com/n8n-io/n8n/issues/29407)) ([a273a9d](https://github.com/n8n-io/n8n/commit/a273a9d3f498d8112605f1277ce7848d8bd357c3))
|
||||
* **editor:** Make instance ai resource link chips open resources ([#29577](https://github.com/n8n-io/n8n/issues/29577)) ([b97ca36](https://github.com/n8n-io/n8n/commit/b97ca36a99d099288cfc127df98038b2b64c03d5))
|
||||
* **editor:** Make textarea resize handle accessible in NDV ([#29676](https://github.com/n8n-io/n8n/issues/29676)) ([9fda733](https://github.com/n8n-io/n8n/commit/9fda7332c4c0a8851a7482365a967ea18db2a816))
|
||||
* **editor:** Mark workflow dirty after debug pinData changes ([#28886](https://github.com/n8n-io/n8n/issues/28886)) ([2beb006](https://github.com/n8n-io/n8n/commit/2beb0062a5f92c883f18abaf9ea33590a41aca49))
|
||||
* **editor:** Never block publishing on node execution issues ([#29479](https://github.com/n8n-io/n8n/issues/29479)) ([5a56459](https://github.com/n8n-io/n8n/commit/5a564591291989f13ac667eed575332f7f4d2a6a))
|
||||
* **editor:** Polish encryption keys date range filter ([#29569](https://github.com/n8n-io/n8n/issues/29569)) ([56412bc](https://github.com/n8n-io/n8n/commit/56412bcce2ef1d364acdbe422f5c88762319bb22))
|
||||
* **editor:** Remove clipping for focus panel textarea ([#28677](https://github.com/n8n-io/n8n/issues/28677)) ([5361257](https://github.com/n8n-io/n8n/commit/5361257a80e515e1cc26cdf10e8ceb78c9ec70be))
|
||||
* **editor:** Restore read-only mode for archived workflows on canvas ([#29559](https://github.com/n8n-io/n8n/issues/29559)) ([a7ef741](https://github.com/n8n-io/n8n/commit/a7ef7416b111384d250f975e718c691b2674fef6))
|
||||
* **editor:** Show permission-aware message on redacted input/output panels ([#29521](https://github.com/n8n-io/n8n/issues/29521)) ([83c400e](https://github.com/n8n-io/n8n/commit/83c400e8d47c875f57dce26680358595822ce012))
|
||||
* **editor:** Surface unofficial verified community node tools in AI Tools picker ([#28985](https://github.com/n8n-io/n8n/issues/28985)) ([f77dfd1](https://github.com/n8n-io/n8n/commit/f77dfd1a11591124e6db61c72ed207067bae6214))
|
||||
* Fix ollama node url path and thinking tokens ([#23963](https://github.com/n8n-io/n8n/issues/23963)) ([4ea1153](https://github.com/n8n-io/n8n/commit/4ea1153dfb903346bead9e6d328ec8f543c80559))
|
||||
* **Google Drive Node:** Resolve original file name when copying with empty name ([#28896](https://github.com/n8n-io/n8n/issues/28896)) ([c274976](https://github.com/n8n-io/n8n/commit/c2749768aa5d173c3354e8d31a18c438ebd5fdfb))
|
||||
* **Merge Node:** Improve SQL Query mode memory efficiency and error reporting ([#28993](https://github.com/n8n-io/n8n/issues/28993)) ([12275c8](https://github.com/n8n-io/n8n/commit/12275c86d992115fef2ded4e5f172730222c5669))
|
||||
* **Microsoft Outlook Trigger Node:** Use per-folder endpoints for folder-scoped message polling ([#29663](https://github.com/n8n-io/n8n/issues/29663)) ([f401f91](https://github.com/n8n-io/n8n/commit/f401f9101d08fc62eef7e051f3baa23638c80c1b))
|
||||
* No Credits state for n8n Connect badge ([#29375](https://github.com/n8n-io/n8n/issues/29375)) ([47ad397](https://github.com/n8n-io/n8n/commit/47ad39777f9525324524f2595fc4506065f33a9c))
|
||||
* **Notion Node:** Support app.notion.com URL format for page and block ID extraction ([#29554](https://github.com/n8n-io/n8n/issues/29554)) ([221c7f7](https://github.com/n8n-io/n8n/commit/221c7f7410d25b89b052e89d745184675b69dc53))
|
||||
* **Postgres Node:** Output Large-Format Numbers As option ignored after pool is cached ([#29477](https://github.com/n8n-io/n8n/issues/29477)) ([a65e181](https://github.com/n8n-io/n8n/commit/a65e181a2213f1b984c225539302a1a12a30cc9b))
|
||||
* **Salesforce Node:** Allow overriding JWT audience with My Domain URL ([#29016](https://github.com/n8n-io/n8n/issues/29016)) ([9decb1e](https://github.com/n8n-io/n8n/commit/9decb1e2a9f6d6612014354d7ca6f8b62600ce9d))
|
||||
* **Schedule Node:** Cap day-of-month jitter at 28 ([#29614](https://github.com/n8n-io/n8n/issues/29614)) ([86f47ee](https://github.com/n8n-io/n8n/commit/86f47ee6dc88397b05bfb784b0092674ba3b4289))
|
||||
* Skip AI tool generation for community trigger nodes ([#29453](https://github.com/n8n-io/n8n/issues/29453)) ([c724dac](https://github.com/n8n-io/n8n/commit/c724dace38ec1e3aa69de40d48e068cf36c962b0))
|
||||
* **Snowflake Node:** Avoid call stack overflow on large result sets ([#29200](https://github.com/n8n-io/n8n/issues/29200)) ([b2ac67f](https://github.com/n8n-io/n8n/commit/b2ac67f15452c625d4dee146a040b6324cdfefbb))
|
||||
* **Telegram Trigger Node:** Drop pending updates when creating a new webhook ([#29103](https://github.com/n8n-io/n8n/issues/29103)) ([4358f1d](https://github.com/n8n-io/n8n/commit/4358f1d51c588e76d03aa677f9b7deabbbc1af9d))
|
||||
* **Todoist Node:** Migrate to Todoist unified API v1 endpoints ([#29532](https://github.com/n8n-io/n8n/issues/29532)) ([5799481](https://github.com/n8n-io/n8n/commit/5799481d1c3bf14806d11ba2928af4f7f88db29f))
|
||||
* Use explicit node references for AI memory session keys ([#29473](https://github.com/n8n-io/n8n/issues/29473)) ([139b803](https://github.com/n8n-io/n8n/commit/139b803daefca44fd66a92156867d77ccdffcc66))
|
||||
* Validate sql ([#24706](https://github.com/n8n-io/n8n/issues/24706)) ([47a6658](https://github.com/n8n-io/n8n/commit/47a6658b2d4cd2d4be5e59b0d61f9bd25b553007))
|
||||
* **Zammad Node:** Add To and CC fields for email articles ([#28860](https://github.com/n8n-io/n8n/issues/28860)) ([e04f027](https://github.com/n8n-io/n8n/commit/e04f027b5dd008eb0c9354d166c716a93cdc48b7))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add instance-level JWKS URI endpoint for JWE public key distribution ([#29498](https://github.com/n8n-io/n8n/issues/29498)) ([794334c](https://github.com/n8n-io/n8n/commit/794334cd79f1ee5a05cd0d818fc801920e0fe6d9))
|
||||
* Add no-runtime-dependencies ESLint rule ([#29366](https://github.com/n8n-io/n8n/issues/29366)) ([8aace75](https://github.com/n8n-io/n8n/commit/8aace75535f53ebf37c2a547849e044948c99cb8))
|
||||
* Add pairwise workflow eval pipeline ([#29123](https://github.com/n8n-io/n8n/issues/29123)) ([fdceec2](https://github.com/n8n-io/n8n/commit/fdceec21b996a1456ceb44389e760a80d75d49a1))
|
||||
* Add valid-credential-references ESLint rule ([#29452](https://github.com/n8n-io/n8n/issues/29452)) ([c6c6f8f](https://github.com/n8n-io/n8n/commit/c6c6f8ff3889a48ac73d5e5bb242e88818707fc0))
|
||||
* **core:** Add --include and --exclude flags to import:credentials command ([#29364](https://github.com/n8n-io/n8n/issues/29364)) ([f5132b9](https://github.com/n8n-io/n8n/commit/f5132b9e9abe23eb1a2b1225d889f1dd83d83f94))
|
||||
* **core:** Add configurable event log path per process ([#29403](https://github.com/n8n-io/n8n/issues/29403)) ([45effb8](https://github.com/n8n-io/n8n/commit/45effb8959e4013d46a022a5a3f901e9d0284d35))
|
||||
* **core:** Add endpoint to toggle mcp access for multiple workflows ([#29007](https://github.com/n8n-io/n8n/issues/29007)) ([0d907d6](https://github.com/n8n-io/n8n/commit/0d907d67945dfd9624eda6f3fb634cee4bd2d195))
|
||||
* **core:** Add JWE decryption to OAuth2 credential flow ([#29497](https://github.com/n8n-io/n8n/issues/29497)) ([ad7cdcc](https://github.com/n8n-io/n8n/commit/ad7cdcc04f47e1c34754636098ff698b7b153d05))
|
||||
* **core:** Add MCP tool search executions ([#29161](https://github.com/n8n-io/n8n/issues/29161)) ([1d9548c](https://github.com/n8n-io/n8n/commit/1d9548c81f6a984882aadd7091cd649967aa7201))
|
||||
* **core:** Add migration for postgres variable values ([#29489](https://github.com/n8n-io/n8n/issues/29489)) ([898ba5a](https://github.com/n8n-io/n8n/commit/898ba5ae2562542af11031b5dfdf0400afb91fbd))
|
||||
* **core:** Add preAuthentication support to requestOAuth2 pipeline ([#29418](https://github.com/n8n-io/n8n/issues/29418)) ([473d49c](https://github.com/n8n-io/n8n/commit/473d49c9b18ff4d8226f54fe0c5c8a2a1c6fdca5))
|
||||
* **core:** Bootstrap legacy CBC and initial GCM encryption keys on startup ([#29400](https://github.com/n8n-io/n8n/issues/29400)) ([9576ab9](https://github.com/n8n-io/n8n/commit/9576ab907cc3bdb560d1b40a1582ecf67c253d3a))
|
||||
* **core:** Broadcast workflow settings updates ([#29459](https://github.com/n8n-io/n8n/issues/29459)) ([9cb1605](https://github.com/n8n-io/n8n/commit/9cb160585c05ccb1770554cd0998ea4d9b0ab3cc))
|
||||
* **core:** Decouple insights pruning max age from license ([#29527](https://github.com/n8n-io/n8n/issues/29527)) ([45c18fb](https://github.com/n8n-io/n8n/commit/45c18fb09c04749063edc3545c38ad37006c0c49))
|
||||
* **core:** Fix user access control logic ([#29481](https://github.com/n8n-io/n8n/issues/29481)) ([484cb2e](https://github.com/n8n-io/n8n/commit/484cb2efba8b33555c4d34bb95680d16a3328c1e))
|
||||
* **core:** Manage MCP settings via environment variables ([#29368](https://github.com/n8n-io/n8n/issues/29368)) ([05e10e2](https://github.com/n8n-io/n8n/commit/05e10e268083fd7f9f1176634f0c1cab88297b94))
|
||||
* **core:** Run evaluation test cases in parallel behind PostHog rollout flag ([#29412](https://github.com/n8n-io/n8n/issues/29412)) ([4c76aa1](https://github.com/n8n-io/n8n/commit/4c76aa1467d08d5f188cf8b7716b52b410f2bd65))
|
||||
* **core:** Use versioned prebuilt Daytona snapshots for Instance AI sandboxes ([#29359](https://github.com/n8n-io/n8n/issues/29359)) ([308d0b4](https://github.com/n8n-io/n8n/commit/308d0b42b32a3372bac3a759b15ee410c9d095eb))
|
||||
* **core:** Warn and skip on duplicate scheduled executions ([#28649](https://github.com/n8n-io/n8n/issues/28649)) ([b8b7571](https://github.com/n8n-io/n8n/commit/b8b75719ba373a27f60c6f471b170216fe7c41a9))
|
||||
* **editor:** Add data encryption keys settings page ([#29068](https://github.com/n8n-io/n8n/issues/29068)) ([656f9c2](https://github.com/n8n-io/n8n/commit/656f9c2d7fc635c117efaeb40bb0fb98256f5ba3))
|
||||
* **editor:** Add environment variable to disable workflow autosave ([#25144](https://github.com/n8n-io/n8n/issues/25144)) ([a2afc47](https://github.com/n8n-io/n8n/commit/a2afc47c226a716b7ae059306e684748c9d65947))
|
||||
* **editor:** Add reveal redacted data permission to custom roles execution section ([#29526](https://github.com/n8n-io/n8n/issues/29526)) ([be22095](https://github.com/n8n-io/n8n/commit/be22095646c0daf2bbdc2afb7ebc4c1e4a50e349))
|
||||
* **editor:** Add transition on Sidebar collapsed ([#29650](https://github.com/n8n-io/n8n/issues/29650)) ([07b5343](https://github.com/n8n-io/n8n/commit/07b53430f9e9efefaa78d90d3a613d5518ede4e5))
|
||||
* **editor:** Hide model selector for unsupported AI Gateway actions ([#29588](https://github.com/n8n-io/n8n/issues/29588)) ([0f7776e](https://github.com/n8n-io/n8n/commit/0f7776e972c1d94d0f61d6d8855865802ef2a273))
|
||||
* **editor:** Move Switch component to core design system ([#27322](https://github.com/n8n-io/n8n/issues/27322)) ([758f89c](https://github.com/n8n-io/n8n/commit/758f89c9ef4b936e1904c244698ccb4d92f6dd51))
|
||||
* **editor:** Track IdP role mapping in provisioning telemetry ([#29416](https://github.com/n8n-io/n8n/issues/29416)) ([40da23f](https://github.com/n8n-io/n8n/commit/40da23f68899bc11240b252d417aa01dec8485a9))
|
||||
* **editor:** Update copy for mcp settings ([#29399](https://github.com/n8n-io/n8n/issues/29399)) ([5f93b48](https://github.com/n8n-io/n8n/commit/5f93b48e79067251e782940489848f81f897d3a4))
|
||||
* Include updatedAt in encryption key response DTO ([#29424](https://github.com/n8n-io/n8n/issues/29424)) ([569f94b](https://github.com/n8n-io/n8n/commit/569f94bb828bdd662bb291bd1d566e4e2a8ebdae))
|
||||
* **instance-ai:** Orchestrator-executed checkpoint tasks for planned workflow verification ([#29049](https://github.com/n8n-io/n8n/issues/29049)) ([ad359b5](https://github.com/n8n-io/n8n/commit/ad359b5e2ceaaf2ba04559e43117d81bc5f2df25))
|
||||
* **Netlify Trigger Node:** Add webhook request verification ([#29256](https://github.com/n8n-io/n8n/issues/29256)) ([1516ec7](https://github.com/n8n-io/n8n/commit/1516ec7c06ab797dbf94fd1b8a0322209e6ee0bc))
|
||||
* **Slack Node:** Allow users to configure OAuth2 scopes ([#28728](https://github.com/n8n-io/n8n/issues/28728)) ([aa0daf9](https://github.com/n8n-io/n8n/commit/aa0daf9fb630661d35e8bd006ed3b749051f7a7d))
|
||||
* Validate workflow-sdk output topology against mode ([#29363](https://github.com/n8n-io/n8n/issues/29363)) ([0a80722](https://github.com/n8n-io/n8n/commit/0a80722dcb3fcdbc23d9e768413b3141ec329adc))
|
||||
|
||||
|
||||
# [2.19.0](https://github.com/n8n-io/n8n/compare/n8n@2.18.0...n8n@2.19.0) (2026-04-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Add credential auth and test for Twilio, Pipedrive, Asana, Freshdesk, Workable ([#28265](https://github.com/n8n-io/n8n/issues/28265)) ([a2aa69d](https://github.com/n8n-io/n8n/commit/a2aa69dbd7c931a869417d28c26297fe5beb86e9))
|
||||
* Additional keys in routing nodes ([#28758](https://github.com/n8n-io/n8n/issues/28758)) ([8e49800](https://github.com/n8n-io/n8n/commit/8e49800421e6611084c1d092cd2aad82696d3e22))
|
||||
* **ai-builder:** Adjust chat trigger memory builderHints ([#29156](https://github.com/n8n-io/n8n/issues/29156)) ([b6fcabc](https://github.com/n8n-io/n8n/commit/b6fcabc0252c9a021892dd940796f7282a45971c))
|
||||
* **ai-builder:** Hide and reap intermediate AI-created workflows ([#29066](https://github.com/n8n-io/n8n/issues/29066)) ([632ae67](https://github.com/n8n-io/n8n/commit/632ae67de3fa33487e1ad513043cf740cb32bb1a))
|
||||
* **ai-builder:** Improve button, icon, callout and workflowSelector schemas ([#28828](https://github.com/n8n-io/n8n/issues/28828)) ([afe4298](https://github.com/n8n-io/n8n/commit/afe4298427c4e4f6cc0ebf11dc5a3b7a41f38f10))
|
||||
* **ai-builder:** Keep workflow identity across rebuild/patch ([#28926](https://github.com/n8n-io/n8n/issues/28926)) ([13029e6](https://github.com/n8n-io/n8n/commit/13029e6d039ac61280543406b88e1b680fe22c55))
|
||||
* **ai-builder:** Prevent duplicate workflow creation on parallel submits in instance AI ([#28793](https://github.com/n8n-io/n8n/issues/28793)) ([782b2d1](https://github.com/n8n-io/n8n/commit/782b2d18b22601199919b7674100dd88aaa8f7ac))
|
||||
* **ai-builder:** Stop treating empty defaults as satisfying required for the Split node ([#28978](https://github.com/n8n-io/n8n/issues/28978)) ([8b33424](https://github.com/n8n-io/n8n/commit/8b33424d0f222a605d38b736e6e4a65682bad4e3))
|
||||
* **ai-builder:** Support 'disabledOptions' in node schemas ([#28894](https://github.com/n8n-io/n8n/issues/28894)) ([bc5e490](https://github.com/n8n-io/n8n/commit/bc5e490f686d908fc4a1efdbb4b2198bdbc98993))
|
||||
* **ai-builder:** Validate merge node connections matches 'numberInputs' parameter ([#28831](https://github.com/n8n-io/n8n/issues/28831)) ([0e838de](https://github.com/n8n-io/n8n/commit/0e838deab27f3f81908e17486d94c604f8ba4fc4))
|
||||
* **ai-builder:** Validate required inputs on AI nodes ([#29159](https://github.com/n8n-io/n8n/issues/29159)) ([97bdbd0](https://github.com/n8n-io/n8n/commit/97bdbd055a5a89ab2c73a4933ac413f429724832))
|
||||
* Allow task runner to invoke skills via slash command prefix ([#29302](https://github.com/n8n-io/n8n/issues/29302)) ([65e55a8](https://github.com/n8n-io/n8n/commit/65e55a88c14a7d1d29a927c62d18d293f701e51d))
|
||||
* **Brevo Node:** Send valid email attachments with correct filenames ([#28816](https://github.com/n8n-io/n8n/issues/28816)) ([7d6f777](https://github.com/n8n-io/n8n/commit/7d6f7778080a488da49b5935bbbb56dd30105724))
|
||||
* **core:** Fixes user search functionality in ProjectSettings for pr… ([#28914](https://github.com/n8n-io/n8n/issues/28914)) ([1eea626](https://github.com/n8n-io/n8n/commit/1eea62629acb62d3ab3683b0049c15be11901039))
|
||||
* **core:** Add missing break statements in filter condition evaluation ([#27708](https://github.com/n8n-io/n8n/issues/27708)) ([1e77f71](https://github.com/n8n-io/n8n/commit/1e77f7146dc63106cd9e486811a0292eba07632e))
|
||||
* **core:** Allow .trim() in workflow-sdk parsing and coerce stringified patches ([#29111](https://github.com/n8n-io/n8n/issues/29111)) ([b44e9d1](https://github.com/n8n-io/n8n/commit/b44e9d12070eb8db614be8cde51216c94d3c63fe))
|
||||
* **core:** Allow SAML connection test without saving config first ([#28702](https://github.com/n8n-io/n8n/issues/28702)) ([828d0ee](https://github.com/n8n-io/n8n/commit/828d0ee9069372ac921f975d66a101bb70d0133a))
|
||||
* **core:** Broadcast MCP availability change via push connection ([#29019](https://github.com/n8n-io/n8n/issues/29019)) ([e183ae4](https://github.com/n8n-io/n8n/commit/e183ae4434a9ec6aee57792846d5483a754cdb56))
|
||||
* **core:** Can't install community nodes on Windows ([#28481](https://github.com/n8n-io/n8n/issues/28481)) ([7e3a2f6](https://github.com/n8n-io/n8n/commit/7e3a2f6885eb11e5eab10a70985a9adc0900f5fa))
|
||||
* **core:** Clean up project mapping rules on provisioning strategy switch ([#28726](https://github.com/n8n-io/n8n/issues/28726)) ([c662862](https://github.com/n8n-io/n8n/commit/c662862d7f4c929176d7f3b7a53332b5359b8adb))
|
||||
* **core:** Declare credentialType on MCP workflow-builder autoAssignedCredentials schema ([#28353](https://github.com/n8n-io/n8n/issues/28353)) ([41424b3](https://github.com/n8n-io/n8n/commit/41424b3e9986da328f1a420e7d46c2d7a7ac3701))
|
||||
* **core:** Fix fixedCollection validation issues and empty setup cards on instance AI ([#28794](https://github.com/n8n-io/n8n/issues/28794)) ([8630845](https://github.com/n8n-io/n8n/commit/8630845cbd288b5b83201c62b3b6c4fef010546e))
|
||||
* **core:** Fix health endpoint cross-origin requests ([#28878](https://github.com/n8n-io/n8n/issues/28878)) ([b6eabb4](https://github.com/n8n-io/n8n/commit/b6eabb4ce5030df2e9d477e27fd5dbdf9d940a1c))
|
||||
* **core:** Fix publishing dynamic credential chathub flow ([#28827](https://github.com/n8n-io/n8n/issues/28827)) ([852d082](https://github.com/n8n-io/n8n/commit/852d0825d7ead81c5e276f482dd383c040730083))
|
||||
* **core:** Generate title once there's enough user context ([#28721](https://github.com/n8n-io/n8n/issues/28721)) ([2d624a5](https://github.com/n8n-io/n8n/commit/2d624a521e4c86c4d1ff5d7c1a151549db0707d7))
|
||||
* **core:** Guard event log parsing against unbounded memory growth ([#28594](https://github.com/n8n-io/n8n/issues/28594)) ([a817cbc](https://github.com/n8n-io/n8n/commit/a817cbc1c03fcd184cd11613819ac0415355adb5))
|
||||
* **core:** Harden event log parse against malformed input ([#29004](https://github.com/n8n-io/n8n/issues/29004)) ([b2b1370](https://github.com/n8n-io/n8n/commit/b2b1370e5d70a02771f315630a0477f2038a259d))
|
||||
* **core:** Hide pre-resolved setup requests from Instance AI wizard ([#28731](https://github.com/n8n-io/n8n/issues/28731)) ([9ea2ef1](https://github.com/n8n-io/n8n/commit/9ea2ef1840d5fb49369c32e9b28d109845d852bd))
|
||||
* **core:** Improve error messages for invalid node and trigger input ([#28053](https://github.com/n8n-io/n8n/issues/28053)) ([0f4c5b3](https://github.com/n8n-io/n8n/commit/0f4c5b396d21e2477099c8436259b143faf5ca90))
|
||||
* **core:** Link existing email users on LDAP sync instead of failing ([#28695](https://github.com/n8n-io/n8n/issues/28695)) ([767b3ab](https://github.com/n8n-io/n8n/commit/767b3abea1bfc1f44277a353d5f9267e4b43f37f))
|
||||
* **core:** Pass userId through webhook test execution path for redaction ([#28775](https://github.com/n8n-io/n8n/issues/28775)) ([19b2388](https://github.com/n8n-io/n8n/commit/19b2388acad90df462613366728632863b6e378a))
|
||||
* **core:** Persist execution metadata for failed executions in scaling mode ([#28774](https://github.com/n8n-io/n8n/issues/28774)) ([1256da9](https://github.com/n8n-io/n8n/commit/1256da91f0f61bd0fb799b47bd190168f82ada20))
|
||||
* **core:** Pin Alpine version in Docker builder stages for reproducible builds ([#29097](https://github.com/n8n-io/n8n/issues/29097)) ([147bd8c](https://github.com/n8n-io/n8n/commit/147bd8c84f05e801217f566a2e6c9f784aeead2f))
|
||||
* **core:** Pin sandbox workflow-sdk to host version ([#29048](https://github.com/n8n-io/n8n/issues/29048)) ([9ea8533](https://github.com/n8n-io/n8n/commit/9ea8533873928e7240e343189f872465bc68569f))
|
||||
* **core:** Preserve manual scopes for inherited generic OAuth2 credentials ([#27858](https://github.com/n8n-io/n8n/issues/27858)) ([5ee534c](https://github.com/n8n-io/n8n/commit/5ee534c3b8490d74ad2d9c0b52d30952da8f0d87))
|
||||
* **core:** Preserve user-selected credentials when AI recreates a node ([#28943](https://github.com/n8n-io/n8n/issues/28943)) ([5fa1f8f](https://github.com/n8n-io/n8n/commit/5fa1f8f7d90c8f0f7fbf0003a9e76ceb06bb6def))
|
||||
* **core:** Prevent Redis connection recovery from being missed ([#28256](https://github.com/n8n-io/n8n/issues/28256)) ([494a855](https://github.com/n8n-io/n8n/commit/494a855d34905750b057b98f3e4eb0c8bcdee317))
|
||||
* **core:** Prevent S3 socket pool exhaustion on partial stream reads ([#28313](https://github.com/n8n-io/n8n/issues/28313)) ([481fbdf](https://github.com/n8n-io/n8n/commit/481fbdf186dc8b166c164592c82ec9b7a5edb8b0))
|
||||
* **core:** Request helper function support setting agentOptions ([#28756](https://github.com/n8n-io/n8n/issues/28756)) ([4ce186a](https://github.com/n8n-io/n8n/commit/4ce186a14dd3b6f6f268afadaa48be8b07227eaa))
|
||||
* **core:** Scope live and test webhook handlers to their route family ([#28804](https://github.com/n8n-io/n8n/issues/28804)) ([bb25d4c](https://github.com/n8n-io/n8n/commit/bb25d4cc388a08fe90b7f504fc67edb4f667404d))
|
||||
* **core:** Shift existing rules on create to avoid order conflict ([#28722](https://github.com/n8n-io/n8n/issues/28722)) ([c55b95f](https://github.com/n8n-io/n8n/commit/c55b95ff0626fe9b73d0e5d45230531777fe7015))
|
||||
* **core:** Support mixed bracket/dot notation in external secrets expressions ([#28711](https://github.com/n8n-io/n8n/issues/28711)) ([a757ddd](https://github.com/n8n-io/n8n/commit/a757ddd4a86f8700b7ee290d659b320018d7417a))
|
||||
* **core:** Surface credential and resource choice when more than one match exists ([#29087](https://github.com/n8n-io/n8n/issues/29087)) ([0d16dd5](https://github.com/n8n-io/n8n/commit/0d16dd5dfb6511db0292b67d450c34efcde55efb))
|
||||
* **core:** Surface pagination state in instance AI credentials list ([#29098](https://github.com/n8n-io/n8n/issues/29098)) ([9345d13](https://github.com/n8n-io/n8n/commit/9345d132fa240528b3ea0f078e55264092308776))
|
||||
* **core:** Upgrade task-runner-launcher to v1.4.5 ([#29155](https://github.com/n8n-io/n8n/issues/29155)) ([b37044e](https://github.com/n8n-io/n8n/commit/b37044e12441e89a80052dc14f22a78792eb9948))
|
||||
* **core:** Use upsert for MCP OAuth consent to allow re-authorization ([#28703](https://github.com/n8n-io/n8n/issues/28703)) ([26ecadc](https://github.com/n8n-io/n8n/commit/26ecadcf94bf8e49cd168f22ed72b5e6ed99d8dd))
|
||||
* **core:** Workflow import cli doesn't deregister crons for deactivated workflows ([#29129](https://github.com/n8n-io/n8n/issues/29129)) ([553976d](https://github.com/n8n-io/n8n/commit/553976d065ff3e82c41cb0e89e9ccfbe947a80c7))
|
||||
* Correct typo in proxy error message: not value -> not valid ([#28318](https://github.com/n8n-io/n8n/issues/28318)) ([b143830](https://github.com/n8n-io/n8n/commit/b143830f24e4079d829735b222bdcd4f15e2319a))
|
||||
* Correctly rename all temporal binary files after execution ([#28960](https://github.com/n8n-io/n8n/issues/28960)) ([71b2b88](https://github.com/n8n-io/n8n/commit/71b2b88eb8b6420dc3fa8e73234e0f82f8d7822a))
|
||||
* Disable "Get 100 free OpenAI API credits" if aiGateway enabled in the license ([#29050](https://github.com/n8n-io/n8n/issues/29050)) ([67bd8de](https://github.com/n8n-io/n8n/commit/67bd8de90fe127e93874b794c1525214ce89c704))
|
||||
* Disable until we encrypt ([#28875](https://github.com/n8n-io/n8n/issues/28875)) ([1b02a0d](https://github.com/n8n-io/n8n/commit/1b02a0dbf49256ef740de58f23d87dad114d4d6b))
|
||||
* **Discord Node:** Correctly handle API rate limits ([#29051](https://github.com/n8n-io/n8n/issues/29051)) ([089d6bd](https://github.com/n8n-io/n8n/commit/089d6bda0e32ee6509ae9ee6840af67356407bb1))
|
||||
* **Discord Node:** Fix file name and mimetype for file upload ([#28807](https://github.com/n8n-io/n8n/issues/28807)) ([d3ef7e3](https://github.com/n8n-io/n8n/commit/d3ef7e3e0fb885adfe83cef8ad4f803592601a29))
|
||||
* E2E test for token exchange ([#28895](https://github.com/n8n-io/n8n/issues/28895)) ([d49049d](https://github.com/n8n-io/n8n/commit/d49049dd3f868be2710f6ba433bd3d610b4b93de))
|
||||
* **EditImage Node:** Fix orientation when modifying images ([#28970](https://github.com/n8n-io/n8n/issues/28970)) ([b649eea](https://github.com/n8n-io/n8n/commit/b649eea01d411335852b2fe14ccd3cee489d382d))
|
||||
* **editor:** Align Model and Sandbox visibility with edit permissions ([#29076](https://github.com/n8n-io/n8n/issues/29076)) ([e10509f](https://github.com/n8n-io/n8n/commit/e10509f11420d9745473e9110bdea9ea509f7009))
|
||||
* **editor:** Allow `name` parameters to be defined by AI ([#28763](https://github.com/n8n-io/n8n/issues/28763)) ([76358a6](https://github.com/n8n-io/n8n/commit/76358a60bee8a1f1c1a151521eaa1db65adf4547))
|
||||
* **editor:** Allow users to logout of dynamic creds in chathub ([#29117](https://github.com/n8n-io/n8n/issues/29117)) ([b1ddbf2](https://github.com/n8n-io/n8n/commit/b1ddbf26b05b85b4f4445955d8abc95fd00a2711))
|
||||
* **editor:** Checklist not showing workflow settings configuration ([#28034](https://github.com/n8n-io/n8n/issues/28034)) ([3c54cda](https://github.com/n8n-io/n8n/commit/3c54cda57577e617413e48190cec88777b305469))
|
||||
* **editor:** Constrain InstanceAiView stacking context below sidebar ([#29324](https://github.com/n8n-io/n8n/issues/29324)) ([b71a31d](https://github.com/n8n-io/n8n/commit/b71a31d2a9f4b44fc4736a2e6a768a99e6da2b5a))
|
||||
* **editor:** Copy to editor not using pinned data from the execution ([#28905](https://github.com/n8n-io/n8n/issues/28905)) ([20d9215](https://github.com/n8n-io/n8n/commit/20d9215736b707a728bc7cb1a234a585ef985bf4))
|
||||
* **editor:** Disable WF version menu only when all actions are unavailable ([#29266](https://github.com/n8n-io/n8n/issues/29266)) ([23170e4](https://github.com/n8n-io/n8n/commit/23170e436ad00e96ad64f09000317bd4720bb0a4))
|
||||
* **editor:** Fix column collapse button not visible in table view ([#28899](https://github.com/n8n-io/n8n/issues/28899)) ([0eb30c6](https://github.com/n8n-io/n8n/commit/0eb30c6ca741dfe73d5254002c3467a65c36e50f))
|
||||
* **editor:** HTTP request node showing warning about credentials not set when they are set ([#28270](https://github.com/n8n-io/n8n/issues/28270)) ([4869e0a](https://github.com/n8n-io/n8n/commit/4869e0a4638d94117b402f2ce177be0084744679))
|
||||
* **editor:** Move tooltip for required RMC fields to the right ([#28803](https://github.com/n8n-io/n8n/issues/28803)) ([713c498](https://github.com/n8n-io/n8n/commit/713c4981b7b88ceb2f8512c20ee5c18914a16ce1))
|
||||
* **editor:** Populate chat history in executions view ([#29229](https://github.com/n8n-io/n8n/issues/29229)) ([cf5cdbf](https://github.com/n8n-io/n8n/commit/cf5cdbfbcf3245a5120b6f71fb8cc5de35091ea4))
|
||||
* **editor:** Prevent executions table flicker on empty auto-refresh ([#28805](https://github.com/n8n-io/n8n/issues/28805)) ([2bd8b47](https://github.com/n8n-io/n8n/commit/2bd8b472072aa853b1a87857890f6cd8343927c8))
|
||||
* **editor:** Refresh wallet balance when usage refresh is clicked ([#29067](https://github.com/n8n-io/n8n/issues/29067)) ([7fd0b37](https://github.com/n8n-io/n8n/commit/7fd0b3761da5913d78c00907a143246d19f8c2b8))
|
||||
* **editor:** Restore missing node icons for HTTP-auth credentials ([#28814](https://github.com/n8n-io/n8n/issues/28814)) ([345ce7f](https://github.com/n8n-io/n8n/commit/345ce7f0dc8850151005291171388b4afc63a358))
|
||||
* **editor:** Save button never shows saved state after saving credentials ([#28486](https://github.com/n8n-io/n8n/issues/28486)) ([4352749](https://github.com/n8n-io/n8n/commit/4352749ec9e5cfcf19258c555fd80db1a9d4df1a))
|
||||
* **editor:** Show error toasts in Instance AI executable canvas ([#29328](https://github.com/n8n-io/n8n/issues/29328)) ([dc33223](https://github.com/n8n-io/n8n/commit/dc33223d3becdb30b59ff66d93eaea6dcec314d4))
|
||||
* **editor:** Show Manage community-node link to admins, not just owners ([#29060](https://github.com/n8n-io/n8n/issues/29060)) ([ef944b4](https://github.com/n8n-io/n8n/commit/ef944b4a52a19f82b3ab5db4a581967510d5b2d0))
|
||||
* **editor:** Show saved credentials when node has mismatched credentials object ([#28693](https://github.com/n8n-io/n8n/issues/28693)) ([d8f742b](https://github.com/n8n-io/n8n/commit/d8f742baa686f85b51042ce7e50f962e67f71574))
|
||||
* **editor:** Surface configured minimum password length in UI ([#28194](https://github.com/n8n-io/n8n/issues/28194)) ([330211c](https://github.com/n8n-io/n8n/commit/330211c5ae914dfe013d6996a706b4ec79f81536))
|
||||
* **Embeddings Google Gemini Node:** Update wrong default embeddings model ([#26977](https://github.com/n8n-io/n8n/issues/26977)) ([a1d733b](https://github.com/n8n-io/n8n/commit/a1d733bb9a0fb722c9fbd439a22b003c2cd5a43e))
|
||||
* Fix 11 security issues in @xmldom/xmldom, defu, @langchain/community and 5 more ([#29126](https://github.com/n8n-io/n8n/issues/29126)) ([35449aa](https://github.com/n8n-io/n8n/commit/35449aaa7b1ca016c919e555f9ba86ee0ea1db8f))
|
||||
* Fix 15 critical issues in protobufjs, defu, @langchain/community and 6 more ([#28661](https://github.com/n8n-io/n8n/issues/28661)) ([6650068](https://github.com/n8n-io/n8n/commit/6650068f00ec220db5f779fd02f686413a10f264))
|
||||
* Fixes AI Assistant opt-in modal terms link ([#28779](https://github.com/n8n-io/n8n/issues/28779)) ([10f619f](https://github.com/n8n-io/n8n/commit/10f619f6d610c180ca19743852167863ae9a7a6e))
|
||||
* **GitHub Node:** Return pagination token as string in resource locators ([#29099](https://github.com/n8n-io/n8n/issues/29099)) ([5dda3b2](https://github.com/n8n-io/n8n/commit/5dda3b2142b752722efb6ef4691ca74b618ae33d))
|
||||
* **Gmail Trigger Node:** Add maxResults limit to prevent OOM on large inboxes ([#28470](https://github.com/n8n-io/n8n/issues/28470)) ([b9c4618](https://github.com/n8n-io/n8n/commit/b9c4618270c04cb6b3f491310999713439260a6b))
|
||||
* **Gmail Trigger Node:** Exclude scheduled emails from trigger query ([#29244](https://github.com/n8n-io/n8n/issues/29244)) ([58d7983](https://github.com/n8n-io/n8n/commit/58d79832bc3933557a89eae0e2c62da67c10cd64))
|
||||
* **Google Gemini Node:** Filter out undefined parts when processing API responses ([#28567](https://github.com/n8n-io/n8n/issues/28567)) ([0aabee0](https://github.com/n8n-io/n8n/commit/0aabee0a99f3e7eaf7a0232f8b76732889730043))
|
||||
* **Google Gemini Node:** Show Number of Images option only for models that supports it ([#29153](https://github.com/n8n-io/n8n/issues/29153)) ([b0ec5cd](https://github.com/n8n-io/n8n/commit/b0ec5cdb69b91bc563c219009aa36864c6746820))
|
||||
* **Jenkins Node:** Properly load job parameters ([#28631](https://github.com/n8n-io/n8n/issues/28631)) ([f2aa016](https://github.com/n8n-io/n8n/commit/f2aa0160af7b51c99a17125e64f3cfd636cf59fb))
|
||||
* **MCP Server Trigger Node:** Only return error name and message in tool error responses ([#28791](https://github.com/n8n-io/n8n/issues/28791)) ([6db02fe](https://github.com/n8n-io/n8n/commit/6db02fe9283211e5aac89b757aadf3e0a8a0bd33))
|
||||
* **Microsoft Outlook Trigger Node:** Show nested subfolders in folder dropdowns ([#28759](https://github.com/n8n-io/n8n/issues/28759)) ([3409a67](https://github.com/n8n-io/n8n/commit/3409a67dfec527a55b79282a784ebf7d955f96da))
|
||||
* **MongoDB Chat Memory Node:** Add connection pool limit ([#28042](https://github.com/n8n-io/n8n/issues/28042)) ([a88ee76](https://github.com/n8n-io/n8n/commit/a88ee76553ed5e1798f2689ff79602c59e2c36bb))
|
||||
* **Moonshot Kimi Node:** Update default model to kimi-k2.6 ([#28859](https://github.com/n8n-io/n8n/issues/28859)) ([1c52b0a](https://github.com/n8n-io/n8n/commit/1c52b0a448f9afd6bdd9eca91b0ba83515afc200))
|
||||
* **MySql Node:** Fix table name filtering ([#28911](https://github.com/n8n-io/n8n/issues/28911)) ([ccbd914](https://github.com/n8n-io/n8n/commit/ccbd91459425713d870bfe03919defd98b366215))
|
||||
* Preserve `structuredContent` in MCP nodes to prevent AI Agent reasoning loops ([#27297](https://github.com/n8n-io/n8n/issues/27297)) ([048e01e](https://github.com/n8n-io/n8n/commit/048e01e0606482f33948db49578d8b2f926d4f92))
|
||||
* **RabbitMQ Trigger Node:** Redeliver messages on failed executions ([#28712](https://github.com/n8n-io/n8n/issues/28712)) ([7be9f65](https://github.com/n8n-io/n8n/commit/7be9f653998b34f51e0a3bda91c7a5dcbe0f3750))
|
||||
* **Schedule Node:** Avoid using 0 day of month ([#28432](https://github.com/n8n-io/n8n/issues/28432)) ([a9e5f0e](https://github.com/n8n-io/n8n/commit/a9e5f0ea482603582298e60af7cd466b1ee3e8bb))
|
||||
* Update hono, protobufjs, yaml, and other dependencies ([#28877](https://github.com/n8n-io/n8n/issues/28877)) ([c4ff018](https://github.com/n8n-io/n8n/commit/c4ff0188dbca498356a4bca7ac941bc82a09affc))
|
||||
* Use different runtime for each agent call, refactor state management ([#27915](https://github.com/n8n-io/n8n/issues/27915)) ([68c289f](https://github.com/n8n-io/n8n/commit/68c289f76fa0be253355a9532cb96ac924f2f1fa))
|
||||
* **WooCommerce Trigger Node:** Guard HMAC verification against missing secret ([#28964](https://github.com/n8n-io/n8n/issues/28964)) ([5cef662](https://github.com/n8n-io/n8n/commit/5cef66269e5101d209fb49b6ce1ee78af24426fe))
|
||||
* **Xero Node:** Update OAuth Scopes to meet new Xero API requirements ([#27563](https://github.com/n8n-io/n8n/issues/27563)) ([7f54ff4](https://github.com/n8n-io/n8n/commit/7f54ff4acf73940afc6a59df851177d2c7b1cafe))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add warning when publishing workflow, if some nodes in it are using ai gateway credentials ([#29089](https://github.com/n8n-io/n8n/issues/29089)) ([d2d7dfc](https://github.com/n8n-io/n8n/commit/d2d7dfc276c92015d1bae0ed352bb4661e7697c4))
|
||||
* **Alibaba Cloud Node:** Switch to dynamic model list fetched from API ([#28839](https://github.com/n8n-io/n8n/issues/28839)) ([3a165d4](https://github.com/n8n-io/n8n/commit/3a165d4843e254267f7177d0c1d88758e044c546))
|
||||
* **API:** Add encryption key management endpoints ([#28897](https://github.com/n8n-io/n8n/issues/28897)) ([5af4e1c](https://github.com/n8n-io/n8n/commit/5af4e1cc191ad956b5af7f081e2bcee08a2d8ebf))
|
||||
* **AwsS3 Node:** Add option to return common prefixes in bucket search ([#28571](https://github.com/n8n-io/n8n/issues/28571)) ([13dbcf9](https://github.com/n8n-io/n8n/commit/13dbcf9bbb8db9070415a39225bddc5c57b8a4b7))
|
||||
* **core:** Accept token-exchanged scoped JWTs on the instance MCP server ([#28909](https://github.com/n8n-io/n8n/issues/28909)) ([1d325a2](https://github.com/n8n-io/n8n/commit/1d325a20ca8a67eb2539df30de278bcce11c3de6))
|
||||
* **core:** Add --projectId filter to export:workflow and export:credentials commands ([#29329](https://github.com/n8n-io/n8n/issues/29329)) ([2d721d2](https://github.com/n8n-io/n8n/commit/2d721d29f3c4d2c33cc8c661e069f75f6a54b96c))
|
||||
* **core:** Add AES-256-GCM cipher support ([#28916](https://github.com/n8n-io/n8n/issues/28916)) ([0c02111](https://github.com/n8n-io/n8n/commit/0c021116bc61d4aa9ccf2fe0f05cdd4668888e92))
|
||||
* **core:** Add audit logs for webhook waiting and resuming ([#28204](https://github.com/n8n-io/n8n/issues/28204)) ([bb6a5e4](https://github.com/n8n-io/n8n/commit/bb6a5e47b08e17fc5f4e704e2c6dead6476ea60c))
|
||||
* **core:** Add delete folder endpoint to public API ([#28984](https://github.com/n8n-io/n8n/issues/28984)) ([f931b17](https://github.com/n8n-io/n8n/commit/f931b17a93a4e1e247f51d04ac84b2a6382b0bc8))
|
||||
* **core:** Add encryptWithKey and decryptWithKey to Cipher service ([#28608](https://github.com/n8n-io/n8n/issues/28608)) ([c40599b](https://github.com/n8n-io/n8n/commit/c40599b82ef7935242f21691ce5f2c9e8986f248))
|
||||
* **core:** Add instance setting to disable public chat in Chat Trigger ([#27565](https://github.com/n8n-io/n8n/issues/27565)) ([af3841f](https://github.com/n8n-io/n8n/commit/af3841f629a33d018677e518e3d9a3d7cd491c5f))
|
||||
* **core:** Add observability for the VM expression engine ([#28715](https://github.com/n8n-io/n8n/issues/28715)) ([f3efcf8](https://github.com/n8n-io/n8n/commit/f3efcf8a5478aa7f76db85802b4d3e6fafc5be74))
|
||||
* **core:** Add opt-in RFC-style outbound User-Agent via env flag ([#28771](https://github.com/n8n-io/n8n/issues/28771)) ([67af2e1](https://github.com/n8n-io/n8n/commit/67af2e177d7b398e910a0b25e22c7b993e537af0))
|
||||
* **core:** Add PATCH endpoint for data table column management ([#29090](https://github.com/n8n-io/n8n/issues/29090)) ([b437371](https://github.com/n8n-io/n8n/commit/b4373715f3e4b6e15ec228a8e6983ca628393d8f))
|
||||
* **core:** Add public API endpoints for data table column management… ([#29073](https://github.com/n8n-io/n8n/issues/29073)) ([2b0add1](https://github.com/n8n-io/n8n/commit/2b0add185f509209a27a6c7981d844545d2aa81f))
|
||||
* **core:** Add public API endpoints for folder management ([#28760](https://github.com/n8n-io/n8n/issues/28760)) ([ff9d7d6](https://github.com/n8n-io/n8n/commit/ff9d7d67561b4d668c0eeefbd9e3eb13de1610e5))
|
||||
* **core:** Add public API endpoints for folder management (folderId) ([#28790](https://github.com/n8n-io/n8n/issues/28790)) ([fda3d5c](https://github.com/n8n-io/n8n/commit/fda3d5cf8d8d166ef6dd1511d905f4963f15a85d))
|
||||
* **core:** Bootstrap encryption key set from environment ([#28716](https://github.com/n8n-io/n8n/issues/28716)) ([c012b52](https://github.com/n8n-io/n8n/commit/c012b52ac23a16fa70e103656cf571f6c30e0219))
|
||||
* **core:** Broadcast workflow updates from MCP tools to open editors ([#28709](https://github.com/n8n-io/n8n/issues/28709)) ([b1ca129](https://github.com/n8n-io/n8n/commit/b1ca12949676dbcb5092c09f3a3db7270c9f9eb7))
|
||||
* **core:** Centralise user listing permissions for internal and publ… ([#28989](https://github.com/n8n-io/n8n/issues/28989)) ([9e694f5](https://github.com/n8n-io/n8n/commit/9e694f5f4191a756b1dc4c46f3f417732e115e27))
|
||||
* **core:** Drop BetaTag from sidebar, use 'Preview' instead for new features ([#29347](https://github.com/n8n-io/n8n/issues/29347)) ([a7d7352](https://github.com/n8n-io/n8n/commit/a7d7352c9be2903476cf7ef1ae5fdc65df5eb1be))
|
||||
* **core:** Guard Instance AI prompts against asking for plaintext secrets ([#28876](https://github.com/n8n-io/n8n/issues/28876)) ([d58e67e](https://github.com/n8n-io/n8n/commit/d58e67e76eda48aacb1e981cd50b99c5f56367cf))
|
||||
* **core:** Make SSO connection settings configurable via env vars ([#28714](https://github.com/n8n-io/n8n/issues/28714)) ([3bd7a28](https://github.com/n8n-io/n8n/commit/3bd7a2847c0fdc3c2709ad21e3a91a5116ac7949))
|
||||
* **core:** OTEL changes to support context propagation ([#28720](https://github.com/n8n-io/n8n/issues/28720)) ([9599fb9](https://github.com/n8n-io/n8n/commit/9599fb9d5416e1c4aae43679484d717a789c165e))
|
||||
* **core:** Remove expression role mapping feature flag ([#29361](https://github.com/n8n-io/n8n/issues/29361)) ([a70a314](https://github.com/n8n-io/n8n/commit/a70a31411d2f42351ea61212721aa9064e0e6391))
|
||||
* **core:** Scale expression isolate pool to 0 after inactivity ([#28472](https://github.com/n8n-io/n8n/issues/28472)) ([10dbf32](https://github.com/n8n-io/n8n/commit/10dbf32596b63ceb489ff14787a95cca97b63d62))
|
||||
* **core:** Stamp x-n8n-feature on proxied LLM calls for cost attribution ([#28987](https://github.com/n8n-io/n8n/issues/28987)) ([e129518](https://github.com/n8n-io/n8n/commit/e1295187f187d8bebf9338a5e07e4a34c8b92c68))
|
||||
* **core:** Wire Cipher to encryption key proxy for key rotation support ([#29013](https://github.com/n8n-io/n8n/issues/29013)) ([641d492](https://github.com/n8n-io/n8n/commit/641d492d56781d05740af652f09a64538b6a8259))
|
||||
* **editor:** Add Delete permanently link to workflow archive toast ([#29157](https://github.com/n8n-io/n8n/issues/29157)) ([98ec56a](https://github.com/n8n-io/n8n/commit/98ec56ad77dfd4bbba1ecbbc8a06554a80448ea9))
|
||||
* **editor:** Move computer use toggle to chat sidebar ([#28974](https://github.com/n8n-io/n8n/issues/28974)) ([47bb91e](https://github.com/n8n-io/n8n/commit/47bb91e9b4ade6638e8d54fb79cae54b90b0a1a2))
|
||||
* **editor:** Track favorite toggle events ([#28810](https://github.com/n8n-io/n8n/issues/28810)) ([86a6d1b](https://github.com/n8n-io/n8n/commit/86a6d1b1fafc8deb8a7611b4bc0fd2d807ed090b))
|
||||
* **editor:** Warn when data table cell value exceeds safe integer range ([#28904](https://github.com/n8n-io/n8n/issues/28904)) ([e62e44f](https://github.com/n8n-io/n8n/commit/e62e44f3f8c42547a1c8726ed8d9a50ca5a069d6))
|
||||
* Enable log streamin settings via env vars ([#29005](https://github.com/n8n-io/n8n/issues/29005)) ([ff6d65e](https://github.com/n8n-io/n8n/commit/ff6d65e22aa758ee78f4f27ac0c781e3754bd917))
|
||||
* **Google Drive Node:** Add Markdown export option for Google Docs download ([#27006](https://github.com/n8n-io/n8n/issues/27006)) ([80cb82e](https://github.com/n8n-io/n8n/commit/80cb82e9c52e6170b75dc0841ee9b143058bdd5e))
|
||||
* **Google Gemini Node:** Gemini default models update ([#28853](https://github.com/n8n-io/n8n/issues/28853)) ([46739a5](https://github.com/n8n-io/n8n/commit/46739a5bc464af81ebf5145d2ae37fd3bfbb5fa8))
|
||||
* In the n8n Connect table on row click open execution related to that usage row ([#29075](https://github.com/n8n-io/n8n/issues/29075)) ([f658278](https://github.com/n8n-io/n8n/commit/f6582783ea80cc99b58265f11563f03088f7f217))
|
||||
* **MiniMax Node:** Add standalone MiniMax vendor node ([#28748](https://github.com/n8n-io/n8n/issues/28748)) ([0295369](https://github.com/n8n-io/n8n/commit/02953693a61ba0856fe53c1e29a6d0b8829404a0))
|
||||
* **OpenAI Node:** Use dynamic model selection for image edit and update analyze model filter ([#29223](https://github.com/n8n-io/n8n/issues/29223)) ([5f2c32a](https://github.com/n8n-io/n8n/commit/5f2c32ad6edea90df0feca2d1d87c6ed6860ef36))
|
||||
* **Postgres Node:** Sort PostgreSQL schemas and tables alphabetically when fetching ([#28036](https://github.com/n8n-io/n8n/issues/28036)) ([11ebf61](https://github.com/n8n-io/n8n/commit/11ebf6147111c87a2d47a5dcb87d2a0873ef60fc))
|
||||
* **Salesforce Node:** Add middle name, suffix, and identity fields to contacts ([#23905](https://github.com/n8n-io/n8n/issues/23905)) ([c7a0f3e](https://github.com/n8n-io/n8n/commit/c7a0f3eb3956da1d74f6becaa927ed3dfe4c6e21))
|
||||
* **Slack Node:** Add emoji reaction filter to Slack Trigger ([#28628](https://github.com/n8n-io/n8n/issues/28628)) ([90843cf](https://github.com/n8n-io/n8n/commit/90843cf4bae9d27087801a56f30fce67dc89e420))
|
||||
|
||||
|
||||
# [2.18.0](https://github.com/n8n-io/n8n/compare/n8n@2.17.0...n8n@2.18.0) (2026-04-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **ai-builder:** Increase orchestrator max steps from default 5 to 60 ([#28429](https://github.com/n8n-io/n8n/issues/28429)) ([3c850f2](https://github.com/n8n-io/n8n/commit/3c850f2711d53ded62a3540c67b9ec02143cbb3f))
|
||||
* **ai-builder:** Scope artifacts panel to resources produced in-thread ([#28678](https://github.com/n8n-io/n8n/issues/28678)) ([7b3696f](https://github.com/n8n-io/n8n/commit/7b3696f3f7d95ab3cbaeb8ca58fdc74264a83b52))
|
||||
* **ai-builder:** Use placeholders for user-provided values instead of hardcoding fake addresses ([#28407](https://github.com/n8n-io/n8n/issues/28407)) ([39c6217](https://github.com/n8n-io/n8n/commit/39c62171092618149fa67ccb9a384a5a3aadd4e8))
|
||||
* **Alibaba Cloud Chat Model Node:** Add credential-level url field for AI gateway compatibility ([#28697](https://github.com/n8n-io/n8n/issues/28697)) ([dd6c28c](https://github.com/n8n-io/n8n/commit/dd6c28c6d16274354b83d3cc6a731f2f7a859a14))
|
||||
* **ClickUp Node:** Unclear error message when using OAuth credentials ([#28584](https://github.com/n8n-io/n8n/issues/28584)) ([19aadf1](https://github.com/n8n-io/n8n/commit/19aadf19f753d64cc2cd80af3c5b3dd957a4ede7))
|
||||
* **core:** Add required field validation to MCP OAuth client registration ([#28490](https://github.com/n8n-io/n8n/issues/28490)) ([8716316](https://github.com/n8n-io/n8n/commit/87163163e67001f69a2a2d7b4a650e0511614d62))
|
||||
* **core:** Cascade-cancel dependent planned tasks when a parent task fails ([#28656](https://github.com/n8n-io/n8n/issues/28656)) ([35f9bed](https://github.com/n8n-io/n8n/commit/35f9bed4de39350717192d9f272ad044ad50b323))
|
||||
* **core:** Enforce credential access checks in dynamic node parameter requests ([#28446](https://github.com/n8n-io/n8n/issues/28446)) ([ac41112](https://github.com/n8n-io/n8n/commit/ac411127314921aaf82b7b97d76eeaa2703b708c))
|
||||
* **core:** Ensure single zod instance across workspace packages ([#28604](https://github.com/n8n-io/n8n/issues/28604)) ([107c48f](https://github.com/n8n-io/n8n/commit/107c48f65c10d26f8f01d1bee5d2eb77b9d26084))
|
||||
* **core:** Filter stale credentials from setup wizard requests ([#28478](https://github.com/n8n-io/n8n/issues/28478)) ([657bdf1](https://github.com/n8n-io/n8n/commit/657bdf136fd0fc01cee3629baf65e130ee80840a))
|
||||
* **core:** Fix public API package update process ([#28475](https://github.com/n8n-io/n8n/issues/28475)) ([34430ae](https://github.com/n8n-io/n8n/commit/34430aedb15fa5305be475582e04f08967415e38))
|
||||
* **core:** Fix workflow-sdk validation for plain workflow objects ([#28416](https://github.com/n8n-io/n8n/issues/28416)) ([62dc073](https://github.com/n8n-io/n8n/commit/62dc073b3d954dc885359962c02ae8aa84d17c43))
|
||||
* **core:** Guard against undefined config properties in credential overwrites ([#28573](https://github.com/n8n-io/n8n/issues/28573)) ([77d27bc](https://github.com/n8n-io/n8n/commit/77d27bc826e4e91c2c589a62cbb6b997cacccd16))
|
||||
* **core:** Handle git fetch failure during source control startup ([#28422](https://github.com/n8n-io/n8n/issues/28422)) ([fa3299d](https://github.com/n8n-io/n8n/commit/fa3299d0425dfa2eaeaca6732dc46e0181e6fd68))
|
||||
* **core:** Handle invalid percent sequences and equals signs in HTTP response headers ([#27691](https://github.com/n8n-io/n8n/issues/27691)) ([ca71d89](https://github.com/n8n-io/n8n/commit/ca71d89d885d01f8663e29a2a5b1f06c713aede8))
|
||||
* **core:** Implement data tables name collision detection on pull ([#26416](https://github.com/n8n-io/n8n/issues/26416)) ([e5aaeb5](https://github.com/n8n-io/n8n/commit/e5aaeb53a93c63a04978e2a6eb7aa7255fcf510b))
|
||||
* **core:** Prevent nodes tool crash on flattened required fields ([#28670](https://github.com/n8n-io/n8n/issues/28670)) ([3e72430](https://github.com/n8n-io/n8n/commit/3e724303c537739319e91f8bcaf7070fe105ffc7))
|
||||
* **core:** Resolve additional keys lazily in VM expression engine ([#28430](https://github.com/n8n-io/n8n/issues/28430)) ([98b833a](https://github.com/n8n-io/n8n/commit/98b833a07d6d0f705633d7cb48298ee953688bd1))
|
||||
* **core:** Skip disabled Azure Key Vault secrets and handle partial fetch failures ([#28325](https://github.com/n8n-io/n8n/issues/28325)) ([6217d08](https://github.com/n8n-io/n8n/commit/6217d08ce9b53d6fd5277fa0708ed13d36e0e934))
|
||||
* **core:** Skip npm outdated check for verified-only community packages ([#28335](https://github.com/n8n-io/n8n/issues/28335)) ([2959b4d](https://github.com/n8n-io/n8n/commit/2959b4dc2a6cfd3733cc83bd6442dddd4cff08d2))
|
||||
* Disable axios built-in proxy for OAuth2 token requests ([#28513](https://github.com/n8n-io/n8n/issues/28513)) ([56f36a6](https://github.com/n8n-io/n8n/commit/56f36a6d1961d95780fb8258e8876d7d512503c2))
|
||||
* **editor:** Advance wizard step on Continue instead of applying setup ([#28698](https://github.com/n8n-io/n8n/issues/28698)) ([3b15e47](https://github.com/n8n-io/n8n/commit/3b15e470b54b13e9fe68e81c81a757c06b264783))
|
||||
* **editor:** Center sub-node icons and refresh triggers panel icons ([#28515](https://github.com/n8n-io/n8n/issues/28515)) ([6739856](https://github.com/n8n-io/n8n/commit/6739856aa32689b43d143ae4909e1f3d85dc4106))
|
||||
* **editor:** Display placeholder sentinels as hint text in setup wizard ([#28482](https://github.com/n8n-io/n8n/issues/28482)) ([bb7d137](https://github.com/n8n-io/n8n/commit/bb7d137cf735bcdf65bbcf8ff58fa911d83121f5))
|
||||
* **editor:** Gate Instance AI input while setup wizard is open ([#28685](https://github.com/n8n-io/n8n/issues/28685)) ([db83a95](https://github.com/n8n-io/n8n/commit/db83a95522957c10a3466f0b57944c8b8827347a))
|
||||
* **editor:** Hide setup parameter issue icons until user interacts with input ([#28010](https://github.com/n8n-io/n8n/issues/28010)) ([00b0558](https://github.com/n8n-io/n8n/commit/00b0558c2b1ed6bc4d47a86cb1bfca8eb55a47bc))
|
||||
* **editor:** Improve disabled Google sign-in button styling and tooltip alignment ([#28536](https://github.com/n8n-io/n8n/issues/28536)) ([e848230](https://github.com/n8n-io/n8n/commit/e8482309478eed05793dcaa4d82185936439663f))
|
||||
* **editor:** Improve setup wizard placeholder detection and card completion scoping ([#28474](https://github.com/n8n-io/n8n/issues/28474)) ([d172113](https://github.com/n8n-io/n8n/commit/d17211342e4ee8c8ec89a9c918017884e2de0763))
|
||||
* **editor:** Only show role assignment warning modal when value actually changed ([#28387](https://github.com/n8n-io/n8n/issues/28387)) ([9c97931](https://github.com/n8n-io/n8n/commit/9c97931ca06d407bec1c6a8bab510d206afba394))
|
||||
* **editor:** Prevent setup wizard disappearing on requestId-driven remount ([#28473](https://github.com/n8n-io/n8n/issues/28473)) ([04d57c5](https://github.com/n8n-io/n8n/commit/04d57c5fd62a5b9a2e086a3f540b7f50a932b62d))
|
||||
* **editor:** Re-initialize SSO store after login to populate OIDC redirect URL ([#28386](https://github.com/n8n-io/n8n/issues/28386)) ([21317b8](https://github.com/n8n-io/n8n/commit/21317b8945dec9169e36b7e5fdf867713018661d))
|
||||
* **editor:** Refine resource dependency badge ([#28087](https://github.com/n8n-io/n8n/issues/28087)) ([f216fda](https://github.com/n8n-io/n8n/commit/f216fda511062a40199b986351693677ebb2919e))
|
||||
* **editor:** Reset OIDC form dirty state after saving IdP settings ([#28388](https://github.com/n8n-io/n8n/issues/28388)) ([1042350](https://github.com/n8n-io/n8n/commit/1042350f4e0f6ed44b51a1d707de665f71437faa))
|
||||
* **editor:** Reset remote values on credentials change ([#26282](https://github.com/n8n-io/n8n/issues/26282)) ([5e11197](https://github.com/n8n-io/n8n/commit/5e111975d4086c060ac3d29d07da7c00ea2103a1))
|
||||
* **editor:** Resolve nodes stuck on loading after execution in instance-ai preview ([#28450](https://github.com/n8n-io/n8n/issues/28450)) ([c97c3b4](https://github.com/n8n-io/n8n/commit/c97c3b4d12e166091be9ea1de969a17d64c36ec2))
|
||||
* **editor:** Restore WASM file paths for cURL import in HTTP Request node ([#28610](https://github.com/n8n-io/n8n/issues/28610)) ([51bc71e](https://github.com/n8n-io/n8n/commit/51bc71e897e2baaf729963bf0f373a73505aee43))
|
||||
* **editor:** Show auth type selector in Instance AI workflow setup ([#28707](https://github.com/n8n-io/n8n/issues/28707)) ([1b13d32](https://github.com/n8n-io/n8n/commit/1b13d325f12a5a27d139c75164114ee41583a902))
|
||||
* **editor:** Show relevant node in workflow activation errors ([#26691](https://github.com/n8n-io/n8n/issues/26691)) ([c9cab11](https://github.com/n8n-io/n8n/commit/c9cab112f99a5da2742012773450bf7721484c28))
|
||||
* **Google Cloud Firestore Node:** Fix empty array serialization in jsonToDocument ([#28213](https://github.com/n8n-io/n8n/issues/28213)) ([7094395](https://github.com/n8n-io/n8n/commit/7094395cef8e71f767df6fa5e242cf2fa42366ed))
|
||||
* **Google Drive Node:** Continue on error support for download file operation ([#28276](https://github.com/n8n-io/n8n/issues/28276)) ([30128c9](https://github.com/n8n-io/n8n/commit/30128c9254be2214e746e0158296c1f1bd8ab4d8))
|
||||
* **Google Gemini Node:** Determine the file extention from MIME type for image and video operations ([#28616](https://github.com/n8n-io/n8n/issues/28616)) ([73659cb](https://github.com/n8n-io/n8n/commit/73659cb3e7eccd48a739829be0a4d7a6557ce4a1))
|
||||
* **GraphQL Node:** Improve error response handling ([#28209](https://github.com/n8n-io/n8n/issues/28209)) ([357fb72](https://github.com/n8n-io/n8n/commit/357fb7210ab201e13e2d3256a7886cf382656f22))
|
||||
* **HubSpot Node:** Rename HubSpot "App Token" auth to "Service Key" ([#28479](https://github.com/n8n-io/n8n/issues/28479)) ([8c3e692](https://github.com/n8n-io/n8n/commit/8c3e6921741f0e28ba28f8fb39797d5e19db71c9))
|
||||
* **HubSpot Trigger Node:** Add missing property selectors ([#28595](https://github.com/n8n-io/n8n/issues/28595)) ([d179f66](https://github.com/n8n-io/n8n/commit/d179f667c0044fd246d8e8535cd3a741d3f96b6f))
|
||||
* **IMAP Node:** Fix out-of-memory crash after ECONNRESET on reconnect ([#28290](https://github.com/n8n-io/n8n/issues/28290)) ([2d0b231](https://github.com/n8n-io/n8n/commit/2d0b231e31f265f39dd95d6794bd74d9b5592056))
|
||||
* Link to n8n website broken in n8n forms ([#28627](https://github.com/n8n-io/n8n/issues/28627)) ([ff950e5](https://github.com/n8n-io/n8n/commit/ff950e5840214c515d413b45f174d9638a51dd39))
|
||||
* **LinkedIn Node:** Update LinkedIn API version in request headers ([#28564](https://github.com/n8n-io/n8n/issues/28564)) ([25e07ca](https://github.com/n8n-io/n8n/commit/25e07cab5a66b04960753055131d355e0323d971))
|
||||
* **OpenAI Node:** Replace hardcoded models with RLC ([#28226](https://github.com/n8n-io/n8n/issues/28226)) ([4070930](https://github.com/n8n-io/n8n/commit/4070930e4c080c634df9b241175941c48afed9dc))
|
||||
* **Schedule Node:** Use elapsed-time check to self-heal after missed triggers ([#28423](https://github.com/n8n-io/n8n/issues/28423)) ([5f8ab01](https://github.com/n8n-io/n8n/commit/5f8ab01f9bb26f4d27f6f882fe1024f27caf4d67))
|
||||
* Update working memory using tools ([#28467](https://github.com/n8n-io/n8n/issues/28467)) ([39189c3](https://github.com/n8n-io/n8n/commit/39189c39859fbb4c1562a03ae3e6cd29195f7d1d))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* Add deployment_key table, entity, repository, and migration ([#28329](https://github.com/n8n-io/n8n/issues/28329)) ([59edd6a](https://github.com/n8n-io/n8n/commit/59edd6ae5421aa6be34ee009a3024e0ca9843467))
|
||||
* Add Prometheus counters for token exchange ([#28453](https://github.com/n8n-io/n8n/issues/28453)) ([c6534fa](https://github.com/n8n-io/n8n/commit/c6534fa0b389a394e7591d3fc5ec565409279004))
|
||||
* AI Gateway credentials endpoint instance url ([#28520](https://github.com/n8n-io/n8n/issues/28520)) ([d012346](https://github.com/n8n-io/n8n/commit/d012346c777455de5bde9cab218f0c4f2d712fa0))
|
||||
* **API:** Add missing credential endpoints (GET by ID and test) ([#28519](https://github.com/n8n-io/n8n/issues/28519)) ([9a65549](https://github.com/n8n-io/n8n/commit/9a65549575bb201c3f55888d71e04663f622eb5b))
|
||||
* **core:** Add `require-node-description-fields` ESLint rule for icon and subtitle ([#28400](https://github.com/n8n-io/n8n/issues/28400)) ([5504099](https://github.com/n8n-io/n8n/commit/550409923a3d8d6961648674024eabb0d0749cfc))
|
||||
* **core:** Add KeyManagerService for encryption key lifecycle management ([#28533](https://github.com/n8n-io/n8n/issues/28533)) ([9dd3e59](https://github.com/n8n-io/n8n/commit/9dd3e59acb6eb94bb38ffe01677ea1c9a108d87b))
|
||||
* **core:** Configure OIDC settings via env vars ([#28185](https://github.com/n8n-io/n8n/issues/28185)) ([36261fb](https://github.com/n8n-io/n8n/commit/36261fbe7ad55a7b3bcc19809b6decb401b245bb))
|
||||
* **core:** Persist deployment_key entries for stability across restarts and key rotation ([#28518](https://github.com/n8n-io/n8n/issues/28518)) ([bb96d2e](https://github.com/n8n-io/n8n/commit/bb96d2e50a6b7cd77ea6256bb1446e8b3b348bd2))
|
||||
* **core:** Support npm dist-tags in community node installation ([#28067](https://github.com/n8n-io/n8n/issues/28067)) ([ca871cc](https://github.com/n8n-io/n8n/commit/ca871cc10aca97de8c0892e0735c9fa2ed16d251))
|
||||
* **core:** Support npm registry token authentication to install private community node packages ([#28228](https://github.com/n8n-io/n8n/issues/28228)) ([8b105cc](https://github.com/n8n-io/n8n/commit/8b105cc0cf6e84e069f6b7f3a98c334cd44876c1))
|
||||
* **core:** Track workflow action source for external API and MCP requests ([#28483](https://github.com/n8n-io/n8n/issues/28483)) ([575c34e](https://github.com/n8n-io/n8n/commit/575c34eae1bdf8e9d5d5fe7d31c92f57f27fcc27))
|
||||
* **core:** Workflow tracing - add workflow version id ([#28424](https://github.com/n8n-io/n8n/issues/28424)) ([9a22fe5](https://github.com/n8n-io/n8n/commit/9a22fe5a255b20be7d0e78fff7e03bf79e50a62f))
|
||||
* **editor:** Add favoriting for projects, folders, workflows and data tables ([#26228](https://github.com/n8n-io/n8n/issues/26228)) ([b1a075f](https://github.com/n8n-io/n8n/commit/b1a075f7609045620563f86df0e15d27b1176d45))
|
||||
* **editor:** Enable workflow execution from instance AI preview canvas ([#28412](https://github.com/n8n-io/n8n/issues/28412)) ([5b376cb](https://github.com/n8n-io/n8n/commit/5b376cb12d6331e4e458a1f1880fcddce76d1db9))
|
||||
* Enable security policy settings via env vars ([#28321](https://github.com/n8n-io/n8n/issues/28321)) ([1108467](https://github.com/n8n-io/n8n/commit/1108467f44bf987c0f5a5a0eafb6396e2745b8ce))
|
||||
* **Linear Trigger Node:** Add signing secret validation ([#28522](https://github.com/n8n-io/n8n/issues/28522)) ([3b248ee](https://github.com/n8n-io/n8n/commit/3b248eedc289c62f32f16da677c75b25df0fcb9f))
|
||||
* **MiniMax Chat Model Node:** Add MiniMax Chat Model sub-node ([#28305](https://github.com/n8n-io/n8n/issues/28305)) ([bd927d9](https://github.com/n8n-io/n8n/commit/bd927d93503a65e0be18c4c40e68dcad96f68d82))
|
||||
* **Slack Node:** Add app_home_opened as a dedicated trigger event ([#28626](https://github.com/n8n-io/n8n/issues/28626)) ([f1dab3e](https://github.com/n8n-io/n8n/commit/f1dab3e29530ee596d68db474024ddbae5fa055a))
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Make Wait node fully durable by removing in-memory execution path ([#28538](https://github.com/n8n-io/n8n/issues/28538)) ([bb9bec3](https://github.com/n8n-io/n8n/commit/bb9bec3ba419d46450122411839f20cd614db920))
|
||||
|
||||
|
||||
# [2.17.0](https://github.com/n8n-io/n8n/compare/n8n@2.16.0...n8n@2.17.0) (2026-04-13)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ The most important directories:
|
|||
workflows. **Contact n8n before
|
||||
starting on any changes here**
|
||||
- [/packages/frontend/@n8n/design-system](/packages/frontend/@n8n/design-system) - Vue frontend components
|
||||
- [/packages/frontend/editor-ui](/packages/editor-ui) - Vue frontend workflow editor
|
||||
- [/packages/frontend/editor-ui](/packages/frontend/editor-ui) - Vue frontend workflow editor
|
||||
- [/packages/node-dev](/packages/node-dev) - CLI to create new n8n-nodes
|
||||
- [/packages/nodes-base](/packages/nodes-base) - Base n8n nodes
|
||||
- [/packages/workflow](/packages/workflow) - Workflow code with interfaces which
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
"**/CHANGELOG.md",
|
||||
"**/cl100k_base.json",
|
||||
"**/o200k_base.json",
|
||||
"**/*.generated.ts"
|
||||
"**/*.generated.ts",
|
||||
"**/expectations/**"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ reviews:
|
|||
|
||||
## Step 4: Design System Style Rules
|
||||
|
||||
Follow `.agents/design-system-style-rules.md` for all CSS/SCSS/Vue style
|
||||
Follow `.claude/plugins/n8n/skills/design-system-rules/SKILL.md` for all CSS/SCSS/Vue style
|
||||
review guidance.
|
||||
|
||||
Enforcement level:
|
||||
|
|
@ -213,7 +213,7 @@ reviews:
|
|||
humans handle edge cases.
|
||||
- name: Design System Tokens
|
||||
description: |-
|
||||
Follow `.agents/design-system-style-rules.md`.
|
||||
Follow `.claude/plugins/n8n/skills/design-system-rules/SKILL.md`.
|
||||
|
||||
Apply balanced enforcement:
|
||||
- Strong warning: hard-coded visual values, legacy token usage, and
|
||||
|
|
|
|||
20
docker/images/engine/Dockerfile
Normal file
20
docker/images/engine/Dockerfile
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
ARG NODE_VERSION=24.14.1
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine3.22
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
RUN apk add --no-cache tini
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# `compiled/` is produced by `pnpm build:docker`. It's a `pnpm deploy --prod`
|
||||
# output containing package.json, dist/, and a node_modules with only
|
||||
# production dependencies — no devDeps, no workspace bloat.
|
||||
COPY --chown=node:node ./compiled /app
|
||||
|
||||
USER node
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["tini", "--"]
|
||||
CMD ["node", "dist/serve.js"]
|
||||
|
|
@ -3,7 +3,7 @@ ARG N8N_VERSION=snapshot
|
|||
|
||||
# Rebuild native addons for the container platform. The base image has no
|
||||
# package manager or build tools, so we compile in a throwaway builder stage.
|
||||
FROM node:${NODE_VERSION}-alpine AS builder
|
||||
FROM node:${NODE_VERSION}-alpine3.22 AS builder
|
||||
COPY ./compiled /usr/local/lib/node_modules/n8n
|
||||
RUN apk add --no-cache python3 make g++ && \
|
||||
cd /usr/local/lib/node_modules/n8n && \
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ ARG PYTHON_VERSION=3.13
|
|||
# ==============================================================================
|
||||
# STAGE 1: JavaScript runner (@n8n/task-runner) artifact from CI
|
||||
# ==============================================================================
|
||||
FROM node:${NODE_VERSION}-alpine AS javascript-runner-builder
|
||||
FROM node:${NODE_VERSION}-alpine3.22 AS javascript-runner-builder
|
||||
COPY ./dist/task-runner-javascript /app/task-runner-javascript
|
||||
|
||||
WORKDIR /app/task-runner-javascript
|
||||
|
|
@ -88,7 +88,7 @@ RUN uv pip install . && rm -rf /app/task-runner-python/src
|
|||
# ==============================================================================
|
||||
FROM alpine:3.22 AS launcher-downloader
|
||||
ARG TARGETPLATFORM
|
||||
ARG LAUNCHER_VERSION=1.4.3
|
||||
ARG LAUNCHER_VERSION=1.4.5
|
||||
|
||||
RUN set -e; \
|
||||
case "$TARGETPLATFORM" in \
|
||||
|
|
@ -108,7 +108,7 @@ RUN set -e; \
|
|||
# ==============================================================================
|
||||
# STAGE 4: Node alpine base for JS task runner
|
||||
# ==============================================================================
|
||||
FROM node:${NODE_VERSION}-alpine AS node-alpine
|
||||
FROM node:${NODE_VERSION}-alpine3.22 AS node-alpine
|
||||
|
||||
# ==============================================================================
|
||||
# STAGE 5: Runtime
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ RUN uv pip install . && rm -rf /app/task-runner-python/src
|
|||
# ==============================================================================
|
||||
FROM debian:bookworm-slim AS launcher-downloader
|
||||
ARG TARGETPLATFORM
|
||||
ARG LAUNCHER_VERSION=1.4.3
|
||||
ARG LAUNCHER_VERSION=1.4.5
|
||||
|
||||
RUN set -e; \
|
||||
apt-get update && apt-get install -y --no-install-recommends wget ca-certificates && \
|
||||
|
|
|
|||
|
|
@ -49,6 +49,8 @@ const config = {
|
|||
// This resolve the path mappings from the tsconfig relative to each jest.config.js
|
||||
moduleNameMapper: {
|
||||
'^@n8n/utils$': resolve(__dirname, 'packages/@n8n/utils/dist/index.cjs'),
|
||||
// jest-resolve@29 doesn't honor `./lib/*` subpath patterns in @anthropic-ai/sdk's exports map
|
||||
'^@anthropic-ai/sdk/lib/(.*)$': '@anthropic-ai/sdk/lib/$1.js',
|
||||
...(compilerOptions?.paths
|
||||
? pathsToModuleNameMapper(compilerOptions.paths, {
|
||||
prefix: `<rootDir>${compilerOptions.baseUrl ? `/${compilerOptions.baseUrl.replace(/^\.\//, '')}` : ''}`,
|
||||
|
|
|
|||
32
package.json
32
package.json
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "n8n-monorepo",
|
||||
"version": "2.17.0",
|
||||
"version": "2.21.0",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=22.16",
|
||||
|
|
@ -21,7 +21,7 @@
|
|||
"typecheck": "turbo typecheck",
|
||||
"dev": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner",
|
||||
"dev:be": "turbo run dev --parallel --env-mode=loose --filter=!@n8n/design-system --filter=!@n8n/chat --filter=!@n8n/task-runner --filter=!n8n-editor-ui",
|
||||
"dev:ai": "turbo run dev --parallel --env-mode=loose --filter=@n8n/nodes-langchain --filter=n8n --filter=n8n-core",
|
||||
"dev:ai": "turbo run dev --parallel --env-mode=loose --filter=@n8n/n8n-nodes-langchain --filter=n8n --filter=n8n-core",
|
||||
"dev:fe": "run-p start \"dev:fe:editor --filter=@n8n/design-system\"",
|
||||
"dev:fe:editor": "turbo run dev --parallel --env-mode=loose --filter=n8n-editor-ui",
|
||||
"dev:e2e": "pnpm --filter=n8n-playwright dev --ui",
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
"lint:styles:fix": "turbo run lint:styles:fix",
|
||||
"lint:affected": "turbo run lint --affected",
|
||||
"lint:fix": "turbo run lint:fix",
|
||||
"lint:ci": "turbo run lint lint:styles",
|
||||
"optimize-svg": "find ./packages -name '*.svg' ! -name 'pipedrive.svg' -print0 | xargs -0 -P16 -L20 npx svgo",
|
||||
"generate:third-party-licenses": "node scripts/generate-third-party-licenses.mjs",
|
||||
"setup-backend-module": "node scripts/ensure-zx.mjs && zx scripts/backend-module/setup.mjs",
|
||||
|
|
@ -72,7 +73,7 @@
|
|||
"jest-mock-extended": "^3.0.4",
|
||||
"lefthook": "^1.7.15",
|
||||
"license-checker": "^25.0.1",
|
||||
"nock": "^14.0.1",
|
||||
"nock": "^14.0.14",
|
||||
"nodemon": "^3.0.1",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"p-limit": "^3.1.0",
|
||||
|
|
@ -102,7 +103,6 @@
|
|||
"@mistralai/mistralai": "^1.10.0",
|
||||
"@n8n/typeorm>@sentry/node": "catalog:sentry",
|
||||
"@types/node": "^20.17.50",
|
||||
"axios": "1.13.5",
|
||||
"chokidar": "4.0.3",
|
||||
"esbuild": "^0.25.0",
|
||||
"expr-eval@2.0.2": "npm:expr-eval-fork@3.0.0",
|
||||
|
|
@ -118,8 +118,6 @@
|
|||
"gaxios": ">=7.1.1",
|
||||
"google-gax": "^4.3.7",
|
||||
"ws": ">=8.17.1",
|
||||
"brace-expansion@1": "1.1.12",
|
||||
"brace-expansion@2": "2.0.3",
|
||||
"date-fns": "2.30.0",
|
||||
"date-fns-tz": "2.0.0",
|
||||
"form-data": "4.0.4",
|
||||
|
|
@ -138,34 +136,42 @@
|
|||
"@smithy/config-resolver": ">=4.4.0",
|
||||
"@rudderstack/rudder-sdk-node@<=3.0.0": "3.0.0",
|
||||
"diff": "8.0.3",
|
||||
"undici@5": "^6.24.0",
|
||||
"undici@6": "^6.24.0",
|
||||
"undici@7": "^7.24.0",
|
||||
"tar": "^7.5.11",
|
||||
"hono": "4.12.7",
|
||||
"ajv@6": "6.14.0",
|
||||
"ajv@7": "8.18.0",
|
||||
"ajv@8": "8.18.0",
|
||||
"bn.js@4": "5.2.3",
|
||||
"bn.js@5": "5.2.3",
|
||||
"langsmith": ">=0.4.6",
|
||||
"lodash": "4.18.1",
|
||||
"lodash-es": "4.18.1",
|
||||
"@xmldom/xmldom": "0.8.12",
|
||||
"word-wrap@<=1.2.4": "1.2.4",
|
||||
"minimatch@<=5.1.8": "5.1.8",
|
||||
"minimatch@10": "10.2.3",
|
||||
"@hono/node-server": "1.19.10",
|
||||
"@hono/node-server": "1.19.13",
|
||||
"protobufjs": "7.5.5",
|
||||
"follow-redirects": "1.16.0",
|
||||
"express-rate-limit": "8.2.2",
|
||||
"underscore": "1.13.8",
|
||||
"fast-xml-parser": "5.5.7",
|
||||
"flatted": "3.4.2",
|
||||
"handlebars": "4.7.9",
|
||||
"path-to-regexp": "8.4.0",
|
||||
"path-to-regexp@<0.1.13": "0.1.13",
|
||||
"picomatch@2": "2.3.2",
|
||||
"picomatch@4": "4.0.4",
|
||||
"brace-expansion@5": "5.0.5",
|
||||
"avsc": "5.7.9"
|
||||
"avsc": "5.7.9",
|
||||
"brace-expansion@<=2.1.0": "2.1.0",
|
||||
"@xmldom/xmldom": "0.8.13",
|
||||
"langsmith": "0.5.19",
|
||||
"yaml@<=2.8.3": "2.8.3",
|
||||
"axios": "1.16.0",
|
||||
"fast-xml-parser": "5.7.2",
|
||||
"hono": "4.12.18",
|
||||
"@anthropic-ai/sdk@<=0.91.1": "0.91.1",
|
||||
"uuid@<=13.0.1": "13.0.1",
|
||||
"fast-uri": "3.1.2"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"bull@4.16.4": "patches/bull@4.16.4.patch",
|
||||
|
|
|
|||
|
|
@ -70,8 +70,7 @@ docs/
|
|||
```
|
||||
|
||||
The **`index.ts`** surface also exports `Workspace` / sandbox / filesystem types,
|
||||
`SqliteMemory` / `PostgresMemory`, `LangSmithTelemetry`, and `evals` alongside the
|
||||
core SDK builders.
|
||||
`InMemoryMemory`, `LangSmithTelemetry`, and `evals` alongside the core SDK builders.
|
||||
|
||||
Optional **peer dependencies** (telemetry): `langsmith`, `@opentelemetry/sdk-trace-node`,
|
||||
`@opentelemetry/sdk-trace-base`, `@opentelemetry/exporter-trace-otlp-http` — all
|
||||
|
|
|
|||
|
|
@ -367,10 +367,11 @@ At end of turn, `saveToMemory()` uses `list.turnDelta()` and
|
|||
`saveMessagesToThread`. If **semantic recall** is configured with an embedder
|
||||
and `memory.saveEmbeddings`, new messages are embedded and stored.
|
||||
|
||||
**Working memory:** when configured, the runtime parses `<working_memory>` …
|
||||
`</working_memory>` regions from assistant text, validates structured JSON if a
|
||||
schema exists, strips the tags from the visible message, and asynchronously
|
||||
persists via `memory.saveWorkingMemory`.
|
||||
**Working memory:** when configured, the runtime injects an `update_working_memory`
|
||||
tool into the agent's tool set. The current state is included in the system prompt
|
||||
so the model can read it; when new information should be persisted the model calls
|
||||
the tool, which validates the input and asynchronously persists via
|
||||
`memory.saveWorkingMemory`.
|
||||
|
||||
**Thread titles:** `titleGeneration` triggers `generateThreadTitle` (fire-and-forget)
|
||||
after a successful save when persistence and memory are present.
|
||||
|
|
@ -414,7 +415,7 @@ src/
|
|||
tool-adapter.ts — buildToolMap, executeTool, toAiSdkTools, suspend / agent-result guards
|
||||
stream.ts — convertChunk, toTokenUsage
|
||||
runtime-helpers.ts — normalizeInput, usage merge, stream error helpers, …
|
||||
working-memory.ts — instruction text, parse/filter for working_memory tags
|
||||
working-memory.ts — instruction text, update_working_memory tool builder
|
||||
strip-orphaned-tool-messages.ts
|
||||
title-generation.ts
|
||||
logger.ts
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user