tachybase_todo/packages/plugins/duplicator/src/server/app-migrator.ts
2023-02-11 00:09:39 +08:00

148 lines
4.1 KiB
TypeScript

import { Application } from '@nocobase/server';
import { applyMixins, AsyncEmitter } from '@nocobase/utils';
import crypto from 'crypto';
import EventEmitter from 'events';
import fsPromises from 'fs/promises';
import inquirer from 'inquirer';
import lodash from 'lodash';
import * as os from 'os';
import path from 'path';
abstract class AppMigrator extends EventEmitter {
protected workDir: string;
public app: Application;
abstract direction: 'restore' | 'dump';
declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
constructor(
app,
options?: {
workDir?: string;
},
) {
super();
this.app = app;
this.workDir = options?.workDir || this.tmpDir();
}
tmpDir() {
return path.resolve(os.tmpdir(), `nocobase-${crypto.randomUUID()}`);
}
getPluginCollections(plugins: string | string[]) {
return lodash
.castArray(plugins)
.map((pluginName) => {
return this.app.db.importedFrom.get(pluginName) || [];
})
.flat();
}
async getAppPlugins() {
const plugins = await this.app.db.getCollection('applicationPlugins').repository.find();
return ['core', ...plugins.map((plugin) => plugin.get('name'))];
}
async getCustomCollections() {
const collections = await this.app.db.getCollection('collections').repository.find();
return collections.filter((collection) => !collection.get('isThrough')).map((collection) => collection.get('name'));
}
async rmDir(dir: string) {
await fsPromises.rm(dir, { recursive: true, force: true });
}
async clearWorkDir() {
await this.rmDir(this.workDir);
}
buildInquirerPluginQuestion(requiredGroups, optionalGroups) {
return {
type: 'checkbox',
name: 'collectionGroups',
message: `Select the plugin collections to be ${this.direction === 'dump' ? 'dumped' : 'restored'}`,
loop: false,
pageSize: 20,
choices: [
new inquirer.Separator('== Required =='),
...requiredGroups.map((collectionGroup) => ({
name: `${collectionGroup.function} (${collectionGroup.pluginName})`,
value: `${collectionGroup.pluginName}.${collectionGroup.function}`,
checked: true,
disabled: true,
})),
new inquirer.Separator('== Optional =='),
...optionalGroups.map((collectionGroup) => ({
name: `${collectionGroup.function} (${collectionGroup.pluginName})`,
value: `${collectionGroup.pluginName}.${collectionGroup.function}`,
checked: this.direction === 'dump',
})),
],
};
}
buildInquirerCollectionQuestion(
collections: {
name: string;
title: string;
}[],
) {
return {
type: 'checkbox',
name: 'userCollections',
message: `Select the collection records to be ${this.direction === 'dump' ? 'dumped' : 'restored'}`,
loop: false,
pageSize: 30,
choices: collections.map((collection) => {
return {
name: collection.title,
value: collection.name,
checked: this.direction === 'dump',
};
}),
};
}
buildInquirerQuestions(requiredGroups, optionalGroups, optionalCollections) {
const questions = [this.buildInquirerPluginQuestion(requiredGroups, optionalGroups)];
if (optionalCollections.length > 0) {
questions.push(this.buildInquirerCollectionQuestion(optionalCollections));
}
return questions;
}
findThroughCollections(collections: string[]) {
return [
...new Set(
collections
.map((collectionName) => this.app.db.getCollection(collectionName))
.map((collection) =>
[...collection.fields.values()].filter((field) => field.through).map((field) => field.through),
)
.flat(),
),
];
}
findSequenceFields(collections: string[]) {
return [
...new Set(
collections
.map((collectionName) => this.app.db.getCollection(collectionName))
.map((collection) => [...collection.fields.values()].filter((field) => field.type === 'sequence'))
.flat(),
),
];
}
}
applyMixins(AppMigrator, [AsyncEmitter]);
export { AppMigrator };