directus
126 строк · 3.8 Кб
1import { resolvePackage } from '@directus/utils/node';2import type { Knex } from 'knex';3import { randomUUID } from 'node:crypto';4import { dirname } from 'node:path';5import { fileURLToPath } from 'node:url';6
7const __dirname = dirname(fileURLToPath(import.meta.url));8
9export async function up(knex: Knex): Promise<void> {10await knex.schema.alterTable('directus_extensions', (table) => {11table.uuid('id').nullable();12table.string('folder');13table.string('source');14table.uuid('bundle');15});16
17const installedExtensions = await knex.select('name').from('directus_extensions');18
19// name: id20const idMap = new Map<string, { id: string; source: 'local' | 'module' }>();21
22for (const { name } of installedExtensions) {23// Delete extension meta status that used the legacy `${name}:${type}` name syntax for24// extension-folder scoped extensions25if (name.includes(':')) {26await knex('directus_extensions').delete().where({ name });27} else {28const id = randomUUID();29
30let source: 'local' | 'module';31
32try {33// The NPM package name is the name used in the database. If we can resolve the34// extension as a node module it's safe to assume it's a npm-module source35resolvePackage(name, __dirname);36source = 'module';37} catch {38source = 'local';39}40
41await knex('directus_extensions').update({ id, source, folder: name }).where({ name });42idMap.set(name, { id, source });43}44}45
46for (const { name } of installedExtensions) {47if (!name.includes('/')) continue;48
49const splittedName = name.split('/');50
51const isScopedModuleBundleParent = name.startsWith('@') && splittedName.length == 2;52
53if (isScopedModuleBundleParent) continue;54
55const isScopedModuleBundleChild = name.startsWith('@') && splittedName.length > 2;56
57const bundleParentName =58isScopedModuleBundleParent || isScopedModuleBundleChild ? splittedName.slice(0, 2).join('/') : splittedName[0];59
60const bundleParent = idMap.get(bundleParentName);61
62if (!bundleParent) continue;63
64await knex('directus_extensions')65.update({66bundle: bundleParent.id,67folder: name.substring(bundleParentName.length + 1),68source: bundleParent.source,69})70.where({ folder: name });71}72
73await knex.schema.alterTable('directus_extensions', (table) => {74table.uuid('id').alter().notNullable();75});76
77await knex.transaction(async (trx) => {78await trx.schema.alterTable('directus_extensions', (table) => {79table.dropPrimary();80table.primary(['id']);81});82});83
84await knex.schema.alterTable('directus_extensions', (table) => {85table.dropColumn('name');86table.string('source').alter().notNullable();87table.string('folder').alter().notNullable();88});89}
90
91/*
92* Note: For local extensions having a different package & folder name,
93* we aren't able to revert to the exact same state as before.
94* But we still need to do the name convertion, in order for the migration to succeed.
95*/
96export async function down(knex: Knex): Promise<void> {97await knex.schema.alterTable('directus_extensions', (table) => {98table.string('name');99});100
101const installedExtensions = await knex.select(['id', 'folder', 'bundle']).from('directus_extensions');102
103const idMap = new Map<string, string>(installedExtensions.map((extension) => [extension.id, extension.folder]));104
105for (const { id, folder, bundle, source } of installedExtensions) {106if (source === 'registry') {107await knex('directus_extensions').delete().where({ id });108continue;109}110
111let name = folder;112
113if (bundle) {114const bundleParentName = idMap.get(bundle);115
116name = `${bundleParentName}/${name}`;117}118
119await knex('directus_extensions').update({ name }).where({ id });120}121
122await knex.schema.alterTable('directus_extensions', (table) => {123table.dropColumns('id', 'folder', 'source', 'bundle');124table.string('name').alter().primary().notNullable();125});126}
127