mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Fix marketplace/extensions migration (#21698)
* Fix marketplace/extensions migration * Drop settings for deprecated extension types * Resolve local v module on migration * Revert workaround now that it happens in migratoin * Don't fail on empty bundles * Account for scoped packages in migration * Fix for matching existing settings to local extension with different folder & package name * Update api/src/extensions/lib/get-extensions-settings.ts --------- Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
import { resolvePackage } from '@directus/utils/node';
|
||||
import type { Knex } from 'knex';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { dirname } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable('directus_extensions', (table) => {
|
||||
@@ -14,22 +19,49 @@ export async function up(knex: Knex): Promise<void> {
|
||||
const idMap = new Map<string, string>();
|
||||
|
||||
for (const { name } of installedExtensions) {
|
||||
const id = randomUUID();
|
||||
await knex('directus_extensions').update({ id, source: 'local' }).where({ name });
|
||||
idMap.set(name, id);
|
||||
// Delete extension meta status that used the legacy `${name}:${type}` name syntax for
|
||||
// extension-folder scoped extensions
|
||||
if (name.includes(':')) {
|
||||
await knex('directus_extensions').delete().where({ name });
|
||||
} else {
|
||||
const id = randomUUID();
|
||||
|
||||
let source;
|
||||
|
||||
try {
|
||||
// The NPM package name is the name used in the database. If we can resolve the
|
||||
// extension as a node module it's safe to assume it's a npm-module source
|
||||
resolvePackage(name, __dirname);
|
||||
source = 'module';
|
||||
} catch {
|
||||
source = 'local';
|
||||
}
|
||||
|
||||
await knex('directus_extensions').update({ id, source }).where({ name });
|
||||
idMap.set(name, id);
|
||||
}
|
||||
}
|
||||
|
||||
// This will also include flat extensions with an NPM org scope, but there's no way to identify
|
||||
// those
|
||||
const bundleNames = Array.from(idMap.keys()).filter((name) => name.includes('/'));
|
||||
|
||||
for (const { name } of installedExtensions) {
|
||||
const bundleParent = bundleNames.find((bundleName) => name.startsWith(bundleName + '/'));
|
||||
if (!name.includes('/')) continue;
|
||||
|
||||
if (!bundleParent) continue;
|
||||
const splittedName = name.split('/');
|
||||
|
||||
const isScopedModuleBundleParent = name.startsWith('@') && splittedName.length == 2;
|
||||
|
||||
if (isScopedModuleBundleParent) continue;
|
||||
|
||||
const isScopedModuleBundleChild = name.startsWith('@') && splittedName.length > 2;
|
||||
|
||||
const bundleParentName =
|
||||
isScopedModuleBundleParent || isScopedModuleBundleChild ? splittedName.slice(0, 2).join('/') : splittedName[0];
|
||||
|
||||
const bundleParentId = idMap.get(bundleParentName);
|
||||
|
||||
if (!bundleParentId) continue;
|
||||
|
||||
await knex('directus_extensions')
|
||||
.update({ bundle: idMap.get(bundleParent), name: name.substring(bundleParent.length + 1) })
|
||||
.update({ bundle: bundleParentId, name: name.substring(bundleParentName.length + 1) })
|
||||
.where({ name });
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,24 @@ export const getExtensionsSettings = async ({
|
||||
|
||||
for (const [folder, extension] of local.entries()) {
|
||||
const settingsExist = localSettings.some((settings) => settings.folder === folder);
|
||||
if (settingsExist) continue;
|
||||
|
||||
const settingsForName = localSettings.find((settings) => settings.folder === extension.name);
|
||||
|
||||
/*
|
||||
* TODO: Consider removing this in follow-up versions after v10.10.0
|
||||
*
|
||||
* Previously, the package name (from package.json) was used to identify
|
||||
* local extensions - now it's the folder name.
|
||||
* If those two are different, we need to check for existing settings
|
||||
* with the package name, too. On a match and if there's no other local extension
|
||||
* with such a folder name, these settings can be taken over with the folder updated.
|
||||
*/
|
||||
if (settingsForName && !local.has(extension.name)) {
|
||||
await service.extensionsItemService.updateOne(settingsForName.id, { folder });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!settingsExist) generateSettingsEntry(folder, extension, 'local');
|
||||
}
|
||||
|
||||
|
||||
@@ -118,7 +118,9 @@ export class ExtensionsService {
|
||||
|
||||
if (!parentBundle) continue;
|
||||
|
||||
const schema = (parentBundle.schema as BundleExtension).entries.find((entry) => entry.name === meta.folder);
|
||||
const schema = (parentBundle.schema as BundleExtension | null)?.entries.find(
|
||||
(entry) => entry.name === meta.folder,
|
||||
);
|
||||
|
||||
if (!schema) continue;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user