Skip to content
CodeMorph

Migrate

A migration plan built from your real dependency graph.

CodeMorph reads every file, resolves every import and orders the work so each file is typed against dependencies that are already TypeScript. No guessing at shapes the model can't see.

Step 1 · Scan

The dependency graph

Scanning runs entirely on your machine and takes seconds on most projects.

Parsed, not pattern-matched

Every .js, .jsx, .mjs, .cjs, .ts and .tsx file is parsed with tree-sitter. Imports, exports and declarations come from the syntax tree, not regular expressions.

Resolved like your toolchain

Relative paths, index files, extension probing, tsconfig and jsconfig path aliases, and TS-style .js specifiers that point at .ts files.

Every edge classified

Each import is recorded as a local file, a Node built-in, a package from package.json, an asset like CSS or JSON, or unresolved, with the reason.

Stored locally

Files, symbols, edges and packages go into a local SQLite database, so the graph can be queried instead of rebuilt.
What the scanner understands
// ES modules, including re-exports and namespace imports
import { db } from "../lib/db";
import * as money from "@/utils/money";      // tsconfig / jsconfig paths
export { hashPassword } from "./crypto";
export * from "./models";                     // barrels

// CommonJS, destructured or not
const { logger } = require("../lib/logger");
module.exports = { addToCart, cartTotal };

// Dynamic imports are recorded and flagged as dynamic
const report = await import("./reports/monthly");

The scan respects your .gitignore chain, so build output and node_modules never enter the graph.

Resolution rules

How every import is resolved

The resolver is deterministic: the same project always produces the same graph, and the first match wins.

  1. 01

    Path aliases

    tsconfig or jsconfig
    If the specifier matches compilerOptions.paths, it's rewritten against baseUrl before anything else.
  2. 02

    Exact path

    The specifier is tried exactly as written, relative to the importing file.
  3. 03

    Extension probing

    .ts, .tsx, .js, .jsx, .mjs, .cjs
    Then each extension in that fixed order, so a migrated file is preferred over its JavaScript original.
  4. 04

    Index files

    Then the same extensions under /index, so ./utils resolves to utils/index.js.
  5. 05

    TypeScript-style specifiers

    NodeNext
    An import of ./dep.js also matches dep.ts or dep.tsx, .mjs matches .mts and .cjs matches .cts, the way TypeScript's NodeNext resolution works.
  6. 06

    Classification

    Anything that isn't a local file is classified as a Node built-in, a package declared in package.json, an asset such as CSS or JSON, or unresolved with a reason.

What's recorded

A graph you can query

The scan stores more than a list of imports. This is what makes good ordering and good answers possible.

RecordWhat it captures
FilesPath, language, lines of code, a content hash, and whether it's a test file, an entry point or has syntax errors
SymbolsEvery declaration with its kind, line range and whether and how it's exported
EdgesEach import or re-export, with flags for dynamic, type-only, side-effect-only and re-export
Edge symbolsExactly which names each import pulls in, and what they're exposed as
PackagesDependencies declared in package.json, with versions
UnresolvedImports that couldn't be resolved, with the reason: not found, or a package missing from package.json

Stored in a local SQLite database. Nothing in it leaves your machine.

Step 2 · Plan

Leaf-first batches

Types flow upward through imports. So the plan starts at the leaves.

  1. 01

    Find strongly connected components

    Tarjan's algorithm
    Files that import each other in a cycle form one component. A cycle is never split across batches, because its files can only be typed together.
  2. 02

    Order components topologically

    dependencies first
    A component closes only after everything it imports has closed, which gives a leaf-first order for free: utilities, then models, then routes, then your entry point.
  3. 03

    Group into batches

    about 10 files each
    Components are packed into batches of roughly ten files for pacing and review. Batch size is presentation, not correctness: the order underneath never changes.
  4. 04

    Show the plan before anything runs

    The Morph panel shows the file count, the batches named by folder and the number of import edges. Edit the plan or start the migration.
Leaf-first migration orderTwelve files in four batches. Batch 1 holds leaf modules in lib and utils, batch 2 the models that import them, batch 3 the API routes and middleware, and batch 4 the entry point. Each batch turns orange while it converts and green once verified.Batch 1 · leavesBatch 2Batch 3Batch 4 · entrylib/db.jslib/db.tslib/logger.jslib/logger.tslib/mailer.jslib/mailer.tsutils/money.jsutils/money.tsmodels/user.jsmodels/user.tsmodels/product.jsmodels/product.tsmodels/order.jsmodels/order.tsmiddleware/auth.jsmiddleware/auth.tsapi/users.jsapi/users.tsapi/cart.jsapi/cart.tsapi/orders.jsapi/orders.tsindex.jsindex.ts
JavaScript, not yet migrated Converting TypeScript, verifiedIllustrative project. Scan & Plan computes this order for your code today.

Reading your plan

What the numbers tell you

The plan is useful even before you convert a single file. It's an honest picture of how tangled a codebase is.

JavaScript files

How many files will be converted. Existing TypeScript is already counted as typed and isn't re-converted.

Batches

How many review sittings the migration takes. Each batch is roughly ten files, named by the folders it covers.

Import edges

How connected the project is. More edges per file means more context flows into each conversion, and better inferred types.

Batch 1

Your leaves: utilities and helpers with no local imports. If batch 1 is small, most of your code depends on a few core modules.

Cycles

Groups of files that import each other. They always migrate together, and a large one is often worth refactoring first.

Last batch

Usually your entry point and configuration: the files that import everything else and are typed last.

Step 3 · Convert

Batch conversion

Each file is converted with its already-typed dependencies in context, then checked before the next batch starts.

Typed against real imports

When models/order.js converts, lib/db.ts and utils/money.ts are already typed, so the model reads their actual signatures.

Progress you can see

Each batch shows its progress as files complete. Explorer dots go grey, orange, green, and a live log lists each file, its tsc result and any repairs.

Repair loop

If tsc reports errors, the compiler output goes back to the model for another attempt. Files that can't be fixed cleanly are flagged for review.

Resumable

Stop at any batch boundary and pick up later. The graph and plan are stored locally, so a paused migration resumes from where it stopped.

Steering

Tell Morph what you want

Type instructions into the Morph panel in plain words. Use @ to add context and / for commands.

“Migrate the api folder, strict mode, keep tests green”

Limits the run to one part of the plan and holds it to strict type-checking and passing tests.

“Start with lib and utils only”

Runs just the leaf batches so you can review the foundations before going further.

“Prefer interfaces for exported object types”

Sets a style preference that applies to every file in the run.

“Don't change module syntax in config files”

Leaves CommonJS in place where your tooling expects it.

“Why did payments.ts need review?”

Asks about a specific result. Morph answers with the diagnostics and call sites behind it.

The output panel

A log you can actually read

The CODEMORPH panel records every step: the scan, the baseline, each file's tsc result and how many repairs it took.

CODEMORPH
[codemorph] scanning shop-api …
[codemorph] parsed 38 files · tree-sitter
[codemorph] dependency graph: 38 nodes · 212 edges · 0 cycles
[codemorph] baseline: tsc --checkJs · 1,879 errors in 38 files
[codemorph] plan ready · 4 batches · leaf-first
[codemorph] batch 1/4 · lib/ · utilities
[codemorph] utils/catchAsync.ts · tsc 0 errors · 0 repairs
[codemorph] utils/logger.ts · tsc 0 errors · 1 repairs
[codemorph] batch 2/4 · models/
[codemorph] models/payment.ts · needs review · 2 any

A file marked needs review couldn't be typed cleanly within the repair budget. It waits in review with its diagnostics instead of being forced through with any.

Step 4 · Review

Diff review

Nothing is written to your project without your approval. Tell Morph what you want in plain words, then review the result file by file.

On a branch

Work happens on a migration branch, so your main history stays clean until you merge.

Accept or reject

Every file is marked accepted, in review or queued. Accept moves you straight to the next one.

Explained

Each file comes with a summary, like which types were inferred from how many call sites.

Edge cases

The tricky parts, handled

Circular imports

Detected as strongly connected components and migrated together, so neither side is typed against a guess.

Barrels and re-export chains

export * and export { x } from are followed to the file that actually defines each symbol.

Dynamic import()

Recorded and flagged as dynamic, so lazy-loaded modules still appear in the plan.

Unresolved imports

Kept in the graph with a reason. Fix the path or the package.json entry and re-scan.

Very large files

Files over 1 MB, usually generated or bundled code, are skipped and listed rather than slowing the scan.

Generated and vendored code

Anything your .gitignore excludes never enters the graph, including nested .gitignore files.

FAQ

Questions about migrating

Does CodeMorph change my files during Scan & Plan?
No. Scanning only reads files. Nothing is converted until you start the migration, and nothing lands in your project until you accept it.
What about projects that are already partly TypeScript?
Mixed projects are expected. Existing .ts and .tsx files are part of the graph and count as already typed. Only JavaScript files are placed into batches.
How are circular imports handled?
Files in a cycle form a single strongly connected component and always land in the same batch, so they can be typed together.
What if an import can't be resolved?
It's recorded as unresolved with a reason, either the file wasn't found or the package isn't in package.json, so you can see exactly where the graph has gaps.
Which frameworks are supported?
CodeMorph works at the module level, so React, Next.js, Vite, Node and Express projects all graph the same way. JSX files are first-class.
How big a project can it handle?
The scan is fast and local, and the plan breaks any size of project into batches of about ten files. Files over 1 MB are skipped, since they're almost always generated code.
What about monorepos?
Open the package you want to migrate as the workspace folder. CodeMorph scans the first folder of a multi-root workspace.
Can I change the plan?
Yes. Use Edit plan, or give Morph instructions such as which folders to migrate first. The underlying dependency order is always respected.
Does it convert CommonJS to ES modules?
Where it's safe. You can tell Morph to leave module syntax alone, for example in config files your tooling loads with require.
What if my project already has a tsconfig.json?
CodeMorph uses it, including path aliases for resolution and its strictness for verification.

Deep dive

Read How CodeMorph verifies every migrated file for the full design of the convert, check and repair loop.

Plan your migration in a minute.

Download the beta, open a project and see your migration plan in under a minute.