From a52077eb60dcab2f75d2e1001cb02ad9eca27231 Mon Sep 17 00:00:00 2001 From: "n8n-assistant[bot]" <100856346+n8n-assistant[bot]@users.noreply.github.com> Date: Fri, 17 Apr 2026 14:45:39 +0100 Subject: [PATCH] fix(editor): Restore WASM file paths for cURL import in HTTP Request node (backport to release-candidate/2.16.x) (#28620) Co-authored-by: Jon Co-authored-by: Claude Sonnet 4.6 Co-authored-by: Matsuuu --- .../frontend/@n8n/i18n/src/locales/en.json | 6 +- .../components/ImportCurlModal.test.ts | 60 ++++++++++++++++--- .../parameters/components/ImportCurlModal.vue | 27 ++++++--- .../ndv/settings/components/NodeSettings.vue | 4 +- packages/frontend/editor-ui/vite.config.mts | 6 +- 5 files changed, 82 insertions(+), 21 deletions(-) diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json index a6c3d27fff7..1a470f25761 100644 --- a/packages/frontend/@n8n/i18n/src/locales/en.json +++ b/packages/frontend/@n8n/i18n/src/locales/en.json @@ -1197,7 +1197,7 @@ "experiments.resourceCenter.seeMore": "See more", "experiments.resourceCenter.sidebar": "Resources", "experiments.resourceCenter.sidebar.inspiration": "Inspiration", - "experiments.resourceCenter.templateCard.useNow": "▶︎ Run workflow", + "experiments.resourceCenter.templateCard.useNow": "▶ Run workflow", "experiments.resourceCenter.templatePreviews.title": "Popular Templates", "experiments.resourceCenter.title": "Resources", "experiments.resourceCenter.viewAllTemplates": "View all templates", @@ -3451,7 +3451,7 @@ "templates.collection": "Collection", "templates.collections": "Collections", "templates.collectionsNotFound": "Collection could not be found", - "templates.connectionWarning": "⚠️ There was a problem fetching workflow templates. Check your internet connection.", + "templates.connectionWarning": "! There was a problem fetching workflow templates. Check your internet connection.", "templates.heading": "Workflow templates", "templates.shareWorkflow": "Share template", "templates.noSearchResults": "Nothing found. Try adjusting your search to see more.", @@ -3981,6 +3981,8 @@ "ImportCurlModal.notice.content": "This will overwrite any changes you have already made to the current node", "importCurlModal.button.label": "Import", "importCurlParameter.label": "Import cURL", + "importCurlParameter.showError.failedToLoad.title": "Failed to load dependencies", + "importCurlParameter.showError.failedToLoad.message": "There was an error loading required dependencies.", "importCurlParameter.showError.invalidCurlCommand.title": "Couldn’t import cURL command", "importCurlParameter.showError.invalidCurlCommand.message": "This command is in an unsupported format", "importCurlParameter.showError.invalidProtocol1.title": "Use the {node} node", diff --git a/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.test.ts b/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.test.ts index eeee9e92311..17b6aa49a51 100644 --- a/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.test.ts +++ b/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.test.ts @@ -15,16 +15,23 @@ vi.mock('@/app/composables/useTelemetry', () => ({ }), })); +const mockShowToast = vi.fn(); +vi.mock('@/app/composables/useToast', () => ({ + useToast: () => ({ showToast: mockShowToast }), +})); + +const mockImportCurlCommand = vi.fn(); +let mockImportOptions: { + onImportSuccess: () => void; + onImportFailure: (data: { invalidProtocol: boolean; protocol?: string }) => void; + onAfterImport: () => void; +}; + vi.mock('@/app/composables/useImportCurlCommand', () => ({ - useImportCurlCommand: (options: { - onImportSuccess: () => void; - onAfterImport: () => void; - }) => ({ - importCurlCommand: () => { - options.onImportSuccess(); - options.onAfterImport(); - }, - }), + useImportCurlCommand: (options: typeof mockImportOptions) => { + mockImportOptions = options; + return { importCurlCommand: mockImportCurlCommand }; + }, })); const renderModal = createComponentRenderer(ImportCurlModal, { @@ -43,6 +50,10 @@ const testNode = { describe('ImportCurlModal', () => { beforeEach(() => { vi.clearAllMocks(); + mockImportCurlCommand.mockImplementation(() => { + mockImportOptions.onImportSuccess(); + mockImportOptions.onAfterImport(); + }); }); it('should show empty input when no curl command exists for active node', async () => { @@ -149,4 +160,35 @@ describe('ImportCurlModal', () => { 'node-1': 'curl -X GET https://api.example.com/other', }); }); + + it('should show error toast and track failure telemetry when import throws (e.g. WASM load failure)', async () => { + mockImportCurlCommand.mockImplementation(() => { + throw new Error('WASM failed to load'); + }); + + const uiStore = mockedStore(useUIStore); + uiStore.modalsById = { + [IMPORT_CURL_MODAL_KEY]: { + open: true, + data: { curlCommands: {} }, + }, + }; + uiStore.modalStack = [IMPORT_CURL_MODAL_KEY]; + const ndvStore = mockedStore(useNDVStore); + ndvStore.activeNode = testNode; + + const { getByTestId } = renderModal(); + await nextTick(); + + const input = getByTestId('import-curl-modal-input'); + await userEvent.type(input, 'curl -X GET https://api.example.com/data'); + const button = getByTestId('import-curl-modal-button'); + await userEvent.click(button); + + expect(mockShowToast).toHaveBeenCalledWith(expect.objectContaining({ type: 'error' })); + expect(mockTelemetryTrack).toHaveBeenCalledWith( + 'User imported curl command', + expect.objectContaining({ success: false }), + ); + }); }); diff --git a/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.vue b/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.vue index 710fe5a3793..44684ac4227 100644 --- a/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.vue +++ b/packages/frontend/editor-ui/src/features/ndv/parameters/components/ImportCurlModal.vue @@ -5,11 +5,13 @@ import { onMounted, ref } from 'vue'; import { useUIStore } from '@/app/stores/ui.store'; import { createEventBus } from '@n8n/utils/event-bus'; import { useTelemetry } from '@/app/composables/useTelemetry'; +import { useToast } from '@/app/composables/useToast'; import { useI18n } from '@n8n/i18n'; import { useNDVStore } from '@/features/ndv/shared/ndv.store'; import { N8nButton, N8nInput, N8nInputLabel, N8nNotice } from '@n8n/design-system'; const telemetry = useTelemetry(); +const toast = useToast(); const i18n = useI18n(); const uiStore = useUIStore(); @@ -80,13 +82,24 @@ function sendTelemetry( } async function onImport() { - const { useImportCurlCommand } = await import('@/app/composables/useImportCurlCommand'); - const { importCurlCommand } = useImportCurlCommand({ - onImportSuccess, - onImportFailure, - onAfterImport, - }); - importCurlCommand(curlCommand); + try { + const { useImportCurlCommand } = await import('@/app/composables/useImportCurlCommand'); + const { importCurlCommand } = useImportCurlCommand({ + onImportSuccess, + onImportFailure, + onAfterImport, + }); + importCurlCommand(curlCommand); + } catch { + // Handles WASM loading failures (e.g. wrong MIME type for tree-sitter.wasm) + toast.showToast({ + title: i18n.baseText('importCurlParameter.showError.failedToLoad.title'), + message: i18n.baseText('importCurlParameter.showError.failedToLoad.message'), + type: 'error', + duration: 0, + }); + onImportFailure({ invalidProtocol: false }); + } } diff --git a/packages/frontend/editor-ui/src/features/ndv/settings/components/NodeSettings.vue b/packages/frontend/editor-ui/src/features/ndv/settings/components/NodeSettings.vue index 329876a3eaf..1b8a128400e 100644 --- a/packages/frontend/editor-ui/src/features/ndv/settings/components/NodeSettings.vue +++ b/packages/frontend/editor-ui/src/features/ndv/settings/components/NodeSettings.vue @@ -460,7 +460,9 @@ const setHttpNodeParameters = (parameters: CurlToJSONResponse) => { name: 'parameters', value: parameters as unknown as INodeParameters, }); - } catch {} + } catch (error) { + console.error('Failed to apply cURL parameters to node:', error); + } }; const onSwitchSelectedNode = (node: string) => { diff --git a/packages/frontend/editor-ui/vite.config.mts b/packages/frontend/editor-ui/vite.config.mts index 28a7b574a01..e32c9cb7c0e 100644 --- a/packages/frontend/editor-ui/vite.config.mts +++ b/packages/frontend/editor-ui/vite.config.mts @@ -110,11 +110,13 @@ const plugins: UserConfig['plugins'] = [ targets: [ { src: 'node_modules/web-tree-sitter/tree-sitter.wasm', - dest: 'dist', + dest: '.', + rename: { stripBase: true }, }, { src: 'node_modules/curlconverter/dist/tree-sitter-bash.wasm', - dest: 'dist', + dest: '.', + rename: { stripBase: true }, }, // wa-sqlite WASM files for OPFS database support (no cross-origin isolation needed) {