Skip to content

Support tsconfig path aliases in migration validation - #7

Merged
jalexw merged 2 commits into
mainfrom
claude/validate-migration-tsconfig-aliases-49671v
Aug 22, 2026
Merged

jalexw merged 2 commits into
mainfrom
claude/validate-migration-tsconfig-aliases-49671v

Conversation

@jalexw

@jalexw jalexw commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds support for resolving TypeScript compilerOptions.paths aliases (e.g., @/sql → src/sql.ts) when validating and importing migration modules. This allows consumers to use the same import aliases in their migrations as they do in their application code.

Key Changes

  • New module src/utils/tsconfigPathAliases.ts: Implements tsconfig.json parsing and path alias resolution

    • Parses JSONC (comments + trailing commas) and resolves baseUrl and compilerOptions.paths
    • Follows extends chains to inherit aliases from parent configs
    • Discovers the nearest tsconfig declaring aliases by walking up from a directory
    • Resolves alias specifiers to actual file paths with extension probing (.ts, .tsx, .js, etc.)
    • Handles TypeScript's ESM-style imports (e.g., @/sql.js → sql.ts)
  • New module src/utils/tsconfigAliasImportHooks.ts: Integrates alias resolution with Node.js and Bun runtimes

    • For Node.js (≥22.15): Uses module.registerHooks() to install a synchronous ESM resolve hook
    • For Bun: Pre-scans the migration import graph and registers alias specifiers as virtual modules (since Bun's runtime hooks don't fire)
    • Scopes alias resolution to the consumer project's files only (tsconfig directory + migration directory) to avoid rewriting imports from this package or dependencies
  • Updated src/utils/validateMigrationDirectory.ts:

    • Accepts optional tsconfigPath option to explicitly specify a tsconfig
    • Auto-discovers the nearest tsconfig declaring aliases if not provided
    • Applies alias resolution when dynamically importing migration modules
    • Reports warnings when aliases are found but the runtime doesn't support resolution
  • Updated src/cli.ts: Adds --tsconfig <path> flag to the validate-migration-directory command

  • Test fixtures: Added three fixture projects demonstrating alias usage:

    • alias-project: Standard setup with baseUrl and both exact and wildcard aliases
    • nobase-project: Tsconfig without baseUrl (paths relative to config file) — exercises Bun's virtual module fallback
    • extends-chain: Demonstrates alias inheritance through extends
  • Comprehensive test coverage: Tests for JSONC parsing, extends chain resolution, exact/wildcard alias matching, extension probing, and end-to-end validation with real migration files

Notable Implementation Details

  • The JSONC parser manually strips comments and trailing commas while preserving string contents, avoiding a dependency on a JSON parser library
  • Alias resolution follows TypeScript's pattern matching rules: exact patterns win, otherwise the wildcard with the longest matching prefix
  • Extension probing mirrors how tsc and bundlers complete extensionless specifiers, including .js→.ts swaps for ESM compatibility
  • The Node.js hook is installed once per process and driven by a mutable scope, allowing multiple validation runs with different tsconfig contexts
  • Bun's virtual module approach uses Bun.Transpiler.scanImports() to statically discover alias usage in the import graph, avoiding the need for runtime hooks

https://claude.ai/code/session_01VvN4yt72bDNyFvUo8TS3z5

…1.4.0

validate-migration-directory previously imported each migration module with
a bare dynamic import(), so migrations importing through tsconfig
compilerOptions.paths aliases (e.g. `import { sql } from "@/sql"`) failed
with "Cannot find package '@/sql'", even though build-db-migrations
resolved the same imports fine.

Validation now discovers the nearest tsconfig.json declaring
compilerOptions.paths (walking up from the migrations directory, parsing
JSONC and following extends chains) and applies those aliases while
importing migration modules:

- Node.js (the runtime the published CLI's shebang selects under bunx/npx):
  a module.registerHooks() resolve hook (Node >= 22.15) maps alias
  specifiers to their target files, and also recovers bundler-style
  extensionless / .js->.ts relative imports in the consumer's files.
- Bun: alias specifiers found by scanning the migrations' static import
  graph with Bun.Transpiler are registered as virtual modules, which also
  covers tsconfigs Bun's built-in paths support rejects (paths without
  baseUrl and non-"./" targets).
- Hooks only rewrite imports made by files under the consumer project or
  the migration directory, and only while validation is running.

The CLI gains a --tsconfig <path> override, prints which tsconfig supplied
the aliases, reports a warning on runtimes without hook support, and hints
at --tsconfig when a bare import fails and no tsconfig was discovered.
validateMigrationDirectory() accepts a tsconfigPath option and reports the
tsconfig it used in its result.

Also updates the repository to Bun 1.4.0 (packageManager, bun-types,
CI workflow, e2e docker images, docs). Lint, typecheck, and the unit
suite pass under both Bun 1.3.x and 1.4.0; a new test validates the
bundled CLI end-to-end under Node.js against a consumer project shaped
like the bug report.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvN4yt72bDNyFvUo8TS3z5
@jalexw jalexw self-assigned this Aug 22, 2026
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvN4yt72bDNyFvUo8TS3z5
@jalexw
jalexw merged commit 16280bb into main Aug 22, 2026
9 checks passed
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.

2 participants