@directus/api
Version:
Directus is a real-time API and App dashboard for managing SQL database content
112 lines (111 loc) • 4.94 kB
JavaScript
import { useEnv } from '@directus/env';
import formatTitle from '@directus/format-title';
import fse from 'fs-extra';
import { orderBy } from 'lodash-es';
import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
import path from 'path';
import { flushCaches } from '../../cache.js';
import { useLogger } from '../../logger/index.js';
import getModuleDefault from '../../utils/get-module-default.js';
const __dirname = dirname(fileURLToPath(import.meta.url));
export default async function run(database, direction, log = true) {
const env = useEnv();
const logger = useLogger();
let migrationFiles = await fse.readdir(__dirname);
const customMigrationsPath = path.resolve(env['MIGRATIONS_PATH']);
let customMigrationFiles = ((await fse.pathExists(customMigrationsPath)) && (await fse.readdir(customMigrationsPath))) || [];
migrationFiles = migrationFiles.filter((file) => /^[0-9]+[A-Z]-[^.]+\.(?:js|ts)$/.test(file));
customMigrationFiles = customMigrationFiles.filter((file) => file.includes('-') && /\.(c|m)?js$/.test(file));
const completedMigrations = await database.select('*').from('directus_migrations').orderBy('version');
const migrations = [
...migrationFiles.map((path) => parseFilePath(path)),
...customMigrationFiles.map((path) => parseFilePath(path, true)),
].sort((a, b) => (a.version > b.version ? 1 : -1));
const migrationKeys = new Set(migrations.map((m) => m.version));
if (migrations.length > migrationKeys.size) {
throw new Error('Migration keys collide! Please ensure that every migration uses a unique key.');
}
function parseFilePath(filePath, custom = false) {
const version = filePath.split('-')[0];
const name = formatTitle(filePath.split('-').slice(1).join('_').split('.')[0]);
const completed = !!completedMigrations.find((migration) => migration.version === version);
return {
file: custom ? path.join(customMigrationsPath, filePath) : path.join(__dirname, filePath),
version,
name,
completed,
};
}
if (direction === 'up')
await up();
if (direction === 'down')
await down();
if (direction === 'latest')
await latest();
async function up() {
const currentVersion = completedMigrations[completedMigrations.length - 1];
let nextVersion;
if (!currentVersion) {
nextVersion = migrations[0];
}
else {
nextVersion = migrations.find((migration) => {
return migration.version > currentVersion.version && migration.completed === false;
});
}
if (!nextVersion) {
throw Error('Nothing to upgrade');
}
const { up } = await import(`file://${nextVersion.file}`);
if (!up) {
logger.warn(`Couldn't find the "up" function from migration ${nextVersion.file}`);
}
if (log) {
logger.info(`Applying ${nextVersion.name}...`);
}
await up(database);
await database.insert({ version: nextVersion.version, name: nextVersion.name }).into('directus_migrations');
await flushCaches(true);
}
async function down() {
const lastAppliedMigration = orderBy(completedMigrations, ['timestamp', 'version'], ['desc', 'desc'])[0];
if (!lastAppliedMigration) {
throw Error('Nothing to downgrade');
}
const migration = migrations.find((migration) => migration.version === lastAppliedMigration.version);
if (!migration) {
throw new Error("Couldn't find migration");
}
const { down } = await import(`file://${migration.file}`);
if (!down) {
logger.warn(`Couldn't find the "down" function from migration ${migration.file}`);
}
if (log) {
logger.info(`Undoing ${migration.name}...`);
}
await down(database);
await database('directus_migrations').delete().where({ version: migration.version });
await flushCaches(true);
}
async function latest() {
let needsCacheFlush = false;
for (const migration of migrations) {
if (migration.completed === false) {
needsCacheFlush = true;
const { up } = getModuleDefault(await import(`file://${migration.file}`));
if (!up) {
logger.warn(`Couldn't find the "up" function from migration ${migration.file}`);
}
if (log) {
logger.info(`Applying ${migration.name}...`);
}
await up(database);
await database.insert({ version: migration.version, name: migration.name }).into('directus_migrations');
}
}
if (needsCacheFlush) {
await flushCaches(true);
}
}
}