Fixing a False “TypeScript Types Removed” Event in npmx

작성자

카테고리:

← 피드로
DEV Community · Anil Loutombam · 2026-07-31 개발(SW)

This is a submission for DEV’s Summer Bug Smash: Clear the Lineup powered by Sentry.

Project Overview

npmx is an open-source browser for the npm registry. It helps developers explore package metadata, versions, dependencies, documentation, and other information.

I worked on npmx issue #2791, which reported an incorrect event in the package-version timeline. npmx could say that a package had removed its TypeScript types even though valid declaration files were still included in the published package.

My fix is available in npmx PR #3102.

Bug Fix or Performance Improvement

A package can expose TypeScript declarations explicitly through fields such as types or typings. It can also provide declarations implicitly by placing them beside its JavaScript entry points.

For example:

dist/index.mjs
dist/index.d.mts

Enter fullscreen mode Exit fullscreen mode

TypeScript can use dist/index.d.mts even when package.json does not contain a top-level types field.

npmx already understood this pattern on its package-analysis page. However, the timeline endpoint used hasBuiltInTypes(), which only checked package metadata.

This caused the two parts of npmx to disagree:

  • The package page correctly showed “Types included.”
  • The timeline incorrectly showed “TypeScript types removed.”

Code

The fix keeps the fast metadata check as the first step. It performs the more expensive published-file analysis only for visible versions that could be part of a type-removal sequence.

The latest implementation selects every untyped version that has an older typed version somewhere earlier in the package’s history:

const possibleTypeRemovals = visibleVersions.filter(version => {
  if (version.hasTypes) return false

  const versionIndex = allVersions.indexOf(version)
  return allVersions
    .slice(versionIndex + 1)
    .some(previousVersion => previousVersion.hasTypes)
})

Enter fullscreen mode Exit fullscreen mode

Checking the full sequence is important. Consider these versions:

1.0.0 — explicit types field
2.0.0 — colocated .d.mts declaration
3.0.0 — colocated .d.mts declaration

Enter fullscreen mode Exit fullscreen mode

An earlier version of my fix analyzed only 2.0.0, because it was directly beside the metadata transition. The latest version also analyzes 3.0.0 and any other affected version in the sequence.

For each candidate, the timeline fetches the package metadata and published file tree. It then reuses the project’s existing analyzePackage() utility:

const { pkg, typesPackage, files } =
  await fetchPackageWithTypesAndFiles(packageName, version.version)

const analysis = analyzePackage(pkg, {
  typesPackage,
  files,
})

if (analysis.types.kind === 'included') {
  version.hasTypes = true
}

Enter fullscreen mode Exit fullscreen mode

If the additional lookup fails, the endpoint preserves the original metadata-only result instead of failing the complete timeline request.

Tests

I started by running the existing test suite and confirming that all 1,716 tests passed before making changes.

I then added a regression test with three package versions:

  • 1.0.0 declares types explicitly.
  • 2.0.0 publishes index.mjs and index.d.mts.
  • 3.0.0 also publishes index.mjs and index.d.mts.

The test verifies that both implicit versions are analyzed and that all three versions report TypeScript types.

expect(fetchPackageWithTypesAndFilesMock)
  .toHaveBeenCalledWith('my-pkg', '2.0.0')

expect(fetchPackageWithTypesAndFilesMock)
  .toHaveBeenCalledWith('my-pkg', '3.0.0')

expect(result.versions).toHaveLength(3)
expect(result.versions[0]?.hasTypes).toBe(true)
expect(result.versions[1]?.hasTypes).toBe(true)
expect(result.versions[2]?.hasTypes).toBe(true)

Enter fullscreen mode Exit fullscreen mode

I also added a genuine-removal test. In that case, the newer version publishes index.mjs without a matching declaration file, so hasTypes correctly remains unset.

The final validation included:

  • TypeScript type checking
  • Repository lint checks
  • The focused timeline test suite
  • All 82 unit-test files
  • The complete unit-test suite with the new regression coverage
  • GitHub continuous-integration checks

My Improvements

The final change improves the timeline in several ways:

  • It recognizes colocated .d.mts declarations.
  • It reuses the existing package-analysis implementation.
  • It handles consecutive implicit-type versions after a metadata transition.
  • It preserves genuine “types removed” events.
  • It avoids failing the entire timeline when an additional file lookup is unavailable.
  • Its tests verify array length before using optional indexed access.

What I Learned

The main lesson was that package metadata does not always describe everything inside a published npm package. Removing a types field does not necessarily mean that TypeScript support was removed.

I also learned why regression tests should cover a sequence rather than only one transition. Testing just 1.0.0 → 2.0.0 missed the behavior of 3.0.0. Extending the test revealed that every affected version after the transition needed file-aware analysis.

Review feedback also improved the test quality. I replaced non-null assertions on indexed versions with an explicit length assertion and optional access.

Most importantly, I reused npmx’s existing analysis logic instead of implementing another declaration-detection algorithm. This keeps the package page and timeline consistent.

Before and After

Before:

1.0.0 — TypeScript types included
2.0.0 — TypeScript types removed
3.0.0 — TypeScript types not detected

Enter fullscreen mode Exit fullscreen mode

This could appear even when 2.0.0 and 3.0.0 contained valid .d.mts declarations.

After:

1.0.0 — TypeScript types included
2.0.0 — TypeScript types included
3.0.0 — TypeScript types included

Enter fullscreen mode Exit fullscreen mode

A release that genuinely removes both the metadata and the declaration files is still treated as a type removal.

Pull Request

The implementation, tests, and review history are available in npmx PR #3102.

원문에서 계속 ↗

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다