Skip to content

Commit

Permalink
Support for TS migrations (#521)
Browse files Browse the repository at this point in the history
* Alternative for transpiling (#522)

* Support for TS migrations

* Typing for shorthands

* Fixed lint

* Checks in custom migration
  • Loading branch information
dolezel committed Nov 29, 2019
1 parent d6bade9 commit 97d2b29
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 28 deletions.
36 changes: 36 additions & 0 deletions .circleci/config.yml
Expand Up @@ -285,6 +285,36 @@ jobs:
- run:
name: test
command: npm run migrate up -- -s myschema public --create-schema -m test/migrations && npm run migrate down 0 -- -s myschema public -m test/migrations --timestamps
test-typescript-migration:
docker:
- image: circleci/node:12
environment:
- DATABASE_URL=postgres://ubuntu@localhost:5432/circle_test
- image: postgres:10.11-alpine
environment:
- POSTGRES_USER=ubuntu
- POSTGRES_DB=circle_test
steps:
- <<: *restore
- <<: *postgres-wait
- run:
name: test
command: npm run migrate up -- --tsconfig tsconfig.json -m test/ts/migrations && npm run migrate down 0 -- --tsconfig tsconfig.json -m test/ts/migrations --timestamps
test-typescript-customrunner:
docker:
- image: circleci/node:12
environment:
- DATABASE_URL=postgres://ubuntu@localhost:5432/circle_test
- image: postgres:10.11-alpine
environment:
- POSTGRES_USER=ubuntu
- POSTGRES_DB=circle_test
steps:
- <<: *restore
- <<: *postgres-wait
- run:
name: test
command: $(npm bin)/ts-node test/ts/customRunner.ts

workflows:
version: 2
Expand Down Expand Up @@ -336,3 +366,9 @@ workflows:
- test-schemas:
requires:
- install
- test-typescript-migration:
requires:
- install
- test-typescript-customrunner:
requires:
- install
43 changes: 38 additions & 5 deletions bin/node-pg-migrate
Expand Up @@ -10,7 +10,7 @@ const ConnectionParameters = require('pg/lib/connection-parameters')
const { default: migrationRunner, Migration } = require('../dist')

process.on('uncaughtException', err => {
console.log(err.stack)
console.error(err.stack)
process.exit(1)
})

Expand Down Expand Up @@ -40,6 +40,7 @@ const timestampArg = 'timestamp'
const dryRunArg = 'dry-run'
const fakeArg = 'fake'
const decamelizeArg = 'decamelize'
const tsconfigArg = 'tsconfig'

const { argv } = yargs
.usage('Usage: $0 [up|down|create|redo] [migrationName] [options]')
Expand Down Expand Up @@ -170,6 +171,12 @@ const { argv } = yargs
type: 'boolean',
})

.option(tsconfigArg, {
default: undefined,
describe: 'path to tsconfig.json file',
type: 'string',
})

.help()

if (argv.version) {
Expand All @@ -194,6 +201,32 @@ let MIGRATIONS_FILE_LANGUAGE = argv[migrationFileLanguageArg]
let CHECK_ORDER = argv[checkOrderArg]
let DECAMELIZE = argv[decamelizeArg]

const tsconfigPath = argv[tsconfigArg]
if (tsconfigPath) {
let tsconfig
let tsnode
try {
// eslint-disable-next-line global-require,import/no-dynamic-require,security/detect-non-literal-require
tsconfig = require(path.resolve(process.cwd(), tsconfigPath))
} catch (err) {
console.error(`Can't load tsconfig.json: ${err.stack}`)
}
try {
// eslint-disable-next-line global-require,import/no-extraneous-dependencies
tsnode = require('ts-node')
} catch (err) {
console.error(`Can't load ts-node: ${err.stack}`)
}
if (tsconfig && tsnode) {
tsnode.register(tsconfig)
if (!MIGRATIONS_FILE_LANGUAGE) {
MIGRATIONS_FILE_LANGUAGE = 'ts'
}
} else {
process.exit(1)
}
}

function readJson(json) {
if (typeof json === 'object') {
SCHEMA = json[schemaArg] || SCHEMA
Expand Down Expand Up @@ -254,7 +287,7 @@ if (action === 'create') {
newMigrationName = newMigrationName.replace(/[_ ]+/g, '-')

if (!newMigrationName) {
console.log("'migrationName' is required.")
console.error("'migrationName' is required.")
yargs.showHelp()
process.exit(1)
}
Expand All @@ -265,7 +298,7 @@ if (action === 'create') {
process.exit(0)
})
.catch(err => {
console.log(err.stack)
console.error(err.stack)
process.exit(1)
})
} else if (action === 'up' || action === 'down' || action === 'redo') {
Expand Down Expand Up @@ -339,11 +372,11 @@ if (action === 'create') {
process.exit(0)
})
.catch(err => {
console.log(err.stack)
console.error(err.stack)
process.exit(1)
})
} else {
console.log('Invalid Action: Must be [up|down|create|redo].')
console.error('Invalid Action: Must be [up|down|create|redo].')
yargs.showHelp()
process.exit(1)
}
Expand Down
1 change: 1 addition & 0 deletions docs/cli.md
Expand Up @@ -71,6 +71,7 @@ You can adjust defaults by passing arguments to `node-pg-migrate`:
- `migrations-table` (`t`) - The table storing which migrations have been run (defaults to `pgmigrations`)
- `ignore-pattern` - Regex pattern for file names to ignore (e.g. `ignore_file|\..*|.*\.spec\.js`)
- `migration-file-language` (`j`) - Language of the migration file to create (`js` or `ts`)
- `tsconfig` - Path to tsconfig.json. Used to setup transpiling of TS migration files. (Also sets `migration-file-language` to typescript, if not overriden)
- `timestamp` - Treats number argument to up/down migration as timestamp (running up migrations less or equal to timestamp or down migrations greater or equal to timestamp)
- `check-order` - Check order of migrations before running them (defaults to `true`, to switch it off supply `--no-check-order` on command line).
(There should be no migration with timestamp lesser than last run migration.)
Expand Down
32 changes: 24 additions & 8 deletions docs/transpiling.md
@@ -1,6 +1,8 @@
# Transpiling Babel or Typescript
# Transpiling

You can use babel or typescript for transpiling migration files. You have e.g. these options:
## Transpiling Babel

You can use babel for transpiling migration files. You have e.g. these options:

### Use global configuration

Expand All @@ -14,17 +16,31 @@ It requires a little setup to use:
1. Create `migrate.js` file with contents:

```
// require('babel-core/register')( { ... your babel config ... } );
// require('ts-node').register( { ... your typescript config ... } );
require('babel-core/register')( { ... your babel config ... } );
require('./node_modules/node-pg-migrate/bin/node-pg-migrate');
```

Uncomment/Use either babel or typescript hook and adjust your config for compiler.
You can then use migration as usual via e.g. `npm run migrate up`. :tada:
## Transpiling Typescript

### Use flag

Typescript is supported out of the box. You need to have installed `ts-node` package and need to pass `tsconfig` arg ([see](https://github.com/salsita/node-pg-migrate/blob/master/docs/cli.md#configuration))

### Use global configuration

Another option is to use [ts-node](https://www.npmjs.com/package/ts-node) CLI directly and it needs to be available globally or as a dependency.
If migrations are in the `/src/migrations` folder then the path can still be referenced with the `-m` CLI option.

```
"migrate": "ts-node node_modules/.bin/node-pg-migrate -m src/migrations -j ts",
"migrate": "ts-node node_modules/.bin/node-pg-migrate -j ts",
```

### Use custom configuration

If you need some more advanced TS config, you need to register transpiler yourself like in using babel configuration.

```
const config = { ... your ts config ... }
require('ts-node').register(config);
// e.g. require("tsconfig-paths").register(config.compilerOptions);
require('./node_modules/node-pg-migrate/bin/node-pg-migrate');
```
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/index.ts
Expand Up @@ -117,6 +117,7 @@ import {
} from './operations/viewsMaterializedTypes'

export {
runner as default,
PgLiteral,
Migration,
PgType,
Expand Down Expand Up @@ -232,4 +233,3 @@ export {
AlterMaterializedViewOptions,
RefreshMaterializedViewOptions,
}
export default runner
22 changes: 12 additions & 10 deletions src/migration.ts
Expand Up @@ -21,19 +21,21 @@ const lstat = promisify(fs.lstat) // eslint-disable-line security/detect-non-lit

const SEPARATOR = '_'

export const loadMigrationFiles = async (dir: string, ignorePattern: string) => {
export const loadMigrationFiles = async (dir: string, ignorePattern?: string) => {
const dirContent = await readdir(`${dir}/`)
const files = await Promise.all(
dirContent.map(async file => {
const stats = await lstat(`${dir}/${file}`)
return stats.isFile() ? file : null
}),
)
const files = (
await Promise.all(
dirContent.map(async file => {
const stats = await lstat(`${dir}/${file}`)
return stats.isFile() ? file : null
}),
)
).sort()
const filter = new RegExp(`^(${ignorePattern})$`) // eslint-disable-line security/detect-non-literal-regexp
return files.filter(i => i && !filter.test(i)).sort()
return ignorePattern === undefined ? files : files.filter(i => i && !filter.test(i))
}

const getLastSuffix = async (dir: string, ignorePattern: string) => {
const getLastSuffix = async (dir: string, ignorePattern?: string) => {
try {
const files = await loadMigrationFiles(dir, ignorePattern)
return files.length > 0 ? path.extname(files[files.length - 1]).substr(1) : undefined
Expand All @@ -50,7 +52,7 @@ export interface RunMigration {

export class Migration implements RunMigration {
// class method that creates a new migration file by cloning the migration template
static async create(name: string, directory: string, language: 'js' | 'ts' | 'sql', ignorePattern: string) {
static async create(name: string, directory: string, language?: 'js' | 'ts' | 'sql', ignorePattern?: string) {
// ensure the migrations directory exists
mkdirp.sync(directory)

Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Expand Up @@ -191,7 +191,7 @@ export interface RunnerOptionConfig {
direction: MigrationDirection
count: number
timestamp?: boolean
ignorePattern: string
ignorePattern?: string
file?: string
dryRun?: boolean
createSchema?: boolean
Expand Down
4 changes: 2 additions & 2 deletions templates/migration-template.ts
@@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/camelcase */
import { MigrationBuilder } from 'node-pg-migrate';
import { MigrationBuilder, ColumnDefinitions } from 'node-pg-migrate';

export const shorthands = undefined;
export const shorthands: ColumnDefinitions = undefined;

export async function up(pgm: MigrationBuilder): Promise<void> {
}
Expand Down
39 changes: 39 additions & 0 deletions test/ts/customRunner.ts
@@ -0,0 +1,39 @@
import { resolve } from 'path'
import runner, { RunnerOption } from '../../dist'

const run = async () => {
try {
const options: Omit<RunnerOption, 'direction'> & { databaseUrl: string } = {
migrationsTable: 'migrations',
dir: resolve(__dirname, 'migrations'),
count: Infinity,
databaseUrl: process.env.DATABASE_URL,
}
const upResult = await runner({
...options,
direction: 'up',
})
if (upResult.length !== 1) {
console.error('There should be exactly one migration processed')
process.exit(1)
}
console.log('Up success')
console.log(upResult)
const downResult = await runner({
...options,
direction: 'down',
})
if (downResult.length !== 1) {
console.error('There should be exactly one migration processed')
process.exit(1)
}
console.log('Down success')
console.log(downResult)
process.exit(0)
} catch (err) {
console.error(err)
process.exit(1)
}
}

run()
15 changes: 15 additions & 0 deletions test/ts/migrations/test-migration.ts
@@ -0,0 +1,15 @@
import { MigrationBuilder, ColumnDefinitions } from '../../../dist'

export const shorthands: ColumnDefinitions = undefined

export async function up(pgm: MigrationBuilder): Promise<void> {
pgm.createTable('t1', {
id: 'id',
string: { type: 'text', notNull: true },
created: {
type: 'timestamp',
notNull: true,
default: pgm.func('current_timestamp'),
},
})
}
1 change: 1 addition & 0 deletions tsconfig.json
@@ -1,6 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"rootDir": "src",
"outDir": "dist",
Expand Down

0 comments on commit 97d2b29

Please sign in to comment.