directus
148 строк · 4.4 Кб
1import { useEnv } from '@directus/env';
2import formatTitle from '@directus/format-title';
3import fse from 'fs-extra';
4import type { Knex } from 'knex';
5import { orderBy } from 'lodash-es';
6import { dirname } from 'node:path';
7import { fileURLToPath } from 'node:url';
8import path from 'path';
9import { flushCaches } from '../../cache.js';
10import { useLogger } from '../../logger.js';
11import type { Migration } from '../../types/index.js';
12import getModuleDefault from '../../utils/get-module-default.js';
13
14const __dirname = dirname(fileURLToPath(import.meta.url));
15
16export default async function run(database: Knex, direction: 'up' | 'down' | 'latest', log = true): Promise<void> {
17const env = useEnv();
18const logger = useLogger();
19
20let migrationFiles = await fse.readdir(__dirname);
21
22const customMigrationsPath = path.resolve(env['MIGRATIONS_PATH'] as string);
23
24let customMigrationFiles =
25((await fse.pathExists(customMigrationsPath)) && (await fse.readdir(customMigrationsPath))) || [];
26
27migrationFiles = migrationFiles.filter((file: string) => /^[0-9]+[A-Z]-[^.]+\.(?:js|ts)$/.test(file));
28
29customMigrationFiles = customMigrationFiles.filter((file: string) => file.includes('-') && /\.(c|m)?js$/.test(file));
30
31const completedMigrations = await database.select<Migration[]>('*').from('directus_migrations').orderBy('version');
32
33const migrations = [
34...migrationFiles.map((path) => parseFilePath(path)),
35...customMigrationFiles.map((path) => parseFilePath(path, true)),
36].sort((a, b) => (a.version! > b.version! ? 1 : -1));
37
38const migrationKeys = new Set(migrations.map((m) => m.version));
39
40if (migrations.length > migrationKeys.size) {
41throw new Error('Migration keys collide! Please ensure that every migration uses a unique key.');
42}
43
44function parseFilePath(filePath: string, custom = false) {
45const version = filePath.split('-')[0];
46const name = formatTitle(filePath.split('-').slice(1).join('_').split('.')[0]!);
47const completed = !!completedMigrations.find((migration) => migration.version === version);
48
49return {
50file: custom ? path.join(customMigrationsPath, filePath) : path.join(__dirname, filePath),
51version,
52name,
53completed,
54};
55}
56
57if (direction === 'up') await up();
58if (direction === 'down') await down();
59if (direction === 'latest') await latest();
60
61async function up() {
62const currentVersion = completedMigrations[completedMigrations.length - 1];
63
64let nextVersion: any;
65
66if (!currentVersion) {
67nextVersion = migrations[0];
68} else {
69nextVersion = migrations.find((migration) => {
70return migration.version! > currentVersion.version && migration.completed === false;
71});
72}
73
74if (!nextVersion) {
75throw Error('Nothing to upgrade');
76}
77
78const { up } = await import(`file://${nextVersion.file}`);
79
80if (!up) {
81logger.warn(`Couldn't find the "up" function from migration ${nextVersion.file}`);
82}
83
84if (log) {
85logger.info(`Applying ${nextVersion.name}...`);
86}
87
88await up(database);
89await database.insert({ version: nextVersion.version, name: nextVersion.name }).into('directus_migrations');
90
91await flushCaches(true);
92}
93
94async function down() {
95const lastAppliedMigration = orderBy(completedMigrations, ['timestamp', 'version'], ['desc', 'desc'])[0];
96
97if (!lastAppliedMigration) {
98throw Error('Nothing to downgrade');
99}
100
101const migration = migrations.find((migration) => migration.version === lastAppliedMigration.version);
102
103if (!migration) {
104throw new Error("Couldn't find migration");
105}
106
107const { down } = await import(`file://${migration.file}`);
108
109if (!down) {
110logger.warn(`Couldn't find the "down" function from migration ${migration.file}`);
111}
112
113if (log) {
114logger.info(`Undoing ${migration.name}...`);
115}
116
117await down(database);
118await database('directus_migrations').delete().where({ version: migration.version });
119
120await flushCaches(true);
121}
122
123async function latest() {
124let needsCacheFlush = false;
125
126for (const migration of migrations) {
127if (migration.completed === false) {
128needsCacheFlush = true;
129const { up } = getModuleDefault(await import(`file://${migration.file}`));
130
131if (!up) {
132logger.warn(`Couldn't find the "up" function from migration ${migration.file}`);
133}
134
135if (log) {
136logger.info(`Applying ${migration.name}...`);
137}
138
139await up(database);
140await database.insert({ version: migration.version, name: migration.name }).into('directus_migrations');
141}
142}
143
144if (needsCacheFlush) {
145await flushCaches(true);
146}
147}
148}
149