Skip to content

Published dist output uses extensionless relative imports, breaking Node's native ESM resolver (ERR_MODULE_NOT_FOUND) #267

Description

@tplass-ias

Summary

Every published package in this SDK (@databricks/sdk-auth, @databricks/sdk-core, @databricks/sdk-scim, and likely others under packages/*) fails to load under Node's native ESM loader with ERR_MODULE_NOT_FOUND, because the compiled dist/*.js output uses extensionless relative import/export specifiers (e.g. from './errors' instead of from './errors.js').

Since each package's package.json declares "type": "module", Node resolves these packages with the strict ESM resolver, which requires explicit file extensions on relative specifiers (Node docs). tsc's moduleResolution: "bundler" (set in tsconfig.base.json) permits extensionless specifiers at compile time on the assumption that a downstream bundler will resolve them — but the packages here are shipped as plain tsc -b output with no bundling step, so nothing ever adds the extensions before publish.

Repro

npm install @databricks/sdk-auth
node -e "require('@databricks/sdk-auth')"
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/node_modules/@databricks/sdk-auth/dist/auth' imported from /node_modules/@databricks/sdk-auth/dist/index.js

Same failure reproduces on the credentials subpath export:

node -e "require('@databricks/sdk-auth/credentials')"
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/node_modules/@databricks/sdk-auth/dist/credentials/errors' imported from /node_modules/@databricks/sdk-auth/dist/credentials/index.js

And in @databricks/sdk-core (a dependency of sdk-auth/sdk-scim), e.g. the profiles subpath used by sdk-auth's default-credentials chain:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/node_modules/@databricks/sdk-core/dist/profiles/errors' imported from /node_modules/@databricks/sdk-core/dist/profiles/index.js

And @databricks/sdk-scim's v1 subpath:

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/node_modules/@databricks/sdk-scim/dist/v1/client' imported from /node_modules/@databricks/sdk-scim/dist/v1/index.js

Reproduced against the latest published version at the time of filing (@databricks/sdk-auth@0.21.0), so this isn't specific to an old release — I confirmed the same extensionless imports are present in the current main branch's compiled output shape (tsconfig.base.json still sets moduleResolution: "bundler").

Environment

  • Node.js v24.18.0 (also reproduces on other Node 20+/22+ versions using the native ESM loader — this is a Node spec requirement, not version-specific)
  • @databricks/sdk-auth@0.17.0, 0.18.0, 0.21.0 (latest at time of filing)
  • @databricks/sdk-core@0.18.0
  • @databricks/sdk-scim@0.17.0

Root cause

tsconfig.base.json:

{
  "compilerOptions": {
    "moduleResolution": "bundler",
    ...
  }
}

packages/*/package.json build script: "build": "tsc -b" — plain tsc compile, no bundler in the publish pipeline, yet moduleResolution: "bundler" is what permits the extensionless specifiers to type-check. Combined with "type": "module" in each published package's package.json, this produces output that is invalid for Node's ESM resolver.

Suggested fix

Either:

  1. Switch to moduleResolution: "node16"/"nodenext" (requires explicit extensions in source import statements, matching what actually gets published), or
  2. Add a build step (e.g. via tsc-alias, a small custom transform, or a bundler like tsup/esbuild) that rewrites/appends .js to relative specifiers in the emitted dist/*.js before publish.

Either fix needs to land in every packages/* package that ships compiled ESM dist/*.js, since the same tsconfig.base.json inheritance affects all of them, not just the three I hit directly.

Impact

This makes every SDK package here unusable under plain require()/import in Node — they can currently only "work" if bundled through a tool (webpack/esbuild/rollup) that resolves extensionless specifiers itself, which most Node.js backend service code does not do. For consumers running compiled/backend Node.js code directly (no bundler in the loop — our case, a Backstage backend plugin), every entry point that imports across files within these packages throws at require-time.

(filed w/ Claude Code)

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions