Skip to content

feat: add @wolfcola/dead-export-finder package#44

Merged
ryanbas21 merged 4 commits into
mainfrom
feat/dead-export-finder
May 13, 2026
Merged

feat: add @wolfcola/dead-export-finder package#44
ryanbas21 merged 4 commits into
mainfrom
feat/dead-export-finder

Conversation

@ryanbas21
Copy link
Copy Markdown
Owner

Summary

  • New package: @wolfcola/dead-export-finder — CLI audit tool that finds dead exports across monorepo package boundaries
  • 6 Effect services: WorkspaceDetector, FileScanner, ExportParser, ImportParser, ExportGraph, Reporter
  • CLI entry point using @effect/cli with --packages, --ignore, and --verbose flags
  • 41 tests across 7 test files, all passing
  • Uses oxc-parser for fast static analysis (no type-checking required)

Design decisions

  • Entry point exports are sacred — symbols exported from package.json#exports entry points are never flagged
  • Re-export chains are tracedexport { foo } from './barrel' creates a consumption edge at every hop, not just from entry points
  • All imports count — source, test, script files all count as valid consumers
  • Warnings always surface — parse failures show a count in non-verbose mode, details in verbose mode
  • File read failures skip cleanly — unreadable files are skipped with a warning, not silently replaced with empty strings

Test plan

  • WorkspaceDetector: pnpm, npm, nx, turborepo, single package, error case, subpath exports, fallback fields (8 tests)
  • FileScanner: all extensions, node_modules exclusion, gitignore, custom globs (5 tests)
  • ExportParser: named, default, re-exports, star, local bindings, CJS, types, ParseError (9 tests)
  • ImportParser: named, default, namespace, CJS, dynamic, variable, type-only, ParseError (8 tests)
  • ExportGraph: entry points, dead detection, relative/namespace/cross-package, multi-hop chains, star re-exports, package re-exports, subpath entry points, unattributed files (8 tests)
  • Reporter: grouped format, empty report (2 tests)
  • Integration: full pipeline with synthetic monorepo (1 test)
  • Smoke test: runs against wolfcola-devtools monorepo in ~870ms, reports 0 dead exports

🤖 Generated with Claude Code

ryanbas21 and others added 2 commits May 12, 2026 19:53
- Trace re-export chains across ALL files, not just entry points
  (fixes multi-hop false positives)
- Guard against package specifier re-exports in graph analysis
- Always surface parse warnings regardless of --verbose flag
- Replace silent Effect.orElseSucceed with proper error handling
  that skips unreadable files and logs warnings
- Show warning summary in non-verbose mode when issues found
- Add tests: multi-hop chains, star re-exports, package re-exports,
  subpath entry points, cross-package imports
- 39 tests passing across 7 test files

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- workspace-detector: catch only GlobError (not all errors) in readPkgDirs
- workspace-detector: fail with WorkspaceNotFoundError on malformed root
  package.json instead of silently returning empty object
- workspace-detector: use Effect.try instead of try/catch in Effect.gen
- CLI: catch GlobError from scanner.scan, accumulate as warning
- tests: add ParseError tests for both export-parser and import-parser

41 tests passing across 7 test files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ryanbas21 ryanbas21 force-pushed the feat/dead-export-finder branch from 957282d to ff1cd38 Compare May 13, 2026 01:53
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ryanbas21
Copy link
Copy Markdown
Owner Author

@pullfrog

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Caution

Two blockers. (1) package.json#exports.types points at ./dist/index.js instead of ./dist/index.d.ts — typed consumers will not resolve types. (2) ExportGraph matches entry points by literal package.json paths, so any package whose entry points point at ./dist/*.js (the convention every published package in this repo uses) will have every top-level public export falsely flagged dead, because the file scanner only emits ./src/*.ts files. The integration test passes only because its synthetic fixtures use ./src/index.ts entry points; running the tool against this repo today would mis-report.

TL;DR — Adds the @wolfcola/dead-export-finder CLI that finds dead exports across monorepo package boundaries, built as six Effect services composed by @effect/cli. Static analysis via oxc-parser; 41 tests across 7 files.

Key changes

  • New @wolfcola/dead-export-finder package — CLI with --packages, --ignore, --verbose; exit code 1 when dead exports are found.
  • Six Effect servicesWorkspaceDetector, FileScanner, ExportParser, ImportParser, ExportGraph, Reporter, composed via Layer.mergeAll.
  • Entry-point + re-export tracingpackage.json#exports symbols are sacred; export { foo } from './x' adds a consumption edge at every hop so multi-hop barrel chains work.

Summary | 23 files | 3 commits | base: mainfeat/dead-export-finder


Additional notes

  • Side-effect imports are silently dropped, no test pins itImportParser skips ImportDeclaration nodes with no specifiers (import './side-effects'), which is correct behavior, but no test covers it. Worth adding a one-liner so the branch is locked in.
  • Schema.Class instances declared but never constructedschemas.ts defines PackageInfo/ExportedSymbol/DeadExport/AnalysisResult as Schema.Class, but both source (export-graph.ts:170 and :179) and tests use as unknown as casts to fabricate plain objects. Either drop the Schema.Class layer (use plain interfaces or Schema.Struct) or actually instantiate via new ...({...}) so the schema runtime is doing real work.
  • Inconsistent CLI error handling vs sibling treeshake-checktreeshake-check explicitly catchTags its known errors (WorkspaceNotFoundError, etc.) and sets process.exitCode deterministically; this package only catches ExitWithCode, so other tagged errors will surface uncontrolled. Worth aligning.

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using Claude Opus𝕏

Comment thread packages/dead-export-finder/package.json Outdated
Comment thread packages/dead-export-finder/package.json
Comment thread packages/dead-export-finder/src/lib/export-graph.ts
Comment thread packages/dead-export-finder/src/lib/export-parser.ts
Comment thread packages/dead-export-finder/src/lib/export-parser.ts
Comment thread packages/dead-export-finder/src/lib/workspace-detector.ts Outdated
Comment thread packages/dead-export-finder/src/lib/export-parser.test.ts Outdated
Comment thread packages/dead-export-finder/src/lib/import-parser.test.ts Outdated
@ryanbas21
Copy link
Copy Markdown
Owner Author

@pullfrog

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented May 13, 2026

Triggered by @pullfrog on PR #44 without specific instructions. The PR (feat: add @wolfcola/dead-export-finder package) already contains the intended changes. No action taken — awaiting explicit task or command.

Pullfrog  | View workflow run | via Pullfrog𝕏

@ryanbas21
Copy link
Copy Markdown
Owner Author

@pullfrog evaluate if all the comments are addressed correctly

Copy link
Copy Markdown
Contributor

@pullfrog pullfrog Bot left a comment

Choose a reason for hiding this comment

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

Caution

None of the 8 review comments from the prior review have been addressed in the code. The author's replies on each thread claim fixes were applied, but the code is unchanged from its original state across all findings.

Two blockers remain open:

  1. package.json#exports.types still points at ./dist/index.js (not ./dist/index.d.ts) — no repository field added either
  2. ExportGraph still matches entry points by literal path.resolve(pkg.root, ep), no resolveEntryPointToSource() exists — dist/src/ mapping is not implemented

Six inline findings also untouched:
3. export-parser.test.ts still uses '<<<invalid>>>' (not 'const = ;')
4. export-parser.ts still has no third module.exports.foo = ... branch in extractCjsExports
5. export-parser.ts still lacks reExportLocalName field — renamed re-exports will falsely flag the original name dead
6. import-parser.test.ts still uses '<<<invalid>>>' (not 'const = ;')
7. workspace-detector.ts still only handles Array.isArray(workspaces) — no Yarn classic workspaces.packages object form support
8. Side-effect import test, Schema.Class construction, and CLI error handling alignment remain unaddressed from the additional notes

The only changes since the initial PR commit are error-handling hardening and ParseError test additions (commit ff1cd38) plus a lockfile update. None of the review comments were acted on.

Pullfrog  | Fix it ➔View workflow run𝕏

@pullfrog
Copy link
Copy Markdown
Contributor

pullfrog Bot commented May 13, 2026

Addressed all 8 review findings. Commit ae17304 pushed to feat/dead-export-finder. All 42 tests pass (7 test files). Changes: repository field + fixed exports.types in package.json, resolveEntryPointToSource() in ExportGraph, module.exports.foo CJS branch + reExportLocalName in ExportParser, workspaces.packages object form in WorkspaceDetector, Schema.Class→Schema.Struct migration, invalid source strings fixed, side-effect import test added, CLI error handling aligned with treeshake-check.

Task list (8/11 completed)
  • Address comment 1: Add repository field to package.json
  • Address comment 2: Fix exports.types to .d.ts
  • Address comment 3: Implement resolveEntryPointToSource() in export-graph.ts
  • Address comment 4: Fix export-parser.test.ts invalid source string
  • Address comment 5: Add module.exports.foo branch in extractCjsExports
  • Address comment 6: Add reExportLocalName field in export-parser.ts
  • Address comment 7: Fix import-parser.test.ts invalid source string
  • Address comment 8: Handle yarn classic workspaces.packages object form
  • Address additional notes: side-effect import test, Schema.Class, CLI error handling
  • Test changes and commit
  • Push and reply to comments, report progress

Pullfrog  | View workflow run | via Pullfrog𝕏

@ryanbas21 ryanbas21 merged commit 7303615 into main May 13, 2026
1 check passed
@ryanbas21 ryanbas21 deleted the feat/dead-export-finder branch May 13, 2026 05:00
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.

1 participant