feat: plugin install (#211)

* feat: plugin install

* fix: install options
This commit is contained in:
ChengLei Shao 2022-02-28 21:49:50 +08:00 committed by GitHub
parent 78f75f5a2f
commit 5e51973b21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 200 additions and 196 deletions

View File

@ -238,7 +238,7 @@ export class PluginACL extends Plugin {
await this.writeRolesToACL(); await this.writeRolesToACL();
}); });
this.app.on('installing.beforeUsersPlugin', async () => { this.app.on('afterInstallUsersPlugin', async () => {
const repository = this.app.db.getRepository('roles'); const repository = this.app.db.getRepository('roles');
await repository.createMany({ await repository.createMany({
records: [ records: [

View File

@ -3,11 +3,8 @@ import { areas, cities, provinces } from 'china-division';
import { resolve } from 'path'; import { resolve } from 'path';
export class ChinaRegionPlugin extends Plugin { export class ChinaRegionPlugin extends Plugin {
async install() {
async beforeLoad() { await this.importData();
this.app.on('installing', async () => {
await this.importData();
});
} }
async load() { async load() {

View File

@ -9,20 +9,18 @@ export default class PluginFileManager extends Plugin {
return process.env.DEFAULT_STORAGE_TYPE; return process.env.DEFAULT_STORAGE_TYPE;
} }
async beforeLoad() { async install() {
this.app.on('installing', async () => { const defaultStorageConfig = getStorageConfig(this.storageType());
const defaultStorageConfig = getStorageConfig(this.storageType()); if (defaultStorageConfig) {
if (defaultStorageConfig) { const Storage = this.db.getCollection('storages');
const Storage = this.db.getCollection('storages'); await Storage.repository.create({
await Storage.repository.create({ values: {
values: { ...defaultStorageConfig.defaults(),
...defaultStorageConfig.defaults(), type: this.storageType(),
type: this.storageType(), default: true,
default: true, },
}, });
}); }
}
});
} }
async load() { async load() {

View File

@ -3,20 +3,18 @@ import { Plugin } from '@nocobase/server';
import { resolve } from 'path'; import { resolve } from 'path';
export class SystemSettingsPlugin extends Plugin { export class SystemSettingsPlugin extends Plugin {
async beforeLoad() { async install() {
this.app.on('installing', async () => { await this.db.getRepository('systemSettings').create({
await this.db.getRepository('systemSettings').create({ values: {
values: { title: 'NocoBase',
title: 'NocoBase', logo: {
logo: { title: 'nocobase-logo',
title: 'nocobase-logo', filename: '682e5ad037dd02a0fe4800a3e91c283b.png',
filename: '682e5ad037dd02a0fe4800a3e91c283b.png', extname: '.png',
extname: '.png', mimetype: 'image/png',
mimetype: 'image/png', url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/682e5ad037dd02a0fe4800a3e91c283b.png',
url: 'https://nocobase.oss-cn-beijing.aliyuncs.com/682e5ad037dd02a0fe4800a3e91c283b.png',
},
}, },
}); },
}); });
} }

View File

@ -5,141 +5,139 @@ import { resolve } from 'path';
import { getAccessible } from './actions/getAccessible'; import { getAccessible } from './actions/getAccessible';
export class UiRoutesStoragePlugin extends Plugin { export class UiRoutesStoragePlugin extends Plugin {
beforeLoad() { async install() {
this.app.on('installing', async () => { const repository = this.app.db.getRepository('uiRoutes');
const repository = this.app.db.getRepository('uiRoutes'); const routes = [
const routes = [ {
{ type: 'redirect',
type: 'redirect', from: '/',
from: '/', to: '/admin',
to: '/admin', exact: true,
exact: true, },
}, {
{ type: 'route',
type: 'route', uiSchema: {
uiSchema: { type: 'void',
type: 'void', 'x-component': 'Menu',
'x-component': 'Menu', 'x-designer': 'Menu.Designer',
'x-designer': 'Menu.Designer', 'x-initializer': 'MenuItemInitializers',
'x-initializer': 'MenuItemInitializers', 'x-component-props': {
'x-component-props': { mode: 'mix',
mode: 'mix', theme: 'dark',
theme: 'dark', // defaultSelectedUid: 'u8',
// defaultSelectedUid: 'u8', onSelect: '{{ onSelect }}',
onSelect: '{{ onSelect }}', sideMenuRefScopeKey: 'sideMenuRef',
sideMenuRefScopeKey: 'sideMenuRef', },
}, properties: {
properties: { // item3: {
// item3: { // type: 'void',
// type: 'void', // title: 'SubMenu u3',
// title: 'SubMenu u3', // 'x-component': 'Menu.SubMenu',
// 'x-component': 'Menu.SubMenu', // 'x-component-props': {},
// 'x-component-props': {}, // properties: {
// properties: { // item6: {
// item6: { // type: 'void',
// type: 'void', // title: 'SubMenu u6',
// title: 'SubMenu u6', // 'x-component': 'Menu.SubMenu',
// 'x-component': 'Menu.SubMenu', // 'x-component-props': {},
// 'x-component-props': {}, // properties: {
// properties: { // item7: {
// item7: { // type: 'void',
// type: 'void', // title: 'Menu Item u7',
// title: 'Menu Item u7', // 'x-component': 'Menu.Item',
// 'x-component': 'Menu.Item', // 'x-component-props': {},
// 'x-component-props': {}, // properties: {
// properties: { // page1: {
// page1: { // type: 'void',
// type: 'void', // 'x-component': 'Page',
// 'x-component': 'Page', // 'x-async': true,
// 'x-async': true, // properties: {
// properties: { // grid1: {
// grid1: { // type: 'void',
// type: 'void', // 'x-component': 'Grid',
// 'x-component': 'Grid', // 'x-item-initializer': 'BlockInitializer',
// 'x-item-initializer': 'BlockInitializer', // properties: {},
// properties: {}, // },
// }, // },
// }, // },
// }, // },
// }, // },
// }, // item8: {
// item8: { // type: 'void',
// type: 'void', // title: 'Menu Item u8',
// title: 'Menu Item u8', // 'x-component': 'Menu.Item',
// 'x-component': 'Menu.Item', // 'x-component-props': {},
// 'x-component-props': {}, // },
// }, // },
// }, // },
// }, // item4: {
// item4: { // type: 'void',
// type: 'void', // title: 'Menu Item u4',
// title: 'Menu Item u4', // 'x-component': 'Menu.Item',
// 'x-component': 'Menu.Item', // 'x-component-props': {},
// 'x-component-props': {}, // },
// }, // item5: {
// item5: { // type: 'void',
// type: 'void', // title: 'Menu Item u5',
// title: 'Menu Item u5', // 'x-component': 'Menu.Item',
// 'x-component': 'Menu.Item', // 'x-component-props': {},
// 'x-component-props': {}, // },
// }, // },
// }, // },
// }, // item1: {
// item1: { // type: 'void',
// type: 'void', // title: 'Menu Item u1',
// title: 'Menu Item u1', // 'x-component': 'Menu.Item',
// 'x-component': 'Menu.Item', // 'x-component-props': {},
// 'x-component-props': {}, // },
// }, // item2: {
// item2: { // type: 'void',
// type: 'void', // title: 'Menu Item u2',
// title: 'Menu Item u2', // 'x-component': 'Menu.Item',
// 'x-component': 'Menu.Item', // 'x-component-props': {},
// 'x-component-props': {}, // },
// }, // item9: {
// item9: { // type: 'void',
// type: 'void', // title: 'SubMenu u9',
// title: 'SubMenu u9', // 'x-component': 'Menu.SubMenu',
// 'x-component': 'Menu.SubMenu', // 'x-component-props': {},
// 'x-component-props': {}, // properties: {
// properties: { // item10: {
// item10: { // type: 'void',
// type: 'void', // title: 'Menu Item u10',
// title: 'Menu Item u10', // 'x-component': 'Menu.Item',
// 'x-component': 'Menu.Item', // 'x-component-props': {},
// 'x-component-props': {}, // },
// }, // },
// }, // },
// },
},
}, },
path: '/admin/:name(.+)?',
component: 'AdminLayout',
title: 'NocoBase Admin',
}, },
{ path: '/admin/:name(.+)?',
type: 'route', component: 'AdminLayout',
component: 'AuthLayout', title: 'NocoBase Admin',
routes: [ },
{ {
type: 'route', type: 'route',
path: '/signin', component: 'AuthLayout',
component: 'SigninPage', routes: [
}, {
{ type: 'route',
type: 'route', path: '/signin',
path: '/signup', component: 'SigninPage',
component: 'SignupPage', },
}, {
], type: 'route',
}, path: '/signup',
]; component: 'SignupPage',
for (const values of routes) { },
await repository.create({ ],
values, },
}); ];
} for (const values of routes) {
}); await repository.create({
values,
});
}
} }
async load() { async load() {

View File

@ -8,6 +8,7 @@ describe('role', () => {
beforeEach(async () => { beforeEach(async () => {
api = mockServer(); api = mockServer();
await api.cleanDb();
api.plugin(require('../server').default); api.plugin(require('../server').default);
api.plugin(PluginACL); api.plugin(PluginACL);
await api.loadAndInstall(); await api.loadAndInstall();

View File

@ -5,40 +5,38 @@ import * as actions from './actions/users';
import * as middlewares from './middlewares'; import * as middlewares from './middlewares';
export default class UsersPlugin extends Plugin { export default class UsersPlugin extends Plugin {
async beforeLoad() { async install() {
const { const {
adminNickname = 'Super Admin', adminNickname = 'Super Admin',
adminEmail = 'admin@nocobase.com', adminEmail = 'admin@nocobase.com',
adminPassword = 'admin123', adminPassword = 'admin123',
} = this.options; } = this.options;
this.app.on('installing', async (...args) => { const User = this.db.getCollection('users');
// TODO 暂时先这么写着,理想状态应该由 app.emitAsync('installing') 内部处理 await User.repository.create({
await this.app.emitAsync('installing.beforeUsersPlugin', ...args); values: {
const User = this.db.getCollection('users'); nickname: adminNickname,
await User.repository.create({ email: adminEmail,
values: { password: adminPassword,
nickname: adminNickname, },
email: adminEmail,
password: adminPassword,
roles: ['admin'],
},
});
await this.app.emitAsync('installing.afterUsersPlugin', ...args);
}); });
}
async beforeLoad() {
this.db.on('users.afterCreateWithAssociations', async (model, options) => { this.db.on('users.afterCreateWithAssociations', async (model, options) => {
const { transaction } = options; const { transaction } = options;
const defaultRole = await this.app.db.getRepository('roles').findOne({ if (this.app.db.getCollection('roles')) {
filter: { const defaultRole = await this.app.db.getRepository('roles').findOne({
default: true, filter: {
}, default: true,
transaction, },
}); transaction,
});
if (defaultRole && (await model.countRoles({ transaction })) == 0) { if (defaultRole && (await model.countRoles({ transaction })) == 0) {
await model.addRoles(defaultRole, { transaction }); await model.addRoles(defaultRole, { transaction });
}
} }
}); });

View File

@ -11,7 +11,7 @@ import { isBoolean } from 'lodash';
import { createACL } from './acl'; import { createACL } from './acl';
import { createCli, createDatabase, createI18n, createResourcer, registerMiddlewares } from './helper'; import { createCli, createDatabase, createI18n, createResourcer, registerMiddlewares } from './helper';
import { Plugin } from './plugin'; import { Plugin } from './plugin';
import { PluginManager } from './plugin-manager'; import { PluginManager, InstallOptions } from './plugin-manager';
export interface ResourcerOptions { export interface ResourcerOptions {
prefix?: string; prefix?: string;
@ -71,12 +71,6 @@ interface StartOptions {
listen?: ListenOptions; listen?: ListenOptions;
} }
interface InstallOptions {
cliArgs?: any[];
clean?: CleanOptions | boolean;
sync?: SyncOptions;
}
export class Application<StateT = DefaultState, ContextT = DefaultContext> extends Koa implements AsyncEmitter { export class Application<StateT = DefaultState, ContextT = DefaultContext> extends Koa implements AsyncEmitter {
public readonly db: Database; public readonly db: Database;
@ -214,11 +208,13 @@ export class Application<StateT = DefaultState, ContextT = DefaultContext> exten
async install(options?: InstallOptions) { async install(options?: InstallOptions) {
await this.emitAsync('beforeInstall', this, options); await this.emitAsync('beforeInstall', this, options);
if (options?.clean) { if (options?.clean) {
await this.db.clean(isBoolean(options.clean) ? { drop: options.clean } : options.clean); await this.db.clean(isBoolean(options.clean) ? { drop: options.clean } : options.clean);
} }
await this.db.sync(options?.sync); await this.db.sync(options?.sync);
await this.emitAsync('installing', this, options); await this.pm.install(options);
await this.emitAsync('afterInstall', this, options); await this.emitAsync('afterInstall', this, options);
} }

View File

@ -1,10 +1,17 @@
import Application from './application'; import Application from './application';
import { Plugin } from './plugin'; import { Plugin } from './plugin';
import { CleanOptions, SyncOptions } from '@nocobase/database';
interface PluginManagerOptions { interface PluginManagerOptions {
app: Application; app: Application;
} }
export interface InstallOptions {
cliArgs?: any[];
clean?: CleanOptions | boolean;
sync?: SyncOptions;
}
export class PluginManager { export class PluginManager {
app: Application; app: Application;
protected plugins = new Map<string, Plugin>(); protected plugins = new Map<string, Plugin>();
@ -46,4 +53,12 @@ export class PluginManager {
await this.app.emitAsync('afterLoadAll'); await this.app.emitAsync('afterLoadAll');
} }
async install(options?: InstallOptions) {
for (const [name, plugin] of this.plugins) {
await this.app.emitAsync('beforeInstallPlugin', plugin, options);
await plugin.install(options);
await this.app.emitAsync('afterInstallPlugin', plugin, options);
}
}
} }

View File

@ -1,6 +1,7 @@
import { Database } from '@nocobase/database'; import { Database } from '@nocobase/database';
import { Application } from './application'; import { Application } from './application';
import path from 'path'; import path from 'path';
import { InstallOptions } from './plugin-manager';
export interface PluginInterface { export interface PluginInterface {
beforeLoad?: () => void; beforeLoad?: () => void;
@ -42,6 +43,8 @@ export abstract class Plugin<O = any> implements PluginInterface {
beforeLoad() {} beforeLoad() {}
async install(options?: InstallOptions) {}
async load() { async load() {
const collectionPath = this.collectionPath(); const collectionPath = this.collectionPath();
if (collectionPath) { if (collectionPath) {