Skip to content

Commit

Permalink
Adding package.json support with dependency #753
Browse files Browse the repository at this point in the history
  • Loading branch information
bpatrik committed Nov 17, 2023
1 parent 3cf45c5 commit 9f52576
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 34 deletions.
23 changes: 19 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"fluent-ffmpeg": "2.1.2",
"image-size": "1.0.2",
"locale": "0.1.0",
"logger": "file:extensions/logger",
"node-geocoder": "4.2.0",
"nodemailer": "6.9.4",
"reflect-metadata": "0.1.13",
Expand Down
81 changes: 52 additions & 29 deletions src/backend/model/extension/ExtensionManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import * as express from 'express';
import {SQLConnection} from '../database/SQLConnection';
import {ExtensionObject} from './ExtensionObject';
import {ExtensionDecoratorObject} from './ExtensionDecorator';
import * as util from 'util';
// eslint-disable-next-line @typescript-eslint/no-var-requires
const exec = util.promisify(require('child_process').exec);

const LOG_TAG = '[ExtensionManager]';

Expand Down Expand Up @@ -68,56 +71,76 @@ export class ExtensionManager implements IObjectManager {
}

Config.Extensions.list = fs
.readdirSync(ProjectPath.ExtensionFolder)
.filter((f): boolean =>
fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory()
);
.readdirSync(ProjectPath.ExtensionFolder)
.filter((f): boolean =>
fs.statSync(path.join(ProjectPath.ExtensionFolder, f)).isDirectory()
);
Config.Extensions.list.sort();
Logger.debug(LOG_TAG, 'Extensions found ', JSON.stringify(Config.Extensions.list));
}

private async callServerFN(fn: (ext: IServerExtension<unknown>, extName: string) => Promise<void>) {
for (let i = 0; i < Config.Extensions.list.length; ++i) {
const extName = Config.Extensions.list[i];
const extPath = path.join(ProjectPath.ExtensionFolder, extName);
const serverExt = path.join(extPath, 'server.js');
if (!fs.existsSync(serverExt)) {
Logger.silly(LOG_TAG, `Skipping ${extName} server initiation. server.js does not exists`);
continue;
private createUniqueExtensionObject(name: string, folder: string): IExtensionObject<unknown> {
let id = name;
if (this.extObjects[id]) {
let i = 0;
while (this.extObjects[`${name}_${++i}`]) { /* empty */
}
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ext = require(serverExt);
await fn(ext, extName);
id = `${name}_${++i}`;
}
}

private createExtensionObject(name: string): IExtensionObject<unknown> {
if (!this.extObjects[name]) {
this.extObjects[name] = new ExtensionObject(name, this.router, this.events);
if (!this.extObjects[id]) {
this.extObjects[id] = new ExtensionObject(id, name, folder, this.router, this.events);
}
return this.extObjects[name];
return this.extObjects[id];
}

private async initExtensions() {
await this.callServerFN(async (ext, extName) => {

for (let i = 0; i < Config.Extensions.list.length; ++i) {
const extFolder = Config.Extensions.list[i];
let extName = extFolder;
const extPath = path.join(ProjectPath.ExtensionFolder, extFolder);
const serverExtPath = path.join(extPath, 'server.js');
const packageJsonPath = path.join(extPath, 'package.json');
if (!fs.existsSync(serverExtPath)) {
Logger.silly(LOG_TAG, `Skipping ${extFolder} server initiation. server.js does not exists`);
continue;
}

if (fs.existsSync(packageJsonPath)) {
Logger.silly(LOG_TAG, `Running: "npm install --omit=dev" in ${extPath}`);
await exec('npm install --omit=dev' ,{
cwd:extPath
});
// eslint-disable-next-line @typescript-eslint/no-var-requires
const pkg = require(packageJsonPath);
if (pkg.name) {
extName = pkg.name;
}
}

// eslint-disable-next-line @typescript-eslint/no-var-requires
const ext = require(serverExtPath);
if (typeof ext?.init === 'function') {
Logger.debug(LOG_TAG, 'Running init on extension: ' + extName);
await ext?.init(this.createExtensionObject(extName));
Logger.debug(LOG_TAG, 'Running init on extension: ' + extFolder);
await ext?.init(this.createUniqueExtensionObject(extName, extPath));
}
});
}
if (Config.Extensions.cleanUpUnusedTables) {
// Clean up tables after all Extension was initialized.
await SQLConnection.removeUnusedTables();
}
}

private async cleanUpExtensions() {
await this.callServerFN(async (ext, extName) => {
for (const extObj of Object.values(this.extObjects)) {
const serverExt = path.join(extObj.folder, 'server.js');
// eslint-disable-next-line @typescript-eslint/no-var-requires
const ext = require(serverExt);
if (typeof ext?.cleanUp === 'function') {
Logger.debug(LOG_TAG, 'Running Init on extension:' + extName);
await ext?.cleanUp(this.createExtensionObject(extName));
Logger.debug(LOG_TAG, 'Running Init on extension:' + extObj.extensionName);
await ext?.cleanUp(ext);
}
});
}
}


Expand Down
6 changes: 5 additions & 1 deletion src/backend/model/extension/ExtensionObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export class ExtensionObject<C> implements IExtensionObject<C> {
public readonly events;
public readonly RESTApi;

constructor(public readonly extensionId: string, extensionRouter: express.Router, events: IExtensionEvents) {
constructor(public readonly extensionId: string,
public readonly extensionName: string,
public readonly folder: string,
extensionRouter: express.Router,
events: IExtensionEvents) {
const logger = createLoggerWrapper(`[Extension][${extensionId}]`);
this._app = new ExtensionApp();
this.config = new ExtensionConfig<C>(extensionId);
Expand Down
10 changes: 10 additions & 0 deletions src/backend/model/extension/IExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,16 @@ export interface IExtensionConfig<C> {
}

export interface IExtensionObject<C> {
/**
* ID of the extension that is internally used. By default the name and ID matches if there is no collision.
*/
extensionId: string,

/**
* Name of the extension
*/
extensionName: string,

/**
* Inner functionality of the app. Use this with caution.
* If you want to go deeper than the standard exposed APIs, you can try doing so here.
Expand Down

0 comments on commit 9f52576

Please sign in to comment.