Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

javascript knexfile can't run typescript migrations #3849

Open
prk3 opened this issue May 10, 2020 · 22 comments
Open

javascript knexfile can't run typescript migrations #3849

prk3 opened this issue May 10, 2020 · 22 comments

Comments

@prk3
Copy link

prk3 commented May 10, 2020

Environment

Knex version: 0.21.1
Database + version: postgres 12
OS: Linux

Bug

  1. Description

When the knexfile has js extension, typescript migrations and seeds can't be run. The error thrown suggests these files are not loaded with typescript loader. Changing knexfile extension to ts fixes the problem.

This is rather unexpected, as the documentation suggests that knex can run migrations of different types, no matter what the knexfile extension is.

loadExtensions: array of file extensions which knex will treat as migrations.
  1. Error message
import Knex from 'knex';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:1047:16)
    at Module._compile (internal/modules/cjs/loader.js:1097:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1153:10)
    at Module.load (internal/modules/cjs/loader.js:977:32)
    at Function.Module._load (internal/modules/cjs/loader.js:877:14)
    at Module.require (internal/modules/cjs/loader.js:1019:19)
    at require (internal/modules/cjs/helpers.js:77:18)
    at FsMigrations.getMigration (/home/przemek/DEV/sundae/sundae-collab-demo-api/node_modules/knex/lib/migrate/sources/fs-migrations.js:82:12)
    at /home/przemek/DEV/sundae/sundae-collab-demo-api/node_modules/knex/lib/migrate/Migrator.js:76:67
    at Array.some (<anonymous>)
  1. Knexfile content
require('dotenv').config();

const defaultSettings = {
  // client
  // connection
};

module.exports.development = {
  ...defaultSettings,
  migrations: {
    loadExtensions: ['.ts'],
    extension: 'ts',
  },
  seeds: {
    loadExtensions: ['.ts'],
  },
};

module.exports.test = {
  ...defaultSettings,
  migrations: {
    loadExtensions: ['.ts'],
    extension: 'ts',
  },
  seeds: {
    loadExtensions: ['.ts'],
  },
};

module.exports.production = {
  ...defaultSettings,
  migrations: {
    directory: 'build/migrations',
  },
  seeds: {
    directory: 'build/seeds',
  },
};

Feature discussion / request

  1. Explain what is your use case

I want to run Typescript migrations and seeds in development environment. In production, where Typescript and ts-node might not be present, compiled migrations and seeds should be used instead. Ideally, one js knexfile would handle both cases.

  1. Explain what kind of feature would support this

Knexfile extension should not influence migration and seed loading. When loadExtensions includes .ts, typescript migrations and seeds should be loaded and run.

@love8587
Copy link

love8587 commented May 20, 2020

@prk3 You could try with npx knex migrate:latest

@prk3
Copy link
Author

prk3 commented May 21, 2020

@love8587
That's how I run migrations. Here is the exact console output. Notice that ts-node/register is used only when knexfile has .ts extension.

$ npx knex migrate:latest
/home/przemek/DEV/sundae/sundae-collab-demo-api/migrations/20200414144724_recipes.ts:1
import Knex from 'knex';
^^^^^^

SyntaxError: Cannot use import statement outside a module
    at wrapSafe (internal/modules/cjs/loader.js:1047:16)
    at Module._compile (internal/modules/cjs/loader.js:1097:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1153:10)
    at Module.load (internal/modules/cjs/loader.js:977:32)
    at Function.Module._load (internal/modules/cjs/loader.js:877:14)
    at Module.require (internal/modules/cjs/loader.js:1019:19)
    at require (internal/modules/cjs/helpers.js:77:18)
    at FsMigrations.getMigration (/home/przemek/DEV/sundae/sundae-collab-demo-api/node_modules/knex/lib/migrate/sources/fs-migrations.js:82:12)
    at /home/przemek/DEV/sundae/sundae-collab-demo-api/node_modules/knex/lib/migrate/Migrator.js:76:67
    at Array.some (<anonymous>)
$ mv knexfile.js knexfile.ts
$ npx knex migrate:latest
Requiring external module ts-node/register
Batch 1 run: 2 migrations
$

@Heverson
Copy link

Heverson commented Jun 9, 2020

Hello @prk3
I have the same problem, when run knex migrate in heroku cli

Running knex --knexfile knexfile.ts migrate:latest on ⬢ backend-ecolab... up, run.5056 (Free)
Failed to load external module ts-node/register
Failed to load external module typescript-node/register
Failed to load external module typescript-register
Failed to load external module typescript-require
Failed to load external module sucrase/register/ts
Failed to load external module @babel/register
Error: Cannot find module 'ts-node/register'
Require stack:
- /app/knexfile.ts
- /app/node_modules/knex/bin/cli.js
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:966:15)
    at Function.Module._load (internal/modules/cjs/loader.js:842:27)
    at Module.require (internal/modules/cjs/loader.js:1026:19)
    at require (internal/modules/cjs/helpers.js:72:18)
    at Object.<anonymous> (/app/knexfile.ts:1:1)
    at Module._compile (internal/modules/cjs/loader.js:1138:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1158:10)
    at Module.load (internal/modules/cjs/loader.js:986:32)
    at Function.Module._load (internal/modules/cjs/loader.js:879:14)
    at Module.require (internal/modules/cjs/loader.js:1026:19)

@lorefnon
Copy link
Collaborator

lorefnon commented Jun 10, 2020

@prk3 If you specify loadExtensions: ['.js'] in the config for production env it should work as expected.

The default value of loadExtensions comes from here which will have to be overridden if you have some files matching those extensions which you don't want to process.

@love8587
Copy link

@lorefnon that's right. I found out it from document and resolved it.

@abcd-ca
Copy link

abcd-ca commented Jun 12, 2020

I can't get it to run a knexfile.ts that has typescript syntax such as,

import dotenv from 'dotenv'

dotenv.config();

without the error,

import dotenv from 'dotenv';
^^^^^^

SyntaxError: Cannot use import statement outside a module

I have tried running it like,

knex --knexfile ./knexfile.ts migrate:make create_users_table -x ts (tried with and without the --knexfile option and with and without the -x ts option)

If I remove the import from knexfile.ts it just hides the problem because the rest of the file is the same as javascript – it creates the migration file with a .ts extension but any imports or requires of typescript code give the same error so I think we need to focus on why it's not interpreting knexfile.ts as typescript. Further, to get rid of the "Failed to load external module ts-node/register" message I did yarn add --dev ts-node which results in the line, "Requiring external module ts-node/register" being displayed before it throws the SyntaxError about import.

I don't know if TS is broken for the knex CLI or if I'm just missing something

@abcd-ca
Copy link

abcd-ca commented Jun 12, 2020

I just noticed that 0.21.1 in the changelog has a bug fix that looks related which I suspect might be causing this side effect issue.

This was incorrect, I tried with 0.21.0 and it didn't fix it. I got the knexfile.ts to work with the import by changing "module": "esnext" to "module": "commonjs" in tsconfig.json. The problem is in my case is that I am using Next.js 9 (9.4.0) and when I run the app, Next.js changes the module setting back to esnext which is apparently a known issue. So, I think this is not a knex problem but if you don't know that module must be commonjs then you're in trouble – I'd recommend adding it to the docs.

@abcd-ca
Copy link

abcd-ca commented Jun 12, 2020

Is there a way to have a separate tsconfig.json file just for the knex CLI as a workaround?

@love8587
Copy link

love8587 commented Jun 12, 2020

@abcd-ca I resolved it below code.

const extension =
  process.env.NODE_CONFIG_ENV === 'production' || process.env.NODE_CONFIG_ENV === 'staging'
    ? 'js'
    : 'ts';

console.log('extension ============> ', extension);

  migrations: {
    tableName: 'knex_migrations',
    extension: extension,
    directory: path.join(BASE_PATH, 'migrations'),
    disableMigrationsListValidation: true,
    loadExtensions: [`.${extension}`],
  },
  seeds: {
    extension: extension,
    directory: path.join(BASE_PATH, 'seeds'),
  },

@abcd-ca
Copy link

abcd-ca commented Jun 12, 2020

@love8587 Yeah that's a good solution to your problem, thanks for sharing! In my case I realized it was a bit of a different problem than yours. In my case I couldn't get knex CLI to work with typescript files at all. In my case it was because my tsconfig.json didn't have module set to commonjs as it requires. Conversely, my UI framework Next.js requires module to be esnext. For now I'm switching back and forth when doing knex CLI activity vs running the server.

@lorefnon
Copy link
Collaborator

lorefnon commented Jun 12, 2020

@abcd-ca You can use:

TS_NODE_COMPILER_OPTIONS='{ "module": "commonjs" }' knex migrate:latest

knex uses ts-node's require hook for .ts extension and that can accept TS compiler options through TS_NODE_COMPILER_OPTIONS env variable. These options will be merged into what you have in your tsconfig.json.

@abcd-ca
Copy link

abcd-ca commented Jun 12, 2020

thanks!

@abcd-ca
Copy link

abcd-ca commented Jun 12, 2020

@lorefnon That gave me this alternative idea:

created a tsconfig.knexcli.json containing an override for module,

{
  "extends": "./tsconfig",
  "compilerOptions": {
    "module": "commonjs"
  }
}

Then when running my commands,

TS_NODE_PROJECT=tsconfig.knexcli.json knex migrate:latest

Just slightly cleaner because the JSON when in an npm script needs to escape the quotes and gets a bit busy looking. Thanks!

@lorefnon
Copy link
Collaborator

@abcd-ca Yes, that certainly looks like a cleaner approach.

@mrsufgi
Copy link

mrsufgi commented Jun 29, 2020

Thanks for your solution @abcd-ca.
Seems like this thread is a mix of two issues.

  1. knexfile.js doesn't transpile .ts migration files by default.
  2. knex cli doesn't support 'esnext' code, even with node14.4

the workaround for 1 was changing to knexfile.ts, it should be enough for most people (as I've seen it, "commonjs" is the most common module resolution). but the workaround for 2 only indicates that we have issues with esm :)

for ts/node maybe we need to run it with

node --loader ts-node/esm.mjs

somewhere down the pipeline?

@Adverbly
Copy link

Adverbly commented Jul 31, 2020

For those seeing

Failed to load external module ts-node/register
Failed to load external module typescript-node/register
Failed to load external module typescript-register
Failed to load external module typescript-require
Failed to load external module sucrase/register/ts
Failed to load external module @babel/register
Error: Cannot find module 'ts-node/register'
...
SyntaxError: Cannot use import statement outside a module

for me the fix was that I was missing ts-node from my package.json. A simple

npm install -D ts-node

was all I needed.

@Isaac-alencar
Copy link

I can't get it to run a knexfile.ts that has typescript syntax such as,

import dotenv from 'dotenv'

dotenv.config();

without the error,

import dotenv from 'dotenv';
^^^^^^

SyntaxError: Cannot use import statement outside a module

I have tried running it like,

knex --knexfile ./knexfile.ts migrate:make create_users_table -x ts (tried with and without the --knexfile option and with and without the -x ts option)

If I remove the import from knexfile.ts it just hides the problem because the rest of the file is the same as javascript – it creates the migration file with a .ts extension but any imports or requires of typescript code give the same error so I think we need to focus on why it's not interpreting knexfile.ts as typescript. Further, to get rid of the "Failed to load external module ts-node/register" message I did yarn add --dev ts-node which results in the line, "Requiring external module ts-node/register" being displayed before it throws the SyntaxError about import.

I don't know if TS is broken for the knex CLI or if I'm just missing something

I have the same issue, and your suggest to add ts-node works to me

@cbaron
Copy link

cbaron commented Dec 7, 2020

@abcd-ca's method of

    "module": "commonjs",

in tsconfig.json worked for me as well

@DininduKanchana
Copy link

loadExtensions: [.${extension}],

@abcd-ca I resolved it below code.

const extension =
  process.env.NODE_CONFIG_ENV === 'production' || process.env.NODE_CONFIG_ENV === 'staging'
    ? 'js'
    : 'ts';

console.log('extension ============> ', extension);
  migrations: {
    tableName: 'knex_migrations',
    extension: extension,
    directory: path.join(BASE_PATH, 'migrations'),
    disableMigrationsListValidation: true,
    loadExtensions: [`.${extension}`],
  },
  seeds: {
    extension: extension,
    directory: path.join(BASE_PATH, 'seeds'),
  },

I did all the things but this solved my issue. Thank you very much. Spent 3+ hours on this.

@jeanbispo
Copy link

I resolve with:

yarn add ts-node -D

and add in package.json scripts:

{
  "scripts": {
     ...,
     "knex": "TS_NODE_COMPILER_OPTIONS='{ \"module\": \"commonjs\" }' npx knex"
  }
}

now use yarn knex <your command>

@hahayusuf
Copy link

hahayusuf commented Feb 15, 2023

Still having issues with this.

This is the error I am getting:

SyntaxError: Cannot use import statement outside a module

@opatajoshua
Copy link

I got it to work by generating a TS knexfile instead knex init -x ts and moving my js config into it

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

No branches or pull requests