Skip to content

feat: support TypeScript migration files based on tsconfig presence - #1639

Draft
beterrabaA wants to merge 10 commits into
salsita:mainfrom
beterrabaA:beterraba-dynamic-ext-file-gen
Draft

beterrabaA wants to merge 10 commits into
salsita:mainfrom
beterrabaA:beterraba-dynamic-ext-file-gen

Conversation

@beterrabaA

@beterrabaA beterrabaA commented May 21, 2026

Copy link
Copy Markdown
Contributor

node-pg-migrate create now generates by default .js or .ts depending on the presence of the tsconfig.json file

image

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🟢 Lines 94.25% (🎯 90%)
⬆️ +0.04%
1675 / 1777
🟢 Statements 94.32% (🎯 90%)
⬆️ +0.05%
1694 / 1796
🟢 Functions 95.61% (🎯 90%)
⬆️ +0.03%
371 / 388
🟢 Branches 88.35% (🎯 85%)
⬆️ +0.06%
1077 / 1219
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
src/migration.ts 77.35%
⬆️ +0.43%
70.27%
⬇️ -0.56%
80.95%
🟰 ±0%
77.14%
⬆️ +0.45%
33-35, 143-145, 167-172, 182-184, 250-286, 352, 377-379, 391, 409-411
src/utils/shouldGenerateTs.ts 100% 100% 100% 100%
Unchanged Files
src/db.ts 80% 75% 78.57% 79.62% 102-104, 137-149, 193, 197-198
src/index.ts 100% 100% 100% 100%
src/logger.ts 100% 100% 100% 100%
src/migrationBuilder.ts 97.32% 77.77% 100% 97.32% 843-846, 986
src/migrationLoader.ts 91.26% 84.74% 90.9% 91.17% 251, 256, 261, 266, 301, 320, 328-332, 451
src/migrationOptions.ts 100% 100% 100% 100%
src/pgType.ts 100% 100% 100% 100%
src/runner.ts 86.82% 77.39% 87.09% 86.5% 235-237, 256-258, 346-347, 403-405, 450, 460-463, 479, 491-493, 526, 628-629, 646-654, 665, 679
src/sqlMigration.ts 100% 100% 100% 100%
src/operations/sql.ts 100% 50% 100% 100%
src/operations/casts/createCast.ts 100% 100% 100% 100%
src/operations/casts/dropCast.ts 100% 100% 100% 100%
src/operations/casts/index.ts 100% 100% 100% 100%
src/operations/domains/alterDomain.ts 100% 100% 100% 100%
src/operations/domains/createDomain.ts 100% 95.45% 100% 100%
src/operations/domains/dropDomain.ts 100% 100% 100% 100%
src/operations/domains/index.ts 100% 100% 100% 100%
src/operations/domains/renameDomain.ts 100% 100% 100% 100%
src/operations/domains/shared.ts 100% 100% 100% 100%
src/operations/extensions/createExtension.ts 100% 100% 100% 100%
src/operations/extensions/dropExtension.ts 100% 100% 100% 100%
src/operations/extensions/index.ts 100% 100% 100% 100%
src/operations/extensions/shared.ts 100% 100% 100% 100%
src/operations/functions/createFunction.ts 96.55% 92% 100% 96.55% 71-73
src/operations/functions/dropFunction.ts 100% 100% 100% 100%
src/operations/functions/index.ts 100% 100% 100% 100%
src/operations/functions/renameFunction.ts 100% 100% 100% 100%
src/operations/functions/shared.ts 100% 100% 100% 100%
src/operations/grants/grantOnSchemas.ts 100% 100% 100% 100%
src/operations/grants/grantOnTables.ts 100% 100% 100% 100%
src/operations/grants/grantRoles.ts 100% 100% 100% 100%
src/operations/grants/index.ts 100% 100% 100% 100%
src/operations/grants/revokeOnSchemas.ts 100% 100% 100% 100%
src/operations/grants/revokeOnTables.ts 100% 100% 100% 100%
src/operations/grants/revokeRoles.ts 100% 100% 100% 100%
src/operations/grants/shared.ts 100% 75% 100% 100%
src/operations/indexes/createIndex.ts 100% 100% 100% 100%
src/operations/indexes/dropIndex.ts 100% 100% 100% 100%
src/operations/indexes/index.ts 100% 100% 100% 100%
src/operations/indexes/renameIndex.ts 100% 100% 100% 100%
src/operations/indexes/shared.ts 100% 88.23% 100% 100%
src/operations/materializedViews/alterMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/createMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/dropMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/index.ts 100% 100% 100% 100%
src/operations/materializedViews/refreshMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/renameMaterializedView.ts 100% 100% 100% 100%
src/operations/materializedViews/renameMaterializedViewColumn.ts 100% 100% 100% 100%
src/operations/materializedViews/shared.ts 100% 83.33% 100% 100%
src/operations/operators/addToOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/createOperator.ts 100% 88.88% 100% 100%
src/operations/operators/createOperatorClass.ts 100% 75% 100% 100%
src/operations/operators/createOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/dropOperator.ts 100% 100% 100% 100%
src/operations/operators/dropOperatorClass.ts 100% 100% 100% 100%
src/operations/operators/dropOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/index.ts 100% 100% 100% 100%
src/operations/operators/removeFromOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/renameOperatorClass.ts 100% 100% 100% 100%
src/operations/operators/renameOperatorFamily.ts 100% 100% 100% 100%
src/operations/operators/shared.ts 81.81% 77.77% 100% 81.81% 24, 38
src/operations/policies/alterPolicy.ts 100% 100% 100% 100%
src/operations/policies/createPolicy.ts 100% 100% 100% 100%
src/operations/policies/dropPolicy.ts 100% 100% 100% 100%
src/operations/policies/index.ts 100% 100% 100% 100%
src/operations/policies/renamePolicy.ts 100% 100% 100% 100%
src/operations/policies/shared.ts 100% 100% 100% 100%
src/operations/roles/alterRole.ts 100% 100% 100% 100%
src/operations/roles/createRole.ts 100% 92.3% 100% 100%
src/operations/roles/dropRole.ts 100% 100% 100% 100%
src/operations/roles/index.ts 100% 100% 100% 100%
src/operations/roles/renameRole.ts 100% 100% 100% 100%
src/operations/roles/shared.ts 100% 86.66% 100% 100%
src/operations/schemas/createSchema.ts 100% 100% 100% 100%
src/operations/schemas/dropSchema.ts 100% 100% 100% 100%
src/operations/schemas/index.ts 100% 100% 100% 100%
src/operations/schemas/renameSchema.ts 100% 100% 100% 100%
src/operations/sequences/alterSequence.ts 91.66% 83.33% 100% 91.66% 24
src/operations/sequences/createSequence.ts 100% 100% 100% 100%
src/operations/sequences/dropSequence.ts 100% 100% 100% 100%
src/operations/sequences/index.ts 100% 100% 100% 100%
src/operations/sequences/renameSequence.ts 100% 100% 100% 100%
src/operations/sequences/shared.ts 81.48% 83.33% 100% 81.48% 41, 43, 49, 63, 69
src/operations/tables/addColumns.ts 100% 85.71% 100% 100%
src/operations/tables/addConstraint.ts 100% 100% 100% 100%
src/operations/tables/alterColumn.ts 91.17% 81.25% 100% 91.17% 70, 77-84
src/operations/tables/alterTable.ts 100% 100% 100% 100%
src/operations/tables/createTable.ts 89.65% 83.33% 100% 89.65% 52-55, 101
src/operations/tables/dropColumns.ts 100% 100% 100% 100%
src/operations/tables/dropConstraint.ts 100% 100% 100% 100%
src/operations/tables/dropTable.ts 100% 100% 100% 100%
src/operations/tables/index.ts 100% 100% 100% 100%
src/operations/tables/renameColumn.ts 100% 100% 100% 100%
src/operations/tables/renameConstraint.ts 100% 100% 100% 100%
src/operations/tables/renameTable.ts 100% 100% 100% 100%
src/operations/tables/shared.ts 86.91% 77.22% 76.47% 86.91% 159, 242, 262, 290, 294-300, 304, 457-478
src/operations/triggers/createTrigger.ts 88.57% 71.79% 100% 88.57% 53, 66, 70, 74-76
src/operations/triggers/dropTrigger.ts 100% 100% 100% 100%
src/operations/triggers/index.ts 100% 100% 100% 100%
src/operations/triggers/renameTrigger.ts 100% 100% 100% 100%
src/operations/triggers/shared.ts 100% 100% 100% 100%
src/operations/types/addTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/addTypeValue.ts 100% 100% 100% 100%
src/operations/types/createType.ts 100% 100% 100% 100%
src/operations/types/dropType.ts 100% 100% 100% 100%
src/operations/types/dropTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/index.ts 100% 100% 100% 100%
src/operations/types/renameType.ts 100% 100% 100% 100%
src/operations/types/renameTypeAttribute.ts 100% 100% 100% 100%
src/operations/types/renameTypeValue.ts 100% 100% 100% 100%
src/operations/types/setTypeAttribute.ts 100% 100% 100% 100%
src/operations/views/alterView.ts 100% 100% 100% 100%
src/operations/views/alterViewColumn.ts 100% 100% 100% 100%
src/operations/views/createView.ts 100% 100% 100% 100%
src/operations/views/dropView.ts 100% 100% 100% 100%
src/operations/views/index.ts 100% 100% 100% 100%
src/operations/views/renameView.ts 100% 100% 100% 100%
src/operations/views/shared.ts 100% 50% 100% 100%
src/utils/PgLiteral.ts 100% 100% 100% 100%
src/utils/comparators.ts 100% 100% 100% 100%
src/utils/createSchemalize.ts 100% 100% 100% 100%
src/utils/createTransformer.ts 100% 100% 100% 100%
src/utils/decamelize.ts 100% 100% 100% 100%
src/utils/escapeValue.ts 100% 100% 100% 100%
src/utils/fileNameUtils.ts 100% 100% 100% 100%
src/utils/formatLines.ts 100% 100% 100% 100%
src/utils/formatParams.ts 100% 87.5% 100% 100%
src/utils/formatPartitionColumns.ts 100% 100% 100% 100%
src/utils/formatSql.ts 100% 100% 100% 100%
src/utils/getMigrationTableSchema.ts 100% 100% 100% 100%
src/utils/getSchemas.ts 100% 100% 100% 100%
src/utils/identity.ts 100% 100% 100% 100%
src/utils/index.ts 100% 100% 100% 100%
src/utils/intersection.ts 100% 100% 100% 100%
src/utils/makeComment.ts 100% 100% 100% 100%
src/utils/quote.ts 100% 100% 100% 100%
src/utils/stringIdGenerator.ts 100% 100% 100% 100%
src/utils/toArray.ts 100% 100% 100% 100%
src/utils/types.ts 100% 100% 100% 100%
Generated in workflow #4010 for commit 7808e4b by the Vitest Coverage Report Action

@beterrabaA

Copy link
Copy Markdown
Contributor Author

I'm adding documentation about this.Don't merge yet.

@beterrabaA

Copy link
Copy Markdown
Contributor Author

It's done.

@Shinigami92

Copy link
Copy Markdown
Collaborator

Problems and questions:

  • missing tests
  • potentially handling of cwd?
  • what happens in case if tsonfig.json with allowJs:true?
  • monorepo support?
  • is it possible to have a repo without tsconfig, but ts support active?

@Shinigami92

Copy link
Copy Markdown
Collaborator

I'm adding documentation about this.Don't merge yet.

Please use drafts 😉

@Shinigami92 Shinigami92 added the c: feature Request for new feature label Jun 22, 2026
@Shinigami92
Shinigami92 marked this pull request as draft June 22, 2026 11:38
@Shinigami92

Copy link
Copy Markdown
Collaborator

@beterrabaA I summon the cat back 🙌 It would be a good time right now to rebase this PR and move on 🙂

@beterrabaA

Copy link
Copy Markdown
Contributor Author

🫡

@beterrabaA

Copy link
Copy Markdown
Contributor Author

Tomorrow, I will deliver it. Sorry for the wait.

tired-spongebob-meme

@beterrabaA
beterrabaA force-pushed the beterraba-dynamic-ext-file-gen branch 2 times, most recently from f3f64d6 to c8b73dd Compare September 7, 2026 03:45
@beterrabaA

beterrabaA commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Sorry for not adding monorepo support.I never worked with it before. Now it's checking if there's a tsconfig file. If there's, will verify the allowJs property. If there's not, it will search for ts files.Were added tests

2391679_c54c1

@beterrabaA
beterrabaA marked this pull request as ready for review September 7, 2026 04:40

@Shinigami92 Shinigami92 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Claude Opus 5 Review:

Verdict: request changes. The goal is good and the precedence design is right, but there's a release-blocking crash and the core heuristic is inverted. Lint, tsc, and all 749 unit tests pass — the problems are all in behavior I had to probe for.

What's right

resolveSuffix keeps the order language || getLastSuffix() || <detected> (src/migration.ts:184), so an explicit -j wins, then the suffix of the existing migrations, and detection only kicks in for an empty migrations dir. That's the correct place to put this, and it means existing projects are untouched.

Blockers

1. Importing the package crashes on a valid tsconfig.json

src/migration.ts:74 calls shouldGenerateTypescript() at module top level, and src/index.ts:1 re-exports Migration from that module — so this runs on every import 'node-pg-migrate'. The comment-stripping regex in src/utils/shouldGenerateTs.ts:17 hands the result to JSON.parse, which rejects trailing commas that tsc accepts happily. Verified against src/index.ts with cwd set to a project whose tsconfig is:

{ "compilerOptions": { "strict": true, "allowJs": true, }, }
IMPORT CRASHED at module-evaluation time:
  SyntaxError: Expected double-quoted property name in JSON at position 68

This takes out up, down, redo and the programmatic API, not just create. Trailing commas in tsconfig are very common. Needs a real JSONC parse (or at minimum a try/catch that degrades to js) — and it must not run at import time.

2. Module-level side effect with an unbounded filesystem scan

src/migration.ts:74 and :176 both execute at import. globSync('**/*.ts') walks the whole tree from cwd on every import, including for commands that never read the value. Measured in this repo: 13.8ms, 270 matches — and it grows with the project. It also freezes cwd at import time, so a later chdir is ignored. Move the call inside resolveSuffix; it's already async and only needed on the create path.

3. allowJs is the wrong signal, and it's used inverted

allowJs: true means "tsc will also accept .js files" — routinely enabled in TypeScript projects that are mid-migration or that consume untyped JS. Treating it as "this is a JS project" gets the common case backwards, and the existsTsConfig && allowsJsInTsConfig branch discards hasTypeScriptFiles entirely:

project actual expected
tsconfig.json + src/index.ts, allowJs: true js ts — it is a TS project
allowJs: true inherited via extends ts js by the PR's own rule
plain JS project with one env.d.ts ts js

Suggestion: drop the allowJs inspection and the whole JSONC-parsing problem with it. existsSync('tsconfig.json') ? 'ts' : 'js' matches what the PR description promises, is what users will predict, and anyone who disagrees already has -j js plus the existing-migration-suffix signal ahead of it.

Should fix

4. extends is not resolved

shouldGenerateTs.ts:22 reads compilerOptions.allowJs off the parsed file only. Verified: a base config with allowJs: true pulled in via extends returns true. Real configs put compilerOptions in an @tsconfig/node22-style base. Resolving extends chains correctly is a genuine amount of work, which is another argument for #3.

5. **/*.ts over-matches

Beyond the .d.ts case in the table above, the ignore list is only node_modules and dist; build, out, .next, coverage, .turbo all still count, and .gitignore isn't consulted.

6. Import style breaks repo convention

shouldGenerateTs.ts:1 uses from 'fs' — every other file in src/ uses the node: prefix. And :2 imports glob/raw while src/migration.ts:1 imports glob; both are valid subpaths but they resolve to the unminified and minified builds respectively, so the dist bundle ends up carrying two copies of glob. Use 'glob'.

7. Take cwd as a parameter

shouldGenerateTypescript(cwd = process.cwd()) would replace the hardcoded './tsconfig.json' and let the test drop process.chdir (test/utils/fileExtension.spec.ts:16). That chdir works today only because vitest 4 defaults to the forked pool with isolation — it becomes a hard failure under pool: 'threads', where process.chdir doesn't exist.

Minor

  • 8. const lastSuff = ... (src/migration.ts:176) is wedged between two function declarations, and the name is cryptic. Inline it into resolveSuffix as part of fixing #2.
  • 9. Three names for one thing: file shouldGenerateTs.ts, export shouldGenerateTypescript, test fileExtension.spec.ts. The repo names each util file after its export.
  • 10. Not re-exported from src/utils/index.ts, unlike every other util, so migration.ts:19 has to deep-import it.
  • 11. Docs still document the old default: docs/src/cli.md:136 (migration-file-languagejs) and :179. Since this changes a default, it also belongs in docs/src/upgrading.md.

Suggested shape

// src/utils/shouldGenerateTs.ts
import { existsSync } from 'node:fs';
import { join } from 'node:path';

/** Detect whether a new migration should default to TypeScript. */
export function shouldGenerateTypescript(cwd: string = process.cwd()): boolean {
  return existsSync(join(cwd, 'tsconfig.json'));
}
// src/migration.ts — no module-level work
async function resolveSuffix(
  directory: string,
  options: CreateOptionsDefault
): Promise<string> {
  const { language, ignorePattern } = options;
  return (
    language ||
    (await getLastSuffix(directory, ignorePattern)) ||
    (shouldGenerateTypescript() ? 'ts' : 'js')
  );
}

That keeps the PR's headline behavior, removes the crash, the import-time scan, the extends gap, and the .d.ts false positive. The test file then reduces to a cwd-argument table and loses its chdir fragility — though it's worth keeping a case asserting an existing .js migration still wins over a present tsconfig, since that's the compatibility guarantee for current users.

Verification notes

How the claims above were checked, for anyone re-running them:

  • Blockers 3–5 and the table: the util's body copied verbatim into a standalone script, run against generated fixture projects (tsconfig with trailing comma, allowJs via extends, JS project plus one .d.ts, TS project with allowJs: true).
  • Blocker 1: jiti.import('src/index.ts') with cwd set to a fixture project holding a trailing-comma tsconfig.
  • Blocker 2: globSync('**/*.ts', { ignore: ['**/node_modules/**', '**/dist/**'] }) timed with process.hrtime.bigint() at the repo root.
  • Baseline: pnpm lint, pnpm ts-check, pnpm vitest run --project unit — all green on the PR branch.

@Shinigami92
Shinigami92 marked this pull request as draft September 12, 2026 09:11

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: feature Request for new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants