💥 Modernize the toolchain and ship dual ESM + CJS - #13
Merged
Merged
Conversation
The .npmignore denylist could not enumerate files that did not exist when it was written, so @tselect/http-method@1.0.0 shipped .nyc_output/ (5 files), .travis.yml and tslint.json alongside dist/. Replace it with a "files" allowlist, which cannot fail open. Also fixes prepare: "tsc", which was a silent no-op under noEmit: true — a clean checkout packed whatever dist/ happened to be lying around. prepack now runs the real build.
Replaces package-lock.json with pnpm-lock.yaml and pins packageManager to pnpm@11.21.0. pnpm settings live in pnpm-workspace.yaml — pnpm 11 ignores pnpm.onlyBuiltDependencies in package.json. Resolving the existing ranges against already-published patches drops the audit from 16 vulnerabilities (2 low, 4 moderate, 10 high) to 3 (2 moderate, 1 high). The remaining three are all in the nyc tree and go away with mocha. Note: the suite could not be run before or after this change — ts-node@9 with Node 24 rejects the angle-bracket cast in test/setup.ts. Pre-existing, fixed by the Vitest migration.
Replaces the tsc-to-CommonJS build with tsdown, emitting both formats plus separate .d.mts and .d.cts declarations behind an exports map. The TypeScript 7 upgrade is folded into this commit rather than kept separate because the two cannot be sequenced apart: tsdown's peer range is ^5 || ^6 || ^7, so it cannot run on the previous typescript@4.9.5, while TypeScript 7 removes moduleResolution node10 (TS5108) and so cannot keep the old config. Whichever landed first would leave the tree unbuildable. src/index.ts now re-exports THTTPMethod with 'export type'. It is a type, and a bundler compiling modules in isolation cannot know that from a value export — rolldown failed the build with MISSING_EXPORT. No effect on consumers; the symbol was never present at runtime. tsconfig drops declaration, outDir, inlineSourceMap and removeComments (tsdown owns emit now), moves to moduleResolution bundler, and gains skipLibCheck. @types/node 12.7.0 -> 22.20.1.
Ports the 7 existing tests one-for-one — same describe/it structure, chai's to.equal becoming toBe — so the assertion count is unchanged at 7. Drops test/setup.ts, test/mocha.opts and test/global.d.ts, whose only job was wiring chai's expect onto the global object. Removes mocha, chai, nyc, ts-node, @types/mocha and @types/chai, plus typedoc, which was declared but never referenced by any script. Deleting those trees takes pnpm audit from 3 vulnerabilities to 0 by subtraction rather than by audit fix. tsconfig now includes test/ as well as src/, which is what makes skipLibCheck necessary — without it tsc typechecks Vitest's own declarations. Coverage is 100% of statements, functions and lines, over 0 branches; the source has no conditionals. Threshold set to the shared floor of 95.
toPascalCase was the only consumer, calling Lodash.capitalize once. Per the workspace dependency-removal policy a dep used for a single helper is vendored rather than carried, which takes this package to zero runtime dependencies. This also fixes a bug the dual-format build exposed: lodash assigns its exports dynamically, so cjs-module-lexer cannot detect them for CJS-to-ESM interop and 'import * as Lodash' resolved to a namespace with no capitalize. The ESM entry threw 'Lodash.capitalize is not a function' on every toPascalCase call while the CJS entry worked, which is why the previous CommonJS-only build never surfaced it. The replacement was differential-tested against lodash.capitalize across 36 inputs (every method in lower, upper, pascal and two mixed cases, plus the empty string) with zero mismatches before lodash was removed. 12 of those are pinned as regression tests here; the rest exercise inputs the signature does not admit. Tests: 7 -> 19.
HTTPMethod.PUT has always existed, but THTTPMethod stopped at 'head' and never listed 'put' or 'PUT'. Every function in the package takes THTTPMethod, so toLowerCase(HTTPMethod.PUT) — passing the package's own enum to the package's own function — did not compile. The runtime behaviour was always correct; only the type disagreed. Widening the union also widens the return type of toLowerCase and toConstantCase, so an exhaustive switch over their result now needs a 'put' arm. That is a compile-time break, which this release can absorb: engines.node >=22 already makes it a major. Guarded against recurrence by asserting HTTPMethod is assignable to THTTPMethod at compile time. Verified non-vacuous: restoring the old union fails typecheck with 7 errors. Tests: 19 -> 30.
TSLint has been end-of-life since 2019. Biome covers lint and format in one tool, matching url and thrown. Config uses preset: "recommended" rather than the recommended: true spelling, which Biome 2.5.8 deprecates and warns on for every run. url still carries the old spelling and should be corrected when it is next touched. All five findings were mechanical, with no public API decisions of the kind thrown's Biome step forced: useImportType on the two remaining value-imports of THTTPMethod, a missing trailing comma in the enum, and export ordering in src/index.ts. Reordering the re-exports changes the order of the export statements only — the exported names are unchanged, verified against the recorded baseline. Tests and typecheck still pass; no source behaviour changed.
The repo declared "license": "MIT" in package.json but carried no LICENSE file, so the published package asserted a licence whose text it never shipped. README restructured to the workspace order — title, badges, description, Requirements, Installation, Usage, API, License — with url's README as the reference. Badges gain CI and coverage; the title becomes the package name. The API section is new: every export is documented, including THTTPMethod, which was not mentioned at all before. keywords was empty, so the package was effectively unfindable on npm. All 21 runnable README examples were executed against the built dist and matched. The type-level examples were typechecked from a scratch consumer resolving through the exports map, which also confirms the documented claim that every function accepts the enum as well as either literal case. The Requirements section's "tested on 22, 24 and 26" matches the reusable workflow's default node-versions matrix.
Adds the standard four-line caller pinned at @v1. No inputs are overridden: es-check-target's es2015 default is already this package's emitted-syntax ceiling, unlike thrown which has to say es2016 out loud. Declares engines.node >=22, the bottom of the CI matrix, so the supported floor is proven by construction rather than asserted. This is what makes the next release a major. Verified locally against what CI will run: lint, typecheck, cov and build all pass, es-check confirms the emitted ESM and CJS both parse as ES2015, and both declaration files are present and non-empty. The .travis.yml this replaces was deleted earlier in the branch; it pinned Node 8.6.0 and 12.8.1 and had not run since the repo moved to the tselect org.
Publishing runs from CI over npm trusted publishing (OIDC), never from a laptop. This is the thin caller for the reusable release workflow in `tselect-npm/.github`, pinned at `@v1` like ci.yml. The filename is load-bearing: npm's trusted publisher config validates the *caller's* workflow filename, so renaming this file breaks publishing with a 404 that says nothing about filenames. `id-token: write` is declared here as well as in the reusable workflow — a called workflow can only narrow the permissions it is given, so omitting it would produce a job with no OIDC token.
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.
Third repo through the modernization runbook in the workspace
CHECKLIST.md, afterurl(the pilot) andthrown. pnpm · tsdown (dual ESM + CJS) · Vitest · Biome ·TypeScript 7 · shared CI from
tselect-npm/.github@v1.The title is
:boom:deliberately. Squash is the only merge method the rulesetsallow, so this PR collapses to one commit and that commit's gitmoji is the only one
the release workflow reads.
:wrench:would infer as no release at all. The branch'sseries is preserved below, since this is the only place it survives.
Why this is a major
Three independent reasons:
engines.nodeis now">=22". It is the bottom of the CI matrix, so the flooris tested by construction.
>=20is not viable: pnpm 11 declaresnode >=22.13anddies on Node 20 before any test runs.
THTTPMethodgains'put' | 'PUT'. This widens the return type oftoLowerCaseand
toConstantCase, so exhaustive switches over their result stop compiling.exportsmap closes deep imports.gh search codefound no consumers of@tselect/http-method/dist, but it is a break in principle.Two behaviour-relevant fixes, both verified rather than argued
toPascalCasewas broken under ESM. lodash assigns its exports dynamically, socjs-module-lexercannot detect them for CJS→ESM interop:import * as Lodash from 'lodash'produced a namespace with nocapitalize, and the ESM entry threwLodash.capitalize is not a functionon every call — while the CJS entry workedperfectly. Nothing was broken in the field, since 1.0.0 is CommonJS-only, but shipping
dual-format without this fix would have broken every ESM consumer. Vendoring the one
helper drops lodash and takes the package to zero runtime dependencies; the vendored
body matched
lodash.capitalizeon 36 inputs before lodash was removed.THTTPMethodnever listedput, sotoLowerCase(HTTPMethod.PUT)— the package'sown enum passed to the package's own function — did not compile. Runtime was always
correct. A compile-time assertion that
HTTPMethodis assignable toTHTTPMethodnowguards it, and was checked non-vacuous by restoring the old union and confirming it
produces 7 typecheck errors.
Also fixed: the published tarball shipped test, coverage and config cruft.
.npmignoreis gone in favour of
"files": ["dist"]— an allowlist cannot fail open.Verification
The whole diff was differential-tested against the published
1.0.0tarball: 176comparisons (44 inputs × 4 functions) with 0 mismatches, plus identical enum
contents and identical export names.
toPascalCase's vendored body is the only intendedruntime change.
Re-verified from a clean clone of this branch:
pnpm audit→ 0 vulnerabilitiesattwclean on all four resolution modes;es-check es2015passes on both entriesdist/index.d.mtsanddist/index.d.ctsboth present and non-emptyrequire()andimportagree on all five runtime exportsNot in this PR
release. The version in
package.jsonstays1.0.0and the major goes out later.ci-requiredruleset and thenpm-publishenvironment, which this repo doesnot yet have. Both belong to the separate scripted provisioning pass.
The
@bluejay-era tags (1.0.0,v2.0.0,v2.0.1,v2.0.2,v3.0.0-beta.0) havebeen removed and
v1.0.0recreated atb86f554— the commit the registry records as@tselect/http-method@1.0.0'sgitHead— so the release workflow's commit rangeresolves on the first try.
masterwas renamed tomainas part of this work.