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:
Pascal Jufer
2024-03-05 21:52:11 +01:00
committed by GitHub
parent d12b7c9033
commit 750522a671
3 changed files with 63 additions and 11 deletions

View File

@@ -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 });
}

View File

@@ -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');
}

View File

@@ -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;