npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@trackunit/migrations

v0.2.76

Published

Coordinator library that upgrades every `@trackunit/*` dependency to its latest published version and runs pending migrations.

Readme

@trackunit/migrations

Coordinator library that upgrades every @trackunit/* dependency to its latest published version and runs pending migrations.

How it works

migrate does two independent things, in this order:

  1. Upgrade dependencies. Every @trackunit/* entry in the consumer's dependencies and devDependencies is moved to its latest published version, resolved from the npm registry. This is unconditional -- a package that ships no codemods still gets bumped -- and the range operator the consumer chose is preserved, so ^1.2.3 becomes ^2.0.0 rather than a pin.
  2. Collect migrations. Each published @trackunit/* package can declare a "migrations" field in its package.json pointing to a migrations.json manifest. The library recursively scans node_modules (including nested/transitive dependencies) to find those packages and queues every migration in their manifests that has not already run.

A migration has an identity, not a version

Nothing in the run compares versions to decide whether a migration applies. A migration has run or it has not, and trackunit-migrations.json records the ones that have, by package:name:

{
  "appliedMigrations": ["@trackunit/react-components:v2-0-0-tag-add-removetaglabel"],
  "migrations": []
}

The manifest that gets read ships inside the version that is installed, so what it declares applies to that version by construction — there is nothing left for a version comparison to decide. This is the same model Flyway and Alembic use, and it removes a whole class of failure that a hand-written version field could not avoid: a tag one release ahead of reality silently withholds a codemod forever, and a tag one release behind silently rewrites source against an API that does not exist yet. Both happened in this repo before the field was removed.

The obligation that replaces it sits with whoever publishes a codemod: a migration must ship in the same release as the API change it migrates, and never earlier. Publishing it early puts it in the manifest of a version it does not apply to, which is the one way this model can still go wrong.

Names are opaque identifiers. The v2-0-0- prefixes are a readability convention matching the directory layout, not something the tool parses. Renaming an entry makes it a different migration and it will run again, so don't.

A manifest entry does still carry a version, and it does exactly one job: it orders a package's codemods, oldest change first, so several codemods touching the same code apply in the sequence they were written for rather than in whatever order the JSON keys happen to sit. It is never a filter. An entry with no version, or one semver cannot read, keeps its manifest position — authoring order is then the best signal available, and a malformed field must not silently reorder anything.

The distinction matters because conflating the two is what the old model got wrong: one field answered both "has this run" and "does this apply", so a tag that was wrong in either direction broke the other question too.

The version bump reads the registry rather than node_modules on purpose. Migrations only exist inside published packages, so gating the bump on discovering a migrations field means a workspace far enough behind can never upgrade: its installed packages predate the field, nothing is discovered, and nothing is bumped.

"Latest published version" means the latest your Node can actually run. Resolution applies the engines.node filter, so a package whose newest release requires a Node you do not have resolves to the newest release you can run instead. That is correct, but silently stopping short reads as the tool failing to upgrade, so the run says when it happened:

  @trackunit/react-components: stopped at 1.10.61; 2.10.21 requires Node >=24.x and this run is on Node 22.14.0

The version named is the one the latest dist-tag points at, because that is what resolution would have picked. A higher release the tag does not point at -- a hotfix from a maintenance branch, or anything deliberately untagged -- was never a candidate on any Node version, so blaming it for the cap would be untrue.

Only a version held back by an unsatisfied engines.node is reported. A newer version passed over for any other reason is not an engine cap, a malformed engines range is treated as "cannot tell" rather than "blocked", and prereleases are never named as the version you are missing. The cap is reported, never worked around: which version gets picked is npm-pick-manifest's decision and it is already the right one.

A package that cannot be resolved is reported and left alone, and the summary says how many were skipped so a partial upgrade is never mistaken for a complete one. If no package resolves, migrate fails instead of reporting success -- that is an environment problem (no network, a proxying or private registry, an expired token), and treating it as "already up to date" would recreate the silent no-op this generator exists to remove.

A package declared in both dependencies and devDependencies is evaluated once per field against that field's own specifier, so a workspace:* devDependency survives an upgrade of the dependencies entry beside it. The registry is still queried once per package name.

One run does the whole job. Migrations live inside published packages, so they are only discoverable once the new versions are on disk — which means discovery has to happen after the install, not before it. When versions change, migrate upgrades package.json, installs, and only then discovers and queues; when nothing needs upgrading there is nothing to install and it all happens in one pass. Either way migrate stops at queueing, so you still review trackunit-migrations.json before run-migrations touches any source.

Two consequences of writing the file after Nx has flushed and locked its file tree. Nx reports UPDATE package.json but not the migrations file, and --dry-run reports the package.json change without discovering migrations, because the install it would need never runs. Nx already warns that a dry run changed nothing.

If the install fails, the run fails. package.json keeps the upgraded versions, because that part succeeded, and no migrations file is written — exiting cleanly there would report a workspace as migrated when discovery never ran. The error names the install command and the re-run.

--skipInstall is the one case that cannot finish in a single run: you have taken responsibility for the install, so the migrations shipped in the new versions are not visible yet. That run says so, writes no migrations file, and names both remaining steps.

Inside the manager monorepo the registry step is skipped -- @trackunit/* libs are workspace projects whose versions are owned by CI -- and migrations are discovered from libs/ and apps/ instead. Collection is otherwise identical; an author can run the codemod they just wrote without waiting for a release.

Then the second generator applies them:

  • run-migrations -- executes the pending migrations from trackunit-migrations.json, records each one that succeeded in appliedMigrations, and clears the list

Only run-migrations records anything. migrate proposes work and carries the applied set through untouched, so a codemod that failed, or that you never ran, is offered again next time.

run-migrations also enforces the record it keeps: an entry whose package:name is already in appliedMigrations is skipped and dropped from the pending list, and an identity listed twice runs once. A queue can name the same codemod twice after a merge, a hand-edit, or the legacy-format fallback that re-queues everything, and re-running is not harmless in general -- a rename that also rewrites an import list, or anything that appends, corrupts source on the second pass, and migrate would not offer it again afterwards to say so.

Both generators treat the workspace root as the root of Nx's file tree, never the directory nx happened to be invoked from. That one root decides where node_modules is scanned, what implementation paths are stored relative to, and where trackunit-migrations.json is read and written. Splitting them would be worse than picking the wrong one, because the halves then disagree while the run still reports success: invoked from apps/foo, discovery would scan apps/foo/node_modules, queue nothing, and write an empty-looking file at the root.

Upgrading from the old format

An earlier version of this file tracked a migratedVersions high-water mark per package. That cannot be translated into identities — it records how far a package got, never which codemods got it there. A workspace still on the old format gets one warning and every migration queued again. Review the list before running it; a codemod whose change is already in place matches nothing and is a no-op.

flowchart TD
  subgraph step1 [Step 1: nx g @trackunit/migrations:migrate]
    scan["Recursively scan node_modules\nfor @trackunit/* packages"]
    scan --> checkField{"package.json\nhas migrations field?"}
    checkField -->|No| skipPkg[Skip package]
    checkField -->|Yes| loadManifest["Load migrations.json"]
    loadManifest --> readState["Read trackunit-migrations.json\nfor appliedMigrations"]
    readState --> filterApplied{"package:name already\nin appliedMigrations?"}
    filterApplied -->|Yes| skipMigration[Skip migration]
    filterApplied -->|No| collect["Add to pending list"]
  end

  subgraph updatePkg [Version Updates: every @trackunit/* dependency]
    listDeps["List @trackunit/* in\nconsumer dependencies +\ndevDependencies"]
    listDeps --> resolveLatest["Resolve latest version\nfrom npm registry"]
    resolveLatest --> compareDeps{"Latest >\npackage.json version?"}
    compareDeps -->|Yes| bumpDep["Rewrite version in\nconsumer package.json,\nkeeping the range operator"]
    compareDeps -->|No| noop[No change]
    bumpDep --> reinstall["Install, then discover\nin the same run --\nmigrations ship inside\nthe new versions"]
  end

  collect --> writeFile["Write trackunit-migrations.json\nwith pending migrations +\nunchanged appliedMigrations"]

  subgraph step2 [Step 2: nx g @trackunit/migrations:run-migrations]
    readFile["Read trackunit-migrations.json"] --> loop["For each pending migration"]
    loop --> importImpl["Import migration implementation"]
    importImpl --> execute["Execute migration against NX Tree\n(AST transforms, file rewrites)"]
    execute --> recordApplied["Record package:name in\nappliedMigrations on success,\nkeep failures pending"]
  end

  writeFile --> review["Developer reviews\ntrackunit-migrations.json"]
  review --> readFile
flowchart LR
  subgraph nodeModules ["node_modules (recursive scan, for migrations only)"]
    rc["@trackunit/react-components\nv2.0.0\nmigrations: ./migrations.json"]
    rcc["@trackunit/react-chart-components\nv1.20.0\nmigrations: ./migrations.json"]
    su["@trackunit/shared-utils\nv1.13.0\n(no migrations field --\nstill version-bumped)"]
    nested["some-lib/node_modules/\n@trackunit/react-components\nv1.21.8 (older, deduped out)"]
  end

  subgraph migrationsFiles [migrations.json per package]
    rcMig["react-components/migrations.json\n- rename-button-kind\n- button-required-variant"]
    rccMig["react-chart-components/migrations.json\n- chart-api-v2"]
  end

  subgraph output [trackunit-migrations.json]
    pending["migrations:\n- rename-button-kind\n- button-required-variant\n- chart-api-v2\n\nappliedMigrations:\n  (filled in by run-migrations)"]
  end

  rc --> rcMig
  rcc --> rccMig
  su -.->|skipped| nodeModules
  nested -.->|deduped| rc
  rcMig --> pending
  rccMig --> pending

Writing classname-aware migrations

If a migration needs to detect or rewrite Tailwind/classname strings in source files (e.g. renaming or removing a utility class), don't reimplement detection logic — import @trackunit/css-classname-utils (libs/css/classname-utils) directly. It has no eslint/@typescript-eslint/utils runtime dependency, so it's safe to use outside an ESLint rule context, and it's already the basis for the equivalent ESLint rules in plugin-trackunit. See the "Shared classname utilities" section of the Tailwind architecture doc for what it exposes, notably replaceClassToken() for word-boundary-safe token replace/remove.

Usage

Step 1: Discover and collect migrations

nx g @trackunit/migrations:migrate

This moves every @trackunit/* dependency in your package.json to its latest published version and writes a trackunit-migrations.json file listing any pending migrations. Review the file before proceeding.

If any version changed, the generator installs the new packages and discovers migrations in the same run -- migrations ship inside the packages themselves, so they are only inspectable once installed. Add --skipInstall to run the install yourself, and --verbose to see the per-package decisions, including the dependencies that were left alone.

Step 2: Run pending migrations

nx g @trackunit/migrations:run-migrations

This executes each migration listed in trackunit-migrations.json, records the ones that succeeded, and clears the pending list. Commit the file: it is what stops those migrations being offered again. Deleting it means every migration runs again on the next migrate.

Internal monorepo

yarn nx g @trackunit/migrations:migrate
yarn nx g @trackunit/migrations:run-migrations

For more info and a full guide on Iris App SDK Development, please visit our Developer Hub.

Trackunit

This package was developed by Trackunit ApS.

Trackunit is the leading SaaS-based IoT solution for the construction industry, offering an ecosystem of hardware, fleet management software & telematics.

The Trackunit logo