packaged plugins

This commit is contained in:
Jan Prochazka
2021-04-15 10:52:02 +02:00
parent 4d5cc119f2
commit 5dd03484ea
9 changed files with 700 additions and 87 deletions

View File

@@ -23,7 +23,7 @@
"build:app": "cd app && yarn install && yarn build", "build:app": "cd app && yarn install && yarn build",
"build:api": "yarn workspace dbgate-api build", "build:api": "yarn workspace dbgate-api build",
"build:web:docker": "yarn workspace dbgate-web build", "build:web:docker": "yarn workspace dbgate-web build",
"build:plugins:frontend": "yarn workspace dbgate-plugin-csv build:frontend && yarn workspace dbgate-plugin-excel build:frontend && yarn workspace dbgate-plugin-mysql build:frontend && yarn workspace dbgate-plugin-mssql build:frontend && yarn workspace dbgate-plugin-mongo build:frontend && yarn workspace dbgate-plugin-postgres build:frontend &&", "build:plugins:frontend": "workspaces-run --only=\"dbgate-plugin-*\" -- yarn build:frontend",
"build:app:local": "cd app && yarn build:local", "build:app:local": "cd app && yarn build:local",
"start:app:local": "cd app && yarn start:local", "start:app:local": "cd app && yarn start:local",
"setCurrentVersion": "node setCurrentVersion", "setCurrentVersion": "node setCurrentVersion",
@@ -39,7 +39,7 @@
"ts:api": "yarn workspace dbgate-api ts", "ts:api": "yarn workspace dbgate-api ts",
"ts:web": "yarn workspace dbgate-web ts", "ts:web": "yarn workspace dbgate-web ts",
"ts": "yarn ts:api && yarn ts:web", "ts": "yarn ts:api && yarn ts:web",
"postinstall": "patch-package && yarn fillNativeModules" "postinstall": "patch-package && yarn fillNativeModules && yarn build:plugins:frontend"
}, },
"dependencies": { "dependencies": {
"concurrently": "^5.1.0", "concurrently": "^5.1.0",
@@ -48,6 +48,7 @@
}, },
"devDependencies": { "devDependencies": {
"copyfiles": "^2.2.0", "copyfiles": "^2.2.0",
"prettier": "^2.2.1" "prettier": "^2.2.1",
"workspaces-run": "^1.0.1"
} }
} }

View File

@@ -33,7 +33,7 @@
"express-basic-auth": "^1.2.0", "express-basic-auth": "^1.2.0",
"express-fileupload": "^1.2.0", "express-fileupload": "^1.2.0",
"find-free-port": "^2.0.0", "find-free-port": "^2.0.0",
"fs-extra": "^8.1.0", "fs-extra": "^9.1.0",
"http": "^0.0.0", "http": "^0.0.0",
"json-stable-stringify": "^1.0.1", "json-stable-stringify": "^1.0.1",
"line-reader": "^0.4.0", "line-reader": "^0.4.0",
@@ -56,6 +56,7 @@
"build": "webpack" "build": "webpack"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^9.0.11",
"@types/lodash": "^4.14.149", "@types/lodash": "^4.14.149",
"dbgate-types": "^4.0.0", "dbgate-types": "^4.0.0",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",

View File

@@ -2,12 +2,13 @@ const fs = require('fs-extra');
const axios = require('axios'); const axios = require('axios');
const path = require('path'); const path = require('path');
const { extractPackageName } = require('dbgate-tools'); const { extractPackageName } = require('dbgate-tools');
const { pluginsdir, datadir } = require('../utility/directories'); const { pluginsdir, datadir, packagedPluginsDir } = require('../utility/directories');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
const compareVersions = require('compare-versions'); const compareVersions = require('compare-versions');
const requirePlugin = require('../shell/requirePlugin'); const requirePlugin = require('../shell/requirePlugin');
const downloadPackage = require('../utility/downloadPackage'); const downloadPackage = require('../utility/downloadPackage');
const hasPermission = require('../utility/hasPermission'); const hasPermission = require('../utility/hasPermission');
const _ = require('lodash');
// async function loadPackageInfo(dir) { // async function loadPackageInfo(dir) {
// const readmeFile = path.join(dir, 'README.md'); // const readmeFile = path.join(dir, 'README.md');
@@ -27,19 +28,22 @@ const hasPermission = require('../utility/hasPermission');
// }; // };
// } // }
const preinstallPluginMinimalVersions = { // const preinstallPluginMinimalVersions = {
'dbgate-plugin-mssql': '1.2.2', // 'dbgate-plugin-mssql': '1.2.2',
'dbgate-plugin-mysql': '1.2.2', // 'dbgate-plugin-mysql': '1.2.2',
'dbgate-plugin-postgres': '1.2.2', // 'dbgate-plugin-postgres': '1.2.2',
'dbgate-plugin-mongo': '1.0.1', // 'dbgate-plugin-mongo': '1.0.1',
'dbgate-plugin-csv': '1.0.9', // 'dbgate-plugin-csv': '1.0.9',
'dbgate-plugin-excel': '1.0.8', // 'dbgate-plugin-excel': '1.0.8',
}; // };
module.exports = { module.exports = {
script_meta: 'get', script_meta: 'get',
async script({ packageName }) { async script({ packageName }) {
const file = path.join(pluginsdir(), packageName, 'dist', 'frontend.js'); const file1 = path.join(packagedPluginsDir(), packageName, 'dist', 'frontend.js');
const file2 = path.join(pluginsdir(), packageName, 'dist', 'frontend.js');
// @ts-ignore
const file = (await fs.exists(file1)) ? file1 : file2;
const data = await fs.readFile(file, { const data = await fs.readFile(file, {
encoding: 'utf-8', encoding: 'utf-8',
}); });
@@ -63,10 +67,12 @@ module.exports = {
const { latest } = infoResp.data['dist-tags']; const { latest } = infoResp.data['dist-tags'];
const manifest = infoResp.data.versions[latest]; const manifest = infoResp.data.versions[latest];
const { readme } = infoResp.data; const { readme } = infoResp.data;
const isPackaged = await fs.pathExists(path.join(packagedPluginsDir(), packageName));
return { return {
readme, readme,
manifest, manifest,
isPackaged,
}; };
} catch (err) { } catch (err) {
return { return {
@@ -88,14 +94,22 @@ module.exports = {
installed_meta: 'get', installed_meta: 'get',
async installed() { async installed() {
const files = await fs.readdir(pluginsdir()); const files1 = await fs.readdir(packagedPluginsDir());
const files2 = await fs.readdir(pluginsdir());
const res = []; const res = [];
for (const packageName of files) { for (const packageName of _.union(files1, files2)) {
const manifest = await fs.readFile(path.join(pluginsdir(), packageName, 'package.json')).then(x => JSON.parse(x)); const isPackaged = files1.includes(packageName);
const manifest = await fs
.readFile(path.join(isPackaged ? packagedPluginsDir() : pluginsdir(), packageName, 'package.json'), {
encoding: 'utf-8',
})
.then(x => JSON.parse(x));
const readmeFile = path.join(pluginsdir(), packageName, 'README.md'); const readmeFile = path.join(pluginsdir(), packageName, 'README.md');
if (await fs.exists(readmeFile)) { if (await fs.pathExists(readmeFile)) {
manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' }); manifest.readme = await fs.readFile(readmeFile, { encoding: 'utf-8' });
} }
manifest.isPackaged = isPackaged;
res.push(manifest); res.push(manifest);
} }
return res; return res;
@@ -106,20 +120,20 @@ module.exports = {
// ); // );
}, },
async saveRemovePlugins() { // async saveRemovePlugins() {
await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n')); // await fs.writeFile(path.join(datadir(), 'removed-plugins'), this.removedPlugins.join('\n'));
}, // },
install_meta: 'post', install_meta: 'post',
async install({ packageName }) { async install({ packageName }) {
if (!hasPermission(`plugins/install`)) return; if (!hasPermission(`plugins/install`)) return;
const dir = path.join(pluginsdir(), packageName); const dir = path.join(pluginsdir(), packageName);
if (!(await fs.exists(dir))) { if (!(await fs.pathExists(dir))) {
await downloadPackage(packageName, dir); await downloadPackage(packageName, dir);
} }
socket.emitChanged(`installed-plugins-changed`); socket.emitChanged(`installed-plugins-changed`);
this.removedPlugins = this.removedPlugins.filter(x => x != packageName); // this.removedPlugins = this.removedPlugins.filter(x => x != packageName);
await this.saveRemovePlugins(); // await this.saveRemovePlugins();
}, },
uninstall_meta: 'post', uninstall_meta: 'post',
@@ -128,7 +142,7 @@ module.exports = {
const dir = path.join(pluginsdir(), packageName); const dir = path.join(pluginsdir(), packageName);
await fs.rmdir(dir, { recursive: true }); await fs.rmdir(dir, { recursive: true });
socket.emitChanged(`installed-plugins-changed`); socket.emitChanged(`installed-plugins-changed`);
this.removedPlugins.push(packageName); // this.removedPlugins.push(packageName);
await this.saveRemovePlugins(); await this.saveRemovePlugins();
}, },
@@ -136,7 +150,7 @@ module.exports = {
async upgrade({ packageName }) { async upgrade({ packageName }) {
if (!hasPermission(`plugins/install`)) return; if (!hasPermission(`plugins/install`)) return;
const dir = path.join(pluginsdir(), packageName); const dir = path.join(pluginsdir(), packageName);
if (await fs.exists(dir)) { if (await fs.pathExists(dir)) {
await fs.rmdir(dir, { recursive: true }); await fs.rmdir(dir, { recursive: true });
await downloadPackage(packageName, dir); await downloadPackage(packageName, dir);
} }
@@ -158,46 +172,46 @@ module.exports = {
return content.driver.getAuthTypes() || null; return content.driver.getAuthTypes() || null;
}, },
async _init() { // async _init() {
const installed = await this.installed(); // const installed = await this.installed();
try { // try {
this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split( // this.removedPlugins = (await fs.readFile(path.join(datadir(), 'removed-plugins'), { encoding: 'utf-8' })).split(
'\n' // '\n'
); // );
} catch (err) { // } catch (err) {
this.removedPlugins = []; // this.removedPlugins = [];
} // }
for (const packageName of Object.keys(preinstallPluginMinimalVersions)) { // for (const packageName of Object.keys(preinstallPluginMinimalVersions)) {
const installedVersion = installed.find(x => x.name == packageName); // const installedVersion = installed.find(x => x.name == packageName);
if (installedVersion) { // if (installedVersion) {
// plugin installed, test, whether upgrade // // plugin installed, test, whether upgrade
const requiredVersion = preinstallPluginMinimalVersions[packageName]; // const requiredVersion = preinstallPluginMinimalVersions[packageName];
if (compareVersions(installedVersion.version, requiredVersion) < 0) { // if (compareVersions(installedVersion.version, requiredVersion) < 0) {
console.log( // console.log(
`Upgrading preinstalled plugin ${packageName}, found ${installedVersion.version}, required version ${requiredVersion}` // `Upgrading preinstalled plugin ${packageName}, found ${installedVersion.version}, required version ${requiredVersion}`
); // );
await this.upgrade({ packageName }); // await this.upgrade({ packageName });
} else { // } else {
console.log( // console.log(
`Plugin ${packageName} not upgraded, found ${installedVersion.version}, required version ${requiredVersion}` // `Plugin ${packageName} not upgraded, found ${installedVersion.version}, required version ${requiredVersion}`
); // );
} // }
continue; // continue;
} // }
if (this.removedPlugins.includes(packageName)) { // if (this.removedPlugins.includes(packageName)) {
// plugin was remvoed, don't install again // // plugin was remvoed, don't install again
continue; // continue;
} // }
try { // try {
console.log('Preinstalling plugin', packageName); // console.log('Preinstalling plugin', packageName);
await this.install({ packageName }); // await this.install({ packageName });
} catch (err) { // } catch (err) {
console.error('Error preinstalling plugin', packageName, err); // console.error('Error preinstalling plugin', packageName, err);
} // }
} // }
}, // },
}; };

View File

@@ -1,6 +1,8 @@
const path = require('path'); const path = require('path');
const { pluginsdir } = require('../utility/directories'); const fs = require('fs');
const { pluginsdir, packagedPluginsDir } = require('../utility/directories');
const nativeModules = require('../nativeModules'); const nativeModules = require('../nativeModules');
const _isRunOnSource = require('../utility/_isRunOnSource');
const loadedPlugins = {}; const loadedPlugins = {};
@@ -9,13 +11,25 @@ const dbgateEnv = {
nativeModules, nativeModules,
}; };
function getModulePath(packageName) {
const packagedModulePath = _isRunOnSource()
? path.join(packagedPluginsDir(), packageName, 'src', 'backend', 'index.js')
: path.join(packagedPluginsDir(), packageName, 'dist', 'backend.js');
if (fs.existsSync(packagedModulePath)) {
return packagedModulePath;
}
return path.join(pluginsdir(), packageName, 'dist', 'backend.js');
}
function requirePlugin(packageName, requiredPlugin = null) { function requirePlugin(packageName, requiredPlugin = null) {
if (!packageName) throw new Error('Missing packageName in plugin'); if (!packageName) throw new Error('Missing packageName in plugin');
if (loadedPlugins[packageName]) return loadedPlugins[packageName]; if (loadedPlugins[packageName]) return loadedPlugins[packageName];
if (requiredPlugin == null) { if (requiredPlugin == null) {
let module; let module;
const modulePath = path.join(pluginsdir(), packageName, 'dist', 'backend.js'); const modulePath = getModulePath(packageName);
console.log(`Loading module ${packageName} from ${modulePath}`); console.log(`Loading module ${packageName} from ${modulePath}`);
try { try {
// @ts-ignore // @ts-ignore

View File

@@ -0,0 +1,5 @@
function _isRunOnSource() {
return __filename.endsWith('_isRunOnSource.js');
}
module.exports = _isRunOnSource;

View File

@@ -2,6 +2,7 @@ const os = require('os');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const cleanDirectory = require('./cleanDirectory'); const cleanDirectory = require('./cleanDirectory');
const _isRunOnSource = require('./_isRunOnSource');
const createDirectories = {}; const createDirectories = {};
const ensureDirectory = (dir, clean) => { const ensureDirectory = (dir, clean) => {
@@ -39,6 +40,13 @@ const pluginsdir = dirFunc('plugins');
const archivedir = dirFunc('archive'); const archivedir = dirFunc('archive');
const filesdir = dirFunc('files'); const filesdir = dirFunc('files');
function packagedPluginsDir() {
if (_isRunOnSource()) {
return path.resolve(__dirname, '../../../../plugins');
}
return path.resolve(__dirname, '../plugins');
}
module.exports = { module.exports = {
datadir, datadir,
jsldir, jsldir,
@@ -48,4 +56,5 @@ module.exports = {
ensureDirectory, ensureDirectory,
pluginsdir, pluginsdir,
filesdir, filesdir,
packagedPluginsDir,
}; };

View File

@@ -23,7 +23,11 @@
<div class="ml-2"> <div class="ml-2">
<div class="flex"> <div class="flex">
<div class="bold">{packageManifest.name}</div> <div class="bold">{packageManifest.name}</div>
{#if packageManifest.isPackaged}
<div class="ml-1 builtin">(builtin)</div>
{:else}
<div class="ml-1">{packageManifest.version}</div> <div class="ml-1">{packageManifest.version}</div>
{/if}
</div> </div>
<div> <div>
{packageManifest.description} {packageManifest.description}
@@ -48,4 +52,8 @@
width: 50px; width: 50px;
height: 50px; height: 50px;
} }
.builtin {
color: var(--theme-font-3);
}
</style> </style>

View File

@@ -24,6 +24,7 @@ import Markdown from '../elements/Markdown.svelte';
}); });
$: readme = $info?.readme; $: readme = $info?.readme;
$: manifest = $info?.manifest; $: manifest = $info?.manifest;
$: isPackaged = $info?.isPackaged;
const handleInstall = async () => { const handleInstall = async () => {
axiosInstance.post('plugins/install', { packageName }); axiosInstance.post('plugins/install', { packageName });
@@ -59,6 +60,9 @@ import Markdown from '../elements/Markdown.svelte';
<span> | </span> <span> | </span>
<span>{installedFound ? installedFound.version : manifest.version}</span> <span>{installedFound ? installedFound.version : manifest.version}</span>
</div> </div>
{#if isPackaged}
<div class="mt-2">Plugin is part of DbGate installation</div>
{:else}
<div class="mt-1"> <div class="mt-1">
{#if hasPermission('plugins/install') && !installedFound} {#if hasPermission('plugins/install') && !installedFound}
<FormStyledButton type="button" value="Install" on:click={handleInstall} /> <FormStyledButton type="button" value="Install" on:click={handleInstall} />
@@ -70,6 +74,7 @@ import Markdown from '../elements/Markdown.svelte';
<FormStyledButton type="button" value="Upgrade" on:click={handleUpgrade} /> <FormStyledButton type="button" value="Upgrade" on:click={handleUpgrade} />
{/if} {/if}
</div> </div>
{/if}
</div> </div>
</div> </div>
<Markdown source={readme} /> <Markdown source={readme} />

570
yarn.lock

File diff suppressed because it is too large Load Diff