107 lines
3.1 KiB
TypeScript
107 lines
3.1 KiB
TypeScript
/**
|
|
* Scaffold a new migration file under `scripts/migrations/`.
|
|
*
|
|
* Usage: npm run migrate:create -- <snake_case_description>
|
|
* e.g.: npm run migrate:create -- add_finance_kyc_column
|
|
*
|
|
* The file is named `<YYYYMMDDHHMMSS>_<description>.ts` (UTC timestamp).
|
|
* Author then edits `up()` to implement the schema change.
|
|
*/
|
|
|
|
import { promises as fs } from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
const SCRIPTS_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
const MIGRATIONS_DIR = path.join(SCRIPTS_DIR, 'migrations');
|
|
|
|
function pad(n: number, width = 2): string {
|
|
return String(n).padStart(width, '0');
|
|
}
|
|
|
|
function utcTimestamp(): string {
|
|
const d = new Date();
|
|
return (
|
|
d.getUTCFullYear().toString() +
|
|
pad(d.getUTCMonth() + 1) +
|
|
pad(d.getUTCDate()) +
|
|
pad(d.getUTCHours()) +
|
|
pad(d.getUTCMinutes()) +
|
|
pad(d.getUTCSeconds())
|
|
);
|
|
}
|
|
|
|
function sanitizeName(input: string): string {
|
|
const cleaned = input
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/[^a-z0-9]+/g, '_')
|
|
.replace(/^_+|_+$/g, '');
|
|
if (!cleaned) {
|
|
throw new Error('Migration name is empty after sanitisation.');
|
|
}
|
|
if (cleaned.length > 80) {
|
|
throw new Error(
|
|
`Migration name "${cleaned}" is too long (${cleaned.length}). Keep it under 80 chars.`
|
|
);
|
|
}
|
|
return cleaned;
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const rawName = process.argv.slice(2).join('_');
|
|
if (!rawName) {
|
|
console.error('Missing migration name.');
|
|
console.error('Usage: npm run migrate:create -- <snake_case_description>');
|
|
process.exit(1);
|
|
}
|
|
|
|
const name = sanitizeName(rawName);
|
|
const ts = utcTimestamp();
|
|
const filename = `${ts}_${name}.ts`;
|
|
const target = path.join(MIGRATIONS_DIR, filename);
|
|
|
|
const body = `/**
|
|
* Migration: ${name.replace(/_/g, ' ')}
|
|
*
|
|
* Generated at ${new Date().toISOString()}.
|
|
* Implement up() with idempotent DDL where possible.
|
|
*/
|
|
|
|
import type { QueryInterface, Sequelize, Transaction } from 'sequelize';
|
|
|
|
export interface MigrationContext {
|
|
queryInterface: QueryInterface;
|
|
sequelize: Sequelize;
|
|
transaction: Transaction;
|
|
}
|
|
|
|
const migration = {
|
|
async up({ sequelize, transaction }: MigrationContext): Promise<void> {
|
|
// TODO: implement the schema change.
|
|
// Example:
|
|
// await sequelize.query(\`
|
|
// ALTER TABLE "applications"
|
|
// ADD COLUMN IF NOT EXISTS "kycReviewedAt" TIMESTAMPTZ NULL;
|
|
// \`, { transaction });
|
|
throw new Error('Migration ${filename} has no up() implementation yet.');
|
|
}
|
|
};
|
|
|
|
export default migration;
|
|
`;
|
|
|
|
await fs.mkdir(MIGRATIONS_DIR, { recursive: true });
|
|
await fs.writeFile(target, body, { flag: 'wx' });
|
|
console.log(`Created ${path.relative(process.cwd(), target)}`);
|
|
console.log('Next:');
|
|
console.log(' 1. Edit the file and implement up().');
|
|
console.log(' 2. Update the matching Sequelize model.');
|
|
console.log(' 3. Run: npm run migrate:up');
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error('create-migration failed:', err?.message || err);
|
|
process.exit(1);
|
|
});
|