exten
await this.pm.load();
}
+ async reload() {
+ this.init();
+ await this.pm.load();
+ }
+
getPlugin(name: string) {
return this.pm.get(name) as P;
}
@@ -250,8 +298,10 @@ export class Application exten
return this.runAsCLI(argv);
}
- async runAsCLI(argv?: readonly string[], options?: ParseOptions) {
- await this.load();
+ async runAsCLI(argv = process.argv, options?: ParseOptions) {
+ if (argv?.[2] !== 'install') {
+ await this.load();
+ }
return this.cli.parseAsync(argv, options);
}
@@ -328,8 +378,6 @@ export class Application exten
}
async install(options: InstallOptions = {}) {
- await this.emitAsync('beforeInstall', this, options);
-
const r = await this.db.version.satisfies({
mysql: '>=8.0.17',
sqlite: '3.x',
@@ -345,6 +393,9 @@ export class Application exten
await this.db.clean(isBoolean(options.clean) ? { drop: options.clean } : options.clean);
}
+ await this.emitAsync('beforeInstall', this, options);
+
+ await this.load();
await this.db.sync(options?.sync);
await this.pm.install(options);
await this.version.update();
diff --git a/packages/core/server/src/commands/index.ts b/packages/core/server/src/commands/index.ts
index d95d4b36e7..e12c954fcb 100644
--- a/packages/core/server/src/commands/index.ts
+++ b/packages/core/server/src/commands/index.ts
@@ -9,6 +9,7 @@ export function registerCli(app: Application) {
require('./migrator').default(app);
require('./start').default(app);
require('./upgrade').default(app);
+ require('./pm').default(app);
// development only with @nocobase/cli
app.command('build').argument('[packages...]');
diff --git a/packages/core/server/src/commands/pm.ts b/packages/core/server/src/commands/pm.ts
new file mode 100644
index 0000000000..5a0ff35430
--- /dev/null
+++ b/packages/core/server/src/commands/pm.ts
@@ -0,0 +1,89 @@
+import axios from 'axios';
+import { resolve } from 'path';
+import Application from '../application';
+
+export default (app: Application) => {
+ app
+ .command('pm')
+ .argument('')
+ .arguments('')
+ .action(async (method, plugins, options, ...args) => {
+ const { APP_PORT, API_BASE_PATH = '/api/', API_BASE_URL } = process.env;
+ const baseURL = API_BASE_URL || `http://localhost:${APP_PORT}${API_BASE_PATH}`;
+ let started = true;
+ try {
+ await axios.get(`${baseURL}app:getLang`);
+ } catch (error) {
+ started = false;
+ }
+ const pm = {
+ async create() {
+ const name = plugins[0];
+ const { PluginGenerator } = require('@nocobase/cli/src/plugin-generator');
+ const generator = new PluginGenerator({
+ cwd: resolve(process.cwd(), name),
+ args: options,
+ context: {
+ name,
+ },
+ });
+ await generator.run();
+ },
+ async add() {
+ if (started) {
+ const res = await axios.get(`${baseURL}pm:add/${plugins.join(',')}`);
+ console.log(res.data);
+ return;
+ }
+ await app.pm.add(plugins);
+ },
+ async enable() {
+ if (started) {
+ const res = await axios.get(`${baseURL}pm:enable/${plugins.join(',')}`);
+ console.log(res.data);
+ return;
+ }
+ const repository = app.db.getRepository('applicationPlugins');
+ await repository.update({
+ filter: {
+ 'name.$in': plugins,
+ },
+ values: {
+ enabled: true,
+ },
+ });
+ },
+ async disable() {
+ if (started) {
+ const res = await axios.get(`${baseURL}pm:disable/${plugins.join(',')}`);
+ console.log(res.data);
+ return;
+ }
+ const repository = app.db.getRepository('applicationPlugins');
+ await repository.update({
+ filter: {
+ 'name.$in': plugins,
+ },
+ values: {
+ enabled: false,
+ },
+ });
+ },
+ async remove() {
+ if (started) {
+ const res = await axios.get(`${baseURL}pm:disable/${plugins.join(',')}`);
+ console.log(res.data);
+ return;
+ }
+ const repository = app.db.getRepository('applicationPlugins');
+ await repository.destroy({
+ filter: {
+ 'name.$in': plugins,
+ },
+ });
+ plugins.map((name) => app.pm.remove(name));
+ },
+ };
+ await pm[method]();
+ });
+};
diff --git a/packages/core/server/src/plugin-manager.ts b/packages/core/server/src/plugin-manager.ts
index 8d8ce5db47..433c333fb0 100644
--- a/packages/core/server/src/plugin-manager.ts
+++ b/packages/core/server/src/plugin-manager.ts
@@ -1,4 +1,4 @@
-import { CleanOptions, SyncOptions } from '@nocobase/database';
+import { CleanOptions, Collection, Model, Repository, SyncOptions } from '@nocobase/database';
import Application from './application';
import { Plugin } from './plugin';
@@ -12,14 +12,158 @@ export interface InstallOptions {
sync?: SyncOptions;
}
-type PluginConstructor = { new(app: Application, options: O): P };
+class PluginManagerRepository extends Repository {
+ getInstance(): Plugin {
+ return;
+ }
+ async add(name: string | string[], options) {}
+ async enable(name: string | string[], options) {}
+ async disable(name: string | string[], options) {}
+ async remove(name: string | string[], options) {}
+ async upgrade(name: string | string[], options) {}
+}
export class PluginManager {
app: Application;
- protected plugins = new Map();
+ collection: Collection;
+ repository: PluginManagerRepository;
+ plugins = new Map();
constructor(options: PluginManagerOptions) {
this.app = options.app;
+ this.collection = this.app.db.collection({
+ name: 'applicationPlugins',
+ fields: [
+ { type: 'string', name: 'name', unique: true },
+ { type: 'string', name: 'version' },
+ { type: 'boolean', name: 'enabled' },
+ { type: 'boolean', name: 'builtIn' },
+ { type: 'json', name: 'options' },
+ ],
+ });
+ const app = this.app;
+ const pm = this;
+ this.repository = this.collection.repository as PluginManagerRepository;
+ this.app.resourcer.define({
+ name: 'pm',
+ actions: {
+ async add(ctx, next) {
+ const { filterByTk } = ctx.action.params;
+ if (!filterByTk) {
+ ctx.throw(400, 'null');
+ }
+ await pm.add(filterByTk);
+ ctx.body = filterByTk;
+ await next();
+ },
+ async enable(ctx, next) {
+ const { filterByTk } = ctx.action.params;
+ if (!filterByTk) {
+ ctx.throw(400, 'filterByTk invalid');
+ }
+ const name = pm.getPackageName(filterByTk);
+ const plugin = pm.get(name);
+ if (plugin.model) {
+ plugin.model.set('enabled', true);
+ await plugin.model.save();
+ }
+ if (!plugin) {
+ ctx.throw(400, 'plugin invalid');
+ }
+ await app.reload();
+ await app.start();
+ ctx.body = 'ok';
+ await next();
+ },
+ async disable(ctx, next) {
+ const { filterByTk } = ctx.action.params;
+ if (!filterByTk) {
+ ctx.throw(400, 'filterByTk invalid');
+ }
+ const name = pm.getPackageName(filterByTk);
+ const plugin = pm.get(name);
+ if (plugin.model) {
+ plugin.model.set('enabled', false);
+ await plugin.model.save();
+ }
+ if (!plugin) {
+ ctx.throw(400, 'plugin invalid');
+ }
+ await app.reload();
+ await app.start();
+ ctx.body = 'ok';
+ await next();
+ },
+ async upgrade(ctx, next) {
+ ctx.body = 'ok';
+ await next();
+ },
+ async remove(ctx, next) {
+ const { filterByTk } = ctx.action.params;
+ if (!filterByTk) {
+ ctx.throw(400, 'filterByTk invalid');
+ }
+ const name = pm.getPackageName(filterByTk);
+ const plugin = pm.get(name);
+ if (plugin.model) {
+ await plugin.model.destroy();
+ }
+ pm.remove(name);
+ await app.reload();
+ await app.start();
+ ctx.body = 'ok';
+ await next();
+ },
+ },
+ });
+ this.app.acl.use(async (ctx, next) => {
+ if (ctx.action.resourceName === 'pm') {
+ ctx.permission = {
+ skip: true,
+ };
+ }
+ await next();
+ });
+ this.app.on('beforeInstall', async () => {
+ await this.collection.sync();
+ });
+ this.app.on('beforeLoadAll', async (options) => {
+ const exists = await this.app.db.collectionExistsInDb('applicationPlugins');
+ if (!exists) {
+ return;
+ }
+ const items = await this.repository.find();
+ for (const item of items) {
+ await this.add(item);
+ }
+ });
+ }
+
+ getPackageName(name: string) {
+ if (name.includes('@nocobase/plugin-')) {
+ return name;
+ }
+ if (name.includes('/')) {
+ return `@${name}`;
+ }
+ return `@nocobase/plugin-${name}`;
+ }
+
+ private addByModel(model) {
+ try {
+ const packageName = this.getPackageName(model.get('name'));
+ require.resolve(packageName);
+ const cls = require(packageName).default;
+ const instance = new cls(this.app, {
+ ...model.get('options'),
+ name: model.get('name'),
+ version: model.get('version'),
+ enabled: model.get('enabled'),
+ });
+ instance.setModel(model);
+ this.plugins.set(packageName, instance);
+ return instance;
+ } catch (error) {}
}
getPlugins() {
@@ -30,13 +174,60 @@ export class PluginManager {
return this.plugins.get(name);
}
- add(pluginClass: PluginConstructor
, options?: O): P {
- const instance = new pluginClass(this.app, options);
+ remove(name: string) {
+ return this.plugins.delete(name);
+ }
+
+ add
(pluginClass: any, options?: O) {
+ if (Array.isArray(pluginClass)) {
+ const addMultiple = async () => {
+ for (const plugin of pluginClass) {
+ await this.add(plugin);
+ }
+ }
+ return addMultiple();
+ }
+ if (typeof pluginClass === 'string') {
+ const packageName = this.getPackageName(pluginClass);
+ try {
+ require.resolve(packageName);
+ } catch (error) {
+ throw new Error(`${pluginClass} plugin does not exist`);
+ }
+ const packageJson = require(`${packageName}/package.json`);
+ const addNew = async () => {
+ let model = await this.repository.findOne({
+ filter: { name: pluginClass },
+ });
+ if (model) {
+ throw new Error(`${pluginClass} plugin already exists`);
+ }
+ model = await this.repository.create({
+ values: {
+ name: pluginClass,
+ version: packageJson.version,
+ enabled: false,
+ options: {},
+ },
+ });
+ return this.addByModel(model);
+ };
+ return addNew();
+ }
+
+ if (pluginClass instanceof Model) {
+ return this.addByModel(pluginClass);
+ }
+
+ const instance = new pluginClass(this.app, {
+ ...options,
+ enabled: true,
+ });
const name = instance.getName();
if (this.plugins.has(name)) {
- throw new Error(`plugin name [${name}] `);
+ throw new Error(`plugin name [${name}] exists`);
}
this.plugins.set(name, instance);
@@ -48,10 +239,16 @@ export class PluginManager {
await this.app.emitAsync('beforeLoadAll');
for (const [name, plugin] of this.plugins) {
+ if (!plugin.enabled) {
+ continue;
+ }
await plugin.beforeLoad();
}
for (const [name, plugin] of this.plugins) {
+ if (!plugin.enabled) {
+ continue;
+ }
await this.app.emitAsync('beforeLoadPlugin', plugin);
await plugin.load();
await this.app.emitAsync('afterLoadPlugin', plugin);
@@ -62,6 +259,9 @@ export class PluginManager {
async install(options: InstallOptions = {}) {
for (const [name, plugin] of this.plugins) {
+ if (!plugin.enabled) {
+ continue;
+ }
await this.app.emitAsync('beforeInstallPlugin', plugin, options);
await plugin.install(options);
await this.app.emitAsync('afterInstallPlugin', plugin, options);
diff --git a/packages/core/server/src/plugin.ts b/packages/core/server/src/plugin.ts
index 8e82188bde..0c26391f4a 100644
--- a/packages/core/server/src/plugin.ts
+++ b/packages/core/server/src/plugin.ts
@@ -1,4 +1,4 @@
-import { Database } from '@nocobase/database';
+import { Database, Model } from '@nocobase/database';
import finder from 'find-package-json';
import { Application } from './application';
import { InstallOptions } from './plugin-manager';
@@ -15,6 +15,7 @@ export interface PluginOptions {
displayName?: string;
description?: string;
version?: string;
+ enabled?: boolean;
install?: (this: Plugin) => void;
load?: (this: Plugin) => void;
plugin?: typeof Plugin;
@@ -27,19 +28,31 @@ export abstract class Plugin implements PluginInterface {
options: O;
app: Application;
db: Database;
+ model: Model;
constructor(app: Application, options?: O) {
this.app = app;
this.db = app.db;
this.setOptions(options);
+ this.initialize();
}
setOptions(options: O) {
this.options = options || ({} as any);
}
+ setModel(model) {
+ this.model = model;
+ }
+
+ get enabled() {
+ return (this.options as any).enabled;
+ }
+
public abstract getName(): string;
+ initialize() {}
+
beforeLoad() {}
async install(options?: InstallOptions) {}
@@ -53,6 +66,10 @@ export abstract class Plugin implements PluginInterface {
}
}
+ async disable() {
+
+ }
+
collectionPath() {
return null;
}
diff --git a/packages/core/test/src/mockServer.ts b/packages/core/test/src/mockServer.ts
index 3d96b6931b..fe8cb7a37f 100644
--- a/packages/core/test/src/mockServer.ts
+++ b/packages/core/test/src/mockServer.ts
@@ -56,7 +56,6 @@ interface Resource {
export class MockServer extends Application {
async loadAndInstall(options: any = {}) {
- await this.load();
await this.install({
...options,
sync: {
diff --git a/packages/plugins/acl/src/__tests__/acl.test.ts b/packages/plugins/acl/src/__tests__/acl.test.ts
index 8e405504e1..23cc835953 100644
--- a/packages/plugins/acl/src/__tests__/acl.test.ts
+++ b/packages/plugins/acl/src/__tests__/acl.test.ts
@@ -1,8 +1,8 @@
import { ACL } from '@nocobase/acl';
import { Database } from '@nocobase/database';
-import { MockServer } from '@nocobase/test';
-import UsersPlugin from '@nocobase/plugin-users';
import { UiSchemaRepository } from '@nocobase/plugin-ui-schema-storage';
+import UsersPlugin from '@nocobase/plugin-users';
+import { MockServer } from '@nocobase/test';
import { prepareApp } from './prepare';
describe('acl', () => {
diff --git a/packages/plugins/acl/src/__tests__/prepare.ts b/packages/plugins/acl/src/__tests__/prepare.ts
index 7c83d93d01..2887fada47 100644
--- a/packages/plugins/acl/src/__tests__/prepare.ts
+++ b/packages/plugins/acl/src/__tests__/prepare.ts
@@ -1,7 +1,7 @@
-import PluginUsers from '@nocobase/plugin-users';
-import PluginErrorHandler from '@nocobase/plugin-error-handler';
import PluginCollectionManager from '@nocobase/plugin-collection-manager';
+import PluginErrorHandler from '@nocobase/plugin-error-handler';
import PluginUiSchema from '@nocobase/plugin-ui-schema-storage';
+import PluginUsers from '@nocobase/plugin-users';
import { mockServer } from '@nocobase/test';
import PluginACL from '../server';
diff --git a/packages/plugins/client/src/server.ts b/packages/plugins/client/src/server.ts
index 43a0e0b507..7d855bcf0b 100644
--- a/packages/plugins/client/src/server.ts
+++ b/packages/plugins/client/src/server.ts
@@ -20,6 +20,7 @@ export class ClientPlugin extends Plugin {
async load() {
this.app.acl.allow('app', 'getLang');
this.app.acl.allow('app', 'getInfo');
+ this.app.acl.allow('app', 'getPlugins');
this.app.acl.allow('plugins', 'getPinned', 'loggedIn');
this.app.resource({
name: 'app',
@@ -53,6 +54,24 @@ export class ClientPlugin extends Plugin {
};
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 {
+ require.resolve(`@nocobase/plugin-${item.name}/client`);
+ return true;
+ } catch (error) {}
+ return false;
+ })
+ .map((item) => item.name);
+ await next();
+ },
},
});
this.app.resource({
@@ -61,8 +80,7 @@ export class ClientPlugin extends Plugin {
// TODO: 临时
async getPinned(ctx, next) {
ctx.body = [
- { component: 'DesignableSwitch', pin: true },
- { component: 'CollectionManagerShortcut', pin: true },
+ { component: 'CollectionManagerShortcut' },
{ component: 'ACLShortcut' },
{ component: 'WorkflowShortcut' },
{ component: 'SchemaTemplateShortcut' },
diff --git a/packages/plugins/collection-manager/src/__tests__/action.test.ts b/packages/plugins/collection-manager/src/__tests__/action.test.ts
index dd5e64245f..dee0a8632e 100644
--- a/packages/plugins/collection-manager/src/__tests__/action.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/action.test.ts
@@ -1,7 +1,5 @@
import { Database } from '@nocobase/database';
-import { mockServer, MockServer } from '@nocobase/test';
-import CollectionManagerPlugin from '@nocobase/plugin-collection-manager';
-import { UiSchemaStoragePlugin } from '@nocobase/plugin-ui-schema-storage';
+import { MockServer } from '@nocobase/test';
import { createApp } from '.';
describe('action test', () => {
@@ -10,7 +8,6 @@ describe('action test', () => {
beforeEach(async () => {
app = await createApp();
- await app.install({ clean: true });
db = app.db;
});
diff --git a/packages/plugins/collection-manager/src/__tests__/beforeInitOptions.test.ts b/packages/plugins/collection-manager/src/__tests__/beforeInitOptions.test.ts
index 10ebfebd05..30542685ca 100644
--- a/packages/plugins/collection-manager/src/__tests__/beforeInitOptions.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/beforeInitOptions.test.ts
@@ -10,7 +10,6 @@ describe('collections repository', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts b/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts
index 3ed1887a6d..6fa9869c12 100644
--- a/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/collections.repository.test.ts
@@ -10,7 +10,6 @@ describe('collections repository', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/field-options/defaultValue.test.ts b/packages/plugins/collection-manager/src/__tests__/field-options/defaultValue.test.ts
index 306bad40fa..de07448f52 100644
--- a/packages/plugins/collection-manager/src/__tests__/field-options/defaultValue.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/field-options/defaultValue.test.ts
@@ -6,8 +6,6 @@ describe('field defaultValue', () => {
beforeEach(async () => {
app = await createApp();
- await app.install({ clean: true });
- await app.start();
await app
.agent()
.resource('collections')
diff --git a/packages/plugins/collection-manager/src/__tests__/field-options/indexes.test.ts b/packages/plugins/collection-manager/src/__tests__/field-options/indexes.test.ts
index 269de0105f..7789c74caa 100644
--- a/packages/plugins/collection-manager/src/__tests__/field-options/indexes.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/field-options/indexes.test.ts
@@ -7,8 +7,6 @@ describe('field indexes', () => {
beforeEach(async () => {
app = await createApp();
- await app.install({ clean: true });
- await app.start();
agent = app.agent();
await agent
.resource('collections')
diff --git a/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts b/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts
index 43a5b885e2..eb71958e3e 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields.repository.test.ts
@@ -10,7 +10,6 @@ describe('collections repository', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/fields/belongsTo.test.ts b/packages/plugins/collection-manager/src/__tests__/fields/belongsTo.test.ts
index f28b1a39c0..1786056811 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields/belongsTo.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields/belongsTo.test.ts
@@ -10,7 +10,6 @@ describe('belongsTo', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/fields/belongsToMany.test.ts b/packages/plugins/collection-manager/src/__tests__/fields/belongsToMany.test.ts
index 57211c24e0..88607e20d8 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields/belongsToMany.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields/belongsToMany.test.ts
@@ -10,7 +10,6 @@ describe('belongsToMany', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/fields/children.test.ts b/packages/plugins/collection-manager/src/__tests__/fields/children.test.ts
index 564b99b1ba..04bce1c1a0 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields/children.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields/children.test.ts
@@ -10,7 +10,6 @@ describe('children options', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/fields/hasMany.test.ts b/packages/plugins/collection-manager/src/__tests__/fields/hasMany.test.ts
index 661f2a5702..4095ebb924 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields/hasMany.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields/hasMany.test.ts
@@ -10,7 +10,6 @@ describe('hasMany field options', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/fields/hasOne.test.ts b/packages/plugins/collection-manager/src/__tests__/fields/hasOne.test.ts
index 2df4b5a3de..9b31f52647 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields/hasOne.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields/hasOne.test.ts
@@ -1,4 +1,4 @@
-import Database, { Collection as DBCollection, StringFieldOptions } from '@nocobase/database';
+import Database, { Collection as DBCollection } from '@nocobase/database';
import Application from '@nocobase/server';
import { createApp } from '..';
@@ -10,7 +10,6 @@ describe('hasOne field options', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts b/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts
index 64ed017910..de185f84b3 100644
--- a/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/fields/reverseField.test.ts
@@ -10,7 +10,6 @@ describe('reverseField options', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts b/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts
index 6b32557234..ad24931cc5 100644
--- a/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/http-api/collections.test.ts
@@ -8,8 +8,6 @@ describe('collections repository', () => {
beforeEach(async () => {
app = await createApp();
agent = app.agent();
- await app.install({ clean: true });
- await app.start();
await agent
.resource('collections')
.create({
diff --git a/packages/plugins/collection-manager/src/__tests__/index.ts b/packages/plugins/collection-manager/src/__tests__/index.ts
index 9926197ece..b8b1e9af4a 100644
--- a/packages/plugins/collection-manager/src/__tests__/index.ts
+++ b/packages/plugins/collection-manager/src/__tests__/index.ts
@@ -15,6 +15,8 @@ export async function createApp(options = {}) {
app.plugin(Plugin);
app.plugin(PluginUiSchema);
- await app.load();
+ // await app.load();
+ await app.install({ clean: true });
+ await app.start();
return app;
}
diff --git a/packages/plugins/collection-manager/src/__tests__/remove-collection.test.ts b/packages/plugins/collection-manager/src/__tests__/remove-collection.test.ts
index da915fac10..49292d6542 100644
--- a/packages/plugins/collection-manager/src/__tests__/remove-collection.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/remove-collection.test.ts
@@ -10,7 +10,6 @@ describe('collections repository', () => {
beforeEach(async () => {
app = await createApp();
- await app.db.sync();
db = app.db;
Collection = db.getCollection('collections');
Field = db.getCollection('fields');
diff --git a/packages/plugins/collection-manager/src/__tests__/resources/collections.fields.test.ts b/packages/plugins/collection-manager/src/__tests__/resources/collections.fields.test.ts
index 1a3762b6f9..584be2cc33 100644
--- a/packages/plugins/collection-manager/src/__tests__/resources/collections.fields.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/resources/collections.fields.test.ts
@@ -6,7 +6,6 @@ describe('collections.fields', () => {
beforeEach(async () => {
app = await createApp();
- await app.install({ clean: true });
});
afterEach(async () => {
diff --git a/packages/plugins/collection-manager/src/__tests__/resources/collections.test.ts b/packages/plugins/collection-manager/src/__tests__/resources/collections.test.ts
index c789fc06d2..2fe99c2cc9 100644
--- a/packages/plugins/collection-manager/src/__tests__/resources/collections.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/resources/collections.test.ts
@@ -7,7 +7,6 @@ describe('collections', () => {
beforeEach(async () => {
app = await createApp();
- await app.install({ clean: true });
});
afterEach(async () => {
diff --git a/packages/plugins/collection-manager/src/__tests__/through.test.ts b/packages/plugins/collection-manager/src/__tests__/through.test.ts
index 54aded2b58..a206d09cae 100644
--- a/packages/plugins/collection-manager/src/__tests__/through.test.ts
+++ b/packages/plugins/collection-manager/src/__tests__/through.test.ts
@@ -12,7 +12,6 @@ describe('collections repository', () => {
await app1.cleanDb();
app1.plugin(PluginErrorHandler);
app1.plugin(Plugin);
- await app1.load();
await app1.install({ clean: true });
await app1.start();
await app1
diff --git a/packages/plugins/multi-app-manager/src/__tests__/mock-get-schema.test.ts b/packages/plugins/multi-app-manager/src/__tests__/mock-get-schema.test.ts
index e201755390..2fc1e89db1 100644
--- a/packages/plugins/multi-app-manager/src/__tests__/mock-get-schema.test.ts
+++ b/packages/plugins/multi-app-manager/src/__tests__/mock-get-schema.test.ts
@@ -1,5 +1,6 @@
import { Plugin, PluginManager } from '@nocobase/server';
import { mockServer } from '@nocobase/test';
+import { uid } from '@nocobase/utils';
import { PluginMultiAppManager } from '../server';
describe('test with start', () => {
@@ -35,9 +36,11 @@ describe('test with start', () => {
const db = app.db;
+ const name = `d_${uid()}`;
+
await db.getRepository('applications').create({
values: {
- name: 'sub1',
+ name,
options: {
plugins: ['test-package'],
},
@@ -47,6 +50,8 @@ describe('test with start', () => {
expect(loadFn).toHaveBeenCalledTimes(1);
expect(installFn).toHaveBeenCalledTimes(1);
+ const subApp = await app.appManager.getApplication(name);
+ await subApp.destroy();
await app.destroy();
});
@@ -60,14 +65,18 @@ describe('test with start', () => {
const db = app.db;
+ const name = `d_${uid()}`;
+
await db.getRepository('applications').create({
values: {
- name: 'sub1',
+ name,
options: {
plugins: ['@nocobase/plugin-ui-schema-storage'],
},
},
});
+ const subApp = await app.appManager.getApplication(name);
+ await subApp.destroy();
await app.destroy();
});
@@ -91,17 +100,21 @@ describe('test with start', () => {
mockGetPluginByName.mockReturnValue(TestPlugin);
PluginManager.resolvePlugin = mockGetPluginByName;
+ const name = `d_${uid()}`;
+ console.log(name);
+
await db.getRepository('applications').create({
values: {
- name: 'sub1',
+ name,
options: {
plugins: ['test-package'],
},
},
});
- expect(app.appManager.applications.get('sub1')).toBeDefined();
+ expect(app.appManager.applications.get(name)).toBeDefined();
+ await app.appManager.applications.get(name).destroy();
await app.stop();
let newApp = mockServer({
@@ -115,14 +128,16 @@ describe('test with start', () => {
await newApp.start();
expect(await newApp.db.getRepository('applications').count()).toEqual(1);
- expect(newApp.appManager.applications.get('sub1')).not.toBeDefined();
+ expect(newApp.appManager.applications.get(name)).not.toBeDefined();
newApp.appManager.setAppSelector(() => {
- return 'sub1';
+ return name;
});
await newApp.agent().resource('test').test();
- expect(newApp.appManager.applications.get('sub1')).toBeDefined();
+ expect(newApp.appManager.applications.get(name)).toBeDefined();
+
+ await newApp.appManager.applications.get(name).destroy();
await app.destroy();
});
diff --git a/packages/plugins/multi-app-manager/src/__tests__/multiple-apps.test.ts b/packages/plugins/multi-app-manager/src/__tests__/multiple-apps.test.ts
index f4b312ee74..709f36030f 100644
--- a/packages/plugins/multi-app-manager/src/__tests__/multiple-apps.test.ts
+++ b/packages/plugins/multi-app-manager/src/__tests__/multiple-apps.test.ts
@@ -1,5 +1,6 @@
import { Database } from '@nocobase/database';
import { mockServer, MockServer } from '@nocobase/test';
+import { uid } from '@nocobase/utils';
import { ApplicationModel } from '..';
import { PluginMultiAppManager } from '../server';
@@ -21,84 +22,89 @@ describe('multiple apps create', () => {
});
it('should create application', async () => {
+ const name = `td_${uid()}`;
const miniApp = await db.getRepository('applications').create({
values: {
- name: 'miniApp',
+ name,
},
});
- expect(app.appManager.applications.get('miniApp')).toBeDefined();
+ expect(app.appManager.applications.get(name)).toBeDefined();
});
it('should remove application', async () => {
+ const name = `td_${uid()}`;
await db.getRepository('applications').create({
values: {
- name: 'miniApp',
+ name,
},
});
- expect(app.appManager.applications.get('miniApp')).toBeDefined();
+ expect(app.appManager.applications.get(name)).toBeDefined();
await db.getRepository('applications').destroy({
filter: {
- name: 'miniApp',
+ name,
},
});
- expect(app.appManager.applications.get('miniApp')).toBeUndefined();
+ expect(app.appManager.applications.get(name)).toBeUndefined();
});
it('should create with plugins', async () => {
+ const name = `td_${uid()}`;
await db.getRepository('applications').create({
values: {
- name: 'miniApp',
+ name,
options: {
plugins: [['@nocobase/plugin-ui-schema-storage', { test: 'B' }]],
},
},
});
- const miniApp = app.appManager.applications.get('miniApp');
+ const miniApp = app.appManager.applications.get(name);
expect(miniApp).toBeDefined();
const plugin = miniApp.pm.get('@nocobase/plugin-ui-schema-storage');
expect(plugin).toBeDefined();
- expect(plugin.options).toEqual({
+ expect(plugin.options).toMatchObject({
test: 'B',
});
});
it('should lazy load applications', async () => {
+ const name = `td_${uid()}`;
await db.getRepository('applications').create({
values: {
- name: 'miniApp',
+ name,
options: {
plugins: ['@nocobase/plugin-ui-schema-storage'],
},
},
});
- await app.appManager.removeApplication('miniApp');
+ await app.appManager.removeApplication(name);
app.appManager.setAppSelector(() => {
- return 'miniApp';
+ return name;
});
- expect(app.appManager.applications.has('miniApp')).toBeFalsy();
+ expect(app.appManager.applications.has(name)).toBeFalsy();
await app.agent().resource('test').test();
- expect(app.appManager.applications.has('miniApp')).toBeTruthy();
+ expect(app.appManager.applications.has(name)).toBeTruthy();
});
it('should change handleAppStart', async () => {
const customHandler = jest.fn();
ApplicationModel.handleAppStart = customHandler;
+ const name = `td_${uid()}`;
await db.getRepository('applications').create({
values: {
- name: 'miniApp',
+ name,
options: {
plugins: ['@nocobase/plugin-ui-schema-storage'],
},
diff --git a/packages/plugins/multi-app-manager/src/models/application.ts b/packages/plugins/multi-app-manager/src/models/application.ts
index 4a7d5ee587..0a89d282c9 100644
--- a/packages/plugins/multi-app-manager/src/models/application.ts
+++ b/packages/plugins/multi-app-manager/src/models/application.ts
@@ -17,12 +17,11 @@ export class ApplicationModel extends Model {
}
static async handleAppStart(app: Application, options: registerAppOptions) {
- await app.load();
-
- if (!lodash.get(options, 'skipInstall', false)) {
+ if (!options?.skipInstall) {
await app.install();
+ } else {
+ await app.load();
}
-
await app.start();
}
@@ -32,14 +31,9 @@ export class ApplicationModel extends Model {
const AppModel = this.constructor as typeof ApplicationModel;
- const app = mainApp.appManager.createApplication(appName, {
- ...AppModel.initOptions(appName, mainApp),
- ...appOptions,
- });
-
- // create database before installation if it not exists
- app.on('beforeInstall', async function createDatabase() {
- const { host, port, username, password, database, dialect } = AppModel.getDatabaseConfig(app);
+ const createDatabase = async () => {
+ const database = appName;
+ const { host, port, username, password, dialect } = mainApp.db.options;
if (dialect === 'mysql') {
const mysql = require('mysql2/promise');
@@ -56,7 +50,7 @@ export class ApplicationModel extends Model {
port,
user: username,
password,
- database: 'postgres'
+ database: 'postgres',
});
await client.connect();
@@ -67,6 +61,15 @@ export class ApplicationModel extends Model {
await client.end();
}
+ };
+
+ if (!options?.skipInstall) {
+ await createDatabase();
+ }
+
+ const app = mainApp.appManager.createApplication(appName, {
+ ...AppModel.initOptions(appName, mainApp),
+ ...appOptions,
});
await AppModel.handleAppStart(app, options);
diff --git a/packages/plugins/users/src/migrations/20220818072639-add-users-phone.ts b/packages/plugins/users/src/migrations/20220818072639-add-users-phone.ts
index 5b6540ca13..fb1e863214 100644
--- a/packages/plugins/users/src/migrations/20220818072639-add-users-phone.ts
+++ b/packages/plugins/users/src/migrations/20220818072639-add-users-phone.ts
@@ -2,7 +2,7 @@ import { Migration } from '@nocobase/server';
export default class AlertSubTableMigration extends Migration {
async up() {
- const match = await this.app.version.satisfies('<=0.7.4-alpha.8');
+ const match = await this.app.version.satisfies('<=0.7.4-alpha.7');
if (!match) {
return;
}
@@ -10,8 +10,8 @@ export default class AlertSubTableMigration extends Migration {
const existed = await Field.count({
filter: {
name: 'phone',
- collectionName: 'users'
- }
+ collectionName: 'users',
+ },
});
if (!existed) {
await Field.create({
@@ -30,12 +30,10 @@ export default class AlertSubTableMigration extends Migration {
},
},
// NOTE: to trigger hook
- context: {}
+ context: {},
});
}
}
- async down() {
-
- }
+ async down() {}
}
diff --git a/packages/plugins/workflow/src/client/WorkflowProvider.tsx b/packages/plugins/workflow/src/client/WorkflowProvider.tsx
index 44e05f261c..9597fddfa7 100644
--- a/packages/plugins/workflow/src/client/WorkflowProvider.tsx
+++ b/packages/plugins/workflow/src/client/WorkflowProvider.tsx
@@ -1,7 +1,7 @@
+import { PluginManagerContext, RouteSwitchContext, SettingsCenterProvider } from '@nocobase/client';
import React, { useContext } from 'react';
-import { PluginManagerContext, RouteSwitchContext } from '@nocobase/client';
import { WorkflowPage } from './WorkflowPage';
-import { WorkflowShortcut } from './WorkflowShortcut';
+import { WorkflowPane, WorkflowShortcut } from './WorkflowShortcut';
export const WorkflowProvider = (props) => {
const ctx = useContext(PluginManagerContext);
@@ -12,17 +12,32 @@ export const WorkflowProvider = (props) => {
component: 'WorkflowPage',
});
return (
-
-
- {props.children}
-
-
+
+
+ {props.children}
+
+
+
);
};
diff --git a/packages/plugins/workflow/src/client/WorkflowShortcut.tsx b/packages/plugins/workflow/src/client/WorkflowShortcut.tsx
index 04fd2a4d10..c6e7b12a6d 100644
--- a/packages/plugins/workflow/src/client/WorkflowShortcut.tsx
+++ b/packages/plugins/workflow/src/client/WorkflowShortcut.tsx
@@ -1,16 +1,14 @@
-import React, { useState } from 'react';
import { PartitionOutlined } from '@ant-design/icons';
import { ISchema } from '@formily/react';
import { uid } from '@formily/shared';
+import { ActionContext, PluginManager, SchemaComponent } from '@nocobase/client';
+import { Card } from 'antd';
+import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
-
-import { PluginManager, ActionContext, SchemaComponent } from '@nocobase/client';
-
+import { useHistory } from 'react-router-dom';
+import { ExecutionResourceProvider } from './ExecutionResourceProvider';
import { workflowSchema } from './schemas/workflows';
import { WorkflowLink } from './WorkflowLink';
-import { ExecutionResourceProvider } from './ExecutionResourceProvider';
-
-
const schema: ISchema = {
type: 'object',
@@ -26,7 +24,44 @@ const schema: ISchema = {
},
};
+const schema2: ISchema = {
+ type: 'object',
+ properties: {
+ [uid()]: workflowSchema,
+ },
+};
+
+export const WorkflowPane = () => {
+ const { t } = useTranslation();
+ const [visible, setVisible] = useState(false);
+ return (
+
+
+
+ );
+};
+
export const WorkflowShortcut = () => {
+ const { t } = useTranslation();
+ const history = useHistory();
+ return (
+ }
+ title={t('Workflow')}
+ onClick={() => {
+ history.push('/admin/settings/workflow/workflows');
+ }}
+ />
+ );
+};
+
+export const WorkflowShortcut2 = () => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
return (
@@ -42,7 +77,7 @@ export const WorkflowShortcut = () => {
schema={schema}
components={{
WorkflowLink,
- ExecutionResourceProvider
+ ExecutionResourceProvider,
}}
/>
diff --git a/packages/presets/nocobase/src/index.ts b/packages/presets/nocobase/src/index.ts
index 08c4c654ed..3f88bd451b 100644
--- a/packages/presets/nocobase/src/index.ts
+++ b/packages/presets/nocobase/src/index.ts
@@ -5,23 +5,33 @@ export class PresetNocoBase extends Plugin {
return this.getPackageName(__dirname);
}
- beforeLoad(): void {
- this.app.loadPluginConfig([
- '@nocobase/plugin-error-handler',
- '@nocobase/plugin-collection-manager',
- '@nocobase/plugin-ui-schema-storage',
- '@nocobase/plugin-ui-routes-storage',
- '@nocobase/plugin-file-manager',
- '@nocobase/plugin-system-settings',
- '@nocobase/plugin-verification',
- '@nocobase/plugin-users',
- '@nocobase/plugin-acl',
- '@nocobase/plugin-china-region',
- '@nocobase/plugin-workflow',
- '@nocobase/plugin-client',
- '@nocobase/plugin-export',
- '@nocobase/plugin-audit-logs',
- ]);
+ initialize() {
+ this.app.on('beforeInstall', async () => {
+ const plugins = [
+ 'error-handler',
+ 'collection-manager',
+ 'ui-schema-storage',
+ 'ui-routes-storage',
+ 'file-manager',
+ 'system-settings',
+ 'verification',
+ 'users',
+ 'acl',
+ 'china-region',
+ 'workflow',
+ 'client',
+ 'export',
+ 'audit-logs',
+ ];
+ for (const plugin of plugins) {
+ const instance = await this.app.pm.add(plugin);
+ if (instance.model && plugin !== 'hello') {
+ instance.model.enabled = true;
+ instance.model.builtIn = true;
+ await instance.model.save();
+ }
+ }
+ });
}
}
diff --git a/packages/samples/hello/client.d.ts b/packages/samples/hello/client.d.ts
new file mode 100755
index 0000000000..765db9222a
--- /dev/null
+++ b/packages/samples/hello/client.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/client';
+export { default } from './lib/client';
+
diff --git a/packages/samples/hello/client.js b/packages/samples/hello/client.js
new file mode 100755
index 0000000000..238820257c
--- /dev/null
+++ b/packages/samples/hello/client.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/client"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/samples/hello/package.json b/packages/samples/hello/package.json
new file mode 100644
index 0000000000..064aa90fe6
--- /dev/null
+++ b/packages/samples/hello/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "@nocobase/plugin-hello-sample",
+ "version": "0.7.4-alpha.7",
+ "main": "lib/server/index.js",
+ "dependencies": {},
+ "devDependencies": {
+ "@nocobase/client": "0.7.4-alpha.7",
+ "@nocobase/server": "0.7.4-alpha.7",
+ "@nocobase/test": "0.7.4-alpha.7"
+ },
+ "peerDependencies": {
+ "@nocobase/client": "*",
+ "@nocobase/server": "*",
+ "@nocobase/test": "*"
+ }
+}
diff --git a/packages/samples/hello/server.d.ts b/packages/samples/hello/server.d.ts
new file mode 100755
index 0000000000..e70edb928a
--- /dev/null
+++ b/packages/samples/hello/server.d.ts
@@ -0,0 +1,4 @@
+// @ts-nocheck
+export * from './lib/server';
+export { default } from './lib/server';
+
diff --git a/packages/samples/hello/server.js b/packages/samples/hello/server.js
new file mode 100755
index 0000000000..d06a7eb92c
--- /dev/null
+++ b/packages/samples/hello/server.js
@@ -0,0 +1,30 @@
+"use strict";
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+var _index = _interopRequireWildcard(require("./lib/server"));
+
+Object.defineProperty(exports, "__esModule", {
+ value: true
+});
+var _exportNames = {};
+Object.defineProperty(exports, "default", {
+ enumerable: true,
+ get: function get() {
+ return _index.default;
+ }
+});
+
+Object.keys(_index).forEach(function (key) {
+ if (key === "default" || key === "__esModule") return;
+ if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
+ if (key in exports && exports[key] === _index[key]) return;
+ Object.defineProperty(exports, key, {
+ enumerable: true,
+ get: function get() {
+ return _index[key];
+ }
+ });
+});
diff --git a/packages/samples/hello/src/client/HelloDesigner.tsx b/packages/samples/hello/src/client/HelloDesigner.tsx
new file mode 100644
index 0000000000..ba5602092c
--- /dev/null
+++ b/packages/samples/hello/src/client/HelloDesigner.tsx
@@ -0,0 +1,22 @@
+import { useFieldSchema } from '@formily/react';
+import {
+ GeneralSchemaDesigner,
+ SchemaSettings,
+ useCollection
+} from '@nocobase/client';
+import React from 'react';
+
+export const HelloDesigner = () => {
+ const { name, title } = useCollection();
+ const fieldSchema = useFieldSchema();
+ return (
+
+
+
+ );
+};
diff --git a/packages/samples/hello/src/client/index.tsx b/packages/samples/hello/src/client/index.tsx
new file mode 100644
index 0000000000..5cddab1e8a
--- /dev/null
+++ b/packages/samples/hello/src/client/index.tsx
@@ -0,0 +1,68 @@
+import { TableOutlined } from '@ant-design/icons';
+import {
+ SchemaComponentOptions,
+ SchemaInitializer,
+ SchemaInitializerContext,
+ SettingsCenterProvider
+} from '@nocobase/client';
+import { Card } from 'antd';
+import React, { useContext } from 'react';
+import { useTranslation } from 'react-i18next';
+import { HelloDesigner } from './HelloDesigner';
+
+export const HelloBlockInitializer = (props) => {
+ const { insert } = props;
+ const { t } = useTranslation();
+ return (
+ }
+ onClick={() => {
+ insert({
+ type: 'void',
+ 'x-component': 'CardItem',
+ 'x-designer': 'HelloDesigner',
+ properties: {
+ hello: {
+ type: 'void',
+ 'x-component': 'div',
+ 'x-content': 'Hello World',
+ },
+ },
+ });
+ }}
+ title={t('Hello block')}
+ />
+ );
+};
+
+export default React.memo((props) => {
+ const items = useContext(SchemaInitializerContext);
+ const children = items.BlockInitializers.items[2].children;
+ children.push({
+ key: 'hello',
+ type: 'item',
+ title: '{{t("Hello block")}}',
+ component: 'HelloBlockInitializer',
+ });
+ return (
+ Hello Settings,
+ },
+ },
+ },
+ }}
+ >
+
+ {props.children}
+
+
+ );
+});
diff --git a/packages/samples/hello/src/index.ts b/packages/samples/hello/src/index.ts
new file mode 100644
index 0000000000..7ddad58145
--- /dev/null
+++ b/packages/samples/hello/src/index.ts
@@ -0,0 +1 @@
+export { default } from './server';
diff --git a/packages/samples/hello/src/server/actions/.gitkeep b/packages/samples/hello/src/server/actions/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/samples/hello/src/server/collections/.gitkeep b/packages/samples/hello/src/server/collections/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/samples/hello/src/server/index.ts b/packages/samples/hello/src/server/index.ts
new file mode 100644
index 0000000000..82f3baf021
--- /dev/null
+++ b/packages/samples/hello/src/server/index.ts
@@ -0,0 +1,36 @@
+import { InstallOptions, Plugin } from '@nocobase/server';
+
+export class HelloPlugin extends Plugin {
+ getName(): string {
+ return this.getPackageName(__dirname);
+ }
+
+ beforeLoad() {
+ // TODO
+ }
+
+ async load() {
+ // TODO
+ // Visit: http://localhost:13000/api/testHello:getInfo
+ this.app.resource({
+ name: 'testHello',
+ actions: {
+ async getInfo(ctx, next) {
+ ctx.body = `Hello hello!`;
+ next();
+ },
+ },
+ });
+ this.app.acl.allow('testHello', 'getInfo');
+ }
+
+ async disable() {
+ // this.app.resourcer.removeResource('testHello');
+ }
+
+ async install(options: InstallOptions) {
+ // TODO
+ }
+}
+
+export default HelloPlugin;
diff --git a/packages/samples/hello/src/server/models/.gitkeep b/packages/samples/hello/src/server/models/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/samples/hello/src/server/repositories/.gitkeep b/packages/samples/hello/src/server/repositories/.gitkeep
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/tsconfig.json b/tsconfig.json
index 6f0e29ad0f..52ad39576e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -20,6 +20,9 @@
"@nocobase/app-*": [
"packages/app/*/src"
],
+ "@nocobase/plugin-*-sample": [
+ "packages/samples/*/src"
+ ],
"@nocobase/plugin-*": [
"packages/plugins/*/src"
],