Skip to content

fix: IOEXT-1726 aio app dev fails when project package.json has "type": "module"#231

Merged
pru55e11 merged 2 commits into
masterfrom
fix/ioext-1726-esm-sibling-package-json
May 22, 2026
Merged

fix: IOEXT-1726 aio app dev fails when project package.json has "type": "module"#231
pru55e11 merged 2 commits into
masterfrom
fix/ioext-1726-esm-sibling-package-json

Conversation

@pru55e11
Copy link
Copy Markdown
Contributor

Summary

Fixes IOEXT-1726: every web action returns 400 "<action> action not found, or does not export main" from aio app dev when the user's project package.json declares "type": "module". aio app deploy is unaffected.

Reproduction (with @adobe/aio-cli@11.1.0, @adobe/aio-lib-runtime@7.3.0, Node 22 or 24):

git clone https://github.com/adobe/commerce-checkout-starter-kit
cd commerce-checkout-starter-kit
npm install
# remove `require-adobe-auth: true` from the `info` action in app.config.yaml
aio app dev -e application
curl -k https://localhost:9080/api/v1/web/commerce-checkout-starter-kit/info
# {"error":"Response is not valid 'message/http'. info action not found, or does not export main"}

Root cause

prepareToBuildAction writes a webpack bundle to <dist>/<pkg>/<action>-temp/index.js with libraryTarget: 'commonjs2', but does not drop a sibling package.json. Node decides the bundle's module type by walking up to the closest package.json, which is the user's project root. When that file has "type": "module", Node parses the CommonJS bundle as ESM. @adobe/aio-cli-plugin-app-dev's defaultActionLoader in run-dev.js calls require(actionPath), which throws, gets caught, and surfaces the misleading "<action> action not found, or does not export main" message.

aio app deploy is unaffected because the deployed bundle runs in an OpenWhisk Node container with no parent package.json to interfere.

Fix

After webpack writes the bundle, drop a sibling package.json pinning the bundle to CommonJS. This is a one-line change inside prepareToBuildAction's non-directory branch:

fs.writeJsonSync(path.join(tempBuildDir, 'package.json'), { type: 'commonjs' })

The sibling is the closest ancestor package.json Node finds when resolving the bundled index.js, so it wins regardless of the project's setting. The change is invisible to deploy: the OpenWhisk Node runtime treats bundled .js actions as CJS already, and an explicit { type: 'commonjs' } is consistent with the bundle's actual format.

Scope

This fixes single-file (webpack-bundled) actions, which is the case in the bug report. Directory-style actions written in ESM would also need the dev plugin's loader to switch from require() to dynamic import(); tracked separately as ACNA-4604.

Test plan

  • New unit test in test/build.actions.test.js asserts prepareToBuildAction writes <temp>/package.json with { type: 'commonjs' } next to the bundled index.js
  • Full test suite passes (457/457, 100% coverage maintained)
  • Manual repro per IOEXT-1726:
    • Without patch: curl /info → HTTP 400, "info action not found, or does not export main"
    • With patch: curl /info → HTTP 200 ✓
  • No deploy regression: deploy path is unchanged for the OpenWhisk runtime; the extra package.json in the zip just makes the bundle's CJS-ness explicit.
  • Windows CI fixed: path separator normalization in test lookup against memfs POSIX virtual filesystem

Risk

Very low. The change only touches output produced for webpack-bundled (single-file) actions. Existing tests continue to pass; the new test locks in the contract.

pru55e11 added 2 commits May 14, 2026 15:51
…oad 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> 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.
Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 PR Reviewer

The fix is well-targeted and the comment clearly explains the reasoning. The implementation is straightforward and the test adequately covers the new behavior. One minor concern is that fs.writeJsonSync is called outside the Promise returned by the webpack callback, meaning it runs after the Promise resolves but is not awaited or error-handled — however since writeJsonSync is synchronous and fs-extra throws synchronously on error, this is acceptable. Overall the code is clean and correct.

LGTM! This PR looks good to merge.


💡 How to re-trigger

Comment /review or /pr-reviewer on this PR

Copy link
Copy Markdown

@mgar mgar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@pru55e11 pru55e11 merged commit 5c26d7c into master May 22, 2026
19 checks passed
@pru55e11 pru55e11 deleted the fix/ioext-1726-esm-sibling-package-json branch May 22, 2026 05:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants