Skip to content

fix(build): resolve vendored blessed requires - #1521

Open
John-David Dalton (jdalton) wants to merge 1 commit into
v1.xfrom
jdalton/surf-1445-external-bare-blessed-requires
Open

fix(build): resolve vendored blessed requires#1521
John-David Dalton (jdalton) wants to merge 1 commit into
v1.xfrom
jdalton/surf-1445-external-bare-blessed-requires

Conversation

@jdalton

@jdalton John-David Dalton (jdalton) commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

socket threat-feed crashes the moment it draws its table, on every run, for everyone, dying with Cannot find module 'blessed/lib/widgets/box'. It is present in 1.1.160 and in the current latest, 1.1.162, and socket analytics and socket audit-log load the same widgets.

Why the module is unreachable

The published package ships blessed at external/blessed/, but four vendored files still ask for it by its bare name. Node only resolves a bare name like blessed/lib/widgets/box by walking up through node_modules directories. external/ is not one, and external/blessed has no package.json, so the module cannot be found even though the file is sitting right there on disk.

Why the existing rewrite did not catch it

copyExternalPackages() already rewrites these requires to relative paths. It just runs too early to matter.

The dist config exports an array of rollup configs, and rollup runs them in order:

  1. The first config bundles src/ into dist/. Its writeBundle hook calls copyExternalPackages(), which rewrites require('blessed/…') to a relative path across external/blessed-contrib/**/*.js.
  2. The configs after it bundle src/external/blessed-contrib/**/*.mjs straight back out to those same files. They mark blessed as external, so the requires they emit stay bare, and they carry no rewrite of their own.

Step 2 lands on top of step 1's work. The rewrite happens, then gets overwritten.

Separately, the rewrite globs with cwd: blessedContribPath, so it never looks inside blessed itself. That is why external/blessed/vendor/tng.js keeps its bare require('blessed/lib/colors').

What changed
  • The blessed-contrib bundle config gets its own socketModifyPlugin, so the requires it emits are already relative at the moment they are written and there is nothing left to clobber. The plugin runs in renderChunk, on final emitted code, and computes the relative path from that specific output file's depth.
  • The rewrite inside copyExternalPackages() now walks blessed's own tree as well as blessed-contrib's, which covers vendor/tng.js.
  • Both share two small helpers. newBareBlessedRequireRegExp() hands back a fresh regexp per call, because socketModifyPlugin advances lastIndex and a shared instance would let chunks skip each other's matches. relativeBlessedPath() falls back to '.' so a file sitting in blessed's own root cannot produce an absolute-looking specifier.
The four files, and the new guard

Scanning the shipped 1.1.162 tarball turns up eight bare requires across four files:

external/blessed/vendor/tng.js                    blessed/lib/colors
external/blessed-contrib/lib/widget/table.js      blessed/lib/widgets/box
external/blessed-contrib/lib/widget/table.js      blessed/lib/widgets/list
external/blessed-contrib/lib/widget/table.js      blessed/lib/widgets/node
external/blessed-contrib/lib/widget/charts/bar.js blessed/lib/widgets/node
external/blessed-contrib/lib/widget/charts/bar.js blessed/lib/widgets/box
external/blessed-contrib/lib/widget/charts/line.js blessed/lib/widgets/box
external/blessed-contrib/lib/widget/charts/line.js blessed/lib/widgets/node

test/external-bare-requires.test.mts walks the built external/ tree and fails on any bare require of a package the build vendors there. Run against 1.1.162 its rule reports exactly those eight and nothing else.

It is deliberately scoped to the three names in EXTERNAL_PACKAGES rather than to "any undeclared package". Widening it that far also picks up braces, micromatch, picomatch and node-gyp under external/@socketsecurity/registry/, plus pty.js and term.js from blessed's optional terminal widget. Those are longstanding and unrelated to this crash, and they deserve their own look rather than being swept in here.

Known gap

The crash tears down the alt screen without restoring terminal state, leaving a one-line scroll region and the tty in raw mode until the user runs reset. That is a separate cleanup bug that outlives any require-path fix, and it is not addressed here.

Refs SURF-1445, SURF-1639.


Note

Medium Risk
Changes published packaging and module resolution for vendored TUI dependencies; impact is broad for blessed-based commands but scoped to build output with a regression test.

Overview
Fixes runtime Cannot find module 'blessed/…' crashes when TUI commands (e.g. threat-feed) render tables, because vendored files under external/ still used bare blessed specifiers that Node cannot resolve there.

The dist build now rewrites those requires in two places that actually stick: copyExternalPackages() walks blessed as well as blessed-contrib, and the blessed-contrib Rollup bundle gets a socketModifyPlugin so emitted require('blessed/…') become relative paths at write time instead of being overwritten by a later bundle pass. Shared helpers newBareBlessedRequireRegExp() and relativeBlessedPath() avoid regexp lastIndex bugs and edge cases at blessed’s root.

Adds test/external-bare-requires.test.mts, which fails if built external/**/*.js still bare-requires blessed, blessed-contrib, or @socketsecurity/registry.

Reviewed by Cursor Bugbot for commit 668365a. Configure here.

The published package shipped bare require('blessed/...') calls in
external/blessed-contrib/lib/widget/{table,charts/bar,charts/line}.js and
external/blessed/vendor/tng.js. Node resolves bare specifiers only through
node_modules, and external/ is not one, so `socket threat-feed` crashed on
launch with "Cannot find module 'blessed/lib/widgets/box'". `socket
analytics` and `socket audit-log` load the same widgets.

copyExternalPackages() already rewired those requires, but it runs in the
first config's writeBundle, and the blessed-contrib configs then bundle
back out over the same files with 'blessed' marked external. Give that
bundle its own rewrite so the emitted requires are relative when written,
and widen the copy pass to blessed's own tree so vendor/tng.js is covered.

Adds a test over the built external/ tree, scoped to the packages the
build vendors there.

Refs SURF-1445, SURF-1639
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant