Skip to content

feat: adopt string_decoder as @unabandoned/string_decoder - #1

Merged
00o-sh merged 2 commits into
mainfrom
claude/string-decoder-fork-68llfg
Aug 15, 2026
Merged

feat: adopt string_decoder as @unabandoned/string_decoder#1
00o-sh merged 2 commits into
mainfrom
claude/string-decoder-fork-68llfg

Conversation

@00o-sh

@00o-sh 00o-sh commented Aug 15, 2026

Copy link
Copy Markdown

What & why

Brings the browserify string_decoder shim into the unabandoned program.

Upstream nodejs/string_decoder has been frozen at 1.3.0 since 2019 while still shipping the abandoned safe-buffer runtime dependency — abandoned + outdated dep, so the prime directive says fork and own it. It is a direct dependency of @unabandoned/browserify (string_decoder: ^1.1.1), the browser shim browserify injects for require('string_decoder'), so the tree runs through us either way.

Standard onboarding

  • Scoped to @unabandoned/string_decoder; repository/bugs/homepage repointed at this fork.
  • engines.node set to >=22.12.
  • Thin reusable-workflow callers for ci / codeql / commitlint / publish / release-cut / release-please / release-security / renovate-config / scorecard, all pinned to unabandoned/.github@d493a18 (v1.0.0) — byte-identical to the readable-stream callers apart from the Node matrix ([22, 24]).
  • renovate.json extending github>unabandoned/renovate-config with forkProcessing: enabled.
  • release-please manifest (1.3.0) + config; commitlint config + commit-msg githook.
  • .unabandoned.yml added (used-by: @unabandoned/browserify), validated against scripts/validate_metadata.py.
  • Removed .travis.yml (a Node 0.8–9 matrix) and the build/ node-core sync script, which is obsolete now that this fork has deliberately diverged from the upstream snapshot.

The safe-buffer decision: dropped, not kept

[email protected] depended only on safe-buffer, which is itself frozen with zero runtime deps of its own — so it was leave-alone-eligible as a clean leaf. I dropped it anyway, because in this case the dependency turned out to be pure legacy shimming rather than a real leaf.

build/files.js in the upstream repo spells it out: the sync script's job was rewriting node core's require('buffer').Buffer into require('safe-buffer').Buffer, so the package could run on Node < 4.5 where Buffer.from/Buffer.allocUnsafe did not yet exist. At engines.node >=22.12 that shim is dead weight, and removing it is just restoring the node-core original:

var Buffer = require('buffer').Buffer;

The lib uses exactly two things off BufferallocUnsafe and isEncoding — both native since Node 4.5 and both present on the browser buffer package (verified against @unabandoned/buffer, which browserify maps buffer to). So this is behaviour-preserving in Node and in the browser.

The result is a package with no runtime dependencies at all, which is a strictly cleaner tree than keeping a frozen unowned leaf. safe-buffer itself is not forked — there is nothing under it to rot.

I verified the swap rather than assuming it: the pre-existing upstream suite was run against the original safe-buffer lib (green), then against the require('buffer') lib (green), before any test conversion happened.

Test suite

Converted to node:test/node:assert. This drops tap@~0.4.8 and the vendored 30 kB node-core test/common harness, which pulled in babel-polyfill, core-util-is and inherits. All upstream assertions are preserved, including the exhaustive writeSequences() split-every-possible-way coverage; expected strings are kept as \u escapes since most of them are replacement characters, combining marks and lone surrogates.

test/verify-dependencies.js (asserted no ^ ranges) is removed — there are no runtime dependencies left to check, and Renovate owns ranges now.

The dev tree is now just commitlint.

Type of change

  • fix / feat / perf — consumer-facing
  • deps — dependency update (add the security label if it fixes a CVE)
  • chore / ci / build / docs / test / refactor — maintenance

Titled feat: so release-please cuts 1.4.0 — staying inside 1.x deliberately, so @unabandoned/browserify can repoint at ^1. This matches how readable-stream (4.7.0 → 4.8.0) and browserify (17.0.x → 17.1.0) were onboarded: keep the upstream major, express the engines change as a minor.

Checklist

  • Commits follow Conventional Commits (commitlint passes)
  • npm test passes on Node 20 / 22 / 24 — see note below
  • No new runtime dependency (or its addition is justified below)
  • Dev tree kept lean (prefer built-in node:test over new runners)

On the Node matrix: this fork declares engines.node >=22.12 and its CI matrix is [22, 24], matching readable-stream. Node 20 is deliberately not covered. Verified locally as 17/17 passing on Node 22.22.2 and Node 24.19.0.

Verified against the packed artifact, not just the working tree

npm pack produces 4 files (LICENSE, README.md, lib/string_decoder.js, package.json). Installing that tarball into an empty project gives:

— one package, no transitive deps, safe-buffer gone. A smoke test driving a split multi-byte character and a split UTF-16 surrogate pair through the installed artifact passes on both Node 22 and Node 24.

I also sanity-checked that the converted suite actually bites: mutating utf8CheckIncomplete in the lib turns 5 of the 17 tests red.

Follow-up

@unabandoned/browserify needs its string_decoder dependency repointed at this fork. That PR is open at unabandoned/browserify#59 and cannot go green until this one is merged and published@unabandoned/string_decoder currently 404s on the registry, so browserify's npm install can't resolve it. Merge and publish order is this PR first.


Generated by Claude Code

claude added 2 commits August 15, 2026 00:15
Bring the browserify string_decoder shim into the unabandoned program.
Upstream has been frozen at 1.3.0 since 2019 while still shipping the
abandoned safe-buffer runtime dependency, so nothing was keeping its tree
current for the bundlers that pull it in transitively.

Scope the package to @unabandoned, point the repository/bugs/homepage URLs
at this fork, and set engines.node to >=22.12.

Drop safe-buffer entirely. Node core reads Buffer off the `buffer` module;
upstream's build script rewrote that line to require('safe-buffer') so the
package could run on Node < 4.5, where Buffer.from/allocUnsafe did not exist.
At >=22.12 the shim is dead weight, so the line is restored to the core
original and the package now has no runtime dependencies at all. Bundlers map
`buffer` to their browser Buffer implementation, which supplies the same
allocUnsafe/isEncoding, so browser builds are unaffected.

Convert the suite to node:test/node:assert, dropping tap and the vendored
node-core test harness that pulled in babel-polyfill, core-util-is and
inherits. The dev tree is now just commitlint.

Add the thin reusable-workflow callers, renovate.json, release-please
manifest and config, commitlint config with the commit-msg githook, and
.unabandoned.yml. Remove .travis.yml and the node-core sync build script,
which is obsolete now that the fork has diverged from the upstream snapshot.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011iUzLQPzUXArm2A27Qy97e
The comment explaining why safe-buffer is gone spelled the old line out as a
literal require call, which reads as a live dependency to naive greps and
dependency scanners even though nothing imports it. Reword both the lib
comment and the README to describe the old shim without the call syntax.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_011iUzLQPzUXArm2A27Qy97e
@00o-sh
00o-sh merged commit c0bb5f1 into main Aug 15, 2026
7 checks passed
@00o-sh
00o-sh deleted the claude/string-decoder-fork-68llfg branch August 15, 2026 00:24
00o-sh added a commit that referenced this pull request Aug 15, 2026
🤖 I have created a release *beep* *boop*
---


##
[1.4.0](string_decoder-v1.3.0...string_decoder-v1.4.0)
(2026-08-15)


### Features

* adopt string_decoder as @unabandoned/string_decoder
([#1](#1))
([c0bb5f1](c0bb5f1))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
00o-sh added a commit to unabandoned/browserify that referenced this pull request Aug 15, 2026
## What & why

`string_decoder` is the browser shim browserify injects for
`require('string_decoder')` (`lib/builtins.js`). Upstream
[`nodejs/string_decoder`](https://github.com/nodejs/string_decoder) has
been frozen at 1.3.0 since 2019 while still carrying the abandoned
`safe-buffer` runtime dependency, so it has been adopted as
`@unabandoned/string_decoder` in unabandoned/string_decoder#1.

This aliases browserify's dependency to the fork:

```diff
-    "string_decoder": "^1.1.1",
+    "string_decoder": "npm:@unabandoned/string_decoder@^1",
```

The fork drops `safe-buffer` entirely — `Buffer` comes straight off the
`buffer` module again, exactly as in node core — so this also removes
`safe-buffer` from browserify's own shipped tree.

## ⚠️ Merge order — this PR cannot go green yet

`@unabandoned/string_decoder` is **not on the registry yet** (`npm view`
404s), so CI's `npm install` cannot resolve the alias until
unabandoned/string_decoder#1 is merged **and published**. Expect this
PR's CI to be red until then; it is not a defect in this diff.

Order is: merge unabandoned/string_decoder#1 → publish → re-run CI here.

## How this was verified locally

Since the fork is unpublished, I reproduced exactly what the alias will
install: `npm pack`ed the fork and unpacked that tarball over
`node_modules/string_decoder`, which is precisely what
`npm:@unabandoned/string_decoder@^1` resolves to on disk.

- `require.resolve('string_decoder/')` — what `lib/builtins.js` uses —
resolves to the fork, and the installed manifest reports
`@unabandoned/[email protected]` with `deps={}`.
- **Full suite, unchanged from baseline on both Node 22.22.2 and Node
24.19.0:** `222 tests / 221 pass / 0 fail / 1 skipped` — identical to
the pre-repoint baseline, so no `b.bundle()` regressed.
- **Explicit bundle check:** built a real bundle whose entry drives a
split multi-byte character (`0xE2 0x82` then `0xAC`) and a split UTF-16
surrogate pair through `StringDecoder`, ran the bundle in a `vm`
sandbox, and asserted the output is `€👍`. Also asserted the fork's lib
is what got packed and that no `safe-buffer` module is present in the
bundle (comments stripped first — the fork's lib carries a comment that
mentions safe-buffer by name).

## Scope note

Only browserify's own direct dependency is repointed.
`node_modules/browserify-sign/node_modules/string_decoder` and
`node_modules/ripemd160/node_modules/string_decoder` still exist, pulled
in by those packages' own `readable-stream@2/3` trees under
`crypto-browserify`. They are untouched here and remain a separate
thread to pull.

## Type of change

- [ ] `fix` / `feat` / `perf` — consumer-facing
- [x] `deps` — dependency update (add the `security` label if it fixes a
CVE)
- [ ] `chore` / `ci` / `build` / `docs` / `test` / `refactor` —
maintenance

Titled `build(deps):` — a dependency repoint, not a feature. No CVE, so
no `security` label and no release fast-path.

## Checklist

- [x] Commits follow Conventional Commits (`commitlint` passes)
- [x] `npm test` passes on Node 20 / 22 / 24 — verified on **22 and
24**, which is this repo's CI matrix (`node-versions: '[22, 24]'`,
`engines.node >=22.12`). Node 20 is deliberately not covered.
- [x] No new runtime dependency (or its addition is justified below) —
this is a repoint, and it *removes* `safe-buffer` from the shipped tree.
- [x] Dev tree kept lean (prefer built-in `node:test` over new runners)

---
_Generated by [Claude
Code](https://claude.ai/code/session_011iUzLQPzUXArm2A27Qy97e)_

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants