Skip to content

About

A Gradle plugin for generating and running PostgreSQL migrations. Enables tracking of what's applied so nothing runs twice. Reads DB config with .env support.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Cytoskeleton

Cytoskeleton is a Gradle plugin for PostgreSQL databases. It gives your project two commands:

  • generateMigration – makes a new, empty SQL migration file for you (with a helpful starting template if you want one).
  • runMigration – reads your migration files and runs them against your PostgreSQL database. It remembers which ones it already ran, so it never runs the same migration twice.

A "migration" is just a .sql file that changes your database (for example, creating a table or adding a column). Keeping these changes in files means your database history lives in your project, next to your code.

The plugin id is com.outpx.cytoskeleton.


What you need before you start

  1. A JDK (Java) version 21. Gradle uses it to run the plugin. If you don't have Java 21, Gradle can download it for you automatically (this project is already set up for that).
  2. A running PostgreSQL database that you can connect to. The plugin does not create a database for you — it connects to one that already exists.

You do not need to install any extra libraries (like a PostgreSQL driver). The plugin already includes everything it needs. Adding the plugin is the only step.


Step 1 – Add the plugin to your project

Open the build.gradle.kts file of the project where you want migrations and add the plugin:

plugins {
  id("com.outpx.cytoskeleton") version "0.1.0"
}

That single line is all it takes. After this, the two commands (generateMigration and runMigration) are available in that project.

Using it before it is published

If the plugin has not been published yet, you can still use it directly from its source folder. In your project's settings.gradle.kts, tell Gradle where the plugin lives:

// settings.gradle.kts
pluginManagement {
  includeBuild("/path/to/cytoskeleton")   // the folder that contains this plugin
}

Then in build.gradle.kts, add the plugin without a version number (the version is not needed when you point at the source folder):

// build.gradle.kts
plugins {
  id("com.outpx.cytoskeleton")
}

There is a full working example in the example/ folder of this repository.


Step 2 – Tell the plugin how to reach your database

The plugin reads your database details from a file called application.properties. By default, the plugin looks for it here:

src/main/resources/application.properties

Create that file and fill in your database details:

cyto.db.host=localhost
cyto.db.port=5432
cyto.db.name=example
cyto.db.credentials.username=postgres
cyto.db.credentials.password=postgres

Here is what each line means:

Property Meaning
cyto.db.host Where your database is (for example, localhost).
cyto.db.port The port your database listens on (PostgreSQL usually uses 5432).
cyto.db.name The name of the database to connect to.
cyto.db.credentials.username The username to log in with.
cyto.db.credentials.password The password to log in with.

All five properties are required. If any are missing, runMigration stops and tells you which one is missing.

Keeping passwords out of your files (optional but recommended)

You usually do not want to write real passwords directly into application.properties, because that file is often shared or committed to git. Instead, you can use a placeholder written as ${SOMETHING} and keep the real value in a separate .env file.

For example, in application.properties:

cyto.db.credentials.username=${DB_USER}
cyto.db.credentials.password=${DB_PASSWORD}

And in a .env file at the top of your project:

DB_USER=postgres
DB_PASSWORD=my-secret-password

When you run a migration, the plugin swaps each ${DB_USER} placeholder for the matching value. It looks for that value in two places, in this order:

  1. The nearest .env file (the plugin searches the current folder, then its parent folders, until it finds one).
  2. Your computer's environment variables (what you would see with echo $DB_USER).

If a placeholder cannot be found in either place, runMigration stops and lists the missing names, so you know exactly what to fix. Remember to add .env to your .gitignore so your secrets are never committed.


Step 3 – Create a migration file

Run this command from your project folder:

./gradlew generateMigration -PmigrationName=CreateUsers
  • -PmigrationName=CreateUsers is required. It is the name of your migration. Pick something that describes what the migration does (for example, CreateUsers, AddEmailColumn).

This creates a new file inside your migrations folder (by default src/main/resources/migrations/). The file name starts with a timestamp so files always run in the order you created them, for example:

src/main/resources/migrations/1787428750594_CreateUsers.sql

The timestamp number in front is added automatically. You then open that file and write your SQL.

Getting a table template automatically

If you are creating a new table, you can ask the plugin to fill the file with a ready-made starting template by adding -PtableName:

./gradlew generateMigration -PmigrationName=CreateUsers -PtableName=users

This writes a CREATE TABLE users (...) statement, plus a few helpful database triggers, into the new file for you. By default the table starts with these columns:

id UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

It also adds triggers that:

  • automatically update the updated_at column whenever a row changes, and
  • send a PostgreSQL notification whenever a row is created, updated, or deleted (useful if something in your app wants to listen for changes).

If you leave out -PtableName, you simply get an empty file to write whatever SQL you like.


Step 4 – Run your migrations

Run this command:

./gradlew runMigration

Here is exactly what happens when you do:

  1. The plugin finds every .sql file in your migrations folder and sorts them by name (which, because of the timestamp, means oldest first).
  2. It reads your application.properties and fills in any ${...} placeholders from your .env file or environment variables.
  3. It connects to your PostgreSQL database using those details.
  4. The first time it runs, it sets up a small bookkeeping table called applied_migrations. This is how it remembers which migrations have already been applied.
  5. It runs each migration that has not been applied before. Migrations that were already applied are skipped. This means running the command again is safe — it will not re-run old migrations.

If there are no migration files, the command simply says there is nothing to do and finishes.


Configuration (optional)

Everything above works with the default folder locations, so you may never need this section. But if you want to change where files live or what the template looks like, you can add settings to your build.gradle.kts.

There are two settings blocks:

  • migrations { } – controls file locations and how migrations run.
  • generateMigration { } – controls the table template used by generateMigration.
migrations {
  // Folder where migration files are created and read from.
  migrationsDir.set(layout.projectDirectory.dir("src/main/resources/migrations"))

  // The file that holds your database connection details.
  applicationProperties.set(layout.projectDirectory.file("src/main/resources/application.properties"))

  // Folder where the search for a `.env` file begins.
  searchStart.set(layout.projectDirectory)

  // Extra SQL that runs once, before your migrations. By default this sets up helper
  // functions the templates rely on (like uuid generation and the notification triggers).
  // Only change this if you know you need something different.
  initSql.set("-- your setup SQL here")
}

generateMigration {
  // The columns placed inside a new CREATE TABLE when you use -PtableName.
  tableSqlContent.set(
    """
    id UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
    """.trimIndent()
  )
}

How the default folder is chosen

If you do not set migrationsDir yourself, the plugin picks a sensible default:

  • If your project uses a JVM plugin (like java or the Kotlin plugin), it uses that project's main resources folder, usually src/main/resources/migrations.
  • If your project does not use any such plugin, it falls back to src/main/resources/migrations.

You can apply a JVM plugin in any order (before or after this plugin) — it does not matter.


Quick reference

Command What it does
./gradlew generateMigration -PmigrationName=Name Create an empty migration file.
./gradlew generateMigration -PmigrationName=Name -PtableName=table Create a migration file pre-filled with a CREATE TABLE template.
./gradlew runMigration Apply all migrations that have not run yet.

License

MIT

About

A Gradle plugin for generating and running PostgreSQL migrations. Enables tracking of what's applied so nothing runs twice. Reads DB config with .env support.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages