mirror of
https://github.com/gristlabs/grist-core.git
synced 2026-03-02 04:09:24 +00:00
(core) move home server into core
Summary: This moves enough server material into core to run a home server. The data engine is not yet incorporated (though in manual testing it works when ported). Test Plan: existing tests pass Reviewers: dsagal Reviewed By: dsagal Differential Revision: https://phab.getgrist.com/D2552
This commit is contained in:
165
app/server/lib/PluginManager.ts
Normal file
165
app/server/lib/PluginManager.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import {DirectoryScanEntry, LocalPlugin} from 'app/common/plugin';
|
||||
import * as log from 'app/server/lib/log';
|
||||
import {readManifest} from 'app/server/lib/manifest';
|
||||
import {getAppPathTo} from 'app/server/lib/places';
|
||||
import * as fse from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
|
||||
/**
|
||||
* Various plugins' related directories.
|
||||
*/
|
||||
export interface PluginDirectories {
|
||||
/**
|
||||
* Directory where built in plugins are located.
|
||||
*/
|
||||
readonly builtIn?: string;
|
||||
/**
|
||||
* Directory where user installed plugins are localted.
|
||||
*/
|
||||
readonly installed?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* The plugin manager class is responsible for providing both built in and installed plugins and
|
||||
* spawning server side plugins's.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* const pluginManager = new PluginManager(appRoot, userRoot);
|
||||
* await pluginManager.initialize();
|
||||
*
|
||||
*/
|
||||
export class PluginManager {
|
||||
|
||||
public pluginsLoaded: Promise<void>;
|
||||
|
||||
// ========== Instance members and methods ==========
|
||||
private _dirs: PluginDirectories;
|
||||
private _validPlugins: LocalPlugin[] = [];
|
||||
private _entries: DirectoryScanEntry[] = [];
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} userRoot: path to user's grist directory; `null` is allowed, to only uses built in plugins.
|
||||
*
|
||||
*/
|
||||
public constructor(public appRoot?: string, userRoot?: string) {
|
||||
this._dirs = {
|
||||
installed: userRoot ? path.join(userRoot, 'plugins') : undefined,
|
||||
builtIn: appRoot ? getAppPathTo(appRoot, 'plugins') : undefined
|
||||
};
|
||||
}
|
||||
|
||||
public dirs(): PluginDirectories {return this._dirs; }
|
||||
|
||||
/**
|
||||
* Create tmp dir and load plugins.
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
try {
|
||||
await (this.pluginsLoaded = this.loadPlugins());
|
||||
} catch (err) {
|
||||
log.error("PluginManager's initialization failed: ", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-load plugins (litterally re-run `loadPlugins`).
|
||||
*/
|
||||
// TODO: it's not clear right now what we do on reload. Do we deactivate plugins that were removed
|
||||
// from the fs? Do we update plugins that have changed on the fs ?
|
||||
public async reloadPlugins(): Promise<void> {
|
||||
return await this.loadPlugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Discover both builtIn and user installed plugins. Logs any failures that happens when scanning
|
||||
* a directory (ie: manifest missing or manifest validation errors etc...)
|
||||
*/
|
||||
public async loadPlugins(): Promise<void> {
|
||||
this._entries = [];
|
||||
|
||||
// Load user installed plugins
|
||||
if (this._dirs.installed) {
|
||||
this._entries.push(...await scanDirectory(this._dirs.installed, "installed"));
|
||||
}
|
||||
|
||||
// Load builtIn plugins
|
||||
if (this._dirs.builtIn) {
|
||||
this._entries.push(...await scanDirectory(this._dirs.builtIn, "builtIn"));
|
||||
}
|
||||
|
||||
if (!process.env.GRIST_EXPERIMENTAL_PLUGINS ||
|
||||
process.env.GRIST_EXPERIMENTAL_PLUGINS === '0') {
|
||||
// Remove experimental plugins
|
||||
this._entries = this._entries.filter(entry => {
|
||||
if (entry.manifest && entry.manifest.experimental) {
|
||||
log.warn("Ignoring experimental plugin %s", entry.id);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
this._validPlugins = this._entries.filter(entry => !entry.errors).map(entry => entry as LocalPlugin);
|
||||
|
||||
this._logScanningReport();
|
||||
}
|
||||
|
||||
public getPlugins(): LocalPlugin[] {
|
||||
return this._validPlugins;
|
||||
}
|
||||
|
||||
|
||||
private _logScanningReport() {
|
||||
const invalidPlugins = this._entries.filter( entry => entry.errors);
|
||||
if (invalidPlugins.length) {
|
||||
for (const plugin of invalidPlugins) {
|
||||
log.warn(`Error loading plugins: Failed to load extension from ${plugin.path}\n` +
|
||||
(plugin.errors!).map(m => " - " + m).join("\n ")
|
||||
);
|
||||
}
|
||||
}
|
||||
log.info(`Found ${this._validPlugins.length} valid plugins on the system`);
|
||||
for (const p of this._validPlugins) {
|
||||
log.debug("PLUGIN %s -- %s", p.id, p.path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function scanDirectory(dir: string, kind: "installed"|"builtIn"): Promise<DirectoryScanEntry[]> {
|
||||
const plugins: DirectoryScanEntry[] = [];
|
||||
let listDir;
|
||||
|
||||
try {
|
||||
listDir = await fse.readdir(dir);
|
||||
} catch (e) {
|
||||
// non existing dir is treated as an empty dir
|
||||
log.info(`No plugins directory: ${e.message}`);
|
||||
return [];
|
||||
}
|
||||
|
||||
for (const id of listDir) {
|
||||
const folderPath = path.join(dir, id),
|
||||
plugin: DirectoryScanEntry = {
|
||||
path: folderPath,
|
||||
id: `${kind}/${id}`
|
||||
};
|
||||
try {
|
||||
plugin.manifest = await readManifest(folderPath);
|
||||
} catch (e) {
|
||||
plugin.errors = [];
|
||||
if (e.message) {
|
||||
plugin.errors.push(e.message);
|
||||
}
|
||||
if (e.notices) {
|
||||
plugin.errors.push(...e.notices);
|
||||
}
|
||||
}
|
||||
plugins.push(plugin);
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
Reference in New Issue
Block a user