Repository: nodejs/node
Pull request: [25.x] module: fix extensionless CJS files in type:module packages

PR #61600 made the CJS loader respect the \`package.json\` \`type\` field for extensionless files, which fixed https://github.com/nodejs/node/issues/61104 but also enforced \`type: "module"\` for extensionless files. This broke CJS extensionless files inside ESM packages (e.g. yargs v17).

Only enforce \`type: "commonjs"\` for extensionless files. For \`type: "module"\`, leave format undefined so syntax detection handles it. This restores the [documented exception](https://nodejs.org/docs/v25.7.0/api/modules.html#enabling) in the CJS documentation that has existed since v16:

> when the nearest parent \`package.json\` file contains a top-level field \`"type"\` with a value of \`"module"\`, those files will be recognized as CommonJS modules only if they are being included via \`require()\`, not when used as the command-line entry point of the program

This PR is targeted at \`v25.x-staging\` only. On \`main\` (v26), #61600 stays as-is and the exception should be removed from the CJS documentation in a separate PR to align the docs with the ESM resolution specification.

Fixes: https://github.com/nodejs/node/issues/61971
Refs: https://github.com/yargs/yargs/issues/2509
Refs: https://github.com/nodejs/node/pull/61600

Changed files:
filename: lib/internal/modules/cjs/loader.js
patch: @@ -1952,12 +1952,12 @@ Module._extensions['.js'] = function(module, filename) {
       format = 'typescript';
     }
   } else if (path.extname(filename) === '') {
-    // Extensionless files skip the .js suffix check above. When type is explicit,
-    // follow it so ESM syntax surfaces as SyntaxError for commonjs instead of
-    // silently delegating to ESM.
+    // Extensionless files skip the .js suffix check above. When type is commonjs, follow it so ESM
+    // syntax surfaces as SyntaxError. For type: module, leave format undefined so our syntax
+    // detection handles it (allowing CJS extensionless files in ESM packages).
     pkg = packageJsonReader.getNearestParentPackageJSON(filename);
     const typeFromPjson = pkg?.data?.type;
-    if (typeFromPjson === 'commonjs' || typeFromPjson === 'module') {
+    if (typeFromPjson === 'commonjs') {
       format = typeFromPjson;
     }
   }

filename: test/es-module/test-extensionless-esm-type-commonjs.js
patch: @@ -17,3 +17,13 @@ spawnSyncAndAssert(process.execPath, [
   stdout: /script STARTED[\s\S]*v\d+\./,
   trim: true,
 });
+
+// CJS extensionless file inside a type: "module" package should work
+// when require()'d. Regression test for https://github.com/nodejs/node/issues/61971
+spawnSyncAndAssert(process.execPath, [
+  '-e', `const m = require(${JSON.stringify(
+    fixtures.path('es-modules', 'extensionless-cjs-module', 'index')
+  )}); if (m.hello !== 'world') throw new Error('expected CJS exports, got: ' + JSON.stringify(m))`,
+], {
+  status: 0,
+});

filename: test/fixtures/es-modules/extensionless-cjs-module/index
patch: @@ -0,0 +1 @@
+module.exports = { hello: 'world' };

filename: test/fixtures/es-modules/extensionless-cjs-module/package.json
patch: @@ -0,0 +1,3 @@
+{
+  "type": "module"
+}