fix(sdk): make the published build importable from plain Node.js - #99
Merged
Conversation
The package ships "type": "module" and is documented for headless tooling,
but `import '@drawtonomy/sdk'` from plain Node failed:
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../dist/types'
Cause: tsconfig used `moduleResolution: "bundler"`, which permits
extensionless relative imports and emits them verbatim. Bundlers resolve
those, but Node's ESM loader requires explicit file extensions. The bundled
test suite stayed green, so only consumers running `node script.mjs` hit it.
- switch tsconfig to module/moduleResolution NodeNext so the compiler
enforces Node's ESM rules at build time
- add the .js extension to relative imports (15 files)
- point the directory import at ./exporter/index.js
No source logic and no public API changed; bundler consumers are unaffected.
Verified with plain Node against dist/: createLaneWithBoundaries →
createSnapshot → exportToOpenDrive / exportToLanelet2 / exportToOpenScenario,
plus parseOpenDriveXml → odrToShapes round-trip (1 road, 2.9994 m lane width,
1 lane recovered). Existing suite: 323 passed.
Also adds __tests__/nodeEsmResolution.test.ts, which spawns a real Node
process against dist/ so a regression in the emitted specifiers fails the
build. An in-process test cannot catch this, since Vite rewrites the imports.
Confirmed the guard fails (ERR_MODULE_NOT_FOUND) when one extension is removed.
kosuke55
added a commit
that referenced
this pull request
Aug 10, 2026
The README documented only the extension (iframe) workflow, while the factories and exporters also work from plain Node.js since the ESM fix (#99) and the factory fixes (#100, #101). Add a copy-paste runnable example that builds a lane + driving path + vehicle and exports OpenDRIVE / OpenSCENARIO / Lanelet2, notes the 0.17.0 requirement, and points scenario logic (triggers, speed profiles) to the editor.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The package ships
"type": "module"and the README documents it for headless tooling, but importing it from plain Node.js fails:So
node script.mjs, CLI tools and CI scripts cannot use the SDK at all today — only bundler-based consumers can.Cause
tsconfig.jsonusedmoduleResolution: "bundler". That mode permits extensionless relative imports and emits them verbatim:Bundlers resolve those, but Node's ESM loader requires explicit file extensions. 45 such specifiers were emitted across
dist/. The existing test suite stayed green because Vite rewrites imports in-process, so nothing surfaced the breakage.Fix
tsconfig.jsonmodule/moduleResolution:bundler→NodeNext, so the compiler enforces Node's ESM rules at build time.jsto relative imports (mechanical)src/index.ts'./exporter'→'./exporter/index.js'No source logic and no public API changed. Bundler consumers are unaffected.
Verification
Ran against the built
dist/with plain Node (no bundler):That covers the full headless cycle:
createLaneWithBoundaries→createSnapshot→ each exporter, plus aparseOpenDriveXml→odrToShapesround-trip that recovers the lane with the correct 2.9994 m width.Existing suite: 323 passed, 4 skipped.
Regression guard
Added
__tests__/nodeEsmResolution.test.ts. It spawns a real Node process againstdist/and runs a generate + export + round-trip cycle.An in-process test cannot catch this class of bug, because Vite rewrites the specifiers before Node ever sees them — which is exactly why the original breakage went unnoticed. The test skips when
dist/is absent, so it does not require a build to be present.I verified the guard actually detects the problem: removing the extension from a single import in
dist/index.jsmakes it fail withERR_MODULE_NOT_FOUND.