From fd84b602a030f8a7bc7d3102f736456b42e325ee Mon Sep 17 00:00:00 2001 From: "Guillaume F." Date: Wed, 29 Jul 2026 14:16:38 +0200 Subject: [PATCH] feat(core): Recommend sleep helper for banned setTimeout/clearTimeout (#35114) Co-authored-by: Claude Sonnet 5 --- .../docs/rules/no-restricted-globals.md | 14 ++++++++++ .../src/rules/no-restricted-globals.test.ts | 28 +++++++++++++++++-- .../src/rules/no-restricted-globals.ts | 27 ++++++++++++++---- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-restricted-globals.md b/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-restricted-globals.md index 57f3d674429..bb4b7cc9396 100644 --- a/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-restricted-globals.md +++ b/packages/@n8n/eslint-plugin-community-nodes/docs/rules/no-restricted-globals.md @@ -33,12 +33,26 @@ export class MyNode implements INodeType { ### ✅ Correct ```typescript +import { sleep } from 'n8n-workflow'; + export class MyNode implements INodeType { async execute(this: IExecuteFunctions) { // Use n8n context methods instead const timezone = this.getTimezone(); + // Use the sleep helper instead of setTimeout + await sleep(1000); + return this.prepareOutputData([]); } } ``` + +## Alternatives to restricted timer globals + +`n8n-workflow` exports helpers that work the same everywhere, including on +n8n Cloud, instead of reaching for the restricted timer globals directly: + +- Instead of `setTimeout(resolve, ms)`, use `await sleep(ms)`. +- Instead of `setTimeout` + `clearTimeout` for a cancellable delay, use + `await sleepWithAbort(ms, abortSignal)`. diff --git a/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.test.ts b/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.test.ts index 660921bce93..37f2a4d0335 100644 --- a/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.test.ts +++ b/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.test.ts @@ -70,6 +70,17 @@ var __dirname = '/path'; const __filename = 'file.js'; `, }, + { + name: 'sleep and sleepWithAbort from n8n-workflow should be allowed', + code: ` +import { sleep, sleepWithAbort } from 'n8n-workflow'; + +async function wait(signal) { + await sleep(1000); + await sleepWithAbort(1000, signal); +} + `, + }, ], invalid: [ { @@ -82,7 +93,12 @@ const __filename = 'file.js'; }, { code: 'setTimeout(() => {}, 1000);', - errors: [{ messageId: 'restrictedGlobal', data: { name: 'setTimeout' } }], + errors: [ + { + messageId: 'restrictedGlobalWithHint', + data: { name: 'setTimeout', hint: "Use the 'sleep' helper from 'n8n-workflow' instead." }, + }, + ], }, { code: 'clearInterval(timer);', @@ -90,7 +106,15 @@ const __filename = 'file.js'; }, { code: 'clearTimeout(timer);', - errors: [{ messageId: 'restrictedGlobal', data: { name: 'clearTimeout' } }], + errors: [ + { + messageId: 'restrictedGlobalWithHint', + data: { + name: 'clearTimeout', + hint: "Use 'sleepWithAbort' from 'n8n-workflow' with an AbortSignal instead.", + }, + }, + ], }, { code: 'setInterval(() => {}, 1000);', diff --git a/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.ts b/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.ts index c9f2f3f4e29..f4a71cc3215 100644 --- a/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.ts +++ b/packages/@n8n/eslint-plugin-community-nodes/src/rules/no-restricted-globals.ts @@ -17,6 +17,12 @@ const restrictedGlobals = [ '__filename', ]; +// Nudge toward the n8n-workflow alternatives that work under n8n Cloud's restrictions +const restrictedGlobalHints: Record = { + setTimeout: "Use the 'sleep' helper from 'n8n-workflow' instead.", + clearTimeout: "Use 'sleepWithAbort' from 'n8n-workflow' with an AbortSignal instead.", +}; + export const NoRestrictedGlobalsRule = createRule({ name: 'no-restricted-globals', meta: { @@ -26,6 +32,7 @@ export const NoRestrictedGlobalsRule = createRule({ }, messages: { restrictedGlobal: "Use of restricted global '{{ name }}' is not allowed", + restrictedGlobalWithHint: "Use of restricted global '{{ name }}' is not allowed. {{ hint }}", }, schema: [], }, @@ -43,11 +50,21 @@ export const NoRestrictedGlobalsRule = createRule({ return; } - context.report({ - node: ref.identifier, - messageId: 'restrictedGlobal', - data: { name }, - }); + const hint = restrictedGlobalHints[name]; + + context.report( + hint + ? { + node: ref.identifier, + messageId: 'restrictedGlobalWithHint', + data: { name, hint }, + } + : { + node: ref.identifier, + messageId: 'restrictedGlobal', + data: { name }, + }, + ); } return {