| name | knip-migration |
| description | Dead-code / unused-dependency detection is migrating from the legacy `unimported` tool to
`knip`, which requires each package to expose an explicit, minimal `package.json#exports`
(no `./*` wildcard). Read this when adding a new package or migrating an existing one.
|
Dead-code detection: explicit exports + knip (not unimported)
The repo is migrating dead-code / unused-dependency detection from the legacy unimported
tool to knip, one package at a time. The root knip.json holds only
the shared rules — a package needs no workspaces entry there, since knip derives its entry files
from package.json (main, bin, exports) and its own defaults
(entry files).
Why this isn't just "swap the tool"
knip treats everything listed in a package's package.json#exports as an entry point.
Today every package exports a ./* wildcard mapped to ./src/*.ts (via the @ledgerhq/source
custom condition declared in tsconfig.base.json):
"./*": {
"@ledgerhq/source": "./src/*.ts",
"import": "./lib-es/*.js",
"require": "./lib/*.js",
"default": "./lib/*.js"
}
Because of this wildcard, knip considers every top-level src/*.ts file "used" and cannot
detect unused ("zombie") top-level source files — the exact thing unimported caught (it
ignored exports and diffed against a curated entry list). So real knip parity for a package
requires replacing the ./* wildcard with explicit, minimal subpath exports that enumerate
the package's true public API.
The few libs already pointed at knip (e.g. libs/env, libs/promise) still keep the ./*
wildcard, so they are only partially migrated — do not copy them as the template.
New package = born migrated
A new package has no consumers, so it should start in the target state — no .unimportedrc.json:
- Explicit
exports only. Enumerate the real public API; do not add a ./* wildcard.
Mirror the . root entry per subpath, keeping the conditions:
"exports": {
".": { "@ledgerhq/source": "./src/index.ts", "import": "./lib-es/index.js", "require": "./lib/index.js", "default": "./lib/index.js" },
"./logic": { "@ledgerhq/source": "./src/logic.ts", "import": "./lib-es/logic.js", "require": "./lib/logic.js", "default": "./lib/logic.js" },
"./lib-es/*": "./lib-es/*.js",
"./lib/*": "./lib/*.js",
Migrating an existing package off unimported
- Audit the real consumers of the package's deep imports.
- Replace the
./* wildcard export with explicit subpath exports (the legit public API).
- Refactor consumers that relied on now-removed arbitrary entry points.
- Switch the dead-code script
unimported → knip, delete .unimportedrc.json, and verify
a deliberately-unused top-level src file is now reported.
Start with leaf / low-dependency packages; ledger-live-common (largest surface) comes last.
Notes
unimported stays until a package reaches knip parity — don't bulk-delete it.
- Unifying the script / nx-target name (
unimported → knip-check) is tracked separately, so
matching the surrounding package's existing script name is fine.
Reviewing
For a new package, flag either of these and point here:
- a
.unimportedrc.json or a script running the bare unimported binary
("unimported": "unimported") — it must run knip instead; or
- a
./* wildcard in package.json#exports — new packages must enumerate explicit exports
so knip can detect zombie files.
The workspaces block in knip.json is a temporary workaround
Everything must work without touching knip.json. The workspaces entries that remain are
band-aids for packages that don't declare their surface properly yet — deep imports behind a ./*
wildcard, runtime entries invisible to package.json (Electron preloads, web workers), or
dependencies knip can't resolve. Each one hides a package that hasn't finished the migration above.
So: don't grow this file. Fixing the package's exports is the real fix, and it lets the
corresponding workspaces entry be deleted. If you truly cannot avoid an entry, keep it to the
smallest possible delta and remember a configured entry replaces knip's default patterns
rather than extending them.