From e0a8845646ca70b55e9b3f977cbe13efe386692b Mon Sep 17 00:00:00 2001 From: Davide Briani Date: Fri, 14 Nov 2025 17:06:01 +0100 Subject: [PATCH] fix: handle nested default exports in Node.js 23+ In Node.js 23 and later, dynamically importing a CommonJS module that was transpiled by Babel results in a different module structure than in earlier versions. The structure becomes: ```js { __esModule: true, default: { default: [Function] }, 'module.exports': { ... } } ``` This caused the "arrangeData is not a function" error because the existing dynamicImport() function only handled cases with exactly 2 keys, but Node.js 23+ adds a 'module.exports' key (3 keys total). Changes: - Updated dynamicImport() in utils.js to detect and handle the Node.js 23+ module structure by checking for the presence of module.exports - Refactored index.js to apply takeDefaultExport() immediately to both the default and custom data arrangers for consistency This fix is backward compatible with older Node.js versions and addresses the TypeError that occurred when running spectaql with Node.js v23+. Fixes #1001. Signed-off-by: Davide Briani --- src/spectaql/index.js | 7 +++---- src/spectaql/utils.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/spectaql/index.js b/src/spectaql/index.js index f517ac4e..b2338627 100644 --- a/src/spectaql/index.js +++ b/src/spectaql/index.js @@ -67,14 +67,15 @@ async function run(opts) { return fileExists(path.normalize(`${themeDir}/${pathSuffix}`)) }) - let arrangeDataModule = arrangeDataDefaultFn + let arrangeData = takeDefaultExport(arrangeDataDefaultFn) if (customDataArrangerSuffixThatExists) { try { - arrangeDataModule = await dynamicImport( + const arrangeDataModule = await dynamicImport( url.pathToFileURL( path.normalize(`${themeDir}/${customDataArrangerSuffixThatExists}`) ) ) + arrangeData = takeDefaultExport(arrangeDataModule) } catch (err) { console.error(err) if ( @@ -100,8 +101,6 @@ async function run(opts) { } } - const arrangeData = takeDefaultExport(arrangeDataModule) - const items = arrangeData({ introspectionResponse, graphQLSchema, diff --git a/src/spectaql/utils.js b/src/spectaql/utils.js index 8c9052c0..22725773 100644 --- a/src/spectaql/utils.js +++ b/src/spectaql/utils.js @@ -40,6 +40,18 @@ export async function dynamicImport(path) { ) { return mojule.default } + // In Node.js 23+, when dynamically importing a CommonJS module that was transpiled + // by Babel with module.exports, the structure can be: + // { __esModule: true, default: { default: [Function] }, 'module.exports': { default: [Function] } } + // In this case, we want to return mojule.default (which contains { default: [Function] }) + // so that takeDefaultExport can properly extract the function + if ( + mojule.__esModule === true && + mojule.default?.default && + mojule['module.exports']?.default + ) { + return mojule.default + } return mojule }