Add support for a package extension bundle type (#15672)

* Add bundle type to constants and types

* Add support for API bundle extensions

* Rename generateExtensionsEntry to generateExtensionsEntrypoint

* Add support for App bundle extensions

* Refactor App extension registration

* Replace extensions inject with useExtensions()

* Replace getInterfaces() with useExtensions()

* Replace getDisplays() with useExtensions()

* Replace getLayouts() with useExtensions()

* Replace getModules() with useExtensions()

* Replace getPanels() with useExtensions()

* Replace getOperations() with useExtensions()

* Add useExtension() composable

* Replace useExtensions() with useExtension() where applicable

* Remove interface getters

* Remove display getters

* Remove layout getters

* Remove module getter

* Remove panel getters

* Remove operation getters

* Rename extension register.ts files to index.ts

* Perform module pre register check in parallel

* Remove Refs from AppExtensionConfigs type

* Remove old extension shims

* Ensure registration of modules is awaited when hydrating

* Add support for scaffolding package extensions

* Add support for building bundle extensions

* Add JsonValue type

* Use json for complex command line flags

* Load internal extensions if custom ones are not available

* Fix extension manifest validation for pack extensions

* Fix tests in shared

* Add SplitEntrypoint type

* Move command specific utils to helpers

* Add SDK version getter

* Move extension dev deps generation to helpers

* Move template path to getter util

* Move template copying to a helper

* Only rename copied template files

* Add directus-extension add command

* Convert provided extension source path to url

* Replace deprecated import.meta.globEager

* Mock URL.createObjectURL to make App unit tests pass

* Update rollup-plugin-typescript2

* indentation

* sort vite glob imported modules

* fix unintentional wrong commit

* Simplify app extension import logic

* reinstall @rollup/plugin-virtual

* add test for getInterfaces() expected sort order

Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com>
Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>
This commit is contained in:
Nicola Krumschmidt
2022-11-16 17:28:52 +01:00
committed by GitHub
parent 0859102a61
commit 7bf90efa62
127 changed files with 1898 additions and 957 deletions

View File

@@ -29,7 +29,7 @@
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, inject, ref } from 'vue';
import { getInterface } from '@/interfaces';
import { useExtension } from '@/composables/use-extension';
export default defineComponent({
props: {
@@ -65,15 +65,8 @@ export default defineComponent({
const values = inject('values', ref<Record<string, any>>({}));
const selectedInterface = computed(() => {
if (props.interface) {
return getInterface(props.interface);
}
if (!values.value[props.interfaceField]) return;
return getInterface(values.value[props.interfaceField]);
});
const selectedInterfaceId = computed(() => props.interface ?? values.value[props.interfaceField] ?? null);
const selectedInterface = useExtension('interface', selectedInterfaceId);
const usesCustomComponent = computed(() => {
if (!selectedInterface.value) return false;

View File

@@ -14,8 +14,8 @@
<script lang="ts">
import { useI18n } from 'vue-i18n';
import { defineComponent, computed, inject, ref, watch } from 'vue';
import { getInterfaces } from '@/interfaces';
import { InterfaceConfig } from '@directus/shared/types';
import { useExtensions } from '@/extensions';
export default defineComponent({
props: {
@@ -32,7 +32,7 @@ export default defineComponent({
setup(props, { emit }) {
const { t } = useI18n();
const { interfaces } = getInterfaces();
const { interfaces } = useExtensions();
const values = inject('values', ref<Record<string, any>>({}));

View File

@@ -60,7 +60,6 @@
<script lang="ts">
import { defineComponent, PropType, computed, ref } from 'vue';
import { getModules } from '@/modules';
import { Settings, SettingsModuleBarModule, SettingsModuleBarLink } from '@directus/shared/types';
import { hideDragImage } from '@/utils/hide-drag-image';
import Draggable from 'vuedraggable';
@@ -69,6 +68,7 @@ import { useI18n } from 'vue-i18n';
import { nanoid } from 'nanoid';
import { Field, DeepPartial } from '@directus/shared/types';
import { MODULE_BAR_DEFAULT } from '@/constants';
import { useExtensions } from '@/extensions';
type PreviewExtra = {
to: string;
@@ -130,7 +130,7 @@ export default defineComponent({
const values = ref<SettingsModuleBarLink | null>();
const initialValues = ref<SettingsModuleBarLink | null>();
const { modules: registeredModules } = getModules();
const { modules: registeredModules } = useExtensions();
const availableModulesAsBarModule = computed<SettingsModuleBarModule[]>(() => {
return registeredModules.value

View File

@@ -0,0 +1,37 @@
import { expect, test, describe } from 'vitest';
import { getInternalInterfaces } from './index';
/**
* Vite v3 reworked the glob import with it's own internal sorting,
* so this is to ensure any changes to vite's internal sorting to not affect the expected sort outcome.
*
* @see {@link https://github.com/directus/directus/pull/15672#issuecomment-1289975933}
*/
const expectedInterfacesSortOrder = [
'boolean',
'input',
'input-autocomplete-api',
'input-code',
'input-hash',
'input-multiline',
'input-rich-text-html',
'input-rich-text-md',
'system-collection',
];
describe('interfaces', () => {
test('getInterfaces() should return the expected sorted order of interfaces', () => {
const interfaces = getInternalInterfaces();
const interfaceIds = interfaces.map((inter) => inter.id);
const interfacesToTest = interfaceIds.filter((inter) => expectedInterfacesSortOrder.includes(inter));
// test all expected sort order
expect(interfacesToTest).toEqual(expectedInterfacesSortOrder);
// test whether input interface is the first one among all the input related interfaces
expect(interfacesToTest.filter((inter) => inter.includes('input')).findIndex((inter) => inter === 'input')).toBe(0);
// system-collection should be not be at the start after sorting. Currently it is within the folder called "_system",
// so it will be the first item by default without our added sort logic in getInterface().
expect(interfacesToTest.findIndex((inter) => inter === 'system-collection')).not.toBe(0);
});
});

View File

@@ -1,13 +1,22 @@
import { shallowRef, Ref } from 'vue';
import { App } from 'vue';
import { InterfaceConfig } from '@directus/shared/types';
import { sortBy } from 'lodash';
const interfacesRaw: Ref<InterfaceConfig[]> = shallowRef([]);
const interfaces: Ref<InterfaceConfig[]> = shallowRef([]);
export function getInternalInterfaces(): InterfaceConfig[] {
const interfaces = import.meta.glob<InterfaceConfig>(['./*/index.ts', './_system/*/index.ts'], {
import: 'default',
eager: true,
});
export function getInterfaces(): { interfaces: Ref<InterfaceConfig[]>; interfacesRaw: Ref<InterfaceConfig[]> } {
return { interfaces, interfacesRaw };
return sortBy(Object.values(interfaces), 'id');
}
export function getInterface(name?: string | null): InterfaceConfig | undefined {
return !name ? undefined : interfaces.value.find(({ id }) => id === name);
export function registerInterfaces(interfaces: InterfaceConfig[], app: App): void {
for (const inter of interfaces) {
app.component(`interface-${inter.id}`, inter.component);
if (typeof inter.options !== 'function' && Array.isArray(inter.options) === false && inter.options !== null) {
app.component(`interface-options-${inter.id}`, inter.options);
}
}
}

View File

@@ -1,35 +0,0 @@
import { getRootPath } from '@/utils/get-root-path';
import { App } from 'vue';
import { getInterfaces } from './index';
import { InterfaceConfig } from '@directus/shared/types';
const { interfacesRaw } = getInterfaces();
export async function registerInterfaces(app: App): Promise<void> {
const interfaceModules = import.meta.globEager('./*/**/index.ts');
const interfaces: InterfaceConfig[] = Object.values(interfaceModules).map((module) => module.default);
try {
const customInterfaces: { default: InterfaceConfig[] } = import.meta.env.DEV
? await import('@directus-extensions-interface')
: await import(/* @vite-ignore */ `${getRootPath()}extensions/interfaces/index.js`);
interfaces.push(...customInterfaces.default);
} catch (err: any) {
// eslint-disable-next-line no-console
console.warn(`Couldn't load custom interfaces`);
// eslint-disable-next-line no-console
console.warn(err);
}
interfacesRaw.value = interfaces;
interfacesRaw.value.forEach((inter: InterfaceConfig) => {
app.component(`interface-${inter.id}`, inter.component);
if (typeof inter.options !== 'function' && Array.isArray(inter.options) === false && inter.options !== null) {
app.component(`interface-options-${inter.id}`, inter.options);
}
});
}