feat: plugin install (#211)
* feat: plugin install * fix: install options
This commit is contained in:
parent
78f75f5a2f
commit
5e51973b21
@ -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: [
|
||||||
|
@ -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() {
|
||||||
|
@ -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() {
|
||||||
|
@ -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',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
@ -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();
|
||||||
|
@ -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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user