From 9e262aa573ef692696c8fc2a7a3d08229d5897f6 Mon Sep 17 00:00:00 2001 From: lookinway Date: Tue, 7 Apr 2026 16:40:55 +0300 Subject: [PATCH] =?UTF-8?q?fix(n8n):=20v1=20form=20compat=20=E2=80=94=20ty?= =?UTF-8?q?pe=20and=20formTitle=20defaults,=20JSON=20extraction=20(v2.0.2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit V1 Form node had no top-level `type` parameter and hid `formTitle` in JSON mode. SharedRouter bodyMap threw "Could not get parameter". - Added `default` support to FieldMap in bodyMap processing - form.type defaults to 'modal' (v1 compat) - form.title defaults to '' (JSON mode compat) - Extract title/close_text/submit_text from JSON payload when body fields are empty (v1 JSON mode had everything in one blob) --- .github/workflows/n8n.yml | 8 ++++---- integrations/n8n/CHANGELOG.md | 6 ++++++ integrations/n8n/README.md | 2 +- .../n8n/nodes/Pachca/GenericFunctions.ts | 5 ++--- integrations/n8n/nodes/Pachca/SharedRouter.ts | 17 +++++++++++++++- integrations/n8n/package.json | 2 +- integrations/n8n/scripts/generate-n8n.ts | 20 ++++++++++++++++++- 7 files changed, 49 insertions(+), 11 deletions(-) diff --git a/.github/workflows/n8n.yml b/.github/workflows/n8n.yml index 89c182f4..5c50d3b3 100644 --- a/.github/workflows/n8n.yml +++ b/.github/workflows/n8n.yml @@ -40,11 +40,11 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 22 @@ -91,13 +91,13 @@ jobs: contents: write id-token: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - uses: oven-sh/setup-bun@v2 with: bun-version: 1.3.4 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v5 with: node-version: 22 registry-url: "https://registry.npmjs.org" diff --git a/integrations/n8n/CHANGELOG.md b/integrations/n8n/CHANGELOG.md index 8cbfff0b..e52d8465 100644 --- a/integrations/n8n/CHANGELOG.md +++ b/integrations/n8n/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## 2.0.2 (2026-04-07) + +### Bug Fixes + +- Fix Form JSON mode crash — `formTitle` hidden in JSON mode but required by bodyMap; now extracts `title`, `close_text`, `submit_text` from JSON payload + ## 2.0.1 (2026-04-07) ### Bug Fixes diff --git a/integrations/n8n/README.md b/integrations/n8n/README.md index 61c706a4..f119c168 100644 --- a/integrations/n8n/README.md +++ b/integrations/n8n/README.md @@ -24,7 +24,7 @@ Or install from archive (Docker, custom n8n images): # Download from GitHub Releases # Find the latest n8n-nodes-pachca.tgz at: # https://github.com/pachca/openapi/releases?q=n8n -wget https://github.com/pachca/openapi/releases/download/n8n-v2.0.1/n8n-nodes-pachca.tgz +wget https://github.com/pachca/openapi/releases/download/n8n-v2.0.2/n8n-nodes-pachca.tgz # Via npm (recommended) cd ~/.n8n/nodes && npm install ./n8n-nodes-pachca.tgz diff --git a/integrations/n8n/nodes/Pachca/GenericFunctions.ts b/integrations/n8n/nodes/Pachca/GenericFunctions.ts index 37f24d38..1f28abb2 100644 --- a/integrations/n8n/nodes/Pachca/GenericFunctions.ts +++ b/integrations/n8n/nodes/Pachca/GenericFunctions.ts @@ -12,7 +12,7 @@ import type { IN8nHttpFullResponse, JsonObject, } from 'n8n-workflow'; -import { NodeApiError, NodeOperationError } from 'n8n-workflow'; +import { NodeApiError, NodeOperationError, sleep } from 'n8n-workflow'; import * as crypto from 'crypto'; // ============================================================================ @@ -421,8 +421,7 @@ export async function makeApiRequestAllPages( totalRetries++; const retryAfter = (err as NodeApiError & { retryAfter?: number })?.retryAfter; const waitSec = retryAfter ?? (code === '429' ? 2 : 1); - // eslint-disable-next-line @n8n/community-nodes/no-restricted-globals - await new Promise(r => setTimeout(r, waitSec * 1000)); + await sleep(waitSec * 1000); continue; } throw error; diff --git a/integrations/n8n/nodes/Pachca/SharedRouter.ts b/integrations/n8n/nodes/Pachca/SharedRouter.ts index 64eae7a3..03be2e1a 100644 --- a/integrations/n8n/nodes/Pachca/SharedRouter.ts +++ b/integrations/n8n/nodes/Pachca/SharedRouter.ts @@ -656,7 +656,7 @@ const ROUTES: Record> = { siblingFields: ['type', 'trigger_id', 'private_metadata', 'callback_id'], special: 'formBlocks', bodyMap: [ - { api: 'title', n8n: 'formTitle' }, + { api: 'title', n8n: 'formTitle', default: '' }, { api: 'type', n8n: 'type', default: 'modal' }, { api: 'trigger_id', n8n: 'triggerId' }, ], @@ -917,6 +917,21 @@ async function executeRoute( if (route.special === 'formBlocks') { const blocks = resolveFormBlocksFromParams(this, i); if (blocks.length) body.blocks = blocks; + // v1 compat: in JSON mode, title/close_text/submit_text may be inside the JSON + // Override empty body fields with values from parsed JSON + let rawJson = ''; + try { rawJson = this.getNodeParameter('formBlocks', i, '') as string; } catch { /* */ } + if (!rawJson) { try { rawJson = this.getNodeParameter('customFormJson', i, '') as string; } catch { /* */ } } + if (rawJson) { + try { + const parsed = JSON.parse(rawJson); + if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { + if (!body.title && parsed.title) body.title = parsed.title; + if (!body.close_text && parsed.close_text) body.close_text = parsed.close_text; + if (!body.submit_text && parsed.submit_text) body.submit_text = parsed.submit_text; + } + } catch { /* not valid JSON or not an object */ } + } } if (route.special === 'unfurlLinkPreviews') { // v1 compat: linkPreviews was a fixedCollection { preview: [{ url, title, description, imageUrl }] } diff --git a/integrations/n8n/package.json b/integrations/n8n/package.json index 35e7bc5f..48d267a8 100644 --- a/integrations/n8n/package.json +++ b/integrations/n8n/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-pachca", - "version": "2.0.1", + "version": "2.0.2", "description": "Pachca node for n8n workflow automation", "license": "MIT", "main": "index.js", diff --git a/integrations/n8n/scripts/generate-n8n.ts b/integrations/n8n/scripts/generate-n8n.ts index a3e3c2af..341ca49b 100644 --- a/integrations/n8n/scripts/generate-n8n.ts +++ b/integrations/n8n/scripts/generate-n8n.ts @@ -2492,10 +2492,13 @@ function buildFieldMapStr(resource: string, op: OperationInfo, f: BodyField): st parts.push(`subKey: '${subKey}'`); } - // v1 compat: form.type didn't exist in v1 — default to 'modal' + // v1 compat: form fields that may be missing in v1 JSON mode or v1 workflows if (resource === 'form' && f.name === 'type') { parts.push(`default: 'modal'`); } + if (resource === 'form' && f.name === 'title') { + parts.push(`default: ''`); + } return `{ ${parts.join(', ')} }`; } @@ -3036,6 +3039,21 @@ async function executeRoute( \tif (route.special === 'formBlocks') { \t\tconst blocks = resolveFormBlocksFromParams(this, i); \t\tif (blocks.length) body.blocks = blocks; +\t\t// v1 compat: in JSON mode, title/close_text/submit_text may be inside the JSON +\t\t// Override empty body fields with values from parsed JSON +\t\tlet rawJson = ''; +\t\ttry { rawJson = this.getNodeParameter('formBlocks', i, '') as string; } catch { /* */ } +\t\tif (!rawJson) { try { rawJson = this.getNodeParameter('customFormJson', i, '') as string; } catch { /* */ } } +\t\tif (rawJson) { +\t\t\ttry { +\t\t\t\tconst parsed = JSON.parse(rawJson); +\t\t\t\tif (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) { +\t\t\t\t\tif (!body.title && parsed.title) body.title = parsed.title; +\t\t\t\t\tif (!body.close_text && parsed.close_text) body.close_text = parsed.close_text; +\t\t\t\t\tif (!body.submit_text && parsed.submit_text) body.submit_text = parsed.submit_text; +\t\t\t\t} +\t\t\t} catch { /* not valid JSON or not an object */ } +\t\t} \t} \tif (route.special === 'unfurlLinkPreviews') { \t\t// v1 compat: linkPreviews was a fixedCollection { preview: [{ url, title, description, imageUrl }] }