mirror of
https://github.com/directus/directus.git
synced 2026-02-07 01:04:59 -05:00
Merge branch 'main' into aggregation
This commit is contained in:
@@ -1,91 +1,139 @@
|
||||
import express, { Router } from 'express';
|
||||
import { ensureDir } from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { AppExtensionType, Extension, ExtensionType } from '@directus/shared/types';
|
||||
import {
|
||||
generateExtensionsEntry,
|
||||
getLocalExtensions,
|
||||
getPackageExtensions,
|
||||
pluralize,
|
||||
resolvePackage,
|
||||
} from '@directus/shared/utils';
|
||||
import { APP_EXTENSION_TYPES, EXTENSION_TYPES, SHARED_DEPS } from '@directus/shared/constants';
|
||||
import getDatabase from './database';
|
||||
import emitter from './emitter';
|
||||
import env from './env';
|
||||
import * as exceptions from './exceptions';
|
||||
import { ServiceUnavailableException } from './exceptions';
|
||||
import logger from './logger';
|
||||
import * as services from './services';
|
||||
import { EndpointRegisterFunction, HookRegisterFunction } from './types';
|
||||
import { HookRegisterFunction, EndpointRegisterFunction } from './types';
|
||||
import fse from 'fs-extra';
|
||||
import { getSchema } from './utils/get-schema';
|
||||
import listFolders from './utils/list-folders';
|
||||
|
||||
import * as services from './services';
|
||||
import { schedule, validate } from 'node-cron';
|
||||
import { REGEX_BETWEEN_PARENS } from './constants';
|
||||
import { rollup } from 'rollup';
|
||||
// @TODO Remove this once a new version of @rollup/plugin-virtual has been released
|
||||
// @ts-expect-error
|
||||
import virtual from '@rollup/plugin-virtual';
|
||||
import alias from '@rollup/plugin-alias';
|
||||
|
||||
export async function ensureFoldersExist(): Promise<void> {
|
||||
const folders = ['endpoints', 'hooks', 'interfaces', 'modules', 'layouts', 'displays'];
|
||||
let extensions: Extension[] = [];
|
||||
let extensionBundles: Partial<Record<AppExtensionType, string>> = {};
|
||||
|
||||
for (const folder of folders) {
|
||||
const folderPath = path.resolve(env.EXTENSIONS_PATH, folder);
|
||||
export async function initializeExtensions(): Promise<void> {
|
||||
await ensureDirsExist();
|
||||
extensions = await getExtensions();
|
||||
extensionBundles = await generateExtensionBundles();
|
||||
|
||||
logger.info(`Loaded extensions: ${listExtensions().join(', ')}`);
|
||||
}
|
||||
|
||||
export function listExtensions(type?: ExtensionType): string[] {
|
||||
if (type === undefined) {
|
||||
return extensions.map((extension) => extension.name);
|
||||
} else {
|
||||
return extensions.filter((extension) => extension.type === type).map((extension) => extension.name);
|
||||
}
|
||||
}
|
||||
|
||||
export function getAppExtensionSource(type: AppExtensionType): string | undefined {
|
||||
return extensionBundles[type];
|
||||
}
|
||||
|
||||
export function registerExtensionEndpoints(router: Router): void {
|
||||
const endpoints = extensions.filter((extension) => extension.type === 'endpoint');
|
||||
registerEndpoints(endpoints, router);
|
||||
}
|
||||
|
||||
export function registerExtensionHooks(): void {
|
||||
const hooks = extensions.filter((extension) => extension.type === 'hook');
|
||||
registerHooks(hooks);
|
||||
}
|
||||
|
||||
async function getExtensions(): Promise<Extension[]> {
|
||||
const packageExtensions = await getPackageExtensions('.');
|
||||
const localExtensions = await getLocalExtensions(env.EXTENSIONS_PATH);
|
||||
|
||||
return [...packageExtensions, ...localExtensions];
|
||||
}
|
||||
|
||||
async function generateExtensionBundles() {
|
||||
const sharedDepsMapping = await getSharedDepsMapping(SHARED_DEPS);
|
||||
const internalImports = Object.entries(sharedDepsMapping).map(([name, path]) => ({
|
||||
find: name,
|
||||
replacement: path,
|
||||
}));
|
||||
|
||||
const bundles: Partial<Record<AppExtensionType, string>> = {};
|
||||
|
||||
for (const extensionType of APP_EXTENSION_TYPES) {
|
||||
const entry = generateExtensionsEntry(extensionType, extensions);
|
||||
|
||||
const bundle = await rollup({
|
||||
input: 'entry',
|
||||
external: SHARED_DEPS,
|
||||
plugins: [virtual({ entry }), alias({ entries: internalImports })],
|
||||
});
|
||||
const { output } = await bundle.generate({ format: 'es' });
|
||||
|
||||
bundles[extensionType] = output[0].code;
|
||||
|
||||
await bundle.close();
|
||||
}
|
||||
|
||||
return bundles;
|
||||
}
|
||||
|
||||
async function ensureDirsExist() {
|
||||
for (const extensionType of EXTENSION_TYPES) {
|
||||
const dirPath = path.resolve(env.EXTENSIONS_PATH, pluralize(extensionType));
|
||||
try {
|
||||
await ensureDir(folderPath);
|
||||
await fse.ensureDir(dirPath);
|
||||
} catch (err) {
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function initializeExtensions(): Promise<void> {
|
||||
await ensureFoldersExist();
|
||||
}
|
||||
async function getSharedDepsMapping(deps: string[]) {
|
||||
const appDir = await fse.readdir(path.join(resolvePackage('@directus/app'), 'dist'));
|
||||
|
||||
export async function listExtensions(type: string): Promise<string[]> {
|
||||
const extensionsPath = env.EXTENSIONS_PATH as string;
|
||||
const location = path.join(extensionsPath, type);
|
||||
const depsMapping: Record<string, string> = {};
|
||||
for (const dep of deps) {
|
||||
const depName = appDir.find((file) => dep.replace(/\//g, '_') === file.substring(0, file.indexOf('.')));
|
||||
|
||||
try {
|
||||
return await listFolders(location);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
throw new ServiceUnavailableException(`Extension folder "extensions/${type}" couldn't be opened`, {
|
||||
service: 'extensions',
|
||||
});
|
||||
if (depName) {
|
||||
depsMapping[dep] = `${env.PUBLIC_URL}/admin/${depName}`;
|
||||
} else {
|
||||
logger.warn(`Couldn't find extension internal dependency "${dep}"`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
return depsMapping;
|
||||
}
|
||||
|
||||
export async function registerExtensions(router: Router): Promise<void> {
|
||||
await registerExtensionHooks();
|
||||
await registerExtensionEndpoints(router);
|
||||
}
|
||||
|
||||
export async function registerExtensionEndpoints(router: Router): Promise<void> {
|
||||
let endpoints: string[] = [];
|
||||
try {
|
||||
endpoints = await listExtensions('endpoints');
|
||||
registerEndpoints(endpoints, router);
|
||||
} catch (err) {
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function registerExtensionHooks(): Promise<void> {
|
||||
let hooks: string[] = [];
|
||||
try {
|
||||
hooks = await listExtensions('hooks');
|
||||
registerHooks(hooks);
|
||||
} catch (err) {
|
||||
logger.warn(err);
|
||||
}
|
||||
}
|
||||
|
||||
function registerHooks(hooks: string[]) {
|
||||
const extensionsPath = env.EXTENSIONS_PATH as string;
|
||||
|
||||
function registerHooks(hooks: Extension[]) {
|
||||
for (const hook of hooks) {
|
||||
try {
|
||||
registerHook(hook);
|
||||
} catch (error) {
|
||||
logger.warn(`Couldn't register hook "${hook}"`);
|
||||
logger.warn(`Couldn't register hook "${hook.name}"`);
|
||||
logger.warn(error);
|
||||
}
|
||||
}
|
||||
|
||||
function registerHook(hook: string) {
|
||||
const hookPath = path.resolve(extensionsPath, 'hooks', hook, 'index.js');
|
||||
function registerHook(hook: Extension) {
|
||||
const hookPath = path.resolve(hook.path, hook.entrypoint || '');
|
||||
const hookInstance: HookRegisterFunction | { default?: HookRegisterFunction } = require(hookPath);
|
||||
|
||||
let register: HookRegisterFunction = hookInstance as HookRegisterFunction;
|
||||
@@ -113,20 +161,18 @@ function registerHooks(hooks: string[]) {
|
||||
}
|
||||
}
|
||||
|
||||
function registerEndpoints(endpoints: string[], router: Router) {
|
||||
const extensionsPath = env.EXTENSIONS_PATH as string;
|
||||
|
||||
function registerEndpoints(endpoints: Extension[], router: Router) {
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
registerEndpoint(endpoint);
|
||||
} catch (error) {
|
||||
logger.warn(`Couldn't register endpoint "${endpoint}"`);
|
||||
logger.warn(`Couldn't register endpoint "${endpoint.name}"`);
|
||||
logger.warn(error);
|
||||
}
|
||||
}
|
||||
|
||||
function registerEndpoint(endpoint: string) {
|
||||
const endpointPath = path.resolve(extensionsPath, 'endpoints', endpoint, 'index.js');
|
||||
function registerEndpoint(endpoint: Extension) {
|
||||
const endpointPath = path.resolve(endpoint.path, endpoint.entrypoint || '');
|
||||
const endpointInstance: EndpointRegisterFunction | { default?: EndpointRegisterFunction } = require(endpointPath);
|
||||
|
||||
let register: EndpointRegisterFunction = endpointInstance as EndpointRegisterFunction;
|
||||
@@ -137,7 +183,7 @@ function registerEndpoints(endpoints: string[], router: Router) {
|
||||
}
|
||||
|
||||
const scopedRouter = express.Router();
|
||||
router.use(`/${endpoint}/`, scopedRouter);
|
||||
router.use(`/${endpoint.name}/`, scopedRouter);
|
||||
|
||||
register(scopedRouter, { services, exceptions, env, database: getDatabase(), getSchema });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user