From 369fc839da2892e0e6b2f05d4b042c087a52ef4c Mon Sep 17 00:00:00 2001 From: Patrick Russell Date: Thu, 14 May 2026 15:51:59 -0700 Subject: [PATCH 1/2] fix: IOEXT-1726 emit sibling package.json so webpack action bundles load as CJS Webpack emits the action bundle with libraryTarget: 'commonjs2', but Node walks up to the closest package.json to decide the module type for the bundled index.js. If the user's project package.json sets "type": "module", Node parses the CJS bundle as ESM and aio-cli-plugin-app-dev's require()-based loader fails with " action not found, or does not export main", breaking aio app dev for every web action. aio app deploy is unaffected because the deployed bundle runs in an OpenWhisk Node container where there is no parent package.json to interfere. Drop a sibling package.json in the webpack temp output that pins the bundle to CommonJS, regardless of the project's module type. This fixes the single-file (webpack-bundled) action case. Directory-style actions written in native ESM remain a separate, larger problem requiring a loader refactor in aio-cli-plugin-app-dev. --- src/build-actions.js | 11 +++++++++++ test/build.actions.test.js | 24 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/build-actions.js b/src/build-actions.js index e90933d1..cbfbf3dd 100644 --- a/src/build-actions.js +++ b/src/build-actions.js @@ -242,6 +242,17 @@ const prepareToBuildAction = async (action, root, dist) => { return resolve(stats) }) }) + + // Fix for IOEXT-1726: webpack emits CommonJS (libraryTarget: 'commonjs2'), + // but Node walks up the directory tree to find the closest package.json + // when deciding the module type for the bundled index.js. If the user's + // project sets "type": "module" (ESM), Node parses the CJS bundle as ESM + // and aio-cli-plugin-app-dev's require()-based loader fails with + // "action not found, or does not export main". Drop a sibling package.json + // that pins the bundle output to CommonJS regardless of the project type. + fs.writeJsonSync(path.join(tempBuildDir, 'package.json'), { + type: 'commonjs' + }) } return { diff --git a/test/build.actions.test.js b/test/build.actions.test.js index 44898f52..16610760 100644 --- a/test/build.actions.test.js +++ b/test/build.actions.test.js @@ -278,6 +278,30 @@ describe('build by bundling js action file with webpack', () => { path.normalize('/dist/actions/sample-app-1.0.0/action.zip')) }) + test('should drop a sibling package.json with type: commonjs in the webpack temp dir (IOEXT-1726)', async () => { + await buildActions(config) + + const tempDir = path.normalize('/dist/actions/sample-app-1.0.0/action-temp') + + // confirm webpack was asked to emit the bundle to the expected temp dir + expect(webpack).toHaveBeenCalledWith(expect.arrayContaining([ + expect.objectContaining({ + output: expect.objectContaining({ + path: tempDir, + filename: 'index.js' + }) + }) + ])) + + // the sibling package.json must scope the bundle as CommonJS so that Node + // does not treat it as ESM when the user's project package.json declares + // "type": "module". + const siblingPath = path.join(tempDir, 'package.json') + const siblingRaw = global.fakeFileSystem.files()[siblingPath] + expect(siblingRaw).toBeDefined() + expect(JSON.parse(siblingRaw)).toEqual({ type: 'commonjs' }) + }) + test('should bundle a single action file using webpack and zip it with includes', async () => { global.fakeFileSystem.reset() global.fakeFileSystem.addJson({ From 154b1cc8f13421908607cc241c48946e3373a7f7 Mon Sep 17 00:00:00 2001 From: Patrick Russell Date: Thu, 21 May 2026 21:36:16 -0700 Subject: [PATCH 2/2] fix: normalize path separators in IOEXT-1726 test for Windows compatibility --- test/build.actions.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/build.actions.test.js b/test/build.actions.test.js index 16610760..7c25d18e 100644 --- a/test/build.actions.test.js +++ b/test/build.actions.test.js @@ -296,7 +296,7 @@ describe('build by bundling js action file with webpack', () => { // the sibling package.json must scope the bundle as CommonJS so that Node // does not treat it as ESM when the user's project package.json declares // "type": "module". - const siblingPath = path.join(tempDir, 'package.json') + const siblingPath = path.join(tempDir, 'package.json').split(path.sep).join('/') const siblingRaw = global.fakeFileSystem.files()[siblingPath] expect(siblingRaw).toBeDefined() expect(JSON.parse(siblingRaw)).toEqual({ type: 'commonjs' })