lipgrate
Version:
Lipgrate is a clean and safe migration toolkit for SQL databases. Designed to be readable, minimal, and powerful.
184 lines (159 loc) • 7.12 kB
JavaScript
const fs = require('fs');
const path = require('path');
const logger = require('../common/logger');
const chalk = require('chalk');
const defaultConfigContent = `// Lipgrate Configuration File
//
// This file contains example configurations for PostgreSQL, MySQL, and SQLite.
// Choose the configuration that matches your database and assign it to module.exports.
// 1. PostgreSQL Configuration
const postgresql = {
client: 'postgresql',
connection: {
host: 'localhost',
user: 'your_pg_user',
password: 'your_pg_password',
database: 'your_pg_db',
port: 5432,
},
migrations: {
directory: './migrations/postgresql'
},
seeds: {
directory: './migrations/seeds'
}
};
// 2. MySQL Configuration
const mysql = {
client: 'mysql',
connection: {
host: 'localhost',
user: 'your_mysql_user',
password: 'your_mysql_password',
database: 'your_mysql_db',
port: 3306,
},
migrations: {
directory: './migrations/mysql'
},
seeds: {
directory: './migrations/seeds'
}
};
// 3. SQLite Configuration
const sqlite = {
client: 'sqlite',
connection: {
filename: './lipgrate.db'
},
migrations: {
directory: './migrations/sqlite'
},
seeds: {
directory: './migrations/seeds'
}
};
// --- EXPORT YOUR CHOSEN CONFIGURATION ---
//
// Select the configuration you want to use by assigning it to module.exports.
// By default, PostgreSQL is selected.
module.exports = postgresql;
// To use MySQL, comment out the line above and uncomment the line below:
// module.exports = mysql;
// To use SQLite, comment out the lines above and uncomment the line below:
// module.exports = sqlite;
`;
const configFileName = 'migrator.config.js';
async function execute() {
let createdSomething = false;
// 1. Handle config file creation
const configPath = path.resolve(process.cwd(), configFileName);
if (fs.existsSync(configPath)) {
logger.warning(`Configuration file '${configFileName}' already exists.`);
} else {
fs.writeFileSync(configPath, defaultConfigContent);
logger.success(`✔ Created configuration file: ${configFileName}`);
createdSomething = true;
}
// 2. Handle migrations directory creation
const migrationsDir = path.resolve(process.cwd(), 'migrations');
if (fs.existsSync(migrationsDir)) {
logger.warning(`Migrations directory 'migrations' already exists.`);
} else {
fs.mkdirSync(migrationsDir, { recursive: true });
logger.success(`✔ Created migrations directory: migrations/`);
// Create subdirectories for each database type
const dbDirs = ['postgresql', 'mysql', 'sqlite'];
dbDirs.forEach(dir => {
const dbDirPath = path.join(migrationsDir, dir);
if (!fs.existsSync(dbDirPath)) {
fs.mkdirSync(dbDirPath);
logger.success(` ✔ Created subdirectory: migrations/${dir}/`);
}
});
createExampleMigrations(migrationsDir);
createdSomething = true;
}
// 3. Handle seeds directory creation
const seedsPath = path.join('migrations', 'seeds');
const seedsDir = path.join(process.cwd(), seedsPath);
if (fs.existsSync(seedsDir)) {
logger.warning(`Seeds directory '${seedsPath}' already exists.`);
} else {
fs.mkdirSync(seedsDir, { recursive: true });
logger.success(`✔ Created seeds directory: ${seedsPath}`);
const exampleSeedFile = path.join(seedsDir, '01_example_seed.js');
const seedContent = `/**
* This is an example seed file.
* Seed files are simple JavaScript modules that export an async 'run' function.
*
* @param {object} db - The database adapter instance.
*/
exports.run = async function (db) {
// It's good practice to log what your seed is doing.
console.log('Seeding initial data...');
// Use 'await' for all database operations.
// Use parameterized queries (the '?' placeholders) to prevent SQL injection.
// This example assumes you have a table that can accept this data.
// Example:
// await db.query(
// 'INSERT INTO products (name, price) VALUES (?, ?), (?, ?)',
// ['Laptop Pro', 1499.99, 'Wireless Mouse', 49.99]
// );
console.log('Finished seeding.');
};`;
fs.writeFileSync(exampleSeedFile, seedContent);
logger.success('✔ Created example seed file.');
createdSomething = true;
}
// Display final message if anything was created
if (createdSomething) {
logger.success('\nLipgrate initialized successfully!');
logger.info('Next steps:\n 1. Edit migrator.config.js to match your database credentials.\n 2. Create your first migration: lipgrate create <migration_name>\n 3. Run migrations: lipgrate migrate\n 4. Seed your database: lipgrate seed');
} else {
logger.info('Everything is already set up. You can start creating migrations.');
}
}
const examplePostgres = `// Example migration for PostgreSQL\n// Creates a 'users' table and an index on the email column.\n\nexports.up = [\n {\n createTable: {\n name: 'users',\n columns: {\n id: 'serial:primary',\n email: 'varchar(255):notNullable:unique',\n password_hash: 'varchar(255):notNullable',\n created_at: 'timestamptz:default(now())',\n updated_at: 'timestamptz:default(now())'\n }\n }\n },\n {\n addIndex: {\n table: 'users',\n columns: 'email',\n name: 'users_email_idx'\n }\n }\n];\n\nexports.down = [\n {\n dropIndex: {\n name: 'users_email_idx'\n }\n },\n {\n dropTable: 'users'\n }\n];`;
const exampleMySql = `// Example migration for MySQL\n// Creates a 'products' table with timestamps.\n\nexports.up = {\n createTable: {\n name: 'products',\n columns: {\n id: 'increments',\n name: 'string:notNullable',\n description: 'text',\n price: 'decimal(10, 2):notNullable'\n },\n options: {\n timestamps: true // Automatically adds created_at and updated_at\n }\n }\n};\n\nexports.down = {\n dropTable: 'products'\n};`;
const exampleSqlite = `// Example migration for SQLite\n// Creates a 'posts' table.\n\nexports.up = {\n createTable: {\n name: 'posts',\n columns: {\n id: 'integer:primary:autoincrement',\n title: 'string:notNullable',\n body: 'text',\n author_id: 'integer'\n }\n }\n};\n\nexports.down = {\n dropTable: 'posts'\n};`;
function createExampleMigrations(migrationsDir) {
const examples = [
{ dir: 'postgresql', name: '001_postgres_example.js.example', content: examplePostgres },
{ dir: 'mysql', name: '002_mysql_example.js.example', content: exampleMySql },
{ dir: 'sqlite', name: '003_sqlite_example.js.example', content: exampleSqlite },
];
try {
examples.forEach(example => {
const dirPath = path.join(migrationsDir, example.dir);
const filePath = path.join(dirPath, example.name);
if (!fs.existsSync(filePath)) {
fs.writeFileSync(filePath, example.content);
}
});
logger.success('✔ Created example migration files in their respective directories.');
} catch (error) {
logger.warning('Could not create example migration files.');
}
}
module.exports = { execute };