nocobase/packages/plugins/client/src/server.ts
chenos 64070b81b9
refactor: migrate adminSchemaUid & mobileSchemaUid to system settings (#2084)
* refactor: migrate adminSchemaUid & mobileSchemaUid to system settings

* fix: error
2023-06-21 11:02:49 +08:00

278 lines
8.8 KiB
TypeScript

import { Plugin, PluginManager } from '@nocobase/server';
import fs from 'fs';
import send from 'koa-send';
import serve from 'koa-static';
import isEmpty from 'lodash/isEmpty';
import { isAbsolute, resolve } from 'path';
import { getAntdLocale } from './antd';
import { getCronLocale } from './cron';
import { getCronstrueLocale } from './cronstrue';
import { getMomentLocale } from './moment-locale';
import { getResourceLocale } from './resource';
async function getReadMe(name: string, locale: string) {
const packageName = PluginManager.getPackageName(name);
const dir = resolve(process.cwd(), 'node_modules', packageName);
const files = [resolve(dir, `README.${locale}.md`), resolve(dir, `README.md`)];
const file = files.find((file) => {
return fs.existsSync(file);
});
return file ? (await fs.promises.readFile(file)).toString() : '';
}
async function getTabs(name: string, locale: string) {
const packageName = PluginManager.getPackageName(name);
const dir = resolve(process.cwd(), 'node_modules', packageName);
const file = resolve(dir, 'docs', locale, 'tabs.json');
if (!fs.existsSync(file)) {
// TODO: compatible README, remove it in all plugin has tabs.json
return [
{
title: 'README',
path: '__README__',
},
];
}
return JSON.parse((await fs.promises.readFile(file)).toString());
}
interface TabInfoParams {
filterByTk: string;
path: string;
locale: string;
}
async function getTabInfo({ filterByTk, path, locale }: TabInfoParams) {
const packageName = PluginManager.getPackageName(filterByTk);
const dir = resolve(process.cwd(), 'node_modules', packageName);
if (path === '__README__') {
return await getReadMe(filterByTk, locale);
}
const files = [
resolve(dir, 'docs', locale, `${path}.md`),
// default
resolve(dir, 'docs', 'en-US', `${path}.md`),
resolve(dir, 'docs', 'zh-CN', `${path}.md`),
];
const file = files.find((file) => {
return fs.existsSync(file);
});
return file ? (await fs.promises.readFile(file)).toString() : '';
}
async function getLang(ctx) {
const SystemSetting = ctx.db.getRepository('systemSettings');
const systemSetting = await SystemSetting.findOne();
const enabledLanguages: string[] = systemSetting.get('enabledLanguages') || [];
const currentUser = ctx.state.currentUser;
let lang = enabledLanguages?.[0] || process.env.APP_LANG || 'en-US';
if (enabledLanguages.includes(currentUser?.appLang)) {
lang = currentUser?.appLang;
}
if (ctx.request.query.locale) {
lang = ctx.request.query.locale;
}
return lang;
}
export class ClientPlugin extends Plugin {
async beforeLoad() {
// const cmd = this.app.findCommand('install');
// if (cmd) {
// cmd.option('--import-demo');
// }
this.app.on('afterInstall', async (app, options) => {
const [opts] = options?.cliArgs || [{}];
if (opts?.importDemo) {
//
}
});
this.db.on('systemSettings.beforeCreate', async (instance, { transaction }) => {
const uiSchemas = this.db.getRepository<any>('uiSchemas');
const schema = await uiSchemas.insert(
{
type: 'void',
'x-component': 'Menu',
'x-designer': 'Menu.Designer',
'x-initializer': 'MenuItemInitializers',
'x-component-props': {
mode: 'mix',
theme: 'dark',
// defaultSelectedUid: 'u8',
onSelect: '{{ onSelect }}',
sideMenuRefScopeKey: 'sideMenuRef',
},
properties: {},
},
{ transaction },
);
instance.set('options.adminSchemaUid', schema['x-uid']);
});
}
async load() {
this.db.addMigrations({
namespace: 'client',
directory: resolve(__dirname, './migrations'),
context: {
plugin: this,
},
});
this.app.acl.allow('app', 'getLang');
this.app.acl.allow('app', 'getInfo');
this.app.acl.allow('app', 'getPlugins');
this.app.acl.allow('plugins', '*', 'public');
this.app.acl.registerSnippet({
name: 'app',
actions: ['app:reboot', 'app:clearCache'],
});
const dialect = this.app.db.sequelize.getDialect();
const locales = require('./locale').default;
const restartMark = resolve(process.cwd(), 'storage', 'restart');
this.app.on('beforeStart', async () => {
if (fs.existsSync(restartMark)) {
fs.unlinkSync(restartMark);
}
});
this.app.resource({
name: 'app',
actions: {
async getInfo(ctx, next) {
const SystemSetting = ctx.db.getRepository('systemSettings');
const systemSetting = await SystemSetting.findOne();
const enabledLanguages: string[] = systemSetting.get('enabledLanguages') || [];
const currentUser = ctx.state.currentUser;
let lang = enabledLanguages?.[0] || process.env.APP_LANG || 'en-US';
if (enabledLanguages.includes(currentUser?.appLang)) {
lang = currentUser?.appLang;
}
ctx.body = {
database: {
dialect,
},
version: await ctx.app.version.get(),
lang,
theme: currentUser?.systemSettings?.theme || systemSetting?.options?.theme || 'default',
};
await next();
},
async getLang(ctx, next) {
const lang = await getLang(ctx);
if (isEmpty(locales[lang])) {
locales[lang] = {};
}
if (isEmpty(locales[lang].resources)) {
locales[lang].resources = await getResourceLocale(lang, ctx.db);
}
if (isEmpty(locales[lang].antd)) {
locales[lang].antd = getAntdLocale(lang);
}
if (isEmpty(locales[lang].cronstrue)) {
locales[lang].cronstrue = getCronstrueLocale(lang);
}
if (isEmpty(locales[lang].cron)) {
locales[lang].cron = getCronLocale(lang);
}
ctx.body = {
lang,
moment: getMomentLocale(lang),
...locales[lang],
};
await next();
},
async getPlugins(ctx, next) {
const pm = ctx.db.getRepository('applicationPlugins');
const items = await pm.find({
filter: {
enabled: true,
},
});
ctx.body = items
.filter((item) => {
try {
const packageName = PluginManager.getPackageName(item.name);
require.resolve(`${packageName}/client`);
return true;
} catch (error) {}
return false;
})
.map((item) => item.name);
await next();
},
async clearCache(ctx, next) {
await ctx.cache.reset();
await next();
},
reboot(ctx) {
const RESTART_CODE = 100;
process.on('exit', (code) => {
if (code === RESTART_CODE && process.env.APP_ENV === 'production') {
fs.writeFileSync(restartMark, '1');
console.log('Restart mark created.');
}
});
ctx.app.on('afterStop', () => {
// Exit with code 100 will restart the process
process.exit(RESTART_CODE);
});
ctx.app.stop();
},
},
});
this.app.resource({
name: 'plugins',
actions: {
async getInfo(ctx, next) {
const lang = await getLang(ctx);
const { filterByTk } = ctx.action.params;
ctx.body = {
filterByTk,
readMe: await getReadMe(filterByTk, lang),
};
await next();
},
async getTabs(ctx, next) {
const lang = await getLang(ctx);
const { filterByTk } = ctx.action.params;
ctx.body = {
filterByTk,
tabs: await getTabs(filterByTk, lang),
};
await next();
},
async getTabInfo(ctx, next) {
const locale = await getLang(ctx);
const { filterByTk } = ctx.action.params;
ctx.body = {
filterByTk,
content: await getTabInfo({ ...(ctx.action.params as any), locale }),
};
await next();
},
},
});
let root = this.options.dist || `./packages/app/client/dist`;
if (!isAbsolute(root)) {
root = resolve(process.cwd(), root);
}
if (process.env.APP_ENV !== 'production' && root) {
this.app.use(
async (ctx, next) => {
if (ctx.path.startsWith(this.app.resourcer.options.prefix)) {
return next();
}
await serve(root)(ctx, next);
// console.log('koa-send', root, ctx.status);
if (ctx.status == 404) {
return send(ctx, 'index.html', { root });
}
},
{ tag: 'clientStatic', before: 'cors' },
);
}
}
}
export default ClientPlugin;