Skip to content

build(websocket): migrate to ESM#41680

Merged
hainenber merged 7 commits into
masterfrom
feat/migrate-websocket-to-esm
Jul 8, 2026
Merged

build(websocket): migrate to ESM#41680
hainenber merged 7 commits into
masterfrom
feat/migrate-websocket-to-esm

Conversation

@hainenber

Copy link
Copy Markdown
Contributor

build(websocket): migrate to ESM

SUMMARY

I was trying to resolve failed CI in a Dependabot PR that bumps cookie to v2 and come to realization that the library now ESM-only. Hence this PR to migrate whole superset-websocket service to ESM.

It's a backend service so expect no regression/backward-incompat after migration.

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A

TESTING INSTRUCTIONS

Green CI for acceptance criteria

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

Signed-off-by: hainenber <dotronghai96@gmail.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
@hainenber hainenber requested a review from villebro July 2, 2026 10:09

import { createLogger } from './logger';
import { buildConfig, RedisConfig } from './config';
import { createLogger } from './logger.js';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: The relative import now points to ./logger.js from a .ts source file, but the dev-server script runs sources directly (node --experimental-strip-types src/index.ts), where src/logger.js does not exist. This will fail module resolution at startup in dev mode; use source-resolvable specifiers for direct-TS execution or stop running TS sources directly. [api mismatch]

Severity Level: Critical 🚨
- ❌ Dev server `npm run dev-server` fails to start.
- ⚠️ Local websocket development and debugging blocked for contributors.
Steps of Reproduction ✅
1. In `superset-websocket/package.json:7–15`, note the `dev-server` script:
`\"dev-server\": \"node --experimental-strip-types src/index.ts start\"` (line 13), which
executes `src/index.ts` directly under Node with experimental type stripping, without
building JS output.

2. In `superset-websocket/README.md:9–20`, the documented way to run locally is `npm run
dev-server` (lines 11–15), so contributors will routinely start the service via this
direct-TS path.

3. In `superset-websocket/src/index.ts:19–31`, the file executed by the dev-server script,
observe the ESM-style relative import `import { createLogger } from './logger.js';` on
line 29, alongside `import { buildConfig, RedisConfig } from './config.js';` on line 30.

4. In `superset-websocket/src/logger.ts` (lines 19–52) and the `src` directory listing
(`ls superset-websocket/src` showing only `config.ts`, `index.ts`, `logger.ts`), confirm
there is no `logger.js` (nor any JS files) in `src/`. When running `npm run dev-server`,
Node resolves `./logger.js` relative to `src/index.ts`, fails to find a matching file, and
throws a module resolution error before the WebSocket server starts, breaking local dev
startup.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-websocket/src/index.ts
**Line:** 29:29
**Comment:**
	*Api Mismatch: The relative import now points to `./logger.js` from a `.ts` source file, but the `dev-server` script runs sources directly (`node --experimental-strip-types src/index.ts`), where `src/logger.js` does not exist. This will fail module resolution at startup in dev mode; use source-resolvable specifiers for direct-TS execution or stop running TS sources directly.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

@bito-code-review

Copy link
Copy Markdown
Contributor

The flagged issue is correct. When using Node.js with --experimental-strip-types, it expects ESM-compliant imports, which require explicit file extensions (like .js) for relative imports. However, since the source file is actually a .ts file, Node.js fails to resolve ./logger.js because it does not exist on the filesystem.

To resolve this while maintaining the ESM-compliant import style required by the new configuration, you should ensure the development environment or the build process correctly maps these imports, or adjust the import to be compatible with the runtime. Given the project is moving to ESM, the standard approach is to use the .js extension in imports as you have done, but ensure the runtime (or a loader) can resolve these to the corresponding .ts files.

Since you are using node --experimental-strip-types, you may need to ensure your environment is configured to handle the resolution of these extensions, or consider using a tool like tsx or ts-node (if re-enabled) that handles this resolution automatically during development. Alternatively, if you want to keep using the native Node.js experimental flag, you might need to ensure your tsconfig.json and module resolution settings are fully aligned with how Node.js resolves ESM imports.

I have checked the PR comments, and there are no other pending review comments to address. Would you like me to help you explore alternative ways to configure the development server to support this ESM-style resolution?

superset-websocket/src/index.ts

import { createLogger } from './logger.js';
import { buildConfig, RedisConfig } from './config.js';

@bito-code-review

bito-code-review Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #8ccb1a

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 8fac1d5..74dd747
    • superset-websocket/src/config.ts
    • superset-websocket/src/index.ts
  • Files skipped - 3
    • superset-websocket/package-lock.json - Reason: Filter setting
    • superset-websocket/package.json - Reason: Filter setting
    • superset-websocket/tsconfig.json - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@rusackas

rusackas commented Jul 7, 2026

Copy link
Copy Markdown
Member

The ESM migration itself reads right, but the dev-server script looks like it'll break... node --experimental-strip-types won't remap the .js import specifiers to the .ts sources, so npm run dev-server dies with ERR_MODULE_NOT_FOUND (the bot comment above caught the same thing). Running it through tsx instead would sort it, or build-first.

It's also conflicting with master, so it'll want a rebase before we merge.

hainenber added 2 commits July 8, 2026 22:07
Signed-off-by: hainenber <dotronghai96@gmail.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
@hainenber

Copy link
Copy Markdown
Contributor Author

Thanks Evan, I missed that detail on dev-server. I've fixed it along with merge conflict mentioned in HEAD commit :D

*/

import { merge as _merge } from 'lodash';
import { merge as _merge } from 'lodash-es';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: This file is now explicitly ESM, but configFromFile() still uses CommonJS require(), which will throw ReferenceError: require is not defined at runtime when loading config. Replace the config file loading path with an ESM-compatible approach (for example createRequire(import.meta.url) or fs + JSON.parse) so startup does not fail. [api mismatch]

Severity Level: Critical 🚨
❌ Websocket service ignores config.json/config.test.json values.
⚠️ Vitest config tests fail, blocking CI success.
⚠️ JWT secret from file never applied to opts.
⚠️ Redis/statds tuning from file never applied.
Steps of Reproduction ✅
1. Open `/workspace/superset/superset-websocket/package.json` where line 5 sets `"type":
"module"` and `/workspace/superset/superset-websocket/tsconfig.json` where line 6 sets
`"module": "nodenext"`, confirming the compiled JS for this service runs as native ESM.

2. Start the websocket service via `npm run start` (script defined at `package.json:7` as
`node dist/index.js start`), which loads the compiled `dist/index.js` entrypoint from
`/workspace/superset/superset-websocket/src/index.ts`.

3. In `/workspace/superset/superset-websocket/src/index.ts` lines 18–23, `environment` and
`startServer` are computed, then `export const opts = buildConfig();` is executed,
importing `buildConfig` from `/workspace/superset/superset-websocket/src/config.ts:192`.

4. In `/workspace/superset/superset-websocket/src/config.ts` lines 192–195,
`buildConfig()` calls `defaultConfig()` and `configFromFile()`, then passes the result
through `applyEnvOverrides()`, so `configFromFile()` runs on every startup and for every
test that calls `buildConfig()`.

5. In the same file, `configFromFile()` at lines 103–107 computes `configFile` as
`'../config.json'` or `'../config.test.json'`, then executes `return require(configFile);`
inside a `try` block; under ESM (package `"type": "module"`), `require` is not defined, so
this line throws `ReferenceError: require is not defined`, which is caught by the `catch`
at lines 108–111, causing a `console.warn('config.json file not found')` and returning
`{}` instead of the JSON contents even when
`/workspace/superset/superset-websocket/config.test.json` exists.

6. When running tests via `npm test` (script at `package.json:9`), `spec/config.test.ts`
line 22 calls `buildConfig()` and expects values sourced from `config.test.json` (e.g.,
`jwtSecret`, redis `db`, statsd host/port). Because `configFromFile()` now always returns
`{}` due to the ESM `require` failure, these expectations no longer match, causing the
config tests to fail and CI to report red; similarly, in production the service will
silently ignore `config.json` and run with defaults plus env vars only.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-websocket/src/config.ts
**Line:** 20:20
**Comment:**
	*Api Mismatch: This file is now explicitly ESM, but `configFromFile()` still uses CommonJS `require()`, which will throw `ReferenceError: require is not defined` at runtime when loading config. Replace the config file loading path with an ESM-compatible approach (for example `createRequire(import.meta.url)` or `fs` + `JSON.parse`) so startup does not fail.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

can JSON.parse(readFileSync("/path/to/file")) for this case too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

oh, nvm. Saw your 2nd suggestion now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes — JSON.parse(readFileSync("/path/to/file")) can work if readFileSync returns a string, or you convert the buffer first.

In Node, readFileSync() without an encoding returns a Buffer, so the safe version is:

import { readFileSync } from 'fs';

const config = JSON.parse(readFileSync(configFile, 'utf8'));

That’s a good ESM-friendly replacement here and avoids require() entirely.

Why this is appropriate

  • Works in native ESM
  • Keeps the config loading simple
  • Still supports config.json / config.test.json at startup and in tests

So yes, this is a valid fix for the issue raised in the thread.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in latest commit

…or cross-module config load

Signed-off-by: hainenber <dotronghai96@gmail.com>
@netlify

netlify Bot commented Jul 8, 2026

Copy link
Copy Markdown

Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit adcda04
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/6a4e80d7cfb9650008ab0406
😎 Deploy Preview https://deploy-preview-41680--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

Comment thread superset-websocket/src/config.ts Outdated
const configFile = isTest ? '../config.test.json' : '../config.json';
try {
return require(configFile);
return JSON.parse(readFileSync(configFile, 'utf8')) as ConfigType;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggestion: Using readFileSync with configFile as a bare relative path resolves against process.cwd(), not against config.ts/dist/config.js. This changes behavior from module-relative loading and will miss superset-websocket/config.test.json (and production config.json) when the process is started from the package root, causing file-based config to be silently skipped. Resolve the path from import.meta.url (or equivalent module directory logic) before reading. [api mismatch]

Severity Level: Critical 🚨
- ❌ WebSocket sidecar container fails startup despite config.json present.
- ❌ JWT secret from config.json never applied to server options.
- ⚠️ Test config.test.json ignored; tests use default connection settings.
Steps of Reproduction ✅
1. Start the `superset-websocket` service using Docker Compose, which defines the
`superset-websocket` container at `docker-compose.yml:18-47` and mounts a config file to
`/home/superset-websocket/config.json` via the volume at `docker-compose.yml:41-43`. The
Dockerfile at `superset-websocket/Dockerfile:40-51` sets `WORKDIR
/home/superset-websocket` and runs `CMD ["npm", "start"]`, which uses the `start` script
in `superset-websocket/package.json:7-8` (`"start": "node dist/index.js start"`), so
Node’s `process.cwd()` is `/home/superset-websocket`.

2. When `dist/index.js` runs, it imports `./config.js` and immediately calls
`buildConfig()` via `export const opts = buildConfig();` at
`superset-websocket/src/index.ts:29-31,81`. `buildConfig()` in
`superset-websocket/src/config.ts:193-195` merges `defaultConfig()` with
`configFromFile()`.

3. Inside `configFromFile()` at `superset-websocket/src/config.ts:104-109`, `NODE_ENV` is
`production`, so `isTest` is false and `configFile` is set to `'../config.json'` at line
106. The call `readFileSync(configFile, 'utf8')` at line 108 resolves `'../config.json'`
relative to `process.cwd()` (`/home/superset-websocket`), so Node attempts to read
`/home/config.json`, but the actual file is at `/home/superset-websocket/config.json`.
This causes `readFileSync` to throw `ENOENT`, the `catch` block at lines 109-111 logs
`config.json file not found`, and `configFromFile()` returns `{}`, silently discarding the
mounted configuration.

4. Because file configuration is ignored, `buildConfig()` returns only `defaultConfig()`
and `opts.jwtSecret` remains the empty string from `defaultConfig()`
(`superset-websocket/src/config.ts:70-72`). With `startServer` true (`process.argv[2] ===
'start'` at `superset-websocket/src/index.ts:79`), the startup check at
`superset-websocket/src/index.ts:98-102` detects `opts.jwtSecret.length < 32`, logs
`Please provide a JWT secret at least 32 bytes long`, and calls `process.exit(1)`, causing
the `superset-websocket` container to fail to start even though a valid `config.json` (or
`config.example.json`) is present and mounted.

Fix in Cursor Fix in VSCode Claude

(Use Cmd/Ctrl + Click for best experience)

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** superset-websocket/src/config.ts
**Line:** 108:108
**Comment:**
	*Api Mismatch: Using `readFileSync` with `configFile` as a bare relative path resolves against `process.cwd()`, not against `config.ts`/`dist/config.js`. This changes behavior from module-relative loading and will miss `superset-websocket/config.test.json` (and production `config.json`) when the process is started from the package root, causing file-based config to be silently skipped. Resolve the path from `import.meta.url` (or equivalent module directory logic) before reading.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix
👍 | 👎

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in latest commit

hainenber added 2 commits July 9, 2026 00:01
Signed-off-by: hainenber <dotronghai96@gmail.com>
Signed-off-by: hainenber <dotronghai96@gmail.com>
@bito-code-review

bito-code-review Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Code Review Agent Run #cfe637

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: 8fac1d5..e23851c
    • superset-websocket/src/config.ts
  • Files skipped - 1
    • superset-websocket/package.json - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • Eslint (Linter) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers a full AI review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@rusackas rusackas left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nice work, @hainenber, LGTM. Went through the codeant flags and the fixes all check out (build-before-start, the require() swap, import.meta.dirname for the path). CI's green, approving.

@hainenber hainenber merged commit 83716cb into master Jul 8, 2026
51 checks passed
@hainenber hainenber deleted the feat/migrate-websocket-to-esm branch July 8, 2026 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants